From patchwork Mon Aug 17 11:43:02 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27779 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 720B2C3263 for ; Mon, 17 Aug 2026 11:44:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0913068215; Mon, 17 Aug 2026 13:43:56 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BEvik7P4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CBE5E6820A for ; Mon, 17 Aug 2026 13:43:52 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8465913BA; Mon, 17 Aug 2026 13:42:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966953; bh=prXjsYMLKYpUfaGb/UbTj193g3+HbJaraP66pBGP0dc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BEvik7P4ltwSXpaYqvGsegHvmHMJd2kwcpq/qBxTJQBdNfEJcZ2IY0cfbMmJuXBCH MgiLJDaceTb0qnJYBVMFFuCMXsgnOXQh0N6G0ZFPVsc/qBaTD/nE9DJDooPhVFkohM EVgiA7vctCysH92lZQfACNE/8q+FWkHALsycDQSI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Jacopo Mondi Subject: [PATCH v5 01/47] libcamera: software_isp: init(): Fix documentation typo Date: Mon, 17 Aug 2026 13:43:02 +0200 Message-ID: <20260817114349.994123-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 c7165771c6..672a5ed36b 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 17 11:43:03 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27780 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 405D2C3263 for ; Mon, 17 Aug 2026 11:44:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 65B716820A; Mon, 17 Aug 2026 13:43:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nS3RXvq9"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2D18F67FEE for ; Mon, 17 Aug 2026 13:43:53 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DE5D1B1A; Mon, 17 Aug 2026 13:42:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966954; bh=0s5lVWDckAnVytyQwittXPAiqFqYHfPymZbFYlD+Ktg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nS3RXvq9a8/XJfQvdhJrVBydtVMvoqZ3fdg2DHXOeTGPcpQ8nH0fmYR0bY93jSrfW KgTIpXhfMT1knSFwgcqfIswAYGKIgNU+PTlDa9h24tLQdHylwKu/t23GTKtMyj5nGi XdWtW83SCWbZFERDUK4L5kb56BXP87uMZ46oJ1qY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Jacopo Mondi Subject: [PATCH v5 02/47] ipa: simple: Remove control presence sanity check Date: Mon, 17 Aug 2026 13:43:03 +0200 Message-ID: <20260817114349.994123-3-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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/simple/soft_simple.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 629e1a32de..e99df1986f 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -311,13 +311,6 @@ void IPASoftSimple::processStats(const uint32_t frame, algo->process(context_, frame, frameContext, stats_, metadata); metadataReady.emit(frame, metadata); - /* Sanity check */ - if (!sensorControls.contains(V4L2_CID_EXPOSURE) || - !sensorControls.contains(V4L2_CID_ANALOGUE_GAIN)) { - LOG(IPASoft, Error) << "Control(s) missing"; - return; - } - ControlList ctrls(sensorInfoMap_); auto &againNew = frameContext.sensor.gain; From patchwork Mon Aug 17 11:43:04 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27781 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 0B5B0C3263 for ; Mon, 17 Aug 2026 11:44:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5539E6827A; Mon, 17 Aug 2026 13:43:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="oufZ0Z3V"; 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 92D9367FEE for ; Mon, 17 Aug 2026 13:43:53 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3EAE4153F; Mon, 17 Aug 2026 13:42:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966954; bh=PNJk0yVp6xeOx946DB0YefAvZv2jDrMbwwdpjTTaLWw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oufZ0Z3VnuGGRyEBknz12WuOzkTehbroMdvGMr3hU3Kns5ajozQs9xAQ9hIvtjuWN 8b6S7ISjBVZGUMOejRrJx4xE5QZanrHHPhn/ex6x+1Z76ZPotwgzC6LKXcaaEWNwVf 9EPDC+5iB9ftcpiVUfs+0mL1absV8nPbT+KByqpI= 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 v5 03/47] ipa: simple: agc: Do not overwrite sensor exposure/gain Date: Mon, 17 Aug 2026 13:43:04 +0200 Message-ID: <20260817114349.994123-4-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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/simple/algorithms/agc.cpp | 11 +++++++---- src/ipa/simple/ipa_context.h | 5 +++++ src/ipa/simple/soft_simple.cpp | 9 +++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index a13a755280..e9bcb2c032 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -67,8 +67,8 @@ Agc::Agc() void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV) { - int32_t &exposure = frameContext.sensor.exposure; - double &again = frameContext.sensor.gain; + int32_t exposure = frameContext.sensor.exposure; + double again = frameContext.sensor.gain; double error = kExposureOptimal - exposureMSV; @@ -115,6 +115,9 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou again = std::clamp(again, context.configuration.agc.againMin, context.configuration.agc.againMax); + frameContext.agc.exposure = exposure; + frameContext.agc.gain = again; + context.activeState.agc.exposure = exposure; context.activeState.agc.again = again; @@ -150,8 +153,8 @@ void Agc::process(IPAContext &context, * Use the new exposure and gain values calculated the last time * there were valid stats. */ - frameContext.sensor.exposure = context.activeState.agc.exposure; - frameContext.sensor.gain = context.activeState.agc.again; + frameContext.agc.exposure = context.activeState.agc.exposure; + frameContext.agc.gain = context.activeState.agc.again; return; } diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index ff312ae8f4..71dc22e899 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/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/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index e99df1986f..3932daaa1f 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -313,10 +313,11 @@ void IPASoftSimple::processStats(const uint32_t frame, ControlList ctrls(sensorInfoMap_); - auto &againNew = frameContext.sensor.gain; - ctrls.set(V4L2_CID_EXPOSURE, frameContext.sensor.exposure); - ctrls.set(V4L2_CID_ANALOGUE_GAIN, - static_cast(camHelper_ ? camHelper_->gainCode(againNew) : againNew)); + int32_t againNew = camHelper_ + ? camHelper_->gainCode(frameContext.agc.gain) + : static_cast(frameContext.agc.gain); + ctrls.set(V4L2_CID_EXPOSURE, frameContext.agc.exposure); + ctrls.set(V4L2_CID_ANALOGUE_GAIN, againNew); setSensorControls.emit(ctrls); } From patchwork Mon Aug 17 11:43:05 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27782 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 291A7C3263 for ; Mon, 17 Aug 2026 11:44:10 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D71D368282; Mon, 17 Aug 2026 13:43:59 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BJ/3BhbC"; 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 E89606820A for ; Mon, 17 Aug 2026 13:43:53 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9EAD3177A; Mon, 17 Aug 2026 13:42:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966954; bh=DrV+jdOy6jsZnPtgHidErBCtUkZ6EdW6azn3jtTE+Ss=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BJ/3BhbCeuazrFbjW8K2jswmE1DaxsUsAnfyN/cRVVx1z8SZLNe71TEBAVowoPwKz dQbOv+3Q6DYFLOHi1lr8v0YtJlEnSeRcZTdsDuJ1pl8WpXYn+Im6uMOfdF5peir8sQ VASSrLwVrTs3OytCuBAqJrz8pc8ZBMB6r51GDiRQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v5 04/47] ipa: rkisp1: Remove `IPASessionConfiguration::sensor.defVBlank` Date: Mon, 17 Aug 2026 13:43:05 +0200 Message-ID: <20260817114349.994123-5-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 58ef163d85..273540a330 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -249,8 +249,6 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.paramFormat = ipaConfig.paramFormat; const IPACameraSensorInfo &info = ipaConfig.sensorInfo; - const ControlInfo vBlank = sensorControls_.find(V4L2_CID_VBLANK)->second; - context_.configuration.sensor.defVBlank = vBlank.def().get(); context_.configuration.sensor.size = info.outputSize; context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; From patchwork Mon Aug 17 11:43:06 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27783 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 B0654C3263 for ; Mon, 17 Aug 2026 11:44:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9CEAD68291; Mon, 17 Aug 2026 13:44:01 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="doY6JJO0"; 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 48B4068247 for ; Mon, 17 Aug 2026 13:43:54 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F179419C6; Mon, 17 Aug 2026 13:42:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966955; bh=LkhkgPNIED+qy83PAN8S0qHrPIcPH1b+xthZgFpDr+A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=doY6JJO0UlFo1TNsT/YxWHTlwBgMQSSsrEp5U9x4p8jfQERAM0Q1cmbpfgM6NFOv3 1X1Q0g3gCPq1tsmUBRbO7mdrkG2idfZzo5KbuLF2gSoGF3oQA94cW2dbDW9VpVTw1S RtEL468klzGVF+eGtOHWlFmVUhX4jCqYHfXQEWHM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v5 05/47] ipa: rkisp1: Move sensor control info map to context Date: Mon, 17 Aug 2026 13:43:06 +0200 Message-ID: <20260817114349.994123-6-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 273540a330..9f1f133e6f 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -75,15 +75,12 @@ protected: private: void updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls); void setControls(unsigned int frame); std::map buffers_; std::map mappedBuffers_; - ControlInfoMap sensorControls_; - /* Local parameter storage */ struct IPAContext context_; }; @@ -163,6 +160,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, LOG(IPARkISP1, Debug) << "Hardware revision is " << hwRevision; context_.sensorInfo = sensorInfo; + context_.sensorControls = sensorControls; context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); if (!context_.camHelper) { @@ -207,7 +205,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, return ret; /* Initialize controls. */ - updateControls(sensorInfo, sensorControls, ipaControls); + updateControls(sensorInfo, ipaControls); return 0; } @@ -227,13 +225,13 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, const std::map &streamConfig, ControlInfoMap *ipaControls) { - sensorControls_ = ipaConfig.sensorControls; + context_.sensorControls = ipaConfig.sensorControls; - const auto itExp = sensorControls_.find(V4L2_CID_EXPOSURE); + const auto itExp = context_.sensorControls.find(V4L2_CID_EXPOSURE); int32_t minExposure = itExp->second.min().get(); int32_t maxExposure = itExp->second.max().get(); - const auto itGain = sensorControls_.find(V4L2_CID_ANALOGUE_GAIN); + const auto itGain = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN); int32_t minGain = itGain->second.min().get(); int32_t maxGain = itGain->second.max().get(); @@ -253,7 +251,7 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; /* Update the camera controls using the new sensor settings. */ - updateControls(info, sensorControls_, ipaControls); + updateControls(info, ipaControls); /* * When the AGC computes the new exposure values for a frame, it needs @@ -385,7 +383,6 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId, } void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls) { ControlInfoMap::Map ctrlMap = rkisp1Controls; @@ -395,7 +392,7 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, * limits and the line duration. */ double lineDuration = context_.configuration.sensor.lineDuration.get(); - const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; + const ControlInfo &v4l2Exposure = context_.sensorControls.find(V4L2_CID_EXPOSURE)->second; int32_t minExposure = v4l2Exposure.min().get() * lineDuration; int32_t maxExposure = v4l2Exposure.max().get() * lineDuration; int32_t defExposure = v4l2Exposure.def().get() * lineDuration; @@ -404,7 +401,7 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, std::forward_as_tuple(minExposure, maxExposure, defExposure)); /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + const ControlInfo &v4l2Gain = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; float minGain = context_.camHelper->gain(v4l2Gain.min().get()); float maxGain = context_.camHelper->gain(v4l2Gain.max().get()); float defGain = context_.camHelper->gain(v4l2Gain.def().get()); @@ -418,11 +415,11 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, * The frame length is computed assuming a fixed line length combined * with the vertical frame sizes. */ - const ControlInfo &v4l2HBlank = sensorControls.find(V4L2_CID_HBLANK)->second; + const ControlInfo &v4l2HBlank = context_.sensorControls.find(V4L2_CID_HBLANK)->second; uint32_t hblank = v4l2HBlank.def().get(); uint32_t lineLength = sensorInfo.outputSize.width + hblank; - const ControlInfo &v4l2VBlank = sensorControls.find(V4L2_CID_VBLANK)->second; + const ControlInfo &v4l2VBlank = context_.sensorControls.find(V4L2_CID_VBLANK)->second; std::array frameHeights{ v4l2VBlank.min().get() + sensorInfo.outputSize.height, v4l2VBlank.max().get() + sensorInfo.outputSize.height, @@ -460,7 +457,7 @@ void IPARkISP1::setControls(unsigned int frame) << "Set controls for frame " << frame << ": exposure " << exposure << ", gain " << frameContext.agc.gain << ", vblank " << vblank; - ControlList ctrls(sensorControls_); + ControlList ctrls(context_.sensorControls); ctrls.set(V4L2_CID_EXPOSURE, static_cast(exposure)); ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast(gain)); ctrls.set(V4L2_CID_VBLANK, static_cast(vblank)); From patchwork Mon Aug 17 11:43:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27784 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 2BB38C3263 for ; Mon, 17 Aug 2026 11:44:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E28AE682A9; Mon, 17 Aug 2026 13:44:03 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Dali/+ln"; 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 01C1868255 for ; Mon, 17 Aug 2026 13:43:54 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 529101AC5; Mon, 17 Aug 2026 13:42:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966955; bh=HE7FhESbEeEO69Rts2Xr+83Ga8T1zdtGXNlXfJsQHZo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Dali/+lnCYP5W3+u3Tw2bP/x7hp3SFdWVqOCkR1bWECzp414aFA/EXAqFmk1FmDIc wsVZlDZBPUvoChGnrP3T3snPYYHro6yU0M86uo/ojTJ6LzaV9cdXE0g4MTQN5vv0Gv s6pgC1GZV3GuXI/GIFF1fapJk/RwpJ61l8L6xSTc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v5 06/47] ipa: rkisp1: Update sensor info when configuring Date: Mon, 17 Aug 2026 13:43:07 +0200 Message-ID: <20260817114349.994123-7-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" 830340615afd ("ipa: rkisp1: Add sensor info to context") added the sensor info to the `IPAContext` type, but it only updates it in `init()`, do it in configure as well. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/rkisp1/rkisp1.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 9f1f133e6f..b31d5b59a8 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -225,6 +225,7 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, const std::map &streamConfig, ControlInfoMap *ipaControls) { + context_.sensorInfo = ipaConfig.sensorInfo; context_.sensorControls = ipaConfig.sensorControls; const auto itExp = context_.sensorControls.find(V4L2_CID_EXPOSURE); From patchwork Mon Aug 17 11:43:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27785 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 90104C3263 for ; Mon, 17 Aug 2026 11:44:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C92BE682B4; Mon, 17 Aug 2026 13:44:06 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="v4K8aHAD"; 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 420F868256 for ; Mon, 17 Aug 2026 13:43:55 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A6C4D1B41; Mon, 17 Aug 2026 13:42:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966955; bh=s+zEl0hHYLEoNlZPFsVZH8RWfTKgkrGwd5gnrgCgLxk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=v4K8aHAD6Pp3Q2tmylXF53DLH87gD4MoZ/w10bwhUmL+94LZ2lxAObr8SCqIu4Hn9 YwWGH+5wF0OZ+96J5rtLyRBn/a098u6YFf4jpx0Tm79Dc6DY/RW7+xnDjueSy8LyvP 4OiwXy8BNoWv+fXYkvv9wWPK6IkHkFITxm/F0emk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v5 07/47] ipa: rkisp1: updateControls(): Use sensor info from context Date: Mon, 17 Aug 2026 13:43:08 +0200 Message-ID: <20260817114349.994123-8-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" It is already available so do not take it as a parameter. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/rkisp1/rkisp1.cpp | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index b31d5b59a8..66a91c4d79 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -74,8 +74,7 @@ protected: std::string logPrefix() const override; private: - void updateControls(const IPACameraSensorInfo &sensorInfo, - ControlInfoMap *ipaControls); + void updateControls(ControlInfoMap *ipaControls); void setControls(unsigned int frame); std::map buffers_; @@ -205,7 +204,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, return ret; /* Initialize controls. */ - updateControls(sensorInfo, ipaControls); + updateControls(ipaControls); return 0; } @@ -247,12 +246,12 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.paramFormat = ipaConfig.paramFormat; - const IPACameraSensorInfo &info = ipaConfig.sensorInfo; - context_.configuration.sensor.size = info.outputSize; - context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; + context_.configuration.sensor.size = context_.sensorInfo.outputSize; + context_.configuration.sensor.lineDuration = context_.sensorInfo.minLineLength * 1.0s + / context_.sensorInfo.pixelRate; /* Update the camera controls using the new sensor settings. */ - updateControls(info, ipaControls); + updateControls(ipaControls); /* * When the AGC computes the new exposure values for a frame, it needs @@ -285,7 +284,7 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, if (algo->disabled_) continue; - int ret = algo->configure(context_, info); + int ret = algo->configure(context_, context_.sensorInfo); if (ret) return ret; } @@ -383,8 +382,7 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId, metadataReady.emit(frame, metadata); } -void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, - ControlInfoMap *ipaControls) +void IPARkISP1::updateControls(ControlInfoMap *ipaControls) { ControlInfoMap::Map ctrlMap = rkisp1Controls; @@ -418,19 +416,19 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, */ const ControlInfo &v4l2HBlank = context_.sensorControls.find(V4L2_CID_HBLANK)->second; uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = sensorInfo.outputSize.width + hblank; + uint32_t lineLength = context_.sensorInfo.outputSize.width + hblank; const ControlInfo &v4l2VBlank = context_.sensorControls.find(V4L2_CID_VBLANK)->second; std::array frameHeights{ - v4l2VBlank.min().get() + sensorInfo.outputSize.height, - v4l2VBlank.max().get() + sensorInfo.outputSize.height, - v4l2VBlank.def().get() + sensorInfo.outputSize.height, + v4l2VBlank.min().get() + context_.sensorInfo.outputSize.height, + v4l2VBlank.max().get() + context_.sensorInfo.outputSize.height, + v4l2VBlank.def().get() + context_.sensorInfo.outputSize.height, }; std::array frameDurations; for (unsigned int i = 0; i < frameHeights.size(); ++i) { uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U); + frameDurations[i] = frameSize / (context_.sensorInfo.pixelRate / 1000000U); } /* \todo Move this (and other agc-related controls) to agc */ From patchwork Mon Aug 17 11:43:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27786 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 C2B75C3301 for ; Mon, 17 Aug 2026 11:44:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 52EBF682A0; Mon, 17 Aug 2026 13:44:08 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="oDf1P3zt"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7E3CD68259 for ; Mon, 17 Aug 2026 13:43:55 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0CB711C4D; Mon, 17 Aug 2026 13:42:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966956; bh=W4dKtbHbFAnd6+5jK+I23/NUB3JyHJ70tFBXlwqF7LY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oDf1P3ztyOk683JrcHEwrs5/pRLYlOzBzoCEcKPJd+a1Zh/hYACofakWhoiQDWT+M +BavoyYW35XlNE0bCcisICU0D6fqDVG8YTBdu+MQGy1s7nfQ6akH5htSgw4yK7tKYE 7KrInrLyo6eDmzNsvUdxLy5OBnWcjkoHcMzBvZsc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v5 08/47] ipa: rkisp1: Move AGC related controls into AGC algorithm Date: Mon, 17 Aug 2026 13:43:09 +0200 Message-ID: <20260817114349.994123-9-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Move the `ControlInfo` setup and related initialization into the AGC algorithm instead of having it in the main IPA file. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/rkisp1/algorithms/agc.cpp | 91 ++++++++++++++++++++++++++++++- src/ipa/rkisp1/rkisp1.cpp | 88 +----------------------------- 2 files changed, 91 insertions(+), 88 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index aace46a8d0..fcaa28f3e4 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -33,13 +33,96 @@ using namespace std::literals::chrono_literals; namespace ipa::rkisp1::algorithms { +LOG_DEFINE_CATEGORY(RkISP1Agc) + +namespace { + +void reconfigure(IPAContext &context) +{ + context.configuration.sensor.lineDuration = + context.sensorInfo.minLineLength * 1.0s / context.sensorInfo.pixelRate; + + double lineDurationUs = context.configuration.sensor.lineDuration.get(); + + /* + * Compute exposure time limits from the V4L2_CID_EXPOSURE control + * limits and the line duration. + */ + + const ControlInfo &v4l2Exposure = context.sensorControls.find(V4L2_CID_EXPOSURE)->second; + int32_t minExposure = v4l2Exposure.min().get(); + int32_t maxExposure = v4l2Exposure.max().get(); + int32_t defExposure = v4l2Exposure.def().get(); + context.ctrlMap[&controls::ExposureTime] = ControlInfo{ + static_cast(minExposure * lineDurationUs), + static_cast(maxExposure * lineDurationUs), + static_cast(defExposure * lineDurationUs), + }; + + /* Compute the analogue gain limits. */ + const ControlInfo &v4l2Gain = context.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + float minGain = context.camHelper->gain(v4l2Gain.min().get()); + float maxGain = context.camHelper->gain(v4l2Gain.max().get()); + float defGain = context.camHelper->gain(v4l2Gain.def().get()); + context.ctrlMap[&controls::AnalogueGain] = ControlInfo{ + minGain, + maxGain, + defGain, + }; + + LOG(RkISP1Agc, Debug) + << "Exposure: [" << minExposure << ", " << maxExposure + << "], gain: [" << minGain << ", " << maxGain << "]"; + + /* + * Compute the frame duration limits. + * + * The frame length is computed assuming a fixed line length combined + * with the vertical frame sizes. + */ + const ControlInfo &v4l2HBlank = context.sensorControls.find(V4L2_CID_HBLANK)->second; + uint32_t hblank = v4l2HBlank.def().get(); + uint32_t lineLength = context.sensorInfo.outputSize.width + hblank; + + const ControlInfo &v4l2VBlank = context.sensorControls.find(V4L2_CID_VBLANK)->second; + std::array frameHeights{ + v4l2VBlank.min().get() + context.sensorInfo.outputSize.height, + v4l2VBlank.max().get() + context.sensorInfo.outputSize.height, + v4l2VBlank.def().get() + context.sensorInfo.outputSize.height, + }; + + std::array frameDurations; + for (unsigned int i = 0; i < frameHeights.size(); ++i) { + uint64_t frameSize = lineLength * frameHeights[i]; + frameDurations[i] = frameSize / (context.sensorInfo.pixelRate / 1000000U); + } + + context.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ + frameDurations[0], + frameDurations[1], + Span{ { frameDurations[2], frameDurations[2] } }, + }; + + /* + * When the AGC computes the new exposure values for a frame, it needs + * to know the limits for exposure time and analogue gain. As it depends + * on the sensor, update it with the controls. + * + * \todo take VBLANK into account for maximum exposure time + */ + context.configuration.sensor.minExposureTime = minExposure * context.configuration.sensor.lineDuration; + context.configuration.sensor.maxExposureTime = maxExposure * context.configuration.sensor.lineDuration; + context.configuration.sensor.minAnalogueGain = minGain; + context.configuration.sensor.maxAnalogueGain = maxGain; +} + +} /* namespace */ + /** * \class Agc * \brief A mean-based auto-exposure algorithm */ -LOG_DEFINE_CATEGORY(RkISP1Agc) - int Agc::parseMeteringModes(IPAContext &context, const ValueNode &tuningData) { if (!tuningData.isDictionary()) @@ -160,6 +243,8 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); context.ctrlMap.merge(controls()); + reconfigure(context); + return 0; } @@ -172,6 +257,8 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) */ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { + reconfigure(context); + /* Configure the default exposure and gain. */ context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain; context.activeState.agc.automatic.exposure = diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 66a91c4d79..38f55b1d86 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -169,9 +169,6 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, return -ENODEV; } - context_.configuration.sensor.lineDuration = - sensorInfo.minLineLength * 1.0s / sensorInfo.pixelRate; - /* Load the tuning data file. */ File file(settings.configurationFile); if (!file.open(File::OpenModeFlag::ReadOnly)) { @@ -227,18 +224,6 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.sensorInfo = ipaConfig.sensorInfo; context_.sensorControls = ipaConfig.sensorControls; - const auto itExp = context_.sensorControls.find(V4L2_CID_EXPOSURE); - int32_t minExposure = itExp->second.min().get(); - int32_t maxExposure = itExp->second.max().get(); - - const auto itGain = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN); - int32_t minGain = itGain->second.min().get(); - int32_t maxGain = itGain->second.max().get(); - - LOG(IPARkISP1, Debug) - << "Exposure: [" << minExposure << ", " << maxExposure - << "], gain: [" << minGain << ", " << maxGain << "]"; - /* Clear the IPA context before the streaming session. */ context_.configuration = {}; context_.activeState = {}; @@ -247,27 +232,6 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.paramFormat = ipaConfig.paramFormat; context_.configuration.sensor.size = context_.sensorInfo.outputSize; - context_.configuration.sensor.lineDuration = context_.sensorInfo.minLineLength * 1.0s - / context_.sensorInfo.pixelRate; - - /* Update the camera controls using the new sensor settings. */ - updateControls(ipaControls); - - /* - * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for exposure time and analogue gain. As it depends - * on the sensor, update it with the controls. - * - * \todo take VBLANK into account for maximum exposure time - */ - context_.configuration.sensor.minExposureTime = - minExposure * context_.configuration.sensor.lineDuration; - context_.configuration.sensor.maxExposureTime = - maxExposure * context_.configuration.sensor.lineDuration; - context_.configuration.sensor.minAnalogueGain = - context_.camHelper->gain(minGain); - context_.configuration.sensor.maxAnalogueGain = - context_.camHelper->gain(maxGain); context_.configuration.raw = std::any_of(streamConfig.begin(), streamConfig.end(), [](auto &cfg) -> bool { @@ -289,6 +253,8 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, return ret; } + updateControls(ipaControls); + return 0; } @@ -386,56 +352,6 @@ void IPARkISP1::updateControls(ControlInfoMap *ipaControls) { ControlInfoMap::Map ctrlMap = rkisp1Controls; - /* - * Compute exposure time limits from the V4L2_CID_EXPOSURE control - * limits and the line duration. - */ - double lineDuration = context_.configuration.sensor.lineDuration.get(); - const ControlInfo &v4l2Exposure = context_.sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get() * lineDuration; - int32_t maxExposure = v4l2Exposure.max().get() * lineDuration; - int32_t defExposure = v4l2Exposure.def().get() * lineDuration; - ctrlMap.emplace(std::piecewise_construct, - std::forward_as_tuple(&controls::ExposureTime), - std::forward_as_tuple(minExposure, maxExposure, defExposure)); - - /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = context_.camHelper->gain(v4l2Gain.min().get()); - float maxGain = context_.camHelper->gain(v4l2Gain.max().get()); - float defGain = context_.camHelper->gain(v4l2Gain.def().get()); - ctrlMap.emplace(std::piecewise_construct, - std::forward_as_tuple(&controls::AnalogueGain), - std::forward_as_tuple(minGain, maxGain, defGain)); - - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - const ControlInfo &v4l2HBlank = context_.sensorControls.find(V4L2_CID_HBLANK)->second; - uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = context_.sensorInfo.outputSize.width + hblank; - - const ControlInfo &v4l2VBlank = context_.sensorControls.find(V4L2_CID_VBLANK)->second; - std::array frameHeights{ - v4l2VBlank.min().get() + context_.sensorInfo.outputSize.height, - v4l2VBlank.max().get() + context_.sensorInfo.outputSize.height, - v4l2VBlank.def().get() + context_.sensorInfo.outputSize.height, - }; - - std::array frameDurations; - for (unsigned int i = 0; i < frameHeights.size(); ++i) { - uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (context_.sensorInfo.pixelRate / 1000000U); - } - - /* \todo Move this (and other agc-related controls) to agc */ - context_.ctrlMap[&controls::FrameDurationLimits] = - ControlInfo(frameDurations[0], frameDurations[1], - ControlValue(Span{ { frameDurations[2], frameDurations[2] } })); - ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end()); *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); } From patchwork Mon Aug 17 11:43:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27787 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 DA82DC3263 for ; Mon, 17 Aug 2026 11:44:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2BB866828A; Mon, 17 Aug 2026 13:44:09 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YB/Bv7iu"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BF62C6825A for ; Mon, 17 Aug 2026 13:43:55 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 68C42177A; Mon, 17 Aug 2026 13:42:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966956; bh=DbJJqWkBO3u/dmf+Izsn1TApMZ20SBZ2QmeCbOg/21Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YB/Bv7iu2KsWX3rPqxe4gw3ZUKhi3FmMFhr7i4pLNq0c8lb5kYi2VmQWs4DnH4xxv mW8KsDZIGJDk7ixpHwFJJsquHmluFAx3mWRp4OVOFFjUWxR6VC0EIO/Z65YKmupkTE AqqtvNKIQ62zGi3+wVyn646ag6uPmlZtpWXpea0s= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v5 09/47] ipa: libipa: agc_mean_luminance: Remove the need for inheritance Date: Mon, 17 Aug 2026 13:43:10 +0200 Message-ID: <20260817114349.994123-10-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 fcaa28f3e4..129f5be2a5 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -221,7 +221,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = parseTuningData(tuningData); + ret = agc_.parseTuningData(tuningData); if (ret) return ret; @@ -241,7 +241,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); @@ -271,9 +271,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); @@ -287,17 +287,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; } @@ -544,47 +543,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 @@ -606,6 +564,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 @@ -646,13 +662,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. @@ -685,8 +694,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 @@ -704,15 +713,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" @@ -724,7 +741,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. @@ -733,7 +750,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 17 11:43:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27788 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 36596C3301 for ; Mon, 17 Aug 2026 11:44:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 227B8682C8; Mon, 17 Aug 2026 13:44:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Oxkk0Iow"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1CDE968261 for ; Mon, 17 Aug 2026 13:43:56 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CE38F13BA; Mon, 17 Aug 2026 13:42:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966957; bh=6+GENnXnyBA1eUeo8nqwgo5dyGwygtOKvwhJiHrJojQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Oxkk0IowLmw7cN0ZLUYlw9MAOLFoJ27c2VzU2caT00WB7Y2fNOXGBjVnpdw7xCMA6 bgqWZyXQ4NezYUpwM3UGYdfZgx3JtGikSUxhA53RXDzXxzSpVaRSBmgzKv5L/BzjCN 9un6zPUKrCjj74BkT2rGouceemUxkZ8ylkGjFEak= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , Jacopo Mondi Subject: [PATCH v5 10/47] ipa: mali-c55: Remove `DigitalGain` support Date: Mon, 17 Aug 2026 13:43:11 +0200 Message-ID: <20260817114349.994123-11-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 17 11:43:12 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27789 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 02999C3263 for ; Mon, 17 Aug 2026 11:44:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A60CA6829E; Mon, 17 Aug 2026 13:44:12 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Pj1rnfOO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7427968269 for ; Mon, 17 Aug 2026 13:43:56 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 299F619C6; Mon, 17 Aug 2026 13:42:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966957; bh=CG/5lGhLw89u2JlsGZ1cB76EsUf32sm1lUlfTOYofpk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Pj1rnfOOWd5fmzF9QrRTfS2zmWmKeverREzc0BBl0g0bkBQLzJ2o9TsctIcyiej8l BoVUTlqyEf/dh0moMSEbMihNUcgmGzWKEoeKnFtAS0T5eW9HeyiKQkNd8DPmBFHO+C hvYVudEkI3CO8wzFC8Czpj6lq6+IdU74pgSmYq0I= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 11/47] ipa: libipa: agc_mean_luminance: Avoid some duplicate lookups Date: Mon, 17 Aug 2026 13:43:12 +0200 Message-ID: <20260817114349.994123-12-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 17 11:43:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27790 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 669D4C3263 for ; Mon, 17 Aug 2026 11:44:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 396F1682CD; Mon, 17 Aug 2026 13:44:16 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YD5fgyBj"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CA5836826B for ; Mon, 17 Aug 2026 13:43:56 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 825E2B1A; Mon, 17 Aug 2026 13:42:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966957; bh=JHBxhySzYed1Tefok8BGSg3jffXWY8+00+iPw/yBxn8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YD5fgyBjP6P3/RHHRuGON4mcgnKEZOsUBMjhl0zPED475xZYc6csRuwYZ6YVhC74o HjodAg7/PMgOGJeO1SHod65ybtXWLIxxaPIPIKsvL4gYEJqxiJr0PxeUHw26sQmzVI c7vwi3SZyDqLJU9lVZjyt/L8nJn/pqV7Hn4mraNQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 12/47] ipa: libipa: agc_mean_luminance: Remove unnecessary `std::shared_ptr` Date: Mon, 17 Aug 2026 13:43:13 +0200 Message-ID: <20260817114349.994123-13-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 17 11:43:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27791 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 065B6C3263 for ; Mon, 17 Aug 2026 11:44:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 97B4F6827A; Mon, 17 Aug 2026 13:44:17 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FQLJLbo4"; 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 268A568231 for ; Mon, 17 Aug 2026 13:43:57 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D651D19C6; Mon, 17 Aug 2026 13:42:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966958; bh=EWO3yH5Wr5l5MIGbuF6jUNvwGZZ/osKrkI/aO5ijz6I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=FQLJLbo4DPh/RZd34jcU5fuoswZKb9iKxIc8EEeXaGUbXml21xvn7AiXCCIcizsoe dGPCdskoBjLDtRFA5HyVwHFmarRXS2TDdHAGzlKFzYr/hQVGIwF/QJ3fN4R37V9xGL Y/ApACOzhyD8+E72YrPCkFR2OmYR1Dqwv/7mh19c= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 13/47] ipa: libipa: agc_mean_luminance: calculateNewEv(): Fix typo Date: Mon, 17 Aug 2026 13:43:14 +0200 Message-ID: <20260817114349.994123-14-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 17 11:43:15 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27792 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 0434DC3301 for ; Mon, 17 Aug 2026 11:44:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1F8B36828A; Mon, 17 Aug 2026 13:44:20 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gBCHnty7"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7EF8068241 for ; Mon, 17 Aug 2026 13:43:57 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 37121B1A; Mon, 17 Aug 2026 13:42:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966958; bh=3abyspfzJbFULQI8Kn40ka0tFmNm2uUWc9XGVgk9ce4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gBCHnty7Qswh1YECHhEepmokGd0JbOeMcTtah80dTXKbH9ybzJPpJChmROY/IpheH yAcFo1IfoeslbTpCcrrWcNffvfijC8cKksEyuVAM0Qwu7Ox+XRc0bJBORrC6hnLjgY HoRwjAAEjZtPoXlO52gcBtGrexRBssvvqp6T6ayo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 14/47] ipa: libipa: agc_mean_luminance: calculateNewEv(): Collect params Date: Mon, 17 Aug 2026 13:43:15 +0200 Message-ID: <20260817114349.994123-15-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 129f5be2a5..8b4bcbd71b 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -716,20 +716,22 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, /* The lower 4 bits are fractional and meant to be discarded. */ Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, [](uint32_t x) { return x >> 4; }); - AgcTraits agcTraits{ - { params->ae.exp_mean, context.hw.numAeCells }, - meteringModes_.at(frameContext.agc.meteringMode), - }; agc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); agc_.setLux(frameContext.lux.lux); utils::Duration newExposureTime; double aGain, qGain, dGain; - std::tie(newExposureTime, aGain, qGain, dGain) = - agc_.calculateNewEv(frameContext.agc.constraintMode, - frameContext.agc.exposureMode, - hist, effectiveExposureValue, agcTraits); + std::tie(newExposureTime, aGain, qGain, dGain) = agc_.calculateNewEv({ + .traits = AgcTraits{ + { params->ae.exp_mean, context.hw.numAeCells }, + meteringModes_.at(frameContext.agc.meteringMode), + }, + .yHist = hist, + .effectiveExposureValue = effectiveExposureValue, + .constraintModeIndex = frameContext.agc.constraintMode, + .exposureModeIndex = frameContext.agc.exposureMode, + }); LOG(RkISP1Agc, Debug) << "Divided up exposure time, analogue gain, quantization gain" From patchwork Mon Aug 17 11:43:16 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27794 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 ED1E2C3301 for ; Mon, 17 Aug 2026 11:44:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5FCAE682C5; Mon, 17 Aug 2026 13:44:24 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Xnn9dtK0"; 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 D09616825F for ; Mon, 17 Aug 2026 13:43:57 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8AFB6153F; Mon, 17 Aug 2026 13:42:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966958; bh=wSsdxALsxwTbBHeFFH9DcGwJFKcjvdZfp/tiG6XWT7A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Xnn9dtK0vlD7Fj1yWxvLsujzJzOOr2DI+sHXrLiVz8k4B46inq/rbsTY5iervaRuU IDH/Lmguwer7sEpHzVKkFK2ds1ZFerHJXd+ZjkjLCriKRgAcI8DfAoEe8x5/K1BaOC mGwVIMcUY23ycFnU3/lBpGZMNgPdeQr9mmHUkWuU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 15/47] ipa: libipa: agc_mean_luminance: constraintClampGain(): Mark `const` Date: Mon, 17 Aug 2026 13:43:16 +0200 Message-ID: <20260817114349.994123-16-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 17 11:43:17 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27793 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 E6562C3263 for ; Mon, 17 Aug 2026 11:44:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 904046827B; Mon, 17 Aug 2026 13:44:22 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cUZoyOlC"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 344E368279 for ; Mon, 17 Aug 2026 13:43:58 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E0269B1A; Mon, 17 Aug 2026 13:42:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966959; bh=IXX2n+Wee4lPZN1mWFe6NGCUv4q+Mkc1GU3Awkl0meQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cUZoyOlCJvA+CMmmU4ToyWMfaAdFKJMZbJJ6249fswmMq4dq33f1FzXDT/OKFfeaV nZl5N3TwGrNbBiEO0rlBucaewNNJmPKGbLTzSmnpOqmZJxe3C7V8fWzCFcdlq9SjFz 2dlk23pIWqEDCfgXB4DpcUSRI48fwj/QHTBVd8tg= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 16/47] ipa: libipa: agc_mean_luminance: calculateNewEv(): Collect results Date: Mon, 17 Aug 2026 13:43:17 +0200 Message-ID: <20260817114349.994123-17-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 8b4bcbd71b..a4567c8258 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -720,9 +720,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, agc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); agc_.setLux(frameContext.lux.lux); - utils::Duration newExposureTime; - double aGain, qGain, dGain; - std::tie(newExposureTime, aGain, qGain, dGain) = agc_.calculateNewEv({ + const auto &newEv = agc_.calculateNewEv({ .traits = AgcTraits{ { params->ae.exp_mean, context.hw.numAeCells }, meteringModes_.at(frameContext.agc.meteringMode), @@ -735,21 +733,21 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, LOG(RkISP1Agc, Debug) << "Divided up exposure time, analogue gain, quantization gain" - << " and digital gain are " << newExposureTime << ", " << aGain - << ", " << qGain << " and " << dGain; + << " and digital gain are " << newEv.exposureTime << ", " << newEv.analogueGain + << ", " << newEv.quantizationGain << " and " << newEv.digitalGain; IPAActiveState &activeState = context.activeState; /* Update the estimated exposure and gain. */ - activeState.agc.automatic.exposure = newExposureTime / lineDuration; - activeState.agc.automatic.gain = aGain; - activeState.agc.automatic.quantizationGain = qGain; + activeState.agc.automatic.exposure = newEv.exposureTime / lineDuration; + activeState.agc.automatic.gain = newEv.analogueGain; + activeState.agc.automatic.quantizationGain = newEv.quantizationGain; activeState.agc.automatic.yTarget = agc_.effectiveYTarget(); /* * Expand the target frame duration so that we do not run faster than * the minimum frame duration when we have short exposures. */ processFrameDuration(context, frameContext, - std::max(frameContext.agc.minFrameDuration, newExposureTime)); + std::max(frameContext.agc.minFrameDuration, newEv.exposureTime)); fillMetadata(context, frameContext, metadata); } From patchwork Mon Aug 17 11:43:18 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27795 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 E50D0C3303 for ; Mon, 17 Aug 2026 11:44:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C3965682C7; Mon, 17 Aug 2026 13:44:25 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="VZXorIWg"; 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 8A6D66827B for ; Mon, 17 Aug 2026 13:43:58 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3FDE913BA; Mon, 17 Aug 2026 13:42:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966959; bh=409NuXNezvvUeBOhPKVUn5osK//PaqvN+pYgg1knk/g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VZXorIWgUI1PA+/ANdQGfVcrPmgpm/zotO3Y+XLZehakjmtw3bfqDzOxRtFcCRPou f+4trLm8Xfs2C4tyRpPQU+WwD63ofz6y6vvPjWqTPlI7CC/CvrYc4Wx4KPWtPwzg1E i7hExkLw3r4aJFem6SgITR89PFKK5XnnWlMXA5WA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 17/47] ipa: libipa: agc_mean_luminance: calculateNewEv(): Return y target Date: Mon, 17 Aug 2026 13:43:18 +0200 Message-ID: <20260817114349.994123-18-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 a4567c8258..8ed9d7ea6d 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -741,7 +741,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, activeState.agc.automatic.exposure = newEv.exposureTime / lineDuration; activeState.agc.automatic.gain = newEv.analogueGain; activeState.agc.automatic.quantizationGain = newEv.quantizationGain; - activeState.agc.automatic.yTarget = agc_.effectiveYTarget(); + activeState.agc.automatic.yTarget = newEv.yTarget; /* * Expand the target frame duration so that we do not run faster than * the minimum frame duration when we have short exposures. From patchwork Mon Aug 17 11:43:19 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27796 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 060EEC3263 for ; Mon, 17 Aug 2026 11:44:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 31B1E682C9; Mon, 17 Aug 2026 13:44:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="neR6jNNz"; 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 D48AD6827E for ; Mon, 17 Aug 2026 13:43:58 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 91985153F; Mon, 17 Aug 2026 13:42:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966959; bh=yQr8ocim8YzfZQpfVbnVItbenNvq4Oka9LtsuvJcaHE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=neR6jNNzkvqpPH2qPcEnoH5nkOVPwBSsYPW/6yeYWNrS6sXZ5V+B4kBscx3YDjr29 /02pDID5NVH+QpDd7uicZR7mYDXlHPC3C+yl6KCCsgr5D8u/oB5sJx1PUjdKEon3EM ftCwEYncldE//Rj3z3HAVdvWo3JHS0aMWQyIpwNc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 18/47] ipa: libipa: agc_mean_luminance: Remove persistent params Date: Mon, 17 Aug 2026 13:43:19 +0200 Message-ID: <20260817114349.994123-19-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 --- src/ipa/libipa/agc_mean_luminance.cpp | 62 +++++++++++---------------- src/ipa/libipa/agc_mean_luminance.h | 20 ++------- src/ipa/rkisp1/algorithms/agc.cpp | 7 ++- 3 files changed, 31 insertions(+), 58 deletions(-) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index fbb5df847e..a64251f9d7 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -177,8 +177,7 @@ static constexpr unsigned int kDefaultLuxLevel = 500; */ AgcMeanLuminance::AgcMeanLuminance() - : filteredExposure_(0s), luxWarningEnabled_(true), - exposureCompensation_(1.0), frameCount_(0), lux_(0) + : filteredExposure_(0s), luxWarningEnabled_(true), frameCount_(0) { } @@ -440,25 +439,6 @@ int AgcMeanLuminance::parseTuningData(const ValueNode &tuningData) return parseExposureModes(tuningData); } -/** - * \fn AgcMeanLuminance::setExposureCompensation() - * \brief Set the exposure compensation value - * \param[in] gain The exposure compensation gain - * - * This function sets the exposure compensation value to be used in the - * AGC calculations. It is expressed as gain instead of EV. - */ - -/** - * \fn AgcMeanLuminance::setLux(int lux) - * \brief Set the lux level - * \param[in] lux The lux level - * - * This function sets the lux level to be used in the AGC calculations. A value - * of 0 means no measurement and a default value of \a kDefaultLuxLevel is used - * if necessary. - */ - /** * \brief Set the ExposureModeHelper limits for this class * \param[in] minExposureTime Minimum exposure time to allow @@ -529,26 +509,23 @@ double AgcMeanLuminance::estimateInitialGain(const Traits &traits, double yTarge /** * \brief Clamp gain within the bounds of a defined constraint - * \param[in] constraintModeIndex The index of the constraint to adhere to - * \param[in] hist A histogram over which to calculate inter-quantile means + * \param[in] params The set of parameters for the calculation * \param[in] gain The gain to clamp * * \return The gain clamped within the constraint bounds */ -double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex, - const Histogram &hist, - double gain) const +double AgcMeanLuminance::constraintClampGain(const Params ¶ms, double gain) const { - auto applyConstraint = [this, &gain, &hist](const AgcConstraint &constraint) { - double lux = lux_; + auto applyConstraint = [&](const AgcConstraint &constraint) { + double lux = params.lux; - if (relativeLuminanceTarget_.size() > 1 && lux_ == 0) + if (relativeLuminanceTarget_.size() > 1 && lux == 0) lux = kDefaultLuxLevel; double target = constraint.yTarget.eval( constraint.yTarget.domain().clamp(lux)); - double newGain = target * hist.bins() / - hist.interQuantileMean(constraint.qLo, constraint.qHi); + double newGain = target * params.yHist.bins() / + params.yHist.interQuantileMean(constraint.qLo, constraint.qHi); if (constraint.bound == AgcConstraint::Bound::Lower && newGain > gain) { @@ -567,7 +544,7 @@ double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex, } }; - const std::vector &constraints = constraintModes_.at(constraintModeIndex); + const std::vector &constraints = constraintModes_.at(params.constraintModeIndex); std::for_each(constraints.begin(), constraints.end(), applyConstraint); std::for_each(additionalConstraints_.begin(), additionalConstraints_.end(), applyConstraint); @@ -577,15 +554,16 @@ double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex, /** * \brief Get the currently effective y target + * \param[in] lux The effective lux value + * \param[in] exposureCompensation The exposure compensation value * * This function returns the current y target including exposure compensation. * * \return The y target value */ -double AgcMeanLuminance::effectiveYTarget() const +double AgcMeanLuminance::effectiveYTarget(double lux, double exposureCompensation) const { - double lux = lux_; - if (relativeLuminanceTarget_.size() > 1 && lux_ == 0) { + if (relativeLuminanceTarget_.size() > 1 && lux == 0) { /* * Warn after a few frames if there is still no lux measurement * available. The number of 10 is chosen a bit arbitrarily. It @@ -609,7 +587,7 @@ double AgcMeanLuminance::effectiveYTarget() const double luminanceTarget = relativeLuminanceTarget_.eval( relativeLuminanceTarget_.domain().clamp(lux)); - return std::min(luminanceTarget * exposureCompensation_, + return std::min(luminanceTarget * exposureCompensation, kMaxRelativeLuminanceTarget); } @@ -664,6 +642,14 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) * * \var AgcMeanLuminance::Params::exposureModeIndex * \brief The index of the exposure mode to use + * + * \var AgcMeanLuminance::Params::lux + * \brief The lux level to be used in the AGC calculations. A value of 0 means no + * measurement and a default value of \a kDefaultLuxLevel is used if necessary. + * + * \var AgcMeanLuminance::Params::exposureCompensation + * \brief The exposure compensation value to be used in the AGC calculations. + * It is expressed as gain instead of EV. */ /** @@ -695,7 +681,7 @@ AgcMeanLuminance::calculateNewEv(const Params ¶ms) */ ExposureModeHelper &exposureModeHelper = exposureModeHelpers_.at(params.exposureModeIndex); - double yTarget = effectiveYTarget(); + double yTarget = effectiveYTarget(params.lux, params.exposureCompensation); if (params.effectiveExposureValue == 0s) { LOG(AgcMeanLuminance, Error) @@ -710,7 +696,7 @@ AgcMeanLuminance::calculateNewEv(const Params ¶ms) } double gain = estimateInitialGain(params.traits, yTarget); - gain = constraintClampGain(params.constraintModeIndex, params.yHist, gain); + gain = constraintClampGain(params, gain); /* * We don't check whether we're already close to the target, because diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index 6418fe2e65..5e986be008 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -50,16 +50,6 @@ public: void configure(utils::Duration lineDuration, const CameraSensorHelper *sensorHelper); int parseTuningData(const ValueNode &tuningData); - void setExposureCompensation(double gain) - { - exposureCompensation_ = gain; - } - - void setLux(unsigned int lux) - { - lux_ = lux; - } - void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, double minGain, double maxGain, std::vector constraints); @@ -84,6 +74,8 @@ public: utils::Duration effectiveExposureValue; uint32_t constraintModeIndex; uint32_t exposureModeIndex; + double lux = 0; + double exposureCompensation = 1; }; struct Result : ExposureModeHelper::Result { @@ -92,7 +84,7 @@ public: [[nodiscard]] Result calculateNewEv(const Params ¶ms); - double effectiveYTarget() const; + double effectiveYTarget(double lux, double exposureCompensation) const; void resetFrameCount() { @@ -105,17 +97,13 @@ private: int parseConstraintModes(const ValueNode &tuningData); int parseExposureModes(const ValueNode &tuningData); double estimateInitialGain(const Traits &traits, double yTarget) const; - double constraintClampGain(uint32_t constraintModeIndex, - const Histogram &hist, - double gain) const; + double constraintClampGain(const Params ¶ms, double gain) const; utils::Duration filterExposure(utils::Duration exposureValue); utils::Duration filteredExposure_; mutable bool luxWarningEnabled_; - double exposureCompensation_; Pwl relativeLuminanceTarget_; uint64_t frameCount_; - unsigned int lux_; std::vector additionalConstraints_; std::map> constraintModes_; diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 8ed9d7ea6d..fc228452c3 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -294,7 +294,7 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) context.configuration.sensor.minAnalogueGain, context.configuration.sensor.maxAnalogueGain, {}); - context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(); + context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(0, 1); agc_.resetFrameCount(); @@ -717,9 +717,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, [](uint32_t x) { return x >> 4; }); - agc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); - agc_.setLux(frameContext.lux.lux); - const auto &newEv = agc_.calculateNewEv({ .traits = AgcTraits{ { params->ae.exp_mean, context.hw.numAeCells }, @@ -729,6 +726,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, .effectiveExposureValue = effectiveExposureValue, .constraintModeIndex = frameContext.agc.constraintMode, .exposureModeIndex = frameContext.agc.exposureMode, + .lux = frameContext.lux.lux, + .exposureCompensation = pow(2.0, frameContext.agc.exposureValue), }); LOG(RkISP1Agc, Debug) From patchwork Mon Aug 17 11:43:20 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27797 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 19CE1C3301 for ; Mon, 17 Aug 2026 11:44:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 90021682CA; Mon, 17 Aug 2026 13:44:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="kZHkCgrq"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3126368281 for ; Mon, 17 Aug 2026 13:43:59 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DC94D177A; Mon, 17 Aug 2026 13:42:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966960; bh=XbunYzFn48nXgB1gnQWOhs7HoWN/EKL5yVEs2PwYrdM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kZHkCgrqF1l/ZwoElQv2ru3CJJTjrhO5J0xCKxuiFRaDKzb9d0scG7xO8wjSeLBgU Su0XCZFmquqPm3cJmnKKeX0cJTyk3GFnX7O/HKcExGMfMy4FPrbBSL3ltNhSBWYb+V 0dYlrWH1h0xpIwfz2le+/dvikKbNKdG/I2sK3D1U= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 19/47] ipa: libipa: agc_mean_luminance: Reset `filteredExposure_` when configuring Date: Mon, 17 Aug 2026 13:43:20 +0200 Message-ID: <20260817114349.994123-20-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Reset `filteredExposure_` to 0 when `configure()` is called so that the current run is not affected by previous ones. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc_mean_luminance.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index a64251f9d7..37e8ff154f 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -367,6 +367,7 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration, helper.configure(lineDuration, sensorHelper); luxWarningEnabled_ = true; + filteredExposure_ = 0s; } /** From patchwork Mon Aug 17 11:43:21 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27798 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 8C400C3263 for ; Mon, 17 Aug 2026 11:44:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F0B29682DB; Mon, 17 Aug 2026 13:44:30 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="J0wOEghO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 801A66824E for ; Mon, 17 Aug 2026 13:43:59 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3CBA813BA; Mon, 17 Aug 2026 13:42:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966960; bh=PSanywnDLbyX9E7YISTgLvrAcQx3564Q+j/SqZKXt/0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=J0wOEghODZlHCDD4L1vVd795OHC/SAE8xCSt8F/n+RXdxOcfTPi0BVYUxTFspNkRA iiUh7C6k/NVt+6zB51zbda6vROjFO4nfqgDhovNORCpVbCt+Fk232dBy4UE2gWr2HA 8vTvdjrahfFVXI85OcgrwkaWErSGqXP35XYFg7j4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 20/47] ipa: Simplify sensor exposure/gain setting/getting Date: Mon, 17 Aug 2026 13:43:21 +0200 Message-ID: <20260817114349.994123-21-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 --- src/ipa/ipu3/ipu3.cpp | 13 ++++----- src/ipa/libipa/agc.cpp | 45 +++++++++++++++++++++++++++++ src/ipa/libipa/agc.h | 52 ++++++++++++++++++++++++++++++++++ src/ipa/libipa/meson.build | 2 ++ src/ipa/mali-c55/mali-c55.cpp | 18 ++++++------ src/ipa/rkisp1/rkisp1.cpp | 12 ++++---- src/ipa/simple/soft_simple.cpp | 16 ++++------- 7 files changed, 123 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 4bdc4b7677..b7897269bb 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..415a6d831f --- /dev/null +++ b/src/ipa/libipa/agc.cpp @@ -0,0 +1,45 @@ +/* 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" + +namespace libcamera { + +namespace ipa { + +namespace agc { + +/** + * \fn extractControls() + * \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() + * \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..4789c06ef8 --- /dev/null +++ b/src/ipa/libipa/agc.h @@ -0,0 +1,52 @@ +/* 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 "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, + int32_t exposure, double gain) +{ + controls.set(V4L2_CID_EXPOSURE, 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 da0dbe4b31..a13146d8a6 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 38f55b1d86..98ec5a5748 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -31,6 +31,7 @@ #include "libcamera/internal/yaml_parser.h" #include "algorithms/algorithm.h" +#include "libipa/agc.h" #include "ipa_context.h" #include "params.h" @@ -328,10 +329,8 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId, stats = reinterpret_cast( mappedBuffers_.at(bufferId).planes()[0].data()); - frameContext.sensor.exposure = - sensorControls.get(V4L2_CID_EXPOSURE).get(); - frameContext.sensor.gain = - context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get()); + std::tie(frameContext.sensor.exposure, frameContext.sensor.gain) + = agc::extractControls(sensorControls, context_.camHelper.get()); ControlList metadata(controls::controls); @@ -365,7 +364,6 @@ void IPARkISP1::setControls(unsigned int frame) IPAFrameContext &frameContext = context_.frameContexts.get(frame); uint32_t exposure = frameContext.agc.exposure; - uint32_t gain = context_.camHelper->gainCode(frameContext.agc.gain); uint32_t vblank = frameContext.agc.vblank; LOG(IPARkISP1, Debug) @@ -373,8 +371,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/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 3932daaa1f..01c41e3a8c 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -27,6 +27,7 @@ #include "libcamera/internal/yaml_parser.h" #include "algorithms/adjust.h" +#include "libipa/agc.h" #include "libipa/camera_sensor_helper.h" #include "module.h" @@ -301,10 +302,8 @@ void IPASoftSimple::processStats(const uint32_t frame, { IPAFrameContext &frameContext = context_.frameContexts.get(frame); - frameContext.sensor.exposure = - sensorControls.get(V4L2_CID_EXPOSURE).get(); - int32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get(); - frameContext.sensor.gain = camHelper_ ? camHelper_->gain(again) : again; + std::tie(frameContext.sensor.exposure, frameContext.sensor.gain) + = agc::extractControls(sensorControls, camHelper_.get()); ControlList metadata(controls::controls); for (const auto &algo : algorithms()) @@ -312,13 +311,8 @@ void IPASoftSimple::processStats(const uint32_t frame, metadataReady.emit(frame, metadata); ControlList ctrls(sensorInfoMap_); - - int32_t againNew = camHelper_ - ? camHelper_->gainCode(frameContext.agc.gain) - : static_cast(frameContext.agc.gain); - ctrls.set(V4L2_CID_EXPOSURE, frameContext.agc.exposure); - ctrls.set(V4L2_CID_ANALOGUE_GAIN, againNew); - + agc::prepareControls(ctrls, camHelper_.get(), + frameContext.agc.exposure, frameContext.agc.gain); setSensorControls.emit(ctrls); } From patchwork Mon Aug 17 11:43:22 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27799 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 08939C3301 for ; Mon, 17 Aug 2026 11:44:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7336D68272; Mon, 17 Aug 2026 13:44:32 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="LaGxUXzU"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E9F1C68283 for ; Mon, 17 Aug 2026 13:43:59 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8D141153F for ; Mon, 17 Aug 2026 13:42:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966960; bh=nE/jgJETXUHD6TJ447omQd4yuKnXTFJBH3A8ghTZeYo=; h=From:To:Subject:Date:In-Reply-To:References:From; b=LaGxUXzU257rH9VSl5y5bVZBZSTmDC8YYpC4EWYijcX44VFIeew/g2Tghhd0GSgO/ UvhAC15XEoDt+xPqnpvTnRA/eg0wYMJze7LSza10fpAFdzOA2QFoD2ByEfeQ0kdOfc qEgDU+xYAMTkDu+gw6UcUSM6HzaoSWy4Ig+PDR78= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 21/47] ipa: libipa: Add `AgcAlgorithm` Date: Mon, 17 Aug 2026 13:43:22 +0200 Message-ID: <20260817114349.994123-22-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 | 637 +++++++++++++++++++++++++++++- src/ipa/libipa/agc.h | 106 +++++ src/ipa/rkisp1/algorithms/agc.cpp | 464 +++------------------- src/ipa/rkisp1/algorithms/agc.h | 10 +- src/ipa/rkisp1/algorithms/lux.cpp | 2 +- src/ipa/rkisp1/ipa_context.cpp | 98 ----- src/ipa/rkisp1/ipa_context.h | 47 +-- src/ipa/rkisp1/rkisp1.cpp | 4 - 8 files changed, 805 insertions(+), 563 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 415a6d831f..3c6b12e452 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -1,16 +1,32 @@ /* 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 */ #include "agc.h" +#include +#include +#include +#include + +#include + +#include + +#include +#include + namespace libcamera { namespace ipa { +using namespace std::chrono_literals; + +LOG_DEFINE_CATEGORY(Agc) + namespace agc { /** @@ -40,6 +56,625 @@ namespace agc { } /* namespace agc */ +/** + * \class AgcAlgorithm + * \brief AgcMeanLuminance wrapper for implementing the Algorithm interface + * + * \todo DigitalGain, DigitalGainMode + */ + +/** + * \struct agc::Session + * \brief Session configuration for AgcAlgorithm + * + * \var agc::Session::minExposureTime + * \brief Minimum exposure time for the streaming session + * + * \var agc::Session::maxExposureTime + * \brief Maximum exposure time for the streaming session + * + * \var agc::Session::minAnalogueGain + * \brief Minimum analogue gain for the streaming session + * + * \var agc::Session::maxAnalogueGain + * \brief Maximum analogue gain for the streaming session + * + * \var agc::Session::minFrameDuration + * \brief Minimum frame duration for the streaming session + * + * \var agc::Session::maxFrameDuration + * \brief Maximum frame duration for the streaming session + * + * \var agc::Session::lineDuration + * \brief Line duration for the streaming session + * + * \var agc::Session::sensor + * \brief Details of the sensor configuration + * + * \var agc::Session::sensor.outputSize + * \brief Configured output size of the sensor + * + * \var agc::Session::autoAllowed + * \copybrief AgcAlgorithm::ConfigurationParams::autoAllowed + * \sa AgcAlgorithm::ConfigurationParams::autoAllowed + */ + +/** + * \struct agc::ActiveState + * \brief Active state for AgcAlgorithm + * + * The \a automatic variables track the latest values computed by algorithm + * based on the latest processed statistics. All other variables track the + * consolidated controls requested in queued requests. + * + * \var agc::ActiveState::manual + * \brief Manual exposure time and analog gain (set through requests) + * + * \var agc::ActiveState::manual.exposure + * \brief Manual exposure time expressed as a number of lines as set by the + * ExposureTime control + * + * \var agc::ActiveState::manual.gain + * \brief Manual analogue gain as set by the AnalogueGain control + * + * \var agc::ActiveState::automatic + * \brief Automatic exposure time and analog gain (computed by the algorithm) + * + * \var agc::ActiveState::automatic.exposure + * \brief Automatic exposure time expressed as a number of lines + * + * \var agc::ActiveState::automatic.gain + * \brief Automatic analogue gain multiplier + * + * \var agc::ActiveState::automatic.quantizationGain + * \brief Automatic quantization gain multiplier + * + * \var agc::ActiveState::automatic.yTarget + * \brief Automatically determined luminance target + * + * \var agc::ActiveState::autoExposureEnabled + * \brief Whether automatic exposure control is enabled by the ExposureTimeMode control + * + * \var agc::ActiveState::autoGainEnabled + * \brief Whether automatic gain control is enabled by the AnalogueGainMode control + * + * \var agc::ActiveState::exposureValue + * \brief Exposure value as set by the ExposureValue control + * + * \var agc::ActiveState::constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var agc::ActiveState::exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var agc::ActiveState::minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var agc::ActiveState::maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + */ + +/** + * \struct agc::FrameContext + * \brief Per-frame context for AgcAlgorithm + * + * \var agc::FrameContext::exposure + * \brief Exposure time expressed as a number of lines computed by the algorithm + * + * \var agc::FrameContext::gain + * \brief Analogue gain multiplier computed by the algorithm + * + * The gain should be translated to the sensor specific gain code before applying. + * + * \var agc::FrameContext::quantizationGain + * \brief Quantization gain multiplier computed by the algorithm + * + * \var agc::FrameContext::exposureValue + * \brief Exposure value as set by the ExposureValue control + * + * \var agc::FrameContext::yTarget + * \brief Luminance target computed by the algorithm + * + * \var agc::FrameContext::vblank + * \brief Vertical blanking parameter computed by the algorithm + * + * \var agc::FrameContext::autoExposureEnabled + * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control + * + * \var agc::FrameContext::autoGainEnabled + * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control + * + * \var agc::FrameContext::constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var agc::FrameContext::exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var agc::FrameContext::minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var agc::FrameContext::maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + * + * \var agc::FrameContext::frameDuration + * \brief The actual FrameDuration used by the algorithm for the frame + * + * \var agc::FrameContext::autoExposureModeChange + * \brief Indicate if autoExposureEnabled has changed from true in the previous + * frame to false in the current frame, and no manual exposure value has been + * supplied in the current frame + * + * \var agc::FrameContext::autoGainModeChange + * \brief Indicate if autoGainEnabled has changed from true in the previous + * frame to false in the current frame, and no manual gain value has been + * supplied in the current frame + */ + +/** + * \struct AgcAlgorithm::ConfigurationParams + * \brief Parameters for AgcAlgorithm::configure() + * + * \var AgcAlgorithm::ConfigurationParams::sensor + * \brief CameraSensorHelper for the sensor + * + * \var AgcAlgorithm::ConfigurationParams::sensorInfo + * \brief Current configuration of the sensor + * + * \var AgcAlgorithm::ConfigurationParams::sensorControls + * \brief ControlInfoMap of the sensor + * + * \var AgcAlgorithm::ConfigurationParams::ctrlMap + * \brief ControlInfoMap::Map to update with controls + * + * \var AgcAlgorithm::ConfigurationParams::autoAllowed + * \brief Whether to enable auto controls + * + * If \a false, the algorithm is set up for manual exposure and gain + * control only, without automatic adjustments. In this mode statistics + * must not be provided to AgcAlgorithm::process(), and ExposureTimeMode + * and AnalogueGainMode will only advertise manual control. + */ + +/** + * \struct AgcAlgorithm::ProcessParams + * \brief Parameters for AgcAlgorithm::process() + * + * \var AgcAlgorithm::ProcessParams::traits + * \brief Implementation of AgcMeanLuminance::Traits + * + * \var AgcAlgorithm::ProcessParams::yHist + * \brief Luminance histogram of the frame + * + * \var AgcAlgorithm::ProcessParams::exposure + * \brief Effective exposure of the frame + * + * \var AgcAlgorithm::ProcessParams::gain + * \brief Effective gain of the frame + * + * \var AgcAlgorithm::ProcessParams::additionalConstraints + * \brief Additional AgcMeanLuminance::AgcConstraints to apply + * + * \var AgcAlgorithm::ProcessParams::lux + * \brief Effective lux value of the frame + */ + +/** + * \brief Load tuning data + */ +int AgcAlgorithm::init(const ValueNode &tuningData) +{ + int ret = impl_.parseTuningData(tuningData); + if (ret) + return ret; + + return 0; +} + +/** + * \brief Initialize the session configuration and active state + * + * \note The IPA algorithm implementation will most likely need to call + * this in its Algorithm::init() implementation in order to provide + * the initial controls for the camera. + */ +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 Handle a \a queueRequest operation + */ +void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext, const ControlList &controls) +{ + if (session.autoAllowed) { + const auto &aeEnable = controls.get(controls::ExposureTimeMode); + if (aeEnable && + (*aeEnable == controls::ExposureTimeModeAuto) != state.autoExposureEnabled) { + state.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); + + LOG(Agc, Debug) + << (state.autoExposureEnabled ? "Enabling" : "Disabling") + << " AGC (exposure)"; + + /* + * If we go from auto -> manual with no manual control + * set, use the last computed value, which we don't + * know until prepare() so save this information. + * + * \todo Check the previous frame at prepare() time + * instead of saving a flag here + */ + if (!state.autoExposureEnabled && !controls.get(controls::ExposureTime)) + frameContext.autoExposureModeChange = true; + } + + const auto &agEnable = controls.get(controls::AnalogueGainMode); + if (agEnable && + (*agEnable == controls::AnalogueGainModeAuto) != state.autoGainEnabled) { + state.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); + + LOG(Agc, Debug) + << (state.autoGainEnabled ? "Enabling" : "Disabling") + << " AGC (gain)"; + /* + * If we go from auto -> manual with no manual control + * set, use the last computed value, which we don't + * know until prepare() so save this information. + */ + if (!state.autoGainEnabled && !controls.get(controls::AnalogueGain)) + frameContext.autoGainModeChange = true; + } + } + + const auto &exposure = controls.get(controls::ExposureTime); + if (exposure && !state.autoExposureEnabled) { + state.manual.exposure = *exposure * 1.0us / session.lineDuration; + + LOG(Agc, Debug) + << "Set exposure to " << state.manual.exposure; + } + + const auto &gain = controls.get(controls::AnalogueGain); + if (gain && !state.autoGainEnabled) { + state.manual.gain = *gain; + + LOG(Agc, Debug) << "Set gain to " << state.manual.gain; + } + + frameContext.autoExposureEnabled = state.autoExposureEnabled; + frameContext.autoGainEnabled = state.autoGainEnabled; + + if (!frameContext.autoExposureEnabled) + frameContext.exposure = state.manual.exposure; + if (!frameContext.autoGainEnabled) + frameContext.gain = state.manual.gain; + + if (!frameContext.autoExposureEnabled && + !frameContext.autoGainEnabled) + frameContext.quantizationGain = 1.0; + + const auto &exposureMode = controls.get(controls::AeExposureMode); + if (exposureMode) + state.exposureMode = + static_cast(*exposureMode); + frameContext.exposureMode = state.exposureMode; + + const auto &constraintMode = controls.get(controls::AeConstraintMode); + if (constraintMode) + state.constraintMode = + static_cast(*constraintMode); + frameContext.constraintMode = state.constraintMode; + + const auto &exposureValue = controls.get(controls::ExposureValue); + if (exposureValue) + state.exposureValue = *exposureValue; + frameContext.exposureValue = state.exposureValue; + + const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); + if (frameDurationLimits) { + /* Limit the control value to the limits in ControlInfo */ + state.minFrameDuration = std::clamp( + std::chrono::microseconds((*frameDurationLimits).front()), + session.minFrameDuration, session.maxFrameDuration); + + state.maxFrameDuration = std::clamp( + std::chrono::microseconds((*frameDurationLimits).back()), + session.minFrameDuration, session.maxFrameDuration); + } + frameContext.minFrameDuration = state.minFrameDuration; + frameContext.maxFrameDuration = state.maxFrameDuration; +} + +/** + * \brief Handle a \a prepare operation + */ +void AgcAlgorithm::prepare(agc::ActiveState &state, agc::FrameContext &frameContext) +{ + uint32_t activeAutoExposure = state.automatic.exposure; + double activeAutoGain = state.automatic.gain; + double activeAutoQGain = state.automatic.quantizationGain; + + /* Populate exposure and gain in auto mode */ + if (frameContext.autoExposureEnabled) { + frameContext.exposure = activeAutoExposure; + frameContext.quantizationGain = activeAutoQGain; + } + if (frameContext.autoGainEnabled) { + frameContext.gain = activeAutoGain; + frameContext.quantizationGain = activeAutoQGain; + } + + /* + * Populate manual exposure and gain from the active auto values when + * transitioning from auto to manual + */ + if (!frameContext.autoExposureEnabled && frameContext.autoExposureModeChange) { + state.manual.exposure = activeAutoExposure; + frameContext.exposure = activeAutoExposure; + } + if (!frameContext.autoGainEnabled && frameContext.autoGainModeChange) { + state.manual.gain = activeAutoGain; + frameContext.gain = activeAutoGain; + frameContext.quantizationGain = activeAutoQGain; + } + + frameContext.yTarget = state.automatic.yTarget; +} + +/** + * \brief Handle a \a process operation + */ +void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext, std::optional &¶ms, + ControlList &metadata) +{ + 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; + + if (frameContext.autoExposureEnabled) { + minExposureTime = session.minExposureTime; + maxExposureTime = std::clamp(frameContext.maxFrameDuration, + session.minExposureTime, + session.maxExposureTime); + } else { + minExposureTime = lineDuration * frameContext.exposure; + maxExposureTime = minExposureTime; + } + + if (frameContext.autoGainEnabled) { + minAnalogueGain = session.minAnalogueGain; + maxAnalogueGain = session.maxAnalogueGain; + } else { + minAnalogueGain = frameContext.gain; + maxAnalogueGain = frameContext.gain; + } + + /* + * The Agc algorithm needs to know the effective exposure value that was + * applied to the sensor when the statistics were collected. + */ + utils::Duration effectiveExposureValue = + lineDuration * params->exposure * params->gain; + + impl_.setLimits(minExposureTime, maxExposureTime, + minAnalogueGain, maxAnalogueGain, + std::move(params->additionalConstraints)); + + const auto &newEv = impl_.calculateNewEv({ + .traits = params->traits, + .yHist = params->yHist, + .effectiveExposureValue = effectiveExposureValue, + .constraintModeIndex = frameContext.constraintMode, + .exposureModeIndex = frameContext.exposureMode, + .lux = params->lux, + .exposureCompensation = pow(2.0, frameContext.exposureValue), + }); + + /* Update the estimated exposure and gain. */ + state.automatic.exposure = newEv.exposureTime / lineDuration; + state.automatic.gain = newEv.analogueGain; + state.automatic.quantizationGain = newEv.quantizationGain; + state.automatic.yTarget = newEv.yTarget; + + 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 4789c06ef8..66aa0eacb0 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -7,13 +7,19 @@ #pragma once +#include #include #include +#include #include +#include + +#include "agc_mean_luminance.h" #include "camera_sensor_helper.h" +#include "histogram.h" namespace libcamera { @@ -21,6 +27,61 @@ namespace ipa { 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) @@ -47,6 +108,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); + + 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 fc228452c3..4c2a066e86 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -8,9 +8,7 @@ #include "agc.h" #include -#include #include -#include #include #include @@ -35,89 +33,6 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Agc) -namespace { - -void reconfigure(IPAContext &context) -{ - context.configuration.sensor.lineDuration = - context.sensorInfo.minLineLength * 1.0s / context.sensorInfo.pixelRate; - - double lineDurationUs = context.configuration.sensor.lineDuration.get(); - - /* - * Compute exposure time limits from the V4L2_CID_EXPOSURE control - * limits and the line duration. - */ - - const ControlInfo &v4l2Exposure = context.sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get(); - int32_t maxExposure = v4l2Exposure.max().get(); - int32_t defExposure = v4l2Exposure.def().get(); - context.ctrlMap[&controls::ExposureTime] = ControlInfo{ - static_cast(minExposure * lineDurationUs), - static_cast(maxExposure * lineDurationUs), - static_cast(defExposure * lineDurationUs), - }; - - /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = context.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = context.camHelper->gain(v4l2Gain.min().get()); - float maxGain = context.camHelper->gain(v4l2Gain.max().get()); - float defGain = context.camHelper->gain(v4l2Gain.def().get()); - context.ctrlMap[&controls::AnalogueGain] = ControlInfo{ - minGain, - maxGain, - defGain, - }; - - LOG(RkISP1Agc, Debug) - << "Exposure: [" << minExposure << ", " << maxExposure - << "], gain: [" << minGain << ", " << maxGain << "]"; - - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - const ControlInfo &v4l2HBlank = context.sensorControls.find(V4L2_CID_HBLANK)->second; - uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = context.sensorInfo.outputSize.width + hblank; - - const ControlInfo &v4l2VBlank = context.sensorControls.find(V4L2_CID_VBLANK)->second; - std::array frameHeights{ - v4l2VBlank.min().get() + context.sensorInfo.outputSize.height, - v4l2VBlank.max().get() + context.sensorInfo.outputSize.height, - v4l2VBlank.def().get() + context.sensorInfo.outputSize.height, - }; - - std::array frameDurations; - for (unsigned int i = 0; i < frameHeights.size(); ++i) { - uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (context.sensorInfo.pixelRate / 1000000U); - } - - context.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ - frameDurations[0], - frameDurations[1], - Span{ { frameDurations[2], frameDurations[2] } }, - }; - - /* - * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for exposure time and analogue gain. As it depends - * on the sensor, update it with the controls. - * - * \todo take VBLANK into account for maximum exposure time - */ - context.configuration.sensor.minExposureTime = minExposure * context.configuration.sensor.lineDuration; - context.configuration.sensor.maxExposureTime = maxExposure * context.configuration.sensor.lineDuration; - context.configuration.sensor.minAnalogueGain = minGain; - context.configuration.sensor.maxAnalogueGain = maxGain; -} - -} /* namespace */ - /** * \class Agc * \brief A mean-based auto-exposure algorithm @@ -221,7 +136,16 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.parseTuningData(tuningData); + ret = agc_.init(tuningData); + if (ret) + return ret; + + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); if (ret) return ret; @@ -230,21 +154,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) if (ret) return ret; - context.ctrlMap[&controls::ExposureTimeMode] = - ControlInfo({ { ControlValue(controls::ExposureTimeModeAuto), - ControlValue(controls::ExposureTimeModeManual) } }, - ControlValue(controls::ExposureTimeModeAuto)); - context.ctrlMap[&controls::AnalogueGainMode] = - ControlInfo({ { ControlValue(controls::AnalogueGainModeAuto), - ControlValue(controls::AnalogueGainModeManual) } }, - ControlValue(controls::AnalogueGainModeAuto)); - /* \todo Move this to the Camera class */ - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true); - context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); - context.ctrlMap.merge(agc_.controls()); - - reconfigure(context); - return 0; } @@ -257,47 +166,24 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) */ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { - reconfigure(context); - - /* Configure the default exposure and gain. */ - context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain; - context.activeState.agc.automatic.exposure = - 10ms / context.configuration.sensor.lineDuration; - context.activeState.agc.automatic.quantizationGain = 1.0; - context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain; - context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure; - context.activeState.agc.autoExposureEnabled = !context.configuration.raw; - context.activeState.agc.autoGainEnabled = !context.configuration.raw; - context.activeState.agc.exposureValue = 0.0; - - context.activeState.agc.constraintMode = - static_cast(agc_.constraintModes().begin()->first); - context.activeState.agc.exposureMode = - static_cast(agc_.exposureModeHelpers().begin()->first); + int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = !context.configuration.raw, + }); + if (ret) + return ret; + context.activeState.agc.meteringMode = static_cast(meteringModes_.begin()->first); - /* Limit the frame duration to match current initialisation */ - ControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits]; - context.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get()); - context.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get()); - context.configuration.agc.measureWindow.h_offs = 0; context.configuration.agc.measureWindow.v_offs = 0; context.configuration.agc.measureWindow.h_size = configInfo.outputSize.width; context.configuration.agc.measureWindow.v_size = configInfo.outputSize.height; - agc_.configure(context.configuration.sensor.lineDuration, context.camHelper.get()); - - agc_.setLimits(context.configuration.sensor.minExposureTime, - context.configuration.sensor.maxExposureTime, - context.configuration.sensor.minAnalogueGain, - context.configuration.sensor.maxAnalogueGain, {}); - - context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(0, 1); - - agc_.resetFrameCount(); - return 0; } @@ -311,73 +197,7 @@ void Agc::queueRequest(IPAContext &context, { auto &agc = context.activeState.agc; - if (!context.configuration.raw) { - const auto &aeEnable = controls.get(controls::ExposureTimeMode); - if (aeEnable && - (*aeEnable == controls::ExposureTimeModeAuto) != agc.autoExposureEnabled) { - agc.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); - - LOG(RkISP1Agc, Debug) - << (agc.autoExposureEnabled ? "Enabling" : "Disabling") - << " AGC (exposure)"; - - /* - * If we go from auto -> manual with no manual control - * set, use the last computed value, which we don't - * know until prepare() so save this information. - * - * \todo Check the previous frame at prepare() time - * instead of saving a flag here - */ - if (!agc.autoExposureEnabled && !controls.get(controls::ExposureTime)) - frameContext.agc.autoExposureModeChange = true; - } - - const auto &agEnable = controls.get(controls::AnalogueGainMode); - if (agEnable && - (*agEnable == controls::AnalogueGainModeAuto) != agc.autoGainEnabled) { - agc.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); - - LOG(RkISP1Agc, Debug) - << (agc.autoGainEnabled ? "Enabling" : "Disabling") - << " AGC (gain)"; - /* - * If we go from auto -> manual with no manual control - * set, use the last computed value, which we don't - * know until prepare() so save this information. - */ - if (!agc.autoGainEnabled && !controls.get(controls::AnalogueGain)) - frameContext.agc.autoGainModeChange = true; - } - } - - const auto &exposure = controls.get(controls::ExposureTime); - if (exposure && !agc.autoExposureEnabled) { - agc.manual.exposure = *exposure * 1.0us - / context.configuration.sensor.lineDuration; - - LOG(RkISP1Agc, Debug) - << "Set exposure to " << agc.manual.exposure; - } - - const auto &gain = controls.get(controls::AnalogueGain); - if (gain && !agc.autoGainEnabled) { - agc.manual.gain = *gain; - - LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain; - } - - frameContext.agc.autoExposureEnabled = agc.autoExposureEnabled; - frameContext.agc.autoGainEnabled = agc.autoGainEnabled; - - if (!frameContext.agc.autoExposureEnabled) - frameContext.agc.exposure = agc.manual.exposure; - if (!frameContext.agc.autoGainEnabled) - frameContext.agc.gain = agc.manual.gain; - - if (!frameContext.agc.autoExposureEnabled && - !frameContext.agc.autoGainEnabled) - frameContext.agc.quantizationGain = 1.0; + agc_.queueRequest(context.configuration.agc, agc, frameContext.agc, controls); const auto &meteringMode = controls.get(controls::AeMeteringMode); if (meteringMode) { @@ -386,42 +206,6 @@ void Agc::queueRequest(IPAContext &context, static_cast(*meteringMode); } frameContext.agc.meteringMode = agc.meteringMode; - - const auto &exposureMode = controls.get(controls::AeExposureMode); - if (exposureMode) - agc.exposureMode = - static_cast(*exposureMode); - frameContext.agc.exposureMode = agc.exposureMode; - - const auto &constraintMode = controls.get(controls::AeConstraintMode); - if (constraintMode) - agc.constraintMode = - static_cast(*constraintMode); - frameContext.agc.constraintMode = agc.constraintMode; - - const auto &exposureValue = controls.get(controls::ExposureValue); - if (exposureValue) - agc.exposureValue = *exposureValue; - frameContext.agc.exposureValue = agc.exposureValue; - - const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); - if (frameDurationLimits) { - /* Limit the control value to the limits in ControlInfo */ - ControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits]; - int64_t minFrameDuration = - std::clamp((*frameDurationLimits).front(), - limits.min().get(), - limits.max().get()); - int64_t maxFrameDuration = - std::clamp((*frameDurationLimits).back(), - limits.min().get(), - limits.max().get()); - - agc.minFrameDuration = std::chrono::microseconds(minFrameDuration); - agc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration); - } - frameContext.agc.minFrameDuration = agc.minFrameDuration; - frameContext.agc.maxFrameDuration = agc.maxFrameDuration; } /** @@ -430,41 +214,13 @@ void Agc::queueRequest(IPAContext &context, void Agc::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) { - uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure; - double activeAutoGain = context.activeState.agc.automatic.gain; - double activeAutoQGain = context.activeState.agc.automatic.quantizationGain; - - /* Populate exposure and gain in auto mode */ - if (frameContext.agc.autoExposureEnabled) { - frameContext.agc.exposure = activeAutoExposure; - frameContext.agc.quantizationGain = activeAutoQGain; - } - if (frameContext.agc.autoGainEnabled) { - frameContext.agc.gain = activeAutoGain; - frameContext.agc.quantizationGain = activeAutoQGain; - } - - /* - * Populate manual exposure and gain from the active auto values when - * transitioning from auto to manual - */ - if (!frameContext.agc.autoExposureEnabled && frameContext.agc.autoExposureModeChange) { - context.activeState.agc.manual.exposure = activeAutoExposure; - frameContext.agc.exposure = activeAutoExposure; - } - if (!frameContext.agc.autoGainEnabled && frameContext.agc.autoGainModeChange) { - context.activeState.agc.manual.gain = activeAutoGain; - frameContext.agc.gain = activeAutoGain; - frameContext.agc.quantizationGain = activeAutoQGain; - } + agc_.prepare(context.activeState.agc, frameContext.agc); if (context.configuration.compress.supported) { frameContext.compress.enable = true; frameContext.compress.gain = frameContext.agc.quantizationGain; } - frameContext.agc.yTarget = context.activeState.agc.automatic.yTarget; - if (frame > 0 && !frameContext.agc.updateMetering) return; @@ -520,50 +276,6 @@ void Agc::prepare(IPAContext &context, const uint32_t frame, static_cast(hstConfig->mode)); } -void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, - ControlList &metadata) -{ - utils::Duration exposureTime = context.configuration.sensor.lineDuration - * frameContext.sensor.exposure; - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); - metadata.set(controls::ExposureTime, exposureTime.get()); - metadata.set(controls::FrameDuration, frameContext.agc.frameDuration.get()); - metadata.set(controls::ExposureTimeMode, - frameContext.agc.autoExposureEnabled - ? controls::ExposureTimeModeAuto - : controls::ExposureTimeModeManual); - metadata.set(controls::AnalogueGainMode, - frameContext.agc.autoGainEnabled - ? controls::AnalogueGainModeAuto - : controls::AnalogueGainModeManual); - - metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); - metadata.set(controls::AeExposureMode, frameContext.agc.exposureMode); - metadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode); - metadata.set(controls::ExposureValue, frameContext.agc.exposureValue); -} - -/** - * \brief Process frame duration and compute vblank - * \param[in] context The shared IPA context - * \param[in] frameContext The current frame context - * \param[in] frameDuration The target frame duration - * - * Compute and populate vblank from the target frame duration. - */ -void Agc::processFrameDuration(IPAContext &context, - IPAFrameContext &frameContext, - utils::Duration frameDuration) -{ - IPACameraSensorInfo &sensorInfo = context.sensorInfo; - utils::Duration lineDuration = context.configuration.sensor.lineDuration; - - frameContext.agc.vblank = (frameDuration / lineDuration) - sensorInfo.outputSize.height; - - /* Update frame duration accounting for line length quantization. */ - frameContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration; -} - namespace { class AgcTraits final : public AgcMeanLuminance::Traits @@ -637,21 +349,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, ControlList &metadata) { - if (!stats) { - processFrameDuration(context, frameContext, - frameContext.agc.minFrameDuration); - fillMetadata(context, frameContext, metadata); - return; - } - - if (!(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP)) { - fillMetadata(context, frameContext, metadata); - LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; - return; - } - - const utils::Duration &lineDuration = context.configuration.sensor.lineDuration; - /* * \todo Verify that the exposure and gain applied by the sensor for * this frame match what has been requested. This isn't a hard @@ -660,95 +357,46 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, * we receive), but is important in manual mode. */ - const rkisp1_cif_isp_stat *params = &stats->params; + const rkisp1_cif_isp_stat *params = nullptr; - /* - * Set the AGC limits using the fixed exposure time and/or gain in - * manual mode, or the sensor limits in auto mode. - */ - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; - - if (frameContext.agc.autoExposureEnabled) { - minExposureTime = context.configuration.sensor.minExposureTime; - maxExposureTime = std::clamp(frameContext.agc.maxFrameDuration, - context.configuration.sensor.minExposureTime, - context.configuration.sensor.maxExposureTime); - } else { - minExposureTime = context.configuration.sensor.lineDuration - * frameContext.agc.exposure; - maxExposureTime = minExposureTime; + if (stats) { + if (stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP) + params = &stats->params; + else + LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; } - if (frameContext.agc.autoGainEnabled) { - minAnalogueGain = context.configuration.sensor.minAnalogueGain; - maxAnalogueGain = context.configuration.sensor.maxAnalogueGain; + if (params) { + std::vector additionalConstraints; + if (context.activeState.wdr.mode != controls::WdrOff) + additionalConstraints.push_back(context.activeState.wdr.constraint); + + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ + .traits = AgcTraits{ + { params->ae.exp_mean, context.hw.numAeCells }, + meteringModes_.at(frameContext.agc.meteringMode), + }, + .yHist = { + /* The lower 4 bits are fractional and meant to be discarded. */ + { params->hist.hist_bins, context.hw.numHistogramBins }, + [](uint32_t x) { return x >> 4; }, + }, + .exposure = frameContext.sensor.exposure, + /* + * Include the quantization gain if it was applied. Do not use + * compress.gain because it will include gains that shall not be + * reported to the user when HDR is implemented. + */ + .gain = frameContext.sensor.gain + * (frameContext.compress.enable ? frameContext.agc.quantizationGain : 1), + .additionalConstraints = std::move(additionalConstraints), + .lux = frameContext.lux.lux, + }}, metadata); } else { - minAnalogueGain = frameContext.agc.gain; - maxAnalogueGain = frameContext.agc.gain; + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {}, metadata); } - std::vector additionalConstraints; - if (context.activeState.wdr.mode != controls::WdrOff) - additionalConstraints.push_back(context.activeState.wdr.constraint); - - agc_.setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain, - std::move(additionalConstraints)); - - /* - * The Agc algorithm needs to know the effective exposure value that was - * applied to the sensor when the statistics were collected. - */ - utils::Duration exposureTime = lineDuration * frameContext.sensor.exposure; - double analogueGain = frameContext.sensor.gain; - utils::Duration effectiveExposureValue = exposureTime * analogueGain; - - /* - * Include the quantization gain if it was applied. Do not use - * compress.gain because it will include gains that shall not be - * reported to the user when HDR is implemented. - */ - if (frameContext.compress.enable) - effectiveExposureValue *= frameContext.agc.quantizationGain; - - /* The lower 4 bits are fractional and meant to be discarded. */ - Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, - [](uint32_t x) { return x >> 4; }); - - const auto &newEv = agc_.calculateNewEv({ - .traits = AgcTraits{ - { params->ae.exp_mean, context.hw.numAeCells }, - meteringModes_.at(frameContext.agc.meteringMode), - }, - .yHist = hist, - .effectiveExposureValue = effectiveExposureValue, - .constraintModeIndex = frameContext.agc.constraintMode, - .exposureModeIndex = frameContext.agc.exposureMode, - .lux = frameContext.lux.lux, - .exposureCompensation = pow(2.0, frameContext.agc.exposureValue), - }); - - LOG(RkISP1Agc, Debug) - << "Divided up exposure time, analogue gain, quantization gain" - << " and digital gain are " << newEv.exposureTime << ", " << newEv.analogueGain - << ", " << newEv.quantizationGain << " and " << newEv.digitalGain; - - IPAActiveState &activeState = context.activeState; - /* Update the estimated exposure and gain. */ - activeState.agc.automatic.exposure = newEv.exposureTime / lineDuration; - activeState.agc.automatic.gain = newEv.analogueGain; - activeState.agc.automatic.quantizationGain = newEv.quantizationGain; - activeState.agc.automatic.yTarget = newEv.yTarget; - /* - * Expand the target frame duration so that we do not run faster than - * the minimum frame duration when we have short exposures. - */ - processFrameDuration(context, frameContext, - std::max(frameContext.agc.minFrameDuration, newEv.exposureTime)); - - fillMetadata(context, frameContext, metadata); + metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h index 0527ca0d5f..3a4d7bc546 100644 --- a/src/ipa/rkisp1/algorithms/agc.h +++ b/src/ipa/rkisp1/algorithms/agc.h @@ -14,7 +14,7 @@ #include -#include "libipa/agc_mean_luminance.h" +#include "libipa/agc.h" #include "algorithm.h" @@ -47,14 +47,8 @@ private: uint8_t computeHistogramPredivider(const Size &size, enum rkisp1_cif_isp_histogram_mode mode); - void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, - ControlList &metadata); - void processFrameDuration(IPAContext &context, - IPAFrameContext &frameContext, - utils::Duration frameDuration); - std::map> meteringModes_; - AgcMeanLuminance agc_; + AgcAlgorithm agc_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp index 86e46c492f..ce6928a55d 100644 --- a/src/ipa/rkisp1/algorithms/lux.cpp +++ b/src/ipa/rkisp1/algorithms/lux.cpp @@ -74,7 +74,7 @@ void Lux::process(IPAContext &context, if (!stats) return; - utils::Duration exposureTime = context.configuration.sensor.lineDuration * + utils::Duration exposureTime = context.configuration.agc.lineDuration * frameContext.sensor.exposure; double gain = frameContext.sensor.gain; diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp index 1f94afda6b..47691674ad 100644 --- a/src/ipa/rkisp1/ipa_context.cpp +++ b/src/ipa/rkisp1/ipa_context.cpp @@ -86,21 +86,6 @@ namespace libcamera::ipa::rkisp1 { * \var IPASessionConfiguration::sensor * \brief Sensor-specific configuration of the IPA * - * \var IPASessionConfiguration::sensor.minExposureTime - * \brief Minimum exposure time supported with the sensor - * - * \var IPASessionConfiguration::sensor.maxExposureTime - * \brief Maximum exposure time supported with the sensor - * - * \var IPASessionConfiguration::sensor.minAnalogueGain - * \brief Minimum analogue gain supported with the sensor - * - * \var IPASessionConfiguration::sensor.maxAnalogueGain - * \brief Maximum analogue gain supported with the sensor - * - * \var IPASessionConfiguration::sensor.lineDuration - * \brief Line duration in microseconds - * * \var IPASessionConfiguration::sensor.size * \brief Sensor output resolution */ @@ -147,49 +132,8 @@ namespace libcamera::ipa::rkisp1 { * \var IPAActiveState::agc * \brief State for the Automatic Gain Control algorithm * - * The \a automatic variables track the latest values computed by algorithm - * based on the latest processed statistics. All other variables track the - * consolidated controls requested in queued requests. - * - * \struct IPAActiveState::agc.manual - * \brief Manual exposure time and analog gain (set through requests) - * - * \var IPAActiveState::agc.manual.exposure - * \brief Manual exposure time expressed as a number of lines as set by the - * ExposureTime control - * - * \var IPAActiveState::agc.manual.gain - * \brief Manual analogue gain as set by the AnalogueGain control - * - * \struct IPAActiveState::agc.automatic - * \brief Automatic exposure time and analog gain (computed by the algorithm) - * - * \var IPAActiveState::agc.automatic.exposure - * \brief Automatic exposure time expressed as a number of lines - * - * \var IPAActiveState::agc.automatic.gain - * \brief Automatic analogue gain multiplier - * - * \var IPAActiveState::agc.autoExposureEnabled - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control - * - * \var IPAActiveState::agc.autoGainEnabled - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control - * - * \var IPAActiveState::agc.constraintMode - * \brief Constraint mode as set by the AeConstraintMode control - * - * \var IPAActiveState::agc.exposureMode - * \brief Exposure mode as set by the AeExposureMode control - * * \var IPAActiveState::agc.meteringMode * \brief Metering mode as set by the AeMeteringMode control - * - * \var IPAActiveState::agc.minFrameDuration - * \brief Minimum frame duration as set by the FrameDurationLimits control - * - * \var IPAActiveState::agc.maxFrameDuration - * \brief Maximum frame duration as set by the FrameDurationLimits control */ /** @@ -314,53 +258,11 @@ namespace libcamera::ipa::rkisp1 { * the vertical blanking period is determined to maintain a consistent frame * rate matched to the FrameDurationLimits as set by the user. * - * \var IPAFrameContext::agc.exposure - * \brief Exposure time expressed as a number of lines computed by the algorithm - * - * \var IPAFrameContext::agc.gain - * \brief Analogue gain multiplier computed by the algorithm - * - * The gain should be adapted to the sensor specific gain code before applying. - * - * \var IPAFrameContext::agc.vblank - * \brief Vertical blanking parameter computed by the algorithm - * - * \var IPAFrameContext::agc.autoExposureEnabled - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control - * - * \var IPAFrameContext::agc.autoGainEnabled - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control - * - * \var IPAFrameContext::agc.constraintMode - * \brief Constraint mode as set by the AeConstraintMode control - * - * \var IPAFrameContext::agc.exposureMode - * \brief Exposure mode as set by the AeExposureMode control - * * \var IPAFrameContext::agc.meteringMode * \brief Metering mode as set by the AeMeteringMode control * - * \var IPAFrameContext::agc.minFrameDuration - * \brief Minimum frame duration as set by the FrameDurationLimits control - * - * \var IPAFrameContext::agc.maxFrameDuration - * \brief Maximum frame duration as set by the FrameDurationLimits control - * - * \var IPAFrameContext::agc.frameDuration - * \brief The actual FrameDuration used by the algorithm for the frame - * * \var IPAFrameContext::agc.updateMetering * \brief Indicate if new ISP AGC metering parameters need to be applied - * - * \var IPAFrameContext::agc.autoExposureModeChange - * \brief Indicate if autoExposureEnabled has changed from true in the previous - * frame to false in the current frame, and no manual exposure value has been - * supplied in the current frame. - * - * \var IPAFrameContext::agc.autoGainModeChange - * \brief Indicate if autoGainEnabled has changed from true in the previous - * frame to false in the current frame, and no manual gain value has been - * supplied in the current frame. */ /** diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index 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; diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 98ec5a5748..731a362cee 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -6,8 +6,6 @@ */ #include -#include -#include #include #include @@ -40,8 +38,6 @@ namespace libcamera { LOG_DEFINE_CATEGORY(IPARkISP1) -using namespace std::literals::chrono_literals; - namespace ipa::rkisp1 { /* Maximum number of frame contexts to be held */ From patchwork Mon Aug 17 11:43:23 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27800 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 980EFC3263 for ; Mon, 17 Aug 2026 11:44:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A939C682D0; Mon, 17 Aug 2026 13:44:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="b+PFfzYa"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 34E2968272 for ; Mon, 17 Aug 2026 13:44:00 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F083513BA for ; Mon, 17 Aug 2026 13:42:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966961; bh=ALJBK2FywlPL3L4AxyqZkhvWxGzYI0/KcJw+PJLn3sA=; h=From:To:Subject:Date:In-Reply-To:References:From; b=b+PFfzYakWZ/u21FP0d8P7SgOSumlltrbGnbmcEjcIcVzOVn+xL/1L27jlXnBCEFZ VSyAexUaHiKyykrE5iIfhMzVhcnJqtOQa9S/mnSfQocwx8TiOAPeOPUUnaaic5VudB 8cbz/trA6jU74Ln6Ptu3LCZ2QJTgwJU9PlFrtrPc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 22/47] ipa: libipa: agc: Adjust controls based on "auto" availablity Date: Mon, 17 Aug 2026 13:43:23 +0200 Message-ID: <20260817114349.994123-23-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 --- src/ipa/libipa/agc.cpp | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 3c6b12e452..1d12ec6b1f 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -369,6 +369,11 @@ 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. */ + 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, @@ -390,16 +395,31 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, 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 17 11:43:24 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27801 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 53F74C3301 for ; Mon, 17 Aug 2026 11:44:37 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BB7CD682D6; Mon, 17 Aug 2026 13:44:36 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="U1JQe6Iv"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8903268237 for ; Mon, 17 Aug 2026 13:44:00 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3DA50153F; Mon, 17 Aug 2026 13:42:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966961; bh=YZ8tFmfxDNALDEV2k7giIp45gobv+1/pIbxLgkWsGGE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=U1JQe6IvfDGLNUBwjwXkLtZgeIq4v8Ki0eGiuYlYlTFHVVam3fpoeFX9T+d42mEz7 g9CK+6e2bfdrDM6WFXBOdwRssPM1AK1yh9l2ZiqcTDYsIXGaXi/yCutM6jBqh8NuN9 +/KE2sexuelv/EK1a89AemNbU2YquQ1f9h1DUxz8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 23/47] ipa: libipa: agc: Omit `setLimits()` in `configure()` Date: Mon, 17 Aug 2026 13:43:24 +0200 Message-ID: <20260817114349.994123-24-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 1d12ec6b1f..8c8ea41ea9 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -346,9 +346,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 17 11:43:25 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27802 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 9AA6AC3263 for ; Mon, 17 Aug 2026 11:44:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1AC08682D6; Mon, 17 Aug 2026 13:44:39 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ICsNPP5K"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E00A168247 for ; Mon, 17 Aug 2026 13:44:00 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 90F1E13BA; Mon, 17 Aug 2026 13:42:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966961; bh=2a59vyYZGLi5whpV5DaF3hZEhADJfKC4rRdJx7wk8xE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ICsNPP5K0+O7qiiQP5KQqqhFIkcl/lIJTdoWYC3IiT6kVHhE1xg+6J0JsMw9dSJIG lJ28bgXZlICIajCfhzvbsYR+KRxpVg8vO3TDIroW06VXIDpwkElxr9PAJRQYYaZib9 rEPUTxXw5szK+PvjYiOU5wOvYDZijcqAOjIBCqnk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 24/47] ipa: libipa: agc: Use default exposure Date: Mon, 17 Aug 2026 13:43:25 +0200 Message-ID: <20260817114349.994123-25-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 8c8ea41ea9..88775088e9 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -351,7 +351,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 17 11:43:26 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27805 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 47178C3263 for ; Mon, 17 Aug 2026 11:44:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 96406682B2; Mon, 17 Aug 2026 13:44:44 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="RrbzVyrp"; 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 4A6C86826F for ; Mon, 17 Aug 2026 13:44:01 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EA765177A; Mon, 17 Aug 2026 13:42:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966962; bh=EyUK9o7oiyzaTfFrlNJllIIZqsHcx56UBp3u1m7Y4ZE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RrbzVyrpz2Mz9Rr8QIHNXpRn7eaAh4eZgcq25x5pQFY83NNPNTAsN+FlYvFja+S+E GLOKk3gtYnco4KUnYdFKDK4/yiXkQetNhC0pwMfOe3sVqCu95/ODqOsEgL09q5ZB5M ge7dOofWRNB6z5q+TkALQhumuvPnsCws/QakOSBI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 25/47] ipa: libipa: agc: Use full range as default `FrameDurationLimits` Date: Mon, 17 Aug 2026 13:43:26 +0200 Message-ID: <20260817114349.994123-26-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 88775088e9..5d85cc69e2 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -390,7 +390,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 17 11:43: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: 27803 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 76461C3263 for ; Mon, 17 Aug 2026 11:44:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6FCFC682F0; Mon, 17 Aug 2026 13:44:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="byAJJUKH"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 916F16828F for ; Mon, 17 Aug 2026 13:44:01 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 54432153F; Mon, 17 Aug 2026 13:42:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966962; bh=+fnt9YFBd3O0qRcSVFmPbmmIYE/KlBI82JkBb0AD+dk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=byAJJUKHqWEYSAzXPV7XfAYlVMUZM4vxrwpUKmx/uQEo9ED++wGUPlFgJEZDfQtNC UHwPCzoI1KnAkrAjvg0RdFehp/fd6JE+MCXoJAeisPQF1yVqdLOjeXMua3DIo/Z6tR b+td1MyTltorJ+kg4z26U2h+URcQJvUR2nM9mZYo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 26/47] ipa: libipa: agc: Adjust debug messages slightly Date: Mon, 17 Aug 2026 13:43:27 +0200 Message-ID: <20260817114349.994123-27-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 5d85cc69e2..ea7e095b0d 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -305,8 +305,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. @@ -634,10 +636,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 17 11:43: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: 27806 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 9957CC3263 for ; Mon, 17 Aug 2026 11:44:46 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3247E682A4; Mon, 17 Aug 2026 13:44:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TBxkVYCP"; 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 E3A2F68296 for ; Mon, 17 Aug 2026 13:44:01 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9F0D413BA; Mon, 17 Aug 2026 13:42:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966962; bh=QHSVRI+t3S0x7+YEQ0Cd/195oq4aKngDuFarjTM5w3U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TBxkVYCPtntbSaEmxX/AFZ/pYIoHhXSOg4oQ8WHc5icIsVYyI/QDEXg6roEARwkL9 c/toOQmghN8/H4tco5ULivcFdXqOn9qG7YdsqchmSPHkRuF6fWgIgpzpxUqrIslqIJ 4RPd9ruaTpTsLCf2oXOeWD7vCqgjKouztleU8zwc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 27/47] ipa: libipa: agc: Store digital gain Date: Mon, 17 Aug 2026 13:43:28 +0200 Message-ID: <20260817114349.994123-28-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 --- 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 ea7e095b0d..41c9949f54 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -129,6 +129,9 @@ namespace agc { * \var agc::ActiveState::automatic.quantizationGain * \brief Automatic quantization gain multiplier * + * \var agc::ActiveState::automatic.digitalGain + * \brief Automatic digital gain multiplier + * * \var agc::ActiveState::automatic.yTarget * \brief Automatically determined luminance target * @@ -355,6 +358,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; @@ -633,13 +637,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 66aa0eacb0..612ac539da 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -52,6 +52,7 @@ struct ActiveState { uint32_t exposure; double gain; double quantizationGain; + double digitalGain; double yTarget; } automatic; From patchwork Mon Aug 17 11:43: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: 27804 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 6F769C3263 for ; Mon, 17 Aug 2026 11:44:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BC7F0682AC; Mon, 17 Aug 2026 13:44:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="oL6Mef2n"; 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 43B2868299 for ; Mon, 17 Aug 2026 13:44:02 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E971EB1A; Mon, 17 Aug 2026 13:42:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966963; bh=zZP3sadw5sAzEwSVJPfUmdFn+vYMz0+7iVaPVyOQ4sU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oL6Mef2nscIY09ByyF6Pe0fBQZwz0PSS7Ac6P2101demkVVFInAjN8tDdIEslRZXv DdC+4lYC4806p1zKuk11LJ50tvfhuuS572i+Qp0+FZzZJO6+m2gKR6bBl4Lg2DLeHB 73bmyED/XgtbhORGpbw5Bj+QpPM4Ad3fDdtAIOmU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 28/47] ipa: libipa: agc: Use minimum line length Date: Mon, 17 Aug 2026 13:43:29 +0200 Message-ID: <20260817114349.994123-29-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 41c9949f54..8ae5d61075 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -283,10 +283,11 @@ int AgcAlgorithm::init(const ValueNode &tuningData) 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(); @@ -319,9 +320,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 17 11:43: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: 27812 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 F0E26C3263 for ; Mon, 17 Aug 2026 11:44:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 651B2682F5; Mon, 17 Aug 2026 13:44:54 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="C+/znAXH"; 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 8E3EB68286 for ; Mon, 17 Aug 2026 13:44:02 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 49BE013BA; Mon, 17 Aug 2026 13:42:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966963; bh=0st/IITFTwgPwdXO3zNmZwlXkaTMTIo3rdr2EloJU0A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=C+/znAXHXX9u7ccWx0c+3Iw0LYtF3wt5M+1yOqA8eb7N6sOriwwoda4wD3cjt0b+j 9e5xc6t1EF7iTgY09LS3y6c0KjZLzgFTt3SuMLr5POAl98guvsqwcBCSxwNZyHIj+c fAwvVN6bRz2PiVtW0+UcfDyocl1sYd4F5MHVpMN0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 29/47] ipa: libipa: agc: Fix multiplication type Date: Mon, 17 Aug 2026 13:43:30 +0200 Message-ID: <20260817114349.994123-30-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 8ae5d61075..15c7bb204d 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -330,8 +330,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 17 11:43: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: 27811 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 3F0E5C3301 for ; Mon, 17 Aug 2026 11:44:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8FCD7682E7; Mon, 17 Aug 2026 13:44:52 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="PTFcoc2K"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D90B668255 for ; Mon, 17 Aug 2026 13:44:02 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9E5A3B1A; Mon, 17 Aug 2026 13:42:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966963; bh=fc46clNUI8GNw609SHBxssVFvm+8mOvrOqhF1EJfveo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PTFcoc2KkIzRMWv/3jZ0e/a/Nr2CPdTKDqjY6MkhoUZC2tmaImN4fjkIbLlgI/jgc wjJVoBmNasDFCXA0CQUonrQvVwmD9jgzf0fvgW5xiMCsAbNxYlBKHpkd8SUlohwmhA mI+6hzAjGntRSYXnUcWQ3ohphj4UzgMuVHN0Za0k= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 30/47] ipa: mali-c55: agc: Port to `AgcAlgorithm` Date: Mon, 17 Aug 2026 13:43:31 +0200 Message-ID: <20260817114349.994123-31-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Use the `AgcAlgorithm` class to implement the mali-c55 agc algorithm. Closes: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/262 Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/mali-c55/algorithms/agc.cpp | 131 ++++++---------------- src/ipa/mali-c55/algorithms/agc.h | 4 +- src/ipa/mali-c55/ipa_context.h | 34 +++--- src/ipa/mali-c55/mali-c55.cpp | 161 +++++----------------------- 4 files changed, 74 insertions(+), 256 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 821f43e9fd..4b675eb7e5 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -13,7 +13,6 @@ #include #include -#include #include "libipa/colours.h" @@ -123,12 +122,19 @@ Agc::Agc() int Agc::init(IPAContext &context, const ValueNode &tuningData) { - int ret = agc_.parseTuningData(tuningData); + int ret = agc_.init(tuningData); if (ret) return ret; - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); - context.ctrlMap.merge(agc_.controls()); + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, // \todo if not raw? + }); + if (ret) + return ret; return 0; } @@ -140,79 +146,24 @@ int Agc::configure(IPAContext &context, if (ret) return ret; - /* - * Defaults; we use whatever the sensor's default exposure is and the - * minimum analogue gain. AEGC is _active_ by default. - */ - context.activeState.agc.autoEnabled = true; - context.activeState.agc.automatic.sensorGain = context.configuration.agc.minAnalogueGain; - context.activeState.agc.automatic.exposure = context.configuration.agc.defaultExposure; - context.activeState.agc.manual.sensorGain = context.configuration.agc.minAnalogueGain; - context.activeState.agc.manual.exposure = context.configuration.agc.defaultExposure; - context.activeState.agc.constraintMode = agc_.constraintModes().begin()->first; - context.activeState.agc.exposureMode = agc_.exposureModeHelpers().begin()->first; - - /* \todo Run this again when FrameDurationLimits is passed in */ - agc_.setLimits(context.configuration.agc.minShutterSpeed, - context.configuration.agc.maxShutterSpeed, - context.configuration.agc.minAnalogueGain, - context.configuration.agc.maxAnalogueGain, - {}); - - agc_.resetFrameCount(); + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, // \todo if not raw? + }); + if (ret) + return ret; return 0; } -void Agc::queueRequest(IPAContext &context, const uint32_t frame, - [[maybe_unused]] IPAFrameContext &frameContext, +void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) { - auto &agc = context.activeState.agc; - - const auto &constraintMode = controls.get(controls::AeConstraintMode); - agc.constraintMode = constraintMode.value_or(agc.constraintMode); - - const auto &exposureMode = controls.get(controls::AeExposureMode); - agc.exposureMode = exposureMode.value_or(agc.exposureMode); - - const auto &agcEnable = controls.get(controls::AeEnable); - if (agcEnable && *agcEnable != agc.autoEnabled) { - agc.autoEnabled = *agcEnable; - - LOG(MaliC55Agc, Info) - << (agc.autoEnabled ? "Enabling" : "Disabling") - << " AGC"; - } - - /* - * If the automatic exposure and gain is enabled we have no further work - * to do here... - */ - if (agc.autoEnabled) - return; - - /* - * ...otherwise we need to look for exposure and gain controls and use - * those to set the activeState. - */ - const auto &exposure = controls.get(controls::ExposureTime); - if (exposure) { - agc.manual.exposure = *exposure * 1.0us / context.configuration.sensor.lineDuration; - - LOG(MaliC55Agc, Debug) - << "Exposure set to " << agc.manual.exposure - << " on request sequence " << frame; - } - - const auto &analogueGain = controls.get(controls::AnalogueGain); - if (analogueGain) { - agc.manual.sensorGain = *analogueGain; - - LOG(MaliC55Agc, Debug) - << "Analogue gain set to " << agc.manual.sensorGain - << " on request sequence " << frame; - } + agc_.queueRequest(context.configuration.agc, context.activeState.agc, frameContext.agc, controls); } void Agc::fillParamsBuffer(MaliC55Params *params, enum MaliC55Blocks type) @@ -265,9 +216,11 @@ void Agc::fillWeightsArrayBuffer(MaliC55Params *params, const enum MaliC55Blocks std::fill(weights.begin(), weights.end(), 1); } -void Agc::prepare([[maybe_unused]] IPAContext &context, const uint32_t frame, - [[maybe_unused]] IPAFrameContext &frameContext, MaliC55Params *params) +void Agc::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, MaliC55Params *params) { + agc_.prepare(context.activeState.agc, frameContext.agc); + if (frame > 0) return; @@ -308,11 +261,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, - [[maybe_unused]] ControlList &metadata) + ControlList &metadata) { - IPASessionConfiguration &configuration = context.configuration; - IPAActiveState &activeState = context.activeState; - if (!stats) { LOG(MaliC55Agc, Error) << "No statistics buffer passed to Agc"; return; @@ -323,32 +273,13 @@ void Agc::process(IPAContext &context, statistics_.gHist.interQuantileMean(0, 1), statistics_.bHist.interQuantileMean(0, 1) } }); - /* - * The Agc algorithm needs to know the effective exposure value that was - * applied to the sensor when the statistics were collected. - */ - uint32_t exposure = frameContext.agc.exposure; - double analogueGain = frameContext.agc.sensorGain; - utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; - utils::Duration effectiveExposureValue = currentShutter * analogueGain; - - const auto &newEv = agc_.calculateNewEv({ + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ .traits = AgcTraits(statistics_), .yHist = statistics_.yHist, - .effectiveExposureValue = effectiveExposureValue, - .constraintModeIndex = activeState.agc.constraintMode, - .exposureModeIndex = activeState.agc.exposureMode, - }); - - LOG(MaliC55Agc, Debug) - << "Divided up shutter, analogue gain and digital gain are " - << newEv.exposureTime << ", " << newEv.analogueGain << " and " << newEv.digitalGain; - - activeState.agc.automatic.exposure = newEv.exposureTime / configuration.sensor.lineDuration; - activeState.agc.automatic.sensorGain = newEv.analogueGain; + .exposure = frameContext.sensor.exposure, + .gain = frameContext.sensor.gain, + }}, metadata); - metadata.set(controls::ExposureTime, currentShutter.get()); - metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain); metadata.set(controls::ColourTemperature, context.activeState.agc.temperatureK); } diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h index ec7fe28c17..809a57c53a 100644 --- a/src/ipa/mali-c55/algorithms/agc.h +++ b/src/ipa/mali-c55/algorithms/agc.h @@ -11,7 +11,7 @@ #include "libcamera/internal/bayer_format.h" -#include "libipa/agc_mean_luminance.h" +#include "libipa/agc.h" #include "libipa/histogram.h" #include "algorithm.h" @@ -68,7 +68,7 @@ private: void fillWeightsArrayBuffer(MaliC55Params *params, enum MaliC55Blocks type); AgcStatistics statistics_; - AgcMeanLuminance agc_; + AgcAlgorithm agc_; }; } /* namespace ipa::mali_c55::algorithms */ diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index 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 a13146d8a6..b4984b285a 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 17 11:43: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: 27808 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 74097C3263 for ; Mon, 17 Aug 2026 11:44:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 02CAC68257; Mon, 17 Aug 2026 13:44:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="c1tiCHP9"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4AD436826E for ; Mon, 17 Aug 2026 13:44:03 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EA4C1153F; Mon, 17 Aug 2026 13:42:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966964; bh=x5hy4qJ2JM8ijxW20QY6dDpCDgViM6Qt2J5Rkd6N8x4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=c1tiCHP9I5We7tWyXP4DIOz5DWZar6lHqN6STD+gGMl6SuZXa91T8NYn2a3MGHC4Z uStu+wNZjWxEYbqr7la6DjdE6qmSF2Lhbd1Vq5MNKd7qNbSoPhUYXhFfHy5FKaunMS Kd2qLa2Xv//o1p71TI+ETPx7HfSvRD/aXKWCV7qg= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 31/47] ipa: ipu3: agc: Port to `AgcAlgorithm` Date: Mon, 17 Aug 2026 13:43:32 +0200 Message-ID: <20260817114349.994123-32-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 | 108 ++++++++++--------------- src/ipa/ipu3/algorithms/agc.h | 15 ++-- src/ipa/ipu3/ipa_context.cpp | 49 +++--------- src/ipa/ipu3/ipa_context.h | 27 +++---- src/ipa/ipu3/ipu3.cpp | 136 +++++--------------------------- 5 files changed, 92 insertions(+), 243 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index e74db62960..c9ea02ed00 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -8,7 +8,6 @@ #include "agc.h" #include -#include #include #include @@ -48,17 +47,10 @@ namespace ipa::ipu3::algorithms { LOG_DEFINE_CATEGORY(IPU3Agc) -/* Minimum limit for analogue gain value */ -static constexpr double kMinAnalogueGain = 1.0; - -/* \todo Honour the FrameDurationLimits control instead of hardcoding a limit */ -static constexpr utils::Duration kMaxExposureTime = 60ms; - /* Histogram constants */ static constexpr uint32_t knumHistogramBins = 256; Agc::Agc() - : minExposureTime_(0s), maxExposureTime_(0s) { } @@ -76,11 +68,18 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.parseTuningData(tuningData); + ret = agc_.init(tuningData); if (ret) return ret; - context.ctrlMap.merge(agc_.controls()); + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); + if (ret) + return ret; return 0; } @@ -95,32 +94,36 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - const IPASessionConfiguration &configuration = context.configuration; - IPAActiveState &activeState = context.activeState; - - stride_ = configuration.grid.stride; - bdsGrid_ = configuration.grid.bdsGrid; - - minExposureTime_ = configuration.agc.minExposureTime; - maxExposureTime_ = std::min(configuration.agc.maxExposureTime, - kMaxExposureTime); - - minAnalogueGain_ = std::max(configuration.agc.minAnalogueGain, kMinAnalogueGain); - maxAnalogueGain_ = configuration.agc.maxAnalogueGain; - - /* Configure the default exposure and gain. */ - activeState.agc.gain = minAnalogueGain_; - activeState.agc.exposure = 10ms / configuration.sensor.lineDuration; - - context.activeState.agc.constraintMode = agc_.constraintModes().begin()->first; - context.activeState.agc.exposureMode = agc_.exposureModeHelpers().begin()->first; + stride_ = context.configuration.grid.stride; + bdsGrid_ = context.configuration.grid.bdsGrid; + + return agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, // \todo if not raw? + }); +} - /* \todo Run this again when FrameDurationLimits is passed in */ - agc_.setLimits(minExposureTime_, maxExposureTime_, minAnalogueGain_, - maxAnalogueGain_, {}); - agc_.resetFrameCount(); +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) +{ + agc_.queueRequest(context.configuration.agc, context.activeState.agc, + frameContext.agc, controls); +} - return 0; +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Agc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] ipu3_uapi_params *params) +{ + agc_.prepare(context.activeState.agc, frameContext.agc); } Histogram Agc::parseStatistics(const ipu3_uapi_stats_3a *stats, @@ -228,16 +231,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, { Histogram hist = parseStatistics(stats, context.configuration.grid.bdsGrid); - /* - * The Agc algorithm needs to know the effective exposure value that was - * applied to the sensor when the statistics were collected. - */ - utils::Duration exposureTime = context.configuration.sensor.lineDuration - * frameContext.sensor.exposure; - double analogueGain = frameContext.sensor.gain; - utils::Duration effectiveExposureValue = exposureTime * analogueGain; - - const auto &newEv = agc_.calculateNewEv({ + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ .traits = AgcTraits{ rgbTriples_, {{ @@ -248,29 +242,9 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, bdsGrid_, }, .yHist = hist, - .effectiveExposureValue = effectiveExposureValue, - .constraintModeIndex = context.activeState.agc.constraintMode, - .exposureModeIndex = context.activeState.agc.exposureMode, - }); - - LOG(IPU3Agc, Debug) - << "Divided up exposure time, analogue gain and digital gain are " - << newEv.exposureTime << ", " << newEv.analogueGain << " and " << newEv.digitalGain; - - IPAActiveState &activeState = context.activeState; - /* Update the estimated exposure time and gain. */ - activeState.agc.exposure = newEv.exposureTime / context.configuration.sensor.lineDuration; - activeState.agc.gain = newEv.analogueGain; - - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); - metadata.set(controls::ExposureTime, exposureTime.get()); - - /* \todo Use VBlank value calculated from each frame exposure. */ - uint32_t vTotal = context.configuration.sensor.size.height - + context.configuration.sensor.defVBlank; - utils::Duration frameDuration = context.configuration.sensor.lineDuration - * vTotal; - metadata.set(controls::FrameDuration, frameDuration.get()); + .exposure = frameContext.sensor.exposure, + .gain = frameContext.sensor.gain, + }}, metadata); } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/ipu3/algorithms/agc.h b/src/ipa/ipu3/algorithms/agc.h index d08da7600e..634b1f8a7a 100644 --- a/src/ipa/ipu3/algorithms/agc.h +++ b/src/ipa/ipu3/algorithms/agc.h @@ -13,7 +13,7 @@ #include -#include "libipa/agc_mean_luminance.h" +#include "libipa/agc.h" #include "libipa/histogram.h" #include "algorithm.h" @@ -32,6 +32,11 @@ public: int init(IPAContext &context, const ValueNode &tuningData) override; int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + ipu3_uapi_params *params) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const ipu3_uapi_stats_3a *stats, @@ -41,17 +46,11 @@ private: Histogram parseStatistics(const ipu3_uapi_stats_3a *stats, const ipu3_uapi_grid_config &grid); - utils::Duration minExposureTime_; - utils::Duration maxExposureTime_; - - double minAnalogueGain_; - double maxAnalogueGain_; - uint32_t stride_; ipu3_uapi_grid_config bdsGrid_; std::vector> rgbTriples_; - AgcMeanLuminance agc_; + AgcAlgorithm agc_; }; } /* namespace ipa::ipu3::algorithms */ diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp index 3b22f79176..c358b09981 100644 --- a/src/ipa/ipu3/ipa_context.cpp +++ b/src/ipa/ipu3/ipa_context.cpp @@ -46,12 +46,21 @@ namespace libcamera::ipa::ipu3 { * \var IPAContext::configuration * \brief The IPA session configuration, immutable during the session * + * \var IPAContext::sensorInfo + * \brief The IPA camera session details, immutable during the session + * + * \var IPAContext::sensorControls + * \brief The camera sensor controls, immutable during the session + * * \var IPAContext::frameContexts * \brief Ring buffer of the IPAFrameContext(s) * * \var IPAContext::activeState * \brief The current state of IPA algorithms * + * \var IPAContext::camHelper + * \brief The camera sensor helper + * * \var IPAContext::ctrlMap * \brief A ControlInfoMap::Map of controls populated by the algorithms */ @@ -95,48 +104,11 @@ namespace libcamera::ipa::ipu3 { /** * \var IPASessionConfiguration::agc * \brief AGC parameters configuration of the IPA - * - * \var IPASessionConfiguration::agc.minExposureTime - * \brief Minimum exposure time supported with the configured sensor - * - * \var IPASessionConfiguration::agc.maxExposureTime - * \brief Maximum exposure time supported with the configured sensor - * - * \var IPASessionConfiguration::agc.minAnalogueGain - * \brief Minimum analogue gain supported with the configured sensor - * - * \var IPASessionConfiguration::agc.maxAnalogueGain - * \brief Maximum analogue gain supported with the configured sensor - */ - -/** - * \var IPASessionConfiguration::sensor - * \brief Sensor-specific configuration of the IPA - * - * \var IPASessionConfiguration::sensor.lineDuration - * \brief Line duration in microseconds - * - * \var IPASessionConfiguration::sensor.defVBlank - * \brief The default vblank value of the sensor - * - * \var IPASessionConfiguration::sensor.size - * \brief Sensor output resolution */ /** * \var IPAActiveState::agc * \brief Context for the Automatic Gain Control algorithm - * - * The exposure and gain determined are expected to be applied to the sensor - * at the earliest opportunity. - * - * \var IPAActiveState::agc.exposure - * \brief Exposure time expressed as a number of lines - * - * \var IPAActiveState::agc.gain - * \brief Analogue gain multiplier - * - * The gain should be adapted to the sensor specific gain code before applying. */ /** @@ -185,6 +157,9 @@ namespace libcamera::ipa::ipu3 { * * \var IPAFrameContext::sensor.gain * \brief Analogue gain multiplier + * + * \var IPAFrameContext::agc + * \brief Per-frame state for the AGC algorithm */ } /* namespace libcamera::ipa::ipu3 */ diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h index 97fcf06cd4..51133b10d5 100644 --- a/src/ipa/ipu3/ipa_context.h +++ b/src/ipa/ipu3/ipa_context.h @@ -15,6 +15,8 @@ #include #include +#include +#include #include namespace libcamera { @@ -32,18 +34,8 @@ struct IPASessionConfiguration { ipu3_uapi_grid_config afGrid; } af; - struct { - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; + struct Agc : agc::Session { } agc; - - struct { - int32_t defVBlank; - utils::Duration lineDuration; - Size size; - } sensor; }; struct IPAActiveState { @@ -53,11 +45,7 @@ struct IPAActiveState { bool stable; } af; - struct { - uint32_t exposure; - double gain; - uint32_t constraintMode; - uint32_t exposureMode; + struct Agc : agc::ActiveState { } agc; struct { @@ -81,6 +69,9 @@ struct IPAFrameContext : public FrameContext { uint32_t exposure; double gain; } sensor; + + struct Agc : agc::FrameContext { + } agc; }; struct IPAContext { @@ -90,10 +81,14 @@ struct IPAContext { } IPASessionConfiguration configuration; + IPACameraSensorInfo sensorInfo; + ControlInfoMap sensorControls; IPAActiveState activeState; FCQueue frameContexts; + std::unique_ptr camHelper; + ControlInfoMap::Map ctrlMap; }; diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp index b7897269bb..d1c69fa4cc 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 17 11:43: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: 27809 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 6026BC3301 for ; Mon, 17 Aug 2026 11:44:49 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D0CBD682A5; Mon, 17 Aug 2026 13:44:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="dZa6/2Wh"; 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 BC05468259 for ; Mon, 17 Aug 2026 13:44:03 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5993DB1A; Mon, 17 Aug 2026 13:42:44 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966964; bh=Blc9egI4kyzc0qyoczRSH0+n7FhJGOeoyuZf8JQCfRQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=dZa6/2Wh8PJDEyFGK8DSdxbbHCCk1W3JtPbS1k0BVBLlMVDhyPB+ERcm5vqf4zsuZ 7TkvRM25cVcxV8LOqacsRyFZKhrYQY+CsYwvkV5KAVOY3OT/D8A0XOmuCq80PGLoq6 t6LuUN68C+Hp7JHvX68MMrAHJlYxC99uFBGRRRkQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 32/47] ipa: libipa: agc_mean_luminance: Remove control handling Date: Mon, 17 Aug 2026 13:43:33 +0200 Message-ID: <20260817114349.994123-33-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The `AgcMeanLuminance` type does not do any actual control handling, so let's remove the `ControlInfoMap::Map` member, and let the user handle that using the `constraintModes()` / `exposureModeHelpers()` getters. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 21 ++++++++++++++++++--- src/ipa/libipa/agc_mean_luminance.cpp | 21 +-------------------- src/ipa/libipa/agc_mean_luminance.h | 8 -------- 3 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 15c7bb204d..ff1e8d5a84 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -372,8 +372,8 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, /* The IPA control maps keep their states, so the removal is necessary. */ 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{ @@ -419,7 +419,22 @@ 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); + } + + { + std::vector options; + 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 17 11:43: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: 27807 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 662E7C3301 for ; Mon, 17 Aug 2026 11:44:47 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0E8AE682B2; Mon, 17 Aug 2026 13:44:47 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="K0T/jRSN"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 218396825A for ; Mon, 17 Aug 2026 13:44:04 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C4441177A; Mon, 17 Aug 2026 13:42:44 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966965; bh=sGWVs16e22YXlBaXuoRI+yweGXGjcu96Hgc6u+i9u68=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=K0T/jRSNy+H6gqKnOvBtjtfISfAJWkvQjyUOo/ZGsE+8zxp/+4n80d3P+bdVMLtTF krHzcWxP+46uktYPFBLfA6WDbnGqQBrl9mKAJS+8/6MebhSFcnbAKbgnbglCxDtJf6 GGk48ej/hUIefMtAbcJja5O5m3bgaxOc9TIdiWvI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH v5 33/47] ipa: libipa: histogram: total(): Use `back()` Date: Mon, 17 Aug 2026 13:43:34 +0200 Message-ID: <20260817114349.994123-34-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 17 11:43: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: 27810 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 65BB6C3263 for ; Mon, 17 Aug 2026 11:44:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9EB23682FE; Mon, 17 Aug 2026 13:44:50 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gnpf5E14"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6A07F682AC for ; Mon, 17 Aug 2026 13:44:04 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 27C8A153F; Mon, 17 Aug 2026 13:42:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966965; bh=Ls7oCRtmwiFmC2UtoV7UpbJ7e4NJuUoOjQF7WXt5U18=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gnpf5E14Y4qOWND1nAiEXJnCNaIQtgVQwBKDYC2YoquX6BMfwM8t/oXYs011v2bPa V0rIEBmxpTs03dQIP9GFfBSqVIvw+3XGZmg9oHbmny7sTOvlbFEvZNeyYlHi8B/emg FFKsUCmUhdAngEGxEkuqObck/Y+4NBHg3bAJekok= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 34/47] ipa: libipa: histogram: Add `operator[]` to query bin Date: Mon, 17 Aug 2026 13:43:35 +0200 Message-ID: <20260817114349.994123-35-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 --- src/ipa/libipa/histogram.cpp | 6 ++++++ src/ipa/libipa/histogram.h | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/src/ipa/libipa/histogram.cpp b/src/ipa/libipa/histogram.cpp index bcf2639082..2f91ffb6d7 100644 --- a/src/ipa/libipa/histogram.cpp +++ b/src/ipa/libipa/histogram.cpp @@ -74,6 +74,12 @@ Histogram::Histogram(Span data) * \return Number of values */ +/** + * \fn Histogram::operator[](size_t bin) const + * \brief Retrieve the value for \a bin + * \return The value in \a bin, or 0 if \a bin is out of range + */ + /** * \brief Cumulative frequency up to a (fractional) point in a bin * \param[in] bin The bin up to which to cumulate diff --git a/src/ipa/libipa/histogram.h b/src/ipa/libipa/histogram.h index 0c02dda2ba..b3f59b7c31 100644 --- a/src/ipa/libipa/histogram.h +++ b/src/ipa/libipa/histogram.h @@ -42,6 +42,13 @@ public: double quantile(double q, uint32_t first = 0, uint32_t last = UINT_MAX) const; double interQuantileMean(double lowQuantile, double hiQuantile) const; + uint32_t operator[](size_t bin) const + { + return bin < bins() + ? cumulative_[bin + 1] - cumulative_[bin] + : 0; + } + private: std::vector cumulative_; }; From patchwork Mon Aug 17 11:43: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: 27813 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 AFD42C3263 for ; Mon, 17 Aug 2026 11:44:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 03387682F0; Mon, 17 Aug 2026 13:44:56 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="s8k5IzHU"; 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 C4CEE682A4 for ; Mon, 17 Aug 2026 13:44:04 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 74840B1A; Mon, 17 Aug 2026 13:42:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966965; bh=RdHUprAh79Ep5t8C6D5rNqBymTLIafidc7RJWV3nmh4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=s8k5IzHU6128IbDEWmqaEyNZr9sIkn1ksFnQkjyN9oJalBoP3DXc/142z50h1LdoM n6kExGUF7kXiLZWIHmqwYI4xfBko2R7Dhsl5ZKanMzllznJXXCFaC7J/sLN2ai/fk5 uRST0okwc144U4oEG5OKEsSZpbZ/gYMG+db6n8lk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Stefan Klug Subject: [PATCH v5 35/47] ipa: simple: Store exposure as `uint32_t` Date: Mon, 17 Aug 2026 13:43:36 +0200 Message-ID: <20260817114349.994123-36-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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/simple/algorithms/agc.cpp | 6 +++--- src/ipa/simple/ipa_context.h | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index e9bcb2c032..93505f7665 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -67,7 +67,7 @@ Agc::Agc() void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV) { - int32_t exposure = frameContext.sensor.exposure; + uint32_t exposure = frameContext.sensor.exposure; double again = frameContext.sensor.gain; double error = kExposureOptimal - exposureMSV; @@ -87,7 +87,7 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou if (factor > 1.0f) { /* Scene too dark: increase exposure first, then gain. */ if (exposure < context.configuration.agc.exposureMax) { - int32_t next = static_cast(exposure * factor); + uint32_t next = exposure * factor; exposure = std::max(next, exposure + 1); } else { double next = again * factor; @@ -105,7 +105,7 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou else again = next; } else { - int32_t next = static_cast(exposure * factor); + uint32_t next = exposure * factor; exposure = std::min(next, exposure - 1); } } diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 71dc22e899..910947b814 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -28,7 +28,7 @@ namespace ipa::soft { 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 17 11:43: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: 27816 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 7616FC3301 for ; Mon, 17 Aug 2026 11:45:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E1F71682DE; Mon, 17 Aug 2026 13:45:00 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Ha2ypu4K"; 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 2EB3C682AE for ; Mon, 17 Aug 2026 13:44:05 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CF6E419C6; Mon, 17 Aug 2026 13:42:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966966; bh=s+TfB73NMyY6mFIPNaZmr8rxPSI3czMM68jccT9MVlc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Ha2ypu4KQPqrYxz4uMTau2VhsTx3I3tt/Tfw35pR4PiETT/y45kVUCOZ6cUxghY9A cORt8BLNs18kJqo8vjwpZJtDrNkQa7jrnJTUBgwH1qBaefSCiM+x+u8x8gUG5fLpbS wapSx/+lfo5RI0F6/Tb/lTVgHufdY39BtScg3QTY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal Subject: [PATCH v5 36/47] ipa: simple: agc: Separate MSV calculation Date: Mon, 17 Aug 2026 13:43:37 +0200 Message-ID: <20260817114349.994123-37-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 --- src/ipa/simple/algorithms/agc.cpp | 72 ++++++++++++++++++------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index 93505f7665..f959abeba5 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -61,6 +62,44 @@ static constexpr float kExpProportionalGain = 0.04; */ static constexpr float kExpMaxStep = 0.15; +namespace { + +std::optional calculateMSV(const SwIspStats::Histogram &histogram, uint8_t blackLevel) +{ + /* + * Calculate Mean Sample Value (MSV) according to formula from: + * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf + */ + const unsigned int blackLevelHistIdx = + blackLevel / (256 / SwIspStats::kYHistogramSize); + const unsigned int histogramSize = + SwIspStats::kYHistogramSize - blackLevelHistIdx; + const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount; + const unsigned int yHistValsPerBinMod = + histogramSize / (histogramSize % kExposureBinsCount + 1); + int exposureBins[kExposureBinsCount] = {}; + unsigned int denom = 0; + unsigned int num = 0; + + if (yHistValsPerBin == 0) + return {}; + + for (unsigned int i = 0; i < histogramSize; i++) { + unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin; + exposureBins[idx] += histogram[blackLevelHistIdx + i]; + } + + for (unsigned int i = 0; i < kExposureBinsCount; i++) { + LOG(IPASoftExposure, Debug) << i << ": " << exposureBins[i]; + denom += exposureBins[i]; + num += exposureBins[i] * (i + 1); + } + + return (denom == 0 ? 0 : static_cast(num) / denom); +} + +} /* namespace */ + Agc::Agc() { } @@ -158,41 +197,14 @@ void Agc::process(IPAContext &context, return; } - /* - * Calculate Mean Sample Value (MSV) according to formula from: - * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf - */ - const auto &histogram = stats->yHistogram; - const unsigned int blackLevelHistIdx = - context.activeState.blc.level / (256 / SwIspStats::kYHistogramSize); - const unsigned int histogramSize = - SwIspStats::kYHistogramSize - blackLevelHistIdx; - const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount; - const unsigned int yHistValsPerBinMod = - histogramSize / (histogramSize % kExposureBinsCount + 1); - int exposureBins[kExposureBinsCount] = {}; - unsigned int denom = 0; - unsigned int num = 0; - - if (yHistValsPerBin == 0) { + auto exposureMSV = calculateMSV(stats->yHistogram, context.activeState.blc.level); + if (!exposureMSV) { LOG(IPASoftExposure, Debug) << "Not adjusting exposure due to insufficient histogram data"; return; } - for (unsigned int i = 0; i < histogramSize; i++) { - unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin; - exposureBins[idx] += histogram[blackLevelHistIdx + i]; - } - - for (unsigned int i = 0; i < kExposureBinsCount; i++) { - LOG(IPASoftExposure, Debug) << i << ": " << exposureBins[i]; - denom += exposureBins[i]; - num += exposureBins[i] * (i + 1); - } - - float exposureMSV = (denom == 0 ? 0 : static_cast(num) / denom); - updateExposure(context, frameContext, exposureMSV); + updateExposure(context, frameContext, *exposureMSV); } REGISTER_IPA_ALGORITHM(Agc, "Agc") From patchwork Mon Aug 17 11:43: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: 27814 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 54E2AC3263 for ; Mon, 17 Aug 2026 11:44:58 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BFBB168302; Mon, 17 Aug 2026 13:44:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="uUUyFLrv"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 79640682AF for ; Mon, 17 Aug 2026 13:44:05 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2FCEF153F; Mon, 17 Aug 2026 13:42:46 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966966; bh=pae+f7DwlbyuKO84NamEy9pcgNm/SQ1TixpP7MIJkbc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uUUyFLrv2S0Y25wXt4iVN/9K3aPIj1n0vEStGMlDFVV0ML2+2hIP1hoYChXtHRbU7 iyl1NyUDkSk5Ok5j9VNDU9Ab9eZxDVzlsigQgFx3AkxGakY9qMjGw2Nn8rLOX5Nj4n uQcJ/lSxB1oCrwiBcPb2roDaS5qqcZs2yiZWZw9s= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Milan Zamazal Subject: [PATCH v5 37/47] ipa: simple: agc: Simplify min gain step handling Date: Mon, 17 Aug 2026 13:43:38 +0200 Message-ID: <20260817114349.994123-38-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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/simple/algorithms/agc.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index f959abeba5..5307fda953 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -130,19 +130,13 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou exposure = std::max(next, exposure + 1); } else { double next = again * factor; - if (next - again < context.configuration.agc.againMinStep) - again += context.configuration.agc.againMinStep; - else - again = next; + again = std::max(next, again + context.configuration.agc.againMinStep); } } else { /* Scene too bright: decrease gain first, then exposure. */ if (again > context.configuration.agc.again10) { double next = again * factor; - if (again - next < context.configuration.agc.againMinStep) - again -= context.configuration.agc.againMinStep; - else - again = next; + again = std::min(next, again - context.configuration.agc.againMinStep); } else { uint32_t next = exposure * factor; exposure = std::min(next, exposure - 1); From patchwork Mon Aug 17 11:43: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: 27817 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 B1A3BC3303 for ; Mon, 17 Aug 2026 11:45:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 14AA268301; Mon, 17 Aug 2026 13:45:02 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="pMJouoWy"; 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 C2AB7682B0 for ; Mon, 17 Aug 2026 13:44:05 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 825D7177A; Mon, 17 Aug 2026 13:42:46 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966966; bh=+LFWo8nxZBf0L0i0Qy/PrLc2+3zzagxxKNpMWK/0UxA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pMJouoWyTdWsnY9zz1jo4NcbNv7U57/M7VY9IyzdPObn1XrvKqSbRT918pu9osNfU vFxPPpCZ1CPiBZKjcdueEr+Zy3eiyU/GMMFCxCL9xmpCRTleD5NQTJf70IHlR6sfZ5 i7yF+eheaX8OjGh9cOzacuCjOdL20Kg7zGi3/+ss= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal Subject: [PATCH v5 38/47] ipa: simple: agc: Use `Histogram` Date: Mon, 17 Aug 2026 13:43:39 +0200 Message-ID: <20260817114349.994123-39-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 --- src/ipa/simple/algorithms/agc.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index 5307fda953..e24eacde72 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -14,6 +14,8 @@ #include +#include + #include "control_ids.h" namespace libcamera { @@ -64,16 +66,16 @@ static constexpr float kExpMaxStep = 0.15; namespace { -std::optional calculateMSV(const SwIspStats::Histogram &histogram, uint8_t blackLevel) +std::optional calculateMSV(const Histogram &histogram, uint8_t blackLevel) { /* * Calculate Mean Sample Value (MSV) according to formula from: * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf */ const unsigned int blackLevelHistIdx = - blackLevel / (256 / SwIspStats::kYHistogramSize); + blackLevel * histogram.bins() / 256; const unsigned int histogramSize = - SwIspStats::kYHistogramSize - blackLevelHistIdx; + histogram.bins() - blackLevelHistIdx; const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount; const unsigned int yHistValsPerBinMod = histogramSize / (histogramSize % kExposureBinsCount + 1); @@ -191,7 +193,7 @@ void Agc::process(IPAContext &context, return; } - auto exposureMSV = calculateMSV(stats->yHistogram, context.activeState.blc.level); + auto exposureMSV = calculateMSV({ stats->yHistogram }, context.activeState.blc.level); if (!exposureMSV) { LOG(IPASoftExposure, Debug) << "Not adjusting exposure due to insufficient histogram data"; From patchwork Mon Aug 17 11:43: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: 27815 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 02B59C3263 for ; Mon, 17 Aug 2026 11:44:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5D40668301; Mon, 17 Aug 2026 13:44:59 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rYnkrymQ"; 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 15E07682B2 for ; Mon, 17 Aug 2026 13:44:06 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CB6691B41; Mon, 17 Aug 2026 13:42:46 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966967; bh=kMOM0V+0/wpMA8xwQrsTELD01dj+v7NBgHh8h+nD0s8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rYnkrymQc/tk8oUfQfGKvVCAcGYdVWY/1ZtyA48qe47OkUl6doLeJM6SE5rrhSuFu vyAqw4owK51ETXqckrAVI4PQLfq59NBMui6deqzDwSZ62HetH+NKqfHjONcWa+lEjW kOGBYn4WtC3sEVtDU6EQzqZ/BSUt91bwrIPAn1Dk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal Subject: [PATCH v5 39/47] ipa: simple: agc: Adjust histogram for black level Date: Mon, 17 Aug 2026 13:43:40 +0200 Message-ID: <20260817114349.994123-40-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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/simple/algorithms/agc.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index e24eacde72..a3e0ebbc3d 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/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 {}; - 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 * std::size(histogram) / 256; + + for (unsigned int i = 0; i < blackLevelHistIdx; i++) + histogram[blackLevelHistIdx] += histogram[i]; + + auto exposureMSV = calculateMSV({ { histogram.begin() + blackLevelHistIdx, histogram.end() } }); if (!exposureMSV) { LOG(IPASoftExposure, Debug) << "Not adjusting exposure due to insufficient histogram data"; From patchwork Mon Aug 17 11:43: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: 27818 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 8D8E7C3263 for ; Mon, 17 Aug 2026 11:45:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 33FD5682DE; Mon, 17 Aug 2026 13:45:03 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="q6tClL4n"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8C219682A5 for ; Mon, 17 Aug 2026 13:44:06 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 23EF11C4D; Mon, 17 Aug 2026 13:42:47 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966967; bh=5OwbNRTWGv+kDm9eR09VHcJRQEAj8M4w5SW5Q3FYqWg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=q6tClL4nmwCzuVCqfIkwwWwFYmhYbu2lLFev1tHdXNq9IxfNXjw2AWtloXjzYT0T4 EeNpBbFALP90KoH/7q/Kvq0myIqBonF/cjBKJ+8UFxPR6AdumTnKqKYH2ocPZ5DrlB 1rA1dPrxRozHd5yjE2Yce27w1EUcIFuME7Ar8hzs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal Subject: [PATCH v5 40/47] ipa: simple: agc: Move to libipa Date: Mon, 17 Aug 2026 13:43:41 +0200 Message-ID: <20260817114349.994123-41-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Move the agc algorithm used into a separate component in libipa, and use it. While it is also a (mean) luminance based algorithm, there is already `AgcMeanLuminance`, so call it `AgcMSV` (from "mean sample value"). This move also removes the dependency on the black level and makes it use the `Histogram` type instead of the software isp specific types. With the removal of the black level information, it is assumed to be 0 and a black level corrected histogram is expected. Signed-off-by: Barnabás Pőcze Reviewed-by: Milan Zamazal --- src/ipa/libipa/agc_msv.cpp | 232 ++++++++++++++++++++++++++++++ src/ipa/libipa/agc_msv.h | 51 +++++++ src/ipa/libipa/meson.build | 2 + src/ipa/simple/algorithms/agc.cpp | 170 ++++------------------ src/ipa/simple/algorithms/agc.h | 7 +- 5 files changed, 316 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..8981570534 --- /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 {}; + + for (unsigned int i = 0; i < histogram.bins(); i++) { + unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin; + exposureBins[idx] += histogram[i]; + } + + for (unsigned int i = 0; i < kExposureBinsCount; i++) { + LOG(AgcMSV, Debug) << i << ": " << exposureBins[i]; + denom += exposureBins[i]; + num += exposureBins[i] * (i + 1); + } + + return (denom == 0 ? 0 : static_cast(num) / denom); +} + +} /* namespace */ + +/** + * \brief Set the limits for the algorithm + */ +void AgcMSV::setLimits(const Limits &limits) +{ + limits_ = limits; +} + +/** + * \brief Calculate a new set of AGC parameters + */ +AgcMSV::Result AgcMSV::calculateNewEv(const Params ¶ms) +{ + auto exposureMSV = calculateMSV(params.yHist); + if (!exposureMSV) { + LOG(AgcMSV, Debug) + << "Not adjusting exposure due to insufficient histogram data"; + return { params.exposure, params.gain }; + } + + return updateExposure(params.exposure, params.gain, *exposureMSV); +} + +AgcMSV::Result AgcMSV::updateExposure(uint32_t exposure, double again, float exposureMSV) +{ + float error = kExposureOptimal - exposureMSV; + if (std::abs(error) <= kExposureSatisfactory) + return { exposure, again }; + + /* + * Compute a proportional correction factor. The sign of the error + * determines the direction: positive error means too dark (increase), + * negative means too bright (decrease). + */ + float step = std::clamp(error * kExpProportionalGain, + -kExpMaxStep, kExpMaxStep); + float factor = 1.0f + step; + + if (factor > 1.0f) { + /* Scene too dark: increase exposure first, then gain. */ + if (exposure < limits_.exposure[1]) { + uint32_t next = exposure * factor; + exposure = std::max(next, exposure + 1); + } else { + double next = again * factor; + again = std::max(next, again + limits_.gainMinStep); + } + } else { + /* Scene too bright: decrease gain first, then exposure. */ + if (again > limits_.gain1) { + double next = again * factor; + again = std::min(next, again - limits_.gainMinStep); + } else { + uint32_t next = exposure * factor; + exposure = std::min(next, exposure - 1); + } + } + + exposure = std::clamp(exposure, limits_.exposure[0], limits_.exposure[1]); + again = std::clamp(again, limits_.gain[0], limits_.gain[1]); + + LOG(AgcMSV, Debug) + << "exposureMSV:" << exposureMSV + << " error:" << error << " factor:" << factor + << " exposure:" << exposure << " analogue-gain:" << again; + + return { exposure, again }; +} + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/agc_msv.h b/src/ipa/libipa/agc_msv.h new file mode 100644 index 0000000000..13e67fd57c --- /dev/null +++ b/src/ipa/libipa/agc_msv.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat Inc. + * + * Luminance mean sample value based AGC algorithm + */ + +#pragma once + +#include "histogram.h" + +#include + +namespace libcamera { + +namespace ipa { + +class AgcMSV +{ +public: + struct Limits { + std::array exposure; + std::array gain; + double gainMinStep; + double gain1; + }; + + struct Params { + const Histogram &yHist; + uint32_t exposure; + double gain; + }; + + struct Result { + uint32_t exposure; + double analogueGain; + }; + + void setLimits(const Limits &limits); + [[nodiscard]] Result calculateNewEv(const Params ¶ms); + +private: + AgcMSV::Result updateExposure(uint32_t exposure, double again, float exposureMSV); + + Limits limits_ = {}; +}; + + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 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/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index a3e0ebbc3d..e455141240 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -7,11 +7,6 @@ #include "agc.h" -#include -#include -#include -#include - #include #include @@ -24,138 +19,22 @@ LOG_DEFINE_CATEGORY(IPASoftExposure) namespace ipa::soft::algorithms { -/* - * The number of bins to use for the optimal exposure calculations. - */ -static constexpr unsigned int kExposureBinsCount = 5; - -/* - * The exposure is optimal when the mean sample value of the histogram is - * in the middle of the range. - */ -static constexpr float kExposureOptimal = kExposureBinsCount / 2.0; - -/* - * This implements the hysteresis for the exposure adjustment. - * It is small enough to have the exposure close to the optimal, and is big - * enough to prevent the exposure from wobbling around the optimal value. - */ -static constexpr float kExposureSatisfactory = 0.2; - -/* - * Proportional gain for exposure/gain adjustment. Maps the MSV error to a - * multiplicative correction factor: - * - * factor = 1.0 + kExpProportionalGain * error - * - * With kExpProportionalGain = 0.04: - * - max error ~2.5 -> factor 1.10 (~10% step, same as before) - * - error 1.0 -> factor 1.04 (~4% step) - * - error 0.3 -> factor 1.012 (~1.2% step) - * - * This replaces the fixed 10% bang-bang step with a proportional correction - * that converges smoothly and avoids overshooting near the target. - */ -static constexpr float kExpProportionalGain = 0.04; - -/* - * Maximum multiplicative step per frame, to bound the correction when the - * scene changes dramatically. - */ -static constexpr float kExpMaxStep = 0.15; - -namespace { - -std::optional calculateMSV(const Histogram &histogram) -{ - /* - * Calculate Mean Sample Value (MSV) according to formula from: - * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf - */ - const unsigned int yHistValsPerBin = histogram.bins() / kExposureBinsCount; - const unsigned int yHistValsPerBinMod = - histogram.bins() / (histogram.bins() % kExposureBinsCount + 1); - int exposureBins[kExposureBinsCount] = {}; - unsigned int denom = 0; - unsigned int num = 0; - - if (yHistValsPerBin == 0) - return {}; - - for (unsigned int i = 0; i < histogram.bins(); i++) { - unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin; - exposureBins[idx] += histogram[i]; - } - - for (unsigned int i = 0; i < kExposureBinsCount; i++) { - LOG(IPASoftExposure, Debug) << i << ": " << exposureBins[i]; - denom += exposureBins[i]; - num += exposureBins[i] * (i + 1); - } - - return (denom == 0 ? 0 : static_cast(num) / denom); -} - -} /* namespace */ - -Agc::Agc() +int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { -} - -void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV) -{ - uint32_t exposure = frameContext.sensor.exposure; - double again = frameContext.sensor.gain; - - double error = kExposureOptimal - exposureMSV; - - if (std::abs(error) <= kExposureSatisfactory) - return; - - /* - * Compute a proportional correction factor. The sign of the error - * determines the direction: positive error means too dark (increase), - * negative means too bright (decrease). - */ - float step = std::clamp(static_cast(error) * kExpProportionalGain, - -kExpMaxStep, kExpMaxStep); - float factor = 1.0f + step; - - if (factor > 1.0f) { - /* Scene too dark: increase exposure first, then gain. */ - if (exposure < context.configuration.agc.exposureMax) { - uint32_t next = exposure * factor; - exposure = std::max(next, exposure + 1); - } else { - double next = again * factor; - again = std::max(next, again + context.configuration.agc.againMinStep); - } - } else { - /* Scene too bright: decrease gain first, then exposure. */ - if (again > context.configuration.agc.again10) { - double next = again * factor; - again = std::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(IPASoftExposure, Debug) - << "exposureMSV " << exposureMSV - << " error " << error << " factor " << factor - << " exp " << exposure << " again " << again; + agc_.setLimits({ + .exposure = { + context.configuration.agc.exposureMin, + context.configuration.agc.exposureMax, + }, + .gain = { + context.configuration.agc.againMin, + context.configuration.agc.againMax, + }, + .gainMinStep = context.configuration.agc.againMinStep, + .gain1 = context.configuration.agc.again10, + }); + + return 0; } void Agc::process(IPAContext &context, @@ -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(IPASoftExposure, 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/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h index 112d9f5a19..2e156e135c 100644 --- a/src/ipa/simple/algorithms/agc.h +++ b/src/ipa/simple/algorithms/agc.h @@ -9,6 +9,8 @@ #include "algorithm.h" +#include + namespace libcamera { namespace ipa::soft::algorithms { @@ -16,8 +18,7 @@ namespace ipa::soft::algorithms { class Agc : public Algorithm { public: - Agc(); - ~Agc() = default; + int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -25,7 +26,7 @@ public: ControlList &metadata) override; private: - void updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV); + AgcMSV agc_; }; } /* namespace ipa::soft::algorithms */ From patchwork Mon Aug 17 11:43: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: 27819 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 4623EC3306 for ; Mon, 17 Aug 2026 11:45:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8B1D3682F9; Mon, 17 Aug 2026 13:45:04 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Mg0l6++D"; 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 0BE20682A1 for ; Mon, 17 Aug 2026 13:44:07 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B6075B1A; Mon, 17 Aug 2026 13:42:47 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966967; bh=HiLJn6QLEptLIfpzNUT6wLxJs5i7JG3zx0m6hZzzqHo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Mg0l6++DutLpnLSd6f2ZRdMBKoUOUhLcNxwhHXdI/Cc6BeGqMekUJqKS5bXJefqcI tGwQWkasQBmp5ayv+K2JfXhNrLuP3cWhQ2ZWcUc3Xk03GBWLU+NIKPeik2gmoTjuP2 Vvf7qGKFAdO+unQ/rpl3bc0i5hTENw+B+yvv6QCI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal Subject: [PATCH v5 41/47] ipa: libipa: agc_msv: Ensure limits are always respected Date: Mon, 17 Aug 2026 13:43:42 +0200 Message-ID: <20260817114349.994123-42-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 --- 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 8981570534..7c6aa39b80 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 17 11:43: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: 27820 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 F2F2FC3308 for ; Mon, 17 Aug 2026 11:45:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 51D91682FA; Mon, 17 Aug 2026 13:45:06 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Y9LG2tTp"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 517A5682B7 for ; Mon, 17 Aug 2026 13:44:07 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0E6DD1C5F; Mon, 17 Aug 2026 13:42:48 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966968; bh=ZM6mJNleSnfNw4JPUPN3fMKZvh9ywFEHwl9AXXGOdAY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Y9LG2tTpus1ctOzjan01rzHxSHWLek7AwaJqKq9vIC9z2FIl8cu6eboUUKfIghv8+ +t/2flYfq+ZtHiNUcmGmc9CC2RCy1zrhbjahLHFCrbSUqvyvO08BNnqXfNbOrVi/Cb 9X60b4ldRfRPWayTmWTZBmvYpSqT9LAnIwV1EZzI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal Subject: [PATCH v5 42/47] ipa: libipa: agc_msv: Handle min gain > 1.0 case Date: Mon, 17 Aug 2026 13:43:43 +0200 Message-ID: <20260817114349.994123-43-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 --- 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 7c6aa39b80..1995ea3707 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 17 11:43: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: 27821 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 1D81BC3301 for ; Mon, 17 Aug 2026 11:45:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A47FC68312; Mon, 17 Aug 2026 13:45:07 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="L4kPmV1v"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9413A6820A for ; Mon, 17 Aug 2026 13:44:07 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 58E001C9B; Mon, 17 Aug 2026 13:42:48 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966968; bh=2i9cahGCuOA/+hV73piQg+pFnKJ/Gpk5CFb7EpTZYPA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=L4kPmV1v9H7atuz4N2wqSQbkeZ/CSNbQhmDn/P4zSFNjgd1JQh2R/9sWETb4ySnlw ZOHjkB74pgqvfP+MUsgPexmXi9WAcXP3DF/cfUuO6Zwv+BvX8hcYzE1Y6hEzpZnUDp jbz0gvkKqxRYl9igyTn4xnVWwYvnwFAyguJYW4OQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal Subject: [PATCH v5 43/47] ipa: simple: Move sensor helper and controls to context Date: Mon, 17 Aug 2026 13:43:44 +0200 Message-ID: <20260817114349.994123-44-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" So that algorithms can access it. Signed-off-by: Barnabás Pőcze Reviewed-by: Milan Zamazal --- src/ipa/simple/algorithms/blc.cpp | 12 +++++++++++ src/ipa/simple/ipa_context.h | 4 ++++ src/ipa/simple/soft_simple.cpp | 34 ++++++++++--------------------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp index e45a913cd9..c69392fd2a 100644 --- a/src/ipa/simple/algorithms/blc.cpp +++ b/src/ipa/simple/algorithms/blc.cpp @@ -42,6 +42,18 @@ int BlackLevel::configure(IPAContext &context, { if (definedLevel_.has_value()) context.configuration.black.level = definedLevel_; + else if (context.camHelper) { + if (auto bl = context.camHelper->blackLevel()) { + /* + * The black level from CameraSensorHelper is a 16 bit value, software ISP + * works with 8 bit pixel values, both regardless of the actual + * sensor pixel width. Hence we obtain the pixel-based black value + * by dividing the value from the helper by 256. + */ + context.configuration.black.level = *bl / 256; + } + } + context.activeState.blc.level = context.configuration.black.level.value_or(16); return 0; diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 910947b814..651c62bf0f 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include @@ -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/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 01c41e3a8c..3cd089f83f 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -77,8 +77,6 @@ private: DebayerParams *params_; SwIspStats *stats_; - std::unique_ptr camHelper_; - ControlInfoMap sensorInfoMap_; /* Local parameter storage */ struct IPAContext context_; @@ -100,8 +98,8 @@ int IPASoftSimple::init(const IPASettings &settings, ControlInfoMap *ipaControls, bool *ccmEnabled) { - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); - if (!camHelper_) { + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!context_.camHelper) { LOG(IPASoft, Warning) << "Failed to create camera sensor helper for " << settings.sensorModel; @@ -202,10 +200,10 @@ int IPASoftSimple::init(const IPASettings &settings, int IPASoftSimple::configure(const IPAConfigInfo &configInfo) { - sensorInfoMap_ = configInfo.sensorControls; + context_.sensorControls = configInfo.sensorControls; - const ControlInfo &exposureInfo = sensorInfoMap_.find(V4L2_CID_EXPOSURE)->second; - const ControlInfo &gainInfo = sensorInfoMap_.find(V4L2_CID_ANALOGUE_GAIN)->second; + const ControlInfo &exposureInfo = context_.sensorControls.find(V4L2_CID_EXPOSURE)->second; + const ControlInfo &gainInfo = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; /* Clear the IPA context before the streaming session. */ context_.configuration = {}; @@ -225,24 +223,14 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo) int32_t againMax = gainInfo.max().get(); int32_t againDef = gainInfo.def().get(); - if (camHelper_) { - context_.configuration.agc.againMin = camHelper_->gain(againMin); - context_.configuration.agc.againMax = camHelper_->gain(againMax); + if (context_.camHelper) { + context_.configuration.agc.againMin = context_.camHelper->gain(againMin); + context_.configuration.agc.againMax = context_.camHelper->gain(againMax); context_.configuration.agc.again10 = std::max(context_.configuration.agc.againMin, 1.0); context_.configuration.agc.againMinStep = (context_.configuration.agc.againMax - context_.configuration.agc.againMin) / 100.0; - if (camHelper_->blackLevel().has_value()) { - /* - * The black level from camHelper_ is a 16 bit value, software ISP - * works with 8 bit pixel values, both regardless of the actual - * sensor pixel width. Hence we obtain the pixel-based black value - * by dividing the value from the helper by 256. - */ - context_.configuration.black.level = - camHelper_->blackLevel().value() / 256; - } } else { context_.configuration.agc.againMax = againMax; context_.configuration.agc.again10 = againDef; @@ -303,15 +291,15 @@ void IPASoftSimple::processStats(const uint32_t frame, IPAFrameContext &frameContext = context_.frameContexts.get(frame); std::tie(frameContext.sensor.exposure, frameContext.sensor.gain) - = agc::extractControls(sensorControls, camHelper_.get()); + = agc::extractControls(sensorControls, context_.camHelper.get()); ControlList metadata(controls::controls); for (const auto &algo : algorithms()) algo->process(context_, frame, frameContext, stats_, metadata); metadataReady.emit(frame, metadata); - ControlList ctrls(sensorInfoMap_); - agc::prepareControls(ctrls, camHelper_.get(), + ControlList ctrls(context_.sensorControls); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc.exposure, frameContext.agc.gain); setSensorControls.emit(ctrls); } From patchwork Mon Aug 17 11:43: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: 27822 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 7BBE5C330A for ; Mon, 17 Aug 2026 11:45:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F1F4B68310; Mon, 17 Aug 2026 13:45:08 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="SvAbtpyK"; 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 DCD4F6828D for ; Mon, 17 Aug 2026 13:44:07 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A063E13BA; Mon, 17 Aug 2026 13:42:48 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966968; bh=VtOiiooA5/m3cz71qkMMVoMDtkW/ANQqG62bnfUvAQs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=SvAbtpyKhIFO+hjzO7QJNhO2kQr2HiAC472In8bbl5SnXcjheWiYfZQaGUA/6xfN/ rQY5cDOHF5ZbtKX3/hz+V6Zh45aAtSznFrUw7bd35SxkKPl/g6hPJcddRBhcqbJvZS JwK1x/SEt/UbbTY+rHYbhVgIFFGYkv5cSDN9G7jA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal Subject: [PATCH v5 44/47] ipa: simple: Update controls after configuration Date: Mon, 17 Aug 2026 13:43:45 +0200 Message-ID: <20260817114349.994123-45-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 --- include/libcamera/internal/software_isp/software_isp.h | 3 ++- include/libcamera/ipa/soft.mojom | 2 +- src/ipa/simple/soft_simple.cpp | 7 +++++-- src/libcamera/pipeline/simple/simple.cpp | 2 +- src/libcamera/software_isp/software_isp.cpp | 9 +++++---- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h index 4f72dce9b1..938d980906 100644 --- a/include/libcamera/internal/software_isp/software_isp.h +++ b/include/libcamera/internal/software_isp/software_isp.h @@ -65,7 +65,8 @@ public: int configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, - const ipa::soft::IPAConfigInfo &configInfo); + const ipa::soft::IPAConfigInfo &configInfo, + ControlInfoMap *ipaControls); int exportBuffers(const Stream *stream, unsigned int count, std::vector> *buffers); diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom index 77328c5fd5..3acac53981 100644 --- a/include/libcamera/ipa/soft.mojom +++ b/include/libcamera/ipa/soft.mojom @@ -22,7 +22,7 @@ interface IPASoftInterface { start() => (int32 ret); stop(); configure(IPAConfigInfo configInfo) - => (int32 ret); + => (int32 ret, libcamera.ControlInfoMap ipaControls); [async] queueRequest(uint32 frame, libcamera.ControlList sensorControls); [async] computeParams(uint32 frame); diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 3cd089f83f..67978c8be8 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -59,7 +59,8 @@ public: const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls, bool *ccmEnabled) override; - int configure(const IPAConfigInfo &configInfo) override; + int configure(const IPAConfigInfo &configInfo, + ControlInfoMap *ipaControls) override; int start() override; void stop() override; @@ -198,7 +199,7 @@ int IPASoftSimple::init(const IPASettings &settings, return 0; } -int IPASoftSimple::configure(const IPAConfigInfo &configInfo) +int IPASoftSimple::configure(const IPAConfigInfo &configInfo, ControlInfoMap *ipaControls) { context_.sensorControls = configInfo.sensorControls; @@ -251,6 +252,8 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo) << context_.configuration.agc.againMax << " (" << context_.configuration.agc.againMinStep << ")"; + *ipaControls = { ControlInfoMap::Map(context_.ctrlMap), controls::controls }; + return 0; } diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index e26f438d9a..3917aa897e 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -1605,7 +1605,7 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) } else { ipa::soft::IPAConfigInfo configInfo; configInfo.sensorControls = data->sensor_->controls(); - return data->swIsp_->configure(inputCfg, outputCfgs, configInfo); + return data->swIsp_->configure(inputCfg, outputCfgs, configInfo, &data->controlInfo_); } } diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp index 672a5ed36b..962a7ec0c0 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::soft::IPAConfigInfo &configInfo) + const ipa::soft::IPAConfigInfo &configInfo, + ControlInfoMap *ipaControls) { ASSERT(ipa_ && debayer_); - int ret = ipa_->configure(configInfo); + int ret = ipa_->configure(configInfo, ipaControls); if (ret < 0) return ret; From patchwork Mon Aug 17 11:43: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: 27824 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 0CAFDC330F for ; Mon, 17 Aug 2026 11:45:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7C798682FD; Mon, 17 Aug 2026 13:45:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="CMGv+Jk/"; 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 2D01B6829F for ; Mon, 17 Aug 2026 13:44:08 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EA75F19C6 for ; Mon, 17 Aug 2026 13:42:48 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966969; bh=lKY+py8ziPsIU89w9CEo57PRQrl3h2bGppSYLUI6i1Y=; h=From:To:Subject:Date:In-Reply-To:References:From; b=CMGv+Jk/nf4O9njmLvOVLaVJr3KrctuYrTNvvJGhoPDOnTlHoYS1dt2wFETEfQF2s /o0cJnKb4kcaf6pR5DCabv8787VVk5wT8pHGUhtRJ6NYsLXMxN/87frs008LwKh9vX w7LQy5OERPrmtf+wmlzacw0kRZS08lIO1Wb6XTPM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 45/47] ipa: libipa: agc: Work without `CameraSensorHelper` Date: Mon, 17 Aug 2026 13:43:46 +0200 Message-ID: <20260817114349.994123-46-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 --- src/ipa/ipu3/algorithms/agc.cpp | 4 +- src/ipa/libipa/agc.cpp | 202 +++++++++++++++++++--------- src/ipa/libipa/agc.h | 9 +- src/ipa/mali-c55/algorithms/agc.cpp | 4 +- src/ipa/rkisp1/algorithms/agc.cpp | 4 +- 5 files changed, 145 insertions(+), 78 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index c9ea02ed00..975f82eaf3 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -68,12 +68,11 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.init(tuningData); + ret = agc_.init(tuningData, context.camHelper.get()); if (ret) return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -98,7 +97,6 @@ int Agc::configure(IPAContext &context, bdsGrid_ = context.configuration.grid.bdsGrid; return agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index ff1e8d5a84..d327548979 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -11,10 +11,12 @@ #include #include #include +#include #include #include +#include #include #include @@ -79,6 +81,9 @@ namespace agc { * \var agc::Session::maxAnalogueGain * \brief Maximum analogue gain for the streaming session * + * \var agc::Session::defAnalogueGain + * \brief Default analogue gain of the configured sensor + * * \var agc::Session::minFrameDuration * \brief Minimum frame duration for the streaming session * @@ -217,9 +222,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 * @@ -264,11 +266,18 @@ namespace agc { /** * \brief Load tuning data */ -int AgcAlgorithm::init(const ValueNode &tuningData) +int AgcAlgorithm::init(const ValueNode &tuningData, CameraSensorHelper *sensor) { - int ret = impl_.parseTuningData(tuningData); - if (ret) - return ret; + if (sensor) { + auto &impl = impl_.emplace(); + int ret = impl.parseTuningData(tuningData); + if (ret) + return ret; + } else { + impl_.emplace(); + } + + sensor_ = sensor; return 0; } @@ -303,10 +312,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 << ']' @@ -345,28 +358,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; @@ -417,25 +423,57 @@ 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::vector options; - for (const auto &[id, _] : impl_.constraintModes()) - options.emplace_back(id); - - config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(options); - } - - { - std::vector options; - for (const auto &[id, _] : impl_.exposureModeHelpers()) - options.emplace_back(id); - - config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options); - } - } + std::visit(utils::overloaded{ + [&](AgcMSV&) { + /* no constraint/exposure mode support */ + state.constraintMode = controls::AeConstraintModeEnum::ConstraintNormal; + state.exposureMode = controls::AeExposureModeEnum::ExposureNormal; + + state.automatic.yTarget = (2.5 - 1) / (5 - 1); /* \todo hack? */ + + if (session.autoAllowed) { + config.ctrlMap[&controls::AeConstraintMode] = ControlInfo( + std::array{ ControlValue(state.constraintMode) } + ); + + config.ctrlMap[&controls::AeExposureMode] = ControlInfo( + std::array{ ControlValue(state.exposureMode) } + ); + } + }, + [&](AgcMeanLuminance& impl) { + state.constraintMode = + static_cast(impl.constraintModes().begin()->first); + state.exposureMode = + static_cast(impl.exposureModeHelpers().begin()->first); + + state.automatic.yTarget = impl.effectiveYTarget(0, 1); + + ASSERT(sensor_); + impl.configure(session.lineDuration, sensor_); + impl.resetFrameCount(); + + if (session.autoAllowed) { + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); + + { + std::vector options; + for (const auto &[id, _] : impl.constraintModes()) + options.emplace_back(id); + + config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(options); + } + + { + std::vector options; + for (const auto &[id, _] : impl.exposureModeHelpers()) + options.emplace_back(id); + + config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options); + } + } + }, + }, impl_); return 0; } @@ -625,36 +663,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 = pow(2.0, frameContext.exposureValue), - }); - - /* Update the estimated exposure and gain. */ - state.automatic.exposure = newEv.exposureTime / lineDuration; - state.automatic.gain = newEv.analogueGain; - state.automatic.quantizationGain = newEv.quantizationGain; - state.automatic.digitalGain = newEv.digitalGain; - state.automatic.yTarget = newEv.yTarget; + std::visit(utils::overloaded{ + [&](AgcMSV& impl) { + impl.setLimits({ + .exposure = { + uint32_t(minExposureTime / lineDuration), + uint32_t(maxExposureTime / lineDuration), + }, + .gain = { + minAnalogueGain, + maxAnalogueGain, + }, + /* gain codes -> step size of 1 */ + .gainMinStep = 1, + /* assume default gain is close to 1.0 */ + .gain1 = session.defAnalogueGain, + }); + + const auto& newEv = impl.calculateNewEv({ + .yHist = params->yHist, + .exposure = params->exposure, + .gain = params->gain, + }); + + state.automatic.exposure = newEv.exposure; + state.automatic.gain = newEv.analogueGain; + }, + [&](AgcMeanLuminance& impl) { + /* + * The Agc algorithm needs to know the effective exposure + * value that was applied to the sensor when the statistics + * were collected. + */ + utils::Duration effectiveExposureValue = + lineDuration * params->exposure * params->gain; + + impl.setLimits(minExposureTime, maxExposureTime, + minAnalogueGain, maxAnalogueGain, + std::move(params->additionalConstraints)); + + const auto &newEv = impl.calculateNewEv({ + .traits = params->traits, + .yHist = params->yHist, + .effectiveExposureValue = effectiveExposureValue, + .constraintModeIndex = frameContext.constraintMode, + .exposureModeIndex = frameContext.exposureMode, + .lux = params->lux, + .exposureCompensation = pow(2.0, frameContext.exposureValue), + }); + + /* Update the estimated exposure and gain. */ + state.automatic.exposure = newEv.exposureTime / lineDuration; + state.automatic.gain = newEv.analogueGain; + state.automatic.quantizationGain = newEv.quantizationGain; + state.automatic.digitalGain = newEv.digitalGain; + state.automatic.yTarget = newEv.yTarget; + }, + }, impl_); + + 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; @@ -664,7 +734,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 612ac539da..b31f7de8fa 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -9,6 +9,7 @@ #include #include +#include #include @@ -18,6 +19,7 @@ #include #include "agc_mean_luminance.h" +#include "agc_msv.h" #include "camera_sensor_helper.h" #include "histogram.h" @@ -32,6 +34,7 @@ struct Session { utils::Duration maxExposureTime; double minAnalogueGain; double maxAnalogueGain; + double defAnalogueGain; utils::Duration minFrameDuration; utils::Duration maxFrameDuration; utils::Duration lineDuration; @@ -113,7 +116,6 @@ class AgcAlgorithm { public: struct ConfigurationParams { - const CameraSensorHelper *sensor; const IPACameraSensorInfo &sensorInfo; const ControlInfoMap &sensorControls; ControlInfoMap::Map &ctrlMap; @@ -129,7 +131,7 @@ public: double lux = 0; }; - int init(const ValueNode &tuningData); + int init(const ValueNode &tuningData, CameraSensorHelper *sensor); int configure(agc::Session &session, agc::ActiveState &state, const ConfigurationParams &config); @@ -151,7 +153,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 4b675eb7e5..140f34ad50 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -122,12 +122,11 @@ Agc::Agc() int Agc::init(IPAContext &context, const ValueNode &tuningData) { - int ret = agc_.init(tuningData); + int ret = agc_.init(tuningData, context.camHelper.get()); if (ret) return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -147,7 +146,6 @@ int Agc::configure(IPAContext &context, return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 4c2a066e86..41a8cd581f 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -136,12 +136,11 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.init(tuningData); + ret = agc_.init(tuningData, context.camHelper.get()); if (ret) return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -167,7 +166,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, From patchwork Mon Aug 17 11:43: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: 27823 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 AAF16C3303 for ; Mon, 17 Aug 2026 11:45:10 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3B8C468312; Mon, 17 Aug 2026 13:45:10 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="C/S46BES"; 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 8596A682B5 for ; Mon, 17 Aug 2026 13:44:08 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 36E0C1CDC; Mon, 17 Aug 2026 13:42:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966969; bh=/N9/PRPYDWmYNTRw9kTxR3DWOWISxYjXtI+bKTmTf6o=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=C/S46BESddQnvuumtwSEBjnQDzHb86AMsAd4qbxMcVVb8Vl8RizB1e+LLXJ65pkvn IaO1X27P6V4+2Qmd5oI1UgpJeEz2q5tE4Y1tgeZyh+vChwrzhg81trAqq3exAUeGtm aii5BXzamfg29sM8suWenLTjDKGCziBULUte3mkc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal Subject: [PATCH v5 46/47] ipa: simple: agc: Port to `AgcAlgorithm` Date: Mon, 17 Aug 2026 13:43:47 +0200 Message-ID: <20260817114349.994123-47-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 --- src/ipa/simple/algorithms/agc.cpp | 136 +++++++++++++++++------------- src/ipa/simple/algorithms/agc.h | 12 ++- src/ipa/simple/ipa_context.h | 20 +---- src/ipa/simple/soft_simple.cpp | 55 +----------- 4 files changed, 93 insertions(+), 130 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index e455141240..4abd471dcf 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -11,30 +11,76 @@ #include -#include "control_ids.h" - namespace libcamera { LOG_DEFINE_CATEGORY(IPASoftExposure) namespace ipa::soft::algorithms { +namespace { + +class AgcTraits : public AgcMeanLuminance::Traits +{ +public: + AgcTraits(const Histogram &yHist) + : yHist_(yHist) + { + } + + double estimateLuminance(double gain) const override + { + /* + * TODO: Improve by asking the weight of saturating and non-saturating + * bins directly from the histogram + */ + double sum = 0; + + for (size_t i = 0; i < yHist_.bins(); i++) + sum += std::min(yHist_.bins(), i * gain) * yHist_[i]; + + return sum / yHist_.total() / yHist_.bins(); + } + +private: + const Histogram &yHist_; +}; + +} /* namespace */ + +int Agc::init(IPAContext &context, const ValueNode &tuningData) +{ + int ret = agc_.init(tuningData, context.camHelper.get()); + if (ret) + return ret; + + return agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, + }); +} + int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - agc_.setLimits({ - .exposure = { - context.configuration.agc.exposureMin, - context.configuration.agc.exposureMax, - }, - .gain = { - context.configuration.agc.againMin, - context.configuration.agc.againMax, - }, - .gainMinStep = context.configuration.agc.againMinStep, - .gain1 = context.configuration.agc.again10, + return agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, }); +} + +void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) +{ + agc_.queueRequest(context.configuration.agc, context.activeState.agc, frameContext.agc, controls); +} - return 0; +void Agc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) +{ + agc_.prepare(context.activeState.agc, frameContext.agc); } void Agc::process(IPAContext &context, @@ -43,51 +89,25 @@ void Agc::process(IPAContext &context, const SwIspStats *stats, ControlList &metadata) { - utils::Duration exposureTime = - context.configuration.agc.lineDuration * frameContext.sensor.exposure; - metadata.set(controls::ExposureTime, exposureTime.get()); - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); - - if (!context.activeState.agc.valid) { - /* - * Init active-state from sensor values in case updateExposure() - * does not run for the first frame. - */ - context.activeState.agc.exposure = frameContext.sensor.exposure; - context.activeState.agc.again = frameContext.sensor.gain; - context.activeState.agc.valid = true; - } - - if (!stats->valid) { - /* - * Use the new exposure and gain values calculated the last time - * there were valid stats. - */ - frameContext.agc.exposure = context.activeState.agc.exposure; - frameContext.agc.gain = context.activeState.agc.again; - return; + if (stats->valid) { + auto histogram = stats->yHistogram; + + const unsigned int blackLevelHistIdx = + context.activeState.blc.level * std::size(histogram) / 256; + for (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 * std::size(histogram) / 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/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h index 2e156e135c..6e37ce6eff 100644 --- a/src/ipa/simple/algorithms/agc.h +++ b/src/ipa/simple/algorithms/agc.h @@ -9,7 +9,7 @@ #include "algorithm.h" -#include +#include namespace libcamera { @@ -18,15 +18,23 @@ namespace ipa::soft::algorithms { class Agc : public Algorithm { public: + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) override; + + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, DebayerParams *params) override; + void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const SwIspStats *stats, ControlList &metadata) override; private: - AgcMSV agc_; + AgcAlgorithm agc_; }; } /* namespace ipa::soft::algorithms */ diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 651c62bf0f..f288a73e9d 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -17,6 +17,7 @@ #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" +#include #include #include #include @@ -29,11 +30,7 @@ namespace libcamera { namespace ipa::soft { 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/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 67978c8be8..081fbb724c 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -107,6 +107,7 @@ int IPASoftSimple::init(const IPASettings &settings, } context_.sensorInfo = sensorInfo; + context_.sensorControls = sensorControls; /* Load the tuning data file */ File file(settings.configurationFile); @@ -180,22 +181,6 @@ int IPASoftSimple::init(const IPASettings &settings, ControlInfoMap::Map ctrlMap = context_.ctrlMap; *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); - /* - * Check if the sensor driver supports the controls required by the - * Soft IPA. - * Don't save the min and max control values yet, as e.g. the limits - * for V4L2_CID_EXPOSURE depend on the configured sensor resolution. - */ - if (sensorControls.find(V4L2_CID_EXPOSURE) == sensorControls.end()) { - LOG(IPASoft, Error) << "Don't have exposure control"; - return -EINVAL; - } - - if (sensorControls.find(V4L2_CID_ANALOGUE_GAIN) == sensorControls.end()) { - LOG(IPASoft, Error) << "Don't have gain control"; - return -EINVAL; - } - return 0; } @@ -203,55 +188,17 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo, ControlInfoMap *ip { context_.sensorControls = configInfo.sensorControls; - const ControlInfo &exposureInfo = context_.sensorControls.find(V4L2_CID_EXPOSURE)->second; - const ControlInfo &gainInfo = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - /* Clear the IPA context before the streaming session. */ context_.configuration = {}; context_.activeState = {}; context_.frameContexts.clear(); - context_.configuration.agc.lineDuration = - context_.sensorInfo.minLineLength * 1.0s / context_.sensorInfo.pixelRate; - context_.configuration.agc.exposureMin = exposureInfo.min().get(); - context_.configuration.agc.exposureMax = exposureInfo.max().get(); - if (!context_.configuration.agc.exposureMin) { - LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear"; - context_.configuration.agc.exposureMin = 1; - } - - int32_t againMin = gainInfo.min().get(); - int32_t againMax = gainInfo.max().get(); - int32_t againDef = gainInfo.def().get(); - - if (context_.camHelper) { - context_.configuration.agc.againMin = context_.camHelper->gain(againMin); - context_.configuration.agc.againMax = context_.camHelper->gain(againMax); - context_.configuration.agc.again10 = std::max(context_.configuration.agc.againMin, 1.0); - context_.configuration.agc.againMinStep = - (context_.configuration.agc.againMax - - context_.configuration.agc.againMin) / - 100.0; - } else { - context_.configuration.agc.againMax = againMax; - context_.configuration.agc.again10 = againDef; - context_.configuration.agc.againMin = againMin; - context_.configuration.agc.againMinStep = 1.0; - } - for (const auto &algo : algorithms()) { int ret = algo->configure(context_, configInfo); if (ret) return ret; } - LOG(IPASoft, Info) - << "Exposure " << context_.configuration.agc.exposureMin << "-" - << context_.configuration.agc.exposureMax - << ", gain " << context_.configuration.agc.againMin << "-" - << context_.configuration.agc.againMax - << " (" << context_.configuration.agc.againMinStep << ")"; - *ipaControls = { ControlInfoMap::Map(context_.ctrlMap), controls::controls }; return 0; From patchwork Mon Aug 17 11:43: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: 27825 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 E1914C3263 for ; Mon, 17 Aug 2026 11:45:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8BEAF6828D; Mon, 17 Aug 2026 13:45:12 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Zc4tbpML"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E4A9E682BB for ; Mon, 17 Aug 2026 13:44:08 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8D60319C6; Mon, 17 Aug 2026 13:42:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966969; bh=bHDuSFzA921QJpRPHPT0r2N5jG/Xue4GmkemJCvzrnQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Zc4tbpML441fmGoEIyRsSU1tckPDE1QHNO2d7ymk7epptxT4sQpi1GDZmNfLSdxnj 7e2rcZ3QPepOE+gLBtlge0OkPqw9LSkaGPnYHmTohtUJOGJPsAMRegJ6EUmf4rs/gj ZXw+L8emvJ9r22dcXxCsm6R0PtgixLWf4TH7a/CA= 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 v5 47/47] ipa: libipa: agc_mean_luminance: Fold resetFrameCount() in configure() Date: Mon, 17 Aug 2026 13:43:48 +0200 Message-ID: <20260817114349.994123-48-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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 d327548979..af9b0b600c 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -451,7 +451,6 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, ASSERT(sensor_); impl.configure(session.lineDuration, sensor_); - impl.resetFrameCount(); if (session.autoAllowed) { config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 8303065008..2459f8b44a 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -169,11 +169,11 @@ static constexpr unsigned int kDefaultLuxLevel = 500; * * IPA modules that want to use this class to implement their AEGC algorithm * should derive AgcMeanLuminance::Traits and override the necessary functions. - * The users must call parseTuningData() in init(), and must also call setLimits() - * and resetFrameCounter() in configure(). They may then use calculateNewEv() in - * process(). If the limits passed to setLimits() change for any reason (for example, - * in response to a FrameDurationLimit control being passed in queueRequest()) - * then setLimits() must be called again with the new values. + * The users must call parseTuningData() in init(), and must also call configure() + * and setLimits() in configure(). They may then use calculateNewEv() in process(). + * If the limits passed to setLimits() change for any reason (for example, in + * response to a FrameDurationLimit control being passed in queueRequest()) then + * setLimits() must be called again with the new values. */ AgcMeanLuminance::AgcMeanLuminance() @@ -354,6 +354,7 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration, luxWarningEnabled_ = true; filteredExposure_ = 0s; + frameCount_ = 0; } /** @@ -699,16 +700,6 @@ AgcMeanLuminance::calculateNewEv(const Params ¶ms) return { exposureModeHelper.splitExposure(newExposureValue), yTarget }; } -/** - * \fn AgcMeanLuminance::resetFrameCount() - * \brief Reset the frame counter - * - * This function resets the internal frame counter, which exists to help the - * algorithm decide whether it should respond instantly or not. The expectation - * is for users to call this function before each camera start call in their - * configure() function. - */ - } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index 46a4d3e3f3..f799618ffc 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -79,11 +79,6 @@ public: double effectiveYTarget(double lux, double exposureCompensation) const; - void resetFrameCount() - { - frameCount_ = 0; - } - private: int parseRelativeLuminanceTarget(const ValueNode &tuningData); int parseConstraint(const ValueNode &modeDict, int32_t id);