From patchwork Sat Oct 17 11:10:28 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alain Cousinie X-Patchwork-Id: 28492 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 2018AC32F4 for ; Sat, 17 Oct 2026 11:11:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C392D68E5E; Sat, 17 Oct 2026 13:11:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=laposte.net header.i=@laposte.net header.b="UNsiDNVt"; dkim-atps=neutral Received: from smtp-outgoing-1902.laposte.net (smtp-outgoing-1902.laposte.net [160.92.124.106]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 579FF68DFC for ; Sat, 17 Oct 2026 13:11:49 +0200 (CEST) X-mail-filterd: {"version":"1.9.7", "queueID":"4j6JzN5kD8zdZLZ", "contextId": "916b9a5c-24bc-4c13-bac7-d953252b844f"} Received: from outgoing-mail.laposte.net (localhost.localdomain [127.0.0.1]) by mlpnf0106.laposte.net (SMTP Server) with ESMTP id 4j6JzN5kD8zdZLZ; Sat, 17 Oct 2026 13:11:44 +0200 (CEST) X-mail-filterd: {"version":"1.9.7", "queueID":"4j6JzN3w94zdZLj", "contextId": "87dfbdce-2379-45b0-b6cb-f6ec4470a31f"} X-lpn-mailing: LEGIT X-lpn-spamrating: 36 X-lpn-spamlevel: not-spam Received: from debian.home (m-dj.hd.free.fr [82.67.82.126]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mlpnf0106.laposte.net (SMTP Server) with ESMTPSA id 4j6JzN3w94zdZLj; Sat, 17 Oct 2026 13:11:44 +0200 (CEST) From: alain.cousinie@laposte.net To: libcamera-devel@lists.libcamera.org Cc: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= , Milan Zamazal , Hans de Goede , Bryan O'Donoghue , =?utf-8?q?Alain_Cousini=C3=A9?= Subject: [PATCH v6 1/4] libcamera: software_isp: Add support for monochrome formats - Shaders Date: Sat, 17 Oct 2026 13:10:28 +0200 Message-ID: <20261017111036.700859-2-alain.cousinie@laposte.net> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261017111036.700859-1-alain.cousinie@laposte.net> References: <10d396ff-2270-46ef-a641-967df76917be@laposte.net> <20261017111036.700859-1-alain.cousinie@laposte.net> MIME-Version: 1.0 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=laposte.net; s=lpn-wlmd; t=1792235509; bh=BjniLqvfoDRs8OF5FFvF5XRsY0+ivyfcuSURgDDG8W4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Type:Content-Transfer-Encoding; b=UNsiDNVtuf4GpVpwBb/czJBfcja6pR/GPZTzjzm+ehk7aBGuGiyg4XeeAGil9qgoJ+1iG1mD7ztlWRRJZQwJM1mq2ExpD8s2HGwh+iFOVrbEx+Ta5YCNMOlzS6Wa6u5HZaE2YFFX4Qmjt+9mOtOdWoXa90/Dmxx7a0Dqq58UuraAzciruBQuN7WGjtjRzNloi6jeZbBHAEINl02v3ZEoa0CH1AKErnF4bykVkE5xXSouN+BVASc999nJ/PLUiM2FzHka57TKqDJttk2CWa90sEGmpH3CZcNm6WYg+kvQTaQaHYcXByMIqH+DJPL4k9Wl9wd0Q2kjbd9r1Gf3/oHFFA==; X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Signed-off-by: Alain Cousinié --- 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 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); +}