[22/30] libcamera: software_isp: gpu: Swtich to two pass logic
diff mbox series

Message ID 20260618122245.946138-23-bryan.odonoghue@linaro.org
State RFC
Headers show
Series
  • RFC/RFT: gpuisp: Multipass with speed optimisations on top
Related show

Commit Message

Bryan O'Donoghue June 18, 2026, 12:22 p.m. UTC
Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
---
 ...gpu_pipeline_shader_pass_blc_normalise.cpp | 54 ++++++++++++++++---
 .../gpu_pipeline_shader_pass_demosiac.cpp     | 49 ++++-------------
 .../gpu_pipeline_shader_pass_demosiac.h       |  3 --
 .../software_isp_pipeline_gpu.cpp             | 26 +++++++--
 4 files changed, 80 insertions(+), 52 deletions(-)

Comments

Milan Zamazal June 24, 2026, 8:40 a.m. UTC | #1
s/Swtich/Switch/

Bryan O'Donoghue <bryan.odonoghue@linaro.org> writes:

> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
> ---
>  ...gpu_pipeline_shader_pass_blc_normalise.cpp | 54 ++++++++++++++++---
>  .../gpu_pipeline_shader_pass_demosiac.cpp     | 49 ++++-------------
>  .../gpu_pipeline_shader_pass_demosiac.h       |  3 --
>  .../software_isp_pipeline_gpu.cpp             | 26 +++++++--
>  4 files changed, 80 insertions(+), 52 deletions(-)
>
> diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp
> index 8e8d7b9d6..c07319ee0 100644
> --- a/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp
> +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp
> @@ -102,6 +102,8 @@ int GpuIspShaderPassBlcNormalise::selectShaders(struct ShaderConfig &shaderCfg,
>  		break;
>  	};
>  
> +	LOG(GpuShaderBlc, Info) << "ShaderPass = " << typeid(this).name() << " selected shader pair ";
> +
>  	return 0;
>  }
>  
> @@ -113,15 +115,14 @@ int GpuIspShaderPassBlcNormalise::getShaderVariableLocations(void)
>  	textureUniformStep_ = glGetUniformLocation(programId_, "tex_step");
>  	textureUniformSize_ = glGetUniformLocation(programId_, "tex_size");
>  	textureUniformStrideFactor_ = glGetUniformLocation(programId_, "stride_factor");
> -	textureUniformBayerFirstRed_ = glGetUniformLocation(programId_, "tex_bayer_first_red");
>  	textureUniformProjMatrix_ = glGetUniformLocation(programId_, "proj_matrix");
> +	blackLevelUniformDataIn_ = glGetUniformLocation(programId_, "blacklevel");
>  
>  	LOG(GpuShaderBlc, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
>  			    << " tex_y " << textureUniformBayerDataIn_
>  			    << " tex_step " << textureUniformStep_
>  			    << " tex_size " << textureUniformSize_
>  			    << " stride_factor " << textureUniformStrideFactor_
> -			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_
>  			    << " proj_matrix " << textureUniformProjMatrix_;
>  
>  	/* TODO: trap errors */
> @@ -130,6 +131,8 @@ int GpuIspShaderPassBlcNormalise::getShaderVariableLocations(void)
>  
>  void GpuIspShaderPassBlcNormalise::setShaderVariableValues(const DebayerParams &params, eGLImage &eglImageIn)
>  {
> +	GLenum err;
> +
>  	/*
>  	 * Raw Bayer 8-bit, and packed raw Bayer 10-bit/12-bit formats
>  	 * are stored in a GL_LUMINANCE texture. The texture width is
> @@ -168,15 +171,34 @@ void GpuIspShaderPassBlcNormalise::setShaderVariableValues(const DebayerParams &
>  		{ 1.0f, 0.0f },
>  	};
>  
> +	err = glGetError();
> +	if (err != GL_NO_ERROR)
> +		LOG(GpuShaderBlc, Error) << "setShaderVariables entry @ error " << err;
> +
>  	/* vertexIn - bayer_8.vert */
>  	glEnableVertexAttribArray(attributeVertex_);
> +
> +	err = glGetError();
> +	if (err != GL_NO_ERROR)
> +		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
> +
>  	glVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE,
>  			      2 * sizeof(GLfloat), vcoordinates);
> +	err = glGetError();
> +	if (err != GL_NO_ERROR)
> +		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
>  
>  	/* textureIn - bayer_8.vert */
>  	glEnableVertexAttribArray(attributeTexture_);
> +	err = glGetError();
> +	if (err != GL_NO_ERROR)
> +		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
> +
>  	glVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE,
>  			      2 * sizeof(GLfloat), tcoordinates);
> +	err = glGetError();
> +	if (err != GL_NO_ERROR)
> +		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;

Would it be possible to use distinct error messages for each of the
calls?  And shouldn't the method return on errors?

>  
>  	/*
>  	 * Set the sampler2D to the respective texture unit for each texutre
> @@ -184,27 +206,43 @@ void GpuIspShaderPassBlcNormalise::setShaderVariableValues(const DebayerParams &
>  	 * texture units
>  	 */
>  	glUniform1i(textureUniformBayerDataIn_, eglImageIn.texture_unit_uniform_id_);
> +	err = glGetError();
> +	if (err != GL_NO_ERROR)
> +		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
>  
>  	/*
>  	 * These values are:
> -	 * firstRed = tex_bayer_first_red - bayer_8.vert
>  	 * imgSize = tex_size - bayer_8.vert
>  	 * step = tex_step - bayer_8.vert
>  	 * Stride = stride_factor identity.vert
>  	 * textureUniformProjMatri = No scaling
>  	 */
> -	glUniform2fv(textureUniformBayerFirstRed_, 1, firstRed);
>  	glUniform2fv(textureUniformSize_, 1, imgSize);
> +	err = glGetError();
> +	if (err != GL_NO_ERROR)
> +		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
> +
>  	glUniform2fv(textureUniformStep_, 1, Step);
> +	err = glGetError();
> +	if (err != GL_NO_ERROR)
> +		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
> +
>  	glUniform1f(textureUniformStrideFactor_, Stride);
> +	err = glGetError();
> +	if (err != GL_NO_ERROR)
> +		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
> +
>  	glUniformMatrix4fv(textureUniformProjMatrix_, 1, GL_FALSE, projMatrix);
> +	err = glGetError();
> +	if (err != GL_NO_ERROR)
> +		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
> +

The same questions as above.

>  	LOG(GpuShaderBlc, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
>  			    << " tex_y " << textureUniformBayerDataIn_
>  			    << " tex_step " << textureUniformStep_
>  			    << " tex_size " << textureUniformSize_
> -			    << " stride_factor " << textureUniformStrideFactor_
> -			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_;
> +			    << " stride_factor " << textureUniformStrideFactor_;
>  
>  	LOG(GpuShaderBlc, Debug) << "textureUniformY_ = 0 "
>  			    << " firstRed.x " << firstRed[0]
> @@ -220,6 +258,10 @@ void GpuIspShaderPassBlcNormalise::setShaderVariableValues(const DebayerParams &
>  	 * 0 = Red, 1 = Green, 2 = Blue
>  	 */
>  	glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]);
> +	err = glGetError();
> +	if (err != GL_NO_ERROR)
> +		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
> +
>  	LOG(GpuShaderBlc, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel;
>  
>  	return;
> diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp
> index b0c431c13..a1ca1916d 100644
> --- a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp
> +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp
> @@ -47,20 +47,20 @@ void GpuIspShaderPassDemosiac::stop()
>  int GpuIspShaderPassDemosiac::selectShaders(struct ShaderConfig &shaderCfg, [[maybe_unused]]PixelFormat &inputFormat, [[maybe_unused]]PixelFormat &outputFormat)
>  {
>  	/* Pixel location parameters */
> -	glFormat_ = GL_LUMINANCE;
> +	glFormat_ = GL_R16F;
>  	bytesPerPixel_ = 1;
>  	shaderStridePixels_ = passInputCfg_.stride;
>  
> -	/* Shader selection */
> +	shaderCfg.vertexShaderData = bayer_unpacked_vert;
> +	shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
> +	shaderCfg.fragmentShaderData = bayer_glr16_to_rgba_frag;
> +	shaderCfg.fragmentShaderDataLen = bayer_glr16_to_rgba_frag_len;
> +
>  	switch (inputFormat) {
>  	case libcamera::formats::SBGGR8:
>  	case libcamera::formats::SGBRG8:
>  	case libcamera::formats::SGRBG8:
>  	case libcamera::formats::SRGGB8:
> -		shaderCfg.fragmentShaderData = bayer_unpacked_frag;
> -		shaderCfg.fragmentShaderDataLen = bayer_unpacked_frag_len;
> -		shaderCfg.vertexShaderData = bayer_unpacked_vert;
> -		shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
>  		break;
>  	case libcamera::formats::SBGGR10_CSI2P:
>  	case libcamera::formats::SGBRG10_CSI2P:
> @@ -68,17 +68,9 @@ int GpuIspShaderPassDemosiac::selectShaders(struct ShaderConfig &shaderCfg, [[ma
>  	case libcamera::formats::SRGGB10_CSI2P:
>  		egl_.pushEnv(shaderCfg.shaderEnv, "#define RAW10P");
>  		if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {
> -			shaderCfg.fragmentShaderData = bayer_unpacked_frag;
> -			shaderCfg.fragmentShaderDataLen = bayer_unpacked_frag_len;
> -			shaderCfg.vertexShaderData = bayer_unpacked_vert;
> -			shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
>  			glFormat_ = GL_RG;
>  			bytesPerPixel_ = 2;
>  		} else {
> -			shaderCfg.fragmentShaderData = bayer_1x_packed_frag;
> -			shaderCfg.fragmentShaderDataLen = bayer_1x_packed_frag_len;
> -			shaderCfg.vertexShaderData = identity_vert;
> -			shaderCfg.vertexShaderDataLen = identity_vert_len;
>  			shaderStridePixels_ = passInputCfg_.size.width;
>  		}
>  		break;
> @@ -88,22 +80,16 @@ int GpuIspShaderPassDemosiac::selectShaders(struct ShaderConfig &shaderCfg, [[ma
>  	case libcamera::formats::SRGGB12_CSI2P:
>  		egl_.pushEnv(shaderCfg.shaderEnv, "#define RAW12P");
>  		if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {
> -			shaderCfg.fragmentShaderData = bayer_unpacked_frag;
> -			shaderCfg.fragmentShaderDataLen = bayer_unpacked_frag_len;
> -			shaderCfg.vertexShaderData = bayer_unpacked_vert;
> -			shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
>  			glFormat_ = GL_RG;
>  			bytesPerPixel_ = 2;
>  		} else {
> -			shaderCfg.fragmentShaderData = bayer_1x_packed_frag;
> -			shaderCfg.fragmentShaderDataLen = bayer_1x_packed_frag_len;
> -			shaderCfg.vertexShaderData = identity_vert;
> -			shaderCfg.vertexShaderDataLen = identity_vert_len;
>  			shaderStridePixels_ = passInputCfg_.size.width;
>  		}
>  		break;
>  	};
>  
> +	LOG(GpuShaderDemosiac, Info) << "ShaderPass = " << typeid(this).name() << " selected shader pair ";
> +
>  	return 0;
>  }
>  
> @@ -112,26 +98,21 @@ int GpuIspShaderPassDemosiac::getShaderVariableLocations(void)
>  	attributeVertex_ = glGetAttribLocation(programId_, "vertexIn");
>  	attributeTexture_ = glGetAttribLocation(programId_, "textureIn");
>  
> -	textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y");
>  	ccmUniformDataIn_ = glGetUniformLocation(programId_, "ccm");
> -	blackLevelUniformDataIn_ = glGetUniformLocation(programId_, "blacklevel");
>  	gammaUniformDataIn_ = glGetUniformLocation(programId_, "gamma");
>  	contrastExpUniformDataIn_ = glGetUniformLocation(programId_, "contrastExp");
>  
> -	textureUniformStep_ = glGetUniformLocation(programId_, "tex_step");
> -	textureUniformSize_ = glGetUniformLocation(programId_, "tex_size");
>  	textureUniformStrideFactor_ = glGetUniformLocation(programId_, "stride_factor");
>  	textureUniformBayerFirstRed_ = glGetUniformLocation(programId_, "tex_bayer_first_red");
>  	textureUniformProjMatrix_ = glGetUniformLocation(programId_, "proj_matrix");
> +	textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y");
> +	textureUniformStep_ = glGetUniformLocation(programId_, "tex_step");
> +	textureUniformSize_ = glGetUniformLocation(programId_, "tex_size");
>  
>  	LOG(GpuShaderDemosiac, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
> -			    << " tex_y " << textureUniformBayerDataIn_
>  			    << " ccm " << ccmUniformDataIn_
> -			    << " blacklevel " << blackLevelUniformDataIn_
>  			    << " gamma " << gammaUniformDataIn_
>  			    << " contrastExp " << contrastExpUniformDataIn_
> -			    << " tex_step " << textureUniformStep_
> -			    << " tex_size " << textureUniformSize_
>  			    << " stride_factor " << textureUniformStrideFactor_
>  			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_
>  			    << " proj_matrix " << textureUniformProjMatrix_;
> @@ -213,8 +194,6 @@ void GpuIspShaderPassDemosiac::setShaderVariableValues(const DebayerParams &para
>  
>  	LOG(GpuShaderDemosiac, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
>  			    << " tex_y " << textureUniformBayerDataIn_
> -			    << " tex_step " << textureUniformStep_
> -			    << " tex_size " << textureUniformSize_
>  			    << " stride_factor " << textureUniformStrideFactor_
>  			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_;
>  
> @@ -242,12 +221,6 @@ void GpuIspShaderPassDemosiac::setShaderVariableValues(const DebayerParams &para
>  	glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm);
>  	LOG(GpuShaderDemosiac, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix;
>  
> -	/*
> -	 * 0 = Red, 1 = Green, 2 = Blue
> -	 */
> -	glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]);
> -	LOG(GpuShaderDemosiac, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel;
> -
>  	/*
>  	 * Gamma
>  	 */
> diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h
> index c83024bc4..2302b2ea0 100644
> --- a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h
> +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h
> @@ -50,9 +50,6 @@ private:
>  	/* Represent per-frame CCM as a uniform vector of floats 3 x 3 */
>  	GLint ccmUniformDataIn_;
>  
> -	/* Black Level compensation */
> -	GLint blackLevelUniformDataIn_;
> -
>  	/* Gamma */
>  	GLint gammaUniformDataIn_;
>  
> diff --git a/src/libcamera/software_isp/software_isp_pipeline_gpu.cpp b/src/libcamera/software_isp/software_isp_pipeline_gpu.cpp
> index 3c9b48154..14f6d2b8c 100644
> --- a/src/libcamera/software_isp/software_isp_pipeline_gpu.cpp
> +++ b/src/libcamera/software_isp/software_isp_pipeline_gpu.cpp
> @@ -186,9 +186,12 @@ int SoftwareIspPipelineGpu::configure(const StreamConfiguration &inputCfg,
>  
>  	/* Configure for one pass */
>  	PassConfig rawSensorIn = { inputCfg.size, inputConfig_.stride, inputPixelFormat_, window };
> +	PassConfig pingPongOut = { inputCfg.size, inputCfg.size.width, inputPixelFormat_, Rectangle(inputCfg.size) };
> +	PassConfig pingPongIn = pingPongOut;
>  	PassConfig rgbaOut = { outputCfg.size, outputConfig_.stride, outputPixelFormat_, Rectangle(outputSize_) };
>  
> -	gpuIspShaderPassDemosiac_.configure(rawSensorIn, rgbaOut);
> +	gpuIspShaderPassBlcNormalise_.configure(rawSensorIn, pingPongOut);
> +	gpuIspShaderPassDemosiac_.configure(pingPongIn, rgbaOut);

Does it make sense to define two variables pingPongOut and pingPongIn
instead of a single one?

>  	return 0;
>  }
> @@ -257,7 +260,17 @@ int SoftwareIspPipelineGpu::processGPU(FrameBuffer *input, FrameBuffer *output,
>  	/* Generate the output render framebuffer as render to texture */
>  	egl_.createOutputDMABufTexture2D(*eglImageRGBAOut_, output->planes()[0].fd.get());
>  
> -	pipelineResult = gpuIspShaderPassDemosiac_.process(*eglImageBayerIn_, *eglImageRGBAOut_, width_, height_, params);
> +	pipelineResult = gpuIspShaderPassBlcNormalise_.process(*eglImageBayerIn_, *eglImagePingPong_[0], width_, height_, params);
> +	if (pipelineResult) {
> +		LOG(Debayer, Error) << "BlcNormalise fail";
> +		return pipelineResult;
> +	}
> +
> +	pipelineResult = gpuIspShaderPassDemosiac_.process(*eglImagePingPong_[0], *eglImageRGBAOut_, width_, height_, params);
> +	if (pipelineResult) {
> +		LOG(Debayer, Error) << "Demosiac fail";
> +		return pipelineResult;
> +	}
>  
>  	GLenum err = glGetError();
>  	if (err != GL_NO_ERROR) {
> @@ -337,15 +350,18 @@ int SoftwareIspPipelineGpu::start()
>  		return -EINVAL;
>  
>  	/* Raw bayer input as texture */
> -	eglImageBayerIn_ = std::make_unique<eGLImage>(gpuIspShaderPassDemosiac_.glFormat_, inputConfig_.stride / gpuIspShaderPassDemosiac_.getBytesPerPixel(), height_, inputConfig_.stride, GL_TEXTURE0, 0);
> +	eglImageBayerIn_ = std::make_unique<eGLImage>(gpuIspShaderPassBlcNormalise_.glFormat_, inputConfig_.stride / gpuIspShaderPassBlcNormalise_.getBytesPerPixel(), height_, inputConfig_.stride, GL_TEXTURE0, 0);
>  
>  	/* Intermediary ping/pong textures */
> -	eglImagePingPong_[0] = std::make_unique<eGLImage>(gpuIspShaderPassDemosiac_.glFormat_, outputSize_.width, outputSize_.height, outputConfig_.stride, GL_TEXTURE1, 1);
> -	eglImagePingPong_[1] = std::make_unique<eGLImage>(gpuIspShaderPassDemosiac_.glFormat_, outputSize_.width, outputSize_.height, outputConfig_.stride, GL_TEXTURE2, 2);
> +	eglImagePingPong_[0] = std::make_unique<eGLImage>(gpuIspShaderPassDemosiac_.glFormat_, width_, height_, outputConfig_.stride, GL_TEXTURE1, 1);
> +	eglImagePingPong_[1] = std::make_unique<eGLImage>(gpuIspShaderPassDemosiac_.glFormat_, width_, height_, outputConfig_.stride, GL_TEXTURE2, 2);
>  
>  	/* Texture we will render to */
>  	eglImageRGBAOut_ = std::make_unique<eGLImage>(GL_RGBA, outputSize_.width, outputSize_.height, outputConfig_.stride, GL_TEXTURE3, 3);
>  
> +	egl_.createOutputTexture2D(*eglImagePingPong_[0]);
> +	egl_.createOutputTexture2D(*eglImagePingPong_[1]);
> +
>  	return 0;
>  }

Patch
diff mbox series

diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp
index 8e8d7b9d6..c07319ee0 100644
--- a/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp
+++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp
@@ -102,6 +102,8 @@  int GpuIspShaderPassBlcNormalise::selectShaders(struct ShaderConfig &shaderCfg,
 		break;
 	};
 
+	LOG(GpuShaderBlc, Info) << "ShaderPass = " << typeid(this).name() << " selected shader pair ";
+
 	return 0;
 }
 
@@ -113,15 +115,14 @@  int GpuIspShaderPassBlcNormalise::getShaderVariableLocations(void)
 	textureUniformStep_ = glGetUniformLocation(programId_, "tex_step");
 	textureUniformSize_ = glGetUniformLocation(programId_, "tex_size");
 	textureUniformStrideFactor_ = glGetUniformLocation(programId_, "stride_factor");
-	textureUniformBayerFirstRed_ = glGetUniformLocation(programId_, "tex_bayer_first_red");
 	textureUniformProjMatrix_ = glGetUniformLocation(programId_, "proj_matrix");
+	blackLevelUniformDataIn_ = glGetUniformLocation(programId_, "blacklevel");
 
 	LOG(GpuShaderBlc, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
 			    << " tex_y " << textureUniformBayerDataIn_
 			    << " tex_step " << textureUniformStep_
 			    << " tex_size " << textureUniformSize_
 			    << " stride_factor " << textureUniformStrideFactor_
-			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_
 			    << " proj_matrix " << textureUniformProjMatrix_;
 
 	/* TODO: trap errors */
@@ -130,6 +131,8 @@  int GpuIspShaderPassBlcNormalise::getShaderVariableLocations(void)
 
 void GpuIspShaderPassBlcNormalise::setShaderVariableValues(const DebayerParams &params, eGLImage &eglImageIn)
 {
+	GLenum err;
+
 	/*
 	 * Raw Bayer 8-bit, and packed raw Bayer 10-bit/12-bit formats
 	 * are stored in a GL_LUMINANCE texture. The texture width is
@@ -168,15 +171,34 @@  void GpuIspShaderPassBlcNormalise::setShaderVariableValues(const DebayerParams &
 		{ 1.0f, 0.0f },
 	};
 
+	err = glGetError();
+	if (err != GL_NO_ERROR)
+		LOG(GpuShaderBlc, Error) << "setShaderVariables entry @ error " << err;
+
 	/* vertexIn - bayer_8.vert */
 	glEnableVertexAttribArray(attributeVertex_);
+
+	err = glGetError();
+	if (err != GL_NO_ERROR)
+		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
+
 	glVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE,
 			      2 * sizeof(GLfloat), vcoordinates);
+	err = glGetError();
+	if (err != GL_NO_ERROR)
+		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
 
 	/* textureIn - bayer_8.vert */
 	glEnableVertexAttribArray(attributeTexture_);
+	err = glGetError();
+	if (err != GL_NO_ERROR)
+		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
+
 	glVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE,
 			      2 * sizeof(GLfloat), tcoordinates);
+	err = glGetError();
+	if (err != GL_NO_ERROR)
+		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
 
 	/*
 	 * Set the sampler2D to the respective texture unit for each texutre
@@ -184,27 +206,43 @@  void GpuIspShaderPassBlcNormalise::setShaderVariableValues(const DebayerParams &
 	 * texture units
 	 */
 	glUniform1i(textureUniformBayerDataIn_, eglImageIn.texture_unit_uniform_id_);
+	err = glGetError();
+	if (err != GL_NO_ERROR)
+		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
 
 	/*
 	 * These values are:
-	 * firstRed = tex_bayer_first_red - bayer_8.vert
 	 * imgSize = tex_size - bayer_8.vert
 	 * step = tex_step - bayer_8.vert
 	 * Stride = stride_factor identity.vert
 	 * textureUniformProjMatri = No scaling
 	 */
-	glUniform2fv(textureUniformBayerFirstRed_, 1, firstRed);
 	glUniform2fv(textureUniformSize_, 1, imgSize);
+	err = glGetError();
+	if (err != GL_NO_ERROR)
+		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
+
 	glUniform2fv(textureUniformStep_, 1, Step);
+	err = glGetError();
+	if (err != GL_NO_ERROR)
+		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
+
 	glUniform1f(textureUniformStrideFactor_, Stride);
+	err = glGetError();
+	if (err != GL_NO_ERROR)
+		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
+
 	glUniformMatrix4fv(textureUniformProjMatrix_, 1, GL_FALSE, projMatrix);
+	err = glGetError();
+	if (err != GL_NO_ERROR)
+		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
+
 
 	LOG(GpuShaderBlc, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
 			    << " tex_y " << textureUniformBayerDataIn_
 			    << " tex_step " << textureUniformStep_
 			    << " tex_size " << textureUniformSize_
-			    << " stride_factor " << textureUniformStrideFactor_
-			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_;
+			    << " stride_factor " << textureUniformStrideFactor_;
 
 	LOG(GpuShaderBlc, Debug) << "textureUniformY_ = 0 "
 			    << " firstRed.x " << firstRed[0]
@@ -220,6 +258,10 @@  void GpuIspShaderPassBlcNormalise::setShaderVariableValues(const DebayerParams &
 	 * 0 = Red, 1 = Green, 2 = Blue
 	 */
 	glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]);
+	err = glGetError();
+	if (err != GL_NO_ERROR)
+		LOG(GpuShaderBlc, Error) << "setShaderVariables error " << err << " line " << __LINE__;
+
 	LOG(GpuShaderBlc, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel;
 
 	return;
diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp
index b0c431c13..a1ca1916d 100644
--- a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp
+++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.cpp
@@ -47,20 +47,20 @@  void GpuIspShaderPassDemosiac::stop()
 int GpuIspShaderPassDemosiac::selectShaders(struct ShaderConfig &shaderCfg, [[maybe_unused]]PixelFormat &inputFormat, [[maybe_unused]]PixelFormat &outputFormat)
 {
 	/* Pixel location parameters */
-	glFormat_ = GL_LUMINANCE;
+	glFormat_ = GL_R16F;
 	bytesPerPixel_ = 1;
 	shaderStridePixels_ = passInputCfg_.stride;
 
-	/* Shader selection */
+	shaderCfg.vertexShaderData = bayer_unpacked_vert;
+	shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
+	shaderCfg.fragmentShaderData = bayer_glr16_to_rgba_frag;
+	shaderCfg.fragmentShaderDataLen = bayer_glr16_to_rgba_frag_len;
+
 	switch (inputFormat) {
 	case libcamera::formats::SBGGR8:
 	case libcamera::formats::SGBRG8:
 	case libcamera::formats::SGRBG8:
 	case libcamera::formats::SRGGB8:
-		shaderCfg.fragmentShaderData = bayer_unpacked_frag;
-		shaderCfg.fragmentShaderDataLen = bayer_unpacked_frag_len;
-		shaderCfg.vertexShaderData = bayer_unpacked_vert;
-		shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
 		break;
 	case libcamera::formats::SBGGR10_CSI2P:
 	case libcamera::formats::SGBRG10_CSI2P:
@@ -68,17 +68,9 @@  int GpuIspShaderPassDemosiac::selectShaders(struct ShaderConfig &shaderCfg, [[ma
 	case libcamera::formats::SRGGB10_CSI2P:
 		egl_.pushEnv(shaderCfg.shaderEnv, "#define RAW10P");
 		if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {
-			shaderCfg.fragmentShaderData = bayer_unpacked_frag;
-			shaderCfg.fragmentShaderDataLen = bayer_unpacked_frag_len;
-			shaderCfg.vertexShaderData = bayer_unpacked_vert;
-			shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
 			glFormat_ = GL_RG;
 			bytesPerPixel_ = 2;
 		} else {
-			shaderCfg.fragmentShaderData = bayer_1x_packed_frag;
-			shaderCfg.fragmentShaderDataLen = bayer_1x_packed_frag_len;
-			shaderCfg.vertexShaderData = identity_vert;
-			shaderCfg.vertexShaderDataLen = identity_vert_len;
 			shaderStridePixels_ = passInputCfg_.size.width;
 		}
 		break;
@@ -88,22 +80,16 @@  int GpuIspShaderPassDemosiac::selectShaders(struct ShaderConfig &shaderCfg, [[ma
 	case libcamera::formats::SRGGB12_CSI2P:
 		egl_.pushEnv(shaderCfg.shaderEnv, "#define RAW12P");
 		if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {
-			shaderCfg.fragmentShaderData = bayer_unpacked_frag;
-			shaderCfg.fragmentShaderDataLen = bayer_unpacked_frag_len;
-			shaderCfg.vertexShaderData = bayer_unpacked_vert;
-			shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
 			glFormat_ = GL_RG;
 			bytesPerPixel_ = 2;
 		} else {
-			shaderCfg.fragmentShaderData = bayer_1x_packed_frag;
-			shaderCfg.fragmentShaderDataLen = bayer_1x_packed_frag_len;
-			shaderCfg.vertexShaderData = identity_vert;
-			shaderCfg.vertexShaderDataLen = identity_vert_len;
 			shaderStridePixels_ = passInputCfg_.size.width;
 		}
 		break;
 	};
 
+	LOG(GpuShaderDemosiac, Info) << "ShaderPass = " << typeid(this).name() << " selected shader pair ";
+
 	return 0;
 }
 
@@ -112,26 +98,21 @@  int GpuIspShaderPassDemosiac::getShaderVariableLocations(void)
 	attributeVertex_ = glGetAttribLocation(programId_, "vertexIn");
 	attributeTexture_ = glGetAttribLocation(programId_, "textureIn");
 
-	textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y");
 	ccmUniformDataIn_ = glGetUniformLocation(programId_, "ccm");
-	blackLevelUniformDataIn_ = glGetUniformLocation(programId_, "blacklevel");
 	gammaUniformDataIn_ = glGetUniformLocation(programId_, "gamma");
 	contrastExpUniformDataIn_ = glGetUniformLocation(programId_, "contrastExp");
 
-	textureUniformStep_ = glGetUniformLocation(programId_, "tex_step");
-	textureUniformSize_ = glGetUniformLocation(programId_, "tex_size");
 	textureUniformStrideFactor_ = glGetUniformLocation(programId_, "stride_factor");
 	textureUniformBayerFirstRed_ = glGetUniformLocation(programId_, "tex_bayer_first_red");
 	textureUniformProjMatrix_ = glGetUniformLocation(programId_, "proj_matrix");
+	textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y");
+	textureUniformStep_ = glGetUniformLocation(programId_, "tex_step");
+	textureUniformSize_ = glGetUniformLocation(programId_, "tex_size");
 
 	LOG(GpuShaderDemosiac, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
-			    << " tex_y " << textureUniformBayerDataIn_
 			    << " ccm " << ccmUniformDataIn_
-			    << " blacklevel " << blackLevelUniformDataIn_
 			    << " gamma " << gammaUniformDataIn_
 			    << " contrastExp " << contrastExpUniformDataIn_
-			    << " tex_step " << textureUniformStep_
-			    << " tex_size " << textureUniformSize_
 			    << " stride_factor " << textureUniformStrideFactor_
 			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_
 			    << " proj_matrix " << textureUniformProjMatrix_;
@@ -213,8 +194,6 @@  void GpuIspShaderPassDemosiac::setShaderVariableValues(const DebayerParams &para
 
 	LOG(GpuShaderDemosiac, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
 			    << " tex_y " << textureUniformBayerDataIn_
-			    << " tex_step " << textureUniformStep_
-			    << " tex_size " << textureUniformSize_
 			    << " stride_factor " << textureUniformStrideFactor_
 			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_;
 
@@ -242,12 +221,6 @@  void GpuIspShaderPassDemosiac::setShaderVariableValues(const DebayerParams &para
 	glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm);
 	LOG(GpuShaderDemosiac, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix;
 
-	/*
-	 * 0 = Red, 1 = Green, 2 = Blue
-	 */
-	glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]);
-	LOG(GpuShaderDemosiac, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel;
-
 	/*
 	 * Gamma
 	 */
diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h
index c83024bc4..2302b2ea0 100644
--- a/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h
+++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_demosiac.h
@@ -50,9 +50,6 @@  private:
 	/* Represent per-frame CCM as a uniform vector of floats 3 x 3 */
 	GLint ccmUniformDataIn_;
 
-	/* Black Level compensation */
-	GLint blackLevelUniformDataIn_;
-
 	/* Gamma */
 	GLint gammaUniformDataIn_;
 
diff --git a/src/libcamera/software_isp/software_isp_pipeline_gpu.cpp b/src/libcamera/software_isp/software_isp_pipeline_gpu.cpp
index 3c9b48154..14f6d2b8c 100644
--- a/src/libcamera/software_isp/software_isp_pipeline_gpu.cpp
+++ b/src/libcamera/software_isp/software_isp_pipeline_gpu.cpp
@@ -186,9 +186,12 @@  int SoftwareIspPipelineGpu::configure(const StreamConfiguration &inputCfg,
 
 	/* Configure for one pass */
 	PassConfig rawSensorIn = { inputCfg.size, inputConfig_.stride, inputPixelFormat_, window };
+	PassConfig pingPongOut = { inputCfg.size, inputCfg.size.width, inputPixelFormat_, Rectangle(inputCfg.size) };
+	PassConfig pingPongIn = pingPongOut;
 	PassConfig rgbaOut = { outputCfg.size, outputConfig_.stride, outputPixelFormat_, Rectangle(outputSize_) };
 
-	gpuIspShaderPassDemosiac_.configure(rawSensorIn, rgbaOut);
+	gpuIspShaderPassBlcNormalise_.configure(rawSensorIn, pingPongOut);
+	gpuIspShaderPassDemosiac_.configure(pingPongIn, rgbaOut);
 
 	return 0;
 }
@@ -257,7 +260,17 @@  int SoftwareIspPipelineGpu::processGPU(FrameBuffer *input, FrameBuffer *output,
 	/* Generate the output render framebuffer as render to texture */
 	egl_.createOutputDMABufTexture2D(*eglImageRGBAOut_, output->planes()[0].fd.get());
 
-	pipelineResult = gpuIspShaderPassDemosiac_.process(*eglImageBayerIn_, *eglImageRGBAOut_, width_, height_, params);
+	pipelineResult = gpuIspShaderPassBlcNormalise_.process(*eglImageBayerIn_, *eglImagePingPong_[0], width_, height_, params);
+	if (pipelineResult) {
+		LOG(Debayer, Error) << "BlcNormalise fail";
+		return pipelineResult;
+	}
+
+	pipelineResult = gpuIspShaderPassDemosiac_.process(*eglImagePingPong_[0], *eglImageRGBAOut_, width_, height_, params);
+	if (pipelineResult) {
+		LOG(Debayer, Error) << "Demosiac fail";
+		return pipelineResult;
+	}
 
 	GLenum err = glGetError();
 	if (err != GL_NO_ERROR) {
@@ -337,15 +350,18 @@  int SoftwareIspPipelineGpu::start()
 		return -EINVAL;
 
 	/* Raw bayer input as texture */
-	eglImageBayerIn_ = std::make_unique<eGLImage>(gpuIspShaderPassDemosiac_.glFormat_, inputConfig_.stride / gpuIspShaderPassDemosiac_.getBytesPerPixel(), height_, inputConfig_.stride, GL_TEXTURE0, 0);
+	eglImageBayerIn_ = std::make_unique<eGLImage>(gpuIspShaderPassBlcNormalise_.glFormat_, inputConfig_.stride / gpuIspShaderPassBlcNormalise_.getBytesPerPixel(), height_, inputConfig_.stride, GL_TEXTURE0, 0);
 
 	/* Intermediary ping/pong textures */
-	eglImagePingPong_[0] = std::make_unique<eGLImage>(gpuIspShaderPassDemosiac_.glFormat_, outputSize_.width, outputSize_.height, outputConfig_.stride, GL_TEXTURE1, 1);
-	eglImagePingPong_[1] = std::make_unique<eGLImage>(gpuIspShaderPassDemosiac_.glFormat_, outputSize_.width, outputSize_.height, outputConfig_.stride, GL_TEXTURE2, 2);
+	eglImagePingPong_[0] = std::make_unique<eGLImage>(gpuIspShaderPassDemosiac_.glFormat_, width_, height_, outputConfig_.stride, GL_TEXTURE1, 1);
+	eglImagePingPong_[1] = std::make_unique<eGLImage>(gpuIspShaderPassDemosiac_.glFormat_, width_, height_, outputConfig_.stride, GL_TEXTURE2, 2);
 
 	/* Texture we will render to */
 	eglImageRGBAOut_ = std::make_unique<eGLImage>(GL_RGBA, outputSize_.width, outputSize_.height, outputConfig_.stride, GL_TEXTURE3, 3);
 
+	egl_.createOutputTexture2D(*eglImagePingPong_[0]);
+	egl_.createOutputTexture2D(*eglImagePingPong_[1]);
+
 	return 0;
 }