From patchwork Fri Nov 13 16:40:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28888 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 29376C3304 for ; Fri, 13 Nov 2026 16:41:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 64A236A81B; Fri, 13 Nov 2026 17:41:00 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="tAjAvCdI"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id F13E669107 for ; Fri, 13 Nov 2026 17:40:57 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2A5DA269; Fri, 13 Nov 2026 17:38:33 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587913; bh=XyIloSZtlSTOte0wp6uoYaxkjXVhKclxZ7Qz6f6REXQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tAjAvCdIcqc9nw/amCoEL1+vhuaJfBrJj1pCfpsNqKz3UnkOK1f/nPbkzf/GqEiZV FKcd4Pto/5q74pB5SQG+M/s0etIdEd6TMq4tgd4EWZhAvdy/EMBRu99lEo5h+FXr7c ldXPjPADQ0WYsMg7/42XwaolO8QtVBKsNVPRudKk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v4 01/21] ipa: libipa: fixedpoint: Shift unsigned type for scaling Date: Fri, 13 Nov 2026 17:40:34 +0100 Message-ID: <20261113164054.267164-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" Avoid shifting into the potential sign bit of `T`. The same is already done in `toFloat()`. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/libipa/fixedpoint.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/libipa/fixedpoint.h b/src/ipa/libipa/fixedpoint.h index ed8aca9f15..537bc774a4 100644 --- a/src/ipa/libipa/fixedpoint.h +++ b/src/ipa/libipa/fixedpoint.h @@ -78,7 +78,7 @@ public: * properly cast negative values. See * https://embeddeduse.com/2013/08/25/casting-a-negative-float-to-an-unsigned-int/ */ - return static_cast(static_cast(std::round(v * (1 << F)))) & bitMask; + return static_cast(static_cast(std::round(v * (UT{ 1 } << F)))) & bitMask; } }; From patchwork Fri Nov 13 16:40:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28889 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 5B537C3304 for ; Fri, 13 Nov 2026 16:41:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B60F66A827; Fri, 13 Nov 2026 17:41:01 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Nq/YZYSH"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8477A69107 for ; Fri, 13 Nov 2026 17:40:58 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7EDEC219; Fri, 13 Nov 2026 17:38:33 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587913; bh=7ipo5lgLqCxVElN5BZF9f2Q10EbmQFiYutelpf7H/tg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Nq/YZYSHNKPjh6EOOt3pH3FEYtH/YXv6BddilfHp8J+GjBaCtzB3rLKgfil94PYey lgg4Tb6yqhqWzY3YewHXHb/6KxuJN0CCx0LT3OucXI1YFL4RU7umpWjsZ765zbNgqu 2xvbjCGaVGUjJJvZsNxRyEtacRPHJWkIG+iwtVP0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v4 02/21] ipa: libipa: quantized: Make floating point type customizable Date: Fri, 13 Nov 2026 17:40:35 +0100 Message-ID: <20261113164054.267164-3-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" Adjust the `Quantized` type to retrieve the floating point from the `FloatingType` type alias in the `Traits` type, instead of hard-coding `float`. Additionally, adjust the various traits type, to provide the now required type alias. They are currently hard-coded to use `float`. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/libipa/fixedpoint.cpp | 7 ++++++- src/ipa/libipa/fixedpoint.h | 11 ++++++----- src/ipa/libipa/quantized.cpp | 10 +++++++++- src/ipa/libipa/quantized.h | 14 +++++++++----- test/ipa/libipa/quantized.cpp | 8 ++++++-- 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/ipa/libipa/fixedpoint.cpp b/src/ipa/libipa/fixedpoint.cpp index b06ad8d267..8c0e9746c7 100644 --- a/src/ipa/libipa/fixedpoint.cpp +++ b/src/ipa/libipa/fixedpoint.cpp @@ -47,6 +47,11 @@ namespace ipa { * \brief The integral storage type used for the fixed-point representation */ +/** + * \typedef FixedPointQTraits::FloatingType + * \brief The type used for the floating-point representation + */ + /** * \var FixedPointQTraits::qMin * \brief Minimum representable quantized integer value @@ -71,7 +76,7 @@ namespace ipa { */ /** - * \fn FixedPointQTraits::fromFloat(float v) + * \fn FixedPointQTraits::fromFloat(FloatingType v) * \brief Convert a floating-point value to a fixed-point integer * \param[in] v The floating-point value to be converted * \return The quantized fixed-point integer representation diff --git a/src/ipa/libipa/fixedpoint.h b/src/ipa/libipa/fixedpoint.h index 537bc774a4..66863075c6 100644 --- a/src/ipa/libipa/fixedpoint.h +++ b/src/ipa/libipa/fixedpoint.h @@ -37,6 +37,7 @@ private: public: using QuantizedType = UT; + using FloatingType = float; static constexpr UT qMin = std::is_signed_v ? -(UT{ 1 } << (bits - 1)) @@ -49,7 +50,7 @@ public: static constexpr float toFloat(QuantizedType q) { if constexpr (std::is_unsigned_v) - return static_cast(q) / static_cast(UT{ 1 } << F); + return static_cast(q) / static_cast(UT{ 1 } << F); /* * Recreate the upper bits in case of a negative number by @@ -60,16 +61,16 @@ public: */ unsigned int remaining_bits = sizeof(UT) * 8 - (I + F); T t = static_cast(static_cast(q) << remaining_bits) >> remaining_bits; - return static_cast(t) / static_cast(UT{ 1 } << F); + return static_cast(t) / static_cast(UT{ 1 } << F); } - static constexpr float min = toFloat(qMin); - static constexpr float max = toFloat(qMax); + static constexpr FloatingType min = toFloat(qMin); + static constexpr FloatingType max = toFloat(qMax); static_assert(min < max, "FixedPointQTraits: Minimum must be less than maximum"); /* Conversion functions required by Quantized */ - static QuantizedType fromFloat(float v) + static QuantizedType fromFloat(FloatingType v) { v = std::clamp(v, min, max); diff --git a/src/ipa/libipa/quantized.cpp b/src/ipa/libipa/quantized.cpp index 06143a97ab..34cf6b1b97 100644 --- a/src/ipa/libipa/quantized.cpp +++ b/src/ipa/libipa/quantized.cpp @@ -58,7 +58,15 @@ namespace ipa { */ /** - * \fn Quantized::Quantized(float x) + * \typedef Quantized::FloatingType + * \brief The floating type used for the value representation + * + * This alias corresponds to \c TraitsType::FloatingType, as defined by + * the traits class. + */ + +/** + * \fn Quantized::Quantized(FloatingType x) * \brief Construct a Quantized value from a floating-point number * \param[in] x The floating-point value to be quantized * diff --git a/src/ipa/libipa/quantized.h b/src/ipa/libipa/quantized.h index 8d7b06a2df..5c41d0b5dd 100644 --- a/src/ipa/libipa/quantized.h +++ b/src/ipa/libipa/quantized.h @@ -21,13 +21,17 @@ template struct Quantized { using TraitsType = Traits; using QuantizedType = typename Traits::QuantizedType; - static_assert(std::is_arithmetic_v, - "Quantized: QuantizedType must be arithmetic"); + using FloatingType = typename Traits::FloatingType; + + static_assert(std::is_integral_v, + "Quantized: QuantizedType must be integral"); + static_assert(std::is_floating_point_v, + "Quantized: FloatingType must be floating"); constexpr Quantized() - : Quantized(0.0f) {} + : Quantized(FloatingType{}) {} - constexpr Quantized(float x) + constexpr Quantized(FloatingType x) : Quantized(Traits::fromFloat(x)) { } @@ -73,7 +77,7 @@ struct Quantized { private: QuantizedType quantized_; - float value_; + FloatingType value_; }; } /* namespace ipa */ diff --git a/test/ipa/libipa/quantized.cpp b/test/ipa/libipa/quantized.cpp index 18655dac52..4b1d869bf2 100644 --- a/test/ipa/libipa/quantized.cpp +++ b/test/ipa/libipa/quantized.cpp @@ -20,7 +20,9 @@ using namespace ipa; struct BrightnessHueTraits { using QuantizedType = uint8_t; - static QuantizedType fromFloat(float v) + using FloatingType = float; + + static QuantizedType fromFloat(FloatingType v) { int quantized = std::lround(v * 128.0f); return std::clamp(quantized, -128, 127); @@ -35,7 +37,9 @@ using BrightnessHueQuantizer = Quantized; struct ContrastSaturationTraits { using QuantizedType = uint8_t; - static QuantizedType fromFloat(float v) + using FloatingType = float; + + static QuantizedType fromFloat(FloatingType v) { int quantized = std::lround(v * 128.0f); return std::clamp(quantized, 0, 255); From patchwork Fri Nov 13 16:40:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28890 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 E4370C3304 for ; Fri, 13 Nov 2026 16:41:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9153A6A81E; Fri, 13 Nov 2026 17:41:02 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ja3ZJAJw"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CA40D690A0 for ; Fri, 13 Nov 2026 17:40:58 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 14960269; Fri, 13 Nov 2026 17:38:34 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587914; bh=+ARIYmR/6U1+rHhiq14e5JfJWpeBtpP2WyZ+kOlryVg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ja3ZJAJwsL8hbUinrhaEfU4VhZC4FCe7CitHudp2wxkJZ68GeVi4AQGqzbsbsBptc Ln2le04vh204QQpiPfPtLU6l3ELbrIbqbl/y5IGj8PbG24pPOEOsXyhCzTD7+oQSW+ Obpwuq1Tgpaf0MxmRKPKVByGCnEclMwLOwfhtHfw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham Subject: [PATCH v4 03/21] ipa: libipa: quantized: Use `double` when necessary Date: Fri, 13 Nov 2026 17:40:36 +0100 Message-ID: <20261113164054.267164-4-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The currently used `float` type (usually IEEE754 binary32) can only represent integers exactly up to 2^24. So automatically use `double` (usually IEEE754 binary64) if more than 24 bits are needed for the fixed-point representation. Also add a static assertion that IEEE754 floating point types are used. But note, that does not guarantee that float and double are binary32 and binary64. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham --- src/ipa/libipa/fixedpoint.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ipa/libipa/fixedpoint.h b/src/ipa/libipa/fixedpoint.h index 66863075c6..0640605bee 100644 --- a/src/ipa/libipa/fixedpoint.h +++ b/src/ipa/libipa/fixedpoint.h @@ -25,19 +25,20 @@ private: static constexpr unsigned int bits = I + F; static_assert(bits <= sizeof(UT) * 8, "FixedPointQTraits: too many bits for type UT"); - /* - * If fixed point storage is required with more than 24 bits, consider - * updating this implementation to use double-precision floating point. - */ - static_assert(bits <= 24, "Floating point precision may be insufficient for more than 24 bits"); + /* IEEE754 binary64 can faithfully represent integers up to 2^53 */ + static_assert(bits <= 53, "Floating point precision may be insufficient"); static constexpr UT bitMask = bits < sizeof(UT) * 8 ? (UT{ 1 } << bits) - 1 : ~UT{ 0 }; public: + /* IEEE754 binary32 can faithfully represent integers up to 2^24 */ using QuantizedType = UT; - using FloatingType = float; + using FloatingType = std::conditional_t; + + static_assert(std::numeric_limits::is_iec559, + "requires IEEE754 floating point"); static constexpr UT qMin = std::is_signed_v ? -(UT{ 1 } << (bits - 1)) @@ -88,7 +89,7 @@ namespace details { template constexpr auto qtype() { - static_assert(Bits <= 32, + static_assert(Bits <= 64, "Unsupported number of bits for quantized type"); if constexpr (Bits <= 8) @@ -97,6 +98,8 @@ constexpr auto qtype() return int16_t(); else if constexpr (Bits <= 32) return int32_t(); + else if constexpr (Bits <= 64) + return int64_t(); } } /* namespace details */ From patchwork Fri Nov 13 16:40:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28891 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 C626CC3304 for ; Fri, 13 Nov 2026 16:41:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 756566A824; Fri, 13 Nov 2026 17:41:05 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UB2PhPin"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8CFB56A800 for ; Fri, 13 Nov 2026 17:40:59 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5A39933D; Fri, 13 Nov 2026 17:38:34 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587915; bh=CeRj0042k0gogHjx8dJpuEK+OPXgTngmrKq2+vhNdck=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UB2PhPin+mLhmp7u1jwiBYlh96JXgjfFvyBtK805/n+X2mSIFQzxT3KrMgBSV/nsT +Wn6V1w0uj5x1lDl8UQK7UmySVD8oXXkH7pZizguE7/CbfGNziY0K64VwHPnRkBa7s Z6mrtK/iwLvnAbFjgzn+3S8UkQLPLtjmbC7l0O4Y= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v4 04/21] ipa: libipa: awb: Log rgb means immediately Date: Fri, 13 Nov 2026 17:40:37 +0100 Message-ID: <20261113164054.267164-5-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" Log the rgb means immediately upon entering `process()`, and also log whether it is valid or not. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/libipa/awb.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp index 3b95fecee8..c4153bcf8d 100644 --- a/src/ipa/libipa/awb.cpp +++ b/src/ipa/libipa/awb.cpp @@ -367,7 +367,13 @@ void AwbAlgorithmBase::process(awb::ActiveState &state, const AwbStats &stats, unsigned int lux, ControlList &metadata) { - if (!stats.valid()) + const bool valid = stats.valid(); + + LOG(Awb, Debug) << std::showpoint + << "means: " << stats.rgbMeans() + << (!valid ? " (invalid)" : ""); + + if (!valid) return; auto awbResult = impl_->calculateAwb(stats, lux, { currentMode_->ctLo, @@ -394,8 +400,8 @@ void AwbAlgorithmBase::process(awb::ActiveState &state, static_cast(frameContext.gains.b()) }); metadata.set(controls::ColourTemperature, frameContext.colourTemperature); - LOG(Awb, Debug) << std::showpoint << "Means " << stats.rgbMeans() - << ", gains " << state.automatic.gains + LOG(Awb, Debug) << std::showpoint + << "gains " << state.automatic.gains << ", temp " << state.automatic.colourTemperature << "K"; } From patchwork Fri Nov 13 16:40:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28892 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 09FF5C3307 for ; Fri, 13 Nov 2026 16:41:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3151B6A800; Fri, 13 Nov 2026 17:41:07 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mVapepjY"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0238B69107 for ; Fri, 13 Nov 2026 17:40:59 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2159F219; Fri, 13 Nov 2026 17:38:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587915; bh=d40woxqDsAZGlr0FbSWfVuag/40Za3bodm3Ee4Wnkt4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mVapepjYmBdqRX+T2mP/yvig8c0x65IFA228jfQ12k8LG15wfF2Ny6DFcKNfW9D6W L9eYLL6hWxfDz8oR+01RFScCdJhnAMzU9ymMkUDYG7GqKCulm0PqHwkT0A6a8T6CeQ Fw/XrflDeSX3s//jIGVKPvAFeZ7FafBmC/54bgYw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v4 05/21] ipa: libipa: awb_bayes: Fix initial max value Date: Fri, 13 Nov 2026 17:40:38 +0100 Message-ID: <20261113164054.267164-6-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" For floating-point types `std::numeric_limits::min()` is the smallest normal positive value. So `lowest()` needs to be used otherwise e.g. a value of 0 will not be properly handled. This type is only used for logging, so no effects on the algorithm. Fixes: c2059585f3c7 ("libipa: awb_bayes: Add logging of value limits") Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/libipa/awb_bayes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp index 10e05962b8..7f3237c0ab 100644 --- a/src/ipa/libipa/awb_bayes.cpp +++ b/src/ipa/libipa/awb_bayes.cpp @@ -66,7 +66,7 @@ class LimitsRecorder public: LimitsRecorder() : min_(std::numeric_limits::max()), - max_(std::numeric_limits::min()) + max_(std::numeric_limits::lowest()) { } From patchwork Fri Nov 13 16:40:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28893 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 66C01C3304 for ; Fri, 13 Nov 2026 16:41:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 201526A82A; Fri, 13 Nov 2026 17:41:08 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="b3JQ+jZ0"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 316A66A81A for ; Fri, 13 Nov 2026 17:41:00 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6806B269; Fri, 13 Nov 2026 17:38:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587915; bh=uLeQNlNUnjL2g17dXuAePQH3nVvLgyPBp5J1AXGG8Og=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=b3JQ+jZ0LdsvlMTOET7wnn6KrCcP6oY4FEeE9z/b5Pvw69I5BV7BHF1t5NCm7OYqx s4GtHo910iBMbAjyTfYjrRpC+IaR0+8O3vSO27boiiP/ReoqB34cGwqqNR7yePbBLh Makduv2BpPCACftcChjz+2UQsy+GXce3mgFD4sT8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham Subject: [PATCH v4 06/21] ipa: libipa: gamma: Static assert lookup node count Date: Fri, 13 Nov 2026 17:40:39 +0100 Message-ID: <20261113164054.267164-7-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" Require at least 2, which is the minimum necessary for the algorithm to function at all. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham --- src/ipa/libipa/gamma.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ipa/libipa/gamma.h b/src/ipa/libipa/gamma.h index 9954f94ba2..e5ac3f96f5 100644 --- a/src/ipa/libipa/gamma.h +++ b/src/ipa/libipa/gamma.h @@ -63,6 +63,8 @@ protected: template class GammaAlgorithm : public GammaAlgorithmBase { + static_assert(NLutNodes >= 2); + public: GammaAlgorithm() : GammaAlgorithmBase(NLutNodes) From patchwork Fri Nov 13 16:40:40 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28895 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 16496C3304 for ; Fri, 13 Nov 2026 16:41:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 380426A842; Fri, 13 Nov 2026 17:41:10 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Ndiwpe5E"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9F2F06A823 for ; Fri, 13 Nov 2026 17:41:00 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A7015493; Fri, 13 Nov 2026 17:38:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587916; bh=PVa917y7hCXRg4WsdEjFDtvldwWJoiDt4nnToykjrK0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Ndiwpe5EVFIW/yvYrkrMwsvzy9W2uujzQhElxxgy3t5bp4SyYVta/Bf9kkALE+VGL qry/FU7fU6bSx0Mw4/VA/N2m6xma04SyVbBW/XQInxLu1LWysKQo3gMa0t2isdX6XS 6SQVZ7tBvH/dPhZ6R8b6DajTerSLtPublpMpMQl0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v4 07/21] ipa: libipa: gamma: Accept const segment lengths Date: Fri, 13 Nov 2026 17:40:40 +0100 Message-ID: <20261113164054.267164-8-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The segment lengths are not modified, so accept `const` qualified arrays, etc. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/libipa/gamma.cpp | 2 +- src/ipa/libipa/gamma.h | 2 +- src/ipa/rkisp1/algorithms/goc.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ipa/libipa/gamma.cpp b/src/ipa/libipa/gamma.cpp index ce66f15d6a..20807ac601 100644 --- a/src/ipa/libipa/gamma.cpp +++ b/src/ipa/libipa/gamma.cpp @@ -109,7 +109,7 @@ const float kDefaultGamma = 2.2f; * @return 0 on success, a negative error code otherwise */ int GammaAlgorithmBase::init(ControlInfoMap::Map &controls, const ValueNode &tuningData, - std::span segments) + std::span segments) { /* * If the caller doesn't pass in a segment list we simply construct the diff --git a/src/ipa/libipa/gamma.h b/src/ipa/libipa/gamma.h index e5ac3f96f5..a8872a6c75 100644 --- a/src/ipa/libipa/gamma.h +++ b/src/ipa/libipa/gamma.h @@ -47,7 +47,7 @@ public: } int init(ControlInfoMap::Map &controls, const ValueNode &tuningData, - std::span segments = {}); + std::span segments = {}); void configure(gamma::ActiveState &state); void queueRequest(gamma::ActiveState &state, const uint32_t frame, diff --git a/src/ipa/rkisp1/algorithms/goc.cpp b/src/ipa/rkisp1/algorithms/goc.cpp index 269ae1ea03..f6961ce17f 100644 --- a/src/ipa/rkisp1/algorithms/goc.cpp +++ b/src/ipa/rkisp1/algorithms/goc.cpp @@ -50,7 +50,7 @@ int GammaOutCorrection::init(IPAContext &context, const ValueNode &tuningData) return -EINVAL; } - std::array segments = { + static constexpr std::array segments = { 64, 64, 64, 64, 128, 128, 128, 128, 256, 256, 256, 512, 512, 512, 512, 512 }; From patchwork Fri Nov 13 16:40:41 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28894 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 CA742C3307 for ; Fri, 13 Nov 2026 16:41:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DAD056A837; Fri, 13 Nov 2026 17:41:08 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="wgtvg8l/"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E5C396A824 for ; Fri, 13 Nov 2026 17:41:00 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2D53DB2D for ; Fri, 13 Nov 2026 17:38:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587916; bh=c2ZxZ/1NdkGQDwE7NcdHFqusBLOKmk+LJc7yQnHH6O8=; h=From:To:Subject:Date:In-Reply-To:References:From; b=wgtvg8l/GYd3JzCi2nGq1EvJhdU84tibfGU+XbxEIW97wH0f7S6s8RKO/svR+8AVf KipwEHHQODycwn2NPss5KBFc/v1rodomZDX4c1ja1hZ9GVrHFu0H7uNU1cCVcgXo+M B6ygEU229iPPmIiRM/q1K8ktPn39oxnLzSZyTwEU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v4 08/21] ipa: libipa: gamma: Use `std::optional` for segment lengths Date: Fri, 13 Nov 2026 17:40:41 +0100 Message-ID: <20261113164054.267164-9-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" Add an `init()` function to `GammaAlgorithm`, and make the one in `GammaAlgorithmBase` protected. And take the list of segment lengths in an `std::optional`. This allows the use of a constant sized span, and thus compile-time checking when calling `init()`. Additionally, change "@return" to "\return" in the documentation. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/gamma.cpp | 83 +++++++++++++++++++++++----------------- src/ipa/libipa/gamma.h | 15 ++++++-- 2 files changed, 59 insertions(+), 39 deletions(-) diff --git a/src/ipa/libipa/gamma.cpp b/src/ipa/libipa/gamma.cpp index 20807ac601..ad34548454 100644 --- a/src/ipa/libipa/gamma.cpp +++ b/src/ipa/libipa/gamma.cpp @@ -69,44 +69,13 @@ const float kDefaultGamma = 2.2f; */ /** - * \brief Initialise the algorithm with the given tuning data - * \param[out] controls The ControlList into which this algorithm's supported - * controls will be emplaced. - * \param[in] tuningData The tuning data to use with the algorithm - * \param[in] segments A vector of segment spacings to define a custom - * X coordinate system for the curve - * - * Parse \a tuningData and \a segments to initialize the gamma correction curve. - * The tuning data may contain a default gamma value to use; otherwise the value - * of \a kDefaultGamma will be taken as the default. The piecewise linear - * function will be applied on a number of knots whose position is described by - * the optional \a segments argument, which describes each segment's relative - * length. - * - * For example, if the gamma correction has to be applied on 16 equally spaced - * sampling points, a \a segments array like: - * - * [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + * \copybrief GammaAlgorithm::init() * - * would result in evenly spaced knee-points along the X-axis. - * - * Hardware may expect the knee-points to be spaced more densely towards the - * start of the curve and more sparsely towards the end, in which case an - * alternative array might be: - * - * [1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 8, 8, 8, 8, 8] - * - * As the values in \a segments represent the distance between two knee-points - * relative to the total distance between the first and last point, the length - * of \a segments should be equal to the number of knee-points minus one. - *; - * If an IPA implementation doesnt't provide \a segments, the GammaAlgorithm - * class consturcts an evenly-spaced default. + * This function provides the underlying non-templated implementation for + * GammaAlgorithm::init(). Refer to the documentation of that function + * for the details. * - * IPA modules are expected to call this function as part of their - * implementation of Algorithm::init() - * - * @return 0 on success, a negative error code otherwise + * \sa GammaAlgorithm::init() */ int GammaAlgorithmBase::init(ControlInfoMap::Map &controls, const ValueNode &tuningData, std::span segments) @@ -247,6 +216,48 @@ void GammaAlgorithmBase::process(gamma::FrameContext &context, ControlList &meta * - https://en.wikipedia.org/wiki/SRGB */ +/** + * \fn GammaAlgorithm::init() + * \brief Initialise the algorithm with the given tuning data + * \param[out] controls The ControlList into which this algorithm's supported + * controls will be emplaced. + * \param[in] tuningData The tuning data to use with the algorithm + * \param[in] segments A vector of segment spacings to define a custom + * X coordinate system for the curve + * + * Parse \a tuningData and \a segments to initialize the gamma correction curve. + * The tuning data may contain a default gamma value to use; otherwise the value + * of \a kDefaultGamma will be taken as the default. The piecewise linear + * function will be applied on a number of knots whose position is described by + * the optional \a segments argument, which describes each segment's relative + * length. + * + * For example, if the gamma correction has to be applied on 16 equally spaced + * sampling points, a \a segments array like: + * + * [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + * + * would result in evenly spaced knee-points along the X-axis. + * + * Hardware may expect the knee-points to be spaced more densely towards the + * start of the curve and more sparsely towards the end, in which case an + * alternative array might be: + * + * [1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 8, 8, 8, 8, 8] + * + * As the values in \a segments represent the distance between two knee-points + * relative to the total distance between the first and last point, the length + * of \a segments should be equal to the number of knee-points minus one. + *; + * If an IPA implementation doesn't provide \a segments, the GammaAlgorithm + * class constructs an evenly-spaced default. + * + * IPA modules are expected to call this function as part of their + * implementation of Algorithm::init() + * + * \return 0 on success, a negative error code otherwise + */ + /** * \fn GammaAlgorithm::prepare() * \tparam T The type of data expected by the hardware's look-up table diff --git a/src/ipa/libipa/gamma.h b/src/ipa/libipa/gamma.h index a8872a6c75..bf71423848 100644 --- a/src/ipa/libipa/gamma.h +++ b/src/ipa/libipa/gamma.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include @@ -46,15 +47,15 @@ public: { } - int init(ControlInfoMap::Map &controls, const ValueNode &tuningData, - std::span segments = {}); - void configure(gamma::ActiveState &state); void queueRequest(gamma::ActiveState &state, const uint32_t frame, gamma::FrameContext &context, const ControlList &controls); void process(gamma::FrameContext &context, ControlList &metadata); protected: + int init(ControlInfoMap::Map &controls, const ValueNode &tuningData, + std::span segments); + unsigned int nLutNodes_; float defaultGamma_; std::vector kneePoints_; @@ -71,6 +72,14 @@ public: { } + int init(ControlInfoMap::Map &controls, const ValueNode &tuningData, + std::optional> segments = {}) + { + return GammaAlgorithmBase::init( + controls, tuningData, + segments ? *segments : std::span{}); + } + template void prepare(gamma::FrameContext &context, std::span lut) { From patchwork Fri Nov 13 16:40:42 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28896 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 44A53C3307 for ; Fri, 13 Nov 2026 16:41:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 18B126A836; Fri, 13 Nov 2026 17:41:12 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ebU0rRRr"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 16FCA6A825 for ; Fri, 13 Nov 2026 17:41:01 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6A6C9C6A for ; Fri, 13 Nov 2026 17:38:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587916; bh=Pdp/dyaaLFBpAZ+8nGsI/81xProSdcxNqQVtiqLzlhQ=; h=From:To:Subject:Date:In-Reply-To:References:From; b=ebU0rRRrCMBhCIA1QsZTycZcqlaJioH8XhWb7a+WxyDj3R34EpDap40yNR+DTIPqt VQM3o9Ps0dL6wzCXqkiSZi+BUEdLZVm9cGfBDk5tb4XdoWMmauTLH6EzYNXN98ZqX+ h/D41ilbYYEc+NYhoa9S94VcON8bKed7bYmlVJsk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v4 09/21] ipa: libipa: ccm: Use 32-bit offsets Date: Fri, 13 Nov 2026 17:40:42 +0100 Message-ID: <20261113164054.267164-10-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The currently used 16-bit offsets are not enough for all platforms. Offsets should ideally be defined as normalized numbers instead of effectively "raw" register values. But that requires breaking the tuning files, so it is postponsed, and this temporary solution is used. Link: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/371 Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham --- src/ipa/ipu3/algorithms/ccm.cpp | 2 +- src/ipa/libipa/ccm.cpp | 2 +- src/ipa/libipa/ccm.h | 4 ++-- src/ipa/rkisp1/algorithms/ccm.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ipa/ipu3/algorithms/ccm.cpp b/src/ipa/ipu3/algorithms/ccm.cpp index 105d815b03..95ba67f8d1 100644 --- a/src/ipa/ipu3/algorithms/ccm.cpp +++ b/src/ipa/ipu3/algorithms/ccm.cpp @@ -57,7 +57,7 @@ void Ccm::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t fram void Ccm::setParameters(ipu3_uapi_params *params, IPAFrameContext &context) { const Matrix &matrix = context.ccm.ccm; - const Matrix &offsets = context.ccm.offsets; + const Matrix &offsets = context.ccm.offsets; params->use.acc_ccm = 1; diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp index 8f3652f7e3..a19ae9f778 100644 --- a/src/ipa/libipa/ccm.cpp +++ b/src/ipa/libipa/ccm.cpp @@ -83,7 +83,7 @@ int CcmAlgorithmBase::init(const ValueNode &tuningData) << "Failed to parse 'offsets' " << "parameter from tuning file; falling back to zero offsets"; - offsets_.setData({ { 0, Matrix({ 0, 0, 0 }) } }); + offsets_.setData({ { 0, Matrix({ 0, 0, 0 }) } }); } return 0; diff --git a/src/ipa/libipa/ccm.h b/src/ipa/libipa/ccm.h index b72b25e2a3..c17d1ef303 100644 --- a/src/ipa/libipa/ccm.h +++ b/src/ipa/libipa/ccm.h @@ -23,7 +23,7 @@ namespace ccm { struct CcmContext { Matrix ccm; - Matrix offsets; + Matrix offsets; }; struct ActiveState { @@ -50,7 +50,7 @@ public: private: unsigned int ct_; Interpolator> ccm_; - Interpolator> offsets_; + Interpolator> offsets_; }; template diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp index 0e54c98710..f54edd44d1 100644 --- a/src/ipa/rkisp1/algorithms/ccm.cpp +++ b/src/ipa/rkisp1/algorithms/ccm.cpp @@ -67,7 +67,7 @@ void Ccm::queueRequest(IPAContext &context, void Ccm::setParameters(RkISP1Params *params, IPAFrameContext &context) { const Matrix &matrix = context.ccm.ccm; - const Matrix &offsets = context.ccm.offsets; + const Matrix &offsets = context.ccm.offsets; auto config = params->block(); config.setEnabled(true); From patchwork Fri Nov 13 16:40:43 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28897 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 2A372C3304 for ; Fri, 13 Nov 2026 16:41:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7CB716A845; Fri, 13 Nov 2026 17:41:15 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="bvENJlQr"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2D6126A82F for ; Fri, 13 Nov 2026 17:41:05 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9B202219; Fri, 13 Nov 2026 17:38:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587920; bh=Z1wv1H8QWditWJ4cii/WUwy0MMoTmcjkLJ+4kC04f7U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=bvENJlQrvnfmaouu0J5OYneC6/zBvoHDIwdC/thIxOrbo3NFm/36R41y1uchpmbhg rX0XPZy+h0wD2jMcXrm1ISPADQt0ShGhwIW5k4WBxgOVGrjGC1P17JX9N22pfTlUs5 fJyVKu5+Zw9tMdxVzlnxmQW0hghAwUrivzmoRyws= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v4 10/21] include: linux: Update to Linux 7.3-rc3 Date: Fri, 13 Nov 2026 17:40:43 +0100 Message-ID: <20261113164054.267164-11-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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: Jacopo Mondi Update the kernel headers to v7.3-rc3 using utils/update-kernel-headers.sh. Preserve the libcamera local modifications manually. This in particular includes the definitions of RPP-X1. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- include/linux/README | 2 +- include/linux/drm.h | 7 + include/linux/drm_fourcc.h | 244 ++++++- include/linux/drm_mode.h | 1 + include/linux/media/dreamchip/rppx1-config.h | 730 +++++++++++++++++++ include/linux/rkisp1-config.h | 113 ++- include/linux/v4l2-controls.h | 2 + include/linux/videodev2.h | 4 + utils/update-kernel-headers.sh | 1 + 9 files changed, 1078 insertions(+), 26 deletions(-) create mode 100644 include/linux/media/dreamchip/rppx1-config.h diff --git a/include/linux/README b/include/linux/README index 9303e0f14a..574168af2e 100644 --- a/include/linux/README +++ b/include/linux/README @@ -1,4 +1,4 @@ # SPDX-License-Identifier: CC0-1.0 -Files in this directory are imported from v7.1 of the Linux kernel. Do not +Files in this directory are imported from v7.3-rc3 of the Linux kernel. Do not modify them manually. diff --git a/include/linux/drm.h b/include/linux/drm.h index 5eecc8bed5..b051d2da48 100644 --- a/include/linux/drm.h +++ b/include/linux/drm.h @@ -1299,6 +1299,13 @@ extern "C" { */ #define DRM_IOCTL_MODE_GETFB2 DRM_IOWR(0xCE, struct drm_mode_fb_cmd2) +/** + * DRM_IOCTL_SYNCOBJ_EVENTFD - Register an eventfd to be signalled by a syncobj. + * + * This can be used to integrate a syncobj in an event loop. + * + * The IOCTL argument is a struct drm_syncobj_eventfd. + */ #define DRM_IOCTL_SYNCOBJ_EVENTFD DRM_IOWR(0xCF, struct drm_syncobj_eventfd) /** diff --git a/include/linux/drm_fourcc.h b/include/linux/drm_fourcc.h index 8e2a6abae6..b4e8bda8ef 100644 --- a/include/linux/drm_fourcc.h +++ b/include/linux/drm_fourcc.h @@ -224,9 +224,9 @@ extern "C" { * [31:0] sign:exponent:mantissa 1:8:23 */ #define DRM_FORMAT_R32F fourcc_code('R', ' ', ' ', 'F') /* [31:0] R 32 little endian */ -#define DRM_FORMAT_GR3232F fourcc_code('G', 'R', ' ', 'F') /* [63:0] R:G 32:32 little endian */ -#define DRM_FORMAT_BGR323232F fourcc_code('B', 'G', 'R', 'F') /* [95:0] R:G:B 32:32:32 little endian */ -#define DRM_FORMAT_ABGR32323232F fourcc_code('A', 'B', '8', 'F') /* [127:0] R:G:B:A 32:32:32:32 little endian */ +#define DRM_FORMAT_GR3232F fourcc_code('G', 'R', ' ', 'F') /* [63:0] G:R 32:32 little endian */ +#define DRM_FORMAT_BGR323232F fourcc_code('B', 'G', 'R', 'F') /* [95:0] B:G:R 32:32:32 little endian */ +#define DRM_FORMAT_ABGR32323232F fourcc_code('A', 'B', '8', 'F') /* [127:0] A:B:G:R 32:32:32:32 little endian */ /* * RGBA format with 10-bit components packed in 64-bit per pixel, with 6 bits @@ -246,6 +246,7 @@ extern "C" { #define DRM_FORMAT_XVUY8888 fourcc_code('X', 'V', 'U', 'Y') /* [31:0] X:Cr:Cb:Y 8:8:8:8 little endian */ #define DRM_FORMAT_VUY888 fourcc_code('V', 'U', '2', '4') /* [23:0] Cr:Cb:Y 8:8:8 little endian */ #define DRM_FORMAT_VUY101010 fourcc_code('V', 'U', '3', '0') /* Y followed by U then V, 10:10:10. Non-linear modifier only */ +#define DRM_FORMAT_XVUY2101010 fourcc_code('X', 'Y', '3', '0') /* [31:0] x:Cr:Cb:Y 2:10:10:10 little endian */ /* * packed Y2xx indicate for each component, xx valid data occupy msb @@ -361,6 +362,14 @@ extern "C" { */ #define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0') /* 2x2 subsampled Cr:Cb plane 10 bits per channel packed */ +/* + * 2 plane YCbCr422. + * 3 10 bit components and 2 padding bits packed into 4 bytes. + * index 0 = Y plane, [31:0] x:Y2:Y1:Y0 2:10:10:10 little endian + * index 1 = Cr:Cb plane, [63:0] x:Cr2:Cb2:Cr1:x:Cb1:Cr0:Cb0 [2:10:10:10:2:10:10:10] little endian + */ +#define DRM_FORMAT_P230 fourcc_code('P', '2', '3', '0') /* 2x1 subsampled Cr:Cb plane 10 bits per channel packed */ + /* 3 plane non-subsampled (444) YCbCr * 16 bits per component, but only 10 bits are used and 6 bits are padded * index 0: Y plane, [15:0] Y:x [10:6] little endian @@ -377,6 +386,15 @@ extern "C" { */ #define DRM_FORMAT_Q401 fourcc_code('Q', '4', '0', '1') +/* + * 3 plane non-subsampled (444) YCbCr LSB aligned + * 10 bpc, 30 bits per sample image data in a single contiguous buffer. + * index 0: Y plane, [31:0] x:Y2:Y1:Y0 [2:10:10:10] little endian + * index 1: Cb plane, [31:0] x:Cb2:Cb1:Cb0 [2:10:10:10] little endian + * index 2: Cr plane, [31:0] x:Cr2:Cr1:Cr0 [2:10:10:10] little endian + */ +#define DRM_FORMAT_T430 fourcc_code('T', '4', '3', '0') + /* * 3 plane YCbCr LSB aligned * In order to use these formats in a similar fashion to MSB aligned ones @@ -433,6 +451,17 @@ extern "C" { #define DRM_FORMAT_YUV444 fourcc_code('Y', 'U', '2', '4') /* non-subsampled Cb (1) and Cr (2) planes */ #define DRM_FORMAT_YVU444 fourcc_code('Y', 'V', '2', '4') /* non-subsampled Cr (1) and Cb (2) planes */ +/* + * Y-only (greyscale) formats + * + * The Y-only formats are handled similarly to the YCbCr formats in the display + * pipeline, with the Cb and Cr implicitly neutral (0.0 in nominal values). This + * also means that COLOR_RANGE property applies to the Y-only formats. + */ + +#define DRM_FORMAT_Y8 fourcc_code('G', 'R', 'E', 'Y') /* 8-bit Y-only */ +#define DRM_FORMAT_XYYY2101010 fourcc_code('Y', 'P', 'A', '4') /* [31:0] x:Y2:Y1:Y0 2:10:10:10 little endian */ + /* Compressed formats */ #define DRM_FORMAT_MJPEG fourcc_code('M', 'J', 'P', 'G') /* Motion-JPEG */ @@ -1694,36 +1723,69 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier) * For multi-plane formats the above surfaces get merged into one plane for * each format plane, based on the required alignment only. * - * Bits Parameter Notes - * ----- ------------------------ --------------------------------------------- + * Bits Parameter Notes + * ------- ------------------------ --------------------------------------------- + * + * DRM format modifier fields on AMD GPUs: + * 7:0 TILE_VERSION Values are AMD_FMT_MOD_TILE_VER_* + * 12:8 TILE Values are AMD_FMT_MOD_TILE__* + * 13 DCC Delta Color Compression, supported on GFX8 and newer + * 55:14 (chip specific) See below for details, depends on GFX block version + * 63:56 Vendor Value is DRM_FORMAT_MOD_VENDOR_AMD + * + * Chip specific fields on Gfx9 and newer: + * 14 DCC_RETILE + * 15 DCC_PIPE_ALIGN + * 16 DCC_INDEPENDENT_64B + * 17 DCC_INDEPENDENT_128B + * 19:18 DCC_MAX_COMPRESSED_BLOCK Values are AMD_FMT_MOD_DCC_BLOCK_* + * 20 DCC_CONSTANT_ENCODE + * 23:21 PIPE_XOR_BITS Only for some chips + * 26:24 BANK_XOR_BITS Only for some chips + * 29:27 PACKERS Only for some chips + * 32:30 RB Only for some chips + * 35:33 PIPE Only for some chips + * 55:36 - Reserved for future use, must be zero + * + * Chip specific fields on Gfx6-8: + * 16:14 MICROTILE Micro tile format + * 21:17 PIPE_CONFIG Number of pipes and how pipes are interleaved + * 24:22 TILE_SPLIT Tile split size + * 26:25 BANK_WIDTH Number of tiles in the X direction in the same bank + * 28:27 BANK_HEIGHT Number of tiles in the Y direction in the same bank + * 30:29 MACRO_TILE_ASPECT Macro tile aspect ratio + * 32:31 NUM_BANKS Number of banks + * 55:33 - Reserved for future use, must be zero * - * 7:0 TILE_VERSION Values are AMD_FMT_MOD_TILE_VER_* - * 12:8 TILE Values are AMD_FMT_MOD_TILE__* - * 13 DCC - * 14 DCC_RETILE - * 15 DCC_PIPE_ALIGN - * 16 DCC_INDEPENDENT_64B - * 17 DCC_INDEPENDENT_128B - * 19:18 DCC_MAX_COMPRESSED_BLOCK Values are AMD_FMT_MOD_DCC_BLOCK_* - * 20 DCC_CONSTANT_ENCODE - * 23:21 PIPE_XOR_BITS Only for some chips - * 26:24 BANK_XOR_BITS Only for some chips - * 29:27 PACKERS Only for some chips - * 32:30 RB Only for some chips - * 35:33 PIPE Only for some chips - * 55:36 - Reserved for future use, must be zero */ #define AMD_FMT_MOD fourcc_mod_code(AMD, 0) #define IS_AMD_FMT_MOD(val) (((val) >> 56) == DRM_FORMAT_MOD_VENDOR_AMD) -/* Reserve 0 for GFX8 and older */ +#define AMD_FMT_MOD_TILE_VER_GFX6 0 #define AMD_FMT_MOD_TILE_VER_GFX9 1 #define AMD_FMT_MOD_TILE_VER_GFX10 2 #define AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS 3 #define AMD_FMT_MOD_TILE_VER_GFX11 4 #define AMD_FMT_MOD_TILE_VER_GFX12 5 +/* + * Gfx6-8 tiling modes. + * A complete reference implementation is found in addrlib in the Mesa code base. + * + * - Microtiled modes (1D): + * Pixel data is organized into micro tiles of 8x8 pixels. + * + * - Macrotiled modes (2D): + * Micro tiles are further organized into macro tiles. + * These are optimized for even load distribution among memory channels. + * + * Note that only THIN1 modes are exposed here. + * THICK and XTHICK are for 3D images and not relevant to DRM format modifiers. + */ +#define AMD_FMT_MOD_TILE_GFX6_1D_TILED_THIN1 0x2 +#define AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1 0x4 + /* * 64K_S is the same for GFX9/GFX10/GFX10_RBPLUS and hence has GFX9 as canonical * version. @@ -1822,6 +1884,146 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier) #define AMD_FMT_MOD_PIPE_SHIFT 33 #define AMD_FMT_MOD_PIPE_MASK 0x7 +/* + * MICRO_TILE_MODE, 3 bits. Determines the micro tile format. + * Only relevant to Gfx6-8. + * + * DISPLAY - Displayable tiling + * THIN - Non-displayable tiling, a.k.a thin micro tiling + * DEPTH, THICK - not exposed, not relevant to DRM format modifier use cases + * ROTATED - not exposed, not implemented in Linux or Mesa + */ +#define AMD_FMT_MOD_MICROTILE_SHIFT 14ULL +#define AMD_FMT_MOD_MICROTILE_MASK 0x7 + +#define AMD_FMT_MOD_MICROTILE_DISPLAY 0x0 +#define AMD_FMT_MOD_MICROTILE_THIN 0x1 + +/* + * PIPE_CONFIG, 5 bits. Number of pipes and how pipes are interleaved on the surface, + * which means the shader engine tile size and packer tile size. + * Typically matches the number of memory channels, or number of RBs. + * Only relevant to Gfx6-8 macro tiled modes. + * + * P_x_x + * where: + * - number of pipes + * x - shader engine tile size + * x - packer tile size + */ +#define AMD_FMT_MOD_PIPE_CONFIG_SHIFT 17ULL +#define AMD_FMT_MOD_PIPE_CONFIG_MASK 0x1f + +#define AMD_FMT_MOD_PIPE_CONFIG_P2 0x0 +#define AMD_FMT_MOD_PIPE_CONFIG_P4_8x16 0x4 +#define AMD_FMT_MOD_PIPE_CONFIG_P4_16x16 0x5 +#define AMD_FMT_MOD_PIPE_CONFIG_P4_16x32 0x6 +#define AMD_FMT_MOD_PIPE_CONFIG_P4_32x32 0x7 +#define AMD_FMT_MOD_PIPE_CONFIG_P8_16x16_8x16 0x8 +#define AMD_FMT_MOD_PIPE_CONFIG_P8_16x32_8x16 0x9 +#define AMD_FMT_MOD_PIPE_CONFIG_P8_32x32_8x16 0xa +#define AMD_FMT_MOD_PIPE_CONFIG_P8_16x32_16x16 0xb +#define AMD_FMT_MOD_PIPE_CONFIG_P8_32x32_16x16 0xc +#define AMD_FMT_MOD_PIPE_CONFIG_P8_32x32_16x32 0xd +#define AMD_FMT_MOD_PIPE_CONFIG_P8_32x64_32x32 0xe +#define AMD_FMT_MOD_PIPE_CONFIG_P16_32x32_8x16 0x10 +#define AMD_FMT_MOD_PIPE_CONFIG_P16_32x32_16x16 0x11 + +/* + * TILE_SPLIT, 3 bits. + * Only relevant to Gfx6-8 macro tiled modes. + * + * On GFX6 (or with depth tiling modes on GFX7 and newer), + * the GFX block uses the GB_TILE_MODE.TILE_SPLIT field directly. + * + * On GFX7 and newer with non-depth tiling modes, the GFX block uses a + * split factor which is stored in the GB_TILE_MODE.SAMPLE_SPLIT field. + * SAMPLE_SPLIT may be: 0 - 1 byte; 1 - 2 bytes; 2 - 4 bytes; 3 - 8 bytes. + * The actual tile size and tile split bytes are calculated as follows: + * + * bpp = ... <- bits per pixel in the current image + * thickness = ... <- depends on array mode; may be: 1, 4, 8 + * num_samples = ... <- number of samples in the current image + * tile_size_pixels = 8 * 8 + * tile_bytes_1x = thickness * tile_size_pixels * bpp / 8 + * sample_split_factor = 1 << SAMPLE_SPLIT + * tile_split_bytes = clamp(tile_bytes_1x * sample_split_factor, 256, dram_row_size_bytes) + * tile_bytes = clamp(tile_bytes_1x * num_samples, 64, tile_split_bytes) + * + * In both cases, the display block (DCE) has no SAMPLE_SPLIT + * and just needs the tile split bytes in the GRPH_CONTROL.GRPH_TILE_SPLIT field. + * To maximize compatibility between GFX6-7, we don't include the SAMPLE_SPLIT + * in the format modifiers. + * + * The actual tile split in bytes is: 64 << field value + * Possible values of this field: + * + * 0 - Tile split is 64 bytes + * 1 - Tile split is 128 bytes + * 2 - Tile split is 256 bytes + * 3 - Tile split is 512 bytes + * 4 - Tile split is 1 KiB + * 5 - Tile split is 2 KiB + * 6 - Tile split is 4 KiB + */ +#define AMD_FMT_MOD_TILE_SPLIT_SHIFT 22ULL +#define AMD_FMT_MOD_TILE_SPLIT_MASK 0x7 + +/* + * BANK_WIDTH, 2 bits. Number of tiles in the X direction in the same bank. + * Only relevant to Gfx6-8 macro tiled modes. + * The actual bank width is: 1 << field value + * Possible values: + * + * 0 - bank width is 1 + * 1 - bank width is 2 + * 2 - bank width is 4 + * 3 - bank width is 8 + */ +#define AMD_FMT_MOD_BANK_WIDTH_SHIFT 25ULL +#define AMD_FMT_MOD_BANK_WIDTH_MASK 0x3 + +/* + * BANK_HEIGHT, 2 bits. Number of tiles in the Y direction in the same bank. + * Only relevant to Gfx6-8 macro tiled modes. + * The actual bank height is: 1 << field value + * Possible values: + * + * 0 - bank height is 1 + * 1 - bank height is 2 + * 2 - bank height is 4 + * 3 - bank height is 8 + */ +#define AMD_FMT_MOD_BANK_HEIGHT_SHIFT 27ULL +#define AMD_FMT_MOD_BANK_HEIGHT_MASK 0x3 + +/* + * MACRO_TILE_ASPECT, 2 bits. Macro tile aspect ratio. + * Only relevant to Gfx6-8 macro tiled modes. + * Possible values: + * + * 0 - aspect ratio is 1:1 + * 1 - aspect ratio is 4:1 + * 2 - aspect ratio is 16:1 + * 3 - aspect ratio is 64:1 + */ +#define AMD_FMT_MOD_MACRO_TILE_ASPECT_SHIFT 29ULL +#define AMD_FMT_MOD_MACRO_TILE_ASPECT_MASK 0x3 + +/* + * NUM_BANKS, 2 bits. Number of banks. + * Only relevant to Gfx6-8 macro tiled modes. + * The actual number of banks is: 2 << field value + * Possible values: + * + * 0 - number of banks is 2 + * 1 - number of banks is 4 + * 2 - number of banks is 8 + * 3 - number of banks is 16 + */ +#define AMD_FMT_MOD_NUM_BANKS_SHIFT 31ULL +#define AMD_FMT_MOD_NUM_BANKS_MASK 0x3 + #define AMD_FMT_MOD_SET(field, value) \ ((__u64)(value) << AMD_FMT_MOD_##field##_SHIFT) #define AMD_FMT_MOD_GET(field, value) \ diff --git a/include/linux/drm_mode.h b/include/linux/drm_mode.h index 381a3e857d..bd435effdc 100644 --- a/include/linux/drm_mode.h +++ b/include/linux/drm_mode.h @@ -155,6 +155,7 @@ extern "C" { /* Panel type property */ #define DRM_MODE_PANEL_TYPE_UNKNOWN 0 #define DRM_MODE_PANEL_TYPE_OLED 1 +#define DRM_MODE_PANEL_TYPE_LCD 2 /* * DRM_MODE_ROTATE_ diff --git a/include/linux/media/dreamchip/rppx1-config.h b/include/linux/media/dreamchip/rppx1-config.h new file mode 100644 index 0000000000..3507a9ab03 --- /dev/null +++ b/include/linux/media/dreamchip/rppx1-config.h @@ -0,0 +1,730 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Dreamchip RPP-X1 ISP Driver - Userspace API + * + * Copyright (C) 2026 Renesas Electronics Corp. + * Copyright (C) 2026 Ideas on Board Oy + * Copyright (C) 2026 Ragnatech AB + */ + +#ifndef __UAPI_RPP_X1_CONFIG_H +#define __UAPI_RPP_X1_CONFIG_H + +#include + +/** + * struct rppx1_window - Measurement window + * + * RPP-X1 measurement window. Different blocks use a window or multiple + * windows for measurement purposes. This defines a common type for all of + * them. The number of relevant bits depends on the block where the window is + * used and is specified in the per-block description + * + * @h_offs: horizontal offset from the left of the frame in pixels + * @v_offs: vertical offset from the top of the frame in pixels + * @h_size: horizontal size of the window in pixels + * @v_size: vertical size of the window in pixels + */ +struct rppx1_window { + __u16 h_offs; + __u16 v_offs; + __u16 h_size; + __u16 v_size; +}; + +/** + * enum rppx1_meas_chan - Measurement point for the Histogram and EXM Modules + * + * Measurement points for the RPP-X1 Histogram measurement module and Exposure + * measurement module. + * + * All tap points are available for the PRE1/PRE2 pipes. Only + * RPPX1_MEAS_CHAN_SEL4 and RPPX1_MEAS_CHAN_SEL7 are available for the + * MAIN_POST pipe. + * + * @RPPX1_MEAS_CHAN_SEL0: after input acquisition + * @RPPX1_MEAS_CHAN_SEL1: after black level subtraction + * @RPPX1_MEAS_CHAN_SEL2: after sensor gamma linearization + * @RPPX1_MEAS_CHAN_SEL3: after lens shading correction + * @RPPX1_MEAS_CHAN_SEL4: after auto white balance gains + * @RPPX1_MEAS_CHAN_SEL5: after defect pixel correction + * @RPPX1_MEAS_CHAN_SEL6: after denoise pre-filter + * @RPPX1_MEAS_CHAN_SEL7: after demosaicing + */ +enum rppx1_meas_chan { + RPPX1_MEAS_CHAN_SEL0, + RPPX1_MEAS_CHAN_SEL1, + RPPX1_MEAS_CHAN_SEL2, + RPPX1_MEAS_CHAN_SEL3, + RPPX1_MEAS_CHAN_SEL4, + RPPX1_MEAS_CHAN_SEL5, + RPPX1_MEAS_CHAN_SEL6, + RPPX1_MEAS_CHAN_SEL7, +}; + +/* --------------------------------------------------------------------------- + * Parameter Structures + * + * The same ISP block might be instantiated in multiple pipeliness and operate + * on a different bitdepth/precision. For fields of varying length among + * different instances of the same block, use a data type that can accommodate + * the larger bitdepth/precision. + */ + +/** + * enum rppx1_params_block_type - RPP-X1 extensible params block types + * + * NOTE: Only append to the enumeration as the numbers are uAPI. + * + * @RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST: AWB Measurement Configuration + * @RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1: PRE1 pipe White Balance Gains + * @RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE2: PRE2 White Balance Gains + * @RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST: MAIN_POST White Balance Gains + * @RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE1: PRE1 pipe Exposure Measurement + * @RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE2: PRE2 pipe Exposure Measurement + * @RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE1: PRE1 pipe Histogram Measurement + * @RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE2: PRE2 pipe Histogram Measurement + * @RPPX1_PARAMS_BLOCK_TYPE_HIST_POST: POST pipe Histogram Measurement + * @RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1: PRE1 pipe Black Level Subtraction + * @RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2: PRE2 pipe Black Level Subtraction + * @RPPX1_PARAMS_BLOCK_TYPE_CCOR_POST: POST pipe Color Correction + * @RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE1: PRE1 pipe Lens Shading Correction + * @RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE2: PRE2 pipe Lens Shading Correction + * @RPPX1_PARAMS_BLOCK_TYPE_GA_HV: Human Vision Pipe Gamma Out Correction + * @RPPX1_PARAMS_BLOCK_TYPE_GA_MV: Machine Vision Gamma Out Correction + * @RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE1: PRE1 pipe Linearization (Sensor De-gamma) + * @RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE2: PRE2 pipe Linearization (Sensor De-gamma) + */ +enum rppx1_params_block_type { + RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST, + RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1, + RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE2, + RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST, + RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE1, + RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE2, + RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE1, + RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE2, + RPPX1_PARAMS_BLOCK_TYPE_HIST_POST, + RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1, + RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2, + RPPX1_PARAMS_BLOCK_TYPE_CCOR_POST, + RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE1, + RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE2, + RPPX1_PARAMS_BLOCK_TYPE_GA_HV, + RPPX1_PARAMS_BLOCK_TYPE_GA_MV, + RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE1, + RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE2, +}; + +/** + * enum rppx1_wbmeas_mode - AWB measurement mode + * + * @RPPX1_WBMEAS_MODE_YCBCR: YCbCr measurement mode + * @RPPX1_WBMEAS_MODE_RGB: RGB measurement mode + */ +enum rppx1_wbmeas_mode { + RPPX1_WBMEAS_MODE_YCBCR, + RPPX1_WBMEAS_MODE_RGB, +}; + +/** + * struct rppx1_wbmeas_params - AWB measurement configuration + * + * The Auto-White Balance measurement module is available on the MAIN_POST pipe. + * It supports two measurement modes, selected by the @mode field. The + * measurement window is programmed through the @wnd field. + * + * To support measurement in YCbCr mode a color conversion matrix with + * programmable offset is available in the @ccor_coeff and @ccor_offs fields. + * The color conversion matrix coefficients are represented as 16 bits signed + * Q4.12 numbers ranging from -8 to +7.99. The per-color channel offsets are + * represented as 25 bits 2's complement integer numbers ranging from -16777216 + * to +16777215. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST) + * @wnd: measurement window + * @mode: measurement mode (from enum rppx1_wbmeas_mode) + * @ymax_cmp: enable Y_MAX compare using @max_y + * @frames: number of frames for mean value calculation (0 = 1 frame) + * @reserved: padding + * @ref_cr_max_r: reference Cr or max red value in RGB mode, 24 bits + * @ref_cb_max_b: reference Cb or max blue value in RGB mode, 24 bits + * @min_y_max_g: luminance minimum value or max green value in RGB mode, 24 bits + * @max_y: luminance maximum value, only valid if @mode is set to YCbCr and + * @ymax_cmp is set to enabled, 24 bits + * @max_csum: chrominance sum maximum value, 24 bits + * @min_c: chrominance minimum value, 24 bits + * @ccor_coeff: coefficients for color conversion matrix, signed 16 bits Q4.6 + * @reserved2: padding + * @ccor_offs: R-G-B color conversion coefficients, signed 25 bits 2's complement + * @reserved3: padding + */ +struct rppx1_wbmeas_params { + struct v4l2_isp_params_block_header header; + struct rppx1_window wnd; + __u8 mode; + __u8 ymax_cmp; + __u8 frames; + __u8 reserved; + __u32 ref_cr_max_r; + __u32 ref_cb_max_b; + __u32 min_y_max_g; + __u32 max_y; + __u32 max_csum; + __u32 min_c; + __u16 ccor_coeff[3][3]; + __u16 reserved2; + __u32 ccor_offs[3]; + __u32 reserved3; +}; + +/** + * struct rppx1_awbg_params - WB gain configuration + * + * The RPP-X1 White Balance Gain module is available in the PRE1 and PRE2 + * pre-fusion pipes and in the MAIN_POST post-fusion pipe. Userspace selects + * which pipe to operate by setting the @header.type field to + * RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1, RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE2 + * or RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST. + * + * The White Balance module allows to specify per-color channel gains, expressed + * as unsigned fixed-point values as 18 bits unsigned integers in Q6.12 format + * with a maximum of 63.999. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1 or + * type = RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE2 or + * type = RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST) + * @gain_red: gain for red component, 18-bit (unsigned Q6.12) + * @gain_green_r: gain for green component in red lines, 18-bit (unsigned Q6.12) + * @gain_blue: gain for blue component, 18-bit (unsigned Q6.12) + * @gain_green_b: gain for green component in blue lines, 18-bit (unsigned Q6.12) + */ +struct rppx1_awbg_params { + struct v4l2_isp_params_block_header header; + __u32 gain_red; + __u32 gain_green_r; + __u32 gain_blue; + __u32 gain_green_b; +}; + +/** + * enum rppx1_exm_mode - Exposure measurement mode + * + * Exaposure measurement mode selection (RGB/Bayer). + * + * @RPPX1_EXP_MEASURING_MODE_DISABLED: no measurement + * @RPPX1_EXP_MEASURING_MODE_RGB: Y/R/G/B measurement + * @RPPX1_EXP_MEASURING_MODE_BAYER: Bayer RGB measurement + */ +enum rppx1_exm_mode { + RPPX1_EXP_MEASURING_MODE_DISABLED, + RPPX1_EXP_MEASURING_MODE_RGB, + RPPX1_EXP_MEASURING_MODE_BAYER, +}; + +/** + * struct rppx1_exm_params - Exposure measurement configuration + * + * The RPP-X1 Exposure measurement unit is available on the PRE1 and PRE2 + * pre-fusion pipes. Userspace selects which pipe to operate by setting + * the @header.type field to RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE1 or + * RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE2. + * + * Exposure measurement is performed in the RGB or Bayer domain, according to + * the setting of the @mode field. The exposure measurement tap point is + * selected according to the value of @channel_sel. + * + * The exposure measurement is performed on an input window specified in @wnd. + * To each color component a programmable weight coefficient is associated. + * Coefficients are represented as unsigned 8 bits integer values in Q1.7 format + * ranging from 0 to 1.992. + * + * The @last_line fields controls when the exposure measurement completes. It + * is usually programmed to the value of (@wnd.v_offs + @wnd.v_size + 1). + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE1 or + * type = RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE2) + * @wnd: measurement window coordinates + * @mode: exposure measure mode (from enum rppx1_exm_mode) + * @last_line: line number for which the exposure measurement completes + * @channel_sel: exposure measurement point (see enum rppx1_meas_chan) + * @coeff_r: coefficient for the red Bayer sample or red color channel, Q1.7 + * @coeff_g_gr: coefficient for the green/red Bayer sample or green color channel, Q1.7 + * @coeff_b: coefficient for the blue Bayer sample or blue color channel, Q1.7 + * @coeff_gb: coefficient for the green/blue Bayer sample, unused in RGB mode, Q1.7 + * @reserved: padding + */ +struct rppx1_exm_params { + struct v4l2_isp_params_block_header header; + struct rppx1_window wnd; + __u32 mode; + __u32 last_line; + __u8 channel_sel; + __u8 coeff_r; + __u8 coeff_g_gr; + __u8 coeff_b; + __u8 coeff_gb; + __u8 reserved[3]; +}; + +/* Histogram */ +#define RPPX1_HIST_WEIGHT_GRIDS_SIZE 25 + +/** + * enum rppx1_hist_mode - Histogram measurement mode + * + * Histogram measurement mode. Select which channel or combination of channels + * the histogram measurement is performed on. + * + * @RPPX1_HIST_MODE_DISABLE: histogram disabled + * @RPPX1_HIST_MODE_RGB_COMBINED: combined RGB histogram + * @RPPX1_HIST_MODE_R_HISTOGRAM: red channel histogram + * @RPPX1_HIST_MODE_GR_HISTOGRAM: green/red channel histogram + * @RPPX1_HIST_MODE_B_HISTOGRAM: blue channel histogram + * @RPPX1_HIST_MODE_GB_HISTOGRAM: green/blue histogram + */ +enum rppx1_hist_mode { + RPPX1_HIST_MODE_DISABLE, + RPPX1_HIST_MODE_RGB_COMBINED, + RPPX1_HIST_MODE_R_HISTOGRAM, + RPPX1_HIST_MODE_GR_HISTOGRAM, + RPPX1_HIST_MODE_B_HISTOGRAM, + RPPX1_HIST_MODE_GB_HISTOGRAM, +}; + +/** + * struct rppx1_hist_params - Histogram measurement configuration + * + * The RPP-X1 Histogram measurement unit is available on the PRE1, PRE2 and + * MAIN_POST pipes. Userspace selects which pipe to operate by setting the + * @header.type field to RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE1, + * RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE2 or + * RPPX1_PARAMS_BLOCK_TYPE_HIST_POST. + * + * The histogram measurement point is selected using the @channel field while + * histogram measurement mode is selected using the @mode field. + * + * Histogram measurement is performed by programming subsampling factors using + * the @v_stepsize and @h_step_inc fields and by weighted windowing, by + * programming the size of the measurement window @wnd with @weights associated + * to each cell of the 5x5 measurement grid. Weights are represented as 5 bits + * integer values ranging from 0 to 16. + * + * The @last_line fields controls when the histogram measurement completes. It + * is usually programmed to the value of (@wnd.v_offs + @wnd.v_size - 1). + * + * Histogram values are calculated by applying a per-color channel coefficient + * represented as an 8 bits unsigned Q1.7 integer value. The @sample_offs and + * @sample_shift fields allow to reduce the color dynamic range on which + * histogram data are produced. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE1, + * type = RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE2 or + * type = RPPX1_PARAMS_BLOCK_TYPE_HIST_POST) + * @wnd: measurement window coordinates + * @last_line: line number for which the histogram measurement completes + * @v_stepsize: vertical subsampling divider, 7 bits + * @h_step_inc: horizontal subsampling step counter, 17 bits + * @sample_offs: sample offset, 24 bits + * @mode: histogram measurement mode (from enum rppx1_hist_mode) + * @channel_sel: histogram measurement point (see enum rppx1_meas_chan) + * @weights: weighting factors for each sub-window (5x5 grid) + * @coeff: R-G-B coefficients, 8 bits unsigned Q1.7 + * @sample_shift: sample shift, 4 bits + * @reserved: padding + */ +struct rppx1_hist_params { + struct v4l2_isp_params_block_header header; + struct rppx1_window wnd; + __u32 last_line; + __u32 v_stepsize; + __u32 h_step_inc; + __u32 sample_offs; + __u8 mode; + __u8 channel_sel; + __u8 weights[RPPX1_HIST_WEIGHT_GRIDS_SIZE]; + __u8 coeff[3]; + __u8 sample_shift; + __u8 reserved; +}; + +/** + * struct rppx1_bls_fixed - BLS fixed subtraction values + * + * Fixed black level values subtracted from sensor data per Bayer channel. + * Negative values result in addition. + * + * The PRE1 pipe BLS module operates on a 24-bits input data and fixed black + * levels are stored as a signed 2's complement representation ranging from + * -2^24 to 2^24-1. + * + * The PRE2 pipe BLS module operates on a 12-bits input data and fixed black + * levels are stored as a signed 2's complement representation ranging from + * -2^12 to 2^12-1. + * + * Userspace is expected to provide fixed black level values with a bit-depth + * matching the one of pipe in use. + * + * These subtraction values are matched with the sensor native Bayer components + * ordering according to the cropping configuration on the input port. + * + * @a: subtraction value for channel A + * @b: subtraction value for channel B + * @c: subtraction value for channel C + * @d: subtraction value for channel D + */ +struct rppx1_bls_fixed { + __u32 a; + __u32 b; + __u32 c; + __u32 d; +}; + +/** + * enum rppx1_bls_mode - BLS subtraction mode + * + * Select if subtracted black level come from fixed or measured values. + * + * @RPPX1_BLS_MODE_FIXED: subtract fixed values + * @RPPX1_BLS_MODE_MEAS: subtract measured values + */ +enum rppx1_bls_mode { + RPPX1_BLS_MODE_FIXED, + RPPX1_BLS_MODE_MEAS, +}; + +/** + * enum rppx1_bls_win_en: BLS measurement configuration + * + * Select the measurement window to use for measured black level values. + * + * @RPPX1_BLS_WIN_EN_OFF: disable measurement + * @RPPX1_BLS_WIN_EN_WIN1: Enable measurement from window 1 + * @RPPX1_BLS_WIN_EN_WIN2: enable measurement from window 2 + * @RPPX1_BLS_WIN_EN_WIN12: enable measurement from window 1 and window 2 + */ +enum rppx1_bls_win_en { + RPPX1_BLS_WIN_EN_OFF, + RPPX1_BLS_WIN_EN_WIN1, + RPPX1_BLS_WIN_EN_WIN2, + RPPX1_BLS_WIN_EN_WIN12, +}; + +/** + * struct rppx1_bls_params - RPP-X1 Black Level Subtraction Module + * + * The RPP-X1 Black Level Subtraction module is available on the PRE1 and PRE2 + * pre-fusion pipes. Userspace selects which pipe to operate by setting the + * @header.type field to RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1 or + * RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2. + * + * The BLS module operates on fixed or measured data according to the setting of + * the @mode field. When RPPX1_BLS_MODE_FIXED is used userspace shall provide + * the per-channel black levels in @fixed. When RPPX1_BLS_MODE_MEAS is used + * userspace shall configure the measurement windows @window1 and optionally + * @window2 to select the optically black pixels region in the input frame. The + * @samples fields controls how many measure samples are used for averaging the + * measured black levels. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1 or + * type == RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2) + * @window1: BLS measurement window 1 (14 bits) + * @window2: BLS measurement window 2 (14 bits) + * @fixed: fixed subtraction values (see enum rppx1_bls_fixed) + * @mode: BLS subtraction mode (see enum rppx1_bls_mode) + * @en_windows: BLS measurement mode (see rppx1_bls_win_en) + * @samples: log2 of the number of measured pixels per Bayer position + * @reserved: padding + */ +struct rppx1_bls_params { + struct v4l2_isp_params_block_header header; + struct rppx1_window window1; + struct rppx1_window window2; + struct rppx1_bls_fixed fixed; + __u8 mode; + __u8 en_windows; + __u8 samples; + __u8 reserved[5]; +}; + +/** + * struct rppx1_ccor_params - Color CORrection configuration + * + * The CCOR (Color Correction) module is available on the MAIN_POST pipe. It + * performs color space correction on a pixel-per-pixel basis using a 3x3 matrix + * of coefficients and per-color channel offsets. + * + * The matrix coefficients are represented as 16 bits signed fixed point values + * in Q4.12 format ranging from -8 to +7.999. + * + * The per-channel color offsets are represented as 2's complement values + * stored in 25 bits ranging from -16777216 to 16777215. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_CCOR_POST) + * @coeff: color correction matrix coefficients, 16 bits signed Q4.12 + * @reserved: padding + * @offset: R, G, B offsets, 2's complement 25 bits + */ +struct rppx1_ccor_params { + struct v4l2_isp_params_block_header header; + __u16 coeff[3][3]; + __u16 reserved; + __u32 offset[3]; +}; + +/* Lens Shade Correction */ +#define RPPX1_LSC_SAMPLES_MAX 17 +#define RPPX1_LSC_NUM_SECTORS 16 + +/** + * struct rppx1_lsc_params - Lens Shading Correction configuration + * + * The RPP-X1 Lens shading correction module is available on the PRE1 and PRE2 + * pre-fusion pipes. Userspace selects which pipe to operate by setting the + * @header.type field to RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE1 or + * RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE2. + * + * The module applies per-color channel correction factors @r_data, @gr_data, + * @gb_data and @b_data as a 16x16 grid mapped on the image. The size of each + * grid segment is expressed by the @x_sect_size and @y_sect_size arrays. Each + * segment shall be at least 8 pixels in size and the sum of all horizontal + * segments @x_sect_size shall match the input frame size width. + * + * The correction factors values are expressed as unsigned Q2.10 integers + * ranging from 1 to 3.999. + * + * Pre-calculated interpolation factors shall be provided in the @x_grad + * and @y_grad fields, expressed as 12 bits integer values. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_LSC) + * @r_data: correction factors for the red channel in Q2.10 format + * @gr_data: correction factors for the green (red) channel in Q2.10 format + * @gb_data: correction factors for the green (blue) channel in Q2.10 format + * @b_data: correction factors for the blue channel in Q2.10 format + * @x_grad: Interpolation gradients for each horizontal sector (12 bits) + * @y_grad: Interpolation gradients for each vertical sector (12 bits) + * @x_sect_size: Horizontal sectors sizes + * @y_sect_size: Vertical sectors sizes + */ +struct rppx1_lsc_params { + struct v4l2_isp_params_block_header header; + __u16 r_data[RPPX1_LSC_SAMPLES_MAX][RPPX1_LSC_SAMPLES_MAX]; + __u16 gr_data[RPPX1_LSC_SAMPLES_MAX][RPPX1_LSC_SAMPLES_MAX]; + __u16 gb_data[RPPX1_LSC_SAMPLES_MAX][RPPX1_LSC_SAMPLES_MAX]; + __u16 b_data[RPPX1_LSC_SAMPLES_MAX][RPPX1_LSC_SAMPLES_MAX]; + __u16 x_grad[RPPX1_LSC_NUM_SECTORS]; + __u16 y_grad[RPPX1_LSC_NUM_SECTORS]; + __u16 x_sect_size[RPPX1_LSC_NUM_SECTORS]; + __u16 y_sect_size[RPPX1_LSC_NUM_SECTORS]; +}; + +/* Gamma Out */ +#define RPPX1_GA_MAX_SAMPLES 17 + +/** + * enum rppx1_ga_seg_mode - Gamma out curve segmentation mode + * + * Segmentation mode of the 16 input sampling points for the Gamma Out + * Correction module. + * + * @RPPX1_GA_SEG_MODE_LOGARITHMIC: logarithmic-like segmentation mode + * @RPPX1_GA_SEG_MODE_EQUIDISTANT: equidistant segmentation mode + */ +enum rppx1_ga_seg_mode { + RPPX1_GA_SEG_MODE_LOGARITHMIC, + RPPX1_GA_SEG_MODE_EQUIDISTANT +}; + +/** + * struct rppx1_ga_params - Gamma Out Correction configuration + * + * The Gamma Out Correction module is available on the Human Vision Output + * Pipe (HV) and the Machine Vision Output Pipe (MV). Userspace selects + * which pipe to operate by setting the @header.type field to + * RPPX1_PARAMS_BLOCK_TYPE_GA_HV or RPPX1_PARAMS_BLOCK_TYPE_GA_MV. + * + * The module allows to apply a @gamma_y gamma correction curve to RGB data + * represented as a table of 16 entries. The 16 input sampling points can be + * equidistant or segmented using a logarithmic scale according to the value of + * @mode. + * + * The gamma curve values are 12 bits on the HV output pipe and 24 bits on the + * MV output pipe. Userspace is expected to provide the curve values with a + * bit-depth matching the one of pipe in use. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_GA_HV or + * type = RPPX1_PARAMS_BLOCK_TYPE_GA_MV) + * @gamma_y: gamma out curve y-axis values + * @mode: gamma curve input segmentation mode (see rppx1_ga_seg_mode) + * @reserved: padding + */ +struct rppx1_ga_params { + struct v4l2_isp_params_block_header header; + __u32 gamma_y[RPPX1_GA_MAX_SAMPLES]; + __u8 mode; + __u8 reserved[3]; +}; + +/* Linearization (Sensor De-gamma) */ +#define RPPX1_LIN_SAMPLE_POINTS_NUM 16 +#define RPPX1_LIN_DEGAMMA_CURVE_NUM 17 + +/** + * struct rppx1_lin_params - Linearization (Sensor De-gamma) configuration + * + * The RPP-X1 linearization module is available on the PRE1 and PRE2 pre-fusion + * pipes. Userspace selects which pipe to operate by setting the @header.type + * field to RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE1 or + * RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE2. + * + * The LIN module applies the per-color channel de-gamma linearization curves + * @curve_r, @curve_g and @curve_b defined on the input sampling points @dx. + * + * For the PRE1 pipe the de-gamma curves values are 24-bits, for the PRE2 pipe + * the de-gamma curve values are 12-bits. + * + * For the PRE1 pipe de-gamma module sampling points @dx values are in the range + * [0, 15] (4 bits). For the PRE2 pipe de-gamma module sampling points values + * are in the range [0, 7] (3 bits). + * + * Userspace is expected to provide the curve values and sampling points with a + * bit-depth matching the one of pipe in use. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE1 or + * RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE2) + * @curve_r: de-gamma linearization curve for red channel + * @curve_g: de-gamma linearization curve for green channel + * @curve_b: de-gamma linearization curve for blue channel + * @dx: input sampling points + * @reserved: padding + */ +struct rppx1_lin_params { + struct v4l2_isp_params_block_header header; + __u32 curve_r[RPPX1_LIN_DEGAMMA_CURVE_NUM]; + __u32 curve_g[RPPX1_LIN_DEGAMMA_CURVE_NUM]; + __u32 curve_b[RPPX1_LIN_DEGAMMA_CURVE_NUM]; + __u8 dx[RPPX1_LIN_SAMPLE_POINTS_NUM]; + __u32 reserved; +}; + +/** + * RPPX1_PARAMS_MAX_SIZE - Maximum size of all RPP-X1 parameter blocks + * + * Some types are reported twice as the same block might be instantiated in + * multiple pipes. + */ +#define RPPX1_PARAMS_MAX_SIZE \ + (sizeof(struct rppx1_wbmeas_params) + \ + sizeof(struct rppx1_awbg_params) + \ + sizeof(struct rppx1_awbg_params) + \ + sizeof(struct rppx1_awbg_params) + \ + sizeof(struct rppx1_exm_params) + \ + sizeof(struct rppx1_exm_params) + \ + sizeof(struct rppx1_hist_params) + \ + sizeof(struct rppx1_hist_params) + \ + sizeof(struct rppx1_hist_params) + \ + sizeof(struct rppx1_bls_params) + \ + sizeof(struct rppx1_bls_params) + \ + sizeof(struct rppx1_ccor_params) + \ + sizeof(struct rppx1_lsc_params) + \ + sizeof(struct rppx1_lsc_params) + \ + sizeof(struct rppx1_ga_params) + \ + sizeof(struct rppx1_ga_params) + \ + sizeof(struct rppx1_lin_params) + \ + sizeof(struct rppx1_lin_params)) + +/* --------------------------------------------------------------------------- + * Statistics Structures + * + * The same ISP block might be instantiated in multiple pipeliness and operate + * on a different bitdepth/precision. For fields of varying length among + * different instances of the same block, use a data type that can accommodate + * the larger bitdepth/precision. + */ + +/** + * enum rppx1_stats_block_type - RPP-X1 extensible stats block types + * + * NOTE: Only append to the enumeration as the numbers are uAPI. + * + * @RPPX1_STATS_BLOCK_TYPE_WBMEAS_POST: post-fusion white-balance measurement + * @RPPX1_STATS_BLOCK_TYPE_EXM_PRE1: pre-fusion pipe1 exposure measurement + * @RPPX1_STATS_BLOCK_TYPE_EXM_PRE2: pre-fusion pipe2 exposure measurement + * @RPPX1_STATS_BLOCK_TYPE_HIST_PRE1: pre-fusion pipe1 histogram + * @RPPX1_STATS_BLOCK_TYPE_HIST_PRE2: pre-fusion pipe2 histogram + * @RPPX1_STATS_BLOCK_TYPE_HIST_POST: post-fusion histogram + */ +enum rppx1_stats_block_type { + RPPX1_STATS_BLOCK_TYPE_WBMEAS_POST, + RPPX1_STATS_BLOCK_TYPE_EXM_PRE1, + RPPX1_STATS_BLOCK_TYPE_EXM_PRE2, + RPPX1_STATS_BLOCK_TYPE_HIST_PRE1, + RPPX1_STATS_BLOCK_TYPE_HIST_PRE2, + RPPX1_STATS_BLOCK_TYPE_HIST_POST, +}; + +/** + * struct rppx1_wbmeas_stats - AWB statistics + * + * @header: block header (type = RPPX1_STATS_BLOCK_TYPE_WBMEAS_POST) + * @cnt: Number of pixels matched + * @mean_y_or_g: mean Y (or G in RGB mode) value, 24-bit + * @mean_cb_or_b: mean Cb (or B in RGB mode) value, 24-bit + * @mean_cr_or_r: mean Cr (or R in RGB mode) value, 24-bit + */ +struct rppx1_wbmeas_stats { + struct v4l2_isp_block_header header; + __u32 cnt; + __u32 mean_y_or_g; + __u32 mean_cb_or_b; + __u32 mean_cr_or_r; +}; + +/* Exposure Measurement */ +#define RPPX1_EXM_NUM_WIN 25 + +/** + * struct rppx1_exm_stats - Exposure measurement + * + * RPP-X1 exposure measurement calculates the mean value on 25 programmable + * windows on the input picture. + * + * @header: block header (type = RPPX1_STATS_BLOCK_TYPE_EXM_PRE1) + * @exp_mean: mean luminance values per block, up to 20-bit + * @reserved: padding + */ +struct rppx1_exm_stats { + struct v4l2_isp_block_header header; + __u32 exp_mean[RPPX1_EXM_NUM_WIN]; + __u32 reserved; +}; + +/* Histogram */ +#define RPPX1_HIST_NUM_BINS 32 + +/** + * struct rppx1_hist_stats - Histogram statistics + * + * @header: block header (type = RPPX1_STATS_BLOCK_TYPE_HIST_POST) + * @hist_bins: accumulation histogram results in unsigned 20-bit Q16.4 format + */ +struct rppx1_hist_stats { + struct v4l2_isp_block_header header; + __u32 hist_bins[RPPX1_HIST_NUM_BINS]; +}; + +/** + * RPPX1_STATS_MAX_SIZE - Maximum size of all RPP-X1 statistics + * + * Some types are reported twice as the same block might be instantiated in + * multiple pipes. + */ +#define RPPX1_STATS_MAX_SIZE \ + (sizeof(struct rppx1_wbmeas_stats) + \ + sizeof(struct rppx1_exm_stats) + \ + sizeof(struct rppx1_exm_stats) + \ + sizeof(struct rppx1_hist_stats) + \ + sizeof(struct rppx1_hist_stats) + \ + sizeof(struct rppx1_hist_stats)) + +#endif /* __UAPI_RPP_X1_CONFIG_H */ diff --git a/include/linux/rkisp1-config.h b/include/linux/rkisp1-config.h index 3e8aa77fde..e2c755e1a9 100644 --- a/include/linux/rkisp1-config.h +++ b/include/linux/rkisp1-config.h @@ -964,6 +964,92 @@ struct rkisp1_cif_isp_wdr_config { __u8 use_iref; }; +/* + * enum rkisp1_cif_isp_cac_h_clip_mode - horizontal clipping mode + * + * @RKISP1_CIF_ISP_CAC_H_CLIP_MODE_4PX: +/- 4 pixels + * @RKISP1_CIF_ISP_CAC_H_CLIP_MODE_4_5PX: +/- 4/5 pixels depending on bayer position + */ +enum rkisp1_cif_isp_cac_h_clip_mode { + RKISP1_CIF_ISP_CAC_H_CLIP_MODE_4PX = 0, + RKISP1_CIF_ISP_CAC_H_CLIP_MODE_4_5PX = 1, +}; + +/** + * enum rkisp1_cif_isp_cac_v_clip_mode - vertical clipping mode + * + * @RKISP1_CIF_ISP_CAC_V_CLIP_MODE_2PX: +/- 2 pixels + * @RKISP1_CIF_ISP_CAC_V_CLIP_MODE_3PX: +/- 3 pixels + * @RKISP1_CIF_ISP_CAC_V_CLIP_MODE_3_4PX: +/- 3/4 pixels depending on bayer position + */ +enum rkisp1_cif_isp_cac_v_clip_mode { + RKISP1_CIF_ISP_CAC_V_CLIP_MODE_2PX = 0, + RKISP1_CIF_ISP_CAC_V_CLIP_MODE_3PX = 1, + RKISP1_CIF_ISP_CAC_V_CLIP_MODE_3_4PX = 2, +}; + +/** + * struct rkisp1_cif_isp_cac_config - chromatic aberration correction configuration + * + * The correction is carried out by shifting the red and blue pixels relative + * to the green ones, depending on the distance from the optical center: + * + * @h_count_start: horizontal coordinate of the optical center (13-bit unsigned integer; [1,8191]) + * @v_count_start: vertical coordinate of the optical center (13-bit unsigned integer; [1,8191]) + * + * For each pixel, the x/y distances from the optical center are calculated and + * then transformed into the [0,255] range based on the following formula: + * + * (((d << 4) >> ns) * nf) >> 5 + * + * where `d` is the distance, `ns` and `nf` are the normalization parameters: + * + * @x_nf: horizontal normalization scale parameter (5-bit unsigned integer; [0,31]) + * @x_ns: horizontal normalization shift parameter (4-bit unsigned integer; [0,15]) + * + * @y_nf: vertical normalization scale parameter (5-bit unsigned integer; [0,31]) + * @y_ns: vertical normalization shift parameter (4-bit unsigned integer; [0,15]) + * + * These parameters should be chosen based on the image resolution, the position + * of the optical center, and the shape of pixels, so that no normalized distance + * is larger than 255. If the pixels have square shape, the two sets of parameters + * should be equal. + * + * The actual amount of correction is calculated with a third degree polynomial: + * + * c[0] * r + c[1] * r^2 + c[2] * r^3 + * + * where `c` is the set of coefficients for the given color, and `r` is distance: + * + * @red: red coefficients (5.4 two's complement; [-16,15.9375]) + * @blue: blue coefficients (5.4 two's complement; [-16,15.9375]) + * + * Finally, the amount is clipped as requested: + * + * @h_clip_mode: maximum horizontal shift (from enum rkisp1_cif_isp_cac_h_clip_mode) + * @v_clip_mode: maximum vertical shift (from enum rkisp1_cif_isp_cac_v_clip_mode) + * + * A positive result will shift away from the optical center, while a negative + * one will shift towards the optical center. In the latter case, the pixel + * values at the edges are duplicated. + */ +struct rkisp1_cif_isp_cac_config { + __u8 h_clip_mode; + __u8 v_clip_mode; + + __u16 h_count_start; + __u16 v_count_start; + + __u16 red[3]; + __u16 blue[3]; + + __u8 x_nf; + __u8 x_ns; + + __u8 y_nf; + __u8 y_ns; +}; + /*---------- PART2: Measurement Statistics ------------*/ /** @@ -1135,6 +1221,7 @@ struct rkisp1_stat_buffer { * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_EXPAND: Companding expand curve * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_COMPRESS: Companding compress curve * @RKISP1_EXT_PARAMS_BLOCK_TYPE_WDR: Wide dynamic range + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_CAC: Chromatic aberration correction */ enum rkisp1_ext_params_block_type { RKISP1_EXT_PARAMS_BLOCK_TYPE_BLS, @@ -1158,6 +1245,7 @@ enum rkisp1_ext_params_block_type { RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_EXPAND, RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_COMPRESS, RKISP1_EXT_PARAMS_BLOCK_TYPE_WDR, + RKISP1_EXT_PARAMS_BLOCK_TYPE_CAC, }; /* For backward compatibility */ @@ -1504,6 +1592,22 @@ struct rkisp1_ext_params_wdr_config { struct rkisp1_cif_isp_wdr_config config; } __attribute__((aligned(8))); +/** + * struct rkisp1_ext_params_cac_config - RkISP1 extensible params CAC config + * + * RkISP1 extensible parameters CAC block. + * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_CAC`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: CAC configuration, see + * :c:type:`rkisp1_cif_isp_cac_config` + */ +struct rkisp1_ext_params_cac_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_cac_config config; +} __attribute__((aligned(8))); + /* * The rkisp1_ext_params_compand_curve_config structure is counted twice as it * is used for both the COMPAND_EXPAND and COMPAND_COMPRESS block types. @@ -1529,14 +1633,15 @@ struct rkisp1_ext_params_wdr_config { sizeof(struct rkisp1_ext_params_compand_bls_config) +\ sizeof(struct rkisp1_ext_params_compand_curve_config) +\ sizeof(struct rkisp1_ext_params_compand_curve_config) +\ - sizeof(struct rkisp1_ext_params_wdr_config)) + sizeof(struct rkisp1_ext_params_wdr_config) +\ + sizeof(struct rkisp1_ext_params_cac_config)) /** - * enum rksip1_ext_param_buffer_version - RkISP1 extensible parameters version + * enum rkisp1_ext_param_buffer_version - RkISP1 extensible parameters version * * @RKISP1_EXT_PARAM_BUFFER_V1: First version of RkISP1 extensible parameters */ -enum rksip1_ext_param_buffer_version { +enum rkisp1_ext_param_buffer_version { RKISP1_EXT_PARAM_BUFFER_V1 = V4L2_ISP_PARAMS_VERSION_V1, }; @@ -1598,7 +1703,7 @@ enum rksip1_ext_param_buffer_version { * +---------------------------------------------------------------------+ * * @version: The RkISP1 extensible parameters buffer version, see - * :c:type:`rksip1_ext_param_buffer_version` + * :c:type:`rkisp1_ext_param_buffer_version` * @data_size: The RkISP1 configuration data effective size, excluding this * header * @data: The RkISP1 extensible configuration data blocks diff --git a/include/linux/v4l2-controls.h b/include/linux/v4l2-controls.h index c0d9cab3c7..5e25e7f1fa 100644 --- a/include/linux/v4l2-controls.h +++ b/include/linux/v4l2-controls.h @@ -464,6 +464,8 @@ enum v4l2_mpeg_video_intra_refresh_period_type { V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_CYCLIC = 1, }; +#define V4L2_CID_MPEG_VIDEO_BACKGROUND_DETECTION (V4L2_CID_CODEC_BASE + 238) + /* CIDs for the MPEG-2 Part 2 (H.262) codec */ #define V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL (V4L2_CID_CODEC_BASE+270) enum v4l2_mpeg_video_mpeg2_level { diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h index 8bccad84ff..c823409c7f 100644 --- a/include/linux/videodev2.h +++ b/include/linux/videodev2.h @@ -864,6 +864,10 @@ struct v4l2_pix_format { #define V4L2_META_FMT_MALI_C55_PARAMS v4l2_fourcc('C', '5', '5', 'P') /* ARM Mali-C55 Parameters */ #define V4L2_META_FMT_MALI_C55_STATS v4l2_fourcc('C', '5', '5', 'S') /* ARM Mali-C55 3A Statistics */ +/* Vendor specific - used for Dreamchip RPP-X1 ISP */ +#define V4L2_META_FMT_RPPX1_PARAMS v4l2_fourcc('D', 'R', '1', 'P') /* Dreamchip RPP-X1 Parameters */ +#define V4L2_META_FMT_RPPX1_STATS v4l2_fourcc('D', 'R', '1', 'S') /* Dreamchip RPP-X1 Statistics */ + /* * Line-based metadata formats. Remember to update v4l_fill_fmtdesc() when * adding new ones! diff --git a/utils/update-kernel-headers.sh b/utils/update-kernel-headers.sh index 3fdea5f1eb..488b87de59 100755 --- a/utils/update-kernel-headers.sh +++ b/utils/update-kernel-headers.sh @@ -56,6 +56,7 @@ headers=" linux/media-bus-format.h linux/media.h linux/media/arm/mali-c55-config.h + linux/media/dreamchip/rppx1-config.h linux/media/v4l2-isp.h linux/rkisp1-config.h linux/stddef.h From patchwork Fri Nov 13 16:40:44 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28899 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 B40BEC3304 for ; Fri, 13 Nov 2026 16:41:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4F1276A81E; Fri, 13 Nov 2026 17:41:17 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="uXNO6+f3"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 21D856A830 for ; Fri, 13 Nov 2026 17:41:07 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5BBE9219; Fri, 13 Nov 2026 17:38:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587922; bh=BxaJoGI1cYsRMGTZyHTz7jkwzMm5Cp8qtdG1RA+rlbs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uXNO6+f3UCpreC5Ou2ndXhrfM+hPFqwKfPeFT1Frmqx+bksm21ETWcb7aIANbjTWM EUU7Ou4uPuwo0H5czZ907bhvu/nnP2pUrUoQuK5kmUc5vRM01Jo20RmLCDjjQqOuRl Z2SOQbrLaa3+B2WkEJpnCJBI5moTy/PhKVgN6hGY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: =?utf-8?q?Niklas_S=C3=B6derlund?= Subject: [PATCH v4 11/21] libcamera: pipeline: Add R-Car Gen4 ISP pipeline Date: Fri, 13 Nov 2026 17:40:44 +0100 Message-ID: <20261113164054.267164-12-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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: Niklas Söderlund Add a pipeline handler for R-Car Gen4. The pipeline will make use of the new "rppx1" ipa module that will be introduced momentarily. There are two distinct parts of the pipeline: one capture block (VIN) that is responsible for acquiring the raw images from the sensor and supplying them to user-space, and the ISP operating in memory-to-memory mode that produces the processed images and statistics from the raw image. Notably, there is no usable scaler in this configuration (the VIN device can only scale non-raw images), so the resolutions need to match in the whole pipeline beginning with the sensor. The pipeline handler supports a maximum of one raw and one processed streams at a time, both with the same resolution. Tested on R-Car V4H Sparrow-Hawk together with IMX219 and IMX708 sensors. Signed-off-by: Niklas Söderlund Signed-off-by: Barnabás Pőcze --- changes in v3: * rebased * uses new ipa interface (rppx1) instead of the rkisp1 * remove the queue of pending requests and use the queueing feature of the PipelineHandler base class * reworked format handling * improved orientation handling wrt. raw formats v2: https://patchwork.libcamera.org/cover/24304/ v1: https://patchwork.libcamera.org/cover/23591/ --- * Changes since v1 - Fix camera names. Was 'imx462 2-001a', is now '/base/soc/i2c@e6510000/cam@1a'. - Fix a compiler issue with some versions of gcc. - Add dependency on rkisp1.mojom. - Rebase on latest master branch which requires reworking some interfaces. --- Documentation/Doxyfile-common.in | 1 + include/libcamera/ipa/meson.build | 1 + include/libcamera/ipa/rppx1.mojom | 40 + meson.build | 1 + meson_options.txt | 1 + src/libcamera/pipeline/rcar-gen4/frames.cpp | 264 ++++++ src/libcamera/pipeline/rcar-gen4/frames.h | 84 ++ src/libcamera/pipeline/rcar-gen4/isp.cpp | 194 ++++ src/libcamera/pipeline/rcar-gen4/isp.h | 39 + src/libcamera/pipeline/rcar-gen4/meson.build | 8 + .../pipeline/rcar-gen4/rcar-gen4.cpp | 880 ++++++++++++++++++ src/libcamera/pipeline/rcar-gen4/vin.cpp | 177 ++++ src/libcamera/pipeline/rcar-gen4/vin.h | 54 ++ 13 files changed, 1744 insertions(+) create mode 100644 include/libcamera/ipa/rppx1.mojom create mode 100644 src/libcamera/pipeline/rcar-gen4/frames.cpp create mode 100644 src/libcamera/pipeline/rcar-gen4/frames.h create mode 100644 src/libcamera/pipeline/rcar-gen4/isp.cpp create mode 100644 src/libcamera/pipeline/rcar-gen4/isp.h create mode 100644 src/libcamera/pipeline/rcar-gen4/meson.build create mode 100644 src/libcamera/pipeline/rcar-gen4/rcar-gen4.cpp create mode 100644 src/libcamera/pipeline/rcar-gen4/vin.cpp create mode 100644 src/libcamera/pipeline/rcar-gen4/vin.h diff --git a/Documentation/Doxyfile-common.in b/Documentation/Doxyfile-common.in index f11ec593d5..131fdcc608 100644 --- a/Documentation/Doxyfile-common.in +++ b/Documentation/Doxyfile-common.in @@ -37,6 +37,7 @@ EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \ @TOP_BUILDDIR@/include/libcamera/ipa/mali-c55_*.h \ @TOP_BUILDDIR@/include/libcamera/ipa/raspberrypi_*.h \ @TOP_BUILDDIR@/include/libcamera/ipa/rkisp1_*.h \ + @TOP_BUILDDIR@/include/libcamera/ipa/rppx1_*.h \ @TOP_BUILDDIR@/include/libcamera/ipa/vimc_*.h EXCLUDE_SYMBOLS = libcamera::BoundMethodArgs \ diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build index fb518526a2..7dcefc8b26 100644 --- a/include/libcamera/ipa/meson.build +++ b/include/libcamera/ipa/meson.build @@ -65,6 +65,7 @@ libcamera_ipa_headers += custom_target('core_ipa_serializer_h', pipeline_ipa_mojom_mapping = { 'ipu3': 'ipu3.mojom', 'mali-c55': 'mali-c55.mojom', + 'rcar-gen4': 'rppx1.mojom', 'rkisp1': 'rkisp1.mojom', 'rpi/pisp': 'raspberrypi.mojom', 'rpi/vc4': 'raspberrypi.mojom', diff --git a/include/libcamera/ipa/rppx1.mojom b/include/libcamera/ipa/rppx1.mojom new file mode 100644 index 0000000000..6d280b9ef1 --- /dev/null +++ b/include/libcamera/ipa/rppx1.mojom @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry. + */ + +module ipa.rppx1; + +import "include/libcamera/ipa/core.mojom"; + +struct IPAConfigInfo { + libcamera.IPACameraSensorInfo sensorInfo; + libcamera.ControlInfoMap sensorControls; +}; + +interface IPARppX1Interface { + init(libcamera.IPASettings settings, + libcamera.IPACameraSensorInfo sensorInfo, + libcamera.ControlInfoMap sensorControls) + => (int32 ret, libcamera.ControlInfoMap ipaControls); + start() => (int32 ret); + stop(); + + configure(IPAConfigInfo configInfo) + => (int32 ret, libcamera.ControlInfoMap ipaControls); + + mapBuffers(array buffers); + unmapBuffers(array ids); + + [async] queueRequest(uint32 frame, libcamera.ControlList reqControls); + [async] computeParams(uint32 frame, uint32 bufferId); + [async] processStats(uint32 frame, uint32 bufferId, + libcamera.ControlList sensorControls); +}; + +interface IPARppX1EventInterface { + paramsComputed(uint32 frame, uint32 bytesused); + setSensorControls(uint32 frame, libcamera.ControlList sensorControls); + metadataReady(uint32 frame, libcamera.ControlList metadata); +}; diff --git a/meson.build b/meson.build index 6d54fc7a93..265a825ffa 100644 --- a/meson.build +++ b/meson.build @@ -218,6 +218,7 @@ pipelines_support = { 'imx8-isi': arch_arm, 'ipu3': arch_x86, 'mali-c55': arch_arm, + 'rcar-gen4': arch_arm, 'rkisp1': arch_arm, 'rpi/pisp': arch_arm, 'rpi/vc4': arch_arm, diff --git a/meson_options.txt b/meson_options.txt index e829ebe604..3c4bc9f8e7 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -81,6 +81,7 @@ option('pipelines', 'imx8-isi', 'ipu3', 'mali-c55', + 'rcar-gen4', 'rkisp1', 'rpi/pisp', 'rpi/vc4', diff --git a/src/libcamera/pipeline/rcar-gen4/frames.cpp b/src/libcamera/pipeline/rcar-gen4/frames.cpp new file mode 100644 index 0000000000..b94bbf8e59 --- /dev/null +++ b/src/libcamera/pipeline/rcar-gen4/frames.cpp @@ -0,0 +1,264 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright 2025 Renesas Electronics Co + * Copyright 2025 Niklas Söderlund + * + * Renesas R-Car Gen4 VIN pipeline + */ + +#include "frames.h" + +#include +#include + +#include +#include + +#include "libcamera/internal/framebuffer.h" +#include "libcamera/internal/pipeline_handler.h" + +#include "isp.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(RCar4) + +int RCar4Frames::start(RCarISPDevice *isp, ipa::rppx1::IPAProxyRppX1 *ipa, + unsigned int bufferCount) +{ + unsigned int ipaBufferId = 1; + + auto pushBuffers = [&](const std::vector> &buffers, + std::queue &queue) { + for (const std::unique_ptr &buffer : buffers) { + std::span planes = buffer->planes(); + + buffer->setCookie(ipaBufferId++); + ipaBuffers_.emplace_back(buffer->cookie(), + std::vector{ planes.begin(), + planes.end() }); + queue.push(buffer.get()); + } + }; + + frameInfo_.clear(); + + utils::scope_exit stopGuard([&] { stop(isp, ipa); }); + + int ret = isp->input_->exportBuffers(bufferCount, &inputBuffers_); + if (ret < 0) { + LOG(RCar4, Error) << "Failed to allocate ISP input buffers"; + return ret; + } + + ret = isp->param_->allocateBuffers(bufferCount, ¶mBuffers_); + if (ret < 0) { + LOG(RCar4, Error) << "Failed to allocate ISP param buffers"; + return ret; + } + + ret = isp->stat_->allocateBuffers(bufferCount, &statBuffers_); + if (ret < 0) { + LOG(RCar4, Error) << "Failed to allocate ISP stat buffers"; + return ret; + } + + ret = isp->output_->exportBuffers(bufferCount, &outputBuffers_); + if (ret < 0) { + LOG(RCar4, Error) << "Failed to allocate ISP output buffers"; + return ret; + } + + for (const std::unique_ptr &buffer : inputBuffers_) + availableInputBuffers_.push(buffer.get()); + + pushBuffers(paramBuffers_, availableParamBuffers_); + pushBuffers(statBuffers_, availableStatBuffers_); + + for (const std::unique_ptr &buffer : outputBuffers_) + availableOutputBuffers_.push(buffer.get()); + + ipa->mapBuffers(ipaBuffers_); + + stopGuard.release(); + return 0; +} + +void RCar4Frames::stop(RCarISPDevice *isp, ipa::rppx1::IPAProxyRppX1 *ipa) +{ + std::vector ids; + + availableInputBuffers_ = {}; + availableParamBuffers_ = {}; + availableStatBuffers_ = {}; + availableOutputBuffers_ = {}; + + outputBuffers_.clear(); + statBuffers_.clear(); + paramBuffers_.clear(); + inputBuffers_.clear(); + + for (IPABuffer &ipabuf : ipaBuffers_) + ids.push_back(ipabuf.id); + + ipa->unmapBuffers(ids); + ipaBuffers_.clear(); + + if (isp->stat_->releaseBuffers()) + LOG(RCar4, Error) << "Failed to release ISP stat buffers"; + + if (isp->param_->releaseBuffers()) + LOG(RCar4, Error) << "Failed to release ISP param buffers"; +} + +RCar4Frames::Info *RCar4Frames::create(Request *request) +{ + unsigned int frame = request->sequence(); + + /* Try to get input and output buffers from request. */ + FrameBuffer *inputBuffer = request->findBuffer(&rawStream_); + FrameBuffer *outputBuffer = request->findBuffer(&outputStream_); + + /* Make sure we have enough internal buffers. */ + if (!inputBuffer && availableInputBuffers_.empty()) { + LOG(RCar4, Debug) << "Input buffer underrun"; + return nullptr; + } + + if (availableParamBuffers_.empty()) { + LOG(RCar4, Debug) << "Parameters buffer underrun"; + return nullptr; + } + + if (availableStatBuffers_.empty()) { + LOG(RCar4, Debug) << "Statistics buffer underrun"; + return nullptr; + } + + if (!outputBuffer && availableOutputBuffers_.empty()) { + LOG(RCar4, Debug) << "Output buffer underrun"; + return nullptr; + } + + /* Select buffers to use. */ + if (!inputBuffer) { + inputBuffer = availableInputBuffers_.front(); + availableInputBuffers_.pop(); + } + + FrameBuffer *paramBuffer = availableParamBuffers_.front(); + availableParamBuffers_.pop(); + + FrameBuffer *statBuffer = availableStatBuffers_.front(); + availableStatBuffers_.pop(); + + if (!outputBuffer) { + outputBuffer = availableOutputBuffers_.front(); + availableOutputBuffers_.pop(); + } + + /* Record the info needed to process one frame. */ + auto [it, inserted] = frameInfo_.try_emplace(frame); + if (!inserted) + return nullptr; + + auto &info = it->second; + + info.frame = frame; + info.request = request; + info.inputBuffer = inputBuffer; + info.paramBuffer = paramBuffer; + info.statBuffer = statBuffer; + info.outputBuffer = outputBuffer; + info.rawDequeued = false; + info.paramDequeued = false; + info.metadataProcessed = false; + info.outputDequeued = false; + + return &info; +} + +void RCar4Frames::remove(RCar4Frames::Info *info) +{ + /* If internal input buffer used, return for reuse. */ + for (const std::unique_ptr &buf : inputBuffers_) { + if (info->inputBuffer == buf.get()) { + availableInputBuffers_.push(info->inputBuffer); + break; + } + } + + /* Return param and stat buffer for reuse. */ + availableParamBuffers_.push(info->paramBuffer); + availableStatBuffers_.push(info->statBuffer); + + /* If internal output buffer used, return for reuse. */ + for (const std::unique_ptr &buf : outputBuffers_) { + if (info->outputBuffer == buf.get()) { + availableOutputBuffers_.push(info->outputBuffer); + break; + } + } + + /* Delete the extended frame information. */ + frameInfo_.erase(info->frame); +} + +bool RCar4Frames::tryComplete(RCar4Frames::Info *info) +{ + Request *request = info->request; + + if (request->hasPendingBuffers()) + return false; + + if (!info->rawDequeued) + return false; + + if (!info->metadataProcessed) + return false; + + if (!info->paramDequeued) + return false; + + if (!info->outputDequeued) + return false; + + remove(info); + + return true; +} + +RCar4Frames::Info *RCar4Frames::find(unsigned int frame) +{ + const auto &itInfo = frameInfo_.find(frame); + + if (itInfo != frameInfo_.end()) + return &itInfo->second; + + LOG(RCar4, Fatal) << "Can't find tracking information for frame " << frame; + + return nullptr; +} + +RCar4Frames::Info *RCar4Frames::find(FrameBuffer *buffer) +{ + for (auto &itInfo : frameInfo_) { + Info *info = &itInfo.second; + + for (const auto &[stream, fb] : info->request->buffers()) + if (buffer == fb) + return info; + + if (info->inputBuffer == buffer || + info->paramBuffer == buffer || + info->statBuffer == buffer || + info->outputBuffer == buffer) + return info; + } + + LOG(RCar4, Fatal) << "Can't find tracking information from buffer"; + + return nullptr; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rcar-gen4/frames.h b/src/libcamera/pipeline/rcar-gen4/frames.h new file mode 100644 index 0000000000..79365f38e5 --- /dev/null +++ b/src/libcamera/pipeline/rcar-gen4/frames.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright 2025 Renesas Electronics Co + * Copyright 2025 Niklas Söderlund + * + * Renesas R-Car Gen4 VIN pipeline + */ + +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "isp.h" + +namespace libcamera { + +class RCarISPDevice; +class FrameBuffer; +class Request; + +class RCar4Frames +{ +public: + struct Info { + unsigned int frame; + Request *request; + + FrameBuffer *inputBuffer; + FrameBuffer *paramBuffer; + FrameBuffer *statBuffer; + FrameBuffer *outputBuffer; + + ControlList effectiveSensorControls; + + bool rawDequeued; + bool paramDequeued; + bool metadataProcessed; + bool outputDequeued; + }; + + int start(RCarISPDevice *isp, ipa::rppx1::IPAProxyRppX1 *ipa, + unsigned int bufferCount); + void stop(RCarISPDevice *isp, ipa::rppx1::IPAProxyRppX1 *ipa); + + Info *create(Request *request); + void remove(Info *info); + bool tryComplete(Info *info); + + Info *find(unsigned int frame); + Info *find(FrameBuffer *buffer); + + Stream rawStream_; + Stream outputStream_; + +private: + std::map frameInfo_; + + /* Buffers for internal use, if none is provided in request. */ + std::vector> inputBuffers_; + std::vector> paramBuffers_; + std::vector> statBuffers_; + std::vector> outputBuffers_; + + /* Queues of available internal buffers. */ + std::queue availableInputBuffers_; + std::queue availableParamBuffers_; + std::queue availableStatBuffers_; + std::queue availableOutputBuffers_; + + /* Buffers mapped and shared with IPA. */ + std::vector ipaBuffers_; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rcar-gen4/isp.cpp b/src/libcamera/pipeline/rcar-gen4/isp.cpp new file mode 100644 index 0000000000..5b1472f374 --- /dev/null +++ b/src/libcamera/pipeline/rcar-gen4/isp.cpp @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright 2025 Renesas Electronics Co + * Copyright 2025 Niklas Söderlund + * + * Renesas R-Car Gen4 ISP pipeline + */ + +#include "isp.h" + +#include + +#include +#include + +#include +#include + +#include "libcamera/internal/media_device.h" +#include "libcamera/internal/v4l2_subdevice.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(RCar4) + +int RCarISPDevice::init(const MediaDevice *media, const std::string &pipeId) +{ + const MediaEntity *entity; + const MediaPad *pad, *next; + + /* Locate IPSCORE, e.g. rcar_isp fed00000.isp core */ + std::unique_ptr core = + V4L2Subdevice::fromEntityName(media, pipeId + " core"); + if (!core) { + LOG(RCar4, Error) << "Failed to find ISPCORE " << pipeId; + return -EINVAL; + } + + entity = core->entity(); + + /* Use the media links to find all video devices. */ + pad = entity->getPadByIndex(0); + next = pad->links()[0]->source(); + input_ = V4L2VideoDevice::fromEntityName(media, next->entity()->name()); + if (!input_) { + LOG(RCar4, Error) << "Failed to find ISP input entity"; + return -EINVAL; + } + + pad = entity->getPadByIndex(1); + next = pad->links()[0]->source(); + param_ = V4L2VideoDevice::fromEntityName(media, next->entity()->name()); + if (!param_) { + LOG(RCar4, Error) << "Failed to find ISP param entity"; + return -EINVAL; + } + + pad = entity->getPadByIndex(2); + next = pad->links()[0]->sink(); + stat_ = V4L2VideoDevice::fromEntityName(media, next->entity()->name()); + if (!stat_) { + LOG(RCar4, Error) << "Failed to find ISP stat entity"; + return -EINVAL; + } + + pad = entity->getPadByIndex(3); + next = pad->links()[0]->sink(); + output_ = V4L2VideoDevice::fromEntityName(media, next->entity()->name()); + if (!output_) { + LOG(RCar4, Error) << "Failed to find ISP output entity"; + return -EINVAL; + } + + /* Open all devices. */ + int ret = input_->open(); + if (ret) + return ret; + + ret = param_->open(); + if (ret) + return ret; + + ret = stat_->open(); + if (ret) + return ret; + + ret = output_->open(); + if (ret) + return ret; + + return 0; +} + +int RCarISPDevice::configure(const V4L2DeviceFormat &sensorFormat, + const PixelFormat &outputPixelFormat) +{ + auto inputFormat = sensorFormat; + + /* Configure the RAW input. */ + int ret = input_->setFormat(&inputFormat); + if (ret) + return ret; + + if (inputFormat.fourcc != sensorFormat.fourcc || inputFormat.size != sensorFormat.size) + return -EINVAL; + + /* Configure the image output. */ + V4L2DeviceFormat outputFormat = {}; + V4L2PixelFormat outputPf = output_->toV4L2PixelFormat(outputPixelFormat); + outputFormat.fourcc = outputPf; + outputFormat.size = inputFormat.size; + ret = output_->setFormat(&outputFormat); + if (ret) + return ret; + + if (outputFormat.fourcc != outputPf || outputFormat.size != inputFormat.size) + return -EINVAL; + + /* Configure paramaters. */ + V4L2DeviceFormat paramFormat = {}; + paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RPPX1_PARAMS); + ret = param_->setFormat(¶mFormat); + if (ret) + return ret; + + /* Configure statistics. */ + V4L2DeviceFormat statFormat = {}; + statFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RPPX1_STATS); + ret = stat_->setFormat(&statFormat); + if (ret) + return ret; + + return 0; +} + +int RCarISPDevice::start(unsigned int bufferCount) +{ + utils::scope_exit stopGuard([&] { stop(); }); + + int ret = input_->importBuffers(bufferCount); + if (ret) { + LOG(RCar4, Error) << "Failed to import ISP input buffers"; + return ret; + } + + ret = output_->importBuffers(bufferCount); + if (ret) { + LOG(RCar4, Error) << "Failed to import ISP output buffers"; + return ret; + } + + ret = output_->streamOn(); + if (ret) { + LOG(RCar4, Error) << "Failed to start ISP output"; + return ret; + } + + ret = param_->streamOn(); + if (ret) { + LOG(RCar4, Error) << "Failed to start ISP param"; + return ret; + } + + ret = stat_->streamOn(); + if (ret) { + LOG(RCar4, Error) << "Failed to start ISP stat"; + return ret; + } + + ret = input_->streamOn(); + if (ret) { + LOG(RCar4, Error) << "Failed to start ISP input"; + return ret; + } + + stopGuard.release(); + return 0; +} + +void RCarISPDevice::stop() +{ + input_->streamOff(); + stat_->streamOff(); + param_->streamOff(); + output_->streamOff(); + + if (output_->releaseBuffers()) + LOG(RCar4, Error) << "Failed to release ISP output buffers"; + + if (input_->releaseBuffers()) + LOG(RCar4, Error) << "Failed to release ISP input buffers"; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rcar-gen4/isp.h b/src/libcamera/pipeline/rcar-gen4/isp.h new file mode 100644 index 0000000000..55987f1f51 --- /dev/null +++ b/src/libcamera/pipeline/rcar-gen4/isp.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright 2025 Renesas Electronics Co + * Copyright 2025 Niklas Söderlund + * + * Renesas R-Car Gen4 ISP pipeline + */ + +#pragma once + +#include +#include + +#include "libcamera/internal/v4l2_videodevice.h" + +namespace libcamera { + +class MediaDevice; +class Size; +struct StreamConfiguration; + +class RCarISPDevice +{ +public: + int init(const MediaDevice *media, const std::string &pipeId); + + int configure(const V4L2DeviceFormat &inputFormat, + const PixelFormat &outputPixelFormat); + + int start(unsigned int bufferCount); + void stop(); + + std::unique_ptr input_; + std::unique_ptr param_; + std::unique_ptr stat_; + std::unique_ptr output_; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rcar-gen4/meson.build b/src/libcamera/pipeline/rcar-gen4/meson.build new file mode 100644 index 0000000000..431eb54e28 --- /dev/null +++ b/src/libcamera/pipeline/rcar-gen4/meson.build @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: CC0-1.0 + +libcamera_internal_sources += files([ + 'frames.cpp', + 'isp.cpp', + 'rcar-gen4.cpp', + 'vin.cpp', +]) diff --git a/src/libcamera/pipeline/rcar-gen4/rcar-gen4.cpp b/src/libcamera/pipeline/rcar-gen4/rcar-gen4.cpp new file mode 100644 index 0000000000..40d4c8dbbc --- /dev/null +++ b/src/libcamera/pipeline/rcar-gen4/rcar-gen4.cpp @@ -0,0 +1,880 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright 2025 Renesas Electronics Co + * Copyright 2025 Niklas Söderlund + * + * Renesas R-Car Gen4 ISP pipeline + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include + +#include "libcamera/internal/camera.h" +#include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/delayed_controls.h" +#include "libcamera/internal/device_enumerator.h" +#include "libcamera/internal/framebuffer.h" +#include "libcamera/internal/ipa_manager.h" +#include "libcamera/internal/media_device.h" +#include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/request.h" +#include "libcamera/internal/v4l2_subdevice.h" +#include "libcamera/internal/v4l2_videodevice.h" + +#include "frames.h" +#include "isp.h" +#include "vin.h" + +namespace libcamera { + +namespace { + +static constexpr unsigned int kMaxRequests = 4; +static constexpr unsigned int kDefaultBufferCount = kMaxRequests; + +} /* namespace */ + +LOG_DEFINE_CATEGORY(RCar4) + +/* ----------------------------------------------------------------------------- + * Camera Data + */ + +class RCar4CameraData final : public Camera::Private +{ +public: + RCar4CameraData(PipelineHandler *pipe) + : Camera::Private(pipe) + { + } + + int init(const MediaDevice *mdev, const std::string &pipeId); + + [[nodiscard]] + bool populateFormats(); + + [[nodiscard]] + std::tuple + findSensorFormat(PixelFormat pixelFormat, Size size, Transform transform) const; + + /* Slots for processing ready buffers. */ + void vinBufferReady(FrameBuffer *buffer); + void inputBufferReady(FrameBuffer *buffer); + void paramBufferReady(FrameBuffer *buffer); + void statBufferReady(FrameBuffer *buffer); + void outputBufferReady(FrameBuffer *buffer); + + /* Slots for processing IPA interactions. */ + void paramsComputed(unsigned int frame, unsigned int bytesused); + void setSensorControls(unsigned int frame, + const ControlList &sensorControls); + void metadataReady(unsigned int frame, const ControlList &metadata); + + RCarVINDevice vin_; + RCarISPDevice isp_; + std::unique_ptr ipa_; + + RCar4Frames frames_; + std::unique_ptr delayedCtrls_; + + std::map> rawFormats_; + std::map> outputFormats_; +}; + +int RCar4CameraData::init(const MediaDevice *mdev, const std::string &pipeId) +{ + int ret = vin_.init(mdev, pipeId); + if (ret) + return ret; + + ret = isp_.init(mdev, pipeId); + if (ret) + return ret; + + ipa_ = pipe()->createIPA(1, 1); + if (!ipa_) { + LOG(RCar4, Error) << "No IPA module found"; + return -ENOENT; + } + + /* The IPA tuning file is made from the sensor name. */ + std::string ipaTuningFile = ipa_->configurationFile( + vin_.sensor()->model() + ".yaml", "uncalibrated.yaml"); + + IPACameraSensorInfo sensorInfo; + ret = vin_.sensor()->sensorInfo(&sensorInfo); + if (ret) { + LOG(RCar4, Error) << "Camera sensor information not available"; + return ret; + } + + IPASettings settings{ + std::move(ipaTuningFile), + vin_.sensor()->model(), + }; + + ret = ipa_->init(std::move(settings), sensorInfo, + vin_.sensor()->controls(), &controlInfo_); + if (ret < 0) { + LOG(RCar4, Error) << "IPA initialization failure"; + return ret; + } + + /* + * Initialize the camera properties. + */ + properties_ = vin_.sensor()->properties(); + const CameraSensorProperties::SensorDelays &delays = vin_.sensor()->sensorDelays(); + std::unordered_map params = { + { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, + { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + { V4L2_CID_VBLANK, { delays.vblankDelay, true } }, + }; + + delayedCtrls_ = std::make_unique( + vin_.sensor()->device(), params); + + /* Connect bufferReady for each video device to a handler. */ + vin_.bufferReady().connect(this, &RCar4CameraData::vinBufferReady); + isp_.input_->bufferReady.connect(this, &RCar4CameraData::inputBufferReady); + isp_.param_->bufferReady.connect(this, &RCar4CameraData::paramBufferReady); + isp_.stat_->bufferReady.connect(this, &RCar4CameraData::statBufferReady); + isp_.output_->bufferReady.connect(this, &RCar4CameraData::outputBufferReady); + + /* Connect IPA signals. */ + ipa_->setSensorControls.connect(this, &RCar4CameraData::setSensorControls); + ipa_->paramsComputed.connect(this, &RCar4CameraData::paramsComputed); + ipa_->metadataReady.connect(this, &RCar4CameraData::metadataReady); + + /* Apply controls at start of frame. */ + vin_.frameStart().connect(delayedCtrls_.get(), &DelayedControls::applyControls); + + if (!populateFormats()) { + LOG(RCar4, Error) + << "Sensor " << vin_.sensor()->entity()->name() + << " has no format and size compatible with the VIN and ISP"; + return -ENOTSUP; + } + + return 0; +} + +namespace { + +/* + * \todo This should obviously be common code. + */ +void filterSizes(std::vector &sizes, std::span filter) +{ + for (auto it = sizes.begin(); it != sizes.end();) { + bool accept = false; + + for (const auto &range : filter) { + accept = range.contains(*it); + if (accept) + break; + } + + if (!accept) + it = sizes.erase(it); + else + ++it; + } +} + +} /* namespace */ + +/* + * \todo This should obviously be common code. + */ +bool RCar4CameraData::populateFormats() +{ + auto vinFormats = vin_.output()->formats(); + const auto &inputFormats = isp_.input_->formats(); + std::set outputSizes; + + rawFormats_.clear(); + outputFormats_.clear(); + + /* Handle missing `VIDIOC_ENUM_FRMSIZES`. */ + if (vinFormats.empty()) { + static const SizeRange fallbackRange = { + { 5, 2 }, + { 4096, 4096 }, + 1, + 1, + }; + + vinFormats = { + { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10), std::vector{ fallbackRange } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG10), std::vector{ fallbackRange } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10), std::vector{ fallbackRange } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10), std::vector{ fallbackRange } }, + }; + } + + for (unsigned int mbusCode : vin_.sensor()->mbusCodes()) { + auto v4pf = BayerFormat::fromMbusCode(mbusCode).toV4L2PixelFormat(); + + auto it = vinFormats.find(v4pf); + if (it == vinFormats.end()) + continue; + + auto it2 = inputFormats.find(v4pf); + if (it2 == inputFormats.end()) + continue; + + auto sizes = vin_.sensor()->sizes(mbusCode); + filterSizes(sizes, it->second); + filterSizes(sizes, it2->second); + + if (sizes.empty()) + continue; + + /* + * \todo This assumes any input size is accepted as output size + * for all output formats. + */ + outputSizes.insert(sizes.begin(), sizes.end()); + + rawFormats_.try_emplace(mbusCode, std::move(sizes)); + } + + for (const auto &[v4pf, sizes] : isp_.output_->formats()) { + auto pf = v4pf.toPixelFormat(); + if (!pf.isValid()) + continue; + + outputFormats_.try_emplace(pf, outputSizes.begin(), outputSizes.end()); + } + + return !rawFormats_.empty() && !outputFormats_.empty(); +} + +/* + * \todo This should obviously be common code. + * + * CameraSensor::getFormat() is not adequate as it cannot take + * specific requirements along a pipeline into account. + */ +std::tuple +RCar4CameraData::findSensorFormat(PixelFormat targetFormat, Size targetSize, + Transform transform) const +{ + struct { + unsigned int mbusCode; + PixelFormat pf; + Size size; + unsigned bpp; + uint64_t areaDiff = -1; + } best = {}; + + const auto targetArea = uint64_t(targetSize.width) * targetSize.height; + + for (const auto &[mbusCode, sizes] : rawFormats_) { + ASSERT(!sizes.empty()); + + auto bayerFormat = BayerFormat::fromMbusCode(mbusCode); + ASSERT(bayerFormat.isValid()); + bayerFormat.order = vin_.sensor()->bayerOrder(transform); + + auto pf = bayerFormat.toPixelFormat(); + ASSERT(pf.isValid()); + + const auto &info = PixelFormatInfo::info(pf); + + for (const Size &size : sizes) { + const auto area = uint64_t(size.width) * size.height; + const auto areaDiff = utils::abs_diff(targetArea, area); + + if ((pf == targetFormat && best.pf != targetFormat) || + areaDiff < best.areaDiff || + (areaDiff == best.areaDiff && info.bitsPerPixel > best.bpp)) + best = { mbusCode, pf, size, info.bitsPerPixel, areaDiff }; + } + + if (targetFormat.isValid() && best.pf == targetFormat) + break; + } + + LOG(RCar4, Debug) + << "format: " << best.pf << ", " + << "size: " << best.size; + + /* + * The un-transformed mbus code is returned as it is expected + * that the sensor driver handles that correctly. + */ + + return { best.pf, best.mbusCode, best.size }; +} + +void RCar4CameraData::vinBufferReady(FrameBuffer *buffer) +{ + RCar4Frames::Info *info = frames_.find(buffer); + if (!info) + return; + + Request *request = info->request; + + /* If the buffer is cancelled force a complete of the whole request. */ + if (buffer->metadata().status == FrameMetadata::FrameCancelled) { + frames_.remove(info); + request->_d()->cancel(); + pipe()->completeRequest(request); + return; + } + + /* Record the sensor's timestamp in the request metadata. */ + request->_d()->metadata().set(controls::SensorTimestamp, + buffer->metadata().timestamp); + + ipa_->computeParams(info->frame, info->paramBuffer->cookie()); +} + +void RCar4CameraData::inputBufferReady(FrameBuffer *buffer) +{ + RCar4Frames::Info *info = frames_.find(buffer); + if (!info) + return; + + Request *request = info->request; + + if (request->findBuffer(&frames_.rawStream_)) + pipe()->completeBuffer(request, buffer); + + info->rawDequeued = true; + + if (frames_.tryComplete(info)) + pipe()->completeRequest(request); +} + +void RCar4CameraData::paramBufferReady(FrameBuffer *buffer) +{ + RCar4Frames::Info *info = frames_.find(buffer); + if (!info) + return; + + Request *request = info->request; + + info->paramDequeued = true; + + if (frames_.tryComplete(info)) + pipe()->completeRequest(request); +} + +void RCar4CameraData::statBufferReady(FrameBuffer *buffer) +{ + RCar4Frames::Info *info = frames_.find(buffer); + if (!info) + return; + + Request *request = info->request; + + if (buffer->metadata().status == FrameMetadata::FrameCancelled) { + info->metadataProcessed = true; + + if (frames_.tryComplete(info)) + pipe()->completeRequest(request); + + return; + } + + ipa_->processStats(info->frame, info->statBuffer->cookie(), + delayedCtrls_->get(buffer->metadata().sequence)); +} + +void RCar4CameraData::outputBufferReady(FrameBuffer *buffer) +{ + RCar4Frames::Info *info = frames_.find(buffer); + if (!info) + return; + + Request *request = info->request; + + if (request->findBuffer(&frames_.outputStream_)) + pipe()->completeBuffer(request, buffer); + + request->_d()->metadata().set(controls::draft::PipelineDepth, 3); + + info->outputDequeued = true; + + if (frames_.tryComplete(info)) + pipe()->completeRequest(request); +} + +void RCar4CameraData::paramsComputed(unsigned int frame, unsigned int bytesused) +{ + RCar4Frames::Info *info = frames_.find(frame); + if (!info) + return; + + info->paramBuffer->_d()->metadata().planes()[0].bytesused = bytesused; + + isp_.output_->queueBuffer(info->outputBuffer); + isp_.param_->queueBuffer(info->paramBuffer); + isp_.stat_->queueBuffer(info->statBuffer); + isp_.input_->queueBuffer(info->inputBuffer); +} + +void RCar4CameraData::setSensorControls([[maybe_unused]] unsigned int frame, + const ControlList &sensorControls) +{ + delayedCtrls_->push(sensorControls); +} + +void RCar4CameraData::metadataReady(unsigned int frame, const ControlList &metadata) +{ + RCar4Frames::Info *info = frames_.find(frame); + if (!info) + return; + + Request *request = info->request; + + request->_d()->metadata().merge(metadata); + info->metadataProcessed = true; + + if (frames_.tryComplete(info)) + pipe()->completeRequest(request); +} + +/* ----------------------------------------------------------------------------- + * Camera Configuration + */ + +class RCar4CameraConfiguration final : public CameraConfiguration +{ +public: + RCar4CameraConfiguration(RCar4CameraData *data); + + Status validate() override; + + const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; } + const Transform &combinedTransform() { return combinedTransform_; } + const PixelFormat &ispOutputFormat() { return ispOutputFormat_; } + +private: + std::shared_ptr data_; + + V4L2SubdeviceFormat sensorFormat_ = {}; + Transform combinedTransform_ = Transform::Identity; + PixelFormat ispOutputFormat_ = {}; +}; + +RCar4CameraConfiguration::RCar4CameraConfiguration(RCar4CameraData *data) + : CameraConfiguration(), data_(data->_o()->shared_from_this(), data) +{ +} + +CameraConfiguration::Status RCar4CameraConfiguration::validate() +{ + if (config_.empty()) + return Invalid; + + if (sensorConfig) { + LOG(RCar4, Error) + << "Setting sensor configuration is not implemented"; + return Invalid; + } + + Status status = validateColorSpaces(ColorSpaceFlag::StreamsShareColorSpace); + + /* Cap the number of entries to the available streams. */ + if (config_.size() > 2) { + config_.resize(2); + status = Adjusted; + } + + Orientation requestedOrientation = orientation; + combinedTransform_ = data_->vin_.sensor()->computeTransform(&orientation); + if (orientation != requestedOrientation) + status = Adjusted; + + StreamConfiguration *rawCfg = nullptr; + StreamConfiguration *processedCfg = nullptr; + + for (size_t i = 0; i < config_.size(); i++) { + StreamConfiguration &cfg = config_.at(i); + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); + + if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) { + if (rawCfg) { + LOG(RCar4, Error) + << "Camera configuration supports only one RAW stream"; + return Invalid; + } + + rawCfg = &cfg; + } else { + if (processedCfg) { + LOG(RCar4, Error) + << "Camera configuration supports only one processed stream"; + return Invalid; + } + + processedCfg = &cfg; + } + + if (cfg.bufferCount == 0) { + cfg.bufferCount = kDefaultBufferCount; + status = Adjusted; + } + } + + ASSERT(rawCfg || processedCfg); + + auto [sensorFormat, sensorCode, sensorSize] = data_->findSensorFormat( + rawCfg ? rawCfg->pixelFormat : PixelFormat{}, + rawCfg ? rawCfg->size : processedCfg->size, + combinedTransform_); + + V4L2DeviceFormat vinFormat = {}; + const auto vinPf = data_->vin_.output()->toV4L2PixelFormat(sensorFormat); + vinFormat.fourcc = vinPf; + vinFormat.size = sensorSize; + + if (data_->vin_.output()->tryFormat(&vinFormat)) + return Invalid; + + /* The format is expected to be accepted without adjustments. */ + if (vinFormat.fourcc != vinPf || vinFormat.size != sensorSize) + return Invalid; + + ispOutputFormat_ = data_->outputFormats_.begin()->first; + sensorFormat_ = { + .code = sensorCode, + .size = sensorSize, + .colorSpace = ColorSpace::Raw, + }; + + /* + * \todo `rvin_mc_try_format()` always sets it to `V4L2_COLORSPACE_SRGB`, + * so overwrite it with the actually expected color space. + */ + vinFormat.colorSpace = ColorSpace::Raw; + + if (rawCfg) { + if (rawCfg->pixelFormat != sensorFormat) + status = Adjusted; + if (rawCfg->size != sensorSize) + status = Adjusted; + if (rawCfg->colorSpace != vinFormat.colorSpace) + status = Adjusted; + + rawCfg->pixelFormat = sensorFormat; + rawCfg->size = vinFormat.size; + rawCfg->stride = vinFormat.planes[0].bpl; + rawCfg->frameSize = vinFormat.planes[0].size; + rawCfg->colorSpace = vinFormat.colorSpace; + + rawCfg->setStream(&data_->frames_.rawStream_); + } + + if (processedCfg) { + V4L2DeviceFormat ispFormat = {}; + ispFormat.fourcc = data_->isp_.output_->toV4L2PixelFormat( + processedCfg->pixelFormat); + ispFormat.size = sensorSize; + + if (data_->isp_.output_->tryFormat(&ispFormat)) + return Invalid; + + auto pf = ispFormat.fourcc.toPixelFormat(); + if (!pf.isValid()) + return Invalid; + + if (ispFormat.size != vinFormat.size) + return Invalid; + + if (processedCfg->pixelFormat != pf) + status = Adjusted; + if (processedCfg->size != ispFormat.size) + status = Adjusted; + if (processedCfg->colorSpace != ispFormat.colorSpace) + status = Adjusted; + + processedCfg->pixelFormat = pf; + processedCfg->size = ispFormat.size; + processedCfg->stride = ispFormat.planes[0].bpl; + processedCfg->frameSize = ispFormat.planes[0].size; + processedCfg->colorSpace = ispFormat.colorSpace; + + processedCfg->setStream(&data_->frames_.outputStream_); + + ispOutputFormat_ = processedCfg->pixelFormat; + } + + return status; +} + +/* ----------------------------------------------------------------------------- + * Pipeline Handler + */ + +class PipelineHandlerRCar4 final : public PipelineHandler +{ +public: + PipelineHandlerRCar4(CameraManager *manager); + + std::unique_ptr generateConfiguration(Camera *camera, + std::span roles) override; + int configure(Camera *camera, CameraConfiguration *config) override; + + int exportFrameBuffers(Camera *camera, Stream *stream, + std::vector> *buffers) override; + + int start(Camera *camera, const ControlList *controls) override; + void stopDevice(Camera *camera) override; + + int queueRequestDevice(Camera *camera, Request *request) override; + + bool match(DeviceEnumerator *enumerator) override; + +private: + RCar4CameraData *cameraData(Camera *camera) + { + return static_cast(camera->_d()); + } + + int createCamera(const MediaDevice *mdev, const std::string &pipeId); +}; + +PipelineHandlerRCar4::PipelineHandlerRCar4(CameraManager *manager) + : PipelineHandler(manager, kMaxRequests) +{ +} + +std::unique_ptr +PipelineHandlerRCar4::generateConfiguration(Camera *camera, + std::span roles) +{ + RCar4CameraData *data = cameraData(camera); + auto config = std::make_unique(data); + + if (roles.empty()) + return config; + + for (const StreamRole role : roles) { + std::map> formats; + PixelFormat pixelFormat; + + switch (role) { + case StreamRole::Raw: + for (const auto &[mbusCode, sizes] : data->rawFormats_) { + auto pf = BayerFormat::fromMbusCode(mbusCode).toPixelFormat(); + ASSERT(pf.isValid()); + formats.try_emplace(pf, sizes.begin(), sizes.end()); + } + + pixelFormat = formats.begin()->first; + break; + default: { + for (const auto &[pf, sizes] : data->outputFormats_) + formats.try_emplace(pf, sizes.begin(), sizes.end()); + + pixelFormat = formats.begin()->first; + break; + } + } + + ASSERT(!formats.empty()); + StreamConfiguration cfg = { std::move(formats) }; + + cfg.pixelFormat = pixelFormat; + cfg.size = { -1u, -1u }; + + config->addConfiguration(cfg); + } + + if (config->validate() == CameraConfiguration::Invalid) + return {}; + + return config; +} + +int PipelineHandlerRCar4::configure(Camera *camera, CameraConfiguration *c) +{ + RCar4CameraConfiguration *config = static_cast(c); + RCar4CameraData *data = cameraData(camera); + V4L2DeviceFormat vinFormat; + + /* Configure VIN and propagate format to ISP. */ + int ret = data->vin_.configure(config->sensorFormat(), + config->combinedTransform(), &vinFormat); + if (ret) + return ret; + + ret = data->isp_.configure(vinFormat, config->ispOutputFormat()); + if (ret) + return ret; + + /* Inform IPA of stream configuration and sensor controls. */ + IPACameraSensorInfo sensorInfo; + ret = data->vin_.sensor()->sensorInfo(&sensorInfo); + if (ret) + return ret; + + ipa::rppx1::IPAConfigInfo ipaConfig{ + std::move(sensorInfo), + data->vin_.sensor()->controls(), + }; + + ret = data->ipa_->configure(std::move(ipaConfig), &data->controlInfo_); + if (ret) { + LOG(RCar4, Error) << "failed configuring IPA (" << ret << ")"; + return ret; + } + + return 0; +} + +int PipelineHandlerRCar4::exportFrameBuffers(Camera *camera, Stream *stream, + std::vector> *buffers) +{ + RCar4CameraData *data = cameraData(camera); + unsigned int count = stream->configuration().bufferCount; + + if (stream == &data->frames_.outputStream_) + return data->isp_.output_->exportBuffers(count, buffers); + + if (stream == &data->frames_.rawStream_) + return data->isp_.input_->exportBuffers(count, buffers); + + return -EINVAL; +} + +int PipelineHandlerRCar4::start(Camera *camera, + [[maybe_unused]] const ControlList *controls) +{ + utils::scope_exit stopGuard([&] { stop(camera); }); + RCar4CameraData *data = cameraData(camera); + + data->delayedCtrls_->reset(); + + /* \todo Handle `controls`. */ + + int ret = data->frames_.start(&data->isp_, data->ipa_.get(), kMaxRequests); + if (ret) + return ret; + + ret = data->vin_.start(kMaxRequests); + if (ret) + return ret; + + ret = data->isp_.start(kMaxRequests); + if (ret) + return ret; + + ret = data->ipa_->start(); + if (ret) + return ret; + + stopGuard.release(); + return 0; +} + +void PipelineHandlerRCar4::stopDevice(Camera *camera) +{ + RCar4CameraData *data = cameraData(camera); + + data->ipa_->stop(); + data->isp_.stop(); + data->vin_.stop(); + + data->frames_.stop(&data->isp_, data->ipa_.get()); +} + +int PipelineHandlerRCar4::queueRequestDevice(Camera *camera, Request *request) +{ + RCar4CameraData *data = cameraData(camera); + + RCar4Frames::Info *info = data->frames_.create(request); + + /* Always expected to have buffers for `kMaxRequests` in-flight requests. */ + ASSERT(info); + + int ret = data->vin_.queueBuffer(info->inputBuffer); + if (ret) { + data->frames_.remove(info); + return ret; + } + + data->ipa_->queueRequest(info->frame, request->controls()); + + return 0; +} + +int PipelineHandlerRCar4::createCamera(const MediaDevice *mdev, + const std::string &pipeId) +{ + auto data = std::make_unique(this); + + int ret = data->init(mdev, pipeId); + if (ret) + return ret; + + const std::string &id = data->vin_.sensor()->id(); + std::set streams{ + &data->frames_.rawStream_, + &data->frames_.outputStream_, + }; + + registerCamera(Camera::create(std::move(data), id, streams)); + + return 0; +} + +bool PipelineHandlerRCar4::match(DeviceEnumerator *enumerator) +{ + DeviceMatch dm("rcar_vin"); + + auto media = acquireMediaDevice(enumerator, dm); + if (!media) + return false; + + bool registered = false; + for (const MediaEntity *entity : media->entities()) { + if (!entity->name().starts_with("rcar_isp")) + continue; + if (entity->name().rfind("core") == std::string::npos) + continue; + + /* + * Isolate the unit address that identifies one ISP + * instance. pipeId will look like + * 'rcar_isp fed00000.isp'. + */ + constexpr size_t prefix = + std::string_view("rcar_isp fed00000.isp").length(); + + std::string pipeId = entity->name().substr(0, prefix); + if (!createCamera(media.get(), pipeId)) + registered = true; + } + + return registered; +} + +REGISTER_PIPELINE_HANDLER(PipelineHandlerRCar4, "rcar-gen4") + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rcar-gen4/vin.cpp b/src/libcamera/pipeline/rcar-gen4/vin.cpp new file mode 100644 index 0000000000..1dc883412a --- /dev/null +++ b/src/libcamera/pipeline/rcar-gen4/vin.cpp @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright 2025 Renesas Electronics Co + * Copyright 2025 Niklas Söderlund + * + * Renesas R-Car Gen4 VIN pipeline + */ + +#include "vin.h" + +#include + +#include + +#include +#include +#include + +#include "libcamera/internal/bayer_format.h" +#include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/media_device.h" +#include "libcamera/internal/v4l2_subdevice.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(RCar4) + +int RCarVINDevice::init(const MediaDevice *media, const std::string &pipeId) +{ + const MediaEntity *entity; + const MediaPad *pad, *next; + + /* + * The current kernel driver connects each sensor to a particular VIN + * device, so that one has to be used, even though technically any + * VIN device could be used if proper link handling is implemented. + */ + + /* Locate IPS Channel Selector, e.g. rcar_isp fed00000.isp */ + csisp_ = V4L2Subdevice::fromEntityName(media, pipeId); + if (!csisp_) { + LOG(RCar4, Error) << "Failed to find Channel Selector " << pipeId; + return -EINVAL; + } + + /* Use the Channel Selector links to find CSI-2 Rx and Sensor. */ + entity = csisp_->entity(); + pad = entity->getPadByIndex(0); + next = pad->links()[0]->source(); + csi2_ = V4L2Subdevice::fromEntityName(media, next->entity()->name()); + if (!csi2_) { + LOG(RCar4, Error) << "Failed to find CSI-2 Rx entity"; + return -EINVAL; + } + + entity = csi2_->entity(); + pad = entity->getPadByIndex(0); + next = pad->links()[0]->source(); + sensor_ = CameraSensorFactoryBase::create(next->entity()); + if (!sensor_) { + LOG(RCar4, Error) << "Failed to find sensor entity"; + return -EINVAL; + } + + /* + * Use the Channel Selector links to find VIN. Currently the first + * VIN device is used unconditionally. + */ + entity = csisp_->entity(); + pad = entity->getPadByIndex(1); + next = pad->links()[0]->sink(); + output_ = V4L2VideoDevice::fromEntityName(media, next->entity()->name()); + if (!output_) { + LOG(RCar4, Error) << "Failed to find VIN entity"; + return -EINVAL; + } + + /* Open all devices. */ + int ret = csi2_->open(); + if (ret) + return ret; + + ret = csisp_->open(); + if (ret) + return ret; + + ret = output_->open(); + if (ret) + return ret; + + return 0; +} + +int RCarVINDevice::configure(const V4L2SubdeviceFormat &format, Transform transform, + V4L2DeviceFormat *outputFormat) +{ + auto sensorFormat = format; + + /* Configure sensor */ + int ret = sensor_->setFormat(&sensorFormat, transform); + if (ret) + return ret; + + /* Configure CSI-2 */ + ret = csi2_->setFormat(0, &sensorFormat); + if (ret) + return ret; + + /* Configure Channel selector. */ + ret = csisp_->setFormat(0, &sensorFormat); + if (ret) + return ret; + + auto bayerFormat = BayerFormat::fromMbusCode(sensorFormat.code); + if (!bayerFormat.isValid()) + return -ENOTSUP; + + /* Transform already applied to format by `CameraSensor::setFormat()`. */ + auto v4pf = bayerFormat.toV4L2PixelFormat(); + + /* Configure VIN */ + outputFormat->fourcc = v4pf; + outputFormat->size = sensorFormat.size; + outputFormat->planesCount = 1; + outputFormat->colorSpace = sensorFormat.colorSpace; + + ret = output_->setFormat(outputFormat); + if (ret) + return ret; + + LOG(RCar4, Debug) + << "sensor: " << sensorFormat << ", " + << "VIN: " << *outputFormat; + + if (outputFormat->size != format.size || outputFormat->fourcc != v4pf) + return -EINVAL; + + return 0; +} + +int RCarVINDevice::start(unsigned int bufferCount) +{ + utils::scope_exit stopGuard([&] { stop(); }); + + int ret = output_->importBuffers(bufferCount); + if (ret) { + LOG(RCar4, Error) << "Failed to import VIN buffers"; + return ret; + } + + ret = output_->streamOn(); + if (ret) { + LOG(RCar4, Error) << "Failed to start VIN"; + return ret; + } + + ret = output_->setFrameStartEnabled(true); + if (ret) { + LOG(RCar4, Error) << "Failed to enable Frame Start"; + return ret; + } + + stopGuard.release(); + return 0; +} + +void RCarVINDevice::stop() +{ + output_->setFrameStartEnabled(false); + + output_->streamOff(); + + if (output_->releaseBuffers()) + LOG(RCar4, Error) << "Failed to release VIN buffers"; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rcar-gen4/vin.h b/src/libcamera/pipeline/rcar-gen4/vin.h new file mode 100644 index 0000000000..0168b1ddee --- /dev/null +++ b/src/libcamera/pipeline/rcar-gen4/vin.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright 2025 Renesas Electronics Co + * Copyright 2025 Niklas Söderlund + * + * Renesas R-Car Gen4 VIN pipeline + */ + +#pragma once + +#include +#include + +#include "libcamera/internal/v4l2_subdevice.h" +#include "libcamera/internal/v4l2_videodevice.h" + +namespace libcamera { + +class CameraSensor; +class FrameBuffer; +class MediaDevice; +enum class Transform; + +class RCarVINDevice +{ +public: + int init(const MediaDevice *media, const std::string &pipeId); + int configure(const V4L2SubdeviceFormat &format, Transform transform, + V4L2DeviceFormat *outputFormat); + + int start(unsigned int bufferCount); + void stop(); + + CameraSensor *sensor() { return sensor_.get(); } + const CameraSensor *sensor() const { return sensor_.get(); } + V4L2VideoDevice *output() { return output_.get(); } + const V4L2VideoDevice *output() const { return output_.get(); } + + int queueBuffer(FrameBuffer *buffer) + { + return output_->queueBuffer(buffer); + } + + Signal &bufferReady() { return output_->bufferReady; } + Signal &frameStart() { return output_->frameStart; } + +private: + std::unique_ptr sensor_; + std::unique_ptr csi2_; + std::unique_ptr csisp_; + std::unique_ptr output_; +}; + +} /* namespace libcamera */ From patchwork Fri Nov 13 16:40:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28898 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 7A4DBC3307 for ; Fri, 13 Nov 2026 16:41:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 630B86A828; Fri, 13 Nov 2026 17:41:16 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="LduehbsY"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 699256A816 for ; Fri, 13 Nov 2026 17:41:07 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A52C0269; Fri, 13 Nov 2026 17:38:42 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587922; bh=Rig98cdPtWK/OpHU2/cKHL1YaK0pQ/Rh5PPg6IKebIc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=LduehbsYLKVqGoiLJH6ErVb5XE9w70edU2UJB3R4GevoCXk96tlKSSCJ5e8e4gzCa 6eVAM3OtUD597llspFVzBrFN5B4gmQYxPyi8C+2n3K4G8viMEmjga3iaPxj6zJHavo 72i7Z7TXMlihp45ApFhnNdvbyhV6lUtaB5ylT6H0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jai Luthra , Jacopo Mondi Subject: [PATCH v4 12/21] ipa: rppx1: Add Date: Fri, 13 Nov 2026 17:40:45 +0100 Message-ID: <20261113164054.267164-13-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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: Jai Luthra Copy the rkisp1 IPA module and remove the unnecessary parts, and adjust it as necessary to create the base for the new rppx1 ipa module. Co-developed-by: Jacopo Mondi Signed-off-by: Jai Luthra Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- meson_options.txt | 2 +- src/ipa/meson.build | 1 + src/ipa/rppx1/algorithms/algorithm.h | 22 +++ src/ipa/rppx1/algorithms/meson.build | 4 + src/ipa/rppx1/data/meson.build | 8 + src/ipa/rppx1/ipa_context.cpp | 37 ++++ src/ipa/rppx1/ipa_context.h | 58 ++++++ src/ipa/rppx1/meson.build | 31 +++ src/ipa/rppx1/module.h | 29 +++ src/ipa/rppx1/params.h | 89 +++++++++ src/ipa/rppx1/rppx1.cpp | 280 +++++++++++++++++++++++++++ src/ipa/rppx1/stats.h | 55 ++++++ 12 files changed, 615 insertions(+), 1 deletion(-) create mode 100644 src/ipa/rppx1/algorithms/algorithm.h create mode 100644 src/ipa/rppx1/algorithms/meson.build create mode 100644 src/ipa/rppx1/data/meson.build create mode 100644 src/ipa/rppx1/ipa_context.cpp create mode 100644 src/ipa/rppx1/ipa_context.h create mode 100644 src/ipa/rppx1/meson.build create mode 100644 src/ipa/rppx1/module.h create mode 100644 src/ipa/rppx1/params.h create mode 100644 src/ipa/rppx1/rppx1.cpp create mode 100644 src/ipa/rppx1/stats.h diff --git a/meson_options.txt b/meson_options.txt index 3c4bc9f8e7..2d98955dcf 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -49,7 +49,7 @@ option('gstreamer', option('ipas', type : 'array', choices : ['ipu3', 'mali-c55', 'rkisp1', 'rpi/pisp', 'rpi/vc4', - 'softisp', 'vimc'], + 'rppx1', 'softisp', 'vimc'], description : 'Select which IPA modules to build') option('lc-compliance', diff --git a/src/ipa/meson.build b/src/ipa/meson.build index ab4a1897f5..17ca211957 100644 --- a/src/ipa/meson.build +++ b/src/ipa/meson.build @@ -27,6 +27,7 @@ ipa_sign = files('ipa-sign.sh') supported_ipas = { 'ipu3': 'ipu3', 'mali-c55': 'mali-c55', + 'rcar-gen4': 'rppx1', 'rkisp1': 'rkisp1', 'rpi/pisp': 'rpi/pisp', 'rpi/vc4': 'rpi/vc4', diff --git a/src/ipa/rppx1/algorithms/algorithm.h b/src/ipa/rppx1/algorithms/algorithm.h new file mode 100644 index 0000000000..f5f002a0af --- /dev/null +++ b/src/ipa/rppx1/algorithms/algorithm.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 control algorithm interface + */ + +#pragma once + +#include + +#include "module.h" + +namespace libcamera { + +namespace ipa::rppx1 { + +using Algorithm = libcamera::ipa::Algorithm; + +} /* namespace ipa::rppx1 */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build new file mode 100644 index 0000000000..5fed9e9d50 --- /dev/null +++ b/src/ipa/rppx1/algorithms/meson.build @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: CC0-1.0 + +rppx1_ipa_algorithms = files([ +]) diff --git a/src/ipa/rppx1/data/meson.build b/src/ipa/rppx1/data/meson.build new file mode 100644 index 0000000000..d1eb321758 --- /dev/null +++ b/src/ipa/rppx1/data/meson.build @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: CC0-1.0 + +conf_files = files([ +]) + +install_data(conf_files, + install_dir : ipa_data_dir / 'rppx1', + install_tag : 'runtime') diff --git a/src/ipa/rppx1/ipa_context.cpp b/src/ipa/rppx1/ipa_context.cpp new file mode 100644 index 0000000000..2cd435d980 --- /dev/null +++ b/src/ipa/rppx1/ipa_context.cpp @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 IPA Context + */ + +#include "ipa_context.h" + +/** + * \file ipa_context.h + * \brief Context and state information shared between the algorithms + */ + +namespace libcamera::ipa::rppx1 { + +/** + * \struct IPASessionConfiguration + * \brief Session configuration for the IPA module + */ + +/** + * \struct IPAActiveState + * \brief Active state for algorithms + */ + +/** + * \struct IPAFrameContext + * \brief Per-frame context for algorithms + */ + +/** + * \struct IPAContext + * \brief Global IPA context data shared between all algorithms + */ + +} /* namespace libcamera::ipa::rppx1 */ diff --git a/src/ipa/rppx1/ipa_context.h b/src/ipa/rppx1/ipa_context.h new file mode 100644 index 0000000000..079fcab782 --- /dev/null +++ b/src/ipa/rppx1/ipa_context.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 IPA Context + */ + +#pragma once + +#include + +#include + +#include +#include +#include + +#include + +#include +#include + +namespace libcamera { + +namespace ipa::rppx1 { + +struct IPASessionConfiguration { +}; + +struct IPAActiveState { +}; + +struct IPAFrameContext : public FrameContext { +}; + +struct IPAContext { + IPAContext(unsigned int frameContextSize) + : frameContexts(frameContextSize) + { + } + + IPACameraSensorInfo sensorInfo; + IPASessionConfiguration configuration; + IPAActiveState activeState; + + FCQueue frameContexts; + + ControlInfoMap::Map ctrlMap; + + ControlInfoMap sensorControls; + + /* Interface to the Camera Helper */ + std::unique_ptr camHelper; +}; + +} /* namespace ipa::rppx1 */ + +} /* namespace libcamera*/ diff --git a/src/ipa/rppx1/meson.build b/src/ipa/rppx1/meson.build new file mode 100644 index 0000000000..98499c3f78 --- /dev/null +++ b/src/ipa/rppx1/meson.build @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: CC0-1.0 + +subdir('algorithms') +subdir('data') + +ipa_name = 'ipa_rppx1' + +rppx1_ipa_sources = files([ + 'ipa_context.cpp', + 'rppx1.cpp', +]) + +rppx1_ipa_sources += rppx1_ipa_algorithms + +mod = shared_module(ipa_name, rppx1_ipa_sources, + name_prefix : '', + include_directories : [ipa_includes], + dependencies : [libcamera_private, libipa_dep], + install : true, + install_dir : ipa_install_dir) + +if ipa_sign_module + custom_target(ipa_name + '.so.sign', + input : mod, + output : ipa_name + '.so.sign', + command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'], + install : false, + build_by_default : true) +endif + +ipa_names += ipa_name diff --git a/src/ipa/rppx1/module.h b/src/ipa/rppx1/module.h new file mode 100644 index 0000000000..04902eae10 --- /dev/null +++ b/src/ipa/rppx1/module.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 IPA Module + */ + +#pragma once + +#include + +#include + +#include + +#include "ipa_context.h" +#include "params.h" +#include "stats.h" + +namespace libcamera { + +namespace ipa::rppx1 { + +using Module = ipa::Module; + +} /* namespace ipa::rppx1 */ + +} /* namespace libcamera*/ diff --git a/src/ipa/rppx1/params.h b/src/ipa/rppx1/params.h new file mode 100644 index 0000000000..315c8a9785 --- /dev/null +++ b/src/ipa/rppx1/params.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 ISP Parameters + */ + +#pragma once + +#include + +#include + +namespace libcamera { + +namespace ipa::rppx1 { + +enum class BlockType : uint16_t { +}; + +namespace details { + +template +struct block_type { +}; + +#define RPPX1_DEFINE_BLOCK_TYPE(blkType, cfgType, id) \ + template<> \ + struct block_type { \ + using type = struct rppx1_##cfgType##_params; \ + static constexpr rppx1_params_block_type blockType = \ + RPPX1_PARAMS_BLOCK_TYPE_##id; \ + }; + +struct params_traits { + using id_type = BlockType; + + template + using id_to_details = block_type; +}; + +} /* namespace details */ + +template +class RppX1ParamsBlock final : public V4L2ParamsBlock +{ +public: + RppX1ParamsBlock(const std::span data) + : V4L2ParamsBlock(data), + configData_(data.subspan(sizeof(v4l2_isp_block_header))) + { + } + + const T *operator->() const override + { + return reinterpret_cast(configData_.data()); + } + + T *operator->() override + { + return reinterpret_cast(configData_.data()); + } + + const T &operator*() const override + { + return *reinterpret_cast(configData_.data()); + } + + T &operator*() override + { + return *reinterpret_cast(configData_.data()); + } + +private: + std::span configData_; +}; + +class RppX1Params : public V4L2Params +{ +public: + RppX1Params(std::span data) + : V4L2Params(data, V4L2_ISP_PARAMS_VERSION_V1) + { + } +}; + +} /* namespace ipa::rppx1 */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/rppx1.cpp b/src/ipa/rppx1/rppx1.cpp new file mode 100644 index 0000000000..4f1b449398 --- /dev/null +++ b/src/ipa/rppx1/rppx1.cpp @@ -0,0 +1,280 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas on Board Oy + * + * Based on rkisp1.cpp + * Copyright (C) 2019, Google Inc. + * + * RPP-X1 Image Processing Algorithms + */ + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "libcamera/internal/mapped_framebuffer.h" +#include "libcamera/internal/yaml_parser.h" + +#include "algorithms/algorithm.h" + +#include "ipa_context.h" +#include "module.h" +#include "params.h" +#include "stats.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(IPARppX1) + +namespace ipa::rppx1 { + +/* Maximum number of frame contexts to be held */ +static constexpr uint32_t kMaxFrameContexts = 16; + +class IPARppX1 final : public IPARppX1Interface, public Module +{ +public: + IPARppX1(); + + int init(const IPASettings &settings, + const IPACameraSensorInfo &sensorInfo, + const ControlInfoMap &sensorControls, + ControlInfoMap *ipaControls) override; + int start() override; + void stop() override; + + int configure(const IPAConfigInfo &ipaConfig, ControlInfoMap *ipaControls) override; + void mapBuffers(const std::vector &buffers) override; + void unmapBuffers(const std::vector &ids) override; + + void queueRequest(const uint32_t frame, const ControlList &controls) override; + void computeParams(const uint32_t frame, const uint32_t bufferId) override; + void processStats(const uint32_t frame, const uint32_t bufferId, + const ControlList &sensorControls) override; + +protected: + std::string logPrefix() const override; + +private: + void updateControls(ControlInfoMap *ipaControls); + void setControls(unsigned int frame); + + std::map mappedBuffers_; + + /* Local parameter storage */ + struct IPAContext context_; +}; + +IPARppX1::IPARppX1() + : context_(kMaxFrameContexts) +{ +} + +std::string IPARppX1::logPrefix() const +{ + return "rppx1"; +} + +int IPARppX1::init(const IPASettings &settings, + const IPACameraSensorInfo &sensorInfo, + const ControlInfoMap &sensorControls, + ControlInfoMap *ipaControls) +{ + context_.sensorInfo = sensorInfo; + context_.sensorControls = sensorControls; + + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!context_.camHelper) { + LOG(IPARppX1, Error) + << "Failed to create camera sensor helper for " + << settings.sensorModel; + return -ENODEV; + } + + /* Load the tuning data file. */ + File file(settings.configurationFile); + if (!file.open(File::OpenModeFlag::ReadOnly)) { + int ret = file.error(); + LOG(IPARppX1, Error) + << "Failed to open configuration file " + << settings.configurationFile << ": " << strerror(-ret); + return ret; + } + + std::unique_ptr data = YamlParser::parse(file); + if (!data) + return -EINVAL; + + unsigned int version = (*data)["version"].get(0); + if (version != 1) { + LOG(IPARppX1, Error) + << "Invalid tuning file version " << version; + return -EINVAL; + } + + if (!data->contains("algorithms")) { + LOG(IPARppX1, Error) + << "Tuning file doesn't contain any algorithm"; + return -EINVAL; + } + + int ret = createAlgorithms(context_, (*data)["algorithms"]); + if (ret) + return ret; + + updateControls(ipaControls); + + return 0; +} + +int IPARppX1::start() +{ + /* \todo Properly handle startup controls. */ + return 0; +} + +void IPARppX1::stop() +{ + context_.frameContexts.clear(); +} + +int IPARppX1::configure(const IPAConfigInfo &ipaConfig, ControlInfoMap *ipaControls) +{ + context_.sensorInfo = ipaConfig.sensorInfo; + context_.sensorControls = ipaConfig.sensorControls; + + /* Clear the IPA context before the streaming session. */ + context_.configuration = {}; + context_.activeState = {}; + context_.frameContexts.clear(); + + for (const auto &a : algorithms()) { + Algorithm *algo = static_cast(a.get()); + + int ret = algo->configure(context_, context_.sensorInfo); + if (ret) + return ret; + } + + updateControls(ipaControls); + + return 0; +} + +void IPARppX1::mapBuffers(const std::vector &buffers) +{ + for (const IPABuffer &buffer : buffers) { + FrameBuffer fb(buffer.planes); + + auto [it, inserted] = mappedBuffers_.try_emplace( + buffer.id, &fb, MappedFrameBuffer::MapFlag::ReadWrite); + ASSERT(inserted); + + if (!it->second.isValid()) { + LOG(IPARppX1, Fatal) + << "Failed to mmap buffer: " + << strerror(it->second.error()); + } + } +} + +void IPARppX1::unmapBuffers(const std::vector &ids) +{ + for (unsigned int id : ids) + mappedBuffers_.erase(id); +} + +void IPARppX1::queueRequest(const uint32_t frame, const ControlList &controls) +{ + IPAFrameContext &frameContext = context_.frameContexts.alloc(frame); + + for (const auto &a : algorithms()) { + Algorithm *algo = static_cast(a.get()); + algo->queueRequest(context_, frame, frameContext, controls); + } +} + +void IPARppX1::computeParams(const uint32_t frame, const uint32_t bufferId) +{ + IPAFrameContext &frameContext = context_.frameContexts.get(frame); + + RppX1Params params(mappedBuffers_.at(bufferId).planes()[0]); + + for (const auto &algo : algorithms()) + algo->prepare(context_, frame, frameContext, ¶ms); + + paramsComputed.emit(frame, params.bytesused()); +} + +void IPARppX1::processStats(const uint32_t frame, const uint32_t bufferId, + [[maybe_unused]] const ControlList &sensorControls) +{ + auto stats = RppX1Stats(mappedBuffers_.at(bufferId).planes()[0]); + if (!stats) + return; + + IPAFrameContext &frameContext = context_.frameContexts.get(frame); + ControlList metadata(controls::controls); + + for (auto const &a : algorithms()) { + Algorithm *algo = static_cast(a.get()); + algo->process(context_, frame, frameContext, &stats, metadata); + } + + setControls(frame); + + metadataReady.emit(frame, metadata); +} + +void IPARppX1::updateControls(ControlInfoMap *ipaControls) +{ + *ipaControls = { + { context_.ctrlMap.begin(), context_.ctrlMap.end() }, + controls::controls, + }; +} + +void IPARppX1::setControls(unsigned int frame) +{ + [[maybe_unused]] IPAFrameContext &frameContext = context_.frameContexts.get(frame); + + ControlList ctrls(context_.sensorControls); + + setSensorControls.emit(frame, ctrls); +} + +} /* namespace ipa::rppx1 */ + +/* + * External IPA module interface + */ + +extern "C" { +const struct IPAModuleInfo ipaModuleInfo = { + IPA_MODULE_API_VERSION, + 1, + "rppx1", +}; + +IPAInterface *ipaCreate() +{ + return new ipa::rppx1::IPARppX1(); +} +} + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/stats.h b/src/ipa/rppx1/stats.h new file mode 100644 index 0000000000..39dc132bac --- /dev/null +++ b/src/ipa/rppx1/stats.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 ISP Statistics + */ + +#pragma once + +#include + +#include + +namespace libcamera { + +namespace ipa::rppx1 { + +enum class StatsType : uint16_t { +}; + +namespace details { + +template +struct stats_type { +}; + +#define RPPX1_DEFINE_STATS_TYPE(blkType, cfgType, id) \ + template<> \ + struct stats_type { \ + using type = struct rppx1_##cfgType##_stats; \ + static constexpr rppx1_stats_block_type blockType = \ + RPPX1_STATS_BLOCK_TYPE_##id; \ + }; + +struct stats_traits { + using id_type = StatsType; + + template + using id_to_details = stats_type; +}; + +} /* namespace details */ + +class RppX1Stats : public V4L2Stats +{ +public: + RppX1Stats(std::span data) + : V4L2Stats(data, V4L2_ISP_VERSION_V1) + { + } +}; + +} /* namespace ipa::rppx1 */ + +} /* namespace libcamera */ From patchwork Fri Nov 13 16:40:46 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28901 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 87191C3307 for ; Fri, 13 Nov 2026 16:41:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id EACDE6A843; Fri, 13 Nov 2026 17:41:20 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gomQLQlb"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AF2DB6A821 for ; Fri, 13 Nov 2026 17:41:07 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F15CB493; Fri, 13 Nov 2026 17:38:42 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587923; bh=B9tFh84J/StznZkyX4xx/I3ME0nlMuQf8j0oCRcVIfM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gomQLQlbRqHiiKe8sOukMtM7q66k3pWPKPBwTYArtQ7gB4W1GP8iVybq/exNQAGe2 Iw0pOgmedypJRgx+sVmNk04t67SvXtJhXIXA6OhaXjmMBz3zW6FbAbTbjYrjoVqgmq Xw2hA7IFfE1OYPu7mbVmJCFo/H+UYBg4fd1HOipY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v4 13/21] ipa: rppx1: blc: Add Date: Fri, 13 Nov 2026 17:40:46 +0100 Message-ID: <20261113164054.267164-14-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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: Jacopo Mondi Add the algorithm to the rppx1 ipa module based on the corresponding algorithm in the rkisp1 ipa module. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze --- src/ipa/rppx1/algorithms/blc.cpp | 111 +++++++++++++++++++++++++++ src/ipa/rppx1/algorithms/blc.h | 41 ++++++++++ src/ipa/rppx1/algorithms/meson.build | 1 + src/ipa/rppx1/params.h | 3 + 4 files changed, 156 insertions(+) create mode 100644 src/ipa/rppx1/algorithms/blc.cpp create mode 100644 src/ipa/rppx1/algorithms/blc.h diff --git a/src/ipa/rppx1/algorithms/blc.cpp b/src/ipa/rppx1/algorithms/blc.cpp new file mode 100644 index 0000000000..8a209ecedd --- /dev/null +++ b/src/ipa/rppx1/algorithms/blc.cpp @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Black Level Correction control + */ + +#include "blc.h" + +#include + +#include + +#include "libcamera/internal/value_node.h" + +/** + * \file blc.h + */ + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +LOG_DEFINE_CATEGORY(RppX1Blc) + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int BlackLevelCorrection::init(IPAContext &context, [[maybe_unused]] const ValueNode &tuningData) +{ + auto blackLevel = context.camHelper->blackLevel(); + if (!blackLevel) { + LOG(RppX1Blc, Error) + << "No black levels provided by camera sensor helper"; + return -ENOTSUP; + } + + blackLevelRed_ = *blackLevel; + blackLevelGreenR_ = *blackLevel; + blackLevelGreenB_ = *blackLevel; + blackLevelBlue_ = *blackLevel; + + LOG(RppX1Blc, Debug) + << "Black levels: red " << blackLevelRed_ + << ", green (red) " << blackLevelGreenR_ + << ", green (blue) " << blackLevelGreenB_ + << ", blue " << blackLevelBlue_; + + return 0; +} + +int BlackLevelCorrection::configure([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context, + const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + RppX1Params *params) +{ + if (frame > 0) + return; + + auto config = params->block(); + config.setEnabled(true); + + config->mode = RPPX1_BLS_MODE_FIXED; + config->en_windows = RPPX1_BLS_WIN_EN_OFF; + + /* + * A separate black level subtraction value can be specified + * for each channel. The corresponding color depends on the + * input configuration. + * + * \todo Separate black level for each channel. + * + * Black level values are 16 bits, scale to the 24-bit domain. + */ + + config->fixed.a = static_cast(blackLevelRed_) << 8; + config->fixed.b = static_cast(blackLevelGreenR_) << 8; + config->fixed.c = static_cast(blackLevelGreenB_) << 8; + config->fixed.d = static_cast(blackLevelBlue_) << 8; +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void BlackLevelCorrection::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + [[maybe_unused]] const RppX1Stats *stats, + ControlList &metadata) +{ + metadata.set(controls::SensorBlackLevels, + { static_cast(blackLevelRed_), + static_cast(blackLevelGreenR_), + static_cast(blackLevelGreenB_), + static_cast(blackLevelBlue_) }); +} + +REGISTER_IPA_ALGORITHM(BlackLevelCorrection, "BlackLevelCorrection") + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/blc.h b/src/ipa/rppx1/algorithms/blc.h new file mode 100644 index 0000000000..3752166d78 --- /dev/null +++ b/src/ipa/rppx1/algorithms/blc.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Black Level Correction control + */ + +#pragma once + +#include + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +class BlackLevelCorrection : public Algorithm +{ +public: + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const RppX1Stats *stats, + ControlList &metadata) override; + +private: + int16_t blackLevelRed_; + int16_t blackLevelGreenR_; + int16_t blackLevelGreenB_; + int16_t blackLevelBlue_; +}; + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build index 5fed9e9d50..165f0ce054 100644 --- a/src/ipa/rppx1/algorithms/meson.build +++ b/src/ipa/rppx1/algorithms/meson.build @@ -1,4 +1,5 @@ # SPDX-License-Identifier: CC0-1.0 rppx1_ipa_algorithms = files([ + 'blc.cpp', ]) diff --git a/src/ipa/rppx1/params.h b/src/ipa/rppx1/params.h index 315c8a9785..05db309914 100644 --- a/src/ipa/rppx1/params.h +++ b/src/ipa/rppx1/params.h @@ -16,6 +16,7 @@ namespace libcamera { namespace ipa::rppx1 { enum class BlockType : uint16_t { + BlsPre1, }; namespace details { @@ -32,6 +33,8 @@ struct block_type { RPPX1_PARAMS_BLOCK_TYPE_##id; \ }; +RPPX1_DEFINE_BLOCK_TYPE(BlsPre1, bls, BLS_PRE1) + struct params_traits { using id_type = BlockType; From patchwork Fri Nov 13 16:40:47 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28900 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 D0580C335C for ; Fri, 13 Nov 2026 16:41:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 754FB6A850; Fri, 13 Nov 2026 17:41:19 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Ejbpqlne"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0E1B26A823 for ; Fri, 13 Nov 2026 17:41:08 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 43224269; Fri, 13 Nov 2026 17:38:43 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587923; bh=TFUovHQfWRvy52jkwqIgIghIjrASW73FdVoT4I2s1Dg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EjbpqlneTo1/rLpCjMF2k/L12b++7tfqfMhmcjwcm5tH46PSorEkuoXEBMo0qEs/u OHBh+Sct/mxmmF53CZcMTNt6N8WaZtn5zrZ/QaWjdhN3XJ4t5Rguv0CWaZffUgsPME h7bdgt+vRcj4qbtvcNDthQbUsEHPr5wmIwTUTy6U= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v4 14/21] ipa: rppx1: agc: Add Date: Fri, 13 Nov 2026 17:40:47 +0100 Message-ID: <20261113164054.267164-15-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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: Jacopo Mondi Add the algorithm to the rppx1 ipa module based on the corresponding algorithm in the rkisp1 ipa module. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze --- src/ipa/rppx1/algorithms/agc.cpp | 348 +++++++++++++++++++++++++++ src/ipa/rppx1/algorithms/agc.h | 47 ++++ src/ipa/rppx1/algorithms/meson.build | 1 + src/ipa/rppx1/ipa_context.h | 19 +- src/ipa/rppx1/params.h | 4 + src/ipa/rppx1/rppx1.cpp | 17 +- src/ipa/rppx1/stats.h | 5 + 7 files changed, 437 insertions(+), 4 deletions(-) create mode 100644 src/ipa/rppx1/algorithms/agc.cpp create mode 100644 src/ipa/rppx1/algorithms/agc.h diff --git a/src/ipa/rppx1/algorithms/agc.cpp b/src/ipa/rppx1/algorithms/agc.cpp new file mode 100644 index 0000000000..eff1f4d065 --- /dev/null +++ b/src/ipa/rppx1/algorithms/agc.cpp @@ -0,0 +1,348 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * AGC/AEC mean-based control algorithm + */ + +#include "agc.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include "libcamera/internal/value_node.h" + +#include "libipa/fixedpoint.h" +#include "libipa/histogram.h" + +/** + * \file agc.h + */ + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +using WindowWeightQ = UQ<1, 4>; +using MeasureCoeffQ = UQ<1, 7>; + +LOG_DEFINE_CATEGORY(RppX1Agc) + +int Agc::parseMeteringModes(IPAContext &context, const ValueNode &tuningData) +{ + if (!tuningData.isDictionary()) + LOG(RppX1Agc, Warning) + << "'AeMeteringMode' parameter not found in tuning file"; + + for (const auto &[key, value] : tuningData.asDict()) { + auto it = controls::AeMeteringModeNameValueMap.find(key); + if (it == controls::AeMeteringModeNameValueMap.end()) { + LOG(RppX1Agc, Warning) + << "Skipping unknown metering mode '" << key << "'"; + continue; + } + + auto weights = value.get>().value_or(utils::defopt); + if (weights.size() != RPPX1_HIST_WEIGHT_GRIDS_SIZE) { + LOG(RppX1Agc, Error) + << "Invalid 'AeMeteringMode:" << key << "': " + << "expected " << RPPX1_HIST_WEIGHT_GRIDS_SIZE << " elements, " + << "got " << weights.size(); + return -EINVAL; + } + + for (const auto &x : weights) { + if (x < 0 || x > 1) { + LOG(RppX1Agc, Error) + << "Invalid 'AeMeteringMode:" << key << "' values: " + << "elements must be in [0;1]"; + return -EINVAL; + } + } + + meteringModes_.try_emplace(it->second, std::move(weights)); + } + + if (meteringModes_.empty()) { + LOG(RppX1Agc, Warning) + << "No metering modes read from tuning file; defaulting to matrix"; + + meteringModes_.try_emplace( + controls::AeMeteringModeMatrix, + RPPX1_HIST_WEIGHT_GRIDS_SIZE, 1.0f); + } + + std::vector meteringModes; + std::vector meteringModeKeys = utils::map_keys(meteringModes_); + std::transform(meteringModeKeys.begin(), meteringModeKeys.end(), + std::back_inserter(meteringModes), + [](int x) { return ControlValue(x); }); + context.ctrlMap[&controls::AeMeteringMode] = ControlInfo(meteringModes); + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Agc::init(IPAContext &context, const ValueNode &tuningData) +{ + int ret; + + ret = agc_.init(tuningData, context.camHelper.get(), { + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); + if (ret) + return ret; + + const ValueNode &meteringModes = tuningData["AeMeteringMode"]; + ret = parseMeteringModes(context, meteringModes); + if (ret) + return ret; + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) +{ + int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); + if (ret) + return ret; + + context.activeState.agc.meteringMode = + static_cast(meteringModes_.begin()->first); + + context.configuration.agc.measureWindow.h_offs = 0; + context.configuration.agc.measureWindow.v_offs = 0; + context.configuration.agc.measureWindow.h_size = configInfo.outputSize.width; + context.configuration.agc.measureWindow.v_size = configInfo.outputSize.height; + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Agc::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + auto &agc = context.activeState.agc; + + agc_.queueRequest(context.configuration.agc, agc, frameContext.agc, controls); + + const auto &meteringMode = controls.get(controls::AeMeteringMode); + if (meteringMode) { + frameContext.agc.updateMetering = agc.meteringMode != *meteringMode; + agc.meteringMode = + static_cast(*meteringMode); + } + frameContext.agc.meteringMode = agc.meteringMode; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Agc::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, RppX1Params *params) +{ + agc_.prepare(context.activeState.agc, frameContext.agc); + + if (frame > 0 && !frameContext.agc.updateMetering) + return; + + auto exmConfig = params->block(); + exmConfig.setEnabled(true); + + exmConfig->wnd = context.configuration.agc.measureWindow; + exmConfig->mode = RPPX1_EXP_MEASURING_MODE_BAYER; + + /* `rppx1_exm_params::last_line` can be programmed but the driver overrides it. */ + exmConfig->channel_sel = RPPX1_MEAS_CHAN_SEL6; + exmConfig->coeff_r = MeasureCoeffQ(1.f).quantized(); + exmConfig->coeff_g_gr = MeasureCoeffQ(1.f).quantized(); + exmConfig->coeff_b = MeasureCoeffQ(1.f).quantized(); + exmConfig->coeff_gb = MeasureCoeffQ(1.f).quantized(); + + auto hst = params->block(); + hst.setEnabled(true); + + /* + * Configure the histogram measurement. Set the window, produce a + * luminance histogram, and set the weights and predivider. + */ + hst->wnd = context.configuration.agc.measureWindow; + + /* + * In RGB mode, a linear combination of the RGB components is used + * for the histogram. Use the ITU-R BT.709 RGB -> Y coefficients. + */ + hst->mode = RPPX1_HIST_MODE_RGB_COMBINED; + hst->channel_sel = RPPX1_MEAS_CHAN_SEL7; + hst->coeff[0] = MeasureCoeffQ(0.2126f).quantized(); + hst->coeff[1] = MeasureCoeffQ(0.7152f).quantized(); + hst->coeff[2] = MeasureCoeffQ(0.0722f).quantized(); + + std::span weights = meteringModes_.at(frameContext.agc.meteringMode); + ASSERT(weights.size() == std::size(hst->weights)); + for (size_t i = 0; i < weights.size(); i++) { + WindowWeightQ w = weights[i]; + hst->weights[i] = w.quantized(); + + LOG(RppX1Agc, Debug) + << "weights[" << i << "]: " << w; + } + + /* + * Each bin has a 16-bit integral component. So the maximum number in it + * is 65535. The worst case is when all pixels are in the same bin. + * + * To ensure no overflow even in that case, one pixel may be sampled out + * of every `skip` pixel. + */ + constexpr uint32_t kBinMax = (1u << 16) - 1; + const double skip = double(hst->wnd.h_size) * hst->wnd.v_size / kBinMax; + + /* + * There is a separate vertical/horizontal step setting, + * but let's try to find something square-ish. + * + * Subsampling by `prediv` vertically and horizontally should + * ensure that no more that `kBinMax` pixels are sampled. + * + * \todo Take weights into account? + */ + const auto prediv = std::clamp(std::ceil(std::sqrt(skip)), 1, 127); + hst->v_stepsize = prediv; + hst->h_step_inc = 65536 / prediv; + + hst->sample_offs = 0; + hst->sample_shift = 0; + + LOG(RppX1Agc, Debug) + << "window: " << Rectangle(hst->wnd.h_offs, hst->wnd.v_offs, hst->wnd.h_size, hst->wnd.v_size) << ", " + << "v_stepsize: " << hst->v_stepsize << ", " + << "h_step_inc: " << hst->h_step_inc; +} + +namespace { + +class AgcTraits final : public AgcMeanLuminance::Traits +{ +public: + AgcTraits(std::span expMeans, std::span weights) + : expMeans_(expMeans), weights_(weights) + { + ASSERT(expMeans_.size() == weights_.size()); + } + + /** + * \brief Estimate the relative luminance of the frame with a given gain + * \param[in] gain The gain to apply to the frame + * + * This function estimates the average relative luminance of the frame that + * would be output by the sensor if an additional \a gain was applied. + * + * The estimation is based on the AE statistics for the current frame. Y + * averages for all cells are first multiplied by the gain, and then saturated + * to approximate the sensor behaviour at high brightness values. The + * approximation is quite rough, as it doesn't take into account non-linearities + * when approaching saturation. + * + * The values are normalized to the [0.0, 1.0] range, where 1.0 corresponds to a + * theoretical perfect reflector of 100% reference white. + * + * More detailed information can be found in: + * https://en.wikipedia.org/wiki/Relative_luminance + * + * \return The relative luminance + */ + double estimateLuminance(double gain) const override + { + constexpr double kMaxExpMean = (1u << 20) - 1; + + double ySum = 0.0; + double wSum = 0.0; + + /* Sum the averages, saturated to the maximum. */ + for (unsigned i = 0; i < expMeans_.size(); i++) { + double w = weights_[i]; + ySum += std::min(expMeans_[i] * gain, kMaxExpMean) * w; + wSum += w; + } + + /* \todo Weight with the AWB gains */ + + return ySum / wSum / kMaxExpMean; + } + +private: + std::span expMeans_; + std::span weights_; +}; + +} /* namespace */ + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const RppX1Stats *stats, + ControlList &metadata) +{ + const auto histPost = stats->block(); + const auto exmPre1 = stats->block(); + + if (!histPost) + LOG(RppX1Agc, Error) << "HIST_POST data is missing in statistics"; + if (!exmPre1) + LOG(RppX1Agc, Error) << "EXM_PRE1 data is missing in statistics"; + + if (histPost && exmPre1) { + static_assert(RPPX1_EXM_NUM_WIN == RPPX1_HIST_WEIGHT_GRIDS_SIZE); + + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ + .traits = AgcTraits{ + exmPre1->exp_mean, + meteringModes_.at(frameContext.agc.meteringMode), + }, + .yHist = { + /* The lower 4 bits are fractional and meant to be discarded. */ + histPost->hist_bins, + [](uint32_t x) { return x >> 4; }, + }, + .exposure = frameContext.sensor.exposure, + .gain = frameContext.sensor.gain, + }}, metadata); + } else { + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {}, metadata); + } + + metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); +} + +REGISTER_IPA_ALGORITHM(Agc, "Agc") + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/agc.h b/src/ipa/rppx1/algorithms/agc.h new file mode 100644 index 0000000000..1b3c55681a --- /dev/null +++ b/src/ipa/rppx1/algorithms/agc.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Rpp-X1 AGC/AEC algorithm + */ + +#pragma once + +#include +#include + +#include "libipa/agc.h" + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +class Agc : public Algorithm +{ +public: + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const RppX1Stats *stats, + ControlList &metadata) override; + +private: + int parseMeteringModes(IPAContext &context, const ValueNode &tuningData); + + std::map> meteringModes_; + AgcAlgorithm agc_; +}; + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build index 165f0ce054..394ea9510b 100644 --- a/src/ipa/rppx1/algorithms/meson.build +++ b/src/ipa/rppx1/algorithms/meson.build @@ -1,5 +1,6 @@ # SPDX-License-Identifier: CC0-1.0 rppx1_ipa_algorithms = files([ + 'agc.cpp', 'blc.cpp', ]) diff --git a/src/ipa/rppx1/ipa_context.h b/src/ipa/rppx1/ipa_context.h index 079fcab782..f6408e73bb 100644 --- a/src/ipa/rppx1/ipa_context.h +++ b/src/ipa/rppx1/ipa_context.h @@ -9,14 +9,14 @@ #include -#include +#include #include #include -#include #include +#include #include #include @@ -25,12 +25,27 @@ namespace libcamera { namespace ipa::rppx1 { struct IPASessionConfiguration { + struct Agc : ipa::agc::Session { + rppx1_window measureWindow; + } agc; }; struct IPAActiveState { + struct Agc : ipa::agc::ActiveState { + controls::AeMeteringModeEnum meteringMode; + } agc; }; struct IPAFrameContext : public FrameContext { + struct { + uint32_t exposure; + double gain; + } sensor; + + struct Agc : ipa::agc::FrameContext { + controls::AeMeteringModeEnum meteringMode; + bool updateMetering; + } agc; }; struct IPAContext { diff --git a/src/ipa/rppx1/params.h b/src/ipa/rppx1/params.h index 05db309914..45adf43613 100644 --- a/src/ipa/rppx1/params.h +++ b/src/ipa/rppx1/params.h @@ -17,6 +17,8 @@ namespace ipa::rppx1 { enum class BlockType : uint16_t { BlsPre1, + ExmPre1, + HistPost, }; namespace details { @@ -34,6 +36,8 @@ struct block_type { }; RPPX1_DEFINE_BLOCK_TYPE(BlsPre1, bls, BLS_PRE1) +RPPX1_DEFINE_BLOCK_TYPE(ExmPre1, exm, EXM_PRE1) +RPPX1_DEFINE_BLOCK_TYPE(HistPost, hist, HIST_POST) struct params_traits { using id_type = BlockType; diff --git a/src/ipa/rppx1/rppx1.cpp b/src/ipa/rppx1/rppx1.cpp index 4f1b449398..4a494a57e9 100644 --- a/src/ipa/rppx1/rppx1.cpp +++ b/src/ipa/rppx1/rppx1.cpp @@ -30,6 +30,8 @@ #include "libcamera/internal/mapped_framebuffer.h" #include "libcamera/internal/yaml_parser.h" +#include + #include "algorithms/algorithm.h" #include "ipa_context.h" @@ -222,7 +224,7 @@ void IPARppX1::computeParams(const uint32_t frame, const uint32_t bufferId) } void IPARppX1::processStats(const uint32_t frame, const uint32_t bufferId, - [[maybe_unused]] const ControlList &sensorControls) + const ControlList &sensorControls) { auto stats = RppX1Stats(mappedBuffers_.at(bufferId).planes()[0]); if (!stats) @@ -231,6 +233,9 @@ void IPARppX1::processStats(const uint32_t frame, const uint32_t bufferId, IPAFrameContext &frameContext = context_.frameContexts.get(frame); ControlList metadata(controls::controls); + std::tie(frameContext.sensor.exposure, frameContext.sensor.gain) = + agc::extractControls(sensorControls, context_.camHelper.get()); + for (auto const &a : algorithms()) { Algorithm *algo = static_cast(a.get()); algo->process(context_, frame, frameContext, &stats, metadata); @@ -251,9 +256,17 @@ void IPARppX1::updateControls(ControlInfoMap *ipaControls) void IPARppX1::setControls(unsigned int frame) { - [[maybe_unused]] IPAFrameContext &frameContext = context_.frameContexts.get(frame); + IPAFrameContext &frameContext = context_.frameContexts.get(frame); + + uint32_t exposure = frameContext.agc.exposure; + uint32_t vblank = frameContext.agc.vblank; + + LOG(IPARppX1, Debug) + << "Set controls for frame " << frame << ": exposure " << exposure + << ", gain " << frameContext.agc.gain << ", vblank " << vblank; ControlList ctrls(context_.sensorControls); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); setSensorControls.emit(frame, ctrls); } diff --git a/src/ipa/rppx1/stats.h b/src/ipa/rppx1/stats.h index 39dc132bac..2925e2bbfc 100644 --- a/src/ipa/rppx1/stats.h +++ b/src/ipa/rppx1/stats.h @@ -16,6 +16,8 @@ namespace libcamera { namespace ipa::rppx1 { enum class StatsType : uint16_t { + ExmPre1, + HistPost, }; namespace details { @@ -32,6 +34,9 @@ struct stats_type { RPPX1_STATS_BLOCK_TYPE_##id; \ }; +RPPX1_DEFINE_STATS_TYPE(ExmPre1, exm, EXM_PRE1) +RPPX1_DEFINE_STATS_TYPE(HistPost, hist, HIST_POST) + struct stats_traits { using id_type = StatsType; From patchwork Fri Nov 13 16:40:48 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28902 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 78D68C335D for ; Fri, 13 Nov 2026 16:41:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5B51B6A859; Fri, 13 Nov 2026 17:41:22 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="j1P9Fh+j"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4944B6A831 for ; Fri, 13 Nov 2026 17:41:08 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8D9DF493; Fri, 13 Nov 2026 17:38:43 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587923; bh=iIkZUwOkHj6cBP8uSoGAYuHMgrjR4P62xZxuBzgSKRs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=j1P9Fh+jgc67qmaaH5htc1QdEbVjMKN3v7Zbz7MxutUKQ7poHSDbYxEOYyyC1QvPg NvHyiWcuDttzZs6ioI5fIluX/6/zYwFsnFIxiyBHLTc7UbdKQaGbtaYAghS501FGEf QNRRl8DA8GxqutHRx7BuKDFnrKuo2ryheQKpkwcM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v4 15/21] ipa: rppx1: awb: Add Date: Fri, 13 Nov 2026 17:40:48 +0100 Message-ID: <20261113164054.267164-16-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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: Jacopo Mondi Add the algorithm to the rppx1 ipa module based on the corresponding algorithm in the rkisp1 ipa module. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze --- src/ipa/rppx1/algorithms/awb.cpp | 266 +++++++++++++++++++++++++++ src/ipa/rppx1/algorithms/awb.h | 48 +++++ src/ipa/rppx1/algorithms/meson.build | 1 + src/ipa/rppx1/ipa_context.h | 10 + src/ipa/rppx1/params.h | 4 + src/ipa/rppx1/stats.h | 2 + 6 files changed, 331 insertions(+) create mode 100644 src/ipa/rppx1/algorithms/awb.cpp create mode 100644 src/ipa/rppx1/algorithms/awb.h diff --git a/src/ipa/rppx1/algorithms/awb.cpp b/src/ipa/rppx1/algorithms/awb.cpp new file mode 100644 index 0000000000..5e23b88146 --- /dev/null +++ b/src/ipa/rppx1/algorithms/awb.cpp @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * AWB control algorithm + */ + +#include "awb.h" + +#include + +#include + +#include + +#include "libcamera/internal/vector.h" + +#include "libipa/fixedpoint.h" + +/** + * \file awb.h + */ + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +LOG_DEFINE_CATEGORY(RppX1Awb) + +class RppX1AwbStats final : public AwbStats +{ +public: + RppX1AwbStats(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_; + } + + bool valid() const override + { + /* Minimum mean value below which AWB can't operate. */ + constexpr double minValue = 0.01; + + return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue || + rgbMeans_.b() > minValue; + } + +private: + RGB rgbMeans_; + double rg_; + double bg_; +}; + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Awb::init(IPAContext &context, const ValueNode &tuningData) +{ + return awbAlgo_.init(tuningData, context.ctrlMap); +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Awb::configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) +{ + awbAlgo_.configure(context.activeState.awb); + + /* + * Define the measurement window for AWB as a centered rectangle + * covering 3/4 of the image width and height. + */ + context.configuration.awb.measureWindow.h_offs = configInfo.outputSize.width / 8; + context.configuration.awb.measureWindow.v_offs = configInfo.outputSize.height / 8; + 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; +} + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Awb::queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, + controls); +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Awb::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, RppX1Params *params) +{ + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); + + auto gain = params->block(); + gain.setEnabled(true); + + gain->gain_green_b = GainQ(static_cast(frameContext.awb.gains.g())).quantized(); + gain->gain_blue = GainQ(static_cast(frameContext.awb.gains.b())).quantized(); + gain->gain_red = GainQ(static_cast(frameContext.awb.gains.r())).quantized(); + gain->gain_green_r = GainQ(static_cast(frameContext.awb.gains.g())).quantized(); + + /* If we have already set the AWB measurement parameters, return. */ + if (frame > 0) + return; + + auto awb = params->block(); + awb.setEnabled(true); + + /* Configure the measure window for AWB. */ + awb->wnd = context.configuration.awb.measureWindow; + + /* Number of frames to use to estimate the means (0 means 1 frame). */ + awb->frames = 0; + + awb->mode = RPPX1_WBMEAS_MODE_YCBCR; + + /* Set the reference Cr and Cb (AWB target) to white. */ + awb->ref_cb_max_b = UQ<0, 24>(0.5f).quantized(); + awb->ref_cr_max_r = UQ<0, 24>(0.5f).quantized(); + + /* + * Filter out pixels based on luminance and chrominance values. + * The acceptable luma values are specified as a [16, 250] + * range, while the acceptable chroma values are specified with + * a minimum of 16 and a maximum Cb+Cr sum of 250. + */ + awb->ymax_cmp = true; + awb->min_y_max_g = UQ<0, 24>(16.f / 255.f).quantized(); + awb->max_y = UQ<0, 24>(250.f / 255.f).quantized(); + + awb->min_c = UQ<0, 24>(16.f / 255.f).quantized(); + awb->max_csum = UQ<0, 24>(250.f / 255.f).quantized(); + + /* + * The suggested coefficients in YCbCr mode are those set forth + * in ITU-R BT.709. + * + * \todo These should be in a common place like colours.{h,cpp}. + * \todo The ordering of the coefficients should be investigated more. + */ + + /* Y coefficients */ + awb->ccor_coeff[0][2] = Q<4, 12>(0.2126f).quantized(); + awb->ccor_coeff[0][0] = Q<4, 12>(0.7152f).quantized(); + awb->ccor_coeff[0][1] = Q<4, 12>(0.0722f).quantized(); + /* Cb coefficients */ + awb->ccor_coeff[1][2] = Q<4, 12>(-0.1146f).quantized(); + awb->ccor_coeff[1][0] = Q<4, 12>(-0.3854f).quantized(); + awb->ccor_coeff[1][1] = Q<4, 12>(0.5f).quantized(); + /* Cr coefficients */ + awb->ccor_coeff[2][2] = Q<4, 12>(0.5f).quantized(); + awb->ccor_coeff[2][0] = Q<4, 12>(-0.4542f).quantized(); + awb->ccor_coeff[2][1] = Q<4, 12>(-0.0458f).quantized(); + /* The recommended offsets are (0, 0.5, 0.5) */ + awb->ccor_offs[0] = Q<1, 24>(0.0).quantized(); + awb->ccor_offs[1] = Q<1, 24>(0.5).quantized(); + awb->ccor_offs[2] = Q<1, 24>(0.5).quantized(); + + LOG(RppX1Awb, Debug) + << "window: " << Rectangle(awb->wnd.h_offs, awb->wnd.v_offs, awb->wnd.h_size, awb->wnd.v_size); +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Awb::process(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const RppX1Stats *stats, + ControlList &metadata) +{ + const auto awb = stats->block(); + if (!awb) + return; + + LOG(RppX1Awb, Debug) << "measured-pixels: " << awb->cnt; + + if (awb->cnt == 0) + return; + + RppX1AwbStats awbStats = calculateRgbMeans(frameContext, *awb); + + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, + 0, metadata); +} + +RppX1AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext, + const rppx1_wbmeas_stats &awb) const +{ + /* Get the YCbCr mean values */ + Vector yuvMeans({ + UQ<0, 24>(awb.mean_y_or_g).value(), + UQ<0, 24>(awb.mean_cb_or_b).value(), + UQ<0, 24>(awb.mean_cr_or_r).value(), + }); + + LOG(RppX1Awb, Debug) + << "yuv-means: (" + << awb.mean_y_or_g << ',' + << awb.mean_cb_or_b << ',' + << awb.mean_cr_or_r << ')' + << " -> " << yuvMeans; + + /* + * The inverse of the coefficients set in `prepare()`. + */ + static const Matrix rec709rgb = { { + 1.0, 0.0, 1.5748, + 1.0, -0.1873, -0.4681, + 1.0, 1.8556, 0.0, + } }; + static const Vector offsets = { { 0.0, 0.5, 0.5 } }; + + auto rgbMeans = rec709rgb * (yuvMeans - offsets); + + /* + * Due to hardware rounding errors in the YCbCr means, the + * calculated RGB means may be negative. This would lead to + * negative gains, messing up calculation. Prevent this by + * clamping the means to positive values. + */ + rgbMeans = rgbMeans.max(0.0); + + /* + * The ISP computes the AWB means after applying the colour gains, + * divide by the gains that were used to get the raw means from the + * sensor. Apply a minimum value to avoid divisions by near-zero. + */ + rgbMeans /= frameContext.awb.gains.max(0.01); + + return RppX1AwbStats(rgbMeans); +} + +REGISTER_IPA_ALGORITHM(Awb, "Awb") + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/awb.h b/src/ipa/rppx1/algorithms/awb.h new file mode 100644 index 0000000000..18d77d962e --- /dev/null +++ b/src/ipa/rppx1/algorithms/awb.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * AWB control algorithm + */ + +#pragma once + +#include "libipa/awb.h" +#include "libipa/fixedpoint.h" + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +class RppX1AwbStats; + +class Awb : public Algorithm +{ +public: + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const RppX1Stats *stats, + ControlList &metadata) override; + +private: + using GainQ = UQ<6, 12>; + + RppX1AwbStats calculateRgbMeans(const IPAFrameContext &frameContext, + const rppx1_wbmeas_stats &awb) const; + + AwbAlgorithm awbAlgo_; +}; + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build index 394ea9510b..4a2c1b52dd 100644 --- a/src/ipa/rppx1/algorithms/meson.build +++ b/src/ipa/rppx1/algorithms/meson.build @@ -2,5 +2,6 @@ rppx1_ipa_algorithms = files([ 'agc.cpp', + 'awb.cpp', 'blc.cpp', ]) diff --git a/src/ipa/rppx1/ipa_context.h b/src/ipa/rppx1/ipa_context.h index f6408e73bb..24fbbfaa28 100644 --- a/src/ipa/rppx1/ipa_context.h +++ b/src/ipa/rppx1/ipa_context.h @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -28,12 +29,19 @@ struct IPASessionConfiguration { struct Agc : ipa::agc::Session { rppx1_window measureWindow; } agc; + + struct { + struct rppx1_window measureWindow; + bool enabled; + } awb; }; struct IPAActiveState { struct Agc : ipa::agc::ActiveState { controls::AeMeteringModeEnum meteringMode; } agc; + + ipa::awb::ActiveState awb; }; struct IPAFrameContext : public FrameContext { @@ -46,6 +54,8 @@ struct IPAFrameContext : public FrameContext { controls::AeMeteringModeEnum meteringMode; bool updateMetering; } agc; + + ipa::awb::FrameContext awb; }; struct IPAContext { diff --git a/src/ipa/rppx1/params.h b/src/ipa/rppx1/params.h index 45adf43613..478d443a33 100644 --- a/src/ipa/rppx1/params.h +++ b/src/ipa/rppx1/params.h @@ -16,9 +16,11 @@ namespace libcamera { namespace ipa::rppx1 { enum class BlockType : uint16_t { + AwbGPre1, BlsPre1, ExmPre1, HistPost, + WbMeasPost, }; namespace details { @@ -35,9 +37,11 @@ struct block_type { RPPX1_PARAMS_BLOCK_TYPE_##id; \ }; +RPPX1_DEFINE_BLOCK_TYPE(AwbGPre1, awbg, AWBG_PRE1) RPPX1_DEFINE_BLOCK_TYPE(BlsPre1, bls, BLS_PRE1) RPPX1_DEFINE_BLOCK_TYPE(ExmPre1, exm, EXM_PRE1) RPPX1_DEFINE_BLOCK_TYPE(HistPost, hist, HIST_POST) +RPPX1_DEFINE_BLOCK_TYPE(WbMeasPost, wbmeas, WBMEAS_POST) struct params_traits { using id_type = BlockType; diff --git a/src/ipa/rppx1/stats.h b/src/ipa/rppx1/stats.h index 2925e2bbfc..2c2a6e3778 100644 --- a/src/ipa/rppx1/stats.h +++ b/src/ipa/rppx1/stats.h @@ -18,6 +18,7 @@ namespace ipa::rppx1 { enum class StatsType : uint16_t { ExmPre1, HistPost, + WbMeasPost, }; namespace details { @@ -36,6 +37,7 @@ struct stats_type { RPPX1_DEFINE_STATS_TYPE(ExmPre1, exm, EXM_PRE1) RPPX1_DEFINE_STATS_TYPE(HistPost, hist, HIST_POST) +RPPX1_DEFINE_STATS_TYPE(WbMeasPost, wbmeas, WBMEAS_POST) struct stats_traits { using id_type = StatsType; From patchwork Fri Nov 13 16:40:49 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28904 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 7534DC335E for ; Fri, 13 Nov 2026 16:41:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AF6FF6A82A; Fri, 13 Nov 2026 17:41:25 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="MN2lwONh"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8ACDA6A833 for ; Fri, 13 Nov 2026 17:41:08 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C2F63269; Fri, 13 Nov 2026 17:38:43 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587923; bh=cNnf/sQ4MZIRRj4g2Ku0q1oAqOYDfuLArBAyT9VjRo4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=MN2lwONhL0TW6BW3zIeMmn5qUdGbt1BWdUmdaQyg1TxxTO7yNx4g7EG1U1I6KGcTr 9cmfYaQepiqpa6Sg3O9BcFVhgZrnO1/WcVPFHUGICL6AyDBKccRQUVwRFzShV1c6m5 G5vkyzUQUY0Bj66C/Ewn69DPE8kgzegEbGxPuVrs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v4 16/21] ipa: rppx1: lux: Add Date: Fri, 13 Nov 2026 17:40:49 +0100 Message-ID: <20261113164054.267164-17-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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: Jacopo Mondi Add the algorithm to the rppx1 ipa module based on the corresponding algorithm in the rkisp1 ipa module. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze --- src/ipa/rppx1/algorithms/agc.cpp | 1 + src/ipa/rppx1/algorithms/awb.cpp | 2 +- src/ipa/rppx1/algorithms/lux.cpp | 76 ++++++++++++++++++++++++++++ src/ipa/rppx1/algorithms/lux.h | 36 +++++++++++++ src/ipa/rppx1/algorithms/meson.build | 1 + src/ipa/rppx1/ipa_context.h | 8 +++ 6 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/ipa/rppx1/algorithms/lux.cpp create mode 100644 src/ipa/rppx1/algorithms/lux.h diff --git a/src/ipa/rppx1/algorithms/agc.cpp b/src/ipa/rppx1/algorithms/agc.cpp index eff1f4d065..0853e29760 100644 --- a/src/ipa/rppx1/algorithms/agc.cpp +++ b/src/ipa/rppx1/algorithms/agc.cpp @@ -333,6 +333,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, }, .exposure = frameContext.sensor.exposure, .gain = frameContext.sensor.gain, + .lux = frameContext.lux.lux, }}, metadata); } else { agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {}, metadata); diff --git a/src/ipa/rppx1/algorithms/awb.cpp b/src/ipa/rppx1/algorithms/awb.cpp index 5e23b88146..fe7d64d77d 100644 --- a/src/ipa/rppx1/algorithms/awb.cpp +++ b/src/ipa/rppx1/algorithms/awb.cpp @@ -209,7 +209,7 @@ void Awb::process(IPAContext &context, RppX1AwbStats awbStats = calculateRgbMeans(frameContext, *awb); awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, - 0, metadata); + frameContext.lux.lux, metadata); } RppX1AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext, diff --git a/src/ipa/rppx1/algorithms/lux.cpp b/src/ipa/rppx1/algorithms/lux.cpp new file mode 100644 index 0000000000..41ae8e1ab6 --- /dev/null +++ b/src/ipa/rppx1/algorithms/lux.cpp @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Lux estimation + */ + +#include "lux.h" + +#include + +#include + +#include "libipa/histogram.h" +#include "libipa/lux.h" + +/** + * \file lux.h + */ + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Lux::init([[maybe_unused]] IPAContext &context, const ValueNode &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]] RppX1Params *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 RppX1Stats *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); + + utils::Duration exposureTime = context.configuration.agc.lineDuration * + frameContext.sensor.exposure; + double gain = frameContext.sensor.gain; + + /* \todo Deduplicate the histogram calculation from AGC */ + const auto histPost = stats->block(); + if (!histPost) + return; + + Histogram yHist(histPost->hist_bins, [](uint32_t x) { return x >> 4; }); + + context.activeState.lux.lux = lux_.estimateLux(exposureTime, gain, 1.0, yHist); +} + +REGISTER_IPA_ALGORITHM(Lux, "Lux") + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/lux.h b/src/ipa/rppx1/algorithms/lux.h new file mode 100644 index 0000000000..e38835be2b --- /dev/null +++ b/src/ipa/rppx1/algorithms/lux.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Lux estimation + */ + +#pragma once + +#include "libipa/lux.h" + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +class Lux : public Algorithm +{ +public: + int init(IPAContext &context, const ValueNode &tuningData) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const RppX1Stats *stats, + ControlList &metadata) override; + +private: + ipa::Lux lux_; +}; + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build index 4a2c1b52dd..19688854a3 100644 --- a/src/ipa/rppx1/algorithms/meson.build +++ b/src/ipa/rppx1/algorithms/meson.build @@ -4,4 +4,5 @@ rppx1_ipa_algorithms = files([ 'agc.cpp', 'awb.cpp', 'blc.cpp', + 'lux.cpp', ]) diff --git a/src/ipa/rppx1/ipa_context.h b/src/ipa/rppx1/ipa_context.h index 24fbbfaa28..9dfc67f252 100644 --- a/src/ipa/rppx1/ipa_context.h +++ b/src/ipa/rppx1/ipa_context.h @@ -42,6 +42,10 @@ struct IPAActiveState { } agc; ipa::awb::ActiveState awb; + + struct { + double lux; + } lux; }; struct IPAFrameContext : public FrameContext { @@ -56,6 +60,10 @@ struct IPAFrameContext : public FrameContext { } agc; ipa::awb::FrameContext awb; + + struct { + double lux; + } lux; }; struct IPAContext { From patchwork Fri Nov 13 16:40:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28903 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 5B16AC3304 for ; Fri, 13 Nov 2026 16:41:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D5ECA6A833; Fri, 13 Nov 2026 17:41:24 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="feQ2usz6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D048A6A82E for ; Fri, 13 Nov 2026 17:41:08 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1F3EEB2D; Fri, 13 Nov 2026 17:38:44 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587924; bh=VPU5MZHp8xz+COu2DtlDpsHKCax6Vlzj6bZfXQH5xok=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=feQ2usz60tqnbcWb8X0Jo771YUbWkIHZdyakPI/Zg5Ix37slscb8cwZ9PgleTmiEb rIa2FG/ln2ZXCpdA+YJK4hKLrKe2vhMcivaMRs3YK9wUajwkwzXc13F/mVFsiJ2djI cTYOf98OaiRwYnmMAhIluCnxWvOpNHSSCFSEX5uE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v4 17/21] ipa: rppx1: ccm: Add Date: Fri, 13 Nov 2026 17:40:50 +0100 Message-ID: <20261113164054.267164-18-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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: Jacopo Mondi Add the algorithm to the rppx1 ipa module based on the corresponding algorithm in the rkisp1 ipa module. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze --- src/ipa/rppx1/algorithms/awb.cpp | 6 ++ src/ipa/rppx1/algorithms/ccm.cpp | 112 +++++++++++++++++++++++++++ src/ipa/rppx1/algorithms/ccm.h | 45 +++++++++++ src/ipa/rppx1/algorithms/meson.build | 1 + src/ipa/rppx1/ipa_context.h | 5 ++ src/ipa/rppx1/params.h | 2 + 6 files changed, 171 insertions(+) create mode 100644 src/ipa/rppx1/algorithms/ccm.cpp create mode 100644 src/ipa/rppx1/algorithms/ccm.h diff --git a/src/ipa/rppx1/algorithms/awb.cpp b/src/ipa/rppx1/algorithms/awb.cpp index fe7d64d77d..474f60b20d 100644 --- a/src/ipa/rppx1/algorithms/awb.cpp +++ b/src/ipa/rppx1/algorithms/awb.cpp @@ -249,6 +249,12 @@ RppX1AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext, */ rgbMeans = rgbMeans.max(0.0); + /* + * The ISP computes the AWB means after applying the CCM. Apply the + * inverse as we want to get the raw means before the colour gains. + */ + rgbMeans = frameContext.ccm.ccm.inverse() * rgbMeans; + /* * The ISP computes the AWB means after applying the colour gains, * divide by the gains that were used to get the raw means from the diff --git a/src/ipa/rppx1/algorithms/ccm.cpp b/src/ipa/rppx1/algorithms/ccm.cpp new file mode 100644 index 0000000000..4d4ed71e46 --- /dev/null +++ b/src/ipa/rppx1/algorithms/ccm.cpp @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Color Correction Matrix control algorithm + */ + +#include "ccm.h" + +#include + +/** + * \file ccm.h + */ + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +LOG_DEFINE_CATEGORY(RppX1Ccm) + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) +{ + return ccmAlgo_.init(tuningData, context.ctrlMap); +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Ccm::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + return ccmAlgo_.configure(context.activeState.ccm, + context.activeState.awb.automatic.colourTemperature); +} + +void Ccm::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + /* Nothing to do here, the ccm will be calculated in prepare() */ + if (frameContext.awb.autoEnabled) + return; + + ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls); +} + +void Ccm::setParameters(rppx1_ccor_params &config, IPAFrameContext &context) +{ + const Matrix &matrix = context.ccm.ccm; + const Matrix &offsets = context.ccm.offsets; + + /* + * Coefficients are 16-bit Q4.12 signed numbers ranging from + * -8 (0x8000) to +7.9996 (0x7fff). + */ + for (unsigned int i = 0; i < 3; i++) { + for (unsigned int j = 0; j < 3; j++) + config.coeff[i][j] = Q<4, 12>(matrix[i][j]).quantized(); + } + + /* + * Offsets are 25 bits 2's complement. + * + * \todo: Better investigate how negative offsets are handled in the + * offsets interpolation. + */ + + for (unsigned int i = 0; i < 3; i++) + config.offset[i] = offsets[i][0]; + + LOG(RppX1Ccm, Debug) << "Setting matrix " << matrix; + LOG(RppX1Ccm, Debug) << "Setting offsets " << offsets; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Ccm::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, RppX1Params *params) +{ + if (frameContext.awb.autoEnabled) + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, + frame, frameContext.awb.colourTemperature); + + auto config = params->block(); + config.setEnabled(true); + + setParameters(*config, frameContext); +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Ccm::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const RppX1Stats *stats, + ControlList &metadata) +{ + ccmAlgo_.process(frameContext.ccm, metadata); +} + +REGISTER_IPA_ALGORITHM(Ccm, "Ccm") + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/ccm.h b/src/ipa/rppx1/algorithms/ccm.h new file mode 100644 index 0000000000..7076147608 --- /dev/null +++ b/src/ipa/rppx1/algorithms/ccm.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Color Correction Matrix control algorithm + */ + +#pragma once + +#include "libipa/ccm.h" +#include "libipa/fixedpoint.h" + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +class Ccm : public Algorithm +{ +public: + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const RppX1Stats *stats, + ControlList &metadata) override; + +private: + void setParameters(rppx1_ccor_params &config, IPAFrameContext &context); + + CcmAlgorithm> ccmAlgo_; +}; + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build index 19688854a3..7e0b4ad011 100644 --- a/src/ipa/rppx1/algorithms/meson.build +++ b/src/ipa/rppx1/algorithms/meson.build @@ -4,5 +4,6 @@ rppx1_ipa_algorithms = files([ 'agc.cpp', 'awb.cpp', 'blc.cpp', + 'ccm.cpp', 'lux.cpp', ]) diff --git a/src/ipa/rppx1/ipa_context.h b/src/ipa/rppx1/ipa_context.h index 9dfc67f252..c0ef73b042 100644 --- a/src/ipa/rppx1/ipa_context.h +++ b/src/ipa/rppx1/ipa_context.h @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -43,6 +44,8 @@ struct IPAActiveState { ipa::awb::ActiveState awb; + ipa::ccm::ActiveState ccm; + struct { double lux; } lux; @@ -61,6 +64,8 @@ struct IPAFrameContext : public FrameContext { ipa::awb::FrameContext awb; + ipa::ccm::FrameContext ccm; + struct { double lux; } lux; diff --git a/src/ipa/rppx1/params.h b/src/ipa/rppx1/params.h index 478d443a33..b863b9ba19 100644 --- a/src/ipa/rppx1/params.h +++ b/src/ipa/rppx1/params.h @@ -18,6 +18,7 @@ namespace ipa::rppx1 { enum class BlockType : uint16_t { AwbGPre1, BlsPre1, + CcorPost, ExmPre1, HistPost, WbMeasPost, @@ -39,6 +40,7 @@ struct block_type { RPPX1_DEFINE_BLOCK_TYPE(AwbGPre1, awbg, AWBG_PRE1) RPPX1_DEFINE_BLOCK_TYPE(BlsPre1, bls, BLS_PRE1) +RPPX1_DEFINE_BLOCK_TYPE(CcorPost, ccor, CCOR_POST) RPPX1_DEFINE_BLOCK_TYPE(ExmPre1, exm, EXM_PRE1) RPPX1_DEFINE_BLOCK_TYPE(HistPost, hist, HIST_POST) RPPX1_DEFINE_BLOCK_TYPE(WbMeasPost, wbmeas, WBMEAS_POST) From patchwork Fri Nov 13 16:40:51 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28905 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 418CDC335C for ; Fri, 13 Nov 2026 16:41:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5D3A86A854; Fri, 13 Nov 2026 17:41:26 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Zy1E4cxl"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 18C0A6A839 for ; Fri, 13 Nov 2026 17:41:09 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5B517493; Fri, 13 Nov 2026 17:38:44 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587924; bh=+pJc8n0tjbUMqzZrdZDKY+g6H+KuJ6Iq03ltPDEjL/w=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Zy1E4cxlKy+eurQT5hhJCskZbL3TTzWOJWChi+CgP/G7OLRkGeC/fp8eYOSCFOMOJ FcsRhrjUdwCQDWB3VW6GylQmKGFVh0ApmH4wa/jKkTqvruRQiHWtpqgwmDMe1FfOY8 CRJmbDdLBR82FUK7qN3nCy6vSbRc+3Ds6CuVS/II= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v4 18/21] ipa: rppx1: gamma: Add Date: Fri, 13 Nov 2026 17:40:51 +0100 Message-ID: <20261113164054.267164-19-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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: Jacopo Mondi Add the algorithm to the rppx1 ipa module based on the corresponding algorithm in the rkisp1 ipa module. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze --- src/ipa/rppx1/algorithms/gamma.cpp | 89 ++++++++++++++++++++++++++++ src/ipa/rppx1/algorithms/gamma.h | 43 ++++++++++++++ src/ipa/rppx1/algorithms/meson.build | 1 + src/ipa/rppx1/ipa_context.h | 5 ++ src/ipa/rppx1/params.h | 2 + 5 files changed, 140 insertions(+) create mode 100644 src/ipa/rppx1/algorithms/gamma.cpp create mode 100644 src/ipa/rppx1/algorithms/gamma.h diff --git a/src/ipa/rppx1/algorithms/gamma.cpp b/src/ipa/rppx1/algorithms/gamma.cpp new file mode 100644 index 0000000000..70f10a341d --- /dev/null +++ b/src/ipa/rppx1/algorithms/gamma.cpp @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Gamma out control + */ + +#include "gamma.h" + +/** + * \file gamma.h + */ + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Gamma::init(IPAContext &context, const ValueNode &tuningData) +{ + /* + * The segments as specified in the reference for + * RPPX1_GA_SEG_MODE_LOGARITHMIC. + */ + static constexpr unsigned int segments[] = { + 64, 64, 64, 64, 128, 128, 128, 128, + 256, 256, 256, 512, 512, 512, 512, 512, + }; + + return gamma_.init(context.ctrlMap, tuningData, segments); +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Gamma::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + gamma_.configure(context.activeState.goc); + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Gamma::queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + gamma_.queueRequest(context.activeState.goc, frame, frameContext.goc, controls); +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Gamma::prepare([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) +{ + if (!frameContext.goc.update) + return; + + auto config = params->block(); + config.setEnabled(true); + + config->mode = RPPX1_GA_SEG_MODE_LOGARITHMIC; + gamma_.prepare(frameContext.goc, std::span(config->gamma_y)); +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Gamma::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const RppX1Stats *stats, + ControlList &metadata) +{ + metadata.set(controls::Gamma, frameContext.goc.gamma); +} + +REGISTER_IPA_ALGORITHM(Gamma, "Gamma") + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/gamma.h b/src/ipa/rppx1/algorithms/gamma.h new file mode 100644 index 0000000000..1b226b0db0 --- /dev/null +++ b/src/ipa/rppx1/algorithms/gamma.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Gamma out control + */ + +#pragma once + +#include + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +class Gamma : public Algorithm +{ +public: + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const RppX1Stats *stats, + ControlList &metadata) override; + +private: + /* RPP_OUT ("human vision") pipeline with 12-bit gamma values */ + GammaAlgorithm> gamma_; +}; + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build index 7e0b4ad011..7394b55bca 100644 --- a/src/ipa/rppx1/algorithms/meson.build +++ b/src/ipa/rppx1/algorithms/meson.build @@ -5,5 +5,6 @@ rppx1_ipa_algorithms = files([ 'awb.cpp', 'blc.cpp', 'ccm.cpp', + 'gamma.cpp', 'lux.cpp', ]) diff --git a/src/ipa/rppx1/ipa_context.h b/src/ipa/rppx1/ipa_context.h index c0ef73b042..0d7b298f43 100644 --- a/src/ipa/rppx1/ipa_context.h +++ b/src/ipa/rppx1/ipa_context.h @@ -21,6 +21,7 @@ #include #include #include +#include namespace libcamera { @@ -46,6 +47,8 @@ struct IPAActiveState { ipa::ccm::ActiveState ccm; + ipa::gamma::ActiveState goc; + struct { double lux; } lux; @@ -66,6 +69,8 @@ struct IPAFrameContext : public FrameContext { ipa::ccm::FrameContext ccm; + ipa::gamma::FrameContext goc; + struct { double lux; } lux; diff --git a/src/ipa/rppx1/params.h b/src/ipa/rppx1/params.h index b863b9ba19..b724fa3162 100644 --- a/src/ipa/rppx1/params.h +++ b/src/ipa/rppx1/params.h @@ -20,6 +20,7 @@ enum class BlockType : uint16_t { BlsPre1, CcorPost, ExmPre1, + GaHv, HistPost, WbMeasPost, }; @@ -42,6 +43,7 @@ RPPX1_DEFINE_BLOCK_TYPE(AwbGPre1, awbg, AWBG_PRE1) RPPX1_DEFINE_BLOCK_TYPE(BlsPre1, bls, BLS_PRE1) RPPX1_DEFINE_BLOCK_TYPE(CcorPost, ccor, CCOR_POST) RPPX1_DEFINE_BLOCK_TYPE(ExmPre1, exm, EXM_PRE1) +RPPX1_DEFINE_BLOCK_TYPE(GaHv, ga, GA_HV) RPPX1_DEFINE_BLOCK_TYPE(HistPost, hist, HIST_POST) RPPX1_DEFINE_BLOCK_TYPE(WbMeasPost, wbmeas, WBMEAS_POST) From patchwork Fri Nov 13 16:40:52 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28906 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 35EA7C3307 for ; Fri, 13 Nov 2026 16:41:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3D1896A862; Fri, 13 Nov 2026 17:41:27 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="MY35xwP6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 605F56A822 for ; Fri, 13 Nov 2026 17:41:09 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9D4AC269; Fri, 13 Nov 2026 17:38:44 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587924; bh=bXGESFcceZy5bQrEo2JWQkHpG7jLxOgvQiBa1I66Y44=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=MY35xwP6tvWV8ZoWM0uJajCY06bRgIORdhFnPfkZi2k4B6Kc5SvoXK3qTVozbepSL 7ULXk+efGFOTkIKPYIXLBiEM5srzodI++7aF56zluN3gr7n6Kl0HvmwRqPIfis1hIQ J1HXcSg3JFbf/S9wNJBKwkJH3NaHl53dHHsvWWgg= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v4 19/21] ipa: rppx1: gsl: Add Date: Fri, 13 Nov 2026 17:40:52 +0100 Message-ID: <20261113164054.267164-20-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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: Jacopo Mondi Add the algorithm to the rppx1 ipa module based on the corresponding algorithm in the rkisp1 ipa module. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze --- src/ipa/rppx1/algorithms/gsl.cpp | 150 +++++++++++++++++++++++++++ src/ipa/rppx1/algorithms/gsl.h | 35 +++++++ src/ipa/rppx1/algorithms/meson.build | 1 + src/ipa/rppx1/params.h | 4 + 4 files changed, 190 insertions(+) create mode 100644 src/ipa/rppx1/algorithms/gsl.cpp create mode 100644 src/ipa/rppx1/algorithms/gsl.h diff --git a/src/ipa/rppx1/algorithms/gsl.cpp b/src/ipa/rppx1/algorithms/gsl.cpp new file mode 100644 index 0000000000..181825afb6 --- /dev/null +++ b/src/ipa/rppx1/algorithms/gsl.cpp @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Gamma Sensor Linearization control + */ + +#include "gsl.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "libcamera/internal/value_node.h" + +/** + * \file gsl.h + */ + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +LOG_DEFINE_CATEGORY(RppX1Gsl) + +/* `LinPre1` only */ +constexpr uint32_t kDxOffset = 8; +constexpr uint32_t kMaxDx = (1u << 4) - 1; +constexpr uint32_t kResolution = 1u << 24; + +namespace { + +[[nodiscard]] +bool parseYLut(const ValueNode &yObject, std::string_view key, uint32_t scale, + std::span res) +{ + auto vals = yObject[key].get>().value_or(utils::defopt); + if (vals.size() != res.size()) { + LOG(RppX1Gsl, Error) + << "Invalid 'y:" << key << "': expected " + << res.size() << " elements, got " << vals.size(); + return false; + } + + for (const auto &[i, y] : utils::enumerate(vals)) { + if (y < 0 || y > 1) { + LOG(RppX1Gsl, Error) + << "Invalid 'y:" << key << "': elements must be in [0; 1]"; + return false; + } + + res[i] = std::lround(y * scale); + } + + return true; +} + +} /* namespace */ + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context, + const ValueNode &tuningData) +{ + /* + * \todo `LinPre2` has different ranges: + * * kDxOffset = 4 + * * kMaxDx = 7 + * * kResolution = 2^12 + * Maybe this parameter could be reworked to be compatible with both. + */ + gammaDx_ = tuningData["x-intervals"].get>().value_or(utils::defopt); + if (gammaDx_.size() != RPPX1_LIN_SAMPLE_POINTS_NUM) { + LOG(RppX1Gsl, Error) + << "Invalid 'x-intervals': expected " + << RPPX1_LIN_SAMPLE_POINTS_NUM << " elements, got " + << gammaDx_.size(); + + return -EINVAL; + } + + uint32_t xSum = 0; + for (const uint8_t x : gammaDx_) { + if (x > kMaxDx) { + LOG(RppX1Gsl, Error) + << "Invalid 'x-intervals': must be at most " << kMaxDx + << ", got " << x; + return -EINVAL; + } + + xSum += 1u << (x + kDxOffset); + } + + /* + * > Typically, the accumulated sum of dx[i] should cover the complete data + * > input range of [0; 2^12) or [0; 2^24). + */ + if (xSum != kResolution) { + LOG(RppX1Gsl, Error) + << "Invalid 'x-intervals': must cover full input range of " << kResolution + << ", got " << xSum; + return -EINVAL; + } + + const ValueNode &yObject = tuningData["y"]; + if (!yObject.isDictionary()) { + LOG(RppX1Gsl, Error) + << "Invalid 'y': must be a dictionary"; + return -EINVAL; + } + + if (!parseYLut(yObject, "red", kResolution - 1, curveYr_) || + !parseYLut(yObject, "green", kResolution - 1, curveYg_) || + !parseYLut(yObject, "blue", kResolution - 1, curveYb_)) + return -EINVAL; + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void GammaSensorLinearization::prepare([[maybe_unused]] IPAContext &context, + const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + RppX1Params *params) +{ + if (frame > 0) + return; + + auto config = params->block(); + config.setEnabled(true); + + std::copy(gammaDx_.begin(), gammaDx_.end(), config->dx); + std::copy(curveYr_.begin(), curveYr_.end(), config->curve_r); + std::copy(curveYg_.begin(), curveYg_.end(), config->curve_g); + std::copy(curveYb_.begin(), curveYb_.end(), config->curve_b); +} + +REGISTER_IPA_ALGORITHM(GammaSensorLinearization, "GammaSensorLinearization") + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/gsl.h b/src/ipa/rppx1/algorithms/gsl.h new file mode 100644 index 0000000000..0c86fbf1ff --- /dev/null +++ b/src/ipa/rppx1/algorithms/gsl.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Gamma Sensor Linearization control + */ + +#pragma once + +#include + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +class GammaSensorLinearization : public Algorithm +{ +public: + int init(IPAContext &context, const ValueNode &tuningData) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) override; + +private: + std::vector gammaDx_; + std::array curveYr_; + std::array curveYg_; + std::array curveYb_; +}; + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build index 7394b55bca..0a73d26ed3 100644 --- a/src/ipa/rppx1/algorithms/meson.build +++ b/src/ipa/rppx1/algorithms/meson.build @@ -6,5 +6,6 @@ rppx1_ipa_algorithms = files([ 'blc.cpp', 'ccm.cpp', 'gamma.cpp', + 'gsl.cpp', 'lux.cpp', ]) diff --git a/src/ipa/rppx1/params.h b/src/ipa/rppx1/params.h index b724fa3162..87a70d960f 100644 --- a/src/ipa/rppx1/params.h +++ b/src/ipa/rppx1/params.h @@ -22,6 +22,8 @@ enum class BlockType : uint16_t { ExmPre1, GaHv, HistPost, + LinPre1, + LinPre2, WbMeasPost, }; @@ -45,6 +47,8 @@ RPPX1_DEFINE_BLOCK_TYPE(CcorPost, ccor, CCOR_POST) RPPX1_DEFINE_BLOCK_TYPE(ExmPre1, exm, EXM_PRE1) RPPX1_DEFINE_BLOCK_TYPE(GaHv, ga, GA_HV) RPPX1_DEFINE_BLOCK_TYPE(HistPost, hist, HIST_POST) +RPPX1_DEFINE_BLOCK_TYPE(LinPre1, lin, LIN_PRE1) +RPPX1_DEFINE_BLOCK_TYPE(LinPre2, lin, LIN_PRE2) RPPX1_DEFINE_BLOCK_TYPE(WbMeasPost, wbmeas, WBMEAS_POST) struct params_traits { From patchwork Fri Nov 13 16:40:53 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28907 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 08945C335F for ; Fri, 13 Nov 2026 16:41:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 66E8E6A851; Fri, 13 Nov 2026 17:41:28 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="RgdOCHFO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BC9A16A841 for ; Fri, 13 Nov 2026 17:41:09 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DEB2C33D; Fri, 13 Nov 2026 17:38:44 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587925; bh=jy8BY79vAKbvxRnNTsIUIM3vDRY4wJ9XgXpUbh0R69M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RgdOCHFO7Tj13cZm68X6quRCTitDaTpc/qEuqdNaoaGRasoHe2Uo31pv6FLo4zBcB 3iVcy/65Zo/UaVKBPit5O8JwrFoZpxj4ELmpNUYD95Jnppg4XS55CSTNnrVmVjOLl1 cl18XzGgN1lB04wz3DCIs6LCMc/tsjrq/wHvUxeI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v4 20/21] ipa: rppx1: lsc: Add Date: Fri, 13 Nov 2026 17:40:53 +0100 Message-ID: <20261113164054.267164-21-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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: Jacopo Mondi Add the algorithm to the rppx1 ipa module based on the corresponding algorithm in the rkisp1 ipa module. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze --- src/ipa/rppx1/algorithms/lsc.cpp | 275 +++++++++++++++++++++++++++ src/ipa/rppx1/algorithms/lsc.h | 58 ++++++ src/ipa/rppx1/algorithms/meson.build | 1 + src/ipa/rppx1/ipa_context.h | 5 + src/ipa/rppx1/params.h | 4 + 5 files changed, 343 insertions(+) create mode 100644 src/ipa/rppx1/algorithms/lsc.cpp create mode 100644 src/ipa/rppx1/algorithms/lsc.h diff --git a/src/ipa/rppx1/algorithms/lsc.cpp b/src/ipa/rppx1/algorithms/lsc.cpp new file mode 100644 index 0000000000..89ad7247d9 --- /dev/null +++ b/src/ipa/rppx1/algorithms/lsc.cpp @@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Lens Shading Correction control + */ + +#include "lsc.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "libcamera/internal/value_node.h" + +#include + +/** + * \file lsc.h + */ + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +LOG_DEFINE_CATEGORY(RppX1Lsc) + +namespace { + +using GradientQ = Q<12, 0>; + +constexpr int kColourTemperatureQuantization = 10; + +std::vector parseSizes(const ValueNode &tuningData, + const char *prop) +{ + std::vector sizes = + tuningData[prop].get>().value_or(utils::defopt); + if (sizes.size() != RPPX1_LSC_NUM_SECTORS) { + LOG(RppX1Lsc, Error) + << "Invalid '" << prop << "' values: expected " + << RPPX1_LSC_NUM_SECTORS + << " elements, got " << sizes.size(); + return {}; + } + + /* + * The sum of all elements must satisfy hardware constraints. + * Validate it here, allowing a 1% tolerance as rounding errors may + * prevent an exact match (further adjustments will be performed in + * LensShadingCorrection::prepare()). + */ + constexpr double expectedSum = 1.0; + + double sum = std::accumulate(sizes.begin(), sizes.end(), 0.0); + if (std::abs(sum - expectedSum) > 0.01) { + LOG(RppX1Lsc, Error) + << "Invalid '" << prop << "' values: sum of the elements" + << " should be " << expectedSum << ", got " << sum; + return {}; + } + + const auto &[min, max] = std::minmax_element(sizes.begin(), sizes.end()); + if (*min <= 0 || *max > 1) { + LOG(RppX1Lsc, Error) + << "Invalid '" << prop << "' values: elements must be in (0;1]"; + return {}; + } + + return sizes; +} + +std::vector sizesListToPositions(std::span sizes) +{ + std::vector positions(sizes.size() + 1); + + positions[0] = 0; + for (size_t i = 0; i < sizes.size(); i++) + positions[i + 1] = positions[i] + sizes[i]; + + return positions; +} + +unsigned int quantize(unsigned int value, unsigned int step) +{ + return std::lround(value / static_cast(step)) * step; +} + +} /* namespace */ + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, + const ValueNode &tuningData) +{ + xSize_ = parseSizes(tuningData, "x-size"); + ySize_ = parseSizes(tuningData, "y-size"); + + if (xSize_.empty() || ySize_.empty()) + return -EINVAL; + + xPos_ = sizesListToPositions(xSize_); + yPos_ = sizesListToPositions(ySize_); + + return lscAlgo_.init(tuningData, context.ctrlMap, { + .keys = { "r", "gr", "gb", "b" }, + .numHSamples = RPPX1_LSC_SAMPLES_MAX, + .numVSamples = RPPX1_LSC_SAMPLES_MAX, + .sensorSize = context.sensorInfo.activeArea.size(), + }); +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int LensShadingCorrection::configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) +{ + const Size &size = configInfo.outputSize; + Size totalSize{}; + + for (unsigned int i = 0; i < RPPX1_LSC_NUM_SECTORS; ++i) { + xSizes_[i] = xSize_[i] * size.width; + ySizes_[i] = ySize_[i] * size.height; + + /* + * > The sum of all 16 {x,y}-size values must exactly match + * > image {width,height}. + * + * Enforce it by computing the last table value separately to + * avoid rounding-induced errors. + */ + if (i == RPPX1_LSC_NUM_SECTORS - 1) { + xSizes_[i] = size.width - totalSize.width; + ySizes_[i] = size.height - totalSize.height; + } + + LOG(RppX1Lsc, Debug) + << i << ": " << xSizes_[i] << "=" << size.width << '*' << xSize_[i] << ", " + << ySizes_[i] << "=" << size.height << '*' << ySize_[i]; + + /* + * > The sector size in {x,y}-direction must be greater than 7. + */ + if (xSizes_[i] < 8 || ySizes_[i] < 8) + return -EINVAL; + if (xSizes_[i] > 1023 || ySizes_[i] > 1023) + return -EINVAL; + + totalSize.width += xSizes_[i]; + totalSize.height += ySizes_[i]; + + /* + * > The gradient values are related to the sector sizes and can be + * > calculated for both x and y direction using the following equation: + * > gradient[i] = int(2^15 / size[i] + 1/2) where i in [0;15] + */ + xGrad_[i] = GradientQ(float(1u << 15) / xSizes_[i]).quantized(); + yGrad_[i] = GradientQ(float(1u << 15) / ySizes_[i]).quantized(); + } + + lastAppliedCt_ = 0; + lastAppliedQuantizedCt_ = 0; + + return lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop, + xPos_, yPos_); +} + +void LensShadingCorrection::setParameters(rppx1_lsc_params &config) +{ + static_assert(sizeof(config.x_sect_size) == sizeof(xSizes_)); + memcpy(config.x_sect_size, xSizes_, sizeof(xSizes_)); + + static_assert(sizeof(config.y_sect_size) == sizeof(ySizes_)); + memcpy(config.y_sect_size, ySizes_, sizeof(ySizes_)); + + static_assert(sizeof(config.x_grad) == sizeof(xGrad_)); + memcpy(config.x_grad, xGrad_, sizeof(xGrad_)); + + static_assert(sizeof(config.y_grad) == sizeof(yGrad_)); + memcpy(config.y_grad, yGrad_, sizeof(yGrad_)); +} + +void LensShadingCorrection::copyTable(rppx1_lsc_params &config, + const lsc::Components &set) +{ + const auto &r = set.at("r"); + std::copy(r.begin(), r.end(), &config.r_data[0][0]); + const auto &gr = set.at("gr"); + std::copy(gr.begin(), gr.end(), &config.gr_data[0][0]); + const auto &gb = set.at("gb"); + std::copy(gb.begin(), gb.end(), &config.gb_data[0][0]); + const auto &b = set.at("b"); + std::copy(b.begin(), b.end(), &config.b_data[0][0]); +} + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void LensShadingCorrection::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc, + controls); +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) +{ + uint32_t ct = frameContext.awb.colourTemperature; + unsigned int quantizedCt = quantize(ct, kColourTemperatureQuantization); + + /* Check if we can skip the update. */ + if (!frameContext.lsc.update) { + if (!frameContext.lsc.enabled) + return; + + /* + * Add a threshold so that oscillations around a quantization + * step don't lead to constant changes. + */ + if (utils::abs_diff(ct, lastAppliedCt_) < kColourTemperatureQuantization / 2) + return; + + if (quantizedCt == lastAppliedQuantizedCt_) + return; + } + + auto config = params->block(); + config.setEnabled(frameContext.lsc.enabled); + + if (!frameContext.lsc.enabled) + return; + + setParameters(*config); + + copyTable(*config, lscAlgo_.interpolateComponents(quantizedCt)); + + lastAppliedCt_ = ct; + lastAppliedQuantizedCt_ = quantizedCt; + + LOG(RppX1Lsc, Debug) + << "ct is " << ct << ", quantized to " << quantizedCt; +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void LensShadingCorrection::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const RppX1Stats *stats, + ControlList &metadata) +{ + lscAlgo_.process(frameContext.lsc, metadata); +} + +REGISTER_IPA_ALGORITHM(LensShadingCorrection, "LensShadingCorrection") + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/lsc.h b/src/ipa/rppx1/algorithms/lsc.h new file mode 100644 index 0000000000..c731637bee --- /dev/null +++ b/src/ipa/rppx1/algorithms/lsc.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Lens Shading Correction control + */ + +#pragma once + +#include + +#include "libipa/fixedpoint.h" +#include "libipa/lsc.h" + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +class LensShadingCorrection : public Algorithm +{ +public: + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const RppX1Stats *stats, + ControlList &metadata) override; +private: + void setParameters(rppx1_lsc_params &config); + void copyTable(rppx1_lsc_params &config, + const lsc::Components &set); + + std::vector xSize_; + std::vector ySize_; + std::vector xPos_; + std::vector yPos_; + uint16_t xGrad_[RPPX1_LSC_NUM_SECTORS]; + uint16_t yGrad_[RPPX1_LSC_NUM_SECTORS]; + uint16_t xSizes_[RPPX1_LSC_NUM_SECTORS]; + uint16_t ySizes_[RPPX1_LSC_NUM_SECTORS]; + + unsigned int lastAppliedCt_ = 0; + unsigned int lastAppliedQuantizedCt_ = 0; + + LscAlgorithm> lscAlgo_; +}; + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build index 0a73d26ed3..5a1068cd7f 100644 --- a/src/ipa/rppx1/algorithms/meson.build +++ b/src/ipa/rppx1/algorithms/meson.build @@ -7,5 +7,6 @@ rppx1_ipa_algorithms = files([ 'ccm.cpp', 'gamma.cpp', 'gsl.cpp', + 'lsc.cpp', 'lux.cpp', ]) diff --git a/src/ipa/rppx1/ipa_context.h b/src/ipa/rppx1/ipa_context.h index 0d7b298f43..0be3cef0e4 100644 --- a/src/ipa/rppx1/ipa_context.h +++ b/src/ipa/rppx1/ipa_context.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace libcamera { @@ -49,6 +50,8 @@ struct IPAActiveState { ipa::gamma::ActiveState goc; + ipa::lsc::ActiveState lsc; + struct { double lux; } lux; @@ -71,6 +74,8 @@ struct IPAFrameContext : public FrameContext { ipa::gamma::FrameContext goc; + ipa::lsc::FrameContext lsc; + struct { double lux; } lux; diff --git a/src/ipa/rppx1/params.h b/src/ipa/rppx1/params.h index 87a70d960f..0f9de424fe 100644 --- a/src/ipa/rppx1/params.h +++ b/src/ipa/rppx1/params.h @@ -22,6 +22,8 @@ enum class BlockType : uint16_t { ExmPre1, GaHv, HistPost, + LscPre1, + LscPre2, LinPre1, LinPre2, WbMeasPost, @@ -47,6 +49,8 @@ RPPX1_DEFINE_BLOCK_TYPE(CcorPost, ccor, CCOR_POST) RPPX1_DEFINE_BLOCK_TYPE(ExmPre1, exm, EXM_PRE1) RPPX1_DEFINE_BLOCK_TYPE(GaHv, ga, GA_HV) RPPX1_DEFINE_BLOCK_TYPE(HistPost, hist, HIST_POST) +RPPX1_DEFINE_BLOCK_TYPE(LscPre1, lsc, LSC_PRE1) +RPPX1_DEFINE_BLOCK_TYPE(LscPre2, lsc, LSC_PRE2) RPPX1_DEFINE_BLOCK_TYPE(LinPre1, lin, LIN_PRE1) RPPX1_DEFINE_BLOCK_TYPE(LinPre2, lin, LIN_PRE2) RPPX1_DEFINE_BLOCK_TYPE(WbMeasPost, wbmeas, WBMEAS_POST) From patchwork Fri Nov 13 16:40:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28908 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 C24F6C335D for ; Fri, 13 Nov 2026 16:41:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0EAB36A857; Fri, 13 Nov 2026 17:41:29 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Mj/5MDvR"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7723F6A829 for ; Fri, 13 Nov 2026 17:41:10 +0100 (CET) Received: from pb-laptop.local (185.221.140.49.nat.pool.zt.hu [185.221.140.49]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4435D493 for ; Fri, 13 Nov 2026 17:38:45 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1794587925; bh=y0S0+cU5d89Cr/DkPtPThv5JewJ8Sgr5rBr3MhFeQr0=; h=From:To:Subject:Date:In-Reply-To:References:From; b=Mj/5MDvROftjCYHgIc3hZhpqqktQ9BBZdr9Bn/Efeei//OcAiD9vQO0SzooF3mWbX 1Lz6HTSTARh9r9xkXRh+gHXi6v44NEVMNFP93cw2Szkbgcfb4dIRuy68xuY9bEvdjc jz2ZqI4Pfd3x3sFEeqmToEOv4CFHwlzI5qLVKXcE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v4 21/21] ipa: rppx1: Add uncalibrated tuning file Date: Fri, 13 Nov 2026 17:40:54 +0100 Message-ID: <20261113164054.267164-22-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.1 In-Reply-To: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> References: <20261113164054.267164-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 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" Enable algorithms that don't need any specific tuning data. Signed-off-by: Barnabás Pőcze Signed-off-by: Barnabás Pőcze --- src/ipa/rppx1/data/meson.build | 1 + src/ipa/rppx1/data/uncalibrated.yaml | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 src/ipa/rppx1/data/uncalibrated.yaml diff --git a/src/ipa/rppx1/data/meson.build b/src/ipa/rppx1/data/meson.build index d1eb321758..b18511441b 100644 --- a/src/ipa/rppx1/data/meson.build +++ b/src/ipa/rppx1/data/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 conf_files = files([ + 'uncalibrated.yaml', ]) install_data(conf_files, diff --git a/src/ipa/rppx1/data/uncalibrated.yaml b/src/ipa/rppx1/data/uncalibrated.yaml new file mode 100644 index 0000000000..9d6319a941 --- /dev/null +++ b/src/ipa/rppx1/data/uncalibrated.yaml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: CC0-1.0 +%YAML 1.1 +--- +version: 1 +algorithms: + - Agc: + - Awb: + - BlackLevelCorrection: + - Ccm: + - Gamma: +...