[v13,7/7] libcamera: software_isp: debayer_egl: Add LSC support
diff mbox series

Message ID 20260810201957.19623-8-mzamazal@redhat.com
State Superseded
Headers show
Series
  • LSC for SoftISP simple pipeline
Related show

Commit Message

Milan Zamazal Aug. 10, 2026, 8:19 p.m. UTC
From: Xander Pronk <xander.c.pronk@gmail.com>

Add support for passing the LSC table from debayerParams to the shaders.

We use 8-bit texture with the original UQ<2, 6> values to avoid trouble
with floats.  32-bit floats don't work in my environment and 16-bit
floats may not be supported in all OpenGL environments.

When LSC is disabled by the runtime control, we provide a constant 1.0
correction table, i.e. a no-op table.  This is preferred to having a
runtime flag, to not disturb GPU computation with runtime switches,
assuming that disabling LSC in runtime is not a common use case.

The LSC texture is created once and then updated.  The texture creation
cannot be done in `configure', because of a different thread.

Co-developed-by: Rick ten Wolde <rick_libcamera@wolde.info>
Signed-off-by: Rick ten Wolde <rick_libcamera@wolde.info>
Signed-off-by: Xander Pronk <xander.c.pronk@gmail.com>
Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
---
 .../internal/software_isp/debayer_params.h    |  2 ++
 src/libcamera/software_isp/debayer.cpp        |  5 +++
 src/libcamera/software_isp/debayer_egl.cpp    | 36 ++++++++++++++++++-
 src/libcamera/software_isp/debayer_egl.h      |  7 ++++
 4 files changed, 49 insertions(+), 1 deletion(-)

Comments

Barnabás Pőcze Aug. 18, 2026, 10:49 a.m. UTC | #1
2026. 08. 10. 22:19 keltezéssel, Milan Zamazal írta:
> From: Xander Pronk <xander.c.pronk@gmail.com>
> 
> Add support for passing the LSC table from debayerParams to the shaders.
> 
> We use 8-bit texture with the original UQ<2, 6> values to avoid trouble
> with floats.  32-bit floats don't work in my environment and 16-bit
> floats may not be supported in all OpenGL environments.
> 
> When LSC is disabled by the runtime control, we provide a constant 1.0
> correction table, i.e. a no-op table.  This is preferred to having a
> runtime flag, to not disturb GPU computation with runtime switches,
> assuming that disabling LSC in runtime is not a common use case.
> 
> The LSC texture is created once and then updated.  The texture creation
> cannot be done in `configure', because of a different thread.
> 
> Co-developed-by: Rick ten Wolde <rick_libcamera@wolde.info>
> Signed-off-by: Rick ten Wolde <rick_libcamera@wolde.info>
> Signed-off-by: Xander Pronk <xander.c.pronk@gmail.com>
> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
> ---
>   .../internal/software_isp/debayer_params.h    |  2 ++
>   src/libcamera/software_isp/debayer.cpp        |  5 +++
>   src/libcamera/software_isp/debayer_egl.cpp    | 36 ++++++++++++++++++-
>   src/libcamera/software_isp/debayer_egl.h      |  7 ++++
>   4 files changed, 49 insertions(+), 1 deletion(-)
> 
> diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h
> index b7860c9dd..1e0b5b553 100644
> --- a/include/libcamera/internal/software_isp/debayer_params.h
> +++ b/include/libcamera/internal/software_isp/debayer_params.h
> @@ -33,6 +33,8 @@ struct DebayerParams {
>   	 **/
>   	static constexpr unsigned int kLscGridSize = 17;
>   	static constexpr unsigned int kLscValuesPerCell = 4;
> +	static constexpr unsigned int kLscBytesPerCell =
> +		kLscValuesPerCell * sizeof(uint8_t);
>   	using LscLookupTable =
>   		std::array<uint8_t, kLscGridSize * kLscGridSize * kLscValuesPerCell>;
>   	static constexpr auto identityLscLut = [] {
> diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp
> index 8295b8fc2..4f6659000 100644
> --- a/src/libcamera/software_isp/debayer.cpp
> +++ b/src/libcamera/software_isp/debayer.cpp
> @@ -53,6 +53,11 @@ namespace libcamera {
>    * \brief Number of pixel values per each of the lens shading grid areas
>    */
>   
> +/**
> + * \var DebayerParams::kLscBytesPerCell
> + * \brief Number of bytes per each of the lens shading grid areas
> + */
> +
>   /**
>    * \typedef DebayerParams::LscLookupTable
>    * \brief Lookup table for lens shading correction
> diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
> index 5227d2bbd..941fdc914 100644
> --- a/src/libcamera/software_isp/debayer_egl.cpp
> +++ b/src/libcamera/software_isp/debayer_egl.cpp
> @@ -115,6 +115,8 @@ int DebayerEGL::getShaderVariableLocations(void)
>   	textureUniformBayerFirstRed_ = glGetUniformLocation(programId_, "tex_bayer_first_red");
>   	textureUniformProjMatrix_ = glGetUniformLocation(programId_, "proj_matrix");
>   
> +	textureUniformLsc_ = glGetUniformLocation(programId_, "lsc_tex");
> +
>   	LOG(Debayer, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
>   			    << " tex_y " << textureUniformBayerDataIn_
>   			    << " awb " << awbUniformDataIn_
> @@ -126,7 +128,8 @@ int DebayerEGL::getShaderVariableLocations(void)
>   			    << " tex_size " << textureUniformSize_
>   			    << " stride_factor " << textureUniformStrideFactor_
>   			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_
> -			    << " proj_matrix " << textureUniformProjMatrix_;
> +			    << " proj_matrix " << textureUniformProjMatrix_
> +			    << " tex_lsc " << textureUniformLsc_;
>   	return 0;
>   }
>   
> @@ -143,6 +146,9 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm
>   	/* Specify GL_OES_EGL_image_external */
>   	egl_.pushEnv(shaderEnv, "#extension GL_OES_EGL_image_external: enable");
>   
> +	if (lscEnabled_)
> +		egl_.pushEnv(shaderEnv, "#define APPLY_LSC");
> +
>   	/*
>   	 * Tell shaders how to re-order output taking account of how the pixels
>   	 * are actually stored by EGL.
> @@ -467,6 +473,31 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara
>   	glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_TRUE, params.combinedMatrix.data().data());
>   	LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix;
>   
> +	if (lscEnabled_) {
> +		bool create = !eglImageLscLookup_;
> +		if (create) {
> +			constexpr unsigned int gridSize = DebayerParams::kLscGridSize;
> +			const unsigned int stride = gridSize * DebayerParams::kLscBytesPerCell;
> +			eglImageLscLookup_ =
> +				std::make_unique<eGLImage>(GL_RGBA,
> +							   gridSize,
> +							   gridSize,
> +							   stride,
> +							   GL_TEXTURE2,
> +							   2);
> +		}
> +		glUniform1i(textureUniformLsc_, eglImageLscLookup_->texture_unit_uniform_id_);
> +		if (params.lscLutVersion != lscLutVersion_) {
> +			if (create)
> +				egl_.createTexture2D(*eglImageLscLookup_, eglImageLscLookup_->format_, GL_UNSIGNED_BYTE,
> +						     params.lscLut.data(), GL_LINEAR);
> +			else
> +				egl_.updateTexture2D(*eglImageLscLookup_, params.lscLut.data());
> +
> +			lscLutVersion_ = params.lscLutVersion;
> +		}

I think I'm going to contradict my earlier review comments, so sorry about that. But after
looking some more at this code, I think the following would be a worthy change.
The main change here is that `setShaderVariableValues()` only ever updates the
texture, and the creation is only done at the start. I feel like that makes things
easier to understand, roughly something like this:

diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
index 077c6ddda8..5645b52a80 100644
--- a/src/libcamera/software_isp/debayer_egl.cpp
+++ b/src/libcamera/software_isp/debayer_egl.cpp
@@ -473,29 +473,11 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara
  	glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_TRUE, params.combinedMatrix.data().data());
  	LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix;
  
-	if (lscEnabled_) {
-		bool create = !eglImageLscLookup_;
-		if (create) {
-			constexpr unsigned int gridSize = DebayerParams::kLscGridSize;
-			const unsigned int stride = gridSize * DebayerParams::kLscBytesPerCell;
-			eglImageLscLookup_ =
-				std::make_unique<eGLImage>(GL_RGBA,
-							   gridSize,
-							   gridSize,
-							   stride,
-							   GL_TEXTURE2,
-							   2);
-		}
+	if (lscEnabled_ && params.lscLutVersion != lscLutVersion_) {
+		egl_.updateTexture2D(*eglImageLscLookup_, params.lscLut.data());
+		lscLutVersion_ = params.lscLutVersion;
+
  		glUniform1i(textureUniformLsc_, eglImageLscLookup_->texture_unit_uniform_id_);
-		if (params.lscLutVersion != lscLutVersion_) {
-			if (create)
-				egl_.createTexture2D(*eglImageLscLookup_, eglImageLscLookup_->format_, GL_UNSIGNED_BYTE,
-						     params.lscLut.data(), GL_LINEAR);
-			else
-				egl_.updateTexture2D(*eglImageLscLookup_, params.lscLut.data());
-
-			lscLutVersion_ = params.lscLutVersion;
-		}
  	}
  
  	/*
@@ -708,6 +690,14 @@ int DebayerEGL::start()
  	if (initBayerShaders(inputPixelFormat_, outputPixelFormat_))
  		return -EINVAL;
  
+	if (lscEnabled_) {
+		constexpr unsigned int gridSize = DebayerParams::kLscGridSize;
+		constexpr unsigned int stride = gridSize * DebayerParams::kLscBytesPerCell;
+
+		eglImageLscLookup_.emplace(GL_RGBA, gridSize, gridSize, stride, GL_TEXTURE2, 2);
+		egl_.createTexture2D(*eglImageLscLookup_, eglImageLscLookup_->format_, GL_UNSIGNED_BYTE, nullptr, GL_LINEAR);
+	}
+
  	return 0;
  }
  
diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h
index c2a08fb064..9dbf6f71d7 100644
--- a/src/libcamera/software_isp/debayer_egl.h
+++ b/src/libcamera/software_isp/debayer_egl.h
@@ -84,7 +84,7 @@ private:
  	unsigned int outputBufferCount_;
  
  	/* LSC lookup table */
-	std::unique_ptr<eGLImage> eglImageLscLookup_;
+	std::optional<eGLImage> eglImageLscLookup_;
  	bool lscEnabled_;
  	std::optional<uint64_t> lscLutVersion_;

  
> +	}
> +
>   	/*
>   	 * 0 = Red, 1 = Green, 2 = Blue
>   	 */
> @@ -687,6 +718,9 @@ void DebayerEGL::stop()
>   	eglImageOutCache_.clear();
>   	eglImageInCache_.clear();
>   
> +	eglImageLscLookup_.reset();
> +	lscLutVersion_.reset();
> +
>   	if (programId_)
>   		glDeleteProgram(programId_);
>   }
> diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h
> index a64dc925a..c2a08fb06 100644
> --- a/src/libcamera/software_isp/debayer_egl.h
> +++ b/src/libcamera/software_isp/debayer_egl.h
> @@ -83,7 +83,11 @@ private:
>   	unsigned int inputBufferCount_;
>   	unsigned int outputBufferCount_;
>   
> +	/* LSC lookup table */
> +	std::unique_ptr<eGLImage> eglImageLscLookup_;
>   	bool lscEnabled_;
> +	std::optional<uint64_t> lscLutVersion_;
> +
>   	/* Shader parameters */
>   	float firstRed_x_;
>   	float firstRed_y_;
> @@ -100,6 +104,9 @@ private:
>   	/* Per-frame AWB gains */
>   	GLint awbUniformDataIn_;
>   
> +	/* Lens shading correction */
> +	GLint textureUniformLsc_;
> +
>   	/* Represent per-frame CCM as a uniform vector of floats 3 x 3 */
>   	GLint ccmUniformDataIn_;
>
Milan Zamazal Aug. 18, 2026, 2:45 p.m. UTC | #2
Barnabás Pőcze <barnabas.pocze@ideasonboard.com> writes:

> 2026. 08. 10. 22:19 keltezéssel, Milan Zamazal írta:
>> From: Xander Pronk <xander.c.pronk@gmail.com>
>> Add support for passing the LSC table from debayerParams to the shaders.
>> We use 8-bit texture with the original UQ<2, 6> values to avoid trouble
>> with floats.  32-bit floats don't work in my environment and 16-bit
>> floats may not be supported in all OpenGL environments.
>> When LSC is disabled by the runtime control, we provide a constant 1.0
>> correction table, i.e. a no-op table.  This is preferred to having a
>> runtime flag, to not disturb GPU computation with runtime switches,
>> assuming that disabling LSC in runtime is not a common use case.
>> The LSC texture is created once and then updated.  The texture creation
>> cannot be done in `configure', because of a different thread.
>> Co-developed-by: Rick ten Wolde <rick_libcamera@wolde.info>
>> Signed-off-by: Rick ten Wolde <rick_libcamera@wolde.info>
>> Signed-off-by: Xander Pronk <xander.c.pronk@gmail.com>
>> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
>> ---
>>   .../internal/software_isp/debayer_params.h    |  2 ++
>>   src/libcamera/software_isp/debayer.cpp        |  5 +++
>>   src/libcamera/software_isp/debayer_egl.cpp    | 36 ++++++++++++++++++-
>>   src/libcamera/software_isp/debayer_egl.h      |  7 ++++
>>   4 files changed, 49 insertions(+), 1 deletion(-)
>> diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h
>> index b7860c9dd..1e0b5b553 100644
>> --- a/include/libcamera/internal/software_isp/debayer_params.h
>> +++ b/include/libcamera/internal/software_isp/debayer_params.h
>> @@ -33,6 +33,8 @@ struct DebayerParams {
>>   	 **/
>>   	static constexpr unsigned int kLscGridSize = 17;
>>   	static constexpr unsigned int kLscValuesPerCell = 4;
>> +	static constexpr unsigned int kLscBytesPerCell =
>> +		kLscValuesPerCell * sizeof(uint8_t);
>>   	using LscLookupTable =
>>   		std::array<uint8_t, kLscGridSize * kLscGridSize * kLscValuesPerCell>;
>>   	static constexpr auto identityLscLut = [] {
>> diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp
>> index 8295b8fc2..4f6659000 100644
>> --- a/src/libcamera/software_isp/debayer.cpp
>> +++ b/src/libcamera/software_isp/debayer.cpp
>> @@ -53,6 +53,11 @@ namespace libcamera {
>>    * \brief Number of pixel values per each of the lens shading grid areas
>>    */
>>   +/**
>> + * \var DebayerParams::kLscBytesPerCell
>> + * \brief Number of bytes per each of the lens shading grid areas
>> + */
>> +
>>   /**
>>    * \typedef DebayerParams::LscLookupTable
>>    * \brief Lookup table for lens shading correction
>> diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
>> index 5227d2bbd..941fdc914 100644
>> --- a/src/libcamera/software_isp/debayer_egl.cpp
>> +++ b/src/libcamera/software_isp/debayer_egl.cpp
>> @@ -115,6 +115,8 @@ int DebayerEGL::getShaderVariableLocations(void)
>>   	textureUniformBayerFirstRed_ = glGetUniformLocation(programId_, "tex_bayer_first_red");
>>   	textureUniformProjMatrix_ = glGetUniformLocation(programId_, "proj_matrix");
>>   +	textureUniformLsc_ = glGetUniformLocation(programId_, "lsc_tex");
>> +
>>   	LOG(Debayer, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
>>   			    << " tex_y " << textureUniformBayerDataIn_
>>   			    << " awb " << awbUniformDataIn_
>> @@ -126,7 +128,8 @@ int DebayerEGL::getShaderVariableLocations(void)
>>   			    << " tex_size " << textureUniformSize_
>>   			    << " stride_factor " << textureUniformStrideFactor_
>>   			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_
>> -			    << " proj_matrix " << textureUniformProjMatrix_;
>> +			    << " proj_matrix " << textureUniformProjMatrix_
>> +			    << " tex_lsc " << textureUniformLsc_;
>>   	return 0;
>>   }
>>   @@ -143,6 +146,9 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm
>>   	/* Specify GL_OES_EGL_image_external */
>>   	egl_.pushEnv(shaderEnv, "#extension GL_OES_EGL_image_external: enable");
>>   +	if (lscEnabled_)
>> +		egl_.pushEnv(shaderEnv, "#define APPLY_LSC");
>> +
>>   	/*
>>   	 * Tell shaders how to re-order output taking account of how the pixels
>>   	 * are actually stored by EGL.
>> @@ -467,6 +473,31 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara
>>   	glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_TRUE, params.combinedMatrix.data().data());
>>   	LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix;
>>   +	if (lscEnabled_) {
>> +		bool create = !eglImageLscLookup_;
>> +		if (create) {
>> +			constexpr unsigned int gridSize = DebayerParams::kLscGridSize;
>> +			const unsigned int stride = gridSize * DebayerParams::kLscBytesPerCell;
>> +			eglImageLscLookup_ =
>> +				std::make_unique<eGLImage>(GL_RGBA,
>> +							   gridSize,
>> +							   gridSize,
>> +							   stride,
>> +							   GL_TEXTURE2,
>> +							   2);
>> +		}
>> +		glUniform1i(textureUniformLsc_, eglImageLscLookup_->texture_unit_uniform_id_);
>> +		if (params.lscLutVersion != lscLutVersion_) {
>> +			if (create)
>> +				egl_.createTexture2D(*eglImageLscLookup_, eglImageLscLookup_->format_, GL_UNSIGNED_BYTE,
>> +						     params.lscLut.data(), GL_LINEAR);
>> +			else
>> +				egl_.updateTexture2D(*eglImageLscLookup_, params.lscLut.data());
>> +
>> +			lscLutVersion_ = params.lscLutVersion;
>> +		}
>
> I think I'm going to contradict my earlier review comments, so sorry about that. But after
> looking some more at this code, I think the following would be a worthy change.
> The main change here is that `setShaderVariableValues()` only ever updates the
> texture, and the creation is only done at the start. I feel like that makes things
> easier to understand, roughly something like this:
>
> diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
> index 077c6ddda8..5645b52a80 100644
> --- a/src/libcamera/software_isp/debayer_egl.cpp
> +++ b/src/libcamera/software_isp/debayer_egl.cpp
> @@ -473,29 +473,11 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara
>  	glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_TRUE, params.combinedMatrix.data().data());
>  	LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix;
>  -	if (lscEnabled_) {
> -		bool create = !eglImageLscLookup_;
> -		if (create) {
> -			constexpr unsigned int gridSize = DebayerParams::kLscGridSize;
> -			const unsigned int stride = gridSize * DebayerParams::kLscBytesPerCell;
> -			eglImageLscLookup_ =
> -				std::make_unique<eGLImage>(GL_RGBA,
> -							   gridSize,
> -							   gridSize,
> -							   stride,
> -							   GL_TEXTURE2,
> -							   2);
> -		}
> +	if (lscEnabled_ && params.lscLutVersion != lscLutVersion_) {
> +		egl_.updateTexture2D(*eglImageLscLookup_, params.lscLut.data());
> +		lscLutVersion_ = params.lscLutVersion;
> +
>  		glUniform1i(textureUniformLsc_, eglImageLscLookup_->texture_unit_uniform_id_);
> -		if (params.lscLutVersion != lscLutVersion_) {
> -			if (create)
> -				egl_.createTexture2D(*eglImageLscLookup_, eglImageLscLookup_->format_, GL_UNSIGNED_BYTE,
> -						     params.lscLut.data(), GL_LINEAR);
> -			else
> -				egl_.updateTexture2D(*eglImageLscLookup_, params.lscLut.data());
> -
> -			lscLutVersion_ = params.lscLutVersion;
> -		}
>  	}
>    	/*
> @@ -708,6 +690,14 @@ int DebayerEGL::start()
>  	if (initBayerShaders(inputPixelFormat_, outputPixelFormat_))
>  		return -EINVAL;
>  +	if (lscEnabled_) {
> +		constexpr unsigned int gridSize = DebayerParams::kLscGridSize;
> +		constexpr unsigned int stride = gridSize * DebayerParams::kLscBytesPerCell;
> +
> +		eglImageLscLookup_.emplace(GL_RGBA, gridSize, gridSize, stride, GL_TEXTURE2, 2);
> +		egl_.createTexture2D(*eglImageLscLookup_, eglImageLscLookup_->format_, GL_UNSIGNED_BYTE, nullptr, GL_LINEAR);
> +	}
> +
>  	return 0;
>  }
>  diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h
> index c2a08fb064..9dbf6f71d7 100644
> --- a/src/libcamera/software_isp/debayer_egl.h
> +++ b/src/libcamera/software_isp/debayer_egl.h
> @@ -84,7 +84,7 @@ private:
>  	unsigned int outputBufferCount_;
>    	/* LSC lookup table */
> -	std::unique_ptr<eGLImage> eglImageLscLookup_;
> +	std::optional<eGLImage> eglImageLscLookup_;
>  	bool lscEnabled_;
>  	std::optional<uint64_t> lscLutVersion_;

Yes, seems to look better.  I'll try to do something along these lines
in v14.

>  > +	}
>> +
>>   	/*
>>   	 * 0 = Red, 1 = Green, 2 = Blue
>>   	 */
>> @@ -687,6 +718,9 @@ void DebayerEGL::stop()
>>   	eglImageOutCache_.clear();
>>   	eglImageInCache_.clear();
>>   +	eglImageLscLookup_.reset();
>> +	lscLutVersion_.reset();
>> +
>>   	if (programId_)
>>   		glDeleteProgram(programId_);
>>   }
>> diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h
>> index a64dc925a..c2a08fb06 100644
>> --- a/src/libcamera/software_isp/debayer_egl.h
>> +++ b/src/libcamera/software_isp/debayer_egl.h
>> @@ -83,7 +83,11 @@ private:
>>   	unsigned int inputBufferCount_;
>>   	unsigned int outputBufferCount_;
>>   +	/* LSC lookup table */
>> +	std::unique_ptr<eGLImage> eglImageLscLookup_;
>>   	bool lscEnabled_;
>> +	std::optional<uint64_t> lscLutVersion_;
>> +
>>   	/* Shader parameters */
>>   	float firstRed_x_;
>>   	float firstRed_y_;
>> @@ -100,6 +104,9 @@ private:
>>   	/* Per-frame AWB gains */
>>   	GLint awbUniformDataIn_;
>>   +	/* Lens shading correction */
>> +	GLint textureUniformLsc_;
>> +
>>   	/* Represent per-frame CCM as a uniform vector of floats 3 x 3 */
>>   	GLint ccmUniformDataIn_;
>>

Patch
diff mbox series

diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h
index b7860c9dd..1e0b5b553 100644
--- a/include/libcamera/internal/software_isp/debayer_params.h
+++ b/include/libcamera/internal/software_isp/debayer_params.h
@@ -33,6 +33,8 @@  struct DebayerParams {
 	 **/
 	static constexpr unsigned int kLscGridSize = 17;
 	static constexpr unsigned int kLscValuesPerCell = 4;
+	static constexpr unsigned int kLscBytesPerCell =
+		kLscValuesPerCell * sizeof(uint8_t);
 	using LscLookupTable =
 		std::array<uint8_t, kLscGridSize * kLscGridSize * kLscValuesPerCell>;
 	static constexpr auto identityLscLut = [] {
diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp
index 8295b8fc2..4f6659000 100644
--- a/src/libcamera/software_isp/debayer.cpp
+++ b/src/libcamera/software_isp/debayer.cpp
@@ -53,6 +53,11 @@  namespace libcamera {
  * \brief Number of pixel values per each of the lens shading grid areas
  */
 
+/**
+ * \var DebayerParams::kLscBytesPerCell
+ * \brief Number of bytes per each of the lens shading grid areas
+ */
+
 /**
  * \typedef DebayerParams::LscLookupTable
  * \brief Lookup table for lens shading correction
diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
index 5227d2bbd..941fdc914 100644
--- a/src/libcamera/software_isp/debayer_egl.cpp
+++ b/src/libcamera/software_isp/debayer_egl.cpp
@@ -115,6 +115,8 @@  int DebayerEGL::getShaderVariableLocations(void)
 	textureUniformBayerFirstRed_ = glGetUniformLocation(programId_, "tex_bayer_first_red");
 	textureUniformProjMatrix_ = glGetUniformLocation(programId_, "proj_matrix");
 
+	textureUniformLsc_ = glGetUniformLocation(programId_, "lsc_tex");
+
 	LOG(Debayer, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
 			    << " tex_y " << textureUniformBayerDataIn_
 			    << " awb " << awbUniformDataIn_
@@ -126,7 +128,8 @@  int DebayerEGL::getShaderVariableLocations(void)
 			    << " tex_size " << textureUniformSize_
 			    << " stride_factor " << textureUniformStrideFactor_
 			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_
-			    << " proj_matrix " << textureUniformProjMatrix_;
+			    << " proj_matrix " << textureUniformProjMatrix_
+			    << " tex_lsc " << textureUniformLsc_;
 	return 0;
 }
 
@@ -143,6 +146,9 @@  int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm
 	/* Specify GL_OES_EGL_image_external */
 	egl_.pushEnv(shaderEnv, "#extension GL_OES_EGL_image_external: enable");
 
+	if (lscEnabled_)
+		egl_.pushEnv(shaderEnv, "#define APPLY_LSC");
+
 	/*
 	 * Tell shaders how to re-order output taking account of how the pixels
 	 * are actually stored by EGL.
@@ -467,6 +473,31 @@  void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara
 	glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_TRUE, params.combinedMatrix.data().data());
 	LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix;
 
+	if (lscEnabled_) {
+		bool create = !eglImageLscLookup_;
+		if (create) {
+			constexpr unsigned int gridSize = DebayerParams::kLscGridSize;
+			const unsigned int stride = gridSize * DebayerParams::kLscBytesPerCell;
+			eglImageLscLookup_ =
+				std::make_unique<eGLImage>(GL_RGBA,
+							   gridSize,
+							   gridSize,
+							   stride,
+							   GL_TEXTURE2,
+							   2);
+		}
+		glUniform1i(textureUniformLsc_, eglImageLscLookup_->texture_unit_uniform_id_);
+		if (params.lscLutVersion != lscLutVersion_) {
+			if (create)
+				egl_.createTexture2D(*eglImageLscLookup_, eglImageLscLookup_->format_, GL_UNSIGNED_BYTE,
+						     params.lscLut.data(), GL_LINEAR);
+			else
+				egl_.updateTexture2D(*eglImageLscLookup_, params.lscLut.data());
+
+			lscLutVersion_ = params.lscLutVersion;
+		}
+	}
+
 	/*
 	 * 0 = Red, 1 = Green, 2 = Blue
 	 */
@@ -687,6 +718,9 @@  void DebayerEGL::stop()
 	eglImageOutCache_.clear();
 	eglImageInCache_.clear();
 
+	eglImageLscLookup_.reset();
+	lscLutVersion_.reset();
+
 	if (programId_)
 		glDeleteProgram(programId_);
 }
diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h
index a64dc925a..c2a08fb06 100644
--- a/src/libcamera/software_isp/debayer_egl.h
+++ b/src/libcamera/software_isp/debayer_egl.h
@@ -83,7 +83,11 @@  private:
 	unsigned int inputBufferCount_;
 	unsigned int outputBufferCount_;
 
+	/* LSC lookup table */
+	std::unique_ptr<eGLImage> eglImageLscLookup_;
 	bool lscEnabled_;
+	std::optional<uint64_t> lscLutVersion_;
+
 	/* Shader parameters */
 	float firstRed_x_;
 	float firstRed_y_;
@@ -100,6 +104,9 @@  private:
 	/* Per-frame AWB gains */
 	GLint awbUniformDataIn_;
 
+	/* Lens shading correction */
+	GLint textureUniformLsc_;
+
 	/* Represent per-frame CCM as a uniform vector of floats 3 x 3 */
 	GLint ccmUniformDataIn_;