From patchwork Thu Jul 23 15:42: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: 27450 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 AE40FBE080 for ; Thu, 23 Jul 2026 15:43:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 316C167ECD; Thu, 23 Jul 2026 17:43:32 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="DYYjqw3R"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4C9C667EB5 for ; Thu, 23 Jul 2026 17:43:30 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7914A1F0F for ; Thu, 23 Jul 2026 17:42:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821349; bh=ObQlGH0hFgSXsqiAGmaWahgAGHl1A8VikaSfWuJ2N9g=; h=From:To:Subject:Date:In-Reply-To:References:From; b=DYYjqw3RLAUrhfHFGO73ozhbCnoKm5ohi3wrweVd0NDTw31ztd0bQNEXs747cGsTC Q6blADLa6NsVdLcWHgVbu3geDLHV5pg9wXmnK3Bwn9YwWEgxhCMhkASyIYyqS+99UQ ODZCDkP9IRpABHUSuNc4Heavx+VAY28gC0luvLsM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 01/43] libcamera: software_isp: init(): Fix documentation typo Date: Thu, 23 Jul 2026 17:42:44 +0200 Message-ID: <20260723154327.1357866-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Don't break the parameter description. Signed-off-by: Barnabás Pőcze --- src/libcamera/software_isp/software_isp.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp index c73a16ce0a..f6b345f6a5 100644 --- a/src/libcamera/software_isp/software_isp.cpp +++ b/src/libcamera/software_isp/software_isp.cpp @@ -75,9 +75,8 @@ LOG_DEFINE_CATEGORY(SoftwareIsp) /** * \brief Constructs SoftwareIsp object * \param[in] pipe The pipeline handler in use - * \param[in] sensor Pointer to the CameraSensor instance owned by the pipeline + * \param[in] sensor Pointer to the CameraSensor instance owned by the pipeline handler * \param[out] ipaControls The IPA controls to update - * handler */ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, ControlInfoMap *ipaControls) From patchwork Thu Jul 23 15:42: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: 27451 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 A8866C3264 for ; Thu, 23 Jul 2026 15:43:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0F31367EDC; Thu, 23 Jul 2026 17:43:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fYv5tM+w"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 87E9A67EBA for ; Thu, 23 Jul 2026 17:43:30 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AF5E91C37 for ; Thu, 23 Jul 2026 17:42:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821349; bh=OFb998A7bxhsobwjqDQbbNj/DCE4bODj2t52c6x4e3E=; h=From:To:Subject:Date:In-Reply-To:References:From; b=fYv5tM+wBWBlnCbYrwRBLnIeRX4aBKVL7XCgbYCms5uz3URiRM6anPkocYr7Toy2R NFQNfryUXHEOK7SPj5JluUm9ByaJRlUdnCNYnRxhYnk/WhGtSadx2F+uuY3YRU1rMD YeNpLJJfN9kdR9pv6kIZpaaOkTpuOexxg45ufk0c= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 02/43] ipa: simple: Remove control presence sanity check Date: Thu, 23 Jul 2026 17:42:45 +0200 Message-ID: <20260723154327.1357866-3-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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 check is useless because `DelayedControls::get()` always populates the result even if the value is "none", so the presence check will always succeed. And furthermore the check also has the issue of being later than the actual access. Link: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/241 Signed-off-by: Barnabás Pőcze --- src/ipa/simple/soft_simple.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 629e1a32de..e99df1986f 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -311,13 +311,6 @@ void IPASoftSimple::processStats(const uint32_t frame, algo->process(context_, frame, frameContext, stats_, metadata); metadataReady.emit(frame, metadata); - /* Sanity check */ - if (!sensorControls.contains(V4L2_CID_EXPOSURE) || - !sensorControls.contains(V4L2_CID_ANALOGUE_GAIN)) { - LOG(IPASoft, Error) << "Control(s) missing"; - return; - } - ControlList ctrls(sensorInfoMap_); auto &againNew = frameContext.sensor.gain; From patchwork Thu Jul 23 15:42: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: 27452 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 093FCBDE17 for ; Thu, 23 Jul 2026 15:43:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1CDF367EE7; Thu, 23 Jul 2026 17:43:36 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="MfRpH4So"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CAB5967E5C for ; Thu, 23 Jul 2026 17:43:30 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E3D7E1F0F; Thu, 23 Jul 2026 17:42:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821350; bh=fjFdr4Dfn+/6Qm2526uGOnN0uAyxQaxJ2HdLHd4Omj4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=MfRpH4SolamwM20KvAry7XyMR00lhspWkPWAkbJoyUf3M0U/oVw4EbV0xzONY7gCy n7d8edrHvPpnvUzJ2SK280ftgXI3QdPWfsx5zdsyNQZqX9Gjy06VNmEXEqrNJEcZCs s8rZdkAh/Vvcuaif7xwro/7qEeuuMKXY+gA6pa5k= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v2 03/43] ipa: simple: agc: Do not overwrite sensor exposure/gain Date: Thu, 23 Jul 2026 17:42:46 +0200 Message-ID: <20260723154327.1357866-4-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" In the agc algorithm do not overwrite the sensor exposure/gain values, instead add a new "agc" member to the frame context and update that. No functional changes are intended as the agc algorithm runs last. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/simple/algorithms/agc.cpp | 11 +++++++---- src/ipa/simple/ipa_context.h | 5 +++++ src/ipa/simple/soft_simple.cpp | 9 +++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index a13a755280..e9bcb2c032 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -67,8 +67,8 @@ Agc::Agc() void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV) { - int32_t &exposure = frameContext.sensor.exposure; - double &again = frameContext.sensor.gain; + int32_t exposure = frameContext.sensor.exposure; + double again = frameContext.sensor.gain; double error = kExposureOptimal - exposureMSV; @@ -115,6 +115,9 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou again = std::clamp(again, context.configuration.agc.againMin, context.configuration.agc.againMax); + frameContext.agc.exposure = exposure; + frameContext.agc.gain = again; + context.activeState.agc.exposure = exposure; context.activeState.agc.again = again; @@ -150,8 +153,8 @@ void Agc::process(IPAContext &context, * Use the new exposure and gain values calculated the last time * there were valid stats. */ - frameContext.sensor.exposure = context.activeState.agc.exposure; - frameContext.sensor.gain = context.activeState.agc.again; + frameContext.agc.exposure = context.activeState.agc.exposure; + frameContext.agc.gain = context.activeState.agc.again; return; } diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 8ccfacb46a..d35fb1e91d 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -66,6 +66,11 @@ struct IPAActiveState { struct IPAFrameContext : public FrameContext { Matrix ccm; + struct { + int32_t exposure; + double gain; + } agc; + struct { int32_t exposure; double gain; diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index e99df1986f..3932daaa1f 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -313,10 +313,11 @@ void IPASoftSimple::processStats(const uint32_t frame, ControlList ctrls(sensorInfoMap_); - auto &againNew = frameContext.sensor.gain; - ctrls.set(V4L2_CID_EXPOSURE, frameContext.sensor.exposure); - ctrls.set(V4L2_CID_ANALOGUE_GAIN, - static_cast(camHelper_ ? camHelper_->gainCode(againNew) : againNew)); + int32_t againNew = camHelper_ + ? camHelper_->gainCode(frameContext.agc.gain) + : static_cast(frameContext.agc.gain); + ctrls.set(V4L2_CID_EXPOSURE, frameContext.agc.exposure); + ctrls.set(V4L2_CID_ANALOGUE_GAIN, againNew); setSensorControls.emit(ctrls); } From patchwork Thu Jul 23 15:42: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: 27453 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 A12B6BE080 for ; Thu, 23 Jul 2026 15:43:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1C50A67EEB; Thu, 23 Jul 2026 17:43:38 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="qVgxpy4e"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2071267EC2 for ; Thu, 23 Jul 2026 17:43:31 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 38FCD1C37; Thu, 23 Jul 2026 17:42:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821350; bh=3lbD0y7TasQbBIsv1vAEgxYhoxV5nBo9rr2AsWiEjaQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qVgxpy4e10m1T3ZVqDB91YiYKIvoNqQYdg+4mTn19gJ+7gu7bq+PSQG+R/5aaZPc7 TFPVraDrrWo8irigKKt591X7wnd+sgOB5uoCR12bwsk8Aka7Zo1i9OuK41Q1uTLRWq vkYMJFIq64RCNFwIPYthl1wUlh2+wMa47WpZs0+o= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v2 04/43] ipa: rkisp1: Remove `IPASessionConfiguration::sensor.defVBlank` Date: Thu, 23 Jul 2026 17:42:47 +0200 Message-ID: <20260723154327.1357866-5-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" It is not used, so remove it. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/rkisp1/ipa_context.cpp | 3 --- src/ipa/rkisp1/ipa_context.h | 1 - src/ipa/rkisp1/rkisp1.cpp | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp index f9d0aff058..302b4cfd70 100644 --- a/src/ipa/rkisp1/ipa_context.cpp +++ b/src/ipa/rkisp1/ipa_context.cpp @@ -98,9 +98,6 @@ namespace libcamera::ipa::rkisp1 { * \var IPASessionConfiguration::sensor.maxAnalogueGain * \brief Maximum analogue gain supported with the sensor * - * \var IPASessionConfiguration::sensor.defVBlank - * \brief The default vblank value of the sensor - * * \var IPASessionConfiguration::sensor.lineDuration * \brief Line duration in microseconds * diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index e5f70d934d..9a4e0f12c6 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -68,7 +68,6 @@ struct IPASessionConfiguration { double minAnalogueGain; double maxAnalogueGain; - int32_t defVBlank; utils::Duration lineDuration; Size size; } sensor; diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 58ef163d85..273540a330 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -249,8 +249,6 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.paramFormat = ipaConfig.paramFormat; const IPACameraSensorInfo &info = ipaConfig.sensorInfo; - const ControlInfo vBlank = sensorControls_.find(V4L2_CID_VBLANK)->second; - context_.configuration.sensor.defVBlank = vBlank.def().get(); context_.configuration.sensor.size = info.outputSize; context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; From patchwork Thu Jul 23 15:42: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: 27454 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 BA1F0BDE17 for ; Thu, 23 Jul 2026 15:43:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 36FFC67ED6; Thu, 23 Jul 2026 17:43:41 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Dcmd4bc2"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7120567EBA for ; Thu, 23 Jul 2026 17:43:31 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 81002210B; Thu, 23 Jul 2026 17:42:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821350; bh=grvjisblXbP5mhTdc+mlAH/ghPuiGi3Rxoyi2oDcTbw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Dcmd4bc2fcYV8oLFz7wkJ5rrLuIKhYWLUkn4/4/QJ2li5Jp50g5ccuHfZSmUtxade YjDNHe4nEvxmcNqWUHr7X6UQtunbLOOjApMTNO0QgGeq7KtGJzwAmTOr4DCL4YCx9B cb9v01a/Bb34ANxH6dn792ac2uzPNdlgwCVKSOH8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v2 05/43] ipa: libipa: agc_mean_luminance: Remove the need for inheritance Date: Thu, 23 Jul 2026 17:42:48 +0200 Message-ID: <20260723154327.1357866-6-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Create a separate type with the necessary callbacks instead of using inheritance. This removes the need for users to have to use member variables to store temporary data that is only needed for luminance estimation. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/ipu3/algorithms/agc.cpp | 109 +++++++++++------- src/ipa/ipu3/algorithms/agc.h | 8 +- src/ipa/libipa/agc_mean_luminance.cpp | 52 +++++---- src/ipa/libipa/agc_mean_luminance.h | 14 ++- src/ipa/mali-c55/algorithms/agc.cpp | 56 ++++++---- src/ipa/mali-c55/algorithms/agc.h | 4 +- src/ipa/rkisp1/algorithms/agc.cpp | 154 ++++++++++++++------------ src/ipa/rkisp1/algorithms/agc.h | 7 +- 8 files changed, 235 insertions(+), 169 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index d6a7036c65..a919418d69 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -76,11 +76,11 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = parseTuningData(tuningData); + ret = agc_.parseTuningData(tuningData); if (ret) return ret; - context.ctrlMap.merge(controls()); + context.ctrlMap.merge(agc_.controls()); return 0; } @@ -112,13 +112,13 @@ int Agc::configure(IPAContext &context, activeState.agc.gain = minAnalogueGain_; activeState.agc.exposure = 10ms / configuration.sensor.lineDuration; - context.activeState.agc.constraintMode = constraintModes().begin()->first; - context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first; + context.activeState.agc.constraintMode = agc_.constraintModes().begin()->first; + context.activeState.agc.exposureMode = agc_.exposureModeHelpers().begin()->first; /* \todo Run this again when FrameDurationLimits is passed in */ - setLimits(minExposureTime_, maxExposureTime_, minAnalogueGain_, - maxAnalogueGain_, {}); - resetFrameCount(); + agc_.setLimits(minExposureTime_, maxExposureTime_, minAnalogueGain_, + maxAnalogueGain_, {}); + agc_.resetFrameCount(); return 0; } @@ -156,39 +156,58 @@ Histogram Agc::parseStatistics(const ipu3_uapi_stats_3a *stats, return Histogram(Span(hist)); } -/** - * \brief Estimate the relative luminance of the frame with a given gain - * \param[in] gain The gain to apply in estimating luminance - * - * The estimation is based on the AWB statistics for the current frame. Red, - * green and blue 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 relative luminance (Y) is computed from the linear RGB components using - * the Rec. 601 formula. 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 of the frame - */ -double Agc::estimateLuminance(double gain) const +namespace { + +class AgcTraits final : public AgcMeanLuminance::Traits { - RGB sum{ 0.0 }; +public: + AgcTraits(Span> rgbTriples, + RGB gains, const ipu3_uapi_grid_config &bdsGrid) + : rgbTriples_(rgbTriples), gains_(gains), bdsGrid_(bdsGrid) + { + } + + /** + * \brief Estimate the relative luminance of the frame with a given gain + * \param[in] gain The gain to apply in estimating luminance + * + * The estimation is based on the AWB statistics for the current frame. Red, + * green and blue 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 relative luminance (Y) is computed from the linear RGB components using + * the Rec. 601 formula. 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 of the frame + */ + + double estimateLuminance(double gain) const override + { + RGB sum{ 0.0 }; - for (unsigned int i = 0; i < rgbTriples_.size(); i++) { - sum.r() += std::min(std::get<0>(rgbTriples_[i]) * gain, 255.0); - sum.g() += std::min(std::get<1>(rgbTriples_[i]) * gain, 255.0); - sum.b() += std::min(std::get<2>(rgbTriples_[i]) * gain, 255.0); + for (unsigned int i = 0; i < rgbTriples_.size(); i++) { + sum.r() += std::min(std::get<0>(rgbTriples_[i]) * gain, 255.0); + sum.g() += std::min(std::get<1>(rgbTriples_[i]) * gain, 255.0); + sum.b() += std::min(std::get<2>(rgbTriples_[i]) * gain, 255.0); + } + + double ySum = rec601LuminanceFromRGB(sum * gains_); + return ySum / (bdsGrid_.height * bdsGrid_.width) / 255; } - RGB gains{{ rGain_, gGain_, bGain_ }}; - double ySum = rec601LuminanceFromRGB(sum * gains); - return ySum / (bdsGrid_.height * bdsGrid_.width) / 255; +private: + Span> rgbTriples_; + RGB gains_; + const ipu3_uapi_grid_config &bdsGrid_; +}; + } /** @@ -208,9 +227,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, ControlList &metadata) { Histogram hist = parseStatistics(stats, context.configuration.grid.bdsGrid); - rGain_ = context.activeState.awb.gains.red; - gGain_ = context.activeState.awb.gains.blue; - bGain_ = context.activeState.awb.gains.green; /* * The Agc algorithm needs to know the effective exposure value that was @@ -220,13 +236,22 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, * frameContext.sensor.exposure; double analogueGain = frameContext.sensor.gain; utils::Duration effectiveExposureValue = exposureTime * analogueGain; + AgcTraits agcTraits{ + rgbTriples_, + {{ + context.activeState.awb.gains.red, + context.activeState.awb.gains.blue, + context.activeState.awb.gains.green, + }}, + bdsGrid_, + }; utils::Duration newExposureTime; double aGain, qGain, dGain; std::tie(newExposureTime, aGain, qGain, dGain) = - calculateNewEv(context.activeState.agc.constraintMode, - context.activeState.agc.exposureMode, hist, - effectiveExposureValue); + agc_.calculateNewEv(context.activeState.agc.constraintMode, + context.activeState.agc.exposureMode, hist, + effectiveExposureValue, agcTraits); LOG(IPU3Agc, Debug) << "Divided up exposure time, analogue gain and digital gain are " diff --git a/src/ipa/ipu3/algorithms/agc.h b/src/ipa/ipu3/algorithms/agc.h index d274a23504..d08da7600e 100644 --- a/src/ipa/ipu3/algorithms/agc.h +++ b/src/ipa/ipu3/algorithms/agc.h @@ -24,7 +24,7 @@ struct IPACameraSensorInfo; namespace ipa::ipu3::algorithms { -class Agc : public Algorithm, public AgcMeanLuminance +class Agc : public Algorithm { public: Agc(); @@ -38,7 +38,6 @@ public: ControlList &metadata) override; private: - double estimateLuminance(double gain) const override; Histogram parseStatistics(const ipu3_uapi_stats_3a *stats, const ipu3_uapi_grid_config &grid); @@ -49,11 +48,10 @@ private: double maxAnalogueGain_; uint32_t stride_; - double rGain_; - double gGain_; - double bGain_; ipu3_uapi_grid_config bdsGrid_; std::vector> rgbTriples_; + + AgcMeanLuminance agc_; }; } /* namespace ipa::ipu3::algorithms */ diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 2d3bc709f3..40e4c03100 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -21,7 +21,7 @@ using namespace libcamera::controls; /** * \file agc_mean_luminance.h - * \brief Base class implementing mean luminance AEGC + * \brief Class implementing mean luminance AEGC */ namespace libcamera { @@ -105,6 +105,30 @@ static constexpr unsigned int kDefaultLuxLevel = 500; * \brief The luminance target for the constraint */ +/** + * \class AgcMeanLuminance::Traits + * \brief A collection of callbacks + * + * This type contains virtual methods that provide the necessary pieces of + * information for the algorithm, and are to be implemented by the user. + */ + +/** + * \fn AgcMeanLuminance::Traits::estimateLuminance(double gain) + * \brief Estimate the luminance of an image, adjusted by a given gain + * \param[in] gain The gain with which to adjust the luminance estimate + * + * This function estimates the average relative luminance of the frame that + * would be output by the sensor if an additional \a gain was applied. It is a + * pure virtual function because estimation of luminance is a hardware-specific + * operation, which depends wholly on the format of the stats that are delivered + * to libcamera from the ISP. Derived classes must override this function with + * one that calculates the normalised mean luminance value across the entire + * image. + * + * \return The normalised relative luminance of the image + */ + /** * \class AgcMeanLuminance * \brief A mean-based auto-exposure algorithm @@ -487,28 +511,12 @@ void AgcMeanLuminance::setLimits(utils::Duration minExposureTime, * \brief Get the controls that have been generated after parsing tuning data */ -/** - * \fn AgcMeanLuminance::estimateLuminance(const double gain) - * \brief Estimate the luminance of an image, adjusted by a given gain - * \param[in] gain The gain with which to adjust the luminance estimate - * - * This function estimates the average relative luminance of the frame that - * would be output by the sensor if an additional \a gain was applied. It is a - * pure virtual function because estimation of luminance is a hardware-specific - * operation, which depends wholly on the format of the stats that are delivered - * to libcamera from the ISP. Derived classes must override this function with - * one that calculates the normalised mean luminance value across the entire - * image. - * - * \return The normalised relative luminance of the image - */ - /** * \brief Estimate the initial gain needed to achieve a relative luminance * target * \return The calculated initial gain */ -double AgcMeanLuminance::estimateInitialGain() const +double AgcMeanLuminance::estimateInitialGain(const Traits &traits) const { double yTarget = effectiveYTarget(); double yGain = 1.0; @@ -520,7 +528,7 @@ double AgcMeanLuminance::estimateInitialGain() const * regions are saturated. */ for (unsigned int i = 0; i < 8; i++) { - double yValue = estimateLuminance(yGain); + double yValue = traits.estimateLuminance(yGain); double extra_gain = std::min(10.0, yTarget / (yValue + .001)); yGain *= extra_gain; @@ -663,6 +671,7 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) * the calculated gain * \param[in] effectiveExposureValue The EV applied to the frame from which the * statistics in use derive + * \param[in] traits The traits object implementing the necessary functions * * Calculate a new exposure value to try to obtain the target. The calculated * exposure value is filtered to prevent rapid changes from frame to frame, and @@ -675,7 +684,8 @@ std::tuple AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex, const Histogram &yHist, - utils::Duration effectiveExposureValue) + utils::Duration effectiveExposureValue, + const Traits &traits) { /* * The pipeline handler should validate that we have received an allowed @@ -696,7 +706,7 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, return exposureModeHelper->splitExposure(10ms); } - double gain = estimateInitialGain(); + double gain = estimateInitialGain(traits); gain = constraintClampGain(constraintModeIndex, yHist, gain); /* diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index 27e92b6fce..f4e1680ab5 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -30,7 +30,7 @@ class AgcMeanLuminance { public: AgcMeanLuminance(); - virtual ~AgcMeanLuminance(); + ~AgcMeanLuminance(); struct AgcConstraint { enum class Bound { @@ -43,6 +43,11 @@ public: Pwl yTarget; }; + struct Traits { + virtual ~Traits() = default; + virtual double estimateLuminance(double gain) const = 0; + }; + void configure(utils::Duration lineDuration, const CameraSensorHelper *sensorHelper); int parseTuningData(const ValueNode &tuningData); @@ -76,7 +81,8 @@ public: std::tuple calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex, - const Histogram &yHist, utils::Duration effectiveExposureValue); + const Histogram &yHist, utils::Duration effectiveExposureValue, + const Traits &traits); double effectiveYTarget() const; @@ -86,13 +92,11 @@ public: } private: - virtual double estimateLuminance(const double gain) const = 0; - int parseRelativeLuminanceTarget(const ValueNode &tuningData); int parseConstraint(const ValueNode &modeDict, int32_t id); int parseConstraintModes(const ValueNode &tuningData); int parseExposureModes(const ValueNode &tuningData); - double estimateInitialGain() const; + double estimateInitialGain(const Traits &traits) const; double constraintClampGain(uint32_t constraintModeIndex, const Histogram &hist, double gain); diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 83bbf69385..91a6156aaf 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -127,20 +127,19 @@ void AgcStatistics::parseStatistics(const mali_c55_stats_buffer *stats) } Agc::Agc() - : AgcMeanLuminance() { } int Agc::init(IPAContext &context, const ValueNode &tuningData) { - int ret = parseTuningData(tuningData); + int ret = agc_.parseTuningData(tuningData); if (ret) return ret; context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); context.ctrlMap[&controls::DigitalGain] = ControlInfo( kMinDigitalGain, kMaxDigitalGain, kMinDigitalGain); - context.ctrlMap.merge(controls()); + context.ctrlMap.merge(agc_.controls()); return 0; } @@ -163,17 +162,17 @@ int Agc::configure(IPAContext &context, context.activeState.agc.manual.sensorGain = context.configuration.agc.minAnalogueGain; context.activeState.agc.manual.exposure = context.configuration.agc.defaultExposure; context.activeState.agc.manual.ispGain = kMinDigitalGain; - context.activeState.agc.constraintMode = constraintModes().begin()->first; - context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first; + context.activeState.agc.constraintMode = agc_.constraintModes().begin()->first; + context.activeState.agc.exposureMode = agc_.exposureModeHelpers().begin()->first; /* \todo Run this again when FrameDurationLimits is passed in */ - setLimits(context.configuration.agc.minShutterSpeed, - context.configuration.agc.maxShutterSpeed, - context.configuration.agc.minAnalogueGain, - context.configuration.agc.maxAnalogueGain, - {}); + agc_.setLimits(context.configuration.agc.minShutterSpeed, + context.configuration.agc.maxShutterSpeed, + context.configuration.agc.minAnalogueGain, + context.configuration.agc.maxAnalogueGain, + {}); - resetFrameCount(); + agc_.resetFrameCount(); return 0; } @@ -320,14 +319,30 @@ void Agc::prepare(IPAContext &context, const uint32_t frame, fillWeightsArrayBuffer(params, MaliC55Blocks::AexpIhistWeights); } -double Agc::estimateLuminance(const double gain) const +namespace { + +class AgcTraits final : public AgcMeanLuminance::Traits { - double rAvg = statistics_.rHist.interQuantileMean(0, 1) * gain; - double gAvg = statistics_.gHist.interQuantileMean(0, 1) * gain; - double bAvg = statistics_.bHist.interQuantileMean(0, 1) * gain; - double yAvg = rec601LuminanceFromRGB({ { rAvg, gAvg, bAvg } }); +public: + AgcTraits(const AgcStatistics &statistics) + : statistics_(statistics) + { + } + + double estimateLuminance(double gain) const override + { + double rAvg = statistics_.rHist.interQuantileMean(0, 1) * gain; + double gAvg = statistics_.gHist.interQuantileMean(0, 1) * gain; + double bAvg = statistics_.bHist.interQuantileMean(0, 1) * gain; + double yAvg = rec601LuminanceFromRGB({ { rAvg, gAvg, bAvg } }); + + return yAvg / kNumHistogramBins; + } + +private: + const AgcStatistics &statistics_; +}; - return yAvg / kNumHistogramBins; } void Agc::process(IPAContext &context, @@ -359,13 +374,14 @@ void Agc::process(IPAContext &context, double totalGain = analogueGain * digitalGain; utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; utils::Duration effectiveExposureValue = currentShutter * totalGain; + AgcTraits agcTraits(statistics_); utils::Duration shutterTime; double aGain, qGain, dGain; std::tie(shutterTime, aGain, qGain, dGain) = - calculateNewEv(activeState.agc.constraintMode, - activeState.agc.exposureMode, statistics_.yHist, - effectiveExposureValue); + agc_.calculateNewEv(activeState.agc.constraintMode, + activeState.agc.exposureMode, statistics_.yHist, + effectiveExposureValue, agcTraits); UQ<5, 8> dGainQ = std::clamp(static_cast(dGain), kMinDigitalGain, diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h index ee913de2b2..e36378a2ac 100644 --- a/src/ipa/mali-c55/algorithms/agc.h +++ b/src/ipa/mali-c55/algorithms/agc.h @@ -43,7 +43,7 @@ private: unsigned int bIndex_; }; -class Agc : public Algorithm, public AgcMeanLuminance +class Agc : public Algorithm { public: Agc(); @@ -64,7 +64,6 @@ public: ControlList &metadata) override; private: - double estimateLuminance(const double gain) const override; void fillGainParamBlock(IPAContext &context, IPAFrameContext &frameContext, MaliC55Params *params); @@ -72,6 +71,7 @@ private: void fillWeightsArrayBuffer(MaliC55Params *params, enum MaliC55Blocks type); AgcStatistics statistics_; + AgcMeanLuminance agc_; }; } /* namespace ipa::mali_c55::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index aace46a8d0..540b64ff99 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -138,7 +138,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = parseTuningData(tuningData); + ret = agc_.parseTuningData(tuningData); if (ret) return ret; @@ -158,7 +158,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) /* \todo Move this to the Camera class */ context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true); context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); - context.ctrlMap.merge(controls()); + context.ctrlMap.merge(agc_.controls()); return 0; } @@ -184,9 +184,9 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) context.activeState.agc.exposureValue = 0.0; context.activeState.agc.constraintMode = - static_cast(constraintModes().begin()->first); + static_cast(agc_.constraintModes().begin()->first); context.activeState.agc.exposureMode = - static_cast(exposureModeHelpers().begin()->first); + static_cast(agc_.exposureModeHelpers().begin()->first); context.activeState.agc.meteringMode = static_cast(meteringModes_.begin()->first); @@ -200,17 +200,16 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) context.configuration.agc.measureWindow.h_size = configInfo.outputSize.width; context.configuration.agc.measureWindow.v_size = configInfo.outputSize.height; - AgcMeanLuminance::configure(context.configuration.sensor.lineDuration, - context.camHelper.get()); + agc_.configure(context.configuration.sensor.lineDuration, context.camHelper.get()); - setLimits(context.configuration.sensor.minExposureTime, - context.configuration.sensor.maxExposureTime, - context.configuration.sensor.minAnalogueGain, - context.configuration.sensor.maxAnalogueGain, {}); + agc_.setLimits(context.configuration.sensor.minExposureTime, + context.configuration.sensor.maxExposureTime, + context.configuration.sensor.minAnalogueGain, + context.configuration.sensor.maxAnalogueGain, {}); - context.activeState.agc.automatic.yTarget = effectiveYTarget(); + context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(); - resetFrameCount(); + agc_.resetFrameCount(); return 0; } @@ -457,47 +456,6 @@ void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, metadata.set(controls::ExposureValue, frameContext.agc.exposureValue); } -/** - * \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. In this case, saturating after the conversion to - * YUV doesn't take into account the fact that the R, G and B components - * contribute differently to the relative luminance. - * - * 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 Agc::estimateLuminance(double gain) const -{ - ASSERT(expMeans_.size() == weights_.size()); - double ySum = 0.0; - double wSum = 0.0; - - /* Sum the averages, saturated to 255. */ - for (unsigned i = 0; i < expMeans_.size(); i++) { - double w = weights_[i]; - ySum += std::min(expMeans_[i] * gain, 255.0) * w; - wSum += w; - } - - /* \todo Weight with the AWB gains */ - - return ySum / wSum / 255; -} - /** * \brief Process frame duration and compute vblank * \param[in] context The shared IPA context @@ -519,6 +477,64 @@ void Agc::processFrameDuration(IPAContext &context, frameContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration; } +namespace { + +class AgcTraits final : public AgcMeanLuminance::Traits +{ +public: + AgcTraits(Span expMeans, Span weights) + : expMeans_(expMeans), weights_(weights) + { + } + + /** + * \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. In this case, saturating after the conversion to + * YUV doesn't take into account the fact that the R, G and B components + * contribute differently to the relative luminance. + * + * 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 + { + ASSERT(expMeans_.size() == weights_.size()); + double ySum = 0.0; + double wSum = 0.0; + + /* Sum the averages, saturated to 255. */ + for (unsigned i = 0; i < expMeans_.size(); i++) { + double w = weights_[i]; + ySum += std::min(expMeans_[i] * gain, 255.0) * w; + wSum += w; + } + + /* \todo Weight with the AWB gains */ + + return ySum / wSum / 255; + } + +private: + Span expMeans_; + Span weights_; +}; + +} + /** * \brief Process RkISP1 statistics, and run AGC operations * \param[in] context The shared IPA context @@ -559,13 +575,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, const rkisp1_cif_isp_stat *params = &stats->params; - /* The lower 4 bits are fractional and meant to be discarded. */ - Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, - [](uint32_t x) { return x >> 4; }); - expMeans_ = { params->ae.exp_mean, context.hw.numAeCells }; - std::vector &modeWeights = meteringModes_.at(frameContext.agc.meteringMode); - weights_ = { modeWeights.data(), modeWeights.size() }; - /* * Set the AGC limits using the fixed exposure time and/or gain in * manual mode, or the sensor limits in auto mode. @@ -598,8 +607,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, if (context.activeState.wdr.mode != controls::WdrOff) additionalConstraints.push_back(context.activeState.wdr.constraint); - setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain, - std::move(additionalConstraints)); + agc_.setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain, + std::move(additionalConstraints)); /* * The Agc algorithm needs to know the effective exposure value that was @@ -617,15 +626,23 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, if (frameContext.compress.enable) effectiveExposureValue *= frameContext.agc.quantizationGain; - setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); - setLux(frameContext.lux.lux); + /* The lower 4 bits are fractional and meant to be discarded. */ + Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, + [](uint32_t x) { return x >> 4; }); + AgcTraits agcTraits{ + { params->ae.exp_mean, context.hw.numAeCells }, + meteringModes_.at(frameContext.agc.meteringMode), + }; + + agc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); + agc_.setLux(frameContext.lux.lux); utils::Duration newExposureTime; double aGain, qGain, dGain; std::tie(newExposureTime, aGain, qGain, dGain) = - calculateNewEv(frameContext.agc.constraintMode, - frameContext.agc.exposureMode, - hist, effectiveExposureValue); + agc_.calculateNewEv(frameContext.agc.constraintMode, + frameContext.agc.exposureMode, + hist, effectiveExposureValue, agcTraits); LOG(RkISP1Agc, Debug) << "Divided up exposure time, analogue gain, quantization gain" @@ -637,7 +654,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, activeState.agc.automatic.exposure = newExposureTime / lineDuration; activeState.agc.automatic.gain = aGain; activeState.agc.automatic.quantizationGain = qGain; - activeState.agc.automatic.yTarget = effectiveYTarget(); + activeState.agc.automatic.yTarget = agc_.effectiveYTarget(); /* * Expand the target frame duration so that we do not run faster than * the minimum frame duration when we have short exposures. @@ -646,7 +663,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, std::max(frameContext.agc.minFrameDuration, newExposureTime)); fillMetadata(context, frameContext, metadata); - expMeans_ = {}; } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h index dec79f2f39..0527ca0d5f 100644 --- a/src/ipa/rkisp1/algorithms/agc.h +++ b/src/ipa/rkisp1/algorithms/agc.h @@ -22,7 +22,7 @@ namespace libcamera { namespace ipa::rkisp1::algorithms { -class Agc : public Algorithm, public AgcMeanLuminance +class Agc : public Algorithm { public: Agc(); @@ -49,15 +49,12 @@ private: void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, ControlList &metadata); - double estimateLuminance(double gain) const override; void processFrameDuration(IPAContext &context, IPAFrameContext &frameContext, utils::Duration frameDuration); - Span expMeans_; - Span weights_; - std::map> meteringModes_; + AgcMeanLuminance agc_; }; } /* namespace ipa::rkisp1::algorithms */ From patchwork Thu Jul 23 15:42: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: 27455 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 14627C3264 for ; Thu, 23 Jul 2026 15:43:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8E2C567ED1; Thu, 23 Jul 2026 17:43:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="prpEvP5F"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B6D3E67ECA for ; Thu, 23 Jul 2026 17:43:31 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D0B9323B9; Thu, 23 Jul 2026 17:42:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821351; bh=KAuFNcNcaodvn8EwTAqvlD3vaqm2rZZSCfQZOrAa3S4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=prpEvP5FC0ZT4SrFLX0f/LzSI+Vfcxo71WY/fQoPWadsrT/4OFPUx1FoRzHGEdT7D MYso3jlx1vfgar6UiSdOxCE879mZc2cxZt2hAjv1c3MRiB8XxzNpRR9nrKmsgQB78f K8o7kefZilQNMCnrUHz4VyCh8GJSlhP5QIRS6+Wk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v2 06/43] ipa: rkisp1: Move sensor control info map to context Date: Thu, 23 Jul 2026 17:42:49 +0200 Message-ID: <20260723154327.1357866-7-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" So that it is available to each algorithm. This will be used by the AGC algorithm. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/rkisp1/ipa_context.cpp | 3 +++ src/ipa/rkisp1/ipa_context.h | 2 ++ src/ipa/rkisp1/rkisp1.cpp | 25 +++++++++++-------------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp index 302b4cfd70..1f94afda6b 100644 --- a/src/ipa/rkisp1/ipa_context.cpp +++ b/src/ipa/rkisp1/ipa_context.cpp @@ -488,6 +488,9 @@ namespace libcamera::ipa::rkisp1 { * * \var IPAContext::frameContexts * \brief Ring buffer of per-frame contexts + * + * \var IPAContext::sensorControls + * \brief V4L2 controls of the sensor */ } /* namespace libcamera::ipa::rkisp1 */ diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index 9a4e0f12c6..eff88b72f5 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -252,6 +252,8 @@ struct IPAContext { ControlInfoMap::Map ctrlMap; + ControlInfoMap sensorControls; + DebugMetadata debugMetadata; /* Interface to the Camera Helper */ diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 273540a330..9f1f133e6f 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -75,15 +75,12 @@ protected: private: void updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls); void setControls(unsigned int frame); std::map buffers_; std::map mappedBuffers_; - ControlInfoMap sensorControls_; - /* Local parameter storage */ struct IPAContext context_; }; @@ -163,6 +160,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, LOG(IPARkISP1, Debug) << "Hardware revision is " << hwRevision; context_.sensorInfo = sensorInfo; + context_.sensorControls = sensorControls; context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); if (!context_.camHelper) { @@ -207,7 +205,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, return ret; /* Initialize controls. */ - updateControls(sensorInfo, sensorControls, ipaControls); + updateControls(sensorInfo, ipaControls); return 0; } @@ -227,13 +225,13 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, const std::map &streamConfig, ControlInfoMap *ipaControls) { - sensorControls_ = ipaConfig.sensorControls; + context_.sensorControls = ipaConfig.sensorControls; - const auto itExp = sensorControls_.find(V4L2_CID_EXPOSURE); + const auto itExp = context_.sensorControls.find(V4L2_CID_EXPOSURE); int32_t minExposure = itExp->second.min().get(); int32_t maxExposure = itExp->second.max().get(); - const auto itGain = sensorControls_.find(V4L2_CID_ANALOGUE_GAIN); + const auto itGain = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN); int32_t minGain = itGain->second.min().get(); int32_t maxGain = itGain->second.max().get(); @@ -253,7 +251,7 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; /* Update the camera controls using the new sensor settings. */ - updateControls(info, sensorControls_, ipaControls); + updateControls(info, ipaControls); /* * When the AGC computes the new exposure values for a frame, it needs @@ -385,7 +383,6 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId, } void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls) { ControlInfoMap::Map ctrlMap = rkisp1Controls; @@ -395,7 +392,7 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, * limits and the line duration. */ double lineDuration = context_.configuration.sensor.lineDuration.get(); - const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; + const ControlInfo &v4l2Exposure = context_.sensorControls.find(V4L2_CID_EXPOSURE)->second; int32_t minExposure = v4l2Exposure.min().get() * lineDuration; int32_t maxExposure = v4l2Exposure.max().get() * lineDuration; int32_t defExposure = v4l2Exposure.def().get() * lineDuration; @@ -404,7 +401,7 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, std::forward_as_tuple(minExposure, maxExposure, defExposure)); /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + const ControlInfo &v4l2Gain = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; float minGain = context_.camHelper->gain(v4l2Gain.min().get()); float maxGain = context_.camHelper->gain(v4l2Gain.max().get()); float defGain = context_.camHelper->gain(v4l2Gain.def().get()); @@ -418,11 +415,11 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, * The frame length is computed assuming a fixed line length combined * with the vertical frame sizes. */ - const ControlInfo &v4l2HBlank = sensorControls.find(V4L2_CID_HBLANK)->second; + const ControlInfo &v4l2HBlank = context_.sensorControls.find(V4L2_CID_HBLANK)->second; uint32_t hblank = v4l2HBlank.def().get(); uint32_t lineLength = sensorInfo.outputSize.width + hblank; - const ControlInfo &v4l2VBlank = sensorControls.find(V4L2_CID_VBLANK)->second; + const ControlInfo &v4l2VBlank = context_.sensorControls.find(V4L2_CID_VBLANK)->second; std::array frameHeights{ v4l2VBlank.min().get() + sensorInfo.outputSize.height, v4l2VBlank.max().get() + sensorInfo.outputSize.height, @@ -460,7 +457,7 @@ void IPARkISP1::setControls(unsigned int frame) << "Set controls for frame " << frame << ": exposure " << exposure << ", gain " << frameContext.agc.gain << ", vblank " << vblank; - ControlList ctrls(sensorControls_); + ControlList ctrls(context_.sensorControls); ctrls.set(V4L2_CID_EXPOSURE, static_cast(exposure)); ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast(gain)); ctrls.set(V4L2_CID_VBLANK, static_cast(vblank)); From patchwork Thu Jul 23 15:42: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: 27456 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 88CEFBE080 for ; Thu, 23 Jul 2026 15:43:44 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8495D67F1A; Thu, 23 Jul 2026 17:43:43 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OC+R8uah"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0B86967ECC for ; Thu, 23 Jul 2026 17:43:32 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2325E1F0F; Thu, 23 Jul 2026 17:42:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821351; bh=HE7FhESbEeEO69Rts2Xr+83Ga8T1zdtGXNlXfJsQHZo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OC+R8uah+Jo2sxL3Uq5KjF1IJZkOQOxLaF2qpBqWH+qQcSr7oeKS0ojVaJk0TXn8N ITb8K47KmjqOgfax/Jci24kie1ufYwzLEyFjyodTlA3Nz2NiiYIWDXnUF5ZjWrODKH Tw/rY8miipuhnAjxg7EEpNHHlN/UiXfAfCyML+bc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v2 07/43] ipa: rkisp1: Update sensor info when configuring Date: Thu, 23 Jul 2026 17:42:50 +0200 Message-ID: <20260723154327.1357866-8-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" 830340615afd ("ipa: rkisp1: Add sensor info to context") added the sensor info to the `IPAContext` type, but it only updates it in `init()`, do it in configure as well. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/rkisp1/rkisp1.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 9f1f133e6f..b31d5b59a8 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -225,6 +225,7 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, const std::map &streamConfig, ControlInfoMap *ipaControls) { + context_.sensorInfo = ipaConfig.sensorInfo; context_.sensorControls = ipaConfig.sensorControls; const auto itExp = context_.sensorControls.find(V4L2_CID_EXPOSURE); From patchwork Thu Jul 23 15:42: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: 27458 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 6894CBDE17 for ; Thu, 23 Jul 2026 15:43:47 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DEE7E67F1D; Thu, 23 Jul 2026 17:43:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="jqvD09VP"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 542AF67ED0 for ; Thu, 23 Jul 2026 17:43:32 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 676132505; Thu, 23 Jul 2026 17:42:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821351; bh=s+zEl0hHYLEoNlZPFsVZH8RWfTKgkrGwd5gnrgCgLxk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jqvD09VPiQLikV2MiFjgAi22psLMuCcez38HcvtawEMvTwG6MZjE/UOLnoXLK0XaP 1l+Ie2L0IvamigfCpmZJYElEjfLAx+QXAN/UqIFQxsvQH9eHrt1V9LJAAFE2a7awJ0 UCGJBv+0yuLPmlV0VjRfwNu7CGL9ExObL/4KAV40= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v2 08/43] ipa: rkisp1: updateControls(): Use sensor info from context Date: Thu, 23 Jul 2026 17:42:51 +0200 Message-ID: <20260723154327.1357866-9-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" It is already available so do not take it as a parameter. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/rkisp1/rkisp1.cpp | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index b31d5b59a8..66a91c4d79 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -74,8 +74,7 @@ protected: std::string logPrefix() const override; private: - void updateControls(const IPACameraSensorInfo &sensorInfo, - ControlInfoMap *ipaControls); + void updateControls(ControlInfoMap *ipaControls); void setControls(unsigned int frame); std::map buffers_; @@ -205,7 +204,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, return ret; /* Initialize controls. */ - updateControls(sensorInfo, ipaControls); + updateControls(ipaControls); return 0; } @@ -247,12 +246,12 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.paramFormat = ipaConfig.paramFormat; - const IPACameraSensorInfo &info = ipaConfig.sensorInfo; - context_.configuration.sensor.size = info.outputSize; - context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; + context_.configuration.sensor.size = context_.sensorInfo.outputSize; + context_.configuration.sensor.lineDuration = context_.sensorInfo.minLineLength * 1.0s + / context_.sensorInfo.pixelRate; /* Update the camera controls using the new sensor settings. */ - updateControls(info, ipaControls); + updateControls(ipaControls); /* * When the AGC computes the new exposure values for a frame, it needs @@ -285,7 +284,7 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, if (algo->disabled_) continue; - int ret = algo->configure(context_, info); + int ret = algo->configure(context_, context_.sensorInfo); if (ret) return ret; } @@ -383,8 +382,7 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId, metadataReady.emit(frame, metadata); } -void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, - ControlInfoMap *ipaControls) +void IPARkISP1::updateControls(ControlInfoMap *ipaControls) { ControlInfoMap::Map ctrlMap = rkisp1Controls; @@ -418,19 +416,19 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, */ const ControlInfo &v4l2HBlank = context_.sensorControls.find(V4L2_CID_HBLANK)->second; uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = sensorInfo.outputSize.width + hblank; + uint32_t lineLength = context_.sensorInfo.outputSize.width + hblank; const ControlInfo &v4l2VBlank = context_.sensorControls.find(V4L2_CID_VBLANK)->second; std::array frameHeights{ - v4l2VBlank.min().get() + sensorInfo.outputSize.height, - v4l2VBlank.max().get() + sensorInfo.outputSize.height, - v4l2VBlank.def().get() + sensorInfo.outputSize.height, + v4l2VBlank.min().get() + context_.sensorInfo.outputSize.height, + v4l2VBlank.max().get() + context_.sensorInfo.outputSize.height, + v4l2VBlank.def().get() + context_.sensorInfo.outputSize.height, }; std::array frameDurations; for (unsigned int i = 0; i < frameHeights.size(); ++i) { uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U); + frameDurations[i] = frameSize / (context_.sensorInfo.pixelRate / 1000000U); } /* \todo Move this (and other agc-related controls) to agc */ From patchwork Thu Jul 23 15:42: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: 27457 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 F3809C328C for ; Thu, 23 Jul 2026 15:43:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 38D1667F18; Thu, 23 Jul 2026 17:43:45 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TB2nQhtO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CF3A567ED6 for ; Thu, 23 Jul 2026 17:43:32 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AEF8D25DE; Thu, 23 Jul 2026 17:42:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821352; bh=ZsEUwLcOLLIyolI+85ptckIex5pLd/PMsxb48IEZNVE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TB2nQhtOl6gxQm4yIwdb+u/fnvNIkvOuw7RwVKDpwT7DxvHQZryCHTnFTnDWhxjWD irNB9jdgGjRKXH2lKlMEygQDhXYsR7jBDDFeZg03wPMngjR0zWStAZH9wb0z0bjorM D+1rIL8lGLDZN23pnBE0GxhQqCcSvJW0p1hPva8Y= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v2 09/43] ipa: rkisp1: Move AGC related controls into AGC algorithm Date: Thu, 23 Jul 2026 17:42:52 +0200 Message-ID: <20260723154327.1357866-10-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Move the `ControlInfo` setup and related initialization into the AGC algorithm instead of having it in the main IPA file. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/rkisp1/algorithms/agc.cpp | 91 ++++++++++++++++++++++++++++++- src/ipa/rkisp1/rkisp1.cpp | 88 +----------------------------- 2 files changed, 91 insertions(+), 88 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 540b64ff99..129f5be2a5 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -33,13 +33,96 @@ using namespace std::literals::chrono_literals; namespace ipa::rkisp1::algorithms { +LOG_DEFINE_CATEGORY(RkISP1Agc) + +namespace { + +void reconfigure(IPAContext &context) +{ + context.configuration.sensor.lineDuration = + context.sensorInfo.minLineLength * 1.0s / context.sensorInfo.pixelRate; + + double lineDurationUs = context.configuration.sensor.lineDuration.get(); + + /* + * Compute exposure time limits from the V4L2_CID_EXPOSURE control + * limits and the line duration. + */ + + const ControlInfo &v4l2Exposure = context.sensorControls.find(V4L2_CID_EXPOSURE)->second; + int32_t minExposure = v4l2Exposure.min().get(); + int32_t maxExposure = v4l2Exposure.max().get(); + int32_t defExposure = v4l2Exposure.def().get(); + context.ctrlMap[&controls::ExposureTime] = ControlInfo{ + static_cast(minExposure * lineDurationUs), + static_cast(maxExposure * lineDurationUs), + static_cast(defExposure * lineDurationUs), + }; + + /* Compute the analogue gain limits. */ + const ControlInfo &v4l2Gain = context.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + float minGain = context.camHelper->gain(v4l2Gain.min().get()); + float maxGain = context.camHelper->gain(v4l2Gain.max().get()); + float defGain = context.camHelper->gain(v4l2Gain.def().get()); + context.ctrlMap[&controls::AnalogueGain] = ControlInfo{ + minGain, + maxGain, + defGain, + }; + + LOG(RkISP1Agc, Debug) + << "Exposure: [" << minExposure << ", " << maxExposure + << "], gain: [" << minGain << ", " << maxGain << "]"; + + /* + * Compute the frame duration limits. + * + * The frame length is computed assuming a fixed line length combined + * with the vertical frame sizes. + */ + const ControlInfo &v4l2HBlank = context.sensorControls.find(V4L2_CID_HBLANK)->second; + uint32_t hblank = v4l2HBlank.def().get(); + uint32_t lineLength = context.sensorInfo.outputSize.width + hblank; + + const ControlInfo &v4l2VBlank = context.sensorControls.find(V4L2_CID_VBLANK)->second; + std::array frameHeights{ + v4l2VBlank.min().get() + context.sensorInfo.outputSize.height, + v4l2VBlank.max().get() + context.sensorInfo.outputSize.height, + v4l2VBlank.def().get() + context.sensorInfo.outputSize.height, + }; + + std::array frameDurations; + for (unsigned int i = 0; i < frameHeights.size(); ++i) { + uint64_t frameSize = lineLength * frameHeights[i]; + frameDurations[i] = frameSize / (context.sensorInfo.pixelRate / 1000000U); + } + + context.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ + frameDurations[0], + frameDurations[1], + Span{ { frameDurations[2], frameDurations[2] } }, + }; + + /* + * When the AGC computes the new exposure values for a frame, it needs + * to know the limits for exposure time and analogue gain. As it depends + * on the sensor, update it with the controls. + * + * \todo take VBLANK into account for maximum exposure time + */ + context.configuration.sensor.minExposureTime = minExposure * context.configuration.sensor.lineDuration; + context.configuration.sensor.maxExposureTime = maxExposure * context.configuration.sensor.lineDuration; + context.configuration.sensor.minAnalogueGain = minGain; + context.configuration.sensor.maxAnalogueGain = maxGain; +} + +} /* namespace */ + /** * \class Agc * \brief A mean-based auto-exposure algorithm */ -LOG_DEFINE_CATEGORY(RkISP1Agc) - int Agc::parseMeteringModes(IPAContext &context, const ValueNode &tuningData) { if (!tuningData.isDictionary()) @@ -160,6 +243,8 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); context.ctrlMap.merge(agc_.controls()); + reconfigure(context); + return 0; } @@ -172,6 +257,8 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) */ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { + reconfigure(context); + /* Configure the default exposure and gain. */ context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain; context.activeState.agc.automatic.exposure = diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 66a91c4d79..38f55b1d86 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -169,9 +169,6 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, return -ENODEV; } - context_.configuration.sensor.lineDuration = - sensorInfo.minLineLength * 1.0s / sensorInfo.pixelRate; - /* Load the tuning data file. */ File file(settings.configurationFile); if (!file.open(File::OpenModeFlag::ReadOnly)) { @@ -227,18 +224,6 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.sensorInfo = ipaConfig.sensorInfo; context_.sensorControls = ipaConfig.sensorControls; - const auto itExp = context_.sensorControls.find(V4L2_CID_EXPOSURE); - int32_t minExposure = itExp->second.min().get(); - int32_t maxExposure = itExp->second.max().get(); - - const auto itGain = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN); - int32_t minGain = itGain->second.min().get(); - int32_t maxGain = itGain->second.max().get(); - - LOG(IPARkISP1, Debug) - << "Exposure: [" << minExposure << ", " << maxExposure - << "], gain: [" << minGain << ", " << maxGain << "]"; - /* Clear the IPA context before the streaming session. */ context_.configuration = {}; context_.activeState = {}; @@ -247,27 +232,6 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.paramFormat = ipaConfig.paramFormat; context_.configuration.sensor.size = context_.sensorInfo.outputSize; - context_.configuration.sensor.lineDuration = context_.sensorInfo.minLineLength * 1.0s - / context_.sensorInfo.pixelRate; - - /* Update the camera controls using the new sensor settings. */ - updateControls(ipaControls); - - /* - * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for exposure time and analogue gain. As it depends - * on the sensor, update it with the controls. - * - * \todo take VBLANK into account for maximum exposure time - */ - context_.configuration.sensor.minExposureTime = - minExposure * context_.configuration.sensor.lineDuration; - context_.configuration.sensor.maxExposureTime = - maxExposure * context_.configuration.sensor.lineDuration; - context_.configuration.sensor.minAnalogueGain = - context_.camHelper->gain(minGain); - context_.configuration.sensor.maxAnalogueGain = - context_.camHelper->gain(maxGain); context_.configuration.raw = std::any_of(streamConfig.begin(), streamConfig.end(), [](auto &cfg) -> bool { @@ -289,6 +253,8 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, return ret; } + updateControls(ipaControls); + return 0; } @@ -386,56 +352,6 @@ void IPARkISP1::updateControls(ControlInfoMap *ipaControls) { ControlInfoMap::Map ctrlMap = rkisp1Controls; - /* - * Compute exposure time limits from the V4L2_CID_EXPOSURE control - * limits and the line duration. - */ - double lineDuration = context_.configuration.sensor.lineDuration.get(); - const ControlInfo &v4l2Exposure = context_.sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get() * lineDuration; - int32_t maxExposure = v4l2Exposure.max().get() * lineDuration; - int32_t defExposure = v4l2Exposure.def().get() * lineDuration; - ctrlMap.emplace(std::piecewise_construct, - std::forward_as_tuple(&controls::ExposureTime), - std::forward_as_tuple(minExposure, maxExposure, defExposure)); - - /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = context_.camHelper->gain(v4l2Gain.min().get()); - float maxGain = context_.camHelper->gain(v4l2Gain.max().get()); - float defGain = context_.camHelper->gain(v4l2Gain.def().get()); - ctrlMap.emplace(std::piecewise_construct, - std::forward_as_tuple(&controls::AnalogueGain), - std::forward_as_tuple(minGain, maxGain, defGain)); - - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - const ControlInfo &v4l2HBlank = context_.sensorControls.find(V4L2_CID_HBLANK)->second; - uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = context_.sensorInfo.outputSize.width + hblank; - - const ControlInfo &v4l2VBlank = context_.sensorControls.find(V4L2_CID_VBLANK)->second; - std::array frameHeights{ - v4l2VBlank.min().get() + context_.sensorInfo.outputSize.height, - v4l2VBlank.max().get() + context_.sensorInfo.outputSize.height, - v4l2VBlank.def().get() + context_.sensorInfo.outputSize.height, - }; - - std::array frameDurations; - for (unsigned int i = 0; i < frameHeights.size(); ++i) { - uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (context_.sensorInfo.pixelRate / 1000000U); - } - - /* \todo Move this (and other agc-related controls) to agc */ - context_.ctrlMap[&controls::FrameDurationLimits] = - ControlInfo(frameDurations[0], frameDurations[1], - ControlValue(Span{ { frameDurations[2], frameDurations[2] } })); - ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end()); *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); } From patchwork Thu Jul 23 15:42: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: 27459 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 9646EC3264 for ; Thu, 23 Jul 2026 15:43:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E7A0667F02; Thu, 23 Jul 2026 17:43:47 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mAwGU/5e"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0CE4067ED7 for ; Thu, 23 Jul 2026 17:43:33 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 28C292946; Thu, 23 Jul 2026 17:42:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821352; bh=LyoRtDpoyydEb7i/al3TCmgICsYNXSvcWVQOr0lGGDM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mAwGU/5e3yN5s1b9hedsJavysMrDLZHOuqNDRIc8rMymCC+F0IKQuPQS8ecj0WKw1 7GHG/fn/xdKxe/z6OilM2Zx59TFSTe6LRPiWwz2lDJZojmxzRBeibPS7LgNxoV84E9 wCruwLyjQRxIF2HV2p38cQptpFuFi96FvHCqktx0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham Subject: [RFC PATCH v2 10/43] ipa: mali-c55: Remove `DigitalGain` support Date: Thu, 23 Jul 2026 17:42:53 +0200 Message-ID: <20260723154327.1357866-11-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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 mali-c55 agc algorithm still mainly uses the `AeEnable` control (see issue below), but the interaction of that control and `DigitalGain` is not well defined, and no other platform supports setting it. So remove it for now until things are properly defined. Link: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/262 Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham --- src/ipa/mali-c55/algorithms/agc.cpp | 57 ++--------------------------- src/ipa/mali-c55/algorithms/agc.h | 3 -- src/ipa/mali-c55/ipa_context.h | 3 -- 3 files changed, 4 insertions(+), 59 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 91a6156aaf..29b7c66159 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -16,7 +16,6 @@ #include #include "libipa/colours.h" -#include "libipa/fixedpoint.h" namespace libcamera { @@ -33,14 +32,6 @@ LOG_DEFINE_CATEGORY(MaliC55Agc) */ static constexpr unsigned int kNumHistogramBins = 256; -/* - * The Mali-C55 ISP has a digital gain block which allows setting gain in Q5.8 - * format, a range of 0.0 to (very nearly) 32.0. We clamp from 1.0 to the actual - * max value which is 8191 * 2^-8. - */ -static constexpr float kMinDigitalGain = 1.0; -static constexpr float kMaxDigitalGain = UQ<5, 8>::TraitsType::max; - uint32_t AgcStatistics::decodeBinValue(uint16_t binVal) { int exponent = (binVal & 0xf000) >> 12; @@ -137,8 +128,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) return ret; context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); - context.ctrlMap[&controls::DigitalGain] = ControlInfo( - kMinDigitalGain, kMaxDigitalGain, kMinDigitalGain); context.ctrlMap.merge(agc_.controls()); return 0; @@ -158,10 +147,8 @@ int Agc::configure(IPAContext &context, context.activeState.agc.autoEnabled = true; context.activeState.agc.automatic.sensorGain = context.configuration.agc.minAnalogueGain; context.activeState.agc.automatic.exposure = context.configuration.agc.defaultExposure; - context.activeState.agc.automatic.ispGain = kMinDigitalGain; context.activeState.agc.manual.sensorGain = context.configuration.agc.minAnalogueGain; context.activeState.agc.manual.exposure = context.configuration.agc.defaultExposure; - context.activeState.agc.manual.ispGain = kMinDigitalGain; context.activeState.agc.constraintMode = agc_.constraintModes().begin()->first; context.activeState.agc.exposureMode = agc_.exposureModeHelpers().begin()->first; @@ -226,32 +213,6 @@ void Agc::queueRequest(IPAContext &context, const uint32_t frame, << "Analogue gain set to " << agc.manual.sensorGain << " on request sequence " << frame; } - - const auto &digitalGain = controls.get(controls::DigitalGain); - if (digitalGain) { - agc.manual.ispGain = *digitalGain; - - LOG(MaliC55Agc, Debug) - << "Digital gain set to " << agc.manual.ispGain - << " on request sequence " << frame; - } -} - -void Agc::fillGainParamBlock(IPAContext &context, IPAFrameContext &frameContext, - MaliC55Params *params) -{ - IPAActiveState &activeState = context.activeState; - UQ<5, 8> gain; - - if (activeState.agc.autoEnabled) - gain = activeState.agc.automatic.ispGain; - else - gain = activeState.agc.manual.ispGain; - - auto block = params->block(); - block->gain = gain.quantized(); - - frameContext.agc.ispGain = gain; } void Agc::fillParamsBuffer(MaliC55Params *params, enum MaliC55Blocks type) @@ -304,11 +265,9 @@ void Agc::fillWeightsArrayBuffer(MaliC55Params *params, const enum MaliC55Blocks std::fill(weights.begin(), weights.end(), 1); } -void Agc::prepare(IPAContext &context, const uint32_t frame, - IPAFrameContext &frameContext, MaliC55Params *params) +void Agc::prepare([[maybe_unused]] IPAContext &context, const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, MaliC55Params *params) { - fillGainParamBlock(context, frameContext, params); - if (frame > 0) return; @@ -370,10 +329,8 @@ void Agc::process(IPAContext &context, */ uint32_t exposure = frameContext.agc.exposure; double analogueGain = frameContext.agc.sensorGain; - double digitalGain = frameContext.agc.ispGain.value(); - double totalGain = analogueGain * digitalGain; utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; - utils::Duration effectiveExposureValue = currentShutter * totalGain; + utils::Duration effectiveExposureValue = currentShutter * analogueGain; AgcTraits agcTraits(statistics_); utils::Duration shutterTime; @@ -383,21 +340,15 @@ void Agc::process(IPAContext &context, activeState.agc.exposureMode, statistics_.yHist, effectiveExposureValue, agcTraits); - UQ<5, 8> dGainQ = std::clamp(static_cast(dGain), - kMinDigitalGain, - kMaxDigitalGain); - LOG(MaliC55Agc, Debug) << "Divided up shutter, analogue gain and digital gain are " - << shutterTime << ", " << aGain << " and " << dGainQ; + << shutterTime << ", " << aGain << " and " << dGain; activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration; activeState.agc.automatic.sensorGain = aGain; - activeState.agc.automatic.ispGain = dGainQ; metadata.set(controls::ExposureTime, currentShutter.get()); metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain); - metadata.set(controls::DigitalGain, frameContext.agc.ispGain.value()); metadata.set(controls::ColourTemperature, context.activeState.agc.temperatureK); } diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h index e36378a2ac..ec7fe28c17 100644 --- a/src/ipa/mali-c55/algorithms/agc.h +++ b/src/ipa/mali-c55/algorithms/agc.h @@ -64,9 +64,6 @@ public: ControlList &metadata) override; private: - void fillGainParamBlock(IPAContext &context, - IPAFrameContext &frameContext, - MaliC55Params *params); void fillParamsBuffer(MaliC55Params *params, enum MaliC55Blocks type); void fillWeightsArrayBuffer(MaliC55Params *params, enum MaliC55Blocks type); diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index ac4b837738..075d6f66ef 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -41,12 +41,10 @@ struct IPAActiveState { struct { uint32_t exposure; double sensorGain; - UQ<5, 8> ispGain; } automatic; struct { uint32_t exposure; double sensorGain; - UQ<5, 8> ispGain; } manual; bool autoEnabled; uint32_t constraintMode; @@ -64,7 +62,6 @@ struct IPAFrameContext : public FrameContext { struct { uint32_t exposure; double sensorGain; - UQ<5, 8> ispGain; } agc; struct { From patchwork Thu Jul 23 15:42: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: 27460 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 CC2B3C32CE for ; Thu, 23 Jul 2026 15:43:49 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 03FE367EFC; Thu, 23 Jul 2026 17:43:49 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="wjsdGaCh"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4537167ED1 for ; Thu, 23 Jul 2026 17:43:33 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 67D962C09 for ; Thu, 23 Jul 2026 17:42:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821352; bh=ta/wrT2oavvg7MUTgIj2aW43nYGzgGvNzNdXqCtXpVE=; h=From:To:Subject:Date:In-Reply-To:References:From; b=wjsdGaChX/QgF5vT3evgEXT0CVXY3g8OW/+6zhWebRp2ep5fcXQkX8jQYWC50qEXd 0WChBw24N55/TD0+WNMlPMLkz6LEqHHaWvDbo696UK89wd2WYCT2t7MeQVDzX6wDM1 kpaUgjUdEvhqzRTbCufBTmwBlTDRPm35YrJAE9nM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 11/43] ipa: libipa: agc_mean_luminance: Avoid some duplicate lookups Date: Thu, 23 Jul 2026 17:42:54 +0200 Message-ID: <20260723154327.1357866-12-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Use iterators/references to avoid multiple map lookups. Also adjust the no exposure mode / constraint cases to look similar. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_mean_luminance.cpp | 35 +++++++++++---------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 40e4c03100..8284c15ce2 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -225,14 +225,12 @@ int AgcMeanLuminance::parseConstraint(const ValueNode &modeDict, int32_t id) } AgcConstraint constraint = { bound, qLo, qHi, std::move(*yTarget) }; - - if (!constraintModes_.count(id)) - constraintModes_[id] = {}; + auto &constraints = constraintModes_[id]; if (idx) - constraintModes_[id].push_back(constraint); + constraints.push_back(std::move(constraint)); else - constraintModes_[id].insert(constraintModes_[id].begin(), constraint); + constraints.insert(constraints.begin(), std::move(constraint)); } return 0; @@ -245,8 +243,8 @@ int AgcMeanLuminance::parseConstraintModes(const ValueNode &tuningData) const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; if (constraintModes.isDictionary()) { for (const auto &[modeName, modeDict] : constraintModes.asDict()) { - if (AeConstraintModeNameValueMap.find(modeName) == - AeConstraintModeNameValueMap.end()) { + auto it = AeConstraintModeNameValueMap.find(modeName); + if (it == AeConstraintModeNameValueMap.end()) { LOG(AgcMeanLuminance, Warning) << "Skipping unknown constraint mode '" << modeName << "'"; continue; @@ -258,13 +256,11 @@ int AgcMeanLuminance::parseConstraintModes(const ValueNode &tuningData) return -EINVAL; } - int ret = parseConstraint(modeDict, - AeConstraintModeNameValueMap.at(modeName)); + int ret = parseConstraint(modeDict, it->second); if (ret) return ret; - availableConstraintModes.push_back( - AeConstraintModeNameValueMap.at(modeName)); + availableConstraintModes.push_back(it->second); } } @@ -282,9 +278,7 @@ int AgcMeanLuminance::parseConstraintModes(const ValueNode &tuningData) Pwl({ { { 0.0, 0.5 } } }) }; - constraintModes_[controls::ConstraintNormal].insert( - constraintModes_[controls::ConstraintNormal].begin(), - constraint); + constraintModes_[controls::ConstraintNormal].push_back(std::move(constraint)); availableConstraintModes.push_back(controls::ConstraintNormal); } @@ -300,8 +294,8 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; if (exposureModes.isDictionary()) { for (const auto &[modeName, modeValues] : exposureModes.asDict()) { - if (AeExposureModeNameValueMap.find(modeName) == - AeExposureModeNameValueMap.end()) { + auto it = AeExposureModeNameValueMap.find(modeName); + if (it == AeExposureModeNameValueMap.end()) { LOG(AgcMeanLuminance, Warning) << "Skipping unknown exposure mode '" << modeName << "'"; continue; @@ -341,8 +335,8 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) std::shared_ptr helper = std::make_shared(stages); - exposureModeHelpers_[AeExposureModeNameValueMap.at(modeName)] = helper; - availableExposureModes.push_back(AeExposureModeNameValueMap.at(modeName)); + exposureModeHelpers_.try_emplace(it->second, std::move(helper)); + availableExposureModes.push_back(it->second); } } @@ -353,14 +347,13 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) * possible before touching gain. */ if (availableExposureModes.empty()) { - int32_t exposureModeId = controls::ExposureNormal; std::vector> stages = { }; std::shared_ptr helper = std::make_shared(stages); - exposureModeHelpers_[exposureModeId] = helper; - availableExposureModes.push_back(exposureModeId); + exposureModeHelpers_.try_emplace(controls::ExposureNormal, std::move(helper)); + availableExposureModes.push_back(controls::ExposureNormal); } controls_[&controls::AeExposureMode] = ControlInfo(availableExposureModes); From patchwork Thu Jul 23 15:42:55 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: 27462 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 56658C32FE for ; Thu, 23 Jul 2026 15:43:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5C64467F21; Thu, 23 Jul 2026 17:43:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="LzhugbI+"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7AD0D67EDA for ; Thu, 23 Jul 2026 17:43:33 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9E82D2C0B for ; Thu, 23 Jul 2026 17:42:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821352; bh=FiqcuUi9DpZ8BHcj0S1GFTJ2m2G4x+SocPWrpFip7R4=; h=From:To:Subject:Date:In-Reply-To:References:From; b=LzhugbI+uCu7PgT2TeeG+ih9NsO3RPzJOTYr+bcNRp2OafzqW1p9nJr6R6ZCRBuVh M1HPG+UCEF7+6mSYYrg/VXROc4M8nrs9xbp60g+Qnf4b/yvVunVlnPBn51Q7+0qRjj yNTkpBf47BAkhehf2MBmnUq48t8TY+n0e1jo4IDs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 12/43] ipa: libipa: agc_mean_luminance: Remove unnecessary `std::shared_ptr` Date: Thu, 23 Jul 2026 17:42:55 +0200 Message-ID: <20260723154327.1357866-13-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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 `ExposureModeHelper` object can be directly in the map, so do that. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_mean_luminance.cpp | 23 ++++++++--------------- src/ipa/libipa/agc_mean_luminance.h | 4 ++-- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 8284c15ce2..faa8a41fd7 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -332,10 +332,7 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) }); } - std::shared_ptr helper = - std::make_shared(stages); - - exposureModeHelpers_.try_emplace(it->second, std::move(helper)); + exposureModeHelpers_.try_emplace(it->second, stages); availableExposureModes.push_back(it->second); } } @@ -347,12 +344,8 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) * possible before touching gain. */ if (availableExposureModes.empty()) { - std::vector> stages = { }; - - std::shared_ptr helper = - std::make_shared(stages); - - exposureModeHelpers_.try_emplace(controls::ExposureNormal, std::move(helper)); + exposureModeHelpers_.try_emplace(controls::ExposureNormal, + Span>{}); availableExposureModes.push_back(controls::ExposureNormal); } @@ -373,7 +366,7 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration, const CameraSensorHelper *sensorHelper) { for (auto &[id, helper] : exposureModeHelpers_) - helper->configure(lineDuration, sensorHelper); + helper.configure(lineDuration, sensorHelper); luxWarningEnabled_ = true; } @@ -484,7 +477,7 @@ void AgcMeanLuminance::setLimits(utils::Duration minExposureTime, std::vector constraints) { for (auto &[id, helper] : exposureModeHelpers_) - helper->setLimits(minExposureTime, maxExposureTime, minGain, maxGain); + helper.setLimits(minExposureTime, maxExposureTime, minGain, maxGain); additionalConstraints_ = std::move(constraints); } @@ -684,7 +677,7 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, * The pipeline handler should validate that we have received an allowed * value for AeExposureMode. */ - std::shared_ptr exposureModeHelper = + ExposureModeHelper &exposureModeHelper = exposureModeHelpers_.at(exposureModeIndex); if (effectiveExposureValue == 0s) { @@ -696,7 +689,7 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, * doesn't get stuck with 0 in case the sensor driver allows a * min exposure of 0. */ - return exposureModeHelper->splitExposure(10ms); + return exposureModeHelper.splitExposure(10ms); } double gain = estimateInitialGain(traits); @@ -718,7 +711,7 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, newExposureValue = filterExposure(newExposureValue); frameCount_++; - return exposureModeHelper->splitExposure(newExposureValue); + return exposureModeHelper.splitExposure(newExposureValue); } /** diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index f4e1680ab5..d5425cd20b 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -69,7 +69,7 @@ public: return constraintModes_; } - const std::map> &exposureModeHelpers() const + const std::map &exposureModeHelpers() const { return exposureModeHelpers_; } @@ -111,7 +111,7 @@ private: std::vector additionalConstraints_; std::map> constraintModes_; - std::map> exposureModeHelpers_; + std::map exposureModeHelpers_; ControlInfoMap::Map controls_; }; From patchwork Thu Jul 23 15:42:56 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: 27461 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 15935BE080 for ; Thu, 23 Jul 2026 15:43:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9223967F08; Thu, 23 Jul 2026 17:43:50 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BNKTLGzG"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B803E67EDB for ; Thu, 23 Jul 2026 17:43:33 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D2BE22C09 for ; Thu, 23 Jul 2026 17:42:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821353; bh=iO87ozqBoE30398kJ8RNSsQTPB4WlSn5wwCdd2npGZ8=; h=From:To:Subject:Date:In-Reply-To:References:From; b=BNKTLGzGNTtc5K5H0F/KH+EsbDg3MpRvLtYymX8A7ky3ZuQ2O6hLmF4yyMXiBSH0V rbKaBWbRSy+3iYdbSFDsUMUL4kv5E2/pzT+kuLuWGL3smrZo98Y+/COcAvZUETlLNs lsqy7MoVmgxXl4ftDIp4Fv2ItYfxI4KrE0qorxqU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 13/43] ipa: libipa: agc_mean_luminance: calculateNewEv(): Fix typo Date: Thu, 23 Jul 2026 17:42:56 +0200 Message-ID: <20260723154327.1357866-14-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" splut -> split Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_mean_luminance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index faa8a41fd7..31636159a2 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -649,7 +649,7 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) } /** - * \brief Calculate the new exposure value and splut it between exposure time + * \brief Calculate the new exposure value and split it between exposure time * and gain * \param[in] constraintModeIndex The index of the current constraint mode * \param[in] exposureModeIndex The index of the current exposure mode From patchwork Thu Jul 23 15:42:57 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: 27464 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 8F16FC3301 for ; Thu, 23 Jul 2026 15:43:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 16F2267F20; Thu, 23 Jul 2026 17:43:54 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="JgtLzCLC"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 012FA67EC7 for ; Thu, 23 Jul 2026 17:43:33 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 221CA1C37 for ; Thu, 23 Jul 2026 17:42:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821353; bh=0guaNOTTnXwSaZhS+tOKH3iYPgTi94jBtetlKj0Ptcw=; h=From:To:Subject:Date:In-Reply-To:References:From; b=JgtLzCLCt8geo4TIbtBYLFQFumZcSjFCjinIzP9SDdxuY1ZFWYn0dm/30tw3MvABS LbN28ei/gaNdrYdyKjxge7RViT+CBdlNBjYMoxisaFwjKBTD6tgAGxAsbn+7RqnmzC MSbB483K6AED4TDIf+hGPSZumW0RKZMM5MuTCCg0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 14/43] ipa: libipa: agc_mean_luminance: calculateNewEv(): Collect params Date: Thu, 23 Jul 2026 17:42:57 +0200 Message-ID: <20260723154327.1357866-15-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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 a new type that contains all parameters for the `calculateNewEv()` function. Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/algorithms/agc.cpp | 28 +++++++++-------- src/ipa/libipa/agc_mean_luminance.cpp | 44 ++++++++++++++++----------- src/ipa/libipa/agc_mean_luminance.h | 12 ++++++-- src/ipa/mali-c55/algorithms/agc.cpp | 12 +++++--- src/ipa/rkisp1/algorithms/agc.cpp | 18 ++++++----- 5 files changed, 68 insertions(+), 46 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index a919418d69..ac5b6e7513 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -236,22 +236,24 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, * frameContext.sensor.exposure; double analogueGain = frameContext.sensor.gain; utils::Duration effectiveExposureValue = exposureTime * analogueGain; - AgcTraits agcTraits{ - rgbTriples_, - {{ - context.activeState.awb.gains.red, - context.activeState.awb.gains.blue, - context.activeState.awb.gains.green, - }}, - bdsGrid_, - }; utils::Duration newExposureTime; double aGain, qGain, dGain; - std::tie(newExposureTime, aGain, qGain, dGain) = - agc_.calculateNewEv(context.activeState.agc.constraintMode, - context.activeState.agc.exposureMode, hist, - effectiveExposureValue, agcTraits); + std::tie(newExposureTime, aGain, qGain, dGain) = agc_.calculateNewEv({ + .traits = AgcTraits{ + rgbTriples_, + {{ + context.activeState.awb.gains.red, + context.activeState.awb.gains.blue, + context.activeState.awb.gains.green, + }}, + bdsGrid_, + }, + .yHist = hist, + .effectiveExposureValue = effectiveExposureValue, + .constraintModeIndex = context.activeState.agc.constraintMode, + .exposureModeIndex = context.activeState.agc.exposureMode, + }); LOG(IPU3Agc, Debug) << "Divided up exposure time, analogue gain and digital gain are " diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 31636159a2..083d67eb70 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -648,16 +648,30 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) return filteredExposure_; } +/** + * \struct AgcMeanLuminance::Params + * \brief Collection of parameters for the mean luminance AGC algorithm + * + * \var AgcMeanLuminance::Params::traits + * \brief The traits object implementing the necessary functions + * + * \var AgcMeanLuminance::Params::yHist + * \brief A histogram from the ISP statistics to use in constraining the calculated gain + * + * \var AgcMeanLuminance::Params::effectiveExposureValue + * \brief The EV applied to the frame from which the statistics in use derive + * + * \var AgcMeanLuminance::Params::constraintModeIndex + * \brief The index of the constraint mode to use + * + * \var AgcMeanLuminance::Params::exposureModeIndex + * \brief The index of the exposure mode to use + */ + /** * \brief Calculate the new exposure value and split it between exposure time * and gain - * \param[in] constraintModeIndex The index of the current constraint mode - * \param[in] exposureModeIndex The index of the current exposure mode - * \param[in] yHist A Histogram from the ISP statistics to use in constraining - * the calculated gain - * \param[in] effectiveExposureValue The EV applied to the frame from which the - * statistics in use derive - * \param[in] traits The traits object implementing the necessary functions + * \param[in] params The set of parameters for the calculation * * Calculate a new exposure value to try to obtain the target. The calculated * exposure value is filtered to prevent rapid changes from frame to frame, and @@ -667,20 +681,16 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) * gain */ std::tuple -AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, - uint32_t exposureModeIndex, - const Histogram &yHist, - utils::Duration effectiveExposureValue, - const Traits &traits) +AgcMeanLuminance::calculateNewEv(const Params ¶ms) { /* * The pipeline handler should validate that we have received an allowed * value for AeExposureMode. */ ExposureModeHelper &exposureModeHelper = - exposureModeHelpers_.at(exposureModeIndex); + exposureModeHelpers_.at(params.exposureModeIndex); - if (effectiveExposureValue == 0s) { + if (params.effectiveExposureValue == 0s) { LOG(AgcMeanLuminance, Error) << "Effective exposure value is 0. This is a bug in AGC " "and must be fixed for proper operation."; @@ -692,8 +702,8 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, return exposureModeHelper.splitExposure(10ms); } - double gain = estimateInitialGain(traits); - gain = constraintClampGain(constraintModeIndex, yHist, gain); + double gain = estimateInitialGain(params.traits); + gain = constraintClampGain(params.constraintModeIndex, params.yHist, gain); /* * We don't check whether we're already close to the target, because @@ -702,7 +712,7 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, * pass through the splitExposure() function. */ - utils::Duration newExposureValue = effectiveExposureValue * gain; + utils::Duration newExposureValue = params.effectiveExposureValue * gain; /* * We filter the exposure value to make sure changes are not too jarring diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index d5425cd20b..eb9d6fec2b 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -79,10 +79,16 @@ public: return controls_; } + struct Params { + const Traits &traits; + const Histogram &yHist; + utils::Duration effectiveExposureValue; + uint32_t constraintModeIndex; + uint32_t exposureModeIndex; + }; + std::tuple - calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex, - const Histogram &yHist, utils::Duration effectiveExposureValue, - const Traits &traits); + calculateNewEv(const Params ¶ms); double effectiveYTarget() const; diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 29b7c66159..fb97b87c6e 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -331,14 +331,16 @@ void Agc::process(IPAContext &context, double analogueGain = frameContext.agc.sensorGain; utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; utils::Duration effectiveExposureValue = currentShutter * analogueGain; - AgcTraits agcTraits(statistics_); utils::Duration shutterTime; double aGain, qGain, dGain; - std::tie(shutterTime, aGain, qGain, dGain) = - agc_.calculateNewEv(activeState.agc.constraintMode, - activeState.agc.exposureMode, statistics_.yHist, - effectiveExposureValue, agcTraits); + std::tie(shutterTime, aGain, qGain, dGain) = agc_.calculateNewEv({ + .traits = AgcTraits(statistics_), + .yHist = statistics_.yHist, + .effectiveExposureValue = effectiveExposureValue, + .constraintModeIndex = activeState.agc.constraintMode, + .exposureModeIndex = activeState.agc.exposureMode, + }); LOG(MaliC55Agc, Debug) << "Divided up shutter, analogue gain and digital gain are " diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 129f5be2a5..8b4bcbd71b 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -716,20 +716,22 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, /* The lower 4 bits are fractional and meant to be discarded. */ Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, [](uint32_t x) { return x >> 4; }); - AgcTraits agcTraits{ - { params->ae.exp_mean, context.hw.numAeCells }, - meteringModes_.at(frameContext.agc.meteringMode), - }; agc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); agc_.setLux(frameContext.lux.lux); utils::Duration newExposureTime; double aGain, qGain, dGain; - std::tie(newExposureTime, aGain, qGain, dGain) = - agc_.calculateNewEv(frameContext.agc.constraintMode, - frameContext.agc.exposureMode, - hist, effectiveExposureValue, agcTraits); + std::tie(newExposureTime, aGain, qGain, dGain) = agc_.calculateNewEv({ + .traits = AgcTraits{ + { params->ae.exp_mean, context.hw.numAeCells }, + meteringModes_.at(frameContext.agc.meteringMode), + }, + .yHist = hist, + .effectiveExposureValue = effectiveExposureValue, + .constraintModeIndex = frameContext.agc.constraintMode, + .exposureModeIndex = frameContext.agc.exposureMode, + }); LOG(RkISP1Agc, Debug) << "Divided up exposure time, analogue gain, quantization gain" From patchwork Thu Jul 23 15:42:58 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: 27463 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 4BD18C328C for ; Thu, 23 Jul 2026 15:43:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4610467F2F; Thu, 23 Jul 2026 17:43:52 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rr2/GhWE"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 31F7667EC4 for ; Thu, 23 Jul 2026 17:43:34 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5ADFE2C09 for ; Thu, 23 Jul 2026 17:42:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821353; bh=z+VGiYwJlanVxNn7nc8MSGI0IQTD/fuduwGKRZO9HXY=; h=From:To:Subject:Date:In-Reply-To:References:From; b=rr2/GhWE+NL1cxfNKZVkOE2/8Hy4rLwPDtooXzeRy5lj0x0EfY0s8YLE+KqjZ/qHx A/nhpTTzn4MuSVKbRA4hJc45k74pGmarGuK2r5fHkj44nw6gMQCwxLKvWLytSRfOw3 /R/R+qcixOfG2ryUwfzQOCr6Bc/ypFvl+ElSMh+0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 15/43] ipa: libipa: agc_mean_luminance: constraintClampGain(): Mark `const` Date: Thu, 23 Jul 2026 17:42:58 +0200 Message-ID: <20260723154327.1357866-16-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" There is no reason not to have this member function as `const`. Change the map lookup to use `at()` just like it is done for `calculateNewEv()`, with the same requirement that validation is the job of the caller. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_mean_luminance.cpp | 4 ++-- src/ipa/libipa/agc_mean_luminance.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 083d67eb70..e8679c66be 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -539,7 +539,7 @@ double AgcMeanLuminance::estimateInitialGain(const Traits &traits) const */ double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex, const Histogram &hist, - double gain) + double gain) const { auto applyConstraint = [this, &gain, &hist](const AgcConstraint &constraint) { double lux = lux_; @@ -569,7 +569,7 @@ double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex, } }; - std::vector &constraints = constraintModes_[constraintModeIndex]; + const std::vector &constraints = constraintModes_.at(constraintModeIndex); std::for_each(constraints.begin(), constraints.end(), applyConstraint); std::for_each(additionalConstraints_.begin(), additionalConstraints_.end(), applyConstraint); diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index eb9d6fec2b..9b82d97ab6 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -105,7 +105,7 @@ private: double estimateInitialGain(const Traits &traits) const; double constraintClampGain(uint32_t constraintModeIndex, const Histogram &hist, - double gain); + double gain) const; utils::Duration filterExposure(utils::Duration exposureValue); utils::Duration filteredExposure_; From patchwork Thu Jul 23 15:42:59 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: 27465 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 60A1CBDE17 for ; Thu, 23 Jul 2026 15:43:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E804467F31; Thu, 23 Jul 2026 17:43:55 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UG9UH4+A"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6BBA867EDE for ; Thu, 23 Jul 2026 17:43:34 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8FF191C37 for ; Thu, 23 Jul 2026 17:42:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821353; bh=BUor4FFuTFvStj6cEasyaVz0CjfD/wopx30LsQF2m+M=; h=From:To:Subject:Date:In-Reply-To:References:From; b=UG9UH4+AUCKGHQ3I0xKbKaMAiEv7NOTmeKChmlOmmVCmJdeVcA8YXypKiIkp94ab4 OroH3F41aPDWLAMBMVYVHEkrIBZ1yUkP7jHRMu1q/HjuZ6edZGWEU4VHzkmWu0YyQy +0RFW7HyTLpOd7x+EkCve7qZVXYoPkdp+bkYZIcI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 16/43] ipa: libipa: agc_mean_luminance: calculateNewEv(): Collect results Date: Thu, 23 Jul 2026 17:42:59 +0200 Message-ID: <20260723154327.1357866-17-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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 a new type that gives proper names to the returned quantities instead of just using an `std::tuple`. Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/algorithms/agc.cpp | 10 ++++------ src/ipa/libipa/agc_mean_luminance.cpp | 11 ++++++++--- src/ipa/libipa/agc_mean_luminance.h | 7 ++++--- src/ipa/libipa/exposure_mode_helper.cpp | 19 ++++++++++++++++++- src/ipa/libipa/exposure_mode_helper.h | 11 ++++++++--- src/ipa/mali-c55/algorithms/agc.cpp | 10 ++++------ src/ipa/rkisp1/algorithms/agc.cpp | 16 +++++++--------- 7 files changed, 53 insertions(+), 31 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index ac5b6e7513..e74db62960 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -237,9 +237,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, double analogueGain = frameContext.sensor.gain; utils::Duration effectiveExposureValue = exposureTime * analogueGain; - utils::Duration newExposureTime; - double aGain, qGain, dGain; - std::tie(newExposureTime, aGain, qGain, dGain) = agc_.calculateNewEv({ + const auto &newEv = agc_.calculateNewEv({ .traits = AgcTraits{ rgbTriples_, {{ @@ -257,12 +255,12 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, LOG(IPU3Agc, Debug) << "Divided up exposure time, analogue gain and digital gain are " - << newExposureTime << ", " << aGain << " and " << dGain; + << newEv.exposureTime << ", " << newEv.analogueGain << " and " << newEv.digitalGain; IPAActiveState &activeState = context.activeState; /* Update the estimated exposure time and gain. */ - activeState.agc.exposure = newExposureTime / context.configuration.sensor.lineDuration; - activeState.agc.gain = aGain; + activeState.agc.exposure = newEv.exposureTime / context.configuration.sensor.lineDuration; + activeState.agc.gain = newEv.analogueGain; metadata.set(controls::AnalogueGain, frameContext.sensor.gain); metadata.set(controls::ExposureTime, exposureTime.get()); diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index e8679c66be..5e12c4b475 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -668,6 +668,11 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) * \brief The index of the exposure mode to use */ +/** + * \struct AgcMeanLuminance::Result + * \brief Collection of results of the mean luminance AGC algorithm + */ + /** * \brief Calculate the new exposure value and split it between exposure time * and gain @@ -680,7 +685,7 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) * \return Tuple of exposure time, analogue gain, quantization gain and digital * gain */ -std::tuple +AgcMeanLuminance::Result AgcMeanLuminance::calculateNewEv(const Params ¶ms) { /* @@ -699,7 +704,7 @@ AgcMeanLuminance::calculateNewEv(const Params ¶ms) * doesn't get stuck with 0 in case the sensor driver allows a * min exposure of 0. */ - return exposureModeHelper.splitExposure(10ms); + return { exposureModeHelper.splitExposure(10ms) }; } double gain = estimateInitialGain(params.traits); @@ -721,7 +726,7 @@ AgcMeanLuminance::calculateNewEv(const Params ¶ms) newExposureValue = filterExposure(newExposureValue); frameCount_++; - return exposureModeHelper.splitExposure(newExposureValue); + return { exposureModeHelper.splitExposure(newExposureValue) }; } /** diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index 9b82d97ab6..aa4c0369ac 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -9,7 +9,6 @@ #include #include -#include #include #include @@ -87,8 +86,10 @@ public: uint32_t exposureModeIndex; }; - std::tuple - calculateNewEv(const Params ¶ms); + struct Result : ExposureModeHelper::Result { + }; + + [[nodiscard]] Result calculateNewEv(const Params ¶ms); double effectiveYTarget() const; diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index 01c5cba8e2..762e27b4fd 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -153,6 +153,23 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons return clamped; } +/** + * \struct ExposureModeHelper::Result + * \brief Result of splitExposure() + * + * \var ExposureModeHelper::Result::exposureTime + * \brief The applicable exposure time + * + * \var ExposureModeHelper::Result::analogueGain + * \brief The applicable analogue gain + * + * \var ExposureModeHelper::Result::quantizationGain + * \brief The applicable quantization gain + * + * \var ExposureModeHelper::Result::digitalGain + * \brief The applicable digital gain + */ + /** * \brief Split exposure into exposure time and gain * \param[in] exposure Exposure value @@ -190,7 +207,7 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons * \return Tuple of exposure time, analogue gain, quantization gain and digital * gain */ -std::tuple +ExposureModeHelper::Result ExposureModeHelper::splitExposure(utils::Duration exposure) const { ASSERT(maxExposureTime_); diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h index 968192ddc5..2bab20f974 100644 --- a/src/ipa/libipa/exposure_mode_helper.h +++ b/src/ipa/libipa/exposure_mode_helper.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include @@ -30,8 +29,14 @@ public: void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, double minGain, double maxGain); - std::tuple - splitExposure(utils::Duration exposure) const; + struct Result { + utils::Duration exposureTime; + double analogueGain; + double quantizationGain; + double digitalGain; + }; + + [[nodiscard]] Result splitExposure(utils::Duration exposure) const; utils::Duration minExposureTime() const { return minExposureTime_; } utils::Duration maxExposureTime() const { return maxExposureTime_; } diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index fb97b87c6e..821f43e9fd 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -332,9 +332,7 @@ void Agc::process(IPAContext &context, utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; utils::Duration effectiveExposureValue = currentShutter * analogueGain; - utils::Duration shutterTime; - double aGain, qGain, dGain; - std::tie(shutterTime, aGain, qGain, dGain) = agc_.calculateNewEv({ + const auto &newEv = agc_.calculateNewEv({ .traits = AgcTraits(statistics_), .yHist = statistics_.yHist, .effectiveExposureValue = effectiveExposureValue, @@ -344,10 +342,10 @@ void Agc::process(IPAContext &context, LOG(MaliC55Agc, Debug) << "Divided up shutter, analogue gain and digital gain are " - << shutterTime << ", " << aGain << " and " << dGain; + << newEv.exposureTime << ", " << newEv.analogueGain << " and " << newEv.digitalGain; - activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration; - activeState.agc.automatic.sensorGain = aGain; + activeState.agc.automatic.exposure = newEv.exposureTime / configuration.sensor.lineDuration; + activeState.agc.automatic.sensorGain = newEv.analogueGain; metadata.set(controls::ExposureTime, currentShutter.get()); metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain); diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 8b4bcbd71b..a4567c8258 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -720,9 +720,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, agc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); agc_.setLux(frameContext.lux.lux); - utils::Duration newExposureTime; - double aGain, qGain, dGain; - std::tie(newExposureTime, aGain, qGain, dGain) = agc_.calculateNewEv({ + const auto &newEv = agc_.calculateNewEv({ .traits = AgcTraits{ { params->ae.exp_mean, context.hw.numAeCells }, meteringModes_.at(frameContext.agc.meteringMode), @@ -735,21 +733,21 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, LOG(RkISP1Agc, Debug) << "Divided up exposure time, analogue gain, quantization gain" - << " and digital gain are " << newExposureTime << ", " << aGain - << ", " << qGain << " and " << dGain; + << " and digital gain are " << newEv.exposureTime << ", " << newEv.analogueGain + << ", " << newEv.quantizationGain << " and " << newEv.digitalGain; IPAActiveState &activeState = context.activeState; /* Update the estimated exposure and gain. */ - activeState.agc.automatic.exposure = newExposureTime / lineDuration; - activeState.agc.automatic.gain = aGain; - activeState.agc.automatic.quantizationGain = qGain; + activeState.agc.automatic.exposure = newEv.exposureTime / lineDuration; + activeState.agc.automatic.gain = newEv.analogueGain; + activeState.agc.automatic.quantizationGain = newEv.quantizationGain; activeState.agc.automatic.yTarget = agc_.effectiveYTarget(); /* * Expand the target frame duration so that we do not run faster than * the minimum frame duration when we have short exposures. */ processFrameDuration(context, frameContext, - std::max(frameContext.agc.minFrameDuration, newExposureTime)); + std::max(frameContext.agc.minFrameDuration, newEv.exposureTime)); fillMetadata(context, frameContext, metadata); } From patchwork Thu Jul 23 15:43:00 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: 27466 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 9C607C3264 for ; Thu, 23 Jul 2026 15:43:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1D54767F34; Thu, 23 Jul 2026 17:43:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ZAMHij6Q"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AC3D767EDF for ; Thu, 23 Jul 2026 17:43:34 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C70442C09 for ; Thu, 23 Jul 2026 17:42:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821353; bh=DQGwG8eoyhaSWWz/WQAxWjnwUjJ8Y4fCtHJ3VCV+bEY=; h=From:To:Subject:Date:In-Reply-To:References:From; b=ZAMHij6Q8KkHc/Qc8c5bef0x9wyCAdvqsFt6lMQ4eqSQm9LmaoNP4xHXF9k6290rT YbG1h+hCCfJZ2Q9F/urYwR2OZNogZ/HGGpobjUf+RL0PLzR27pFcA/hDw1zpreyWeR fB0plxz1lHeh3NM/d4ffZkIKVGYp9XRMezjeOgYc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 17/43] ipa: libipa: agc_mean_luminance: calculateNewEv(): Return y target Date: Thu, 23 Jul 2026 17:43:00 +0200 Message-ID: <20260723154327.1357866-18-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Return the current applicable y target as part of the result set. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_mean_luminance.cpp | 13 ++++++++----- src/ipa/libipa/agc_mean_luminance.h | 3 ++- src/ipa/rkisp1/algorithms/agc.cpp | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 5e12c4b475..d25b1e8322 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -502,9 +502,8 @@ void AgcMeanLuminance::setLimits(utils::Duration minExposureTime, * target * \return The calculated initial gain */ -double AgcMeanLuminance::estimateInitialGain(const Traits &traits) const +double AgcMeanLuminance::estimateInitialGain(const Traits &traits, double yTarget) const { - double yTarget = effectiveYTarget(); double yGain = 1.0; /* @@ -671,6 +670,9 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) /** * \struct AgcMeanLuminance::Result * \brief Collection of results of the mean luminance AGC algorithm + * + * \var AgcMeanLuminance::Result::yTarget + * \brief The current y target including exposure compensation */ /** @@ -694,6 +696,7 @@ AgcMeanLuminance::calculateNewEv(const Params ¶ms) */ ExposureModeHelper &exposureModeHelper = exposureModeHelpers_.at(params.exposureModeIndex); + double yTarget = effectiveYTarget(); if (params.effectiveExposureValue == 0s) { LOG(AgcMeanLuminance, Error) @@ -704,10 +707,10 @@ AgcMeanLuminance::calculateNewEv(const Params ¶ms) * doesn't get stuck with 0 in case the sensor driver allows a * min exposure of 0. */ - return { exposureModeHelper.splitExposure(10ms) }; + return { exposureModeHelper.splitExposure(10ms), yTarget }; } - double gain = estimateInitialGain(params.traits); + double gain = estimateInitialGain(params.traits, yTarget); gain = constraintClampGain(params.constraintModeIndex, params.yHist, gain); /* @@ -726,7 +729,7 @@ AgcMeanLuminance::calculateNewEv(const Params ¶ms) newExposureValue = filterExposure(newExposureValue); frameCount_++; - return { exposureModeHelper.splitExposure(newExposureValue) }; + return { exposureModeHelper.splitExposure(newExposureValue), yTarget }; } /** diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index aa4c0369ac..6418fe2e65 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -87,6 +87,7 @@ public: }; struct Result : ExposureModeHelper::Result { + double yTarget; }; [[nodiscard]] Result calculateNewEv(const Params ¶ms); @@ -103,7 +104,7 @@ private: int parseConstraint(const ValueNode &modeDict, int32_t id); int parseConstraintModes(const ValueNode &tuningData); int parseExposureModes(const ValueNode &tuningData); - double estimateInitialGain(const Traits &traits) const; + double estimateInitialGain(const Traits &traits, double yTarget) const; double constraintClampGain(uint32_t constraintModeIndex, const Histogram &hist, double gain) const; diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index a4567c8258..8ed9d7ea6d 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -741,7 +741,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, activeState.agc.automatic.exposure = newEv.exposureTime / lineDuration; activeState.agc.automatic.gain = newEv.analogueGain; activeState.agc.automatic.quantizationGain = newEv.quantizationGain; - activeState.agc.automatic.yTarget = agc_.effectiveYTarget(); + activeState.agc.automatic.yTarget = newEv.yTarget; /* * Expand the target frame duration so that we do not run faster than * the minimum frame duration when we have short exposures. From patchwork Thu Jul 23 15:43:01 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: 27467 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 EBE82C3303 for ; Thu, 23 Jul 2026 15:43:58 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7873867F3C; Thu, 23 Jul 2026 17:43:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="kgoShcmT"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E5FB467EE0 for ; Thu, 23 Jul 2026 17:43:34 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 10AB41C37 for ; Thu, 23 Jul 2026 17:42:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821354; bh=2gBIBSGVZe6CFk459kjlDy3CLc4okzrbkmA9hkrsVDY=; h=From:To:Subject:Date:In-Reply-To:References:From; b=kgoShcmTyS5HuuxbuHzzyAZ6RVX4Fm/Mm0GgjPVTkKyFPIOB9+Nkol6dZCSiV0zEW rRoUkP2nlVf3vs/AKEvj52YMD/XW6wBsmaZZYH4O6XTVeTmu4ECUFE0KdGkT/adXpw KxFIXEmHHEyyeYHuEYZ2tnD2+FlGyoBVBNqQDRvU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 18/43] ipa: libipa: agc_mean_luminance: Remove persistent params Date: Thu, 23 Jul 2026 17:43:01 +0200 Message-ID: <20260723154327.1357866-19-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Remove `set{Lux,ExposureCompensation}()` and pass them via `Params`. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_mean_luminance.cpp | 62 +++++++++++---------------- src/ipa/libipa/agc_mean_luminance.h | 20 ++------- src/ipa/rkisp1/algorithms/agc.cpp | 7 ++- 3 files changed, 31 insertions(+), 58 deletions(-) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index d25b1e8322..38c9588e46 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -178,8 +178,7 @@ static constexpr unsigned int kDefaultLuxLevel = 500; */ AgcMeanLuminance::AgcMeanLuminance() - : filteredExposure_(0s), luxWarningEnabled_(true), - exposureCompensation_(1.0), frameCount_(0), lux_(0) + : filteredExposure_(0s), luxWarningEnabled_(true), frameCount_(0) { } @@ -441,25 +440,6 @@ int AgcMeanLuminance::parseTuningData(const ValueNode &tuningData) return parseExposureModes(tuningData); } -/** - * \fn AgcMeanLuminance::setExposureCompensation() - * \brief Set the exposure compensation value - * \param[in] gain The exposure compensation gain - * - * This function sets the exposure compensation value to be used in the - * AGC calculations. It is expressed as gain instead of EV. - */ - -/** - * \fn AgcMeanLuminance::setLux(int lux) - * \brief Set the lux level - * \param[in] lux The lux level - * - * This function sets the lux level to be used in the AGC calculations. A value - * of 0 means no measurement and a default value of \a kDefaultLuxLevel is used - * if necessary. - */ - /** * \brief Set the ExposureModeHelper limits for this class * \param[in] minExposureTime Minimum exposure time to allow @@ -530,26 +510,23 @@ double AgcMeanLuminance::estimateInitialGain(const Traits &traits, double yTarge /** * \brief Clamp gain within the bounds of a defined constraint - * \param[in] constraintModeIndex The index of the constraint to adhere to - * \param[in] hist A histogram over which to calculate inter-quantile means + * \param[in] params The set of parameters for the calculation * \param[in] gain The gain to clamp * * \return The gain clamped within the constraint bounds */ -double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex, - const Histogram &hist, - double gain) const +double AgcMeanLuminance::constraintClampGain(const Params ¶ms, double gain) const { - auto applyConstraint = [this, &gain, &hist](const AgcConstraint &constraint) { - double lux = lux_; + auto applyConstraint = [&](const AgcConstraint &constraint) { + double lux = params.lux; - if (relativeLuminanceTarget_.size() > 1 && lux_ == 0) + if (relativeLuminanceTarget_.size() > 1 && lux == 0) lux = kDefaultLuxLevel; double target = constraint.yTarget.eval( constraint.yTarget.domain().clamp(lux)); - double newGain = target * hist.bins() / - hist.interQuantileMean(constraint.qLo, constraint.qHi); + double newGain = target * params.yHist.bins() / + params.yHist.interQuantileMean(constraint.qLo, constraint.qHi); if (constraint.bound == AgcConstraint::Bound::Lower && newGain > gain) { @@ -568,7 +545,7 @@ double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex, } }; - const std::vector &constraints = constraintModes_.at(constraintModeIndex); + const std::vector &constraints = constraintModes_.at(params.constraintModeIndex); std::for_each(constraints.begin(), constraints.end(), applyConstraint); std::for_each(additionalConstraints_.begin(), additionalConstraints_.end(), applyConstraint); @@ -578,15 +555,16 @@ double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex, /** * \brief Get the currently effective y target + * \param[in] lux The effective lux value + * \param[in] exposureCompensation The exposure compensation value * * This function returns the current y target including exposure compensation. * * \return The y target value */ -double AgcMeanLuminance::effectiveYTarget() const +double AgcMeanLuminance::effectiveYTarget(double lux, double exposureCompensation) const { - double lux = lux_; - if (relativeLuminanceTarget_.size() > 1 && lux_ == 0) { + if (relativeLuminanceTarget_.size() > 1 && lux == 0) { /* * Warn after a few frames if there is still no lux measurement * available. The number of 10 is chosen a bit arbitrarily. It @@ -610,7 +588,7 @@ double AgcMeanLuminance::effectiveYTarget() const double luminanceTarget = relativeLuminanceTarget_.eval( relativeLuminanceTarget_.domain().clamp(lux)); - return std::min(luminanceTarget * exposureCompensation_, + return std::min(luminanceTarget * exposureCompensation, kMaxRelativeLuminanceTarget); } @@ -665,6 +643,14 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) * * \var AgcMeanLuminance::Params::exposureModeIndex * \brief The index of the exposure mode to use + * + * \var AgcMeanLuminance::Params::lux + * \brief The lux level to be used in the AGC calculations. A value of 0 means no + * measurement and a default value of \a kDefaultLuxLevel is used if necessary. + * + * \var AgcMeanLuminance::Params::exposureCompensation + * \brief The exposure compensation value to be used in the AGC calculations. + * It is expressed as gain instead of EV. */ /** @@ -696,7 +682,7 @@ AgcMeanLuminance::calculateNewEv(const Params ¶ms) */ ExposureModeHelper &exposureModeHelper = exposureModeHelpers_.at(params.exposureModeIndex); - double yTarget = effectiveYTarget(); + double yTarget = effectiveYTarget(params.lux, params.exposureCompensation); if (params.effectiveExposureValue == 0s) { LOG(AgcMeanLuminance, Error) @@ -711,7 +697,7 @@ AgcMeanLuminance::calculateNewEv(const Params ¶ms) } double gain = estimateInitialGain(params.traits, yTarget); - gain = constraintClampGain(params.constraintModeIndex, params.yHist, gain); + gain = constraintClampGain(params, gain); /* * We don't check whether we're already close to the target, because diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index 6418fe2e65..5e986be008 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -50,16 +50,6 @@ public: void configure(utils::Duration lineDuration, const CameraSensorHelper *sensorHelper); int parseTuningData(const ValueNode &tuningData); - void setExposureCompensation(double gain) - { - exposureCompensation_ = gain; - } - - void setLux(unsigned int lux) - { - lux_ = lux; - } - void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, double minGain, double maxGain, std::vector constraints); @@ -84,6 +74,8 @@ public: utils::Duration effectiveExposureValue; uint32_t constraintModeIndex; uint32_t exposureModeIndex; + double lux = 0; + double exposureCompensation = 1; }; struct Result : ExposureModeHelper::Result { @@ -92,7 +84,7 @@ public: [[nodiscard]] Result calculateNewEv(const Params ¶ms); - double effectiveYTarget() const; + double effectiveYTarget(double lux, double exposureCompensation) const; void resetFrameCount() { @@ -105,17 +97,13 @@ private: int parseConstraintModes(const ValueNode &tuningData); int parseExposureModes(const ValueNode &tuningData); double estimateInitialGain(const Traits &traits, double yTarget) const; - double constraintClampGain(uint32_t constraintModeIndex, - const Histogram &hist, - double gain) const; + double constraintClampGain(const Params ¶ms, double gain) const; utils::Duration filterExposure(utils::Duration exposureValue); utils::Duration filteredExposure_; mutable bool luxWarningEnabled_; - double exposureCompensation_; Pwl relativeLuminanceTarget_; uint64_t frameCount_; - unsigned int lux_; std::vector additionalConstraints_; std::map> constraintModes_; diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 8ed9d7ea6d..fc228452c3 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -294,7 +294,7 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) context.configuration.sensor.minAnalogueGain, context.configuration.sensor.maxAnalogueGain, {}); - context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(); + context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(0, 1); agc_.resetFrameCount(); @@ -717,9 +717,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, [](uint32_t x) { return x >> 4; }); - agc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); - agc_.setLux(frameContext.lux.lux); - const auto &newEv = agc_.calculateNewEv({ .traits = AgcTraits{ { params->ae.exp_mean, context.hw.numAeCells }, @@ -729,6 +726,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, .effectiveExposureValue = effectiveExposureValue, .constraintModeIndex = frameContext.agc.constraintMode, .exposureModeIndex = frameContext.agc.exposureMode, + .lux = frameContext.lux.lux, + .exposureCompensation = pow(2.0, frameContext.agc.exposureValue), }); LOG(RkISP1Agc, Debug) From patchwork Thu Jul 23 15:43:02 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: 27468 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 A9F23C32CE for ; Thu, 23 Jul 2026 15:44:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 09FA567F3E; Thu, 23 Jul 2026 17:44:00 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="X7Y8gMOh"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3266967EE1 for ; Thu, 23 Jul 2026 17:43:35 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 486A02C09 for ; Thu, 23 Jul 2026 17:42:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821354; bh=ZmRmqsBV/dTO9FIhf9w7o0CSyuSGyjpU7zMdQM1i8Eo=; h=From:To:Subject:Date:In-Reply-To:References:From; b=X7Y8gMOhH/e6QdQCt/SYTzO6evzoDzE0qmgwC/wQZZBiMwBTep7XGP1h89J7CzwKI DgXeFDUJKB7wwYDLT1Jt66oSmbJsFmdJkYcftHloWouiFKNy+EeQqFHpA3tr117f6C cooVIBviuxx6V+NYTb49XfUcKiy+Dmmk6cL5vEeo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 19/43] ipa: libipa: agc_mean_luminance: Reset `filteredExposure_` when configuring Date: Thu, 23 Jul 2026 17:43:02 +0200 Message-ID: <20260723154327.1357866-20-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Reset `filteredExposure_` to 0 when `configure()` is called so that the current run is not affected by previous ones. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_mean_luminance.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 38c9588e46..a40b34b725 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -368,6 +368,7 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration, helper.configure(lineDuration, sensorHelper); luxWarningEnabled_ = true; + filteredExposure_ = 0s; } /** From patchwork Thu Jul 23 15:43:03 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: 27470 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 411FFC32FE for ; Thu, 23 Jul 2026 15:44:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9D69567F43; Thu, 23 Jul 2026 17:44:04 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="evWBDo43"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5DE6667ED2 for ; Thu, 23 Jul 2026 17:43:35 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8460E1C37 for ; Thu, 23 Jul 2026 17:42:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821354; bh=pvOoEl6K/zN1hhJVV5+pVpjqvC1d+U2qEwMpNAFizvs=; h=From:To:Subject:Date:In-Reply-To:References:From; b=evWBDo439+YpzLgjnkZ6ap45SpZq1CwBtmKsGsrmrYKf9F6biraUN+MjPgLmh9RTq z2VtwEbWWWPzco6sZsNFxJQe8lhLfguuNsoIELtnseZzX+4z/o834JiNY0d4j9p9dZ Z6fSTMgLwCJlCvkrzoe7CdE7a69NtqMTpmTY3Sbc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 20/43] ipa: Simplify sensor exposure/gain setting/getting Date: Thu, 23 Jul 2026 17:43:03 +0200 Message-ID: <20260723154327.1357866-21-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Move the extraction and preparation of `V4L2_CID_{EXPOSURE,ANALOGUE_GAIN}` into separate functions. This also implements support for not having a a camera sensor helper. Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/ipu3.cpp | 14 +++++----- src/ipa/libipa/agc.h | 49 ++++++++++++++++++++++++++++++++++ src/ipa/libipa/meson.build | 1 + src/ipa/mali-c55/mali-c55.cpp | 17 +++++------- src/ipa/rkisp1/rkisp1.cpp | 11 +++----- src/ipa/simple/soft_simple.cpp | 15 +++-------- 6 files changed, 72 insertions(+), 35 deletions(-) create mode 100644 src/ipa/libipa/agc.h diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp index 4bdc4b7677..51379808cd 100644 --- a/src/ipa/ipu3/ipu3.cpp +++ b/src/ipa/ipu3/ipu3.cpp @@ -36,6 +36,7 @@ #include "libcamera/internal/mapped_framebuffer.h" #include "libcamera/internal/yaml_parser.h" +#include "libipa/agc.h" #include "libipa/camera_sensor_helper.h" #include "ipa_context.h" @@ -596,8 +597,8 @@ void IPAIPU3::processStats(const uint32_t frame, IPAFrameContext &frameContext = context_.frameContexts.get(frame); - frameContext.sensor.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get(); - frameContext.sensor.gain = camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get()); + std::tie(frameContext.sensor.exposure, frameContext.sensor.gain) + = agc::extractControls(sensorControls, camHelper_.get()); ControlList metadata(controls::controls); @@ -642,12 +643,11 @@ void IPAIPU3::queueRequest(const uint32_t frame, const ControlList &controls) */ void IPAIPU3::setControls(unsigned int frame) { - int32_t exposure = context_.activeState.agc.exposure; - int32_t gain = camHelper_->gainCode(context_.activeState.agc.gain); - ControlList ctrls(sensorCtrls_); - ctrls.set(V4L2_CID_EXPOSURE, exposure); - ctrls.set(V4L2_CID_ANALOGUE_GAIN, gain); + agc::prepareControls( + ctrls, camHelper_.get(), + context_.activeState.agc.exposure, context_.activeState.agc.gain + ); ControlList lensCtrls(lensCtrls_); lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h new file mode 100644 index 0000000000..5247425952 --- /dev/null +++ b/src/ipa/libipa/agc.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * AGC-related functionality + */ + +#pragma once + +#include + +#include + +#include + +#include "camera_sensor_helper.h" + +namespace libcamera { + +namespace ipa { + +namespace agc { + +[[nodiscard]] +inline std::pair +extractControls(const ControlList &controls, const CameraSensorHelper *sensor) +{ + auto exposure = controls.get(V4L2_CID_EXPOSURE).get(); + auto gainCode = controls.get(V4L2_CID_ANALOGUE_GAIN).get(); + + return { + uint32_t(exposure), + sensor ? sensor->gain(gainCode) : gainCode, + }; +} + +inline void +prepareControls(ControlList &controls, const CameraSensorHelper *sensor, + int32_t exposure, double gain) +{ + controls.set(V4L2_CID_EXPOSURE, exposure); + controls.set(V4L2_CID_ANALOGUE_GAIN, int32_t(sensor ? sensor->gainCode(gain) : gain)); +} + +} /* namespace agc */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 963c5ee730..05f1a8749c 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 libipa_headers = files([ + 'agc.h', 'agc_mean_luminance.h', 'algorithm.h', 'awb_bayes.h', diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 1d3af0627f..65e5297766 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -27,6 +27,7 @@ #include "libcamera/internal/yaml_parser.h" #include "algorithms/algorithm.h" +#include "libipa/agc.h" #include "libipa/camera_sensor_helper.h" #include "ipa_context.h" @@ -141,20 +142,18 @@ void IPAMaliC55::setControls() { IPAActiveState &activeState = context_.activeState; uint32_t exposure; - uint32_t gain; + double gain; if (activeState.agc.autoEnabled) { exposure = activeState.agc.automatic.exposure; - gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain); + gain = activeState.agc.automatic.sensorGain; } else { exposure = activeState.agc.manual.exposure; - gain = camHelper_->gainCode(activeState.agc.manual.sensorGain); + gain = activeState.agc.manual.sensorGain; } ControlList ctrls(sensorControls_); - ctrls.set(V4L2_CID_EXPOSURE, static_cast(exposure)); - ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast(gain)); - + agc::prepareControls(ctrls, camHelper_.get(), exposure, gain); setSensorControls.emit(ctrls); } @@ -352,10 +351,8 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, stats = reinterpret_cast( buffers_.at(bufferId).planes()[0].data()); - frameContext.agc.exposure = - sensorControls.get(V4L2_CID_EXPOSURE).get(); - frameContext.agc.sensorGain = - camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get()); + std::tie(frameContext.agc.exposure, frameContext.agc.sensorGain) + = agc::extractControls(sensorControls, camHelper_.get()); ControlList metadata(controls::controls); diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 38f55b1d86..acb878d814 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -31,6 +31,7 @@ #include "libcamera/internal/yaml_parser.h" #include "algorithms/algorithm.h" +#include "libipa/agc.h" #include "ipa_context.h" #include "params.h" @@ -328,10 +329,8 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId, stats = reinterpret_cast( mappedBuffers_.at(bufferId).planes()[0].data()); - frameContext.sensor.exposure = - sensorControls.get(V4L2_CID_EXPOSURE).get(); - frameContext.sensor.gain = - context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get()); + std::tie(frameContext.sensor.exposure, frameContext.sensor.gain) + = agc::extractControls(sensorControls, context_.camHelper.get()); ControlList metadata(controls::controls); @@ -365,7 +364,6 @@ void IPARkISP1::setControls(unsigned int frame) IPAFrameContext &frameContext = context_.frameContexts.get(frame); uint32_t exposure = frameContext.agc.exposure; - uint32_t gain = context_.camHelper->gainCode(frameContext.agc.gain); uint32_t vblank = frameContext.agc.vblank; LOG(IPARkISP1, Debug) @@ -373,8 +371,7 @@ void IPARkISP1::setControls(unsigned int frame) << ", gain " << frameContext.agc.gain << ", vblank " << vblank; ControlList ctrls(context_.sensorControls); - ctrls.set(V4L2_CID_EXPOSURE, static_cast(exposure)); - ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast(gain)); + agc::prepareControls(ctrls, context_.camHelper.get(), exposure, frameContext.agc.gain); ctrls.set(V4L2_CID_VBLANK, static_cast(vblank)); setSensorControls.emit(frame, ctrls); diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 3932daaa1f..a38fcff7d8 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -27,6 +27,7 @@ #include "libcamera/internal/yaml_parser.h" #include "algorithms/adjust.h" +#include "libipa/agc.h" #include "libipa/camera_sensor_helper.h" #include "module.h" @@ -301,10 +302,8 @@ void IPASoftSimple::processStats(const uint32_t frame, { IPAFrameContext &frameContext = context_.frameContexts.get(frame); - frameContext.sensor.exposure = - sensorControls.get(V4L2_CID_EXPOSURE).get(); - int32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get(); - frameContext.sensor.gain = camHelper_ ? camHelper_->gain(again) : again; + std::tie(frameContext.sensor.exposure, frameContext.sensor.gain) + = agc::extractControls(sensorControls, camHelper_.get()); ControlList metadata(controls::controls); for (const auto &algo : algorithms()) @@ -312,13 +311,7 @@ void IPASoftSimple::processStats(const uint32_t frame, metadataReady.emit(frame, metadata); ControlList ctrls(sensorInfoMap_); - - int32_t againNew = camHelper_ - ? camHelper_->gainCode(frameContext.agc.gain) - : static_cast(frameContext.agc.gain); - ctrls.set(V4L2_CID_EXPOSURE, frameContext.agc.exposure); - ctrls.set(V4L2_CID_ANALOGUE_GAIN, againNew); - + agc::prepareControls(ctrls, camHelper_.get(), frameContext.agc.exposure, frameContext.agc.gain); setSensorControls.emit(ctrls); } From patchwork Thu Jul 23 15:43:04 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: 27469 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 C9793BE080 for ; Thu, 23 Jul 2026 15:44:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1E6EF67F2C; Thu, 23 Jul 2026 17:44:02 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QSur8nlO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B20CB67EE4 for ; Thu, 23 Jul 2026 17:43:35 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B89F92C09 for ; Thu, 23 Jul 2026 17:42:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821354; bh=EZFWPuML0YM7g/SywwmeTfYry8mjSRCAcmXzWCF01n8=; h=From:To:Subject:Date:In-Reply-To:References:From; b=QSur8nlO0CRYdzQqtOfrDgvM/gJeTIIl32P55Mlu9XNE+jV4LTnVX9shBfCcinrH1 p5LC5h4/8KkaiARUt+WTLC7FiYMtcFJgp+4KJXI+f9opG7oW8v4QOi/4xnB2Hl8ebf zGxx7mJCa3mQtJH6FYVuFj4f6Y9YIURL2TqdSea0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 21/43] ipa: libipa: Add `AgcAlgorithm` Date: Thu, 23 Jul 2026 17:43:04 +0200 Message-ID: <20260723154327.1357866-22-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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 a class that implements the `Algorithm` interface using `AgcMeanLuminance` based on the rkisp1 `Agc` algorithm, with slight adjustments. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 639 +++++++++++++++++++++++++++++++++++++ src/ipa/libipa/agc.h | 98 ++++++ src/ipa/libipa/meson.build | 1 + 3 files changed, 738 insertions(+) create mode 100644 src/ipa/libipa/agc.cpp diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp new file mode 100644 index 0000000000..e16a02fdde --- /dev/null +++ b/src/ipa/libipa/agc.cpp @@ -0,0 +1,639 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, 2026 Ideas On Board + * + * Generic AGC algorithm + */ + +#include "agc.h" + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +namespace libcamera { + +namespace ipa { + +using namespace std::chrono_literals; + +LOG_DEFINE_CATEGORY(Agc) + +/** + * \class AgcAlgorithm + * \brief AgcMeanLuminance wrapper for implementing the Algorithm interface + * + * \todo DigitalGain, DigitalGainMode + */ + +/** + * \struct agc::Session + * \brief Session configuration for AgcAlgorithm + * + * \var agc::Session::minExposureTime + * \brief Minimum exposure time supported with the configured sensor + * + * \var agc::Session::maxExposureTime + * \brief Maximum exposure time supported with the configured sensor + * + * \var agc::Session::minAnalogueGain + * \brief Minimum analogue gain supported with the configured sensor + * + * \var agc::Session::maxAnalogueGain + * \brief Maximum analogue gain supported with the configured sensor + * + * \var agc::Session::minFrameDuration + * \brief Minimum frame duration supported with the configured sensor + * + * \var agc::Session::maxFrameDuration + * \brief Maximum frame duration supported with the configured sensor + * + * \var agc::Session::lineDuration + * \brief Line duration with the configured sensor and output size + * + * \var agc::Session::sensor + * \brief Details of the sensor configuration + * + * \var agc::Session::sensor.outputSize + * \brief Configured output size of the sensor + * + * \var agc::Session::autoAllowed + * \brief Whether automatic controls are allowed + */ + +/** + * \struct agc::ActiveState + * \brief Active state for AgcAlgorithm + * + * The \a automatic variables track the latest values computed by algorithm + * based on the latest processed statistics. All other variables track the + * consolidated controls requested in queued requests. + * + * \var agc::ActiveState::manual + * \brief Manual exposure time and analog gain (set through requests) + * + * \var agc::ActiveState::manual.exposure + * \brief Manual exposure time expressed as a number of lines as set by the + * ExposureTime control + * + * \var agc::ActiveState::manual.gain + * \brief Manual analogue gain as set by the AnalogueGain control + * + * \var agc::ActiveState::automatic + * \brief Automatic exposure time and analog gain (computed by the algorithm) + * + * \var agc::ActiveState::automatic.exposure + * \brief Automatic exposure time expressed as a number of lines + * + * \var agc::ActiveState::automatic.gain + * \brief Automatic analogue gain multiplier + * + * \var agc::ActiveState::automatic.quantizationGain + * \brief Automatic quantization gain multiplier + * + * \var agc::ActiveState::automatic.yTarget + * \brief Automatically determined luminance target + * + * \var agc::ActiveState::autoExposureEnabled + * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control + * + * \var agc::ActiveState::autoGainEnabled + * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control + * + * \var agc::ActiveState::exposureValue + * \brief Exposure value as set by the ExposureValue control + * + * \var agc::ActiveState::constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var agc::ActiveState::exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var agc::ActiveState::minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var agc::ActiveState::maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + */ + +/** + * \struct agc::FrameContext + * \brief Per-frame context for AgcAlgorithm + * + * \var agc::FrameContext::exposure + * \brief Exposure time expressed as a number of lines computed by the algorithm + * + * \var agc::FrameContext::gain + * \brief Analogue gain multiplier computed by the algorithm + * + * The gain should be adapted to the sensor specific gain code before applying. + * + * \var agc::FrameContext::quantizationGain + * \brief Quantization gain multiplier computed by the algorithm + * + * \var agc::FrameContext::exposureValue + * \brief Exposure value as set by the ExposureValue control + * + * \var agc::FrameContext::yTarget + * \brief Luminance target computed by the algorithm + * + * \var agc::FrameContext::vblank + * \brief Vertical blanking parameter computed by the algorithm + * + * \var agc::FrameContext::autoExposureEnabled + * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control + * + * \var agc::FrameContext::autoGainEnabled + * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control + * + * \var agc::FrameContext::constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var agc::FrameContext::exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var agc::FrameContext::minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var agc::FrameContext::maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + * + * \var agc::FrameContext::frameDuration + * \brief The actual FrameDuration used by the algorithm for the frame + * + * \var agc::FrameContext::autoExposureModeChange + * \brief Indicate if autoExposureEnabled has changed from true in the previous + * frame to false in the current frame, and no manual exposure value has been + * supplied in the current frame. + * + * \var agc::FrameContext::autoGainModeChange + * \brief Indicate if autoGainEnabled has changed from true in the previous + * frame to false in the current frame, and no manual gain value has been + * supplied in the current frame. + */ + +/** + * \struct AgcAlgorithm::ConfigurationParams + * \brief Parameters for AgcAlgorithm::configure() + * + * \var AgcAlgorithm::ConfigurationParams::sensor + * \brief CameraSensorHelper for the sensor + * + * \var AgcAlgorithm::ConfigurationParams::sensorInfo + * \brief Details of the sensor + * + * \var AgcAlgorithm::ConfigurationParams::sensorControls + * \brief ControlInfoMap of the sensor + * + * \var AgcAlgorithm::ConfigurationParams::ctrlMap + * \brief ControlMap to update with controls + * + * \var AgcAlgorithm::ConfigurationParams::autoAllowed + * \brief Whether to enable auto controls + */ + +/** + * \struct AgcAlgorithm::ProcessParams + * \brief Parameters for AgcAlgorithm::process() + * + * \var AgcAlgorithm::ProcessParams::traits + * \brief Implementation of AgcMeanLuminance::Traits + * + * \var AgcAlgorithm::ProcessParams::yHist + * \brief Luminance histogram of the frame + * + * \var AgcAlgorithm::ProcessParams::exposure + * \brief Effective exposure of the frame + * + * \var AgcAlgorithm::ProcessParams::gain + * \brief Effective gain of the frame + * + * \var AgcAlgorithm::ProcessParams::additionalConstraints + * \brief Additional AgcMeanLuminance::AgcConstraints to apply + * + * \var AgcAlgorithm::ProcessParams::lux + * \brief Effective lux value of the frame + */ + +/** + * \brief Load tuning data + */ +int AgcAlgorithm::init(const ValueNode &tuningData) +{ + int ret = impl_.parseTuningData(tuningData); + if (ret) + return ret; + + return 0; +} + +/** + * \brief Initialize the session configuration and active state + */ +int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, const ConfigurationParams &config) +{ + session = {}; + session.lineDuration = config.sensorInfo.minLineLength * 1.0s + / config.sensorInfo.pixelRate; + session.sensor.outputSize = config.sensorInfo.outputSize; + session.autoAllowed = config.autoAllowed; + + const double lineDurationUs = session.lineDuration.get(); + + /* + * Compute exposure time limits from the V4L2_CID_EXPOSURE control + * limits and the line duration. + */ + + const ControlInfo &v4l2Exposure = config.sensorControls.find(V4L2_CID_EXPOSURE)->second; + int32_t minExposure = v4l2Exposure.min().get(); + int32_t maxExposure = v4l2Exposure.max().get(); + int32_t defExposure = v4l2Exposure.def().get(); + config.ctrlMap[&controls::ExposureTime] = ControlInfo{ + static_cast(minExposure * lineDurationUs), + static_cast(maxExposure * lineDurationUs), + static_cast(defExposure * lineDurationUs), + }; + + /* Compute the analogue gain limits. */ + const ControlInfo &v4l2Gain = config.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + float minGain = config.sensor->gain(v4l2Gain.min().get()); + float maxGain = config.sensor->gain(v4l2Gain.max().get()); + float defGain = config.sensor->gain(v4l2Gain.def().get()); + config.ctrlMap[&controls::AnalogueGain] = ControlInfo{ + minGain, + maxGain, + defGain, + }; + + LOG(Agc, Debug) + << "Exposure: [" << minExposure << ", " << maxExposure + << "], gain: [" << minGain << ", " << maxGain << "]"; + + /* + * Compute the frame duration limits. + * + * The frame length is computed assuming a fixed line length combined + * with the vertical frame sizes. + */ + const ControlInfo &v4l2HBlank = config.sensorControls.find(V4L2_CID_HBLANK)->second; + uint32_t hblank = v4l2HBlank.def().get(); + uint32_t lineLength = config.sensorInfo.outputSize.width + hblank; + + const ControlInfo &v4l2VBlank = config.sensorControls.find(V4L2_CID_VBLANK)->second; + std::array frameHeights{ + v4l2VBlank.min().get() + config.sensorInfo.outputSize.height, + v4l2VBlank.max().get() + config.sensorInfo.outputSize.height, + v4l2VBlank.def().get() + config.sensorInfo.outputSize.height, + }; + + std::array frameDurations; + for (unsigned int i = 0; i < frameHeights.size(); ++i) { + uint64_t frameSize = lineLength * frameHeights[i]; + frameDurations[i] = frameSize / (config.sensorInfo.pixelRate / 1000000U); + } + + config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ + frameDurations[0], + frameDurations[1], + Span{ { frameDurations[2], frameDurations[2] } }, + }; + + session.minFrameDuration = std::chrono::microseconds(frameDurations[0]); + session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]); + + /* + * When the AGC computes the new exposure values for a frame, it needs + * to know the limits for exposure time and analogue gain. As it depends + * on the sensor, update it with the controls. + * + * \todo take VBLANK into account for maximum exposure time + */ + session.minExposureTime = minExposure * session.lineDuration; + session.maxExposureTime = maxExposure * session.lineDuration; + session.minAnalogueGain = minGain; + session.maxAnalogueGain = maxGain; + + impl_.configure(session.lineDuration, config.sensor); + impl_.setLimits(session.minExposureTime, session.maxExposureTime, + session.minAnalogueGain, session.maxAnalogueGain, + {}); + impl_.resetFrameCount(); + + /* Configure the default exposure and gain. */ + state = {}; + state.automatic.gain = session.minAnalogueGain; + state.automatic.exposure = 10ms / session.lineDuration; + state.automatic.quantizationGain = 1; + state.automatic.yTarget = impl_.effectiveYTarget(0, 1); + state.manual.gain = state.automatic.gain; + state.manual.exposure = state.automatic.exposure; + state.autoExposureEnabled = session.autoAllowed; + state.autoGainEnabled = session.autoAllowed; + state.exposureValue = 0; + + state.constraintMode = + static_cast(impl_.constraintModes().begin()->first); + state.exposureMode = + static_cast(impl_.exposureModeHelpers().begin()->first); + + state.minFrameDuration = session.minFrameDuration; + state.maxFrameDuration = session.maxFrameDuration; + + const auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) { + std::array values; + size_t count = 0; + + if (session.autoAllowed) + values[count++] = ControlValue(automatic); + + values[count++] = ControlValue(manual); + + config.ctrlMap[&cid] = ControlInfo{ + { values.data(), count }, + ControlValue(session.autoAllowed ? automatic : manual), + }; + }; + + add(controls::ExposureTimeMode, controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual); + add(controls::AnalogueGainMode, controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual); + + /* \todo Move this to the `Camera` class. */ + config.ctrlMap[&controls::AeEnable] = ControlInfo{ + false, + session.autoAllowed, + session.autoAllowed, + }; + + if (session.autoAllowed) { + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); + + for (const auto &[id, info] : impl_.controls()) + config.ctrlMap[id] = info; + } else { + config.ctrlMap.erase(&controls::ExposureValue); + + for (const auto &[id, info] : impl_.controls()) + config.ctrlMap.erase(id); + } + + + return 0; +} + +/** + * \brief Handle a \a queueRequest operation + */ +void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext, const ControlList &controls) +{ + if (session.autoAllowed) { + const auto &aeEnable = controls.get(controls::ExposureTimeMode); + if (aeEnable && + (*aeEnable == controls::ExposureTimeModeAuto) != state.autoExposureEnabled) { + state.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); + + LOG(Agc, Debug) + << (state.autoExposureEnabled ? "Enabling" : "Disabling") + << " AGC (exposure)"; + + /* + * If we go from auto -> manual with no manual control + * set, use the last computed value, which we don't + * know until prepare() so save this information. + * + * \todo Check the previous frame at prepare() time + * instead of saving a flag here + */ + if (!state.autoExposureEnabled && !controls.get(controls::ExposureTime)) + frameContext.autoExposureModeChange = true; + } + + const auto &agEnable = controls.get(controls::AnalogueGainMode); + if (agEnable && + (*agEnable == controls::AnalogueGainModeAuto) != state.autoGainEnabled) { + state.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); + + LOG(Agc, Debug) + << (state.autoGainEnabled ? "Enabling" : "Disabling") + << " AGC (gain)"; + /* + * If we go from auto -> manual with no manual control + * set, use the last computed value, which we don't + * know until prepare() so save this information. + */ + if (!state.autoGainEnabled && !controls.get(controls::AnalogueGain)) + frameContext.autoGainModeChange = true; + } + } + + const auto &exposure = controls.get(controls::ExposureTime); + if (exposure && !state.autoExposureEnabled) { + state.manual.exposure = *exposure * 1.0us / session.lineDuration; + + LOG(Agc, Debug) + << "Set exposure to " << state.manual.exposure; + } + + const auto &gain = controls.get(controls::AnalogueGain); + if (gain && !state.autoGainEnabled) { + state.manual.gain = *gain; + + LOG(Agc, Debug) << "Set gain to " << state.manual.gain; + } + + frameContext.autoExposureEnabled = state.autoExposureEnabled; + frameContext.autoGainEnabled = state.autoGainEnabled; + + if (!frameContext.autoExposureEnabled) + frameContext.exposure = state.manual.exposure; + if (!frameContext.autoGainEnabled) + frameContext.gain = state.manual.gain; + + if (!frameContext.autoExposureEnabled && + !frameContext.autoGainEnabled) + frameContext.quantizationGain = 1.0; + + const auto &exposureMode = controls.get(controls::AeExposureMode); + if (exposureMode) + state.exposureMode = + static_cast(*exposureMode); + frameContext.exposureMode = state.exposureMode; + + const auto &constraintMode = controls.get(controls::AeConstraintMode); + if (constraintMode) + state.constraintMode = + static_cast(*constraintMode); + frameContext.constraintMode = state.constraintMode; + + const auto &exposureValue = controls.get(controls::ExposureValue); + if (exposureValue) + state.exposureValue = *exposureValue; + frameContext.exposureValue = state.exposureValue; + + const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); + if (frameDurationLimits) { + /* Limit the control value to the limits in ControlInfo */ + state.minFrameDuration = std::clamp( + std::chrono::microseconds((*frameDurationLimits).front()), + session.minFrameDuration, + session.maxFrameDuration + ); + + state.maxFrameDuration = std::clamp( + std::chrono::microseconds((*frameDurationLimits).back()), + session.minFrameDuration, + session.maxFrameDuration + ); + } + frameContext.minFrameDuration = state.minFrameDuration; + frameContext.maxFrameDuration = state.maxFrameDuration; +} + +/** + * \brief Handle a \a prepare operation + */ +void AgcAlgorithm::prepare(agc::ActiveState &state, agc::FrameContext &frameContext) +{ + uint32_t activeAutoExposure = state.automatic.exposure; + double activeAutoGain = state.automatic.gain; + double activeAutoQGain = state.automatic.quantizationGain; + + /* Populate exposure and gain in auto mode */ + if (frameContext.autoExposureEnabled) { + frameContext.exposure = activeAutoExposure; + frameContext.quantizationGain = activeAutoQGain; + } + if (frameContext.autoGainEnabled) { + frameContext.gain = activeAutoGain; + frameContext.quantizationGain = activeAutoQGain; + } + + /* + * Populate manual exposure and gain from the active auto values when + * transitioning from auto to manual + */ + if (!frameContext.autoExposureEnabled && frameContext.autoExposureModeChange) { + state.manual.exposure = activeAutoExposure; + frameContext.exposure = activeAutoExposure; + } + if (!frameContext.autoGainEnabled && frameContext.autoGainModeChange) { + state.manual.gain = activeAutoGain; + frameContext.gain = activeAutoGain; + frameContext.quantizationGain = activeAutoQGain; + } + + frameContext.yTarget = state.automatic.yTarget; +} + +/** + * \brief Handle a \a process operation + */ +void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext, std::optional &¶ms, + ControlList &metadata) +{ + const utils::Duration &lineDuration = session.lineDuration; + utils::Duration newExposureTime = {}; + + if (params) { + ASSERT(session.autoAllowed); + + /* + * Set the AGC limits using the fixed exposure time and/or gain in + * manual mode, or the sensor limits in auto mode. + */ + utils::Duration minExposureTime; + utils::Duration maxExposureTime; + double minAnalogueGain; + double maxAnalogueGain; + + if (frameContext.autoExposureEnabled) { + minExposureTime = session.minExposureTime; + maxExposureTime = std::clamp(frameContext.maxFrameDuration, session.minExposureTime, session.maxExposureTime); + } else { + minExposureTime = lineDuration * frameContext.exposure; + maxExposureTime = minExposureTime; + } + + if (frameContext.autoGainEnabled) { + minAnalogueGain = session.minAnalogueGain; + maxAnalogueGain = session.maxAnalogueGain; + } else { + minAnalogueGain = frameContext.gain; + maxAnalogueGain = frameContext.gain; + } + + /* + * The Agc algorithm needs to know the effective exposure value that was + * applied to the sensor when the statistics were collected. + */ + utils::Duration effectiveExposureValue = + lineDuration * params->exposure * params->gain; + + impl_.setLimits(minExposureTime, maxExposureTime, + minAnalogueGain, maxAnalogueGain, + std::move(params->additionalConstraints)); + + const auto &newEv = impl_.calculateNewEv({ + .traits = params->traits, + .yHist = params->yHist, + .effectiveExposureValue = effectiveExposureValue, + .constraintModeIndex = frameContext.constraintMode, + .exposureModeIndex = frameContext.exposureMode, + .lux = params->lux, + .exposureCompensation = pow(2.0, frameContext.exposureValue), + }); + + LOG(Agc, Debug) + << "Divided up exposure time, analogue gain, quantization gain" + << " and digital gain are " << newEv.exposureTime << ", " << newEv.analogueGain + << ", " << newEv.quantizationGain << " and " << newEv.digitalGain; + + /* Update the estimated exposure and gain. */ + state.automatic.exposure = newEv.exposureTime / lineDuration; + state.automatic.gain = newEv.analogueGain; + state.automatic.quantizationGain = newEv.quantizationGain; + state.automatic.yTarget = newEv.yTarget; + + newExposureTime = newEv.exposureTime; + } + + /* + * Expand the target frame duration so that we do not run faster than + * the minimum frame duration when we have short exposures. + */ + const auto frameDuration = std::max(frameContext.minFrameDuration, newExposureTime); + frameContext.vblank = (frameDuration / lineDuration) - session.sensor.outputSize.height; + + /* Update frame duration accounting for line length quantization. */ + frameContext.frameDuration = (session.sensor.outputSize.height + frameContext.vblank) * lineDuration; + + metadata.set(controls::AnalogueGain, frameContext.gain); + metadata.set(controls::ExposureTime, utils::Duration(lineDuration * frameContext.exposure).get()); + metadata.set(controls::FrameDuration, frameContext.frameDuration.get()); + metadata.set(controls::ExposureTimeMode, + frameContext.autoExposureEnabled + ? controls::ExposureTimeModeAuto + : controls::ExposureTimeModeManual); + metadata.set(controls::AnalogueGainMode, + frameContext.autoGainEnabled + ? controls::AnalogueGainModeAuto + : controls::AnalogueGainModeManual); + + metadata.set(controls::AeExposureMode, frameContext.exposureMode); + metadata.set(controls::AeConstraintMode, frameContext.constraintMode); + metadata.set(controls::ExposureValue, frameContext.exposureValue); +} + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index 5247425952..1eace12908 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -7,13 +7,19 @@ #pragma once +#include #include #include +#include #include +#include + +#include "agc_mean_luminance.h" #include "camera_sensor_helper.h" +#include "histogram.h" namespace libcamera { @@ -42,8 +48,100 @@ prepareControls(ControlList &controls, const CameraSensorHelper *sensor, controls.set(V4L2_CID_ANALOGUE_GAIN, int32_t(sensor ? sensor->gainCode(gain) : gain)); } +struct Session { + utils::Duration minExposureTime; + utils::Duration maxExposureTime; + double minAnalogueGain; + double maxAnalogueGain; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; + + utils::Duration lineDuration; + + struct { + Size outputSize; + } sensor; + + bool autoAllowed; +}; + +struct ActiveState { + struct { + uint32_t exposure; + double gain; + } manual; + struct { + uint32_t exposure; + double gain; + double quantizationGain; + double yTarget; + } automatic; + + bool autoExposureEnabled; + bool autoGainEnabled; + double exposureValue; + controls::AeConstraintModeEnum constraintMode; + controls::AeExposureModeEnum exposureMode; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; +}; + +struct FrameContext { + uint32_t exposure; + double gain; + double quantizationGain; + double exposureValue; + double yTarget; + uint32_t vblank; + bool autoExposureEnabled; + bool autoGainEnabled; + controls::AeConstraintModeEnum constraintMode; + controls::AeExposureModeEnum exposureMode; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; + utils::Duration frameDuration; + bool autoExposureModeChange; + bool autoGainModeChange; +}; + } /* namespace agc */ +class AgcAlgorithm +{ +public: + struct ConfigurationParams { + const CameraSensorHelper *sensor; + const IPACameraSensorInfo &sensorInfo; + const ControlInfoMap &sensorControls; + ControlInfoMap::Map &ctrlMap; + bool autoAllowed = true; + }; + + int init(const ValueNode &tuningData); + + int configure(agc::Session &session, agc::ActiveState &state, const ConfigurationParams &config); + + void queueRequest(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext, const ControlList &controls); + + void prepare(agc::ActiveState &state, agc::FrameContext &frameContext); + + struct ProcessParams { + const AgcMeanLuminance::Traits &traits; + const Histogram &yHist; + uint32_t exposure; + double gain; + std::vector &&additionalConstraints = {}; + double lux = 0; + }; + + void process(const agc::Session &session, agc::ActiveState &state, agc::FrameContext &frameContext, + std::optional &¶ms, ControlList &metadata); + +private: + AgcMeanLuminance impl_; +}; + } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 05f1a8749c..ca681fa5af 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -23,6 +23,7 @@ libipa_headers = files([ ]) libipa_sources = files([ + 'agc.cpp', 'agc_mean_luminance.cpp', 'algorithm.cpp', 'awb_bayes.cpp', From patchwork Thu Jul 23 15:43:05 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: 27472 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 AF2BFC3308 for ; Thu, 23 Jul 2026 15:44:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 468C867F1D; Thu, 23 Jul 2026 17:44:07 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="aLtw0KBf"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EEB0967EC5 for ; Thu, 23 Jul 2026 17:43:35 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 19DDC1C37 for ; Thu, 23 Jul 2026 17:42:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821355; bh=r7fms7akk/zwjiuHiG6GIK0QnaIjwx2y4+nqZtRnBa0=; h=From:To:Subject:Date:In-Reply-To:References:From; b=aLtw0KBfP2ruDa9VtCeEq4d5CQDtK04I2K0TWomdy9Y4VBCUFqxSnhnqLVvlEOnuK j4pCnd4ycj2qQ0EbgVIuB7rQoK+/waRpmbrSvc9DxDbWibEx+ubdtGJUHY06eMo+Lk 4A6eF6hZvmDXvpP3HGb1HQ8ZWTQGBefSvFsl2TlQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 22/43] ipa: libipa: agc: Clamp exposure value Date: Thu, 23 Jul 2026 17:43:05 +0200 Message-ID: <20260723154327.1357866-23-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" It is possible that the minimum exposure time in microseconds is not an integer. In that case the minimum `ExposureTime` in the `ControlInfo` will therefore be actually lower than the real minimum. This can cause issues: if the minimum published exposure time is set, the division when setting `agc.manual.exposure` might produce a value in [0;1), leading to the manual exposure being set to 0, leading to an assertion failure in `ExposureModeHelper::splitExposure()`. Note that this is theoretically possible even when the exposure time is determined automatically by `AgcMeanLuminance`. Store the true integer limits of the exposure as well, and ensure that the exposure (time) -> exposure (line) conversions always clamp. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 27 ++++++++++++++++++++++++--- src/ipa/libipa/agc.h | 2 ++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index e16a02fdde..8a2525ec98 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -38,6 +38,12 @@ LOG_DEFINE_CATEGORY(Agc) * \struct agc::Session * \brief Session configuration for AgcAlgorithm * + * \var agc::Session::minExposure + * \brief Minimum exposure (in lines) supported with the configured sensor + * + * \var agc::Session::maxExposure + * \brief Maximum exposure (in lines) supported with the configured sensor + * * \var agc::Session::minExposureTime * \brief Minimum exposure time supported with the configured sensor * @@ -223,6 +229,19 @@ LOG_DEFINE_CATEGORY(Agc) * \brief Effective lux value of the frame */ +namespace { + +[[nodiscard]] uint32_t clampExposure(utils::Duration exposureTime, const agc::Session &session) +{ + return std::clamp( + exposureTime / session.lineDuration, + session.minExposure, + session.maxExposure + ); +} + +} /* namespace */ + /** * \brief Load tuning data */ @@ -317,6 +336,8 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, cons * * \todo take VBLANK into account for maximum exposure time */ + session.minExposure = minExposure; + session.maxExposure = maxExposure; session.minExposureTime = minExposure * session.lineDuration; session.maxExposureTime = maxExposure * session.lineDuration; session.minAnalogueGain = minGain; @@ -331,7 +352,7 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, cons /* Configure the default exposure and gain. */ state = {}; state.automatic.gain = session.minAnalogueGain; - state.automatic.exposure = 10ms / session.lineDuration; + state.automatic.exposure = clampExposure(10ms, session); state.automatic.quantizationGain = 1; state.automatic.yTarget = impl_.effectiveYTarget(0, 1); state.manual.gain = state.automatic.gain; @@ -437,7 +458,7 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s const auto &exposure = controls.get(controls::ExposureTime); if (exposure && !state.autoExposureEnabled) { - state.manual.exposure = *exposure * 1.0us / session.lineDuration; + state.manual.exposure = clampExposure(*exposure * 1us, session); LOG(Agc, Debug) << "Set exposure to " << state.manual.exposure; @@ -599,7 +620,7 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, << ", " << newEv.quantizationGain << " and " << newEv.digitalGain; /* Update the estimated exposure and gain. */ - state.automatic.exposure = newEv.exposureTime / lineDuration; + state.automatic.exposure = clampExposure(newEv.exposureTime, session); state.automatic.gain = newEv.analogueGain; state.automatic.quantizationGain = newEv.quantizationGain; state.automatic.yTarget = newEv.yTarget; diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index 1eace12908..c11dbf80cd 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -49,6 +49,8 @@ prepareControls(ControlList &controls, const CameraSensorHelper *sensor, } struct Session { + uint32_t minExposure; + uint32_t maxExposure; utils::Duration minExposureTime; utils::Duration maxExposureTime; double minAnalogueGain; From patchwork Thu Jul 23 15:43:06 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: 27471 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 30C7EC3306 for ; Thu, 23 Jul 2026 15:44:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8554167EC6; Thu, 23 Jul 2026 17:44:06 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WqFPFsNN"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 341F567EE8 for ; Thu, 23 Jul 2026 17:43:36 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 52B092C09 for ; Thu, 23 Jul 2026 17:42:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821355; bh=6TN2S2GDu7LKbEzEZ5dxiyxqJQ1+SpfXq6yxq9RPN5Y=; h=From:To:Subject:Date:In-Reply-To:References:From; b=WqFPFsNNbrh6KGCTbUdhYDm2Ohanm7Ntia1d064kkm5id5NJ7hCQ7UfnaDwSHrlUv ludntW9l1MeXT+xzl/9spycSn5a+J5041o32GA18U4P984S/DPx0iyFYvhiOrP6XV3 LwMHjDcmrW79E9MaXxDdO8I2mfPL0jbOZ6ey/lhE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 23/43] ipa: libipa: agc: Adjust debug messages slightly Date: Thu, 23 Jul 2026 17:43:06 +0200 Message-ID: <20260723154327.1357866-24-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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 log message showing the result of AGC seems too verbose to me, and the labels and values are far from each other. And also log the line duration when showing exposure and gain limits. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 8a2525ec98..49b3034746 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -294,8 +294,11 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, cons }; LOG(Agc, Debug) - << "Exposure: [" << minExposure << ", " << maxExposure - << "], gain: [" << minGain << ", " << maxGain << "]"; + << "exposure:[" << minExposure << ',' << maxExposure << ']' + << " gain: [" << minGain << ',' << maxGain << ']' + << " line-duration:" << session.lineDuration + << " sensor-output:" << session.sensor.outputSize + ; /* * Compute the frame duration limits. @@ -615,9 +618,11 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, }); LOG(Agc, Debug) - << "Divided up exposure time, analogue gain, quantization gain" - << " and digital gain are " << newEv.exposureTime << ", " << newEv.analogueGain - << ", " << newEv.quantizationGain << " and " << newEv.digitalGain; + << "exposure-time:" << newEv.exposureTime + << " analogue-gain" << newEv.analogueGain + << " quantization-gain" << newEv.quantizationGain + << " digital-gain: " << newEv.digitalGain + ; /* Update the estimated exposure and gain. */ state.automatic.exposure = clampExposure(newEv.exposureTime, session); From patchwork Thu Jul 23 15:43:07 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: 27473 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 9432BC328C for ; Thu, 23 Jul 2026 15:44:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2F47167F2D; Thu, 23 Jul 2026 17:44:08 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="hqNzQ4VE"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 64CE567E5C for ; Thu, 23 Jul 2026 17:43:36 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8AC141C37 for ; Thu, 23 Jul 2026 17:42:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821355; bh=iLWNupkj4FCiaDjXo9PV4n8grrXEgk0vU1VxnRr8ROw=; h=From:To:Subject:Date:In-Reply-To:References:From; b=hqNzQ4VE3ReVDdQKhQo7acqYf3I1Fijk8tN+JgbkWZl2Xm8s1fDBnoyTQ1xYfei81 eL2UWbLyjn0JN2Yskn4XXPfe0I9hssalVCoDHSEtcMgyuH3+pCTVWz02LJIRjaYhzi l8NMfYBheARJIiieHxnncBg0EB/ipu+oqQg1IGpg= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 24/43] ipa: libipa: agc: Use full range as default `FrameDurationLimits` Date: Thu, 23 Jul 2026 17:43:07 +0200 Message-ID: <20260723154327.1357866-25-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Currently the default value for `FrameDurationLimits` is meaningless because the full range is used by default. So advertise the full range. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 49b3034746..edcbab1e0b 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -326,7 +326,7 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, cons config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ frameDurations[0], frameDurations[1], - Span{ { frameDurations[2], frameDurations[2] } }, + Span{ { frameDurations[0], frameDurations[1] } }, }; session.minFrameDuration = std::chrono::microseconds(frameDurations[0]); From patchwork Thu Jul 23 15:43:08 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: 27474 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 2E44DC330A for ; Thu, 23 Jul 2026 15:44:10 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9626067F4B; Thu, 23 Jul 2026 17:44:09 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fUaOkXbB"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9556C67ED4 for ; Thu, 23 Jul 2026 17:43:36 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id BD8FA2C0B for ; Thu, 23 Jul 2026 17:42:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821355; bh=Wi9xjYH957JYDQ4WTv72x9XHTABa+6xnKJ3UAIcFj0o=; h=From:To:Subject:Date:In-Reply-To:References:From; b=fUaOkXbBREX2wEJKXhLpkqrD9n5Fd5O0KrLskP9NCo6z0VM6relXw5b4dn+/N7pma ZjnVkgGYiaHX7bj3S1HLojiGHFtRJieIwT50nYfyYQR/nOBiSaaOJGBz35zJYiYH4j xmP17NsATI9JaXxTcNonQ2ic9ZKWbowcSy5Gt+BI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 25/43] ipa: libipa: agc: Use default exposure Date: Thu, 23 Jul 2026 17:43:08 +0200 Message-ID: <20260723154327.1357866-26-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Instead of hard-coding 10ms, just use the default exposure time as determined by the V4L2 control. This ensures that the advertised default in the `ControlInfo` matches what actually happens. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index edcbab1e0b..54c5c6c349 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -355,7 +355,7 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, cons /* Configure the default exposure and gain. */ state = {}; state.automatic.gain = session.minAnalogueGain; - state.automatic.exposure = clampExposure(10ms, session); + state.automatic.exposure = clampExposure(defExposure * session.lineDuration, session); state.automatic.quantizationGain = 1; state.automatic.yTarget = impl_.effectiveYTarget(0, 1); state.manual.gain = state.automatic.gain; From patchwork Thu Jul 23 15:43:09 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: 27476 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 03280BDE17 for ; Thu, 23 Jul 2026 15:44:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 86A4E67EE3; Thu, 23 Jul 2026 17:44:12 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="dG3F+HX9"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1A92E67EC6 for ; Thu, 23 Jul 2026 17:43:37 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F2D562C09 for ; Thu, 23 Jul 2026 17:42:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821356; bh=zb5FKxWO9Bu24VvBt/54WxAkUpOK5uWWcRF/fZuhRMs=; h=From:To:Subject:Date:In-Reply-To:References:From; b=dG3F+HX9MPsJb1ZGfVY7jyoUEbgDskjNyhO/UIjkwpYvzqhm1wWipGxwo+oGQKsCj MOq+IglTrX02lGTMsXoJQnCPOM6KEzFARyd4Fy5c+BngwBQZavSKpHlOvvHbc7MXfz uI/TJVmruBI082rvBED92YxP/n2uIS5BZIQIw6N8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 26/43] ipa: rkisp1: agc: Port to `AgcAlgorithm` Date: Thu, 23 Jul 2026 17:43:09 +0200 Message-ID: <20260723154327.1357866-27-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Use the `AgcAlgorithm` class to implement the rkisp1 agc algorithm. Signed-off-by: Barnabás Pőcze --- src/ipa/rkisp1/algorithms/agc.cpp | 464 ++++-------------------------- src/ipa/rkisp1/algorithms/agc.h | 10 +- src/ipa/rkisp1/algorithms/lux.cpp | 2 +- src/ipa/rkisp1/ipa_context.cpp | 98 ------- src/ipa/rkisp1/ipa_context.h | 47 +-- src/ipa/rkisp1/rkisp1.cpp | 4 - 6 files changed, 63 insertions(+), 562 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index fc228452c3..4c2a066e86 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -8,9 +8,7 @@ #include "agc.h" #include -#include #include -#include #include #include @@ -35,89 +33,6 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Agc) -namespace { - -void reconfigure(IPAContext &context) -{ - context.configuration.sensor.lineDuration = - context.sensorInfo.minLineLength * 1.0s / context.sensorInfo.pixelRate; - - double lineDurationUs = context.configuration.sensor.lineDuration.get(); - - /* - * Compute exposure time limits from the V4L2_CID_EXPOSURE control - * limits and the line duration. - */ - - const ControlInfo &v4l2Exposure = context.sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get(); - int32_t maxExposure = v4l2Exposure.max().get(); - int32_t defExposure = v4l2Exposure.def().get(); - context.ctrlMap[&controls::ExposureTime] = ControlInfo{ - static_cast(minExposure * lineDurationUs), - static_cast(maxExposure * lineDurationUs), - static_cast(defExposure * lineDurationUs), - }; - - /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = context.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = context.camHelper->gain(v4l2Gain.min().get()); - float maxGain = context.camHelper->gain(v4l2Gain.max().get()); - float defGain = context.camHelper->gain(v4l2Gain.def().get()); - context.ctrlMap[&controls::AnalogueGain] = ControlInfo{ - minGain, - maxGain, - defGain, - }; - - LOG(RkISP1Agc, Debug) - << "Exposure: [" << minExposure << ", " << maxExposure - << "], gain: [" << minGain << ", " << maxGain << "]"; - - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - const ControlInfo &v4l2HBlank = context.sensorControls.find(V4L2_CID_HBLANK)->second; - uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = context.sensorInfo.outputSize.width + hblank; - - const ControlInfo &v4l2VBlank = context.sensorControls.find(V4L2_CID_VBLANK)->second; - std::array frameHeights{ - v4l2VBlank.min().get() + context.sensorInfo.outputSize.height, - v4l2VBlank.max().get() + context.sensorInfo.outputSize.height, - v4l2VBlank.def().get() + context.sensorInfo.outputSize.height, - }; - - std::array frameDurations; - for (unsigned int i = 0; i < frameHeights.size(); ++i) { - uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (context.sensorInfo.pixelRate / 1000000U); - } - - context.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ - frameDurations[0], - frameDurations[1], - Span{ { frameDurations[2], frameDurations[2] } }, - }; - - /* - * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for exposure time and analogue gain. As it depends - * on the sensor, update it with the controls. - * - * \todo take VBLANK into account for maximum exposure time - */ - context.configuration.sensor.minExposureTime = minExposure * context.configuration.sensor.lineDuration; - context.configuration.sensor.maxExposureTime = maxExposure * context.configuration.sensor.lineDuration; - context.configuration.sensor.minAnalogueGain = minGain; - context.configuration.sensor.maxAnalogueGain = maxGain; -} - -} /* namespace */ - /** * \class Agc * \brief A mean-based auto-exposure algorithm @@ -221,7 +136,16 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.parseTuningData(tuningData); + ret = agc_.init(tuningData); + if (ret) + return ret; + + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); if (ret) return ret; @@ -230,21 +154,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) if (ret) return ret; - context.ctrlMap[&controls::ExposureTimeMode] = - ControlInfo({ { ControlValue(controls::ExposureTimeModeAuto), - ControlValue(controls::ExposureTimeModeManual) } }, - ControlValue(controls::ExposureTimeModeAuto)); - context.ctrlMap[&controls::AnalogueGainMode] = - ControlInfo({ { ControlValue(controls::AnalogueGainModeAuto), - ControlValue(controls::AnalogueGainModeManual) } }, - ControlValue(controls::AnalogueGainModeAuto)); - /* \todo Move this to the Camera class */ - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true); - context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); - context.ctrlMap.merge(agc_.controls()); - - reconfigure(context); - return 0; } @@ -257,47 +166,24 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) */ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { - reconfigure(context); - - /* Configure the default exposure and gain. */ - context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain; - context.activeState.agc.automatic.exposure = - 10ms / context.configuration.sensor.lineDuration; - context.activeState.agc.automatic.quantizationGain = 1.0; - context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain; - context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure; - context.activeState.agc.autoExposureEnabled = !context.configuration.raw; - context.activeState.agc.autoGainEnabled = !context.configuration.raw; - context.activeState.agc.exposureValue = 0.0; - - context.activeState.agc.constraintMode = - static_cast(agc_.constraintModes().begin()->first); - context.activeState.agc.exposureMode = - static_cast(agc_.exposureModeHelpers().begin()->first); + int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = !context.configuration.raw, + }); + if (ret) + return ret; + context.activeState.agc.meteringMode = static_cast(meteringModes_.begin()->first); - /* Limit the frame duration to match current initialisation */ - ControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits]; - context.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get()); - context.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get()); - 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; - agc_.configure(context.configuration.sensor.lineDuration, context.camHelper.get()); - - agc_.setLimits(context.configuration.sensor.minExposureTime, - context.configuration.sensor.maxExposureTime, - context.configuration.sensor.minAnalogueGain, - context.configuration.sensor.maxAnalogueGain, {}); - - context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(0, 1); - - agc_.resetFrameCount(); - return 0; } @@ -311,73 +197,7 @@ void Agc::queueRequest(IPAContext &context, { auto &agc = context.activeState.agc; - if (!context.configuration.raw) { - const auto &aeEnable = controls.get(controls::ExposureTimeMode); - if (aeEnable && - (*aeEnable == controls::ExposureTimeModeAuto) != agc.autoExposureEnabled) { - agc.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); - - LOG(RkISP1Agc, Debug) - << (agc.autoExposureEnabled ? "Enabling" : "Disabling") - << " AGC (exposure)"; - - /* - * If we go from auto -> manual with no manual control - * set, use the last computed value, which we don't - * know until prepare() so save this information. - * - * \todo Check the previous frame at prepare() time - * instead of saving a flag here - */ - if (!agc.autoExposureEnabled && !controls.get(controls::ExposureTime)) - frameContext.agc.autoExposureModeChange = true; - } - - const auto &agEnable = controls.get(controls::AnalogueGainMode); - if (agEnable && - (*agEnable == controls::AnalogueGainModeAuto) != agc.autoGainEnabled) { - agc.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); - - LOG(RkISP1Agc, Debug) - << (agc.autoGainEnabled ? "Enabling" : "Disabling") - << " AGC (gain)"; - /* - * If we go from auto -> manual with no manual control - * set, use the last computed value, which we don't - * know until prepare() so save this information. - */ - if (!agc.autoGainEnabled && !controls.get(controls::AnalogueGain)) - frameContext.agc.autoGainModeChange = true; - } - } - - const auto &exposure = controls.get(controls::ExposureTime); - if (exposure && !agc.autoExposureEnabled) { - agc.manual.exposure = *exposure * 1.0us - / context.configuration.sensor.lineDuration; - - LOG(RkISP1Agc, Debug) - << "Set exposure to " << agc.manual.exposure; - } - - const auto &gain = controls.get(controls::AnalogueGain); - if (gain && !agc.autoGainEnabled) { - agc.manual.gain = *gain; - - LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain; - } - - frameContext.agc.autoExposureEnabled = agc.autoExposureEnabled; - frameContext.agc.autoGainEnabled = agc.autoGainEnabled; - - if (!frameContext.agc.autoExposureEnabled) - frameContext.agc.exposure = agc.manual.exposure; - if (!frameContext.agc.autoGainEnabled) - frameContext.agc.gain = agc.manual.gain; - - if (!frameContext.agc.autoExposureEnabled && - !frameContext.agc.autoGainEnabled) - frameContext.agc.quantizationGain = 1.0; + agc_.queueRequest(context.configuration.agc, agc, frameContext.agc, controls); const auto &meteringMode = controls.get(controls::AeMeteringMode); if (meteringMode) { @@ -386,42 +206,6 @@ void Agc::queueRequest(IPAContext &context, static_cast(*meteringMode); } frameContext.agc.meteringMode = agc.meteringMode; - - const auto &exposureMode = controls.get(controls::AeExposureMode); - if (exposureMode) - agc.exposureMode = - static_cast(*exposureMode); - frameContext.agc.exposureMode = agc.exposureMode; - - const auto &constraintMode = controls.get(controls::AeConstraintMode); - if (constraintMode) - agc.constraintMode = - static_cast(*constraintMode); - frameContext.agc.constraintMode = agc.constraintMode; - - const auto &exposureValue = controls.get(controls::ExposureValue); - if (exposureValue) - agc.exposureValue = *exposureValue; - frameContext.agc.exposureValue = agc.exposureValue; - - const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); - if (frameDurationLimits) { - /* Limit the control value to the limits in ControlInfo */ - ControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits]; - int64_t minFrameDuration = - std::clamp((*frameDurationLimits).front(), - limits.min().get(), - limits.max().get()); - int64_t maxFrameDuration = - std::clamp((*frameDurationLimits).back(), - limits.min().get(), - limits.max().get()); - - agc.minFrameDuration = std::chrono::microseconds(minFrameDuration); - agc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration); - } - frameContext.agc.minFrameDuration = agc.minFrameDuration; - frameContext.agc.maxFrameDuration = agc.maxFrameDuration; } /** @@ -430,41 +214,13 @@ void Agc::queueRequest(IPAContext &context, void Agc::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) { - uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure; - double activeAutoGain = context.activeState.agc.automatic.gain; - double activeAutoQGain = context.activeState.agc.automatic.quantizationGain; - - /* Populate exposure and gain in auto mode */ - if (frameContext.agc.autoExposureEnabled) { - frameContext.agc.exposure = activeAutoExposure; - frameContext.agc.quantizationGain = activeAutoQGain; - } - if (frameContext.agc.autoGainEnabled) { - frameContext.agc.gain = activeAutoGain; - frameContext.agc.quantizationGain = activeAutoQGain; - } - - /* - * Populate manual exposure and gain from the active auto values when - * transitioning from auto to manual - */ - if (!frameContext.agc.autoExposureEnabled && frameContext.agc.autoExposureModeChange) { - context.activeState.agc.manual.exposure = activeAutoExposure; - frameContext.agc.exposure = activeAutoExposure; - } - if (!frameContext.agc.autoGainEnabled && frameContext.agc.autoGainModeChange) { - context.activeState.agc.manual.gain = activeAutoGain; - frameContext.agc.gain = activeAutoGain; - frameContext.agc.quantizationGain = activeAutoQGain; - } + agc_.prepare(context.activeState.agc, frameContext.agc); if (context.configuration.compress.supported) { frameContext.compress.enable = true; frameContext.compress.gain = frameContext.agc.quantizationGain; } - frameContext.agc.yTarget = context.activeState.agc.automatic.yTarget; - if (frame > 0 && !frameContext.agc.updateMetering) return; @@ -520,50 +276,6 @@ void Agc::prepare(IPAContext &context, const uint32_t frame, static_cast(hstConfig->mode)); } -void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, - ControlList &metadata) -{ - utils::Duration exposureTime = context.configuration.sensor.lineDuration - * frameContext.sensor.exposure; - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); - metadata.set(controls::ExposureTime, exposureTime.get()); - metadata.set(controls::FrameDuration, frameContext.agc.frameDuration.get()); - metadata.set(controls::ExposureTimeMode, - frameContext.agc.autoExposureEnabled - ? controls::ExposureTimeModeAuto - : controls::ExposureTimeModeManual); - metadata.set(controls::AnalogueGainMode, - frameContext.agc.autoGainEnabled - ? controls::AnalogueGainModeAuto - : controls::AnalogueGainModeManual); - - metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); - metadata.set(controls::AeExposureMode, frameContext.agc.exposureMode); - metadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode); - metadata.set(controls::ExposureValue, frameContext.agc.exposureValue); -} - -/** - * \brief Process frame duration and compute vblank - * \param[in] context The shared IPA context - * \param[in] frameContext The current frame context - * \param[in] frameDuration The target frame duration - * - * Compute and populate vblank from the target frame duration. - */ -void Agc::processFrameDuration(IPAContext &context, - IPAFrameContext &frameContext, - utils::Duration frameDuration) -{ - IPACameraSensorInfo &sensorInfo = context.sensorInfo; - utils::Duration lineDuration = context.configuration.sensor.lineDuration; - - frameContext.agc.vblank = (frameDuration / lineDuration) - sensorInfo.outputSize.height; - - /* Update frame duration accounting for line length quantization. */ - frameContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration; -} - namespace { class AgcTraits final : public AgcMeanLuminance::Traits @@ -637,21 +349,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, ControlList &metadata) { - if (!stats) { - processFrameDuration(context, frameContext, - frameContext.agc.minFrameDuration); - fillMetadata(context, frameContext, metadata); - return; - } - - if (!(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP)) { - fillMetadata(context, frameContext, metadata); - LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; - return; - } - - const utils::Duration &lineDuration = context.configuration.sensor.lineDuration; - /* * \todo Verify that the exposure and gain applied by the sensor for * this frame match what has been requested. This isn't a hard @@ -660,95 +357,46 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, * we receive), but is important in manual mode. */ - const rkisp1_cif_isp_stat *params = &stats->params; + const rkisp1_cif_isp_stat *params = nullptr; - /* - * Set the AGC limits using the fixed exposure time and/or gain in - * manual mode, or the sensor limits in auto mode. - */ - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; - - if (frameContext.agc.autoExposureEnabled) { - minExposureTime = context.configuration.sensor.minExposureTime; - maxExposureTime = std::clamp(frameContext.agc.maxFrameDuration, - context.configuration.sensor.minExposureTime, - context.configuration.sensor.maxExposureTime); - } else { - minExposureTime = context.configuration.sensor.lineDuration - * frameContext.agc.exposure; - maxExposureTime = minExposureTime; + if (stats) { + if (stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP) + params = &stats->params; + else + LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; } - if (frameContext.agc.autoGainEnabled) { - minAnalogueGain = context.configuration.sensor.minAnalogueGain; - maxAnalogueGain = context.configuration.sensor.maxAnalogueGain; + if (params) { + std::vector additionalConstraints; + if (context.activeState.wdr.mode != controls::WdrOff) + additionalConstraints.push_back(context.activeState.wdr.constraint); + + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ + .traits = AgcTraits{ + { params->ae.exp_mean, context.hw.numAeCells }, + meteringModes_.at(frameContext.agc.meteringMode), + }, + .yHist = { + /* The lower 4 bits are fractional and meant to be discarded. */ + { params->hist.hist_bins, context.hw.numHistogramBins }, + [](uint32_t x) { return x >> 4; }, + }, + .exposure = frameContext.sensor.exposure, + /* + * Include the quantization gain if it was applied. Do not use + * compress.gain because it will include gains that shall not be + * reported to the user when HDR is implemented. + */ + .gain = frameContext.sensor.gain + * (frameContext.compress.enable ? frameContext.agc.quantizationGain : 1), + .additionalConstraints = std::move(additionalConstraints), + .lux = frameContext.lux.lux, + }}, metadata); } else { - minAnalogueGain = frameContext.agc.gain; - maxAnalogueGain = frameContext.agc.gain; + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {}, metadata); } - std::vector additionalConstraints; - if (context.activeState.wdr.mode != controls::WdrOff) - additionalConstraints.push_back(context.activeState.wdr.constraint); - - agc_.setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain, - std::move(additionalConstraints)); - - /* - * The Agc algorithm needs to know the effective exposure value that was - * applied to the sensor when the statistics were collected. - */ - utils::Duration exposureTime = lineDuration * frameContext.sensor.exposure; - double analogueGain = frameContext.sensor.gain; - utils::Duration effectiveExposureValue = exposureTime * analogueGain; - - /* - * Include the quantization gain if it was applied. Do not use - * compress.gain because it will include gains that shall not be - * reported to the user when HDR is implemented. - */ - if (frameContext.compress.enable) - effectiveExposureValue *= frameContext.agc.quantizationGain; - - /* The lower 4 bits are fractional and meant to be discarded. */ - Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, - [](uint32_t x) { return x >> 4; }); - - const auto &newEv = agc_.calculateNewEv({ - .traits = AgcTraits{ - { params->ae.exp_mean, context.hw.numAeCells }, - meteringModes_.at(frameContext.agc.meteringMode), - }, - .yHist = hist, - .effectiveExposureValue = effectiveExposureValue, - .constraintModeIndex = frameContext.agc.constraintMode, - .exposureModeIndex = frameContext.agc.exposureMode, - .lux = frameContext.lux.lux, - .exposureCompensation = pow(2.0, frameContext.agc.exposureValue), - }); - - LOG(RkISP1Agc, Debug) - << "Divided up exposure time, analogue gain, quantization gain" - << " and digital gain are " << newEv.exposureTime << ", " << newEv.analogueGain - << ", " << newEv.quantizationGain << " and " << newEv.digitalGain; - - IPAActiveState &activeState = context.activeState; - /* Update the estimated exposure and gain. */ - activeState.agc.automatic.exposure = newEv.exposureTime / lineDuration; - activeState.agc.automatic.gain = newEv.analogueGain; - activeState.agc.automatic.quantizationGain = newEv.quantizationGain; - activeState.agc.automatic.yTarget = newEv.yTarget; - /* - * Expand the target frame duration so that we do not run faster than - * the minimum frame duration when we have short exposures. - */ - processFrameDuration(context, frameContext, - std::max(frameContext.agc.minFrameDuration, newEv.exposureTime)); - - fillMetadata(context, frameContext, metadata); + metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h index 0527ca0d5f..3a4d7bc546 100644 --- a/src/ipa/rkisp1/algorithms/agc.h +++ b/src/ipa/rkisp1/algorithms/agc.h @@ -14,7 +14,7 @@ #include -#include "libipa/agc_mean_luminance.h" +#include "libipa/agc.h" #include "algorithm.h" @@ -47,14 +47,8 @@ private: uint8_t computeHistogramPredivider(const Size &size, enum rkisp1_cif_isp_histogram_mode mode); - void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, - ControlList &metadata); - void processFrameDuration(IPAContext &context, - IPAFrameContext &frameContext, - utils::Duration frameDuration); - std::map> meteringModes_; - AgcMeanLuminance agc_; + AgcAlgorithm agc_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp index 86e46c492f..ce6928a55d 100644 --- a/src/ipa/rkisp1/algorithms/lux.cpp +++ b/src/ipa/rkisp1/algorithms/lux.cpp @@ -74,7 +74,7 @@ void Lux::process(IPAContext &context, if (!stats) return; - utils::Duration exposureTime = context.configuration.sensor.lineDuration * + utils::Duration exposureTime = context.configuration.agc.lineDuration * frameContext.sensor.exposure; double gain = frameContext.sensor.gain; diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp index 1f94afda6b..47691674ad 100644 --- a/src/ipa/rkisp1/ipa_context.cpp +++ b/src/ipa/rkisp1/ipa_context.cpp @@ -86,21 +86,6 @@ namespace libcamera::ipa::rkisp1 { * \var IPASessionConfiguration::sensor * \brief Sensor-specific configuration of the IPA * - * \var IPASessionConfiguration::sensor.minExposureTime - * \brief Minimum exposure time supported with the sensor - * - * \var IPASessionConfiguration::sensor.maxExposureTime - * \brief Maximum exposure time supported with the sensor - * - * \var IPASessionConfiguration::sensor.minAnalogueGain - * \brief Minimum analogue gain supported with the sensor - * - * \var IPASessionConfiguration::sensor.maxAnalogueGain - * \brief Maximum analogue gain supported with the sensor - * - * \var IPASessionConfiguration::sensor.lineDuration - * \brief Line duration in microseconds - * * \var IPASessionConfiguration::sensor.size * \brief Sensor output resolution */ @@ -147,49 +132,8 @@ namespace libcamera::ipa::rkisp1 { * \var IPAActiveState::agc * \brief State for the Automatic Gain Control algorithm * - * The \a automatic variables track the latest values computed by algorithm - * based on the latest processed statistics. All other variables track the - * consolidated controls requested in queued requests. - * - * \struct IPAActiveState::agc.manual - * \brief Manual exposure time and analog gain (set through requests) - * - * \var IPAActiveState::agc.manual.exposure - * \brief Manual exposure time expressed as a number of lines as set by the - * ExposureTime control - * - * \var IPAActiveState::agc.manual.gain - * \brief Manual analogue gain as set by the AnalogueGain control - * - * \struct IPAActiveState::agc.automatic - * \brief Automatic exposure time and analog gain (computed by the algorithm) - * - * \var IPAActiveState::agc.automatic.exposure - * \brief Automatic exposure time expressed as a number of lines - * - * \var IPAActiveState::agc.automatic.gain - * \brief Automatic analogue gain multiplier - * - * \var IPAActiveState::agc.autoExposureEnabled - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control - * - * \var IPAActiveState::agc.autoGainEnabled - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control - * - * \var IPAActiveState::agc.constraintMode - * \brief Constraint mode as set by the AeConstraintMode control - * - * \var IPAActiveState::agc.exposureMode - * \brief Exposure mode as set by the AeExposureMode control - * * \var IPAActiveState::agc.meteringMode * \brief Metering mode as set by the AeMeteringMode control - * - * \var IPAActiveState::agc.minFrameDuration - * \brief Minimum frame duration as set by the FrameDurationLimits control - * - * \var IPAActiveState::agc.maxFrameDuration - * \brief Maximum frame duration as set by the FrameDurationLimits control */ /** @@ -314,53 +258,11 @@ namespace libcamera::ipa::rkisp1 { * the vertical blanking period is determined to maintain a consistent frame * rate matched to the FrameDurationLimits as set by the user. * - * \var IPAFrameContext::agc.exposure - * \brief Exposure time expressed as a number of lines computed by the algorithm - * - * \var IPAFrameContext::agc.gain - * \brief Analogue gain multiplier computed by the algorithm - * - * The gain should be adapted to the sensor specific gain code before applying. - * - * \var IPAFrameContext::agc.vblank - * \brief Vertical blanking parameter computed by the algorithm - * - * \var IPAFrameContext::agc.autoExposureEnabled - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control - * - * \var IPAFrameContext::agc.autoGainEnabled - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control - * - * \var IPAFrameContext::agc.constraintMode - * \brief Constraint mode as set by the AeConstraintMode control - * - * \var IPAFrameContext::agc.exposureMode - * \brief Exposure mode as set by the AeExposureMode control - * * \var IPAFrameContext::agc.meteringMode * \brief Metering mode as set by the AeMeteringMode control * - * \var IPAFrameContext::agc.minFrameDuration - * \brief Minimum frame duration as set by the FrameDurationLimits control - * - * \var IPAFrameContext::agc.maxFrameDuration - * \brief Maximum frame duration as set by the FrameDurationLimits control - * - * \var IPAFrameContext::agc.frameDuration - * \brief The actual FrameDuration used by the algorithm for the frame - * * \var IPAFrameContext::agc.updateMetering * \brief Indicate if new ISP AGC metering parameters need to be applied - * - * \var IPAFrameContext::agc.autoExposureModeChange - * \brief Indicate if autoExposureEnabled has changed from true in the previous - * frame to false in the current frame, and no manual exposure value has been - * supplied in the current frame. - * - * \var IPAFrameContext::agc.autoGainModeChange - * \brief Indicate if autoGainEnabled has changed from true in the previous - * frame to false in the current frame, and no manual gain value has been - * supplied in the current frame. */ /** diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index eff88b72f5..9ef96a2085 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -24,7 +24,7 @@ #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" -#include "libipa/agc_mean_luminance.h" +#include "libipa/agc.h" #include "libipa/camera_sensor_helper.h" #include "libipa/fc_queue.h" #include "libipa/fixedpoint.h" @@ -49,7 +49,7 @@ struct IPAHwSettings { }; struct IPASessionConfiguration { - struct { + struct Agc : agc::Session { struct rkisp1_cif_isp_window measureWindow; } agc; @@ -63,12 +63,6 @@ struct IPASessionConfiguration { } compress; struct { - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; - - utils::Duration lineDuration; Size size; } sensor; @@ -77,26 +71,8 @@ struct IPASessionConfiguration { }; struct IPAActiveState { - struct { - struct { - uint32_t exposure; - double gain; - } manual; - struct { - uint32_t exposure; - double gain; - double quantizationGain; - double yTarget; - } automatic; - - bool autoExposureEnabled; - bool autoGainEnabled; - double exposureValue; - controls::AeConstraintModeEnum constraintMode; - controls::AeExposureModeEnum exposureMode; + struct Agc : agc::ActiveState { controls::AeMeteringModeEnum meteringMode; - utils::Duration minFrameDuration; - utils::Duration maxFrameDuration; } agc; struct { @@ -155,24 +131,9 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { - struct { - uint32_t exposure; - double gain; - double exposureValue; - double quantizationGain; - uint32_t vblank; - double yTarget; - bool autoExposureEnabled; - bool autoGainEnabled; - controls::AeConstraintModeEnum constraintMode; - controls::AeExposureModeEnum exposureMode; + struct Agc : agc::FrameContext { controls::AeMeteringModeEnum meteringMode; - utils::Duration minFrameDuration; - utils::Duration maxFrameDuration; - utils::Duration frameDuration; bool updateMetering; - bool autoExposureModeChange; - bool autoGainModeChange; } agc; struct { diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index acb878d814..6425d8f1e8 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -6,8 +6,6 @@ */ #include -#include -#include #include #include @@ -40,8 +38,6 @@ namespace libcamera { LOG_DEFINE_CATEGORY(IPARkISP1) -using namespace std::literals::chrono_literals; - namespace ipa::rkisp1 { /* Maximum number of frame contexts to be held */ From patchwork Thu Jul 23 15:43:10 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: 27475 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 A9E1BC3301 for ; Thu, 23 Jul 2026 15:44:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 310EB67F02; Thu, 23 Jul 2026 17:44:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="C0V/KkGh"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5EC0367ED9 for ; Thu, 23 Jul 2026 17:43:37 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7863B2C0B for ; Thu, 23 Jul 2026 17:42:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821356; bh=1pcctY2ceejlO861K6gJI0E7BflUz4f0bBMItBsIl/c=; h=From:To:Subject:Date:In-Reply-To:References:From; b=C0V/KkGhHQy78cQbZ3wcwtgmDHEmqUPKeewnEYcxnUlp7ZpZS35WC9VgxxviSz4WJ 3sw3X+FncSFtkfm4fOsp3sEXKTGVfSOafkmPK2Y8Dc0Hk+TYTW6m13OEk8FKdq75O6 7Ed9tfqSCOwjpxA2c02SXZvpE3rz/WOcTbijXJeE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 27/43] ipa: mali-c55: agc: Port to `AgcAlgorithm` Date: Thu, 23 Jul 2026 17:43:10 +0200 Message-ID: <20260723154327.1357866-28-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Use the `AgcAlgorithm` class to implement the mali-c55 agc algorithm. Closes: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/262 Signed-off-by: Barnabás Pőcze --- src/ipa/mali-c55/algorithms/agc.cpp | 130 ++++++---------------- src/ipa/mali-c55/algorithms/agc.h | 4 +- src/ipa/mali-c55/ipa_context.h | 35 +++--- src/ipa/mali-c55/mali-c55.cpp | 160 +++++----------------------- 4 files changed, 75 insertions(+), 254 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 821f43e9fd..8af4df53e7 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -123,12 +123,19 @@ Agc::Agc() int Agc::init(IPAContext &context, const ValueNode &tuningData) { - int ret = agc_.parseTuningData(tuningData); + int ret = agc_.init(tuningData); if (ret) return ret; - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); - context.ctrlMap.merge(agc_.controls()); + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, // \todo if not raw? + }); + if (ret) + return ret; return 0; } @@ -140,79 +147,24 @@ int Agc::configure(IPAContext &context, if (ret) return ret; - /* - * Defaults; we use whatever the sensor's default exposure is and the - * minimum analogue gain. AEGC is _active_ by default. - */ - context.activeState.agc.autoEnabled = true; - context.activeState.agc.automatic.sensorGain = context.configuration.agc.minAnalogueGain; - context.activeState.agc.automatic.exposure = context.configuration.agc.defaultExposure; - context.activeState.agc.manual.sensorGain = context.configuration.agc.minAnalogueGain; - context.activeState.agc.manual.exposure = context.configuration.agc.defaultExposure; - context.activeState.agc.constraintMode = agc_.constraintModes().begin()->first; - context.activeState.agc.exposureMode = agc_.exposureModeHelpers().begin()->first; - - /* \todo Run this again when FrameDurationLimits is passed in */ - agc_.setLimits(context.configuration.agc.minShutterSpeed, - context.configuration.agc.maxShutterSpeed, - context.configuration.agc.minAnalogueGain, - context.configuration.agc.maxAnalogueGain, - {}); - - agc_.resetFrameCount(); + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, // \todo if not raw? + }); + if (ret) + return ret; return 0; } -void Agc::queueRequest(IPAContext &context, const uint32_t frame, - [[maybe_unused]] IPAFrameContext &frameContext, +void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) { - auto &agc = context.activeState.agc; - - const auto &constraintMode = controls.get(controls::AeConstraintMode); - agc.constraintMode = constraintMode.value_or(agc.constraintMode); - - const auto &exposureMode = controls.get(controls::AeExposureMode); - agc.exposureMode = exposureMode.value_or(agc.exposureMode); - - const auto &agcEnable = controls.get(controls::AeEnable); - if (agcEnable && *agcEnable != agc.autoEnabled) { - agc.autoEnabled = *agcEnable; - - LOG(MaliC55Agc, Info) - << (agc.autoEnabled ? "Enabling" : "Disabling") - << " AGC"; - } - - /* - * If the automatic exposure and gain is enabled we have no further work - * to do here... - */ - if (agc.autoEnabled) - return; - - /* - * ...otherwise we need to look for exposure and gain controls and use - * those to set the activeState. - */ - const auto &exposure = controls.get(controls::ExposureTime); - if (exposure) { - agc.manual.exposure = *exposure * 1.0us / context.configuration.sensor.lineDuration; - - LOG(MaliC55Agc, Debug) - << "Exposure set to " << agc.manual.exposure - << " on request sequence " << frame; - } - - const auto &analogueGain = controls.get(controls::AnalogueGain); - if (analogueGain) { - agc.manual.sensorGain = *analogueGain; - - LOG(MaliC55Agc, Debug) - << "Analogue gain set to " << agc.manual.sensorGain - << " on request sequence " << frame; - } + agc_.queueRequest(context.configuration.agc, context.activeState.agc, frameContext.agc, controls); } void Agc::fillParamsBuffer(MaliC55Params *params, enum MaliC55Blocks type) @@ -265,9 +217,11 @@ void Agc::fillWeightsArrayBuffer(MaliC55Params *params, const enum MaliC55Blocks std::fill(weights.begin(), weights.end(), 1); } -void Agc::prepare([[maybe_unused]] IPAContext &context, const uint32_t frame, - [[maybe_unused]] IPAFrameContext &frameContext, MaliC55Params *params) +void Agc::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, MaliC55Params *params) { + agc_.prepare(context.activeState.agc, frameContext.agc); + if (frame > 0) return; @@ -310,9 +264,6 @@ void Agc::process(IPAContext &context, const mali_c55_stats_buffer *stats, [[maybe_unused]] ControlList &metadata) { - IPASessionConfiguration &configuration = context.configuration; - IPAActiveState &activeState = context.activeState; - if (!stats) { LOG(MaliC55Agc, Error) << "No statistics buffer passed to Agc"; return; @@ -323,33 +274,12 @@ void Agc::process(IPAContext &context, statistics_.gHist.interQuantileMean(0, 1), statistics_.bHist.interQuantileMean(0, 1) } }); - /* - * The Agc algorithm needs to know the effective exposure value that was - * applied to the sensor when the statistics were collected. - */ - uint32_t exposure = frameContext.agc.exposure; - double analogueGain = frameContext.agc.sensorGain; - utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; - utils::Duration effectiveExposureValue = currentShutter * analogueGain; - - const auto &newEv = agc_.calculateNewEv({ + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ .traits = AgcTraits(statistics_), .yHist = statistics_.yHist, - .effectiveExposureValue = effectiveExposureValue, - .constraintModeIndex = activeState.agc.constraintMode, - .exposureModeIndex = activeState.agc.exposureMode, - }); - - LOG(MaliC55Agc, Debug) - << "Divided up shutter, analogue gain and digital gain are " - << newEv.exposureTime << ", " << newEv.analogueGain << " and " << newEv.digitalGain; - - activeState.agc.automatic.exposure = newEv.exposureTime / configuration.sensor.lineDuration; - activeState.agc.automatic.sensorGain = newEv.analogueGain; - - metadata.set(controls::ExposureTime, currentShutter.get()); - metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain); - metadata.set(controls::ColourTemperature, context.activeState.agc.temperatureK); + .exposure = frameContext.sensor.exposure, + .gain = frameContext.sensor.gain, + }}, metadata); } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h index ec7fe28c17..809a57c53a 100644 --- a/src/ipa/mali-c55/algorithms/agc.h +++ b/src/ipa/mali-c55/algorithms/agc.h @@ -11,7 +11,7 @@ #include "libcamera/internal/bayer_format.h" -#include "libipa/agc_mean_luminance.h" +#include "libipa/agc.h" #include "libipa/histogram.h" #include "algorithm.h" @@ -68,7 +68,7 @@ private: void fillWeightsArrayBuffer(MaliC55Params *params, enum MaliC55Blocks type); AgcStatistics statistics_; - AgcMeanLuminance agc_; + AgcAlgorithm agc_; }; } /* namespace ipa::mali_c55::algorithms */ diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index 075d6f66ef..9dd947d7fc 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -12,6 +12,8 @@ #include "libcamera/internal/bayer_format.h" +#include +#include #include #include "libipa/fixedpoint.h" @@ -21,34 +23,17 @@ namespace libcamera { namespace ipa::mali_c55 { struct IPASessionConfiguration { - struct { - utils::Duration minShutterSpeed; - utils::Duration maxShutterSpeed; - uint32_t defaultExposure; - double minAnalogueGain; - double maxAnalogueGain; + struct Agc : agc::Session { } agc; struct { BayerFormat::Order bayerOrder; - utils::Duration lineDuration; uint32_t blackLevel; } sensor; }; struct IPAActiveState { - struct { - struct { - uint32_t exposure; - double sensorGain; - } automatic; - struct { - uint32_t exposure; - double sensorGain; - } manual; - bool autoEnabled; - uint32_t constraintMode; - uint32_t exposureMode; + struct Agc : agc::ActiveState { uint32_t temperatureK; } agc; @@ -59,10 +44,13 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { + struct Agc : agc::FrameContext { + } agc; + struct { uint32_t exposure; - double sensorGain; - } agc; + double gain; + } sensor; struct { UQ<4, 8> rGain; @@ -77,10 +65,15 @@ struct IPAContext { } IPASessionConfiguration configuration; + IPACameraSensorInfo sensorInfo; IPAActiveState activeState; FCQueue frameContexts; + ControlInfoMap sensorControls; + + std::unique_ptr camHelper; + ControlInfoMap::Map ctrlMap; }; diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 65e5297766..c0c2c83dca 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -5,7 +5,6 @@ * Mali-C55 ISP image processing algorithms */ -#include #include #include #include @@ -66,21 +65,11 @@ protected: std::string logPrefix() const override; private: - void updateSessionConfiguration(const IPACameraSensorInfo &info, - const ControlInfoMap &sensorControls, - BayerFormat::Order bayerOrder); - void updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, - ControlInfoMap *ipaControls); - void setControls(); + void updateControls(ControlInfoMap *ipaControls); + void setControls(const IPAFrameContext &frameContext); std::map buffers_; - ControlInfoMap sensorControls_; - - /* Interface to the Camera Helper */ - std::unique_ptr camHelper_; - /* Local parameter storage */ struct IPAContext context_; }; @@ -102,8 +91,8 @@ std::string IPAMaliC55::logPrefix() const int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, ControlInfoMap *ipaControls) { - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); - if (!camHelper_) { + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!context_.camHelper) { LOG(IPAMaliC55, Error) << "Failed to create camera sensor helper for " << settings.sensorModel; @@ -129,31 +118,23 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig return -EINVAL; } + context_.sensorControls = ipaConfig.sensorControls; + context_.sensorInfo = ipaConfig.sensorInfo; + int ret = createAlgorithms(context_, (*data)["algorithms"]); if (ret) return ret; - updateControls(ipaConfig.sensorInfo, ipaConfig.sensorControls, ipaControls); + updateControls(ipaControls); return 0; } -void IPAMaliC55::setControls() +void IPAMaliC55::setControls(const IPAFrameContext &frameContext) { - IPAActiveState &activeState = context_.activeState; - uint32_t exposure; - double gain; - - if (activeState.agc.autoEnabled) { - exposure = activeState.agc.automatic.exposure; - gain = activeState.agc.automatic.sensorGain; - } else { - exposure = activeState.agc.manual.exposure; - gain = activeState.agc.manual.sensorGain; - } + ControlList ctrls(context_.sensorControls); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc.exposure, frameContext.agc.gain); - ControlList ctrls(sensorControls_); - agc::prepareControls(ctrls, camHelper_.get(), exposure, gain); setSensorControls.emit(ctrls); } @@ -167,111 +148,19 @@ void IPAMaliC55::stop() context_.frameContexts.clear(); } -void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info, - const ControlInfoMap &sensorControls, - BayerFormat::Order bayerOrder) -{ - context_.configuration.sensor.bayerOrder = bayerOrder; - - const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get(); - int32_t maxExposure = v4l2Exposure.max().get(); - int32_t defExposure = v4l2Exposure.def().get(); - - const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - int32_t minGain = v4l2Gain.min().get(); - int32_t maxGain = v4l2Gain.max().get(); - - /* - * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for shutter speed and analogue gain. - * As it depends on the sensor, update it with the controls. - * - * \todo take VBLANK into account for maximum shutter speed - */ - context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; - context_.configuration.agc.minShutterSpeed = minExposure * context_.configuration.sensor.lineDuration; - context_.configuration.agc.maxShutterSpeed = maxExposure * context_.configuration.sensor.lineDuration; - context_.configuration.agc.defaultExposure = defExposure; - context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain); - context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain); - - if (camHelper_->blackLevel().has_value()) { - /* - * The black level from CameraSensorHelper is a 16-bit value. - * The Mali-C55 ISP expects 20-bit settings, so we shift it to - * the appropriate width - */ - context_.configuration.sensor.blackLevel = - camHelper_->blackLevel().value() << 4; - } -} - -void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, - ControlInfoMap *ipaControls) +void IPAMaliC55::updateControls(ControlInfoMap *ipaControls) { ControlInfoMap::Map ctrlMap; - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - const ControlInfo &v4l2HBlank = sensorControls.find(V4L2_CID_HBLANK)->second; - uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = sensorInfo.outputSize.width + hblank; - - const ControlInfo &v4l2VBlank = sensorControls.find(V4L2_CID_VBLANK)->second; - std::array frameHeights{ - v4l2VBlank.min().get() + sensorInfo.outputSize.height, - v4l2VBlank.max().get() + sensorInfo.outputSize.height, - v4l2VBlank.def().get() + sensorInfo.outputSize.height, - }; - - std::array frameDurations; - for (unsigned int i = 0; i < frameHeights.size(); ++i) { - uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U); - } - - ctrlMap[&controls::FrameDurationLimits] = ControlInfo(frameDurations[0], - frameDurations[1], - Span{ { frameDurations[2], frameDurations[2] } }); - - /* - * Compute exposure time limits from the V4L2_CID_EXPOSURE control - * limits and the line duration. - */ - double lineDuration = sensorInfo.minLineLength / sensorInfo.pixelRate; - - const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get() * lineDuration; - int32_t maxExposure = v4l2Exposure.max().get() * lineDuration; - int32_t defExposure = v4l2Exposure.def().get() * lineDuration; - ctrlMap[&controls::ExposureTime] = ControlInfo(minExposure, maxExposure, defExposure); - - /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = camHelper_->gain(v4l2Gain.min().get()); - float maxGain = camHelper_->gain(v4l2Gain.max().get()); - float defGain = camHelper_->gain(v4l2Gain.def().get()); - ctrlMap[&controls::AnalogueGain] = ControlInfo(minGain, maxGain, defGain); - - /* - * Merge in any controls that we support either statically or from the - * algorithms. - */ ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end()); - *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); } int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder, ControlInfoMap *ipaControls) { - sensorControls_ = ipaConfig.sensorControls; + context_.sensorControls = ipaConfig.sensorControls; + context_.sensorInfo = ipaConfig.sensorInfo; /* Clear the IPA context before the streaming session. */ context_.configuration = {}; @@ -280,9 +169,16 @@ int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder, const IPACameraSensorInfo &info = ipaConfig.sensorInfo; - updateSessionConfiguration(info, ipaConfig.sensorControls, - static_cast(bayerOrder)); - updateControls(info, ipaConfig.sensorControls, ipaControls); + context_.configuration.sensor.bayerOrder = static_cast(bayerOrder); + + if (auto bl = context_.camHelper->blackLevel()) { + /* + * The black level from CameraSensorHelper is a 16-bit value. + * The Mali-C55 ISP expects 20-bit settings, so we shift it to + * the appropriate width + */ + context_.configuration.sensor.blackLevel = *bl << 4; + } for (const auto &a : algorithms()) { Algorithm *algo = static_cast(a.get()); @@ -292,6 +188,8 @@ int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder, return ret; } + updateControls(ipaControls); + return 0; } @@ -351,8 +249,8 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, stats = reinterpret_cast( buffers_.at(bufferId).planes()[0].data()); - std::tie(frameContext.agc.exposure, frameContext.agc.sensorGain) - = agc::extractControls(sensorControls, camHelper_.get()); + std::tie(frameContext.sensor.exposure, frameContext.sensor.gain) + = agc::extractControls(sensorControls, context_.camHelper.get()); ControlList metadata(controls::controls); @@ -362,7 +260,7 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, algo->process(context_, request, frameContext, stats, metadata); } - setControls(); + setControls(frameContext); statsProcessed.emit(request, metadata); } From patchwork Thu Jul 23 15:43:11 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: 27477 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 DFA04C330F for ; Thu, 23 Jul 2026 15:44:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6618667F61; Thu, 23 Jul 2026 17:44:13 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="DlBOGd7F"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C565167EE9 for ; Thu, 23 Jul 2026 17:43:37 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DDA991F0F for ; Thu, 23 Jul 2026 17:42:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821357; bh=Vt7om6ccARtXL5GpKXsiQHPVFf5cfN3oZo6/DWPDRVs=; h=From:To:Subject:Date:In-Reply-To:References:From; b=DlBOGd7FUmDSX6+cKQmYizhVkzO0mvup/BaxBlelrBQ46DsC00o40NCweSOMKVvmR /flyXBrYxyEBdHfZ/QTMyHXsL99+wggY4Y33/Ub7cNaYl6ilUyeWag2TN1qGSQ6tWt roGNq4/OL2OzcvDMDPLn7VQNlFMVjaRQHiOqpvl4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 28/43] ipa: ipu3: agc: Port to `AgcAlgorithm` Date: Thu, 23 Jul 2026 17:43:11 +0200 Message-ID: <20260723154327.1357866-29-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Use the `AgcAlgorithm` class to implement the ipu3 agc algorithm. Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/algorithms/agc.cpp | 108 ++++++++++--------------- src/ipa/ipu3/algorithms/agc.h | 15 ++-- src/ipa/ipu3/ipa_context.cpp | 49 +++--------- src/ipa/ipu3/ipa_context.h | 27 +++---- src/ipa/ipu3/ipu3.cpp | 135 +++++--------------------------- 5 files changed, 92 insertions(+), 242 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index e74db62960..c9ea02ed00 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -8,7 +8,6 @@ #include "agc.h" #include -#include #include #include @@ -48,17 +47,10 @@ namespace ipa::ipu3::algorithms { LOG_DEFINE_CATEGORY(IPU3Agc) -/* Minimum limit for analogue gain value */ -static constexpr double kMinAnalogueGain = 1.0; - -/* \todo Honour the FrameDurationLimits control instead of hardcoding a limit */ -static constexpr utils::Duration kMaxExposureTime = 60ms; - /* Histogram constants */ static constexpr uint32_t knumHistogramBins = 256; Agc::Agc() - : minExposureTime_(0s), maxExposureTime_(0s) { } @@ -76,11 +68,18 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.parseTuningData(tuningData); + ret = agc_.init(tuningData); if (ret) return ret; - context.ctrlMap.merge(agc_.controls()); + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); + if (ret) + return ret; return 0; } @@ -95,32 +94,36 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - const IPASessionConfiguration &configuration = context.configuration; - IPAActiveState &activeState = context.activeState; - - stride_ = configuration.grid.stride; - bdsGrid_ = configuration.grid.bdsGrid; - - minExposureTime_ = configuration.agc.minExposureTime; - maxExposureTime_ = std::min(configuration.agc.maxExposureTime, - kMaxExposureTime); - - minAnalogueGain_ = std::max(configuration.agc.minAnalogueGain, kMinAnalogueGain); - maxAnalogueGain_ = configuration.agc.maxAnalogueGain; - - /* Configure the default exposure and gain. */ - activeState.agc.gain = minAnalogueGain_; - activeState.agc.exposure = 10ms / configuration.sensor.lineDuration; - - context.activeState.agc.constraintMode = agc_.constraintModes().begin()->first; - context.activeState.agc.exposureMode = agc_.exposureModeHelpers().begin()->first; + stride_ = context.configuration.grid.stride; + bdsGrid_ = context.configuration.grid.bdsGrid; + + return agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, // \todo if not raw? + }); +} - /* \todo Run this again when FrameDurationLimits is passed in */ - agc_.setLimits(minExposureTime_, maxExposureTime_, minAnalogueGain_, - maxAnalogueGain_, {}); - agc_.resetFrameCount(); +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) +{ + agc_.queueRequest(context.configuration.agc, context.activeState.agc, + frameContext.agc, controls); +} - return 0; +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Agc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] ipu3_uapi_params *params) +{ + agc_.prepare(context.activeState.agc, frameContext.agc); } Histogram Agc::parseStatistics(const ipu3_uapi_stats_3a *stats, @@ -228,16 +231,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, { Histogram hist = parseStatistics(stats, context.configuration.grid.bdsGrid); - /* - * The Agc algorithm needs to know the effective exposure value that was - * applied to the sensor when the statistics were collected. - */ - utils::Duration exposureTime = context.configuration.sensor.lineDuration - * frameContext.sensor.exposure; - double analogueGain = frameContext.sensor.gain; - utils::Duration effectiveExposureValue = exposureTime * analogueGain; - - const auto &newEv = agc_.calculateNewEv({ + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ .traits = AgcTraits{ rgbTriples_, {{ @@ -248,29 +242,9 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, bdsGrid_, }, .yHist = hist, - .effectiveExposureValue = effectiveExposureValue, - .constraintModeIndex = context.activeState.agc.constraintMode, - .exposureModeIndex = context.activeState.agc.exposureMode, - }); - - LOG(IPU3Agc, Debug) - << "Divided up exposure time, analogue gain and digital gain are " - << newEv.exposureTime << ", " << newEv.analogueGain << " and " << newEv.digitalGain; - - IPAActiveState &activeState = context.activeState; - /* Update the estimated exposure time and gain. */ - activeState.agc.exposure = newEv.exposureTime / context.configuration.sensor.lineDuration; - activeState.agc.gain = newEv.analogueGain; - - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); - metadata.set(controls::ExposureTime, exposureTime.get()); - - /* \todo Use VBlank value calculated from each frame exposure. */ - uint32_t vTotal = context.configuration.sensor.size.height - + context.configuration.sensor.defVBlank; - utils::Duration frameDuration = context.configuration.sensor.lineDuration - * vTotal; - metadata.set(controls::FrameDuration, frameDuration.get()); + .exposure = frameContext.sensor.exposure, + .gain = frameContext.sensor.gain, + }}, metadata); } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/ipu3/algorithms/agc.h b/src/ipa/ipu3/algorithms/agc.h index d08da7600e..634b1f8a7a 100644 --- a/src/ipa/ipu3/algorithms/agc.h +++ b/src/ipa/ipu3/algorithms/agc.h @@ -13,7 +13,7 @@ #include -#include "libipa/agc_mean_luminance.h" +#include "libipa/agc.h" #include "libipa/histogram.h" #include "algorithm.h" @@ -32,6 +32,11 @@ public: int init(IPAContext &context, const ValueNode &tuningData) override; int configure(IPAContext &context, const IPAConfigInfo &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, + ipu3_uapi_params *params) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const ipu3_uapi_stats_3a *stats, @@ -41,17 +46,11 @@ private: Histogram parseStatistics(const ipu3_uapi_stats_3a *stats, const ipu3_uapi_grid_config &grid); - utils::Duration minExposureTime_; - utils::Duration maxExposureTime_; - - double minAnalogueGain_; - double maxAnalogueGain_; - uint32_t stride_; ipu3_uapi_grid_config bdsGrid_; std::vector> rgbTriples_; - AgcMeanLuminance agc_; + AgcAlgorithm agc_; }; } /* namespace ipa::ipu3::algorithms */ diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp index 3b22f79176..c358b09981 100644 --- a/src/ipa/ipu3/ipa_context.cpp +++ b/src/ipa/ipu3/ipa_context.cpp @@ -46,12 +46,21 @@ namespace libcamera::ipa::ipu3 { * \var IPAContext::configuration * \brief The IPA session configuration, immutable during the session * + * \var IPAContext::sensorInfo + * \brief The IPA camera session details, immutable during the session + * + * \var IPAContext::sensorControls + * \brief The camera sensor controls, immutable during the session + * * \var IPAContext::frameContexts * \brief Ring buffer of the IPAFrameContext(s) * * \var IPAContext::activeState * \brief The current state of IPA algorithms * + * \var IPAContext::camHelper + * \brief The camera sensor helper + * * \var IPAContext::ctrlMap * \brief A ControlInfoMap::Map of controls populated by the algorithms */ @@ -95,48 +104,11 @@ namespace libcamera::ipa::ipu3 { /** * \var IPASessionConfiguration::agc * \brief AGC parameters configuration of the IPA - * - * \var IPASessionConfiguration::agc.minExposureTime - * \brief Minimum exposure time supported with the configured sensor - * - * \var IPASessionConfiguration::agc.maxExposureTime - * \brief Maximum exposure time supported with the configured sensor - * - * \var IPASessionConfiguration::agc.minAnalogueGain - * \brief Minimum analogue gain supported with the configured sensor - * - * \var IPASessionConfiguration::agc.maxAnalogueGain - * \brief Maximum analogue gain supported with the configured sensor - */ - -/** - * \var IPASessionConfiguration::sensor - * \brief Sensor-specific configuration of the IPA - * - * \var IPASessionConfiguration::sensor.lineDuration - * \brief Line duration in microseconds - * - * \var IPASessionConfiguration::sensor.defVBlank - * \brief The default vblank value of the sensor - * - * \var IPASessionConfiguration::sensor.size - * \brief Sensor output resolution */ /** * \var IPAActiveState::agc * \brief Context for the Automatic Gain Control algorithm - * - * The exposure and gain determined are expected to be applied to the sensor - * at the earliest opportunity. - * - * \var IPAActiveState::agc.exposure - * \brief Exposure time expressed as a number of lines - * - * \var IPAActiveState::agc.gain - * \brief Analogue gain multiplier - * - * The gain should be adapted to the sensor specific gain code before applying. */ /** @@ -185,6 +157,9 @@ namespace libcamera::ipa::ipu3 { * * \var IPAFrameContext::sensor.gain * \brief Analogue gain multiplier + * + * \var IPAFrameContext::agc + * \brief Per-frame state for the AGC algorithm */ } /* namespace libcamera::ipa::ipu3 */ diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h index 97fcf06cd4..51133b10d5 100644 --- a/src/ipa/ipu3/ipa_context.h +++ b/src/ipa/ipu3/ipa_context.h @@ -15,6 +15,8 @@ #include #include +#include +#include #include namespace libcamera { @@ -32,18 +34,8 @@ struct IPASessionConfiguration { ipu3_uapi_grid_config afGrid; } af; - struct { - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; + struct Agc : agc::Session { } agc; - - struct { - int32_t defVBlank; - utils::Duration lineDuration; - Size size; - } sensor; }; struct IPAActiveState { @@ -53,11 +45,7 @@ struct IPAActiveState { bool stable; } af; - struct { - uint32_t exposure; - double gain; - uint32_t constraintMode; - uint32_t exposureMode; + struct Agc : agc::ActiveState { } agc; struct { @@ -81,6 +69,9 @@ struct IPAFrameContext : public FrameContext { uint32_t exposure; double gain; } sensor; + + struct Agc : agc::FrameContext { + } agc; }; struct IPAContext { @@ -90,10 +81,14 @@ struct IPAContext { } IPASessionConfiguration configuration; + IPACameraSensorInfo sensorInfo; + ControlInfoMap sensorControls; IPAActiveState activeState; FCQueue frameContexts; + std::unique_ptr camHelper; + ControlInfoMap::Map ctrlMap; }; diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp index 51379808cd..0935050a33 100644 --- a/src/ipa/ipu3/ipu3.cpp +++ b/src/ipa/ipu3/ipu3.cpp @@ -6,7 +6,6 @@ */ #include -#include #include #include #include @@ -166,24 +165,15 @@ protected: std::string logPrefix() const override; private: - void updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, - ControlInfoMap *ipaControls); - void updateSessionConfiguration(const ControlInfoMap &sensorControls); + void updateControls(ControlInfoMap *ipaControls); void setControls(unsigned int frame); void calculateBdsGrid(const Size &bdsOutputSize); std::map buffers_; - ControlInfoMap sensorCtrls_; ControlInfoMap lensCtrls_; - IPACameraSensorInfo sensorInfo_; - - /* Interface to the Camera Helper */ - std::unique_ptr camHelper_; - /* Local parameter storage */ struct IPAContext context_; }; @@ -198,36 +188,6 @@ std::string IPAIPU3::logPrefix() const return "ipu3"; } -/** - * \brief Compute IPASessionConfiguration using the sensor information and the - * sensor V4L2 controls - */ -void IPAIPU3::updateSessionConfiguration(const ControlInfoMap &sensorControls) -{ - const ControlInfo vBlank = sensorControls.find(V4L2_CID_VBLANK)->second; - context_.configuration.sensor.defVBlank = vBlank.def().get(); - - const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get(); - int32_t maxExposure = v4l2Exposure.max().get(); - - const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - int32_t minGain = v4l2Gain.min().get(); - int32_t maxGain = v4l2Gain.max().get(); - - /* - * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for exposure time and analogue gain. - * As it depends on the sensor, update it with the controls. - * - * \todo take VBLANK into account for maximum exposure time - */ - context_.configuration.agc.minExposureTime = minExposure * context_.configuration.sensor.lineDuration; - context_.configuration.agc.maxExposureTime = maxExposure * context_.configuration.sensor.lineDuration; - context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain); - context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain); -} - /** * \brief Compute camera controls using the sensor information and the sensor * V4L2 controls @@ -240,54 +200,12 @@ void IPAIPU3::updateSessionConfiguration(const ControlInfoMap &sensorControls) * - controls::ExposureTime * - controls::FrameDurationLimits */ -void IPAIPU3::updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, - ControlInfoMap *ipaControls) +void IPAIPU3::updateControls(ControlInfoMap *ipaControls) { - ControlInfoMap::Map controls{}; - double lineDuration = context_.configuration.sensor.lineDuration.get(); - - /* - * Compute exposure time limits by using line length and pixel rate - * converted to microseconds. Use the V4L2_CID_EXPOSURE control to get - * exposure min, max and default and convert it from lines to - * microseconds. - */ - const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get() * lineDuration; - int32_t maxExposure = v4l2Exposure.max().get() * lineDuration; - int32_t defExposure = v4l2Exposure.def().get() * lineDuration; - controls[&controls::ExposureTime] = ControlInfo(minExposure, maxExposure, - defExposure); + ControlInfoMap::Map ctrlMap; - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - const ControlInfo &v4l2HBlank = sensorControls.find(V4L2_CID_HBLANK)->second; - uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = sensorInfo.outputSize.width + hblank; - - const ControlInfo &v4l2VBlank = sensorControls.find(V4L2_CID_VBLANK)->second; - std::array frameHeights{ - v4l2VBlank.min().get() + sensorInfo.outputSize.height, - v4l2VBlank.max().get() + sensorInfo.outputSize.height, - v4l2VBlank.def().get() + sensorInfo.outputSize.height, - }; - - std::array frameDurations; - for (unsigned int i = 0; i < frameHeights.size(); ++i) { - uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U); - } - controls[&controls::FrameDurationLimits] = ControlInfo(frameDurations[0], - frameDurations[1], - Span{ { frameDurations[2], frameDurations[2] } }); - - controls.insert(context_.ctrlMap.begin(), context_.ctrlMap.end()); - *ipaControls = ControlInfoMap(std::move(controls), controls::controls); + ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end()); + *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); } /** @@ -302,18 +220,19 @@ int IPAIPU3::init(const IPASettings &settings, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls) { - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); - if (camHelper_ == nullptr) { + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!context_.camHelper) { LOG(IPAIPU3, Error) << "Failed to create camera sensor helper for " << settings.sensorModel; return -ENODEV; } + context_.sensorInfo = sensorInfo; + context_.sensorControls = sensorControls; + /* Clean context */ context_.configuration = {}; - context_.configuration.sensor.lineDuration = - sensorInfo.minLineLength * 1.0s / sensorInfo.pixelRate; /* Load the tuning data file. */ File file(settings.configurationFile); @@ -347,7 +266,7 @@ int IPAIPU3::init(const IPASettings &settings, return ret; /* Initialize controls. */ - updateControls(sensorInfo, sensorControls, ipaControls); + updateControls(ipaControls); return 0; } @@ -466,7 +385,8 @@ int IPAIPU3::configure(const IPAConfigInfo &configInfo, return -ENODATA; } - sensorInfo_ = configInfo.sensorInfo; + context_.sensorInfo = configInfo.sensorInfo; + context_.sensorControls = configInfo.sensorControls; lensCtrls_ = configInfo.lensControls; @@ -475,31 +395,16 @@ int IPAIPU3::configure(const IPAConfigInfo &configInfo, context_.configuration = {}; context_.frameContexts.clear(); - /* Initialise the sensor configuration. */ - context_.configuration.sensor.lineDuration = - sensorInfo_.minLineLength * 1.0s / sensorInfo_.pixelRate; - context_.configuration.sensor.size = sensorInfo_.outputSize; - - /* - * Compute the sensor V4L2 controls to be used by the algorithms and - * to be set on the sensor. - */ - sensorCtrls_ = configInfo.sensorControls; - calculateBdsGrid(configInfo.bdsOutputSize); - /* Update the camera controls using the new sensor settings. */ - updateControls(sensorInfo_, sensorCtrls_, ipaControls); - - /* Update the IPASessionConfiguration using the sensor settings. */ - updateSessionConfiguration(sensorCtrls_); - for (const auto &algo : algorithms()) { int ret = algo->configure(context_, configInfo); if (ret) return ret; } + updateControls(ipaControls); + return 0; } @@ -598,7 +503,7 @@ void IPAIPU3::processStats(const uint32_t frame, IPAFrameContext &frameContext = context_.frameContexts.get(frame); std::tie(frameContext.sensor.exposure, frameContext.sensor.gain) - = agc::extractControls(sensorControls, camHelper_.get()); + = agc::extractControls(sensorControls, context_.camHelper.get()); ControlList metadata(controls::controls); @@ -643,10 +548,12 @@ void IPAIPU3::queueRequest(const uint32_t frame, const ControlList &controls) */ void IPAIPU3::setControls(unsigned int frame) { - ControlList ctrls(sensorCtrls_); + IPAFrameContext &frameContext = context_.frameContexts.get(frame); + + ControlList ctrls(context_.sensorControls); agc::prepareControls( - ctrls, camHelper_.get(), - context_.activeState.agc.exposure, context_.activeState.agc.gain + ctrls, context_.camHelper.get(), + frameContext.agc.exposure, frameContext.agc.gain ); ControlList lensCtrls(lensCtrls_); From patchwork Thu Jul 23 15:43:12 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: 27478 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 2DC0BC3264 for ; Thu, 23 Jul 2026 15:44:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9809567F2B; Thu, 23 Jul 2026 17:44:14 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Bxpt61Ky"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0569D67EBA for ; Thu, 23 Jul 2026 17:43:38 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2F62B23B9 for ; Thu, 23 Jul 2026 17:42:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821357; bh=aWh+IgfsTvy5vDPYR92YbZbmyRF0EDyYCrGoKHPcQPE=; h=From:To:Subject:Date:In-Reply-To:References:From; b=Bxpt61KyNhC5alKblhPrJEVwXOAyeCjqq8YVBZVZglaT93TPIM01NhhtfwY3AnEih keli8LKcxTgKRkzAULqxjC+byJqTdWKxjEOv/ssQqSNwhBO6M2xPxFy7v3d+0fKl9X ZX0ra7p2hsW1V3Z8+kOdmoITNVC6qk+QUbepbSno= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 29/43] ipa: libipa: agc_mean_luminance: Remove control handling Date: Thu, 23 Jul 2026 17:43:12 +0200 Message-ID: <20260723154327.1357866-30-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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 `AgcMeanLuminance` type does not do any actual control handling, so let's remove the `ControlInfoMap::Map` member, and let the user handle that using the `constraintModes()` / `exposureModeHelpers()` getters. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 23 +++++++++++++++++------ src/ipa/libipa/agc_mean_luminance.cpp | 20 +------------------- src/ipa/libipa/agc_mean_luminance.h | 8 -------- 3 files changed, 18 insertions(+), 33 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 54c5c6c349..27edc96fc6 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -400,16 +400,27 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, cons if (session.autoAllowed) { config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); - for (const auto &[id, info] : impl_.controls()) - config.ctrlMap[id] = info; + { + std::vector options; + for (const auto &[id, _] : impl_.constraintModes()) + options.emplace_back(id); + + config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(options); + } + + { + std::vector options; + for (const auto &[id, _] : impl_.exposureModeHelpers()) + options.emplace_back(id); + + config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options); + } } else { config.ctrlMap.erase(&controls::ExposureValue); - - for (const auto &[id, info] : impl_.controls()) - config.ctrlMap.erase(id); + config.ctrlMap.erase(&controls::AeConstraintMode); + config.ctrlMap.erase(&controls::AeExposureMode); } - return 0; } diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index a40b34b725..2c9c0b352c 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -237,8 +237,6 @@ int AgcMeanLuminance::parseConstraint(const ValueNode &modeDict, int32_t id) int AgcMeanLuminance::parseConstraintModes(const ValueNode &tuningData) { - std::vector availableConstraintModes; - const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; if (constraintModes.isDictionary()) { for (const auto &[modeName, modeDict] : constraintModes.asDict()) { @@ -258,8 +256,6 @@ int AgcMeanLuminance::parseConstraintModes(const ValueNode &tuningData) int ret = parseConstraint(modeDict, it->second); if (ret) return ret; - - availableConstraintModes.push_back(it->second); } } @@ -278,18 +274,13 @@ int AgcMeanLuminance::parseConstraintModes(const ValueNode &tuningData) }; constraintModes_[controls::ConstraintNormal].push_back(std::move(constraint)); - availableConstraintModes.push_back(controls::ConstraintNormal); } - controls_[&controls::AeConstraintMode] = ControlInfo(availableConstraintModes); - return 0; } int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) { - std::vector availableExposureModes; - const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; if (exposureModes.isDictionary()) { for (const auto &[modeName, modeValues] : exposureModes.asDict()) { @@ -332,7 +323,6 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) } exposureModeHelpers_.try_emplace(it->second, stages); - availableExposureModes.push_back(it->second); } } @@ -342,14 +332,11 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) * in the ExposureModeHelper simply driving the exposure time as high as * possible before touching gain. */ - if (availableExposureModes.empty()) { + if (exposureModeHelpers_.empty()) { exposureModeHelpers_.try_emplace(controls::ExposureNormal, Span>{}); - availableExposureModes.push_back(controls::ExposureNormal); } - controls_[&controls::AeExposureMode] = ControlInfo(availableExposureModes); - return 0; } @@ -473,11 +460,6 @@ void AgcMeanLuminance::setLimits(utils::Duration minExposureTime, * \brief Get the ExposureModeHelpers that have been parsed from tuning data */ -/** - * \fn AgcMeanLuminance::controls() - * \brief Get the controls that have been generated after parsing tuning data - */ - /** * \brief Estimate the initial gain needed to achieve a relative luminance * target diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index 5e986be008..46a4d3e3f3 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -13,8 +13,6 @@ #include -#include - #include "libcamera/internal/value_node.h" #include "exposure_mode_helper.h" @@ -63,11 +61,6 @@ public: return exposureModeHelpers_; } - ControlInfoMap::Map controls() - { - return controls_; - } - struct Params { const Traits &traits; const Histogram &yHist; @@ -108,7 +101,6 @@ private: std::vector additionalConstraints_; std::map> constraintModes_; std::map exposureModeHelpers_; - ControlInfoMap::Map controls_; }; } /* namespace ipa */ From patchwork Thu Jul 23 15:43:13 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: 27479 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 20239C3333 for ; Thu, 23 Jul 2026 15:44:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B534867ECC; Thu, 23 Jul 2026 17:44:15 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="KhngJhzG"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3D5C267EE5 for ; Thu, 23 Jul 2026 17:43:38 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 651F31F0F for ; Thu, 23 Jul 2026 17:42:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821357; bh=vYNKrtwpQ7pF2/VIa2Jud7H5GYX8mbuk5zg6vblXO2g=; h=From:To:Subject:Date:In-Reply-To:References:From; b=KhngJhzGVbCGD77RNJX75WAXnV0k6a5fVWoleEJxdxQkSE3OegqbQwd6fu1OEZPi8 0FlKnzoyK20TOVBC89XeLOjwOKwAV4fYS3MvoTERBeHhg/Y6VsiS7dpJGBKZaN89k8 WDTz/tKpyNih9OA/lrw31ZomkIK++0r80XywqbSE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 30/43] ipa: libipa: histogram: total(): Use `back()` Date: Thu, 23 Jul 2026 17:43:13 +0200 Message-ID: <20260723154327.1357866-31-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Use the back method of `std::vector` to get the last element instead of indexing; this is clearer. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/histogram.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/libipa/histogram.h b/src/ipa/libipa/histogram.h index 8cf8bb6d16..0c02dda2ba 100644 --- a/src/ipa/libipa/histogram.h +++ b/src/ipa/libipa/histogram.h @@ -37,7 +37,7 @@ public: size_t bins() const { return cumulative_.size() - 1; } Span data() const { return cumulative_; } - uint64_t total() const { return cumulative_[cumulative_.size() - 1]; } + uint64_t total() const { return cumulative_.back(); } uint64_t cumulativeFrequency(double bin) const; double quantile(double q, uint32_t first = 0, uint32_t last = UINT_MAX) const; double interQuantileMean(double lowQuantile, double hiQuantile) const; From patchwork Thu Jul 23 15:43:14 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: 27481 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 2D161C3303 for ; Thu, 23 Jul 2026 15:44:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D8BE567F47; Thu, 23 Jul 2026 17:44:17 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QRfz/BTY"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8630667ECD for ; Thu, 23 Jul 2026 17:43:38 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9A97723B9 for ; Thu, 23 Jul 2026 17:42:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821357; bh=2kiikFk3037quxt7ucPm4CXAxdeytL1/WZOa4jGuNEY=; h=From:To:Subject:Date:In-Reply-To:References:From; b=QRfz/BTYS8hywYKRipActou6YRg2fGVwmrPzp11OVTN4DXVItZzjhvbGzc/D1IexY e7M+sqn7Crfe4naS6HC7dWYa0AF/Suy95nIG6xN4l1g9nweRZZjHj69rtLQaioHI3H xGza+UBRQwqOukGqPE8FjbVT2UvBndTuaCP3BTNI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 31/43] ipa: libipa: histogram: Add `operator[]` to query bin Date: Thu, 23 Jul 2026 17:43:14 +0200 Message-ID: <20260723154327.1357866-32-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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 `operator[]` that takes an integral bin number and returns the count pertaining to that specific bin. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/histogram.cpp | 6 ++++++ src/ipa/libipa/histogram.h | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/src/ipa/libipa/histogram.cpp b/src/ipa/libipa/histogram.cpp index bcf2639082..2f91ffb6d7 100644 --- a/src/ipa/libipa/histogram.cpp +++ b/src/ipa/libipa/histogram.cpp @@ -74,6 +74,12 @@ Histogram::Histogram(Span data) * \return Number of values */ +/** + * \fn Histogram::operator[](size_t bin) const + * \brief Retrieve the value for \a bin + * \return The value in \a bin, or 0 if \a bin is out of range + */ + /** * \brief Cumulative frequency up to a (fractional) point in a bin * \param[in] bin The bin up to which to cumulate diff --git a/src/ipa/libipa/histogram.h b/src/ipa/libipa/histogram.h index 0c02dda2ba..b3f59b7c31 100644 --- a/src/ipa/libipa/histogram.h +++ b/src/ipa/libipa/histogram.h @@ -42,6 +42,13 @@ public: double quantile(double q, uint32_t first = 0, uint32_t last = UINT_MAX) const; double interQuantileMean(double lowQuantile, double hiQuantile) const; + uint32_t operator[](size_t bin) const + { + return bin < bins() + ? cumulative_[bin + 1] - cumulative_[bin] + : 0; + } + private: std::vector cumulative_; }; From patchwork Thu Jul 23 15:43:15 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: 27480 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 3CD78C3334 for ; Thu, 23 Jul 2026 15:44:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B6E2E67F40; Thu, 23 Jul 2026 17:44:16 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="lYZCtDss"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B912067EF8 for ; Thu, 23 Jul 2026 17:43:38 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DCB881F0F for ; Thu, 23 Jul 2026 17:42:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821358; bh=XWYl5QGq6gBfqtoA0bh8WYysMWXQrmWXfdW7cIDgIR0=; h=From:To:Subject:Date:In-Reply-To:References:From; b=lYZCtDss/aoR6YW2ny0k1i/NNa1UeYQanr9ST3cUgduy092nJXWBQajYRqgiB/IKa ZL9F3EdSuM2xEbBAOzH64n+WkIhEtEWmhRNI5LrS8sp/e6LqYB0dyi3SqWzAUQ8WHY dGEEsL6xW+J4ZrkcBpJERbCB7qEEKFMkw44Zokng= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 32/43] ipa: simple: Store exposure as `uint32_t` Date: Thu, 23 Jul 2026 17:43:15 +0200 Message-ID: <20260723154327.1357866-33-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Every other IPA module does that, so switch to `uint32_t` here as well. Signed-off-by: Barnabás Pőcze --- src/ipa/simple/algorithms/agc.cpp | 6 +++--- src/ipa/simple/ipa_context.h | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index e9bcb2c032..93505f7665 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -67,7 +67,7 @@ Agc::Agc() void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV) { - int32_t exposure = frameContext.sensor.exposure; + uint32_t exposure = frameContext.sensor.exposure; double again = frameContext.sensor.gain; double error = kExposureOptimal - exposureMSV; @@ -87,7 +87,7 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou if (factor > 1.0f) { /* Scene too dark: increase exposure first, then gain. */ if (exposure < context.configuration.agc.exposureMax) { - int32_t next = static_cast(exposure * factor); + uint32_t next = exposure * factor; exposure = std::max(next, exposure + 1); } else { double next = again * factor; @@ -105,7 +105,7 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou else again = next; } else { - int32_t next = static_cast(exposure * factor); + uint32_t next = exposure * factor; exposure = std::min(next, exposure - 1); } } diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index d35fb1e91d..980dc93cc4 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -26,7 +26,7 @@ namespace ipa::soft { struct IPASessionConfiguration { struct { - int32_t exposureMin, exposureMax; + uint32_t exposureMin, exposureMax; double againMin, againMax, again10, againMinStep; utils::Duration lineDuration; } agc; @@ -37,14 +37,14 @@ struct IPASessionConfiguration { struct IPAActiveState { struct { - int32_t exposure; + uint32_t exposure; double again; bool valid; } agc; struct { uint8_t level; - int32_t lastExposure; + uint32_t lastExposure; double lastGain; } blc; @@ -67,12 +67,12 @@ struct IPAFrameContext : public FrameContext { Matrix ccm; struct { - int32_t exposure; + uint32_t exposure; double gain; } agc; struct { - int32_t exposure; + uint32_t exposure; double gain; } sensor; From patchwork Thu Jul 23 15:43:16 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: 27482 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 7A3AFC32CE for ; Thu, 23 Jul 2026 15:44:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0455767F52; Thu, 23 Jul 2026 17:44:19 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="oGKB23Ux"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id F1AA667EFB for ; Thu, 23 Jul 2026 17:43:38 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1FA7D23B9 for ; Thu, 23 Jul 2026 17:42:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821358; bh=JnG6U3ReObdvdyhBhEGao+Rl2y+v4KH1tXn9bxTK8yA=; h=From:To:Subject:Date:In-Reply-To:References:From; b=oGKB23Uxq3lpJ3g8BobRu7vuXdJI3urZO0s72+3+QygrniQPidkgV3O1MRjHFYbNx i41OrtWNwYlf2M7l2GsReTa16lMeyNh7GFhz3tlF7jKFKM1TkGdeZVXlHqVCEvhULc hdiUIsq8pgyxJ8gGa/JdFofbyETwJ9APSGqzA2vI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 33/43] ipa: simple: agc: Separate MSV calculation Date: Thu, 23 Jul 2026 17:43:16 +0200 Message-ID: <20260723154327.1357866-34-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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 a separate function that does the MSV calculation. Signed-off-by: Barnabás Pőcze --- src/ipa/simple/algorithms/agc.cpp | 72 ++++++++++++++++++------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index 93505f7665..f959abeba5 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -61,6 +62,44 @@ static constexpr float kExpProportionalGain = 0.04; */ static constexpr float kExpMaxStep = 0.15; +namespace { + +std::optional calculateMSV(const SwIspStats::Histogram &histogram, uint8_t blackLevel) +{ + /* + * Calculate Mean Sample Value (MSV) according to formula from: + * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf + */ + const unsigned int blackLevelHistIdx = + blackLevel / (256 / SwIspStats::kYHistogramSize); + const unsigned int histogramSize = + SwIspStats::kYHistogramSize - blackLevelHistIdx; + const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount; + const unsigned int yHistValsPerBinMod = + histogramSize / (histogramSize % kExposureBinsCount + 1); + int exposureBins[kExposureBinsCount] = {}; + unsigned int denom = 0; + unsigned int num = 0; + + if (yHistValsPerBin == 0) + return {}; + + for (unsigned int i = 0; i < histogramSize; i++) { + unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin; + exposureBins[idx] += histogram[blackLevelHistIdx + i]; + } + + for (unsigned int i = 0; i < kExposureBinsCount; i++) { + LOG(IPASoftExposure, Debug) << i << ": " << exposureBins[i]; + denom += exposureBins[i]; + num += exposureBins[i] * (i + 1); + } + + return (denom == 0 ? 0 : static_cast(num) / denom); +} + +} /* namespace */ + Agc::Agc() { } @@ -158,41 +197,14 @@ void Agc::process(IPAContext &context, return; } - /* - * Calculate Mean Sample Value (MSV) according to formula from: - * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf - */ - const auto &histogram = stats->yHistogram; - const unsigned int blackLevelHistIdx = - context.activeState.blc.level / (256 / SwIspStats::kYHistogramSize); - const unsigned int histogramSize = - SwIspStats::kYHistogramSize - blackLevelHistIdx; - const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount; - const unsigned int yHistValsPerBinMod = - histogramSize / (histogramSize % kExposureBinsCount + 1); - int exposureBins[kExposureBinsCount] = {}; - unsigned int denom = 0; - unsigned int num = 0; - - if (yHistValsPerBin == 0) { + auto exposureMSV = calculateMSV(stats->yHistogram, context.activeState.blc.level); + if (!exposureMSV) { LOG(IPASoftExposure, Debug) << "Not adjusting exposure due to insufficient histogram data"; return; } - for (unsigned int i = 0; i < histogramSize; i++) { - unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin; - exposureBins[idx] += histogram[blackLevelHistIdx + i]; - } - - for (unsigned int i = 0; i < kExposureBinsCount; i++) { - LOG(IPASoftExposure, Debug) << i << ": " << exposureBins[i]; - denom += exposureBins[i]; - num += exposureBins[i] * (i + 1); - } - - float exposureMSV = (denom == 0 ? 0 : static_cast(num) / denom); - updateExposure(context, frameContext, exposureMSV); + updateExposure(context, frameContext, *exposureMSV); } REGISTER_IPA_ALGORITHM(Agc, "Agc") From patchwork Thu Jul 23 15:43:17 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: 27483 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 D6F49C3336 for ; Thu, 23 Jul 2026 15:44:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8466967F72; Thu, 23 Jul 2026 17:44:20 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BnFNrcsh"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2EFFE67EE3 for ; Thu, 23 Jul 2026 17:43:39 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 57ADF1F0F for ; Thu, 23 Jul 2026 17:42:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821358; bh=3VJsg23vpRZkimnUndQSKOVsX62mssI5yD3j+TV5wQ0=; h=From:To:Subject:Date:In-Reply-To:References:From; b=BnFNrcshFN6lndTC1F/GE1P9qhYwd3yJ5r8UxfH7TIpT6O1qahQl/GBf3y8TiRlqX wPMQpQfsLDcekbMZnqdBEl3Rk7CDo4SW5nKcQcXKhwcNzgh6YvrOFk5HKa+uDLh1LW aPEAXF9FVuB0X0TCd0TI2rheBHyGaS8XQV8Z1JE8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 34/43] ipa: simple: agc: Simplify min gain step handling Date: Thu, 23 Jul 2026 17:43:17 +0200 Message-ID: <20260723154327.1357866-35-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Use `std::{min,max}()` just like it is done for the exposure. Signed-off-by: Barnabás Pőcze --- src/ipa/simple/algorithms/agc.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index f959abeba5..b25c303835 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -130,19 +130,13 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou exposure = std::max(next, exposure + 1); } else { double next = again * factor; - if (next - again < context.configuration.agc.againMinStep) - again += context.configuration.agc.againMinStep; - else - again = next; + again = std::max(next, again + context.configuration.agc.againMinStep); } } else { /* Scene too bright: decrease gain first, then exposure. */ if (again > context.configuration.agc.again10) { double next = again * factor; - if (again - next < context.configuration.agc.againMinStep) - again -= context.configuration.agc.againMinStep; - else - again = next; + again = std::max(next, again - context.configuration.agc.againMinStep); } else { uint32_t next = exposure * factor; exposure = std::min(next, exposure - 1); From patchwork Thu Jul 23 15:43:18 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: 27484 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 A64FAC3338 for ; Thu, 23 Jul 2026 15:44:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 29FD267F4A; Thu, 23 Jul 2026 17:44:21 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Ob4oGJRf"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 65E1867EE2 for ; Thu, 23 Jul 2026 17:43:39 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8D63623B9 for ; Thu, 23 Jul 2026 17:42:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821358; bh=E985DDPrSQTUb2R+O+XZ6N1q8b6Z6AIwpAePZa0yVVk=; h=From:To:Subject:Date:In-Reply-To:References:From; b=Ob4oGJRfQPL9HFqXhEW90r9h0EgeF98L1QH1EwaNcsXX30twajdrN02acsaR+5EIl CttQqmIqypmJjcmLKRgiWOismsjDi1FeT61rX05HPb3o/XJuLw+wYTCSwR6u63z43l TqpnQ64wmV+Fm+UX0DB4K+5QEo6uftkEARZHKacQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 35/43] ipa: simple: agc: Use `Histogram` Date: Thu, 23 Jul 2026 17:43:18 +0200 Message-ID: <20260723154327.1357866-36-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Use the generic `Histogram` type. This is needed to decouple the implementation from the software isp, in order to make this usable by other components. Signed-off-by: Barnabás Pőcze --- src/ipa/simple/algorithms/agc.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index b25c303835..cc33b269bf 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -14,6 +14,8 @@ #include +#include + #include "control_ids.h" namespace libcamera { @@ -64,16 +66,16 @@ static constexpr float kExpMaxStep = 0.15; namespace { -std::optional calculateMSV(const SwIspStats::Histogram &histogram, uint8_t blackLevel) +std::optional calculateMSV(const Histogram &histogram, uint8_t blackLevel) { /* * Calculate Mean Sample Value (MSV) according to formula from: * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf */ const unsigned int blackLevelHistIdx = - blackLevel / (256 / SwIspStats::kYHistogramSize); + blackLevel * histogram.bins() / 256; const unsigned int histogramSize = - SwIspStats::kYHistogramSize - blackLevelHistIdx; + histogram.bins() - blackLevelHistIdx; const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount; const unsigned int yHistValsPerBinMod = histogramSize / (histogramSize % kExposureBinsCount + 1); @@ -191,7 +193,7 @@ void Agc::process(IPAContext &context, return; } - auto exposureMSV = calculateMSV(stats->yHistogram, context.activeState.blc.level); + auto exposureMSV = calculateMSV({ stats->yHistogram }, context.activeState.blc.level); if (!exposureMSV) { LOG(IPASoftExposure, Debug) << "Not adjusting exposure due to insufficient histogram data"; From patchwork Thu Jul 23 15:43:19 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: 27485 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 9BE69BE080 for ; Thu, 23 Jul 2026 15:44:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1B40B67F82; Thu, 23 Jul 2026 17:44:22 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ND4zj1N/"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A014167EEF for ; Thu, 23 Jul 2026 17:43:39 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C397B1F0F for ; Thu, 23 Jul 2026 17:42:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821358; bh=Ch7Eiq1lpvW3U4J8QDDus2uEA7uHeblo7NL8vJMXO1U=; h=From:To:Subject:Date:In-Reply-To:References:From; b=ND4zj1N/BWy1Uiu9xVRQJzYi7w1Rf+zQOiFFTPVLmKCJjd0h+Kqbtng1MOaSrMLCK HJtJre04FuknBIHWvOAzih4LJSPYNeRzgBveDv4dBoCdnGslTJdU6UdqcCI9B/UAph H30ZKta5wyhWC6Ur1AHubnG6o2RlcoOiYC4rooHc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 36/43] ipa: simple: agc: Adjust histogram for black level Date: Thu, 23 Jul 2026 17:43:19 +0200 Message-ID: <20260723154327.1357866-37-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Instead of providing the black separately, adjust the luminance histogram according to the current black level. This changes the behaviour slightly as previously everything under the black level was ignored, and only the remaining part was split into the 5 bins, but now the full histogram is split into the 5 bins. TODO: or should the previous behaviour be preserved and a smaller histogram be used without adjustments? TODO: is it `i < blackLevelHistIdx` or `i <= blackLevelHistIdx` ??? Signed-off-by: Barnabás Pőcze --- src/ipa/simple/algorithms/agc.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index cc33b269bf..199b444f5a 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -66,19 +67,15 @@ static constexpr float kExpMaxStep = 0.15; namespace { -std::optional calculateMSV(const Histogram &histogram, uint8_t blackLevel) +std::optional calculateMSV(const Histogram &histogram) { /* * Calculate Mean Sample Value (MSV) according to formula from: * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf */ - const unsigned int blackLevelHistIdx = - blackLevel * histogram.bins() / 256; - const unsigned int histogramSize = - histogram.bins() - blackLevelHistIdx; - const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount; + const unsigned int yHistValsPerBin = histogram.bins() / kExposureBinsCount; const unsigned int yHistValsPerBinMod = - histogramSize / (histogramSize % kExposureBinsCount + 1); + histogram.bins() / (histogram.bins() % kExposureBinsCount + 1); int exposureBins[kExposureBinsCount] = {}; unsigned int denom = 0; unsigned int num = 0; @@ -86,9 +83,9 @@ std::optional calculateMSV(const Histogram &histogram, uint8_t blackLevel if (yHistValsPerBin == 0) return {}; - for (unsigned int i = 0; i < histogramSize; i++) { + for (unsigned int i = 0; i < histogram.bins(); i++) { unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin; - exposureBins[idx] += histogram[blackLevelHistIdx + i]; + exposureBins[idx] += histogram[i]; } for (unsigned int i = 0; i < kExposureBinsCount; i++) { @@ -193,7 +190,14 @@ void Agc::process(IPAContext &context, return; } - auto exposureMSV = calculateMSV({ stats->yHistogram }, context.activeState.blc.level); + auto histogram = stats->yHistogram; + const unsigned int blackLevelHistIdx = + context.activeState.blc.level * std::size(histogram) / 256; + + for (unsigned int i = 1; i < blackLevelHistIdx; i++) + histogram[0] += std::exchange(histogram[i], 0); + + auto exposureMSV = calculateMSV({ histogram }); if (!exposureMSV) { LOG(IPASoftExposure, Debug) << "Not adjusting exposure due to insufficient histogram data"; From patchwork Thu Jul 23 15:43:20 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: 27486 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 AF3B5C32FE for ; Thu, 23 Jul 2026 15:44:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4B82167F32; Thu, 23 Jul 2026 17:44:23 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="PDO9VAmL"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E5FE667ECA for ; Thu, 23 Jul 2026 17:43:39 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 06D2423B9 for ; Thu, 23 Jul 2026 17:42:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821359; bh=lNNzOgGhrkEv51Bw/xKoFPbnCAEmMF1Eb6AmodeUOVM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=PDO9VAmLRyKuu3CYTjo3oGJd68m67edmm6ic+4ddPKCC4BijXppfMETGXzgEGkKev hec6GyPOy7vWOzKo37PCO1FAw3y8zKI7jD/50Q/Awu4zBdy7dx7LpEX+y6HixpByln TtUNeq46Bq1m8Berkv/IBsFR++n3rvWDZgW7m0fY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 37/43] ipa: simple: agc: Move to libipa Date: Thu, 23 Jul 2026 17:43:20 +0200 Message-ID: <20260723154327.1357866-38-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Move the agc algorithm used into a separate component in libipa, and use it. While it is also a (mean) luminance based algorithm, there is already `AgcMeanLuminance`, so call it `AgcMSV` (from "mean sample value"). This move also removes the dependency on the black level and makes it use the `Histogram` type instead of the software isp specific types. With the removal of the black level information, it is assumed to be 0 and a black level corrected histogram is expected. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_msv.cpp | 218 ++++++++++++++++++++++++++++++ src/ipa/libipa/agc_msv.h | 51 +++++++ src/ipa/libipa/meson.build | 2 + src/ipa/simple/algorithms/agc.cpp | 167 ++++------------------- src/ipa/simple/algorithms/agc.h | 7 +- 5 files changed, 300 insertions(+), 145 deletions(-) create mode 100644 src/ipa/libipa/agc_msv.cpp create mode 100644 src/ipa/libipa/agc_msv.h diff --git a/src/ipa/libipa/agc_msv.cpp b/src/ipa/libipa/agc_msv.cpp new file mode 100644 index 0000000000..63580a0055 --- /dev/null +++ b/src/ipa/libipa/agc_msv.cpp @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat Inc. + * + * Luminance mean sample value based AGC algorithm + */ + +#include "agc_msv.h" + +#include +#include +#include + +#include + +#include "histogram.h" + +namespace libcamera { + +namespace ipa { + +LOG_DEFINE_CATEGORY(AgcMSV) + +/** + * \class AgcMSV + * \brief Binned mean luminance based AGC algorithm + */ + +/** + * \class AgcMSV::Limits + * \brief Set of limits for the algorithm + * + * \var AgcMSV::Limits::exposure + * \brief Minimum and maximum allowed exposure (in lines) + * + * \var AgcMSV::Limits::gain + * \brief Minimum and maximum allowed gain + * + * \var AgcMSV::Limits::gainMinStep + * \brief Minimum allowed gain adjustment + * + * \var AgcMSV::Limits::gain1 + * \brief The gain value assumed to result in a gain of 1.0 + * + * The algorithm will not lower the gain value below this. + */ + +/** + * \struct AgcMSV::Params + * \brief Collection of parameters for the algorithm + * + * \var AgcMSV::Params::yHist + * \brief Luminance histogram of the frame + * + * \var AgcMSV::Params::exposure + * \brief Effective exposure of the frame + * + * \var AgcMSV::Params::gain + * \brief Effective gain of the frame + */ + +/** + * \struct AgcMSV::Result + * \brief Collection of results of the algorithm + * + * \var AgcMSV::Result::exposure + * \brief The applicable exposure (in lines) + * + * \var AgcMSV::Result::analogueGain + * \brief The applicable analogue gain + */ + +namespace { + +/* + * The number of bins to use for the optimal exposure calculations. + */ +static constexpr unsigned int kExposureBinsCount = 5; + +/* + * The exposure is optimal when the mean sample value of the histogram is + * in the middle of the range. + */ +static constexpr float kExposureOptimal = kExposureBinsCount / 2.0; + +/* + * This implements the hysteresis for the exposure adjustment. + * It is small enough to have the exposure close to the optimal, and is big + * enough to prevent the exposure from wobbling around the optimal value. + */ +static constexpr float kExposureSatisfactory = 0.2; + +/* + * Proportional gain for exposure/gain adjustment. Maps the MSV error to a + * multiplicative correction factor: + * + * factor = 1.0 + kExpProportionalGain * error + * + * With kExpProportionalGain = 0.04: + * - max error ~2.5 -> factor 1.10 (~10% step, same as before) + * - error 1.0 -> factor 1.04 (~4% step) + * - error 0.3 -> factor 1.012 (~1.2% step) + * + * This replaces the fixed 10% bang-bang step with a proportional correction + * that converges smoothly and avoids overshooting near the target. + */ +static constexpr float kExpProportionalGain = 0.04; + +/* + * Maximum multiplicative step per frame, to bound the correction when the + * scene changes dramatically. + */ +static constexpr float kExpMaxStep = 0.15; + +std::optional calculateMSV(const Histogram &histogram) +{ + /* + * Calculate Mean Sample Value (MSV) according to formula from: + * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf + */ + const unsigned int yHistValsPerBin = histogram.bins() / kExposureBinsCount; + const unsigned int yHistValsPerBinMod = + histogram.bins() / (histogram.bins() % kExposureBinsCount + 1); + int exposureBins[kExposureBinsCount] = {}; + unsigned int denom = 0; + unsigned int num = 0; + + if (yHistValsPerBin == 0) + return {}; + + for (unsigned int i = 0; i < histogram.bins(); i++) { + unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin; + exposureBins[idx] += histogram[i]; + } + + for (unsigned int i = 0; i < kExposureBinsCount; i++) { + LOG(AgcMSV, Debug) << i << ": " << exposureBins[i]; + denom += exposureBins[i]; + num += exposureBins[i] * (i + 1); + } + + return (denom == 0 ? 0 : static_cast(num) / denom); +} + +} /* namespace */ + +/** + * \brief Set the limits for the algorithm + */ +void AgcMSV::setLimits(const Limits &limits) +{ + limits_ = limits; +} + +/** + * \brief Calculate a new set of AGC parameters + */ +AgcMSV::Result AgcMSV::calculateNewEv(const Params ¶ms) +{ + auto exposureMSV = calculateMSV(params.yHist); + if (!exposureMSV) { + LOG(AgcMSV, Debug) + << "Not adjusting exposure due to insufficient histogram data"; + return { params.exposure, params.gain }; + } + + return updateExposure(params.exposure, params.gain, *exposureMSV); +} + +AgcMSV::Result AgcMSV::updateExposure(uint32_t exposure, double again, float exposureMSV) +{ + float error = kExposureOptimal - exposureMSV; + if (std::abs(error) <= kExposureSatisfactory) + return { exposure, again }; + + /* + * Compute a proportional correction factor. The sign of the error + * determines the direction: positive error means too dark (increase), + * negative means too bright (decrease). + */ + float step = std::clamp(error * kExpProportionalGain, + -kExpMaxStep, kExpMaxStep); + float factor = 1.0f + step; + + if (factor > 1.0f) { + /* Scene too dark: increase exposure first, then gain. */ + if (exposure < limits_.exposure[1]) { + uint32_t next = exposure * factor; + exposure = std::max(next, exposure + 1); + } else { + double next = again * factor; + again = std::max(next, again + limits_.gainMinStep); + } + } else { + /* Scene too bright: decrease gain first, then exposure. */ + if (again > limits_.gain1) { + double next = again * factor; + again = std::min(next, again - limits_.gainMinStep); + } else { + uint32_t next = exposure * factor; + exposure = std::min(next, exposure - 1); + } + } + + exposure = std::clamp(exposure, limits_.exposure[0], limits_.exposure[1]); + again = std::clamp(again, limits_.gain[0], limits_.gain[1]); + + LOG(AgcMSV, Debug) + << "exposureMSV:" << exposureMSV + << " error:" << error << " factor:" << factor + << " exposure:" << exposure << " analogue-gain:" << again; + + return { exposure, again }; +} + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/agc_msv.h b/src/ipa/libipa/agc_msv.h new file mode 100644 index 0000000000..13e67fd57c --- /dev/null +++ b/src/ipa/libipa/agc_msv.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat Inc. + * + * Luminance mean sample value based AGC algorithm + */ + +#pragma once + +#include "histogram.h" + +#include + +namespace libcamera { + +namespace ipa { + +class AgcMSV +{ +public: + struct Limits { + std::array exposure; + std::array gain; + double gainMinStep; + double gain1; + }; + + struct Params { + const Histogram &yHist; + uint32_t exposure; + double gain; + }; + + struct Result { + uint32_t exposure; + double analogueGain; + }; + + void setLimits(const Limits &limits); + [[nodiscard]] Result calculateNewEv(const Params ¶ms); + +private: + AgcMSV::Result updateExposure(uint32_t exposure, double again, float exposureMSV); + + Limits limits_ = {}; +}; + + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index ca681fa5af..8896eb8f89 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -3,6 +3,7 @@ libipa_headers = files([ 'agc.h', 'agc_mean_luminance.h', + 'agc_msv.h', 'algorithm.h', 'awb_bayes.h', 'awb_grey.h', @@ -25,6 +26,7 @@ libipa_headers = files([ libipa_sources = files([ 'agc.cpp', 'agc_mean_luminance.cpp', + 'agc_msv.cpp', 'algorithm.cpp', 'awb_bayes.cpp', 'awb_grey.cpp', diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index 199b444f5a..7b980c7382 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -7,10 +7,6 @@ #include "agc.h" -#include -#include -#include -#include #include #include @@ -25,138 +21,22 @@ LOG_DEFINE_CATEGORY(IPASoftExposure) namespace ipa::soft::algorithms { -/* - * The number of bins to use for the optimal exposure calculations. - */ -static constexpr unsigned int kExposureBinsCount = 5; - -/* - * The exposure is optimal when the mean sample value of the histogram is - * in the middle of the range. - */ -static constexpr float kExposureOptimal = kExposureBinsCount / 2.0; - -/* - * This implements the hysteresis for the exposure adjustment. - * It is small enough to have the exposure close to the optimal, and is big - * enough to prevent the exposure from wobbling around the optimal value. - */ -static constexpr float kExposureSatisfactory = 0.2; - -/* - * Proportional gain for exposure/gain adjustment. Maps the MSV error to a - * multiplicative correction factor: - * - * factor = 1.0 + kExpProportionalGain * error - * - * With kExpProportionalGain = 0.04: - * - max error ~2.5 -> factor 1.10 (~10% step, same as before) - * - error 1.0 -> factor 1.04 (~4% step) - * - error 0.3 -> factor 1.012 (~1.2% step) - * - * This replaces the fixed 10% bang-bang step with a proportional correction - * that converges smoothly and avoids overshooting near the target. - */ -static constexpr float kExpProportionalGain = 0.04; - -/* - * Maximum multiplicative step per frame, to bound the correction when the - * scene changes dramatically. - */ -static constexpr float kExpMaxStep = 0.15; - -namespace { - -std::optional calculateMSV(const Histogram &histogram) -{ - /* - * Calculate Mean Sample Value (MSV) according to formula from: - * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf - */ - const unsigned int yHistValsPerBin = histogram.bins() / kExposureBinsCount; - const unsigned int yHistValsPerBinMod = - histogram.bins() / (histogram.bins() % kExposureBinsCount + 1); - int exposureBins[kExposureBinsCount] = {}; - unsigned int denom = 0; - unsigned int num = 0; - - if (yHistValsPerBin == 0) - return {}; - - for (unsigned int i = 0; i < histogram.bins(); i++) { - unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin; - exposureBins[idx] += histogram[i]; - } - - for (unsigned int i = 0; i < kExposureBinsCount; i++) { - LOG(IPASoftExposure, Debug) << i << ": " << exposureBins[i]; - denom += exposureBins[i]; - num += exposureBins[i] * (i + 1); - } - - return (denom == 0 ? 0 : static_cast(num) / denom); -} - -} /* namespace */ - -Agc::Agc() +int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { -} - -void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV) -{ - uint32_t exposure = frameContext.sensor.exposure; - double again = frameContext.sensor.gain; - - double error = kExposureOptimal - exposureMSV; - - if (std::abs(error) <= kExposureSatisfactory) - return; - - /* - * Compute a proportional correction factor. The sign of the error - * determines the direction: positive error means too dark (increase), - * negative means too bright (decrease). - */ - float step = std::clamp(static_cast(error) * kExpProportionalGain, - -kExpMaxStep, kExpMaxStep); - float factor = 1.0f + step; - - if (factor > 1.0f) { - /* Scene too dark: increase exposure first, then gain. */ - if (exposure < context.configuration.agc.exposureMax) { - uint32_t next = exposure * factor; - exposure = std::max(next, exposure + 1); - } else { - double next = again * factor; - again = std::max(next, again + context.configuration.agc.againMinStep); - } - } else { - /* Scene too bright: decrease gain first, then exposure. */ - if (again > context.configuration.agc.again10) { - double next = again * factor; - again = std::max(next, again - context.configuration.agc.againMinStep); - } else { - uint32_t next = exposure * factor; - exposure = std::min(next, exposure - 1); - } - } - - exposure = std::clamp(exposure, context.configuration.agc.exposureMin, - context.configuration.agc.exposureMax); - again = std::clamp(again, context.configuration.agc.againMin, - context.configuration.agc.againMax); - - frameContext.agc.exposure = exposure; - frameContext.agc.gain = again; - - context.activeState.agc.exposure = exposure; - context.activeState.agc.again = again; - - LOG(IPASoftExposure, Debug) - << "exposureMSV " << exposureMSV - << " error " << error << " factor " << factor - << " exp " << exposure << " again " << again; + agc_.setLimits({ + .exposure = { + context.configuration.agc.exposureMin, + context.configuration.agc.exposureMax, + }, + .gain = { + context.configuration.agc.againMin, + context.configuration.agc.againMax, + }, + .gainMinStep = context.configuration.agc.againMinStep, + .gain1 = context.configuration.agc.again10, + }); + + return 0; } void Agc::process(IPAContext &context, @@ -197,14 +77,17 @@ void Agc::process(IPAContext &context, for (unsigned int i = 1; i < blackLevelHistIdx; i++) histogram[0] += std::exchange(histogram[i], 0); - auto exposureMSV = calculateMSV({ histogram }); - if (!exposureMSV) { - LOG(IPASoftExposure, Debug) - << "Not adjusting exposure due to insufficient histogram data"; - return; - } + const auto &newEv = agc_.calculateNewEv({ + .yHist = { histogram }, + .exposure = frameContext.sensor.exposure, + .gain = frameContext.sensor.gain, + }); + + frameContext.agc.exposure = newEv.exposure; + frameContext.agc.gain = newEv.analogueGain; - updateExposure(context, frameContext, *exposureMSV); + context.activeState.agc.exposure = frameContext.agc.exposure; + context.activeState.agc.again = frameContext.agc.gain; } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h index 112d9f5a19..2e156e135c 100644 --- a/src/ipa/simple/algorithms/agc.h +++ b/src/ipa/simple/algorithms/agc.h @@ -9,6 +9,8 @@ #include "algorithm.h" +#include + namespace libcamera { namespace ipa::soft::algorithms { @@ -16,8 +18,7 @@ namespace ipa::soft::algorithms { class Agc : public Algorithm { public: - Agc(); - ~Agc() = default; + int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -25,7 +26,7 @@ public: ControlList &metadata) override; private: - void updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV); + AgcMSV agc_; }; } /* namespace ipa::soft::algorithms */ From patchwork Thu Jul 23 15:43:21 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: 27487 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 4840CC3339 for ; Thu, 23 Jul 2026 15:44:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A121F67F80; Thu, 23 Jul 2026 17:44:24 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="DmgtWS0k"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 301DB67EEA for ; Thu, 23 Jul 2026 17:43:40 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 50D052C0B for ; Thu, 23 Jul 2026 17:42:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821359; bh=tqxwRekX1pe5lTqv8KeMnWE5PYwZfpJxr4s+icm4tOo=; h=From:To:Subject:Date:In-Reply-To:References:From; b=DmgtWS0kqAkD9LQYvcDA5P+55zvDgUtalhlP3gAUIkkTqR5r770bRc2MiPwECd86l 5+xrgFIdf25+LgVBUwklfNrb9FJuS6U0RzB9j8rurmSdPGKyFJkPYjBuAF1unKJj8u GiiRXJ7mvnH5P+K4QizkrPGAW82f2xfrA/HxzILE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 38/43] ipa: libipa: agc_msv: Ensure limits are always respected Date: Thu, 23 Jul 2026 17:43:21 +0200 Message-ID: <20260723154327.1357866-39-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Make sure that even in the presence of continous limit changes, the returned exposure and gain values are always within bounds. This is needed for integration into `AgcAlgorithm`, which might vary the limits for every invocation. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_msv.cpp | 84 ++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/src/ipa/libipa/agc_msv.cpp b/src/ipa/libipa/agc_msv.cpp index 63580a0055..956551685c 100644 --- a/src/ipa/libipa/agc_msv.cpp +++ b/src/ipa/libipa/agc_msv.cpp @@ -157,59 +157,65 @@ void AgcMSV::setLimits(const Limits &limits) */ AgcMSV::Result AgcMSV::calculateNewEv(const Params ¶ms) { - auto exposureMSV = calculateMSV(params.yHist); - if (!exposureMSV) { + AgcMSV::Result result = { params.exposure, params.gain }; + + if (auto exposureMSV = calculateMSV(params.yHist)) { + result = updateExposure(params.exposure, params.gain, *exposureMSV); + } else { LOG(AgcMSV, Debug) << "Not adjusting exposure due to insufficient histogram data"; - return { params.exposure, params.gain }; } - return updateExposure(params.exposure, params.gain, *exposureMSV); + result.exposure = std::clamp(result.exposure, limits_.exposure[0], limits_.exposure[1]); + result.analogueGain = std::clamp(result.analogueGain, limits_.gain[0], limits_.gain[1]); + + LOG(AgcMSV, Debug) + << "exposure:" << result.exposure + << " analogue-gain:" << result.analogueGain; + + return result; } AgcMSV::Result AgcMSV::updateExposure(uint32_t exposure, double again, float exposureMSV) { float error = kExposureOptimal - exposureMSV; - if (std::abs(error) <= kExposureSatisfactory) - return { exposure, again }; - /* - * Compute a proportional correction factor. The sign of the error - * determines the direction: positive error means too dark (increase), - * negative means too bright (decrease). - */ - float step = std::clamp(error * kExpProportionalGain, - -kExpMaxStep, kExpMaxStep); - float factor = 1.0f + step; - - if (factor > 1.0f) { - /* Scene too dark: increase exposure first, then gain. */ - if (exposure < limits_.exposure[1]) { - uint32_t next = exposure * factor; - exposure = std::max(next, exposure + 1); - } else { - double next = again * factor; - again = std::max(next, again + limits_.gainMinStep); - } - } else { - /* Scene too bright: decrease gain first, then exposure. */ - if (again > limits_.gain1) { - double next = again * factor; - again = std::min(next, again - limits_.gainMinStep); + LOG(AgcMSV, Debug) + << "exposureMSV:" << exposureMSV << " error:" << error; + + if (std::abs(error) > kExposureSatisfactory) { + /* + * Compute a proportional correction factor. The sign of the error + * determines the direction: positive error means too dark (increase), + * negative means too bright (decrease). + */ + float step = std::clamp(error * kExpProportionalGain, + -kExpMaxStep, kExpMaxStep); + float factor = 1.0f + step; + + LOG(AgcMSV, Debug) << "factor:" << factor; + + if (factor > 1.0f) { + /* Scene too dark: increase exposure first, then gain. */ + if (exposure < limits_.exposure[1]) { + uint32_t next = exposure * factor; + exposure = std::max(next, exposure + 1); + } else { + double next = again * factor; + again = std::max(next, again + limits_.gainMinStep); + } } else { - uint32_t next = exposure * factor; - exposure = std::min(next, exposure - 1); + /* Scene too bright: decrease gain first, then exposure. */ + if (again > limits_.gain1) { + double next = again * factor; + again = std::min(next, again - limits_.gainMinStep); + } else { + uint32_t next = exposure * factor; + exposure = std::min(next, exposure - 1); + } } } - exposure = std::clamp(exposure, limits_.exposure[0], limits_.exposure[1]); - again = std::clamp(again, limits_.gain[0], limits_.gain[1]); - - LOG(AgcMSV, Debug) - << "exposureMSV:" << exposureMSV - << " error:" << error << " factor:" << factor - << " exposure:" << exposure << " analogue-gain:" << again; - return { exposure, again }; } From patchwork Thu Jul 23 15:43:22 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: 27488 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 39C31C3306 for ; Thu, 23 Jul 2026 15:44:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E338F67F21; Thu, 23 Jul 2026 17:44:25 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="uUS+67AD"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6BD9067EEE for ; Thu, 23 Jul 2026 17:43:40 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8B0F723B9 for ; Thu, 23 Jul 2026 17:42:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821359; bh=us5b72jVS9CEihNUNUbHeWP3SVXlyNl+8YqisAEAHEU=; h=From:To:Subject:Date:In-Reply-To:References:From; b=uUS+67ADB6btGh+NjRKBurdluGvYT0awqoi3uR/ewyc0gjdZKduFq10W3mbplUY+l gpBhCTmXuKtEvkwVk+yZYT1yD0DfjkX0PRAmOO7zfaOwUtMCwcf0TvIGHNfXKX83nE REgFjyhPKFkHKydPYzN1WFhogX0+4/1AoVga8Jfs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 39/43] ipa: libipa: agc_msv: Handle min gain > 1.0 case Date: Thu, 23 Jul 2026 17:43:22 +0200 Message-ID: <20260723154327.1357866-40-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" When manual gain control is enabled in `AgcAlgorithm`, the limits will be set to that particular value. This is very likely larger than 1.0. In this case, however, the algorithm should still adjust the exposure time if it can. So compare the gain with the minimum of 1.0 and the configured min gain, in order to ensure that the exposure will be adjusted if the lower boundary of the allowed analogue gains is reached. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_msv.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/libipa/agc_msv.cpp b/src/ipa/libipa/agc_msv.cpp index 956551685c..9fec2ba48a 100644 --- a/src/ipa/libipa/agc_msv.cpp +++ b/src/ipa/libipa/agc_msv.cpp @@ -206,7 +206,7 @@ AgcMSV::Result AgcMSV::updateExposure(uint32_t exposure, double again, float exp } } else { /* Scene too bright: decrease gain first, then exposure. */ - if (again > limits_.gain1) { + if (again > std::max(limits_.gain1, limits_.gain[0])) { double next = again * factor; again = std::min(next, again - limits_.gainMinStep); } else { From patchwork Thu Jul 23 15:43:23 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: 27490 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 C95A7C333B for ; Thu, 23 Jul 2026 15:44:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 50DE067F7B; Thu, 23 Jul 2026 17:44:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Mp+3bVAh"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9CBDB67ECC for ; Thu, 23 Jul 2026 17:43:40 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C77D02C0B for ; Thu, 23 Jul 2026 17:42:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821359; bh=WrBlfneeoKPFe1wqh6QNu0tcSBpaKix0eDIGnf41kDE=; h=From:To:Subject:Date:In-Reply-To:References:From; b=Mp+3bVAh9mWzW4/+BnR6Ak3+Ld9ojjRP7SLDvKq8mtCpls5gDEnUjC590l8MxcHSa ET4iC+xxdeuW96FyoV/NCOkyPjsfzy7/bhmt+jQ/aXJL+ORjbAoxZSim8wjvDhD9Vv ovJWSVhKOq7zk5qowPmDgoiUAjVxBjzk2obmOb7U= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 40/43] ipa: simple: Move sensor helper and controls to context Date: Thu, 23 Jul 2026 17:43:23 +0200 Message-ID: <20260723154327.1357866-41-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" So that algorithms can access it. Signed-off-by: Barnabás Pőcze --- src/ipa/simple/algorithms/blc.cpp | 12 +++++++++++ src/ipa/simple/ipa_context.h | 4 ++++ src/ipa/simple/soft_simple.cpp | 34 ++++++++++--------------------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp index 677be56ed6..793c6663df 100644 --- a/src/ipa/simple/algorithms/blc.cpp +++ b/src/ipa/simple/algorithms/blc.cpp @@ -42,6 +42,18 @@ int BlackLevel::configure(IPAContext &context, { if (definedLevel_.has_value()) context.configuration.black.level = definedLevel_; + else if (context.camHelper) { + if (auto bl = context.camHelper->blackLevel()) { + /* + * The black level from CameraSensorHelper is a 16 bit value, software ISP + * works with 8 bit pixel values, both regardless of the actual + * sensor pixel width. Hence we obtain the pixel-based black value + * by dividing the value from the helper by 256. + */ + context.configuration.black.level = *bl / 256; + } + } + context.activeState.blc.level = context.configuration.black.level.value_or(16); return 0; diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 980dc93cc4..96b3c5b21d 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include @@ -16,6 +17,7 @@ #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" +#include #include #include "core_ipa_interface.h" @@ -90,6 +92,8 @@ struct IPAContext { } IPACameraSensorInfo sensorInfo; + ControlInfoMap sensorControls; + std::unique_ptr camHelper; IPASessionConfiguration configuration; IPAActiveState activeState; FCQueue frameContexts; diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index a38fcff7d8..717b44cf6d 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -77,8 +77,6 @@ private: DebayerParams *params_; SwIspStats *stats_; - std::unique_ptr camHelper_; - ControlInfoMap sensorInfoMap_; /* Local parameter storage */ struct IPAContext context_; @@ -100,8 +98,8 @@ int IPASoftSimple::init(const IPASettings &settings, ControlInfoMap *ipaControls, bool *ccmEnabled) { - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); - if (!camHelper_) { + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!context_.camHelper) { LOG(IPASoft, Warning) << "Failed to create camera sensor helper for " << settings.sensorModel; @@ -202,10 +200,10 @@ int IPASoftSimple::init(const IPASettings &settings, int IPASoftSimple::configure(const IPAConfigInfo &configInfo) { - sensorInfoMap_ = configInfo.sensorControls; + context_.sensorControls = configInfo.sensorControls; - const ControlInfo &exposureInfo = sensorInfoMap_.find(V4L2_CID_EXPOSURE)->second; - const ControlInfo &gainInfo = sensorInfoMap_.find(V4L2_CID_ANALOGUE_GAIN)->second; + const ControlInfo &exposureInfo = context_.sensorControls.find(V4L2_CID_EXPOSURE)->second; + const ControlInfo &gainInfo = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; /* Clear the IPA context before the streaming session. */ context_.configuration = {}; @@ -225,24 +223,14 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo) int32_t againMax = gainInfo.max().get(); int32_t againDef = gainInfo.def().get(); - if (camHelper_) { - context_.configuration.agc.againMin = camHelper_->gain(againMin); - context_.configuration.agc.againMax = camHelper_->gain(againMax); + if (context_.camHelper) { + context_.configuration.agc.againMin = context_.camHelper->gain(againMin); + context_.configuration.agc.againMax = context_.camHelper->gain(againMax); context_.configuration.agc.again10 = std::max(context_.configuration.agc.againMin, 1.0); context_.configuration.agc.againMinStep = (context_.configuration.agc.againMax - context_.configuration.agc.againMin) / 100.0; - if (camHelper_->blackLevel().has_value()) { - /* - * The black level from camHelper_ is a 16 bit value, software ISP - * works with 8 bit pixel values, both regardless of the actual - * sensor pixel width. Hence we obtain the pixel-based black value - * by dividing the value from the helper by 256. - */ - context_.configuration.black.level = - camHelper_->blackLevel().value() / 256; - } } else { context_.configuration.agc.againMax = againMax; context_.configuration.agc.again10 = againDef; @@ -303,15 +291,15 @@ void IPASoftSimple::processStats(const uint32_t frame, IPAFrameContext &frameContext = context_.frameContexts.get(frame); std::tie(frameContext.sensor.exposure, frameContext.sensor.gain) - = agc::extractControls(sensorControls, camHelper_.get()); + = agc::extractControls(sensorControls, context_.camHelper.get()); ControlList metadata(controls::controls); for (const auto &algo : algorithms()) algo->process(context_, frame, frameContext, stats_, metadata); metadataReady.emit(frame, metadata); - ControlList ctrls(sensorInfoMap_); - agc::prepareControls(ctrls, camHelper_.get(), frameContext.agc.exposure, frameContext.agc.gain); + ControlList ctrls(context_.sensorControls); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc.exposure, frameContext.agc.gain); setSensorControls.emit(ctrls); } From patchwork Thu Jul 23 15:43:24 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: 27489 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 128F4C333A for ; Thu, 23 Jul 2026 15:44:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A82C167F58; Thu, 23 Jul 2026 17:44:26 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mSyubCDt"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D3DC867F05 for ; Thu, 23 Jul 2026 17:43:40 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 06FD123B9 for ; Thu, 23 Jul 2026 17:42:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821360; bh=vRgrIZrtaPKRBBGdq3Bnp2egkRCiS3SDlWQUkx3rZbU=; h=From:To:Subject:Date:In-Reply-To:References:From; b=mSyubCDt8gxAVa6VmW/U+6RC30MxWwo2L9DVCT1iZ1meNWug3Ej61G7Tw7a8WLVSn 92bcLVkbOPGBGWEmmXxPKrCFnl9FDg5xLNRjGiYe/nAylm8pB2lFOZLQBscKPlgCWK 2AWouBRtW9iOUI4UCGUIqDFpbHVzCRSMHJq1eYjY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 41/43] ipa: simple: Update controls after configuration Date: Thu, 23 Jul 2026 17:43:24 +0200 Message-ID: <20260723154327.1357866-42-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Update the control info map after the algorithms are reconfigured. Signed-off-by: Barnabás Pőcze --- include/libcamera/internal/software_isp/software_isp.h | 3 ++- include/libcamera/ipa/soft.mojom | 2 +- src/ipa/simple/soft_simple.cpp | 7 +++++-- src/libcamera/pipeline/simple/simple.cpp | 2 +- src/libcamera/software_isp/software_isp.cpp | 9 +++++---- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h index 4f72dce9b1..938d980906 100644 --- a/include/libcamera/internal/software_isp/software_isp.h +++ b/include/libcamera/internal/software_isp/software_isp.h @@ -65,7 +65,8 @@ public: int configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, - const ipa::soft::IPAConfigInfo &configInfo); + const ipa::soft::IPAConfigInfo &configInfo, + ControlInfoMap *ipaControls); int exportBuffers(const Stream *stream, unsigned int count, std::vector> *buffers); diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom index 77328c5fd5..3acac53981 100644 --- a/include/libcamera/ipa/soft.mojom +++ b/include/libcamera/ipa/soft.mojom @@ -22,7 +22,7 @@ interface IPASoftInterface { start() => (int32 ret); stop(); configure(IPAConfigInfo configInfo) - => (int32 ret); + => (int32 ret, libcamera.ControlInfoMap ipaControls); [async] queueRequest(uint32 frame, libcamera.ControlList sensorControls); [async] computeParams(uint32 frame); diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 717b44cf6d..8b5df884d4 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -59,7 +59,8 @@ public: const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls, bool *ccmEnabled) override; - int configure(const IPAConfigInfo &configInfo) override; + int configure(const IPAConfigInfo &configInfo, + ControlInfoMap *ipaControls) override; int start() override; void stop() override; @@ -198,7 +199,7 @@ int IPASoftSimple::init(const IPASettings &settings, return 0; } -int IPASoftSimple::configure(const IPAConfigInfo &configInfo) +int IPASoftSimple::configure(const IPAConfigInfo &configInfo, ControlInfoMap *ipaControls) { context_.sensorControls = configInfo.sensorControls; @@ -251,6 +252,8 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo) << context_.configuration.agc.againMax << " (" << context_.configuration.agc.againMinStep << ")"; + *ipaControls = { ControlInfoMap::Map(context_.ctrlMap), controls::controls }; + return 0; } diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index e26f438d9a..3917aa897e 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -1605,7 +1605,7 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) } else { ipa::soft::IPAConfigInfo configInfo; configInfo.sensorControls = data->sensor_->controls(); - return data->swIsp_->configure(inputCfg, outputCfgs, configInfo); + return data->swIsp_->configure(inputCfg, outputCfgs, configInfo, &data->controlInfo_); } } diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp index f6b345f6a5..a51e1a65c9 100644 --- a/src/libcamera/software_isp/software_isp.cpp +++ b/src/libcamera/software_isp/software_isp.cpp @@ -277,17 +277,18 @@ uint32_t SoftwareIsp::preferredInputStride(const PixelFormat &inputFormat, const * \brief Configure the SoftwareIsp object according to the passed in parameters * \param[in] inputCfg The input configuration * \param[in] outputCfgs The output configurations - * \param[in] configInfo The IPA configuration data, received from the pipeline - * handler + * \param[in] configInfo The IPA configuration data, received from the pipeline handler + * \param[out] ipaControls The IPA controls to update * \return 0 on success, a negative errno on failure */ int SoftwareIsp::configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, - const ipa::soft::IPAConfigInfo &configInfo) + const ipa::soft::IPAConfigInfo &configInfo, + ControlInfoMap *ipaControls) { ASSERT(ipa_ && debayer_); - int ret = ipa_->configure(configInfo); + int ret = ipa_->configure(configInfo, ipaControls); if (ret < 0) return ret; From patchwork Thu Jul 23 15:43:25 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: 27491 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 71BEFC3308 for ; Thu, 23 Jul 2026 15:44:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 055E667F5B; Thu, 23 Jul 2026 17:44:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Wg9GNl61"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2672E67F07 for ; Thu, 23 Jul 2026 17:43:41 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3C8322C0B for ; Thu, 23 Jul 2026 17:42:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821360; bh=maa82bq6221z5M7tMgjIaPDPLQQ5RzcDFWXl9wN5/HQ=; h=From:To:Subject:Date:In-Reply-To:References:From; b=Wg9GNl61oZ7I711rn5KHbm11w8Od8oB0HOodpnbs21qoTKglGZTdR6qBk3Ev4868w 5zpyV8Mk69+ddg9qgvAPW0fg7YvLoaY9YItZKi7QU9YVoNUwF0f1Jp0rdjpXo6Uouy wfQZ0pa91on0zv8JFoKTvqwffLMJk+ojPOjG2BpQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 42/43] ipa: libipa: agc: Work without `CameraSensorHelper` Date: Thu, 23 Jul 2026 17:43:25 +0200 Message-ID: <20260723154327.1357866-43-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Use the agc algorithm extracted from the simple ipa module (AgcMSV) to provide some kind of operation when a `CameraSensorHelper` is not available. Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/algorithms/agc.cpp | 4 +- src/ipa/libipa/agc.cpp | 242 ++++++++++++++++++---------- src/ipa/libipa/agc.h | 9 +- src/ipa/mali-c55/algorithms/agc.cpp | 4 +- src/ipa/rkisp1/algorithms/agc.cpp | 4 +- 5 files changed, 170 insertions(+), 93 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index c9ea02ed00..975f82eaf3 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -68,12 +68,11 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.init(tuningData); + ret = agc_.init(tuningData, context.camHelper.get()); if (ret) return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -98,7 +97,6 @@ int Agc::configure(IPAContext &context, bdsGrid_ = context.configuration.grid.bdsGrid; return agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 27edc96fc6..683da93662 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -11,10 +11,12 @@ #include #include #include +#include #include #include +#include #include #include @@ -56,6 +58,9 @@ LOG_DEFINE_CATEGORY(Agc) * \var agc::Session::maxAnalogueGain * \brief Maximum analogue gain supported with the configured sensor * + * \var agc::Session::defAnalogueGain + * \brief Default analogue gain of the configured sensor + * * \var agc::Session::minFrameDuration * \brief Minimum frame duration supported with the configured sensor * @@ -190,9 +195,6 @@ LOG_DEFINE_CATEGORY(Agc) * \struct AgcAlgorithm::ConfigurationParams * \brief Parameters for AgcAlgorithm::configure() * - * \var AgcAlgorithm::ConfigurationParams::sensor - * \brief CameraSensorHelper for the sensor - * * \var AgcAlgorithm::ConfigurationParams::sensorInfo * \brief Details of the sensor * @@ -231,13 +233,14 @@ LOG_DEFINE_CATEGORY(Agc) namespace { +[[nodiscard]] uint32_t clampExposure(uint32_t exposure, const agc::Session &session) +{ + return std::clamp(exposure, session.minExposure, session.maxExposure); +} + [[nodiscard]] uint32_t clampExposure(utils::Duration exposureTime, const agc::Session &session) { - return std::clamp( - exposureTime / session.lineDuration, - session.minExposure, - session.maxExposure - ); + return clampExposure(exposureTime / session.lineDuration, session); } } /* namespace */ @@ -245,11 +248,18 @@ namespace { /** * \brief Load tuning data */ -int AgcAlgorithm::init(const ValueNode &tuningData) +int AgcAlgorithm::init(const ValueNode &tuningData, CameraSensorHelper *sensor) { - int ret = impl_.parseTuningData(tuningData); - if (ret) - return ret; + if (sensor) { + auto& impl = impl_.emplace(); + int ret = impl.parseTuningData(tuningData); + if (ret) + return ret; + } else { + impl_.emplace(); + } + + sensor_ = sensor; return 0; } @@ -283,10 +293,14 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, cons }; /* Compute the analogue gain limits. */ + const auto extractGain = [&](const ControlValue &v) { + auto gainCode = v.get(); + return sensor_ ? sensor_->gain(gainCode) : gainCode; + }; const ControlInfo &v4l2Gain = config.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = config.sensor->gain(v4l2Gain.min().get()); - float maxGain = config.sensor->gain(v4l2Gain.max().get()); - float defGain = config.sensor->gain(v4l2Gain.def().get()); + float minGain = extractGain(v4l2Gain.min()); + float maxGain = extractGain(v4l2Gain.max()); + float defGain = extractGain(v4l2Gain.def()); config.ctrlMap[&controls::AnalogueGain] = ControlInfo{ minGain, maxGain, @@ -345,30 +359,19 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, cons session.maxExposureTime = maxExposure * session.lineDuration; session.minAnalogueGain = minGain; session.maxAnalogueGain = maxGain; - - impl_.configure(session.lineDuration, config.sensor); - impl_.setLimits(session.minExposureTime, session.maxExposureTime, - session.minAnalogueGain, session.maxAnalogueGain, - {}); - impl_.resetFrameCount(); + session.defAnalogueGain = defGain; /* Configure the default exposure and gain. */ state = {}; state.automatic.gain = session.minAnalogueGain; state.automatic.exposure = clampExposure(defExposure * session.lineDuration, session); state.automatic.quantizationGain = 1; - state.automatic.yTarget = impl_.effectiveYTarget(0, 1); state.manual.gain = state.automatic.gain; state.manual.exposure = state.automatic.exposure; state.autoExposureEnabled = session.autoAllowed; state.autoGainEnabled = session.autoAllowed; state.exposureValue = 0; - state.constraintMode = - static_cast(impl_.constraintModes().begin()->first); - state.exposureMode = - static_cast(impl_.exposureModeHelpers().begin()->first); - state.minFrameDuration = session.minFrameDuration; state.maxFrameDuration = session.maxFrameDuration; @@ -397,25 +400,63 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, cons session.autoAllowed, }; - if (session.autoAllowed) { - config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); - - { - std::vector options; - for (const auto &[id, _] : impl_.constraintModes()) - options.emplace_back(id); - - config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(options); - } - - { - std::vector options; - for (const auto &[id, _] : impl_.exposureModeHelpers()) - options.emplace_back(id); - - config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options); - } - } else { + std::visit(utils::overloaded{ + [&](AgcMSV&) { + /* no constraint/exposure mode support */ + state.constraintMode = controls::AeConstraintModeEnum::ConstraintNormal; + state.exposureMode = controls::AeExposureModeEnum::ExposureNormal; + + state.automatic.quantizationGain = 1; /* no CameraSensorHelper */ + state.automatic.yTarget = (2.5 - 1) / (5 - 1); + + if (session.autoAllowed) { + config.ctrlMap[&controls::AeConstraintMode] = ControlInfo( + std::array{ ControlValue(state.constraintMode) } + ); + + config.ctrlMap[&controls::AeExposureMode] = ControlInfo( + std::array{ ControlValue(state.exposureMode) } + ); + } + }, + [&](AgcMeanLuminance& impl) { + state.constraintMode = + static_cast(impl.constraintModes().begin()->first); + state.exposureMode = + static_cast(impl.exposureModeHelpers().begin()->first); + + state.automatic.yTarget = impl.effectiveYTarget(0, 1); + + ASSERT(sensor_); + impl.configure(session.lineDuration, sensor_); + impl.setLimits(session.minExposureTime, session.maxExposureTime, + session.minAnalogueGain, session.maxAnalogueGain, + {}); + impl.resetFrameCount(); + + if (session.autoAllowed) { + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); + + { + std::vector options; + for (const auto &[id, _] : impl.constraintModes()) + options.emplace_back(id); + + config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(options); + } + + { + std::vector options; + for (const auto &[id, _] : impl.exposureModeHelpers()) + options.emplace_back(id); + + config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options); + } + } + }, + }, impl_); + + if (!session.autoAllowed) { config.ctrlMap.erase(&controls::ExposureValue); config.ctrlMap.erase(&controls::AeConstraintMode); config.ctrlMap.erase(&controls::AeExposureMode); @@ -607,41 +648,80 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, maxAnalogueGain = frameContext.gain; } - /* - * The Agc algorithm needs to know the effective exposure value that was - * applied to the sensor when the statistics were collected. - */ - utils::Duration effectiveExposureValue = - lineDuration * params->exposure * params->gain; - - impl_.setLimits(minExposureTime, maxExposureTime, - minAnalogueGain, maxAnalogueGain, - std::move(params->additionalConstraints)); - - const auto &newEv = impl_.calculateNewEv({ - .traits = params->traits, - .yHist = params->yHist, - .effectiveExposureValue = effectiveExposureValue, - .constraintModeIndex = frameContext.constraintMode, - .exposureModeIndex = frameContext.exposureMode, - .lux = params->lux, - .exposureCompensation = pow(2.0, frameContext.exposureValue), - }); - - LOG(Agc, Debug) - << "exposure-time:" << newEv.exposureTime - << " analogue-gain" << newEv.analogueGain - << " quantization-gain" << newEv.quantizationGain - << " digital-gain: " << newEv.digitalGain - ; - - /* Update the estimated exposure and gain. */ - state.automatic.exposure = clampExposure(newEv.exposureTime, session); - state.automatic.gain = newEv.analogueGain; - state.automatic.quantizationGain = newEv.quantizationGain; - state.automatic.yTarget = newEv.yTarget; - - newExposureTime = newEv.exposureTime; + std::visit(utils::overloaded{ + [&](AgcMSV& impl) { + impl.setLimits({ + .exposure = { + uint32_t(minExposureTime / lineDuration), + uint32_t(maxExposureTime / lineDuration), + }, + .gain = { + minAnalogueGain, + maxAnalogueGain, + }, + /* gain codes -> step size of 1 */ + .gainMinStep = 1, + /* assume default gain is close to 1.0 */ + .gain1 = session.defAnalogueGain, + }); + + const auto& newEv = impl.calculateNewEv({ + .yHist = params->yHist, + .exposure = params->exposure, + .gain = params->gain, + }); + + state.automatic.exposure = newEv.exposure; + state.automatic.gain = newEv.analogueGain; + + newExposureTime = newEv.exposure * lineDuration; + + LOG(Agc, Debug) + << "exposure-time:" << newExposureTime + << " analogue-gain:" << newEv.analogueGain + ; + }, + [&](AgcMeanLuminance& impl) { + /* + * The Agc algorithm needs to know the effective exposure value that was + * applied to the sensor when the statistics were collected. + */ + utils::Duration effectiveExposureValue = + lineDuration * params->exposure * params->gain; + + impl.setLimits(minExposureTime, maxExposureTime, + minAnalogueGain, maxAnalogueGain, + std::move(params->additionalConstraints)); + + const auto &newEv = impl.calculateNewEv({ + .traits = params->traits, + .yHist = params->yHist, + .effectiveExposureValue = effectiveExposureValue, + .constraintModeIndex = frameContext.constraintMode, + .exposureModeIndex = frameContext.exposureMode, + .lux = params->lux, + .exposureCompensation = pow(2.0, frameContext.exposureValue), + }); + + LOG(Agc, Debug) + << "exposure-time:" << newEv.exposureTime + << " analogue-gain:" << newEv.analogueGain + << " quantization-gain:" << newEv.quantizationGain + << " digital-gain:" << newEv.digitalGain + ; + + /* Update the estimated exposure and gain. */ + state.automatic.exposure = newEv.exposureTime / lineDuration; + state.automatic.gain = newEv.analogueGain; + state.automatic.quantizationGain = newEv.quantizationGain; + state.automatic.yTarget = newEv.yTarget; + + newExposureTime = newEv.exposureTime; + }, + }, impl_); + + /* Avoid out of range values due to rounding, etc. */ + state.automatic.exposure = clampExposure(state.automatic.exposure, session); } /* diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index c11dbf80cd..52896b7851 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -9,6 +9,7 @@ #include #include +#include #include @@ -18,6 +19,7 @@ #include #include "agc_mean_luminance.h" +#include "agc_msv.h" #include "camera_sensor_helper.h" #include "histogram.h" @@ -55,6 +57,7 @@ struct Session { utils::Duration maxExposureTime; double minAnalogueGain; double maxAnalogueGain; + double defAnalogueGain; utils::Duration minFrameDuration; utils::Duration maxFrameDuration; @@ -112,14 +115,13 @@ class AgcAlgorithm { public: struct ConfigurationParams { - const CameraSensorHelper *sensor; const IPACameraSensorInfo &sensorInfo; const ControlInfoMap &sensorControls; ControlInfoMap::Map &ctrlMap; bool autoAllowed = true; }; - int init(const ValueNode &tuningData); + int init(const ValueNode &tuningData, CameraSensorHelper *sensor); int configure(agc::Session &session, agc::ActiveState &state, const ConfigurationParams &config); @@ -141,7 +143,8 @@ public: std::optional &¶ms, ControlList &metadata); private: - AgcMeanLuminance impl_; + std::variant impl_; + CameraSensorHelper *sensor_ = nullptr; }; } /* namespace ipa */ diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 8af4df53e7..00fb7bfa70 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -123,12 +123,11 @@ Agc::Agc() int Agc::init(IPAContext &context, const ValueNode &tuningData) { - int ret = agc_.init(tuningData); + int ret = agc_.init(tuningData, context.camHelper.get()); if (ret) return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -148,7 +147,6 @@ int Agc::configure(IPAContext &context, return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 4c2a066e86..41a8cd581f 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -136,12 +136,11 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.init(tuningData); + ret = agc_.init(tuningData, context.camHelper.get()); if (ret) return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -167,7 +166,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, From patchwork Thu Jul 23 15:43:26 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: 27492 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 46CCAC333C for ; Thu, 23 Jul 2026 15:44:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BE6D267F75; Thu, 23 Jul 2026 17:44:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="SpFERWt0"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5C26B67ED0 for ; Thu, 23 Jul 2026 17:43:41 +0200 (CEST) Received: from pb-laptop.local (185.182.215.156.nat.pool.zt.hu [185.182.215.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7D92923B9 for ; Thu, 23 Jul 2026 17:42:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784821360; bh=/stMkR+OSyGOyMTcTHMBqzUc6Ain7zBYox2nE68AigY=; h=From:To:Subject:Date:In-Reply-To:References:From; b=SpFERWt0DctzXOgV7w/njLDwAjUylSb389em/L0y08yqpVyqlXpjfrWcLKChipTSD TmQ9YkEfgQ/GdP69z56K8oZNBhuSe3J/stBACUeOluKUy4++kWoyZy6vgRUhZBWdz6 QWTN9Y5BVhv8nPsrIOlELqsniKnxLxXnizAtfjH8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v2 43/43] ipa: simple: agc: Port to `AgcAlgorithm` Date: Thu, 23 Jul 2026 17:43:26 +0200 Message-ID: <20260723154327.1357866-44-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260723154327.1357866-1-barnabas.pocze@ideasonboard.com> References: <20260723154327.1357866-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" Use the `AgcAlgorithm` class to implement the agc algorithm. Signed-off-by: Barnabás Pőcze --- src/ipa/simple/algorithms/agc.cpp | 134 +++++++++++++++++------------- src/ipa/simple/algorithms/agc.h | 12 ++- src/ipa/simple/ipa_context.h | 15 +--- src/ipa/simple/soft_simple.cpp | 55 +----------- 4 files changed, 93 insertions(+), 123 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index 7b980c7382..927bc6ab22 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -13,30 +13,76 @@ #include -#include "control_ids.h" - namespace libcamera { LOG_DEFINE_CATEGORY(IPASoftExposure) namespace ipa::soft::algorithms { +namespace { + +class AgcTraits : public AgcMeanLuminance::Traits +{ +public: + AgcTraits(const Histogram &yHist) + : yHist_(yHist) + { + } + + double estimateLuminance(double gain) const override + { + /* + * TODO: Improve by asking the weight of saturating and non-saturating + * bins directly from the histogram + */ + double sum = 0; + + for (size_t i = 0; i < yHist_.bins(); i++) + sum += std::min(yHist_.bins(), i * gain) * yHist_[i]; + + return sum / yHist_.total() / yHist_.bins(); + } + +private: + const Histogram &yHist_; +}; + +} /* namespace */ + +int Agc::init(IPAContext &context, const ValueNode &tuningData) +{ + int ret = agc_.init(tuningData, context.camHelper.get()); + if (ret) + return ret; + + return agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, + }); +} + int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - agc_.setLimits({ - .exposure = { - context.configuration.agc.exposureMin, - context.configuration.agc.exposureMax, - }, - .gain = { - context.configuration.agc.againMin, - context.configuration.agc.againMax, - }, - .gainMinStep = context.configuration.agc.againMinStep, - .gain1 = context.configuration.agc.again10, + return agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, }); +} + +void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) +{ + agc_.queueRequest(context.configuration.agc, context.activeState.agc, frameContext.agc, controls); +} - return 0; +void Agc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) +{ + agc_.prepare(context.activeState.agc, frameContext.agc); } void Agc::process(IPAContext &context, @@ -45,49 +91,25 @@ void Agc::process(IPAContext &context, const SwIspStats *stats, ControlList &metadata) { - utils::Duration exposureTime = - context.configuration.agc.lineDuration * frameContext.sensor.exposure; - metadata.set(controls::ExposureTime, exposureTime.get()); - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); - - if (!context.activeState.agc.valid) { - /* - * Init active-state from sensor values in case updateExposure() - * does not run for the first frame. - */ - context.activeState.agc.exposure = frameContext.sensor.exposure; - context.activeState.agc.again = frameContext.sensor.gain; - context.activeState.agc.valid = true; - } - - if (!stats->valid) { - /* - * Use the new exposure and gain values calculated the last time - * there were valid stats. - */ - frameContext.agc.exposure = context.activeState.agc.exposure; - frameContext.agc.gain = context.activeState.agc.again; - return; + if (stats->valid) { + auto histogram = stats->yHistogram; + + const unsigned int blackLevelHistIdx = + context.activeState.blc.level * std::size(histogram) / 256; + for (size_t i = 1; i < blackLevelHistIdx; i++) + histogram[0] += std::exchange(histogram[i], 0); + + Histogram yHist(histogram); + + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ + .traits = AgcTraits(yHist), + .yHist = yHist, + .exposure = frameContext.sensor.exposure, + .gain = frameContext.sensor.gain, + }}, metadata); + } else { + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {}, metadata); } - - auto histogram = stats->yHistogram; - const unsigned int blackLevelHistIdx = - context.activeState.blc.level * std::size(histogram) / 256; - - for (unsigned int i = 1; i < blackLevelHistIdx; i++) - histogram[0] += std::exchange(histogram[i], 0); - - const auto &newEv = agc_.calculateNewEv({ - .yHist = { histogram }, - .exposure = frameContext.sensor.exposure, - .gain = frameContext.sensor.gain, - }); - - frameContext.agc.exposure = newEv.exposure; - frameContext.agc.gain = newEv.analogueGain; - - context.activeState.agc.exposure = frameContext.agc.exposure; - context.activeState.agc.again = frameContext.agc.gain; } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h index 2e156e135c..6e37ce6eff 100644 --- a/src/ipa/simple/algorithms/agc.h +++ b/src/ipa/simple/algorithms/agc.h @@ -9,7 +9,7 @@ #include "algorithm.h" -#include +#include namespace libcamera { @@ -18,15 +18,23 @@ namespace ipa::soft::algorithms { class Agc : public Algorithm { public: + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, const IPAConfigInfo &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, DebayerParams *params) override; + void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const SwIspStats *stats, ControlList &metadata) override; private: - AgcMSV agc_; + AgcAlgorithm agc_; }; } /* namespace ipa::soft::algorithms */ diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 96b3c5b21d..63889cd8f6 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -17,6 +17,7 @@ #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" +#include #include #include @@ -27,10 +28,7 @@ namespace libcamera { namespace ipa::soft { struct IPASessionConfiguration { - struct { - uint32_t exposureMin, exposureMax; - double againMin, againMax, again10, againMinStep; - utils::Duration lineDuration; + struct Agc : agc::Session { } agc; struct { std::optional level; @@ -38,10 +36,7 @@ struct IPASessionConfiguration { }; struct IPAActiveState { - struct { - uint32_t exposure; - double again; - bool valid; + struct Agc : agc::ActiveState { } agc; struct { @@ -68,9 +63,7 @@ struct IPAActiveState { struct IPAFrameContext : public FrameContext { Matrix ccm; - struct { - uint32_t exposure; - double gain; + struct Agc : agc::FrameContext { } agc; struct { diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 8b5df884d4..f35e269deb 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -107,6 +107,7 @@ int IPASoftSimple::init(const IPASettings &settings, } context_.sensorInfo = sensorInfo; + context_.sensorControls = sensorControls; /* Load the tuning data file */ File file(settings.configurationFile); @@ -180,22 +181,6 @@ int IPASoftSimple::init(const IPASettings &settings, ControlInfoMap::Map ctrlMap = context_.ctrlMap; *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); - /* - * Check if the sensor driver supports the controls required by the - * Soft IPA. - * Don't save the min and max control values yet, as e.g. the limits - * for V4L2_CID_EXPOSURE depend on the configured sensor resolution. - */ - if (sensorControls.find(V4L2_CID_EXPOSURE) == sensorControls.end()) { - LOG(IPASoft, Error) << "Don't have exposure control"; - return -EINVAL; - } - - if (sensorControls.find(V4L2_CID_ANALOGUE_GAIN) == sensorControls.end()) { - LOG(IPASoft, Error) << "Don't have gain control"; - return -EINVAL; - } - return 0; } @@ -203,55 +188,17 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo, ControlInfoMap *ip { context_.sensorControls = configInfo.sensorControls; - const ControlInfo &exposureInfo = context_.sensorControls.find(V4L2_CID_EXPOSURE)->second; - const ControlInfo &gainInfo = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - /* Clear the IPA context before the streaming session. */ context_.configuration = {}; context_.activeState = {}; context_.frameContexts.clear(); - context_.configuration.agc.lineDuration = - context_.sensorInfo.minLineLength * 1.0s / context_.sensorInfo.pixelRate; - context_.configuration.agc.exposureMin = exposureInfo.min().get(); - context_.configuration.agc.exposureMax = exposureInfo.max().get(); - if (!context_.configuration.agc.exposureMin) { - LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear"; - context_.configuration.agc.exposureMin = 1; - } - - int32_t againMin = gainInfo.min().get(); - int32_t againMax = gainInfo.max().get(); - int32_t againDef = gainInfo.def().get(); - - if (context_.camHelper) { - context_.configuration.agc.againMin = context_.camHelper->gain(againMin); - context_.configuration.agc.againMax = context_.camHelper->gain(againMax); - context_.configuration.agc.again10 = std::max(context_.configuration.agc.againMin, 1.0); - context_.configuration.agc.againMinStep = - (context_.configuration.agc.againMax - - context_.configuration.agc.againMin) / - 100.0; - } else { - context_.configuration.agc.againMax = againMax; - context_.configuration.agc.again10 = againDef; - context_.configuration.agc.againMin = againMin; - context_.configuration.agc.againMinStep = 1.0; - } - for (const auto &algo : algorithms()) { int ret = algo->configure(context_, configInfo); if (ret) return ret; } - LOG(IPASoft, Info) - << "Exposure " << context_.configuration.agc.exposureMin << "-" - << context_.configuration.agc.exposureMax - << ", gain " << context_.configuration.agc.againMin << "-" - << context_.configuration.agc.againMax - << " (" << context_.configuration.agc.againMinStep << ")"; - *ipaControls = { ControlInfoMap::Map(context_.ctrlMap), controls::controls }; return 0;