[RFC,v1,5/8] libcamera: pipeline: Set vblank on more platforms
diff mbox series

Message ID 20260827104108.1432632-6-barnabas.pocze@ideasonboard.com
State New
Headers show
Series
  • ipa: libipa: agc: Take exposure margin into account
Related show

Commit Message

Barnabás Pőcze Aug. 27, 2026, 10:41 a.m. UTC
The common `AgcAlgorithm` provides the desired vblank value the given
frame, so adjust `agc::prepareControls()` accordingly, and propagate
the vblank value to the sensors in the pipeline handlers.

Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
---
 src/ipa/ipu3/ipu3.cpp                        | 3 +--
 src/ipa/libipa/agc.cpp                       | 5 ++---
 src/ipa/libipa/agc.h                         | 9 +++++----
 src/ipa/mali-c55/mali-c55.cpp                | 3 +--
 src/ipa/rkisp1/rkisp1.cpp                    | 4 +---
 src/ipa/softisp/softisp.cpp                  | 3 +--
 src/libcamera/pipeline/ipu3/ipu3.cpp         | 1 +
 src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1 +
 src/libcamera/pipeline/simple/simple.cpp     | 1 +
 9 files changed, 14 insertions(+), 16 deletions(-)

Comments

Stefan Klug Aug. 27, 2026, 1:44 p.m. UTC | #1
Hi Barnabás,

Quoting Barnabás Pőcze (2026-08-27 12:41:05)
> The common `AgcAlgorithm` provides the desired vblank value the given
> frame, so adjust `agc::prepareControls()` accordingly, and propagate
> the vblank value to the sensors in the pipeline handlers.
> 
> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> ---
>  src/ipa/ipu3/ipu3.cpp                        | 3 +--
>  src/ipa/libipa/agc.cpp                       | 5 ++---
>  src/ipa/libipa/agc.h                         | 9 +++++----
>  src/ipa/mali-c55/mali-c55.cpp                | 3 +--
>  src/ipa/rkisp1/rkisp1.cpp                    | 4 +---
>  src/ipa/softisp/softisp.cpp                  | 3 +--
>  src/libcamera/pipeline/ipu3/ipu3.cpp         | 1 +
>  src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1 +
>  src/libcamera/pipeline/simple/simple.cpp     | 1 +
>  9 files changed, 14 insertions(+), 16 deletions(-)
> 
> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
> index 031b2e4f4d..f8990e3a4b 100644
> --- a/src/ipa/ipu3/ipu3.cpp
> +++ b/src/ipa/ipu3/ipu3.cpp
> @@ -551,8 +551,7 @@ void IPAIPU3::setControls(unsigned int frame)
>         IPAFrameContext &frameContext = context_.frameContexts.get(frame);
>  
>         ControlList ctrls(context_.sensorControls);
> -       agc::prepareControls(ctrls, context_.camHelper.get(),
> -                            frameContext.agc.exposure, frameContext.agc.gain);
> +       agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
>  
>         ControlList lensCtrls(lensCtrls_);
>         lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE,
> diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp
> index 34379c0d61..ce5b2ab6f6 100644
> --- a/src/ipa/libipa/agc.cpp
> +++ b/src/ipa/libipa/agc.cpp
> @@ -56,11 +56,10 @@ namespace agc {
>  
>  /**
>   * \fn prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
> - *                     uint32_t exposure, double gain)
> + *                     const FrameContext &frameContext)
>   * \param[out] controls The controls list to populate
>   * \param[in] sensor The CameraSensorHelper
> - * \param[in] exposure The exposure (in lines)
> - * \param[in] gain The analogue gain
> + * \param[in] frameContext The agc frame context
>   *
>   * This function sets \a V4L2_CID_EXPOSURE and \a V4L2_CID_ANALOGUE_GAIN
>   * in \a controls. The gain is mapped to the gain code if \a sensor is provided,
> diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h
> index 9f95f23ea8..388693d21d 100644
> --- a/src/ipa/libipa/agc.h
> +++ b/src/ipa/libipa/agc.h
> @@ -107,13 +107,14 @@ extractControls(const ControlList &controls, const CameraSensorHelper *sensor)
>  
>  inline void
>  prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
> -               uint32_t exposure, double gain)
> +               const FrameContext &frameContext)
>  {
> -       controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
> +       controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(frameContext.exposure));
>         controls.set(V4L2_CID_ANALOGUE_GAIN,
>                      static_cast<int32_t>(sensor
> -                                         ? sensor->gainCode(gain)
> -                                         : static_cast<uint32_t>(gain)));
> +                                         ? sensor->gainCode(frameContext.gain)
> +                                         : static_cast<uint32_t>(frameContext.gain)));
> +       controls.set(V4L2_CID_VBLANK, static_cast<int32_t>(frameContext.vblank));
>  }

I think the extractControls() and prepareControls() functions have
reached a size that they could be moved to the cpp. Or is there a
specific reason to keep them in the header?

>  
>  } /* namespace agc */
> diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
> index 1e7cd1995d..201225128a 100644
> --- a/src/ipa/mali-c55/mali-c55.cpp
> +++ b/src/ipa/mali-c55/mali-c55.cpp
> @@ -134,8 +134,7 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig
>  void IPAMaliC55::setControls(const IPAFrameContext &frameContext)
>  {
>         ControlList ctrls(context_.sensorControls);
> -       agc::prepareControls(ctrls, context_.camHelper.get(),
> -                            frameContext.agc.exposure, frameContext.agc.gain);
> +       agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
>  
>         setSensorControls.emit(ctrls);
>  }
> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
> index 79ab7338c1..cefaf40aee 100644
> --- a/src/ipa/rkisp1/rkisp1.cpp
> +++ b/src/ipa/rkisp1/rkisp1.cpp
> @@ -367,9 +367,7 @@ void IPARkISP1::setControls(unsigned int frame)
>                 << ", gain " << frameContext.agc.gain << ", vblank " << vblank;

What about moving that log message into prepareControls()? I think that
is relevant debug information on all platforms.

Best regards,
Stefan

> 
>         ControlList ctrls(context_.sensorControls);
> -       agc::prepareControls(ctrls, context_.camHelper.get(),
> -                            exposure, frameContext.agc.gain);
> -       ctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank));
> +       agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
>  
>         setSensorControls.emit(frame, ctrls);
>  }
> diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp
> index 8acfcd1a0b..fbb3f3302f 100644
> --- a/src/ipa/softisp/softisp.cpp
> +++ b/src/ipa/softisp/softisp.cpp
> @@ -249,8 +249,7 @@ void IPASoftIsp::processStats(const uint32_t frame,
>         metadataReady.emit(frame, metadata);
>  
>         ControlList ctrls(context_.sensorControls);
> -       agc::prepareControls(ctrls, context_.camHelper.get(),
> -                            frameContext.agc.exposure, frameContext.agc.gain);
> +       agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
>         setSensorControls.emit(ctrls);
>  }
>  
> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
> index 0f3e169d3b..4147f9475a 100644
> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp
> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
> @@ -1084,6 +1084,7 @@ int PipelineHandlerIPU3::registerCameras()
>                 std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
>                         { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
>                         { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
> +                       { V4L2_CID_VBLANK, { delays.vblankDelay, true } },
>                 };
>  
>                 data->delayedCtrls_ =
> diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> index 599ff88b59..ef156d421b 100644
> --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> @@ -1909,6 +1909,7 @@ bool PipelineHandlerMaliC55::registerMemoryInputCamera(MediaLink *link)
>         std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
>                 { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
>                 { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
> +               { V4L2_CID_VBLANK, { delays.vblankDelay, true } },
>         };
>  
>         data->delayedCtrls_ =
> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
> index ca98e74790..4928ef50e9 100644
> --- a/src/libcamera/pipeline/simple/simple.cpp
> +++ b/src/libcamera/pipeline/simple/simple.cpp
> @@ -569,6 +569,7 @@ SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe,
>         std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
>                 { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
>                 { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
> +               { V4L2_CID_VBLANK, { delays.vblankDelay, true } },
>         };
>         delayedCtrls_ = std::make_unique<DelayedControls>(sensor_->device(), params);
>  
> -- 
> 2.55.0
>
Jacopo Mondi Aug. 27, 2026, 3:20 p.m. UTC | #2
Hi Dan

On Thu, Aug 27, 2026 at 12:41:05PM +0200, Barnabás Pőcze wrote:
> The common `AgcAlgorithm` provides the desired vblank value the given
> frame, so adjust `agc::prepareControls()` accordingly, and propagate
> the vblank value to the sensors in the pipeline handlers.
>
> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>

Do you happen to be able to re-test this one on IPU3 ?

> ---
>  src/ipa/ipu3/ipu3.cpp                        | 3 +--
>  src/ipa/libipa/agc.cpp                       | 5 ++---
>  src/ipa/libipa/agc.h                         | 9 +++++----
>  src/ipa/mali-c55/mali-c55.cpp                | 3 +--
>  src/ipa/rkisp1/rkisp1.cpp                    | 4 +---
>  src/ipa/softisp/softisp.cpp                  | 3 +--
>  src/libcamera/pipeline/ipu3/ipu3.cpp         | 1 +
>  src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1 +
>  src/libcamera/pipeline/simple/simple.cpp     | 1 +
>  9 files changed, 14 insertions(+), 16 deletions(-)
>
> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
> index 031b2e4f4d..f8990e3a4b 100644
> --- a/src/ipa/ipu3/ipu3.cpp
> +++ b/src/ipa/ipu3/ipu3.cpp
> @@ -551,8 +551,7 @@ void IPAIPU3::setControls(unsigned int frame)
>  	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
>
>  	ControlList ctrls(context_.sensorControls);
> -	agc::prepareControls(ctrls, context_.camHelper.get(),
> -			     frameContext.agc.exposure, frameContext.agc.gain);
> +	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
>
>  	ControlList lensCtrls(lensCtrls_);
>  	lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE,
> diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp
> index 34379c0d61..ce5b2ab6f6 100644
> --- a/src/ipa/libipa/agc.cpp
> +++ b/src/ipa/libipa/agc.cpp
> @@ -56,11 +56,10 @@ namespace agc {
>
>  /**
>   * \fn prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
> - *                     uint32_t exposure, double gain)
> + *                     const FrameContext &frameContext)
>   * \param[out] controls The controls list to populate
>   * \param[in] sensor The CameraSensorHelper
> - * \param[in] exposure The exposure (in lines)
> - * \param[in] gain The analogue gain
> + * \param[in] frameContext The agc frame context
>   *
>   * This function sets \a V4L2_CID_EXPOSURE and \a V4L2_CID_ANALOGUE_GAIN
>   * in \a controls. The gain is mapped to the gain code if \a sensor is provided,
> diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h
> index 9f95f23ea8..388693d21d 100644
> --- a/src/ipa/libipa/agc.h
> +++ b/src/ipa/libipa/agc.h
> @@ -107,13 +107,14 @@ extractControls(const ControlList &controls, const CameraSensorHelper *sensor)
>
>  inline void
>  prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
> -		uint32_t exposure, double gain)
> +		const FrameContext &frameContext)
>  {
> -	controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
> +	controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(frameContext.exposure));
>  	controls.set(V4L2_CID_ANALOGUE_GAIN,
>  		     static_cast<int32_t>(sensor
> -					  ? sensor->gainCode(gain)
> -					  : static_cast<uint32_t>(gain)));
> +					  ? sensor->gainCode(frameContext.gain)
> +					  : static_cast<uint32_t>(frameContext.gain)));
> +	controls.set(V4L2_CID_VBLANK, static_cast<int32_t>(frameContext.vblank));
>  }
>
>  } /* namespace agc */
> diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
> index 1e7cd1995d..201225128a 100644
> --- a/src/ipa/mali-c55/mali-c55.cpp
> +++ b/src/ipa/mali-c55/mali-c55.cpp
> @@ -134,8 +134,7 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig
>  void IPAMaliC55::setControls(const IPAFrameContext &frameContext)
>  {
>  	ControlList ctrls(context_.sensorControls);
> -	agc::prepareControls(ctrls, context_.camHelper.get(),
> -			     frameContext.agc.exposure, frameContext.agc.gain);
> +	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
>
>  	setSensorControls.emit(ctrls);
>  }
> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
> index 79ab7338c1..cefaf40aee 100644
> --- a/src/ipa/rkisp1/rkisp1.cpp
> +++ b/src/ipa/rkisp1/rkisp1.cpp
> @@ -367,9 +367,7 @@ void IPARkISP1::setControls(unsigned int frame)
>  		<< ", gain " << frameContext.agc.gain << ", vblank " << vblank;
>
>  	ControlList ctrls(context_.sensorControls);
> -	agc::prepareControls(ctrls, context_.camHelper.get(),
> -			     exposure, frameContext.agc.gain);
> -	ctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank));
> +	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
>
>  	setSensorControls.emit(frame, ctrls);
>  }
> diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp
> index 8acfcd1a0b..fbb3f3302f 100644
> --- a/src/ipa/softisp/softisp.cpp
> +++ b/src/ipa/softisp/softisp.cpp
> @@ -249,8 +249,7 @@ void IPASoftIsp::processStats(const uint32_t frame,
>  	metadataReady.emit(frame, metadata);
>
>  	ControlList ctrls(context_.sensorControls);
> -	agc::prepareControls(ctrls, context_.camHelper.get(),
> -			     frameContext.agc.exposure, frameContext.agc.gain);
> +	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
>  	setSensorControls.emit(ctrls);
>  }
>
> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
> index 0f3e169d3b..4147f9475a 100644
> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp
> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
> @@ -1084,6 +1084,7 @@ int PipelineHandlerIPU3::registerCameras()
>  		std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
>  			{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
>  			{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
> +			{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
>  		};
>
>  		data->delayedCtrls_ =
> diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> index 599ff88b59..ef156d421b 100644
> --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> @@ -1909,6 +1909,7 @@ bool PipelineHandlerMaliC55::registerMemoryInputCamera(MediaLink *link)
>  	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
>  		{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
>  		{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
> +		{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
>  	};
>
>  	data->delayedCtrls_ =
> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
> index ca98e74790..4928ef50e9 100644
> --- a/src/libcamera/pipeline/simple/simple.cpp
> +++ b/src/libcamera/pipeline/simple/simple.cpp
> @@ -569,6 +569,7 @@ SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe,
>  	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
>  		{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
>  		{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
> +		{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
>  	};
>  	delayedCtrls_ = std::make_unique<DelayedControls>(sensor_->device(), params);
>
> --
> 2.55.0
>
Dan Scally Aug. 27, 2026, 3:22 p.m. UTC | #3
Hello

On 27/08/2026 16:20, Jacopo Mondi wrote:
> Hi Dan
> 
> On Thu, Aug 27, 2026 at 12:41:05PM +0200, Barnabás Pőcze wrote:
>> The common `AgcAlgorithm` provides the desired vblank value the given
>> frame, so adjust `agc::prepareControls()` accordingly, and propagate
>> the vblank value to the sensors in the pipeline handlers.
>>
>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> 
> Do you happen to be able to re-test this one on IPU3 ?

This v1 series you mean? Or just this patch or something? I can test anything, but possibly not 
until Tuesday as I was triggering the problem we saw with my lightbox before and I'm away from home 
until then so I don't have it (though I do have the IPU3 with me).

Thanks
Dan

> 
>> ---
>>   src/ipa/ipu3/ipu3.cpp                        | 3 +--
>>   src/ipa/libipa/agc.cpp                       | 5 ++---
>>   src/ipa/libipa/agc.h                         | 9 +++++----
>>   src/ipa/mali-c55/mali-c55.cpp                | 3 +--
>>   src/ipa/rkisp1/rkisp1.cpp                    | 4 +---
>>   src/ipa/softisp/softisp.cpp                  | 3 +--
>>   src/libcamera/pipeline/ipu3/ipu3.cpp         | 1 +
>>   src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1 +
>>   src/libcamera/pipeline/simple/simple.cpp     | 1 +
>>   9 files changed, 14 insertions(+), 16 deletions(-)
>>
>> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
>> index 031b2e4f4d..f8990e3a4b 100644
>> --- a/src/ipa/ipu3/ipu3.cpp
>> +++ b/src/ipa/ipu3/ipu3.cpp
>> @@ -551,8 +551,7 @@ void IPAIPU3::setControls(unsigned int frame)
>>   	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
>>
>>   	ControlList ctrls(context_.sensorControls);
>> -	agc::prepareControls(ctrls, context_.camHelper.get(),
>> -			     frameContext.agc.exposure, frameContext.agc.gain);
>> +	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
>>
>>   	ControlList lensCtrls(lensCtrls_);
>>   	lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE,
>> diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp
>> index 34379c0d61..ce5b2ab6f6 100644
>> --- a/src/ipa/libipa/agc.cpp
>> +++ b/src/ipa/libipa/agc.cpp
>> @@ -56,11 +56,10 @@ namespace agc {
>>
>>   /**
>>    * \fn prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
>> - *                     uint32_t exposure, double gain)
>> + *                     const FrameContext &frameContext)
>>    * \param[out] controls The controls list to populate
>>    * \param[in] sensor The CameraSensorHelper
>> - * \param[in] exposure The exposure (in lines)
>> - * \param[in] gain The analogue gain
>> + * \param[in] frameContext The agc frame context
>>    *
>>    * This function sets \a V4L2_CID_EXPOSURE and \a V4L2_CID_ANALOGUE_GAIN
>>    * in \a controls. The gain is mapped to the gain code if \a sensor is provided,
>> diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h
>> index 9f95f23ea8..388693d21d 100644
>> --- a/src/ipa/libipa/agc.h
>> +++ b/src/ipa/libipa/agc.h
>> @@ -107,13 +107,14 @@ extractControls(const ControlList &controls, const CameraSensorHelper *sensor)
>>
>>   inline void
>>   prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
>> -		uint32_t exposure, double gain)
>> +		const FrameContext &frameContext)
>>   {
>> -	controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
>> +	controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(frameContext.exposure));
>>   	controls.set(V4L2_CID_ANALOGUE_GAIN,
>>   		     static_cast<int32_t>(sensor
>> -					  ? sensor->gainCode(gain)
>> -					  : static_cast<uint32_t>(gain)));
>> +					  ? sensor->gainCode(frameContext.gain)
>> +					  : static_cast<uint32_t>(frameContext.gain)));
>> +	controls.set(V4L2_CID_VBLANK, static_cast<int32_t>(frameContext.vblank));
>>   }
>>
>>   } /* namespace agc */
>> diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
>> index 1e7cd1995d..201225128a 100644
>> --- a/src/ipa/mali-c55/mali-c55.cpp
>> +++ b/src/ipa/mali-c55/mali-c55.cpp
>> @@ -134,8 +134,7 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig
>>   void IPAMaliC55::setControls(const IPAFrameContext &frameContext)
>>   {
>>   	ControlList ctrls(context_.sensorControls);
>> -	agc::prepareControls(ctrls, context_.camHelper.get(),
>> -			     frameContext.agc.exposure, frameContext.agc.gain);
>> +	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
>>
>>   	setSensorControls.emit(ctrls);
>>   }
>> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
>> index 79ab7338c1..cefaf40aee 100644
>> --- a/src/ipa/rkisp1/rkisp1.cpp
>> +++ b/src/ipa/rkisp1/rkisp1.cpp
>> @@ -367,9 +367,7 @@ void IPARkISP1::setControls(unsigned int frame)
>>   		<< ", gain " << frameContext.agc.gain << ", vblank " << vblank;
>>
>>   	ControlList ctrls(context_.sensorControls);
>> -	agc::prepareControls(ctrls, context_.camHelper.get(),
>> -			     exposure, frameContext.agc.gain);
>> -	ctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank));
>> +	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
>>
>>   	setSensorControls.emit(frame, ctrls);
>>   }
>> diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp
>> index 8acfcd1a0b..fbb3f3302f 100644
>> --- a/src/ipa/softisp/softisp.cpp
>> +++ b/src/ipa/softisp/softisp.cpp
>> @@ -249,8 +249,7 @@ void IPASoftIsp::processStats(const uint32_t frame,
>>   	metadataReady.emit(frame, metadata);
>>
>>   	ControlList ctrls(context_.sensorControls);
>> -	agc::prepareControls(ctrls, context_.camHelper.get(),
>> -			     frameContext.agc.exposure, frameContext.agc.gain);
>> +	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
>>   	setSensorControls.emit(ctrls);
>>   }
>>
>> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
>> index 0f3e169d3b..4147f9475a 100644
>> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp
>> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
>> @@ -1084,6 +1084,7 @@ int PipelineHandlerIPU3::registerCameras()
>>   		std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
>>   			{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
>>   			{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
>> +			{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
>>   		};
>>
>>   		data->delayedCtrls_ =
>> diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
>> index 599ff88b59..ef156d421b 100644
>> --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
>> +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
>> @@ -1909,6 +1909,7 @@ bool PipelineHandlerMaliC55::registerMemoryInputCamera(MediaLink *link)
>>   	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
>>   		{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
>>   		{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
>> +		{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
>>   	};
>>
>>   	data->delayedCtrls_ =
>> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
>> index ca98e74790..4928ef50e9 100644
>> --- a/src/libcamera/pipeline/simple/simple.cpp
>> +++ b/src/libcamera/pipeline/simple/simple.cpp
>> @@ -569,6 +569,7 @@ SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe,
>>   	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
>>   		{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
>>   		{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
>> +		{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
>>   	};
>>   	delayedCtrls_ = std::make_unique<DelayedControls>(sensor_->device(), params);
>>
>> --
>> 2.55.0
>>
Jacopo Mondi Aug. 27, 2026, 3:25 p.m. UTC | #4
HI Dan

On Thu, Aug 27, 2026 at 04:22:56PM +0100, Dan Scally wrote:
> Hello
>
> On 27/08/2026 16:20, Jacopo Mondi wrote:
> > Hi Dan
> >
> > On Thu, Aug 27, 2026 at 12:41:05PM +0200, Barnabás Pőcze wrote:
> > > The common `AgcAlgorithm` provides the desired vblank value the given
> > > frame, so adjust `agc::prepareControls()` accordingly, and propagate
> > > the vblank value to the sensors in the pipeline handlers.
> > >
> > > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> >
> > Do you happen to be able to re-test this one on IPU3 ?
>
> This v1 series you mean? Or just this patch or something? I can test

Yeah this series.

I recall you reported oscillations on a version of the libIPA AGC
rework that included these changes, which now have been sent as a
separate series

> anything, but possibly not until Tuesday as I was triggering the problem we
> saw with my lightbox before and I'm away from home until then so I don't
> have it (though I do have the IPU3 with me).

Don't worry ;)

>
> Thanks
> Dan
>
> >
> > > ---
> > >   src/ipa/ipu3/ipu3.cpp                        | 3 +--
> > >   src/ipa/libipa/agc.cpp                       | 5 ++---
> > >   src/ipa/libipa/agc.h                         | 9 +++++----
> > >   src/ipa/mali-c55/mali-c55.cpp                | 3 +--
> > >   src/ipa/rkisp1/rkisp1.cpp                    | 4 +---
> > >   src/ipa/softisp/softisp.cpp                  | 3 +--
> > >   src/libcamera/pipeline/ipu3/ipu3.cpp         | 1 +
> > >   src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1 +
> > >   src/libcamera/pipeline/simple/simple.cpp     | 1 +
> > >   9 files changed, 14 insertions(+), 16 deletions(-)
> > >
> > > diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
> > > index 031b2e4f4d..f8990e3a4b 100644
> > > --- a/src/ipa/ipu3/ipu3.cpp
> > > +++ b/src/ipa/ipu3/ipu3.cpp
> > > @@ -551,8 +551,7 @@ void IPAIPU3::setControls(unsigned int frame)
> > >   	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
> > >
> > >   	ControlList ctrls(context_.sensorControls);
> > > -	agc::prepareControls(ctrls, context_.camHelper.get(),
> > > -			     frameContext.agc.exposure, frameContext.agc.gain);
> > > +	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
> > >
> > >   	ControlList lensCtrls(lensCtrls_);
> > >   	lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE,
> > > diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp
> > > index 34379c0d61..ce5b2ab6f6 100644
> > > --- a/src/ipa/libipa/agc.cpp
> > > +++ b/src/ipa/libipa/agc.cpp
> > > @@ -56,11 +56,10 @@ namespace agc {
> > >
> > >   /**
> > >    * \fn prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
> > > - *                     uint32_t exposure, double gain)
> > > + *                     const FrameContext &frameContext)
> > >    * \param[out] controls The controls list to populate
> > >    * \param[in] sensor The CameraSensorHelper
> > > - * \param[in] exposure The exposure (in lines)
> > > - * \param[in] gain The analogue gain
> > > + * \param[in] frameContext The agc frame context
> > >    *
> > >    * This function sets \a V4L2_CID_EXPOSURE and \a V4L2_CID_ANALOGUE_GAIN
> > >    * in \a controls. The gain is mapped to the gain code if \a sensor is provided,
> > > diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h
> > > index 9f95f23ea8..388693d21d 100644
> > > --- a/src/ipa/libipa/agc.h
> > > +++ b/src/ipa/libipa/agc.h
> > > @@ -107,13 +107,14 @@ extractControls(const ControlList &controls, const CameraSensorHelper *sensor)
> > >
> > >   inline void
> > >   prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
> > > -		uint32_t exposure, double gain)
> > > +		const FrameContext &frameContext)
> > >   {
> > > -	controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
> > > +	controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(frameContext.exposure));
> > >   	controls.set(V4L2_CID_ANALOGUE_GAIN,
> > >   		     static_cast<int32_t>(sensor
> > > -					  ? sensor->gainCode(gain)
> > > -					  : static_cast<uint32_t>(gain)));
> > > +					  ? sensor->gainCode(frameContext.gain)
> > > +					  : static_cast<uint32_t>(frameContext.gain)));
> > > +	controls.set(V4L2_CID_VBLANK, static_cast<int32_t>(frameContext.vblank));
> > >   }
> > >
> > >   } /* namespace agc */
> > > diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
> > > index 1e7cd1995d..201225128a 100644
> > > --- a/src/ipa/mali-c55/mali-c55.cpp
> > > +++ b/src/ipa/mali-c55/mali-c55.cpp
> > > @@ -134,8 +134,7 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig
> > >   void IPAMaliC55::setControls(const IPAFrameContext &frameContext)
> > >   {
> > >   	ControlList ctrls(context_.sensorControls);
> > > -	agc::prepareControls(ctrls, context_.camHelper.get(),
> > > -			     frameContext.agc.exposure, frameContext.agc.gain);
> > > +	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
> > >
> > >   	setSensorControls.emit(ctrls);
> > >   }
> > > diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
> > > index 79ab7338c1..cefaf40aee 100644
> > > --- a/src/ipa/rkisp1/rkisp1.cpp
> > > +++ b/src/ipa/rkisp1/rkisp1.cpp
> > > @@ -367,9 +367,7 @@ void IPARkISP1::setControls(unsigned int frame)
> > >   		<< ", gain " << frameContext.agc.gain << ", vblank " << vblank;
> > >
> > >   	ControlList ctrls(context_.sensorControls);
> > > -	agc::prepareControls(ctrls, context_.camHelper.get(),
> > > -			     exposure, frameContext.agc.gain);
> > > -	ctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank));
> > > +	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
> > >
> > >   	setSensorControls.emit(frame, ctrls);
> > >   }
> > > diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp
> > > index 8acfcd1a0b..fbb3f3302f 100644
> > > --- a/src/ipa/softisp/softisp.cpp
> > > +++ b/src/ipa/softisp/softisp.cpp
> > > @@ -249,8 +249,7 @@ void IPASoftIsp::processStats(const uint32_t frame,
> > >   	metadataReady.emit(frame, metadata);
> > >
> > >   	ControlList ctrls(context_.sensorControls);
> > > -	agc::prepareControls(ctrls, context_.camHelper.get(),
> > > -			     frameContext.agc.exposure, frameContext.agc.gain);
> > > +	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
> > >   	setSensorControls.emit(ctrls);
> > >   }
> > >
> > > diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
> > > index 0f3e169d3b..4147f9475a 100644
> > > --- a/src/libcamera/pipeline/ipu3/ipu3.cpp
> > > +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
> > > @@ -1084,6 +1084,7 @@ int PipelineHandlerIPU3::registerCameras()
> > >   		std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
> > >   			{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
> > >   			{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
> > > +			{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
> > >   		};
> > >
> > >   		data->delayedCtrls_ =
> > > diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> > > index 599ff88b59..ef156d421b 100644
> > > --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> > > +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> > > @@ -1909,6 +1909,7 @@ bool PipelineHandlerMaliC55::registerMemoryInputCamera(MediaLink *link)
> > >   	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
> > >   		{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
> > >   		{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
> > > +		{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
> > >   	};
> > >
> > >   	data->delayedCtrls_ =
> > > diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
> > > index ca98e74790..4928ef50e9 100644
> > > --- a/src/libcamera/pipeline/simple/simple.cpp
> > > +++ b/src/libcamera/pipeline/simple/simple.cpp
> > > @@ -569,6 +569,7 @@ SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe,
> > >   	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
> > >   		{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
> > >   		{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
> > > +		{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
> > >   	};
> > >   	delayedCtrls_ = std::make_unique<DelayedControls>(sensor_->device(), params);
> > >
> > > --
> > > 2.55.0
> > >
>

Patch
diff mbox series

diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
index 031b2e4f4d..f8990e3a4b 100644
--- a/src/ipa/ipu3/ipu3.cpp
+++ b/src/ipa/ipu3/ipu3.cpp
@@ -551,8 +551,7 @@  void IPAIPU3::setControls(unsigned int frame)
 	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
 
 	ControlList ctrls(context_.sensorControls);
-	agc::prepareControls(ctrls, context_.camHelper.get(),
-			     frameContext.agc.exposure, frameContext.agc.gain);
+	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
 
 	ControlList lensCtrls(lensCtrls_);
 	lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE,
diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp
index 34379c0d61..ce5b2ab6f6 100644
--- a/src/ipa/libipa/agc.cpp
+++ b/src/ipa/libipa/agc.cpp
@@ -56,11 +56,10 @@  namespace agc {
 
 /**
  * \fn prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
- *                     uint32_t exposure, double gain)
+ *                     const FrameContext &frameContext)
  * \param[out] controls The controls list to populate
  * \param[in] sensor The CameraSensorHelper
- * \param[in] exposure The exposure (in lines)
- * \param[in] gain The analogue gain
+ * \param[in] frameContext The agc frame context
  *
  * This function sets \a V4L2_CID_EXPOSURE and \a V4L2_CID_ANALOGUE_GAIN
  * in \a controls. The gain is mapped to the gain code if \a sensor is provided,
diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h
index 9f95f23ea8..388693d21d 100644
--- a/src/ipa/libipa/agc.h
+++ b/src/ipa/libipa/agc.h
@@ -107,13 +107,14 @@  extractControls(const ControlList &controls, const CameraSensorHelper *sensor)
 
 inline void
 prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
-		uint32_t exposure, double gain)
+		const FrameContext &frameContext)
 {
-	controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
+	controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(frameContext.exposure));
 	controls.set(V4L2_CID_ANALOGUE_GAIN,
 		     static_cast<int32_t>(sensor
-					  ? sensor->gainCode(gain)
-					  : static_cast<uint32_t>(gain)));
+					  ? sensor->gainCode(frameContext.gain)
+					  : static_cast<uint32_t>(frameContext.gain)));
+	controls.set(V4L2_CID_VBLANK, static_cast<int32_t>(frameContext.vblank));
 }
 
 } /* namespace agc */
diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
index 1e7cd1995d..201225128a 100644
--- a/src/ipa/mali-c55/mali-c55.cpp
+++ b/src/ipa/mali-c55/mali-c55.cpp
@@ -134,8 +134,7 @@  int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig
 void IPAMaliC55::setControls(const IPAFrameContext &frameContext)
 {
 	ControlList ctrls(context_.sensorControls);
-	agc::prepareControls(ctrls, context_.camHelper.get(),
-			     frameContext.agc.exposure, frameContext.agc.gain);
+	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
 
 	setSensorControls.emit(ctrls);
 }
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index 79ab7338c1..cefaf40aee 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -367,9 +367,7 @@  void IPARkISP1::setControls(unsigned int frame)
 		<< ", gain " << frameContext.agc.gain << ", vblank " << vblank;
 
 	ControlList ctrls(context_.sensorControls);
-	agc::prepareControls(ctrls, context_.camHelper.get(),
-			     exposure, frameContext.agc.gain);
-	ctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank));
+	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
 
 	setSensorControls.emit(frame, ctrls);
 }
diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp
index 8acfcd1a0b..fbb3f3302f 100644
--- a/src/ipa/softisp/softisp.cpp
+++ b/src/ipa/softisp/softisp.cpp
@@ -249,8 +249,7 @@  void IPASoftIsp::processStats(const uint32_t frame,
 	metadataReady.emit(frame, metadata);
 
 	ControlList ctrls(context_.sensorControls);
-	agc::prepareControls(ctrls, context_.camHelper.get(),
-			     frameContext.agc.exposure, frameContext.agc.gain);
+	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
 	setSensorControls.emit(ctrls);
 }
 
diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index 0f3e169d3b..4147f9475a 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -1084,6 +1084,7 @@  int PipelineHandlerIPU3::registerCameras()
 		std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
 			{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
 			{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
+			{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
 		};
 
 		data->delayedCtrls_ =
diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
index 599ff88b59..ef156d421b 100644
--- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
+++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
@@ -1909,6 +1909,7 @@  bool PipelineHandlerMaliC55::registerMemoryInputCamera(MediaLink *link)
 	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
 		{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
 		{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
+		{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
 	};
 
 	data->delayedCtrls_ =
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index ca98e74790..4928ef50e9 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -569,6 +569,7 @@  SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe,
 	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
 		{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
 		{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
+		{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
 	};
 	delayedCtrls_ = std::make_unique<DelayedControls>(sensor_->device(), params);