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

Message ID 20261026082602.60417-2-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:25 a.m. UTC
Signed-off-by: Alain Cousinié <alain.cousinie@laposte.net>
---
 src/libcamera/shaders/meson.build         |  3 +
 src/libcamera/shaders/mono.vert           | 25 ++++++++
 src/libcamera/shaders/mono_1x_packed.frag | 69 +++++++++++++++++++++++
 src/libcamera/shaders/mono_unpacked.frag  | 54 ++++++++++++++++++
 4 files changed, 151 insertions(+)
 create mode 100644 src/libcamera/shaders/mono.vert
 create mode 100644 src/libcamera/shaders/mono_1x_packed.frag
 create mode 100644 src/libcamera/shaders/mono_unpacked.frag

--
2.55.0

Comments

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

> Signed-off-by: Alain Cousinié <alain.cousinie@laposte.net>

There is usually something worth to say in the commit message, to help
reviewers and future generations understand the change.

> ---
>  src/libcamera/shaders/meson.build         |  3 +
>  src/libcamera/shaders/mono.vert           | 25 ++++++++
>  src/libcamera/shaders/mono_1x_packed.frag | 69 +++++++++++++++++++++++
>  src/libcamera/shaders/mono_unpacked.frag  | 54 ++++++++++++++++++
>  4 files changed, 151 insertions(+)
>  create mode 100644 src/libcamera/shaders/mono.vert
>  create mode 100644 src/libcamera/shaders/mono_1x_packed.frag
>  create mode 100644 src/libcamera/shaders/mono_unpacked.frag
>
> diff --git a/src/libcamera/shaders/meson.build b/src/libcamera/shaders/meson.build
> index c409ff9b0..80b127d3d 100644
> --- a/src/libcamera/shaders/meson.build
> +++ b/src/libcamera/shaders/meson.build
> @@ -7,6 +7,9 @@ shader_files = files([
>      'bayer_unpacked.frag',
>      'bayer_unpacked.vert',
>      'identity.vert',
> +    'mono_1x_packed.frag',
> +    'mono_unpacked.frag',
> +    'mono.vert',

As already mentioned in my reply to v6 discussion, I'm still not
convinced mono.vert should be introduced rather than the same change
applied to identity.vert + bayer_packed.frag.  If I'm mistaken, please
explain in the commit message.

>  ])
>
>  # Generate header from shaders
> diff --git a/src/libcamera/shaders/mono.vert b/src/libcamera/shaders/mono.vert
> new file mode 100644
> index 000000000..474ef462c
> --- /dev/null
> +++ b/src/libcamera/shaders/mono.vert
> @@ -0,0 +1,25 @@
> +/* SPDX-License-Identifier: BSD-2-Clause */
> +/*
> + * Copyright (C) 2026, Alain Cousinié
> + *
> + * Optimized vertex shader for MIPI CSI-2 packed monochrome formats
> + * Pre-calculates exact pixel coordinates to reduce Fragment Shader overhead
> + */
> +
> +attribute vec4 vertexIn;
> +attribute vec2 textureIn;
> +
> +varying vec2 textureOut;
> +varying vec2 v_pixelCoords;
> +
> +uniform mat4 proj_matrix;
> +uniform float stride_factor;
> +uniform vec2 tex_size;
> +
> +void main(void)
> +{
> +	gl_Position = proj_matrix * vertexIn;
> +
> +	textureOut = vec2(textureIn.x * stride_factor, textureIn.y);
> +	v_pixelCoords = textureOut * tex_size;

Would non-mono debayering benefit from this change too?

> +}
> diff --git a/src/libcamera/shaders/mono_1x_packed.frag b/src/libcamera/shaders/mono_1x_packed.frag
> new file mode 100644
> index 000000000..d23c8be8d
> --- /dev/null
> +++ b/src/libcamera/shaders/mono_1x_packed.frag
> @@ -0,0 +1,69 @@
> +/* SPDX-License-Identifier: BSD-2-Clause */
> +/*
> + * Copyright (C) 2026, Alain Cousinié
> + * Unified Fragment shader for MIPI CSI-2 packed monochrome formats (R10P / R12P)
> + */
> +
> +#ifdef GL_ES
> +#ifdef GL_FRAGMENT_PRECISION_HIGH
> +/* High precision prevents float rounding errors during bit-shifting math. */

Is it mono specific or would the other shaders benefit from this change too?

> +precision highp float;
> +#else
> +precision mediump float;
> +#endif
> +#endif
> +
> +varying vec2            textureOut;

Unused here.

> +varying vec2            v_pixelCoords;
> +
> +uniform sampler2D       tex_y;
> +uniform float           gamma;
> +uniform float           contrastExp;
> +uniform vec3            blacklevel;
> +
> +#define INV_TEX_WIDTH  (1.0 / TEX_WIDTH)
> +#define INV_TEX_HEIGHT (1.0 / TEX_HEIGHT)
> +
> +void main(void)
> +{
> +	float px = floor(v_pixelCoords.x);
> +	float py = floor(v_pixelCoords.y);
> +
> +	/* Compute absolute macro-block start byte and local pixel index. */
> +	float blockStart  = floor(px / BLOCK_PIXELS) * BLOCK_BYTES;

Well, these and other constants are not defined yet but no problem as
the shaders are not used yet.

> +	float idx         = px - (blockStart * (BLOCK_PIXELS / BLOCK_BYTES));
> +
> +	/* Target the center of the dedicated MSB texel. */
> +	float xMSB        = (blockStart + idx + 0.5) * INV_TEX_WIDTH;
> +
> +	/* Target the shared trailing byte containing the LSBs (1/8bits = 0.125) . */
> +	float lsbOffset   = BLOCK_PIXELS + floor(idx * LSB_SIZE * 0.125);
> +	float xLSB        = (blockStart + lsbOffset + 0.5) * INV_TEX_WIDTH;
> +	float normY       = (py + 0.5) * INV_TEX_HEIGHT;
> +
> +	float byteHigh = floor(texture2D(tex_y, vec2(xMSB, normY)).r * 255.0 + 0.5);
> +	float byteLow  = floor(texture2D(tex_y, vec2(xLSB, normY)).r * 255.0 + 0.5);
> +
> +	/* Isolate the pixel's LSB bits inside the shared byte */
> +	float totalBitsPos   = idx * LSB_SIZE;
> +	float bitStartInByte = totalBitsPos - (floor(totalBitsPos * 0.125) * 8.0);
> +
> +	float divisionFactor = pow(2.0, bitStartInByte);
> +	float shiftedLsb     = floor(byteLow / divisionFactor);
> +	float lsbBits        = shiftedLsb - (floor(shiftedLsb / LSB_MASK_VAL) * LSB_MASK_VAL);
> +
> +	/* Reconstruct full native intensity value. */
> +	float rawValue = (byteHigh * LSB_MASK_VAL) + lsbBits;
> +
> +	float normalizedIntensity = rawValue * INV_MAX_VAL;
> +	normalizedIntensity = clamp(normalizedIntensity - blacklevel.g, 0.0, 1.0);
> +
> +	/* Apply S-Curve contrast stretching. */
> +	float centerDist = 1.0 - abs(2 * normalizedIntensity - 1.0);
> +	float singlePow  = 0.5 * pow(centerDist, contrastExp);
> +	normalizedIntensity = mix(singlePow, 1.0 - singlePow, step(0.5, normalizedIntensity));
> +
> +	/* Apply gamma correction and output. */
> +	vec3 rgb = pow(vec3(normalizedIntensity), vec3(gamma));
> +	gl_FragColor = vec4(rgb, 1.0);
> +}
> diff --git a/src/libcamera/shaders/mono_unpacked.frag b/src/libcamera/shaders/mono_unpacked.frag
> new file mode 100644
> index 000000000..38051a5bb
> --- /dev/null
> +++ b/src/libcamera/shaders/mono_unpacked.frag
> @@ -0,0 +1,54 @@
> +/* SPDX-License-Identifier: BSD-2-Clause */
> +/*
> + * Copyright (C) 2026, Alain Cousinié
> + * Unified Fragment shader for unpacked monochrome formats (R8, R10, R12, R16)
> + */
> +
> +/* Required for 2-channel GL_RG texture support in OpenGL ES 2.0 (#version 100) */
> +#extension GL_EXT_texture_rg : enable
> +
> +#ifdef GL_ES
> +#ifdef GL_FRAGMENT_PRECISION_HIGH
> +/* High precision prevents float rounding errors during bit-shifting math. */
> +precision highp float;
> +#else
> +precision mediump float;
> +#endif
> +#endif
> +
> +varying vec2            textureOut;
> +
> +uniform sampler2D       tex_y;
> +uniform float           gamma;
> +uniform float           contrastExp;
> +uniform vec3            blacklevel;
> +
> +void main(void)
> +{
> +	float rawValue = 0.0;
> +
> +#ifdef RAW8
> +	/* 8-bit unpacked: single channel extraction. */
> +	rawValue = floor(texture2D(tex_y, textureOut).r * 255.0 + 0.5);
> +#else
> +	/* Multi-byte unpacked: merge Red (low byte) and Green (high byte). */
> +	vec4 texel = texture2D(tex_y, textureOut);
> +
> +	float byteLow  = floor(texel.r * 255.0 + 0.5);
> +	float byteHigh = floor(texel.g * 255.0 + 0.5);
> +
> +	rawValue = (byteHigh * 256.0) + byteLow;
> +#endif
> +
> +	float normalizedIntensity = rawValue * INV_MAX_VAL;
> +	normalizedIntensity = clamp(normalizedIntensity - blacklevel.g, 0.0, 1.0);
> +
> +	/* S-Curve contrast stretching. */
> +	float centerDist = 1.0 - abs(2.0 * normalizedIntensity - 1.0);
> +	float singlePow  = 0.5 * pow(centerDist, contrastExp);
> +	normalizedIntensity = mix(singlePow, 1.0 - singlePow, step(0.5, normalizedIntensity));
> +
> +	/* Gamma correction and output. */
> +	vec3 rgb = pow(vec3(normalizedIntensity), vec3(gamma));
> +	gl_FragColor = vec4(rgb, 1.0);
> +}
> --
> 2.55.0

Patch
diff mbox series

diff --git a/src/libcamera/shaders/meson.build b/src/libcamera/shaders/meson.build
index c409ff9b0..80b127d3d 100644
--- a/src/libcamera/shaders/meson.build
+++ b/src/libcamera/shaders/meson.build
@@ -7,6 +7,9 @@  shader_files = files([
     'bayer_unpacked.frag',
     'bayer_unpacked.vert',
     'identity.vert',
+    'mono_1x_packed.frag',
+    'mono_unpacked.frag',
+    'mono.vert',
 ])

 # Generate header from shaders
diff --git a/src/libcamera/shaders/mono.vert b/src/libcamera/shaders/mono.vert
new file mode 100644
index 000000000..474ef462c
--- /dev/null
+++ b/src/libcamera/shaders/mono.vert
@@ -0,0 +1,25 @@ 
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2026, Alain Cousinié
+ *
+ * Optimized vertex shader for MIPI CSI-2 packed monochrome formats
+ * Pre-calculates exact pixel coordinates to reduce Fragment Shader overhead
+ */
+
+attribute vec4 vertexIn;
+attribute vec2 textureIn;
+
+varying vec2 textureOut;
+varying vec2 v_pixelCoords;
+
+uniform mat4 proj_matrix;
+uniform float stride_factor;
+uniform vec2 tex_size;
+
+void main(void)
+{
+	gl_Position = proj_matrix * vertexIn;
+
+	textureOut = vec2(textureIn.x * stride_factor, textureIn.y);
+	v_pixelCoords = textureOut * tex_size;
+}
diff --git a/src/libcamera/shaders/mono_1x_packed.frag b/src/libcamera/shaders/mono_1x_packed.frag
new file mode 100644
index 000000000..d23c8be8d
--- /dev/null
+++ b/src/libcamera/shaders/mono_1x_packed.frag
@@ -0,0 +1,69 @@ 
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2026, Alain Cousinié
+ * Unified Fragment shader for MIPI CSI-2 packed monochrome formats (R10P / R12P)
+ */
+
+#ifdef GL_ES
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+/* High precision prevents float rounding errors during bit-shifting math. */
+precision highp float;
+#else
+precision mediump float;
+#endif
+#endif
+
+varying vec2            textureOut;
+varying vec2            v_pixelCoords;
+
+uniform sampler2D       tex_y;
+uniform float           gamma;
+uniform float           contrastExp;
+uniform vec3            blacklevel;
+
+#define INV_TEX_WIDTH  (1.0 / TEX_WIDTH)
+#define INV_TEX_HEIGHT (1.0 / TEX_HEIGHT)
+
+void main(void)
+{
+	float px = floor(v_pixelCoords.x);
+	float py = floor(v_pixelCoords.y);
+
+	/* Compute absolute macro-block start byte and local pixel index. */
+	float blockStart  = floor(px / BLOCK_PIXELS) * BLOCK_BYTES;
+	float idx         = px - (blockStart * (BLOCK_PIXELS / BLOCK_BYTES));
+
+	/* Target the center of the dedicated MSB texel. */
+	float xMSB        = (blockStart + idx + 0.5) * INV_TEX_WIDTH;
+
+	/* Target the shared trailing byte containing the LSBs (1/8bits = 0.125) . */
+	float lsbOffset   = BLOCK_PIXELS + floor(idx * LSB_SIZE * 0.125);
+	float xLSB        = (blockStart + lsbOffset + 0.5) * INV_TEX_WIDTH;
+	float normY       = (py + 0.5) * INV_TEX_HEIGHT;
+
+	float byteHigh = floor(texture2D(tex_y, vec2(xMSB, normY)).r * 255.0 + 0.5);
+	float byteLow  = floor(texture2D(tex_y, vec2(xLSB, normY)).r * 255.0 + 0.5);
+
+	/* Isolate the pixel's LSB bits inside the shared byte */
+	float totalBitsPos   = idx * LSB_SIZE;
+	float bitStartInByte = totalBitsPos - (floor(totalBitsPos * 0.125) * 8.0);
+
+	float divisionFactor = pow(2.0, bitStartInByte);
+	float shiftedLsb     = floor(byteLow / divisionFactor);
+	float lsbBits        = shiftedLsb - (floor(shiftedLsb / LSB_MASK_VAL) * LSB_MASK_VAL);
+
+	/* Reconstruct full native intensity value. */
+	float rawValue = (byteHigh * LSB_MASK_VAL) + lsbBits;
+
+	float normalizedIntensity = rawValue * INV_MAX_VAL;
+	normalizedIntensity = clamp(normalizedIntensity - blacklevel.g, 0.0, 1.0);
+
+	/* Apply S-Curve contrast stretching. */
+	float centerDist = 1.0 - abs(2 * normalizedIntensity - 1.0);
+	float singlePow  = 0.5 * pow(centerDist, contrastExp);
+	normalizedIntensity = mix(singlePow, 1.0 - singlePow, step(0.5, normalizedIntensity));
+
+	/* Apply gamma correction and output. */
+	vec3 rgb = pow(vec3(normalizedIntensity), vec3(gamma));
+	gl_FragColor = vec4(rgb, 1.0);
+}
diff --git a/src/libcamera/shaders/mono_unpacked.frag b/src/libcamera/shaders/mono_unpacked.frag
new file mode 100644
index 000000000..38051a5bb
--- /dev/null
+++ b/src/libcamera/shaders/mono_unpacked.frag
@@ -0,0 +1,54 @@ 
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2026, Alain Cousinié
+ * Unified Fragment shader for unpacked monochrome formats (R8, R10, R12, R16)
+ */
+
+/* Required for 2-channel GL_RG texture support in OpenGL ES 2.0 (#version 100) */
+#extension GL_EXT_texture_rg : enable
+
+#ifdef GL_ES
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+/* High precision prevents float rounding errors during bit-shifting math. */
+precision highp float;
+#else
+precision mediump float;
+#endif
+#endif
+
+varying vec2            textureOut;
+
+uniform sampler2D       tex_y;
+uniform float           gamma;
+uniform float           contrastExp;
+uniform vec3            blacklevel;
+
+void main(void)
+{
+	float rawValue = 0.0;
+
+#ifdef RAW8
+	/* 8-bit unpacked: single channel extraction. */
+	rawValue = floor(texture2D(tex_y, textureOut).r * 255.0 + 0.5);
+#else
+	/* Multi-byte unpacked: merge Red (low byte) and Green (high byte). */
+	vec4 texel = texture2D(tex_y, textureOut);
+
+	float byteLow  = floor(texel.r * 255.0 + 0.5);
+	float byteHigh = floor(texel.g * 255.0 + 0.5);
+
+	rawValue = (byteHigh * 256.0) + byteLow;
+#endif
+
+	float normalizedIntensity = rawValue * INV_MAX_VAL;
+	normalizedIntensity = clamp(normalizedIntensity - blacklevel.g, 0.0, 1.0);
+
+	/* S-Curve contrast stretching. */
+	float centerDist = 1.0 - abs(2.0 * normalizedIntensity - 1.0);
+	float singlePow  = 0.5 * pow(centerDist, contrastExp);
+	normalizedIntensity = mix(singlePow, 1.0 - singlePow, step(0.5, normalizedIntensity));
+
+	/* Gamma correction and output. */
+	vec3 rgb = pow(vec3(normalizedIntensity), vec3(gamma));
+	gl_FragColor = vec4(rgb, 1.0);
+}