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

Message ID cb56172b-0808-4515-8de3-5f3ef9da12e9@laposte.net
State New
Headers show
Series
  • libcamera: software_isp: Add R10 and R10_CSI2P monochrome formats
Related show

Commit Message

Alain Cousinié Oct. 3, 2026, 3:45 p.m. UTC
From 051ad3d6cf7bad93b9b03842674aa0c0b34b8a45 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alain=20Cousini=C3=A9?= <alain.cousinie@laposte.net>
Date: Thu, 1 Oct 2026 16:31:05 +0200
Subject: [PATCH] libcamera: software_isp: Add R10 and R10_CSI2P monochrome
  formats
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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

      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;

--
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;
+    }
+