@@ -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
new file mode 100644
@@ -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);
+}
new file mode 100644
@@ -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);
+}
+
@@ -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;
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