From patchwork Sat Jun 20 23:00:28 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26998 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 CEA73C3303 for ; Sat, 20 Jun 2026 23:00:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1142365702; Sun, 21 Jun 2026 01:00:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="i7vb/rGr"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 13AD2656D6 for ; Sun, 21 Jun 2026 01:00:36 +0200 (CEST) Received: from [192.168.0.240] (cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B875CB8B; Sun, 21 Jun 2026 00:59:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781996398; bh=9qKWrXCM3qnZu1o6pRQj8bxGCCmlSd/kEaWHYvvQaPo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=i7vb/rGrMja1RtZGp5eo+td7S0iFW/w3JAF13Um4Phw0FPoZoL2/LgXPn5fkqQRcQ yTJCJXio+vxhhkZhGkrRk/eiOF+nsYeB4fRrVD7BKazHHBL2ZWEJ+WQOFJZFGA4fP1 gBQShtIMI0xT3r6cZX4tQVpaKYoThHboBNPbTAlM= From: Kieran Bingham Date: Sun, 21 Jun 2026 00:00:28 +0100 Subject: [PATCH 1/7] libcamera: vector: Add clamp operation MIME-Version: 1.0 Message-Id: <20260621-kbingham-awb-saturation-v1-1-b91ea59c6cfb@ideasonboard.com> References: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> In-Reply-To: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Laurent Pinchart X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1781996434; l=2357; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=9qKWrXCM3qnZu1o6pRQj8bxGCCmlSd/kEaWHYvvQaPo=; b=wcBL9tQXhGZHKwNGQKtPeZHXRoIGu923Cnic5hspX+2KXHKq3qEsZuk8o5ZsteAFQocNOrEz2 8z2hFZ33oHaBw6T0s41gPtPE8kKvyb3F6OmpbrqGcoA4tB+VWE9ieeR X-Developer-Key: i=kieran.bingham@ideasonboard.com; a=ed25519; pk=IOxS2C6nWHNjLfkDR71Iesk904i6wJDfEERqV7hDBdY= 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" Provide a helper that will perform a std::clamp given a scaler high and low value to clamp to. Suggested-by: Laurent Pinchart Signed-off-by: Kieran Bingham --- include/libcamera/internal/vector.h | 8 ++++++++ src/libcamera/vector.cpp | 8 ++++++++ test/vector.cpp | 1 + 3 files changed, 17 insertions(+) diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h index 093efafa8982fe553a953316ac794bef46eec5d4..17ecbda50f2dc4363548a4a29639a751a187dac6 100644 --- a/include/libcamera/internal/vector.h +++ b/include/libcamera/internal/vector.h @@ -185,6 +185,14 @@ public: return apply(*this, scalar, [](T a, T b) -> T { return std::max(a, b); }); } + constexpr Vector clamp(T low, T high) const + { + Vector result; + for (unsigned int i = 0; i < Rows; i++) + result[i] = std::clamp(data_[i], low, high); + return result; + } + constexpr T dot(const Vector &other) const { T ret = 0; diff --git a/src/libcamera/vector.cpp b/src/libcamera/vector.cpp index 47b3d1af36d90f9719c527b8458fbfe07412239e..a135ab498e17149b9dc54de7204e8a34262a2da9 100644 --- a/src/libcamera/vector.cpp +++ b/src/libcamera/vector.cpp @@ -224,6 +224,14 @@ LOG_DEFINE_CATEGORY(Vector) * \return The element-wise maximum of this vector and \a scalar */ +/** + * \fn Vector::clamp(T low, T high) const + * \brief Clamp the vector element-wise between \a low and \a high + * \param[in] low The lower limit + * \param[in] high The upper limit + * \return A vector with each element clamped between \a low and \a high + */ + /** * \fn Vector::dot(const Vector &other) const * \brief Compute the dot product diff --git a/test/vector.cpp b/test/vector.cpp index 4ff908e8f682f19d65c55d071fcd300071cf90ac..2f3d97d93dccbe153f2d1cf536423f7446a22aab 100644 --- a/test/vector.cpp +++ b/test/vector.cpp @@ -72,6 +72,7 @@ protected: ASSERT_EQ(v2.min(4.0), (Vector{{ 1.0, 4.0, 4.0 }})); ASSERT_EQ(v2.max(v3), (Vector{{ 4.0, 4.0, 8.0 }})); ASSERT_EQ(v2.max(4.0), (Vector{{ 4.0, 4.0, 8.0 }})); + ASSERT_EQ(v2.clamp(2.0, 6.0), (Vector{{ 2.0, 4.0, 6.0 }})); ASSERT_EQ(v2.dot(v3), 52.0); From patchwork Sat Jun 20 23:00:29 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26997 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 130ACC3302 for ; Sat, 20 Jun 2026 23:00:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 598B062899; Sun, 21 Jun 2026 01:00:39 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QlU1bJE8"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 25516656DD for ; Sun, 21 Jun 2026 01:00:36 +0200 (CEST) Received: from [192.168.0.240] (cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F16A4CF5; Sun, 21 Jun 2026 00:59:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781996399; bh=D509HpL8p5KRn2kdLFIfhcUDFCTpKKZwYkSVgUsRw4I=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=QlU1bJE8KO652RjW0PBjL2Q4SQNBEocgCFKswvtN72XGq85KFjFwzK2qSP8wfYkbh 0p2nEliHMNmxm+yp1z6v6CfKsLFHwfWZN/VOHKwmJDemdfhca4okMFVRFk76LPTMaE xfqZiWYTC4Y+paOY/8bVrb7mogQ+gJWMbGabJJJ4= From: Kieran Bingham Date: Sun, 21 Jun 2026 00:00:29 +0100 Subject: [PATCH 2/7] ipa: rkisp1: awb: Utilise clamp function MIME-Version: 1.0 Message-Id: <20260621-kbingham-awb-saturation-v1-2-b91ea59c6cfb@ideasonboard.com> References: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> In-Reply-To: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1781996434; l=910; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=D509HpL8p5KRn2kdLFIfhcUDFCTpKKZwYkSVgUsRw4I=; b=IP+G3XtibBNCcNP/0EsaRmhSxrZI8hmH/nA1xtyItihLYgJMMfdeL9Sk/i8zm/gVu2J/cSkx+ ccHvbNZF7uBDdZqOFSSrICFkAqI+prGO3a8MpxXmdDYfwm7WZo05PXS X-Developer-Key: i=kieran.bingham@ideasonboard.com; a=ed25519; pk=IOxS2C6nWHNjLfkDR71Iesk904i6wJDfEERqV7hDBdY= 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" Use the newly introduced 'clamp' operation on the RGB vector type to restrict the raw RGB means. Signed-off-by: Kieran Bingham Reviewed-by: Laurent Pinchart --- src/ipa/rkisp1/algorithms/awb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index 5ae5b647164329573f0a233ee36d1db7b3683ef9..0eb05c2b6e1ac9e159e050bae3d5c56ec3045299 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -332,7 +332,7 @@ void Awb::process(IPAContext &context, * divisions by zero when computing the raw means in subsequent * iterations. */ - awbResult.gains = awbResult.gains.max(1.0 / 256).min(1023.0 / 256); + awbResult.gains = awbResult.gains.clamp(1.0 / 256, 1023.0 / 256); /* Filter the values to avoid oscillations. */ double speed = 0.2; From patchwork Sat Jun 20 23:00:30 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26999 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 33053C3302 for ; Sat, 20 Jun 2026 23:00:47 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 85747656FC; Sun, 21 Jun 2026 01:00:41 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Am1Qqrew"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4739A656DE for ; Sun, 21 Jun 2026 01:00:36 +0200 (CEST) Received: from [192.168.0.240] (cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 331551049; Sun, 21 Jun 2026 00:59:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781996399; bh=yNNkQomgjsnwQWfaoyVI0qozibfNlMv3i9Yw8PwVAwo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Am1QqrewIMMacxkxAr8NdE5WVMizI4yJGTmk+WRcS4Lem/NHxKX6FEoygdlLIMJNs w+Zh5EmgsZjVYhjGTIVn4J9dUDrAWSLHsuIFroF6Q82Hp0ES9THj+sFY3EwltGK2C3 jd2YL66+474gRCkvBq3K+e2Do3H14GXqq2BTyBaw= From: Kieran Bingham Date: Sun, 21 Jun 2026 00:00:30 +0100 Subject: [PATCH 3/7] ipa: simple: Limit the black level value MIME-Version: 1.0 Message-Id: <20260621-kbingham-awb-saturation-v1-3-b91ea59c6cfb@ideasonboard.com> References: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> In-Reply-To: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Milan Zamazal , Bryan O'Donoghue X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1781996434; l=1658; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=gmewv5noS55Y81VtEu3iVbH9ai00n6FV99WE4gKDKjQ=; b=8AAomWBRkQt327OI902Jv9DJ2Twfrw3PuKn3EOqSZ9e2/BuUdVD7DX6a9LRRW7cG9ehJbLbS1 jNTFuALfVPAAKvD5jYzSydU77GamHHgqJezRbg2BsZjJWC8d91x/ojx X-Developer-Key: i=kieran.bingham@ideasonboard.com; a=ed25519; pk=IOxS2C6nWHNjLfkDR71Iesk904i6wJDfEERqV7hDBdY= 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" From: Milan Zamazal The black level value is passed to image processing, where it's used in pixel value computations. To avoid troubles with weird black level values (which are mostly theoretical but we should be still safe against e.g. crazy values in testing tuning files) like division by zero, let's make sure the black level passed to the image processing is lower than the maximum pixel value. Signed-off-by: Milan Zamazal Reviewed-by: Bryan O'Donoghue Signed-off-by: Kieran Bingham --- src/ipa/simple/algorithms/blc.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp index 677be56ed6699ae8aaa8a786ab16f7299a82eb46..058cb372620134cb15a0655d3553b3b767b47f0e 100644 --- a/src/ipa/simple/algorithms/blc.cpp +++ b/src/ipa/simple/algorithms/blc.cpp @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* - * Copyright (C) 2024-2025, Red Hat Inc. + * Copyright (C) 2024-2026, Red Hat Inc. * * Black level handling */ @@ -52,8 +52,9 @@ void BlackLevel::prepare(IPAContext &context, [[maybe_unused]] IPAFrameContext &frameContext, DebayerParams *params) { - /* Latch the blacklevel gain so GPUISP can apply. */ - params->blackLevel = RGB(context.activeState.blc.level / 255.0f); + /* Make sure the black level is sane, i.e. below maximum pixel value. */ + params->blackLevel = RGB(context.activeState.blc.level / 255.0f) + .min(0.99); } void BlackLevel::process(IPAContext &context, From patchwork Sat Jun 20 23:00:31 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 27000 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 41EE9C3305 for ; Sat, 20 Jun 2026 23:00:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1DE45656EB; Sun, 21 Jun 2026 01:00:43 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FHfKSjj2"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6A565656DF for ; Sun, 21 Jun 2026 01:00:36 +0200 (CEST) Received: from [192.168.0.240] (cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7A5A9145C; Sun, 21 Jun 2026 00:59:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781996399; bh=1Dsd99PDsCvYk3wseluybJJFhnPM+XvVCmzr+tTgNsc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=FHfKSjj2RNDxoDIaLdZQg/tAEpMoNtLz25m082TPAQPUTaRYFknlDVDUnim+rfeX3 hPPl29sqa5hKC3gxJwoReoNHlyQ66fHdWTHXh2VnOwbvqCdU5FUvCukBSeV3ZZgcFR 19lxlhkKsWh8kmvbJXjNlPJkM3B7hEc9SMdHI9uE= From: Kieran Bingham Date: Sun, 21 Jun 2026 00:00:31 +0100 Subject: [PATCH 4/7] libcamera: software_isp: Fix black level application in GPU ISP MIME-Version: 1.0 Message-Id: <20260621-kbingham-awb-saturation-v1-4-b91ea59c6cfb@ideasonboard.com> References: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> In-Reply-To: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Milan Zamazal , Bryan O'Donoghue X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1781996434; l=2107; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=HO351AQHr9+o9JwpNlbZtGFYAw5B+ZtR08q33yD8XDY=; b=ym43Y/VEniUySN+wjKKPYTwnM+Cq+4G5XHRhUqMzTvq3i3NYtfTb4w5nMqPyjCiNIDf5tNz4s p4f2FVb5iSVAjmA+BH7S4l0WzVnwS6Zm2byy6GrrUvJN+suMQtrJ9vb X-Developer-Key: i=kieran.bingham@ideasonboard.com; a=ed25519; pk=IOxS2C6nWHNjLfkDR71Iesk904i6wJDfEERqV7hDBdY= 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" From: Milan Zamazal In GPU ISP fragment shaders, the black level is simply subtracted from the pixel value. This means the highest pixel values can never be reached, possibly resulting in wrong brightness or colour shifts. Fix this by spreading the resulting value to the whole 0.0..1.0 range. The preceding simple pipeline IPA patch ensures `blacklevel' is less than 1.0, preventing division by zero here. Signed-off-by: Milan Zamazal Reviewed-by: Bryan O'Donoghue Signed-off-by: Kieran Bingham --- src/libcamera/shaders/bayer_1x_packed.frag | 6 +++++- src/libcamera/shaders/bayer_unpacked.frag | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libcamera/shaders/bayer_1x_packed.frag b/src/libcamera/shaders/bayer_1x_packed.frag index 23747f78a6313503a46b61ed5bae6e7c178c5745..fbd15b32e5b3b510ba0dd5a75704f9ff3942e00c 100644 --- a/src/libcamera/shaders/bayer_1x_packed.frag +++ b/src/libcamera/shaders/bayer_1x_packed.frag @@ -225,7 +225,11 @@ void main(void) vec3(patterns.y, C, patterns.x) : vec3(patterns.wz, C)); - rgb = rgb - blacklevel; + /* + * \todo: Black level normalising, AWB and digital gain could be + * reworked into a single multiplication. + */ + rgb = (rgb - blacklevel) / (1.0 - blacklevel); /* * CCM is a 3x3 in the format diff --git a/src/libcamera/shaders/bayer_unpacked.frag b/src/libcamera/shaders/bayer_unpacked.frag index 1b85196ae16130670eb3d1c077ab4884119ae63c..0f85e9a4ded7f43ce4fdabf1e045275ae2bc8f53 100644 --- a/src/libcamera/shaders/bayer_unpacked.frag +++ b/src/libcamera/shaders/bayer_unpacked.frag @@ -128,7 +128,11 @@ void main(void) { vec3(PATTERN.w, C, PATTERN.z) : vec3(PATTERN.yx, C)); - rgb = rgb - blacklevel; + /* + * \todo: Black level normalising, AWB and digital gain could be + * reworked into a single multiplication. + */ + rgb = (rgb - blacklevel) / (1.0 - blacklevel); /* * CCM is a 3x3 in the format From patchwork Sat Jun 20 23:00:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 27001 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 21CF8C3308 for ; Sat, 20 Jun 2026 23:00:49 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E7273656EC; Sun, 21 Jun 2026 01:00:44 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="V9kY6MYP"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8867B656E2 for ; Sun, 21 Jun 2026 01:00:36 +0200 (CEST) Received: from [192.168.0.240] (cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C66F817D8; Sun, 21 Jun 2026 00:59:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781996399; bh=G2KmPxn7MDxy+1ldnSusw90wucEnlxKSCdpUja9ph6Q=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=V9kY6MYPZZ3eaciHXMzRxocXUoTi1AuMMAGIqMbmALnMibRQpxcooa40ZUohXNBVk jWQHf4c050BqI3PXHplDOixqdY3ZkSFIPfE4oUulfWssgg1cc1xyeZ9z4XrzIjm4rJ W+OEVJmMhnCikhjj1dFlkvUKuaWnr5aWAUw+daBY= From: Kieran Bingham Date: Sun, 21 Jun 2026 00:00:32 +0100 Subject: [PATCH 5/7] libcamera: software_isp: Fix black level handling in CPU ISP MIME-Version: 1.0 Message-Id: <20260621-kbingham-awb-saturation-v1-5-b91ea59c6cfb@ideasonboard.com> References: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> In-Reply-To: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Milan Zamazal X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1781996434; l=6483; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=Z3taOw8EuwTfN3kX7QTe2JdvX1aHOQdk8busc/t9/sA=; b=9mzyxOpJVt6w4nRt6lwjgIoRJgFllh/xBQuzia5Bm+d/93RFs+TjNbMVjdntzqRr68Jw+OhKL X3IpGqL+PyuAO3wy3iOAlC9Q9UsRus7aENTK548y39W0m9QpE517iEi X-Developer-Key: i=kieran.bingham@ideasonboard.com; a=ed25519; pk=IOxS2C6nWHNjLfkDR71Iesk904i6wJDfEERqV7hDBdY= 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" From: Milan Zamazal The black level handling in CPU ISP has two flaws: - The black level is applied after white balance rather than before. - It doesn't handle black levels with different values for individual colour channels. The flaws are in both CCM and non-CCM cases. The wrong black level and white balance application order is well visible when the white balance gains are significantly different from 1.0. Then the output differs significantly from GPU ISP output, which uses the correct order. This patch changes the computations of the lookup tables in a way that fixes both the problems. [Kieran: Fix indexing from Milans' suggestion, use new clamp] [Kieran: Use enumerate, and div updates from Laurent's suggestion] Signed-off-by: Milan Zamazal Signed-off-by: Kieran Bingham --- src/libcamera/software_isp/debayer_cpu.cpp | 60 ++++++++++++++---------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp index d2596d32bbcdeaaab2e2c287e3f01ae22c442884..9d6a08b3333f988570cb5d3f7cea4c117f7c2530 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -979,32 +979,20 @@ void DebayerCpuThread::process4(uint32_t frame, const uint8_t *src, uint8_t *dst void DebayerCpu::updateGammaTable(const DebayerParams ¶ms) { - const RGB blackLevel = params.blackLevel; - /* Take let's say the green channel black level */ - const unsigned int blackIndex = blackLevel[1] * gammaTable_.size(); const float gamma = params.gamma; const float contrastExp = params.contrastExp; - const float divisor = gammaTable_.size() - blackIndex - 1.0; - for (unsigned int i = blackIndex; i < gammaTable_.size(); i++) { - float normalized = (i - blackIndex) / divisor; + const float divisor = gammaTable_.size() - 1.0; + for (auto [i, value] : utils::enumerate(gammaTable_)) { + float normalized = i / divisor; /* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */ /* Apply simple S-curve */ if (normalized < 0.5) normalized = 0.5 * std::pow(normalized / 0.5, contrastExp); else normalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp); - gammaTable_[i] = UINT8_MAX * - std::pow(normalized, gamma); + value = UINT8_MAX * std::pow(normalized, gamma); } - /* - * Due to CCM operations, the table lookup may reach indices below the black - * level. Let's set the table values below black level to the minimum - * non-black value to prevent problems when the minimum value is - * significantly non-zero (for example, when the image should be all grey). - */ - std::fill(gammaTable_.begin(), gammaTable_.begin() + blackIndex, - gammaTable_[blackIndex]); } void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) @@ -1016,11 +1004,15 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) if (gammaUpdateNeeded) updateGammaTable(params); + /* Processing order: black level -> gains -> gamma */ auto matrixChanged = [](const Matrix &m1, const Matrix &m2) -> bool { return !std::equal(m1.data().begin(), m1.data().end(), m2.data().begin()); }; const unsigned int gammaTableSize = gammaTable_.size(); - const double div = static_cast(kRGBLookupSize) / gammaTableSize; + + const RGB blackIndex = params.blackLevel * kRGBLookupSize; + const RGB div = (RGB(kRGBLookupSize) - blackIndex).max(1.0); + if (ccmEnabled_) { if (gammaUpdateNeeded || matrixChanged(params.combinedMatrix, params_.combinedMatrix)) { @@ -1030,17 +1022,19 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) const unsigned int redIndex = swapRedBlueGains_ ? 2 : 0; const unsigned int greenIndex = 1; const unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2; + for (unsigned int i = 0; i < kRGBLookupSize; i++) { - red[i].r = std::round(i * params.combinedMatrix[redIndex][0]); - red[i].g = std::round(i * params.combinedMatrix[greenIndex][0]); - red[i].b = std::round(i * params.combinedMatrix[blueIndex][0]); - green[i].r = std::round(i * params.combinedMatrix[redIndex][1]); - green[i].g = std::round(i * params.combinedMatrix[greenIndex][1]); - green[i].b = std::round(i * params.combinedMatrix[blueIndex][1]); - blue[i].r = std::round(i * params.combinedMatrix[redIndex][2]); - blue[i].g = std::round(i * params.combinedMatrix[greenIndex][2]); - blue[i].b = std::round(i * params.combinedMatrix[blueIndex][2]); - gammaLut_[i] = gammaTable_[i / div]; + const RGB rgb = ((RGB(i) - blackIndex) * kRGBLookupSize / div).max(0.0); + red[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][0]); + red[i].g = std::round(rgb.r() * params.combinedMatrix[greenIndex][0]); + red[i].b = std::round(rgb.r() * params.combinedMatrix[blueIndex][0]); + green[i].r = std::round(rgb.g() * params.combinedMatrix[redIndex][1]); + green[i].g = std::round(rgb.g() * params.combinedMatrix[greenIndex][1]); + green[i].b = std::round(rgb.g() * params.combinedMatrix[blueIndex][1]); + blue[i].r = std::round(rgb.b() * params.combinedMatrix[redIndex][2]); + blue[i].g = std::round(rgb.b() * params.combinedMatrix[greenIndex][2]); + blue[i].b = std::round(rgb.b() * params.combinedMatrix[blueIndex][2]); + gammaLut_[i] = gammaTable_[i * gammaTableSize / kRGBLookupSize]; } } } else { @@ -1049,12 +1043,14 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) auto &red = swapRedBlueGains_ ? blue_ : red_; auto &green = green_; auto &blue = swapRedBlueGains_ ? red_ : blue_; + for (unsigned int i = 0; i < kRGBLookupSize; i++) { - /* Apply gamma after gain! */ - const RGB lutGains = (gains * i / div).min(gammaTableSize - 1); - red[i] = gammaTable_[static_cast(lutGains.r())]; - green[i] = gammaTable_[static_cast(lutGains.g())]; - blue[i] = gammaTable_[static_cast(lutGains.b())]; + const RGB lutGains = + (gains * (RGB(i) - blackIndex) * gammaTableSize / div) + .clamp(0.0, gammaTableSize - 1); + red[i] = gammaTable_[lutGains.r()]; + green[i] = gammaTable_[lutGains.g()]; + blue[i] = gammaTable_[lutGains.b()]; } } } From patchwork Sat Jun 20 23:00:33 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 27002 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 7211BC330A for ; Sat, 20 Jun 2026 23:00:49 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2076A656E2; Sun, 21 Jun 2026 01:00:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WAlsjo1L"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C58C762899 for ; Sun, 21 Jun 2026 01:00:36 +0200 (CEST) Received: from [192.168.0.240] (cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1113018A2; Sun, 21 Jun 2026 01:00:00 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781996400; bh=Ibjo4eaudDdOYgNiLtnzyC7qbdq6Mx1seSA2HTgq6CQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=WAlsjo1LTsg/Y+TxpG48P9inSzpo8/2PtCLQtit8CXqXBOv1ZfUV8v035CvbabkBC x7NU9a6EKdnujVnNMR3SNB8eUKdCfzdJtGFDNusaYkuu7PLerHvjFyKuxA92n+Em1N hGkt2dCyvShG/q0FQ+X8syhynETCSQ/6MqGH+DV4= From: Kieran Bingham Date: Sun, 21 Jun 2026 00:00:33 +0100 Subject: [PATCH 6/7] softisp: Split AWB from Combined Matrix MIME-Version: 1.0 Message-Id: <20260621-kbingham-awb-saturation-v1-6-b91ea59c6cfb@ideasonboard.com> References: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> In-Reply-To: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Milan Zamazal X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1781996434; l=8995; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=Ibjo4eaudDdOYgNiLtnzyC7qbdq6Mx1seSA2HTgq6CQ=; b=zU/62TsF+OPfE4Rbl/iSEY9v+TlOtzKto93HoUQy95EqYuC6/d8ZPLYmXF/7gwNrVRxHHQZmu yyM60/8VskSAyyUlNUMJ1r8O8e8P16MHGg1KgvInZTtO3wHkhAvEjfV X-Developer-Key: i=kieran.bingham@ideasonboard.com; a=ed25519; pk=IOxS2C6nWHNjLfkDR71Iesk904i6wJDfEERqV7hDBdY= 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" The AWB and CCM are currently combined into a single matrix which gets passed to the GPU and CPU ISP implementations. This unfortunately can cause saturation bugs between the AWB and CCM in areas of high brightness in a given scene, where one colour channel will saturate before the others unevenly. Remove AWB gains from the combined Matrix to allow a specific AWB Gain stage to be applied, which is then clamped equally on all channels. For CPU ISP the gains are applied and clamped before applying the CCM. Reviewed-by: Milan Zamazal Signed-off-by: Milan Zamazal Signed-off-by: Kieran Bingham --- src/ipa/simple/algorithms/awb.cpp | 16 +++------------- src/ipa/simple/ipa_context.h | 5 +---- src/libcamera/shaders/bayer_1x_packed.frag | 4 ++++ src/libcamera/shaders/bayer_unpacked.frag | 4 ++++ src/libcamera/software_isp/debayer_cpu.cpp | 10 +++++----- src/libcamera/software_isp/debayer_egl.cpp | 5 +++++ src/libcamera/software_isp/debayer_egl.h | 3 +++ 7 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp index f5c88ea6f896ff8a7ffdc328e09a4d4aa99df5e4..05155c83d172d64609053ba940a4c12a2248bb04 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -38,15 +38,8 @@ void Awb::prepare(IPAContext &context, DebayerParams *params) { auto &gains = context.activeState.awb.gains; - Matrix gainMatrix = { { gains.r(), 0, 0, - 0, gains.g(), 0, - 0, 0, gains.b() } }; - context.activeState.combinedMatrix = - gainMatrix * context.activeState.combinedMatrix; - - frameContext.gains.red = gains.r(); - frameContext.gains.blue = gains.b(); + frameContext.gains = gains; params->gains = gains; } @@ -59,11 +52,8 @@ void Awb::process(IPAContext &context, const SwIspStats::Histogram &histogram = stats->yHistogram; const uint8_t blackLevel = context.activeState.blc.level; - const float mdGains[] = { - static_cast(frameContext.gains.red), - static_cast(frameContext.gains.blue) - }; - metadata.set(controls::ColourGains, mdGains); + metadata.set(controls::ColourGains, { frameContext.gains.r(), + frameContext.gains.b() }); if (!stats->valid) return; diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 34f7403a41d690cb3f0c271827ae2e915b6ea49d..8ccfacb46a59cedb5a0ad051d67f7c1f40af4b52 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -71,10 +71,7 @@ struct IPAFrameContext : public FrameContext { double gain; } sensor; - struct { - double red; - double blue; - } gains; + RGB gains; float gamma; std::optional contrast; diff --git a/src/libcamera/shaders/bayer_1x_packed.frag b/src/libcamera/shaders/bayer_1x_packed.frag index fbd15b32e5b3b510ba0dd5a75704f9ff3942e00c..6b3f7532c177f277e43c27e498dfadcb9cd4e26c 100644 --- a/src/libcamera/shaders/bayer_1x_packed.frag +++ b/src/libcamera/shaders/bayer_1x_packed.frag @@ -65,6 +65,7 @@ uniform vec2 tex_step; uniform vec2 tex_bayer_first_red; uniform sampler2D tex_y; +uniform vec3 awb; uniform mat3 ccm; uniform vec3 blacklevel; uniform float gamma; @@ -231,6 +232,9 @@ void main(void) */ rgb = (rgb - blacklevel) / (1.0 - blacklevel); + /* Apply AWB gains, and saturate each channel at sensor range */ + rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0)); + /* * CCM is a 3x3 in the format * diff --git a/src/libcamera/shaders/bayer_unpacked.frag b/src/libcamera/shaders/bayer_unpacked.frag index 0f85e9a4ded7f43ce4fdabf1e045275ae2bc8f53..44535312125d0e7e5cca372f4e8e0f8ad9fce8b3 100644 --- a/src/libcamera/shaders/bayer_unpacked.frag +++ b/src/libcamera/shaders/bayer_unpacked.frag @@ -24,6 +24,7 @@ uniform sampler2D tex_y; varying vec4 center; varying vec4 yCoord; varying vec4 xCoord; +uniform vec3 awb; uniform mat3 ccm; uniform vec3 blacklevel; uniform float gamma; @@ -134,6 +135,9 @@ void main(void) { */ rgb = (rgb - blacklevel) / (1.0 - blacklevel); + /* Apply AWB gains, and saturate each channel at sensor range */ + rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0)); + /* * CCM is a 3x3 in the format * diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp index 9d6a08b3333f988570cb5d3f7cea4c117f7c2530..49382b4c2719bf02f6a806c9a9ac560c8daae866 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -1011,20 +1011,22 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) const unsigned int gammaTableSize = gammaTable_.size(); const RGB blackIndex = params.blackLevel * kRGBLookupSize; + const RGB gains = params.gains; const RGB div = (RGB(kRGBLookupSize) - blackIndex).max(1.0); if (ccmEnabled_) { if (gammaUpdateNeeded || - matrixChanged(params.combinedMatrix, params_.combinedMatrix)) { + matrixChanged(params.combinedMatrix, params_.combinedMatrix) || + params.gains != params_.gains) { auto &red = swapRedBlueGains_ ? blueCcm_ : redCcm_; auto &green = greenCcm_; auto &blue = swapRedBlueGains_ ? redCcm_ : blueCcm_; const unsigned int redIndex = swapRedBlueGains_ ? 2 : 0; const unsigned int greenIndex = 1; const unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2; - for (unsigned int i = 0; i < kRGBLookupSize; i++) { - const RGB rgb = ((RGB(i) - blackIndex) * kRGBLookupSize / div).max(0.0); + const RGB rgb = (gains * (RGB(i) - blackIndex) * kRGBLookupSize / div) + .clamp(0.0, kRGBLookupSize - 1); red[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][0]); red[i].g = std::round(rgb.r() * params.combinedMatrix[greenIndex][0]); red[i].b = std::round(rgb.r() * params.combinedMatrix[blueIndex][0]); @@ -1039,11 +1041,9 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) } } else { if (gammaUpdateNeeded || params.gains != params_.gains) { - auto &gains = params.gains; auto &red = swapRedBlueGains_ ? blue_ : red_; auto &green = green_; auto &blue = swapRedBlueGains_ ? red_ : blue_; - for (unsigned int i = 0; i < kRGBLookupSize; i++) { const RGB lutGains = (gains * (RGB(i) - blackIndex) * gammaTableSize / div) diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 5213166578b6aadc26078443181b96a2c59fa9d5..6ba197966d85e1ab3bdc337b9e4ddeb20f1ec2fa 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -115,6 +115,7 @@ int DebayerEGL::getShaderVariableLocations(void) attributeTexture_ = glGetAttribLocation(programId_, "textureIn"); textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y"); + awbUniformDataIn_ = glGetUniformLocation(programId_, "awb"); ccmUniformDataIn_ = glGetUniformLocation(programId_, "ccm"); blackLevelUniformDataIn_ = glGetUniformLocation(programId_, "blacklevel"); gammaUniformDataIn_ = glGetUniformLocation(programId_, "gamma"); @@ -128,6 +129,7 @@ int DebayerEGL::getShaderVariableLocations(void) LOG(Debayer, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_ << " tex_y " << textureUniformBayerDataIn_ + << " awb " << awbUniformDataIn_ << " ccm " << ccmUniformDataIn_ << " blacklevel " << blackLevelUniformDataIn_ << " gamma " << gammaUniformDataIn_ @@ -504,6 +506,9 @@ void DebayerEGL::setShaderVariableValues(const DebayerParams ¶ms) glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]); LOG(Debayer, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel; + glUniform3f(awbUniformDataIn_, params.gains[0], params.gains[1], params.gains[2]); + LOG(Debayer, Debug) << " awbUniformDataIn_ " << awbUniformDataIn_ << " data " << params.gains; + /* * Gamma */ diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h index fbd5430e4270678cf05d59cc98dff48315d2bb04..4c7c864726b7d5de1e92d4e74b0bc92860dc04c0 100644 --- a/src/libcamera/software_isp/debayer_egl.h +++ b/src/libcamera/software_isp/debayer_egl.h @@ -90,6 +90,9 @@ private: GLint textureUniformBayerDataIn_; + /* Per-frame AWB gains */ + GLint awbUniformDataIn_; + /* Represent per-frame CCM as a uniform vector of floats 3 x 3 */ GLint ccmUniformDataIn_; From patchwork Sat Jun 20 23:00:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 27003 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 20281C3303 for ; Sat, 20 Jun 2026 23:00:50 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D0109656F1; Sun, 21 Jun 2026 01:00:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="hNOYGvmn"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 08420656E3 for ; Sun, 21 Jun 2026 01:00:37 +0200 (CEST) Received: from [192.168.0.240] (cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5331618E5; Sun, 21 Jun 2026 01:00:00 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781996400; bh=/no/cZODy8Va4tcU7NE+8kpiju3sSy4KKajESvyHYLA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=hNOYGvmnk1K/wTNRY6yLDmOzQx7LkmheoPQMsvHeis4VWordHR8Y3W3wsEF42Sti5 Xza2vUjkwLeMLgjy3K9fYDapThI9u9XAtRW3tcVsUQD8+D9uAq1jfGos+ZA9asceNh QpABLyOG3MEigCpSZu77Z7Eff/O4vkDtGTGUv4zM= From: Kieran Bingham Date: Sun, 21 Jun 2026 00:00:34 +0100 Subject: [PATCH 7/7] shaders: bayer: Use native matrix multiplication MIME-Version: 1.0 Message-Id: <20260621-kbingham-awb-saturation-v1-7-b91ea59c6cfb@ideasonboard.com> References: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> In-Reply-To: <20260621-kbingham-awb-saturation-v1-0-b91ea59c6cfb@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1781996434; l=6299; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=/no/cZODy8Va4tcU7NE+8kpiju3sSy4KKajESvyHYLA=; b=TQyiWYqBzG73gnXB6atj42B8Z78pCJk6BbEPzk3k+IDoJAUDIsS4H9/QtSf8xth6u/Z3aODG3 07D7nfZx3BKAucGMglLJDHga3TF247rWNXbBgoS2BTVx2cdJwjILBH1 X-Developer-Key: i=kieran.bingham@ideasonboard.com; a=ed25519; pk=IOxS2C6nWHNjLfkDR71Iesk904i6wJDfEERqV7hDBdY= 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" Remove the open coded matrix multiplication as glsl can perform this directly. Adapt the uniform ccmUniformDataIn_ to ensure we correctly upload the matrix in row major ordering. Signed-off-by: Kieran Bingham --- src/libcamera/shaders/bayer_1x_packed.frag | 45 ++---------------------------- src/libcamera/shaders/bayer_unpacked.frag | 45 ++---------------------------- src/libcamera/software_isp/debayer_egl.cpp | 13 +-------- 3 files changed, 5 insertions(+), 98 deletions(-) diff --git a/src/libcamera/shaders/bayer_1x_packed.frag b/src/libcamera/shaders/bayer_1x_packed.frag index 6b3f7532c177f277e43c27e498dfadcb9cd4e26c..0b641a5f35c53a20811315c4e5def18f301cae69 100644 --- a/src/libcamera/shaders/bayer_1x_packed.frag +++ b/src/libcamera/shaders/bayer_1x_packed.frag @@ -235,49 +235,8 @@ void main(void) /* Apply AWB gains, and saturate each channel at sensor range */ rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0)); - /* - * CCM is a 3x3 in the format - * - * +--------------+----------------+---------------+ - * | RedRedGain | RedGreenGain | RedBlueGain | - * +--------------+----------------+---------------+ - * | GreenRedGain | GreenGreenGain | GreenBlueGain | - * +--------------+----------------+---------------+ - * | BlueRedGain | BlueGreenGain | BlueBlueGain | - * +--------------+----------------+---------------+ - * - * Rout = RedRedGain * Rin + RedGreenGain * Gin + RedBlueGain * Bin - * Gout = GreenRedGain * Rin + GreenGreenGain * Gin + GreenBlueGain * Bin - * Bout = BlueRedGain * Rin + BlueGreenGain * Gin + BlueBlueGain * Bin - * - * We upload to the GPU without transposition glUniformMatrix3f(.., .., GL_FALSE, ccm); - * - * CPU - * float ccm [] = { - * RedRedGain, RedGreenGain, RedBlueGain, - * GreenRedGain, GreenGreenGain, GreenBlueGain, - * BlueRedGain, BlueGreenGain, BlueBlueGain, - * }; - * - * GPU - * ccm = { - * RedRedGain, GreenRedGain, BlueRedGain, - * RedGreenGain, GreenGreenGain, BlueGreenGain, - * RedBlueGain, GreenBlueGain, BlueBlueGain, - * } - * - * However the indexing for the mat data-type is column major hence - * ccm[0][0] = RedRedGain, ccm[0][1] = RedGreenGain, ccm[0][2] = RedBlueGain - * - */ - float rin, gin, bin; - rin = rgb.r; - gin = rgb.g; - bin = rgb.b; - - rgb.r = (rin * ccm[0][0]) + (gin * ccm[0][1]) + (bin * ccm[0][2]); - rgb.g = (rin * ccm[1][0]) + (gin * ccm[1][1]) + (bin * ccm[1][2]); - rgb.b = (rin * ccm[2][0]) + (gin * ccm[2][1]) + (bin * ccm[2][2]); + /* Colour Correction Matrix */ + rgb = ccm * rgb; /* * Contrast diff --git a/src/libcamera/shaders/bayer_unpacked.frag b/src/libcamera/shaders/bayer_unpacked.frag index 44535312125d0e7e5cca372f4e8e0f8ad9fce8b3..10c5e941b67c6409312d4091e54d87c1037fae32 100644 --- a/src/libcamera/shaders/bayer_unpacked.frag +++ b/src/libcamera/shaders/bayer_unpacked.frag @@ -138,49 +138,8 @@ void main(void) { /* Apply AWB gains, and saturate each channel at sensor range */ rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0)); - /* - * CCM is a 3x3 in the format - * - * +--------------+----------------+---------------+ - * | RedRedGain | RedGreenGain | RedBlueGain | - * +--------------+----------------+---------------+ - * | GreenRedGain | GreenGreenGain | GreenBlueGain | - * +--------------+----------------+---------------+ - * | BlueRedGain | BlueGreenGain | BlueBlueGain | - * +--------------+----------------+---------------+ - * - * Rout = RedRedGain * Rin + RedGreenGain * Gin + RedBlueGain * Bin - * Gout = GreenRedGain * Rin + GreenGreenGain * Gin + GreenBlueGain * Bin - * Bout = BlueRedGain * Rin + BlueGreenGain * Gin + BlueBlueGain * Bin - * - * We upload to the GPU without transposition glUniformMatrix3f(.., .., GL_FALSE, ccm); - * - * CPU - * float ccm [] = { - * RedRedGain, RedGreenGain, RedBlueGain, - * GreenRedGain, GreenGreenGain, GreenBlueGain, - * BlueRedGain, BlueGreenGain, BlueBlueGain, - * }; - * - * GPU - * ccm = { - * RedRedGain, GreenRedGain, BlueRedGain, - * RedGreenGain, GreenGreenGain, BlueGreenGain, - * RedBlueGain, GreenBlueGain, BlueBlueGain, - * } - * - * However the indexing for the mat data-type is column major hence - * ccm[0][0] = RedRedGain, ccm[0][1] = RedGreenGain, ccm[0][2] = RedBlueGain - * - */ - float rin, gin, bin; - rin = rgb.r; - gin = rgb.g; - bin = rgb.b; - - rgb.r = (rin * ccm[0][0]) + (gin * ccm[0][1]) + (bin * ccm[0][2]); - rgb.g = (rin * ccm[1][0]) + (gin * ccm[1][1]) + (bin * ccm[1][2]); - rgb.b = (rin * ccm[2][0]) + (gin * ccm[2][1]) + (bin * ccm[2][2]); + /* Colour Correction Matrix */ + rgb = ccm * rgb; /* * Contrast diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 6ba197966d85e1ab3bdc337b9e4ddeb20f1ec2fa..0ec2a98cfafbf87f4de8f160b7b779f61943d287 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -486,18 +486,7 @@ void DebayerEGL::setShaderVariableValues(const DebayerParams ¶ms) << " textureUniformStrideFactor_ " << Stride << " textureUniformProjMatrix_ " << textureUniformProjMatrix_; - GLfloat ccm[9] = { - params.combinedMatrix[0][0], - params.combinedMatrix[0][1], - params.combinedMatrix[0][2], - params.combinedMatrix[1][0], - params.combinedMatrix[1][1], - params.combinedMatrix[1][2], - params.combinedMatrix[2][0], - params.combinedMatrix[2][1], - params.combinedMatrix[2][2], - }; - glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm); + glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_TRUE, params.combinedMatrix.data().data()); LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; /*