[{"id":39827,"web_url":"https://patchwork.libcamera.org/comment/39827/","msgid":"<amNeBAgfuM0XDpPU@zed>","date":"2026-07-24T12:49:46","subject":"Re: [RFC PATCH v2 16/43] ipa: libipa: agc_mean_luminance:\n\tcalculateNewEv(): Collect results","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Barnabás\n\nOn Thu, Jul 23, 2026 at 05:42:59PM +0200, Barnabás Pőcze wrote:\n> Add a new type that gives proper names to the returned quantities\n> instead of just using an `std::tuple`.\n>\n> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n> ---\n>  src/ipa/ipu3/algorithms/agc.cpp         | 10 ++++------\n>  src/ipa/libipa/agc_mean_luminance.cpp   | 11 ++++++++---\n>  src/ipa/libipa/agc_mean_luminance.h     |  7 ++++---\n>  src/ipa/libipa/exposure_mode_helper.cpp | 19 ++++++++++++++++++-\n>  src/ipa/libipa/exposure_mode_helper.h   | 11 ++++++++---\n>  src/ipa/mali-c55/algorithms/agc.cpp     | 10 ++++------\n>  src/ipa/rkisp1/algorithms/agc.cpp       | 16 +++++++---------\n>  7 files changed, 53 insertions(+), 31 deletions(-)\n>\n> diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp\n> index ac5b6e7513..e74db62960 100644\n> --- a/src/ipa/ipu3/algorithms/agc.cpp\n> +++ b/src/ipa/ipu3/algorithms/agc.cpp\n> @@ -237,9 +237,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>  \tdouble analogueGain = frameContext.sensor.gain;\n>  \tutils::Duration effectiveExposureValue = exposureTime * analogueGain;\n>\n> -\tutils::Duration newExposureTime;\n> -\tdouble aGain, qGain, dGain;\n> -\tstd::tie(newExposureTime, aGain, qGain, dGain) = agc_.calculateNewEv({\n> +\tconst auto &newEv = agc_.calculateNewEv({\n\nDoesn't agc_.calculateNewEv() construct a Result before returning it ?\nDoes using a reference here help ?\n\n>  \t\t.traits = AgcTraits{\n>  \t\t\trgbTriples_,\n>  \t\t\t{{\n> @@ -257,12 +255,12 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>\n>  \tLOG(IPU3Agc, Debug)\n>  \t\t<< \"Divided up exposure time, analogue gain and digital gain are \"\n> -\t\t<< newExposureTime << \", \" << aGain << \" and \" << dGain;\n> +\t\t<< newEv.exposureTime << \", \" << newEv.analogueGain << \" and \" << newEv.digitalGain;\n>\n>  \tIPAActiveState &activeState = context.activeState;\n>  \t/* Update the estimated exposure time and gain. */\n> -\tactiveState.agc.exposure = newExposureTime / context.configuration.sensor.lineDuration;\n> -\tactiveState.agc.gain = aGain;\n> +\tactiveState.agc.exposure = newEv.exposureTime / context.configuration.sensor.lineDuration;\n> +\tactiveState.agc.gain = newEv.analogueGain;\n>\n>  \tmetadata.set(controls::AnalogueGain, frameContext.sensor.gain);\n>  \tmetadata.set(controls::ExposureTime, exposureTime.get<std::micro>());\n> diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp\n> index e8679c66be..5e12c4b475 100644\n> --- a/src/ipa/libipa/agc_mean_luminance.cpp\n> +++ b/src/ipa/libipa/agc_mean_luminance.cpp\n> @@ -668,6 +668,11 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue)\n>   * \\brief The index of the exposure mode to use\n>   */\n>\n> +/**\n> + * \\struct AgcMeanLuminance::Result\n> + * \\brief Collection of results of the mean luminance AGC algorithm\n\n        \\sa ExposureModeHelper::Result\n\nbut maybe it's not necessary as doxygen already provides an\ninheritance diagram for the two types\n\n> + */\n> +\n>  /**\n>   * \\brief Calculate the new exposure value and split it between exposure time\n>   * and gain\n> @@ -680,7 +685,7 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue)\n>   * \\return Tuple of exposure time, analogue gain, quantization gain and digital\n>   * gain\n>   */\n> -std::tuple<utils::Duration, double, double, double>\n> +AgcMeanLuminance::Result\n>  AgcMeanLuminance::calculateNewEv(const Params &params)\n>  {\n>  \t/*\n> @@ -699,7 +704,7 @@ AgcMeanLuminance::calculateNewEv(const Params &params)\n>  \t\t * doesn't get stuck with 0 in case the sensor driver allows a\n>  \t\t * min exposure of 0.\n>  \t\t */\n> -\t\treturn exposureModeHelper.splitExposure(10ms);\n> +\t\treturn { exposureModeHelper.splitExposure(10ms) };\n>  \t}\n>\n>  \tdouble gain = estimateInitialGain(params.traits);\n> @@ -721,7 +726,7 @@ AgcMeanLuminance::calculateNewEv(const Params &params)\n>  \tnewExposureValue = filterExposure(newExposureValue);\n>\n>  \tframeCount_++;\n> -\treturn exposureModeHelper.splitExposure(newExposureValue);\n> +\treturn { exposureModeHelper.splitExposure(newExposureValue) };\n>  }\n>\n>  /**\n> diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h\n> index 9b82d97ab6..aa4c0369ac 100644\n> --- a/src/ipa/libipa/agc_mean_luminance.h\n> +++ b/src/ipa/libipa/agc_mean_luminance.h\n> @@ -9,7 +9,6 @@\n>\n>  #include <map>\n>  #include <memory>\n> -#include <tuple>\n>  #include <vector>\n>\n>  #include <libcamera/base/utils.h>\n> @@ -87,8 +86,10 @@ public:\n>  \t\tuint32_t exposureModeIndex;\n>  \t};\n>\n> -\tstd::tuple<utils::Duration, double, double, double>\n> -\tcalculateNewEv(const Params &params);\n> +\tstruct Result : ExposureModeHelper::Result {\n> +\t};\n> +\n> +\t[[nodiscard]] Result calculateNewEv(const Params &params);\n>\n>  \tdouble effectiveYTarget() const;\n>\n> diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp\n> index 01c5cba8e2..762e27b4fd 100644\n> --- a/src/ipa/libipa/exposure_mode_helper.cpp\n> +++ b/src/ipa/libipa/exposure_mode_helper.cpp\n> @@ -153,6 +153,23 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons\n>  \treturn clamped;\n>  }\n>\n> +/**\n> + * \\struct ExposureModeHelper::Result\n> + * \\brief Result of splitExposure()\n> + *\n> + * \\var ExposureModeHelper::Result::exposureTime\n> + * \\brief The applicable exposure time\n> + *\n> + * \\var ExposureModeHelper::Result::analogueGain\n> + * \\brief The applicable analogue gain\n> + *\n> + * \\var ExposureModeHelper::Result::quantizationGain\n> + * \\brief The applicable quantization gain\n> + *\n> + * \\var ExposureModeHelper::Result::digitalGain\n> + * \\brief The applicable digital gain\n> + */\n> +\n>  /**\n>   * \\brief Split exposure into exposure time and gain\n>   * \\param[in] exposure Exposure value\n> @@ -190,7 +207,7 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons\n>   * \\return Tuple of exposure time, analogue gain, quantization gain and digital\n>   * gain\n>   */\n> -std::tuple<utils::Duration, double, double, double>\n> +ExposureModeHelper::Result\n>  ExposureModeHelper::splitExposure(utils::Duration exposure) const\n>  {\n>  \tASSERT(maxExposureTime_);\n> diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h\n> index 968192ddc5..2bab20f974 100644\n> --- a/src/ipa/libipa/exposure_mode_helper.h\n> +++ b/src/ipa/libipa/exposure_mode_helper.h\n> @@ -7,7 +7,6 @@\n>\n>  #pragma once\n>\n> -#include <tuple>\n>  #include <utility>\n>  #include <vector>\n>\n> @@ -30,8 +29,14 @@ public:\n>  \tvoid setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime,\n>  \t\t       double minGain, double maxGain);\n>\n> -\tstd::tuple<utils::Duration, double, double, double>\n> -\tsplitExposure(utils::Duration exposure) const;\n> +\tstruct Result {\n> +\t\tutils::Duration exposureTime;\n> +\t\tdouble analogueGain;\n> +\t\tdouble quantizationGain;\n> +\t\tdouble digitalGain;\n> +\t};\n> +\n> +\t[[nodiscard]] Result splitExposure(utils::Duration exposure) const;\n>\n>  \tutils::Duration minExposureTime() const { return minExposureTime_; }\n>  \tutils::Duration maxExposureTime() const { return maxExposureTime_; }\n> diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp\n> index fb97b87c6e..821f43e9fd 100644\n> --- a/src/ipa/mali-c55/algorithms/agc.cpp\n> +++ b/src/ipa/mali-c55/algorithms/agc.cpp\n> @@ -332,9 +332,7 @@ void Agc::process(IPAContext &context,\n>  \tutils::Duration currentShutter = exposure * configuration.sensor.lineDuration;\n>  \tutils::Duration effectiveExposureValue = currentShutter * analogueGain;\n>\n> -\tutils::Duration shutterTime;\n> -\tdouble aGain, qGain, dGain;\n> -\tstd::tie(shutterTime, aGain, qGain, dGain) = agc_.calculateNewEv({\n> +\tconst auto &newEv = agc_.calculateNewEv({\n\nSame question on & for this and rkisp1\n\n>  \t\t.traits = AgcTraits(statistics_),\n>  \t\t.yHist = statistics_.yHist,\n>  \t\t.effectiveExposureValue = effectiveExposureValue,\n> @@ -344,10 +342,10 @@ void Agc::process(IPAContext &context,\n>\n>  \tLOG(MaliC55Agc, Debug)\n>  \t\t<< \"Divided up shutter, analogue gain and digital gain are \"\n> -\t\t<< shutterTime << \", \" << aGain << \" and \" << dGain;\n> +\t\t<< newEv.exposureTime << \", \" << newEv.analogueGain << \" and \" << newEv.digitalGain;\n>\n> -\tactiveState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration;\n> -\tactiveState.agc.automatic.sensorGain = aGain;\n> +\tactiveState.agc.automatic.exposure = newEv.exposureTime / configuration.sensor.lineDuration;\n> +\tactiveState.agc.automatic.sensorGain = newEv.analogueGain;\n>\n>  \tmetadata.set(controls::ExposureTime, currentShutter.get<std::micro>());\n>  \tmetadata.set(controls::AnalogueGain, frameContext.agc.sensorGain);\n> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp\n> index 8b4bcbd71b..a4567c8258 100644\n> --- a/src/ipa/rkisp1/algorithms/agc.cpp\n> +++ b/src/ipa/rkisp1/algorithms/agc.cpp\n> @@ -720,9 +720,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>  \tagc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue));\n>  \tagc_.setLux(frameContext.lux.lux);\n>\n> -\tutils::Duration newExposureTime;\n> -\tdouble aGain, qGain, dGain;\n> -\tstd::tie(newExposureTime, aGain, qGain, dGain) = agc_.calculateNewEv({\n> +\tconst auto &newEv = agc_.calculateNewEv({\n>  \t\t.traits = AgcTraits{\n>  \t\t\t{ params->ae.exp_mean, context.hw.numAeCells },\n>  \t\t\tmeteringModes_.at(frameContext.agc.meteringMode),\n> @@ -735,21 +733,21 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>\n>  \tLOG(RkISP1Agc, Debug)\n>  \t\t<< \"Divided up exposure time, analogue gain, quantization gain\"\n> -\t\t<< \" and digital gain are \" << newExposureTime << \", \" << aGain\n> -\t\t<< \", \" << qGain << \" and \" << dGain;\n> +\t\t<< \" and digital gain are \" << newEv.exposureTime << \", \" << newEv.analogueGain\n> +\t\t<< \", \" << newEv.quantizationGain << \" and \" << newEv.digitalGain;\n>\n>  \tIPAActiveState &activeState = context.activeState;\n>  \t/* Update the estimated exposure and gain. */\n> -\tactiveState.agc.automatic.exposure = newExposureTime / lineDuration;\n> -\tactiveState.agc.automatic.gain = aGain;\n> -\tactiveState.agc.automatic.quantizationGain = qGain;\n> +\tactiveState.agc.automatic.exposure = newEv.exposureTime / lineDuration;\n> +\tactiveState.agc.automatic.gain = newEv.analogueGain;\n> +\tactiveState.agc.automatic.quantizationGain = newEv.quantizationGain;\n>  \tactiveState.agc.automatic.yTarget = agc_.effectiveYTarget();\n>  \t/*\n>  \t * Expand the target frame duration so that we do not run faster than\n>  \t * the minimum frame duration when we have short exposures.\n>  \t */\n>  \tprocessFrameDuration(context, frameContext,\n> -\t\t\t     std::max(frameContext.agc.minFrameDuration, newExposureTime));\n> +\t\t\t     std::max(frameContext.agc.minFrameDuration, newEv.exposureTime));\n\nminors apart\nReviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n\nThanks\n  j\n\n>\n>  \tfillMetadata(context, frameContext, metadata);\n>  }\n> --\n> 2.55.0\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 0951BBDE4C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 24 Jul 2026 12:49:52 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 1D8D267F0D;\n\tFri, 24 Jul 2026 14:49:51 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0F30567E89\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Jul 2026 14:49:50 +0200 (CEST)","from ideasonboard.com (93-46-82-201.ip106.fastwebnet.it\n\t[93.46.82.201])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 792C0524;\n\tFri, 24 Jul 2026 14:48:48 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"DVKPVCq8\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1784897328;\n\tbh=mKyZRoMQswevSZyKFhJHJLOAbmyfDFmUBLMs2HvyIfY=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=DVKPVCq855ZkWdtqmKuuIg+s/pKtwODMeo7dXcTYViZiFZnvM5iOt0I+HcXjSnAor\n\trHboLb5t67tz/7VAM1KrbaMDqus7HBG63nBQe65R/JEvpCT3DMXagkwuVWFFXqKL4C\n\tyFzqqAUx1yslrDqtCWK+210DwKcYgGM1hOIz05kY=","Date":"Fri, 24 Jul 2026 14:49:46 +0200","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [RFC PATCH v2 16/43] ipa: libipa: agc_mean_luminance:\n\tcalculateNewEv(): Collect results","Message-ID":"<amNeBAgfuM0XDpPU@zed>","References":"<20260723154327.1357866-1-barnabas.pocze@ideasonboard.com>\n\t<20260723154327.1357866-17-barnabas.pocze@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20260723154327.1357866-17-barnabas.pocze@ideasonboard.com>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":39830,"web_url":"https://patchwork.libcamera.org/comment/39830/","msgid":"<83a16b96-db9f-47f1-a405-e961c991bb1c@ideasonboard.com>","date":"2026-07-24T13:05:40","subject":"Re: [RFC PATCH v2 16/43] ipa: libipa: agc_mean_luminance:\n\tcalculateNewEv(): Collect results","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"2026. 07. 24. 14:49 keltezéssel, Jacopo Mondi írta:\n> Hi Barnabás\n> \n> On Thu, Jul 23, 2026 at 05:42:59PM +0200, Barnabás Pőcze wrote:\n>> Add a new type that gives proper names to the returned quantities\n>> instead of just using an `std::tuple`.\n>>\n>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n>> ---\n>>   src/ipa/ipu3/algorithms/agc.cpp         | 10 ++++------\n>>   src/ipa/libipa/agc_mean_luminance.cpp   | 11 ++++++++---\n>>   src/ipa/libipa/agc_mean_luminance.h     |  7 ++++---\n>>   src/ipa/libipa/exposure_mode_helper.cpp | 19 ++++++++++++++++++-\n>>   src/ipa/libipa/exposure_mode_helper.h   | 11 ++++++++---\n>>   src/ipa/mali-c55/algorithms/agc.cpp     | 10 ++++------\n>>   src/ipa/rkisp1/algorithms/agc.cpp       | 16 +++++++---------\n>>   7 files changed, 53 insertions(+), 31 deletions(-)\n>>\n>> diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp\n>> index ac5b6e7513..e74db62960 100644\n>> --- a/src/ipa/ipu3/algorithms/agc.cpp\n>> +++ b/src/ipa/ipu3/algorithms/agc.cpp\n>> @@ -237,9 +237,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>>   \tdouble analogueGain = frameContext.sensor.gain;\n>>   \tutils::Duration effectiveExposureValue = exposureTime * analogueGain;\n>>\n>> -\tutils::Duration newExposureTime;\n>> -\tdouble aGain, qGain, dGain;\n>> -\tstd::tie(newExposureTime, aGain, qGain, dGain) = agc_.calculateNewEv({\n>> +\tconst auto &newEv = agc_.calculateNewEv({\n> \n> Doesn't agc_.calculateNewEv() construct a Result before returning it ?\n> Does using a reference here help ?\n\nIt does not change anything, it's just what I usually do in these cases. You could\nuse `auto`, `auot&&`, or even `AgcMeanLuminance::Result`, all of them would effectively\nbe the same. This applies at every call-site.\n\n\n> \n>>   \t\t.traits = AgcTraits{\n>>   \t\t\trgbTriples_,\n>>   \t\t\t{{\n>> @@ -257,12 +255,12 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>>\n>>   \tLOG(IPU3Agc, Debug)\n>>   \t\t<< \"Divided up exposure time, analogue gain and digital gain are \"\n>> -\t\t<< newExposureTime << \", \" << aGain << \" and \" << dGain;\n>> +\t\t<< newEv.exposureTime << \", \" << newEv.analogueGain << \" and \" << newEv.digitalGain;\n>>\n>>   \tIPAActiveState &activeState = context.activeState;\n>>   \t/* Update the estimated exposure time and gain. */\n>> -\tactiveState.agc.exposure = newExposureTime / context.configuration.sensor.lineDuration;\n>> -\tactiveState.agc.gain = aGain;\n>> +\tactiveState.agc.exposure = newEv.exposureTime / context.configuration.sensor.lineDuration;\n>> +\tactiveState.agc.gain = newEv.analogueGain;\n>>\n>>   \tmetadata.set(controls::AnalogueGain, frameContext.sensor.gain);\n>>   \tmetadata.set(controls::ExposureTime, exposureTime.get<std::micro>());\n>> diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp\n>> index e8679c66be..5e12c4b475 100644\n>> --- a/src/ipa/libipa/agc_mean_luminance.cpp\n>> +++ b/src/ipa/libipa/agc_mean_luminance.cpp\n>> @@ -668,6 +668,11 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue)\n>>    * \\brief The index of the exposure mode to use\n>>    */\n>>\n>> +/**\n>> + * \\struct AgcMeanLuminance::Result\n>> + * \\brief Collection of results of the mean luminance AGC algorithm\n> \n>          \\sa ExposureModeHelper::Result\n> \n> but maybe it's not necessary as doxygen already provides an\n> inheritance diagram for the two types\n\nGood point, I did not check what it looks like. But I've just done so, and\nthere is an inheritance diagram, as well as an option under the direct members:\n\n   Public Attributes inherited from libcamera::ipa::ExposureModeHelper::Result\n\nand clicking on it brings up the members. So I think it's fine.\n   \n\n> \n>> + */\n>> +\n>>   /**\n>>    * \\brief Calculate the new exposure value and split it between exposure time\n>>    * and gain\n>> @@ -680,7 +685,7 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue)\n>>    * \\return Tuple of exposure time, analogue gain, quantization gain and digital\n>>    * gain\n>>    */\n>> -std::tuple<utils::Duration, double, double, double>\n>> +AgcMeanLuminance::Result\n>>   AgcMeanLuminance::calculateNewEv(const Params &params)\n>>   {\n>>   \t/*\n>> @@ -699,7 +704,7 @@ AgcMeanLuminance::calculateNewEv(const Params &params)\n>>   \t\t * doesn't get stuck with 0 in case the sensor driver allows a\n>>   \t\t * min exposure of 0.\n>>   \t\t */\n>> -\t\treturn exposureModeHelper.splitExposure(10ms);\n>> +\t\treturn { exposureModeHelper.splitExposure(10ms) };\n>>   \t}\n>>\n>>   \tdouble gain = estimateInitialGain(params.traits);\n>> @@ -721,7 +726,7 @@ AgcMeanLuminance::calculateNewEv(const Params &params)\n>>   \tnewExposureValue = filterExposure(newExposureValue);\n>>\n>>   \tframeCount_++;\n>> -\treturn exposureModeHelper.splitExposure(newExposureValue);\n>> +\treturn { exposureModeHelper.splitExposure(newExposureValue) };\n>>   }\n>>\n>>   /**\n>> diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h\n>> index 9b82d97ab6..aa4c0369ac 100644\n>> --- a/src/ipa/libipa/agc_mean_luminance.h\n>> +++ b/src/ipa/libipa/agc_mean_luminance.h\n>> @@ -9,7 +9,6 @@\n>>\n>>   #include <map>\n>>   #include <memory>\n>> -#include <tuple>\n>>   #include <vector>\n>>\n>>   #include <libcamera/base/utils.h>\n>> @@ -87,8 +86,10 @@ public:\n>>   \t\tuint32_t exposureModeIndex;\n>>   \t};\n>>\n>> -\tstd::tuple<utils::Duration, double, double, double>\n>> -\tcalculateNewEv(const Params &params);\n>> +\tstruct Result : ExposureModeHelper::Result {\n>> +\t};\n>> +\n>> +\t[[nodiscard]] Result calculateNewEv(const Params &params);\n>>\n>>   \tdouble effectiveYTarget() const;\n>>\n>> diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp\n>> index 01c5cba8e2..762e27b4fd 100644\n>> --- a/src/ipa/libipa/exposure_mode_helper.cpp\n>> +++ b/src/ipa/libipa/exposure_mode_helper.cpp\n>> @@ -153,6 +153,23 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons\n>>   \treturn clamped;\n>>   }\n>>\n>> +/**\n>> + * \\struct ExposureModeHelper::Result\n>> + * \\brief Result of splitExposure()\n>> + *\n>> + * \\var ExposureModeHelper::Result::exposureTime\n>> + * \\brief The applicable exposure time\n>> + *\n>> + * \\var ExposureModeHelper::Result::analogueGain\n>> + * \\brief The applicable analogue gain\n>> + *\n>> + * \\var ExposureModeHelper::Result::quantizationGain\n>> + * \\brief The applicable quantization gain\n>> + *\n>> + * \\var ExposureModeHelper::Result::digitalGain\n>> + * \\brief The applicable digital gain\n>> + */\n>> +\n>>   /**\n>>    * \\brief Split exposure into exposure time and gain\n>>    * \\param[in] exposure Exposure value\n>> @@ -190,7 +207,7 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons\n>>    * \\return Tuple of exposure time, analogue gain, quantization gain and digital\n>>    * gain\n>>    */\n>> -std::tuple<utils::Duration, double, double, double>\n>> +ExposureModeHelper::Result\n>>   ExposureModeHelper::splitExposure(utils::Duration exposure) const\n>>   {\n>>   \tASSERT(maxExposureTime_);\n>> diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h\n>> index 968192ddc5..2bab20f974 100644\n>> --- a/src/ipa/libipa/exposure_mode_helper.h\n>> +++ b/src/ipa/libipa/exposure_mode_helper.h\n>> @@ -7,7 +7,6 @@\n>>\n>>   #pragma once\n>>\n>> -#include <tuple>\n>>   #include <utility>\n>>   #include <vector>\n>>\n>> @@ -30,8 +29,14 @@ public:\n>>   \tvoid setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime,\n>>   \t\t       double minGain, double maxGain);\n>>\n>> -\tstd::tuple<utils::Duration, double, double, double>\n>> -\tsplitExposure(utils::Duration exposure) const;\n>> +\tstruct Result {\n>> +\t\tutils::Duration exposureTime;\n>> +\t\tdouble analogueGain;\n>> +\t\tdouble quantizationGain;\n>> +\t\tdouble digitalGain;\n>> +\t};\n>> +\n>> +\t[[nodiscard]] Result splitExposure(utils::Duration exposure) const;\n>>\n>>   \tutils::Duration minExposureTime() const { return minExposureTime_; }\n>>   \tutils::Duration maxExposureTime() const { return maxExposureTime_; }\n>> diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp\n>> index fb97b87c6e..821f43e9fd 100644\n>> --- a/src/ipa/mali-c55/algorithms/agc.cpp\n>> +++ b/src/ipa/mali-c55/algorithms/agc.cpp\n>> @@ -332,9 +332,7 @@ void Agc::process(IPAContext &context,\n>>   \tutils::Duration currentShutter = exposure * configuration.sensor.lineDuration;\n>>   \tutils::Duration effectiveExposureValue = currentShutter * analogueGain;\n>>\n>> -\tutils::Duration shutterTime;\n>> -\tdouble aGain, qGain, dGain;\n>> -\tstd::tie(shutterTime, aGain, qGain, dGain) = agc_.calculateNewEv({\n>> +\tconst auto &newEv = agc_.calculateNewEv({\n> \n> Same question on & for this and rkisp1\n> \n>>   \t\t.traits = AgcTraits(statistics_),\n>>   \t\t.yHist = statistics_.yHist,\n>>   \t\t.effectiveExposureValue = effectiveExposureValue,\n>> @@ -344,10 +342,10 @@ void Agc::process(IPAContext &context,\n>>\n>>   \tLOG(MaliC55Agc, Debug)\n>>   \t\t<< \"Divided up shutter, analogue gain and digital gain are \"\n>> -\t\t<< shutterTime << \", \" << aGain << \" and \" << dGain;\n>> +\t\t<< newEv.exposureTime << \", \" << newEv.analogueGain << \" and \" << newEv.digitalGain;\n>>\n>> -\tactiveState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration;\n>> -\tactiveState.agc.automatic.sensorGain = aGain;\n>> +\tactiveState.agc.automatic.exposure = newEv.exposureTime / configuration.sensor.lineDuration;\n>> +\tactiveState.agc.automatic.sensorGain = newEv.analogueGain;\n>>\n>>   \tmetadata.set(controls::ExposureTime, currentShutter.get<std::micro>());\n>>   \tmetadata.set(controls::AnalogueGain, frameContext.agc.sensorGain);\n>> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp\n>> index 8b4bcbd71b..a4567c8258 100644\n>> --- a/src/ipa/rkisp1/algorithms/agc.cpp\n>> +++ b/src/ipa/rkisp1/algorithms/agc.cpp\n>> @@ -720,9 +720,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>>   \tagc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue));\n>>   \tagc_.setLux(frameContext.lux.lux);\n>>\n>> -\tutils::Duration newExposureTime;\n>> -\tdouble aGain, qGain, dGain;\n>> -\tstd::tie(newExposureTime, aGain, qGain, dGain) = agc_.calculateNewEv({\n>> +\tconst auto &newEv = agc_.calculateNewEv({\n>>   \t\t.traits = AgcTraits{\n>>   \t\t\t{ params->ae.exp_mean, context.hw.numAeCells },\n>>   \t\t\tmeteringModes_.at(frameContext.agc.meteringMode),\n>> @@ -735,21 +733,21 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>>\n>>   \tLOG(RkISP1Agc, Debug)\n>>   \t\t<< \"Divided up exposure time, analogue gain, quantization gain\"\n>> -\t\t<< \" and digital gain are \" << newExposureTime << \", \" << aGain\n>> -\t\t<< \", \" << qGain << \" and \" << dGain;\n>> +\t\t<< \" and digital gain are \" << newEv.exposureTime << \", \" << newEv.analogueGain\n>> +\t\t<< \", \" << newEv.quantizationGain << \" and \" << newEv.digitalGain;\n>>\n>>   \tIPAActiveState &activeState = context.activeState;\n>>   \t/* Update the estimated exposure and gain. */\n>> -\tactiveState.agc.automatic.exposure = newExposureTime / lineDuration;\n>> -\tactiveState.agc.automatic.gain = aGain;\n>> -\tactiveState.agc.automatic.quantizationGain = qGain;\n>> +\tactiveState.agc.automatic.exposure = newEv.exposureTime / lineDuration;\n>> +\tactiveState.agc.automatic.gain = newEv.analogueGain;\n>> +\tactiveState.agc.automatic.quantizationGain = newEv.quantizationGain;\n>>   \tactiveState.agc.automatic.yTarget = agc_.effectiveYTarget();\n>>   \t/*\n>>   \t * Expand the target frame duration so that we do not run faster than\n>>   \t * the minimum frame duration when we have short exposures.\n>>   \t */\n>>   \tprocessFrameDuration(context, frameContext,\n>> -\t\t\t     std::max(frameContext.agc.minFrameDuration, newExposureTime));\n>> +\t\t\t     std::max(frameContext.agc.minFrameDuration, newEv.exposureTime));\n> \n> minors apart\n> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> \n> Thanks\n>    j\n> \n>>\n>>   \tfillMetadata(context, frameContext, metadata);\n>>   }\n>> --\n>> 2.55.0\n>>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 20B34BDE17\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 24 Jul 2026 13:05:46 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id DBC6567F0A;\n\tFri, 24 Jul 2026 15:05:44 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0373967E89\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Jul 2026 15:05:44 +0200 (CEST)","from [192.168.33.42] (185.182.215.156.nat.pool.zt.hu\n\t[185.182.215.156])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 3F0D373B;\n\tFri, 24 Jul 2026 15:04:42 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"mqMKezwf\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1784898282;\n\tbh=wNL/pnbmy8Yfg7gxSoYXX6jFZ2J4qcg3Q7ZR77+dVLY=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=mqMKezwfXdkbJYsFIK3h0dovdGCbG7WErw4xo0IQww/aFbZLYpyWafg3NxaaSJ4Tq\n\tAx/HS/DMDBEs4cmCPeN40zaq3CJ0+YSCmzPlqg1YtaKQ3tNn7asw57CVwP2nfqki55\n\t00ATCsyZcPRw5utB28vriYqvF7a8QgmBrIozEF3I=","Message-ID":"<83a16b96-db9f-47f1-a405-e961c991bb1c@ideasonboard.com>","Date":"Fri, 24 Jul 2026 15:05:40 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [RFC PATCH v2 16/43] ipa: libipa: agc_mean_luminance:\n\tcalculateNewEv(): Collect results","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","References":"<20260723154327.1357866-1-barnabas.pocze@ideasonboard.com>\n\t<20260723154327.1357866-17-barnabas.pocze@ideasonboard.com>\n\t<amNeBAgfuM0XDpPU@zed>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<amNeBAgfuM0XDpPU@zed>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":39834,"web_url":"https://patchwork.libcamera.org/comment/39834/","msgid":"<amNlLAhut5AKRelp@zed>","date":"2026-07-24T13:15:02","subject":"Re: [RFC PATCH v2 16/43] ipa: libipa: agc_mean_luminance:\n\tcalculateNewEv(): Collect results","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Barnabás\n\nOn Fri, Jul 24, 2026 at 03:05:40PM +0200, Barnabás Pőcze wrote:\n> 2026. 07. 24. 14:49 keltezéssel, Jacopo Mondi írta:\n> > Hi Barnabás\n> >\n> > On Thu, Jul 23, 2026 at 05:42:59PM +0200, Barnabás Pőcze wrote:\n> > > Add a new type that gives proper names to the returned quantities\n> > > instead of just using an `std::tuple`.\n> > >\n> > > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n> > > ---\n> > >   src/ipa/ipu3/algorithms/agc.cpp         | 10 ++++------\n> > >   src/ipa/libipa/agc_mean_luminance.cpp   | 11 ++++++++---\n> > >   src/ipa/libipa/agc_mean_luminance.h     |  7 ++++---\n> > >   src/ipa/libipa/exposure_mode_helper.cpp | 19 ++++++++++++++++++-\n> > >   src/ipa/libipa/exposure_mode_helper.h   | 11 ++++++++---\n> > >   src/ipa/mali-c55/algorithms/agc.cpp     | 10 ++++------\n> > >   src/ipa/rkisp1/algorithms/agc.cpp       | 16 +++++++---------\n> > >   7 files changed, 53 insertions(+), 31 deletions(-)\n> > >\n> > > diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp\n> > > index ac5b6e7513..e74db62960 100644\n> > > --- a/src/ipa/ipu3/algorithms/agc.cpp\n> > > +++ b/src/ipa/ipu3/algorithms/agc.cpp\n> > > @@ -237,9 +237,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n> > >   \tdouble analogueGain = frameContext.sensor.gain;\n> > >   \tutils::Duration effectiveExposureValue = exposureTime * analogueGain;\n> > >\n> > > -\tutils::Duration newExposureTime;\n> > > -\tdouble aGain, qGain, dGain;\n> > > -\tstd::tie(newExposureTime, aGain, qGain, dGain) = agc_.calculateNewEv({\n> > > +\tconst auto &newEv = agc_.calculateNewEv({\n> >\n> > Doesn't agc_.calculateNewEv() construct a Result before returning it ?\n> > Does using a reference here help ?\n>\n> It does not change anything, it's just what I usually do in these cases. You could\n> use `auto`, `auot&&`, or even `AgcMeanLuminance::Result`, all of them would effectively\n> be the same. This applies at every call-site.\n>\n\nDid I already mention I don't like 'auto' ? :)\n\n>\n> >\n> > >   \t\t.traits = AgcTraits{\n> > >   \t\t\trgbTriples_,\n> > >   \t\t\t{{\n> > > @@ -257,12 +255,12 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n> > >\n> > >   \tLOG(IPU3Agc, Debug)\n> > >   \t\t<< \"Divided up exposure time, analogue gain and digital gain are \"\n> > > -\t\t<< newExposureTime << \", \" << aGain << \" and \" << dGain;\n> > > +\t\t<< newEv.exposureTime << \", \" << newEv.analogueGain << \" and \" << newEv.digitalGain;\n> > >\n> > >   \tIPAActiveState &activeState = context.activeState;\n> > >   \t/* Update the estimated exposure time and gain. */\n> > > -\tactiveState.agc.exposure = newExposureTime / context.configuration.sensor.lineDuration;\n> > > -\tactiveState.agc.gain = aGain;\n> > > +\tactiveState.agc.exposure = newEv.exposureTime / context.configuration.sensor.lineDuration;\n> > > +\tactiveState.agc.gain = newEv.analogueGain;\n> > >\n> > >   \tmetadata.set(controls::AnalogueGain, frameContext.sensor.gain);\n> > >   \tmetadata.set(controls::ExposureTime, exposureTime.get<std::micro>());\n> > > diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp\n> > > index e8679c66be..5e12c4b475 100644\n> > > --- a/src/ipa/libipa/agc_mean_luminance.cpp\n> > > +++ b/src/ipa/libipa/agc_mean_luminance.cpp\n> > > @@ -668,6 +668,11 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue)\n> > >    * \\brief The index of the exposure mode to use\n> > >    */\n> > >\n> > > +/**\n> > > + * \\struct AgcMeanLuminance::Result\n> > > + * \\brief Collection of results of the mean luminance AGC algorithm\n> >\n> >          \\sa ExposureModeHelper::Result\n> >\n> > but maybe it's not necessary as doxygen already provides an\n> > inheritance diagram for the two types\n>\n> Good point, I did not check what it looks like. But I've just done so, and\n> there is an inheritance diagram, as well as an option under the direct members:\n>\n>   Public Attributes inherited from libcamera::ipa::ExposureModeHelper::Result\n>\n> and clicking on it brings up the members. So I think it's fine.\n>\n\nI think so, yes\n\n> >\n> > > + */\n> > > +\n> > >   /**\n> > >    * \\brief Calculate the new exposure value and split it between exposure time\n> > >    * and gain\n> > > @@ -680,7 +685,7 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue)\n> > >    * \\return Tuple of exposure time, analogue gain, quantization gain and digital\n> > >    * gain\n> > >    */\n> > > -std::tuple<utils::Duration, double, double, double>\n> > > +AgcMeanLuminance::Result\n> > >   AgcMeanLuminance::calculateNewEv(const Params &params)\n> > >   {\n> > >   \t/*\n> > > @@ -699,7 +704,7 @@ AgcMeanLuminance::calculateNewEv(const Params &params)\n> > >   \t\t * doesn't get stuck with 0 in case the sensor driver allows a\n> > >   \t\t * min exposure of 0.\n> > >   \t\t */\n> > > -\t\treturn exposureModeHelper.splitExposure(10ms);\n> > > +\t\treturn { exposureModeHelper.splitExposure(10ms) };\n> > >   \t}\n> > >\n> > >   \tdouble gain = estimateInitialGain(params.traits);\n> > > @@ -721,7 +726,7 @@ AgcMeanLuminance::calculateNewEv(const Params &params)\n> > >   \tnewExposureValue = filterExposure(newExposureValue);\n> > >\n> > >   \tframeCount_++;\n> > > -\treturn exposureModeHelper.splitExposure(newExposureValue);\n> > > +\treturn { exposureModeHelper.splitExposure(newExposureValue) };\n> > >   }\n> > >\n> > >   /**\n> > > diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h\n> > > index 9b82d97ab6..aa4c0369ac 100644\n> > > --- a/src/ipa/libipa/agc_mean_luminance.h\n> > > +++ b/src/ipa/libipa/agc_mean_luminance.h\n> > > @@ -9,7 +9,6 @@\n> > >\n> > >   #include <map>\n> > >   #include <memory>\n> > > -#include <tuple>\n> > >   #include <vector>\n> > >\n> > >   #include <libcamera/base/utils.h>\n> > > @@ -87,8 +86,10 @@ public:\n> > >   \t\tuint32_t exposureModeIndex;\n> > >   \t};\n> > >\n> > > -\tstd::tuple<utils::Duration, double, double, double>\n> > > -\tcalculateNewEv(const Params &params);\n> > > +\tstruct Result : ExposureModeHelper::Result {\n> > > +\t};\n> > > +\n> > > +\t[[nodiscard]] Result calculateNewEv(const Params &params);\n> > >\n> > >   \tdouble effectiveYTarget() const;\n> > >\n> > > diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp\n> > > index 01c5cba8e2..762e27b4fd 100644\n> > > --- a/src/ipa/libipa/exposure_mode_helper.cpp\n> > > +++ b/src/ipa/libipa/exposure_mode_helper.cpp\n> > > @@ -153,6 +153,23 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons\n> > >   \treturn clamped;\n> > >   }\n> > >\n> > > +/**\n> > > + * \\struct ExposureModeHelper::Result\n> > > + * \\brief Result of splitExposure()\n> > > + *\n> > > + * \\var ExposureModeHelper::Result::exposureTime\n> > > + * \\brief The applicable exposure time\n> > > + *\n> > > + * \\var ExposureModeHelper::Result::analogueGain\n> > > + * \\brief The applicable analogue gain\n> > > + *\n> > > + * \\var ExposureModeHelper::Result::quantizationGain\n> > > + * \\brief The applicable quantization gain\n> > > + *\n> > > + * \\var ExposureModeHelper::Result::digitalGain\n> > > + * \\brief The applicable digital gain\n> > > + */\n> > > +\n> > >   /**\n> > >    * \\brief Split exposure into exposure time and gain\n> > >    * \\param[in] exposure Exposure value\n> > > @@ -190,7 +207,7 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons\n> > >    * \\return Tuple of exposure time, analogue gain, quantization gain and digital\n> > >    * gain\n> > >    */\n> > > -std::tuple<utils::Duration, double, double, double>\n> > > +ExposureModeHelper::Result\n> > >   ExposureModeHelper::splitExposure(utils::Duration exposure) const\n> > >   {\n> > >   \tASSERT(maxExposureTime_);\n> > > diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h\n> > > index 968192ddc5..2bab20f974 100644\n> > > --- a/src/ipa/libipa/exposure_mode_helper.h\n> > > +++ b/src/ipa/libipa/exposure_mode_helper.h\n> > > @@ -7,7 +7,6 @@\n> > >\n> > >   #pragma once\n> > >\n> > > -#include <tuple>\n> > >   #include <utility>\n> > >   #include <vector>\n> > >\n> > > @@ -30,8 +29,14 @@ public:\n> > >   \tvoid setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime,\n> > >   \t\t       double minGain, double maxGain);\n> > >\n> > > -\tstd::tuple<utils::Duration, double, double, double>\n> > > -\tsplitExposure(utils::Duration exposure) const;\n> > > +\tstruct Result {\n> > > +\t\tutils::Duration exposureTime;\n> > > +\t\tdouble analogueGain;\n> > > +\t\tdouble quantizationGain;\n> > > +\t\tdouble digitalGain;\n> > > +\t};\n> > > +\n> > > +\t[[nodiscard]] Result splitExposure(utils::Duration exposure) const;\n> > >\n> > >   \tutils::Duration minExposureTime() const { return minExposureTime_; }\n> > >   \tutils::Duration maxExposureTime() const { return maxExposureTime_; }\n> > > diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp\n> > > index fb97b87c6e..821f43e9fd 100644\n> > > --- a/src/ipa/mali-c55/algorithms/agc.cpp\n> > > +++ b/src/ipa/mali-c55/algorithms/agc.cpp\n> > > @@ -332,9 +332,7 @@ void Agc::process(IPAContext &context,\n> > >   \tutils::Duration currentShutter = exposure * configuration.sensor.lineDuration;\n> > >   \tutils::Duration effectiveExposureValue = currentShutter * analogueGain;\n> > >\n> > > -\tutils::Duration shutterTime;\n> > > -\tdouble aGain, qGain, dGain;\n> > > -\tstd::tie(shutterTime, aGain, qGain, dGain) = agc_.calculateNewEv({\n> > > +\tconst auto &newEv = agc_.calculateNewEv({\n> >\n> > Same question on & for this and rkisp1\n> >\n> > >   \t\t.traits = AgcTraits(statistics_),\n> > >   \t\t.yHist = statistics_.yHist,\n> > >   \t\t.effectiveExposureValue = effectiveExposureValue,\n> > > @@ -344,10 +342,10 @@ void Agc::process(IPAContext &context,\n> > >\n> > >   \tLOG(MaliC55Agc, Debug)\n> > >   \t\t<< \"Divided up shutter, analogue gain and digital gain are \"\n> > > -\t\t<< shutterTime << \", \" << aGain << \" and \" << dGain;\n> > > +\t\t<< newEv.exposureTime << \", \" << newEv.analogueGain << \" and \" << newEv.digitalGain;\n> > >\n> > > -\tactiveState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration;\n> > > -\tactiveState.agc.automatic.sensorGain = aGain;\n> > > +\tactiveState.agc.automatic.exposure = newEv.exposureTime / configuration.sensor.lineDuration;\n> > > +\tactiveState.agc.automatic.sensorGain = newEv.analogueGain;\n> > >\n> > >   \tmetadata.set(controls::ExposureTime, currentShutter.get<std::micro>());\n> > >   \tmetadata.set(controls::AnalogueGain, frameContext.agc.sensorGain);\n> > > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp\n> > > index 8b4bcbd71b..a4567c8258 100644\n> > > --- a/src/ipa/rkisp1/algorithms/agc.cpp\n> > > +++ b/src/ipa/rkisp1/algorithms/agc.cpp\n> > > @@ -720,9 +720,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n> > >   \tagc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue));\n> > >   \tagc_.setLux(frameContext.lux.lux);\n> > >\n> > > -\tutils::Duration newExposureTime;\n> > > -\tdouble aGain, qGain, dGain;\n> > > -\tstd::tie(newExposureTime, aGain, qGain, dGain) = agc_.calculateNewEv({\n> > > +\tconst auto &newEv = agc_.calculateNewEv({\n> > >   \t\t.traits = AgcTraits{\n> > >   \t\t\t{ params->ae.exp_mean, context.hw.numAeCells },\n> > >   \t\t\tmeteringModes_.at(frameContext.agc.meteringMode),\n> > > @@ -735,21 +733,21 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n> > >\n> > >   \tLOG(RkISP1Agc, Debug)\n> > >   \t\t<< \"Divided up exposure time, analogue gain, quantization gain\"\n> > > -\t\t<< \" and digital gain are \" << newExposureTime << \", \" << aGain\n> > > -\t\t<< \", \" << qGain << \" and \" << dGain;\n> > > +\t\t<< \" and digital gain are \" << newEv.exposureTime << \", \" << newEv.analogueGain\n> > > +\t\t<< \", \" << newEv.quantizationGain << \" and \" << newEv.digitalGain;\n> > >\n> > >   \tIPAActiveState &activeState = context.activeState;\n> > >   \t/* Update the estimated exposure and gain. */\n> > > -\tactiveState.agc.automatic.exposure = newExposureTime / lineDuration;\n> > > -\tactiveState.agc.automatic.gain = aGain;\n> > > -\tactiveState.agc.automatic.quantizationGain = qGain;\n> > > +\tactiveState.agc.automatic.exposure = newEv.exposureTime / lineDuration;\n> > > +\tactiveState.agc.automatic.gain = newEv.analogueGain;\n> > > +\tactiveState.agc.automatic.quantizationGain = newEv.quantizationGain;\n> > >   \tactiveState.agc.automatic.yTarget = agc_.effectiveYTarget();\n> > >   \t/*\n> > >   \t * Expand the target frame duration so that we do not run faster than\n> > >   \t * the minimum frame duration when we have short exposures.\n> > >   \t */\n> > >   \tprocessFrameDuration(context, frameContext,\n> > > -\t\t\t     std::max(frameContext.agc.minFrameDuration, newExposureTime));\n> > > +\t\t\t     std::max(frameContext.agc.minFrameDuration, newEv.exposureTime));\n> >\n> > minors apart\n> > Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> >\n> > Thanks\n> >    j\n> >\n> > >\n> > >   \tfillMetadata(context, frameContext, metadata);\n> > >   }\n> > > --\n> > > 2.55.0\n> > >\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 67FC0BDE4C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 24 Jul 2026 13:15:07 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 1934067F21;\n\tFri, 24 Jul 2026 15:15:07 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 5015567E89\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Jul 2026 15:15:05 +0200 (CEST)","from ideasonboard.com (93-46-82-201.ip106.fastwebnet.it\n\t[93.46.82.201])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C104E9A4;\n\tFri, 24 Jul 2026 15:14:03 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"fhDHci7F\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1784898843;\n\tbh=FjFsHdvV/uwDQ9JwxKz+a385cLnjzrmE54RCeO04mYw=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=fhDHci7Fo9qVYUBECuf9WL/XVzDSc0M7B1WqQV2TGGeQ7U86LoZXuS1/e8UecqbRX\n\tQfty5T5lp5My/VTGMMv2xrhMbL8nXlVrWR4neg+OMdF+lJbT4FBXDq7RYHNa//oLnh\n\tkznDaU2GcAo5jf9XQJB6RWTAw4StnGR94mUZbULE=","Date":"Fri, 24 Jul 2026 15:15:02 +0200","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>, \n\tlibcamera-devel@lists.libcamera.org","Subject":"Re: [RFC PATCH v2 16/43] ipa: libipa: agc_mean_luminance:\n\tcalculateNewEv(): Collect results","Message-ID":"<amNlLAhut5AKRelp@zed>","References":"<20260723154327.1357866-1-barnabas.pocze@ideasonboard.com>\n\t<20260723154327.1357866-17-barnabas.pocze@ideasonboard.com>\n\t<amNeBAgfuM0XDpPU@zed>\n\t<83a16b96-db9f-47f1-a405-e961c991bb1c@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<83a16b96-db9f-47f1-a405-e961c991bb1c@ideasonboard.com>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]