ipa: rkisp1: agc: awb: Apply digital gain using ISP
diff mbox series

Message ID 20250827222032.644435-1-niklas.soderlund+renesas@ragnatech.se
State New
Headers show
Series
  • ipa: rkisp1: agc: awb: Apply digital gain using ISP
Related show

Commit Message

Niklas Söderlund Aug. 27, 2025, 10:20 p.m. UTC
The digital gain is already calculated in the AGC algorithm but the
value is not consumed by the AWB algorithm, which is where the gain
stage is located. Add the needed plumbing to carry the value between the
two algorithms.

This is needed to get good images from sensors such as the IMX219 where
large sensor frames needs a small VBLANK value. This results in images
that are so under exposed that even the sensors analog gain can't
compensate. The digital gain stage of the ISP helps with this.

Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 src/ipa/rkisp1/algorithms/agc.cpp | 12 ++++++++++--
 src/ipa/rkisp1/algorithms/awb.cpp |  4 +++-
 src/ipa/rkisp1/ipa_context.h      |  3 +++
 3 files changed, 16 insertions(+), 3 deletions(-)

Comments

Kieran Bingham Aug. 27, 2025, 10:38 p.m. UTC | #1
Hi Niklas,

Quoting Niklas Söderlund (2025-08-27 23:20:32)
> The digital gain is already calculated in the AGC algorithm but the
> value is not consumed by the AWB algorithm, which is where the gain
> stage is located. Add the needed plumbing to carry the value between the
> two algorithms.
> 
> This is needed to get good images from sensors such as the IMX219 where
> large sensor frames needs a small VBLANK value. This results in images

Do you really mean vblank here? Or is it just that you're hitting a
limitation on the exposure time or frame duration limit ? (and thus end
up with a small vblank...?)

> that are so under exposed that even the sensors analog gain can't
> compensate. The digital gain stage of the ISP helps with this.

This is interesting - but I think would soon be superceeded by the
capabilities we can use in the companding block or such with recent
development from Stefan in his WDR series ...

See [0] and in particular [1] ....
[0] https://patchwork.libcamera.org/project/libcamera/list/?series=5382
[1] https://patchwork.libcamera.org/patch/24120/

Using AWB to apply a gain here can be problematic because we can
saturate a single channel and I think the quantisation of the gain here
is quite coarse?

But perhaps we'll end up with potentially multiple places we 'could'
apply the digital gain ...

Kieran

> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> ---
>  src/ipa/rkisp1/algorithms/agc.cpp | 12 ++++++++++--
>  src/ipa/rkisp1/algorithms/awb.cpp |  4 +++-
>  src/ipa/rkisp1/ipa_context.h      |  3 +++
>  3 files changed, 16 insertions(+), 3 deletions(-)
> 
> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> index 35440b67e999..17aaa259244b 100644
> --- a/src/ipa/rkisp1/algorithms/agc.cpp
> +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> @@ -174,9 +174,11 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
>  {
>         /* Configure the default exposure and gain. */
>         context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain;
> +       context.activeState.agc.automatic.ispGain = 1.0;
>         context.activeState.agc.automatic.exposure =
>                 10ms / context.configuration.sensor.lineDuration;
>         context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;
> +       context.activeState.agc.manual.ispGain = 1.0;
>         context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
>         context.activeState.agc.autoExposureEnabled = !context.configuration.raw;
>         context.activeState.agc.autoGainEnabled = !context.configuration.raw;
> @@ -280,8 +282,10 @@ void Agc::queueRequest(IPAContext &context,
>  
>         if (!frameContext.agc.autoExposureEnabled)
>                 frameContext.agc.exposure = agc.manual.exposure;
> -       if (!frameContext.agc.autoGainEnabled)
> +       if (!frameContext.agc.autoGainEnabled) {
>                 frameContext.agc.gain = agc.manual.gain;
> +               frameContext.agc.ispGain = agc.manual.ispGain;
> +       }
>  
>         const auto &meteringMode = controls.get(controls::AeMeteringMode);
>         if (meteringMode) {
> @@ -336,12 +340,15 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
>  {
>         uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure;
>         double activeAutoGain = context.activeState.agc.automatic.gain;
> +       double activeAutoIspGain = context.activeState.agc.automatic.ispGain;
>  
>         /* Populate exposure and gain in auto mode */
>         if (frameContext.agc.autoExposureEnabled)
>                 frameContext.agc.exposure = activeAutoExposure;
> -       if (frameContext.agc.autoGainEnabled)
> +       if (frameContext.agc.autoGainEnabled) {
>                 frameContext.agc.gain = activeAutoGain;
> +               frameContext.agc.ispGain = activeAutoIspGain;
> +       }
>  
>         /*
>          * Populate manual exposure and gain from the active auto values when
> @@ -581,6 +588,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
>         /* Update the estimated exposure and gain. */
>         activeState.agc.automatic.exposure = newExposureTime / lineDuration;
>         activeState.agc.automatic.gain = aGain;
> +       activeState.agc.automatic.ispGain = dGain;
>  
>         /*
>          * Expand the target frame duration so that we do not run faster than
> diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> index 399fb51be414..569cac4a3466 100644
> --- a/src/ipa/rkisp1/algorithms/awb.cpp
> +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> @@ -218,7 +218,9 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
>          */
>         if (frameContext.awb.autoEnabled) {
>                 const auto &awb = context.activeState.awb;
> -               frameContext.awb.gains = awb.automatic.gains;
> +               const auto &agc = context.activeState.agc;
> +
> +               frameContext.awb.gains = awb.automatic.gains * agc.automatic.ispGain;
>                 frameContext.awb.temperatureK = awb.automatic.temperatureK;
>         }
>  
> diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> index 7ccc7b501aff..7180b0b7dff2 100644
> --- a/src/ipa/rkisp1/ipa_context.h
> +++ b/src/ipa/rkisp1/ipa_context.h
> @@ -73,10 +73,12 @@ struct IPAActiveState {
>                 struct {
>                         uint32_t exposure;
>                         double gain;
> +                       double ispGain;
>                 } manual;
>                 struct {
>                         uint32_t exposure;
>                         double gain;
> +                       double ispGain;
>                 } automatic;
>  
>                 bool autoExposureEnabled;
> @@ -130,6 +132,7 @@ struct IPAFrameContext : public FrameContext {
>         struct {
>                 uint32_t exposure;
>                 double gain;
> +               double ispGain;
>                 double exposureValue;
>                 uint32_t vblank;
>                 bool autoExposureEnabled;
> -- 
> 2.51.0
>
Niklas Söderlund Aug. 27, 2025, 10:58 p.m. UTC | #2
Hi Kieran,

On 2025-08-27 23:38:45 +0100, Kieran Bingham wrote:
> Hi Niklas,
> 
> Quoting Niklas Söderlund (2025-08-27 23:20:32)
> > The digital gain is already calculated in the AGC algorithm but the
> > value is not consumed by the AWB algorithm, which is where the gain
> > stage is located. Add the needed plumbing to carry the value between the
> > two algorithms.
> > 
> > This is needed to get good images from sensors such as the IMX219 where
> > large sensor frames needs a small VBLANK value. This results in images
> 
> Do you really mean vblank here? Or is it just that you're hitting a
> limitation on the exposure time or frame duration limit ? (and thus end
> up with a small vblank...?)

Yes, they are all intertwined in my head ;-) The default FrameLimits 
renders a VBLANK setting, this VBLANK setting limits the range of the 
EXPOSURE. Even if the IPA max the EXPOSURE and ANALOG_GAIN the image is 
very dark. From a .ko modules point of view the driving factor is the 
VBLANK setting.

> 
> > that are so under exposed that even the sensors analog gain can't
> > compensate. The digital gain stage of the ISP helps with this.
> 
> This is interesting - but I think would soon be superceeded by the
> capabilities we can use in the companding block or such with recent
> development from Stefan in his WDR series ...

I can't tell if it will, or not.

> 
> See [0] and in particular [1] ....
> [0] https://patchwork.libcamera.org/project/libcamera/list/?series=5382
> [1] https://patchwork.libcamera.org/patch/24120/
> 
> Using AWB to apply a gain here can be problematic because we can
> saturate a single channel

I can set the gain on each channel separately, but I only get one value 
from libipa. Maybe I'm misunderstanding you, or maybe this is why it 
will be superseded by Stefan's work?

> and I think the quantisation of the gain here
> is quite coarse?

The digital gain I setting used by this patch comes from libipa at the 
same time as the exposure and analog gain settings. It's just that the 
rksip1 IPA have never used the digital gain setting returned.

> 
> But perhaps we'll end up with potentially multiple places we 'could'
> apply the digital gain ...

I'm using a RPi Module 2 so I compered how the RPi IPA handles this, and 
it applies DIGITAL_GAIN on the sensor. I have tried this and it works, 
but I gather using the sensor for DIGITAL_GAIN is not the best idea 
these days. And I could not find any other block in the ISP where this 
could be done.

> 
> Kieran
> 
> > 
> > Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> > ---
> >  src/ipa/rkisp1/algorithms/agc.cpp | 12 ++++++++++--
> >  src/ipa/rkisp1/algorithms/awb.cpp |  4 +++-
> >  src/ipa/rkisp1/ipa_context.h      |  3 +++
> >  3 files changed, 16 insertions(+), 3 deletions(-)
> > 
> > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> > index 35440b67e999..17aaa259244b 100644
> > --- a/src/ipa/rkisp1/algorithms/agc.cpp
> > +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> > @@ -174,9 +174,11 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
> >  {
> >         /* Configure the default exposure and gain. */
> >         context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain;
> > +       context.activeState.agc.automatic.ispGain = 1.0;
> >         context.activeState.agc.automatic.exposure =
> >                 10ms / context.configuration.sensor.lineDuration;
> >         context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;
> > +       context.activeState.agc.manual.ispGain = 1.0;
> >         context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
> >         context.activeState.agc.autoExposureEnabled = !context.configuration.raw;
> >         context.activeState.agc.autoGainEnabled = !context.configuration.raw;
> > @@ -280,8 +282,10 @@ void Agc::queueRequest(IPAContext &context,
> >  
> >         if (!frameContext.agc.autoExposureEnabled)
> >                 frameContext.agc.exposure = agc.manual.exposure;
> > -       if (!frameContext.agc.autoGainEnabled)
> > +       if (!frameContext.agc.autoGainEnabled) {
> >                 frameContext.agc.gain = agc.manual.gain;
> > +               frameContext.agc.ispGain = agc.manual.ispGain;
> > +       }
> >  
> >         const auto &meteringMode = controls.get(controls::AeMeteringMode);
> >         if (meteringMode) {
> > @@ -336,12 +340,15 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
> >  {
> >         uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure;
> >         double activeAutoGain = context.activeState.agc.automatic.gain;
> > +       double activeAutoIspGain = context.activeState.agc.automatic.ispGain;
> >  
> >         /* Populate exposure and gain in auto mode */
> >         if (frameContext.agc.autoExposureEnabled)
> >                 frameContext.agc.exposure = activeAutoExposure;
> > -       if (frameContext.agc.autoGainEnabled)
> > +       if (frameContext.agc.autoGainEnabled) {
> >                 frameContext.agc.gain = activeAutoGain;
> > +               frameContext.agc.ispGain = activeAutoIspGain;
> > +       }
> >  
> >         /*
> >          * Populate manual exposure and gain from the active auto values when
> > @@ -581,6 +588,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> >         /* Update the estimated exposure and gain. */
> >         activeState.agc.automatic.exposure = newExposureTime / lineDuration;
> >         activeState.agc.automatic.gain = aGain;
> > +       activeState.agc.automatic.ispGain = dGain;
> >  
> >         /*
> >          * Expand the target frame duration so that we do not run faster than
> > diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> > index 399fb51be414..569cac4a3466 100644
> > --- a/src/ipa/rkisp1/algorithms/awb.cpp
> > +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> > @@ -218,7 +218,9 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
> >          */
> >         if (frameContext.awb.autoEnabled) {
> >                 const auto &awb = context.activeState.awb;
> > -               frameContext.awb.gains = awb.automatic.gains;
> > +               const auto &agc = context.activeState.agc;
> > +
> > +               frameContext.awb.gains = awb.automatic.gains * agc.automatic.ispGain;
> >                 frameContext.awb.temperatureK = awb.automatic.temperatureK;
> >         }
> >  
> > diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> > index 7ccc7b501aff..7180b0b7dff2 100644
> > --- a/src/ipa/rkisp1/ipa_context.h
> > +++ b/src/ipa/rkisp1/ipa_context.h
> > @@ -73,10 +73,12 @@ struct IPAActiveState {
> >                 struct {
> >                         uint32_t exposure;
> >                         double gain;
> > +                       double ispGain;
> >                 } manual;
> >                 struct {
> >                         uint32_t exposure;
> >                         double gain;
> > +                       double ispGain;
> >                 } automatic;
> >  
> >                 bool autoExposureEnabled;
> > @@ -130,6 +132,7 @@ struct IPAFrameContext : public FrameContext {
> >         struct {
> >                 uint32_t exposure;
> >                 double gain;
> > +               double ispGain;
> >                 double exposureValue;
> >                 uint32_t vblank;
> >                 bool autoExposureEnabled;
> > -- 
> > 2.51.0
> >
Kieran Bingham Aug. 27, 2025, 11:23 p.m. UTC | #3
Quoting Niklas Söderlund (2025-08-27 23:58:56)
> Hi Kieran,
> 
> On 2025-08-27 23:38:45 +0100, Kieran Bingham wrote:
> > Hi Niklas,
> > 
> > Quoting Niklas Söderlund (2025-08-27 23:20:32)
> > > The digital gain is already calculated in the AGC algorithm but the
> > > value is not consumed by the AWB algorithm, which is where the gain
> > > stage is located. Add the needed plumbing to carry the value between the
> > > two algorithms.
> > > 
> > > This is needed to get good images from sensors such as the IMX219 where
> > > large sensor frames needs a small VBLANK value. This results in images
> > 
> > Do you really mean vblank here? Or is it just that you're hitting a
> > limitation on the exposure time or frame duration limit ? (and thus end
> > up with a small vblank...?)
> 
> Yes, they are all intertwined in my head ;-) The default FrameLimits 
> renders a VBLANK setting, this VBLANK setting limits the range of the 
> EXPOSURE. Even if the IPA max the EXPOSURE and ANALOG_GAIN the image is 
> very dark. From a .ko modules point of view the driving factor is the 
> VBLANK setting.
> 
> > 
> > > that are so under exposed that even the sensors analog gain can't
> > > compensate. The digital gain stage of the ISP helps with this.
> > 
> > This is interesting - but I think would soon be superceeded by the
> > capabilities we can use in the companding block or such with recent
> > development from Stefan in his WDR series ...
> 
> I can't tell if it will, or not.
> 
> > 
> > See [0] and in particular [1] ....
> > [0] https://patchwork.libcamera.org/project/libcamera/list/?series=5382
> > [1] https://patchwork.libcamera.org/patch/24120/
> > 
> > Using AWB to apply a gain here can be problematic because we can
> > saturate a single channel
> 
> I can set the gain on each channel separately, but I only get one value 
> from libipa. Maybe I'm misunderstanding you, or maybe this is why it 
> will be superseded by Stefan's work?
> 
> > and I think the quantisation of the gain here
> > is quite coarse?
> 
> The digital gain I setting used by this patch comes from libipa at the 
> same time as the exposure and analog gain settings. It's just that the 
> rksip1 IPA have never used the digital gain setting returned.

I mean the available values that can be applied through the AWB gains...

> > But perhaps we'll end up with potentially multiple places we 'could'
> > apply the digital gain ...
> 
> I'm using a RPi Module 2 so I compered how the RPi IPA handles this, and 
> it applies DIGITAL_GAIN on the sensor. I have tried this and it works, 
> but I gather using the sensor for DIGITAL_GAIN is not the best idea 

We haven't used digital gain on sensors yet - but perhaps that is indeed
another source that could be utilised.

> these days. And I could not find any other block in the ISP where this 
> could be done.

The companding/compress block implemented by the WDR series has a
'fine-grained/floating point' range gain that can be applied. I've
already been talking with Stefan to use this to similarly apply digital
gain - so I'm just waiting for his series to land ;-)

---
Kieran

> 
> > 
> > Kieran
> > 
> > > 
> > > Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> > > ---
> > >  src/ipa/rkisp1/algorithms/agc.cpp | 12 ++++++++++--
> > >  src/ipa/rkisp1/algorithms/awb.cpp |  4 +++-
> > >  src/ipa/rkisp1/ipa_context.h      |  3 +++
> > >  3 files changed, 16 insertions(+), 3 deletions(-)
> > > 
> > > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> > > index 35440b67e999..17aaa259244b 100644
> > > --- a/src/ipa/rkisp1/algorithms/agc.cpp
> > > +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> > > @@ -174,9 +174,11 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
> > >  {
> > >         /* Configure the default exposure and gain. */
> > >         context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain;
> > > +       context.activeState.agc.automatic.ispGain = 1.0;
> > >         context.activeState.agc.automatic.exposure =
> > >                 10ms / context.configuration.sensor.lineDuration;
> > >         context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;
> > > +       context.activeState.agc.manual.ispGain = 1.0;
> > >         context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
> > >         context.activeState.agc.autoExposureEnabled = !context.configuration.raw;
> > >         context.activeState.agc.autoGainEnabled = !context.configuration.raw;
> > > @@ -280,8 +282,10 @@ void Agc::queueRequest(IPAContext &context,
> > >  
> > >         if (!frameContext.agc.autoExposureEnabled)
> > >                 frameContext.agc.exposure = agc.manual.exposure;
> > > -       if (!frameContext.agc.autoGainEnabled)
> > > +       if (!frameContext.agc.autoGainEnabled) {
> > >                 frameContext.agc.gain = agc.manual.gain;
> > > +               frameContext.agc.ispGain = agc.manual.ispGain;
> > > +       }
> > >  
> > >         const auto &meteringMode = controls.get(controls::AeMeteringMode);
> > >         if (meteringMode) {
> > > @@ -336,12 +340,15 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
> > >  {
> > >         uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure;
> > >         double activeAutoGain = context.activeState.agc.automatic.gain;
> > > +       double activeAutoIspGain = context.activeState.agc.automatic.ispGain;
> > >  
> > >         /* Populate exposure and gain in auto mode */
> > >         if (frameContext.agc.autoExposureEnabled)
> > >                 frameContext.agc.exposure = activeAutoExposure;
> > > -       if (frameContext.agc.autoGainEnabled)
> > > +       if (frameContext.agc.autoGainEnabled) {
> > >                 frameContext.agc.gain = activeAutoGain;
> > > +               frameContext.agc.ispGain = activeAutoIspGain;
> > > +       }
> > >  
> > >         /*
> > >          * Populate manual exposure and gain from the active auto values when
> > > @@ -581,6 +588,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> > >         /* Update the estimated exposure and gain. */
> > >         activeState.agc.automatic.exposure = newExposureTime / lineDuration;
> > >         activeState.agc.automatic.gain = aGain;
> > > +       activeState.agc.automatic.ispGain = dGain;
> > >  
> > >         /*
> > >          * Expand the target frame duration so that we do not run faster than
> > > diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> > > index 399fb51be414..569cac4a3466 100644
> > > --- a/src/ipa/rkisp1/algorithms/awb.cpp
> > > +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> > > @@ -218,7 +218,9 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
> > >          */
> > >         if (frameContext.awb.autoEnabled) {
> > >                 const auto &awb = context.activeState.awb;
> > > -               frameContext.awb.gains = awb.automatic.gains;
> > > +               const auto &agc = context.activeState.agc;
> > > +
> > > +               frameContext.awb.gains = awb.automatic.gains * agc.automatic.ispGain;
> > >                 frameContext.awb.temperatureK = awb.automatic.temperatureK;
> > >         }
> > >  
> > > diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> > > index 7ccc7b501aff..7180b0b7dff2 100644
> > > --- a/src/ipa/rkisp1/ipa_context.h
> > > +++ b/src/ipa/rkisp1/ipa_context.h
> > > @@ -73,10 +73,12 @@ struct IPAActiveState {
> > >                 struct {
> > >                         uint32_t exposure;
> > >                         double gain;
> > > +                       double ispGain;
> > >                 } manual;
> > >                 struct {
> > >                         uint32_t exposure;
> > >                         double gain;
> > > +                       double ispGain;
> > >                 } automatic;
> > >  
> > >                 bool autoExposureEnabled;
> > > @@ -130,6 +132,7 @@ struct IPAFrameContext : public FrameContext {
> > >         struct {
> > >                 uint32_t exposure;
> > >                 double gain;
> > > +               double ispGain;
> > >                 double exposureValue;
> > >                 uint32_t vblank;
> > >                 bool autoExposureEnabled;
> > > -- 
> > > 2.51.0
> > >
> 
> -- 
> Kind Regards,
> Niklas Söderlund
Naushir Patuck Aug. 28, 2025, 12:12 p.m. UTC | #4
Hi Niklas and Kieran,

On Wed, 27 Aug 2025 at 23:59, Niklas Söderlund
<niklas.soderlund+renesas@ragnatech.se> wrote:
>
> Hi Kieran,
>
> On 2025-08-27 23:38:45 +0100, Kieran Bingham wrote:
> > Hi Niklas,
> >
> > Quoting Niklas Söderlund (2025-08-27 23:20:32)
> > > The digital gain is already calculated in the AGC algorithm but the
> > > value is not consumed by the AWB algorithm, which is where the gain
> > > stage is located. Add the needed plumbing to carry the value between the
> > > two algorithms.
> > >
> > > This is needed to get good images from sensors such as the IMX219 where
> > > large sensor frames needs a small VBLANK value. This results in images
> >
> > Do you really mean vblank here? Or is it just that you're hitting a
> > limitation on the exposure time or frame duration limit ? (and thus end
> > up with a small vblank...?)
>
> Yes, they are all intertwined in my head ;-) The default FrameLimits
> renders a VBLANK setting, this VBLANK setting limits the range of the
> EXPOSURE. Even if the IPA max the EXPOSURE and ANALOG_GAIN the image is
> very dark. From a .ko modules point of view the driving factor is the
> VBLANK setting.
>
> >
> > > that are so under exposed that even the sensors analog gain can't
> > > compensate. The digital gain stage of the ISP helps with this.
> >
> > This is interesting - but I think would soon be superceeded by the
> > capabilities we can use in the companding block or such with recent
> > development from Stefan in his WDR series ...
>
> I can't tell if it will, or not.
>
> >
> > See [0] and in particular [1] ....
> > [0] https://patchwork.libcamera.org/project/libcamera/list/?series=5382
> > [1] https://patchwork.libcamera.org/patch/24120/
> >
> > Using AWB to apply a gain here can be problematic because we can
> > saturate a single channel
>
> I can set the gain on each channel separately, but I only get one value
> from libipa. Maybe I'm misunderstanding you, or maybe this is why it
> will be superseded by Stefan's work?
>
> > and I think the quantisation of the gain here
> > is quite coarse?
>
> The digital gain I setting used by this patch comes from libipa at the
> same time as the exposure and analog gain settings. It's just that the
> rksip1 IPA have never used the digital gain setting returned.
>
> >
> > But perhaps we'll end up with potentially multiple places we 'could'
> > apply the digital gain ...
>
> I'm using a RPi Module 2 so I compered how the RPi IPA handles this, and
> it applies DIGITAL_GAIN on the sensor. I have tried this and it works,
> but I gather using the sensor for DIGITAL_GAIN is not the best idea
> these days. And I could not find any other block in the ISP where this
> could be done.

Just a quick correction, we don't apply digital gain on the sensor.
Instead, we only apply digital gain (if needed) in the ISP pipeline.

That said, we don't have any issues with setting up a bright image
with a shutter speed/analogue gain combination on the IMX219.   With
digital gain, we only aim to correct the line length and/or gain code
quantization effects, so you would typically only add a few percent
gain.  I suspect you might be encountering another problem in the IPA
perhaps that is limiting your shutter speed, likely based on VBLANK as
you mentioned earlier.

Naush


>
> >
> > Kieran
> >
> > >
> > > Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> > > ---
> > >  src/ipa/rkisp1/algorithms/agc.cpp | 12 ++++++++++--
> > >  src/ipa/rkisp1/algorithms/awb.cpp |  4 +++-
> > >  src/ipa/rkisp1/ipa_context.h      |  3 +++
> > >  3 files changed, 16 insertions(+), 3 deletions(-)
> > >
> > > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> > > index 35440b67e999..17aaa259244b 100644
> > > --- a/src/ipa/rkisp1/algorithms/agc.cpp
> > > +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> > > @@ -174,9 +174,11 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
> > >  {
> > >         /* Configure the default exposure and gain. */
> > >         context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain;
> > > +       context.activeState.agc.automatic.ispGain = 1.0;
> > >         context.activeState.agc.automatic.exposure =
> > >                 10ms / context.configuration.sensor.lineDuration;
> > >         context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;
> > > +       context.activeState.agc.manual.ispGain = 1.0;
> > >         context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
> > >         context.activeState.agc.autoExposureEnabled = !context.configuration.raw;
> > >         context.activeState.agc.autoGainEnabled = !context.configuration.raw;
> > > @@ -280,8 +282,10 @@ void Agc::queueRequest(IPAContext &context,
> > >
> > >         if (!frameContext.agc.autoExposureEnabled)
> > >                 frameContext.agc.exposure = agc.manual.exposure;
> > > -       if (!frameContext.agc.autoGainEnabled)
> > > +       if (!frameContext.agc.autoGainEnabled) {
> > >                 frameContext.agc.gain = agc.manual.gain;
> > > +               frameContext.agc.ispGain = agc.manual.ispGain;
> > > +       }
> > >
> > >         const auto &meteringMode = controls.get(controls::AeMeteringMode);
> > >         if (meteringMode) {
> > > @@ -336,12 +340,15 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
> > >  {
> > >         uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure;
> > >         double activeAutoGain = context.activeState.agc.automatic.gain;
> > > +       double activeAutoIspGain = context.activeState.agc.automatic.ispGain;
> > >
> > >         /* Populate exposure and gain in auto mode */
> > >         if (frameContext.agc.autoExposureEnabled)
> > >                 frameContext.agc.exposure = activeAutoExposure;
> > > -       if (frameContext.agc.autoGainEnabled)
> > > +       if (frameContext.agc.autoGainEnabled) {
> > >                 frameContext.agc.gain = activeAutoGain;
> > > +               frameContext.agc.ispGain = activeAutoIspGain;
> > > +       }
> > >
> > >         /*
> > >          * Populate manual exposure and gain from the active auto values when
> > > @@ -581,6 +588,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> > >         /* Update the estimated exposure and gain. */
> > >         activeState.agc.automatic.exposure = newExposureTime / lineDuration;
> > >         activeState.agc.automatic.gain = aGain;
> > > +       activeState.agc.automatic.ispGain = dGain;
> > >
> > >         /*
> > >          * Expand the target frame duration so that we do not run faster than
> > > diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> > > index 399fb51be414..569cac4a3466 100644
> > > --- a/src/ipa/rkisp1/algorithms/awb.cpp
> > > +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> > > @@ -218,7 +218,9 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
> > >          */
> > >         if (frameContext.awb.autoEnabled) {
> > >                 const auto &awb = context.activeState.awb;
> > > -               frameContext.awb.gains = awb.automatic.gains;
> > > +               const auto &agc = context.activeState.agc;
> > > +
> > > +               frameContext.awb.gains = awb.automatic.gains * agc.automatic.ispGain;
> > >                 frameContext.awb.temperatureK = awb.automatic.temperatureK;
> > >         }
> > >
> > > diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> > > index 7ccc7b501aff..7180b0b7dff2 100644
> > > --- a/src/ipa/rkisp1/ipa_context.h
> > > +++ b/src/ipa/rkisp1/ipa_context.h
> > > @@ -73,10 +73,12 @@ struct IPAActiveState {
> > >                 struct {
> > >                         uint32_t exposure;
> > >                         double gain;
> > > +                       double ispGain;
> > >                 } manual;
> > >                 struct {
> > >                         uint32_t exposure;
> > >                         double gain;
> > > +                       double ispGain;
> > >                 } automatic;
> > >
> > >                 bool autoExposureEnabled;
> > > @@ -130,6 +132,7 @@ struct IPAFrameContext : public FrameContext {
> > >         struct {
> > >                 uint32_t exposure;
> > >                 double gain;
> > > +               double ispGain;
> > >                 double exposureValue;
> > >                 uint32_t vblank;
> > >                 bool autoExposureEnabled;
> > > --
> > > 2.51.0
> > >
>
> --
> Kind Regards,
> Niklas Söderlund
Niklas Söderlund Aug. 28, 2025, 1:52 p.m. UTC | #5
Hi Naushir,

Thanks for the clarification.

On 2025-08-28 13:12:32 +0100, Naushir Patuck wrote:
> Hi Niklas and Kieran,
> 
> On Wed, 27 Aug 2025 at 23:59, Niklas Söderlund
> <niklas.soderlund+renesas@ragnatech.se> wrote:
> >
> > Hi Kieran,
> >
> > On 2025-08-27 23:38:45 +0100, Kieran Bingham wrote:
> > > Hi Niklas,
> > >
> > > Quoting Niklas Söderlund (2025-08-27 23:20:32)
> > > > The digital gain is already calculated in the AGC algorithm but the
> > > > value is not consumed by the AWB algorithm, which is where the gain
> > > > stage is located. Add the needed plumbing to carry the value between the
> > > > two algorithms.
> > > >
> > > > This is needed to get good images from sensors such as the IMX219 where
> > > > large sensor frames needs a small VBLANK value. This results in images
> > >
> > > Do you really mean vblank here? Or is it just that you're hitting a
> > > limitation on the exposure time or frame duration limit ? (and thus end
> > > up with a small vblank...?)
> >
> > Yes, they are all intertwined in my head ;-) The default FrameLimits
> > renders a VBLANK setting, this VBLANK setting limits the range of the
> > EXPOSURE. Even if the IPA max the EXPOSURE and ANALOG_GAIN the image is
> > very dark. From a .ko modules point of view the driving factor is the
> > VBLANK setting.
> >
> > >
> > > > that are so under exposed that even the sensors analog gain can't
> > > > compensate. The digital gain stage of the ISP helps with this.
> > >
> > > This is interesting - but I think would soon be superceeded by the
> > > capabilities we can use in the companding block or such with recent
> > > development from Stefan in his WDR series ...
> >
> > I can't tell if it will, or not.
> >
> > >
> > > See [0] and in particular [1] ....
> > > [0] https://patchwork.libcamera.org/project/libcamera/list/?series=5382
> > > [1] https://patchwork.libcamera.org/patch/24120/
> > >
> > > Using AWB to apply a gain here can be problematic because we can
> > > saturate a single channel
> >
> > I can set the gain on each channel separately, but I only get one value
> > from libipa. Maybe I'm misunderstanding you, or maybe this is why it
> > will be superseded by Stefan's work?
> >
> > > and I think the quantisation of the gain here
> > > is quite coarse?
> >
> > The digital gain I setting used by this patch comes from libipa at the
> > same time as the exposure and analog gain settings. It's just that the
> > rksip1 IPA have never used the digital gain setting returned.
> >
> > >
> > > But perhaps we'll end up with potentially multiple places we 'could'
> > > apply the digital gain ...
> >
> > I'm using a RPi Module 2 so I compered how the RPi IPA handles this, and
> > it applies DIGITAL_GAIN on the sensor. I have tried this and it works,
> > but I gather using the sensor for DIGITAL_GAIN is not the best idea
> > these days. And I could not find any other block in the ISP where this
> > could be done.
> 
> Just a quick correction, we don't apply digital gain on the sensor.
> Instead, we only apply digital gain (if needed) in the ISP pipeline.

I was just investigating my issue and found that the RPI/VC4 IPA is the 
only IPA that sets the V4L2_CID_DIGITAL_GAIN. I might have 
misunderstood how that is used.

> 
> That said, we don't have any issues with setting up a bright image
> with a shutter speed/analogue gain combination on the IMX219.   With
> digital gain, we only aim to correct the line length and/or gain code
> quantization effects, so you would typically only add a few percent
> gain.  I suspect you might be encountering another problem in the IPA
> perhaps that is limiting your shutter speed, likely based on VBLANK as
> you mentioned earlier.

I think so too. After digging more in this I don't think using digital 
gain the way I do in this patch is a good way forward.

> 
> Naush
> 
> 
> >
> > >
> > > Kieran
> > >
> > > >
> > > > Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> > > > ---
> > > >  src/ipa/rkisp1/algorithms/agc.cpp | 12 ++++++++++--
> > > >  src/ipa/rkisp1/algorithms/awb.cpp |  4 +++-
> > > >  src/ipa/rkisp1/ipa_context.h      |  3 +++
> > > >  3 files changed, 16 insertions(+), 3 deletions(-)
> > > >
> > > > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> > > > index 35440b67e999..17aaa259244b 100644
> > > > --- a/src/ipa/rkisp1/algorithms/agc.cpp
> > > > +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> > > > @@ -174,9 +174,11 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
> > > >  {
> > > >         /* Configure the default exposure and gain. */
> > > >         context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain;
> > > > +       context.activeState.agc.automatic.ispGain = 1.0;
> > > >         context.activeState.agc.automatic.exposure =
> > > >                 10ms / context.configuration.sensor.lineDuration;
> > > >         context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;
> > > > +       context.activeState.agc.manual.ispGain = 1.0;
> > > >         context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
> > > >         context.activeState.agc.autoExposureEnabled = !context.configuration.raw;
> > > >         context.activeState.agc.autoGainEnabled = !context.configuration.raw;
> > > > @@ -280,8 +282,10 @@ void Agc::queueRequest(IPAContext &context,
> > > >
> > > >         if (!frameContext.agc.autoExposureEnabled)
> > > >                 frameContext.agc.exposure = agc.manual.exposure;
> > > > -       if (!frameContext.agc.autoGainEnabled)
> > > > +       if (!frameContext.agc.autoGainEnabled) {
> > > >                 frameContext.agc.gain = agc.manual.gain;
> > > > +               frameContext.agc.ispGain = agc.manual.ispGain;
> > > > +       }
> > > >
> > > >         const auto &meteringMode = controls.get(controls::AeMeteringMode);
> > > >         if (meteringMode) {
> > > > @@ -336,12 +340,15 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
> > > >  {
> > > >         uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure;
> > > >         double activeAutoGain = context.activeState.agc.automatic.gain;
> > > > +       double activeAutoIspGain = context.activeState.agc.automatic.ispGain;
> > > >
> > > >         /* Populate exposure and gain in auto mode */
> > > >         if (frameContext.agc.autoExposureEnabled)
> > > >                 frameContext.agc.exposure = activeAutoExposure;
> > > > -       if (frameContext.agc.autoGainEnabled)
> > > > +       if (frameContext.agc.autoGainEnabled) {
> > > >                 frameContext.agc.gain = activeAutoGain;
> > > > +               frameContext.agc.ispGain = activeAutoIspGain;
> > > > +       }
> > > >
> > > >         /*
> > > >          * Populate manual exposure and gain from the active auto values when
> > > > @@ -581,6 +588,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> > > >         /* Update the estimated exposure and gain. */
> > > >         activeState.agc.automatic.exposure = newExposureTime / lineDuration;
> > > >         activeState.agc.automatic.gain = aGain;
> > > > +       activeState.agc.automatic.ispGain = dGain;
> > > >
> > > >         /*
> > > >          * Expand the target frame duration so that we do not run faster than
> > > > diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> > > > index 399fb51be414..569cac4a3466 100644
> > > > --- a/src/ipa/rkisp1/algorithms/awb.cpp
> > > > +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> > > > @@ -218,7 +218,9 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
> > > >          */
> > > >         if (frameContext.awb.autoEnabled) {
> > > >                 const auto &awb = context.activeState.awb;
> > > > -               frameContext.awb.gains = awb.automatic.gains;
> > > > +               const auto &agc = context.activeState.agc;
> > > > +
> > > > +               frameContext.awb.gains = awb.automatic.gains * agc.automatic.ispGain;
> > > >                 frameContext.awb.temperatureK = awb.automatic.temperatureK;
> > > >         }
> > > >
> > > > diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> > > > index 7ccc7b501aff..7180b0b7dff2 100644
> > > > --- a/src/ipa/rkisp1/ipa_context.h
> > > > +++ b/src/ipa/rkisp1/ipa_context.h
> > > > @@ -73,10 +73,12 @@ struct IPAActiveState {
> > > >                 struct {
> > > >                         uint32_t exposure;
> > > >                         double gain;
> > > > +                       double ispGain;
> > > >                 } manual;
> > > >                 struct {
> > > >                         uint32_t exposure;
> > > >                         double gain;
> > > > +                       double ispGain;
> > > >                 } automatic;
> > > >
> > > >                 bool autoExposureEnabled;
> > > > @@ -130,6 +132,7 @@ struct IPAFrameContext : public FrameContext {
> > > >         struct {
> > > >                 uint32_t exposure;
> > > >                 double gain;
> > > > +               double ispGain;
> > > >                 double exposureValue;
> > > >                 uint32_t vblank;
> > > >                 bool autoExposureEnabled;
> > > > --
> > > > 2.51.0
> > > >
> >
> > --
> > Kind Regards,
> > Niklas Söderlund
Niklas Söderlund Aug. 28, 2025, 1:55 p.m. UTC | #6
Hi Kieran,

On 2025-08-28 00:23:40 +0100, Kieran Bingham wrote:
> Quoting Niklas Söderlund (2025-08-27 23:58:56)
> > Hi Kieran,
> > 
> > On 2025-08-27 23:38:45 +0100, Kieran Bingham wrote:
> > > Hi Niklas,
> > > 
> > > Quoting Niklas Söderlund (2025-08-27 23:20:32)
> > > > The digital gain is already calculated in the AGC algorithm but the
> > > > value is not consumed by the AWB algorithm, which is where the gain
> > > > stage is located. Add the needed plumbing to carry the value between the
> > > > two algorithms.
> > > > 
> > > > This is needed to get good images from sensors such as the IMX219 where
> > > > large sensor frames needs a small VBLANK value. This results in images
> > > 
> > > Do you really mean vblank here? Or is it just that you're hitting a
> > > limitation on the exposure time or frame duration limit ? (and thus end
> > > up with a small vblank...?)
> > 
> > Yes, they are all intertwined in my head ;-) The default FrameLimits 
> > renders a VBLANK setting, this VBLANK setting limits the range of the 
> > EXPOSURE. Even if the IPA max the EXPOSURE and ANALOG_GAIN the image is 
> > very dark. From a .ko modules point of view the driving factor is the 
> > VBLANK setting.
> > 
> > > 
> > > > that are so under exposed that even the sensors analog gain can't
> > > > compensate. The digital gain stage of the ISP helps with this.
> > > 
> > > This is interesting - but I think would soon be superceeded by the
> > > capabilities we can use in the companding block or such with recent
> > > development from Stefan in his WDR series ...
> > 
> > I can't tell if it will, or not.
> > 
> > > 
> > > See [0] and in particular [1] ....
> > > [0] https://patchwork.libcamera.org/project/libcamera/list/?series=5382
> > > [1] https://patchwork.libcamera.org/patch/24120/
> > > 
> > > Using AWB to apply a gain here can be problematic because we can
> > > saturate a single channel
> > 
> > I can set the gain on each channel separately, but I only get one value 
> > from libipa. Maybe I'm misunderstanding you, or maybe this is why it 
> > will be superseded by Stefan's work?
> > 
> > > and I think the quantisation of the gain here
> > > is quite coarse?
> > 
> > The digital gain I setting used by this patch comes from libipa at the 
> > same time as the exposure and analog gain settings. It's just that the 
> > rksip1 IPA have never used the digital gain setting returned.
> 
> I mean the available values that can be applied through the AWB gains...
> 
> > > But perhaps we'll end up with potentially multiple places we 'could'
> > > apply the digital gain ...
> > 
> > I'm using a RPi Module 2 so I compered how the RPi IPA handles this, and 
> > it applies DIGITAL_GAIN on the sensor. I have tried this and it works, 
> > but I gather using the sensor for DIGITAL_GAIN is not the best idea 
> 
> We haven't used digital gain on sensors yet - but perhaps that is indeed
> another source that could be utilised.

It feels a bit dangerous to do so, not all sensors expose digital gain 
controls.

> 
> > these days. And I could not find any other block in the ISP where this 
> > could be done.
> 
> The companding/compress block implemented by the WDR series has a
> 'fine-grained/floating point' range gain that can be applied. I've
> already been talking with Stefan to use this to similarly apply digital
> gain - so I'm just waiting for his series to land ;-)

Not all rkisp1 variations have this block, the one in R-Car don't so it 
would not help my use-case. That being said I dug some more in this and 
I think I found out what I can do differently without touching the 
rkisp1 IPA.

I propose this patch be rejected.

> 
> ---
> Kieran
> 
> > 
> > > 
> > > Kieran
> > > 
> > > > 
> > > > Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> > > > ---
> > > >  src/ipa/rkisp1/algorithms/agc.cpp | 12 ++++++++++--
> > > >  src/ipa/rkisp1/algorithms/awb.cpp |  4 +++-
> > > >  src/ipa/rkisp1/ipa_context.h      |  3 +++
> > > >  3 files changed, 16 insertions(+), 3 deletions(-)
> > > > 
> > > > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> > > > index 35440b67e999..17aaa259244b 100644
> > > > --- a/src/ipa/rkisp1/algorithms/agc.cpp
> > > > +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> > > > @@ -174,9 +174,11 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
> > > >  {
> > > >         /* Configure the default exposure and gain. */
> > > >         context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain;
> > > > +       context.activeState.agc.automatic.ispGain = 1.0;
> > > >         context.activeState.agc.automatic.exposure =
> > > >                 10ms / context.configuration.sensor.lineDuration;
> > > >         context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;
> > > > +       context.activeState.agc.manual.ispGain = 1.0;
> > > >         context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
> > > >         context.activeState.agc.autoExposureEnabled = !context.configuration.raw;
> > > >         context.activeState.agc.autoGainEnabled = !context.configuration.raw;
> > > > @@ -280,8 +282,10 @@ void Agc::queueRequest(IPAContext &context,
> > > >  
> > > >         if (!frameContext.agc.autoExposureEnabled)
> > > >                 frameContext.agc.exposure = agc.manual.exposure;
> > > > -       if (!frameContext.agc.autoGainEnabled)
> > > > +       if (!frameContext.agc.autoGainEnabled) {
> > > >                 frameContext.agc.gain = agc.manual.gain;
> > > > +               frameContext.agc.ispGain = agc.manual.ispGain;
> > > > +       }
> > > >  
> > > >         const auto &meteringMode = controls.get(controls::AeMeteringMode);
> > > >         if (meteringMode) {
> > > > @@ -336,12 +340,15 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
> > > >  {
> > > >         uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure;
> > > >         double activeAutoGain = context.activeState.agc.automatic.gain;
> > > > +       double activeAutoIspGain = context.activeState.agc.automatic.ispGain;
> > > >  
> > > >         /* Populate exposure and gain in auto mode */
> > > >         if (frameContext.agc.autoExposureEnabled)
> > > >                 frameContext.agc.exposure = activeAutoExposure;
> > > > -       if (frameContext.agc.autoGainEnabled)
> > > > +       if (frameContext.agc.autoGainEnabled) {
> > > >                 frameContext.agc.gain = activeAutoGain;
> > > > +               frameContext.agc.ispGain = activeAutoIspGain;
> > > > +       }
> > > >  
> > > >         /*
> > > >          * Populate manual exposure and gain from the active auto values when
> > > > @@ -581,6 +588,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> > > >         /* Update the estimated exposure and gain. */
> > > >         activeState.agc.automatic.exposure = newExposureTime / lineDuration;
> > > >         activeState.agc.automatic.gain = aGain;
> > > > +       activeState.agc.automatic.ispGain = dGain;
> > > >  
> > > >         /*
> > > >          * Expand the target frame duration so that we do not run faster than
> > > > diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> > > > index 399fb51be414..569cac4a3466 100644
> > > > --- a/src/ipa/rkisp1/algorithms/awb.cpp
> > > > +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> > > > @@ -218,7 +218,9 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
> > > >          */
> > > >         if (frameContext.awb.autoEnabled) {
> > > >                 const auto &awb = context.activeState.awb;
> > > > -               frameContext.awb.gains = awb.automatic.gains;
> > > > +               const auto &agc = context.activeState.agc;
> > > > +
> > > > +               frameContext.awb.gains = awb.automatic.gains * agc.automatic.ispGain;
> > > >                 frameContext.awb.temperatureK = awb.automatic.temperatureK;
> > > >         }
> > > >  
> > > > diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> > > > index 7ccc7b501aff..7180b0b7dff2 100644
> > > > --- a/src/ipa/rkisp1/ipa_context.h
> > > > +++ b/src/ipa/rkisp1/ipa_context.h
> > > > @@ -73,10 +73,12 @@ struct IPAActiveState {
> > > >                 struct {
> > > >                         uint32_t exposure;
> > > >                         double gain;
> > > > +                       double ispGain;
> > > >                 } manual;
> > > >                 struct {
> > > >                         uint32_t exposure;
> > > >                         double gain;
> > > > +                       double ispGain;
> > > >                 } automatic;
> > > >  
> > > >                 bool autoExposureEnabled;
> > > > @@ -130,6 +132,7 @@ struct IPAFrameContext : public FrameContext {
> > > >         struct {
> > > >                 uint32_t exposure;
> > > >                 double gain;
> > > > +               double ispGain;
> > > >                 double exposureValue;
> > > >                 uint32_t vblank;
> > > >                 bool autoExposureEnabled;
> > > > -- 
> > > > 2.51.0
> > > >
> > 
> > -- 
> > Kind Regards,
> > Niklas Söderlund
Naushir Patuck Aug. 28, 2025, 1:55 p.m. UTC | #7
Hi Niklas,

On Thu, 28 Aug 2025 at 14:52, Niklas Söderlund
<niklas.soderlund+renesas@ragnatech.se> wrote:
>
> Hi Naushir,
>
> Thanks for the clarification.
>
> On 2025-08-28 13:12:32 +0100, Naushir Patuck wrote:
> > Hi Niklas and Kieran,
> >
> > On Wed, 27 Aug 2025 at 23:59, Niklas Söderlund
> > <niklas.soderlund+renesas@ragnatech.se> wrote:
> > >
> > > Hi Kieran,
> > >
> > > On 2025-08-27 23:38:45 +0100, Kieran Bingham wrote:
> > > > Hi Niklas,
> > > >
> > > > Quoting Niklas Söderlund (2025-08-27 23:20:32)
> > > > > The digital gain is already calculated in the AGC algorithm but the
> > > > > value is not consumed by the AWB algorithm, which is where the gain
> > > > > stage is located. Add the needed plumbing to carry the value between the
> > > > > two algorithms.
> > > > >
> > > > > This is needed to get good images from sensors such as the IMX219 where
> > > > > large sensor frames needs a small VBLANK value. This results in images
> > > >
> > > > Do you really mean vblank here? Or is it just that you're hitting a
> > > > limitation on the exposure time or frame duration limit ? (and thus end
> > > > up with a small vblank...?)
> > >
> > > Yes, they are all intertwined in my head ;-) The default FrameLimits
> > > renders a VBLANK setting, this VBLANK setting limits the range of the
> > > EXPOSURE. Even if the IPA max the EXPOSURE and ANALOG_GAIN the image is
> > > very dark. From a .ko modules point of view the driving factor is the
> > > VBLANK setting.
> > >
> > > >
> > > > > that are so under exposed that even the sensors analog gain can't
> > > > > compensate. The digital gain stage of the ISP helps with this.
> > > >
> > > > This is interesting - but I think would soon be superceeded by the
> > > > capabilities we can use in the companding block or such with recent
> > > > development from Stefan in his WDR series ...
> > >
> > > I can't tell if it will, or not.
> > >
> > > >
> > > > See [0] and in particular [1] ....
> > > > [0] https://patchwork.libcamera.org/project/libcamera/list/?series=5382
> > > > [1] https://patchwork.libcamera.org/patch/24120/
> > > >
> > > > Using AWB to apply a gain here can be problematic because we can
> > > > saturate a single channel
> > >
> > > I can set the gain on each channel separately, but I only get one value
> > > from libipa. Maybe I'm misunderstanding you, or maybe this is why it
> > > will be superseded by Stefan's work?
> > >
> > > > and I think the quantisation of the gain here
> > > > is quite coarse?
> > >
> > > The digital gain I setting used by this patch comes from libipa at the
> > > same time as the exposure and analog gain settings. It's just that the
> > > rksip1 IPA have never used the digital gain setting returned.
> > >
> > > >
> > > > But perhaps we'll end up with potentially multiple places we 'could'
> > > > apply the digital gain ...
> > >
> > > I'm using a RPi Module 2 so I compered how the RPi IPA handles this, and
> > > it applies DIGITAL_GAIN on the sensor. I have tried this and it works,
> > > but I gather using the sensor for DIGITAL_GAIN is not the best idea
> > > these days. And I could not find any other block in the ISP where this
> > > could be done.
> >
> > Just a quick correction, we don't apply digital gain on the sensor.
> > Instead, we only apply digital gain (if needed) in the ISP pipeline.
>
> I was just investigating my issue and found that the RPI/VC4 IPA is the
> only IPA that sets the V4L2_CID_DIGITAL_GAIN. I might have
> misunderstood how that is used.

Ah, I understand now.  Use of V4L2_CID_DIGITAL_GAIN in ipa/vc4.cpp is
indeed used to set up the digital gain, but is directed to the ISP
driver, not the sensor subdev.  This is probably where the confusion
comes from.

Regards,
Naush

>
> >
> > That said, we don't have any issues with setting up a bright image
> > with a shutter speed/analogue gain combination on the IMX219.   With
> > digital gain, we only aim to correct the line length and/or gain code
> > quantization effects, so you would typically only add a few percent
> > gain.  I suspect you might be encountering another problem in the IPA
> > perhaps that is limiting your shutter speed, likely based on VBLANK as
> > you mentioned earlier.
>
> I think so too. After digging more in this I don't think using digital
> gain the way I do in this patch is a good way forward.
>
> >
> > Naush
> >
> >
> > >
> > > >
> > > > Kieran
> > > >
> > > > >
> > > > > Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> > > > > ---
> > > > >  src/ipa/rkisp1/algorithms/agc.cpp | 12 ++++++++++--
> > > > >  src/ipa/rkisp1/algorithms/awb.cpp |  4 +++-
> > > > >  src/ipa/rkisp1/ipa_context.h      |  3 +++
> > > > >  3 files changed, 16 insertions(+), 3 deletions(-)
> > > > >
> > > > > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> > > > > index 35440b67e999..17aaa259244b 100644
> > > > > --- a/src/ipa/rkisp1/algorithms/agc.cpp
> > > > > +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> > > > > @@ -174,9 +174,11 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
> > > > >  {
> > > > >         /* Configure the default exposure and gain. */
> > > > >         context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain;
> > > > > +       context.activeState.agc.automatic.ispGain = 1.0;
> > > > >         context.activeState.agc.automatic.exposure =
> > > > >                 10ms / context.configuration.sensor.lineDuration;
> > > > >         context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;
> > > > > +       context.activeState.agc.manual.ispGain = 1.0;
> > > > >         context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
> > > > >         context.activeState.agc.autoExposureEnabled = !context.configuration.raw;
> > > > >         context.activeState.agc.autoGainEnabled = !context.configuration.raw;
> > > > > @@ -280,8 +282,10 @@ void Agc::queueRequest(IPAContext &context,
> > > > >
> > > > >         if (!frameContext.agc.autoExposureEnabled)
> > > > >                 frameContext.agc.exposure = agc.manual.exposure;
> > > > > -       if (!frameContext.agc.autoGainEnabled)
> > > > > +       if (!frameContext.agc.autoGainEnabled) {
> > > > >                 frameContext.agc.gain = agc.manual.gain;
> > > > > +               frameContext.agc.ispGain = agc.manual.ispGain;
> > > > > +       }
> > > > >
> > > > >         const auto &meteringMode = controls.get(controls::AeMeteringMode);
> > > > >         if (meteringMode) {
> > > > > @@ -336,12 +340,15 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
> > > > >  {
> > > > >         uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure;
> > > > >         double activeAutoGain = context.activeState.agc.automatic.gain;
> > > > > +       double activeAutoIspGain = context.activeState.agc.automatic.ispGain;
> > > > >
> > > > >         /* Populate exposure and gain in auto mode */
> > > > >         if (frameContext.agc.autoExposureEnabled)
> > > > >                 frameContext.agc.exposure = activeAutoExposure;
> > > > > -       if (frameContext.agc.autoGainEnabled)
> > > > > +       if (frameContext.agc.autoGainEnabled) {
> > > > >                 frameContext.agc.gain = activeAutoGain;
> > > > > +               frameContext.agc.ispGain = activeAutoIspGain;
> > > > > +       }
> > > > >
> > > > >         /*
> > > > >          * Populate manual exposure and gain from the active auto values when
> > > > > @@ -581,6 +588,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> > > > >         /* Update the estimated exposure and gain. */
> > > > >         activeState.agc.automatic.exposure = newExposureTime / lineDuration;
> > > > >         activeState.agc.automatic.gain = aGain;
> > > > > +       activeState.agc.automatic.ispGain = dGain;
> > > > >
> > > > >         /*
> > > > >          * Expand the target frame duration so that we do not run faster than
> > > > > diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> > > > > index 399fb51be414..569cac4a3466 100644
> > > > > --- a/src/ipa/rkisp1/algorithms/awb.cpp
> > > > > +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> > > > > @@ -218,7 +218,9 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
> > > > >          */
> > > > >         if (frameContext.awb.autoEnabled) {
> > > > >                 const auto &awb = context.activeState.awb;
> > > > > -               frameContext.awb.gains = awb.automatic.gains;
> > > > > +               const auto &agc = context.activeState.agc;
> > > > > +
> > > > > +               frameContext.awb.gains = awb.automatic.gains * agc.automatic.ispGain;
> > > > >                 frameContext.awb.temperatureK = awb.automatic.temperatureK;
> > > > >         }
> > > > >
> > > > > diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> > > > > index 7ccc7b501aff..7180b0b7dff2 100644
> > > > > --- a/src/ipa/rkisp1/ipa_context.h
> > > > > +++ b/src/ipa/rkisp1/ipa_context.h
> > > > > @@ -73,10 +73,12 @@ struct IPAActiveState {
> > > > >                 struct {
> > > > >                         uint32_t exposure;
> > > > >                         double gain;
> > > > > +                       double ispGain;
> > > > >                 } manual;
> > > > >                 struct {
> > > > >                         uint32_t exposure;
> > > > >                         double gain;
> > > > > +                       double ispGain;
> > > > >                 } automatic;
> > > > >
> > > > >                 bool autoExposureEnabled;
> > > > > @@ -130,6 +132,7 @@ struct IPAFrameContext : public FrameContext {
> > > > >         struct {
> > > > >                 uint32_t exposure;
> > > > >                 double gain;
> > > > > +               double ispGain;
> > > > >                 double exposureValue;
> > > > >                 uint32_t vblank;
> > > > >                 bool autoExposureEnabled;
> > > > > --
> > > > > 2.51.0
> > > > >
> > >
> > > --
> > > Kind Regards,
> > > Niklas Söderlund
>
> --
> Kind Regards,
> Niklas Söderlund

Patch
diff mbox series

diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index 35440b67e999..17aaa259244b 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -174,9 +174,11 @@  int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
 {
 	/* Configure the default exposure and gain. */
 	context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain;
+	context.activeState.agc.automatic.ispGain = 1.0;
 	context.activeState.agc.automatic.exposure =
 		10ms / context.configuration.sensor.lineDuration;
 	context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;
+	context.activeState.agc.manual.ispGain = 1.0;
 	context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
 	context.activeState.agc.autoExposureEnabled = !context.configuration.raw;
 	context.activeState.agc.autoGainEnabled = !context.configuration.raw;
@@ -280,8 +282,10 @@  void Agc::queueRequest(IPAContext &context,
 
 	if (!frameContext.agc.autoExposureEnabled)
 		frameContext.agc.exposure = agc.manual.exposure;
-	if (!frameContext.agc.autoGainEnabled)
+	if (!frameContext.agc.autoGainEnabled) {
 		frameContext.agc.gain = agc.manual.gain;
+		frameContext.agc.ispGain = agc.manual.ispGain;
+	}
 
 	const auto &meteringMode = controls.get(controls::AeMeteringMode);
 	if (meteringMode) {
@@ -336,12 +340,15 @@  void Agc::prepare(IPAContext &context, const uint32_t frame,
 {
 	uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure;
 	double activeAutoGain = context.activeState.agc.automatic.gain;
+	double activeAutoIspGain = context.activeState.agc.automatic.ispGain;
 
 	/* Populate exposure and gain in auto mode */
 	if (frameContext.agc.autoExposureEnabled)
 		frameContext.agc.exposure = activeAutoExposure;
-	if (frameContext.agc.autoGainEnabled)
+	if (frameContext.agc.autoGainEnabled) {
 		frameContext.agc.gain = activeAutoGain;
+		frameContext.agc.ispGain = activeAutoIspGain;
+	}
 
 	/*
 	 * Populate manual exposure and gain from the active auto values when
@@ -581,6 +588,7 @@  void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
 	/* Update the estimated exposure and gain. */
 	activeState.agc.automatic.exposure = newExposureTime / lineDuration;
 	activeState.agc.automatic.gain = aGain;
+	activeState.agc.automatic.ispGain = dGain;
 
 	/*
 	 * Expand the target frame duration so that we do not run faster than
diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
index 399fb51be414..569cac4a3466 100644
--- a/src/ipa/rkisp1/algorithms/awb.cpp
+++ b/src/ipa/rkisp1/algorithms/awb.cpp
@@ -218,7 +218,9 @@  void Awb::prepare(IPAContext &context, const uint32_t frame,
 	 */
 	if (frameContext.awb.autoEnabled) {
 		const auto &awb = context.activeState.awb;
-		frameContext.awb.gains = awb.automatic.gains;
+		const auto &agc = context.activeState.agc;
+
+		frameContext.awb.gains = awb.automatic.gains * agc.automatic.ispGain;
 		frameContext.awb.temperatureK = awb.automatic.temperatureK;
 	}
 
diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
index 7ccc7b501aff..7180b0b7dff2 100644
--- a/src/ipa/rkisp1/ipa_context.h
+++ b/src/ipa/rkisp1/ipa_context.h
@@ -73,10 +73,12 @@  struct IPAActiveState {
 		struct {
 			uint32_t exposure;
 			double gain;
+			double ispGain;
 		} manual;
 		struct {
 			uint32_t exposure;
 			double gain;
+			double ispGain;
 		} automatic;
 
 		bool autoExposureEnabled;
@@ -130,6 +132,7 @@  struct IPAFrameContext : public FrameContext {
 	struct {
 		uint32_t exposure;
 		double gain;
+		double ispGain;
 		double exposureValue;
 		uint32_t vblank;
 		bool autoExposureEnabled;