[14/30] libcamera: software_isp: gpu: Add GpuIspShaderPassBlcNormalise
diff mbox series

Message ID 20260618122245.946138-15-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
Make GpuIspShaderPassBlcNormalise which takes existing logic to select
fragment/vertex shaders based on the incoming CSI2 packign but, select new
shaders whose task in life is to apply our blacklevel normalisation step
and output a normalised 16 bit float.

The unpacked 16f will then be fed into the existing unpacked demosiac
shader. This patch compiles but doesn't do anything useful yet.

Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
---
 ...gpu_pipeline_shader_pass_blc_normalise.cpp | 228 ++++++++++++++++++
 .../gpu_pipeline_shader_pass_blc_normalise.h  |  55 +++++
 src/libcamera/software_isp/meson.build        |   1 +
 3 files changed, 284 insertions(+)
 create mode 100644 src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp
 create mode 100644 src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.h

Comments

Milan Zamazal June 23, 2026, 1:46 p.m. UTC | #1
Bryan O'Donoghue <bryan.odonoghue@linaro.org> writes:

> Make GpuIspShaderPassBlcNormalise which takes existing logic to select
> fragment/vertex shaders based on the incoming CSI2 packign but, select new

s/packign/packing/

> shaders whose task in life is to apply our blacklevel normalisation step
> and output a normalised 16 bit float.
>
> The unpacked 16f will then be fed into the existing unpacked demosiac

s/unpacked//

> shader. This patch compiles but doesn't do anything useful yet.
>
> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
> ---
>  ...gpu_pipeline_shader_pass_blc_normalise.cpp | 228 ++++++++++++++++++
>  .../gpu_pipeline_shader_pass_blc_normalise.h  |  55 +++++
>  src/libcamera/software_isp/meson.build        |   1 +
>  3 files changed, 284 insertions(+)
>  create mode 100644 src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp
>  create mode 100644 src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.h
>
> 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
> new file mode 100644
> index 000000000..8e8d7b9d6
> --- /dev/null
> +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp
> @@ -0,0 +1,228 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2026, Linaro Ltd
> + *
> + * Authors:
> + * Bryan O'Donoghue <bryan.odonoghue@linaro.org>
> + *
> + * GpuIspShaderPassBlcNormalise base class
> + */
> +
> +#include <stdint.h>
> +
> +#include <libcamera/base/log.h>
> +#include <libcamera/base/object.h>
> +#include <libcamera/base/signal.h>
> +
> +#include <libcamera/geometry.h>
> +#include <libcamera/formats.h>
> +#include <libcamera/stream.h>
> +
> +#include "libcamera/internal/bayer_format.h"
> +#include "libcamera/internal/egl.h"
> +#include "libcamera/internal/software_isp/debayer_params.h"
> +
> +#include "gpu_pipeline_shader_pass_blc_normalise.h"
> +
> +/**
> + * \file software_isp.cpp
> + * \brief Simple software ISP implementation
> + */
> +
> +namespace libcamera {
> +
> +LOG_DEFINE_CATEGORY(GpuShaderBlc)
> +
> +int GpuIspShaderPassBlcNormalise::start()
> +{
> +	return 0;
> +}

The method is the same as in the inherited class.

> +
> +void GpuIspShaderPassBlcNormalise::stop()
> +{
> +}

And again, no need to delete the program as in the inherited class?

It looks like these methods should be simply inherited in common cases?

> +
> +int GpuIspShaderPassBlcNormalise::selectShaders(struct ShaderConfig &shaderCfg, [[maybe_unused]]PixelFormat &inputFormat, [[maybe_unused]]PixelFormat &outputFormat)
> +{
> +	/* Pixel location parameters */
> +	glFormat_ = GL_LUMINANCE;
> +	bytesPerPixel_ = 1;
> +	shaderStridePixels_ = passInputCfg_.stride;
> +
> +	/* Shader selection */
> +	switch (inputFormat) {
> +	case libcamera::formats::SBGGR8:
> +	case libcamera::formats::SGBRG8:
> +	case libcamera::formats::SGRBG8:
> +	case libcamera::formats::SRGGB8:
> +		shaderCfg.fragmentShaderData = bayer_unpacked_to_blc_glr16f_frag;
> +		shaderCfg.fragmentShaderDataLen = bayer_unpacked_to_blc_glr16f_frag_len;
> +		shaderCfg.vertexShaderData = bayer_unpacked_vert;
> +		shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;

The introductory comment in bayer_unpacked_to_blc_glr16f.frag says it
pairs with identity.vert.

> +		break;
> +	case libcamera::formats::SBGGR10_CSI2P:
> +	case libcamera::formats::SGBRG10_CSI2P:
> +	case libcamera::formats::SGRBG10_CSI2P:
> +	case libcamera::formats::SRGGB10_CSI2P:
> +		egl_.pushEnv(shaderCfg.shaderEnv, "#define RAW10P");
> +		if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {
> +			shaderCfg.fragmentShaderData = bayer_unpacked_to_blc_glr16f_frag;
> +			shaderCfg.fragmentShaderDataLen = bayer_unpacked_to_blc_glr16f_frag_len;
> +			shaderCfg.vertexShaderData = bayer_unpacked_vert;
> +			shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
> +			glFormat_ = GL_RG;
> +			bytesPerPixel_ = 2;
> +		} else {
> +			shaderCfg.fragmentShaderData = bayer_1x_packed_to_blc_glr16f_frag;
> +			shaderCfg.fragmentShaderDataLen = bayer_1x_packed_to_blc_glr16f_frag_len;
> +			shaderCfg.vertexShaderData = identity_vert;
> +			shaderCfg.vertexShaderDataLen = identity_vert_len;
> +			shaderStridePixels_ = passInputCfg_.size.width;
> +		}
> +		break;
> +	case libcamera::formats::SBGGR12_CSI2P:
> +	case libcamera::formats::SGBRG12_CSI2P:
> +	case libcamera::formats::SGRBG12_CSI2P:
> +	case libcamera::formats::SRGGB12_CSI2P:
> +		egl_.pushEnv(shaderCfg.shaderEnv, "#define RAW12P");
> +		if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {
> +			shaderCfg.fragmentShaderData = bayer_unpacked_to_blc_glr16f_frag;
> +			shaderCfg.fragmentShaderDataLen = bayer_unpacked_to_blc_glr16f_frag_len;
> +			shaderCfg.vertexShaderData = bayer_unpacked_vert;
> +			shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
> +			glFormat_ = GL_RG;
> +			bytesPerPixel_ = 2;
> +		} else {
> +			shaderCfg.fragmentShaderData = bayer_1x_packed_to_blc_glr16f_frag;
> +			shaderCfg.fragmentShaderDataLen = bayer_1x_packed_to_blc_glr16f_frag_len;
> +			shaderCfg.vertexShaderData = identity_vert;
> +			shaderCfg.vertexShaderDataLen = identity_vert_len;
> +			shaderStridePixels_ = passInputCfg_.size.width;
> +		}
> +		break;
> +	};

Looks like there should be some opportunity here for deduplication of
the repeated blocks.

> +
> +	return 0;
> +}
> +
> +int GpuIspShaderPassBlcNormalise::getShaderVariableLocations(void)
> +{
> +	attributeVertex_ = glGetAttribLocation(programId_, "vertexIn");
> +	attributeTexture_ = glGetAttribLocation(programId_, "textureIn");
> +	textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y");
> +	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");
> +
> +	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 */

Which ones?

> +	return 0;
> +}
> +
> +void GpuIspShaderPassBlcNormalise::setShaderVariableValues(const DebayerParams &params, eGLImage &eglImageIn)
> +{
> +	/*
> +	 * Raw Bayer 8-bit, and packed raw Bayer 10-bit/12-bit formats
> +	 * are stored in a GL_LUMINANCE texture. The texture width is
> +	 * equal to the stride.
> +	 */
> +	GLfloat firstRed[] = { firstRed_x_, firstRed_y_ };
> +	GLfloat imgSize[] = { (GLfloat)passInputCfg_.size.width,
> +			      (GLfloat)passInputCfg_.size.height };
> +	GLfloat Step[] = { static_cast<float>(bytesPerPixel_) / (passInputCfg_.stride - 1),
> +			   1.0f / (passInputCfg_.size.height - 1) };
> +	GLfloat Stride = (GLfloat)passInputCfg_.size.width / (shaderStridePixels_ / bytesPerPixel_);
> +	/*
> +	 * Scale input to output size, keeping the aspect ratio and preferring
> +	 * cropping over black bars.
> +	 */
> +	GLfloat scale = std::max((GLfloat)passInputCfg_.window.width / passInputCfg_.size.width,
> +				 (GLfloat)passInputCfg_.window.height / passInputCfg_.size.height);
> +	GLfloat trans = -(1.0f - scale);
> +	GLfloat projMatrix[] = {
> +		scale, 0, 0, 0,
> +		0, scale, 0, 0,
> +		0, 0, 1, 0,
> +		trans, trans, 0, 1
> +	};
> +	/* Static const coordinates */
> +	static const GLfloat vcoordinates[4][2] = {
> +		{ -1.0f, -1.0f },
> +		{ -1.0f, +1.0f },
> +		{ +1.0f, +1.0f },
> +		{ +1.0f, -1.0f },
> +	};
> +	static const GLfloat tcoordinates[4][2] = {
> +		{ 0.0f, 0.0f },
> +		{ 0.0f, 1.0f },
> +		{ 1.0f, 1.0f },
> +		{ 1.0f, 0.0f },
> +	};
> +
> +	/* vertexIn - bayer_8.vert */
> +	glEnableVertexAttribArray(attributeVertex_);
> +	glVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE,
> +			      2 * sizeof(GLfloat), vcoordinates);
> +
> +	/* textureIn - bayer_8.vert */
> +	glEnableVertexAttribArray(attributeTexture_);
> +	glVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE,
> +			      2 * sizeof(GLfloat), tcoordinates);
> +
> +	/*
> +	 * Set the sampler2D to the respective texture unit for each texutre
> +	 * To simultaneously sample multiple textures we need to use multiple
> +	 * texture units
> +	 */
> +	glUniform1i(textureUniformBayerDataIn_, eglImageIn.texture_unit_uniform_id_);
> +
> +	/*
> +	 * 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);
> +	glUniform2fv(textureUniformStep_, 1, Step);
> +	glUniform1f(textureUniformStrideFactor_, Stride);
> +	glUniformMatrix4fv(textureUniformProjMatrix_, 1, GL_FALSE, projMatrix);
> +
> +	LOG(GpuShaderBlc, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
> +			    << " tex_y " << textureUniformBayerDataIn_
> +			    << " tex_step " << textureUniformStep_
> +			    << " tex_size " << textureUniformSize_
> +			    << " stride_factor " << textureUniformStrideFactor_
> +			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_;
> +
> +	LOG(GpuShaderBlc, Debug) << "textureUniformY_ = 0 "
> +			    << " firstRed.x " << firstRed[0]
> +			    << " firstRed.y " << firstRed[1]
> +			    << " textureUniformSize_.width " << imgSize[0]
> +			    << " textureUniformSize_.height " << imgSize[1]
> +			    << " textureUniformStep_.x " << Step[0]
> +			    << " textureUniformStep_.y " << Step[1]
> +			    << " textureUniformStrideFactor_ " << Stride
> +			    << " textureUniformProjMatrix_ " << textureUniformProjMatrix_;
> +
> +	/*
> +	 * 0 = Red, 1 = Green, 2 = Blue
> +	 */
> +	glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]);
> +	LOG(GpuShaderBlc, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel;
> +
> +	return;
> +}
> +
> +} /* namespace libcamera */
> diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.h b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.h
> new file mode 100644
> index 000000000..64ea2fc11
> --- /dev/null
> +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.h
> @@ -0,0 +1,55 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2026, Linaro Ltd
> + *
> + * Authors:
> + * Bryan O'Donoghue <bryan.odonoghue@linaro.org>
> + *
> + * GpuIspPipelineShaderPass base class
> + */
> +
> +#pragma once
> +
> +#include <stdint.h>
> +
> +#include <libcamera/base/log.h>
> +#include <libcamera/base/object.h>
> +#include <libcamera/base/signal.h>
> +
> +#include <libcamera/geometry.h>
> +#include <libcamera/stream.h>
> +
> +#include "libcamera/internal/egl.h"
> +#include "libcamera/internal/software_isp/debayer_params.h"
> +
> +#include "gpu_pipeline_shader_pass.h"
> +
> +namespace libcamera {
> +
> +class FrameBuffer;
> +
> +class GpuIspShaderPassBlcNormalise : public GpuIspShaderPass
> +{
> +public:
> +	GpuIspShaderPassBlcNormalise(eGL& egl) : GpuIspShaderPass(egl) {};
> +
> +	int start();
> +	void stop();
> +
> +	/* Things that every ISP pipeline pass will need to do */
> +	int getShaderVariableLocations(void);

s/void//

> +	void setShaderVariableValues(const DebayerParams &params, eGLImage &eglImageIn);
> +	int selectShaders(struct ShaderConfig &shaderCfg, PixelFormat &inputFormat, PixelFormat &outputFormat);

`override's?

> +	const char *name() const override { return "GpuIspShaderPassBlcNormalise"; }
> +
> +private:
> +	/* Shader parameters */
> +	GLint textureUniformStep_;
> +	GLint textureUniformSize_;
> +
> +	/* Black Level compensation */
> +	GLint blackLevelUniformDataIn_;
> +
> +};
> +
> +} /* namespace libcamera */
> diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
> index 3e7d21318..e9caa3629 100644
> --- a/src/libcamera/software_isp/meson.build
> +++ b/src/libcamera/software_isp/meson.build
> @@ -34,6 +34,7 @@ if mesa_works
>          'software_isp_pipeline_gpu.cpp',
>          'gpu_pipeline_shader_pass.cpp',
>          'gpu_pipeline_shader_pass_demosiac.cpp',
> +	'gpu_pipeline_shader_pass_blc_normalise.cpp',
>      ])
>      libcamera_deps += [
>          libegl,
Milan Zamazal June 25, 2026, 7:33 p.m. UTC | #2
Milan Zamazal <mzamazal@redhat.com> writes:

> Bryan O'Donoghue <bryan.odonoghue@linaro.org> writes:
>
>> Make GpuIspShaderPassBlcNormalise which takes existing logic to select
>> fragment/vertex shaders based on the incoming CSI2 packign but, select new
>
> s/packign/packing/
>
>> shaders whose task in life is to apply our blacklevel normalisation step
>> and output a normalised 16 bit float.
>>
>> The unpacked 16f will then be fed into the existing unpacked demosiac
>
> s/unpacked//
>
>> shader. This patch compiles but doesn't do anything useful yet.
>>
>> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
>> ---
>>  ...gpu_pipeline_shader_pass_blc_normalise.cpp | 228 ++++++++++++++++++
>>  .../gpu_pipeline_shader_pass_blc_normalise.h  |  55 +++++
>>  src/libcamera/software_isp/meson.build        |   1 +
>>  3 files changed, 284 insertions(+)
>>  create mode 100644 src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp
>>  create mode 100644 src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.h
>>
>> 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
>> new file mode 100644
>> index 000000000..8e8d7b9d6
>> --- /dev/null
>> +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp
>> @@ -0,0 +1,228 @@
>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
>> +/*
>> + * Copyright (C) 2026, Linaro Ltd
>> + *
>> + * Authors:
>> + * Bryan O'Donoghue <bryan.odonoghue@linaro.org>
>> + *
>> + * GpuIspShaderPassBlcNormalise base class
>> + */
>> +
>> +#include <stdint.h>
>> +
>> +#include <libcamera/base/log.h>
>> +#include <libcamera/base/object.h>
>> +#include <libcamera/base/signal.h>
>> +
>> +#include <libcamera/geometry.h>
>> +#include <libcamera/formats.h>
>> +#include <libcamera/stream.h>
>> +
>> +#include "libcamera/internal/bayer_format.h"
>> +#include "libcamera/internal/egl.h"
>> +#include "libcamera/internal/software_isp/debayer_params.h"
>> +
>> +#include "gpu_pipeline_shader_pass_blc_normalise.h"
>> +
>> +/**
>> + * \file software_isp.cpp
>> + * \brief Simple software ISP implementation
>> + */
>> +
>> +namespace libcamera {
>> +
>> +LOG_DEFINE_CATEGORY(GpuShaderBlc)
>> +
>> +int GpuIspShaderPassBlcNormalise::start()
>> +{
>> +	return 0;
>> +}
>
> The method is the same as in the inherited class.
>
>> +
>> +void GpuIspShaderPassBlcNormalise::stop()
>> +{
>> +}
>
> And again, no need to delete the program as in the inherited class?
>
> It looks like these methods should be simply inherited in common cases?
>
>> +
>> +int GpuIspShaderPassBlcNormalise::selectShaders(struct ShaderConfig &shaderCfg, [[maybe_unused]]PixelFormat &inputFormat, [[maybe_unused]]PixelFormat &outputFormat)
>> +{
>> +	/* Pixel location parameters */
>> +	glFormat_ = GL_LUMINANCE;
>> +	bytesPerPixel_ = 1;
>> +	shaderStridePixels_ = passInputCfg_.stride;
>> +
>> +	/* Shader selection */
>> +	switch (inputFormat) {
>> +	case libcamera::formats::SBGGR8:
>> +	case libcamera::formats::SGBRG8:
>> +	case libcamera::formats::SGRBG8:
>> +	case libcamera::formats::SRGGB8:
>> +		shaderCfg.fragmentShaderData = bayer_unpacked_to_blc_glr16f_frag;
>> +		shaderCfg.fragmentShaderDataLen = bayer_unpacked_to_blc_glr16f_frag_len;
>> +		shaderCfg.vertexShaderData = bayer_unpacked_vert;
>> +		shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
>
> The introductory comment in bayer_unpacked_to_blc_glr16f.frag says it
> pairs with identity.vert.

The comment is correct, the identity vertex shader should be used to
normalise both the packed and unpacked encodings (the same for the other
formats below).  The use of the wrong shader here results in the error
about missing textureOut, which is set only by the identity vertex
shader.

>> +		break;
>> +	case libcamera::formats::SBGGR10_CSI2P:
>> +	case libcamera::formats::SGBRG10_CSI2P:
>> +	case libcamera::formats::SGRBG10_CSI2P:
>> +	case libcamera::formats::SRGGB10_CSI2P:
>> +		egl_.pushEnv(shaderCfg.shaderEnv, "#define RAW10P");
>> +		if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {
>> +			shaderCfg.fragmentShaderData = bayer_unpacked_to_blc_glr16f_frag;
>> +			shaderCfg.fragmentShaderDataLen = bayer_unpacked_to_blc_glr16f_frag_len;
>> +			shaderCfg.vertexShaderData = bayer_unpacked_vert;
>> +			shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
>> +			glFormat_ = GL_RG;
>> +			bytesPerPixel_ = 2;
>> +		} else {
>> +			shaderCfg.fragmentShaderData = bayer_1x_packed_to_blc_glr16f_frag;
>> +			shaderCfg.fragmentShaderDataLen = bayer_1x_packed_to_blc_glr16f_frag_len;
>> +			shaderCfg.vertexShaderData = identity_vert;
>> +			shaderCfg.vertexShaderDataLen = identity_vert_len;
>> +			shaderStridePixels_ = passInputCfg_.size.width;
>> +		}
>> +		break;
>> +	case libcamera::formats::SBGGR12_CSI2P:
>> +	case libcamera::formats::SGBRG12_CSI2P:
>> +	case libcamera::formats::SGRBG12_CSI2P:
>> +	case libcamera::formats::SRGGB12_CSI2P:
>> +		egl_.pushEnv(shaderCfg.shaderEnv, "#define RAW12P");
>> +		if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {
>> +			shaderCfg.fragmentShaderData = bayer_unpacked_to_blc_glr16f_frag;
>> +			shaderCfg.fragmentShaderDataLen = bayer_unpacked_to_blc_glr16f_frag_len;
>> +			shaderCfg.vertexShaderData = bayer_unpacked_vert;
>> +			shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
>> +			glFormat_ = GL_RG;
>> +			bytesPerPixel_ = 2;
>> +		} else {
>> +			shaderCfg.fragmentShaderData = bayer_1x_packed_to_blc_glr16f_frag;
>> +			shaderCfg.fragmentShaderDataLen = bayer_1x_packed_to_blc_glr16f_frag_len;
>> +			shaderCfg.vertexShaderData = identity_vert;
>> +			shaderCfg.vertexShaderDataLen = identity_vert_len;
>> +			shaderStridePixels_ = passInputCfg_.size.width;
>> +		}
>> +		break;
>> +	};
>
> Looks like there should be some opportunity here for deduplication of
> the repeated blocks.
>
>> +
>> +	return 0;
>> +}
>> +
>> +int GpuIspShaderPassBlcNormalise::getShaderVariableLocations(void)
>> +{
>> +	attributeVertex_ = glGetAttribLocation(programId_, "vertexIn");
>> +	attributeTexture_ = glGetAttribLocation(programId_, "textureIn");
>> +	textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y");
>> +	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");
>> +
>> + 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 */
>
> Which ones?
>
>> +	return 0;
>> +}
>> +
>> +void GpuIspShaderPassBlcNormalise::setShaderVariableValues(const DebayerParams &params, eGLImage &eglImageIn)
>> +{
>> +	/*
>> +	 * Raw Bayer 8-bit, and packed raw Bayer 10-bit/12-bit formats
>> +	 * are stored in a GL_LUMINANCE texture. The texture width is
>> +	 * equal to the stride.
>> +	 */
>> +	GLfloat firstRed[] = { firstRed_x_, firstRed_y_ };
>> +	GLfloat imgSize[] = { (GLfloat)passInputCfg_.size.width,
>> +			      (GLfloat)passInputCfg_.size.height };
>> +	GLfloat Step[] = { static_cast<float>(bytesPerPixel_) / (passInputCfg_.stride - 1),
>> +			   1.0f / (passInputCfg_.size.height - 1) };
>> +	GLfloat Stride = (GLfloat)passInputCfg_.size.width / (shaderStridePixels_ / bytesPerPixel_);
>> +	/*
>> +	 * Scale input to output size, keeping the aspect ratio and preferring
>> +	 * cropping over black bars.
>> +	 */
>> +	GLfloat scale = std::max((GLfloat)passInputCfg_.window.width / passInputCfg_.size.width,
>> +				 (GLfloat)passInputCfg_.window.height / passInputCfg_.size.height);
>> +	GLfloat trans = -(1.0f - scale);
>> +	GLfloat projMatrix[] = {
>> +		scale, 0, 0, 0,
>> +		0, scale, 0, 0,
>> +		0, 0, 1, 0,
>> +		trans, trans, 0, 1
>> +	};
>> +	/* Static const coordinates */
>> +	static const GLfloat vcoordinates[4][2] = {
>> +		{ -1.0f, -1.0f },
>> +		{ -1.0f, +1.0f },
>> +		{ +1.0f, +1.0f },
>> +		{ +1.0f, -1.0f },
>> +	};
>> +	static const GLfloat tcoordinates[4][2] = {
>> +		{ 0.0f, 0.0f },
>> +		{ 0.0f, 1.0f },
>> +		{ 1.0f, 1.0f },
>> +		{ 1.0f, 0.0f },
>> +	};
>> +
>> +	/* vertexIn - bayer_8.vert */
>> +	glEnableVertexAttribArray(attributeVertex_);
>> +	glVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE,
>> +			      2 * sizeof(GLfloat), vcoordinates);
>> +
>> +	/* textureIn - bayer_8.vert */
>> +	glEnableVertexAttribArray(attributeTexture_);
>> +	glVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE,
>> +			      2 * sizeof(GLfloat), tcoordinates);
>> +
>> +	/*
>> +	 * Set the sampler2D to the respective texture unit for each texutre
>> +	 * To simultaneously sample multiple textures we need to use multiple
>> +	 * texture units
>> +	 */
>> +	glUniform1i(textureUniformBayerDataIn_, eglImageIn.texture_unit_uniform_id_);
>> +
>> +	/*
>> +	 * 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);
>> +	glUniform2fv(textureUniformStep_, 1, Step);
>> +	glUniform1f(textureUniformStrideFactor_, Stride);
>> +	glUniformMatrix4fv(textureUniformProjMatrix_, 1, GL_FALSE, projMatrix);
>> +
>> + LOG(GpuShaderBlc, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
>> +			    << " tex_y " << textureUniformBayerDataIn_
>> +			    << " tex_step " << textureUniformStep_
>> +			    << " tex_size " << textureUniformSize_
>> +			    << " stride_factor " << textureUniformStrideFactor_
>> +			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_;
>> +
>> +	LOG(GpuShaderBlc, Debug) << "textureUniformY_ = 0 "
>> +			    << " firstRed.x " << firstRed[0]
>> +			    << " firstRed.y " << firstRed[1]
>> +			    << " textureUniformSize_.width " << imgSize[0]
>> +			    << " textureUniformSize_.height " << imgSize[1]
>> +			    << " textureUniformStep_.x " << Step[0]
>> +			    << " textureUniformStep_.y " << Step[1]
>> +			    << " textureUniformStrideFactor_ " << Stride
>> +			    << " textureUniformProjMatrix_ " << textureUniformProjMatrix_;
>> +
>> +	/*
>> +	 * 0 = Red, 1 = Green, 2 = Blue
>> +	 */
>> + glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]);
>> + LOG(GpuShaderBlc, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel;
>> +
>> +	return;
>> +}
>> +
>> +} /* namespace libcamera */
>> diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.h b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.h
>> new file mode 100644
>> index 000000000..64ea2fc11
>> --- /dev/null
>> +++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.h
>> @@ -0,0 +1,55 @@
>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
>> +/*
>> + * Copyright (C) 2026, Linaro Ltd
>> + *
>> + * Authors:
>> + * Bryan O'Donoghue <bryan.odonoghue@linaro.org>
>> + *
>> + * GpuIspPipelineShaderPass base class
>> + */
>> +
>> +#pragma once
>> +
>> +#include <stdint.h>
>> +
>> +#include <libcamera/base/log.h>
>> +#include <libcamera/base/object.h>
>> +#include <libcamera/base/signal.h>
>> +
>> +#include <libcamera/geometry.h>
>> +#include <libcamera/stream.h>
>> +
>> +#include "libcamera/internal/egl.h"
>> +#include "libcamera/internal/software_isp/debayer_params.h"
>> +
>> +#include "gpu_pipeline_shader_pass.h"
>> +
>> +namespace libcamera {
>> +
>> +class FrameBuffer;
>> +
>> +class GpuIspShaderPassBlcNormalise : public GpuIspShaderPass
>> +{
>> +public:
>> +	GpuIspShaderPassBlcNormalise(eGL& egl) : GpuIspShaderPass(egl) {};
>> +
>> +	int start();
>> +	void stop();
>> +
>> +	/* Things that every ISP pipeline pass will need to do */
>> +	int getShaderVariableLocations(void);
>
> s/void//
>
>> +	void setShaderVariableValues(const DebayerParams &params, eGLImage &eglImageIn);
>> + int selectShaders(struct ShaderConfig &shaderCfg, PixelFormat &inputFormat, PixelFormat &outputFormat);
>
> `override's?
>
>> +	const char *name() const override { return "GpuIspShaderPassBlcNormalise"; }
>> +
>> +private:
>> +	/* Shader parameters */
>> +	GLint textureUniformStep_;
>> +	GLint textureUniformSize_;
>> +
>> +	/* Black Level compensation */
>> +	GLint blackLevelUniformDataIn_;
>> +
>> +};
>> +
>> +} /* namespace libcamera */
>> diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
>> index 3e7d21318..e9caa3629 100644
>> --- a/src/libcamera/software_isp/meson.build
>> +++ b/src/libcamera/software_isp/meson.build
>> @@ -34,6 +34,7 @@ if mesa_works
>>          'software_isp_pipeline_gpu.cpp',
>>          'gpu_pipeline_shader_pass.cpp',
>>          'gpu_pipeline_shader_pass_demosiac.cpp',
>> +	'gpu_pipeline_shader_pass_blc_normalise.cpp',
>>      ])
>>      libcamera_deps += [
>>          libegl,

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
new file mode 100644
index 000000000..8e8d7b9d6
--- /dev/null
+++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.cpp
@@ -0,0 +1,228 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026, Linaro Ltd
+ *
+ * Authors:
+ * Bryan O'Donoghue <bryan.odonoghue@linaro.org>
+ *
+ * GpuIspShaderPassBlcNormalise base class
+ */
+
+#include <stdint.h>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/signal.h>
+
+#include <libcamera/geometry.h>
+#include <libcamera/formats.h>
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/egl.h"
+#include "libcamera/internal/software_isp/debayer_params.h"
+
+#include "gpu_pipeline_shader_pass_blc_normalise.h"
+
+/**
+ * \file software_isp.cpp
+ * \brief Simple software ISP implementation
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(GpuShaderBlc)
+
+int GpuIspShaderPassBlcNormalise::start()
+{
+	return 0;
+}
+
+void GpuIspShaderPassBlcNormalise::stop()
+{
+}
+
+int GpuIspShaderPassBlcNormalise::selectShaders(struct ShaderConfig &shaderCfg, [[maybe_unused]]PixelFormat &inputFormat, [[maybe_unused]]PixelFormat &outputFormat)
+{
+	/* Pixel location parameters */
+	glFormat_ = GL_LUMINANCE;
+	bytesPerPixel_ = 1;
+	shaderStridePixels_ = passInputCfg_.stride;
+
+	/* Shader selection */
+	switch (inputFormat) {
+	case libcamera::formats::SBGGR8:
+	case libcamera::formats::SGBRG8:
+	case libcamera::formats::SGRBG8:
+	case libcamera::formats::SRGGB8:
+		shaderCfg.fragmentShaderData = bayer_unpacked_to_blc_glr16f_frag;
+		shaderCfg.fragmentShaderDataLen = bayer_unpacked_to_blc_glr16f_frag_len;
+		shaderCfg.vertexShaderData = bayer_unpacked_vert;
+		shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
+		break;
+	case libcamera::formats::SBGGR10_CSI2P:
+	case libcamera::formats::SGBRG10_CSI2P:
+	case libcamera::formats::SGRBG10_CSI2P:
+	case libcamera::formats::SRGGB10_CSI2P:
+		egl_.pushEnv(shaderCfg.shaderEnv, "#define RAW10P");
+		if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {
+			shaderCfg.fragmentShaderData = bayer_unpacked_to_blc_glr16f_frag;
+			shaderCfg.fragmentShaderDataLen = bayer_unpacked_to_blc_glr16f_frag_len;
+			shaderCfg.vertexShaderData = bayer_unpacked_vert;
+			shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
+			glFormat_ = GL_RG;
+			bytesPerPixel_ = 2;
+		} else {
+			shaderCfg.fragmentShaderData = bayer_1x_packed_to_blc_glr16f_frag;
+			shaderCfg.fragmentShaderDataLen = bayer_1x_packed_to_blc_glr16f_frag_len;
+			shaderCfg.vertexShaderData = identity_vert;
+			shaderCfg.vertexShaderDataLen = identity_vert_len;
+			shaderStridePixels_ = passInputCfg_.size.width;
+		}
+		break;
+	case libcamera::formats::SBGGR12_CSI2P:
+	case libcamera::formats::SGBRG12_CSI2P:
+	case libcamera::formats::SGRBG12_CSI2P:
+	case libcamera::formats::SRGGB12_CSI2P:
+		egl_.pushEnv(shaderCfg.shaderEnv, "#define RAW12P");
+		if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {
+			shaderCfg.fragmentShaderData = bayer_unpacked_to_blc_glr16f_frag;
+			shaderCfg.fragmentShaderDataLen = bayer_unpacked_to_blc_glr16f_frag_len;
+			shaderCfg.vertexShaderData = bayer_unpacked_vert;
+			shaderCfg.vertexShaderDataLen = bayer_unpacked_vert_len;
+			glFormat_ = GL_RG;
+			bytesPerPixel_ = 2;
+		} else {
+			shaderCfg.fragmentShaderData = bayer_1x_packed_to_blc_glr16f_frag;
+			shaderCfg.fragmentShaderDataLen = bayer_1x_packed_to_blc_glr16f_frag_len;
+			shaderCfg.vertexShaderData = identity_vert;
+			shaderCfg.vertexShaderDataLen = identity_vert_len;
+			shaderStridePixels_ = passInputCfg_.size.width;
+		}
+		break;
+	};
+
+	return 0;
+}
+
+int GpuIspShaderPassBlcNormalise::getShaderVariableLocations(void)
+{
+	attributeVertex_ = glGetAttribLocation(programId_, "vertexIn");
+	attributeTexture_ = glGetAttribLocation(programId_, "textureIn");
+	textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y");
+	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");
+
+	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 */
+	return 0;
+}
+
+void GpuIspShaderPassBlcNormalise::setShaderVariableValues(const DebayerParams &params, eGLImage &eglImageIn)
+{
+	/*
+	 * Raw Bayer 8-bit, and packed raw Bayer 10-bit/12-bit formats
+	 * are stored in a GL_LUMINANCE texture. The texture width is
+	 * equal to the stride.
+	 */
+	GLfloat firstRed[] = { firstRed_x_, firstRed_y_ };
+	GLfloat imgSize[] = { (GLfloat)passInputCfg_.size.width,
+			      (GLfloat)passInputCfg_.size.height };
+	GLfloat Step[] = { static_cast<float>(bytesPerPixel_) / (passInputCfg_.stride - 1),
+			   1.0f / (passInputCfg_.size.height - 1) };
+	GLfloat Stride = (GLfloat)passInputCfg_.size.width / (shaderStridePixels_ / bytesPerPixel_);
+	/*
+	 * Scale input to output size, keeping the aspect ratio and preferring
+	 * cropping over black bars.
+	 */
+	GLfloat scale = std::max((GLfloat)passInputCfg_.window.width / passInputCfg_.size.width,
+				 (GLfloat)passInputCfg_.window.height / passInputCfg_.size.height);
+	GLfloat trans = -(1.0f - scale);
+	GLfloat projMatrix[] = {
+		scale, 0, 0, 0,
+		0, scale, 0, 0,
+		0, 0, 1, 0,
+		trans, trans, 0, 1
+	};
+	/* Static const coordinates */
+	static const GLfloat vcoordinates[4][2] = {
+		{ -1.0f, -1.0f },
+		{ -1.0f, +1.0f },
+		{ +1.0f, +1.0f },
+		{ +1.0f, -1.0f },
+	};
+	static const GLfloat tcoordinates[4][2] = {
+		{ 0.0f, 0.0f },
+		{ 0.0f, 1.0f },
+		{ 1.0f, 1.0f },
+		{ 1.0f, 0.0f },
+	};
+
+	/* vertexIn - bayer_8.vert */
+	glEnableVertexAttribArray(attributeVertex_);
+	glVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE,
+			      2 * sizeof(GLfloat), vcoordinates);
+
+	/* textureIn - bayer_8.vert */
+	glEnableVertexAttribArray(attributeTexture_);
+	glVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE,
+			      2 * sizeof(GLfloat), tcoordinates);
+
+	/*
+	 * Set the sampler2D to the respective texture unit for each texutre
+	 * To simultaneously sample multiple textures we need to use multiple
+	 * texture units
+	 */
+	glUniform1i(textureUniformBayerDataIn_, eglImageIn.texture_unit_uniform_id_);
+
+	/*
+	 * 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);
+	glUniform2fv(textureUniformStep_, 1, Step);
+	glUniform1f(textureUniformStrideFactor_, Stride);
+	glUniformMatrix4fv(textureUniformProjMatrix_, 1, GL_FALSE, projMatrix);
+
+	LOG(GpuShaderBlc, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
+			    << " tex_y " << textureUniformBayerDataIn_
+			    << " tex_step " << textureUniformStep_
+			    << " tex_size " << textureUniformSize_
+			    << " stride_factor " << textureUniformStrideFactor_
+			    << " tex_bayer_first_red " << textureUniformBayerFirstRed_;
+
+	LOG(GpuShaderBlc, Debug) << "textureUniformY_ = 0 "
+			    << " firstRed.x " << firstRed[0]
+			    << " firstRed.y " << firstRed[1]
+			    << " textureUniformSize_.width " << imgSize[0]
+			    << " textureUniformSize_.height " << imgSize[1]
+			    << " textureUniformStep_.x " << Step[0]
+			    << " textureUniformStep_.y " << Step[1]
+			    << " textureUniformStrideFactor_ " << Stride
+			    << " textureUniformProjMatrix_ " << textureUniformProjMatrix_;
+
+	/*
+	 * 0 = Red, 1 = Green, 2 = Blue
+	 */
+	glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]);
+	LOG(GpuShaderBlc, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel;
+
+	return;
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.h b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.h
new file mode 100644
index 000000000..64ea2fc11
--- /dev/null
+++ b/src/libcamera/software_isp/gpu_pipeline_shader_pass_blc_normalise.h
@@ -0,0 +1,55 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026, Linaro Ltd
+ *
+ * Authors:
+ * Bryan O'Donoghue <bryan.odonoghue@linaro.org>
+ *
+ * GpuIspPipelineShaderPass base class
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/signal.h>
+
+#include <libcamera/geometry.h>
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/egl.h"
+#include "libcamera/internal/software_isp/debayer_params.h"
+
+#include "gpu_pipeline_shader_pass.h"
+
+namespace libcamera {
+
+class FrameBuffer;
+
+class GpuIspShaderPassBlcNormalise : public GpuIspShaderPass
+{
+public:
+	GpuIspShaderPassBlcNormalise(eGL& egl) : GpuIspShaderPass(egl) {};
+
+	int start();
+	void stop();
+
+	/* Things that every ISP pipeline pass will need to do */
+	int getShaderVariableLocations(void);
+	void setShaderVariableValues(const DebayerParams &params, eGLImage &eglImageIn);
+	int selectShaders(struct ShaderConfig &shaderCfg, PixelFormat &inputFormat, PixelFormat &outputFormat);
+	const char *name() const override { return "GpuIspShaderPassBlcNormalise"; }
+
+private:
+	/* Shader parameters */
+	GLint textureUniformStep_;
+	GLint textureUniformSize_;
+
+	/* Black Level compensation */
+	GLint blackLevelUniformDataIn_;
+
+};
+
+} /* namespace libcamera */
diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
index 3e7d21318..e9caa3629 100644
--- a/src/libcamera/software_isp/meson.build
+++ b/src/libcamera/software_isp/meson.build
@@ -34,6 +34,7 @@  if mesa_works
         'software_isp_pipeline_gpu.cpp',
         'gpu_pipeline_shader_pass.cpp',
         'gpu_pipeline_shader_pass_demosiac.cpp',
+	'gpu_pipeline_shader_pass_blc_normalise.cpp',
     ])
     libcamera_deps += [
         libegl,