[5/5] ipa: software_isp: AGC: Stop using delayed control for previous values
diff mbox series

Message ID 20250923190657.21453-6-hansg@kernel.org
State Superseded
Headers show
Series
  • ipa: software_isp: AGC: Fox AGC oscillation bug
Related show

Commit Message

Hans de Goede Sept. 23, 2025, 7:06 p.m. UTC
Due to a combination of not having correct control-delay information for
various sensors as well as the generic nature of the simple-pipeline +
software-ISP meaning that any pipeline delays are unknown it is impossible
to get reliable control-delay values.

Wrong control-delay values can lead to pretty bad oscilation. Since atm
it is not possible to fix the wrong control-delay values, stop using
the old sensor values reported by the delayed-controlled mechanism.

Instead remember the gain and exposures set the last time the algorithm
runs (initializing the cached values with the actual sensor values on
the first frame), combined with skipping a fixed number of frames after
changing values.

Signed-off-by: Hans de Goede <hansg@kernel.org>
---
 src/ipa/simple/algorithms/agc.cpp | 30 +++++++++++++++++++++++++-----
 src/ipa/simple/algorithms/agc.h   |  3 ++-
 src/ipa/simple/ipa_context.h      |  6 ++++++
 src/ipa/simple/soft_simple.cpp    |  4 ++--
 4 files changed, 35 insertions(+), 8 deletions(-)

Comments

Milan Zamazal Sept. 24, 2025, 11:32 a.m. UTC | #1
Hi Hans,

thank you for the patch.

Hans de Goede <hansg@kernel.org> writes:

> Due to a combination of not having correct control-delay information for
> various sensors as well as the generic nature of the simple-pipeline +
> software-ISP meaning that any pipeline delays are unknown 

The issue is that we cannot be sure at which frame the pipeline applies
exposure changes and that the drivers don't report exposure/gain in
metadata?  It might be useful to mention the reasons in the commit
message.

> it is impossible to get reliable control-delay values.
>
> Wrong control-delay values can lead to pretty bad oscilation. Since atm
> it is not possible to fix the wrong control-delay values, stop using
> the old sensor values reported by the delayed-controlled mechanism.
>
> Instead remember the gain and exposures set the last time the algorithm
> runs (initializing the cached values with the actual sensor values on
> the first frame), combined with skipping a fixed number of frames after
> changing values.

A question is whether we, now when we do it less frequently, should
adjust the exposure/gain in larger increments, possibly based on the
deviation from the optimal exposure and/or recent adjustments.  Just a
thought for followup stuff, not suggesting addressing it here.

> Signed-off-by: Hans de Goede <hansg@kernel.org>
> ---
>  src/ipa/simple/algorithms/agc.cpp | 30 +++++++++++++++++++++++++-----
>  src/ipa/simple/algorithms/agc.h   |  3 ++-
>  src/ipa/simple/ipa_context.h      |  6 ++++++
>  src/ipa/simple/soft_simple.cpp    |  4 ++--
>  4 files changed, 35 insertions(+), 8 deletions(-)
>
> diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp
> index cdde56ba2..3c0e20ddc 100644
> --- a/src/ipa/simple/algorithms/agc.cpp
> +++ b/src/ipa/simple/algorithms/agc.cpp
> @@ -41,7 +41,15 @@ Agc::Agc()
>  {
>  }
>  
> -void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV)
> +int Agc::configure(IPAContext &context,
> +		   [[maybe_unused]] const IPAConfigInfo &configInfo)
> +{
> +	context.activeState.agc.skipFrames = 0;
> +
> +	return 0;
> +}
> +
> +void Agc::updateExposure(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, double exposureMSV)
>  {
>  	/*
>  	 * kExpDenominator of 10 gives ~10% increment/decrement;
> @@ -51,8 +59,18 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
>  	static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
>  	static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;
>  
> -	int32_t &exposure = frameContext.sensor.exposure;
> -	double &again = frameContext.sensor.gain;
> +	int32_t &skipFrames = context.activeState.agc.skipFrames;
> +	int32_t &exposure = context.activeState.agc.exposure;
> +	double &again = context.activeState.agc.again;
> +
> +	/* Set initial-gain values from sensor on first frame */
> +	if (frame == 0) {
> +		exposure = frameContext.sensor.exposure;
> +		again = frameContext.sensor.gain;
> +	}
> +
> +	if (skipFrames && --skipFrames)
> +		return;

I think something like

  if (skipFrames > 1) {
          skipFrames--;  
          return;
  }

would be much clearer.

>  	if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
>  		if (exposure < context.configuration.agc.exposureMax) {
> @@ -68,6 +86,7 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
>  			else
>  				again = next;
>  		}
> +		skipFrames = 3;

Perhaps the value of `3' deserves a declared constant, with an
explanation why 3 is a good value.

>  	}
>  
>  	if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
> @@ -84,6 +103,7 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
>  			else
>  				exposure = next;
>  		}
> +		skipFrames = 3;
>  	}
>  
>  	exposure = std::clamp(exposure, context.configuration.agc.exposureMin,
> @@ -97,7 +117,7 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
>  }
>  
>  void Agc::process(IPAContext &context,
> -		  [[maybe_unused]] const uint32_t frame,
> +		  const uint32_t frame,
>  		  IPAFrameContext &frameContext,
>  		  const SwIspStats *stats,
>  		  ControlList &metadata)
> @@ -135,7 +155,7 @@ void Agc::process(IPAContext &context,
>  	}
>  
>  	float exposureMSV = (denom == 0 ? 0 : static_cast<float>(num) / denom);
> -	updateExposure(context, frameContext, exposureMSV);
> +	updateExposure(context, frame, frameContext, exposureMSV);
>  }
>  
>  REGISTER_IPA_ALGORITHM(Agc, "Agc")
> diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h
> index 112d9f5a1..ef387664f 100644
> --- a/src/ipa/simple/algorithms/agc.h
> +++ b/src/ipa/simple/algorithms/agc.h
> @@ -19,13 +19,14 @@ public:
>  	Agc();
>  	~Agc() = default;
>  
> +	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
>  	void process(IPAContext &context, const uint32_t frame,
>  		     IPAFrameContext &frameContext,
>  		     const SwIspStats *stats,
>  		     ControlList &metadata) override;
>  
>  private:
> -	void updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV);
> +	void updateExposure(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, double exposureMSV);
>  };
>  
>  } /* namespace ipa::soft::algorithms */
> diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
> index ba525a881..cdab980a1 100644
> --- a/src/ipa/simple/ipa_context.h
> +++ b/src/ipa/simple/ipa_context.h
> @@ -37,6 +37,12 @@ struct IPASessionConfiguration {
>  };
>  
>  struct IPAActiveState {
> +	struct {
> +		int32_t skipFrames;

Unsigned?

> +		int32_t exposure;
> +		double again;
> +	} agc;
> +
>  	struct {
>  		uint8_t level;
>  		int32_t lastExposure;
> diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
> index f0764ef46..f8c6291e2 100644
> --- a/src/ipa/simple/soft_simple.cpp
> +++ b/src/ipa/simple/soft_simple.cpp
> @@ -327,8 +327,8 @@ void IPASoftSimple::processStats(const uint32_t frame,
>  
>  	ControlList ctrls(sensorInfoMap_);
>  
> -	auto &againNew = frameContext.sensor.gain;
> -	ctrls.set(V4L2_CID_EXPOSURE, frameContext.sensor.exposure);
> +	auto &againNew = context_.activeState.agc.again;
> +	ctrls.set(V4L2_CID_EXPOSURE, context_.activeState.agc.exposure);
>  	ctrls.set(V4L2_CID_ANALOGUE_GAIN,
>  		  static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(againNew) : againNew));

Patch
diff mbox series

diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp
index cdde56ba2..3c0e20ddc 100644
--- a/src/ipa/simple/algorithms/agc.cpp
+++ b/src/ipa/simple/algorithms/agc.cpp
@@ -41,7 +41,15 @@  Agc::Agc()
 {
 }
 
-void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV)
+int Agc::configure(IPAContext &context,
+		   [[maybe_unused]] const IPAConfigInfo &configInfo)
+{
+	context.activeState.agc.skipFrames = 0;
+
+	return 0;
+}
+
+void Agc::updateExposure(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, double exposureMSV)
 {
 	/*
 	 * kExpDenominator of 10 gives ~10% increment/decrement;
@@ -51,8 +59,18 @@  void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
 	static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
 	static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;
 
-	int32_t &exposure = frameContext.sensor.exposure;
-	double &again = frameContext.sensor.gain;
+	int32_t &skipFrames = context.activeState.agc.skipFrames;
+	int32_t &exposure = context.activeState.agc.exposure;
+	double &again = context.activeState.agc.again;
+
+	/* Set initial-gain values from sensor on first frame */
+	if (frame == 0) {
+		exposure = frameContext.sensor.exposure;
+		again = frameContext.sensor.gain;
+	}
+
+	if (skipFrames && --skipFrames)
+		return;
 
 	if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
 		if (exposure < context.configuration.agc.exposureMax) {
@@ -68,6 +86,7 @@  void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
 			else
 				again = next;
 		}
+		skipFrames = 3;
 	}
 
 	if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
@@ -84,6 +103,7 @@  void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
 			else
 				exposure = next;
 		}
+		skipFrames = 3;
 	}
 
 	exposure = std::clamp(exposure, context.configuration.agc.exposureMin,
@@ -97,7 +117,7 @@  void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
 }
 
 void Agc::process(IPAContext &context,
-		  [[maybe_unused]] const uint32_t frame,
+		  const uint32_t frame,
 		  IPAFrameContext &frameContext,
 		  const SwIspStats *stats,
 		  ControlList &metadata)
@@ -135,7 +155,7 @@  void Agc::process(IPAContext &context,
 	}
 
 	float exposureMSV = (denom == 0 ? 0 : static_cast<float>(num) / denom);
-	updateExposure(context, frameContext, exposureMSV);
+	updateExposure(context, frame, frameContext, exposureMSV);
 }
 
 REGISTER_IPA_ALGORITHM(Agc, "Agc")
diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h
index 112d9f5a1..ef387664f 100644
--- a/src/ipa/simple/algorithms/agc.h
+++ b/src/ipa/simple/algorithms/agc.h
@@ -19,13 +19,14 @@  public:
 	Agc();
 	~Agc() = default;
 
+	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
 	void process(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext,
 		     const SwIspStats *stats,
 		     ControlList &metadata) override;
 
 private:
-	void updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV);
+	void updateExposure(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, double exposureMSV);
 };
 
 } /* namespace ipa::soft::algorithms */
diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
index ba525a881..cdab980a1 100644
--- a/src/ipa/simple/ipa_context.h
+++ b/src/ipa/simple/ipa_context.h
@@ -37,6 +37,12 @@  struct IPASessionConfiguration {
 };
 
 struct IPAActiveState {
+	struct {
+		int32_t skipFrames;
+		int32_t exposure;
+		double again;
+	} agc;
+
 	struct {
 		uint8_t level;
 		int32_t lastExposure;
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index f0764ef46..f8c6291e2 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -327,8 +327,8 @@  void IPASoftSimple::processStats(const uint32_t frame,
 
 	ControlList ctrls(sensorInfoMap_);
 
-	auto &againNew = frameContext.sensor.gain;
-	ctrls.set(V4L2_CID_EXPOSURE, frameContext.sensor.exposure);
+	auto &againNew = context_.activeState.agc.again;
+	ctrls.set(V4L2_CID_EXPOSURE, context_.activeState.agc.exposure);
 	ctrls.set(V4L2_CID_ANALOGUE_GAIN,
 		  static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(againNew) : againNew));