From patchwork Sat Oct 3 15:58:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alain_Cousini=C3=A9?= X-Patchwork-Id: 28394 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 64112BDCB2 for ; Sat, 3 Oct 2026 15:58:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6D31C689ED; Sat, 3 Oct 2026 17:58:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=laposte.net header.i=@laposte.net header.b="XE+rwAw9"; dkim-atps=neutral Received: from smtp-outgoing-1601.laposte.net (smtp-outgoing-1601.laposte.net [160.92.124.96]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6C020689EA for ; Sat, 3 Oct 2026 17:58:52 +0200 (CEST) X-mail-filterd: {"version":"1.9.7", "queueID":"4hxr111tSlz10MQN", "contextId": "07e70952-2b22-4db3-b9ad-319300462b63"} Received: from outgoing-mail.laposte.net (localhost.localdomain [127.0.0.1]) by mlpnf0120.laposte.net (SMTP Server) with ESMTP id 4hxr111tSlz10MQN; Sat, 3 Oct 2026 17:58:45 +0200 (CEST) X-mail-filterd: {"version":"1.9.7", "queueID":"4hxr110Vl2z10MQ4", "contextId": "f619e557-aec9-4ffe-9329-d982e3709b2d"} X-lpn-mailing: LEGIT X-lpn-spamrating: 40 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 mlpnf0120.laposte.net (SMTP Server) with ESMTPSA id 4hxr110Vl2z10MQ4; Sat, 3 Oct 2026 17:58:45 +0200 (CEST) From: =?utf-8?q?Alain_Cousini=C3=A9?= To: libcamera-devel@lists.libcamera.org Cc: =?utf-8?q?Alain_Cousini=C3=A9?= Subject: [PATCH v2] libcamera: software_isp: Add R10 and R10_CSI2P monochrome formats Date: Sat, 3 Oct 2026 17:58:38 +0200 Message-ID: <20261003155838.91284-1-alain.cousinie@laposte.net> X-Mailer: git-send-email 2.55.0 MIME-Version: 1.0 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=laposte.net; s=lpn-wlmd; t=1791043132; bh=MvwSmYeytgbT0VqRXLsNuoJmcTMmBsXDSFropNjweFU=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding; b=XE+rwAw9e+dcr3+QJjikl80nOuBvnHt66auuYVr8J2lYRnyHyQxd8TeJvoXNd0hdwA2Yc39BJFRzqJwHnGIBE3F8L+jpJhtwHlh1EBxp59bXxmCQ7FgcVJIK0UUe4lPMZ5Jxo2BGol9oXQgef+ir0oRzY7xc+1n23nUF8HZxVuJaRYqodmfP4IQQKR3RUQ7qG1qqSOnLiVDZNpDVYLNvJ/i3Yj90d+9mFuZSQBMVrDyva+bW06vAISnCLDhzL6PkzRQvL9ZcY972cH6G2hdAQwY5Dy/udgivqv9J4jnxX5LrKFP2Z1QfEJMSO7z/gfeURkivX9BXfaZ4+K3eaRHjkg==; 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" Implement support for R10 and R10_CSI2P monochrome packed and unpacked formats in the software ISP and shaders. Signed-off-by: Alain CousiniƩ --- 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 --- -- 2.55.0 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; + } + 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(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;