From patchwork Mon Aug 24 09:13: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: 28033 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 2D3BAC3339 for ; Mon, 24 Aug 2026 09:14:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 795E6683CC; Mon, 24 Aug 2026 11:14:14 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fuBTJtkt"; 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 4687868397 for ; Mon, 24 Aug 2026 11:14:11 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DDCA3C24; Mon, 24 Aug 2026 11:12:46 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562767; bh=2MQGXWTPPm4hf9TZjiUNduy/MQG4GgtlPSo5lKBnmx0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fuBTJtktyCz9MHwRWSq6RqXVkffMoLQ0xyHJdIHOKeYiGIP79r/XvEf+oG/1LdIJl OBWgxHsF7Vyb9JqAdgmXoRAmBN3TyE6NzSddOMhIqgkm+EMt7j/wdaZW8F6mrI2V5k T6bJB4nXh2XkJ2It3WKOv0Hc9pZBDLV3wI42zdOk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Jacopo Mondi Subject: [PATCH v8 01/47] libcamera: software_isp: init(): Fix documentation typo Date: Mon, 24 Aug 2026 11:13:20 +0200 Message-ID: <20260824091407.502020-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Milan Zamazal Reviewed-by: Jacopo Mondi --- 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 ae86c20a88..bcb7e1c8fb 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 24 09:13: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: 28034 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 8FE90C3339 for ; Mon, 24 Aug 2026 09:14:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 75EA3683F4; Mon, 24 Aug 2026 11:14:15 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UdMWHC8d"; 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 739AF683A5 for ; Mon, 24 Aug 2026 11:14:11 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 239F9C25; Mon, 24 Aug 2026 11:12:47 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562767; bh=ElEaI4fVujys9WYkp2b6S2VBNKfGPV94E1Pdkj4WZxM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UdMWHC8dhtw7bc9uQN/WXHTp9zH9B7IXtQn36xxrozYTdsc0KhlbiymHLd8xYgcWE 7c9MI003pruBCnjMk7KrO9OvoE7I5g2r9GZS4BksI07ioFISe7UwFxuKf1dbPAr0FW hqmEUReAVxDRaQAXQuZEJgKWZ7bXHCJ65JhqOg4Y= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Jacopo Mondi Subject: [PATCH v8 02/47] ipa: softisp: Remove control presence sanity check Date: Mon, 24 Aug 2026 11:13:21 +0200 Message-ID: <20260824091407.502020-3-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Milan Zamazal Reviewed-by: Jacopo Mondi --- src/ipa/softisp/softisp.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp index aec04c528c..f1a04bf53e 100644 --- a/src/ipa/softisp/softisp.cpp +++ b/src/ipa/softisp/softisp.cpp @@ -311,13 +311,6 @@ void IPASoftIsp::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(IPASoftIsp, Error) << "Control(s) missing"; - return; - } - ControlList ctrls(sensorInfoMap_); auto &againNew = frameContext.sensor.gain; From patchwork Mon Aug 24 09:13: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: 28035 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 6B7AEC3339 for ; Mon, 24 Aug 2026 09:14:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9A9EB68401; Mon, 24 Aug 2026 11:14:18 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="jxfA3ykv"; 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 AF876683AE for ; Mon, 24 Aug 2026 11:14:11 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5D3E3C23; Mon, 24 Aug 2026 11:12:47 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562767; bh=cdroYqvOBqJ+E4/65+UNcd9WnF8q/c4HGPXs5v7XnxM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jxfA3ykv/h69DfABJgpUXzyENYfsjm/IkkXLe2OuZmkZmE+RMf4meZ2dvZZkNzU8U JLwRETER/lnsikutxZthJdWT84tkdTHem4HMhfh3dCNk5F85TYfLhA+1BB29/wiWBl MFKUEWcwReYU0xYHeItdl67/8r6Q8VMZ5ufHDZGY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi , Milan Zamazal Subject: [PATCH v8 03/47] ipa: softisp: agc: Do not overwrite sensor exposure/gain Date: Mon, 24 Aug 2026 11:13:22 +0200 Message-ID: <20260824091407.502020-4-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Milan Zamazal --- src/ipa/softisp/algorithms/agc.cpp | 11 +++++++---- src/ipa/softisp/ipa_context.h | 5 +++++ src/ipa/softisp/softisp.cpp | 9 +++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/ipa/softisp/algorithms/agc.cpp b/src/ipa/softisp/algorithms/agc.cpp index 63b41544c3..f12ab977ec 100644 --- a/src/ipa/softisp/algorithms/agc.cpp +++ b/src/ipa/softisp/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/softisp/ipa_context.h b/src/ipa/softisp/ipa_context.h index 2eed32f88c..8934871648 100644 --- a/src/ipa/softisp/ipa_context.h +++ b/src/ipa/softisp/ipa_context.h @@ -67,6 +67,11 @@ struct IPAFrameContext : public FrameContext { ipa::awb::FrameContext awb; ipa::ccm::FrameContext ccm; + struct { + int32_t exposure; + double gain; + } agc; + struct { int32_t exposure; double gain; diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp index f1a04bf53e..967605038a 100644 --- a/src/ipa/softisp/softisp.cpp +++ b/src/ipa/softisp/softisp.cpp @@ -313,10 +313,11 @@ void IPASoftIsp::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 24 09:13: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: 28036 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 5615DC3339 for ; Mon, 24 Aug 2026 09:14:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DDD626840F; Mon, 24 Aug 2026 11:14:20 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="kPqA/iVZ"; 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 EC468683A7 for ; Mon, 24 Aug 2026 11:14:11 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A0759C2D; Mon, 24 Aug 2026 11:12:47 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562767; bh=DPHQsqIbILbMHlycUd+Ry8qgfeI177qPWjf9Uz6O2P8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kPqA/iVZ5otP4xhIab3wD+qAQiMjeER36Iwova2d/cXW342yB2kAFdo9j6lqrDMiw KFkW39Sv1EjIj6Kq43C+yjitOFSt5e+L3SkvTWVhcGrvKL7/TVhefkjl3mV7TlP8IN fcnEHugg295zAGriXMHvOHq13nqkeVymBx93HTIs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v8 04/47] ipa: rkisp1: Remove `IPASessionConfiguration::sensor.defVBlank` Date: Mon, 24 Aug 2026 11:13:23 +0200 Message-ID: <20260824091407.502020-5-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 7b4346c54e..2a8a5b3b55 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -73,7 +73,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 e0dde542c7..1f43c9da29 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 24 09:13: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: 28037 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 E1B8DC3339 for ; Mon, 24 Aug 2026 09:14:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 98F3368416; Mon, 24 Aug 2026 11:14:22 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="K15JKi/B"; 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 2F30D68397 for ; Mon, 24 Aug 2026 11:14:12 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DCD8FC31; Mon, 24 Aug 2026 11:12:47 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562768; bh=WL4FZblSSHIcYHU1p2QZAT36AmxLKL7IFZgwVmLdVaM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=K15JKi/BIGzOayLme+LIcR+0iGZixf5gFpDVRFAnNOAA3+fl0uDCeG/OZ5JXVbS5v x7TfPVF4xBIvaJyWcAjgaOfxcVCcjCf1NmA9SshheC/xY0ZA8K3/iH6vWPqdESLGT3 2GkxzrykzXqIJhQXVlnjJDUq8GfnqpD2Z+DaKB/Q= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v8 05/47] ipa: rkisp1: Move sensor control info map to context Date: Mon, 24 Aug 2026 11:13:24 +0200 Message-ID: <20260824091407.502020-6-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 2a8a5b3b55..cd213dd991 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -233,6 +233,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 1f43c9da29..570f16c8cc 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 24 09:13: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: 28039 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 E9D65C3339 for ; Mon, 24 Aug 2026 09:14:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1ED4768405; Mon, 24 Aug 2026 11:14:25 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="lsQFp+GY"; 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 A79C6683CE for ; Mon, 24 Aug 2026 11:14:12 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 21CF3C34; Mon, 24 Aug 2026 11:12:48 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562768; bh=kwwxp5oJgHhCSzy+iP/F6qE2LpCrQFEZJpynYcbzbeY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lsQFp+GYN5ykm5W+SOMR9/jouF8pTgUIKLN0SKW5su/ZnoaBUYRten8tHyvFmgYOY rl3NDKSO/9MoOqInez/Qk2L+whHSB3opzz0gWbVhpiueKGG+farK8ck8rvyjDI9n4T RX2UemYnHc6mpH33/TnqqnxgDIM+qnZCcjs5iNZs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v8 06/47] ipa: rkisp1: Update sensor info when configuring Date: Mon, 24 Aug 2026 11:13:25 +0200 Message-ID: <20260824091407.502020-7-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 570f16c8cc..4a9d5c86fa 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 24 09:13: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: 28038 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 6A06BC3339 for ; Mon, 24 Aug 2026 09:14:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B7A646841B; Mon, 24 Aug 2026 11:14:23 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rK6/+Yf4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E7BE9683D2 for ; Mon, 24 Aug 2026 11:14:12 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8DDBEC3B; Mon, 24 Aug 2026 11:12:48 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562768; bh=a+4VooOtiMuOc8kzggl8oVpztHKWdE7bIGk1bvQ8sP0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rK6/+Yf49JE9fb/NK7rH+q57JuLGLYHrr1Ap/LHhAu5McxzqeP9FOavecTa2qApUj 9lrO430Y38oE1ojNRtAM3/3JUsvfIuVo+6hdXceV9o+RVe+jH9CYd8/db8XMmVqJs+ zjEVqKCUikyOdD59iOCLdwRpb3bxEiFPH4O8M0ys= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v8 07/47] ipa: rkisp1: updateControls(): Use sensor info from context Date: Mon, 24 Aug 2026 11:13:26 +0200 Message-ID: <20260824091407.502020-8-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 4a9d5c86fa..a4d80c8dc6 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 24 09:13: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: 28040 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 9A84FC3339 for ; Mon, 24 Aug 2026 09:14:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BD00668422; Mon, 24 Aug 2026 11:14:26 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="oss7xwTK"; 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 2428D683D5 for ; Mon, 24 Aug 2026 11:14:13 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C84D6C3C; Mon, 24 Aug 2026 11:12:48 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562768; bh=KgFbTyDL7nqwBEJagIwnP8AlQU2JNbG8H2wVgN9XH6U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oss7xwTKEKSOhBevNdd61HzFOSpynMC/zRdxe11r/LDRp+6Hy+rQtY/+XQUaNm7DL WXykCwAgw2mSLtWnIhruRElokl+193QAhzQ8W923k3k25WPsTVTTHusreDNJtG2h/p B95lQIWtzvsRd9LvoNqXXVVUlTScNP7Rn/7ljnjA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v8 08/47] ipa: rkisp1: Move AGC related controls into AGC algorithm Date: Mon, 24 Aug 2026 11:13:27 +0200 Message-ID: <20260824091407.502020-9-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 | 92 ++++++++++++++++++++++++++++++- src/ipa/rkisp1/rkisp1.cpp | 92 +------------------------------ 2 files changed, 92 insertions(+), 92 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index aace46a8d0..1167480321 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -8,6 +8,7 @@ #include "agc.h" #include +#include #include #include #include @@ -33,13 +34,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 +244,8 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); context.ctrlMap.merge(controls()); + reconfigure(context); + return 0; } @@ -172,6 +258,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 a4d80c8dc6..38e773fe56 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -6,8 +6,6 @@ */ #include -#include -#include #include #include @@ -39,8 +37,6 @@ namespace libcamera { LOG_DEFINE_CATEGORY(IPARkISP1) -using namespace std::literals::chrono_literals; - namespace ipa::rkisp1 { /* Maximum number of frame contexts to be held */ @@ -169,9 +165,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 +220,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 +228,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 +249,8 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, return ret; } + updateControls(ipaControls); + return 0; } @@ -386,56 +348,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 24 09:13: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: 28041 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 A8C1EC3339 for ; Mon, 24 Aug 2026 09:14:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 466DD68426; Mon, 24 Aug 2026 11:14:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="EATBKKx0"; 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 64D4F683AF for ; Mon, 24 Aug 2026 11:14:13 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0C8B0C3E; Mon, 24 Aug 2026 11:12:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562769; bh=+QS/t1F88hmCxgedyFM8WI1JwZatA+y31A4N+gC0yxQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EATBKKx0fEwER3l6SguVpmFYmGJ7Hnv6/OlaeCfmqcWYWuikKrKJUXPq6hydSAK4t Wkq7+ewEaZy6ACArmKMlWfJ4qaPYQI0a6jaMRy9S5c3PNpZ9QphjafFTx1DfVQsudm Hjgw8njnG1gLE0C7ytKA1Vf2HOO2FNvzm/WHSQeM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v8 09/47] ipa: libipa: agc_mean_luminance: Remove the need for inheritance Date: Mon, 24 Aug 2026 11:13:28 +0200 Message-ID: <20260824091407.502020-10-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 1167480321..d162cb80d6 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -222,7 +222,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = parseTuningData(tuningData); + ret = agc_.parseTuningData(tuningData); if (ret) return ret; @@ -242,7 +242,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()); reconfigure(context); @@ -272,9 +272,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); @@ -288,17 +288,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; } @@ -545,47 +544,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 @@ -607,6 +565,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 @@ -647,13 +663,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. @@ -686,8 +695,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 @@ -705,15 +714,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" @@ -725,7 +742,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. @@ -734,7 +751,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 24 09:13: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: 28042 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 E7CBEC3339 for ; Mon, 24 Aug 2026 09:14:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CB8A76838A; Mon, 24 Aug 2026 11:14:29 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="sgY5tV8+"; 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 AB769683DC for ; Mon, 24 Aug 2026 11:14:13 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 522BDC42; Mon, 24 Aug 2026 11:12:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562769; bh=6+GENnXnyBA1eUeo8nqwgo5dyGwygtOKvwhJiHrJojQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=sgY5tV8+smuDtK2K2oQIZ8FBeHZzj7WXTrXc1g8TnvDFcB+iDoeaQ2zaZxERErwl3 PX35KgmmQnCuTb+jYhA0DSNzF7TIC1GqC1xoee4QJGZvKLMuoAE9h2r6jBrfXHffR3 84FSUictWquD4N0uJklf1EcJLvQvtIYkGhprZ0zE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v8 10/47] ipa: mali-c55: Remove `DigitalGain` support Date: Mon, 24 Aug 2026 11:13:29 +0200 Message-ID: <20260824091407.502020-11-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Jacopo Mondi --- 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 7017dfc87c..5d1a0e1256 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -46,12 +46,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; @@ -68,7 +66,6 @@ struct IPAFrameContext : public FrameContext { struct { uint32_t exposure; double sensorGain; - UQ<5, 8> ispGain; } agc; ipa::awb::FrameContext awb; From patchwork Mon Aug 24 09:13: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: 28044 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 61F35C3339 for ; Mon, 24 Aug 2026 09:14:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 414676842B; Mon, 24 Aug 2026 11:14:33 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="O+i0NRn3"; 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 D16F4683E2 for ; Mon, 24 Aug 2026 11:14:13 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 89BBEC2D; Mon, 24 Aug 2026 11:12:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562769; bh=CG/5lGhLw89u2JlsGZ1cB76EsUf32sm1lUlfTOYofpk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=O+i0NRn3FJRP6FGX6k1363N0ViUR34/qmVNcomb6uqaCjeUPHLgqPNwZ6bzmVYXhl fuxPVq7dWD335UFT742mJGVFKL3l/wxISHL3PHsLYLRLyedaxRzTJ77jNBbOTU+Bjr 6uzi2vkzdBNtP9G2J0ZnkA3xUGDpqVN/Q7m7hC3s= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 11/47] ipa: libipa: agc_mean_luminance: Avoid some duplicate lookups Date: Mon, 24 Aug 2026 11:13:30 +0200 Message-ID: <20260824091407.502020-12-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Stefan Klug --- 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 24 09:13: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: 28043 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 5A177C3339 for ; Mon, 24 Aug 2026 09:14:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9467868405; Mon, 24 Aug 2026 11:14:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="lZ2eQmZf"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1E56C6838A for ; Mon, 24 Aug 2026 11:14:14 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C1DCEC24; Mon, 24 Aug 2026 11:12:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562769; bh=JHBxhySzYed1Tefok8BGSg3jffXWY8+00+iPw/yBxn8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lZ2eQmZfX9C7VYURzJE6rHSJmdS2jMJ9SuxQg/RKtKqgnqmIm9CeU3Aolk7WMpZo/ RoByFY8x5HfTArfXokF5i/WDSQjy366Wrp9DoVHA/HA0rRNWTd/+gPb/P4FqvyYceW zTc8eVJbUU3n2mJI0kRLGorckyQkFuYl1W+SVdaQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 12/47] ipa: libipa: agc_mean_luminance: Remove unnecessary `std::shared_ptr` Date: Mon, 24 Aug 2026 11:13:31 +0200 Message-ID: <20260824091407.502020-13-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Stefan Klug --- 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 24 09:13: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: 28045 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 8DD46C3339 for ; Mon, 24 Aug 2026 09:14:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DB4CA68429; Mon, 24 Aug 2026 11:14:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gHUTJSJF"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4E6EB683B2 for ; Mon, 24 Aug 2026 11:14:14 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 06C63C44; Mon, 24 Aug 2026 11:12:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562770; bh=EWO3yH5Wr5l5MIGbuF6jUNvwGZZ/osKrkI/aO5ijz6I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gHUTJSJF6F2tArRWT1GJLI7kpZfx+yAF5oy73ZXNNjQGlvYyEkFtIYtfkk+LKdLq9 aim20u6G4huAsQtZw3VtefQWL4GodEO/W29QatNBuwWmzGLHe9+gUQ9mr/nSHbJSpJ 7pbFIFLFk0hEygl8yvLT2sBjqQTGg14q2Pt4/xMU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 13/47] ipa: libipa: agc_mean_luminance: calculateNewEv(): Fix typo Date: Mon, 24 Aug 2026 11:13:32 +0200 Message-ID: <20260824091407.502020-14-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Stefan Klug --- 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 24 09:13: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: 28046 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 E5D08C3339 for ; Mon, 24 Aug 2026 09:14:37 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 84949683E9; Mon, 24 Aug 2026 11:14:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="kqBnfDIz"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 89255683E9 for ; Mon, 24 Aug 2026 11:14:14 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3DFAFC2D; Mon, 24 Aug 2026 11:12:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562770; bh=dUJLdhzN8Pbs6co92rLa0df0zb0AbOdKrq2HNa1yGPY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kqBnfDIzqo1vpM4Y+f4RwwILqEgwQe0ReWhFZ0Zm5Aha8bsOYqrZTOi0fDClCEAed JSqWfZRm1LZsBEHeF011Zv9MCyr09ex18d8/8VCCsnNsXg1s3jFt9JD6pZScmTEnO/ puq62SrS91Kneg/mxJyK4cnSvKVew70t5fe17Kss= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 14/47] ipa: libipa: agc_mean_luminance: calculateNewEv(): Collect params Date: Mon, 24 Aug 2026 11:13:33 +0200 Message-ID: <20260824091407.502020-15-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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. The function already has a number of arguments, and more will be added. Having a separate type for them allows the use of designated initializers, which helps avoid mixing up the values, especially since most of them are of simple arithmetic types. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Stefan Klug --- 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 d162cb80d6..058ec422b7 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -717,20 +717,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 24 09:13: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: 28047 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 298E2C3339 for ; Mon, 24 Aug 2026 09:14:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7584F68432; Mon, 24 Aug 2026 11:14:38 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="LPUHj661"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C518C683EA for ; Mon, 24 Aug 2026 11:14:14 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 79B60C24; Mon, 24 Aug 2026 11:12:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562770; bh=wSsdxALsxwTbBHeFFH9DcGwJFKcjvdZfp/tiG6XWT7A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=LPUHj661OPsY+OgYXYblO09ZgvvQaO5irHhe0O5tGQQ6JDFUQsc4gFDdfJf0DTUk+ mWO5m8sWSs1diNSdhMccmtxP505eLLoTuAEI1jURqS9FKlva0PDT4hQViuoC7YDCPs H6uX5WDdVUZWkPY11o8aExONUoJq7bW4z40B+LHw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 15/47] ipa: libipa: agc_mean_luminance: constraintClampGain(): Mark `const` Date: Mon, 24 Aug 2026 11:13:34 +0200 Message-ID: <20260824091407.502020-16-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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`. This also necessitates the use of `at()` for the map lookup, so use it just like it is already done in `calculateNewEv()`. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Stefan Klug --- 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 24 09:13: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: 28048 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 9A920C3339 for ; Mon, 24 Aug 2026 09:14:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 20794683F9; Mon, 24 Aug 2026 11:14:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cVJErhIw"; 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 0EFFD683F2 for ; Mon, 24 Aug 2026 11:14:15 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B59F3C25; Mon, 24 Aug 2026 11:12:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562770; bh=eCKpUbI/jNpqigOzjRA8RKRAWqw7iwedNDa0PPon8To=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cVJErhIwAJ75ylSUntLhz1zM3VIdoEYnahtiXgAcniE72ldLEL0hcvB1v38I+3NcV 1iRw0dFil7rSwYbMp2sJ3Ibpr489HVVizPa9uzd7ZUbNdWDPZpxkoEc4lrzif3id3C J4a1DikD9fE/vVLsI0fYKwsGy/8w3qb46i4x6B+E= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 16/47] ipa: libipa: agc_mean_luminance: calculateNewEv(): Collect results Date: Mon, 24 Aug 2026 11:13:35 +0200 Message-ID: <20260824091407.502020-17-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Jacopo Mondi Reviewed-by: Stefan Klug --- 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 058ec422b7..64c5c04695 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -721,9 +721,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), @@ -736,21 +734,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 24 09:13:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28050 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 2C903C3339 for ; Mon, 24 Aug 2026 09:14:44 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id ABD5C6843A; Mon, 24 Aug 2026 11: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="tszm8pCO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 51880683B6 for ; Mon, 24 Aug 2026 11:14:15 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F08BCC31; Mon, 24 Aug 2026 11:12:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562771; bh=Cb00OlbGh9yzRPcwcwg+AGJ/d1UFt53h1LETdJdiCYo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tszm8pCOcOJ74+JwJKs4ARWa+pI+DEvv3Qb1HBwJMpakE+PA0H5dFIBBSinmpqR+P sj644Zp/huq1fUxPMzl54MIxeWiwHjEdDZA2e47K/hBTd1FQts2aq9Q12fnyjsQXrz m0mjJiWuxwqreYI+qEJPw7BjKo6w2UBLYg+nFsjk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 17/47] ipa: libipa: agc_mean_luminance: calculateNewEv(): Return y target Date: Mon, 24 Aug 2026 11:13:36 +0200 Message-ID: <20260824091407.502020-18-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Stefan Klug --- 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 64c5c04695..5349b83544 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -742,7 +742,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 24 09:13:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28049 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 9DFB3C3339 for ; Mon, 24 Aug 2026 09:14:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3CE51683C2; Mon, 24 Aug 2026 11: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="G3gCkqWo"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id ACD6D683F5 for ; Mon, 24 Aug 2026 11:14:15 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3D12BC24; Mon, 24 Aug 2026 11:12:51 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562771; bh=teeAjDeIZ5jQAf413T5wyBAw9tTrOPPrhf9lB7tqlnU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=G3gCkqWoTj3lbO8i5m9Anw47rw5+Jnn4bPTB3qeAn77dUiwllvr+fFC3J9QIvg9MY i/Nf+3RNn1yBFesjLUdAAJFUZXmY4UYNRGKonkdazwTF4AjjNPE9Qqboli/QsH9cAj iEyHA/rogviTZ9znnhrMz4N9XM3cIIWIh1ZUSK98= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 18/47] ipa: libipa: agc_mean_luminance: Remove persistent params Date: Mon, 24 Aug 2026 11:13:37 +0200 Message-ID: <20260824091407.502020-19-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Jacopo Mondi Reviewed-by: Stefan Klug --- 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 5349b83544..7230232d24 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -295,7 +295,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(); @@ -718,9 +718,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 }, @@ -730,6 +727,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 24 09:13:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28052 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 1ABE2C3339 for ; Mon, 24 Aug 2026 09:14:47 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8FEDB6843D; Mon, 24 Aug 2026 11: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="tmTXPF7f"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EB068683F7 for ; Mon, 24 Aug 2026 11:14:15 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 94782C44; Mon, 24 Aug 2026 11:12:51 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562771; bh=pZqAcFLeRaZf+8/2JGYDuk2WMrQd03WVsvFpRsjiJao=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tmTXPF7flAeLTRnjdyc52z5S8VV4N7XfJWYbaPyAPVWExIUvZtSX/2vHg72tiSgno 6R24SDSG9rwi5F7wjAB80LXYhK1hXrTI1ZF4yvOQxrDzCBedKTTCfXokFmDmmKsy2Q FLm8b2qrnZbX3TmMGG9sH7KLjz0puP2iOx3IFAyE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 19/47] ipa: libipa: agc_mean_luminance: Reset `filteredExposure_` when configuring Date: Mon, 24 Aug 2026 11:13:38 +0200 Message-ID: <20260824091407.502020-20-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Stefan Klug --- 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 24 09:13:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28051 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 930DCC3339 for ; Mon, 24 Aug 2026 09:14:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 22C0B68438; Mon, 24 Aug 2026 11:14:45 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gIaxxmYq"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3C7E2683F9 for ; Mon, 24 Aug 2026 11:14:16 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DD6C8C23; Mon, 24 Aug 2026 11:12:51 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562772; bh=+a6J9LP9f+VXY5fPFI2h52n0V3yd/pSuFL90a+UHdzA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gIaxxmYq5cNLtWtOMynZOia4vCaR2Mv2Gg0puzNbtW/mfU27ImOFmqJrpmO2qdb9Y 8Uhmcd/hv7ROpxJgZvSG/duUZwTuUaGkr7Ad1xOAx4xtacunH3IPL+RdW9v/lwJe5A Mpv8GeoBLmkTAtyCw/h1CCme7dR0KlnfG+aakRN8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 20/47] ipa: Simplify sensor exposure/gain setting/getting Date: Mon, 24 Aug 2026 11:13:39 +0200 Message-ID: <20260824091407.502020-21-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Acked-by: Jacopo Mondi Reviewed-by: Stefan Klug --- src/ipa/ipu3/ipu3.cpp | 13 ++++----- src/ipa/libipa/agc.cpp | 51 +++++++++++++++++++++++++++++++++ src/ipa/libipa/agc.h | 53 +++++++++++++++++++++++++++++++++++ src/ipa/libipa/meson.build | 2 ++ src/ipa/mali-c55/mali-c55.cpp | 18 ++++++------ src/ipa/rkisp1/rkisp1.cpp | 12 ++++---- src/ipa/softisp/softisp.cpp | 16 ++++------- 7 files changed, 130 insertions(+), 35 deletions(-) create mode 100644 src/ipa/libipa/agc.cpp create mode 100644 src/ipa/libipa/agc.h diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp index 42d475ecc6..4b4e524d90 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,10 @@ 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.cpp b/src/ipa/libipa/agc.cpp new file mode 100644 index 0000000000..864b73d5f1 --- /dev/null +++ b/src/ipa/libipa/agc.cpp @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas On Board + * + * Auto exposure/gain algorithm for implementing the IPA-specific AGC algorithms + */ + +#include "agc.h" + +/** + * \file agc.h + * \brief libipa AGC algorithm + */ + +namespace libcamera { + +namespace ipa { + +namespace agc { + +/** + * \fn extractControls(const ControlList &controls, const CameraSensorHelper *sensor) + * \param[in] controls The controls list to extract from + * \param[in] sensor The CameraSensorHelper + * + * This function extracts \a V4L2_CID_EXPOSURE and \a V4L2_CID_ANALOGUE_GAIN + * from \a controls and then returns the exposure and gain values. The gain + * code is mapped to the real gain value if \a sensor is provided, otherwise + * the gain code is returned. + * + * \return A pair of exposure and analogue gain extracted from \a controls + */ + +/** + * \fn prepareControls(ControlList &controls, const CameraSensorHelper *sensor, + * uint32_t exposure, double gain) + * \param[out] controls The controls list to extract from + * \param[in] sensor The CameraSensorHelper + * \param[in] exposure The exposure (in lines) + * \param[in] gain The analogue gain + * + * This function sets \a V4L2_CID_EXPOSURE and \a V4L2_CID_ANALOGUE_GAIN + * in \a controls. The gain is mapped to the gain code if \a sensor is provided, + * otherwise the gain value will be used directly. + */ + +} /* namespace agc */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h new file mode 100644 index 0000000000..5a67464086 --- /dev/null +++ b/src/ipa/libipa/agc.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * Auto exposure/gain algorithm for implementing the IPA-specific AGC algorithms + */ + +#pragma once + +#include +#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 { + static_cast(exposure), + sensor ? sensor->gain(gainCode) : gainCode, + }; +} + +inline void +prepareControls(ControlList &controls, const CameraSensorHelper *sensor, + uint32_t exposure, double gain) +{ + controls.set(V4L2_CID_EXPOSURE, static_cast(exposure)); + controls.set(V4L2_CID_ANALOGUE_GAIN, + static_cast(sensor + ? sensor->gainCode(gain) + : static_cast(gain))); +} + +} /* namespace agc */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index cbfa7cd409..fa49abcd55 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', @@ -27,6 +28,7 @@ libipa_headers = files([ ]) libipa_sources = files([ + 'agc.cpp', 'agc_mean_luminance.cpp', 'algorithm.cpp', 'awb_bayes.cpp', diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 37350eb28f..7696fae665 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" @@ -143,20 +144,19 @@ 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); } @@ -354,10 +354,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 38e773fe56..79ab7338c1 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -29,6 +29,7 @@ #include "libcamera/internal/yaml_parser.h" #include "algorithms/algorithm.h" +#include "libipa/agc.h" #include "ipa_context.h" #include "params.h" @@ -324,10 +325,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); @@ -361,7 +360,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) @@ -369,8 +367,8 @@ 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/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp index 967605038a..ec0d0f0489 100644 --- a/src/ipa/softisp/softisp.cpp +++ b/src/ipa/softisp/softisp.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 IPASoftIsp::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,8 @@ void IPASoftIsp::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 24 09:13:40 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28053 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 7E5CFC3339 for ; Mon, 24 Aug 2026 09:14:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 12E3068426; Mon, 24 Aug 2026 11: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="R37+XvwI"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C97B0683CB for ; Mon, 24 Aug 2026 11:14:16 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2F2F0C25 for ; Mon, 24 Aug 2026 11:12:52 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562772; bh=FEZVIj3G/YuhVqscv5ktK31HdcNC4+xbtjlc7YS/8IA=; h=From:To:Subject:Date:In-Reply-To:References:From; b=R37+XvwIDIhjQYqHLgjmZqw9H6juJMjszArdiLfkM97BLklhCzzzuvu829lI6rzbr VxG/9AK0ZZKKAurlA4pA2ynA4LSRBD3cjOr9VEwvSqSzyJFFZZii28fxra9LqofeFV Oa93zyjPHfZNdupfC7psGWgjGUJg6n6iwU5/2VKs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v8 21/47] ipa: libipa: Add `AgcAlgorithm` Date: Mon, 24 Aug 2026 11:13:40 +0200 Message-ID: <20260824091407.502020-22-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 the following main adjustments: * the parameters for `process()` have been made optional to handle the cases where statistics are not available; * the "raw" capture check has been replaced with the "autoAllowed" session parameter; * the controls are only provided after `configure()`. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 729 +++++++++++++++++++++++++++++- src/ipa/libipa/agc.h | 111 +++++ src/ipa/rkisp1/algorithms/agc.cpp | 461 +++---------------- 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 +- 7 files changed, 896 insertions(+), 562 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 864b73d5f1..e311bfd0d6 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -1,21 +1,41 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* - * Copyright (C) 2026 Ideas On Board + * Copyright (C) 2021-2026 Ideas On Board * - * Auto exposure/gain algorithm for implementing the IPA-specific AGC algorithms + * libIPA Agc algorithm */ #include "agc.h" +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include + /** * \file agc.h * \brief libipa AGC algorithm */ - namespace libcamera { namespace ipa { +using namespace std::chrono_literals; + +LOG_DEFINE_CATEGORY(Agc) + namespace agc { /** @@ -44,8 +64,711 @@ namespace agc { * otherwise the gain value will be used directly. */ +/** + * \struct Session + * \brief Session configuration for AgcAlgorithm + * + * \var Session::minExposureTime + * \brief Minimum exposure time for the streaming session + * + * \var Session::maxExposureTime + * \brief Maximum exposure time for the streaming session + * + * \var Session::minAnalogueGain + * \brief Minimum analogue gain for the streaming session + * + * \var Session::maxAnalogueGain + * \brief Maximum analogue gain for the streaming session + * + * \var Session::minFrameDuration + * \brief Minimum frame duration for the streaming session + * + * \var Session::maxFrameDuration + * \brief Maximum frame duration for the streaming session + * + * \var Session::lineDuration + * \brief Line duration for the streaming session + * + * \var Session::sensor + * \brief Details of the sensor configuration + * + * \var Session::sensor.outputSize + * \brief Configured output size of the sensor + * + * \var Session::autoAllowed + * \copybrief AgcAlgorithm::ConfigurationParams::autoAllowed + * \sa AgcAlgorithm::ConfigurationParams::autoAllowed + */ + +/** + * \struct 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 ActiveState::manual + * \brief Manual exposure time and analog gain (set through requests) + * + * \var ActiveState::manual.exposure + * \brief Manual exposure time expressed as a number of lines as set by the + * ExposureTime control + * + * \var ActiveState::manual.gain + * \brief Manual analogue gain as set by the AnalogueGain control + * + * \var ActiveState::automatic + * \brief Automatic exposure time and analog gain (computed by the algorithm) + * + * \var ActiveState::automatic.exposure + * \brief Automatic exposure time expressed as a number of lines + * + * \var ActiveState::automatic.gain + * \brief Automatic analogue gain multiplier + * + * \var ActiveState::automatic.quantizationGain + * \brief Automatic quantization gain multiplier + * + * \var ActiveState::automatic.yTarget + * \brief Automatically determined luminance target + * + * \var ActiveState::autoExposureEnabled + * \brief Whether automatic exposure control is enabled by the ExposureTimeMode control + * + * \var ActiveState::autoGainEnabled + * \brief Whether automatic gain control is enabled by the AnalogueGainMode control + * + * \var ActiveState::exposureValue + * \brief Exposure value as set by the ExposureValue control + * + * \var ActiveState::constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var ActiveState::exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var ActiveState::minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var ActiveState::maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + */ + +/** + * \struct FrameContext + * \brief Per-frame context for AgcAlgorithm + * + * \var FrameContext::exposure + * \brief Exposure time expressed as a number of lines computed by the algorithm + * + * \var FrameContext::gain + * \brief Analogue gain multiplier computed by the algorithm + * + * The gain should be translated to the sensor specific gain code before applying. + * + * \var FrameContext::quantizationGain + * \brief Quantization gain multiplier computed by the algorithm + * + * \var FrameContext::exposureValue + * \brief Exposure value as set by the ExposureValue control + * + * \var FrameContext::yTarget + * \brief Luminance target computed by the algorithm + * + * \var FrameContext::vblank + * \brief Vertical blanking parameter computed by the algorithm + * + * \var FrameContext::autoExposureEnabled + * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control + * + * \var FrameContext::autoGainEnabled + * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control + * + * \var FrameContext::constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var FrameContext::exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var FrameContext::minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var FrameContext::maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + * + * \var FrameContext::frameDuration + * \brief The actual FrameDuration used by the algorithm for the frame + * + * \var 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 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 + */ + } /* namespace agc */ +/** + * \class AgcAlgorithm + * \brief libIPA LSC algorithm algorithm + * + * The AgcAlgorithm class can be used to implement automatic exposure/gain + * control in an IPA module, conforming to the prescribed Algorithm interface. + * Internally AgcMeanLuminance is used, this class merely concerns itself with + * processing the sensor properties, establishing limits, managing controls, + * providing the resulting metadata, and driving the actual algorithm in + * process(). + * + * Users should compose the agc::Session, agc::ActiveState, and agc::FrameContext + * structures into their session configuration, active state, and frame contexts, + * respectively. Furthermore, in their implementation of the Algorithm virtual + * function, they should simply call the identically named member function of + * AgcAlgorithm. + * + * \todo DigitalGain, DigitalGainMode + * \todo Expand documentation + */ + +/** + * \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 and configure + * \param[in] tuningData The tuning data + * \param[in] config The algorithm configuration + * + * This function loads the tuning data and configures the algorithm as if + * by a call to configure(), in order to provide the available controls + * in ConfigurationParams::ctrlMap. + * + * The tuning data format is that of AgcMeanLuminance. Refer to + * AgcMeanLuminance::parseTuningData() for more details. + * + * \return 0 on success, or a negative error code + * + * \sa Algorithm::init() + */ +int AgcAlgorithm::init(const ValueNode &tuningData, const ConfigurationParams &config) +{ + int ret = impl_.parseTuningData(tuningData); + if (ret) + return ret; + + /* + * The purpose of this `configure()` is merely to provide the + * available controls in `config.ctrlMap`. + * + * \todo Remove it once IPA modules have been changed to + * configure the algorithms during initialization. + */ + + agc::Session dummySession; + agc::ActiveState dummyState; + + return configure(dummySession, dummyState, config); +} + +/** + * \brief Initialize the session configuration and active state + * \param[in] session The agc session configuration + * \param[in] state The agc active state + * \param[in] config The algorithm configuration + * + * This function initializes \a session and \a state based on the tuning + * data loaded by init() and the configuration in \a config. + * + * It also updates ConfigurationParams::ctrlMap with the limits of the various + * available agc-related controls. Users are expected to propagate these controls + * to the camera. + * + * \return 0 on success, or a negative error code + * + * \sa Algorithm::configure() + */ +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; + + /* \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] } }, + }; + config.ctrlMap[&controls::ExposureTimeMode] = ControlInfo{ + {{ controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual }}, + controls::ExposureTimeModeAuto, + }; + config.ctrlMap[&controls::AnalogueGainMode] = ControlInfo{ + {{ controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual }}, + controls::AnalogueGainModeAuto, + }; + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); + config.ctrlMap.merge(impl_.controls()); + + return 0; +} + +/** + * \brief Queue a request + * \param[in] session The agc session configuration + * \param[in] state The agc active state + * \param[in] frameContext The agc frame context + * \param[in] controls The list of controls associated with a Request + * + * This functions processes the agc-related controls in \a controls for the frame + * denoted by \a frameContext, and updates \a state and \a frameContext accordingly. + * + * \sa Algorithm::queueRequest() + */ +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 Prepare a frame + * \param[in] state The agc active state + * \param[in] frameContext The agc frame context + * + * This function prepares the parameters for the frame denoted by \a frameContext. + * After a call to this function, the values of \ref agc::FrameContext::exposure + * "frameContext.exposure" and \ref agc::FrameContext::gain "frameContext.gain" + * will be finalized and may be used by the caller (see agc::prepareControls()). + * + * \todo Finalize \ref agc::FrameContext::vblank "frameContext.vblank" as well + * + * \sa Algorithm::prepare() + */ +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 Process frame statistics + * \param[in] session The agc session configuration + * \param[in] state The agc active state + * \param[in] frameContext The agc frame context + * \param[in] params The algorithm parameters + * \param[in] metadata The list of metadata + * + * This function processes the statistics for the completed frame denoted by + * \a frameContext, runs the AGC implementation, and updates \a state appropriately. + * This function also populates \a metadata for the completed frame. + * + * \a params must be omitted if the session was configured without "autoAllowed", + * and it may be omitted even if auto control is enabled, for example, if the + * statistics could not be delivered due to some ephemeral error. This ensures + * that the algorithm state will not go out of sync, and that metadata is produced + * as expected. + * + * Care must be taken to convert the platform specific statistics to the format + * expected in \a params. See ProcessParams for the details. + * + * \sa Algorithm::process() + */ +void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext, std::optional &¶ms, + ControlList &metadata) +{ + if (!params) { + processFrameDuration(session, frameContext, frameContext.minFrameDuration); + fillMetadata(session, frameContext, metadata); + return; + } + + ASSERT(session.autoAllowed); + + const utils::Duration &lineDuration = session.lineDuration; + + /* + * 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; + + /* \todo This uses the configuration from an already completed frame. */ + + 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 = std::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; + + LOG(Agc, Debug) + << "Divided up exposure time, analogue gain, quantization gain" + << " and digital gain are " << newEv.exposureTime + << ", " << 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. + */ + processFrameDuration(session, frameContext, + std::max(frameContext.minFrameDuration, newEv.exposureTime)); + + fillMetadata(session, frameContext, metadata); +} + +/** + * \brief Process frame duration and compute vblank + * \param[in] session The session parameters + * \param[in] frameContext The current frame context + * \param[in] frameDuration The target frame duration + * + * Compute and populate vblank from the target frame duration. + */ +void AgcAlgorithm::processFrameDuration(const agc::Session &session, + agc::FrameContext &frameContext, + utils::Duration frameDuration) +{ + const utils::Duration &lineDuration = session.lineDuration; + + 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; +} + +void AgcAlgorithm::fillMetadata(const agc::Session &session, + const agc::FrameContext &frameContext, + ControlList &metadata) +{ + + metadata.set(controls::AnalogueGain, frameContext.gain); + metadata.set(controls::ExposureTime, + utils::Duration(session.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 5a67464086..06600f0208 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -7,21 +7,87 @@ #pragma once +#include #include #include +#include #include +#include + +#include #include +#include +#include "agc_mean_luminance.h" #include "camera_sensor_helper.h" namespace libcamera { +struct IPACameraSensorInfo; + namespace ipa { +class Histogram; + namespace agc { +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; +}; + [[nodiscard]] inline std::pair extractControls(const ControlList &controls, const CameraSensorHelper *sensor) @@ -48,6 +114,51 @@ prepareControls(ControlList &controls, const CameraSensorHelper *sensor, } /* namespace agc */ +class AgcAlgorithm +{ +public: + struct ConfigurationParams { + const CameraSensorHelper *sensor; + const IPACameraSensorInfo &sensorInfo; + const ControlInfoMap &sensorControls; + ControlInfoMap::Map &ctrlMap; + bool autoAllowed = true; + }; + + struct ProcessParams { + const AgcMeanLuminance::Traits &traits; + const Histogram &yHist; + uint32_t exposure; + double gain; + std::vector &&additionalConstraints = {}; + double lux = 0; + }; + + int init(const ValueNode &tuningData, const ConfigurationParams &config); + + 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); + + void process(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext, std::optional &¶ms, + ControlList &metadata); + +private: + void processFrameDuration(const agc::Session &session, + agc::FrameContext &frameContext, + utils::Duration frameDuration); + void fillMetadata(const agc::Session &session, + const agc::FrameContext &frameContext, + ControlList &metadata); + + AgcMeanLuminance impl_; +}; + } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 7230232d24..bb0345ca87 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -8,10 +8,7 @@ #include "agc.h" #include -#include -#include #include -#include #include #include @@ -36,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 @@ -222,7 +136,12 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.parseTuningData(tuningData); + ret = agc_.init(tuningData, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); if (ret) return ret; @@ -231,21 +150,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; } @@ -258,47 +162,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; } @@ -312,73 +193,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) { @@ -387,42 +202,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; } /** @@ -431,41 +210,13 @@ void Agc::queueRequest(IPAContext &context, void Agc::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) { - uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure; - double activeAutoGain = context.activeState.agc.automatic.gain; - double activeAutoQGain = context.activeState.agc.automatic.quantizationGain; - - /* Populate exposure and gain in auto mode */ - if (frameContext.agc.autoExposureEnabled) { - frameContext.agc.exposure = activeAutoExposure; - frameContext.agc.quantizationGain = activeAutoQGain; - } - if (frameContext.agc.autoGainEnabled) { - frameContext.agc.gain = activeAutoGain; - frameContext.agc.quantizationGain = activeAutoQGain; - } - - /* - * Populate manual exposure and gain from the active auto values when - * transitioning from auto to manual - */ - if (!frameContext.agc.autoExposureEnabled && frameContext.agc.autoExposureModeChange) { - context.activeState.agc.manual.exposure = activeAutoExposure; - frameContext.agc.exposure = activeAutoExposure; - } - if (!frameContext.agc.autoGainEnabled && frameContext.agc.autoGainModeChange) { - context.activeState.agc.manual.gain = activeAutoGain; - frameContext.agc.gain = activeAutoGain; - frameContext.agc.quantizationGain = activeAutoQGain; - } + agc_.prepare(context.activeState.agc, frameContext.agc); if (context.configuration.compress.supported) { frameContext.compress.enable = true; frameContext.compress.gain = frameContext.agc.quantizationGain; } - frameContext.agc.yTarget = context.activeState.agc.automatic.yTarget; - if (frame > 0 && !frameContext.agc.updateMetering) return; @@ -521,50 +272,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 @@ -638,21 +345,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 @@ -661,95 +353,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 cd213dd991..cc07bb9462 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/awb.h" #include "libipa/camera_sensor_helper.h" #include "libipa/ccm.h" @@ -57,7 +57,7 @@ struct RKISP1AwbSession { }; struct IPASessionConfiguration { - struct { + struct Agc : agc::Session { struct rkisp1_cif_isp_window measureWindow; } agc; @@ -68,12 +68,6 @@ struct IPASessionConfiguration { } compress; struct { - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; - - utils::Duration lineDuration; Size size; } sensor; @@ -82,26 +76,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; ipa::awb::ActiveState awb; @@ -145,24 +121,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; ipa::awb::FrameContext awb; From patchwork Mon Aug 24 09:13:41 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28055 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 909EEC3339 for ; Mon, 24 Aug 2026 09:14:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0777E68432; Mon, 24 Aug 2026 11: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="k5Z2BQp4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0EADF683AE for ; Mon, 24 Aug 2026 11:14:17 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B250EC24; Mon, 24 Aug 2026 11:12:52 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562772; bh=E6ladgqeRI9CuDFnafrF9Rbqt7Gy1GuyPO1N/sZaMhM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=k5Z2BQp4nYmDIBqjRGINJNb7RKxVoKWZQ4FKztOq/6uvz1BAcsZTJ2QyNvdB4pR/i T8IMwUR/0hqZ/hCzSgTsbAWIIVY3mbOxYDz7F8IvOjOsd1qyif8NXhT/UEF7wl1zSw WWGzoY7OO8V1g3wtkIp6WY8uLhILcjr0uRrbDQ4Q= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 22/47] ipa: libipa: agc: Adjust controls based on "auto" availablity Date: Mon, 24 Aug 2026 11:13:41 +0200 Message-ID: <20260824091407.502020-23-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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" If the auto mode is not possible (e.g. without statistics in raw mode), then the controls should be adjusted to reflect that. Specifically, ensure that the `ExposureValue` control and the controls from `AgcMeanLuminance` are only present if the auto mode is enabled. Furthermore, only advertise `{AnalogueGain,ExposureTime}ModeAuto` conditionally. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Stefan Klug --- src/ipa/libipa/agc.cpp | 43 +++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index e311bfd0d6..98f8bf244b 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -427,6 +427,16 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, state.minFrameDuration = session.minFrameDuration; state.maxFrameDuration = session.maxFrameDuration; + /* + * The IPA control maps keep their states, so the removal is necessary. + * + * \todo Remove it once IPA modules have been changed to always use a new + * ControlInfoMap::Map during configuration. + */ + config.ctrlMap.erase(&controls::ExposureValue); + for (const auto &[id, _] : impl_.controls()) + config.ctrlMap.erase(id); + /* \todo Move this to the `Camera` class. */ config.ctrlMap[&controls::AeEnable] = ControlInfo{ false, session.autoAllowed, session.autoAllowed @@ -443,16 +453,31 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, frameDurations[0], frameDurations[1], Span{ { frameDurations[2], frameDurations[2] } }, }; - config.ctrlMap[&controls::ExposureTimeMode] = ControlInfo{ - {{ controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual }}, - controls::ExposureTimeModeAuto, - }; - config.ctrlMap[&controls::AnalogueGainMode] = ControlInfo{ - {{ controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual }}, - controls::AnalogueGainModeAuto, + + 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), + }; }; - config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); - config.ctrlMap.merge(impl_.controls()); + + add(controls::ExposureTimeMode, + controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual); + add(controls::AnalogueGainMode, + controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual); + + if (session.autoAllowed) { + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); + config.ctrlMap.merge(impl_.controls()); + } return 0; } From patchwork Mon Aug 24 09:13:42 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28056 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 C74CDC3339 for ; Mon, 24 Aug 2026 09:14:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7468968426; Mon, 24 Aug 2026 11: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="QOqk6KAg"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4A1E0683FA for ; Mon, 24 Aug 2026 11:14:17 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F079FC25; Mon, 24 Aug 2026 11:12:52 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562773; bh=/i5qA2+7GrHnHpQGBCxSiky2kvm53r0bisuWaIil8qw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QOqk6KAgkDWrv1c8IBaWLE/zoe9OC5fFbjXIrlJ2NwvvRuN0raf28Z28Ea1DjIi67 TrBmJ7m4P2vnJ8o8Y4u6/iJPrBusdsCZm3RO/AYb3qFLAV3VvT3oKULgeqRExuv4H7 R1fZIxO/23xTqNBUxmWPulWtxZGKpUBIDh2ykQ1g= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 23/47] ipa: libipa: agc: Omit `setLimits()` in `configure()` Date: Mon, 24 Aug 2026 11:13:42 +0200 Message-ID: <20260824091407.502020-24-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Jacopo Mondi Reviewed-by: Stefan Klug --- 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 98f8bf244b..87f96fa4a4 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -404,9 +404,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 24 09:13:43 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28054 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 0CDD8C3339 for ; Mon, 24 Aug 2026 09:14:50 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 97AE968438; Mon, 24 Aug 2026 11: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="Nmnhw4ol"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8B6D8683C3 for ; Mon, 24 Aug 2026 11:14:17 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 37078C31; Mon, 24 Aug 2026 11:12:53 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562773; bh=+cxF0KQJOkS12B70rwwbf75/y45TjqYWzTnbcw8HvRM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Nmnhw4oloo4mygNp8EKxEI7rU2li53UVWGjhcmAacZUILPFHYzVREI6xEkiKgBSG7 2PSW4Gajox5GITr2kxizsYqwnG+tZVo07v0A4FIlHF+SPGXOJF1uVAVGWhfg/qMHOA tDHwLI0bvYPekRkaSUaBxcQDcZfpwkzw98MxHRvU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 24/47] ipa: libipa: agc: Use default exposure Date: Mon, 24 Aug 2026 11:13:43 +0200 Message-ID: <20260824091407.502020-25-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Stefan Klug --- 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 87f96fa4a4..5eb1761211 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -409,7 +409,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 24 09:13:44 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28057 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 CFA68C333A for ; Mon, 24 Aug 2026 09:14:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2EA8468397; Mon, 24 Aug 2026 11: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="Ad0zOugn"; 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 CA2E3683C2 for ; Mon, 24 Aug 2026 11:14:17 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7A5D3C23; Mon, 24 Aug 2026 11:12:53 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562773; bh=d7+4SjlK8s2e7s7rimMmAKMBHNBWEZFlSjzYd+9KnWg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Ad0zOugnrOKyatbHqaVm4rfPNm1HZ0cZaqQG/GWQY1An5lMzTknFjVzxnaXLLUVwM U7GMs807DMKYSaHUsTzCXAXUCY7tq7SiR4KFcggdErcm3zDOZPtoAgd34DctoqvKuO Sxfd06nfNWTr7ztjwG4PyD3wDxtBfp3pMVZSO/58= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 25/47] ipa: libipa: agc: Use full range as default `FrameDurationLimits` Date: Mon, 24 Aug 2026 11:13:44 +0200 Message-ID: <20260824091407.502020-26-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Stefan Klug --- 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 5eb1761211..a9820067bd 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -448,7 +448,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] } }, }; const auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) { From patchwork Mon Aug 24 09:13:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28058 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 AC240C3339 for ; Mon, 24 Aug 2026 09:14:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id EADA0683FE; Mon, 24 Aug 2026 11: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="B3vyDqiv"; 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 04207683C6 for ; Mon, 24 Aug 2026 11:14:18 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B5FC1C34; Mon, 24 Aug 2026 11:12:53 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562773; bh=f1xguX7s+kNPupxdowWEbvn7XxYyVEq5I36B+pBM+vQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=B3vyDqivyizrb6RakuQPOlhtQKmJZyajOlADeI2kT6jlVIvDA31QEUngr99B9BKWF A7+4RrydV44KXP0GHAlGo1faBBWP1vw7vwk590WtUC1y+MaZjasp+JFbR8HZolwW4n guSZNbUwMRLGEshumlK/MqWnF7PeHT622bdmjJm4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v8 26/47] ipa: libipa: agc: Adjust debug messages slightly Date: Mon, 24 Aug 2026 11:13:45 +0200 Message-ID: <20260824091407.502020-27-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 a9820067bd..7ef7f2ecdf 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -363,8 +363,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. @@ -733,10 +735,10 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, state.automatic.yTarget = newEv.yTarget; LOG(Agc, Debug) - << "Divided up exposure time, analogue gain, quantization gain" - << " and digital gain are " << newEv.exposureTime - << ", " << state.automatic.gain << ", " << state.automatic.quantizationGain - << " and " << newEv.digitalGain; + << "exposure-time: " << newEv.exposureTime << ", " + << "analogue-gain: " << state.automatic.gain << ", " + << "quantization-gain: " << state.automatic.quantizationGain << ", " + << "digital-gain: " << newEv.digitalGain; /* * Expand the target frame duration so that we do not run faster than From patchwork Mon Aug 24 09: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: 28059 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 7C53FC333A for ; Mon, 24 Aug 2026 09:14:55 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AF0F56840E; Mon, 24 Aug 2026 11: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="ft5WL4Yb"; 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 422ED683A7 for ; Mon, 24 Aug 2026 11:14:18 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EBF6BC24; Mon, 24 Aug 2026 11:12:53 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562774; bh=EDe1ZtE1bZowxbON4T7XJa8VqSBUZImtc66y24KlkqM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ft5WL4Yb36PtCdvpPEHi/EljXlk03XxKyBajWO8HbEDzqTETgrHl8m8jODXhC11Jx uDVBsZfYfT5sbtGaDpW7izdb4ss0+lBocmmRePSU0K/fTBPszlhzX7Dy9WJ2qze2wO +0LHRtH6SbL5QhAwdmeUns7Gtp3eYAmM4xbLzbBk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 27/47] ipa: libipa: agc: Store digital gain Date: Mon, 24 Aug 2026 11:13:46 +0200 Message-ID: <20260824091407.502020-28-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Jacopo Mondi Reviewed-by: Stefan Klug --- 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 7ef7f2ecdf..fd0b3529ac 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -130,6 +130,9 @@ namespace agc { * \var ActiveState::automatic.quantizationGain * \brief Automatic quantization gain multiplier * + * \var ActiveState::automatic.digitalGain + * \brief Automatic digital gain multiplier + * * \var ActiveState::automatic.yTarget * \brief Automatically determined luminance target * @@ -413,6 +416,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; @@ -732,13 +736,14 @@ 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; LOG(Agc, Debug) << "exposure-time: " << newEv.exposureTime << ", " << "analogue-gain: " << state.automatic.gain << ", " << "quantization-gain: " << state.automatic.quantizationGain << ", " - << "digital-gain: " << newEv.digitalGain; + << "digital-gain: " << state.automatic.digitalGain; /* * Expand the target frame duration so that we do not run faster than diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index 06600f0208..7c5a7c3518 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -58,6 +58,7 @@ struct ActiveState { uint32_t exposure; double gain; double quantizationGain; + double digitalGain; double yTarget; } automatic; From patchwork Mon Aug 24 09: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: 28061 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 BD251C3339 for ; Mon, 24 Aug 2026 09:14:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4981068426; Mon, 24 Aug 2026 11: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="lL+dpySj"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 825DE683FD for ; Mon, 24 Aug 2026 11:14:18 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 34090C25; Mon, 24 Aug 2026 11:12:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562774; bh=MfrEGRVoLpJBrSSnsTyKzH8nDIZ3YT3BgN2W1HQK6+U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lL+dpySjqRNjPEWbHVGabW0ZUcbFWjmslF/6Mt9yn1m9XSFH8Xc3EwrqXz/DRA9MQ OTEqZ89ISV/ZIq64xKL1vO8RcFlTIHOu2PpXqT7lbyTNUEl9niZ1qDfb5t3xuzU8sA WCJIxRm+PtZGqQlBaCDM3fgqR2eYHqYC8u/90IPg= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 28/47] ipa: libipa: agc: Use minimum line length Date: Mon, 24 Aug 2026 11:13:47 +0200 Message-ID: <20260824091407.502020-29-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Stefan Klug --- 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 fd0b3529ac..299fa8cb0c 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -341,10 +341,11 @@ int AgcAlgorithm::init(const ValueNode &tuningData, const ConfigurationParams &c int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, const ConfigurationParams &config) { + const uint32_t 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(); @@ -377,9 +378,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 24 09: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: 28060 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 528D5C333B for ; Mon, 24 Aug 2026 09:14:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AC14968453; Mon, 24 Aug 2026 11: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="ih1ojA+Y"; 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 BD49D68402 for ; Mon, 24 Aug 2026 11:14:18 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6C0F9C3B; Mon, 24 Aug 2026 11:12:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562774; bh=F5FqJ1SI9nsydifSotge15aw/ALJoVJV3JsBGh4sjjY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ih1ojA+YFbnOfAWvytVFzQ3SVVSLIjKB/85PQxhtz0Dk17C+MNrECVD88VapLmRgi nqAstewdP6tP+t0gOX2Pd5XwS8LMKiZlUxhb0dYMQT31RIp0JekXQ2MwubMSxOX+Xy 3ti/9vQAmKOFYexKw/uvcpsT8XpirmSflD6M/+mo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 29/47] ipa: libipa: agc: Fix multiplication type Date: Mon, 24 Aug 2026 11:13:48 +0200 Message-ID: <20260824091407.502020-30-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Jacopo Mondi Reviewed-by: Stefan Klug --- 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 299fa8cb0c..e056644752 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -388,8 +388,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 = static_cast(lineLength) * frameHeights[i]; + frameDurations[i] = frameSize * 1000000U / config.sensorInfo.pixelRate; } /* From patchwork Mon Aug 24 09: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: 28062 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 0E3A8C3339 for ; Mon, 24 Aug 2026 09:14:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 958826843F; Mon, 24 Aug 2026 11: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="MJo6klHD"; 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 1A34568406 for ; Mon, 24 Aug 2026 11:14:19 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id ACD8FC45; Mon, 24 Aug 2026 11:12:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562774; bh=4AyfpSA1OCYKWah3TOC4c59Z6Zk0eto9kF2Hjnmg6hw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=MJo6klHDmVBUwRrhdXYKeWpWLfz9Mji5kZCcb8uoNlQ86AN2rL4s+nIaqP2eeZOT4 OR2Ro+RunFojg2lwUnEoml3CyUsQukKRurvu6EklC/psFFgQhfh9tSJOkCWha5jbW+ SV6xssLquiAA4aCCFhgblp0CWR6D6KgCTJU8s8u8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 30/47] ipa: mali-c55: agc: Port to `AgcAlgorithm` Date: Mon, 24 Aug 2026 11:13:49 +0200 Message-ID: <20260824091407.502020-31-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Stefan Klug --- src/ipa/mali-c55/algorithms/agc.cpp | 131 +++++----------------- src/ipa/mali-c55/algorithms/agc.h | 4 +- src/ipa/mali-c55/ipa_context.h | 34 +++--- src/ipa/mali-c55/mali-c55.cpp | 161 +++++----------------------- 4 files changed, 69 insertions(+), 261 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 821f43e9fd..96e0a4c7e9 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,14 +122,12 @@ Agc::Agc() int Agc::init(IPAContext &context, const ValueNode &tuningData) { - int ret = agc_.parseTuningData(tuningData); - if (ret) - return ret; - - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); - context.ctrlMap.merge(agc_.controls()); - - return 0; + return agc_.init(tuningData, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); } int Agc::configure(IPAContext &context, @@ -140,79 +137,23 @@ 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, + }); + 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 +206,11 @@ void Agc::fillWeightsArrayBuffer(MaliC55Params *params, const enum MaliC55Blocks std::fill(weights.begin(), weights.end(), 1); } -void Agc::prepare([[maybe_unused]] IPAContext &context, const uint32_t frame, - [[maybe_unused]] IPAFrameContext &frameContext, MaliC55Params *params) +void Agc::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, MaliC55Params *params) { + agc_.prepare(context.activeState.agc, frameContext.agc); + if (frame > 0) return; @@ -308,11 +251,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 +263,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 5d1a0e1256..cd370e25fc 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -14,6 +14,8 @@ #include "libcamera/internal/bayer_format.h" +#include +#include #include #include "libipa/awb.h" @@ -26,34 +28,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; @@ -63,10 +47,12 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { + agc::FrameContext agc; + struct { uint32_t exposure; - double sensorGain; - } agc; + double gain; + } sensor; ipa::awb::FrameContext awb; ipa::ccm::FrameContext ccm; @@ -85,6 +71,10 @@ struct IPAContext { 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 7696fae665..1e7cd1995d 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_; }; @@ -104,8 +93,8 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig { context_.sensorInfo = ipaConfig.sensorInfo; - 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; @@ -131,32 +120,23 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig return -EINVAL; } + context_.sensorControls = ipaConfig.sensorControls; + 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); } @@ -170,111 +150,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 = {}; @@ -283,9 +171,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()); @@ -295,6 +190,8 @@ int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder, return ret; } + updateControls(ipaControls); + return 0; } @@ -354,8 +251,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); @@ -365,7 +262,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 24 09: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: 28063 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 0359DC3339 for ; Mon, 24 Aug 2026 09:15:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9DB406843A; Mon, 24 Aug 2026 11: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="jdUU8vfB"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7A5B268407 for ; Mon, 24 Aug 2026 11:14:19 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0AF22C23; Mon, 24 Aug 2026 11:12:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562775; bh=wmJuxz1Re6Vk3Ok1uyrLIYpPnBnm+3tAPr9QU65eshA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jdUU8vfBAsQq+fSNis1e93GoBrctjQbUMuwHDWTs+AoELigXZzP3hyQJOqJF8zime kldwAJC1Nc2aSOkwSGdTohy1Cz2qDKwb1ZImQmOZYy6bFCi7EZUBibFdK8xhgxFxEa rJZOpr7Y1vHVFFd3bOsE/BilYLOonRxj6gmrZ28Y= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v8 31/47] ipa: ipu3: agc: Port to `AgcAlgorithm` Date: Mon, 24 Aug 2026 11:13:50 +0200 Message-ID: <20260824091407.502020-32-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Jacopo Mondi --- src/ipa/ipu3/algorithms/agc.cpp | 111 +++++++++----------------- src/ipa/ipu3/algorithms/agc.h | 15 ++-- src/ipa/ipu3/ipa_context.cpp | 49 +++--------- src/ipa/ipu3/ipa_context.h | 30 +++---- src/ipa/ipu3/ipu3.cpp | 136 +++++--------------------------- 5 files changed, 89 insertions(+), 252 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index e74db62960..29a562d667 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) { } @@ -74,15 +66,12 @@ Agc::Agc() */ int Agc::init(IPAContext &context, const ValueNode &tuningData) { - int ret; - - ret = agc_.parseTuningData(tuningData); - if (ret) - return ret; - - context.ctrlMap.merge(agc_.controls()); - - return 0; + return agc_.init(tuningData, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); } /** @@ -95,32 +84,35 @@ 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, + }); +} - /* \todo Run this again when FrameDurationLimits is passed in */ - agc_.setLimits(minExposureTime_, maxExposureTime_, minAnalogueGain_, - maxAnalogueGain_, {}); - agc_.resetFrameCount(); +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) +{ + agc_.queueRequest(context.configuration.agc, context.activeState.agc, + frameContext.agc, controls); +} - return 0; +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Agc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] ipu3_uapi_params *params) +{ + agc_.prepare(context.activeState.agc, frameContext.agc); } Histogram Agc::parseStatistics(const ipu3_uapi_stats_3a *stats, @@ -228,16 +220,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 +231,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..e8f27bc429 100644 --- a/src/ipa/ipu3/ipa_context.h +++ b/src/ipa/ipu3/ipa_context.h @@ -15,6 +15,10 @@ #include #include +#include + +#include +#include #include namespace libcamera { @@ -32,18 +36,7 @@ struct IPASessionConfiguration { ipu3_uapi_grid_config afGrid; } af; - struct { - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; - } agc; - - struct { - int32_t defVBlank; - utils::Duration lineDuration; - Size size; - } sensor; + agc::Session agc; }; struct IPAActiveState { @@ -53,12 +46,7 @@ struct IPAActiveState { bool stable; } af; - struct { - uint32_t exposure; - double gain; - uint32_t constraintMode; - uint32_t exposureMode; - } agc; + agc::ActiveState agc; struct { struct { @@ -81,6 +69,8 @@ struct IPAFrameContext : public FrameContext { uint32_t exposure; double gain; } sensor; + + agc::FrameContext agc; }; struct IPAContext { @@ -90,10 +80,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 4b4e524d90..031b2e4f4d 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,11 @@ void IPAIPU3::queueRequest(const uint32_t frame, const ControlList &controls) */ void IPAIPU3::setControls(unsigned int frame) { - ControlList ctrls(sensorCtrls_); - agc::prepareControls(ctrls, camHelper_.get(), - context_.activeState.agc.exposure, - context_.activeState.agc.gain); + IPAFrameContext &frameContext = context_.frameContexts.get(frame); + + ControlList ctrls(context_.sensorControls); + agc::prepareControls(ctrls, context_.camHelper.get(), + frameContext.agc.exposure, frameContext.agc.gain); ControlList lensCtrls(lensCtrls_); lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, From patchwork Mon Aug 24 09: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: 28064 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 111FCC3339 for ; Mon, 24 Aug 2026 09:15:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9927F68400; Mon, 24 Aug 2026 11: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="Yx7+y9AH"; 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 C609468397 for ; Mon, 24 Aug 2026 11:14:19 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7E163C31; Mon, 24 Aug 2026 11:12:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562775; bh=rbOgZwDcoZ1lcekZQ5Cgf4xmhMN2YQo7lkp4bB+irOQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Yx7+y9AHi1zDv2T3Se2Iw49ohNVlfyWRtBh9gnU3S0uEMaTGuLZ70bUG5KdakjuQ+ FraK/JehPJEL2cvf0DH1ZN+RAUHR4hnN6WAmxqDOoF9gvsRsCrm7NlbRqo/Sy0yOEO qKQ1bAhYQCGmtX7gycoWHsY1vCpVzVuGX6VG0Nr0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 32/47] ipa: libipa: agc_mean_luminance: Remove control handling Date: Mon, 24 Aug 2026 11:13:51 +0200 Message-ID: <20260824091407.502020-33-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Stefan Klug --- src/ipa/libipa/agc.cpp | 15 ++++++++++++--- src/ipa/libipa/agc_mean_luminance.cpp | 21 +-------------------- src/ipa/libipa/agc_mean_luminance.h | 8 -------- 3 files changed, 13 insertions(+), 31 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index e056644752..1c096c984d 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -435,8 +435,8 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, * ControlInfoMap::Map during configuration. */ config.ctrlMap.erase(&controls::ExposureValue); - for (const auto &[id, _] : impl_.controls()) - config.ctrlMap.erase(id); + config.ctrlMap.erase(&controls::AeConstraintMode); + config.ctrlMap.erase(&controls::AeExposureMode); /* \todo Move this to the `Camera` class. */ config.ctrlMap[&controls::AeEnable] = ControlInfo{ @@ -477,7 +477,16 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, if (session.autoAllowed) { config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); - config.ctrlMap.merge(impl_.controls()); + + std::vector options; + for (const auto &[id, _] : impl_.constraintModes()) + options.emplace_back(id); + config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(options); + + options.clear(); + for (const auto &[id, _] : impl_.exposureModeHelpers()) + options.emplace_back(id); + config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options); } 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 24 09: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: 28065 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 4F188C333A for ; Mon, 24 Aug 2026 09:15:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AB21368452; Mon, 24 Aug 2026 11:15:01 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="RX34TCa0"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0A987683CC for ; Mon, 24 Aug 2026 11:14:20 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B6C8EC46; Mon, 24 Aug 2026 11:12:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562775; bh=sGWVs16e22YXlBaXuoRI+yweGXGjcu96Hgc6u+i9u68=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RX34TCa0m2JjBoc+/7YbJZnl6JN5adMHjjSW++OPteKbkFQqg+2emXTyzP27EMRLJ 1LbSgC2FOCHfNmP6hiJlOQHsWpYmziKI7L4sfHIwmRP7EnLMxBmpK3qP0DaU1BUFzu KknR8ay0IUBQTa1PnMvSw1Oy4EXBRSTJ2zYUs9SQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 33/47] ipa: libipa: histogram: total(): Use `back()` Date: Mon, 24 Aug 2026 11:13:52 +0200 Message-ID: <20260824091407.502020-34-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Stefan Klug --- 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 24 09: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: 28066 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 4EA48C333B for ; Mon, 24 Aug 2026 09:15:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E153368445; Mon, 24 Aug 2026 11:15:02 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="M0ypFs4K"; 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 5AB99683FE for ; Mon, 24 Aug 2026 11:14:20 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F070BC24; Mon, 24 Aug 2026 11:12:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562776; bh=vZH47pP4meIWhSbirIGAt94i/xrUfimWtcgTwEnSqA8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=M0ypFs4KP9meMrwSbaqjaK/mddhUzZG8FxMNzqKzlFBWglzjiagAYYGgvVOLxxPSm Zr4EfulGVMBvhcAj2nbh+sd+3ot298CDB8obFXLBKLWhM8UsTLnjbx6BoNs6FWaEVW RsG7PmHWO4/RSwRgHL2n/qgEcQm7dn91J5KeNnFI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v8 34/47] ipa: libipa: histogram: Add `operator[]` to query bin Date: Mon, 24 Aug 2026 11:13:53 +0200 Message-ID: <20260824091407.502020-35-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Jacopo Mondi Reviewed-by: Stefan Klug --- src/ipa/libipa/histogram.cpp | 6 ++++++ src/ipa/libipa/histogram.h | 5 +++++ 2 files changed, 11 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..6192641766 100644 --- a/src/ipa/libipa/histogram.h +++ b/src/ipa/libipa/histogram.h @@ -42,6 +42,11 @@ 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 cumulative_[bin + 1] - cumulative_[bin]; + } + private: std::vector cumulative_; }; From patchwork Mon Aug 24 09: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: 28067 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 A1159C333C for ; Mon, 24 Aug 2026 09:15:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 237C268446; Mon, 24 Aug 2026 11: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="YLM+d77j"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 918A3683CE for ; Mon, 24 Aug 2026 11:14:20 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 45912C34; Mon, 24 Aug 2026 11:12:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562776; bh=O/h3dW5fSaasmO91wO/puI3jd1Z6e2lvxjIlSZQ9258=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YLM+d77jQ+F/0Lne10ateebcUdu8JITVL7tIaixQa0RHhETYPI20pIv2SaycUDdP/ hbUNHntyizn9JnhXCaOX+Bp7XGlPNswVDStWdTrkpGuDbn6tT4DBOHn9qDU8eBmoUd SHGrweqX/b0U+MWgQcm3BZeN0GEltcezvpbb7ty4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Stefan Klug Subject: [PATCH v8 35/47] ipa: softisp: Store exposure as `uint32_t` Date: Mon, 24 Aug 2026 11:13:54 +0200 Message-ID: <20260824091407.502020-36-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Milan Zamazal Reviewed-by: Stefan Klug --- src/ipa/softisp/algorithms/agc.cpp | 6 +++--- src/ipa/softisp/ipa_context.h | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ipa/softisp/algorithms/agc.cpp b/src/ipa/softisp/algorithms/agc.cpp index f12ab977ec..a89f74b7cf 100644 --- a/src/ipa/softisp/algorithms/agc.cpp +++ b/src/ipa/softisp/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/softisp/ipa_context.h b/src/ipa/softisp/ipa_context.h index 8934871648..f0c3c0afbf 100644 --- a/src/ipa/softisp/ipa_context.h +++ b/src/ipa/softisp/ipa_context.h @@ -28,7 +28,7 @@ namespace ipa::softisp { struct IPASessionConfiguration { struct { - int32_t exposureMin, exposureMax; + uint32_t exposureMin, exposureMax; double againMin, againMax, again10, againMinStep; utils::Duration lineDuration; } agc; @@ -42,14 +42,14 @@ struct IPAActiveState { ipa::ccm::ActiveState ccm; 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; @@ -68,12 +68,12 @@ struct IPAFrameContext : public FrameContext { ipa::ccm::FrameContext 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 24 09: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: 28068 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 E8E7CC3339 for ; Mon, 24 Aug 2026 09:15:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 785DD68435; Mon, 24 Aug 2026 11: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="bprd+RI+"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D04C66840E for ; Mon, 24 Aug 2026 11:14:20 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7D6E5C47; Mon, 24 Aug 2026 11:12:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562776; bh=ieDiyWUsusfJaZFyz1AUuj8wwsTyg3hpNqqQiNlLZAo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=bprd+RI+TLrQDWVNwMJff/JTpf4FB4WH0kjPWXRc/AGFQC8TkYJsl5bKW7c5N0dll FyFjVPqGfOf6SAlxIGmHUszSw2b6yG/rM9KtUCCd1mcQrXL8SdTJvZ7wD2NBdL/Rdd 3wRG1z2B4Bl8Q0WLHLHGay5P+B+EAvz/eLYsgW2M= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Jacopo Mondi , Stefan Klug Subject: [PATCH v8 36/47] ipa: softisp: agc: Separate MSV calculation Date: Mon, 24 Aug 2026 11:13:55 +0200 Message-ID: <20260824091407.502020-37-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Milan Zamazal Reviewed-by: Jacopo Mondi Reviewed-by: Stefan Klug --- src/ipa/softisp/algorithms/agc.cpp | 72 +++++++++++++++++------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/src/ipa/softisp/algorithms/agc.cpp b/src/ipa/softisp/algorithms/agc.cpp index a89f74b7cf..5cc7514d0e 100644 --- a/src/ipa/softisp/algorithms/agc.cpp +++ b/src/ipa/softisp/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 std::nullopt; + + 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(IPASoftIspExposure, 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(IPASoftIspExposure, 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(IPASoftIspExposure, 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 24 09: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: 28069 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 EA3C5C333A for ; Mon, 24 Aug 2026 09:15:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9867B683EF; Mon, 24 Aug 2026 11: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="uQ2+LkXX"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 21F4068410 for ; Mon, 24 Aug 2026 11:14:21 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C0274C25; Mon, 24 Aug 2026 11:12:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562776; bh=Ych64XtVG4bu82TKotNP2uBqir3smOA0WDxWjtT30s0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uQ2+LkXXIkEPPCAzBgOvnKNV/EyYC/ZG9DdF56Kb1tCRu6fam1qAu3jkcE3hYDpvs 1Hh3PDh6XO/laMmZivPsYDKxYmtgZPHG1eNRFZqAq1CfaZ+6pvMKtn5hU5NkGLLqSS rdlXjUWQP0dheVb9R60KUwUauHz1So62caSpU0xI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Milan Zamazal Subject: [PATCH v8 37/47] ipa: softisp: agc: Simplify min gain step handling Date: Mon, 24 Aug 2026 11:13:56 +0200 Message-ID: <20260824091407.502020-38-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Acked-by: Jacopo Mondi Reviewed-by: Milan Zamazal --- src/ipa/softisp/algorithms/agc.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ipa/softisp/algorithms/agc.cpp b/src/ipa/softisp/algorithms/agc.cpp index 5cc7514d0e..a3420318e0 100644 --- a/src/ipa/softisp/algorithms/agc.cpp +++ b/src/ipa/softisp/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::min(next, again - context.configuration.agc.againMinStep); } else { uint32_t next = exposure * factor; exposure = std::min(next, exposure - 1); From patchwork Mon Aug 24 09: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: 28070 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 D74D9C333B for ; Mon, 24 Aug 2026 09:15:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7760D68463; Mon, 24 Aug 2026 11: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="O3BbLQsH"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5B49868412 for ; Mon, 24 Aug 2026 11:14:21 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 031D9C48; Mon, 24 Aug 2026 11:12:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562777; bh=Uxcs0AW7WSbXVGkPIqvZkKf0WTrBXp7xvpDynS1wz0k=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=O3BbLQsHeKcWq/408yBBxBPFulb+VWmqK7pRtiDvJqfjk2lEu+VUdBvUU7oWQn8Sx 94G6L4IqA0YmCaYUXlP3uF6yzjMjsiJw3RPI+Fn7jU86zF0a8V99A1k9tj0oPAdohz rgyIA3J1DcAEalzKmfmv+YRh8HXy/N5TkidgNoSw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Jacopo Mondi Subject: [PATCH v8 38/47] ipa: softisp: agc: Use `Histogram` Date: Mon, 24 Aug 2026 11:13:57 +0200 Message-ID: <20260824091407.502020-39-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Milan Zamazal Reviewed-by: Jacopo Mondi --- src/ipa/softisp/algorithms/agc.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ipa/softisp/algorithms/agc.cpp b/src/ipa/softisp/algorithms/agc.cpp index a3420318e0..0027365b33 100644 --- a/src/ipa/softisp/algorithms/agc.cpp +++ b/src/ipa/softisp/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(IPASoftIspExposure, Debug) << "Not adjusting exposure due to insufficient histogram data"; From patchwork Mon Aug 24 09: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: 28071 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 4FC05C333C for ; Mon, 24 Aug 2026 09:15:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E9FF068455; Mon, 24 Aug 2026 11: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="pja5KmYM"; 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 8EF47683D5 for ; Mon, 24 Aug 2026 11:14:21 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 49118C3C; Mon, 24 Aug 2026 11:12:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562777; bh=gvzQtB26+5qhgERXlgFWe+w4KJk+BMSzAacIZgMO4jQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pja5KmYMhdwFpBE1djMc/gP3t0u2yH36g7vqkR7kJt4Zg3oFyOLhZYq7xZ+6ekChU DfCeZRb2kM9tK66D5e4Wi36EzkqXEM/Mscbb8C1jdCgxyx1SRiLMwE43qsxif5ynCa I/EvFj+sRnW0xeaSFsiOCt+A0acxK0mTJyjEzLWU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal Subject: [PATCH v8 39/47] ipa: softisp: agc: Adjust histogram for black level Date: Mon, 24 Aug 2026 11:13:58 +0200 Message-ID: <20260824091407.502020-40-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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. Everything under the black level is summed into the bin corresponding to the current black level, and the MSV calculation is carried out on the partial histogram that starts at the bin of the 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 ignored bins are instead summed into the "first" bin of the "new" histogram. This can result in the same image with the same statistics being perceived as darker by the algorithm. Signed-off-by: Barnabás Pőcze Reviewed-by: Milan Zamazal --- src/ipa/softisp/algorithms/agc.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/ipa/softisp/algorithms/agc.cpp b/src/ipa/softisp/algorithms/agc.cpp index 0027365b33..b56d57cfeb 100644 --- a/src/ipa/softisp/algorithms/agc.cpp +++ b/src/ipa/softisp/algorithms/agc.cpp @@ -66,19 +66,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 +82,9 @@ std::optional calculateMSV(const Histogram &histogram, uint8_t blackLevel if (yHistValsPerBin == 0) return std::nullopt; - 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 +189,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 * histogram.size() / 256; + + for (unsigned int i = 0; i < blackLevelHistIdx; i++) + histogram[blackLevelHistIdx] += histogram[i]; + + auto exposureMSV = calculateMSV({ { histogram.begin() + blackLevelHistIdx, histogram.end() } }); if (!exposureMSV) { LOG(IPASoftIspExposure, Debug) << "Not adjusting exposure due to insufficient histogram data"; From patchwork Mon Aug 24 09: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: 28072 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 9E62FC3339 for ; Mon, 24 Aug 2026 09:15:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3477C68466; Mon, 24 Aug 2026 11: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="pspKgWON"; 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 E748368413 for ; Mon, 24 Aug 2026 11:14:21 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7CDE7C49; Mon, 24 Aug 2026 11:12:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562777; bh=DMFncnKXxLF8zDUXLjMoso73EUfnuwz4JUe7jR7b+Ko=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pspKgWONbRdQ0QPciaRN+Adb7iRjIDAScq1VwL0bzUKQK3bKVHow4I7TALWEo9nr+ suJdBjVFDiTEK8kcB+SIW4qV+uNEXIe3w/u1ksAZzhFzoyTV3F2Yqxnjl7rVFaH0MR +X0gWszXtAQRbjkd/PELfw6xblL0eriFKOCT3Xkk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Jacopo Mondi Subject: [PATCH v8 40/47] ipa: softisp: agc: Move to libipa Date: Mon, 24 Aug 2026 11:13:59 +0200 Message-ID: <20260824091407.502020-41-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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"). Signed-off-by: Barnabás Pőcze Reviewed-by: Milan Zamazal Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc_msv.cpp | 232 +++++++++++++++++++++++++++++ src/ipa/libipa/agc_msv.h | 52 +++++++ src/ipa/libipa/meson.build | 2 + src/ipa/softisp/algorithms/agc.cpp | 170 ++++----------------- src/ipa/softisp/algorithms/agc.h | 7 +- 5 files changed, 317 insertions(+), 146 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..395f3cb46a --- /dev/null +++ b/src/ipa/libipa/agc_msv.cpp @@ -0,0 +1,232 @@ +/* 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 + * + * This is a best-effort, low-complexity auto exposure/gain algorithm based on + * the 2007 paper "Automatic camera exposure control" + * (https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf) + * + * Due to its simplicity, it is a better fit than AgcMeanLuminance when + * operating a sensor without a known gain model. + * + * The algorithm operates by inspecting the luminance histogram of a frame, and + * then splitting it into a fixed number of bins. Then an average is calculated + * (MSV ~ mean sample value), which is compared to the target MSV. If the current + * MSV is too low, first the exposure time, then the analogue gain is raised. + * If it is too high, first the analogue gain, then the exposure time is + * reduced. + */ + +/** + * \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 (in lines) + * + * \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 std::nullopt; + + 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..b3be080e64 --- /dev/null +++ b/src/ipa/libipa/agc_msv.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat Inc. + * + * Luminance mean sample value based AGC algorithm + */ + +#pragma once + +#include +#include + +namespace libcamera { + +namespace ipa { + +class Histogram; + +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 fa49abcd55..0fb65a2393 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', @@ -30,6 +31,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/softisp/algorithms/agc.cpp b/src/ipa/softisp/algorithms/agc.cpp index b56d57cfeb..ccdc6a2029 100644 --- a/src/ipa/softisp/algorithms/agc.cpp +++ b/src/ipa/softisp/algorithms/agc.cpp @@ -7,11 +7,6 @@ #include "agc.h" -#include -#include -#include -#include - #include #include @@ -24,138 +19,22 @@ LOG_DEFINE_CATEGORY(IPASoftIspExposure) namespace ipa::softisp::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 std::nullopt; - - 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(IPASoftIspExposure, 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::min(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(IPASoftIspExposure, 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, @@ -196,14 +75,19 @@ void Agc::process(IPAContext &context, for (unsigned int i = 0; i < blackLevelHistIdx; i++) histogram[blackLevelHistIdx] += histogram[i]; - auto exposureMSV = calculateMSV({ { histogram.begin() + blackLevelHistIdx, histogram.end() } }); - if (!exposureMSV) { - LOG(IPASoftIspExposure, Debug) - << "Not adjusting exposure due to insufficient histogram data"; - return; - } + const auto &newEv = agc_.calculateNewEv({ + .yHist = { + { histogram.begin() + blackLevelHistIdx, histogram.end() }, + }, + .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/softisp/algorithms/agc.h b/src/ipa/softisp/algorithms/agc.h index 3694461920..6b064b7ee5 100644 --- a/src/ipa/softisp/algorithms/agc.h +++ b/src/ipa/softisp/algorithms/agc.h @@ -9,6 +9,8 @@ #include "algorithm.h" +#include + namespace libcamera { namespace ipa::softisp::algorithms { @@ -16,8 +18,7 @@ namespace ipa::softisp::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::softisp::algorithms */ From patchwork Mon Aug 24 09: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: 28075 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 36F07C333D for ; Mon, 24 Aug 2026 09:15:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C1EA568455; Mon, 24 Aug 2026 11: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="RbNilrzC"; 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 6F8C7683EF for ; Mon, 24 Aug 2026 11:14:22 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 23EBAC23; Mon, 24 Aug 2026 11:12:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562778; bh=2K7JVx1QkO/9syl5LRegUhV9PxV6Go8AZduQF8EEH00=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RbNilrzCgah9it7b6+e1N7J+M6SB2osgn37LvwmWItZ+i7cqoAwbiGRxXW7sQUW9w wu1lj9a+ixL2DdCfSIo/onoTOVBA8fyhedDZbbcZMLsa929K8Ak40r/s1u5tmrkMzW 89itHGAN9GnN7XM6UvBbkfY/r3DBg2TLUSe9XqRU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Jacopo Mondi Subject: [PATCH v8 41/47] ipa: libipa: agc_msv: Ensure limits are always respected Date: Mon, 24 Aug 2026 11:14:00 +0200 Message-ID: <20260824091407.502020-42-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Milan Zamazal Acked-by: Jacopo Mondi --- 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 395f3cb46a..98fd3cd509 100644 --- a/src/ipa/libipa/agc_msv.cpp +++ b/src/ipa/libipa/agc_msv.cpp @@ -171,59 +171,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 24 09: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: 28073 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 CD677C333A for ; Mon, 24 Aug 2026 09:15:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 609C768455; Mon, 24 Aug 2026 11: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="TM/wtJk/"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AB92168417 for ; Mon, 24 Aug 2026 11:14:22 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5C214C31; Mon, 24 Aug 2026 11:12:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562778; bh=WTvE7uTUpwAsYeftPZPutuB3boXe6Edehs9gnNtKVq0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TM/wtJk/Z91RLd43N85cAn2pTi5URxoakFB4jeKl/qQSNKIbZTNunaMJYKsqApSLM 24B94hcdS3NKpXKbpnirHSqTkVPQE+FmeG2PIwrTBFfYO65PgRtUXX27MEHm/+gBYn ishhvJMEBCERRPyQitfbZUKOGI6bvu31ohZrY4JI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Jacopo Mondi Subject: [PATCH v8 42/47] ipa: libipa: agc_msv: Handle min gain > 1.0 case Date: Mon, 24 Aug 2026 11:14:01 +0200 Message-ID: <20260824091407.502020-43-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 maximum 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 Reviewed-by: Milan Zamazal Reviewed-by: Jacopo Mondi --- 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 98fd3cd509..c5b8df983b 100644 --- a/src/ipa/libipa/agc_msv.cpp +++ b/src/ipa/libipa/agc_msv.cpp @@ -220,7 +220,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 24 09: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: 28074 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 23630C333B for ; Mon, 24 Aug 2026 09:15:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9C2FF6845E; Mon, 24 Aug 2026 11: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="fAYJECas"; 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 DFA8F68400 for ; Mon, 24 Aug 2026 11:14:22 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 95D15C3E; Mon, 24 Aug 2026 11:12:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562778; bh=E2Ym3cGP6SozRMXfITxuPeWF2QL1cjfONKnXwIjkVDA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fAYJECasjIGrBNbh20/y2mYEMtxwX5ZeBAWrPktXk5fqlj5FT1DklssJklO4tblzh qz1jXKh18wLlPV6mycF0oyhhLjnvvsXEi6om5oh+161s9tWFknTV7r6jTTgTXVOGqs KPn8xQEj5hsp1B6SbFqG2hXsY2a9I9LncTA/H1GE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Jacopo Mondi Subject: [PATCH v8 43/47] ipa: softisp: Move sensor helper and controls to context Date: Mon, 24 Aug 2026 11:14:02 +0200 Message-ID: <20260824091407.502020-44-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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. This also moves the black level handling into the blc algorithm instead of doing it in the main ipa source file. Signed-off-by: Barnabás Pőcze Reviewed-by: Milan Zamazal Reviewed-by: Jacopo Mondi --- src/ipa/softisp/algorithms/blc.cpp | 12 +++++++++++ src/ipa/softisp/ipa_context.h | 4 ++++ src/ipa/softisp/softisp.cpp | 34 ++++++++++-------------------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/ipa/softisp/algorithms/blc.cpp b/src/ipa/softisp/algorithms/blc.cpp index e35aabc35e..c7714aa345 100644 --- a/src/ipa/softisp/algorithms/blc.cpp +++ b/src/ipa/softisp/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/softisp/ipa_context.h b/src/ipa/softisp/ipa_context.h index f0c3c0afbf..957564a56d 100644 --- a/src/ipa/softisp/ipa_context.h +++ b/src/ipa/softisp/ipa_context.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include @@ -17,6 +18,7 @@ #include "libcamera/internal/vector.h" #include +#include #include #include @@ -89,6 +91,8 @@ struct IPAContext { } IPACameraSensorInfo sensorInfo; + ControlInfoMap sensorControls; + std::unique_ptr camHelper; IPASessionConfiguration configuration; IPAActiveState activeState; FCQueue frameContexts; diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp index ec0d0f0489..283c037ac9 100644 --- a/src/ipa/softisp/softisp.cpp +++ b/src/ipa/softisp/softisp.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 IPASoftIsp::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(IPASoftIsp, Warning) << "Failed to create camera sensor helper for " << settings.sensorModel; @@ -202,10 +200,10 @@ int IPASoftIsp::init(const IPASettings &settings, int IPASoftIsp::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 IPASoftIsp::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 IPASoftIsp::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(), + ControlList ctrls(context_.sensorControls); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc.exposure, frameContext.agc.gain); setSensorControls.emit(ctrls); } From patchwork Mon Aug 24 09: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: 28076 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 31F23C333C for ; Mon, 24 Aug 2026 09:15:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C5D0A6841E; Mon, 24 Aug 2026 11: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="n0psjmcH"; 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 1FF9B683F0 for ; Mon, 24 Aug 2026 11:14:23 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CEF65C34; Mon, 24 Aug 2026 11:12:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562778; bh=7AReM5uFJw3YU4wQnKj6D7tEWJo6emVL6ATBtkot+n4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=n0psjmcHudftDUWjwjeG1gTQhcZlOdIzcQtb2wmB0neoYSbuGIkn4Qk+wdc7cafry mvh30F1kumv0b97poV43gHYXAQF31Ub6EY/1vaeaOwPEO57fPbdUC5GpSRU1D9V4K2 4V0kqIzY7xVHjaDa2mtaQTyBKh01q2YuGJOs5w5c= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Jacopo Mondi Subject: [PATCH v8 44/47] ipa: softisp: Update controls after configuration Date: Mon, 24 Aug 2026 11:14:03 +0200 Message-ID: <20260824091407.502020-45-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Milan Zamazal Reviewed-by: Jacopo Mondi --- include/libcamera/internal/software_isp/software_isp.h | 3 ++- include/libcamera/ipa/softisp.mojom | 2 +- src/ipa/softisp/softisp.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 da893d2421..b8b24bb1b1 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::softisp::IPAConfigInfo &configInfo); + const ipa::softisp::IPAConfigInfo &configInfo, + ControlInfoMap *ipaControls); int exportBuffers(const Stream *stream, unsigned int count, std::vector> *buffers); diff --git a/include/libcamera/ipa/softisp.mojom b/include/libcamera/ipa/softisp.mojom index a892d84f19..209af1fde6 100644 --- a/include/libcamera/ipa/softisp.mojom +++ b/include/libcamera/ipa/softisp.mojom @@ -22,7 +22,7 @@ interface IPASoftIspInterface { 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/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp index 283c037ac9..692606338a 100644 --- a/src/ipa/softisp/softisp.cpp +++ b/src/ipa/softisp/softisp.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 IPASoftIsp::init(const IPASettings &settings, return 0; } -int IPASoftIsp::configure(const IPAConfigInfo &configInfo) +int IPASoftIsp::configure(const IPAConfigInfo &configInfo, ControlInfoMap *ipaControls) { context_.sensorControls = configInfo.sensorControls; @@ -251,6 +252,8 @@ int IPASoftIsp::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 02a98f594b..f2dc613c5a 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::softisp::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 bcb7e1c8fb..f8efd24424 100644 --- a/src/libcamera/software_isp/software_isp.cpp +++ b/src/libcamera/software_isp/software_isp.cpp @@ -278,17 +278,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::softisp::IPAConfigInfo &configInfo) + const ipa::softisp::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 24 09: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: 28077 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 60260C3339 for ; Mon, 24 Aug 2026 09:15:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E22A16844A; Mon, 24 Aug 2026 11: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="Dc6I2rhK"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6BADB683F4 for ; Mon, 24 Aug 2026 11:14:23 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 13A87C24; Mon, 24 Aug 2026 11:12:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562779; bh=ZTRnuRb0dMuVw8TJnZLY8aQY3VfNeTveyWdFK8PvmyM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Dc6I2rhK1bzpQtKYp6T0AtiiASJY5Zila01ffTbiEx8Bs03iTQ0SgU3/lQ/vI8v0v rYTxnRuYBbYCjKnEM8BXUL3ZvLghdG/7kKuL+GHQUC2ZymQVVABdy3TA8l3Ki/MpFo lXmJAnS1VIE9fpDxkE1Oh4w3+z3YbW2dZoPUTcmY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v8 45/47] ipa: libipa: agc: Work without `CameraSensorHelper` Date: Mon, 24 Aug 2026 11:14:04 +0200 Message-ID: <20260824091407.502020-46-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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` can operate without a `CameraSensorHelper`, but in that case it assumes an "ideal" gain model, where gains are truly continuous, and a gain of x will apply a gain of exactly x to the result. This is not a good fit when only gain codes are available. So now that the agc algorithm from the simple ipa has been extracted (AgcMSV), let's use that in `AgcAlgorithm` to be able to always provide some level of automatic exposure/gain control. Even though the main use case for operating without a known gain model is empirically determining the gain model using the manual controls. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/ipu3/algorithms/agc.cpp | 4 +- src/ipa/libipa/agc.cpp | 202 +++++++++++++++++++--------- src/ipa/libipa/agc.h | 10 +- src/ipa/mali-c55/algorithms/agc.cpp | 4 +- src/ipa/rkisp1/algorithms/agc.cpp | 4 +- 5 files changed, 148 insertions(+), 76 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index 29a562d667..22846514be 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -66,8 +66,7 @@ Agc::Agc() */ int Agc::init(IPAContext &context, const ValueNode &tuningData) { - return agc_.init(tuningData, { - .sensor = context.camHelper.get(), + return agc_.init(tuningData, context.camHelper.get(), { .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -88,7 +87,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 1c096c984d..9f37e1b8ee 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -13,11 +13,13 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -80,6 +82,9 @@ namespace agc { * \var Session::maxAnalogueGain * \brief Maximum analogue gain for the streaming session * + * \var Session::defAnalogueGain + * \brief Default analogue gain of the configured sensor + * * \var Session::minFrameDuration * \brief Minimum frame duration for the streaming session * @@ -222,10 +227,9 @@ namespace agc { * * The AgcAlgorithm class can be used to implement automatic exposure/gain * control in an IPA module, conforming to the prescribed Algorithm interface. - * Internally AgcMeanLuminance is used, this class merely concerns itself with - * processing the sensor properties, establishing limits, managing controls, - * providing the resulting metadata, and driving the actual algorithm in - * process(). + * Internally AgcMeanLuminance or AgcMSV is used, this class merely concerns + * itself with processing the sensor properties, establishing limits, managing + * controls, and providing the resulting metadata. * * Users should compose the agc::Session, agc::ActiveState, and agc::FrameContext * structures into their session configuration, active state, and frame contexts, @@ -241,9 +245,6 @@ namespace 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 * @@ -288,24 +289,35 @@ namespace agc { /** * \brief Load tuning data and configure * \param[in] tuningData The tuning data + * \param[in] sensor The camera sensor helper * \param[in] config The algorithm configuration * * This function loads the tuning data and configures the algorithm as if * by a call to configure(), in order to provide the available controls * in ConfigurationParams::ctrlMap. * - * The tuning data format is that of AgcMeanLuminance. Refer to - * AgcMeanLuminance::parseTuningData() for more details. + * If a CameraSensorHelper is provided in \a sensor, an instance of + * AgcMeanLuminance is instantiated and the tuning data is loaded by + * calling AgcMeanLuminance::parseTuningData(). Otherwise an instance + * of AgcMSV is instantiated, and in this case the tuning data is ignored. * * \return 0 on success, or a negative error code * * \sa Algorithm::init() */ -int AgcAlgorithm::init(const ValueNode &tuningData, const ConfigurationParams &config) +int AgcAlgorithm::init(const ValueNode &tuningData, CameraSensorHelper *sensor, + const ConfigurationParams &config) { - 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; /* * The purpose of this `configure()` is merely to provide the @@ -361,10 +373,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 << "], " @@ -403,28 +419,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; @@ -475,19 +484,52 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, add(controls::AnalogueGainMode, controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual); - if (session.autoAllowed) { - config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); + std::visit(utils::overloaded{ + [&](AgcMSV &) { + /* No constraint/exposure mode support. */ + state.constraintMode = controls::AeConstraintModeEnum::ConstraintNormal; + state.exposureMode = controls::AeExposureModeEnum::ExposureNormal; - std::vector options; - for (const auto &[id, _] : impl_.constraintModes()) - options.emplace_back(id); - config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(options); + state.automatic.yTarget = 0; /* Not supported. */ - options.clear(); - for (const auto &[id, _] : impl_.exposureModeHelpers()) - options.emplace_back(id); - config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options); - } + if (!session.autoAllowed) + return; + + 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); + + impl.configure(session.lineDuration, sensor_); + impl.resetFrameCount(); + + if (!session.autoAllowed) + return; + + 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); + + options.clear(); + for (const auto &[id, _] : impl.exposureModeHelpers()) + options.emplace_back(id); + config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options); + }, + }, impl_); return 0; } @@ -718,36 +760,68 @@ 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 = std::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 = { + static_cast(minExposureTime / lineDuration), + static_cast(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 = std::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_); + + const utils::Duration newExposureTime = state.automatic.exposure * lineDuration; LOG(Agc, Debug) - << "exposure-time: " << newEv.exposureTime << ", " + << "exposure-time: " << newExposureTime << ", " << "analogue-gain: " << state.automatic.gain << ", " << "quantization-gain: " << state.automatic.quantizationGain << ", " << "digital-gain: " << state.automatic.digitalGain; @@ -757,7 +831,7 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, * the minimum frame duration when we have short exposures. */ processFrameDuration(session, frameContext, - std::max(frameContext.minFrameDuration, newEv.exposureTime)); + std::max(frameContext.minFrameDuration, newExposureTime)); fillMetadata(session, frameContext, metadata); } diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index 7c5a7c3518..e820077e7a 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -21,6 +22,7 @@ #include #include "agc_mean_luminance.h" +#include "agc_msv.h" #include "camera_sensor_helper.h" namespace libcamera { @@ -38,6 +40,7 @@ struct Session { utils::Duration maxExposureTime; double minAnalogueGain; double maxAnalogueGain; + double defAnalogueGain; utils::Duration minFrameDuration; utils::Duration maxFrameDuration; utils::Duration lineDuration; @@ -119,7 +122,6 @@ class AgcAlgorithm { public: struct ConfigurationParams { - const CameraSensorHelper *sensor; const IPACameraSensorInfo &sensorInfo; const ControlInfoMap &sensorControls; ControlInfoMap::Map &ctrlMap; @@ -135,7 +137,8 @@ public: double lux = 0; }; - int init(const ValueNode &tuningData, const ConfigurationParams &config); + int init(const ValueNode &tuningData, CameraSensorHelper *sensor, + const ConfigurationParams &config); int configure(agc::Session &session, agc::ActiveState &state, const ConfigurationParams &config); @@ -157,7 +160,8 @@ private: const agc::FrameContext &frameContext, ControlList &metadata); - 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 96e0a4c7e9..e977ef75bd 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -122,8 +122,7 @@ Agc::Agc() int Agc::init(IPAContext &context, const ValueNode &tuningData) { - return agc_.init(tuningData, { - .sensor = context.camHelper.get(), + return agc_.init(tuningData, context.camHelper.get(), { .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -138,7 +137,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 bb0345ca87..79a7474cb2 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -136,8 +136,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.init(tuningData, { - .sensor = context.camHelper.get(), + ret = agc_.init(tuningData, context.camHelper.get(), { .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -163,7 +162,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 24 09: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: 28078 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 83E12C333A for ; Mon, 24 Aug 2026 09:15:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 08EDA6844F; Mon, 24 Aug 2026 11: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="S1GVPa8/"; 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 B0AF9683F3 for ; Mon, 24 Aug 2026 11:14:23 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5A2E5C46; Mon, 24 Aug 2026 11:12:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562779; bh=4aeNZk87L52A+S1Zaq+WEnjbxFiVVOKwPzegscQkgbA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=S1GVPa8/8sVOUfS0bqSwLVBd6wO0MUwdReLhpa34Ter9KXJPd3bBJThjvA6e3BJ8N 3yNuuKqngj+igGqpTM8ZomG5dADoCe/ujnpVOuzVrbD+YbEKSAIx0fn1PA6U1Muzjh dOiS9EXQ7ti9lVJQsnkyMQcl0va8Gyci80TSn7vQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Stefan Klug Subject: [PATCH v8 46/47] ipa: softisp: agc: Port to `AgcAlgorithm` Date: Mon, 24 Aug 2026 11:14:05 +0200 Message-ID: <20260824091407.502020-47-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 Reviewed-by: Milan Zamazal Acked-by: Stefan Klug --- src/ipa/softisp/algorithms/agc.cpp | 130 ++++++++++++++++------------- src/ipa/softisp/algorithms/agc.h | 12 ++- src/ipa/softisp/ipa_context.h | 20 +---- src/ipa/softisp/softisp.cpp | 55 +----------- 4 files changed, 87 insertions(+), 130 deletions(-) diff --git a/src/ipa/softisp/algorithms/agc.cpp b/src/ipa/softisp/algorithms/agc.cpp index ccdc6a2029..3e962ed288 100644 --- a/src/ipa/softisp/algorithms/agc.cpp +++ b/src/ipa/softisp/algorithms/agc.cpp @@ -11,30 +11,70 @@ #include -#include "control_ids.h" - namespace libcamera { LOG_DEFINE_CATEGORY(IPASoftIspExposure) namespace ipa::softisp::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) +{ + return agc_.init(tuningData, context.camHelper.get(), { + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); +} + 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, }); +} + +void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) +{ + agc_.queueRequest(context.configuration.agc, context.activeState.agc, frameContext.agc, controls); +} - return 0; +void Agc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) +{ + agc_.prepare(context.activeState.agc, frameContext.agc); } void Agc::process(IPAContext &context, @@ -43,51 +83,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 * histogram.size() / 256; + for (unsigned int i = 0; i < blackLevelHistIdx; i++) + histogram[blackLevelHistIdx] += histogram[i]; + + Histogram yHist({ histogram.begin() + blackLevelHistIdx, histogram.end() }); + + 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 * histogram.size() / 256; - - for (unsigned int i = 0; i < blackLevelHistIdx; i++) - histogram[blackLevelHistIdx] += histogram[i]; - - const auto &newEv = agc_.calculateNewEv({ - .yHist = { - { histogram.begin() + blackLevelHistIdx, histogram.end() }, - }, - .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/softisp/algorithms/agc.h b/src/ipa/softisp/algorithms/agc.h index 6b064b7ee5..d89fef4d3e 100644 --- a/src/ipa/softisp/algorithms/agc.h +++ b/src/ipa/softisp/algorithms/agc.h @@ -9,7 +9,7 @@ #include "algorithm.h" -#include +#include namespace libcamera { @@ -18,15 +18,23 @@ namespace ipa::softisp::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::softisp::algorithms */ diff --git a/src/ipa/softisp/ipa_context.h b/src/ipa/softisp/ipa_context.h index 957564a56d..397e6c99b2 100644 --- a/src/ipa/softisp/ipa_context.h +++ b/src/ipa/softisp/ipa_context.h @@ -17,6 +17,7 @@ #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" +#include #include #include #include @@ -29,11 +30,7 @@ namespace libcamera { namespace ipa::softisp { struct IPASessionConfiguration { - struct { - uint32_t exposureMin, exposureMax; - double againMin, againMax, again10, againMinStep; - utils::Duration lineDuration; - } agc; + ipa::agc::Session agc; struct { std::optional level; } black; @@ -42,12 +39,7 @@ struct IPASessionConfiguration { struct IPAActiveState { ipa::awb::ActiveState awb; ipa::ccm::ActiveState ccm; - - struct { - uint32_t exposure; - double again; - bool valid; - } agc; + ipa::agc::ActiveState agc; struct { uint8_t level; @@ -68,11 +60,7 @@ struct IPAActiveState { struct IPAFrameContext : public FrameContext { ipa::awb::FrameContext awb; ipa::ccm::FrameContext ccm; - - struct { - uint32_t exposure; - double gain; - } agc; + ipa::agc::FrameContext agc; struct { uint32_t exposure; diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp index 692606338a..8acfcd1a0b 100644 --- a/src/ipa/softisp/softisp.cpp +++ b/src/ipa/softisp/softisp.cpp @@ -107,6 +107,7 @@ int IPASoftIsp::init(const IPASettings &settings, } context_.sensorInfo = sensorInfo; + context_.sensorControls = sensorControls; /* Load the tuning data file */ File file(settings.configurationFile); @@ -180,22 +181,6 @@ int IPASoftIsp::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(IPASoftIsp, Error) << "Don't have exposure control"; - return -EINVAL; - } - - if (sensorControls.find(V4L2_CID_ANALOGUE_GAIN) == sensorControls.end()) { - LOG(IPASoftIsp, Error) << "Don't have gain control"; - return -EINVAL; - } - return 0; } @@ -203,55 +188,17 @@ int IPASoftIsp::configure(const IPAConfigInfo &configInfo, ControlInfoMap *ipaCo { 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(IPASoftIsp, 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(IPASoftIsp, 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 24 09: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: 28079 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 708A2C333B for ; Mon, 24 Aug 2026 09:15:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 065EC6841E; Mon, 24 Aug 2026 11: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="RMXmPJxG"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EFB2068423 for ; Mon, 24 Aug 2026 11:14:23 +0200 (CEST) Received: from pb-laptop.local (185.221.143.32.nat.pool.zt.hu [185.221.143.32]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A1402C47; Mon, 24 Aug 2026 11:12:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562779; bh=ctsnWYaEPNwfDZM7zz/CZjwULJfDyqU0sQQTIhOWYfQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RMXmPJxGrWSPZHztqvYtDGU4iCkXoghzq4lBTMLmvNEiDLLSfrPs+dmMG7At9dMSV j70xNh8VWVmKpJBRGtQEewSvLecLNT3um/vgrwZ1eX327kyxQIKJAXSZ9oCFMPRYvw w1jhxX75JzFtwzMvaWH6DLltk+iF3bbBWuP4wjm4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug , Paul Elder Subject: [PATCH v8 47/47] ipa: libipa: agc_mean_luminance: Fold resetFrameCount() in configure() Date: Mon, 24 Aug 2026 11:14:06 +0200 Message-ID: <20260824091407.502020-48-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260824091407.502020-1-barnabas.pocze@ideasonboard.com> References: <20260824091407.502020-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 9f37e1b8ee..2bf74c93c3 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -512,7 +512,6 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, state.automatic.yTarget = impl.effectiveYTarget(0, 1); impl.configure(session.lineDuration, sensor_); - impl.resetFrameCount(); if (!session.autoAllowed) return; 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);