From patchwork Tue Aug 25 10:50:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Emilio Miranda X-Patchwork-Id: 28080 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 34D28C3308 for ; Tue, 25 Aug 2026 11:03:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E33E4683B8; Tue, 25 Aug 2026 13:03:03 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="M85TCqGb"; dkim-atps=neutral Received: from mail-wm1-x32a.google.com (mail-wm1-x32a.google.com [IPv6:2a00:1450:4864:20::32a]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id F20DE6834C for ; Tue, 25 Aug 2026 13:03:02 +0200 (CEST) Received: by mail-wm1-x32a.google.com with SMTP id 5b1f17b1804b1-499ac87c92bso35795855e9.1 for ; Tue, 25 Aug 2026 04:03:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787655782; x=1788260582; darn=lists.libcamera.org; h=content-transfer-encoding:content-type:mime-version:message-id:date :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to :content-type; bh=W3smtti1Rq0vq71+s/yFSL+07YbOHjjFoEebaLP8Cr0=; b=M85TCqGbUnQJyLNmSUz3dc5+p9mPFZrOjGaRh7vvjsycDg28LhJQO4b2Pz+r1B/hZQ bit72Rb9IAYfStJWHr/a6shSgM9GIHacCqYKDoVYrUptVX+r4DGIl5bQ/RobsiTr6I3J 7+Qv3bRgiUyrY99P+Gwh/VLbqzHLbO4OXIw1qU5jsTPZSJQT9jcSKNQ2xsq8QOycCFDA ZTV1sREdPB1d+ftqkdI+TSakpD6/GWfDWThi2TEiuDfA8QGYBt9ezZ5E2jCDozZy/fJJ Et7AQLYcXckK1NZLTf27QeX6bPdUD8FP3K9TA4e8LyzjopxTm9V7vTXQI+M8ZWspcPRe joPA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787655782; x=1788260582; h=content-transfer-encoding:content-type:mime-version:message-id:date :subject:cc:to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject :date:message-id:reply-to:content-type; bh=W3smtti1Rq0vq71+s/yFSL+07YbOHjjFoEebaLP8Cr0=; b=EHFBJywL17QDQucnQVexdg3zMqjqVf1ChdixVP0mCv1Qlugjrd8tj2OkDdKymHvz8N 7DhPDXezY+TaeIDTXJ5E4VrFLJgorXmsh/05eChXX5eHFGSzr6qt+uUw+o5L6FQzSubb bO+9VQPuD84Q24Ypf06/w1gWbQvqym86Kpto/wSzmfERXS0eSVnu5jb56V6n0hfBPD1A IrwGx7BkdvCuxE2futXprsLHf8ZmgLCymKQ8GNKRbEp+FyQBPKKmuBn1Qb9bJk2IvYBq 2fIu/7vjl0Tse5VHnHjIZhUBr108b3vJZhSgKRACRkLZ0Z2aTo7sYjGJL7WRNmyiNP0N YP8g== X-Gm-Message-State: AFuF++l7hCGUzJ/TZKagiGdt8MQvxo7V/sHamNxNN+31zcyHQDdso5hX OdleBwhHX7vtS+1n1QN9NXcMAhYDj7B9BmGOy9ftFE7xUWTr5nQmIyGjpe5PJg== X-Gm-Gg: AR+sD10i5dvuYBDxQSKp+oyJi5tLrm/fe9mNyhDy51MSuzPi+i1VBNnJaf3SdUGkDzG +55J8gQG6hgfgsWnGt6/WfplT1vqzeGy/ATMZpbEE8nYwLBWD9VezfMyRlOfqj2FHQZ4+eJzNQl lafN69C1Xl5CNc9jdC2WpItliAULq7WW2DHHJMcFbuREV0q3fM4bLIOdO1Bm/GMc/ctsScDuvHG 4XTxiMznqGnk+B2xk2c3nX1hHADfd9LNBqex/I57oLhpXOw8Dv9HqwIyNjRftwzTuBClGN99Hkg ZaVhlcrozLvhYdkjm4Gv5WAMZef6RNiUu5LwQ/IUBafuXCvcDWmt9Z/v7uRBMoucKmeU2BIAD/t JT5gmQOnQIJBf5BA44wUt7+02h/DGOv+p5LuLQAXfRgD1+9na8jqwOCWT/MIYWz7HGLeSX9mRsp QyDT/JipWBGp+F4DkV1SZDpWezpsMO/FXuuLXBPM/kxKlmIMrvp43QLDC0BCmd7KwD6L6V5nHJ9 c/UjSWajHpH6jEjrNE= X-Received: by 2002:a05:600c:8b55:b0:495:4d88:e630 with SMTP id 5b1f17b1804b1-499d64fd858mr65892335e9.10.1787655781399; Tue, 25 Aug 2026 04:03:01 -0700 (PDT) Received: from xps9315.home ([90.165.155.167]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-482dd853b8fsm1232996f8f.8.2026.08.25.04.02.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 25 Aug 2026 04:03:00 -0700 (PDT) From: Emilio Miranda To: libcamera-devel@lists.libcamera.org Cc: barnabas.pocze@ideasonboard.com, robert.mader@collabora.com, devve.3@gmail.com, mzamazal@redhat.com, kieran.bingham@ideasonboard.com, sjm@opendigital.cc Subject: [PATCH] ipa: simple: Derive AGC minimum gain step from sensor granularity Date: Tue, 25 Aug 2026 12:50:32 +0200 Message-ID: <20260825110209.125104-2-gemilio@gmail.com> X-Mailer: git-send-email 2.55.0 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 minimum gain step is computed as 1% of the sensor's gain range. For a sensor with a wide gain range that is far larger than the hardware can resolve: the OV01A10 spans 1-63.9961, giving a step of 0.629961, which is 63% at unity gain where the sensor resolves 1/256 = 0.0039. updateExposure() substitutes againMinStep whenever the proportional correction is smaller than it. Once the exposure saturates and gain is the only remaining control, the AGC therefore applies a 63% change every time it computes a 2% one, and never settles: again 1.00000 exposureMSV 1.99 error +0.51 factor 1.020 -> 1.62996 again 1.62996 exposureMSV 2.90 error -0.41 factor 0.984 -> 1.00000 That is 0.7 EV of brightness flicker at frame rate, observed on a Dell XPS 13 9315 (IPU6, simple pipeline and software ISP). This is not specific to one sensor. Without a CameraSensorHelper the gain values are raw register codes, where a step of 1.0 means one code and is harmless; adding a helper switches the units and the same formula starts producing most of a stop. Any sensor with a wide gain range is exposed to this once it gains a helper. Sample the step at both ends of the gain range and take the smaller one, so that non-uniform gain models are covered as well. This yields 0.00390625 for the OV01A10, and the gain then tracks continuously instead of alternating between two values. Reported at https://bugzilla.redhat.com/show_bug.cgi?id=2483190 Suggested-by: Barnabás Pőcze Signed-off-by: Emilio Miranda --- Based on v0.7.2 (base-commit below) rather than master, as discussed in the thread, for a potential 0.7.3. This implements the min-of-both-ends form Barnabás suggested. Checked against every gain helper in tree: all 28 yield a non-zero step, and the two ends of the range differ by up to ~1000x in both directions (Vd55g1/Vd56g3 are finest at the top, Imx283 at the bottom), so sampling only one end would have been wrong. AnalogueGainExp has no users in tree at present. Tested on the OV01A10 at 1280x720, where the exposure saturates and gain becomes the only remaining control, against an unpatched build in the same scene: the smallest correction the AGC can make drops from ~15% of current gain to ~1.5%, median 19.4% -> 2.6%. Unpatched, the absolute gain step has an interquartile range of exactly 162-323 codes, i.e. one and two times againMinStep; patched, the step scales with the gain level as it should. Disclosure: I investigated this with an AI assistant (Claude). The measurements are from my own hardware and I have verified them myself. src/ipa/simple/soft_simple.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) base-commit: 191e202178f02430b5942397c70d215cdd2056fa diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 629e1a32d..300806948 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -6,6 +6,7 @@ */ #include +#include #include #include @@ -228,10 +229,23 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo) context_.configuration.agc.againMin = camHelper_->gain(againMin); context_.configuration.agc.againMax = camHelper_->gain(againMax); context_.configuration.agc.again10 = std::max(context_.configuration.agc.againMin, 1.0); + /* + * The minimum gain step must reflect what the sensor can + * actually resolve. Deriving it from a fraction of the gain + * range yields a huge step for sensors with a wide range: the + * OV01A10 spans 1-63.9961, giving 0.63 at unity gain where the + * hardware resolves 1/256. The AGC can then make no correction + * smaller than 63%, and oscillates around the target once the + * exposure saturates and gain is the only remaining control. + * + * Sample the step at both ends of the range and take the + * smaller one, so that non-uniform gain models are covered. + */ context_.configuration.agc.againMinStep = - (context_.configuration.agc.againMax - - context_.configuration.agc.againMin) / - 100.0; + std::min(std::fabs(camHelper_->gain(againMin + 1) - + camHelper_->gain(againMin)), + std::fabs(camHelper_->gain(againMax) - + camHelper_->gain(againMax - 1))); if (camHelper_->blackLevel().has_value()) { /* * The black level from camHelper_ is a 16 bit value, software ISP