[v7,4/5] libcamera: software_isp: Add support for monochrome formats - GPU
diff mbox series

Message ID 20261026082602.60417-5-alain.cousinie@laposte.net
State New
Headers show
Series
  • libcamera: software_isp: Add support for raw monochrome formats
Related show

Commit Message

Alain Cousinie Oct. 26, 2026, 8:26 a.m. UTC
Signed-off-by: Alain CousiniƩ <alain.cousinie@laposte.net>
---
 src/libcamera/software_isp/debayer_egl.cpp | 159 +++++++++++++++++++--
 src/libcamera/software_isp/debayer_egl.h   |   2 +
 2 files changed, 149 insertions(+), 12 deletions(-)

--
2.55.0

Comments

Milan Zamazal Oct. 26, 2026, 5:48 p.m. UTC | #1
alain.cousinie@laposte.net writes:

> Signed-off-by: Alain CousiniƩ <alain.cousinie@laposte.net>
> ---
>  src/libcamera/software_isp/debayer_egl.cpp | 159 +++++++++++++++++++--
>  src/libcamera/software_isp/debayer_egl.h   |   2 +
>  2 files changed, 149 insertions(+), 12 deletions(-)
>
> diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
> index 33933b11d..4afcbbb33 100644
> --- a/src/libcamera/software_isp/debayer_egl.cpp
> +++ b/src/libcamera/software_isp/debayer_egl.cpp
> @@ -61,6 +61,21 @@ int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
>  						   formats::XBGR8888,
>  						   formats::ABGR8888 };
>
> +	if (bayerFormat.order == BayerFormat::Order::MONO) {
> +		if (bayerFormat.packing == BayerFormat::Packing::None) {
> +			config.bpp = (bayerFormat.bitDepth + 7) & ~7;
> +			config.patternSize.width = 2;
> +			config.patternSize.height = 2;

Still not explained anywhere why we need these patternSize settings for mono.

> +		} else if (bayerFormat.packing == BayerFormat::Packing::CSI2) {
> +			config.bpp = bayerFormat.bitDepth;
> +			config.patternSize.width = (bayerFormat.bitDepth == 12) ? 2 : 4;
> +			config.patternSize.height = 2;
> +		}
> +
> +		config.outputFormats = outputFormats;
> +		return 0;
> +	}
> +
>  	if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10) &&
>  	    bayerFormat.packing == BayerFormat::Packing::None &&
>  	    isStandardBayerOrder(bayerFormat.order)) {
> @@ -172,6 +187,17 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm
>  	shaderStridePixels_ = inputConfig_.stride;
>
>  	switch (inputFormat) {
> +	case libcamera::formats::R8:
> +	case libcamera::formats::R10:
> +	case libcamera::formats::R12:
> +	/*
> +	 * \todo: Add full conversion testing for R14 once the format is upstreamed
> +	case libcamera::formats::R14:
> +	 */

I'm not sure the maintainers will like those commented out parts.
`#if 0' might be better but I cannot speak for them.

> +	case libcamera::formats::R16:
> +		firstRed_x_ = 0.0;
> +		firstRed_y_ = 0.0;
> +		break;
>  	case libcamera::formats::SBGGR8:
>  	case libcamera::formats::SBGGR10_CSI2P:
>  	case libcamera::formats::SBGGR12_CSI2P:
> @@ -203,6 +229,103 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm
>
>  	/* Shader selection */
>  	switch (inputFormat) {
> +	case libcamera::formats::R8:
> +		egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 255.0)");
> +		egl_.pushEnv(shaderEnv, "#define RAW8");
> +		fragmentShaderData = mono_unpacked_frag;
> +		vertexShaderData = mono_vert;
> +		glFormat_ = GL_LUMINANCE;
> +		bytesPerPixel_ = 1;
> +		break;
> +	case libcamera::formats::R10: {
> +		BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat);
> +
> +		egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 1023.0)");
> +		if (bayerFormat.packing == BayerFormat::Packing::None) {
> +			egl_.pushEnv(shaderEnv, "#define RAW10");
> +			fragmentShaderData = mono_unpacked_frag;
> +			vertexShaderData = mono_vert;
> +			glFormat_ = GL_RG;
> +			bytesPerPixel_ = 2;
> +		} else {
> +			egl_.pushEnv(shaderEnv, "#define BLOCK_PIXELS 4.0");
> +			egl_.pushEnv(shaderEnv, "#define BLOCK_BYTES 5.0");
> +			egl_.pushEnv(shaderEnv, "#define LSB_SIZE 2.0");
> +			egl_.pushEnv(shaderEnv, "#define LSB_MASK_VAL 4.0");

LSB_MASK_VAL == 2 ^ LSB_SIZE -- is it possible to compute the value in
the shader, once?

> +			egl_.pushEnv(shaderEnv, (std::string("#define TEX_WIDTH ") + std::to_string(inputConfig_.stride) + ".0").c_str());
> +			egl_.pushEnv(shaderEnv, (std::string("#define TEX_HEIGHT ") + std::to_string(height_) + ".0").c_str());
> +
> +			fragmentShaderData = mono_1x_packed_frag;
> +			vertexShaderData = mono_vert;
> +			glFormat_ = GL_LUMINANCE;
> +			bytesPerPixel_ = 1;
> +			shaderStridePixels_ = inputConfig_.stride;

Is this correct, regarding the computations performed in mono.vert?  The
debayer shaders use `width_'.

> +		}
> +		break;
> +	}
> +	case libcamera::formats::R12: {
> +		BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat);
> +
> +		egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 4095.0)");
> +		if (bayerFormat.packing == BayerFormat::Packing::None) {
> +			egl_.pushEnv(shaderEnv, "#define RAW12");
> +			fragmentShaderData = mono_unpacked_frag;
> +			vertexShaderData = mono_vert;
> +			glFormat_ = GL_RG;
> +			bytesPerPixel_ = 2;
> +		} else {
> +			egl_.pushEnv(shaderEnv, "#define BLOCK_PIXELS 2.0");
> +			egl_.pushEnv(shaderEnv, "#define BLOCK_BYTES 3.0");
> +			egl_.pushEnv(shaderEnv, "#define LSB_SIZE 4.0");
> +			egl_.pushEnv(shaderEnv, "#define LSB_MASK_VAL 16.0");
> +			egl_.pushEnv(shaderEnv, (std::string("#define TEX_WIDTH ") + std::to_string(inputConfig_.stride) + ".0").c_str());
> +			egl_.pushEnv(shaderEnv, (std::string("#define TEX_HEIGHT ") + std::to_string(height_) + ".0").c_str());
> +
> +			fragmentShaderData = mono_1x_packed_frag;
> +			vertexShaderData = mono_vert;
> +			glFormat_ = GL_LUMINANCE;
> +			bytesPerPixel_ = 1;
> +			shaderStridePixels_ = inputConfig_.stride;
> +		}
> +		break;
> +	}
> +	/*
> +	 * \todo: Add full conversion testing for R14 once the format is upstreamed
> +	case libcamera::formats::R14: {
> +		BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat);
> +
> +		egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 16383.0)");
> +		if (bayerFormat.packing == BayerFormat::Packing::None) {
> +			egl_.pushEnv(shaderEnv, "#define RAW14");
> +			fragmentShaderData = mono_unpacked_frag;
> +			vertexShaderData = mono_vert;
> +			glFormat_ = GL_RG;
> +			bytesPerPixel_ = 2;
> +		} else {
> +			egl_.pushEnv(shaderEnv, "#define BLOCK_PIXELS 4.0");
> +			egl_.pushEnv(shaderEnv, "#define BLOCK_BYTES 7.0");
> +			egl_.pushEnv(shaderEnv, "#define LSB_SIZE 6.0");
> +			egl_.pushEnv(shaderEnv, "#define LSB_MASK_VAL 64.0");
> +			egl_.pushEnv(shaderEnv, (std::string("#define TEX_WIDTH ") + std::to_string(inputConfig_.stride) + ".0").c_str());
> +			egl_.pushEnv(shaderEnv, (std::string("#define TEX_HEIGHT ") + std::to_string(height_) + ".0").c_str());
> +
> +			fragmentShaderData = mono_1x_packed_frag;
> +			vertexShaderData = mono_vert;
> +			glFormat_ = GL_LUMINANCE;
> +			bytesPerPixel_ = 1;
> +			shaderStridePixels_ = inputConfig_.stride;
> +		}
> +		break;
> +	}
> +	 */
> +	case libcamera::formats::R16:
> +		egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 65535.0)");
> +		egl_.pushEnv(shaderEnv, "#define RAW16");
> +		fragmentShaderData = mono_unpacked_frag;
> +		vertexShaderData = mono_vert;
> +		glFormat_ = GL_RG;
> +		bytesPerPixel_ = 2;
> +		break;
>  	case libcamera::formats::SBGGR8:
>  	case libcamera::formats::SGBRG8:
>  	case libcamera::formats::SGRBG8:
> @@ -279,7 +402,7 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm
>
>  int DebayerEGL::configure(const StreamConfiguration &inputCfg,
>  			  const std::vector<std::reference_wrapper<const StreamConfiguration>> &outputCfgs,
> -			  [[maybe_unused]] bool ccmEnabled,
> +			  bool ccmEnabled,
>  			  bool lscEnabled)
>  {
>  	if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0) {
> @@ -300,6 +423,7 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg,
>  		return -EINVAL;
>  	}
>
> +	ccmEnabled_ = ccmEnabled;
>  	lscEnabled_ = lscEnabled;
>
>  	inputConfig_.stride = inputCfg.stride;
> @@ -331,10 +455,17 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg,
>  	outputSize_ = outputCfg.size;
>  	nativeOutputSize_ = outSizeRange.max;
>
> -	window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) &
> -		    ~(inputConfig_.patternSize.width - 1);
> -	window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) &
> -		    ~(inputConfig_.patternSize.height - 1);
> +	/* Setup the window geometry according to the format type */
> +	BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
> +	if (bayerFormat.order == BayerFormat::Order::MONO) {
> +		window_.x = 0;
> +		window_.y = 0;
> +	} else {
> +		window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) &
> +			    ~(inputConfig_.patternSize.width - 1);
> +		window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) &
> +			    ~(inputConfig_.patternSize.height - 1);
> +	}
>  	window_.width = outputCfg.size.width;
>  	window_.height = outputCfg.size.height;
>
> @@ -474,9 +605,11 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara
>  			    << " textureUniformProjMatrix_ " << textureUniformProjMatrix_;
>
>  	/* Pre-transpose matrix for GLES 2.0. */
> -	Matrix<float, 3, 3> ccm = params.combinedMatrix.transpose();
> -	glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm.data().data());
> -	LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix;
> +	if (ccmEnabled_) {
> +		Matrix<float, 3, 3> ccm = params.combinedMatrix.transpose();
> +		glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm.data().data());
> +		LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix;
> +	}

What if using debayer shaders and Ccm algorithm is disabled?  We still
must set ccmUniformDataIn_, right?  params.combinedMatrix is an identity
matrix in such a case.

>
>  	if (lscEnabled_ && params.lscLutVersion != lscLutVersion_) {
>  		egl_.updateTexture2D(*eglImageLscLookup_, params.lscLut.data());
> @@ -723,12 +856,11 @@ void DebayerEGL::stop()
>  SizeRange DebayerEGL::sizes(PixelFormat inputFormat, const Size &inputSize)
>  {
>  	Size patternSize = this->patternSize(inputFormat);
> -	unsigned int borderHeight = patternSize.height;
> -
>  	if (patternSize.isNull())
>  		return {};
>
>  	/* No need for top/bottom border with a pattern height of 2 */
> +	unsigned int borderHeight = patternSize.height;
>  	if (patternSize.height == 2)
>  		borderHeight = 0;
>
> @@ -743,9 +875,12 @@ SizeRange DebayerEGL::sizes(PixelFormat inputFormat, const Size &inputSize)
>  		return {};
>  	}
>
> +	BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat);
> +	bool localIsMono = (bayerFormat.order == BayerFormat::Order::MONO);
> +
>  	return SizeRange(Size(patternSize.width, patternSize.height),
> -			 Size((inputSize.width - 2 * patternSize.width) & ~(patternSize.width - 1),
> -			      (inputSize.height - 2 * borderHeight) & ~(patternSize.height - 1)),
> +			 Size((inputSize.width - (localIsMono ? 0 : 2 * patternSize.width)) & ~(patternSize.width - 1),
> +			      (inputSize.height - (localIsMono ? 0 : 2 * borderHeight)) & ~(patternSize.height - 1)),
>  			 patternSize.width, patternSize.height);
>  }
>
> diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h
> index cbbc6a450..0db080016 100644
> --- a/src/libcamera/software_isp/debayer_egl.h
> +++ b/src/libcamera/software_isp/debayer_egl.h
> @@ -128,6 +128,8 @@ private:
>  	GLint glFormat_;
>  	unsigned int bytesPerPixel_;
>  	uint32_t shaderStridePixels_;
> +
> +	bool ccmEnabled_;
>  };
>
>  } /* namespace libcamera */
> --
> 2.55.0

Patch
diff mbox series

diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
index 33933b11d..4afcbbb33 100644
--- a/src/libcamera/software_isp/debayer_egl.cpp
+++ b/src/libcamera/software_isp/debayer_egl.cpp
@@ -61,6 +61,21 @@  int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
 						   formats::XBGR8888,
 						   formats::ABGR8888 };

+	if (bayerFormat.order == BayerFormat::Order::MONO) {
+		if (bayerFormat.packing == BayerFormat::Packing::None) {
+			config.bpp = (bayerFormat.bitDepth + 7) & ~7;
+			config.patternSize.width = 2;
+			config.patternSize.height = 2;
+		} else if (bayerFormat.packing == BayerFormat::Packing::CSI2) {
+			config.bpp = bayerFormat.bitDepth;
+			config.patternSize.width = (bayerFormat.bitDepth == 12) ? 2 : 4;
+			config.patternSize.height = 2;
+		}
+
+		config.outputFormats = outputFormats;
+		return 0;
+	}
+
 	if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10) &&
 	    bayerFormat.packing == BayerFormat::Packing::None &&
 	    isStandardBayerOrder(bayerFormat.order)) {
@@ -172,6 +187,17 @@  int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm
 	shaderStridePixels_ = inputConfig_.stride;

 	switch (inputFormat) {
+	case libcamera::formats::R8:
+	case libcamera::formats::R10:
+	case libcamera::formats::R12:
+	/*
+	 * \todo: Add full conversion testing for R14 once the format is upstreamed
+	case libcamera::formats::R14:
+	 */
+	case libcamera::formats::R16:
+		firstRed_x_ = 0.0;
+		firstRed_y_ = 0.0;
+		break;
 	case libcamera::formats::SBGGR8:
 	case libcamera::formats::SBGGR10_CSI2P:
 	case libcamera::formats::SBGGR12_CSI2P:
@@ -203,6 +229,103 @@  int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm

 	/* Shader selection */
 	switch (inputFormat) {
+	case libcamera::formats::R8:
+		egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 255.0)");
+		egl_.pushEnv(shaderEnv, "#define RAW8");
+		fragmentShaderData = mono_unpacked_frag;
+		vertexShaderData = mono_vert;
+		glFormat_ = GL_LUMINANCE;
+		bytesPerPixel_ = 1;
+		break;
+	case libcamera::formats::R10: {
+		BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat);
+
+		egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 1023.0)");
+		if (bayerFormat.packing == BayerFormat::Packing::None) {
+			egl_.pushEnv(shaderEnv, "#define RAW10");
+			fragmentShaderData = mono_unpacked_frag;
+			vertexShaderData = mono_vert;
+			glFormat_ = GL_RG;
+			bytesPerPixel_ = 2;
+		} else {
+			egl_.pushEnv(shaderEnv, "#define BLOCK_PIXELS 4.0");
+			egl_.pushEnv(shaderEnv, "#define BLOCK_BYTES 5.0");
+			egl_.pushEnv(shaderEnv, "#define LSB_SIZE 2.0");
+			egl_.pushEnv(shaderEnv, "#define LSB_MASK_VAL 4.0");
+			egl_.pushEnv(shaderEnv, (std::string("#define TEX_WIDTH ") + std::to_string(inputConfig_.stride) + ".0").c_str());
+			egl_.pushEnv(shaderEnv, (std::string("#define TEX_HEIGHT ") + std::to_string(height_) + ".0").c_str());
+
+			fragmentShaderData = mono_1x_packed_frag;
+			vertexShaderData = mono_vert;
+			glFormat_ = GL_LUMINANCE;
+			bytesPerPixel_ = 1;
+			shaderStridePixels_ = inputConfig_.stride;
+		}
+		break;
+	}
+	case libcamera::formats::R12: {
+		BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat);
+
+		egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 4095.0)");
+		if (bayerFormat.packing == BayerFormat::Packing::None) {
+			egl_.pushEnv(shaderEnv, "#define RAW12");
+			fragmentShaderData = mono_unpacked_frag;
+			vertexShaderData = mono_vert;
+			glFormat_ = GL_RG;
+			bytesPerPixel_ = 2;
+		} else {
+			egl_.pushEnv(shaderEnv, "#define BLOCK_PIXELS 2.0");
+			egl_.pushEnv(shaderEnv, "#define BLOCK_BYTES 3.0");
+			egl_.pushEnv(shaderEnv, "#define LSB_SIZE 4.0");
+			egl_.pushEnv(shaderEnv, "#define LSB_MASK_VAL 16.0");
+			egl_.pushEnv(shaderEnv, (std::string("#define TEX_WIDTH ") + std::to_string(inputConfig_.stride) + ".0").c_str());
+			egl_.pushEnv(shaderEnv, (std::string("#define TEX_HEIGHT ") + std::to_string(height_) + ".0").c_str());
+
+			fragmentShaderData = mono_1x_packed_frag;
+			vertexShaderData = mono_vert;
+			glFormat_ = GL_LUMINANCE;
+			bytesPerPixel_ = 1;
+			shaderStridePixels_ = inputConfig_.stride;
+		}
+		break;
+	}
+	/*
+	 * \todo: Add full conversion testing for R14 once the format is upstreamed
+	case libcamera::formats::R14: {
+		BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat);
+
+		egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 16383.0)");
+		if (bayerFormat.packing == BayerFormat::Packing::None) {
+			egl_.pushEnv(shaderEnv, "#define RAW14");
+			fragmentShaderData = mono_unpacked_frag;
+			vertexShaderData = mono_vert;
+			glFormat_ = GL_RG;
+			bytesPerPixel_ = 2;
+		} else {
+			egl_.pushEnv(shaderEnv, "#define BLOCK_PIXELS 4.0");
+			egl_.pushEnv(shaderEnv, "#define BLOCK_BYTES 7.0");
+			egl_.pushEnv(shaderEnv, "#define LSB_SIZE 6.0");
+			egl_.pushEnv(shaderEnv, "#define LSB_MASK_VAL 64.0");
+			egl_.pushEnv(shaderEnv, (std::string("#define TEX_WIDTH ") + std::to_string(inputConfig_.stride) + ".0").c_str());
+			egl_.pushEnv(shaderEnv, (std::string("#define TEX_HEIGHT ") + std::to_string(height_) + ".0").c_str());
+
+			fragmentShaderData = mono_1x_packed_frag;
+			vertexShaderData = mono_vert;
+			glFormat_ = GL_LUMINANCE;
+			bytesPerPixel_ = 1;
+			shaderStridePixels_ = inputConfig_.stride;
+		}
+		break;
+	}
+	 */
+	case libcamera::formats::R16:
+		egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 65535.0)");
+		egl_.pushEnv(shaderEnv, "#define RAW16");
+		fragmentShaderData = mono_unpacked_frag;
+		vertexShaderData = mono_vert;
+		glFormat_ = GL_RG;
+		bytesPerPixel_ = 2;
+		break;
 	case libcamera::formats::SBGGR8:
 	case libcamera::formats::SGBRG8:
 	case libcamera::formats::SGRBG8:
@@ -279,7 +402,7 @@  int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm

 int DebayerEGL::configure(const StreamConfiguration &inputCfg,
 			  const std::vector<std::reference_wrapper<const StreamConfiguration>> &outputCfgs,
-			  [[maybe_unused]] bool ccmEnabled,
+			  bool ccmEnabled,
 			  bool lscEnabled)
 {
 	if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0) {
@@ -300,6 +423,7 @@  int DebayerEGL::configure(const StreamConfiguration &inputCfg,
 		return -EINVAL;
 	}

+	ccmEnabled_ = ccmEnabled;
 	lscEnabled_ = lscEnabled;

 	inputConfig_.stride = inputCfg.stride;
@@ -331,10 +455,17 @@  int DebayerEGL::configure(const StreamConfiguration &inputCfg,
 	outputSize_ = outputCfg.size;
 	nativeOutputSize_ = outSizeRange.max;

-	window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) &
-		    ~(inputConfig_.patternSize.width - 1);
-	window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) &
-		    ~(inputConfig_.patternSize.height - 1);
+	/* Setup the window geometry according to the format type */
+	BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
+	if (bayerFormat.order == BayerFormat::Order::MONO) {
+		window_.x = 0;
+		window_.y = 0;
+	} else {
+		window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) &
+			    ~(inputConfig_.patternSize.width - 1);
+		window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) &
+			    ~(inputConfig_.patternSize.height - 1);
+	}
 	window_.width = outputCfg.size.width;
 	window_.height = outputCfg.size.height;

@@ -474,9 +605,11 @@  void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara
 			    << " textureUniformProjMatrix_ " << textureUniformProjMatrix_;

 	/* Pre-transpose matrix for GLES 2.0. */
-	Matrix<float, 3, 3> ccm = params.combinedMatrix.transpose();
-	glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm.data().data());
-	LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix;
+	if (ccmEnabled_) {
+		Matrix<float, 3, 3> ccm = params.combinedMatrix.transpose();
+		glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm.data().data());
+		LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix;
+	}

 	if (lscEnabled_ && params.lscLutVersion != lscLutVersion_) {
 		egl_.updateTexture2D(*eglImageLscLookup_, params.lscLut.data());
@@ -723,12 +856,11 @@  void DebayerEGL::stop()
 SizeRange DebayerEGL::sizes(PixelFormat inputFormat, const Size &inputSize)
 {
 	Size patternSize = this->patternSize(inputFormat);
-	unsigned int borderHeight = patternSize.height;
-
 	if (patternSize.isNull())
 		return {};

 	/* No need for top/bottom border with a pattern height of 2 */
+	unsigned int borderHeight = patternSize.height;
 	if (patternSize.height == 2)
 		borderHeight = 0;

@@ -743,9 +875,12 @@  SizeRange DebayerEGL::sizes(PixelFormat inputFormat, const Size &inputSize)
 		return {};
 	}

+	BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat);
+	bool localIsMono = (bayerFormat.order == BayerFormat::Order::MONO);
+
 	return SizeRange(Size(patternSize.width, patternSize.height),
-			 Size((inputSize.width - 2 * patternSize.width) & ~(patternSize.width - 1),
-			      (inputSize.height - 2 * borderHeight) & ~(patternSize.height - 1)),
+			 Size((inputSize.width - (localIsMono ? 0 : 2 * patternSize.width)) & ~(patternSize.width - 1),
+			      (inputSize.height - (localIsMono ? 0 : 2 * borderHeight)) & ~(patternSize.height - 1)),
 			 patternSize.width, patternSize.height);
 }

diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h
index cbbc6a450..0db080016 100644
--- a/src/libcamera/software_isp/debayer_egl.h
+++ b/src/libcamera/software_isp/debayer_egl.h
@@ -128,6 +128,8 @@  private:
 	GLint glFormat_;
 	unsigned int bytesPerPixel_;
 	uint32_t shaderStridePixels_;
+
+	bool ccmEnabled_;
 };

 } /* namespace libcamera */