From patchwork Mon Aug 3 13:13: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: 27550 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 7F19DBDE4C for ; Mon, 3 Aug 2026 13:14:47 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DB5D568063; Mon, 3 Aug 2026 15:14:41 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="l+8Cb5bG"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0705C68063 for ; Mon, 3 Aug 2026 15:14:39 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 117375B3 for ; Mon, 3 Aug 2026 15:13:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762810; bh=ObQlGH0hFgSXsqiAGmaWahgAGHl1A8VikaSfWuJ2N9g=; h=From:To:Subject:Date:In-Reply-To:References:From; b=l+8Cb5bGvz0O3gPeKGOPmlIjHoHt041PDt3aQVo4Efrp2aGWxrFC4ORR5xCN7TR/h 7l+Jxeye77ahcflzmu0AtVPT0yE815m4xBH40ni/VA9uTJcVD2psOcbqAsFSuxRTk+ lezBO6q923aMiGy1I42TF7PMnA+GLkscdrIUNLT8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 01/50] libcamera: software_isp: init(): Fix documentation typo Date: Mon, 3 Aug 2026 15:13:46 +0200 Message-ID: <20260803131435.153927-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:13: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: 27551 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 AEE59BDE4C for ; Mon, 3 Aug 2026 13:14:50 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A386C68084; Mon, 3 Aug 2026 15:14:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ZPpM1F/l"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2F40268067 for ; Mon, 3 Aug 2026 15:14:39 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3CFBF8D4 for ; Mon, 3 Aug 2026 15:13:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762810; bh=OFb998A7bxhsobwjqDQbbNj/DCE4bODj2t52c6x4e3E=; h=From:To:Subject:Date:In-Reply-To:References:From; b=ZPpM1F/l+BLMnKTQ52MNwbw8CMw4J0LZCjyKfL6mec5xSheAeAFqmvj6YOnNMQ+7Y z6oJD9Z8yXRGBitHsmlbXyVkT05TzjSCB+aenagdw2D+eK/cgAKl6Dxiz9tplxZFYM DuULsDdOUIOVqONFDJQEkrQ8SWI3xgoY/XxRumeU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 02/50] ipa: simple: Remove control presence sanity check Date: Mon, 3 Aug 2026 15:13:47 +0200 Message-ID: <20260803131435.153927-3-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:13: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: 27552 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 1461DBDE4C for ; Mon, 3 Aug 2026 13:14:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4A15F6808D; Mon, 3 Aug 2026 15:14:43 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Yz90Unlj"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 59F5468069 for ; Mon, 3 Aug 2026 15:14:39 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6D4B0512; Mon, 3 Aug 2026 15:13:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762810; bh=fjFdr4Dfn+/6Qm2526uGOnN0uAyxQaxJ2HdLHd4Omj4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Yz90UnljmhQ1PFgOZx/J3NSTbfi6hlU+nn/YVerfNSIm5qW6OYiGmPc4D5Z+BJSwK 17BJHeUtt9yQFMqYTxNR0v/ze3TQ2/3KyKmVER4rhBLKWqukc2xQGFQafvbwlblJZy nDe+0Ca01PGnKg36KcC6YT8mvApCbKVUs7cpfvAc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v3 03/50] ipa: simple: agc: Do not overwrite sensor exposure/gain Date: Mon, 3 Aug 2026 15:13:48 +0200 Message-ID: <20260803131435.153927-4-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:13: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: 27553 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 AF427BE080 for ; Mon, 3 Aug 2026 13:14:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 16B2268092; Mon, 3 Aug 2026 15:14:44 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UGNK7+hr"; 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 94ED56806B for ; Mon, 3 Aug 2026 15:14:39 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AC59F5B3; Mon, 3 Aug 2026 15:13:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762810; bh=3lbD0y7TasQbBIsv1vAEgxYhoxV5nBo9rr2AsWiEjaQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UGNK7+hrywR6X+iuAjoVL0G9hFmPbsT+EhoiT7i8IunvnYHpsDhYLWKyWd7pxxtSe jYQRJbtTwXJW6RuBWftUHWPZP23WEiiHP9xsXkiQw8bMnCxBoqtnn5Dy5eySw69VO5 POEXOigRD5vK/f7ZXPuxl7Cj4dwVfazZk/008bhA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v3 04/50] ipa: rkisp1: Remove `IPASessionConfiguration::sensor.defVBlank` Date: Mon, 3 Aug 2026 15:13:49 +0200 Message-ID: <20260803131435.153927-5-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:13: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: 27554 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 3723FBDE4C for ; Mon, 3 Aug 2026 13:14:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B6BB368098; Mon, 3 Aug 2026 15:14:44 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ndRqesN5"; 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 ED8AE68063 for ; Mon, 3 Aug 2026 15:14:39 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E6A9B8D4; Mon, 3 Aug 2026 15:13:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762811; bh=Yyg+90NjvciV7spWuUknaJD9qPWJD/eOhBJ7mNUKEFo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ndRqesN5k71P0YmL9R3NeMdepVGnp6r8bmaGLwWPQmWiQ01dbiiEdlty6vHQ1ZsdJ 23zdS6jER/8VwgeKM1wLPZGjxHnf/DaYL03r1vgdPILjz+u85i2lZb5GP479Fw1asG LruA10/GjZqONcYy16QrSPTvHwCCHdMTm+QCQI2U= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v3 05/50] ipa: libipa: agc_mean_luminance: Remove the need for inheritance Date: Mon, 3 Aug 2026 15:13:50 +0200 Message-ID: <20260803131435.153927-6-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 | 69 +++++++----- 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, 243 insertions(+), 178 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..2879d3f58e 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 @@ -144,13 +168,12 @@ static constexpr unsigned int kDefaultLuxLevel = 500; * will determine the supportable precision of the constraints. * * IPA modules that want to use this class to implement their AEGC algorithm - * should derive it and provide an overriding estimateLuminance() function for - * this class to use. They must call parseTuningData() in init(), and must also - * call setLimits() and resetFrameCounter() in configure(). They may then use - * calculateNewEv() in process(). If the limits passed to setLimits() change for - * any reason (for example, in response to a FrameDurationLimit control being - * passed in queueRequest()) then setLimits() must be called again with the new - * values. + * should derive AgcMeanLuminance::Traits and override the necessary functions. + * The users must call parseTuningData() in init(), and must also call setLimits() + * and resetFrameCounter() in configure(). They may then use calculateNewEv() in + * process(). If the limits passed to setLimits() change for any reason (for example, + * in response to a FrameDurationLimit control being passed in queueRequest()) + * then setLimits() must be called again with the new values. */ AgcMeanLuminance::AgcMeanLuminance() @@ -487,28 +510,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 +527,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 +670,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 +683,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 +705,7 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, return exposureModeHelper->splitExposure(10ms); } - double gain = estimateInitialGain(); + double gain = estimateInitialGain(traits); gain = constraintClampGain(constraintModeIndex, yHist, gain); /* @@ -724,8 +733,8 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, * * This function resets the internal frame counter, which exists to help the * algorithm decide whether it should respond instantly or not. The expectation - * is for derived classes to call this function before each camera start call in - * their configure() function. + * is for users to call this function before each camera start call in their + * configure() function. */ } /* namespace ipa */ 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 Mon Aug 3 13:13: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: 27555 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 A98B4BE080 for ; Mon, 3 Aug 2026 13:14:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5EF196809D; Mon, 3 Aug 2026 15:14:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BN//HYsE"; 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 2E03B68067 for ; Mon, 3 Aug 2026 15:14:40 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 436F4981; Mon, 3 Aug 2026 15:13:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762811; bh=KAuFNcNcaodvn8EwTAqvlD3vaqm2rZZSCfQZOrAa3S4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BN//HYsEftpHbcjEayiIrctZ7Zy1CDEdHEi694/m50nQRkZ6NQPU9Kp6jDTzOloJp IUvGxFrU6pNSx4ps/5FuHFoswLuf+SJdl/pT3uy1bh5nUld3eBIEskZB25CGwYee1y tTHo3a9qmCPzRK8tjv9Ql3uVJ1gfOLcaj15kI1TE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v3 06/50] ipa: rkisp1: Move sensor control info map to context Date: Mon, 3 Aug 2026 15:13:51 +0200 Message-ID: <20260803131435.153927-7-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:13: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: 27556 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 3C8C6C3264 for ; Mon, 3 Aug 2026 13:14:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6381E68070; Mon, 3 Aug 2026 15:14:47 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ThhTAsXu"; 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 9DEAB68078 for ; Mon, 3 Aug 2026 15:14:40 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7F172ABF; Mon, 3 Aug 2026 15:13:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762811; bh=HE7FhESbEeEO69Rts2Xr+83Ga8T1zdtGXNlXfJsQHZo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ThhTAsXulhT7kPzih579jlQaKzAH67jhRrMzDRN3E9OAe1VAHUf+d+blnxuCu73ka gS8ADeS9POsOSklMgGDJVY2HWQrIeN0bypPBjNJGHGIn9qsHZDhJQjduLm+9G6sLWW uq/rcDRved0SHNQCatYUMGQ/2XhjvXhsuwsActGw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v3 07/50] ipa: rkisp1: Update sensor info when configuring Date: Mon, 3 Aug 2026 15:13:52 +0200 Message-ID: <20260803131435.153927-8-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:13: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: 27557 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 7866ABDE4C for ; Mon, 3 Aug 2026 13:15:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 26F37680A1; Mon, 3 Aug 2026 15:14:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="lBFL5v8o"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DB33D6807A for ; Mon, 3 Aug 2026 15:14:40 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E8A995B3; Mon, 3 Aug 2026 15:13:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762812; bh=s+zEl0hHYLEoNlZPFsVZH8RWfTKgkrGwd5gnrgCgLxk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lBFL5v8oNwp3PUK/UjK9qBy3H4oz6sao8YeX83m1jErnwZsNV6MRywH+HTAHu6im+ dIgT9zfLtM6wwwPSILWjK9vZcG5HLwX9ol3OAR1oojZbWRGNUvNhnNlcuhwgwtQjXa 029o15xosxmxSNrMfUB2LBwHhN3dGBLoirYevdmc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v3 08/50] ipa: rkisp1: updateControls(): Use sensor info from context Date: Mon, 3 Aug 2026 15:13:53 +0200 Message-ID: <20260803131435.153927-9-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:13: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: 27558 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 4E6E0C328C for ; Mon, 3 Aug 2026 13:15:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5884368063; Mon, 3 Aug 2026 15:14:49 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TV0ugpaN"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 25E6D6807D for ; Mon, 3 Aug 2026 15:14:41 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 306FB512; Mon, 3 Aug 2026 15:13:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762812; bh=ZsEUwLcOLLIyolI+85ptckIex5pLd/PMsxb48IEZNVE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TV0ugpaNe1cFc4xo34GQvtqkE2M6I4zB16svvordgN4woXlzd09jaWO+IliDjAsPz gsIb/FGQDqJtH65fgC4nKRKpW63QQ7jGD8I7iWXeJFp9VRBuMuWPy8uwokrdeEtRRO yEu2dtkE2d5+R1SQPxKzxr4/Cqh63+79C8tHcOz4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [RFC PATCH v3 09/50] ipa: rkisp1: Move AGC related controls into AGC algorithm Date: Mon, 3 Aug 2026 15:13:54 +0200 Message-ID: <20260803131435.153927-10-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:13: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: 27559 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 AC4BDC32CE for ; Mon, 3 Aug 2026 13:15:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3E87068089; Mon, 3 Aug 2026 15:14:50 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="DJOpKc4t"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 561886806D for ; Mon, 3 Aug 2026 15:14:41 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6BE10BE1; Mon, 3 Aug 2026 15:13:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762812; bh=LyoRtDpoyydEb7i/al3TCmgICsYNXSvcWVQOr0lGGDM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=DJOpKc4to0SKx5LO8hW0mKdgkKd8NPsd7izCC6tqfbXClEkRZxW9MNrghqjJQiZw8 vSlyI0wWxcBG8OZjTKYPeOuWfqqCMteCwVDEJ5GPgvjQLl8nam5M0Q/HYsNR4btT1z McCrvos4ihMmRo29PxydaUBBZZHqVp5tEOsaCD2w= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham Subject: [RFC PATCH v3 10/50] ipa: mali-c55: Remove `DigitalGain` support Date: Mon, 3 Aug 2026 15:13:55 +0200 Message-ID: <20260803131435.153927-11-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:13: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: 27560 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 C5E6CC32FE for ; Mon, 3 Aug 2026 13:15:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4E68C680CD; Mon, 3 Aug 2026 15:14:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fsGSiHZk"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8DCFE68079 for ; Mon, 3 Aug 2026 15:14:41 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A3555D77; Mon, 3 Aug 2026 15:13:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762812; bh=TF3uAZiCUgk7wKcS0YTwytYDzyJKf7Ep6UusEIImEIw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fsGSiHZkorcL2LFSRVW4oaJzOYrr0PTSyTy5tJtWf6LxiUA1iXa9qnNg5BEblh/4P vnJQLVaMNXOhHxQR2goc2ZNAR4iPdOE24WpSZVHassAeebdMe1y8xBi+sdMhP1Gp6N i+zmKN+Z+wJKBPbjnpzjQhTVxHgTBEAAoESuJNX0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 11/50] ipa: libipa: agc_mean_luminance: Avoid some duplicate lookups Date: Mon, 3 Aug 2026 15:13:56 +0200 Message-ID: <20260803131435.153927-12-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Reviewed-by: Jacopo Mondi --- 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 2879d3f58e..1b0c43febf 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -224,14 +224,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; @@ -244,8 +242,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; @@ -257,13 +255,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); } } @@ -281,9 +277,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); } @@ -299,8 +293,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; @@ -340,8 +334,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); } } @@ -352,14 +346,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 Mon Aug 3 13:13: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: 27561 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 CCFC9C3301 for ; Mon, 3 Aug 2026 13:15:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 588FA68086; Mon, 3 Aug 2026 15:14:52 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TJFWMc70"; 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 BE3616807C for ; Mon, 3 Aug 2026 15:14:41 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D7BAA18E1; Mon, 3 Aug 2026 15:13:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762812; bh=78XzNzIBmEs2ez2lXqIXGQlAO6+sc5nnZlvfbsG2q5w=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TJFWMc70vbK2FjWvxk8FrjB6GUStXTNcrOtLAzdI1Jdx4JCBePvF3CfAeQ4iG8m/A aiiFf9Uo2Q5tFARcNs4teDd8PXCmogAisnTpq0zPjxXwScv9JVa7IICoOfeCpEhbzo O9h8RkGOoE8GnqzzonKwxN8teAHDuFQo1EqrJ6CI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 12/50] ipa: libipa: agc_mean_luminance: Remove unnecessary `std::shared_ptr` Date: Mon, 3 Aug 2026 15:13:57 +0200 Message-ID: <20260803131435.153927-13-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Reviewed-by: Jacopo Mondi --- 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 1b0c43febf..c1ec5a3ddb 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -331,10 +331,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); } } @@ -346,12 +343,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); } @@ -372,7 +365,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; } @@ -483,7 +476,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); } @@ -683,7 +676,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) { @@ -695,7 +688,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); @@ -717,7 +710,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 Mon Aug 3 13:13: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: 27562 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 07C0CC3303 for ; Mon, 3 Aug 2026 13:15:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2C8A8680C2; Mon, 3 Aug 2026 15:14:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="hmaoo0G6"; 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 03B2568080 for ; Mon, 3 Aug 2026 15:14:42 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 189098D4; Mon, 3 Aug 2026 15:13:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762813; bh=qksYeBrZEvmtU1uyfJbwQaCNUvOAR8wUos1lNgo3qvc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hmaoo0G6Yj7BEgkHC+Nw4vz65pEdzs/ldmchVt+okjRD0lXKjNMOV5EPSnecWRked hjAqkCSn4j5+7qtbyaUc5Shn2tmiknnwU80fmAWIvlKsv6rCKkoJ2CCCo2UQ9rXrNZ G8N59MgAIryiRFCkbJmelyFYk8hN3HsTXkockXJ8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 13/50] ipa: libipa: agc_mean_luminance: calculateNewEv(): Fix typo Date: Mon, 3 Aug 2026 15:13:58 +0200 Message-ID: <20260803131435.153927-14-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Reviewed-by: Jacopo Mondi --- 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 c1ec5a3ddb..4eb70ef0f7 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -648,7 +648,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 Mon Aug 3 13:13: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: 27563 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 D7965C3306 for ; Mon, 3 Aug 2026 13:15:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C8CF7680CA; Mon, 3 Aug 2026 15:14:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ozFCR1Q/"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3494268081 for ; Mon, 3 Aug 2026 15:14:42 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4D96A981 for ; Mon, 3 Aug 2026 15:13:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762813; bh=42OHRHPu2IXFw2J24J+rnbbxXyKn8OisVRP1e4Y/WQs=; h=From:To:Subject:Date:In-Reply-To:References:From; b=ozFCR1Q/SBXK3+MMff3ZAIGUbIHwDuBRY3oXhNP61dwzoODZXIS3vAZtMlbzJKdPC xE39PATNoWOd73fQ9gpnPAvMqFTy98IPUFqb/V4KMT2S/tY0Sc9mFKSOt+40u6e2BF jX6G7Y9jEkIzNuwijPcNCBzKeS4nYonexF4RO5o0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 14/50] ipa: libipa: agc_mean_luminance: calculateNewEv(): Collect params Date: Mon, 3 Aug 2026 15:13:59 +0200 Message-ID: <20260803131435.153927-15-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 4eb70ef0f7..bf7d164346 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -647,16 +647,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 exppsure value 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 @@ -666,20 +680,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."; @@ -691,8 +701,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 @@ -701,7 +711,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 Mon Aug 3 13:14: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: 27564 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 9DF32C3308 for ; Mon, 3 Aug 2026 13:15:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9603D680CF; Mon, 3 Aug 2026 15:14:54 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="unt+fmDt"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 65B1768089 for ; Mon, 3 Aug 2026 15:14:42 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7DEBA8D4; Mon, 3 Aug 2026 15:13:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762813; bh=tKb6tEzEF6yvv6s4kVkzvZX6ALH2umnJsxQh52QMicI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=unt+fmDtESzU1WCeCyRRYR0W1Ds+3FUwTJL5lR7y+772Sxj9EAKCs7MaV1KBD2hzt zZJ6r4izj/NvOROphHWL75RFcFDC0z+FvjPmmWR58+ZTIJx2tktUSnMeqeYVNbPamZ 0GFaTea8dRuNcz+l8mnG4WivbOnAZVRSJhYYZzgA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 15/50] ipa: libipa: agc_mean_luminance: constraintClampGain(): Mark `const` Date: Mon, 3 Aug 2026 15:14:00 +0200 Message-ID: <20260803131435.153927-16-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Reviewed-by: Jacopo Mondi --- 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 bf7d164346..3a14d778a3 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -538,7 +538,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_; @@ -568,7 +568,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 Mon Aug 3 13:14: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: 27565 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 6FC69C330A for ; Mon, 3 Aug 2026 13:15:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6D33C680B1; Mon, 3 Aug 2026 15:14:55 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="IHj2O2fQ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 939D468074 for ; Mon, 3 Aug 2026 15:14:42 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B074C981 for ; Mon, 3 Aug 2026 15:13:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762813; bh=KJE1mx406F3h8rj34PrmZSMDb46Q+eU9Hl1xdQtfyN0=; h=From:To:Subject:Date:In-Reply-To:References:From; b=IHj2O2fQQyppxsPN2nt+R1/2L8AhkNfkFHHnRFqHQoxyFkMg5DWPWCn+lTwKQYF9M 4tK/la21JTIfw9cKI27XBaTXFULmY279sLCetbF40vVTcp/XDCIokmEZHDA6LH672R sgi2VJdWmltCYpVmbGXCIBXR9r7vsiaoRub/ngmQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 16/50] ipa: libipa: agc_mean_luminance: calculateNewEv(): Collect results Date: Mon, 3 Aug 2026 15:14:01 +0200 Message-ID: <20260803131435.153927-17-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 3a14d778a3..8e6eafd853 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -667,6 +667,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 @@ -679,7 +684,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) { /* @@ -698,7 +703,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); @@ -720,7 +725,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 Mon Aug 3 13:14: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: 27566 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 321A9C330F for ; Mon, 3 Aug 2026 13:15:10 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 39E30680DA; Mon, 3 Aug 2026 15:14:56 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="DeaMZuSK"; 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 CD07668087 for ; Mon, 3 Aug 2026 15:14:42 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DE2458D4; Mon, 3 Aug 2026 15:13:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762814; bh=2ffI5m7ui4tllLJdb75Hg86sJ2Pach5LwjJa4T68mZw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=DeaMZuSKyS37YDxnN8WgybFSlH9ZHFXamTqv7QRBWl5D+dXewehSVIuTTArLfeTwH dY+dCwLK25Lhm1Bv/yN+Sgv39BiP7QvCJ5eldcEOdU4JwlUWsYXczK4SpGdLAKYale lBqe7BbVTbRJfd3E+qHDHcv83wi9n8B3fE9/EmNc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 17/50] ipa: libipa: agc_mean_luminance: calculateNewEv(): Return y target Date: Mon, 3 Aug 2026 15:14:02 +0200 Message-ID: <20260803131435.153927-18-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 rkisp1 agc algorithms needs to save the current y target for the wdr algorithm. Instead of having to recalculate it, just return it as part of the result set. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- 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 8e6eafd853..fbb5df847e 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -501,9 +501,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; /* @@ -670,6 +669,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 */ /** @@ -693,6 +695,7 @@ AgcMeanLuminance::calculateNewEv(const Params ¶ms) */ ExposureModeHelper &exposureModeHelper = exposureModeHelpers_.at(params.exposureModeIndex); + double yTarget = effectiveYTarget(); if (params.effectiveExposureValue == 0s) { LOG(AgcMeanLuminance, Error) @@ -703,10 +706,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); /* @@ -725,7 +728,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 Mon Aug 3 13:14: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: 27567 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 D3ADCC3333 for ; Mon, 3 Aug 2026 13:15:10 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 12E17680D6; Mon, 3 Aug 2026 15:14:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ltIG6I7t"; 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 02E3B6808A for ; Mon, 3 Aug 2026 15:14:43 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 21CF75B3 for ; Mon, 3 Aug 2026 15:13:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762814; bh=A2vg9rmnayD4Hmx0dqu4xrBrY5+xSSp0yHavHc0cnw0=; h=From:To:Subject:Date:In-Reply-To:References:From; b=ltIG6I7tFaDYFxhkuvyJIPBVDh+xsbeLX4yJFfF7ji0C5gFPHCZdpQnzarIKV24eL +j+EKtxyTga2N3+yQlVaUbLyQ/yOf35KRfqqFob1SAWIN+8n+g5gtGz1TidvWmI/wc +c+sz8VABRksb751Bc2ldXnQsZsvGJvkFR6hUG3U= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 18/50] ipa: libipa: agc_mean_luminance: Remove persistent params Date: Mon, 3 Aug 2026 15:14:03 +0200 Message-ID: <20260803131435.153927-19-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 fbb5df847e..a64251f9d7 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -177,8 +177,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) { } @@ -440,25 +439,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 @@ -529,26 +509,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) { @@ -567,7 +544,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); @@ -577,15 +554,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 @@ -609,7 +587,7 @@ double AgcMeanLuminance::effectiveYTarget() const double luminanceTarget = relativeLuminanceTarget_.eval( relativeLuminanceTarget_.domain().clamp(lux)); - return std::min(luminanceTarget * exposureCompensation_, + return std::min(luminanceTarget * exposureCompensation, kMaxRelativeLuminanceTarget); } @@ -664,6 +642,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. */ /** @@ -695,7 +681,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) @@ -710,7 +696,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 Mon Aug 3 13:14: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: 27568 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 B14C1C3334 for ; Mon, 3 Aug 2026 13:15:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2823C680DB; Mon, 3 Aug 2026 15:14:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OB/3wQFe"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4056B6808C for ; Mon, 3 Aug 2026 15:14:43 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4EACD8D4; Mon, 3 Aug 2026 15:13:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762814; bh=XbunYzFn48nXgB1gnQWOhs7HoWN/EKL5yVEs2PwYrdM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OB/3wQFehHveb3VOWKwtUcMo6NBGNTUakAJmQlLnRZjbp7rPCjYEFUVI4tXkn122a cedflkonasXnLTmz1Vl7ssA8+yV5ExoRAkWeprziCdo1SKRkR4SpzvkU8ZwZWmMVcw 2/WIxkEtUq3ieJr1a5UutDhBukUlxyUqITYUA290= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 19/50] ipa: libipa: agc_mean_luminance: Reset `filteredExposure_` when configuring Date: Mon, 3 Aug 2026 15:14:04 +0200 Message-ID: <20260803131435.153927-20-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Reviewed-by: Jacopo Mondi --- 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 a64251f9d7..37e8ff154f 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -367,6 +367,7 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration, helper.configure(lineDuration, sensorHelper); luxWarningEnabled_ = true; + filteredExposure_ = 0s; } /** From patchwork Mon Aug 3 13:14: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: 27569 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 2210EC3336 for ; Mon, 3 Aug 2026 13:15:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2670D68091; Mon, 3 Aug 2026 15:14:59 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FpI8Fiqu"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 74BC36808E for ; Mon, 3 Aug 2026 15:14:43 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8D95A5B3 for ; Mon, 3 Aug 2026 15:13:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762814; bh=pvOoEl6K/zN1hhJVV5+pVpjqvC1d+U2qEwMpNAFizvs=; h=From:To:Subject:Date:In-Reply-To:References:From; b=FpI8FiqubuSOujOnfYXG+gAq8l/+sHnRdjffXkiRI5kZ26fMFh1YM8rq5YAFXxOol bd8nDM9+yAO9m71hTlRDUfDpNmgcW3sq8f7sBVc8nfaaU8+RKoERGf5bBRH6MZnnb1 8iCY541OM3SRsP/AY+PTKEWJs8CxiUhWmyKtBK4o= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 20/50] ipa: Simplify sensor exposure/gain setting/getting Date: Mon, 3 Aug 2026 15:14:05 +0200 Message-ID: <20260803131435.153927-21-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:14: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: 27570 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 BC528BE080 for ; Mon, 3 Aug 2026 13:15:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B6FE4680EC; Mon, 3 Aug 2026 15:15:00 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gR/dS2Vm"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C3FAF6807B for ; Mon, 3 Aug 2026 15:14:43 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C1B258D4 for ; Mon, 3 Aug 2026 15:13:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762814; bh=9/ut5yDCutr02NFylPDywzs/1AavtpUKFWGMhth48+A=; h=From:To:Subject:Date:In-Reply-To:References:From; b=gR/dS2VmdTCl6YkXrabQAozzh3Ex6JDPjrSd8TCn4zyqVBQqMQhaZjmNczABV/x1f Vtdi/H3QpNt0tLpnaVFVKWYwgg/N0zuFEQEczYPnGDGTX5nglFaMm7XCwsjHGZxSsY yzK9j/ou3RKS0buPXV95Kul0Owi9leNRbwH0SZkg= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 21/50] ipa: libipa: Add `AgcAlgorithm` Date: Mon, 3 Aug 2026 15:14:06 +0200 Message-ID: <20260803131435.153927-22-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 | 644 +++++++++++++++++++++++++++++++++++++ src/ipa/libipa/agc.h | 100 ++++++ src/ipa/libipa/meson.build | 1 + 3 files changed, 745 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..a8f91f864e --- /dev/null +++ b/src/ipa/libipa/agc.cpp @@ -0,0 +1,644 @@ +/* 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 for the streaming session + * + * \var agc::Session::maxExposureTime + * \brief Maximum exposure time for the streaming session + * + * \var agc::Session::minAnalogueGain + * \brief Minimum analogue gain for the streaming session + * + * \var agc::Session::maxAnalogueGain + * \brief Maximum analogue gain for the streaming session + * + * \var agc::Session::minFrameDuration + * \brief Minimum frame duration for the streaming session + * + * \var agc::Session::maxFrameDuration + * \brief Maximum frame duration for the streaming session + * + * \var agc::Session::lineDuration + * \brief Line duration for the streaming session + * + * \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 + * \copybrief AgcAlgorithm::ConfigurationParams::autoAllowed + * \sa AgcAlgorithm::ConfigurationParams::autoAllowed + */ + +/** + * \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 Whether automatic exposure control is enabled by the ExposureTimeMode control + * + * \var agc::ActiveState::autoGainEnabled + * \brief Whether automatic gain control is enabled 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 translated 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 Current configuration of the sensor + * + * \var AgcAlgorithm::ConfigurationParams::sensorControls + * \brief ControlInfoMap of the sensor + * + * \var AgcAlgorithm::ConfigurationParams::ctrlMap + * \brief ControlInfoMap::Map to update with controls + * + * \var AgcAlgorithm::ConfigurationParams::autoAllowed + * \brief Whether to enable auto controls + * + * If \a false, the algorithm is set up for manual exposure and gain + * control only, without automatic adjustments. In this mode statistics + * must not be provided to AgcAlgorithm::process(), and ExposureTimeMode + * and AnalogueGainMode will only advertise manual control. + */ + +/** + * \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.autoAllowed = config.autoAllowed; + session.lineDuration = + config.sensorInfo.minLineLength * 1.0s / config.sensorInfo.pixelRate; + session.sensor.outputSize = config.sensorInfo.outputSize; + + 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(); + + /* 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()); + + 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); + } + + /* + * 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; + session.minFrameDuration = std::chrono::microseconds(frameDurations[0]); + session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]); + + 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, + }; + config.ctrlMap[&controls::AnalogueGain] = ControlInfo{ + minGain, + maxGain, + defGain, + }; + config.ctrlMap[&controls::ExposureTime] = ControlInfo{ + static_cast(minExposure * lineDurationUs), + static_cast(maxExposure * lineDurationUs), + static_cast(defExposure * lineDurationUs), + }; + config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ + frameDurations[0], + frameDurations[1], + Span{ { frameDurations[2], frameDurations[2] } }, + }; + + 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), + }); + + /* 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; + + LOG(Agc, Debug) + << "Divided up exposure time, analogue gain, quantization gain" + << " and digital gain are " << utils::Duration(state.automatic.exposure * lineDuration) + << ", " << state.automatic.gain << ", " << state.automatic.quantizationGain + << " and " << newEv.digitalGain; + } + + /* + * 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..ab85d9a3bd 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,102 @@ 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 Mon Aug 3 13:14: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: 27572 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 2CCEBC3339 for ; Mon, 3 Aug 2026 13:15:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5202068092; Mon, 3 Aug 2026 15:15:04 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="c3seOd4K"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EE3D968091 for ; Mon, 3 Aug 2026 15:14:43 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 141C45B3 for ; Mon, 3 Aug 2026 15:13:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762815; bh=5ShJ7WhxvZwZ2UFjC2q6rpeaDATnrCwjS4LBE8V3sg4=; h=From:To:Subject:Date:In-Reply-To:References:From; b=c3seOd4KIm252NZGCFM3h8IoC1kKdPdLTHDgqQudwfh5SQuR31N1agmQ8U8whttVB ITXgs5sx7L8TV7YQ8L/wdL6+AZJ9rV5bXM4uql2nk5Swn4+kYdybgBRiacrOf1XsSg IwSHPD/8OTPuVVhEnnB0bb3iE7W67UIP6trAdfq4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 22/50] ipa: libipa: agc: Omit `setLimits()` in `configure()` Date: Mon, 3 Aug 2026 15:14:07 +0200 Message-ID: <20260803131435.153927-23-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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" `AgcMeanLuminance::setLimits()` is only required to be called before `calculateNewEv()`, so the call in `configure()` has no real effect because no calculation is carried out. So remove it. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index a8f91f864e..d751a8b0b8 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -313,9 +313,6 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]); impl_.configure(session.lineDuration, config.sensor); - impl_.setLimits(session.minExposureTime, session.maxExposureTime, - session.minAnalogueGain, session.maxAnalogueGain, - {}); impl_.resetFrameCount(); /* Configure the default exposure and gain. */ From patchwork Mon Aug 3 13:14: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: 27571 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 95582C3338 for ; Mon, 3 Aug 2026 13:15:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0FB8E680DF; Mon, 3 Aug 2026 15:15:03 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NmGGoHfk"; 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 3101068093 for ; Mon, 3 Aug 2026 15:14:44 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 45730512; Mon, 3 Aug 2026 15:13:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762815; bh=RClYYqGKmEfZhEKDUyMz44xdrd2SCDzCumwwFjJRFB0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NmGGoHfkK9Fl8ARogKIq6bPLEUQPQy0DY6VpFQ2hb5mJA8UxBo/8V5Ff2WL/B4tR7 kfwAUUaCa6mrcQpsde7i1wdf+nsu3gUpeF8Fe0quzXNFzxLCGFMwD1lxzQB7fJwtat /TMgZOX1z/QEZ0BijLIk4EowTH3C6qHjDXRby+hA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 23/50] ipa: libipa: agc: Use default exposure Date: Mon, 3 Aug 2026 15:14:08 +0200 Message-ID: <20260803131435.153927-24-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Reviewed-by: Jacopo Mondi --- 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 d751a8b0b8..69debd8af7 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -318,7 +318,7 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, /* Configure the default exposure and gain. */ state = {}; state.automatic.gain = session.minAnalogueGain; - state.automatic.exposure = 10ms / session.lineDuration; + state.automatic.exposure = defExposure; state.automatic.quantizationGain = 1; state.automatic.yTarget = impl_.effectiveYTarget(0, 1); state.manual.gain = state.automatic.gain; From patchwork Mon Aug 3 13:14: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: 27573 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 A8DF7C333A for ; Mon, 3 Aug 2026 13:15:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4421F680F2; Mon, 3 Aug 2026 15:15:05 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Ua+N1yXn"; 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 67BEE68095 for ; Mon, 3 Aug 2026 15:14:44 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7C09D5B3; Mon, 3 Aug 2026 15:13:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762815; bh=wxN0ATWYfrvfxkM4rdOqzzsSXX8nqRvUIKKmVolxXLs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Ua+N1yXn57y9x8O6xJ1rvZt+cL6oc0npipCo6bkg+7Ql0AN8b2MSe0vu64X7SpPt8 rnU5AUwkUD2w+1O/wWYlrz21Z3Ln022SzXgg3nPSe2eL/wnXB8lYMKxgnoByoqU8oE mUcFS86oseNu3BwLPbtkrW4RILn5gUPvg+4wx3yQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 24/50] ipa: libipa: agc: Use full range as default `FrameDurationLimits` Date: Mon, 3 Aug 2026 15:14:09 +0200 Message-ID: <20260803131435.153927-25-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Reviewed-by: Jacopo Mondi --- 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 69debd8af7..78a938568c 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -372,7 +372,7 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ frameDurations[0], frameDurations[1], - Span{ { frameDurations[2], frameDurations[2] } }, + Span{ { frameDurations[0], frameDurations[1] } }, }; if (session.autoAllowed) { From patchwork Mon Aug 3 13:14: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: 27575 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 AC3BEC333C for ; Mon, 3 Aug 2026 13:15:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 84C73680CD; Mon, 3 Aug 2026 15:15:07 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="G/fU6K//"; 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 9E28F68097 for ; Mon, 3 Aug 2026 15:14:44 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B1169512; Mon, 3 Aug 2026 15:13:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762815; bh=x66YuLWtynSNp2hj+fWb2QhK8rI/MVTFPFDqBXxPkw8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=G/fU6K//Yzbp/hmGgFcoWtwHCHgQgJ+jEQ6cwyTRO3yR6A31J6e6AmeQ7xyniWmCH L/RkPfBY+5X1GPkfn0z7IuAIl0aP6USMlCBd/DrgYOYyoDEuGNsUkR8R++dyBXWczl b7i2PnnE9gYtXGFa8U5mFaPGkaMAtHwou6fCGLik= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 25/50] ipa: libipa: agc: Adjust debug messages slightly Date: Mon, 3 Aug 2026 15:14:10 +0200 Message-ID: <20260803131435.153927-26-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 78a938568c..5273c1f64b 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -272,8 +272,10 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, float defGain = config.sensor->gain(v4l2Gain.def().get()); 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. @@ -601,10 +603,10 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, newExposureTime = newEv.exposureTime; LOG(Agc, Debug) - << "Divided up exposure time, analogue gain, quantization gain" - << " and digital gain are " << utils::Duration(state.automatic.exposure * lineDuration) - << ", " << state.automatic.gain << ", " << state.automatic.quantizationGain - << " and " << newEv.digitalGain; + << "exposure-time:" << utils::Duration(state.automatic.exposure * lineDuration) + << " analogue-gain:" << state.automatic.gain + << " quantization-gain:" << state.automatic.quantizationGain + << " digital-gain:" << newEv.digitalGain; } /* From patchwork Mon Aug 3 13:14: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: 27578 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 752FFC333F for ; Mon, 3 Aug 2026 13:15:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C0F08680E5; Mon, 3 Aug 2026 15:15:09 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="csZZQxbj"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CC3D568099 for ; Mon, 3 Aug 2026 15:14:44 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E7AC6981 for ; Mon, 3 Aug 2026 15:13:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762816; bh=kz+5+wqwO0NDaVrSqKS/DXHRZS7pQug6lnSow8DYewM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=csZZQxbjPO5umjDlc1FPKg9OB7aGmJehDz22DYCtkfDqmsAjmqlIVEOz2nntZt8kP v5O1W2foNe58JMdBH1LhhBbTFJqhy0voxKYfbINz4+4PRjuepZpE+EZblec8kt85l6 rjbLcKsCty9TKKZT8pTcJsI5W5X4WDyPhbrvW+ao= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 26/50] ipa: libipa: agc: Store digital gain Date: Mon, 3 Aug 2026 15:14:11 +0200 Message-ID: <20260803131435.153927-27-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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" While digital gain support is not implemented yet, start by storing the suggested digital gain in the active state. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 7 ++++++- src/ipa/libipa/agc.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 5273c1f64b..e9055ddd7b 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -100,6 +100,9 @@ LOG_DEFINE_CATEGORY(Agc) * \var agc::ActiveState::automatic.quantizationGain * \brief Automatic quantization gain multiplier * + * \var agc::ActiveState::automatic.digitalGain + * \brief Automatic digital gain multiplier + * * \var agc::ActiveState::automatic.yTarget * \brief Automatically determined luminance target * @@ -322,6 +325,7 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, state.automatic.gain = session.minAnalogueGain; state.automatic.exposure = defExposure; state.automatic.quantizationGain = 1; + state.automatic.digitalGain = 1; state.automatic.yTarget = impl_.effectiveYTarget(0, 1); state.manual.gain = state.automatic.gain; state.manual.exposure = state.automatic.exposure; @@ -598,6 +602,7 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, state.automatic.exposure = newEv.exposureTime / lineDuration; state.automatic.gain = newEv.analogueGain; state.automatic.quantizationGain = newEv.quantizationGain; + state.automatic.digitalGain = newEv.digitalGain; state.automatic.yTarget = newEv.yTarget; newExposureTime = newEv.exposureTime; @@ -606,7 +611,7 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, << "exposure-time:" << utils::Duration(state.automatic.exposure * lineDuration) << " analogue-gain:" << state.automatic.gain << " quantization-gain:" << state.automatic.quantizationGain - << " digital-gain:" << newEv.digitalGain; + << " digital-gain:" << state.automatic.digitalGain; } /* diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index ab85d9a3bd..b0f811e93d 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -74,6 +74,7 @@ struct ActiveState { uint32_t exposure; double gain; double quantizationGain; + double digitalGain; double yTarget; } automatic; From patchwork Mon Aug 3 13:14:12 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27574 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 29C54C333B for ; Mon, 3 Aug 2026 13:15:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 74DCD680A7; Mon, 3 Aug 2026 15:15:06 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ckvrRZgk"; 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 08B7A6809B for ; Mon, 3 Aug 2026 15:14:45 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 249435B3 for ; Mon, 3 Aug 2026 15:13:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762816; bh=QGU7SH5jLslYQ2ZvW3px3dppTJ8XOHsiwgoDXuP6T0Y=; h=From:To:Subject:Date:In-Reply-To:References:From; b=ckvrRZgkiPO1YJz2xSTN/sT8SUqDW/2yY+O8N6jYdZpaVOCKbU11knL0VcO45/Bjn /O7gZ0vC5YN5f/aSPfPWG+r3cg/qPiyqV/JFdT9v7t9Hawxal5PgyY1PA7UBPm1ERU Ss2hsFT9Xs8ptPSjrp1WjNkaZVWucz0d+XHbaI88= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 27/50] ipa: libipa: agc: Use minimum line length Date: Mon, 3 Aug 2026 15:14:12 +0200 Message-ID: <20260803131435.153927-28-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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" `IPACameraSensorInfo::minLineLength` already contains the minimum line length, taking the output format and minimum horizontal blanking into account. Furthermore, the `CameraSensor` implementations already set the horizontal blanking to the minimum when initializing the sensor. And finally the line duration is already calculated with the minimum line length. So use the minimum for the frame duration calculations as well. --- src/ipa/libipa/agc.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index e9055ddd7b..0f787925cb 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -250,10 +250,11 @@ int AgcAlgorithm::init(const ValueNode &tuningData) int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, const ConfigurationParams &config) { + const auto lineLength = config.sensorInfo.minLineLength; + session = {}; session.autoAllowed = config.autoAllowed; - session.lineDuration = - config.sensorInfo.minLineLength * 1.0s / config.sensorInfo.pixelRate; + session.lineDuration = lineLength * 1.0s / config.sensorInfo.pixelRate; session.sensor.outputSize = config.sensorInfo.outputSize; const double lineDurationUs = session.lineDuration.get(); @@ -286,9 +287,6 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, * 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{ From patchwork Mon Aug 3 13:14: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: 27576 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 2682DC333D for ; Mon, 3 Aug 2026 13:15:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 48A06680EF; Mon, 3 Aug 2026 15:15:08 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vz4LbAh/"; 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 32CF96806F for ; Mon, 3 Aug 2026 15:14:45 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 53200512 for ; Mon, 3 Aug 2026 15:13:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762816; bh=of4UYaR2YupW3cysmpKoxiRlZJ/ADr5ya8FRWarQAC0=; h=From:To:Subject:Date:In-Reply-To:References:From; b=vz4LbAh/QGl8YJy7Jy4VVdKf9UmXXLiUsOT1lsYDJmmm1barQroJYN95OGhfhDTsV 6yr7WR1UIIUtwPf83T/gRmxljfaJrEIO3yJ4jBJv2pXK3b+iuti0cFKnAJKZ0OiDEN B9S3TLXYpTxpgHRf9rPRP7Rg69+AauSoDfchBrSw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 28/50] ipa: libipa: agc: Fix multiplication type Date: Mon, 3 Aug 2026 15:14:13 +0200 Message-ID: <20260803131435.153927-29-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 type of `frameSize` is `uint64_t`, but it is assigned the result of a 32-bit multiplication. So make one of the terms a 64-bit integer so that the result will be actually 64-bit. Furthermore, also adjust the calculation to do the multiplication first just to be as accurate as possible. No wraparound will happen as long as the frame size is less than about 16.7 TiB. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 0f787925cb..f420dfdce4 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -297,8 +297,8 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, 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); + uint64_t frameSize = uint64_t(lineLength) * frameHeights[i]; + frameDurations[i] = frameSize * 1000000U / config.sensorInfo.pixelRate; } /* From patchwork Mon Aug 3 13:14: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: 27577 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 E8D1FC333E for ; Mon, 3 Aug 2026 13:15:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1290D680F0; Mon, 3 Aug 2026 15:15:09 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="H/FW7byO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6562E68088 for ; Mon, 3 Aug 2026 15:14:45 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 83DAB5B3 for ; Mon, 3 Aug 2026 15:13:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762816; bh=5FCt395jOPww3Y7Uvh/WXpl6qsA0bNNWtDqi6cVxeQg=; h=From:To:Subject:Date:In-Reply-To:References:From; b=H/FW7byOAvU25rvqCZ+Kn58VhzJteSD1dLxpVKP0EwC6vLLr8lKJQDSmz1gF/lFRZ 4RHEuaxu15tYKuJEpUd76lcGUnStG/lCd60wGmb2YoTHCjhaE2fq6Tya/tvNmjNLq2 S/iarPWPz0mWUSVZGSMy9ETv6hbwqXxcldQTqnrs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 29/50] ipa: libipa: agc: Calculate vblank and frame duration sooner Date: Mon, 3 Aug 2026 15:14:14 +0200 Message-ID: <20260803131435.153927-30-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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" Calculating the vblank and frame duration is problematic in `process()` because at the moment it is calculated for an already finished frame based on the new suggested exposure time. Instead, move the calculation to `prepare()` where the frame's exposure and gain are finalized. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 29 ++++++++++++++--------------- src/ipa/libipa/agc.h | 3 ++- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index f420dfdce4..69f731569e 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -502,7 +502,7 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s /** * \brief Handle a \a prepare operation */ -void AgcAlgorithm::prepare(agc::ActiveState &state, agc::FrameContext &frameContext) +void AgcAlgorithm::prepare(const agc::Session &session, agc::ActiveState &state, agc::FrameContext &frameContext) { uint32_t activeAutoExposure = state.automatic.exposure; double activeAutoGain = state.automatic.gain; @@ -533,6 +533,19 @@ void AgcAlgorithm::prepare(agc::ActiveState &state, agc::FrameContext &frameCont } frameContext.yTarget = state.automatic.yTarget; + + /* + * 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 / session.lineDuration, + frameContext.exposure); + frameContext.vblank = frameDuration - session.sensor.outputSize.height; + + /* Update frame duration accounting for line length quantization. */ + frameContext.frameDuration = + (session.sensor.outputSize.height + frameContext.vblank) * session.lineDuration; } /** @@ -543,7 +556,6 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, ControlList &metadata) { const utils::Duration &lineDuration = session.lineDuration; - utils::Duration newExposureTime = {}; if (params) { ASSERT(session.autoAllowed); @@ -603,8 +615,6 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, state.automatic.digitalGain = newEv.digitalGain; state.automatic.yTarget = newEv.yTarget; - newExposureTime = newEv.exposureTime; - LOG(Agc, Debug) << "exposure-time:" << utils::Duration(state.automatic.exposure * lineDuration) << " analogue-gain:" << state.automatic.gain @@ -612,17 +622,6 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, << " digital-gain:" << state.automatic.digitalGain; } - /* - * 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()); diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index b0f811e93d..e96c6926b2 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -126,7 +126,8 @@ public: void queueRequest(const agc::Session &session, agc::ActiveState &state, agc::FrameContext &frameContext, const ControlList &controls); - void prepare(agc::ActiveState &state, agc::FrameContext &frameContext); + void prepare(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext); struct ProcessParams { const AgcMeanLuminance::Traits &traits; From patchwork Mon Aug 3 13:14: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: 27579 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 DD340C3264 for ; Mon, 3 Aug 2026 13:15:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B8A20680E3; Mon, 3 Aug 2026 15:15:10 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="IX/mNQ20"; 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 B769C680A4 for ; Mon, 3 Aug 2026 15:14:45 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B6AA6512; Mon, 3 Aug 2026 15:13:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762816; bh=jqHU3EXrWXTWCi+5W9t5vd2eIGMhGt7rkoRUGldDAVM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IX/mNQ20irKhKvRhYluio4ttgpzKSVkqVU9729xd+FleOURAhfw6Pwm0M5CuexEjB qpmEYFD/dGraxgoL0v0TgrKawV1mnZJi7oQKW2f9e7N4YzTwr61pS43V8dq536ZLH6 MDy75n+xvc3/hZrW8kvJc8D5GMcmGpIyW2vIEyu0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 30/50] ipa: rkisp1: agc: Port to `AgcAlgorithm` Date: Mon, 3 Aug 2026 15:14:15 +0200 Message-ID: <20260803131435.153927-31-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Reviewed-by: Jacopo Mondi --- 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..06e0646981 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.configuration.agc, 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 Mon Aug 3 13:14: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: 27580 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 9E27CC3340 for ; Mon, 3 Aug 2026 13:15:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D7269680E8; Mon, 3 Aug 2026 15:15:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="uWBN0F72"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 03F5F67EE4 for ; Mon, 3 Aug 2026 15:14:46 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 122305B3; Mon, 3 Aug 2026 15:13:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762817; bh=5ZftDUgDFoplj1dcFv0ScMOkvtHNFDA8jjAnN7qSeV4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uWBN0F72+pb0ibBTe43dRsgqrAj92tVPwXTsNQf+9vyccVbHbxf6znH+6HQ8pd7cm 6hKGs7EbvN+mI2q67kTxZD8WMx5OwT9XQ00lAjClHTMpQwDF5qLOEyYZWqrMz3b7L4 L0WUWB+nEFL8vyH8DLDR1HmcL0hF8zC7KRS0w3QY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 31/50] ipa: mali-c55: agc: Port to `AgcAlgorithm` Date: Mon, 3 Aug 2026 15:14:16 +0200 Message-ID: <20260803131435.153927-32-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Reviewed-by: Jacopo Mondi --- src/ipa/mali-c55/algorithms/agc.cpp | 131 ++++++----------------- 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(+), 255 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 821f43e9fd..87586e8a82 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -13,7 +13,6 @@ #include #include -#include #include "libipa/colours.h" @@ -123,12 +122,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 +146,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 +216,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.configuration.agc, context.activeState.agc, frameContext.agc); + if (frame > 0) return; @@ -308,11 +261,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, - [[maybe_unused]] ControlList &metadata) + ControlList &metadata) { - IPASessionConfiguration &configuration = context.configuration; - IPAActiveState &activeState = context.activeState; - if (!stats) { LOG(MaliC55Agc, Error) << "No statistics buffer passed to Agc"; return; @@ -323,32 +273,13 @@ 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; + .exposure = frameContext.sensor.exposure, + .gain = frameContext.sensor.gain, + }}, metadata); - metadata.set(controls::ExposureTime, currentShutter.get()); - metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain); 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 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..55a56f7748 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,16 @@ namespace libcamera { namespace ipa::mali_c55 { struct IPASessionConfiguration { - struct { - utils::Duration minShutterSpeed; - utils::Duration maxShutterSpeed; - uint32_t defaultExposure; - double minAnalogueGain; - double maxAnalogueGain; - } 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 +43,12 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { + agc::FrameContext agc; + struct { uint32_t exposure; - double sensorGain; - } agc; + double gain; + } sensor; struct { UQ<4, 8> rGain; @@ -77,10 +63,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 Mon Aug 3 13:14: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: 27581 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 3CEC2C3341 for ; Mon, 3 Aug 2026 13:15:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9F617680FA; Mon, 3 Aug 2026 15:15:12 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="lNiJjwJ+"; 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 3CBD668078 for ; Mon, 3 Aug 2026 15:14:46 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4E577981 for ; Mon, 3 Aug 2026 15:13:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762817; bh=SAT1MSN78EIijGidhD38BG0q/iK6HX/uMGbfBf/Kr8o=; h=From:To:Subject:Date:In-Reply-To:References:From; b=lNiJjwJ+GGFGsZJuMsWLuz+cW+mNeI8VBxqxd6jXA9IqTczUfdKlHJov2NTpA7b3Q HA61ImDeZfb2HxhsA+gRerLqt6tkq/4cKBJUETvqmJhihkJFk3aZiyo9h9igCAhvi/ B4ExXCZATj28YjeOoP1ZTlBRMFZW0ScJWvYXm4fQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 32/50] ipa: ipu3: agc: Port to `AgcAlgorithm` Date: Mon, 3 Aug 2026 15:14:17 +0200 Message-ID: <20260803131435.153927-33-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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..91923c7f70 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.configuration.agc, 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 Mon Aug 3 13:14: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: 27582 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 BE17AC3342 for ; Mon, 3 Aug 2026 13:15:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A8E5568101; Mon, 3 Aug 2026 15:15:13 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NqyeNT5j"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7D0CB680A2 for ; Mon, 3 Aug 2026 15:14:46 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 87043ABF; Mon, 3 Aug 2026 15:13:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762817; bh=Vr+UrpcktJDrknFg+LET839LEoofGTIqR2S51uwxe90=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NqyeNT5jsEHrCCImKdJecEqeGAVQ4KfJ4SzSXrov034uziWYcFcInmlm+635d34M3 tymeHKY7Y8Y6ry1kV6pzjCJXxxbY8VGE7QvM2uHwgHHOHyawkF0nNw5yPuwjAH8qDY oHF9ZNudVko5iCSadqDW/tHoXYwQFTTPuB8YKtcY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 33/50] ipa: libipa: agc_mean_luminance: Remove control handling Date: Mon, 3 Aug 2026 15:14:18 +0200 Message-ID: <20260803131435.153927-34-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 22 +++++++++++++++++----- src/ipa/libipa/agc_mean_luminance.cpp | 21 +-------------------- 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 69f731569e..3efa4a89e1 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -382,13 +382,25 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, 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 37e8ff154f..8303065008 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -236,8 +236,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()) { @@ -257,8 +255,6 @@ int AgcMeanLuminance::parseConstraintModes(const ValueNode &tuningData) int ret = parseConstraint(modeDict, it->second); if (ret) return ret; - - availableConstraintModes.push_back(it->second); } } @@ -277,18 +273,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()) { @@ -331,7 +322,6 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) } exposureModeHelpers_.try_emplace(it->second, stages); - availableExposureModes.push_back(it->second); } } @@ -341,13 +331,9 @@ 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; } @@ -472,11 +458,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 Mon Aug 3 13:14: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: 27584 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 92CD1BDE4C for ; Mon, 3 Aug 2026 13:15:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 87CAE68080; Mon, 3 Aug 2026 15:15:15 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ZMi92USq"; 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 C6CC56807A for ; Mon, 3 Aug 2026 15:14:46 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C704C512; Mon, 3 Aug 2026 15:13:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762817; bh=Tco1sySte1EpSQtv1XDJ2p8BUe1HTciWJVdURdyMVzI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZMi92USqHQudFwRPCvlCC8OLcvWHmvfBbUXQpbWffQlvzWJ0HmF14IdevkseAzZ7s xeKI4QR029N3GKnT106L2QIjf60SIuHT+HNqVWhRxebOm+yzCqoFZQXEANfp7bAwgG dB5sMkxnoayYRbJw9XyVi1B9oksAIhkutblTPWnQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 34/50] ipa: libipa: histogram: total(): Use `back()` Date: Mon, 3 Aug 2026 15:14:19 +0200 Message-ID: <20260803131435.153927-35-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Reviewed-by: Jacopo Mondi --- 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 Mon Aug 3 13:14: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: 27583 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 54C8CC3343 for ; Mon, 3 Aug 2026 13:15:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BB1CA6807C; Mon, 3 Aug 2026 15:15:14 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cNHGA+bC"; 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 EA100680AC for ; Mon, 3 Aug 2026 15:14:46 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0A0F75B3 for ; Mon, 3 Aug 2026 15:13:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762818; bh=2kiikFk3037quxt7ucPm4CXAxdeytL1/WZOa4jGuNEY=; h=From:To:Subject:Date:In-Reply-To:References:From; b=cNHGA+bCmuPyPi22zYgo6sn4Qe2wDAhIn8PtJbsb2pFsabpUa9Pfl1i0XDATjAAo/ i6ygTeZ7AGQsM2fV79veuXL1DVZG68oceBhu/6W0U5RmyjSXqljsVU4QljezPAA2MQ WTZ5MEzshCZlb6X1wEo+iE35FbpljviZzccIOxV8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 35/50] ipa: libipa: histogram: Add `operator[]` to query bin Date: Mon, 3 Aug 2026 15:14:20 +0200 Message-ID: <20260803131435.153927-36-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:14: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: 27585 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 22030C3344 for ; Mon, 3 Aug 2026 13:15:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 507316810F; Mon, 3 Aug 2026 15:15:16 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="qoNPAFWi"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 256F6680A7 for ; Mon, 3 Aug 2026 15:14:47 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3E63A981 for ; Mon, 3 Aug 2026 15:13:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762818; bh=XWYl5QGq6gBfqtoA0bh8WYysMWXQrmWXfdW7cIDgIR0=; h=From:To:Subject:Date:In-Reply-To:References:From; b=qoNPAFWiC39ijZN+/sI9Q4wG1v9TcjAIoVZDFuQqazyD7j8nr+hYrgrSpU8c1YvdI l/PTMWENaHKr/eDjSZt42n50MIKq64foZFaCZpDBsvNKmsNFS+5y1vtFesms3S6pY8 kkJuVsTTjUsxUThDHkUE1GMKOTgEjnE0T2PY/5vw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 36/50] ipa: simple: Store exposure as `uint32_t` Date: Mon, 3 Aug 2026 15:14:21 +0200 Message-ID: <20260803131435.153927-37-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:14: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: 27586 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 B51BDC3345 for ; Mon, 3 Aug 2026 13:15:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D410E6810A; Mon, 3 Aug 2026 15:15:17 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ovYYLv6q"; 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 5242B680A9 for ; Mon, 3 Aug 2026 15:14:47 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6E92A512 for ; Mon, 3 Aug 2026 15:13:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762818; bh=JnG6U3ReObdvdyhBhEGao+Rl2y+v4KH1tXn9bxTK8yA=; h=From:To:Subject:Date:In-Reply-To:References:From; b=ovYYLv6qyyB+MSYzJFygHt4GpEXp30BA3wjM8F3XJTOx9rBFcD6FF09c6e+eLZdaP 6/ldIwaIM0ydmW2cvmEmt/bbvZKZ5jYFbhwucMsHE93JSpwS3u5kk9SWYRz5oPh8u0 5I5zp3o4+kOEmDbXWmtNyPUI8fv4cSconP6d197c= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 37/50] ipa: simple: agc: Separate MSV calculation Date: Mon, 3 Aug 2026 15:14:22 +0200 Message-ID: <20260803131435.153927-38-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:14: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: 27587 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 3CEF3C3346 for ; Mon, 3 Aug 2026 13:15:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 269D7680E6; Mon, 3 Aug 2026 15:15:19 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AuDVcWrS"; 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 83AB5680A8 for ; Mon, 3 Aug 2026 15:14:47 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9DAB25B3 for ; Mon, 3 Aug 2026 15:13:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762818; bh=3VJsg23vpRZkimnUndQSKOVsX62mssI5yD3j+TV5wQ0=; h=From:To:Subject:Date:In-Reply-To:References:From; b=AuDVcWrS62cDpjbQkMchF5H/XDZ0J4IHeHdWo6NFzvm3jTsgiJpg0kc5nPQQqKD/e wQkYK1yh5WzbocxfPkb+AcQLkaLnbA/j7cUKATkxSqGRcTPwYEY1lhG7YWUNV9RTQp rK/Ridzu7Dj+lqrXI93TtZsq0QzDF1bFDVzf8Cw0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 38/50] ipa: simple: agc: Simplify min gain step handling Date: Mon, 3 Aug 2026 15:14:23 +0200 Message-ID: <20260803131435.153927-39-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:14: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: 27588 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 C2D99C3347 for ; Mon, 3 Aug 2026 13:15:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A63D5680F6; Mon, 3 Aug 2026 15:15:19 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="K9cG57rH"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id ACA676807D for ; Mon, 3 Aug 2026 15:14:47 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CC432512 for ; Mon, 3 Aug 2026 15:13:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762818; bh=E985DDPrSQTUb2R+O+XZ6N1q8b6Z6AIwpAePZa0yVVk=; h=From:To:Subject:Date:In-Reply-To:References:From; b=K9cG57rHoeaQ5sy75Mpvlf78Vxg8YW9z/wuNneO4somMsE7RXjqm9UwTlBMNu5rQJ Pt2acPt5Q1qXfG2XE2O17lUpJOQN6KyccB5zjehAjMZ85fPEUis1fABZJTkBfjfzyr d8xkJA+fN1qiWHiI7sgD7G9AoNzKUbblZf+nFG5Q= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 39/50] ipa: simple: agc: Use `Histogram` Date: Mon, 3 Aug 2026 15:14:24 +0200 Message-ID: <20260803131435.153927-40-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:14: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: 27589 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 44272C3348 for ; Mon, 3 Aug 2026 13:15:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 49AF6680FC; Mon, 3 Aug 2026 15:15:20 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="M/5lBKz6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DBB5E680B4 for ; Mon, 3 Aug 2026 15:14:47 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 06E085B3 for ; Mon, 3 Aug 2026 15:13:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762819; bh=Ch7Eiq1lpvW3U4J8QDDus2uEA7uHeblo7NL8vJMXO1U=; h=From:To:Subject:Date:In-Reply-To:References:From; b=M/5lBKz6U37LiY7zSnbA3c6NrOfaPBd8HysWWwmhHcw7Usj7wcUzm9tnh8c/9Mo6N Vao76eea+reNTgbNZ8Py20aY1MLpcR/uwBAhGd+kzMAr2pat+Mx7XpvkQmt0Hg+X2h prS/b3Pv2t8lp9gdJS3qlvN9Dju/mYuGZefepiyw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 40/50] ipa: simple: agc: Adjust histogram for black level Date: Mon, 3 Aug 2026 15:14:25 +0200 Message-ID: <20260803131435.153927-41-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:14: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: 27591 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 48325C334A for ; Mon, 3 Aug 2026 13:15:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6BC04680CA; Mon, 3 Aug 2026 15:15:21 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nJ4EOrZX"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1E2B168072 for ; Mon, 3 Aug 2026 15:14:48 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 377E6512 for ; Mon, 3 Aug 2026 15:13:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762819; bh=lNNzOgGhrkEv51Bw/xKoFPbnCAEmMF1Eb6AmodeUOVM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=nJ4EOrZXLsVqgdl36jmM6bgkXk4fdco63Jv9HJif1Um86arPpIrdP/iQE4GYnUURv CnI+u529GVogVJy78ppRaste5DI7v88QrugolP/I0W76VGIvnv9PZMMfTYRR9wsaM0 yruNJkXrhGI2yCMIH3cSv1g8obtefIDHypr0iFB8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 41/50] ipa: simple: agc: Move to libipa Date: Mon, 3 Aug 2026 15:14:26 +0200 Message-ID: <20260803131435.153927-42-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:14:27 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: 27590 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 CA628C3349 for ; Mon, 3 Aug 2026 13:15:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D737A6810C; Mon, 3 Aug 2026 15:15:20 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="izXHkm7m"; 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 522B0680BB for ; Mon, 3 Aug 2026 15:14:48 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6BC585B3 for ; Mon, 3 Aug 2026 15:13:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762819; bh=tqxwRekX1pe5lTqv8KeMnWE5PYwZfpJxr4s+icm4tOo=; h=From:To:Subject:Date:In-Reply-To:References:From; b=izXHkm7m6h5h9/vDIvRW6KkJPEPZ9I+FUDxg8OH2QA9vrYJRHiwY5LC1c21OaHTjs 5He43n61mj+vCYOLyF51r+f5kuhRTEaH9MhYzG2XpUWYLf1AbLkCytydb5ssivWKVT XwpWQZW6mIiEeX6QBmLr3oevC7ptE6oEVTkupDP4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 42/50] ipa: libipa: agc_msv: Ensure limits are always respected Date: Mon, 3 Aug 2026 15:14:27 +0200 Message-ID: <20260803131435.153927-43-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:14:28 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: 27596 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 8CA44C334E for ; Mon, 3 Aug 2026 13:15:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 467A968102; Mon, 3 Aug 2026 15:15:24 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="uk0b3NzN"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7C66D68080 for ; Mon, 3 Aug 2026 15:14:48 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9DADD512 for ; Mon, 3 Aug 2026 15:13:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762819; bh=us5b72jVS9CEihNUNUbHeWP3SVXlyNl+8YqisAEAHEU=; h=From:To:Subject:Date:In-Reply-To:References:From; b=uk0b3NzNfhFLy+HMY4p9E4ARucw88Ao0poqg7Bn9XaobfEwSvLQMXkeksJLUsP3Ql BM8C6dBBpDXUKD6PyLhhDVwGN1/52pkYem2qzq6aNp/WZxcApTklqwQFjj4lWQv0UB FURQVT8OS24GX+8erDazz0dzMzBQHIyq5QvEuA68= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 43/50] ipa: libipa: agc_msv: Handle min gain > 1.0 case Date: Mon, 3 Aug 2026 15:14:28 +0200 Message-ID: <20260803131435.153927-44-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:14:29 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: 27595 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 1B1BFC328C for ; Mon, 3 Aug 2026 13:15:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A5A85680F0; Mon, 3 Aug 2026 15:15:23 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="tSOrFxXO"; 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 B1187680BA for ; Mon, 3 Aug 2026 15:14:48 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CE02D981 for ; Mon, 3 Aug 2026 15:13:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762819; bh=WrBlfneeoKPFe1wqh6QNu0tcSBpaKix0eDIGnf41kDE=; h=From:To:Subject:Date:In-Reply-To:References:From; b=tSOrFxXOWg0X8D9InHcST47S7KRExvy7BPyTjHCckA619u2/08ny+HLEP7rF9WZyn q6Jnc6Z8zE/LldCTaCnSg02Ic/OCZzI5n2VyQqnVNA4hsrPnA3l7u3D0lQytwJqQjo JBu7fQfXfLbTHuI1dWy7p8BcvZI4aPKFg3ms7toQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 44/50] ipa: simple: Move sensor helper and controls to context Date: Mon, 3 Aug 2026 15:14:29 +0200 Message-ID: <20260803131435.153927-45-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:14:30 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: 27592 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 C754DC334B for ; Mon, 3 Aug 2026 13:15:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0390E68112; Mon, 3 Aug 2026 15:15:22 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UDgB/cFx"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E49B46807C for ; Mon, 3 Aug 2026 15:14:48 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 05DB4512 for ; Mon, 3 Aug 2026 15:13:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762820; bh=vRgrIZrtaPKRBBGdq3Bnp2egkRCiS3SDlWQUkx3rZbU=; h=From:To:Subject:Date:In-Reply-To:References:From; b=UDgB/cFxc2xOIlu8plztRKwoR3UWKPOWvYo455XBSyBQpbFTMdTeF7SuqKL3/Qx+B /f2vTXfDdKuhpxpoDPx0FvRKDto8PN72BtSkw3sYCyzhu/l1WBv2KUDzNLGtHbbpVf kLw52nks5gkwO78jtk4gvG353KVr1sFfxgvzEASc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 45/50] ipa: simple: Update controls after configuration Date: Mon, 3 Aug 2026 15:14:30 +0200 Message-ID: <20260803131435.153927-46-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 Mon Aug 3 13:14:31 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: 27593 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 60571C334C for ; Mon, 3 Aug 2026 13:15:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 86BFC68114; Mon, 3 Aug 2026 15:15:22 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="niedp9VD"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 29101680B6 for ; Mon, 3 Aug 2026 15:14:49 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3CAB05B3 for ; Mon, 3 Aug 2026 15:13:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762820; bh=uVwY8ebloj2wewVG1SXsYBxf63jtyTvh3CVAWVzZ5Ac=; h=From:To:Subject:Date:In-Reply-To:References:From; b=niedp9VDb6uJWzJUTttel/GJV8k9z5x1y+qhZdU5qMjYau1rh2vsd1w1V0a8fytYf ql1btYZi3/d1GROWHka/VixpnQPoivOIc/O6EJO94tEqvnyvcnmOdGHUGo2qhGzwWp An9bLIq9IVsSnPiHc2+rg3bQqaDjhBDxzjS2q5IM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 46/50] ipa: libipa: agc: Work without `CameraSensorHelper` Date: Mon, 3 Aug 2026 15:14:31 +0200 Message-ID: <20260803131435.153927-47-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 | 197 +++++++++++++++++++--------- src/ipa/libipa/agc.h | 9 +- src/ipa/mali-c55/algorithms/agc.cpp | 4 +- src/ipa/rkisp1/algorithms/agc.cpp | 4 +- 5 files changed, 142 insertions(+), 76 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index 91923c7f70..a79898520c 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 3efa4a89e1..b8b4cc9e83 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 @@ -50,6 +52,9 @@ LOG_DEFINE_CATEGORY(Agc) * \var agc::Session::maxAnalogueGain * \brief Maximum analogue gain for the streaming session * + * \var agc::Session::defAnalogueGain + * \brief Default analogue gain of the configured sensor + * * \var agc::Session::minFrameDuration * \brief Minimum frame duration for the streaming session * @@ -188,9 +193,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 Current configuration of the sensor * @@ -235,11 +237,18 @@ LOG_DEFINE_CATEGORY(Agc) /** * \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; } @@ -270,10 +279,14 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, int32_t defExposure = v4l2Exposure.def().get(); /* 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()); LOG(Agc, Debug) << "exposure:[" << minExposure << ',' << maxExposure << ']' @@ -312,28 +325,21 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, session.maxExposureTime = maxExposure * session.lineDuration; session.minAnalogueGain = minGain; session.maxAnalogueGain = maxGain; + session.defAnalogueGain = defGain; session.minFrameDuration = std::chrono::microseconds(frameDurations[0]); session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]); - impl_.configure(session.lineDuration, config.sensor); - impl_.resetFrameCount(); - /* Configure the default exposure and gain. */ state = {}; state.automatic.gain = session.minAnalogueGain; state.automatic.exposure = defExposure; state.automatic.quantizationGain = 1; state.automatic.digitalGain = 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; @@ -379,25 +385,59 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, Span{ { frameDurations[0], frameDurations[1] } }, }; - 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.yTarget = (2.5 - 1) / (5 - 1); /* \todo hack? */ + + 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.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); @@ -599,33 +639,62 @@ 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), - }); - - /* Update the estimated exposure and gain. */ - state.automatic.exposure = newEv.exposureTime / lineDuration; - state.automatic.gain = newEv.analogueGain; - state.automatic.quantizationGain = newEv.quantizationGain; - state.automatic.digitalGain = newEv.digitalGain; - state.automatic.yTarget = newEv.yTarget; + 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; + }, + [&](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), + }); + + /* Update the estimated exposure and gain. */ + state.automatic.exposure = newEv.exposureTime / lineDuration; + state.automatic.gain = newEv.analogueGain; + state.automatic.quantizationGain = newEv.quantizationGain; + state.automatic.digitalGain = newEv.digitalGain; + state.automatic.yTarget = newEv.yTarget; + }, + }, impl_); LOG(Agc, Debug) << "exposure-time:" << utils::Duration(state.automatic.exposure * lineDuration) diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index e96c6926b2..4f8038ae41 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" @@ -53,6 +55,7 @@ struct Session { utils::Duration maxExposureTime; double minAnalogueGain; double maxAnalogueGain; + double defAnalogueGain; utils::Duration minFrameDuration; utils::Duration maxFrameDuration; @@ -111,14 +114,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); @@ -143,7 +145,8 @@ public: 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 87586e8a82..c869cc0235 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -122,12 +122,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, @@ -147,7 +146,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 06e0646981..c4de8d4bab 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 Mon Aug 3 13:14:32 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: 27594 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 B98B9C334D for ; Mon, 3 Aug 2026 13:15:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 167CE680FB; Mon, 3 Aug 2026 15:15:23 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="PXDznkks"; 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 5AD11680B8 for ; Mon, 3 Aug 2026 15:14:49 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 79AF0512 for ; Mon, 3 Aug 2026 15:13:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762820; bh=oON34GAosbFdFIswtIKfVIKOx9fKKj1IlHX+JRinKf4=; h=From:To:Subject:Date:In-Reply-To:References:From; b=PXDznkksI+4G3CVFMGF61YkGcVkQb/MlobUTUZ5YPEky8oMp4nrNq7a5K0/o76P4E ptBWLaj6bNo1vWjDN15Ve7vx3q15MdUy48P2slVv0xyzDNdwfuCMmUZzwPx90nirwb Tq/j6n4P9f5uUsnUloYTEBj1rbeooOVFeIDvbrJA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 47/50] ipa: libipa: agc: Take parameters from active state for calculation Date: Mon, 3 Aug 2026 15:14:32 +0200 Message-ID: <20260803131435.153927-48-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 `frameContext` is of an already completed frame, its exposure/gain/etc is of no real concern for future frames, so take the most recent settings from the active state. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index b8b4cc9e83..bb6891cf51 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -621,22 +621,22 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, double minAnalogueGain; double maxAnalogueGain; - if (frameContext.autoExposureEnabled) { + if (state.autoExposureEnabled) { minExposureTime = session.minExposureTime; - maxExposureTime = std::clamp(frameContext.maxFrameDuration, + maxExposureTime = std::clamp(state.maxFrameDuration, session.minExposureTime, session.maxExposureTime); } else { - minExposureTime = lineDuration * frameContext.exposure; + minExposureTime = lineDuration * state.manual.exposure; maxExposureTime = minExposureTime; } - if (frameContext.autoGainEnabled) { + if (state.autoGainEnabled) { minAnalogueGain = session.minAnalogueGain; maxAnalogueGain = session.maxAnalogueGain; } else { - minAnalogueGain = frameContext.gain; - maxAnalogueGain = frameContext.gain; + minAnalogueGain = state.manual.gain; + maxAnalogueGain = state.manual.gain; } std::visit(utils::overloaded{ @@ -681,10 +681,10 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, .traits = params->traits, .yHist = params->yHist, .effectiveExposureValue = effectiveExposureValue, - .constraintModeIndex = frameContext.constraintMode, - .exposureModeIndex = frameContext.exposureMode, + .constraintModeIndex = state.constraintMode, + .exposureModeIndex = state.exposureMode, .lux = params->lux, - .exposureCompensation = pow(2.0, frameContext.exposureValue), + .exposureCompensation = pow(2.0, state.exposureValue), }); /* Update the estimated exposure and gain. */ From patchwork Mon Aug 3 13:14:33 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: 27599 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 19CE5C3351 for ; Mon, 3 Aug 2026 13:15:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2D33968106; Mon, 3 Aug 2026 15:15:26 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Po1Wj//N"; 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 8D52F680BD for ; Mon, 3 Aug 2026 15:14:49 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AAD9F5B3 for ; Mon, 3 Aug 2026 15:13:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762820; bh=xUB/TqYock30WMOlUMbVvtBtQQx3fpBMYAhEQATA+2w=; h=From:To:Subject:Date:In-Reply-To:References:From; b=Po1Wj//NhScwIXzT9r27BkEVZRwWCfpploqXbovFDCw7L1m8Mq0W/6khUMpAv5RNH U1yxMTI0vlLZYgT1s3ENCkSWQG81MFlpgssvpIQ9WgukM7aJEct+hTeK9ugeRTYKQ2 1xbd0WLdh6toVTQZxM3CllD9T7lxT/O550jpW0Aw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 48/50] ipa: simple: agc: Port to `AgcAlgorithm` Date: Mon, 3 Aug 2026 15:14:33 +0200 Message-ID: <20260803131435.153927-49-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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/libipa/agc.cpp | 14 ++-- 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 +----------- 5 files changed, 99 insertions(+), 131 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index bb6891cf51..169a53f2cd 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -707,14 +707,12 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, 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::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); diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index 7b980c7382..9926b3c593 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.configuration.agc, 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; From patchwork Mon Aug 3 13:14:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27598 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 785B7C3350 for ; Mon, 3 Aug 2026 13:15:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 771286811E; Mon, 3 Aug 2026 15:15:25 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Nz9QHrYe"; 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 BF39E68075 for ; Mon, 3 Aug 2026 15:14:49 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DDC51512 for ; Mon, 3 Aug 2026 15:13:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762820; bh=UQb6u+AzrJyQBlWloxXjUlpt0xqzbCYGmOJsyP74WEg=; h=From:To:Subject:Date:In-Reply-To:References:From; b=Nz9QHrYegXgY0btiinIQ61+hP48KZxZhVAuyHYvAbZftH4C4+6irlCCGkBzNxe8WS QkE5DmYHqCS9zpgjO5rTI0ogYcsxYTZ0EpccJAou/MTjQcWwt32kT6+uqIeGHJ6+SQ Agb7DO6+hw+PyPQdgbo7erZ4hh1agqGfCq+z4ABc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 49/50] libcamera: pipeline: Set vblank on more platforms Date: Mon, 3 Aug 2026 15:14:34 +0200 Message-ID: <20260803131435.153927-50-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-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 common `AgcAlgorithm` provides the desired vblank value the given frame, so adjust `agc::prepareControls()` accordingly, and propagate the vblank value to the sensors in the pipeline handlers. Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/ipu3.cpp | 5 +- src/ipa/libipa/agc.h | 48 +++++++++++--------- src/ipa/mali-c55/mali-c55.cpp | 2 +- src/ipa/rkisp1/rkisp1.cpp | 3 +- src/ipa/simple/soft_simple.cpp | 2 +- src/libcamera/pipeline/ipu3/ipu3.cpp | 1 + src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1 + src/libcamera/pipeline/simple/simple.cpp | 1 + 8 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp index 0935050a33..7db7270480 100644 --- a/src/ipa/ipu3/ipu3.cpp +++ b/src/ipa/ipu3/ipu3.cpp @@ -551,10 +551,7 @@ void IPAIPU3::setControls(unsigned int frame) IPAFrameContext &frameContext = context_.frameContexts.get(frame); ControlList ctrls(context_.sensorControls); - agc::prepareControls( - ctrls, context_.camHelper.get(), - frameContext.agc.exposure, frameContext.agc.gain - ); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); ControlList lensCtrls(lensCtrls_); lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index 4f8038ae41..97153541fc 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -29,27 +29,6 @@ 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)); -} - struct Session { utils::Duration minExposureTime; utils::Duration maxExposureTime; @@ -108,6 +87,33 @@ struct FrameContext { bool autoGainModeChange; }; + +[[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, + const FrameContext &frameContext) +{ + uint32_t gain = sensor + ? sensor->gainCode(frameContext.gain) + : uint32_t(frameContext.gain); + + controls.set(V4L2_CID_EXPOSURE, int32_t(frameContext.exposure)); + controls.set(V4L2_CID_ANALOGUE_GAIN, int32_t(gain)); + controls.set(V4L2_CID_VBLANK, int32_t(frameContext.vblank)); +} + } /* namespace agc */ class AgcAlgorithm diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index c0c2c83dca..160f7ac88a 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -133,7 +133,7 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig void IPAMaliC55::setControls(const IPAFrameContext &frameContext) { ControlList ctrls(context_.sensorControls); - agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc.exposure, frameContext.agc.gain); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); setSensorControls.emit(ctrls); } diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 6425d8f1e8..033e69f9f0 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -367,8 +367,7 @@ void IPARkISP1::setControls(unsigned int frame) << ", gain " << frameContext.agc.gain << ", vblank " << vblank; ControlList ctrls(context_.sensorControls); - agc::prepareControls(ctrls, context_.camHelper.get(), exposure, frameContext.agc.gain); - ctrls.set(V4L2_CID_VBLANK, static_cast(vblank)); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); setSensorControls.emit(frame, ctrls); } diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index f35e269deb..5d1e0deb87 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -249,7 +249,7 @@ void IPASoftSimple::processStats(const uint32_t frame, metadataReady.emit(frame, metadata); ControlList ctrls(context_.sensorControls); - agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc.exposure, frameContext.agc.gain); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); setSensorControls.emit(ctrls); } diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp index 0f3e169d3b..4147f9475a 100644 --- a/src/libcamera/pipeline/ipu3/ipu3.cpp +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp @@ -1084,6 +1084,7 @@ int PipelineHandlerIPU3::registerCameras() std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + { V4L2_CID_VBLANK, { delays.vblankDelay, true } }, }; data->delayedCtrls_ = diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp index 599ff88b59..ef156d421b 100644 --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -1909,6 +1909,7 @@ bool PipelineHandlerMaliC55::registerMemoryInputCamera(MediaLink *link) std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + { V4L2_CID_VBLANK, { delays.vblankDelay, true } }, }; data->delayedCtrls_ = diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index 3917aa897e..968aab6740 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -569,6 +569,7 @@ SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe, std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + { V4L2_CID_VBLANK, { delays.vblankDelay, true } }, }; delayedCtrls_ = std::make_unique(sensor_->device(), params); From patchwork Mon Aug 3 13:14:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27597 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 15888C334F for ; Mon, 3 Aug 2026 13:15:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E0C736811C; Mon, 3 Aug 2026 15:15:24 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="aKX40sNB"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0E5ED680BF for ; Mon, 3 Aug 2026 15:14:50 +0200 (CEST) Received: from pb-laptop.local (185.221.141.208.nat.pool.zt.hu [185.221.141.208]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1A9095B3; Mon, 3 Aug 2026 15:13:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785762821; bh=kacR/suTA4055UNYcQaPB3NQZqnpB8DfktUn8fW/LX0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=aKX40sNBh7XhKGOdoEE68rkU8AvRfofAROR/AhqOYbFtFJVIV66ngvVE6AK6Q0fVa tVi8bdss41hBtGpDlPVIOdIhW94iiQMJVfUClzf89uXPhRTmBi6Y3grHvOsjj0e6D0 GwNSGlVaND6ivtpXiLBML9PGPXusNWTdFy/h/rFU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug , Paul Elder Subject: [RFC PATCH v3 50/50] ipa: libipa: agc_mean_luminance: Fold resetFrameCount() in configure() Date: Mon, 3 Aug 2026 15:14:35 +0200 Message-ID: <20260803131435.153927-51-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> References: <20260803131435.153927-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Jacopo Mondi The AgcMeanLuminance::resetFrameCount() function has to be called after a call to AgcMeanLuminance::configure(). As the two calls always happen one after another, do not require each IPA implementation to do that but fold instead the call to resetFrameCount() in AgcMeanLuminance::configure(). Update the AgcMeanLuminance class documentation accordingly. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze Reviewed-by: Stefan Klug Reviewed-by: Paul Elder --- src/ipa/libipa/agc.cpp | 1 - src/ipa/libipa/agc_mean_luminance.cpp | 21 ++++++--------------- src/ipa/libipa/agc_mean_luminance.h | 5 ----- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 169a53f2cd..cd10bd45b6 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -413,7 +413,6 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, ASSERT(sensor_); impl.configure(session.lineDuration, sensor_); - impl.resetFrameCount(); if (session.autoAllowed) { config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 8303065008..2459f8b44a 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -169,11 +169,11 @@ static constexpr unsigned int kDefaultLuxLevel = 500; * * IPA modules that want to use this class to implement their AEGC algorithm * should derive AgcMeanLuminance::Traits and override the necessary functions. - * The users must call parseTuningData() in init(), and must also call setLimits() - * and resetFrameCounter() in configure(). They may then use calculateNewEv() in - * process(). If the limits passed to setLimits() change for any reason (for example, - * in response to a FrameDurationLimit control being passed in queueRequest()) - * then setLimits() must be called again with the new values. + * The users must call parseTuningData() in init(), and must also call configure() + * and setLimits() in configure(). They may then use calculateNewEv() in process(). + * If the limits passed to setLimits() change for any reason (for example, in + * response to a FrameDurationLimit control being passed in queueRequest()) then + * setLimits() must be called again with the new values. */ AgcMeanLuminance::AgcMeanLuminance() @@ -354,6 +354,7 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration, luxWarningEnabled_ = true; filteredExposure_ = 0s; + frameCount_ = 0; } /** @@ -699,16 +700,6 @@ AgcMeanLuminance::calculateNewEv(const Params ¶ms) return { exposureModeHelper.splitExposure(newExposureValue), yTarget }; } -/** - * \fn AgcMeanLuminance::resetFrameCount() - * \brief Reset the frame counter - * - * This function resets the internal frame counter, which exists to help the - * algorithm decide whether it should respond instantly or not. The expectation - * is for users to call this function before each camera start call in their - * configure() function. - */ - } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index 46a4d3e3f3..f799618ffc 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -79,11 +79,6 @@ public: double effectiveYTarget(double lux, double exposureCompensation) const; - void resetFrameCount() - { - frameCount_ = 0; - } - private: int parseRelativeLuminanceTarget(const ValueNode &tuningData); int parseConstraint(const ValueNode &modeDict, int32_t id);