{"id":23865,"url":"https://patchwork.libcamera.org/api/1.1/patches/23865/?format=json","web_url":"https://patchwork.libcamera.org/patch/23865/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20250721074853.1463358-7-naush@raspberrypi.com>","date":"2025-07-21T07:47:26","name":"[v2,6/7] ipa: rpi: Update digital gain handling in IPA base and derived classes","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"d791deecc49e3a8267cf7b73ebc4995951767eda","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/1.1/people/34/?format=json","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/23865/mbox/","series":[{"id":5303,"url":"https://patchwork.libcamera.org/api/1.1/series/5303/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5303","date":"2025-07-21T07:47:20","name":"Raspberry Pi AEC/AGC update","version":2,"mbox":"https://patchwork.libcamera.org/series/5303/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/23865/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/23865/checks/","tags":{},"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 F13C6BDCC1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 21 Jul 2025 07:49:11 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 08B9468FD6;\n\tMon, 21 Jul 2025 09:49:11 +0200 (CEST)","from mail-wm1-x32b.google.com (mail-wm1-x32b.google.com\n\t[IPv6:2a00:1450:4864:20::32b])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E970368FCF\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 21 Jul 2025 09:48:59 +0200 (CEST)","by mail-wm1-x32b.google.com with SMTP id\n\t5b1f17b1804b1-4535fc0485dso7198475e9.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 21 Jul 2025 00:48:59 -0700 (PDT)","from NAUSH-P-DELL.pitowers.org ([93.93.133.154])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-4562e802afasm151101765e9.12.2025.07.21.00.48.58\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 21 Jul 2025 00:48:58 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"ZNP3hrSy\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1753084139; x=1753688939;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=lRuNQgN56gu4G18be4zT6O0Mp0CBKGWcJG4krGOuu/I=;\n\tb=ZNP3hrSyqb8qw7BH0oER+pc/4FPuInWNUGSr9l5D7tQP75RcHhfKLr9DEhH0czTGYq\n\tbWVoGNXnSyzgu3SKzhAwltrup66dCuUNJVyshd+jB7qWHA+dKqz0yY90jCecFpu/PBhR\n\tOXWfd+O92l9/jumoJ9dEFo15O3Zdsnn07mZBzozfUdZhFCD4ilBpN3SlugEWlV2tJLkK\n\t8S5rbS7n9zGrsvyguNUdh9qnFm04wVEY+HQE6T/2o2KAZmhiNy7DTKDv4xT6HHbPUISi\n\tzfyWRfvH1Gl5PXy0cB6N59+dTXfOEW223snihE4YJd1ebFJGMoLuUzJx7hcnz9+f+dLL\n\tOTRw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1753084139; x=1753688939;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=lRuNQgN56gu4G18be4zT6O0Mp0CBKGWcJG4krGOuu/I=;\n\tb=otgPlnuP/sCFkjJkJWkuFRKvnvAdnieLyFNu3MQ/OP21PQNZAI8+r+BoV4DShN9BEH\n\tn0APZUO4QTjbi7DJ4n9gQBhYoONmcEak4Hl6vg2aLsLAOifywRJ+/AzZLIb3z+eRP8TZ\n\t6QFHHLvxMoAJRKRaeIrZ1ySWe0O8BJ9oJWVBFvj9hoX9Qf3B59PFE+SHPm4rhLpu64Hg\n\tw85/KmLpDcUWt9F3Z6c7eSxnPs8GlVM/JOLlzbbU+L00HViNlfNbroY+X9KeNtJHf1xy\n\taHrWl0XTDcDxNkVXBYtFKi8agbCdh4+3gUhpCscnq+bk/hFoiTf3P0xcz0LTrrVKthrL\n\tQXmw==","X-Gm-Message-State":"AOJu0YwVYjj2M64QHaf8/C1ePOGNKH8X+nv5pR1vhjovLO7KBcQZMmSB\n\tee3VDKST7kUnMNKYvPh3c82WViQLLyOPGTPQXiw5Deh/8lZbq0IUw01eyWQwhAHQAgvT502wu2z\n\trG+EFZ0A=","X-Gm-Gg":"ASbGncuz5rIbeeXIEjYmRS3AtWGgN4elZtsKRLm3TYBF12ILsGaD5iExY6tsFxOd4o8\n\tujRzJ7eISF/KJjH5OBVHQrFWtPc7uq6g0tlzgW8m9c4etCFeczF7Y73JspqT3jj1bfUhEMlDxI4\n\tetR3o/9oMFq20x492HM+/eIdQbvKgeyH32mQW1zKV7wUwgtWUk7HtAU52utn9WnhH3FuX3CJMhV\n\ti/2RW6MnyfZJ2ewQFOdzhLgIjDx9Gzj4N8ZAIsAY6PZTPgsy7/CSIIjH3i7/h4in3YlJWIe0I2f\n\ticqwB15FQm0DbEQENUmpfEyr3fBgsmpOijbi4KwhFQvd16/hSCoTNX6JaG0AbivQ0TBF1Nvdy+y\n\tegQcdYQgvwPWYE9t9tXiiM8vMGiezeznroNyFQgroxA==","X-Google-Smtp-Source":"AGHT+IFepQHqYX8SHoX0hOEQLEsBO3hROqmRv/PqYYRQoGPJDWcGFDA/TvTD2c0Kj6NQfd7y0UKezw==","X-Received":"by 2002:a05:600c:a306:b0:456:7cf:5268 with SMTP id\n\t5b1f17b1804b1-4562e277c74mr55034845e9.4.1753084138935; \n\tMon, 21 Jul 2025 00:48:58 -0700 (PDT)","From":"Naushir Patuck <naush@raspberrypi.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"David Plowman <david.plowman@raspberrypi.com>,\n\tNaushir Patuck <naush@raspberrypi.com>","Subject":"[PATCH v2 6/7] ipa: rpi: Update digital gain handling in IPA base\n\tand derived classes","Date":"Mon, 21 Jul 2025 08:47:26 +0100","Message-ID":"<20250721074853.1463358-7-naush@raspberrypi.com>","X-Mailer":"git-send-email 2.43.0","In-Reply-To":"<20250721074853.1463358-1-naush@raspberrypi.com>","References":"<20250721074853.1463358-1-naush@raspberrypi.com>","MIME-Version":"1.0","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>"},"content":"From: David Plowman <david.plowman@raspberrypi.com>\n\nHere we update the digital gain handling to use the value computed by\nprocess() in the AgcStatus, not the version that was previously in the\nAgcPrepareStatus.\n\nBecause we apply this digital gain directly with no further\nmodification, we have to update it to reflect any exposure/gain\nquantisation that happens (in IpaBase::applyAGC).\n\nWe must also run the new platformPrepareAgc() even when we're skipping\nplatformPrepareIsp(), which has been split out of the previous\nplatformPrepareIsp() implementation.\n\nSigned-off-by: David Plowman <david.plowman@raspberrypi.com>\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\nReviewed-by: Naushir Patuck <naush@raspberrypi.com>\n---\n src/ipa/rpi/common/ipa_base.cpp | 50 +++++++++++++++++++--------\n src/ipa/rpi/common/ipa_base.h   |  6 +++-\n src/ipa/rpi/pisp/pisp.cpp       | 61 +++++++++++++++++++++------------\n src/ipa/rpi/vc4/vc4.cpp         | 28 +++++++++------\n 4 files changed, 98 insertions(+), 47 deletions(-)","diff":"diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp\nindex 98690b80d5d3..1408c5e32a69 100644\n--- a/src/ipa/rpi/common/ipa_base.cpp\n+++ b/src/ipa/rpi/common/ipa_base.cpp\n@@ -298,20 +298,23 @@ void IpaBase::start(const ControlList &controls, StartResult *result)\n \tframeLengths_.clear();\n \tframeLengths_.resize(FrameLengthsQueueSize, 0s);\n \n-\t/* SwitchMode may supply updated exposure/gain values to use. */\n-\tAgcStatus agcStatus;\n-\tagcStatus.exposureTime = 0.0s;\n-\tagcStatus.analogueGain = 0.0;\n+\t/*\n+\t * SwitchMode may supply updated exposure/gain values to use.\n+\t * agcStatus_ will store these values for us to use until delayed_status values\n+\t * start to appear.\n+\t */\n+\tagcStatus_.exposureTime = 0.0s;\n+\tagcStatus_.analogueGain = 0.0;\n \n-\tmetadata.get(\"agc.status\", agcStatus);\n-\tif (agcStatus.exposureTime && agcStatus.analogueGain) {\n+\tmetadata.get(\"agc.status\", agcStatus_);\n+\tif (agcStatus_.exposureTime && agcStatus_.analogueGain) {\n \t\tControlList ctrls(sensorCtrls_);\n-\t\tapplyAGC(&agcStatus, ctrls);\n+\t\tapplyAGC(&agcStatus_, ctrls);\n \t\tresult->controls = std::move(ctrls);\n \t\tsetCameraTimeoutValue();\n \t}\n \t/* Make a note of this as it tells us the HDR status of the first few frames. */\n-\thdrStatus_ = agcStatus.hdr;\n+\thdrStatus_ = agcStatus_.hdr;\n \n \t/*\n \t * AF: If no lens position was specified, drive lens to a default position.\n@@ -486,7 +489,9 @@ void IpaBase::prepareIsp(const PrepareParams &params)\n \t\tcontroller_.prepare(&rpiMetadata);\n \t\t/* Actually prepare the ISP parameters for the frame. */\n \t\tplatformPrepareIsp(params, rpiMetadata);\n-\t}\n+\t\tplatformPrepareAgc(rpiMetadata);\n+\t} else\n+\t\tplatformPrepareAgc(rpiMetadata);\n \n \tframeCount_++;\n \n@@ -525,6 +530,7 @@ void IpaBase::processStats(const ProcessParams &params)\n \tif (rpiMetadata.get(\"agc.status\", agcStatus) == 0) {\n \t\tControlList ctrls(sensorCtrls_);\n \t\tapplyAGC(&agcStatus, ctrls);\n+\t\trpiMetadata.set(\"agc.status\", agcStatus);\n \t\tsetDelayedControls.emit(ctrls, ipaContext);\n \t\tsetCameraTimeoutValue();\n \t}\n@@ -1422,9 +1428,6 @@ void IpaBase::reportMetadata(unsigned int ipaContext)\n \t}\n \n \tAgcPrepareStatus *agcPrepareStatus = rpiMetadata.getLocked<AgcPrepareStatus>(\"agc.prepare_status\");\n-\tif (agcPrepareStatus)\n-\t\tlibcameraMetadata_.set(controls::DigitalGain, agcPrepareStatus->digitalGain);\n-\n \tRPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(\n \t\tcontroller_.getAlgorithm(\"agc\"));\n \tif (agc) {\n@@ -1437,6 +1440,13 @@ void IpaBase::reportMetadata(unsigned int ipaContext)\n \t\t\t\t\t\t       : controls::AeStateSearching);\n \t}\n \n+\tconst AgcStatus *agcStatus = rpiMetadata.getLocked<AgcStatus>(\"agc.delayed_status\");\n+\tif (agcStatus)\n+\t\tlibcameraMetadata_.set(controls::DigitalGain, agcStatus->digitalGain);\n+\telse\n+\t\tlibcameraMetadata_.set(controls::DigitalGain, agcStatus_.digitalGain);\n+\t/* The HDR metadata reporting will use this agcStatus too. */\n+\n \tLuxStatus *luxStatus = rpiMetadata.getLocked<LuxStatus>(\"lux.status\");\n \tif (luxStatus)\n \t\tlibcameraMetadata_.set(controls::Lux, luxStatus->lux);\n@@ -1526,7 +1536,6 @@ void IpaBase::reportMetadata(unsigned int ipaContext)\n \t * delayed_status to be available, we use the HDR status that came out of the\n \t * switchMode call.\n \t */\n-\tconst AgcStatus *agcStatus = rpiMetadata.getLocked<AgcStatus>(\"agc.delayed_status\");\n \tconst HdrStatus &hdrStatus = agcStatus ? agcStatus->hdr : hdrStatus_;\n \tif (!hdrStatus.mode.empty() && hdrStatus.mode != \"Off\") {\n \t\tint32_t hdrMode = controls::HdrModeOff;\n@@ -1584,7 +1593,7 @@ void IpaBase::applyFrameDurations(Duration minFrameDuration, Duration maxFrameDu\n \t\tagc->setMaxExposureTime(maxExposureTime);\n }\n \n-void IpaBase::applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls)\n+void IpaBase::applyAGC(struct AgcStatus *agcStatus, ControlList &ctrls)\n {\n \tconst int32_t minGainCode = helper_->gainCode(mode_.minAnalogueGain);\n \tconst int32_t maxGainCode = helper_->gainCode(mode_.maxAnalogueGain);\n@@ -1613,6 +1622,19 @@ void IpaBase::applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls)\n \tctrls.set(V4L2_CID_EXPOSURE, exposureLines);\n \tctrls.set(V4L2_CID_ANALOGUE_GAIN, gainCode);\n \n+\t/*\n+\t * We must update the digital gain to make up for any quantisation that happens, and\n+\t * communicate that back into the metadata so that it will appear as the \"delayed\" status.\n+\t * (Note that \"exposure\" is already the \"actual\" exposure.)\n+\t */\n+\tdouble actualGain = helper_->gain(gainCode);\n+\tdouble ratio = agcStatus->analogueGain / actualGain;\n+\tratio *= agcStatus->exposureTime / exposure;\n+\tdouble newDigitalGain = agcStatus->digitalGain * ratio;\n+\tagcStatus->digitalGain = newDigitalGain;\n+\tagcStatus->analogueGain = actualGain;\n+\tagcStatus->exposureTime = exposure;\n+\n \t/*\n \t * At present, there is no way of knowing if a control is read-only.\n \t * As a workaround, assume that if the minimum and maximum values of\ndiff --git a/src/ipa/rpi/common/ipa_base.h b/src/ipa/rpi/common/ipa_base.h\nindex e818104ba633..e2f6e330b2ab 100644\n--- a/src/ipa/rpi/common/ipa_base.h\n+++ b/src/ipa/rpi/common/ipa_base.h\n@@ -73,6 +73,9 @@ protected:\n \t/* Remember the HDR status after a mode switch. */\n \tHdrStatus hdrStatus_;\n \n+\t/* Remember the AGC status after a mode switch. */\n+\tAgcStatus agcStatus_;\n+\n \t/* Whether the stitch block (if available) needs to swap buffers. */\n \tbool stitchSwapBuffers_;\n \n@@ -86,6 +89,7 @@ private:\n \n \tvirtual void platformPrepareIsp(const PrepareParams &params,\n \t\t\t\t\tRPiController::Metadata &rpiMetadata) = 0;\n+\tvirtual void platformPrepareAgc(RPiController::Metadata &rpiMetadata) = 0;\n \tvirtual RPiController::StatisticsPtr platformProcessStats(Span<uint8_t> mem) = 0;\n \n \tvoid setMode(const IPACameraSensorInfo &sensorInfo);\n@@ -97,7 +101,7 @@ private:\n \tvoid fillDeviceStatus(const ControlList &sensorControls, unsigned int ipaContext);\n \tvoid reportMetadata(unsigned int ipaContext);\n \tvoid applyFrameDurations(utils::Duration minFrameDuration, utils::Duration maxFrameDuration);\n-\tvoid applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls);\n+\tvoid applyAGC(struct AgcStatus *agcStatus, ControlList &ctrls);\n \n \tstd::map<unsigned int, MappedFrameBuffer> buffers_;\n \ndiff --git a/src/ipa/rpi/pisp/pisp.cpp b/src/ipa/rpi/pisp/pisp.cpp\nindex e1a804f533bb..ab70d8f42636 100644\n--- a/src/ipa/rpi/pisp/pisp.cpp\n+++ b/src/ipa/rpi/pisp/pisp.cpp\n@@ -218,13 +218,14 @@ private:\n \n \tvoid platformPrepareIsp(const PrepareParams &params,\n \t\t\t\tRPiController::Metadata &rpiMetadata) override;\n+\tvoid platformPrepareAgc(RPiController::Metadata &rpiMetadata) override;\n \tRPiController::StatisticsPtr platformProcessStats(Span<uint8_t> mem) override;\n \n \tvoid handleControls(const ControlList &controls) override;\n \n-\tvoid applyWBG(const AwbStatus *awbStatus, const AgcPrepareStatus *agcStatus,\n+\tvoid applyWBG(const AwbStatus *awbStatus, double digitalGain,\n \t\t      pisp_be_global_config &global);\n-\tvoid applyDgOnly(const AgcPrepareStatus *agcPrepareStatus, pisp_be_global_config &global);\n+\tvoid applyDgOnly(double digitalGain, pisp_be_global_config &global);\n \tvoid applyCAC(const CacStatus *cacStatus, pisp_be_global_config &global);\n \tvoid applyContrast(const ContrastStatus *contrastStatus,\n \t\t\t   pisp_be_global_config &global);\n@@ -341,7 +342,6 @@ void IpaPiSP::platformPrepareIsp([[maybe_unused]] const PrepareParams &params,\n \t\t\t\tPISP_BE_RGB_ENABLE_SHARPEN + PISP_BE_RGB_ENABLE_SAT_CONTROL);\n \n \tNoiseStatus *noiseStatus = rpiMetadata.getLocked<NoiseStatus>(\"noise.status\");\n-\tAgcPrepareStatus *agcPrepareStatus = rpiMetadata.getLocked<AgcPrepareStatus>(\"agc.prepare_status\");\n \n \t{\n \t\t/* All Frontend config goes first, we do not want to hold the FE lock for long! */\n@@ -355,14 +355,6 @@ void IpaPiSP::platformPrepareIsp([[maybe_unused]] const PrepareParams &params,\n \t\tif (blackLevelStatus)\n \t\t\tapplyBlackLevel(blackLevelStatus, global);\n \n-\t\tAwbStatus *awbStatus = rpiMetadata.getLocked<AwbStatus>(\"awb.status\");\n-\t\tif (awbStatus && agcPrepareStatus) {\n-\t\t\t/* Applies digital gain as well. */\n-\t\t\tapplyWBG(awbStatus, agcPrepareStatus, global);\n-\t\t} else if (agcPrepareStatus) {\n-\t\t\t/* Mono sensor fallback for digital gain. */\n-\t\t\tapplyDgOnly(agcPrepareStatus, global);\n-\t\t}\n \t}\n \n \tCacStatus *cacStatus = rpiMetadata.getLocked<CacStatus>(\"cac.status\");\n@@ -443,6 +435,34 @@ void IpaPiSP::platformPrepareIsp([[maybe_unused]] const PrepareParams &params,\n \t}\n }\n \n+void IpaPiSP::platformPrepareAgc(RPiController::Metadata &rpiMetadata)\n+{\n+\tstd::scoped_lock<RPiController::Metadata> l(rpiMetadata);\n+\n+\tAgcStatus *delayedAgcStatus = rpiMetadata.getLocked<AgcStatus>(\"agc.delayed_status\");\n+\t/* If no delayed status, use the gain from the last mode switch. */\n+\tdouble digitalGain = delayedAgcStatus ? delayedAgcStatus->digitalGain : agcStatus_.digitalGain;\n+\tAwbStatus *awbStatus = rpiMetadata.getLocked<AwbStatus>(\"awb.status\");\n+\n+\tpisp_be_global_config global;\n+\tbe_->GetGlobal(global);\n+\n+\t{\n+\t\t/* All Frontend config goes first, we do not want to hold the FE lock for long! */\n+\t\tstd::scoped_lock<FrontEnd> lf(*fe_);\n+\n+\t\tif (awbStatus) {\n+\t\t\t/* Applies digital gain as well. */\n+\t\t\tapplyWBG(awbStatus, digitalGain, global);\n+\t\t} else {\n+\t\t\t/* Mono sensor fallback for digital gain. */\n+\t\t\tapplyDgOnly(digitalGain, global);\n+\t\t}\n+\t}\n+\n+\tbe_->SetGlobal(global);\n+}\n+\n RPiController::StatisticsPtr IpaPiSP::platformProcessStats(Span<uint8_t> mem)\n {\n \tusing namespace RPiController;\n@@ -515,12 +535,11 @@ void IpaPiSP::handleControls(const ControlList &controls)\n \t}\n }\n \n-void IpaPiSP::applyWBG(const AwbStatus *awbStatus, const AgcPrepareStatus *agcPrepareStatus,\n+void IpaPiSP::applyWBG(const AwbStatus *awbStatus, double digitalGain,\n \t\t       pisp_be_global_config &global)\n {\n \tpisp_wbg_config wbg;\n \tpisp_fe_rgby_config rgby = {};\n-\tdouble dg = agcPrepareStatus ? agcPrepareStatus->digitalGain : 1.0;\n \tdouble minColourGain = std::min({ awbStatus->gainR, awbStatus->gainG, awbStatus->gainB, 1.0 });\n \t/* The 0.1 here doesn't mean much, but just stops arithmetic errors and extreme behaviour. */\n \tdouble extraGain = 1.0 / std::max({ minColourGain, 0.1 });\n@@ -536,9 +555,9 @@ void IpaPiSP::applyWBG(const AwbStatus *awbStatus, const AgcPrepareStatus *agcPr\n \tdouble gainG = awbStatus->gainG * extraGain;\n \tdouble gainB = awbStatus->gainB * extraGain;\n \n-\twbg.gain_r = clampField(dg * gainR, 14, 10);\n-\twbg.gain_g = clampField(dg * gainG, 14, 10);\n-\twbg.gain_b = clampField(dg * gainB, 14, 10);\n+\twbg.gain_r = clampField(digitalGain * gainR, 14, 10);\n+\twbg.gain_g = clampField(digitalGain * gainG, 14, 10);\n+\twbg.gain_b = clampField(digitalGain * gainB, 14, 10);\n \n \t/*\n \t * The YCbCr conversion block should contain the appropriate YCbCr\n@@ -561,15 +580,15 @@ void IpaPiSP::applyWBG(const AwbStatus *awbStatus, const AgcPrepareStatus *agcPr\n \tglobal.bayer_enables |= PISP_BE_BAYER_ENABLE_WBG;\n }\n \n-void IpaPiSP::applyDgOnly(const AgcPrepareStatus *agcPrepareStatus, pisp_be_global_config &global)\n+void IpaPiSP::applyDgOnly(double digitalGain, pisp_be_global_config &global)\n {\n \tpisp_wbg_config wbg;\n \n-\twbg.gain_r = clampField(agcPrepareStatus->digitalGain, 14, 10);\n-\twbg.gain_g = clampField(agcPrepareStatus->digitalGain, 14, 10);\n-\twbg.gain_b = clampField(agcPrepareStatus->digitalGain, 14, 10);\n+\twbg.gain_r = clampField(digitalGain, 14, 10);\n+\twbg.gain_g = clampField(digitalGain, 14, 10);\n+\twbg.gain_b = clampField(digitalGain, 14, 10);\n \n-\tLOG(IPARPI, Debug) << \"Applying DG (only) : \" << agcPrepareStatus->digitalGain;\n+\tLOG(IPARPI, Debug) << \"Applying DG (only) : \" << digitalGain;\n \n \tbe_->SetWbg(wbg);\n \tglobal.bayer_enables |= PISP_BE_BAYER_ENABLE_WBG;\ndiff --git a/src/ipa/rpi/vc4/vc4.cpp b/src/ipa/rpi/vc4/vc4.cpp\nindex 8a7a37c870ed..b2fec9344804 100644\n--- a/src/ipa/rpi/vc4/vc4.cpp\n+++ b/src/ipa/rpi/vc4/vc4.cpp\n@@ -57,14 +57,14 @@ private:\n \tint32_t platformConfigure(const ConfigParams &params, ConfigResult *result) override;\n \n \tvoid platformPrepareIsp(const PrepareParams &params, RPiController::Metadata &rpiMetadata) override;\n+\tvoid platformPrepareAgc([[maybe_unused]] RPiController::Metadata &rpiMetadata) override;\n \tRPiController::StatisticsPtr platformProcessStats(Span<uint8_t> mem) override;\n \n \tvoid handleControls(const ControlList &controls) override;\n \tbool validateIspControls();\n \n \tvoid applyAWB(const struct AwbStatus *awbStatus, ControlList &ctrls);\n-\tvoid applyDG(const struct AgcPrepareStatus *dgStatus,\n-\t\t     const struct AwbStatus *awbStatus, ControlList &ctrls);\n+\tvoid applyDG(double digitalGain, const struct AwbStatus *awbStatus, ControlList &ctrls);\n \tvoid applyCCM(const struct CcmStatus *ccmStatus, ControlList &ctrls);\n \tvoid applyBlackLevel(const struct BlackLevelStatus *blackLevelStatus, ControlList &ctrls);\n \tvoid applyGamma(const struct ContrastStatus *contrastStatus, ControlList &ctrls);\n@@ -78,6 +78,7 @@ private:\n \n \t/* VC4 ISP controls. */\n \tControlInfoMap ispCtrls_;\n+\tControlList ctrls_;\n \n \t/* LS table allocation passed in from the pipeline handler. */\n \tSharedFD lsTableHandle_;\n@@ -107,6 +108,7 @@ int32_t IpaVc4::platformStart([[maybe_unused]] const ControlList &controls,\n int32_t IpaVc4::platformConfigure(const ConfigParams &params, [[maybe_unused]] ConfigResult *result)\n {\n \tispCtrls_ = params.ispControls;\n+\tctrls_ = ControlList(ispCtrls_);\n \tif (!validateIspControls()) {\n \t\tLOG(IPARPI, Error) << \"ISP control validation failed.\";\n \t\treturn -1;\n@@ -139,7 +141,7 @@ int32_t IpaVc4::platformConfigure(const ConfigParams &params, [[maybe_unused]] C\n void IpaVc4::platformPrepareIsp([[maybe_unused]] const PrepareParams &params,\n \t\t\t\tRPiController::Metadata &rpiMetadata)\n {\n-\tControlList ctrls(ispCtrls_);\n+\tControlList &ctrls = ctrls_;\n \n \t/* Lock the metadata buffer to avoid constant locks/unlocks. */\n \tstd::unique_lock<RPiController::Metadata> lock(rpiMetadata);\n@@ -152,9 +154,6 @@ void IpaVc4::platformPrepareIsp([[maybe_unused]] const PrepareParams &params,\n \tif (ccmStatus)\n \t\tapplyCCM(ccmStatus, ctrls);\n \n-\tAgcPrepareStatus *dgStatus = rpiMetadata.getLocked<AgcPrepareStatus>(\"agc.prepare_status\");\n-\tapplyDG(dgStatus, awbStatus, ctrls);\n-\n \tAlscStatus *lsStatus = rpiMetadata.getLocked<AlscStatus>(\"alsc.status\");\n \tif (lsStatus)\n \t\tapplyLS(lsStatus, ctrls);\n@@ -190,9 +189,18 @@ void IpaVc4::platformPrepareIsp([[maybe_unused]] const PrepareParams &params,\n \t\tif (!lensctrls.empty())\n \t\t\tsetLensControls.emit(lensctrls);\n \t}\n+}\n \n-\tif (!ctrls.empty())\n-\t\tsetIspControls.emit(ctrls);\n+void IpaVc4::platformPrepareAgc(RPiController::Metadata &rpiMetadata)\n+{\n+\tAgcStatus *delayedAgcStatus = rpiMetadata.getLocked<AgcStatus>(\"agc.delayed_status\");\n+\tdouble digitalGain = delayedAgcStatus ? delayedAgcStatus->digitalGain : agcStatus_.digitalGain;\n+\tAwbStatus *awbStatus = rpiMetadata.getLocked<AwbStatus>(\"awb.status\");\n+\n+\tapplyDG(digitalGain, awbStatus, ctrls_);\n+\n+\tsetIspControls.emit(ctrls_);\n+\tctrls_ = ControlList(ispCtrls_);\n }\n \n RPiController::StatisticsPtr IpaVc4::platformProcessStats(Span<uint8_t> mem)\n@@ -329,11 +337,9 @@ void IpaVc4::applyAWB(const struct AwbStatus *awbStatus, ControlList &ctrls)\n \t\t  static_cast<int32_t>(awbStatus->gainB * 1000));\n }\n \n-void IpaVc4::applyDG(const struct AgcPrepareStatus *dgStatus,\n+void IpaVc4::applyDG(double digitalGain,\n \t\t     const struct AwbStatus *awbStatus, ControlList &ctrls)\n {\n-\tdouble digitalGain = dgStatus ? dgStatus->digitalGain : 1.0;\n-\n \tif (awbStatus) {\n \t\t/*\n \t\t * We must apply sufficient extra digital gain to stop any of the channel gains being\n","prefixes":["v2","6/7"]}