From patchwork Mon Nov 2 10:15:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28745 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 E17CFC3261 for ; Mon, 2 Nov 2026 10:16:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8F30969368; Mon, 2 Nov 2026 11:16:34 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="MuMeRyjN"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 44274691F0 for ; Mon, 2 Nov 2026 11:16:17 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F373020E4; Mon, 2 Nov 2026 11:14:00 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793614441; bh=Z8CRdHCJ1nFyacQGjEaY5cUqDhs7IM2sf6lYtXmh/YU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=MuMeRyjNV/4AySD6YjPXty6YPC7ZEsb1SsVhtaHEU1B9LxQu+1Qr4VYMfgz+gZt/F /ShBUXqjFXy3pQjyxQpkrH2O9/xRMTJK1G1BZ5FhemVJlKI+W/9GGJGG/NAmEUlXny Cgvjl+6nOAMXIYrFwB5U7xsOfNo3f5pwa/NfTOFU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v6 10/24] ipa: libipa: exposure_mode_helper: Consider last stage as hard-limit Date: Mon, 2 Nov 2026 11:15:56 +0100 Message-ID: <20261102101610.877390-11-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261102101610.877390-1-barnabas.pocze@ideasonboard.com> References: <20261102101610.877390-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 / tuning file creator. So make sure that the exposure time and analogue gain is limited by the last stage, in addition to the runtime controlled upper bound. With this change, at least one stage is required, so an assertions are added. And the general agc algorithm is adjusted to provide stages for the fallback "Maximize" mode. Closes: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/362 Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 7 ++++++- src/ipa/libipa/exposure_mode_helper.cpp | 23 +++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 3bc7e67b71..2a8dd36244 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -463,7 +463,12 @@ void addFallbacks(AgcMeanLuminance::Configuration &config) if (config.exposureModeHelpers.empty()) config.exposureModeHelpers.try_emplace( controls::AeExposureModeMaximize, - std::vector{}); + std::vector{ + ExposureModeHelper::Stage{ + .exposureTime = utils::Duration::max(), + .gain = std::numeric_limits::max(), + }, + }); } } /* namespace */ diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index 4076d6f385..9714cc950e 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -84,6 +84,7 @@ ExposureModeHelper::ExposureModeHelper(std::vector &&stages) : stages_(std::move(stages)), lineDuration_(1us), minExposureTime_(0us), maxExposureTime_(0us), minGain_(0), maxGain_(0), sensorHelper_(nullptr) { + ASSERT(!stages_.empty()); } /** @@ -203,8 +204,8 @@ 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, + * and the digital gain will be increased 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 @@ -219,6 +220,7 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const { ASSERT(maxExposureTime_); ASSERT(maxGain_); + ASSERT(!stages_.empty()); utils::Duration exposureTime; double gain; @@ -240,11 +242,11 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const exposure / (exposureTime * gain * quantGain) }; } + utils::Duration stageExposureTime = {}; double stageGain = clampGain(1.0); for (const auto &stage : stages_) { - utils::Duration stageExposureTime = clampExposureTime(stage.exposureTime, - &quantGain); + stageExposureTime = clampExposureTime(stage.exposureTime, &quantGain); /* * We perform the clamping on both exposure time and gain in @@ -265,7 +267,7 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const } /* Clamp the exposure time to stageExposureTime and regulate gain. */ - stageGain = clampGain(stage.gain); + stageGain = clampGain(stage.gain, &quantGain2); if (stageExposureTime * stageGain >= exposure) { exposureTime = stageExposureTime; @@ -278,14 +280,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 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. + * If no stages could satisfy the desired exposure, then use the last + * stage's exposure time and gain, which is the maximum allowed. */ - exposureTime = clampExposureTime(exposure / stageGain, &quantGain); - gain = clampGain(exposure / exposureTime, &quantGain2); + exposureTime = stageExposureTime; + gain = stageGain; quantGain *= quantGain2; return { exposureTime, gain, quantGain,