From patchwork Tue Apr 7 22:01:04 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26484 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 77517BDCBD for ; Tue, 7 Apr 2026 22:02:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9105962D66; Wed, 8 Apr 2026 00:02:44 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nfUN0mOK"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E855562CF8 for ; Wed, 8 Apr 2026 00:02:40 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1411C6A6; Wed, 8 Apr 2026 00:01:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599273; bh=oE3seNGpVKrTZwrJbFVl1Kzr/R9vtqYUaf0XvehVwrk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=nfUN0mOKd7g9xsyN/CnRHtXTZ73qsL3MBt55n1vacQXnuO1UF+XJ4DWBVCDD8oxMq GA6301xqBttOXZWVii4MhkfkTBX6gaPLlRR6OvsMgyYZX0R9mZiVCRJF5BImSzUU9k Iz560a8vpBTs1LMYAuPcEtFFm9rJnRLhUNXKkCVo= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:04 +0100 Subject: [PATCH 01/13] libcamera: shaders: unpacked: Fix indentations MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-1-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599359; l=11533; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=oE3seNGpVKrTZwrJbFVl1Kzr/R9vtqYUaf0XvehVwrk=; b=h0BSXZwD51H236n8YlJbmBFC/LjIo2JtKDbT7Sbi5/aAH9G+BoQRJd8Shn64l23FIlqX9LYNn kYe/eeH0lqMBj/zXSsl2jCYch4omwTlaB/OONdg7NAdWhDVmrzUAxls 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" Reflow the unpacked bayer shader to use tabs for indentation and match the style of the bayer_packed fragment shader. Signed-off-by: Kieran Bingham Reviewed-by: Laurent Pinchart --- src/libcamera/shaders/bayer_unpacked.frag | 264 +++++++++++++++--------------- 1 file changed, 132 insertions(+), 132 deletions(-) diff --git a/src/libcamera/shaders/bayer_unpacked.frag b/src/libcamera/shaders/bayer_unpacked.frag index 1b85196ae16130670eb3d1c077ab4884119ae63c..76ffc47a8a29f242c1fba88f32bd8db731edeee0 100644 --- a/src/libcamera/shaders/bayer_unpacked.frag +++ b/src/libcamera/shaders/bayer_unpacked.frag @@ -31,163 +31,163 @@ uniform float contrastExp; float apply_contrast(float value) { - // Apply simple S-curve - 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); + // Apply simple S-curve + 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) { - vec3 rgb; + vec3 rgb; - #if defined(RAW10P) - #define pixel(p) p.r / 4.0 + p.g * 64.0 - #define fetch(x, y) pixel(texture2D(tex_y, vec2(x, y))) - #elif defined(RAW12P) - #define pixel(p) p.r / 16.0 + p.g * 16.0 - #define fetch(x, y) pixel(texture2D(tex_y, vec2(x, y))) - #else - #define fetch(x, y) texture2D(tex_y, vec2(x, y)).r - #endif + #if defined(RAW10P) + #define pixel(p) p.r / 4.0 + p.g * 64.0 + #define fetch(x, y) pixel(texture2D(tex_y, vec2(x, y))) + #elif defined(RAW12P) + #define pixel(p) p.r / 16.0 + p.g * 16.0 + #define fetch(x, y) pixel(texture2D(tex_y, vec2(x, y))) + #else + #define fetch(x, y) texture2D(tex_y, vec2(x, y)).r + #endif - float C = fetch(center.x, center.y); // ( 0, 0) - const vec4 kC = vec4( 4.0, 6.0, 5.0, 5.0) / 8.0; + float C = fetch(center.x, center.y); // ( 0, 0) + const vec4 kC = vec4( 4.0, 6.0, 5.0, 5.0) / 8.0; - // Determine which of four types of pixels we are on. - vec2 alternate = mod(floor(center.zw), 2.0); + // Determine which of four types of pixels we are on. + vec2 alternate = mod(floor(center.zw), 2.0); - vec4 Dvec = vec4( - fetch(xCoord[1], yCoord[1]), // (-1,-1) - fetch(xCoord[1], yCoord[2]), // (-1, 1) - fetch(xCoord[2], yCoord[1]), // ( 1,-1) - fetch(xCoord[2], yCoord[2])); // ( 1, 1) + vec4 Dvec = vec4( + fetch(xCoord[1], yCoord[1]), // (-1,-1) + fetch(xCoord[1], yCoord[2]), // (-1, 1) + fetch(xCoord[2], yCoord[1]), // ( 1,-1) + fetch(xCoord[2], yCoord[2])); // ( 1, 1) - vec4 PATTERN = (kC.xyz * C).xyzz; + vec4 PATTERN = (kC.xyz * C).xyzz; - // Can also be a dot product with (1,1,1,1) on hardware where that is - // specially optimized. - // Equivalent to: D = Dvec[0] + Dvec[1] + Dvec[2] + Dvec[3]; - Dvec.xy += Dvec.zw; - Dvec.x += Dvec.y; + // Can also be a dot product with (1,1,1,1) on hardware where that is + // specially optimized. + // Equivalent to: D = Dvec[0] + Dvec[1] + Dvec[2] + Dvec[3]; + Dvec.xy += Dvec.zw; + Dvec.x += Dvec.y; - vec4 value = vec4( - fetch(center.x, yCoord[0]), // ( 0,-2) - fetch(center.x, yCoord[1]), // ( 0,-1) - fetch(xCoord[0], center.y), // (-2, 0) - fetch(xCoord[1], center.y)); // (-1, 0) + vec4 value = vec4( + fetch(center.x, yCoord[0]), // ( 0,-2) + fetch(center.x, yCoord[1]), // ( 0,-1) + fetch(xCoord[0], center.y), // (-2, 0) + fetch(xCoord[1], center.y)); // (-1, 0) - vec4 temp = vec4( - fetch(center.x, yCoord[3]), // ( 0, 2) - fetch(center.x, yCoord[2]), // ( 0, 1) - fetch(xCoord[3], center.y), // ( 2, 0) - fetch(xCoord[2], center.y)); // ( 1, 0) + vec4 temp = vec4( + fetch(center.x, yCoord[3]), // ( 0, 2) + fetch(center.x, yCoord[2]), // ( 0, 1) + fetch(xCoord[3], center.y), // ( 2, 0) + fetch(xCoord[2], center.y)); // ( 1, 0) - // Even the simplest compilers should be able to constant-fold these to - // avoid the division. - // Note that on scalar processors these constants force computation of some - // identical products twice. - const vec4 kA = vec4(-1.0, -1.5, 0.5, -1.0) / 8.0; - const vec4 kB = vec4( 2.0, 0.0, 0.0, 4.0) / 8.0; - const vec4 kD = vec4( 0.0, 2.0, -1.0, -1.0) / 8.0; + // Even the simplest compilers should be able to constant-fold these to + // avoid the division. + // Note that on scalar processors these constants force computation of some + // identical products twice. + const vec4 kA = vec4(-1.0, -1.5, 0.5, -1.0) / 8.0; + const vec4 kB = vec4( 2.0, 0.0, 0.0, 4.0) / 8.0; + const vec4 kD = vec4( 0.0, 2.0, -1.0, -1.0) / 8.0; - // Conserve constant registers and take advantage of free swizzle on load - #define kE (kA.xywz) - #define kF (kB.xywz) + // Conserve constant registers and take advantage of free swizzle on load + #define kE (kA.xywz) + #define kF (kB.xywz) - value += temp; + value += temp; - // There are five filter patterns (identity, cross, checker, - // theta, phi). Precompute the terms from all of them and then - // use swizzles to assign to color channels. - // - // Channel Matches - // x cross (e.g., EE G) - // y checker (e.g., EE B) - // z theta (e.g., EO R) - // w phi (e.g., EO R) - #define A (value[0]) - #define B (value[1]) - #define D (Dvec.x) - #define E (value[2]) - #define F (value[3]) + // There are five filter patterns (identity, cross, checker, + // theta, phi). Precompute the terms from all of them and then + // use swizzles to assign to color channels. + // + // Channel Matches + // x cross (e.g., EE G) + // y checker (e.g., EE B) + // z theta (e.g., EO R) + // w phi (e.g., EO R) + #define A (value[0]) + #define B (value[1]) + #define D (Dvec.x) + #define E (value[2]) + #define F (value[3]) - // Avoid zero elements. On a scalar processor this saves two MADDs - // and it has no effect on a vector processor. - PATTERN.yzw += (kD.yz * D).xyy; + // Avoid zero elements. On a scalar processor this saves two MADDs + // and it has no effect on a vector processor. + PATTERN.yzw += (kD.yz * D).xyy; - PATTERN += (kA.xyz * A).xyzx + (kE.xyw * E).xyxz; - PATTERN.xw += kB.xw * B; - PATTERN.xz += kF.xz * F; + PATTERN += (kA.xyz * A).xyzx + (kE.xyw * E).xyxz; + PATTERN.xw += kB.xw * B; + PATTERN.xz += kF.xz * F; - rgb = (alternate.y == 0.0) ? - ((alternate.x == 0.0) ? - vec3(C, PATTERN.xy) : - vec3(PATTERN.z, C, PATTERN.w)) : - ((alternate.x == 0.0) ? - vec3(PATTERN.w, C, PATTERN.z) : - vec3(PATTERN.yx, C)); + rgb = (alternate.y == 0.0) ? + ((alternate.x == 0.0) ? + vec3(C, PATTERN.xy) : + vec3(PATTERN.z, C, PATTERN.w)) : + ((alternate.x == 0.0) ? + vec3(PATTERN.w, C, PATTERN.z) : + vec3(PATTERN.yx, C)); - rgb = rgb - blacklevel; + rgb = rgb - blacklevel; - /* - * 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; + /* + * 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]); + 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]); - /* - * Contrast - */ - 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); + /* + * Contrast + */ + 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); - /* Apply gamma after colour correction */ - rgb = pow(rgb, vec3(gamma)); + /* Apply gamma after colour correction */ + rgb = pow(rgb, vec3(gamma)); #if defined (SWAP_BLUE) - gl_FragColor = vec4(rgb.bgr, 1.0); + gl_FragColor = vec4(rgb.bgr, 1.0); #else - gl_FragColor = vec4(rgb, 1.0); + gl_FragColor = vec4(rgb, 1.0); #endif } From patchwork Tue Apr 7 22:01:05 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26485 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 1294FBDCBD for ; Tue, 7 Apr 2026 22:02:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 17EE762DB3; Wed, 8 Apr 2026 00:02:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="plrZJoD3"; 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 1D4EA62D4E for ; Wed, 8 Apr 2026 00:02:41 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5A63E78E; Wed, 8 Apr 2026 00:01:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599273; bh=GZHQq9+jIh6UviRqwn4nW5G/p4P5DRND9QKZt5BZ/+w=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=plrZJoD3fsPnL6VSAjRJ/jqAUpDrjA/OHhNNZy5gipzjydCo6cBqqSGss80TzWRIH BjijU0c9Q8mOtICc/ddHpKOokjm/QAfAFSXSKWOy5xz1G7A2dqu0Hm64L4wjMFc755 HQvZF5F1bPK+a9fXyoUcjSTTFY6xyz55/NU/Mlw0= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:05 +0100 Subject: [PATCH 02/13] softisp: Split AWB from Combined Matrix MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-2-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599359; l=6147; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=GZHQq9+jIh6UviRqwn4nW5G/p4P5DRND9QKZt5BZ/+w=; b=0JrTVKw8nf+CrHPNW0JzPARMPLZ6C5HvVQY/TiOndT0yJgS38Y07ySr6l8gRrL88Or+iWG2CS tT+9pZSN0wxA0X6FG6qt6DYpSqMG7KYn1FGp7U0ZdGaXk55f0Jq5g4M 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" Move the AWB gains out of the combined matrix and pass them directly to the EGL shaders. 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_egl.cpp | 5 +++++ src/libcamera/software_isp/debayer_egl.h | 3 +++ 6 files changed, 20 insertions(+), 17 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 23747f78a6313503a46b61ed5bae6e7c178c5745..9a1992e219dd066945b3f46ec509f47a31590385 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; @@ -227,6 +228,9 @@ void main(void) rgb = rgb - blacklevel; + /* Apply AWB gains, and saturate each channel at sensor range */ + rgb = clamp(rgb * awb, vec3(0.0), vec3(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 76ffc47a8a29f242c1fba88f32bd8db731edeee0..87e4e4915fe19679943fdcc3d213a0224b89065e 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; @@ -130,6 +131,9 @@ void main(void) { rgb = rgb - blacklevel; + /* Apply AWB gains, and saturate each channel at sensor range */ + rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0) - blacklevel); + /* * CCM is a 3x3 in the format * diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 2ad258bca69fb8dff19e35d9239ebd7d350590ae..738036e649224f370bdf9a0cc59b399f8b1066de 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -100,6 +100,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"); @@ -113,6 +114,7 @@ int DebayerEGL::getShaderVariableLocations(void) LOG(Debayer, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_ << " tex_y " << textureUniformBayerDataIn_ + << " awb " << awbUniformDataIn_ << " ccm " << ccmUniformDataIn_ << " blacklevel " << blackLevelUniformDataIn_ << " gamma " << gammaUniformDataIn_ @@ -481,6 +483,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 bdde676f2394e7298006c88bbec75917800af4ad..23ef99117fbd209246ae565141a2365c6a7b0a63 100644 --- a/src/libcamera/software_isp/debayer_egl.h +++ b/src/libcamera/software_isp/debayer_egl.h @@ -97,6 +97,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 Tue Apr 7 22:01:06 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26486 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 2299DBDCBD for ; Tue, 7 Apr 2026 22:02:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id EC80C62DD2; Wed, 8 Apr 2026 00:02:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Cvu7Co7p"; 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 5CC7A62D66 for ; Wed, 8 Apr 2026 00:02:41 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A037F1C25; Wed, 8 Apr 2026 00:01:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599273; bh=bY67C6uJP8ZsZbZQ9qGjWmYA523zk4Q6DpGuKKMIBSs=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Cvu7Co7pFaVmjRwO1K5a+ZdV5/vMWFR3+10IWGIBpdApCFy1HYI/qy79L4NXhNs25 RvH4lSwByAAC0JiCFZ6T2XHA1c+N43E7h4RLqmQHoa4kFqtecy2riz3KgrK0ICQh10 Y6b6KJx8A3+0I1JRz7hgB5GhskhArC6OuVd1+FsA= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:06 +0100 Subject: [PATCH 03/13] libcamera: software_isp: Fix black level handling in CPU ISP MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-3-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599359; l=6327; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=J/NGwZlg7OEyKZmBQ8iQMgpXJzyjsJs2TFIf1nVIDU8=; b=lTGEh4dP6hVFLsdZ3k99gmJ0DVtzp9WZI7s4RG3EB8OPfaKZ50ZlghugbECadLtXYKlrHZVag Vfr41dYU7xFDd0m+QLv+vNLhSmxw/uHFlShdNYCDIbjSLRrPA43vPIo 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. Signed-off-by: Milan Zamazal --- src/libcamera/software_isp/debayer_cpu.cpp | 60 +++++++++++++++--------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp index dd0fff8714144417467bac16a1055d1185782d14..5356d6bbec11c30fa0b05659d44a91e69e2b79d0 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -879,15 +879,12 @@ 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 (unsigned int i = 0; i < gammaTable_.size(); i++) { + 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) @@ -897,14 +894,6 @@ void DebayerCpu::updateGammaTable(const DebayerParams ¶ms) gammaTable_[i] = 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) @@ -916,11 +905,13 @@ 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; + RGB blackIndex = params.blackLevel * kRGBLookupSize; + if (ccmEnabled_) { if (gammaUpdateNeeded || matrixChanged(params.combinedMatrix, params_.combinedMatrix)) { @@ -930,17 +921,21 @@ 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; + const RGB div = + (RGB(kRGBLookupSize) - blackIndex).max(1.0) / + kRGBLookupSize; 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) / div).max(0.0); + red[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][0]); + red[i].g = std::round(rgb.g() * params.combinedMatrix[greenIndex][0]); + red[i].b = std::round(rgb.b() * params.combinedMatrix[blueIndex][0]); + green[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][1]); + green[i].g = std::round(rgb.g() * params.combinedMatrix[greenIndex][1]); + green[i].b = std::round(rgb.b() * params.combinedMatrix[blueIndex][1]); + blue[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][2]); + blue[i].g = std::round(rgb.g() * params.combinedMatrix[greenIndex][2]); + blue[i].b = std::round(rgb.b() * params.combinedMatrix[blueIndex][2]); + gammaLut_[i] = gammaTable_[i * gammaTableSize / kRGBLookupSize]; } } } else { @@ -949,12 +944,17 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) auto &red = swapRedBlueGains_ ? blue_ : red_; auto &green = green_; auto &blue = swapRedBlueGains_ ? red_ : blue_; + const RGB div = + (RGB(kRGBLookupSize) - blackIndex).max(1.0) / + gammaTableSize; 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) / div) + .min(gammaTableSize - 1) + .max(0.0); + red[i] = gammaTable_[lutGains.r()]; + green[i] = gammaTable_[lutGains.g()]; + blue[i] = gammaTable_[lutGains.b()]; } } } From patchwork Tue Apr 7 22:01:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26487 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 A2C2BC32BB for ; Tue, 7 Apr 2026 22:02:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 072C862DDA; Wed, 8 Apr 2026 00:02:49 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="P093AwE+"; 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 9DF0662CEB for ; Wed, 8 Apr 2026 00:02:41 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E5A371C3F; Wed, 8 Apr 2026 00:01:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599274; bh=iZAHoQLS2AsifD7XmJAW9+6UedqcEBhtvRK5onvL/+M=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=P093AwE+OoflH4csdp5ywjudXoK++JZFCDLxpzGylT82YcGMqNzFO8vp8q08XIyb7 seAs/mnJzHrBpuHJCysUwkQ8XmQBuctNOvAFTNCcvBFmv+1lyGzu4OgxYDG3mCL8Ik ycB9tHDElUDt6pTTusQxxxubYN/mNgNoiCiQTukk= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:07 +0100 Subject: [PATCH 04/13] libcamera: software_isp: Apply gains in CPU ISP MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-4-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599359; l=4292; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=/qHwlJH4HM+aP5KdyZDAbML9TLl/S2aWLr0jTOIFApY=; b=t94QFNq9U/XuWPGm+5FO2FgHBHXHcSlYLGZ4ZRYQezwUA9X1L2Zv56bppl8BVYHmcqFdKq7qu mRaiRoIbEKSATVmR3gCyW2Rrx8zO/fJAnDBXJOHAZeD0fmRh8eRVsTK 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 One of the preceding patches removed white balance gains from the combined matrix. Which is correct but the gains are not applied now in CPU ISP when a CCM is used. This patch applies the gains in the CCM table. It also clamps the pixel values if they are out of range after applying the gains. Signed-off-by: Milan Zamazal --- src/libcamera/software_isp/debayer_cpu.cpp | 45 +++++++++++++++--------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp index 5356d6bbec11c30fa0b05659d44a91e69e2b79d0..2dc0df5597938cc19317187f7b1ed8a10b1cc111 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -911,45 +911,46 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) }; const unsigned int gammaTableSize = gammaTable_.size(); RGB blackIndex = params.blackLevel * kRGBLookupSize; + 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; - const RGB div = - (RGB(kRGBLookupSize) - blackIndex).max(1.0) / - kRGBLookupSize; for (unsigned int i = 0; i < kRGBLookupSize; i++) { - const RGB rgb = ((RGB(i) - blackIndex) / div).max(0.0); - red[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][0]); - red[i].g = std::round(rgb.g() * params.combinedMatrix[greenIndex][0]); - red[i].b = std::round(rgb.b() * params.combinedMatrix[blueIndex][0]); - green[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][1]); - green[i].g = std::round(rgb.g() * params.combinedMatrix[greenIndex][1]); - green[i].b = std::round(rgb.b() * params.combinedMatrix[blueIndex][1]); - blue[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][2]); - blue[i].g = std::round(rgb.g() * params.combinedMatrix[greenIndex][2]); - blue[i].b = std::round(rgb.b() * params.combinedMatrix[blueIndex][2]); + const RGB rgb = + (gains * (RGB(i) - blackIndex) * kRGBLookupSize / div) + .min(kRGBLookupSize - 1) + .max(0.0); + red[i].r = std::round(rgb.r() * params.combinedMatrix[0][0]); + red[i].g = std::round(rgb.g() * params.combinedMatrix[1][0]); + red[i].b = std::round(rgb.b() * params.combinedMatrix[2][0]); + green[i].r = std::round(rgb.r() * params.combinedMatrix[0][1]); + green[i].g = std::round(rgb.g() * params.combinedMatrix[1][1]); + green[i].b = std::round(rgb.b() * params.combinedMatrix[2][1]); + blue[i].r = std::round(rgb.r() * params.combinedMatrix[0][2]); + blue[i].g = std::round(rgb.g() * params.combinedMatrix[1][2]); + blue[i].b = std::round(rgb.b() * params.combinedMatrix[2][2]); + if (swapRedBlueGains_) { + std::swap(red[i].r, red[i].b); + std::swap(green[i].r, green[i].b); + std::swap(blue[i].r, blue[i].b); + } gammaLut_[i] = gammaTable_[i * gammaTableSize / kRGBLookupSize]; } } } else { if (gammaUpdateNeeded || params.gains != params_.gains) { - auto &gains = params.gains; auto &red = swapRedBlueGains_ ? blue_ : red_; auto &green = green_; auto &blue = swapRedBlueGains_ ? red_ : blue_; - const RGB div = - (RGB(kRGBLookupSize) - blackIndex).max(1.0) / - gammaTableSize; for (unsigned int i = 0; i < kRGBLookupSize; i++) { const RGB lutGains = - (gains * (RGB(i) - blackIndex) / div) + (gains * (RGB(i) - blackIndex) * gammaTableSize / div) .min(gammaTableSize - 1) .max(0.0); red[i] = gammaTable_[lutGains.r()]; From patchwork Tue Apr 7 22:01:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26488 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 84A40BDCBD for ; Tue, 7 Apr 2026 22:02:58 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F2A7B62DBE; Wed, 8 Apr 2026 00:02:49 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="bCK0dQ7P"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E1D8C62D9D for ; Wed, 8 Apr 2026 00:02:41 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 39CB61C4D; Wed, 8 Apr 2026 00:01:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599274; bh=pnU+rcO/VXxf1fNpcS7C00kKk27svJxN5ZJorFc2y74=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=bCK0dQ7P/qqqvn/4jPlBUSjSIEkqS3MhTdurZXEm1K/xhLiAVD8W5UCwGHsSI9QQa NR7bRhGEITI43UnrJNzRuy8nKO7zXT09KDK9PmrJd5O7Nqrt9AtY2lydaA0acCTdU6 xEDWIjnU46d7hqkJxTVuN/bZ+r93I8yaCtsIZTDg= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:08 +0100 Subject: [PATCH 05/13] shaders: bayer: Use native matrix multiplication MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-5-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599359; l=6348; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=pnU+rcO/VXxf1fNpcS7C00kKk27svJxN5ZJorFc2y74=; b=VtoBd1eV/n9TUeovxEFRzQU6Tto//wqicGKX2OPhjlwK4N9SRcAOiwPV0X21cVYs18Ialu7E8 8coyDLAwuXcDSV++eMBwPNy1374C2LpIqpT73hi23xGavLJTdWkfJx6 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 | 44 ++--------------------------- src/libcamera/software_isp/debayer_egl.cpp | 12 ++------ 3 files changed, 7 insertions(+), 94 deletions(-) diff --git a/src/libcamera/shaders/bayer_1x_packed.frag b/src/libcamera/shaders/bayer_1x_packed.frag index 9a1992e219dd066945b3f46ec509f47a31590385..57f0be9b264bdbdf523291dc9b35ab5fd6c9ef6a 100644 --- a/src/libcamera/shaders/bayer_1x_packed.frag +++ b/src/libcamera/shaders/bayer_1x_packed.frag @@ -231,49 +231,8 @@ void main(void) /* Apply AWB gains, and saturate each channel at sensor range */ rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0) - blacklevel); - /* - * 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]); + /* Row major Colour Correction Matrix multiplication */ + rgb = ccm * rgb; /* * Contrast diff --git a/src/libcamera/shaders/bayer_unpacked.frag b/src/libcamera/shaders/bayer_unpacked.frag index 87e4e4915fe19679943fdcc3d213a0224b89065e..52de0a7901cfac33dbc7b306f1840ec0048cd8ea 100644 --- a/src/libcamera/shaders/bayer_unpacked.frag +++ b/src/libcamera/shaders/bayer_unpacked.frag @@ -134,49 +134,9 @@ void main(void) { /* Apply AWB gains, and saturate each channel at sensor range */ rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0) - blacklevel); - /* - * 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]); + /* Row major Colour Correction Matrix multiplication */ + rgb = ccm * rgb; /* * Contrast diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 738036e649224f370bdf9a0cc59b399f8b1066de..1022f595cc71751cd0c9133b3e5755f591224dc4 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -464,15 +464,9 @@ void DebayerEGL::setShaderVariableValues(const DebayerParams ¶ms) << " 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], + params.combinedMatrix[0][0], params.combinedMatrix[1][0], params.combinedMatrix[2][0], + params.combinedMatrix[0][1], params.combinedMatrix[1][1], params.combinedMatrix[2][1], + params.combinedMatrix[0][2], params.combinedMatrix[1][2], params.combinedMatrix[2][2], }; glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm); LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; From patchwork Tue Apr 7 22:01:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26489 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 75F6DC32F5 for ; Tue, 7 Apr 2026 22:02:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E8DD562DEC; Wed, 8 Apr 2026 00:02:50 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="bgERZKK4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2B24B62DA0 for ; Wed, 8 Apr 2026 00:02:42 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 78A296A6; Wed, 8 Apr 2026 00:01:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599274; bh=PBHUldN1WdxIrokwZoMzPpOP155RcwSuQlPOXE1uJ0A=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=bgERZKK4Nhw9Sy89KtooRcd7pA+3IBHjr1S8TUtIBACCOZkj/SM4Ug1saw/hXUfQp pd5cvjc7x3m8cSdrOiFhCFmavwyWTssXRhsYDfiMdLcaKGjsiV6MXHnq8vXkvld1cO aHzxIljVtAHBVnOZsW3DVxRUXx+MQ3ho0HLt3egA= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:09 +0100 Subject: [PATCH 06/13] ipa: libipa: provide lux context structures MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-6-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599360; l=2818; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=PBHUldN1WdxIrokwZoMzPpOP155RcwSuQlPOXE1uJ0A=; b=b3vzdNwxd8c+kUZvqQSG79kZmYvRoBk3WPgYoPJT7VwsA/KXGINOZd48fKjVjzcqwGiJqGQdX bkxxcf9bphUCCdiLbkGj1SAmdfRcNef5DZMK/plTYOJTpPUN24tiayr 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" Move the ActiveState and FrameContext as used by the RKISP1 lux to be exposed by the libipa implementation for re-use. Signed-off-by: Kieran Bingham --- src/ipa/libipa/lux.cpp | 16 ++++++++++++++++ src/ipa/libipa/lux.h | 12 ++++++++++++ src/ipa/rkisp1/ipa_context.h | 13 +++++-------- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/ipa/libipa/lux.cpp b/src/ipa/libipa/lux.cpp index 899e88248f04fbe0d6986528c81b468384fe4a9f..012c6fb367142ce40f60f5e102e3addb142c4a6b 100644 --- a/src/ipa/libipa/lux.cpp +++ b/src/ipa/libipa/lux.cpp @@ -33,6 +33,22 @@ LOG_DEFINE_CATEGORY(Lux) namespace ipa { +/** + * \struct lux::ActiveState + * \brief Active lux estimation state shared across frames + * + * \var lux::ActiveState::lux + * \brief The most recently estimated lux value + */ + +/** + * \struct lux::FrameContext + * \brief Per-frame lux estimation context + * + * \var lux::FrameContext::lux + * \brief The lux value estimation used for processing the frame + */ + /** * \class Lux * \brief Class that implements lux estimation diff --git a/src/ipa/libipa/lux.h b/src/ipa/libipa/lux.h index d95bcdafcfcdb44641ddb12306e4705157dc09ae..2aa630a1097e4bc672c792395d2d179dc084c7d8 100644 --- a/src/ipa/libipa/lux.h +++ b/src/ipa/libipa/lux.h @@ -16,6 +16,18 @@ class YamlObject; namespace ipa { +namespace lux { + +struct ActiveState { + double lux; +}; + +struct FrameContext { + double lux; +}; + +} /* namespace lux */ + class Histogram; class Lux diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index e61391bb1510bb642dafc544ee7775390e6948d6..63a6f2b7d884bfff2553175fa32e45ae747fb4c7 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -28,6 +28,7 @@ #include "libipa/camera_sensor_helper.h" #include "libipa/fc_queue.h" #include "libipa/fixedpoint.h" +#include "libipa/lux.h" namespace libcamera { @@ -78,6 +79,8 @@ struct IPASessionConfiguration { }; struct IPAActiveState { + ipa::lux::ActiveState lux; + struct { struct { uint32_t exposure; @@ -137,10 +140,6 @@ struct IPAActiveState { double gamma; } goc; - struct { - double lux; - } lux; - struct { controls::WdrModeEnum mode; AgcMeanLuminance::AgcConstraint constraint; @@ -154,6 +153,8 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { + ipa::lux::FrameContext lux; + struct { uint32_t exposure; double gain; @@ -219,10 +220,6 @@ struct IPAFrameContext : public FrameContext { Matrix ccm; } ccm; - struct { - double lux; - } lux; - struct { controls::WdrModeEnum mode; double strength; From patchwork Tue Apr 7 22:01:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26491 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 97132C32F6 for ; Tue, 7 Apr 2026 22:03:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0D9C062D4E; Wed, 8 Apr 2026 00:02:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gNsNm7zZ"; 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 67FBA62D9C for ; Wed, 8 Apr 2026 00:02:42 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id BA9402443; Wed, 8 Apr 2026 00:01:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599274; bh=ZAvcYWuGTp2ILpHWc5OXx7gFaN9n1H2xKSndLRp6N8w=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=gNsNm7zZCcidMG0JRkB5Wg4yr+eWoVThem2hzOJWp2rIWpuLDEgQbuIR3SBbh5gQz a6SuEvQVIXIjZLhtWqTj1FY1pFWzHFH6fvfDNNpz+KTOjNupPnFnr3uCqNE336QxM1 R8maoW+pniHzeK7PUw47QS/OPIaqy0iV2BvFHxa4= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:10 +0100 Subject: [PATCH 07/13] ipa: simple: Add lux algorithm to the SoftIPA MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-7-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599360; l=5240; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=ZAvcYWuGTp2ILpHWc5OXx7gFaN9n1H2xKSndLRp6N8w=; b=0Wa2cG+9ySiJ/oXn6dJAsufAMA2MJZSGg9F0nMRLbZ9Yaf8szKp1O3gcz0bYXalS1aTDSvfqf MOl2YliwjomDt4dfkajOGWmyUz8jcwIHwdQe4TLUlNjrsFfdFapAmZS 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" Utilise the lux component of libipa to map in the lux estimate process using the yHistgram of the Soft ISP statistics. Signed-off-by: Kieran Bingham --- src/ipa/simple/algorithms/lux.cpp | 93 +++++++++++++++++++++++++++++++++++ src/ipa/simple/algorithms/lux.h | 39 +++++++++++++++ src/ipa/simple/algorithms/meson.build | 1 + src/ipa/simple/ipa_context.h | 5 ++ 4 files changed, 138 insertions(+) diff --git a/src/ipa/simple/algorithms/lux.cpp b/src/ipa/simple/algorithms/lux.cpp new file mode 100644 index 0000000000000000000000000000000000000000..03b44ac584ae141509e79954159272504cdba17d --- /dev/null +++ b/src/ipa/simple/algorithms/lux.cpp @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * Simple Lux control + */ + +#include "lux.h" + +#include + +#include + +#include "libipa/histogram.h" +#include "libipa/lux.h" + +/** + * \file lux.h + */ + +namespace libcamera { + +namespace ipa::soft::algorithms { + +/** + * \class Lux + * \brief SoftISP Lux control + * + * The Lux algorithm is responsible for estimating the lux level of the image. + * It doesn't take or generate any controls, but it provides a lux level for + * other algorithms (such as AGC) to use. + */ + +/** + * \brief Construct a SoftISP Lux algo module + */ +Lux::Lux() +{ +} + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Lux::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) +{ + return lux_.parseTuningData(tuningData); +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Lux::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] DebayerParams *params) +{ + frameContext.lux.lux = context.activeState.lux.lux; +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Lux::process(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const SwIspStats *stats, + ControlList &metadata) +{ + /* + * Report the lux level used by algorithms to prepare this frame + * not the lux level *of* this frame. + */ + metadata.set(controls::Lux, frameContext.lux.lux); + + if (!stats) + return; + + /* Todo: Sensor configuration should move out of AGC */ + utils::Duration exposureTime = context.configuration.agc.lineDuration * + frameContext.sensor.exposure; + double gain = frameContext.sensor.gain; + double digitalGain = 1.0; + + Histogram yHist(stats->yHistogram); + + context.activeState.lux.lux = + lux_.estimateLux(exposureTime, gain, digitalGain, yHist); +} + +REGISTER_IPA_ALGORITHM(Lux, "Lux") + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/lux.h b/src/ipa/simple/algorithms/lux.h new file mode 100644 index 0000000000000000000000000000000000000000..04ec4c163ede422369977017cca70d12a8d361fb --- /dev/null +++ b/src/ipa/simple/algorithms/lux.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * Simple Lux control + */ + +#pragma once + +#include + +#include "libipa/lux.h" + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::soft::algorithms { + +class Lux : public Algorithm +{ +public: + Lux(); + + int init(IPAContext &context, const YamlObject &tuningData) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + DebayerParams *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const SwIspStats *stats, + ControlList &metadata) override; + +private: + ipa::Lux lux_; +}; + +} /* namespace ipa::soft::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build index 73c63722083ff92147253c6b10440ce50743988d..27e73c9a0eea2241cc4a4cefd1594c976fb59318 100644 --- a/src/ipa/simple/algorithms/meson.build +++ b/src/ipa/simple/algorithms/meson.build @@ -6,4 +6,5 @@ soft_simple_ipa_algorithms = files([ 'agc.cpp', 'blc.cpp', 'ccm.cpp', + 'lux.cpp', ]) diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 8ccfacb46a59cedb5a0ad051d67f7c1f40af4b52..2bd7c4642b118d7bb94b1b16cdf4ede5fb2554b5 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -17,6 +17,7 @@ #include "libcamera/internal/vector.h" #include +#include #include "core_ipa_interface.h" @@ -36,6 +37,8 @@ struct IPASessionConfiguration { }; struct IPAActiveState { + ipa::lux::ActiveState lux; + struct { int32_t exposure; double again; @@ -64,6 +67,8 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { + ipa::lux::FrameContext lux; + Matrix ccm; struct { From patchwork Tue Apr 7 22:01:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26490 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 1E333C32BB for ; Tue, 7 Apr 2026 22:03:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5674D62DCB; Wed, 8 Apr 2026 00:02:52 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="EQBReuJ0"; 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 AD5B762CF8 for ; Wed, 8 Apr 2026 00:02:42 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 037D224A2; Wed, 8 Apr 2026 00:01:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599275; bh=uE5srgoqwc7FrlNaVkAEvYLUx5VbJENakieXKN38U1c=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=EQBReuJ0zcRSv6laA4r0SM25lDm2Drq5S/GDtPRLrdpOM9QlkncWFXqRakW4Qt/ft ritwBuZAKku1+zYQFIYwxB9ZYWE4LIaHk5s7ONb5+18fy1F0YO47BDWJ1iolRNcqz8 x3zTGXsziMwja7K9vepdTSXj52lWMtXzZkF4LDk4= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:11 +0100 Subject: [PATCH 08/13] ipa: libipa: Awb: Provide common context storage MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-8-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599360; l=4445; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=uE5srgoqwc7FrlNaVkAEvYLUx5VbJENakieXKN38U1c=; b=1Ml/X+qdCuE6CW74C+D9CYmssT0Dkn1wyIu4kJyA2NOO1/yX+5BW5fy1QLxJCiJP27TSmjva6 i8rDBBcPvF8DFwlKlSMZLuUq7xCdvO8bagl+ff7d5c/oF+m7/C1cKDH 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" Expose a common structure for storing Awb Session configuration, Active state and Frame Context data. Signed-off-by: Kieran Bingham --- src/ipa/libipa/awb.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/awb.h | 26 ++++++++++++++++++++++++ src/ipa/rkisp1/ipa_context.h | 28 ++++++++------------------ 3 files changed, 81 insertions(+), 20 deletions(-) diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp index 214bac8b56a6ca7b0ac9954f0aa742cd613e6141..f215bea0b59483eadf95572f073928a4eb1275f4 100644 --- a/src/ipa/libipa/awb.cpp +++ b/src/ipa/libipa/awb.cpp @@ -22,6 +22,53 @@ LOG_DEFINE_CATEGORY(Awb) namespace ipa { +/** + * \struct awb::Session + * \brief Session-wide AWB configuration + * + * \var awb::Session::enabled + * \brief True when AWB processing is enabled for the session + */ + +/** + * \struct awb::ActiveState + * \brief Active AWB state shared across frames + * + * \var awb::ActiveState::manual + * \brief The most recent manually requested AWB state + * + * \var awb::ActiveState::automatic + * \brief The most recent automatically calculated AWB state + * + * \var awb::ActiveState::autoEnabled + * \brief True when automatic AWB is currently selected + */ + +/** + * \struct awb::ActiveState::AwbState + * \brief AWB gains and colour temperature for one operating mode + * + * \var awb::ActiveState::AwbState::gains + * \brief The white balance gains for this AWB state + * + * \var awb::ActiveState::AwbState::temperatureK + * \brief The colour temperature for this AWB state, in Kelvin + */ + +/** + * \struct awb::FrameContext + * \brief Per-frame AWB state applied to a captured frame + * + * \var awb::FrameContext::gains + * \brief The white balance gains applied to the frame + * + * \var awb::FrameContext::autoEnabled + * \brief True when the frame uses automatic AWB + * + * \var awb::FrameContext::temperatureK + * \brief The colour temperature used for the frame, in Kelvin + */ + /** * \class AwbResult * \brief The result of an AWB calculation diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h index f4a86038635f984ac03b1e466c0fcd4e6d5e22bd..764be849270bcd42ecdf67aea3d13afa170c7499 100644 --- a/src/ipa/libipa/awb.h +++ b/src/ipa/libipa/awb.h @@ -20,6 +20,32 @@ namespace libcamera { namespace ipa { +namespace awb { + +struct Session { + bool enabled; +}; + +struct ActiveState { + struct AwbState { + RGB gains; + unsigned int temperatureK; + }; + + AwbState manual; + AwbState automatic; + + bool autoEnabled; +}; + +struct FrameContext { + RGB gains; + bool autoEnabled; + unsigned int temperatureK; +}; + +} /* namespace awb */ + struct AwbResult { RGB gains; double colourTemperature; diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index 63a6f2b7d884bfff2553175fa32e45ae747fb4c7..85a8ea3acc1fd75ff6b49576800ab7615cebce2c 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -25,6 +25,7 @@ #include "libcamera/internal/vector.h" #include "libipa/agc_mean_luminance.h" +#include "libipa/awb.h" #include "libipa/camera_sensor_helper.h" #include "libipa/fc_queue.h" #include "libipa/fixedpoint.h" @@ -49,15 +50,16 @@ struct IPAHwSettings { bool compand; }; +struct RKISP1AwbSession : public ipa::awb::Session { + struct rkisp1_cif_isp_window measureWindow; +}; + struct IPASessionConfiguration { struct { struct rkisp1_cif_isp_window measureWindow; } agc; - struct { - struct rkisp1_cif_isp_window measureWindow; - bool enabled; - } awb; + struct RKISP1AwbSession awb; struct { bool supported; @@ -103,17 +105,7 @@ struct IPAActiveState { utils::Duration maxFrameDuration; } agc; - struct { - struct AwbState { - RGB gains; - unsigned int temperatureK; - }; - - AwbState manual; - AwbState automatic; - - bool autoEnabled; - } awb; + ipa::awb::ActiveState awb; struct { Matrix manual; @@ -175,11 +167,7 @@ struct IPAFrameContext : public FrameContext { bool autoGainModeChange; } agc; - struct { - RGB gains; - bool autoEnabled; - unsigned int temperatureK; - } awb; + ipa::awb::FrameContext awb; struct { BrightnessQ brightness; From patchwork Tue Apr 7 22:01:12 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26492 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 13676C32F7 for ; Tue, 7 Apr 2026 22:03:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A6E2362DA7; Wed, 8 Apr 2026 00:02:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OWT8u0yi"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E59A062DA8 for ; Wed, 8 Apr 2026 00:02:42 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 427F578E; Wed, 8 Apr 2026 00:01:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599275; bh=oozcjV0cZYSOeVz0QApO4JVpN6M3yAnj1517g54bPlQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=OWT8u0yidvsv9CZnvowc2BvN+UGcrbsMSh+wmexQcSBiTId1XxNj+iUQwDItF/jRp vT4xh6ZrjakK6++YWPLxV3CU2t61KZtBEdblfnTmiW1NqoOnLWyJbeki86kHyAzW91 weBzTb0ixOdd03FIsaVXem5ny9KeFrWjdHo0glII= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:12 +0100 Subject: [PATCH 09/13] libcamera: vector: Convertor Constructor MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-9-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599360; l=1757; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=oozcjV0cZYSOeVz0QApO4JVpN6M3yAnj1517g54bPlQ=; b=VRRG1WjbZKei9rJaWng/94lcPy49YA2lH8SifodcvKa5VlV/nENRTUdyD2U37BIZ5Gvdh0mHb Yowe1me0IMJBSX2Xh3vrafzquzdjIUEqoofb3xxB7jtQHlge4NgVg+z 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" Extend the Vector class with a constructor to support casting to the storage type when the given parameters are compatible. Signed-off-by: Kieran Bingham --- include/libcamera/internal/vector.h | 8 ++++++++ src/libcamera/vector.cpp | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h index 16b6aef0b38f4b094d83449b5cdb1ba48b17c2bf..d29547f170e37c15f85e46abca82f89c8995115d 100644 --- a/include/libcamera/internal/vector.h +++ b/include/libcamera/internal/vector.h @@ -51,6 +51,14 @@ public: std::copy(data.begin(), data.end(), data_.begin()); } + template && + !std::is_same_v> * = nullptr> + constexpr Vector(const Vector &other) + { + for (unsigned int i = 0; i < Rows; i++) + data_[i] = static_cast(other[i]); + } + const T &operator[](size_t i) const { ASSERT(i < data_.size()); diff --git a/src/libcamera/vector.cpp b/src/libcamera/vector.cpp index 4dad1b9001c5df97f71031724729563cae0962c3..397e370fd023caef069095dc5abebe2d6d76848a 100644 --- a/src/libcamera/vector.cpp +++ b/src/libcamera/vector.cpp @@ -52,6 +52,13 @@ LOG_DEFINE_CATEGORY(Vector) * The size of \a data must be equal to the dimension size Rows of the vector. */ +/** + * \fn Vector::Vector(const Vector &other) + * \brief Construct a vector by converting another vector's element type + * \tparam U The source vector element type + * \param[in] other The vector to convert from + */ + /** * \fn T Vector::operator[](size_t i) const * \brief Index to an element in the vector From patchwork Tue Apr 7 22:01:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26493 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 62AEFC32F8 for ; Tue, 7 Apr 2026 22:03:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1918862D66; Wed, 8 Apr 2026 00:02:55 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="HGg39Y8F"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 33B1662D4E for ; Wed, 8 Apr 2026 00:02:43 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7FFD2257A; Wed, 8 Apr 2026 00:01:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599275; bh=rbs3mKEUMOc5h/sZ+nnL7K22ky+pgA3A36uWXG67KIM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=HGg39Y8FsAgy5bPKeao//ELLJx+E0zAa69LeKddrD/PbW94kaxVqQNy10djOcvRUw 2JhnzfLJmAimZuJ4jv6vZsj/lPo1UDhFmNBH+ewUC+Iqq6AU2D26t17NzDXuKHGPIU g3AB/T0NsPdbh2Gio6lay6tW8UM+lHbTjSUbFkZo= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:13 +0100 Subject: [PATCH 10/13] ipa: simple: Convert awb to libipa implementation MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-10-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599360; l=12978; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=rbs3mKEUMOc5h/sZ+nnL7K22ky+pgA3A36uWXG67KIM=; b=6LbPOZq/ynlgBb2NlD2UGY0/hMIrkIZFVq+4/SokpqNOwtN4Y+pZsfyyAWWiCrRgyCCtf4JK2 8AiDn5de/LUBw9J5H9P9BVhh+t4l6GztckKL26OaRabHKDPYPqX2kxJ 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" This brings in both Greyworld and Bayes from libipa assuming the tuning file provides the Bayesian priors from calibration. Manual controls become available and enabled as well, and the storage in the Context is moved to the new common types. Signed-off-by: Kieran Bingham --- src/ipa/simple/algorithms/awb.cpp | 215 +++++++++++++++++++++++++++++++++----- src/ipa/simple/algorithms/awb.h | 12 +++ src/ipa/simple/algorithms/ccm.cpp | 2 +- src/ipa/simple/ipa_context.h | 12 +-- 4 files changed, 209 insertions(+), 32 deletions(-) diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp index 05155c83d172d64609053ba940a4c12a2248bb04..90c05e86bae6eefe4874feeb1263af07acd5fcfc 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -14,6 +14,8 @@ #include +#include "libipa/awb_bayes.h" +#include "libipa/awb_grey.h" #include "libipa/colours.h" #include "simple/ipa_context.h" @@ -23,24 +25,173 @@ LOG_DEFINE_CATEGORY(IPASoftAwb) namespace ipa::soft::algorithms { +constexpr int32_t kMinColourTemperature = 2500; +constexpr int32_t kMaxColourTemperature = 10000; +constexpr int32_t kDefaultColourTemperature = 5000; + +/* Identical to RKISP1AwbStats ... why ? */ +class SimpleAwbStats final : public AwbStats +{ +public: + SimpleAwbStats(const RGB &rgbMeans) + : rgbMeans_(rgbMeans) + { + rg_ = rgbMeans_.r() / rgbMeans_.g(); + bg_ = rgbMeans_.b() / rgbMeans_.g(); + } + + double computeColourError(const RGB &gains) const override + { + /* + * Compute the sum of the squared colour error (non-greyness) as + * it appears in the log likelihood equation. + */ + double deltaR = gains.r() * rg_ - 1.0; + double deltaB = gains.b() * bg_ - 1.0; + double delta2 = deltaR * deltaR + deltaB * deltaB; + + return delta2; + } + + RGB rgbMeans() const override + { + return rgbMeans_; + } + +private: + RGB rgbMeans_; + double rg_; + double bg_; +}; + +int Awb::init(IPAContext &context, const YamlObject &tuningData) +{ + auto &cmap = context.ctrlMap; + + cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature, + kMaxColourTemperature, + kDefaultColourTemperature); + + cmap[&controls::AwbEnable] = ControlInfo(false, true); + cmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f, + Span{ { 1.0f, 1.0f } }); + + if (!tuningData.contains("algorithm")) + LOG(IPASoftAwb, Info) << "No AWB algorithm specified." + << " Default to grey world"; + + auto mode = tuningData["algorithm"].get("grey"); + if (mode == "grey") { + awbAlgo_ = std::make_unique(); + } else if (mode == "bayes") { + awbAlgo_ = std::make_unique(); + } else { + LOG(IPASoftAwb, Error) << "Unknown AWB algorithm: " << mode; + return -EINVAL; + } + LOG(IPASoftAwb, Debug) << "Using AWB algorithm: " << mode; + + int ret = awbAlgo_->init(tuningData); + if (ret) { + LOG(IPASoftAwb, Error) << "Failed to init AWB algorithm"; + return ret; + } + + const auto &src = awbAlgo_->controls(); + cmap.insert(src.begin(), src.end()); + + return 0; +} + int Awb::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - auto &gains = context.activeState.awb.gains; - gains = { { 1.0, 1.0, 1.0 } }; + context.activeState.awb.manual.gains = RGB{ 1.0 }; + auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature); + if (gains) + context.activeState.awb.automatic.gains = *gains; + else + context.activeState.awb.automatic.gains = RGB{ 1.0 }; + + context.activeState.awb.autoEnabled = true; + context.activeState.awb.manual.temperatureK = kDefaultColourTemperature; + context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature; + + context.configuration.awb.enabled = true; return 0; } +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Awb::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + const ControlList &controls) +{ + auto &awb = context.activeState.awb; + + const auto &awbEnable = controls.get(controls::AwbEnable); + if (awbEnable && *awbEnable != awb.autoEnabled) { + awb.autoEnabled = *awbEnable; + + LOG(IPASoftAwb, Debug) + << (*awbEnable ? "Enabling" : "Disabling") << " AWB"; + } + + awbAlgo_->handleControls(controls); + + frameContext.awb.autoEnabled = awb.autoEnabled; + + if (awb.autoEnabled) + return; + + const auto &colourGains = controls.get(controls::ColourGains); + const auto &colourTemperature = controls.get(controls::ColourTemperature); + bool update = false; + if (colourGains) { + awb.manual.gains.r() = (*colourGains)[0]; + awb.manual.gains.b() = (*colourGains)[1]; + /* + * \todo Colour temperature reported in metadata is now + * incorrect, as we can't deduce the temperature from the gains. + * This will be fixed with the bayes AWB algorithm. + */ + update = true; + } else if (colourTemperature) { + awb.manual.temperatureK = *colourTemperature; + const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature); + if (gains) { + awb.manual.gains.r() = gains->r(); + awb.manual.gains.b() = gains->b(); + update = true; + } + } + + if (update) + LOG(IPASoftAwb, Debug) + << "Set colour gains to " << awb.manual.gains; + + frameContext.awb.gains = awb.manual.gains; + frameContext.awb.temperatureK = awb.manual.temperatureK; +} + void Awb::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, DebayerParams *params) { - auto &gains = context.activeState.awb.gains; + /* + * When AutoAWB is enabled, this is the latest opportunity to take + * the most recent and up to date desired AWB gains. + */ + if (frameContext.awb.autoEnabled) { + frameContext.awb.gains = context.activeState.awb.automatic.gains; + frameContext.awb.temperatureK = context.activeState.awb.automatic.temperatureK; + } - frameContext.gains = gains; - params->gains = gains; + params->gains = frameContext.awb.gains; } void Awb::process(IPAContext &context, @@ -49,15 +200,19 @@ void Awb::process(IPAContext &context, const SwIspStats *stats, ControlList &metadata) { - const SwIspStats::Histogram &histogram = stats->yHistogram; - const uint8_t blackLevel = context.activeState.blc.level; + IPAActiveState &activeState = context.activeState; + RGB gains = frameContext.awb.gains; - metadata.set(controls::ColourGains, { frameContext.gains.r(), - frameContext.gains.b() }); + metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled); + metadata.set(controls::ColourGains, { gains.r(), gains.b() }); + metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK); if (!stats->valid) return; + const SwIspStats::Histogram &histogram = stats->yHistogram; + const uint8_t blackLevel = context.activeState.blc.level; + /* * Black level must be subtracted to get the correct AWB ratios, they * would be off if they were computed from the whole brightness range @@ -67,30 +222,42 @@ void Awb::process(IPAContext &context, histogram.begin(), histogram.end(), uint64_t(0)); const uint64_t offset = blackLevel * nPixels; const uint64_t minValid = 1; + /* * Make sure the sums are at least minValid, while preventing unsigned * integer underflow. */ const RGB sum = stats->sum_.max(offset + minValid) - offset; - /* - * Calculate red and blue gains for AWB. - * Clamp max gain at 4.0, this also avoids 0 division. - */ - auto &gains = context.activeState.awb.gains; - gains = { { - sum.r() <= sum.g() / 4 ? 4.0f : static_cast(sum.g()) / sum.r(), - 1.0, - sum.b() <= sum.g() / 4 ? 4.0f : static_cast(sum.g()) / sum.b(), - } }; + RGB rgbMeans = { { static_cast(sum.r() / nPixels), + static_cast(sum.g() / nPixels), + static_cast(sum.b() / nPixels) } }; - RGB rgbGains{ { 1 / gains.r(), 1 / gains.g(), 1 / gains.b() } }; - context.activeState.awb.temperatureK = estimateCCT(rgbGains); - metadata.set(controls::ColourTemperature, context.activeState.awb.temperatureK); + /* + * Todo: Determine the minimum allowed thresholds from the mean + * but we currently have the sum - not the mean value! + */ + SimpleAwbStats awbStats{ rgbMeans }; + + AwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux); + + /* Todo: Check if clamping required */ + + /* Filter the values to avoid oscillations. */ + double speed = 0.2; + double ct = awbResult.colourTemperature; + ct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed); + awbResult.gains = awbResult.gains * speed + + activeState.awb.automatic.gains * (1 - speed); + + activeState.awb.automatic.temperatureK = static_cast(ct); + activeState.awb.automatic.gains = awbResult.gains; LOG(IPASoftAwb, Debug) - << "gain R/B: " << gains << "; temperature: " - << context.activeState.awb.temperatureK; + << std::showpoint + << "Means " << rgbMeans << ", gains " + << activeState.awb.automatic.gains << ", temp " + << activeState.awb.automatic.temperatureK << "K"; } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h index ad993f39c18002547301b0588dfde143382854a9..fa8f38f65d6e9fdd18418361711e683916b9a9ba 100644 --- a/src/ipa/simple/algorithms/awb.h +++ b/src/ipa/simple/algorithms/awb.h @@ -7,6 +7,8 @@ #pragma once +#include "libipa/awb.h" + #include "algorithm.h" namespace libcamera { @@ -19,7 +21,14 @@ public: Awb() = default; ~Awb() = default; + int init(IPAContext &context, + const YamlObject &tuningData) override; int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + + void queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -29,6 +38,9 @@ public: IPAFrameContext &frameContext, const SwIspStats *stats, ControlList &metadata) override; + +private: + std::unique_ptr awbAlgo_; }; } /* namespace ipa::soft::algorithms */ diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp index 911a5af2c90b55187f0d98519f3c29b8e0804567..6dace73202a800b2c6375c30083e1ed50ef425b1 100644 --- a/src/ipa/simple/algorithms/ccm.cpp +++ b/src/ipa/simple/algorithms/ccm.cpp @@ -44,7 +44,7 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) { - const unsigned int ct = context.activeState.awb.temperatureK; + const unsigned int ct = frameContext.awb.temperatureK; /* Change CCM only on bigger temperature changes. */ if (!currentCcm_ || diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 2bd7c4642b118d7bb94b1b16cdf4ede5fb2554b5..67b03b5b835f59cf2e339d21377e06d1bbe79b6f 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -16,6 +16,7 @@ #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" +#include #include #include @@ -26,6 +27,8 @@ namespace libcamera { namespace ipa::soft { struct IPASessionConfiguration { + ipa::awb::Session awb; + struct { int32_t exposureMin, exposureMax; double againMin, againMax, again10, againMinStep; @@ -38,6 +41,7 @@ struct IPASessionConfiguration { struct IPAActiveState { ipa::lux::ActiveState lux; + ipa::awb::ActiveState awb; struct { int32_t exposure; @@ -51,11 +55,6 @@ struct IPAActiveState { double lastGain; } blc; - struct { - RGB gains; - unsigned int temperatureK; - } awb; - Matrix combinedMatrix; struct { @@ -68,6 +67,7 @@ struct IPAActiveState { struct IPAFrameContext : public FrameContext { ipa::lux::FrameContext lux; + ipa::awb::FrameContext awb; Matrix ccm; @@ -76,8 +76,6 @@ struct IPAFrameContext : public FrameContext { double gain; } sensor; - RGB gains; - float gamma; std::optional contrast; std::optional saturation; From patchwork Tue Apr 7 22:01:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26495 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 4AF02BDCBD for ; Tue, 7 Apr 2026 22:03:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 684C562DB3; Wed, 8 Apr 2026 00:02:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nL2GjPH9"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B8E5B62DB6 for ; Wed, 8 Apr 2026 00:02:43 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C1F7D2598; Wed, 8 Apr 2026 00:01:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599275; bh=ACGMEBHGXk8xztCJFbq3gIfrocN+U7dV5cite7ZFeJI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=nL2GjPH9SKXK5XVZ2lAiiBCsy3gjNcSThBhuuxa/MeRlVy/TM3C8LqHS6fIUhfdKZ lR9t7Dd8f1vOuHVqCRAceROVjxPh6IGOw4TQR1wmuNHNJjStbP1KkBoDpnRNiCwekv t1HRBQnSWUx/PlWBPkKRlVddI8v4XPyoIKevZG+c= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:14 +0100 Subject: [PATCH 11/13] ipa: libipa: awb: Move configure to common MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-11-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599360; l=5011; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=ACGMEBHGXk8xztCJFbq3gIfrocN+U7dV5cite7ZFeJI=; b=kIQxN28QMXXNFfvy3Zrr+V5bTB+XYTHqzJ20nW/u8TOiuUSkVC+dcB1WYg7xR1eem771schDs DypABzgo5smC3OkXt1u6SMPPs4Afv3DeRCIbmx/L9XhmHDMfkJwNwfG 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" Refactor the AWB configuration to use the common implementation supplied by libipa awb module. Signed-off-by: Kieran Bingham --- src/ipa/libipa/awb.cpp | 29 +++++++++++++++++++++++++++++ src/ipa/libipa/awb.h | 3 +++ src/ipa/rkisp1/algorithms/awb.cpp | 13 +------------ src/ipa/simple/algorithms/awb.cpp | 16 ++-------------- 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp index f215bea0b59483eadf95572f073928a4eb1275f4..d0c577958caebc0b7e24fe58506da964df2200fe 100644 --- a/src/ipa/libipa/awb.cpp +++ b/src/ipa/libipa/awb.cpp @@ -11,6 +11,8 @@ #include +constexpr int32_t kDefaultColourTemperature = 5000; + /** * \file awb.h * \brief Base classes for AWB algorithms @@ -139,6 +141,33 @@ namespace ipa { * \return 0 on success, a negative error code otherwise */ +/** + * \brief Configure the Awb algorithm given an IPAConfigInfo + * \param[in] state The AWB specific active state shared across frames + * \param[in] session The AWB specific session configuration + * + * Configure and initialise the AWB algorithm module. + * + * \return 0 if successful, an error code otherwise + */ +int AwbAlgorithm::configure(awb::ActiveState &state, awb::Session &session) +{ + state.manual.gains = RGB{ 1.0 }; + auto gains = gainsFromColourTemperature(kDefaultColourTemperature); + if (gains) + state.automatic.gains = *gains; + else + state.automatic.gains = RGB{ 1.0 }; + + state.autoEnabled = true; + state.manual.temperatureK = kDefaultColourTemperature; + state.automatic.temperatureK = kDefaultColourTemperature; + + session.enabled = true; + + return 0; +} + /** * \fn AwbAlgorithm::calculateAwb() * \brief Calculate AWB data from the given statistics diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h index 764be849270bcd42ecdf67aea3d13afa170c7499..4ceae537686f8f4c93686fab4b9efbc06e112b1d 100644 --- a/src/ipa/libipa/awb.h +++ b/src/ipa/libipa/awb.h @@ -65,6 +65,9 @@ public: virtual ~AwbAlgorithm() = default; virtual int init(const YamlObject &tuningData) = 0; + + int configure(awb::ActiveState &state, awb::Session &session); + virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0; virtual std::optional> gainsFromColourTemperature(double colourTemperature) = 0; diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index f83da545be856c08cd758dc20a5ace91344101cf..86d5dfed3e1c2bb587705f05362229db3cdadafd 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -128,16 +128,7 @@ int Awb::init(IPAContext &context, const YamlObject &tuningData) int Awb::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { - context.activeState.awb.manual.gains = RGB{ 1.0 }; - auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature); - if (gains) - context.activeState.awb.automatic.gains = *gains; - else - context.activeState.awb.automatic.gains = RGB{ 1.0 }; - - context.activeState.awb.autoEnabled = true; - context.activeState.awb.manual.temperatureK = kDefaultColourTemperature; - context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature; + awbAlgo_->configure(context.activeState.awb, context.configuration.awb); /* * Define the measurement window for AWB as a centered rectangle @@ -148,8 +139,6 @@ int Awb::configure(IPAContext &context, context.configuration.awb.measureWindow.h_size = 3 * configInfo.outputSize.width / 4; context.configuration.awb.measureWindow.v_size = 3 * configInfo.outputSize.height / 4; - context.configuration.awb.enabled = true; - return 0; } diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp index 90c05e86bae6eefe4874feeb1263af07acd5fcfc..230b7807075941f086911549066e39c0a04dff5c 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -106,20 +106,8 @@ int Awb::init(IPAContext &context, const YamlObject &tuningData) int Awb::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - context.activeState.awb.manual.gains = RGB{ 1.0 }; - auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature); - if (gains) - context.activeState.awb.automatic.gains = *gains; - else - context.activeState.awb.automatic.gains = RGB{ 1.0 }; - - context.activeState.awb.autoEnabled = true; - context.activeState.awb.manual.temperatureK = kDefaultColourTemperature; - context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature; - - context.configuration.awb.enabled = true; - - return 0; + return awbAlgo_->configure(context.activeState.awb, + context.configuration.awb); } /** From patchwork Tue Apr 7 22:01:15 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26494 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 F0985C32F9 for ; Tue, 7 Apr 2026 22:03:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4183562DEF; Wed, 8 Apr 2026 00:02:56 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FB4iUJkg"; 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 E79FD62DAD for ; Wed, 8 Apr 2026 00:02:43 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 11A151C4D; Wed, 8 Apr 2026 00:01:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599276; bh=BEhM5gmAYP5zsWL3XAYTLQ3/oVZz6IBL19P9xUhlFLk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=FB4iUJkg9vQU0Jl9+RQxYZ5RPT5R5fvCI2Pfs8lFUCJd+V4ugP99KXOnwYchYofEV 6guYRwj8LYhhweDWcfiQKH2DlB5FLggaC+xXD/csRToIu1sT/dfVnjpTqCiYTuYZ5t 1vxENm6Q5WFAxD+ynrx0uLcGSqFoXYjfUS0TJjUc= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:15 +0100 Subject: [PATCH 12/13] ipa: libipa: awb: convert to common queueRequest MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-12-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599360; l=8073; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=BEhM5gmAYP5zsWL3XAYTLQ3/oVZz6IBL19P9xUhlFLk=; b=9yx8t0PgTjf80Ar8+BmoYFl9IlEx/LuUBf7dQWuHqL2mrHz9pxqfOGZAAeHmaLrlQCz0FGxBe c/cmeZbO5rZBvTaqcM4iOVl52mvyifrCYJd42Shkt7IBXrW6vDV5NFz 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" Move the now duplicated implementation for both soft IPA and rkisp1 IPA for managing requests at queue time into the common libipa AWB implementation. Signed-off-by: Kieran Bingham --- src/ipa/libipa/awb.cpp | 59 +++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/awb.h | 5 ++++ src/ipa/rkisp1/algorithms/awb.cpp | 49 ++------------------------------ src/ipa/simple/algorithms/awb.cpp | 52 ++++------------------------------ 4 files changed, 72 insertions(+), 93 deletions(-) diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp index d0c577958caebc0b7e24fe58506da964df2200fe..cac6b4bd206faa4f32680e16c7522685cfd30cac 100644 --- a/src/ipa/libipa/awb.cpp +++ b/src/ipa/libipa/awb.cpp @@ -168,6 +168,65 @@ int AwbAlgorithm::configure(awb::ActiveState &state, awb::Session &session) return 0; } +/** + * \brief Provide control values to the algorithm + * \param[in] state The AWB specific active state shared across frames + * \param[in] frame The frame number to apply the control values + * \param[in] frameContext The current frame's AWB specific context + * \param[in] controls The list of user controls + */ +void AwbAlgorithm::queueRequest(awb::ActiveState &state, + [[maybe_unused]] const uint32_t frame, + awb::FrameContext &frameContext, + const ControlList &controls) +{ + const auto &awbEnable = controls.get(controls::AwbEnable); + if (awbEnable && *awbEnable != state.autoEnabled) { + state.autoEnabled = *awbEnable; + + LOG(Awb, Debug) + << (*awbEnable ? "Enabling" : "Disabling") << " AWB"; + } + + /* Handle controls from subclass algo (Grey or Bayes) */ + handleControls(controls); + + frameContext.autoEnabled = state.autoEnabled; + + /* Todo: Check to see if we should always parse the following controls */ + if (frameContext.autoEnabled) + return; + + const auto &colourGains = controls.get(controls::ColourGains); + const auto &colourTemperature = controls.get(controls::ColourTemperature); + bool update = false; + if (colourGains) { + state.manual.gains.r() = (*colourGains)[0]; + state.manual.gains.b() = (*colourGains)[1]; + /* + * \todo Colour temperature reported in metadata is now + * incorrect, as we can't deduce the temperature from the gains. + * This will be fixed with the bayes AWB algorithm. + */ + update = true; + } else if (colourTemperature) { + state.manual.temperatureK = *colourTemperature; + const auto &gains = gainsFromColourTemperature(*colourTemperature); + if (gains) { + state.manual.gains.r() = gains->r(); + state.manual.gains.b() = gains->b(); + update = true; + } + } + + if (update) + LOG(Awb, Debug) + << "Set colour gains to " << state.manual.gains; + + frameContext.gains = state.manual.gains; + frameContext.temperatureK = state.manual.temperatureK; +} + /** * \fn AwbAlgorithm::calculateAwb() * \brief Calculate AWB data from the given statistics diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h index 4ceae537686f8f4c93686fab4b9efbc06e112b1d..0256ff8ca3429288c317d3ee940255c4a5391357 100644 --- a/src/ipa/libipa/awb.h +++ b/src/ipa/libipa/awb.h @@ -68,6 +68,11 @@ public: int configure(awb::ActiveState &state, awb::Session &session); + void queueRequest(awb::ActiveState &state, + [[maybe_unused]] const uint32_t frame, + awb::FrameContext &frameContext, + const ControlList &controls); + virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0; virtual std::optional> gainsFromColourTemperature(double colourTemperature) = 0; diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index 86d5dfed3e1c2bb587705f05362229db3cdadafd..b91132fc6177650b9338867359583bf5429ea7e5 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -146,55 +146,12 @@ int Awb::configure(IPAContext &context, * \copydoc libcamera::ipa::Algorithm::queueRequest */ void Awb::queueRequest(IPAContext &context, - [[maybe_unused]] const uint32_t frame, + const uint32_t frame, IPAFrameContext &frameContext, const ControlList &controls) { - auto &awb = context.activeState.awb; - - const auto &awbEnable = controls.get(controls::AwbEnable); - if (awbEnable && *awbEnable != awb.autoEnabled) { - awb.autoEnabled = *awbEnable; - - LOG(RkISP1Awb, Debug) - << (*awbEnable ? "Enabling" : "Disabling") << " AWB"; - } - - awbAlgo_->handleControls(controls); - - frameContext.awb.autoEnabled = awb.autoEnabled; - - if (awb.autoEnabled) - return; - - const auto &colourGains = controls.get(controls::ColourGains); - const auto &colourTemperature = controls.get(controls::ColourTemperature); - bool update = false; - if (colourGains) { - awb.manual.gains.r() = (*colourGains)[0]; - awb.manual.gains.b() = (*colourGains)[1]; - /* - * \todo Colour temperature reported in metadata is now - * incorrect, as we can't deduce the temperature from the gains. - * This will be fixed with the bayes AWB algorithm. - */ - update = true; - } else if (colourTemperature) { - awb.manual.temperatureK = *colourTemperature; - const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature); - if (gains) { - awb.manual.gains.r() = gains->r(); - awb.manual.gains.b() = gains->b(); - update = true; - } - } - - if (update) - LOG(RkISP1Awb, Debug) - << "Set colour gains to " << awb.manual.gains; - - frameContext.awb.gains = awb.manual.gains; - frameContext.awb.temperatureK = awb.manual.temperatureK; + awbAlgo_->queueRequest(context.activeState.awb, frame, frameContext.awb, + controls); } /** diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp index 230b7807075941f086911549066e39c0a04dff5c..d8516871562724fc65ffbb67161ff4fd988c4b1c 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -114,55 +114,13 @@ int Awb::configure(IPAContext &context, * \copydoc libcamera::ipa::Algorithm::queueRequest */ void Awb::queueRequest(IPAContext &context, - [[maybe_unused]] const uint32_t frame, - [[maybe_unused]] IPAFrameContext &frameContext, + const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) { - auto &awb = context.activeState.awb; - - const auto &awbEnable = controls.get(controls::AwbEnable); - if (awbEnable && *awbEnable != awb.autoEnabled) { - awb.autoEnabled = *awbEnable; - - LOG(IPASoftAwb, Debug) - << (*awbEnable ? "Enabling" : "Disabling") << " AWB"; - } - - awbAlgo_->handleControls(controls); - - frameContext.awb.autoEnabled = awb.autoEnabled; - - if (awb.autoEnabled) - return; - - const auto &colourGains = controls.get(controls::ColourGains); - const auto &colourTemperature = controls.get(controls::ColourTemperature); - bool update = false; - if (colourGains) { - awb.manual.gains.r() = (*colourGains)[0]; - awb.manual.gains.b() = (*colourGains)[1]; - /* - * \todo Colour temperature reported in metadata is now - * incorrect, as we can't deduce the temperature from the gains. - * This will be fixed with the bayes AWB algorithm. - */ - update = true; - } else if (colourTemperature) { - awb.manual.temperatureK = *colourTemperature; - const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature); - if (gains) { - awb.manual.gains.r() = gains->r(); - awb.manual.gains.b() = gains->b(); - update = true; - } - } - - if (update) - LOG(IPASoftAwb, Debug) - << "Set colour gains to " << awb.manual.gains; - - frameContext.awb.gains = awb.manual.gains; - frameContext.awb.temperatureK = awb.manual.temperatureK; + awbAlgo_->queueRequest(context.activeState.awb, + frame, frameContext.awb, + controls); } void Awb::prepare(IPAContext &context, From patchwork Tue Apr 7 22:01:16 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26496 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 94F90C32FA for ; Tue, 7 Apr 2026 22:03:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 55FA162E07; Wed, 8 Apr 2026 00:02:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ekxKGIOd"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2622562DA7 for ; Wed, 8 Apr 2026 00:02:44 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5123725B7; Wed, 8 Apr 2026 00:01:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599276; bh=5JfiMzRudcttUvGqI+sxwQvMjSY3DKrSpztYaCa7ooo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ekxKGIOdKJsdEOWjgwx0iQS2TqsaysAy/V8oPVisT+KodfmPBtIqC/2ajxphJHdm4 ueOYMlDr0EcU3dNnp+Eol/eMxLRE0rbRhHdsOJBd/miESX9lqIzQLHnP2qE2jnKXqF 04EGMpIOQGCZ187DRqL0CgMgbRieG9B+FWFQ/Jfk= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:16 +0100 Subject: [PATCH 13/13] ipa: libipa: awb: factor out common prepare MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-13-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@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=1775599360; l=4270; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=5JfiMzRudcttUvGqI+sxwQvMjSY3DKrSpztYaCa7ooo=; b=HGzGuZlj5Vhg3l1F0ilZUj99PFgK7adjPqpiy0DN+k72pPcv2n5VDbFBtmGxDSWpBf40EQZ3Q Jajqiz1A51tD+yKhg5cmgQxH56LYA80TNMGDVRbGKmfL8I/SojpUF8o 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" Move the common duplicated code from both the simple and rkisp1 IPA modules and add these to the prepare function of libipa awb module. Signed-off-by: Kieran Bingham --- src/ipa/libipa/awb.cpp | 20 ++++++++++++++++++++ src/ipa/libipa/awb.h | 4 ++++ src/ipa/rkisp1/algorithms/awb.cpp | 11 ++--------- src/ipa/simple/algorithms/awb.cpp | 12 +++--------- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp index cac6b4bd206faa4f32680e16c7522685cfd30cac..b079a9c24b0d2d1e11b8da1087023af715765e31 100644 --- a/src/ipa/libipa/awb.cpp +++ b/src/ipa/libipa/awb.cpp @@ -227,6 +227,26 @@ void AwbAlgorithm::queueRequest(awb::ActiveState &state, frameContext.temperatureK = state.manual.temperatureK; } +/** + * \brief Prepare the AWB frame context ready for usage + * \param[in] state The AWB specific active state shared across frames + * \param[in] frame The frame number to apply the control values + * \param[in] frameContext The current frame's AWB specific context + */ +void AwbAlgorithm::prepare(awb::ActiveState &state, + [[maybe_unused]] const uint32_t frame, + awb::FrameContext &frameContext) +{ + /* + * When AutoAWB is enabled, this is the latest opportunity to take + * the most recent and up to date desired AWB gains. + */ + if (frameContext.autoEnabled) { + frameContext.gains = state.automatic.gains; + frameContext.temperatureK = state.automatic.temperatureK; + } +} + /** * \fn AwbAlgorithm::calculateAwb() * \brief Calculate AWB data from the given statistics diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h index 0256ff8ca3429288c317d3ee940255c4a5391357..ae061f4082ed947c5b8df95bd33566be6777849f 100644 --- a/src/ipa/libipa/awb.h +++ b/src/ipa/libipa/awb.h @@ -73,6 +73,10 @@ public: awb::FrameContext &frameContext, const ControlList &controls); + void prepare(awb::ActiveState &state, + [[maybe_unused]] const uint32_t frame, + awb::FrameContext &frameContext); + virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0; virtual std::optional> gainsFromColourTemperature(double colourTemperature) = 0; diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index b91132fc6177650b9338867359583bf5429ea7e5..9aacb0cff0e747dba588e45434c108d592742701 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -160,15 +160,8 @@ void Awb::queueRequest(IPAContext &context, void Awb::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) { - /* - * This is the latest time we can read the active state. This is the - * most up-to-date automatic values we can read. - */ - if (frameContext.awb.autoEnabled) { - const auto &awb = context.activeState.awb; - frameContext.awb.gains = awb.automatic.gains; - frameContext.awb.temperatureK = awb.automatic.temperatureK; - } + awbAlgo_->prepare(context.activeState.awb, + frame, frameContext.awb); auto gainConfig = params->block(); gainConfig.setEnabled(true); diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp index d8516871562724fc65ffbb67161ff4fd988c4b1c..6ce1ed340a5338e25f44145942de95f7f4f6c637 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -124,18 +124,12 @@ void Awb::queueRequest(IPAContext &context, } void Awb::prepare(IPAContext &context, - [[maybe_unused]] const uint32_t frame, + const uint32_t frame, IPAFrameContext &frameContext, DebayerParams *params) { - /* - * When AutoAWB is enabled, this is the latest opportunity to take - * the most recent and up to date desired AWB gains. - */ - if (frameContext.awb.autoEnabled) { - frameContext.awb.gains = context.activeState.awb.automatic.gains; - frameContext.awb.temperatureK = context.activeState.awb.automatic.temperatureK; - } + awbAlgo_->prepare(context.activeState.awb, + frame, frameContext.awb); params->gains = frameContext.awb.gains; }