From patchwork Mon Oct 26 08:25:58 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alain Cousinie X-Patchwork-Id: 28604 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 969CDC3261 for ; Mon, 26 Oct 2026 08:27:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3133568FDC; Mon, 26 Oct 2026 09:27:00 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=laposte.net header.i=@laposte.net header.b="PD0fYPRs"; dkim-atps=neutral Received: from smtp-outgoing-1901.laposte.net (smtp-outgoing-1901.laposte.net [160.92.124.105]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 98C1668FBD for ; Mon, 26 Oct 2026 09:26:56 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmtz0Sm6zgY7R", "contextId": "6f74d44b-209e-4f26-9d3e-d6b7ff8f9e3b"} Received: from outgoing-mail.laposte.net (localhost.localdomain [127.0.0.1]) by mlpnf0104.laposte.net (SMTP Server) with ESMTP id 4jCmtz0Sm6zgY7R; Mon, 26 Oct 2026 09:26:51 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmtx5y0BzgY7m", "contextId": "7de1ee0b-42ca-4a09-b699-b6aff70f865b"} X-lpn-mailing: LEGIT X-lpn-spamrating: 36 X-lpn-spamlevel: not-spam Received: from debian (unknown [212.133.41.21]) (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 mlpnf0104.laposte.net (SMTP Server) with ESMTPSA id 4jCmtx5y0BzgY7m; Mon, 26 Oct 2026 09:26:49 +0100 (CET) From: alain.cousinie@laposte.net To: libcamera-devel@lists.libcamera.org Cc: bryan.odonoghue@linaro.org, barnabas.pocze@ideasonboard.com, mzamazal@redhat.com, johannes.goede@oss.qualcomm.com, laurent.pinchart@ideasonboard.com, jai.luthra@ideasonboard.com, =?utf-8?q?Alain_Cousini=C3=A9?= Subject: [PATCH v7 1/5] libcamera: software_isp: Add support for monochrome formats - Shaders Date: Mon, 26 Oct 2026 09:25:58 +0100 Message-ID: <20261026082602.60417-2-alain.cousinie@laposte.net> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261026082602.60417-1-alain.cousinie@laposte.net> References: <20261026082602.60417-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=1793003216; bh=Fu82metiZZgO0qxqcMP6B06LlhORdV0aXn1dTFDxPVg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Type:Content-Transfer-Encoding; b=PD0fYPRsCRExxju9ri+y1IFS+ggiwQqj58zFcjtNaRV8bBfMocN1w28guKZF1ekinOBY7xaNhz88rqHFjxCcYe8CtlzhRtvb/b6SkBwWRPQi6HaHa8k6hAXgTaWKBaJA+Xgj5yMb89hgWfJM+Go/JsJGeonh3d5O3tjPp3ng6ALvpqMmy+wk7uV219jLbR7+6DNYtKA7C9cIvzU/EEI14GpBMpZFnnn9TNIJ+4qNzAMCkJAftlFrXpwKDIZ+CHhbzMcSKoXK3wavzsiMpyYWx9WIeKvDmvtzAtdltjZ4yncXuiWBbnvvoyHM3504Tr9e9JIfVQ2Vg+IqLAYmqUqETQ==; 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 | 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 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); +} From patchwork Mon Oct 26 08:25:59 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alain Cousinie X-Patchwork-Id: 28605 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 E8837C3261 for ; Mon, 26 Oct 2026 08:27:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 19E5F68FDA; Mon, 26 Oct 2026 09:27:02 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=laposte.net header.i=@laposte.net header.b="c4jUbtKP"; dkim-atps=neutral Received: from smtp-outgoing-1901.laposte.net (smtp-outgoing-1901.laposte.net [160.92.124.105]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1A0C668E58 for ; Mon, 26 Oct 2026 09:26:58 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmv02vBgzgY7m", "contextId": "d198212d-53c9-48de-ab52-90e770aca1f7"} Received: from outgoing-mail.laposte.net (localhost.localdomain [127.0.0.1]) by mlpnf0104.laposte.net (SMTP Server) with ESMTP id 4jCmv02vBgzgY7m; Mon, 26 Oct 2026 09:26:52 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmtz3XFkzgY7R", "contextId": "f40a76b7-a85f-4e9f-9b43-10fadbe0acf2"} X-lpn-mailing: LEGIT X-lpn-spamrating: 36 X-lpn-spamlevel: not-spam Received: from debian (unknown [212.133.41.21]) (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 mlpnf0104.laposte.net (SMTP Server) with ESMTPSA id 4jCmtz3XFkzgY7R; Mon, 26 Oct 2026 09:26:51 +0100 (CET) From: alain.cousinie@laposte.net To: libcamera-devel@lists.libcamera.org Cc: bryan.odonoghue@linaro.org, barnabas.pocze@ideasonboard.com, mzamazal@redhat.com, johannes.goede@oss.qualcomm.com, laurent.pinchart@ideasonboard.com, jai.luthra@ideasonboard.com, =?utf-8?q?Alain_Cousini=C3=A9?= Subject: [PATCH v7 2/5] libcamera: software_isp: Add support for monochrome formats - Stats Date: Mon, 26 Oct 2026 09:25:59 +0100 Message-ID: <20261026082602.60417-3-alain.cousinie@laposte.net> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261026082602.60417-1-alain.cousinie@laposte.net> References: <20261026082602.60417-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=1793003217; bh=rfKnxJrV6+Fn+SwdkJupoQ/ztQDFxFpkcXp7zmETsDw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Type:Content-Transfer-Encoding; b=c4jUbtKP0JrK9RBN2Ci69Xlqm34c6znZaD1NddwZ5RGM3uWnNxO8brpURi4EdMjbognxqCPuQTdZ0tTD6u2KPnb1LrsL3oeVVjTlkqg0D2LmaPitmiUDvt13nYgASorNNPBNEtYG52aWdSLX7olFeNTXu94S2yb86S8FoYBWVSR93jrYeG8uvqmm69jfX/SV9YVe4/prFZxyxPvoZy/el8IWZtsrZxZKWjj7CKl01il+5wxqefjR7Sy4l7d9Q1dWCUSFNm10wQGQehEzcZC+XvWd+3doQ7EhOz6DS9ysmvrigQFKhauf0hFDijlvJDZDMuroDVR/SzRe/ZNhzKhLIw==; 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é --- .../internal/software_isp/swstats_cpu.h | 17 ++ src/libcamera/software_isp/swstats_cpu.cpp | 225 ++++++++++++++++++ 2 files changed, 242 insertions(+) -- 2.55.0 diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h index 551870921..2e03c6a55 100644 --- a/include/libcamera/internal/software_isp/swstats_cpu.h +++ b/include/libcamera/internal/software_isp/swstats_cpu.h @@ -101,8 +101,25 @@ private: /* Bayer 12 bpp packed */ void statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats); void statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats); + /* Mono 8 bpp unpacked */ + void statsMono8Line0(const uint8_t *src[], SwIspStats &stats); + /* Mono 10 bpp unpacked */ + void statsMono10Line0(const uint8_t *src[], SwIspStats &stats); + /* Mono 10 bpp packed */ + void statsMono10PLine0(const uint8_t *src[], SwIspStats &stats); + /* Mono 12 bpp unpacked */ + void statsMono12Line0(const uint8_t *src[], SwIspStats &stats); + /* Mono 12 bpp packed */ + void statsMono12PLine0(const uint8_t *src[], SwIspStats &stats); + /* Mono 14 bpp unpacked */ + void statsMono14Line0(const uint8_t *src[], SwIspStats &stats); + /* Mono 14 bpp packed */ + void statsMono14PLine0(const uint8_t *src[], SwIspStats &stats); + /* Mono 16 bpp unpacked */ + void statsMono16Line0(const uint8_t *src[], SwIspStats &stats); void processBayerFrame2(MappedFrameBuffer &in); + void processMonoFrame2(MappedFrameBuffer &in); processFrameFn processFrame_; diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp index 7fb77ce7d..0ce08a01b 100644 --- a/src/libcamera/software_isp/swstats_cpu.cpp +++ b/src/libcamera/software_isp/swstats_cpu.cpp @@ -375,6 +375,162 @@ void SwStatsCpu::statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats) SWSTATS_FINISH_LINE_STATS() } +/* + * Horizontal sub-sampling optimization: evaluates 1 pixel out of 4 (x += 4) + * to reduce CPU overhead. Each sample gets a weight of +4 to preserve global + * frame statistical density required by the Auto Exposure (AE) algorithm. + */ +inline void storeMonoAccumulation(SwIspStats &stats, uint64_t value) +{ + stats.sum_.r() += value; + stats.sum_.g() += value; + stats.sum_.b() += value; +} + +void SwStatsCpu::statsMono8Line0(const uint8_t *src[], SwIspStats &stats) +{ + const uint8_t *cur = src[1] + window_.x; + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + sum += *cur; + stats.yHistogram[*cur >> 2] += 4; + cur += 4; + } + /* Process 4 pixels per iteration : x << 2 */ + storeMonoAccumulation(stats, sum << 2); +} + +void SwStatsCpu::statsMono10Line0(const uint8_t *src[], SwIspStats &stats) +{ + /* + * Little-Endian optimization: Unpacked 10-bit format stores data + * right-aligned in a 2-byte container. Reading via a 16-bit pointer + * allows clean data access before applying the bit-shift. + */ + const uint16_t *cur = reinterpret_cast(src[1] + window_.x * 2); + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint16_t val = *cur; + sum += val; + stats.yHistogram[(val >> 4) & 0x3F] += 4; + cur += 4; + } + /* Process 4 pixels per iteration : x 10 >> 2 to 8bits and x << 2 */ + storeMonoAccumulation(stats, sum); +} + +void SwStatsCpu::statsMono12Line0(const uint8_t *src[], SwIspStats &stats) +{ + /* + * 12-bit unpacked processing. Data is read via a native 16-bit pointer + * and right-shifted to fit the histogram range. + */ + const uint16_t *cur = reinterpret_cast(src[1] + window_.x * 2); + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint16_t val = *cur; + sum += val; + stats.yHistogram[(val >> 6) & 0x3F] += 4; + cur += 4; + } + /* Process 4 pixels per iteration : 12 >> 4 to 8bits and x << 2 */ + storeMonoAccumulation(stats, sum >> 2); +} + +void SwStatsCpu::statsMono14Line0(const uint8_t *src[], SwIspStats &stats) +{ + /* + * 14-bit unpacked processing. Data is read via a native 16-bit pointer + * and right-shifted to fit the histogram range. + */ + const uint16_t *cur = reinterpret_cast(src[1] + window_.x * 2); + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint16_t val = *cur; + sum += val; + stats.yHistogram[(val >> 8) & 0x3F] += 4; + cur += 4; + } + /* Process 4 pixels per iteration : 14 >> 6 to 8bits and x << 2 */ + storeMonoAccumulation(stats, sum >> 4); +} + +void SwStatsCpu::statsMono10PLine0(const uint8_t *src[], SwIspStats &stats) +{ + /* + * MIPI CSI-2 10-bit packed: 4 pixels in 5 bytes. Stepping 5 bytes + * evaluates the head MSB pixel of each block as a native 8-bit value. + */ + const uint8_t *cur = src[1] + window_.x * 5 / 4; + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint8_t val = *cur; + sum += val; + stats.yHistogram[val >> 2] += 4; + cur += 5; + } + /* Process 4 pixels per iteration : first byte x << 2 */ + storeMonoAccumulation(stats, sum << 2); +} + +void SwStatsCpu::statsMono12PLine0(const uint8_t *src[], SwIspStats &stats) +{ + /* + * MIPI CSI-2 12-bit packed: 2 pixels in 3 bytes. Stepping 6 bytes + * advances by two full blocks for optimal data alignment. + */ + const uint8_t *cur = src[1] + window_.x * 3 / 2; + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint8_t val = *cur; + sum += val; + stats.yHistogram[val >> 2] += 4; + cur += 6; + } + /* Process 4 pixels per iteration : first byte x << 2 */ + storeMonoAccumulation(stats, sum << 2); +} + +void SwStatsCpu::statsMono14PLine0(const uint8_t *src[], SwIspStats &stats) +{ + /* + * MIPI CSI-2 14-bit packed: 4 pixels in 6 bytes. Stepping 6 bytes + * advances by two full blocks for optimal data alignment. + */ + const uint8_t *cur = src[1] + window_.x * 7 / 4; + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint8_t val = *cur; + sum += val; + stats.yHistogram[val >> 2] += 4; + cur += 7; + } + /* Process 4 pixels per iteration : first byte x << 2 */ + storeMonoAccumulation(stats, sum << 2); +} + +void SwStatsCpu::statsMono16Line0(const uint8_t *src[], SwIspStats &stats) +{ + const uint16_t *cur = reinterpret_cast(src[1] + window_.x * 2); + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint16_t val = *cur; + sum += val; + stats.yHistogram[val >> 10] += 4; + cur += 4; + } + /* Process 4 pixels per iteration : 16 >> 8 to 8bits and x << 2 */ + storeMonoAccumulation(stats, sum >> 6); +} + /** * \brief Reset state to start statistics gathering for a new frame * \param[in] frame The frame number @@ -494,6 +650,56 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat uint8_t bitDepth = bayerFormat.bitDepth; + if (bayerFormat.order == BayerFormat::Order::MONO) { + /* No skip line for mono */ + ySkipMask_ = 0; + xShift_ = 0; + sumShift_ = 0; + processFrame_ = &SwStatsCpu::processMonoFrame2; + + if (bayerFormat.packing == BayerFormat::Packing::None) { + patternSize_.width = 2; + patternSize_.height = 2; + + switch (bitDepth) { + case 8: + stats0_ = &SwStatsCpu::statsMono8Line0; + return 0; + case 10: + stats0_ = &SwStatsCpu::statsMono10Line0; + return 0; + case 12: + stats0_ = &SwStatsCpu::statsMono12Line0; + return 0; + case 14: + stats0_ = &SwStatsCpu::statsMono14Line0; + return 0; + case 16: + stats0_ = &SwStatsCpu::statsMono16Line0; + return 0; + } + } + if (bayerFormat.packing == BayerFormat::Packing::CSI2) { + patternSize_.height = 2; + + switch (bitDepth) { + case 10: + patternSize_.width = 4; + stats0_ = &SwStatsCpu::statsMono10PLine0; + return 0; + case 12: + patternSize_.width = 2; + stats0_ = &SwStatsCpu::statsMono12PLine0; + return 0; + case 14: + patternSize_.width = 4; + stats0_ = &SwStatsCpu::statsMono14PLine0; + return 0; + } + } + return -EINVAL; + } + if ((bitDepth == 10 || bitDepth == 12) && bayerFormat.packing == BayerFormat::Packing::CSI2) { if (bitDepth == 10) @@ -594,6 +800,25 @@ void SwStatsCpu::processBayerFrame2(MappedFrameBuffer &in) } } +void SwStatsCpu::processMonoFrame2(MappedFrameBuffer &in) +{ + const uint8_t *src = in.planes()[0].data(); + const uint8_t *linePointers[3]; + + src += window_.y * stride_; + + for (unsigned int y = 0; y < window_.height; y++) { + if (y & ySkipMask_) { + src += stride_; + continue; + } + + linePointers[1] = src; + (this->*stats0_)(linePointers, stats_[0]); + src += stride_; + } +} + /** * \brief Calculate statistics for a frame in one go * \param[in] frame The frame number From patchwork Mon Oct 26 08:26:00 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alain Cousinie X-Patchwork-Id: 28606 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 50D9EC3302 for ; Mon, 26 Oct 2026 08:27:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 009C168FD9; Mon, 26 Oct 2026 09:27:03 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=laposte.net header.i=@laposte.net header.b="cIdlNxnO"; dkim-atps=neutral Received: from smtp-outgoing-1603.laposte.net (smtp-outgoing-1603.laposte.net [160.92.124.98]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EC7C968FBA for ; Mon, 26 Oct 2026 09:26:58 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmv13Z2fzgY7m", "contextId": "e048617a-7252-40e2-82cd-89f1563f633f"} Received: from outgoing-mail.laposte.net (localhost.localdomain [127.0.0.1]) by mlpnf0104.laposte.net (SMTP Server) with ESMTP id 4jCmv13Z2fzgY7m; Mon, 26 Oct 2026 09:26:53 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmv040qKzgY7R", "contextId": "fc0959fe-8552-4c62-9d93-f231a782fbdf"} X-lpn-mailing: LEGIT X-lpn-spamrating: 36 X-lpn-spamlevel: not-spam Received: from debian (unknown [212.133.41.21]) (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 mlpnf0104.laposte.net (SMTP Server) with ESMTPSA id 4jCmv040qKzgY7R; Mon, 26 Oct 2026 09:26:52 +0100 (CET) From: alain.cousinie@laposte.net To: libcamera-devel@lists.libcamera.org Cc: bryan.odonoghue@linaro.org, barnabas.pocze@ideasonboard.com, mzamazal@redhat.com, johannes.goede@oss.qualcomm.com, laurent.pinchart@ideasonboard.com, jai.luthra@ideasonboard.com, =?utf-8?q?Alain_Cousini=C3=A9?= Subject: [PATCH v7 3/5] libcamera: software_isp: Add support for monochrome formats - CPU Date: Mon, 26 Oct 2026 09:26:00 +0100 Message-ID: <20261026082602.60417-4-alain.cousinie@laposte.net> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261026082602.60417-1-alain.cousinie@laposte.net> References: <20261026082602.60417-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=1793003218; bh=Q7EGMRC444U2yopW18AoexmjK9uvZLxWv7mHeYhbVTI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Type:Content-Transfer-Encoding; b=cIdlNxnO99rMiASmBcJKcVpBlUqItbideK2mjoao1R3SAl04ZFpVR+AE79J/FH/5Q6crpfoA7K7aZ5TisXPDjXyUKh/st7yf58dOfFrxU+3nicoAh90AiQwMaVNnSHI5DGYE32d8eKXVbIUZ74XP+rALvp/i+KjUQ5V1vvERkxfwmTfTXmRfJnB77mwJE13+8xI625kgHDNRV8XBsLGFynm1fkQeGQlJ7l5cB1vHWai3bbXMYu0pEfpeBwLM1iTj8ce2eVwv+D0/3BM1W+y1WqcZ1p+ABaI491Hud0u3HYcqbe9z2+XlYJIlfb93NlpQv6OBdxjDQnQ7MonuR9T2yw==; 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/software_isp/debayer_cpu.cpp | 231 +++++++++++++++++++-- src/libcamera/software_isp/debayer_cpu.h | 25 +++ 2 files changed, 244 insertions(+), 12 deletions(-) -- 2.55.0 diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp index 91867baa6..a22582931 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -50,6 +50,7 @@ private: void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src); void memcpyNextLine(const uint8_t *linePointers[]); void process2(uint32_t frame, const uint8_t *src, uint8_t *dst); + void processMono2(uint32_t frame, const uint8_t *src, uint8_t *dst); void process4(uint32_t frame, const uint8_t *src, uint8_t *dst); /* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */ @@ -424,6 +425,123 @@ void DebayerCpu::debayer12P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]) } } +template +void DebayerCpu::debayer8_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint8_t *cur = src[1]; + + for (int x = 0; x < static_cast(window_.width);) { + uint8_t pixel_val = *cur++; + STORE_PIXEL(pixel_val, pixel_val, pixel_val) + } +} + +template +void DebayerCpu::debayer10_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint16_t *cur = reinterpret_cast(src[1]); + + for (int x = 0; x < static_cast(window_.width);) { + uint16_t native_val = *cur++; + uint8_t pixel_val = static_cast(native_val >> 2); + + STORE_PIXEL(pixel_val, pixel_val, pixel_val) + } +} + + +template +void DebayerCpu::debayer12_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint16_t *cur = reinterpret_cast(src[1]); + + for (int x = 0; x < static_cast(window_.width);) { + uint16_t native_val = *cur++; + uint8_t pixel_val = static_cast(native_val >> 4); + + STORE_PIXEL(pixel_val, pixel_val, pixel_val) + } +} + +template +void DebayerCpu::debayer14_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint16_t *cur = reinterpret_cast(src[1]); + + for (int x = 0; x < static_cast(window_.width);) { + uint16_t native_val = *cur++; + uint8_t pixel_val = static_cast(native_val >> 6); + + STORE_PIXEL(pixel_val, pixel_val, pixel_val) + } +} + +template +void DebayerCpu::debayer10P_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint8_t *cur = src[1]; + + for (int x = 0; x < static_cast(window_.width);) { + uint8_t gray0 = *cur++; + uint8_t gray1 = *cur++; + uint8_t gray2 = *cur++; + uint8_t gray3 = *cur++; + cur++; + + STORE_PIXEL(gray0, gray0, gray0) + STORE_PIXEL(gray1, gray1, gray1) + STORE_PIXEL(gray2, gray2, gray2) + STORE_PIXEL(gray3, gray3, gray3) + } +} + +template +void DebayerCpu::debayer12P_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint8_t *cur = src[1]; + + for (int x = 0; x < static_cast(window_.width);) { + uint8_t gray0 = *cur++; + uint8_t gray1 = *cur++; + cur++; + + STORE_PIXEL(gray0, gray0, gray0) + STORE_PIXEL(gray1, gray1, gray1) + } +} + +template +void DebayerCpu::debayer14P_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint8_t *cur = src[1]; + + for (int x = 0; x < static_cast(window_.width);) { + uint8_t gray0 = *cur++; + uint8_t gray1 = *cur++; + uint8_t gray2 = *cur++; + uint8_t gray3 = *cur++; + cur += 3; + + STORE_PIXEL(gray0, gray0, gray0) + STORE_PIXEL(gray1, gray1, gray1) + STORE_PIXEL(gray2, gray2, gray2) + STORE_PIXEL(gray3, gray3, gray3) + } +} + +template +void DebayerCpu::debayer16_MONO_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const uint16_t *cur = reinterpret_cast(src[1]); + + for (int x = 0; x < static_cast(window_.width);) { + uint16_t native_val = *cur++; + uint8_t pixel_val = static_cast(native_val >> 8); + + STORE_PIXEL(pixel_val, pixel_val, pixel_val) + } +} + /* * Setup the Debayer object according to the passed in parameters. * Return 0 on success, a negative errno value on failure @@ -433,6 +551,7 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf { BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + std::vector outputFormats = { formats::RGB888, formats::XRGB8888, formats::ARGB8888, @@ -440,6 +559,20 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf formats::XBGR8888, formats::ABGR8888 }; + if (bayerFormat.order == BayerFormat::Order::MONO) { + if (bayerFormat.packing == BayerFormat::Packing::None) { + config.bpp = (bayerFormat.bitDepth + 7) & ~7; + config.patternSize.width = 2; + config.patternSize.height = 2; + } else if (bayerFormat.packing == BayerFormat::Packing::CSI2) { + config.bpp = bayerFormat.bitDepth; + config.patternSize.width = (bayerFormat.bitDepth == 12) ? 2 : 4; + config.patternSize.height = 2; + } + config.outputFormats = outputFormats; + return 0; + } + if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && bayerFormat.packing == BayerFormat::Packing::None && isStandardBayerOrder(bayerFormat.order)) { @@ -513,8 +646,6 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat, bool ccmEnabled) { - BayerFormat bayerFormat = - BayerFormat::fromPixelFormat(inputFormat); bool addAlphaByte = false; xShift_ = 0; @@ -525,6 +656,48 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, return -EINVAL; }; + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + + if (bayerFormat.order == BayerFormat::Order::MONO) { + if (outputFormat == formats::XRGB8888 || outputFormat == formats::ARGB8888 || + outputFormat == formats::XBGR8888 || outputFormat == formats::ABGR8888) { + addAlphaByte = true; + } + if (bayerFormat.packing == BayerFormat::Packing::None) { + switch (bayerFormat.bitDepth) { + case 8: + SET_DEBAYER_METHODS(debayer8_MONO_BGR888, debayer8_MONO_BGR888) + return 0; + case 10: + SET_DEBAYER_METHODS(debayer10_MONO_BGR888, debayer10_MONO_BGR888) + return 0; + case 12: + SET_DEBAYER_METHODS(debayer12_MONO_BGR888, debayer12_MONO_BGR888) + return 0; + case 14: + SET_DEBAYER_METHODS(debayer14_MONO_BGR888, debayer14_MONO_BGR888) + return 0; + case 16: + SET_DEBAYER_METHODS(debayer16_MONO_BGR888, debayer16_MONO_BGR888) + return 0; + } + } + if (bayerFormat.packing == BayerFormat::Packing::CSI2) { + switch (bayerFormat.bitDepth) { + case 10: + SET_DEBAYER_METHODS(debayer10P_MONO_BGR888, debayer10P_MONO_BGR888) + return 0; + case 12: + SET_DEBAYER_METHODS(debayer12P_MONO_BGR888, debayer12P_MONO_BGR888) + return 0; + case 14: + SET_DEBAYER_METHODS(debayer14P_MONO_BGR888, debayer14P_MONO_BGR888) + return 0; + } + } + return invalidFmt(); + } + switch (outputFormat) { case formats::XRGB8888: case formats::ARGB8888: @@ -633,6 +806,9 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg, return -EINVAL; } + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputCfg.pixelFormat); + isMono_ = (bayerFormat.order == BayerFormat::Order::MONO); + if (stats_->configure(inputCfg, threads_.size()) != 0) return -EINVAL; @@ -683,10 +859,16 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg, */ params_.gamma = 1.0; - window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & - ~(inputConfig_.patternSize.width - 1); - window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & - ~(inputConfig_.patternSize.height - 1); + /* Setup the window geometry according to the format type */ + if (isMono_) { + window_.x = 0; + window_.y = 0; + } else { + window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & + ~(inputConfig_.patternSize.width - 1); + window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & + ~(inputConfig_.patternSize.height - 1); + } window_.width = outputCfg.size.width; window_.height = outputCfg.size.height; @@ -828,7 +1010,9 @@ void DebayerCpuThread::process(uint32_t frame, const uint8_t *src, uint8_t *dst) /* Adjust dst for yStart_ */ dst += yStart_ * debayer_->outputConfig_.stride; - if (debayer_->inputConfig_.patternSize.height == 2) + if (debayer_->isMono_) + processMono2(frame, src, dst); + else if (debayer_->inputConfig_.patternSize.height == 2) process2(frame, src, dst); else process4(frame, src, dst); @@ -905,6 +1089,27 @@ void DebayerCpuThread::process2(uint32_t frame, const uint8_t *src, uint8_t *dst } } +void DebayerCpuThread::processMono2(uint32_t frame, const uint8_t *src, uint8_t *dst) +{ + unsigned int outputStride = debayer_->outputConfig_.stride; + unsigned int inputStride = debayer_->inputConfig_.stride; + /* Holds [0] previous- [1] current- [2] next-line */ + const uint8_t *linePointers[3]; + + for (unsigned int y = yStart_; y < yEnd_; y += 2) { + linePointers[1] = src; + debayer_->stats_->processLine0(frame, y, linePointers, threadIndex_); + debayer_->debayer0(dst, linePointers); + src += inputStride; + dst += outputStride; + + linePointers[1] = src; + debayer_->debayer1(dst, linePointers); + src += inputStride; + dst += outputStride; + } +} + void DebayerCpuThread::process4(uint32_t frame, const uint8_t *src, uint8_t *dst) { unsigned int outputStride = debayer_->outputConfig_.stride; @@ -1125,12 +1330,11 @@ void DebayerCpu::stop() SizeRange DebayerCpu::sizes(PixelFormat inputFormat, const Size &inputSize) { Size patternSize = this->patternSize(inputFormat); - unsigned int borderHeight = patternSize.height; - if (patternSize.isNull()) return {}; /* No need for top/bottom border with a pattern height of 2 */ + unsigned int borderHeight = patternSize.height; if (patternSize.height == 2) borderHeight = 0; @@ -1141,13 +1345,16 @@ SizeRange DebayerCpu::sizes(PixelFormat inputFormat, const Size &inputSize) if (inputSize.width < (3 * patternSize.width) || inputSize.height < (2 * borderHeight + patternSize.height)) { LOG(Debayer, Warning) - << "Input format size too small: " << inputSize.toString(); + << "Input format size too small: " << inputSize; return {}; } + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + bool localIsMono = (bayerFormat.order == BayerFormat::Order::MONO); + return SizeRange(Size(patternSize.width, patternSize.height), - Size((inputSize.width - 2 * patternSize.width) & ~(patternSize.width - 1), - (inputSize.height - 2 * borderHeight) & ~(patternSize.height - 1)), + Size((inputSize.width - (localIsMono ? 0 : 2 * patternSize.width)) & ~(patternSize.width - 1), + (inputSize.height - (localIsMono ? 0 : 2 * borderHeight)) & ~(patternSize.height - 1)), patternSize.width, patternSize.height); } diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h index ae570893b..0543d75dc 100644 --- a/src/libcamera/software_isp/debayer_cpu.h +++ b/src/libcamera/software_isp/debayer_cpu.h @@ -120,6 +120,30 @@ private: void debayer12P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]); template void debayer12P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]); + /* 8-bit raw monochrome format */ + template + void debayer8_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* unpacked 10-bit raw monochrome format */ + template + void debayer10_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* unpacked 12-bit raw monochrome format */ + template + void debayer12_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* unpacked 14-bit raw monochrome format */ + template + void debayer14_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* CSI-2 packed 10-bit raw monochrome format */ + template + void debayer10P_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* CSI-2 packed 12-bit raw monochrome format */ + template + void debayer12P_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* CSI-2 packed 14-bit raw monochrome format */ + template + void debayer14P_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); + /* Mono 16 bpp unpacked */ + template + void debayer16_MONO_BGR888(uint8_t *dst, const uint8_t *src[]); static int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config); int setupStandardBayerOrder(BayerFormat::Order order); @@ -147,6 +171,7 @@ private: std::array gammaTable_; LookupTable gammaLut_; bool ccmEnabled_; + bool isMono_; DebayerParams params_; debayerFn debayer0_; From patchwork Mon Oct 26 08:26:01 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alain Cousinie X-Patchwork-Id: 28607 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 38DF6C3261 for ; Mon, 26 Oct 2026 08:27:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A2DAB68FCE; Mon, 26 Oct 2026 09:27:05 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=laposte.net header.i=@laposte.net header.b="TriKXtoB"; dkim-atps=neutral Received: from smtp-outgoing-1903.laposte.net (smtp-outgoing-1903.laposte.net [160.92.124.107]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3DE4468E58 for ; Mon, 26 Oct 2026 09:26:59 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmv23xFGzgY7m", "contextId": "5e0ceb63-dfa2-413d-bd74-ff32e9b5ae15"} Received: from outgoing-mail.laposte.net (localhost.localdomain [127.0.0.1]) by mlpnf0104.laposte.net (SMTP Server) with ESMTP id 4jCmv23xFGzgY7m; Mon, 26 Oct 2026 09:26:54 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmv14jYczgY7R", "contextId": "a7cb5d56-04ef-4bd6-b05c-b142f33ee6c1"} X-lpn-mailing: LEGIT X-lpn-spamrating: 36 X-lpn-spamlevel: not-spam Received: from debian (unknown [212.133.41.21]) (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 mlpnf0104.laposte.net (SMTP Server) with ESMTPSA id 4jCmv14jYczgY7R; Mon, 26 Oct 2026 09:26:53 +0100 (CET) From: alain.cousinie@laposte.net To: libcamera-devel@lists.libcamera.org Cc: bryan.odonoghue@linaro.org, barnabas.pocze@ideasonboard.com, mzamazal@redhat.com, johannes.goede@oss.qualcomm.com, laurent.pinchart@ideasonboard.com, jai.luthra@ideasonboard.com, =?utf-8?q?Alain_Cousini=C3=A9?= Subject: [PATCH v7 4/5] libcamera: software_isp: Add support for monochrome formats - GPU Date: Mon, 26 Oct 2026 09:26:01 +0100 Message-ID: <20261026082602.60417-5-alain.cousinie@laposte.net> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261026082602.60417-1-alain.cousinie@laposte.net> References: <20261026082602.60417-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=1793003218; bh=xOVqaQN9WA9tlQZOAWbmFv8iATsCJ4SknMMeQwhdoAs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Type:Content-Transfer-Encoding; b=TriKXtoBW1cre3uo78VdRZ7s3ZYktPJqUz+OOGJi4yWaGP3+Uzw9ClgaAA2pyGVURwPEiGpg4A9Fmq7fFejwiJjGav/VX0W+PxGl8zzTsPU2hIzjkrN3T80xFtsov9seLleVEL6mU2Vkk50G3Jj/1h0iGUNG5KJEtmZKDGRDA1T0Q+Bd+0Vl0r9hY76FQjojuNWP0Jk+NLP0LswCVWpCdwNGJiWq9kLciab0UrA/c1vcCQEsGJzs3Jn7b5wzX8uo6hg5Bxch3Fygg5otY/pm1CoT1W+1Nkdgd+UEsAjFq/0OJwpXZuUc+mEtIGqHwVf/d+HRl9Y1fmSWoWFww2AUAw==; 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/software_isp/debayer_egl.cpp | 159 +++++++++++++++++++-- src/libcamera/software_isp/debayer_egl.h | 2 + 2 files changed, 149 insertions(+), 12 deletions(-) -- 2.55.0 diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 33933b11d..4afcbbb33 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -61,6 +61,21 @@ int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf formats::XBGR8888, formats::ABGR8888 }; + if (bayerFormat.order == BayerFormat::Order::MONO) { + if (bayerFormat.packing == BayerFormat::Packing::None) { + config.bpp = (bayerFormat.bitDepth + 7) & ~7; + config.patternSize.width = 2; + config.patternSize.height = 2; + } else if (bayerFormat.packing == BayerFormat::Packing::CSI2) { + config.bpp = bayerFormat.bitDepth; + config.patternSize.width = (bayerFormat.bitDepth == 12) ? 2 : 4; + config.patternSize.height = 2; + } + + config.outputFormats = outputFormats; + return 0; + } + if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10) && bayerFormat.packing == BayerFormat::Packing::None && isStandardBayerOrder(bayerFormat.order)) { @@ -172,6 +187,17 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm shaderStridePixels_ = inputConfig_.stride; switch (inputFormat) { + case libcamera::formats::R8: + case libcamera::formats::R10: + case libcamera::formats::R12: + /* + * \todo: Add full conversion testing for R14 once the format is upstreamed + case libcamera::formats::R14: + */ + case libcamera::formats::R16: + firstRed_x_ = 0.0; + firstRed_y_ = 0.0; + break; case libcamera::formats::SBGGR8: case libcamera::formats::SBGGR10_CSI2P: case libcamera::formats::SBGGR12_CSI2P: @@ -203,6 +229,103 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm /* Shader selection */ switch (inputFormat) { + case libcamera::formats::R8: + egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 255.0)"); + egl_.pushEnv(shaderEnv, "#define RAW8"); + fragmentShaderData = mono_unpacked_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_LUMINANCE; + bytesPerPixel_ = 1; + break; + case libcamera::formats::R10: { + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + + egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 1023.0)"); + if (bayerFormat.packing == BayerFormat::Packing::None) { + egl_.pushEnv(shaderEnv, "#define RAW10"); + fragmentShaderData = mono_unpacked_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_RG; + bytesPerPixel_ = 2; + } else { + egl_.pushEnv(shaderEnv, "#define BLOCK_PIXELS 4.0"); + egl_.pushEnv(shaderEnv, "#define BLOCK_BYTES 5.0"); + egl_.pushEnv(shaderEnv, "#define LSB_SIZE 2.0"); + egl_.pushEnv(shaderEnv, "#define LSB_MASK_VAL 4.0"); + egl_.pushEnv(shaderEnv, (std::string("#define TEX_WIDTH ") + std::to_string(inputConfig_.stride) + ".0").c_str()); + egl_.pushEnv(shaderEnv, (std::string("#define TEX_HEIGHT ") + std::to_string(height_) + ".0").c_str()); + + fragmentShaderData = mono_1x_packed_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_LUMINANCE; + bytesPerPixel_ = 1; + shaderStridePixels_ = inputConfig_.stride; + } + break; + } + case libcamera::formats::R12: { + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + + egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 4095.0)"); + if (bayerFormat.packing == BayerFormat::Packing::None) { + egl_.pushEnv(shaderEnv, "#define RAW12"); + fragmentShaderData = mono_unpacked_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_RG; + bytesPerPixel_ = 2; + } else { + egl_.pushEnv(shaderEnv, "#define BLOCK_PIXELS 2.0"); + egl_.pushEnv(shaderEnv, "#define BLOCK_BYTES 3.0"); + egl_.pushEnv(shaderEnv, "#define LSB_SIZE 4.0"); + egl_.pushEnv(shaderEnv, "#define LSB_MASK_VAL 16.0"); + egl_.pushEnv(shaderEnv, (std::string("#define TEX_WIDTH ") + std::to_string(inputConfig_.stride) + ".0").c_str()); + egl_.pushEnv(shaderEnv, (std::string("#define TEX_HEIGHT ") + std::to_string(height_) + ".0").c_str()); + + fragmentShaderData = mono_1x_packed_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_LUMINANCE; + bytesPerPixel_ = 1; + shaderStridePixels_ = inputConfig_.stride; + } + break; + } + /* + * \todo: Add full conversion testing for R14 once the format is upstreamed + case libcamera::formats::R14: { + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + + egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 16383.0)"); + if (bayerFormat.packing == BayerFormat::Packing::None) { + egl_.pushEnv(shaderEnv, "#define RAW14"); + fragmentShaderData = mono_unpacked_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_RG; + bytesPerPixel_ = 2; + } else { + egl_.pushEnv(shaderEnv, "#define BLOCK_PIXELS 4.0"); + egl_.pushEnv(shaderEnv, "#define BLOCK_BYTES 7.0"); + egl_.pushEnv(shaderEnv, "#define LSB_SIZE 6.0"); + egl_.pushEnv(shaderEnv, "#define LSB_MASK_VAL 64.0"); + egl_.pushEnv(shaderEnv, (std::string("#define TEX_WIDTH ") + std::to_string(inputConfig_.stride) + ".0").c_str()); + egl_.pushEnv(shaderEnv, (std::string("#define TEX_HEIGHT ") + std::to_string(height_) + ".0").c_str()); + + fragmentShaderData = mono_1x_packed_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_LUMINANCE; + bytesPerPixel_ = 1; + shaderStridePixels_ = inputConfig_.stride; + } + break; + } + */ + case libcamera::formats::R16: + egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 65535.0)"); + egl_.pushEnv(shaderEnv, "#define RAW16"); + fragmentShaderData = mono_unpacked_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_RG; + bytesPerPixel_ = 2; + break; case libcamera::formats::SBGGR8: case libcamera::formats::SGBRG8: case libcamera::formats::SGRBG8: @@ -279,7 +402,7 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm int DebayerEGL::configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, - [[maybe_unused]] bool ccmEnabled, + bool ccmEnabled, bool lscEnabled) { if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0) { @@ -300,6 +423,7 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, return -EINVAL; } + ccmEnabled_ = ccmEnabled; lscEnabled_ = lscEnabled; inputConfig_.stride = inputCfg.stride; @@ -331,10 +455,17 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, outputSize_ = outputCfg.size; nativeOutputSize_ = outSizeRange.max; - window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & - ~(inputConfig_.patternSize.width - 1); - window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & - ~(inputConfig_.patternSize.height - 1); + /* Setup the window geometry according to the format type */ + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputCfg.pixelFormat); + if (bayerFormat.order == BayerFormat::Order::MONO) { + window_.x = 0; + window_.y = 0; + } else { + window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & + ~(inputConfig_.patternSize.width - 1); + window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & + ~(inputConfig_.patternSize.height - 1); + } window_.width = outputCfg.size.width; window_.height = outputCfg.size.height; @@ -474,9 +605,11 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara << " textureUniformProjMatrix_ " << textureUniformProjMatrix_; /* Pre-transpose matrix for GLES 2.0. */ - Matrix ccm = params.combinedMatrix.transpose(); - glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm.data().data()); - LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; + if (ccmEnabled_) { + Matrix ccm = params.combinedMatrix.transpose(); + glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm.data().data()); + LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; + } if (lscEnabled_ && params.lscLutVersion != lscLutVersion_) { egl_.updateTexture2D(*eglImageLscLookup_, params.lscLut.data()); @@ -723,12 +856,11 @@ void DebayerEGL::stop() SizeRange DebayerEGL::sizes(PixelFormat inputFormat, const Size &inputSize) { Size patternSize = this->patternSize(inputFormat); - unsigned int borderHeight = patternSize.height; - if (patternSize.isNull()) return {}; /* No need for top/bottom border with a pattern height of 2 */ + unsigned int borderHeight = patternSize.height; if (patternSize.height == 2) borderHeight = 0; @@ -743,9 +875,12 @@ SizeRange DebayerEGL::sizes(PixelFormat inputFormat, const Size &inputSize) return {}; } + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + bool localIsMono = (bayerFormat.order == BayerFormat::Order::MONO); + return SizeRange(Size(patternSize.width, patternSize.height), - Size((inputSize.width - 2 * patternSize.width) & ~(patternSize.width - 1), - (inputSize.height - 2 * borderHeight) & ~(patternSize.height - 1)), + Size((inputSize.width - (localIsMono ? 0 : 2 * patternSize.width)) & ~(patternSize.width - 1), + (inputSize.height - (localIsMono ? 0 : 2 * borderHeight)) & ~(patternSize.height - 1)), patternSize.width, patternSize.height); } diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h index cbbc6a450..0db080016 100644 --- a/src/libcamera/software_isp/debayer_egl.h +++ b/src/libcamera/software_isp/debayer_egl.h @@ -128,6 +128,8 @@ private: GLint glFormat_; unsigned int bytesPerPixel_; uint32_t shaderStridePixels_; + + bool ccmEnabled_; }; } /* namespace libcamera */ From patchwork Mon Oct 26 08:26:02 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alain Cousinie X-Patchwork-Id: 28608 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 EA12CC3261 for ; Mon, 26 Oct 2026 08:27:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5E31768FEB; Mon, 26 Oct 2026 09:27:07 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=laposte.net header.i=@laposte.net header.b="fbqElN/X"; dkim-atps=neutral Received: from smtp-outgoing-1702.laposte.net (smtp-outgoing-1702.laposte.net [160.92.124.100]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 100A968FDA for ; Mon, 26 Oct 2026 09:27:00 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmv34K0SzgY7m", "contextId": "749ead37-4186-41b7-a6b0-5bc0554262ac"} Received: from outgoing-mail.laposte.net (localhost.localdomain [127.0.0.1]) by mlpnf0104.laposte.net (SMTP Server) with ESMTP id 4jCmv34K0SzgY7m; Mon, 26 Oct 2026 09:26:55 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmv250nzzgY7R", "contextId": "86399fa9-e7c9-452d-bbb6-5454c7388fd2"} X-lpn-mailing: LEGIT X-lpn-spamrating: 36 X-lpn-spamlevel: not-spam Received: from debian (unknown [212.133.41.21]) (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 mlpnf0104.laposte.net (SMTP Server) with ESMTPSA id 4jCmv250nzzgY7R; Mon, 26 Oct 2026 09:26:54 +0100 (CET) From: alain.cousinie@laposte.net To: libcamera-devel@lists.libcamera.org Cc: bryan.odonoghue@linaro.org, barnabas.pocze@ideasonboard.com, mzamazal@redhat.com, johannes.goede@oss.qualcomm.com, laurent.pinchart@ideasonboard.com, jai.luthra@ideasonboard.com, =?utf-8?q?Alain_Cousini=C3=A9?= Subject: [PATCH v7 5/5] libcamera: software_isp: Add support for monochrome formats - SOFT Date: Mon, 26 Oct 2026 09:26:02 +0100 Message-ID: <20261026082602.60417-6-alain.cousinie@laposte.net> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261026082602.60417-1-alain.cousinie@laposte.net> References: <20261026082602.60417-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=1793003219; bh=b+Qm8w4NYNzHD2L3UtUVUrSSOmnQdam2VspmUyodjH0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Type:Content-Transfer-Encoding; b=fbqElN/XdyRSgCEMIEdg17pkTOlSt0m7E2bsDS2wbo3Jx+dyf/B5bVsYzQlR6BD6p3Qdpuh9Qc3fuHJEHdZuXkhe2iTUO6qCz1S8gOkjPzLd6swTU1eflyqNb+inBJ0/VWbsE22aja9070bttLHMzRMJCPzNcePzTR3ihLjigwtsTLHOIXW0PxxgPfTYuDgL0hEuUZY8AnUCA2/OijoI8uCBtaeJFYCxFszr7FxKS/rsB0xaP7d4j7KEqzXA9dRBB5dA2xPsJ0m5ZD3EXzL8P6y3GhzgJh9HXFNihw9O8kT+V26HXj6321Ll1IjQg+xMGC9A56Nfz7VDk9DyTM0UVQ==; 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/software_isp/software_isp.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) -- 2.55.0 diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp index 627625b4f..4784804ee 100644 --- a/src/libcamera/software_isp/software_isp.cpp +++ b/src/libcamera/software_isp/software_isp.cpp @@ -294,6 +294,17 @@ int SoftwareIsp::configure(const StreamConfiguration &inputCfg, if (ret < 0) return ret; + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputCfg.pixelFormat); + if (bayerFormat.order == BayerFormat::Order::MONO) { + /* \todo Move to IPA uncalibrated-mono.yaml when lifecycle allows */ + ccmEnabled_ = false; + lscEnabled_ = false; + } else { + /* \todo Move to IPA uncalibrated.yaml when lifecycle allows */ + ccmEnabled_ = true; + lscEnabled_ = true; + } + ret = debayer_->configure(inputCfg, outputCfgs, ccmEnabled_, lscEnabled_); if (ret < 0) return ret;