[v2] libcamera: software_isp: Add R10 and R10_CSI2P monochrome formats
diff mbox series

Message ID 20261003155838.91284-1-alain.cousinie@laposte.net
State New
Headers show
Series
  • [v2] libcamera: software_isp: Add R10 and R10_CSI2P monochrome formats
Related show

Commit Message

Alain Cousinié Oct. 3, 2026, 3:58 p.m. UTC
Implement support for R10 and R10_CSI2P monochrome packed and
unpacked formats in the software ISP and shaders.

Signed-off-by: Alain Cousinié <alain.cousinie@laposte.net>
---
This patch was tested on an HP 14-eu0xxx laptop with the og0va1b camera module (testing on og0ve1b is currently in progress) using qcam.

 src/libcamera/shaders/meson.build             |  2 +
 src/libcamera/shaders/raw_mono_1x_packed.frag | 50 +++++++++++++++++++
 src/libcamera/shaders/raw_mono_unpacked.frag  | 47 +++++++++++++++++
 src/libcamera/software_isp/debayer_egl.cpp    | 42 ++++++++++++++++
 4 files changed, 141 insertions(+)
 create mode 100644 src/libcamera/shaders/raw_mono_1x_packed.frag
 create mode 100644 src/libcamera/shaders/raw_mono_unpacked.frag
---
--
2.55.0

Patch
diff mbox series

diff --git a/src/libcamera/shaders/meson.build b/src/libcamera/shaders/meson.build
index c409ff9b0..546053051 100644
--- a/src/libcamera/shaders/meson.build
+++ b/src/libcamera/shaders/meson.build
@@ -7,6 +7,8 @@  shader_files = files([
     'bayer_unpacked.frag',
     'bayer_unpacked.vert',
     'identity.vert',
+    'raw_mono_1x_packed.frag',
+    'raw_mono_unpacked.frag',
 ])

 # Generate header from shaders
diff --git a/src/libcamera/shaders/raw_mono_1x_packed.frag b/src/libcamera/shaders/raw_mono_1x_packed.frag
new file mode 100644
index 000000000..983834cc8
--- /dev/null
+++ b/src/libcamera/shaders/raw_mono_1x_packed.frag
@@ -0,0 +1,50 @@ 
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2026, Alain Cousinié
+ * raw_mono_1x_packed.frag - Fragment shader for packed monochrome formats (R10_CSI2P)
+ */
+
+#ifdef GL_ES
+precision highp float;
+#endif
+
+#define BPP		1.25
+
+varying vec2 textureOut;
+
+uniform vec2 tex_size;
+uniform sampler2D tex_y;
+uniform float gamma;
+uniform float contrastExp;
+
+float apply_contrast(float value)
+{
+	if (value < 0.5)
+		return 0.5 * pow(value / 0.5, contrastExp);
+	else
+		return 1.0 - 0.5 * pow((1.0 - value) / 0.5, contrastExp);
+}
+
+void main(void)
+{
+	vec2 pixel_coords = floor(textureOut * tex_size);
+
+	vec2 center_pixel = pixel_coords + vec2(0.5);
+	vec2 center_bytes;
+	center_bytes.y = center_pixel.y;
+	center_bytes.x = floor(BPP * center_pixel.x);
+
+	center_bytes = center_bytes / tex_size;
+
+	/* Linear sampling: GPU merges byte transitions. */
+	float C = texture2D(tex_y, center_bytes).r;
+	vec3 rgb = vec3(C, C, C);
+
+	rgb = clamp(rgb, 0.0, 1.0);
+	rgb.r = apply_contrast(rgb.r);
+	rgb.g = apply_contrast(rgb.g);
+	rgb.b = apply_contrast(rgb.b);
+	rgb = pow(rgb, vec3(gamma));
+
+	gl_FragColor = vec4(rgb, 1.0);
+}
diff --git a/src/libcamera/shaders/raw_mono_unpacked.frag b/src/libcamera/shaders/raw_mono_unpacked.frag
new file mode 100644
index 000000000..f8a0c14ae
--- /dev/null
+++ b/src/libcamera/shaders/raw_mono_unpacked.frag
@@ -0,0 +1,47 @@ 
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2026, Alain Cousinié
+ * raw_mono_unpacked.frag - Fragment shader for raw unpacked monochrome formats (R10)
+ */
+
+#extension GL_EXT_texture_rg : enable
+
+#ifdef GL_ES
+precision highp float;
+#endif
+
+varying vec2 textureOut;
+
+uniform sampler2D tex_y;
+uniform float gamma;
+uniform float contrastExp;
+
+float apply_contrast(float value)
+{
+	if (value < 0.5)
+		return 0.5 * pow(value / 0.5, contrastExp);
+	else
+		return 1.0 - 0.5 * pow((1.0 - value) / 0.5, contrastExp);
+}
+
+void main(void)
+{
+	/* GL_RG format sampling (16 bits):
+	 * The .r component contains the 8 MSBs,
+	 * The .g component contains the remaining LSBs.
+	 */
+	vec4 texel = texture2D(tex_y, textureOut);
+	float C = texel.r + texel.g / 256.0;
+
+	vec3 rgb = vec3(C, C, C);
+
+	/* Generic rendering processing */
+	rgb = clamp(rgb, 0.0, 1.0);
+	rgb.r = apply_contrast(rgb.r);
+	rgb.g = apply_contrast(rgb.g);
+	rgb.b = apply_contrast(rgb.b);
+	rgb = pow(rgb, vec3(gamma));
+
+	gl_FragColor = vec4(rgb, 1.0);
+}
+
diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
index 300822f9d..2a57fb7d4 100644
--- a/src/libcamera/software_isp/debayer_egl.cpp
+++ b/src/libcamera/software_isp/debayer_egl.cpp
@@ -61,6 +61,17 @@  int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
 						   formats::XBGR8888,
 						   formats::ABGR8888 };

+	/* Handle Monochrome case using bayerFormat */
+	if (bayerFormat.order == BayerFormat::Order::MONO) {
+		bool isPacked = (bayerFormat.packing == BayerFormat::Packing::CSI2);
+
+		config.bpp = bayerFormat.bitDepth; /* Dynamic (10 bits for this sensor) */
+		config.patternSize.width = isPacked ? 4 : 1;
+		config.patternSize.height = 1; /* No 2x2 pattern in monochrome */
+		config.outputFormats = outputFormats;
+		return 0;
+	}
+
 	if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10) &&
 	    bayerFormat.packing == BayerFormat::Packing::None &&
 	    isStandardBayerOrder(bayerFormat.order)) {
@@ -166,6 +177,11 @@  int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm
 	shaderStridePixels_ = inputConfig_.stride;

 	switch (inputFormat) {
+	/* Monochrome cases: No color phase, proper initialization to 0 for R10 and R10_CSI2P */
+	case libcamera::formats::R10:
+		firstRed_x_ = 0.0;
+		firstRed_y_ = 0.0;
+		break;
 	case libcamera::formats::SBGGR8:
 	case libcamera::formats::SBGGR10_CSI2P:
 	case libcamera::formats::SBGGR12_CSI2P:
@@ -197,6 +213,25 @@  int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm

 	/* Shader selection */
 	switch (inputFormat) {
+	case libcamera::formats::R10: {
+		/* Raw 10-bit Monochrome Case (Handles both Packed R10_CSI2P and Unpacked R10 sharing the same value) */
+		BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat);
+
+		if (bayerFormat.packing == BayerFormat::Packing::None) {
+			fragmentShaderData = raw_mono_unpacked_frag;
+			vertexShaderData = bayer_unpacked_vert;
+			glFormat_ = GL_RG;
+			bytesPerPixel_ = 2;
+		} else {
+			fragmentShaderData = raw_mono_1x_packed_frag;
+			vertexShaderData = identity_vert;
+			/* Use configured input stride instead of theoretical width */
+			glFormat_ = GL_LUMINANCE;
+			bytesPerPixel_ = 1;
+			shaderStridePixels_ = inputConfig_.stride;
+		}
+		break;
+	}
 	case libcamera::formats::SBGGR8:
 	case libcamera::formats::SGBRG8:
 	case libcamera::formats::SGRBG8:
@@ -516,6 +551,13 @@  eGLImage *DebayerEGL::getCachedInputFrameBuffer(FrameBuffer *input, std::optiona
 		eglImageInCache_.emplace_back(fd, std::make_unique<eGLImage>(glFormat_, inputConfig_.stride / bytesPerPixel_, height_, inputConfig_.stride, GL_TEXTURE0, 0));
 		eglImageIn = eglImageInCache_.back().second.get();

+		/* Force GL_NEAREST to preserve LSBs of packed monochrome format */
+		BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputPixelFormat_);
+		if (bayerFormat.order == BayerFormat::Order::MONO && bayerFormat.packing == BayerFormat::Packing::CSI2) {
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		}
+
 		if (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) == 0)
 			return eglImageIn;