From patchwork Mon Oct 19 18:45: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: 28508 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 5DB62C324C for ; Mon, 19 Oct 2026 18:45:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A6BDC68EDA; Mon, 19 Oct 2026 20:45:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="PrBPpHwx"; 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 2F3A068EBE for ; Mon, 19 Oct 2026 20:45:31 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E5043E72 for ; Mon, 19 Oct 2026 20:43:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435405; bh=iNfIJesyPxdw+FrR4pAY9HIAYC07fx6Eguq+AXgTqD8=; h=From:To:Subject:Date:In-Reply-To:References:From; b=PrBPpHwxWYjI9cv7Wx0xJ/djJjjyCmMdB1DWCirkOCsvnoH9WWHBvDwk3/CrmUWFK GQsuaOz5QlsOUnUXiK/Sr8vydwbaO5JYkn3sTmg9TRKMxkVZiFLaflbC0vrIM3/0nd JRED0zo/a8MDyauSnGiikq3j9w23GnP850iNR2Bo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 01/19] ipa: libipa: exposure_mode_helper: Remove defaulted destructor Date: Mon, 19 Oct 2026 20:45:08 +0200 Message-ID: <20261019184526.252977-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 does not have any effect, the destructor will be defaulted implicitly, so remove it. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/exposure_mode_helper.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h index a56e19061b..d0d468a6a2 100644 --- a/src/ipa/libipa/exposure_mode_helper.h +++ b/src/ipa/libipa/exposure_mode_helper.h @@ -23,7 +23,6 @@ class ExposureModeHelper { public: ExposureModeHelper(std::span> stages); - ~ExposureModeHelper() = default; void configure(utils::Duration lineLength, const CameraSensorHelper *sensorHelper); void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, From patchwork Mon Oct 19 18:45: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: 28509 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 B3E77C324C for ; Mon, 19 Oct 2026 18:45:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D325A68EC9; Mon, 19 Oct 2026 20:45:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Z2QjBwni"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6017E68E44 for ; Mon, 19 Oct 2026 20:45:31 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3BC8C593 for ; Mon, 19 Oct 2026 20:43:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435405; bh=+j1o/UnQgpR6eBKmzabmDoXUD4GepNiA39+4MTT6AFg=; h=From:To:Subject:Date:In-Reply-To:References:From; b=Z2QjBwniivez4c6qhGSxNmSN+3wdpzVslbbVayHqUDXRst5iqHjyzlXXFnHiQ6tUX GGlIRWTKpg9v231/HEKkwhDwTN0xr3odjCsTjE3lpdhuIzHW6aFxcjHk1CorcnB6UJ 8/A4RPh/No7KDecwql3nanwn+/hlY0f9Q21kSrYs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 02/19] ipa: libipa: exposure_mode_helper: Keep stage params together Date: Mon, 19 Oct 2026 20:45:09 +0200 Message-ID: <20261019184526.252977-3-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 `Stage` that contains both the exposure time and gain in one place, and have a single vector of `Stage`s instead of the two same size exposure time / gain vectors. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_mean_luminance.cpp | 12 ++++----- src/ipa/libipa/exposure_mode_helper.cpp | 33 +++++++++++++++---------- src/ipa/libipa/exposure_mode_helper.h | 12 +++++---- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 72833247c5..eff639fb67 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -314,15 +314,15 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) return -EINVAL; } - std::vector> stages; - for (unsigned int i = 0; i < exposureTimes.size(); i++) { + std::vector stages; + for (size_t i = 0; i < exposureTimes.size(); i++) { stages.push_back({ - std::chrono::microseconds(exposureTimes[i]), - gains[i] + .exposureTime = std::chrono::microseconds(exposureTimes[i]), + .gain = gains[i], }); } - exposureModeHelpers_.try_emplace(it->second, stages); + exposureModeHelpers_.try_emplace(it->second, std::move(stages)); } } @@ -334,7 +334,7 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) */ if (exposureModeHelpers_.empty()) exposureModeHelpers_.try_emplace(controls::ExposureNormal, - std::span>{}); + std::vector{}); return 0; } diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index b3e75f6358..ef14729471 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -59,24 +59,31 @@ namespace ipa { * used. */ + /** + * \class ExposureModeHelper::Stage + * \brief Details of an exposure stage + * + * This type contains the parameters of a single exposure stage as used by the + * algorithm. + * + * \var ExposureModeHelper::Stage::exposureTime + * \brief The maximum exposure time of the stage + * + * \var ExposureModeHelper::Stage::gain + * \brief The maximum gain of the stage + */ + /** * \brief Construct an ExposureModeHelper instance * \param[in] stages The vector of paired exposure time and gain limits * - * The input stages are exposure time and _total_ gain pairs; the gain - * encompasses both analogue and digital gain. - * * The vector of stages may be empty. In that case, the helper will simply use * the runtime limits set through setLimits() instead. */ -ExposureModeHelper::ExposureModeHelper(std::span> stages) - : lineDuration_(1us), minExposureTime_(0us), maxExposureTime_(0us), - minGain_(0), maxGain_(0), sensorHelper_(nullptr) +ExposureModeHelper::ExposureModeHelper(std::vector stages) + : stages_(std::move(stages)), lineDuration_(1us), minExposureTime_(0us), + maxExposureTime_(0us), minGain_(0), maxGain_(0), sensorHelper_(nullptr) { - for (const auto &[s, g] : stages) { - exposureTimes_.push_back(s); - gains_.push_back(g); - } } /** @@ -236,10 +243,10 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const double stageGain = clampGain(1.0); double lastStageGain = stageGain; - for (unsigned int stage = 0; stage < gains_.size(); stage++) { - utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage], + for (const auto &stage : stages_) { + utils::Duration stageExposureTime = clampExposureTime(stage.exposureTime, &quantGain); - stageGain = clampGain(gains_[stage]); + stageGain = clampGain(stage.gain); /* * We perform the clamping on both exposure time and gain in diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h index d0d468a6a2..c474cc2c13 100644 --- a/src/ipa/libipa/exposure_mode_helper.h +++ b/src/ipa/libipa/exposure_mode_helper.h @@ -7,8 +7,6 @@ #pragma once -#include -#include #include #include @@ -22,7 +20,12 @@ namespace ipa { class ExposureModeHelper { public: - ExposureModeHelper(std::span> stages); + struct Stage { + utils::Duration exposureTime; + double gain; + }; + + ExposureModeHelper(std::vector stages); void configure(utils::Duration lineLength, const CameraSensorHelper *sensorHelper); void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, @@ -47,8 +50,7 @@ private: double *quantizationGain = nullptr) const; double clampGain(double gain, double *quantizationGain = nullptr) const; - std::vector exposureTimes_; - std::vector gains_; + std::vector stages_; utils::Duration lineDuration_; utils::Duration minExposureTime_; From patchwork Mon Oct 19 18:45: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: 28510 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 34A63C324C for ; Mon, 19 Oct 2026 18:45:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A2AAB68ECF; Mon, 19 Oct 2026 20:45:36 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AgrE/DCt"; 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 948D568EC1 for ; Mon, 19 Oct 2026 20:45:31 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 76C6DE72 for ; Mon, 19 Oct 2026 20:43:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435405; bh=YCzcfOhc8mAHFHeXpZHDZe4cZdfis1PQQmGkOmtO16s=; h=From:To:Subject:Date:In-Reply-To:References:From; b=AgrE/DCt5uXxC9854q5egnIfqq7q+2I0xmK6U7+P6WOTV1K2bfO/WNRancLI+9y/n mhWJF3Fwg5A5J6X0ZFZtSMnPYphcFzB2iWsKgsTOKZ/X9cWEp4N1LM+TF2Zz4CsQRI npMzYsxHWdIT7HanIqD9x1QRquq9cOTq0bTSviZk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 03/19] ipa: libipa: exposure_mode_helper: Remove limit getters Date: Mon, 19 Oct 2026 20:45:10 +0200 Message-ID: <20261019184526.252977-4-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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" These getters have not been used since their introduction, so remove them for now. They can be easily restored if needed. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/exposure_mode_helper.cpp | 25 ------------------------- src/ipa/libipa/exposure_mode_helper.h | 5 ----- 2 files changed, 30 deletions(-) diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index ef14729471..380edee825 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -294,31 +294,6 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const exposure / (exposureTime * gain * quantGain) }; } -/** - * \fn ExposureModeHelper::minExposureTime() - * \brief Retrieve the configured minimum exposure time limit set through - * setLimits() - * \return The minExposureTime_ value - */ - -/** - * \fn ExposureModeHelper::maxExposureTime() - * \brief Retrieve the configured maximum exposure time set through setLimits() - * \return The maxExposureTime_ value - */ - -/** - * \fn ExposureModeHelper::minGain() - * \brief Retrieve the configured minimum gain set through setLimits() - * \return The minGain_ value - */ - -/** - * \fn ExposureModeHelper::maxGain() - * \brief Retrieve the configured maximum gain set through setLimits() - * \return The maxGain_ value - */ - } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h index c474cc2c13..a9398d3b4c 100644 --- a/src/ipa/libipa/exposure_mode_helper.h +++ b/src/ipa/libipa/exposure_mode_helper.h @@ -40,11 +40,6 @@ public: [[nodiscard]] Result splitExposure(utils::Duration exposure) const; - utils::Duration minExposureTime() const { return minExposureTime_; } - utils::Duration maxExposureTime() const { return maxExposureTime_; } - double minGain() const { return minGain_; } - double maxGain() const { return maxGain_; } - private: utils::Duration clampExposureTime(utils::Duration exposureTime, double *quantizationGain = nullptr) const; From patchwork Mon Oct 19 18:45: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: 28511 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 3B063C324C for ; Mon, 19 Oct 2026 18:45:50 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 007A568EEB; Mon, 19 Oct 2026 20:45:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="IfZM8UtQ"; 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 0908168E44 for ; Mon, 19 Oct 2026 20:45:32 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B2328593 for ; Mon, 19 Oct 2026 20:43:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435405; bh=bHhtrUvoiPIBUDSS5JcRzWTJXJESTeQnPQJx4/Wu50g=; h=From:To:Subject:Date:In-Reply-To:References:From; b=IfZM8UtQXoX0YNbNC/PVoe8dBnnzdu54piMV5wXvMo6Gsl/0jnK0VArLiZURrDOwb fSqMwmjpG5M7LhYfx3XFy+Xvp71/p6yZQx29V7Hvx2XHY5LyahChpbRKfdRUm7Qx+h 9CHYUCbGg6FeeKnYOGp8NzRtszVjOQlcL3mrsfYM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 04/19] ipa: libipa: exposure_mode_helper: Move `stageGain` variable Date: Mon, 19 Oct 2026 20:45:11 +0200 Message-ID: <20261019184526.252977-5-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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" After the loop, `stageGain == lastStageGain`, so use `lastStageGain` in the later calculation instead, and make `stageGain` "local" to the loop, just like `stageExposure` already is. And with that, also move the calculation a bit later, just before the value is actually used. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/exposure_mode_helper.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index 380edee825..51f4be98e2 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -240,13 +240,11 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const exposure / (exposureTime * gain * quantGain) }; } - double stageGain = clampGain(1.0); - double lastStageGain = stageGain; + double lastStageGain = clampGain(1.0); for (const auto &stage : stages_) { utils::Duration stageExposureTime = clampExposureTime(stage.exposureTime, &quantGain); - stageGain = clampGain(stage.gain); /* * We perform the clamping on both exposure time and gain in @@ -267,6 +265,8 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const } /* Clamp the exposureTime to stageExposureTime and regulate gain. */ + const double stageGain = clampGain(stage.gain); + if (stageExposureTime * stageGain >= exposure) { exposureTime = stageExposureTime; gain = clampGain(exposure / exposureTime, &quantGain2); @@ -283,10 +283,10 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const * From here on all we can do is max out the exposure time, followed by * the analogue gain. If we still haven't achieved the target we send * the rest of the exposure time to digital gain. If we were given no - * stages to use then the default stageGain of 1.0 is used so that - * exposure time is maxed before gain is touched at all. + * stages to use then the default gain of 1.0 is used so that exposure + * time is maxed before gain is touched at all. */ - exposureTime = clampExposureTime(exposure / stageGain, &quantGain); + exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain); gain = clampGain(exposure / exposureTime, &quantGain2); quantGain *= quantGain2; From patchwork Mon Oct 19 18:45: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: 28512 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 02D2AC3302 for ; Mon, 19 Oct 2026 18:45:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CBDD568EE4; Mon, 19 Oct 2026 20:45:38 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="lqzRzmrf"; 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 3E81768EBE for ; Mon, 19 Oct 2026 20:45:32 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2083FE72 for ; Mon, 19 Oct 2026 20:43:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435406; bh=+aEng39cPMXWU3HbTD27th6WUEaK9u602lZ829pd/qQ=; h=From:To:Subject:Date:In-Reply-To:References:From; b=lqzRzmrfiGMZ266EMVGMAuImdBcoTGnL286HelcjkgOiAFDgPOs6dpYpRNLVO38Ra +BoGeF2rhBcD4dUTljgH8oWXuhxt1VrxTBH/Srl4RX/7dAKPWAD/7onxFCQA6sloH9 Kl/CESCZW1y2mW6zCnZjS/OXqPQQhFsNTgCjodk4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 05/19] ipa: libipa: exposure_mode_helper: Handle no stages Date: Mon, 19 Oct 2026 20:45:12 +0200 Message-ID: <20261019184526.252977-6-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 the handling of the "no stage" explicit. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/exposure_mode_helper.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index 51f4be98e2..7a810a3673 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -240,7 +240,22 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const exposure / (exposureTime * gain * quantGain) }; } - double lastStageGain = clampGain(1.0); + const double unitGain = clampGain(1.0); + + /* + * If no stages are present, max out the exposure time, then the + * analogue gain, and use digital gain for the rest. + */ + if (stages_.empty()) { + exposureTime = clampExposureTime(exposure / unitGain, &quantGain); + gain = clampGain(exposure / exposureTime, &quantGain2); + quantGain *= quantGain2; + + return { exposureTime, gain, quantGain, + exposure / (exposureTime * gain * quantGain) }; + } + + double lastStageGain = unitGain; for (const auto &stage : stages_) { utils::Duration stageExposureTime = clampExposureTime(stage.exposureTime, @@ -282,9 +297,7 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const /* * From here on all we can do is max out the exposure time, followed by * the analogue gain. If we still haven't achieved the target we send - * the rest of the exposure time to digital gain. If we were given no - * stages to use then the default gain of 1.0 is used so that exposure - * time is maxed before gain is touched at all. + * the rest of the exposure time to digital gain. */ exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain); gain = clampGain(exposure / exposureTime, &quantGain2); From patchwork Mon Oct 19 18:45: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: 28513 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 77437C324C for ; Mon, 19 Oct 2026 18:45:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BC4E568EEE; Mon, 19 Oct 2026 20:45:41 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="JU18JZ6j"; 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 9BB9168EC1 for ; Mon, 19 Oct 2026 20:45:32 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7CB89593 for ; Mon, 19 Oct 2026 20:43:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435406; bh=9RSMKK+Z6+1uddptWGCuJE2yemrS5ZhvR1CkMoH6Pgg=; h=From:To:Subject:Date:In-Reply-To:References:From; b=JU18JZ6j5+Kzz58ZQnyYoc/jRZpUcdrq1vvfo5bfD7c6hzldLvDJ5jivoablYMWuF ppUA8g2Q3v2ktp8h3Cii1l0aDHHzIZhnyQw8rz0h8x7mmTLAEHB0g9xA68b92Gm80v eLgrYCJblZG82Xay2UY8jwWEGOPnimzmVmM59W6s= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 06/19] ipa: libipa: exposure_mode_helper: Consider last stage as hard-limit Date: Mon, 19 Oct 2026 20:45:13 +0200 Message-ID: <20261019184526.252977-7-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 `splitExposure()` will raise the exposure and analogue gain above what is specified in the last stage, if the desired exposure could not be reached. This behaviour is different from that of the raspberry pi ipa, and also could be surprising to the user. So make sure that the exposure time and analogue gain is limited by the last stage, in addition to the runtime controlled upper bound. Closes: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/362 Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/exposure_mode_helper.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index 7a810a3673..cadd1903d8 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -203,8 +203,9 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons * value. * * If no combination of exposure time and gain limits is found that meets the - * required exposure, the helper falls-back to simply maximising the exposure - * time first, followed by analogue gain, followed by digital gain. + * required exposure, then the exposure and gain of the last stage will be used, + * unless there are no stages, in which case first the exposure, then the + * analogue gain, then the digital gain will be maximized as necessary. * * During the calculations the gain missed due to quantization is recorded and * returned as quantization gain. The quantization gain is not included in the @@ -295,12 +296,11 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const } /* - * From here on all we can do is max out the exposure time, followed by - * the analogue gain. If we still haven't achieved the target we send - * the rest of the exposure time to digital gain. + * If no stages could satisfy the desired exposure, then use the last + * stage's exposure time and gain. */ - exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain); - gain = clampGain(exposure / exposureTime, &quantGain2); + exposureTime = clampExposureTime(stages_.back().exposureTime, &quantGain); + gain = clampGain(stages_.back().gain, &quantGain2); quantGain *= quantGain2; return { exposureTime, gain, quantGain, From patchwork Mon Oct 19 18:45: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: 28514 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 11CE5C3302 for ; Mon, 19 Oct 2026 18:45:55 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F0AD168EC1; Mon, 19 Oct 2026 20:45:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="dq9AZb5j"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D459168ECE for ; Mon, 19 Oct 2026 20:45:32 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B75B5E72 for ; Mon, 19 Oct 2026 20:43:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435406; bh=wtTAY5vtM/caAdMzMwrGAuygupIwZ6ByCAMx7byYGpc=; h=From:To:Subject:Date:In-Reply-To:References:From; b=dq9AZb5j2j7GPje0STN8QnaC44bBK7PR2eEjB1WEP6oNVm8AgGVpF7IYCa9ajJVeY 5DWkGuVeMu4G9xWuKnWqaQgqakAPssWSKKNBDxIsEdpnFQDuz7MFpAE9YRbdIKA82B VcmpZyZ569DZ82BSoPDlOOJn5e8JiFflpg0hmTkM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 07/19] ipa: libipa: exposure_mode_helper: Add separate maximize/minimize modes Date: Mon, 19 Oct 2026 20:45:14 +0200 Message-ID: <20261019184526.252977-8-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 two separate modes of operation to `ExposureModeHelper` in addition to the already existing "staged" mode. Starting with this change, the list of stages cannot be empty, and it will not default to maximizing the exposure first, then the gains. The behaviour has to be selected explicitly using one of the two new modes. The "maximize" mode is similar to the previous fallback behaviour, it maximizes exposure time as much as possible before resorting to increasing gains. The "minimize" mode is the opposite of this, it tries to keep the exposure time as low as possible, using as much gain as possible. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_mean_luminance.cpp | 6 +- src/ipa/libipa/exposure_mode_helper.cpp | 167 +++++++++++++++++------- src/ipa/libipa/exposure_mode_helper.h | 36 +++-- 3 files changed, 151 insertions(+), 58 deletions(-) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index eff639fb67..f0e47cc06d 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -328,13 +328,11 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) /* * If we don't have any exposure modes in the tuning data we create an - * ExposureModeHelper using an empty vector of stages. This will result - * in the ExposureModeHelper simply driving the exposure time as high as - * possible before touching gain. + * ExposureModeHelper in "maximize" mode. */ if (exposureModeHelpers_.empty()) exposureModeHelpers_.try_emplace(controls::ExposureNormal, - std::vector{}); + ExposureModeHelper::Maximize{}); return 0; } diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index cadd1903d8..13dce50c48 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -7,6 +7,7 @@ #include "exposure_mode_helper.h" #include +#include #include @@ -74,16 +75,55 @@ namespace ipa { */ /** - * \brief Construct an ExposureModeHelper instance + * \class ExposureModeHelper::Maximize + * \brief Tag type to select mode of operation + * + * When an instance is constructed with this tag type, it will + * be initialized in a mode of operation where it tries to use + * as much exposure time as possible when reaching the target. + * Consequently, this mode minimizes the use of gain. + */ + +/** + * \class ExposureModeHelper::Minimize + * \brief Tag type to select mode of operation + * + * When an instance is constructed with this tag type, it will + * be initialized in a mode of operation where it tries to use + * as little exposure time as possible when reaching the target. + * Consequently, this mode maximizes the use of gain. + */ + +/** + * \brief Construct an ExposureModeHelper in Maximize mode + */ +ExposureModeHelper::ExposureModeHelper(Maximize) + : mode_(Maximize{}) +{ +} + +/** + * \brief Construct an ExposureModeHelper in Minimize mode + */ +ExposureModeHelper::ExposureModeHelper(Minimize) + : mode_(Minimize{}) +{ +} + +/** + * \brief Construct an ExposureModeHelper instance in staged mode * \param[in] stages The vector of paired exposure time and gain limits * - * The vector of stages may be empty. In that case, the helper will simply use - * the runtime limits set through setLimits() instead. + * This overload initializes the object in such a mode where it uses the + * stages specified in \a stages to decide how much exposure time and gain + * should be used to reach a given target exposure. + * + * The vector of stages must not be empty. */ ExposureModeHelper::ExposureModeHelper(std::vector stages) - : stages_(std::move(stages)), lineDuration_(1us), minExposureTime_(0us), - maxExposureTime_(0us), minGain_(0), maxGain_(0), sensorHelper_(nullptr) + : mode_(std::move(stages)) { + ASSERT(!std::get_if>(&mode_)->empty()); } /** @@ -182,30 +222,11 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons * \param[in] exposure Exposure value * * This function divides a given exposure into exposure time, analogue and - * digital gain by iterating through stages of exposure time and gain limits. - * At each stage the current stage's exposure time limit is multiplied by the - * previous stage's gain limit (or 1.0 initially) to see if the combination of - * the two can meet the required exposure. If they cannot then the current - * stage's exposure time limit is multiplied by the same stage's gain limit to - * see if that combination can meet the required exposure time. If they cannot - * then the function moves to consider the next stage. - * - * When a combination of exposure time and gain _stage_ limits are found that - * are sufficient to meet the required exposure, the function attempts to reduce - * exposure time as much as possible whilst fixing gain and still meeting the - * exposure. If a _runtime_ limit prevents exposure time from being lowered - * enough to meet the exposure with gain fixed at the stage limit, gain is also - * lowered to compensate. - * - * Once the exposure time and gain values are ascertained, gain is assigned as - * analogue gain as much as possible, with digital gain only in use if the - * maximum analogue gain runtime limit is unable to accommodate the exposure - * value. + * digital gain according to the mode of operation configured at construction time: * - * If no combination of exposure time and gain limits is found that meets the - * required exposure, then the exposure and gain of the last stage will be used, - * unless there are no stages, in which case first the exposure, then the - * analogue gain, then the digital gain will be maximized as necessary. + * - ExposureModeHelper::ExposureModeHelper(Maximize) + * - ExposureModeHelper::ExposureModeHelper(Maximize) + * - ExposureModeHelper::ExposureModeHelper(std::vector) * * During the calculations the gain missed due to quantization is recorded and * returned as quantization gain. The quantization gain is not included in the @@ -229,8 +250,8 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const bool exposureTimeFixed = minExposureTime_ == maxExposureTime_; /* - * There's no point entering the loop if we cannot change either gain - * nor exposure time anyway. + * There's no point entering further processing if we can change neither + * gain nor exposure time anyway. */ if (exposureTimeFixed && gainFixed) { exposureTime = clampExposureTime(minExposureTime_, &quantGain); @@ -241,24 +262,78 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const exposure / (exposureTime * gain * quantGain) }; } - const double unitGain = clampGain(1.0); + return std::visit([&](const auto &mode) { + return splitExposure(exposure, mode); + }, mode_); +} - /* - * If no stages are present, max out the exposure time, then the - * analogue gain, and use digital gain for the rest. - */ - if (stages_.empty()) { - exposureTime = clampExposureTime(exposure / unitGain, &quantGain); - gain = clampGain(exposure / exposureTime, &quantGain2); - quantGain *= quantGain2; +ExposureModeHelper::Result +ExposureModeHelper::splitExposure(utils::Duration exposure, Maximize) const +{ + double quantGain, quantGain2; - return { exposureTime, gain, quantGain, - exposure / (exposureTime * gain * quantGain) }; - } + utils::Duration exposureTime = clampExposureTime(exposure / clampGain(1.0), &quantGain); + double gain = clampGain(exposure / exposureTime, &quantGain2); + quantGain *= quantGain2; + + return { exposureTime, gain, quantGain, + exposure / (exposureTime * gain * quantGain) }; +} + +ExposureModeHelper::Result +ExposureModeHelper::splitExposure(utils::Duration exposure, Minimize) const +{ + double quantGain, quantGain2; + + double gain = clampGain(exposure / clampExposureTime(minExposureTime_), &quantGain2); + utils::Duration exposureTime = clampExposureTime(exposure / gain, &quantGain); + quantGain *= quantGain2; + + return { exposureTime, gain, quantGain, + exposure / (exposureTime * gain * quantGain) }; +} + +/** + * \brief Split exposure into exposure time and gain + * \param[in] exposure Exposure value + * \param[in] stages The list of exposure stages + * + * This function divides a given exposure into exposure time, analogue and + * digital gain by iterating through stages of exposure time and gain limits. + * At each stage the current stage's exposure time limit is multiplied by the + * previous stage's gain limit (or 1.0 initially) to see if the combination of + * the two can meet the required exposure. If they cannot then the current + * stage's exposure time limit is multiplied by the same stage's gain limit to + * see if that combination can meet the required exposure time. If they cannot + * then the function moves to consider the next stage. + * + * When a combination of exposure time and gain _stage_ limits are found that + * are sufficient to meet the required exposure, the function attempts to reduce + * exposure time as much as possible whilst fixing gain and still meeting the + * exposure. If a _runtime_ limit prevents exposure time from being lowered + * enough to meet the exposure with gain fixed at the stage limit, gain is also + * lowered to compensate. + * + * Once the exposure time and gain values are ascertained, gain is assigned as + * analogue gain as much as possible, with digital gain only in use if the + * maximum analogue gain runtime limit is unable to accommodate the exposure + * value. + * + * If no combination of exposure time and gain limits is found that meets the + * required exposure, then the exposure and gain of the last stage will be used. + */ +ExposureModeHelper::Result +ExposureModeHelper::splitExposure(utils::Duration exposure, std::span stages) const +{ + ASSERT(!stages.empty()); - double lastStageGain = unitGain; + utils::Duration exposureTime; + double gain; + double quantGain; + double quantGain2; + double lastStageGain = clampGain(1.0); - for (const auto &stage : stages_) { + for (const auto &stage : stages) { utils::Duration stageExposureTime = clampExposureTime(stage.exposureTime, &quantGain); @@ -299,8 +374,8 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const * If no stages could satisfy the desired exposure, then use the last * stage's exposure time and gain. */ - exposureTime = clampExposureTime(stages_.back().exposureTime, &quantGain); - gain = clampGain(stages_.back().gain, &quantGain2); + exposureTime = clampExposureTime(stages.back().exposureTime, &quantGain); + gain = clampGain(stages.back().gain, &quantGain2); quantGain *= quantGain2; return { exposureTime, gain, quantGain, diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h index a9398d3b4c..a6cfb83880 100644 --- a/src/ipa/libipa/exposure_mode_helper.h +++ b/src/ipa/libipa/exposure_mode_helper.h @@ -7,6 +7,8 @@ #pragma once +#include +#include #include #include @@ -24,7 +26,15 @@ public: utils::Duration exposureTime; double gain; }; + struct Maximize { + explicit Maximize() = default; + }; + struct Minimize { + explicit Minimize() = default; + }; + ExposureModeHelper(Maximize); + ExposureModeHelper(Minimize); ExposureModeHelper(std::vector stages); void configure(utils::Duration lineLength, const CameraSensorHelper *sensorHelper); @@ -45,14 +55,24 @@ private: double *quantizationGain = nullptr) const; double clampGain(double gain, double *quantizationGain = nullptr) const; - std::vector stages_; - - utils::Duration lineDuration_; - utils::Duration minExposureTime_; - utils::Duration maxExposureTime_; - double minGain_; - double maxGain_; - const CameraSensorHelper *sensorHelper_; + [[nodiscard]] Result splitExposure(utils::Duration exposure, + Maximize) const; + [[nodiscard]] Result splitExposure(utils::Duration exposure, + Minimize) const; + [[nodiscard]] Result splitExposure(utils::Duration exposure, + std::span stages) const; + + std::variant< + Maximize, + Minimize, + std::vector + > mode_; + utils::Duration lineDuration_ = std::chrono::microseconds(1); + utils::Duration minExposureTime_ = {}; + utils::Duration maxExposureTime_ = {}; + double minGain_ = 0; + double maxGain_ = 0; + const CameraSensorHelper *sensorHelper_ = nullptr; }; } /* namespace ipa */ From patchwork Mon Oct 19 18:45: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: 28515 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 3EB70C324C for ; Mon, 19 Oct 2026 18:45:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2239868ED9; Mon, 19 Oct 2026 20:45:45 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FILmEFY0"; 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 18B8968ED1 for ; Mon, 19 Oct 2026 20:45:33 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EE482593 for ; Mon, 19 Oct 2026 20:43:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435407; bh=VhSZ1BTG+jnNFy1ZfcxjAhfB9165TOgaHioiMRxEou4=; h=From:To:Subject:Date:In-Reply-To:References:From; b=FILmEFY0OSJfSBzBK1VxRvwtB8yWC03edvYAKKKrB76nErhogzIflf039lw3qPiY+ HFJ6DCpemCScDn8jzVDP5f189712rbKb3s7WaFG6a5sTQn6Tqm1LJQNYecRe7CPtXC vrQWdKpjkptKAKfm3xBA5/vJs9RibZ52hdP5VYig= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 08/19] libcamera: controls: Add two new exposure modes Date: Mon, 19 Oct 2026 20:45:15 +0200 Message-ID: <20261019184526.252977-9-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 two new `AeExposureMode` enumerators: "Minimize" and "Maximize". The already existing exposure modes depend on specific configuration in tuning files. These two new modes implement very simple exposure splitting behaviour: either minimize / maximize the used exposure time. The "staged" exposure modes limit the maximum exposure / gain to those of the last stage. This means that e.g. the exposure time will not be raised even if the application would allow it by explicitly changing `FrameDurationLimits`. Short of manual AGC it is not really possible to work around that on the application side. And having to modify tuning files is inconvenient. The new "Maximize" mode is a better fit to represent how `AgcMSV` operates, so adjust the reported exposure mode if that algorithm used. And lastly, change `AgcMeanLuminance::parseTuningData()` so that it always creates exposure mode helper instances for the two new modes as they require no configuration. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 2 +- src/ipa/libipa/agc_mean_luminance.cpp | 25 ++++++++++++++++--------- src/libcamera/control_ids_core.yaml | 6 ++++++ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 51b05e3c2a..e793de832b 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -489,7 +489,7 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, [&](AgcMSV &) { /* No constraint/exposure mode support. */ state.constraintMode = controls::AeConstraintModeEnum::ConstraintNormal; - state.exposureMode = controls::AeExposureModeEnum::ExposureNormal; + state.exposureMode = controls::AeExposureModeEnum::ExposureMaximize; state.automatic.yTarget = 0; /* Not supported. */ diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index f0e47cc06d..3ecdebd3cf 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -281,11 +281,20 @@ int AgcMeanLuminance::parseConstraintModes(const ValueNode &tuningData) int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) { + static const std::pair modes[] = { + { "ExposureNormal", controls::ExposureNormal }, + { "ExposureShort", controls::ExposureShort }, + { "ExposureLong", controls::ExposureLong }, + { "ExposureCustom", controls::ExposureCustom }, + }; + const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; if (exposureModes.isDictionary()) { for (const auto &[modeName, modeValues] : exposureModes.asDict()) { - auto it = AeExposureModeNameValueMap.find(modeName); - if (it == AeExposureModeNameValueMap.end()) { + auto it = std::find_if(std::begin(modes), std::end(modes), [&name = modeName](const auto &m) { + return m.first == name; + }); + if (it == std::end(modes)) { LOG(AgcMeanLuminance, Warning) << "Skipping unknown exposure mode '" << modeName << "'"; continue; @@ -326,13 +335,11 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) } } - /* - * If we don't have any exposure modes in the tuning data we create an - * ExposureModeHelper in "maximize" mode. - */ - if (exposureModeHelpers_.empty()) - exposureModeHelpers_.try_emplace(controls::ExposureNormal, - ExposureModeHelper::Maximize{}); + /* These need no configuration, so they are always available. */ + exposureModeHelpers_.try_emplace(controls::ExposureMaximize, + ExposureModeHelper::Maximize{}); + exposureModeHelpers_.try_emplace(controls::ExposureMinimize, + ExposureModeHelper::Minimize{}); return 0; } diff --git a/src/libcamera/control_ids_core.yaml b/src/libcamera/control_ids_core.yaml index 89991d03d7..c29d9fb1d3 100644 --- a/src/libcamera/control_ids_core.yaml +++ b/src/libcamera/control_ids_core.yaml @@ -188,6 +188,12 @@ controls: - name: ExposureCustom value: 3 description: Custom exposure mode. + - name: ExposureMaximize + value: 4 + description: Maximize exposure time. + - name: ExposureMinimize + value: 5 + description: Minimize exposure time. - ExposureValue: type: float From patchwork Mon Oct 19 18:45: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: 28516 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 5A9C9C3316 for ; Mon, 19 Oct 2026 18:45:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C164068EFC; Mon, 19 Oct 2026 20:45:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="VACPd2Tc"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5EFC368ED3 for ; Mon, 19 Oct 2026 20:45:33 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 30558E72; Mon, 19 Oct 2026 20:43:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435407; bh=jLqmWZYogSEE1SlpHBhkB06fBbtOIoJcy59a4ssj/cg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VACPd2TcBnqEXyf3fiADsUlt5Z76VFSzTyQxG34e8m0938zRXmkn9FZ0XvmxKu21S MoR62DQu46JGKirDI5jLS8T5GME2DFVKNouQm4tlThwgUatAbSSAAfe3SZNZSVDft2 65Lvumagq2+Nrac/m6tO5RTrc3mzdUVD1UWz7quk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Jacopo Mondi Subject: [RFC PATCH v3 09/19] ipa: libipa: agc: Keep frame duration limits ordered Date: Mon, 19 Oct 2026 20:45:16 +0200 Message-ID: <20261019184526.252977-10-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 frame duration limits from controls are already clamped to the session limits, but that does not prevent a situation where min > max. Use the just computed `state.minFrameDuration` as the lower bound when clamping the max value in order to avoid this possibility. Signed-off-by: Barnabás Pőcze Reviewed-by: Stefan Klug Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index e793de832b..76d0bfd669 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -642,7 +642,7 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s state.maxFrameDuration = std::clamp( std::chrono::microseconds((*frameDurationLimits).back()), - session.minFrameDuration, session.maxFrameDuration); + state.minFrameDuration, session.maxFrameDuration); } frameContext.minFrameDuration = state.minFrameDuration; frameContext.maxFrameDuration = state.maxFrameDuration; From patchwork Mon Oct 19 18:45: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: 28517 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 31110C3357 for ; Mon, 19 Oct 2026 18:45:58 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D027668EF8; Mon, 19 Oct 2026 20:45:47 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ZJ6o3G0l"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A5E1A68ED4 for ; Mon, 19 Oct 2026 20:45:33 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7A899593; Mon, 19 Oct 2026 20:43:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435407; bh=tcf1hydhn+NPpFMxmOfZC+437ObbMVs1A9d+WQKTRas=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZJ6o3G0lSLJjQpLiFkfrpwZIB76/YfIWYmB5xGwUDRRjr89ttoItGsuyAQn7mAUe4 xHG0wTWYS8T4YwtWoyW1Vw3sce8GULT3TsfURaNO4aRJGKc4GGvtLLsGGDObNlhfpg rnKEVozt4e32bQgZQmlz0auR2d5hK+BNVsHDplbI= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Jacopo Mondi Subject: [RFC PATCH v3 10/19] ipa: libipa: agc: Retrieve `FrameDurationLimits` earlier Date: Mon, 19 Oct 2026 20:45:17 +0200 Message-ID: <20261019184526.252977-11-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 frame duration limits will be used to clamp the manually provided exposure time. Handle them earlier so that they are available. Signed-off-by: Barnabás Pőcze Reviewed-by: Stefan Klug Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 76d0bfd669..2cfd4c554c 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -591,6 +591,20 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s } } + 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()), + state.minFrameDuration, session.maxFrameDuration); + } + frameContext.minFrameDuration = state.minFrameDuration; + frameContext.maxFrameDuration = state.maxFrameDuration; + const auto &exposure = controls.get(controls::ExposureTime); if (exposure && !state.autoExposureEnabled) { state.manual.exposure = *exposure * 1.0us / session.lineDuration; @@ -632,20 +646,6 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s 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()), - state.minFrameDuration, session.maxFrameDuration); - } - frameContext.minFrameDuration = state.minFrameDuration; - frameContext.maxFrameDuration = state.maxFrameDuration; } /** From patchwork Mon Oct 19 18:45: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: 28518 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 1D41CC324C for ; Mon, 19 Oct 2026 18:45:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8807268EF9; Mon, 19 Oct 2026 20:45:49 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OZddD1wf"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DF50068ED6 for ; Mon, 19 Oct 2026 20:45:33 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id BFE8DE72 for ; Mon, 19 Oct 2026 20:43:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435407; bh=+gBza+BHZMf65VV1aUwghDN7Jp9C0HXQQ7wulEJXHRk=; h=From:To:Subject:Date:In-Reply-To:References:From; b=OZddD1wfypZCt6lI6wNmFy7UPBhJ42CVlVUyr3IfIy7BEtCC0eGA/B2SpqO2LeLOK BRpUoAvpVbVMbYUMxC9SP19FXA7qmMa5eMmmkcCwDfmszUOvJ3l93Nqwh7mjq28h/k wqJ42+HYxnu725NhJpETcEkv2ouWehggKuedbPcs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 11/19] ipa: libipa: agc: Report `FrameDurationLimits` Date: Mon, 19 Oct 2026 20:45:18 +0200 Message-ID: <20261019184526.252977-12-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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" `FrameDurationLimits` can be reported in metadata, to signal the exact limits that were used when processing the frame. This was missing from the agc algorithm, so add it. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 2cfd4c554c..99fb623258 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -869,6 +869,9 @@ void AgcAlgorithm::fillMetadata(const agc::Session &session, metadata.set(controls::ExposureTime, utils::Duration(session.lineDuration * frameContext.exposure).get()); metadata.set(controls::FrameDuration, frameContext.frameDuration.get()); + metadata.set(controls::FrameDurationLimits, + { static_cast(frameContext.minFrameDuration.get()), + static_cast(frameContext.maxFrameDuration.get()) }); metadata.set(controls::ExposureTimeMode, frameContext.autoExposureEnabled ? controls::ExposureTimeModeAuto : controls::ExposureTimeModeManual); From patchwork Mon Oct 19 18:45: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: 28520 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 7271EC3307 for ; Mon, 19 Oct 2026 18:46:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6EA0D68F0C; Mon, 19 Oct 2026 20:45:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="g0ov9F6w"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1E73068ED7 for ; Mon, 19 Oct 2026 20:45:34 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 016E9593 for ; Mon, 19 Oct 2026 20:43:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435408; bh=ORzl40oy4sK4C/knz9e2pfZpoaJi1/lUP2pnh+MzwNM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=g0ov9F6wYgvz4eU6za9auGhvjSJ6YKMEL9bkoKrB2MJk56mJVyUrenN1YrUT+HXoE X/HIGqrknoZjTKAYsmu1JhPbaUsmh722q78L+3RsUOTxlq9NA8BjtvPw7kw/pzGjT4 xqyUlKs8MigoQEFrrfYQq1Q0Z+o+J+33WZKMuQ+E= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 12/19] ipa: libipa: agc: Calculate vblank and frame duration sooner Date: Mon, 19 Oct 2026 20:45:19 +0200 Message-ID: <20261019184526.252977-13-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Calculating the vblank and frame duration is problematic in `process()` because at the moment it is calculated for an already finished frame based on the new suggested exposure time. Instead, move the calculation to `prepare()` where the frame's exposure and gain are finalized. Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/algorithms/agc.cpp | 2 +- src/ipa/libipa/agc.cpp | 36 ++++++++++++++--------------- src/ipa/libipa/agc.h | 6 ++--- src/ipa/mali-c55/algorithms/agc.cpp | 2 +- src/ipa/rkisp1/algorithms/agc.cpp | 2 +- src/ipa/softisp/algorithms/agc.cpp | 2 +- 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index ccf65da317..afe80dc062 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -111,7 +111,7 @@ 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); + agc_.prepare(context.configuration.agc, context.activeState.agc, frameContext.agc); } Histogram Agc::parseStatistics(const ipu3_uapi_stats_3a *stats, diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 99fb623258..c13cd3554d 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -650,6 +650,7 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s /** * \brief Prepare a frame + * \param[in] session The agc session configuration * \param[in] state The agc active state * \param[in] frameContext The agc frame context * @@ -659,11 +660,10 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s * \ref agc::FrameContext::gain "frameContext.gain" will be finalized * and may be used by the caller (see agc::prepareControls()). * - * \todo Finalize \ref agc::FrameContext::vblank "frameContext.vblank" as well - * * \sa Algorithm::prepare() */ -void AgcAlgorithm::prepare(agc::ActiveState &state, agc::FrameContext &frameContext) +void AgcAlgorithm::prepare(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext) { uint32_t activeAutoExposure = state.automatic.exposure; double activeAutoGain = state.automatic.gain; @@ -694,6 +694,8 @@ void AgcAlgorithm::prepare(agc::ActiveState &state, agc::FrameContext &frameCont } frameContext.yTarget = state.automatic.yTarget; + + processFrameDuration(session, frameContext); } /** @@ -724,7 +726,6 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, ControlList &metadata) { if (!params) { - processFrameDuration(session, frameContext, frameContext.minFrameDuration); fillMetadata(session, frameContext, metadata); return; } @@ -828,13 +829,6 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, << "quantization-gain: " << state.automatic.quantizationGain << ", " << "digital-gain: " << state.automatic.digitalGain; - /* - * Expand the target frame duration so that we do not run faster than - * the minimum frame duration when we have short exposures. - */ - processFrameDuration(session, frameContext, - std::max(frameContext.minFrameDuration, newExposureTime)); - fillMetadata(session, frameContext, metadata); } @@ -842,22 +836,26 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, * \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. + * Compute and populate vblank for \a frameContext. */ void AgcAlgorithm::processFrameDuration(const agc::Session &session, - agc::FrameContext &frameContext, - utils::Duration frameDuration) + agc::FrameContext &frameContext) { const utils::Duration &lineDuration = session.lineDuration; - frameContext.vblank = - (frameDuration / lineDuration) - session.sensor.outputSize.height; + /* + * Expand the target frame duration so that we do not run faster than + * the minimum frame duration when we have short exposures. + */ + const auto frameLength = std::max( + frameContext.minFrameDuration / lineDuration, + frameContext.exposure); + + frameContext.vblank = frameLength - session.sensor.outputSize.height; /* Update frame duration accounting for line length quantization. */ - frameContext.frameDuration = - (session.sensor.outputSize.height + frameContext.vblank) * lineDuration; + frameContext.frameDuration = frameLength * lineDuration; } void AgcAlgorithm::fillMetadata(const agc::Session &session, diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index a5700d2625..4914c9cee2 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -146,7 +146,8 @@ public: void queueRequest(const agc::Session &session, agc::ActiveState &state, agc::FrameContext &frameContext, const ControlList &controls); - void prepare(agc::ActiveState &state, agc::FrameContext &frameContext); + void prepare(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext); void process(const agc::Session &session, agc::ActiveState &state, agc::FrameContext &frameContext, std::optional &¶ms, @@ -154,8 +155,7 @@ public: private: void processFrameDuration(const agc::Session &session, - agc::FrameContext &frameContext, - utils::Duration frameDuration); + agc::FrameContext &frameContext); void fillMetadata(const agc::Session &session, const agc::FrameContext &frameContext, ControlList &metadata); diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 2c747da891..5684b4df95 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -207,7 +207,7 @@ void Agc::fillWeightsArrayBuffer(MaliC55Params *params, const enum MaliC55Blocks void Agc::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, MaliC55Params *params) { - agc_.prepare(context.activeState.agc, frameContext.agc); + agc_.prepare(context.configuration.agc, context.activeState.agc, frameContext.agc); if (frame > 0) return; diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 1f1d6c96a3..34e186d8b6 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -209,7 +209,7 @@ void Agc::queueRequest(IPAContext &context, void Agc::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) { - agc_.prepare(context.activeState.agc, frameContext.agc); + agc_.prepare(context.configuration.agc, context.activeState.agc, frameContext.agc); if (context.configuration.compress.supported) { frameContext.compress.enable = true; diff --git a/src/ipa/softisp/algorithms/agc.cpp b/src/ipa/softisp/algorithms/agc.cpp index ea234bc2ae..7d2a53b385 100644 --- a/src/ipa/softisp/algorithms/agc.cpp +++ b/src/ipa/softisp/algorithms/agc.cpp @@ -74,7 +74,7 @@ void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t fram void Agc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) { - agc_.prepare(context.activeState.agc, frameContext.agc); + agc_.prepare(context.configuration.agc, context.activeState.agc, frameContext.agc); } void Agc::process(IPAContext &context, From patchwork Mon Oct 19 18:45: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: 28519 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 D52FFC3302 for ; Mon, 19 Oct 2026 18:45:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 93FA368ED1; Mon, 19 Oct 2026 20:45:50 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ihr4TXuI"; 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 646A568EC4 for ; Mon, 19 Oct 2026 20:45:34 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 37ADEE72; Mon, 19 Oct 2026 20:43:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435408; bh=+srkd2e3zfvosJyJ8WCzWPKsjtxHEVefRB/JmYixrm4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ihr4TXuI2e42s1fA7tm0k7DZrIAAkVimz8k6HgaHHG+N4RI+ymsHopXCXppzR8Q5c dTdiBt6wNNzGhwQ2d/2L/x4ush2ZaLOK6z7jEbsk6vkiwFGQMF7/6UhgJwd1F/iQfC gX9fl1vD+crHu6qyh2LyVJXxmv8pIMTcbBZAi6R8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Jacopo Mondi Subject: [RFC PATCH v3 13/19] ipa: libipa: agc: Take parameters from active state for calculation Date: Mon, 19 Oct 2026 20:45:20 +0200 Message-ID: <20261019184526.252977-14-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The `frameContext` is of an already completed frame, its exposure/gain/etc is of no real concern for future frames, so take the most recent settings from the active state. This is still not ideal, but better than the previous status quo. Signed-off-by: Barnabás Pőcze Reviewed-by: Stefan Klug Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index c13cd3554d..0884615535 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -743,24 +743,22 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, double minAnalogueGain; double maxAnalogueGain; - /* \todo This uses the configuration from an already completed frame. */ - - if (frameContext.autoExposureEnabled) { + if (state.autoExposureEnabled) { minExposureTime = session.minExposureTime; - maxExposureTime = std::clamp(frameContext.maxFrameDuration, + maxExposureTime = std::clamp(state.maxFrameDuration, session.minExposureTime, session.maxExposureTime); } else { - minExposureTime = lineDuration * frameContext.exposure; + minExposureTime = lineDuration * state.manual.exposure; maxExposureTime = minExposureTime; } - if (frameContext.autoGainEnabled) { + if (state.autoGainEnabled) { minAnalogueGain = session.minAnalogueGain; maxAnalogueGain = session.maxAnalogueGain; } else { - minAnalogueGain = frameContext.gain; - maxAnalogueGain = frameContext.gain; + minAnalogueGain = state.manual.gain; + maxAnalogueGain = state.manual.gain; } std::visit(utils::overloaded{ @@ -806,10 +804,10 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, .traits = params->traits, .yHist = params->yHist, .effectiveExposureValue = effectiveExposureValue, - .constraintModeIndex = frameContext.constraintMode, - .exposureModeIndex = frameContext.exposureMode, + .constraintModeIndex = state.constraintMode, + .exposureModeIndex = state.exposureMode, .lux = params->lux, - .exposureCompensation = std::pow(2.0, frameContext.exposureValue), + .exposureCompensation = std::pow(2.0, state.exposureValue), }); /* Update the estimated exposure and gain. */ From patchwork Mon Oct 19 18:45: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: 28521 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 29585C3316 for ; Mon, 19 Oct 2026 18:46:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AC3A568ED7; Mon, 19 Oct 2026 20:45:52 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="InjrYj8t"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9A6B068ED9 for ; Mon, 19 Oct 2026 20:45:34 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7F6E2593 for ; Mon, 19 Oct 2026 20:43:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435408; bh=2iSL6MICk7hbVlqME9w8+v/ZGueMSAFyjLWb3+yr9yM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=InjrYj8tGRyWL3/QriMmSGStP865tUE6IDpPy5VW5ezX6H07RooPT0E6NFrnkC33s BGuv6VzA8kwSN03bUCrboHuf2v2uoLDKtzXbnXFYvhJXXSaLUNNNGeb+iUIwWedrZy Wh4aDPKzHYDfb5m1x2IwfA/d5x5IEDIqx607duU8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 14/19] libcamera: pipeline: Set vblank on more platforms Date: Mon, 19 Oct 2026 20:45:21 +0200 Message-ID: <20261019184526.252977-15-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The common `AgcAlgorithm` provides the desired vblank value the given frame, so adjust `agc::prepareControls()` accordingly, and propagate the vblank value to the sensors in the pipeline handlers. Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/ipu3.cpp | 3 +-- src/ipa/libipa/agc.cpp | 5 ++--- src/ipa/libipa/agc.h | 9 +++++---- src/ipa/mali-c55/mali-c55.cpp | 3 +-- src/ipa/rkisp1/rkisp1.cpp | 4 +--- src/ipa/softisp/softisp.cpp | 3 +-- src/libcamera/pipeline/ipu3/ipu3.cpp | 1 + src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1 + src/libcamera/pipeline/simple/simple.cpp | 1 + 9 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp index 30d52cfcf9..e4333539af 100644 --- a/src/ipa/ipu3/ipu3.cpp +++ b/src/ipa/ipu3/ipu3.cpp @@ -551,8 +551,7 @@ void IPAIPU3::setControls(unsigned int frame) IPAFrameContext &frameContext = context_.frameContexts.get(frame); ControlList ctrls(context_.sensorControls); - agc::prepareControls(ctrls, context_.camHelper.get(), - frameContext.agc.exposure, frameContext.agc.gain); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); ControlList lensCtrls(lensCtrls_); lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 0884615535..7fbaeefb3d 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -56,11 +56,10 @@ namespace agc { /** * \fn prepareControls(ControlList &controls, const CameraSensorHelper *sensor, - * uint32_t exposure, double gain) + * const FrameContext &frameContext) * \param[out] controls The controls list to populate * \param[in] sensor The CameraSensorHelper - * \param[in] exposure The exposure (in lines) - * \param[in] gain The analogue gain + * \param[in] frameContext The agc frame context * * 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, diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index 4914c9cee2..3ca36aea64 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -107,13 +107,14 @@ extractControls(const ControlList &controls, const CameraSensorHelper *sensor) inline void prepareControls(ControlList &controls, const CameraSensorHelper *sensor, - uint32_t exposure, double gain) + const FrameContext &frameContext) { - controls.set(V4L2_CID_EXPOSURE, static_cast(exposure)); + controls.set(V4L2_CID_EXPOSURE, static_cast(frameContext.exposure)); controls.set(V4L2_CID_ANALOGUE_GAIN, static_cast(sensor - ? sensor->gainCode(gain) - : static_cast(gain))); + ? sensor->gainCode(frameContext.gain) + : static_cast(frameContext.gain))); + controls.set(V4L2_CID_VBLANK, static_cast(frameContext.vblank)); } } /* namespace agc */ diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 9a2918cf2c..9541417c96 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -134,8 +134,7 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig void IPAMaliC55::setControls(const IPAFrameContext &frameContext) { ControlList ctrls(context_.sensorControls); - agc::prepareControls(ctrls, context_.camHelper.get(), - frameContext.agc.exposure, frameContext.agc.gain); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); setSensorControls.emit(ctrls); } diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 79ab7338c1..cefaf40aee 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -367,9 +367,7 @@ void IPARkISP1::setControls(unsigned int frame) << ", gain " << frameContext.agc.gain << ", vblank " << vblank; ControlList ctrls(context_.sensorControls); - agc::prepareControls(ctrls, context_.camHelper.get(), - exposure, frameContext.agc.gain); - ctrls.set(V4L2_CID_VBLANK, static_cast(vblank)); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); setSensorControls.emit(frame, ctrls); } diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp index 8acfcd1a0b..fbb3f3302f 100644 --- a/src/ipa/softisp/softisp.cpp +++ b/src/ipa/softisp/softisp.cpp @@ -249,8 +249,7 @@ void IPASoftIsp::processStats(const uint32_t frame, metadataReady.emit(frame, metadata); ControlList ctrls(context_.sensorControls); - agc::prepareControls(ctrls, context_.camHelper.get(), - frameContext.agc.exposure, frameContext.agc.gain); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); setSensorControls.emit(ctrls); } diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp index 14cab9e555..46af776ee9 100644 --- a/src/libcamera/pipeline/ipu3/ipu3.cpp +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp @@ -1084,6 +1084,7 @@ int PipelineHandlerIPU3::registerCameras() std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + { V4L2_CID_VBLANK, { delays.vblankDelay, true } }, }; data->delayedCtrls_ = diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp index 73a03373c8..8225472ce8 100644 --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -1909,6 +1909,7 @@ bool PipelineHandlerMaliC55::registerMemoryInputCamera(MediaLink *link) std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + { V4L2_CID_VBLANK, { delays.vblankDelay, true } }, }; data->delayedCtrls_ = diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index 35c29ceca1..6444df7217 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -569,6 +569,7 @@ SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe, std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + { V4L2_CID_VBLANK, { delays.vblankDelay, true } }, }; delayedCtrls_ = std::make_unique(sensor_->device(), params); From patchwork Mon Oct 19 18:45: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: 28522 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 A2B3FC3358 for ; Mon, 19 Oct 2026 18:46:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D3B2868EF4; Mon, 19 Oct 2026 20:45:54 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="JkJ8YDv4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D47B968EDB for ; Mon, 19 Oct 2026 20:45:34 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B3D77E72 for ; Mon, 19 Oct 2026 20:43:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435408; bh=UaPmXD/Y1N7DMmdRqoi2fU74cgw5Mh7DtCmJ/aLiRsk=; h=From:To:Subject:Date:In-Reply-To:References:From; b=JkJ8YDv4EGazg0p/T/jpO2FIVlCHqsYfUiPdeKujMOaQU1FFQkN35h8u/FAtxE60v B3d5stL+n9Sy8dJLuPgLHH7zls7j/Oh5Qs6wf1DW3cSBN6QeO1RNPj4vQYUu8MoOTJ FJISCAeutQ9qeP4Q2rsldU/JU5hTmLFeFPwGDbGU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 15/19] ipa: libipa: agc: Rework frame duration limit calculation Date: Mon, 19 Oct 2026 20:45:22 +0200 Message-ID: <20261019184526.252977-16-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 frame duration is calculated as `frameHeight * lineDuration`, but it was effectively repeating the line duration calculation, but with microsecond precision. Instead, simply reuse the already calculated line duration, and use the already calculated min/max values in the provided in `IPACameraSensorInfo`. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 7fbaeefb3d..ac81e0f601 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -388,25 +388,6 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, << "line-duration: " << session.lineDuration << ", " << "sensor-output: " << session.sensor.outputSize; - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - - 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 = static_cast(lineLength) * frameHeights[i]; - frameDurations[i] = frameSize * 1000000U / config.sensorInfo.pixelRate; - } /* * When the AGC computes the new exposure values for a frame, it needs @@ -420,8 +401,8 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, session.minAnalogueGain = minGain; session.maxAnalogueGain = maxGain; session.defAnalogueGain = defGain; - session.minFrameDuration = std::chrono::microseconds(frameDurations[0]); - session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]); + session.minFrameDuration = config.sensorInfo.minFrameLength * session.lineDuration; + session.maxFrameDuration = config.sensorInfo.maxFrameLength * session.lineDuration; /* Configure the default exposure and gain. */ state = {}; @@ -460,8 +441,12 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, static_cast(defExposure * lineDurationUs), }; config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ - frameDurations[0], frameDurations[1], - std::span{ { frameDurations[0], frameDurations[1] } }, + static_cast(session.minFrameDuration.get()), + static_cast(session.maxFrameDuration.get()), + std::span{ { + static_cast(state.minFrameDuration.get()), + static_cast(state.maxFrameDuration.get()), + } }, }; const auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) { From patchwork Mon Oct 19 18:45: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: 28523 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 519F7C335A for ; Mon, 19 Oct 2026 18:46:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8522968EF6; Mon, 19 Oct 2026 20:45:56 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="tvxpHmsL"; 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 249EA68EDE for ; Mon, 19 Oct 2026 20:45:35 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E9CD2593; Mon, 19 Oct 2026 20:43:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435409; bh=2yiui2T6pedd8LAGgN2yVMGMVEYklSJ4xzV0fKiwAdE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tvxpHmsLy4PJFrW48hn6FMuKiB3TEBXodT9/JrYVApuggHdzXeidTXIvN5Mhd37ST /Co6mKTigGND++8B/JgCYROCglvxjj3RC2TYHOJVqshOluqkgf5LQbI8WGABqlxr57 X9UFKZSatfgVdIqIoypJFQ2v6Ljgm9tQrzx4AWWQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 16/19] ipa: camera_sensor_helper: Introduce exposure margin Date: Mon, 19 Oct 2026 20:45:23 +0200 Message-ID: <20261019184526.252977-17-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 There is a margin between the maximum achievable shutter time and the frame length. The value is sensor-dependent and should be used to adjust the maximum achievable exposure time whenever the frame length changes. Introduce in the CameraSensorHelper the notion of exposureMargin_ as the minimum difference in number of lines between the current frame length and the maximum exposure time. This feature is already implemented in the RPi CamHelper class hierarchy with the name of frameIntegrationDiff. Populate the CameraSensorHelper instances with an exposureMargin_. The value of the exposure margin comes from the mainline driver version of each sensor, and it has been compared with the frameIntegrationDiff_ value in the rpi camera helpers. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/camera_sensor_helper.cpp | 68 +++++++++++++++++++++++++ src/ipa/libipa/camera_sensor_helper.h | 2 + 2 files changed, 70 insertions(+) diff --git a/src/ipa/libipa/camera_sensor_helper.cpp b/src/ipa/libipa/camera_sensor_helper.cpp index 359040ec74..3e13a844bc 100644 --- a/src/ipa/libipa/camera_sensor_helper.cpp +++ b/src/ipa/libipa/camera_sensor_helper.cpp @@ -76,6 +76,16 @@ namespace ipa { * \return The black level of the sensor, or std::nullopt if not known */ +/** + * \fn CameraSensorHelper::exposureMargin() + * \brief Fetch the exposure margin of the sensor + * + * This function returns the exposure margin of the sensor, in lines, which + * is the minimum difference between the integration time and frame length. + * + * \return The exposure margin of the sensor, or std::nullopt if not known + */ + /** * \brief Compute gain code from the analogue gain absolute value * \param[in] gain The real gain to pass @@ -229,6 +239,12 @@ double CameraSensorHelper::quantizeGain(double _gain, double *quantizationGain) * sensor specific. Use this variable to store the values at init time. */ +/** + * \var CameraSensorHelper::exposureMargin_ + * \brief The smallest margin between the integration time and the frame length + * in lines + */ + /** * \class CameraSensorHelperFactoryBase * \brief Base class for camera sensor helper factories @@ -387,6 +403,7 @@ public: { /* Power-on default value: 168 at 12bits. */ blackLevel_ = 2688; + exposureMargin_ = 4; } uint32_t gainCode(double gain) const override @@ -473,6 +490,11 @@ REGISTER_CAMERA_SENSOR_HELPER("ar0144", CameraSensorHelperAr0144) class CameraSensorHelperAr0521 : public CameraSensorHelper { public: + CameraSensorHelperAr0521() + { + exposureMargin_ = 4; + } + uint32_t gainCode(double gain) const override { gain = std::clamp(gain, 1.0, 15.5); @@ -503,6 +525,7 @@ public: /* From datasheet: 64 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 100, 0, 0, 1024 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("gc05a2", CameraSensorHelperGc05a2) @@ -515,6 +538,7 @@ public: /* From datasheet: 64 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 100, 0, 0, 1024 }; + exposureMargin_ = 16; } }; REGISTER_CAMERA_SENSOR_HELPER("gc08a3", CameraSensorHelperGc08a3) @@ -525,6 +549,7 @@ public: CameraSensorHelperHm1246() { gain_ = AnalogueGainLinear{ 1, 16, 0, 16 }; + exposureMargin_ = 2; } }; REGISTER_CAMERA_SENSOR_HELPER("hm1246", CameraSensorHelperHm1246) @@ -537,6 +562,7 @@ public: /* From datasheet: 64 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 512, -1, 512 }; + exposureMargin_ = 10; } }; REGISTER_CAMERA_SENSOR_HELPER("imx214", CameraSensorHelperImx214) @@ -549,6 +575,7 @@ public: /* From datasheet: 64 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 256, -1, 256 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("imx219", CameraSensorHelperImx219) @@ -561,6 +588,7 @@ public: /* From datasheet: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 512, -1, 512 }; + exposureMargin_ = 10; } }; REGISTER_CAMERA_SENSOR_HELPER("imx258", CameraSensorHelperImx258) @@ -573,6 +601,7 @@ public: /* From datasheet: 0x32 at 10bits. */ blackLevel_ = 3200; gain_ = AnalogueGainLinear{ 0, 2048, -1, 2048 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("imx283", CameraSensorHelperImx283) @@ -585,6 +614,7 @@ public: /* From datasheet: 0xf0 at 12bits. */ blackLevel_ = 3840; gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; + exposureMargin_ = 2; } }; REGISTER_CAMERA_SENSOR_HELPER("imx290", CameraSensorHelperImx290) @@ -595,6 +625,11 @@ public: CameraSensorHelperImx296() { gain_ = AnalogueGainExp{ 1.0, expGainDb(0.1) }; + /* + * The driver doesn't apply any margin. Use the value + * in RPi's CamHelper. + */ + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("imx296", CameraSensorHelperImx296) @@ -612,6 +647,7 @@ public: /* From datasheet: 0x32 at 10bits. */ blackLevel_ = 3200; gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; + exposureMargin_ = 10; } }; REGISTER_CAMERA_SENSOR_HELPER("imx335", CameraSensorHelperImx335) @@ -624,6 +660,7 @@ public: /* From datasheet: 64 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 1024, -1, 1024 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("imx355", CameraSensorHelperImx355) @@ -634,6 +671,7 @@ public: CameraSensorHelperImx415() { gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("imx415", CameraSensorHelperImx415) @@ -652,6 +690,7 @@ public: blackLevel_ = 4096; /* gain = 1024/(1024 - code); code = [0, 800]; max gain = x4.57 */ gain_ = AnalogueGainLinear{ 0, 1024, -1, 1024 }; + exposureMargin_ = 18; } }; REGISTER_CAMERA_SENSOR_HELPER("imx471", CameraSensorHelperImx471) @@ -662,6 +701,7 @@ public: CameraSensorHelperImx477() { gain_ = AnalogueGainLinear{ 0, 1024, -1, 1024 }; + exposureMargin_ = 22; } }; REGISTER_CAMERA_SENSOR_HELPER("imx477", CameraSensorHelperImx477) @@ -674,6 +714,7 @@ public: /* From datasheet: 0xc8 at 12bits. */ blackLevel_ = 3200; gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; + exposureMargin_ = 2; } }; REGISTER_CAMERA_SENSOR_HELPER("imx662", CameraSensorHelperImx662) @@ -686,6 +727,7 @@ public: /* From datasheet: 0x32 at 10bits. */ blackLevel_ = 3200; gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; + exposureMargin_ = 8; /* FIXME: kernel driver: 8, rpi: 4 */ } }; REGISTER_CAMERA_SENSOR_HELPER("imx678", CameraSensorHelperImx678) @@ -697,6 +739,7 @@ public: { blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 1024, -1, 1024 }; + exposureMargin_ = 48; } }; REGISTER_CAMERA_SENSOR_HELPER("imx708", CameraSensorHelperImx708) @@ -709,6 +752,7 @@ public: /* From dark frame measurement: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 1, 0, 0, 256 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov01a10", CameraSensorHelperOv01a10) @@ -721,6 +765,7 @@ public: /* From Linux kernel driver: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 6; } }; REGISTER_CAMERA_SENSOR_HELPER("ov08d10", CameraSensorHelperOv08d10) @@ -732,6 +777,7 @@ public: { blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov08x40", CameraSensorHelperOv08x40) @@ -744,6 +790,7 @@ public: blackLevel_ = 3600; /* gain is fixed to 1x, this is just a place holder. */ gain_ = AnalogueGainLinear{ 0, 2048, -1, 2048 }; + exposureMargin_ = 6; } }; REGISTER_CAMERA_SENSOR_HELPER("mira220", CameraSensorHelperMira220) @@ -758,6 +805,7 @@ public: * This has been validated with some empirical testing only. */ gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("ov2685", CameraSensorHelperOv2685) @@ -770,6 +818,7 @@ public: /* From Linux kernel driver: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov2740", CameraSensorHelperOv2740) @@ -782,6 +831,7 @@ public: /* From datasheet: 0x40 at 12bits. */ blackLevel_ = 1024; gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("ov4689", CameraSensorHelperOv4689) @@ -794,6 +844,14 @@ public: /* From datasheet: 0x10 at 10bits. */ blackLevel_ = 1024; gain_ = AnalogueGainLinear{ 1, 0, 0, 16 }; + /* + * Very convoluted in the driver that however applies a margin + * of 4 lines when setting vts. + * + * cap_vts = cap_shutter + 4; + * ret = ov5640_set_vts(sensor, cap_vts); + */ + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5640", CameraSensorHelperOv5640) @@ -804,6 +862,7 @@ public: CameraSensorHelperOv5647() { gain_ = AnalogueGainLinear{ 1, 0, 0, 16 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5647", CameraSensorHelperOv5647) @@ -814,6 +873,7 @@ public: CameraSensorHelperOv5670() { gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5670", CameraSensorHelperOv5670) @@ -826,6 +886,7 @@ public: /* From Linux kernel driver: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5675", CameraSensorHelperOv5675) @@ -837,6 +898,7 @@ public: { blackLevel_ = 1024; gain_ = AnalogueGainLinear{ 1, 0, 0, 16 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5693", CameraSensorHelperOv5693) @@ -847,6 +909,7 @@ public: CameraSensorHelperOv64a40() { gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 32; } }; REGISTER_CAMERA_SENSOR_HELPER("ov64a40", CameraSensorHelperOv64a40) @@ -863,6 +926,7 @@ public: * See: https://patchwork.linuxtv.org/project/linux-media/patch/20221106171129.166892-2-nicholas@rothemail.net/#142267 */ gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("ov8858", CameraSensorHelperOv8858) @@ -873,6 +937,7 @@ public: CameraSensorHelperOv8865() { gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov8865", CameraSensorHelperOv8865) @@ -883,6 +948,7 @@ public: CameraSensorHelperOv13858() { gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov13858", CameraSensorHelperOv13858) @@ -895,6 +961,7 @@ public: /* From datasheet: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 32, -1, 32 }; + exposureMargin_ = 64; } }; REGISTER_CAMERA_SENSOR_HELPER("vd55g1", CameraSensorHelperVd55g1) @@ -907,6 +974,7 @@ public: /* From datasheet: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 32, -1, 32 }; + exposureMargin_ = 75; } }; REGISTER_CAMERA_SENSOR_HELPER("vd56g3", CameraSensorHelperVd56g3) diff --git a/src/ipa/libipa/camera_sensor_helper.h b/src/ipa/libipa/camera_sensor_helper.h index bd3d0beec7..a9e55c832d 100644 --- a/src/ipa/libipa/camera_sensor_helper.h +++ b/src/ipa/libipa/camera_sensor_helper.h @@ -27,6 +27,7 @@ public: virtual ~CameraSensorHelper() = default; std::optional blackLevel() const { return blackLevel_; } + std::optional exposureMargin() const { return exposureMargin_; } virtual uint32_t gainCode(double gain) const; virtual double gain(uint32_t gainCode) const; double quantizeGain(double gain, double *quantizationGain) const; @@ -46,6 +47,7 @@ protected: std::optional blackLevel_; std::variant gain_; + std::optional exposureMargin_; private: LIBCAMERA_DISABLE_COPY_AND_MOVE(CameraSensorHelper) From patchwork Mon Oct 19 18:45: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: 28524 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 F098CC335B for ; Mon, 19 Oct 2026 18:46:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2AF9C68EFA; Mon, 19 Oct 2026 20:45:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="pX4vR137"; 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 5460468EDF for ; Mon, 19 Oct 2026 20:45:35 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 367EFE72 for ; Mon, 19 Oct 2026 20:43:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435409; bh=KqlnM5cidR61cqfTo1JO6IMKVg1PTGd5YURIDjBdv+M=; h=From:To:Subject:Date:In-Reply-To:References:From; b=pX4vR137wJkL0mpQW37kqHrlG3xUoS5rmDcAmaNdONMyzd8V0XcoUodn+S03YnKPQ 3bt+ZFJIZ3ceO0omrXYAeWuIW0mraMeg93JiO0d8lp77JixmFxSv8++ti5C5uNFeKr qOEtLWrF0hj00kxIa40Ds87e7STq1CuRgnI3dO8M= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 17/19] ipa: libipa: agc: Move control info map setup Date: Mon, 19 Oct 2026 20:45:24 +0200 Message-ID: <20261019184526.252977-18-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 setup of common controls to the end of `configure()`. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 82 +++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index ac81e0f601..9069257475 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -428,47 +428,6 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, config.ctrlMap.erase(&controls::AeConstraintMode); config.ctrlMap.erase(&controls::AeExposureMode); - /* \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{ - static_cast(session.minFrameDuration.get()), - static_cast(session.maxFrameDuration.get()), - std::span{ { - static_cast(state.minFrameDuration.get()), - static_cast(state.maxFrameDuration.get()), - } }, - }; - - const auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) { - std::array values; - size_t count = 0; - - if (session.autoAllowed) - values[count++] = ControlValue(automatic); - - values[count++] = ControlValue(manual); - - config.ctrlMap[&cid] = ControlInfo{ - { values.data(), count }, - ControlValue(session.autoAllowed ? automatic : manual), - }; - }; - - add(controls::ExposureTimeMode, - controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual); - add(controls::AnalogueGainMode, - controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual); - std::visit(utils::overloaded{ [&](AgcMSV &) { /* No constraint/exposure mode support. */ @@ -515,6 +474,47 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, }, }, impl_); + /* \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{ + static_cast(session.minFrameDuration.get()), + static_cast(session.maxFrameDuration.get()), + std::span{ { + static_cast(state.minFrameDuration.get()), + static_cast(state.maxFrameDuration.get()), + } }, + }; + + const auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) { + std::array values; + size_t count = 0; + + if (session.autoAllowed) + values[count++] = ControlValue(automatic); + + values[count++] = ControlValue(manual); + + config.ctrlMap[&cid] = ControlInfo{ + { values.data(), count }, + ControlValue(session.autoAllowed ? automatic : manual), + }; + }; + + add(controls::ExposureTimeMode, + controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual); + add(controls::AnalogueGainMode, + controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual); + return 0; } From patchwork Mon Oct 19 18:45: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: 28526 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 681CFC335C for ; Mon, 19 Oct 2026 18:46:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C1FAF68F06; Mon, 19 Oct 2026 20:45:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="eMfVj2Df"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8ECB968EE2 for ; Mon, 19 Oct 2026 20:45:35 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6E747593 for ; Mon, 19 Oct 2026 20:43:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435409; bh=uxeV1N6z/sekiiXO5Cqw0F8qJ8k2XF1QkfGQEzK3UJo=; h=From:To:Subject:Date:In-Reply-To:References:From; b=eMfVj2DfFsyljUt58JbYKtF1KjP55uhIDDwox3z7affwPe5jNBtTxm3ZomuPL2mH5 1rjbgrheBPmPFzlcjuB6rdf7OyNHiLjZ/KZaR9xSBjLRdoRH+TFvqCvAnjFSM/jeLC G8JQZPwF0n4x0lSC8OFA6aFuTgzn6LpfYMm3EW7s= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v3 18/19] ipa: libipa: exposure_mode_helper: Add getter for max exp time Date: Mon, 19 Oct 2026 20:45:25 +0200 Message-ID: <20261019184526.252977-19-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 getter that can be used to retrieve the maximum "static" exposure time (i.e. established by configuration, not by runtime `setLimits()` calls). Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/exposure_mode_helper.cpp | 21 +++++++++++++++++++++ src/ipa/libipa/exposure_mode_helper.h | 2 ++ 2 files changed, 23 insertions(+) diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index 13dce50c48..6345a47c54 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -10,6 +10,7 @@ #include #include +#include /** * \file exposure_mode_helper.h @@ -382,6 +383,26 @@ ExposureModeHelper::splitExposure(utils::Duration exposure, std::span &stages) { + return stages.back().exposureTime; + }, + }, mode_); +} + } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h index a6cfb83880..ce140192b5 100644 --- a/src/ipa/libipa/exposure_mode_helper.h +++ b/src/ipa/libipa/exposure_mode_helper.h @@ -50,6 +50,8 @@ public: [[nodiscard]] Result splitExposure(utils::Duration exposure) const; + [[nodiscard]] utils::Duration maxStageExposureTime() const; + private: utils::Duration clampExposureTime(utils::Duration exposureTime, double *quantizationGain = nullptr) const; From patchwork Mon Oct 19 18:45: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: 28525 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 AEBA9C3357 for ; Mon, 19 Oct 2026 18:46:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0AFBD68F03; Mon, 19 Oct 2026 20:45:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mJMvV1El"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CB5CA68EC5 for ; Mon, 19 Oct 2026 20:45:35 +0200 (CEST) Received: from pb-laptop.local (185.221.141.235.nat.pool.zt.hu [185.221.141.235]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A46C9E72; Mon, 19 Oct 2026 20:43:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1792435409; bh=LJU0jLTMk9zOVIvE87Ynm52fJgReQqQNn7PX91qgP/A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mJMvV1ElXMYjF1hdklq+l2Xp7GYPfZ1WiUt+c6SpXULpaFvN09Sh7zMvHzwerQt6g KIvHcOjJA4urVKiH3DSZX3wVNNr1txYJ3/dW+LiIbeaPcJmleqTDUsa3UriRzC+aUC O7bnWuZzh/qY/un2gMXtwPcxcpjIard+K0p5MQWE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v3 19/19] ipa: libipa: agc: Take exposure margin into account Date: Mon, 19 Oct 2026 20:45:26 +0200 Message-ID: <20261019184526.252977-20-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019184526.252977-1-barnabas.pocze@ideasonboard.com> References: <20261019184526.252977-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 sensor exposure time depends on the vertical blanking amount and the exposure margin. Instead of relying on the maximum exposure available at onfiguration, use the frame duration limits (~ vblank) and exposure margin to dynamically calculate the max available exposure time. Signed-off-by: Barnabás Pőcze Tested-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 128 +++++++++++++++++++++++++++++++++-------- src/ipa/libipa/agc.h | 3 +- 2 files changed, 106 insertions(+), 25 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 9069257475..a5814f8f24 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -70,12 +70,12 @@ namespace agc { * \struct Session * \brief Session configuration for AgcAlgorithm * + * \var Session::minExposure + * \brief Minimum exposure (in lines) for the streaming session + * * \var Session::minExposureTime * \brief Minimum exposure time for the streaming session * - * \var Session::maxExposureTime - * \brief Maximum exposure time for the streaming session - * * \var Session::minAnalogueGain * \brief Minimum analogue gain for the streaming session * @@ -100,6 +100,11 @@ namespace agc { * \var Session::sensor.outputSize * \brief Configured output size of the sensor * + * \var Session::sensor.exposureMargin + * \brief Exposure margin of the sensor + * + * \sa CameraSensorHelper::exposureMargin() + * * \var Session::autoAllowed * \copybrief AgcAlgorithm::ConfigurationParams::autoAllowed * \sa AgcAlgorithm::ConfigurationParams::autoAllowed @@ -221,6 +226,27 @@ namespace agc { } /* namespace agc */ +namespace { + +[[nodiscard]] +uint32_t clampExposure(const agc::Session &session, const agc::ActiveState &state, + uint32_t exposure) +{ + const uint32_t maxFrame = state.maxFrameDuration / session.lineDuration; + const uint32_t maxExposure = maxFrame - session.sensor.exposureMargin; + + return std::clamp(exposure, session.minExposure, maxExposure); +} + +[[nodiscard]] +uint32_t clampExposure(const agc::Session &session, const agc::ActiveState &state, + utils::Duration exposureTime) +{ + return clampExposure(session, state, exposureTime / session.lineDuration); +} + +} /* namespace */ + /** * \class AgcAlgorithm * \brief libIPA LSC algorithm algorithm @@ -360,7 +386,12 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, session.lineDuration = lineLength * 1.0s / config.sensorInfo.pixelRate; session.sensor.outputSize = config.sensorInfo.outputSize; - const double lineDurationUs = session.lineDuration.get(); + auto exposureMargin = sensor_ ? sensor_->exposureMargin() : std::nullopt; + session.sensor.exposureMargin = exposureMargin.value_or(4); + if (!exposureMargin) + LOG(Agc, Warning) + << "Sensor exposure margin not available, using " + << session.sensor.exposureMargin; /* * Compute exposure time limits from the V4L2_CID_EXPOSURE control @@ -369,7 +400,6 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, 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. */ @@ -382,28 +412,46 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, float maxGain = extractGain(v4l2Gain.max()); float defGain = extractGain(v4l2Gain.def()); - LOG(Agc, Debug) - << "exposure: [" << minExposure << ',' << maxExposure << "], " - << "gain: [" << minGain << ',' << maxGain << "], " - << "line-duration: " << session.lineDuration << ", " - << "sensor-output: " << session.sensor.outputSize; - - /* * 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.minExposure = minExposure; session.minExposureTime = minExposure * session.lineDuration; - session.maxExposureTime = maxExposure * session.lineDuration; session.minAnalogueGain = minGain; session.maxAnalogueGain = maxGain; session.defAnalogueGain = defGain; session.minFrameDuration = config.sensorInfo.minFrameLength * session.lineDuration; session.maxFrameDuration = config.sensorInfo.maxFrameLength * session.lineDuration; + /* + * The assumption is that the minimum exposure time is independent of vblank, + * only the maximum exposure time is affected by it. + */ + const uint32_t maxExposure = config.sensorInfo.maxFrameLength - session.sensor.exposureMargin; + const utils::Duration maxExposureTime = maxExposure * session.lineDuration; + + LOG(Agc, Debug) + << "exposure: [" << session.minExposure << ',' << maxExposure << "], " + << "exposure-time: [" << session.minExposureTime << ',' << maxExposureTime << "], " + << "gain: [" << session.minAnalogueGain << ',' << session.maxAnalogueGain << "], " + << "line-length: " << lineLength << ", " + << "line-duration: " << session.lineDuration << ", " + << "frame-height: [" << config.sensorInfo.minFrameLength << ',' << config.sensorInfo.maxFrameLength << "], " + << "sensor-output: " << session.sensor.outputSize << ", " + << "sensor-exposure-margin: " << session.sensor.exposureMargin; + + /* + * This is a sanity check. If this fails, something is very likely off. + * Equality could theoretically be allowed, but it is not so that + * `(frameDuration / lineDuration) - exposureMargin` is no less + * than the minimum exposure time even if the division is slightly + * inaccurate. + */ + if (session.minExposure + session.sensor.exposureMargin >= config.sensorInfo.minFrameLength) + return -EINVAL; + /* Configure the default exposure and gain. */ state = {}; state.automatic.gain = session.minAnalogueGain; @@ -428,6 +476,8 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, config.ctrlMap.erase(&controls::AeConstraintMode); config.ctrlMap.erase(&controls::AeExposureMode); + utils::Duration maxStageExposureTime = {}; + std::visit(utils::overloaded{ [&](AgcMSV &) { /* No constraint/exposure mode support. */ @@ -468,12 +518,40 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(options); options.clear(); - for (const auto &[id, _] : impl.exposureModeHelpers()) + for (const auto &[id, emh] : impl.exposureModeHelpers()) { options.emplace_back(id); + maxStageExposureTime = std::max( + maxStageExposureTime, + emh.maxStageExposureTime()); + } config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options); }, }, impl_); + if (session.autoAllowed) { + if (maxStageExposureTime) { + /* + * If there is at least one configured limit, select the + * default frame duration limits so that it does not + * prevent from reaching that limit. + */ + const utils::Duration margin = + session.sensor.exposureMargin * session.lineDuration; + + state.maxFrameDuration = std::clamp( + maxStageExposureTime + margin, + session.minFrameDuration, session.maxFrameDuration); + } else { + /* + * If there is no configured limit anywhere, enforce an arbitrary + * 10 fps to keep the frame rate reasonable. + */ + state.maxFrameDuration = std::clamp( + utils::Duration(1.0s / 10), + session.minFrameDuration, session.maxFrameDuration); + } + } + /* \todo Move this to the `Camera` class. */ config.ctrlMap[&controls::AeEnable] = ControlInfo{ false, session.autoAllowed, session.autoAllowed @@ -482,9 +560,10 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, minGain, maxGain, defGain }; config.ctrlMap[&controls::ExposureTime] = ControlInfo{ - static_cast(minExposure * lineDurationUs), - static_cast(maxExposure * lineDurationUs), - static_cast(defExposure * lineDurationUs), + static_cast(session.minExposureTime.get()), + static_cast(maxExposureTime.get()), + static_cast(state.automatic.exposure * + session.lineDuration.get()), }; config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ static_cast(session.minFrameDuration.get()), @@ -591,7 +670,7 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s const auto &exposure = controls.get(controls::ExposureTime); if (exposure && !state.autoExposureEnabled) { - state.manual.exposure = *exposure * 1.0us / session.lineDuration; + state.manual.exposure = clampExposure(session, state, *exposure * 1.0us); LOG(Agc, Debug) << "Set exposure to " << state.manual.exposure; } @@ -729,9 +808,8 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, if (state.autoExposureEnabled) { minExposureTime = session.minExposureTime; - maxExposureTime = std::clamp(state.maxFrameDuration, - session.minExposureTime, - session.maxExposureTime); + maxExposureTime = state.maxFrameDuration - + session.sensor.exposureMargin * lineDuration; } else { minExposureTime = lineDuration * state.manual.exposure; maxExposureTime = minExposureTime; @@ -803,6 +881,8 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, }, }, impl_); + state.automatic.exposure = clampExposure(session, state, state.automatic.exposure); + const utils::Duration newExposureTime = state.automatic.exposure * lineDuration; LOG(Agc, Debug) @@ -832,7 +912,7 @@ void AgcAlgorithm::processFrameDuration(const agc::Session &session, */ const auto frameLength = std::max( frameContext.minFrameDuration / lineDuration, - frameContext.exposure); + frameContext.exposure + session.sensor.exposureMargin); frameContext.vblank = frameLength - session.sensor.outputSize.height; diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index 3ca36aea64..6c9f4b2fb8 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -36,8 +36,8 @@ class Histogram; namespace agc { struct Session { + uint32_t minExposure; utils::Duration minExposureTime; - utils::Duration maxExposureTime; double minAnalogueGain; double maxAnalogueGain; double defAnalogueGain; @@ -47,6 +47,7 @@ struct Session { struct { Size outputSize; + uint32_t exposureMargin; } sensor; bool autoAllowed;