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

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

Commit Message

Alain Cousinie Oct. 17, 2026, 11:10 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  | 51 +++++++++++++++++
 4 files changed, 148 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

Bryan O'Donoghue Oct. 20, 2026, 5:24 p.m. UTC | #1
On 17/10/2026 12:10, alain.cousinie@laposte.net wrote:
> 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  | 51 +++++++++++++++++
>   4 files changed, 148 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',
>   ])
> 
>   # 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;
> +}

You could use identity.vert if you dropped the v_pixelCoords stuff here, 
I don't see why you couldn't calculate that in the follow-on fragment 
shader.

I wonder if this new .vert is strictly necessary ?

> 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..9ea9a7b79
> --- /dev/null
> +++ b/src/libcamera/shaders/mono_unpacked.frag
> @@ -0,0 +1,51 @@
> +/* SPDX-License-Identifier: BSD-2-Clause */
> +/*
> + * Copyright (C) 2026, Alain Cousinié
> + * Unified Fragment shader for unpacked monochrome formats (R8, R10, R12, R16)
> + */
> +
> +#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..9ea9a7b79
--- /dev/null
+++ b/src/libcamera/shaders/mono_unpacked.frag
@@ -0,0 +1,51 @@ 
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2026, Alain Cousinié
+ * Unified Fragment shader for unpacked monochrome formats (R8, R10, R12, R16)
+ */
+
+#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);
+}