| Message ID | 20260428133952.6582-4-david.plowman@raspberrypi.com |
|---|---|
| State | Superseded |
| Headers | show |
| Series |
|
| Related | show |
Hi David On Tue, Apr 28, 2026 at 02:26:39PM +0100, David Plowman wrote: > When a request is about to be processed, this commit separates > "delayed controls" (camera-related ones that take "a few frames" to > apply) from "immediate controls" (ISP-related controls) that will > happen instantly. > > The immediate controls are held back until the delayed controls that > they were submitted with, have happened. This means all the controls > submitted in a request happen atomically. > > We therefore already have the sequence number of the request whose > controls have just been applied, so we additionally attach this to the > current request as "ControlId" metadata. > > Signed-off-by: David Plowman <david.plowman@raspberrypi.com> > --- > src/ipa/rpi/common/ipa_base.cpp | 24 ++++++---- > .../pipeline/rpi/common/pipeline_base.cpp | 46 +++++++++++++++++++ > .../pipeline/rpi/common/pipeline_base.h | 8 ++++ > src/libcamera/pipeline/rpi/pisp/pisp.cpp | 3 ++ > src/libcamera/pipeline/rpi/vc4/vc4.cpp | 3 ++ > 5 files changed, 76 insertions(+), 8 deletions(-) > > diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp > index faa77719..dacafa57 100644 > --- a/src/ipa/rpi/common/ipa_base.cpp > +++ b/src/ipa/rpi/common/ipa_base.cpp > @@ -431,6 +431,15 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms) > rpiMetadata.clear(); > fillDeviceStatus(params.sensorControls, ipaContext); > > + /* > + * When there are controls, it's important that we don't skip running the > + * IPAs, as that can mess with synchronisation. Crucially though, we need > + * to know whether there were controls when this comes back as the > + * _delayed_ metadata, hence why we flag this in the metadata itself. > + */ > + if (!params.requestControls.empty()) > + rpiMetadata.set("ipa.request_controls", true); > + > if (params.buffers.embedded) { > /* > * Pipeline handler has supplied us with an embedded data buffer, > @@ -451,7 +460,7 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms) > */ > AgcStatus agcStatus; > bool hdrChange = false; > - RPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext]; > + RPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext % rpiMetadata_.size()]; > if (!delayedMetadata.get<AgcStatus>("agc.status", agcStatus)) { > rpiMetadata.set("agc.delayed_status", agcStatus); > hdrChange = agcStatus.hdr.mode != hdrStatus_.mode; > @@ -464,9 +473,13 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms) > */ > helper_->prepare(embeddedBuffer, rpiMetadata); > > + bool delayedRequestControls = false; > + delayedMetadata.get<bool>("ipa.request_controls", delayedRequestControls); > + > /* Allow a 10% margin on the comparison below. */ > Duration delta = (frameTimestamp - lastRunTimestamp_) * 1.0ns; > - if (lastRunTimestamp_ && frameCount_ > invalidCount_ && > + if (!delayedRequestControls && params.requestControls.empty() && > + lastRunTimestamp_ && frameCount_ > invalidCount_ && > delta < controllerMinFrameDuration_ * 0.9 && !hdrChange) { > /* > * Ensure we merge the previous frame's metadata with the current > @@ -535,7 +548,7 @@ void IpaBase::processStats(const ProcessParams ¶ms) > ControlList ctrls(sensorCtrls_); > applyAGC(&agcStatus, ctrls); > rpiMetadata.set("agc.status", agcStatus); > - setDelayedControls.emit(ctrls, ipaContext); > + setDelayedControls.emit(ctrls, params.ipaContext); > setCameraTimeoutValue(); > } > > @@ -951,8 +964,6 @@ void IpaBase::applyControls(const ControlList &controls) > > /* The control provides units of microseconds. */ > agc->setFixedExposureTime(0, ctrl.second.get<int32_t>() * 1.0us); > - > - libcameraMetadata_.set(controls::ExposureTime, ctrl.second.get<int32_t>()); > break; > } > > @@ -976,9 +987,6 @@ void IpaBase::applyControls(const ControlList &controls) > break; > > agc->setFixedGain(0, ctrl.second.get<float>()); > - > - libcameraMetadata_.set(controls::AnalogueGain, > - ctrl.second.get<float>()); > break; > } > > diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp > index f6ef8a3b..28f44753 100644 > --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp > +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp > @@ -1528,4 +1528,50 @@ void CameraData::fillRequestMetadata(const ControlList &bufferControls, Request > } > } > > +static bool isControlDelayed(unsigned int id) > +{ > + return id == controls::ExposureTime || > + id == controls::AnalogueGain || > + id == controls::FrameDurationLimits || Fine with these that match a V4L2 control > + id == controls::AeEnable || > + id == controls::ExposureTimeMode || > + id == controls::AnalogueGainMode; But aren't these knobs that control the IPA behaviour rather than controls for the HW (which take a delay to apply) ? > +} > + > +void CameraData::handleControlLists(uint32_t delayContext) > +{ > + /* > + * The delayContext is the sequence number after it's gone through the various > + * pipeline delays, so that's what gets reported as the "ControlListSequence" > + * in the metadata, being the sequence number of the request whose ControlList > + * has just been applied. > + */ > + Request *request = requestQueue_.front(); > + request->_d()->metadata().set(controls::rpi::ControlListSequence, delayContext); > + > + /* > + * Controls that take effect immediately (typically ISP controls) have to be > + * delayed so as to synchronise with those controls that do get delayed. So we > + * must remove them from the current request, and push them onto a queue so > + * that they can be used later. > + */ > + ControlList controls = std::move(request->controls()); > + request->controls().clear(); I'm fine with the control list sequence number being reported through controls::rpi::ControlListSequence. I'm less at ease with the idea we're re-constructing the ControlList inside a Request. Any application that holds a reference to any of those controls will fail if we're messing up the storage where those controls live. I recall you said you don't really care about the controls of a completed request as there's nothing interesting there. Does that mean you would be fine simply reporting ControlListSequence and not reworking the ControlList inside a Request ? > + immediateControls_.push({ request->sequence(), {} }); > + for (const auto &ctrl : controls) { > + if (isControlDelayed(ctrl.first)) > + request->controls().set(ctrl.first, ctrl.second); > + else > + immediateControls_.back().controls.set(ctrl.first, ctrl.second); > + } > + > + /* "Immediate" controls that have become due are now merged back into this request. */ > + while (!immediateControls_.empty() && > + immediateControls_.front().controlListId <= delayContext) { > + request->controls().merge(immediateControls_.front().controls, > + ControlList::MergePolicy::OverwriteExisting); > + immediateControls_.pop(); > + } > +} > + > } /* namespace libcamera */ > diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h > index 7bfac33e..d3f044a4 100644 > --- a/src/libcamera/pipeline/rpi/common/pipeline_base.h > +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h > @@ -180,10 +180,18 @@ public: > > ClockRecovery wallClockRecovery_; > > + struct ImmediateControlsEntry { > + uint64_t controlListId; > + ControlList controls; > + }; > + std::queue<ImmediateControlsEntry> immediateControls_; > + > protected: > void fillRequestMetadata(const ControlList &bufferControls, > Request *request); > > + void handleControlLists(uint32_t delayContext); > + > virtual void tryRunPipeline() = 0; > > private: > diff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp > index c7799f2b..70bbac5a 100644 > --- a/src/libcamera/pipeline/rpi/pisp/pisp.cpp > +++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp > @@ -2322,6 +2322,9 @@ void PiSPCameraData::tryRunPipeline() > > fillRequestMetadata(job.sensorControls, request); > > + /* This sorts out synchronisation with ControlLists in earlier requests. */ > + handleControlLists(job.delayContext); > + > /* Set our state to say the pipeline is active. */ > state_ = State::Busy; > > diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp > index f99cfdbc..a1b44af1 100644 > --- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp > +++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp > @@ -939,6 +939,9 @@ void Vc4CameraData::tryRunPipeline() > > fillRequestMetadata(bayerFrame.controls, request); > > + /* This sorts out synchronisation with ControlLists in earlier requests. */ > + handleControlLists(bayerFrame.delayContext); > + > /* Set our state to say the pipeline is active. */ > state_ = State::Busy; > > -- > 2.47.3 >
Hi Jacopo Thanks for looking at this again. On Thu, 7 May 2026 at 08:42, Jacopo Mondi <jacopo.mondi@ideasonboard.com> wrote: > > Hi David > > On Tue, Apr 28, 2026 at 02:26:39PM +0100, David Plowman wrote: > > When a request is about to be processed, this commit separates > > "delayed controls" (camera-related ones that take "a few frames" to > > apply) from "immediate controls" (ISP-related controls) that will > > happen instantly. > > > > The immediate controls are held back until the delayed controls that > > they were submitted with, have happened. This means all the controls > > submitted in a request happen atomically. > > > > We therefore already have the sequence number of the request whose > > controls have just been applied, so we additionally attach this to the > > current request as "ControlId" metadata. > > > > Signed-off-by: David Plowman <david.plowman@raspberrypi.com> > > --- > > src/ipa/rpi/common/ipa_base.cpp | 24 ++++++---- > > .../pipeline/rpi/common/pipeline_base.cpp | 46 +++++++++++++++++++ > > .../pipeline/rpi/common/pipeline_base.h | 8 ++++ > > src/libcamera/pipeline/rpi/pisp/pisp.cpp | 3 ++ > > src/libcamera/pipeline/rpi/vc4/vc4.cpp | 3 ++ > > 5 files changed, 76 insertions(+), 8 deletions(-) > > > > diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp > > index faa77719..dacafa57 100644 > > --- a/src/ipa/rpi/common/ipa_base.cpp > > +++ b/src/ipa/rpi/common/ipa_base.cpp > > @@ -431,6 +431,15 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms) > > rpiMetadata.clear(); > > fillDeviceStatus(params.sensorControls, ipaContext); > > > > + /* > > + * When there are controls, it's important that we don't skip running the > > + * IPAs, as that can mess with synchronisation. Crucially though, we need > > + * to know whether there were controls when this comes back as the > > + * _delayed_ metadata, hence why we flag this in the metadata itself. > > + */ > > + if (!params.requestControls.empty()) > > + rpiMetadata.set("ipa.request_controls", true); > > + > > if (params.buffers.embedded) { > > /* > > * Pipeline handler has supplied us with an embedded data buffer, > > @@ -451,7 +460,7 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms) > > */ > > AgcStatus agcStatus; > > bool hdrChange = false; > > - RPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext]; > > + RPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext % rpiMetadata_.size()]; > > if (!delayedMetadata.get<AgcStatus>("agc.status", agcStatus)) { > > rpiMetadata.set("agc.delayed_status", agcStatus); > > hdrChange = agcStatus.hdr.mode != hdrStatus_.mode; > > @@ -464,9 +473,13 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms) > > */ > > helper_->prepare(embeddedBuffer, rpiMetadata); > > > > + bool delayedRequestControls = false; > > + delayedMetadata.get<bool>("ipa.request_controls", delayedRequestControls); > > + > > /* Allow a 10% margin on the comparison below. */ > > Duration delta = (frameTimestamp - lastRunTimestamp_) * 1.0ns; > > - if (lastRunTimestamp_ && frameCount_ > invalidCount_ && > > + if (!delayedRequestControls && params.requestControls.empty() && > > + lastRunTimestamp_ && frameCount_ > invalidCount_ && > > delta < controllerMinFrameDuration_ * 0.9 && !hdrChange) { > > /* > > * Ensure we merge the previous frame's metadata with the current > > @@ -535,7 +548,7 @@ void IpaBase::processStats(const ProcessParams ¶ms) > > ControlList ctrls(sensorCtrls_); > > applyAGC(&agcStatus, ctrls); > > rpiMetadata.set("agc.status", agcStatus); > > - setDelayedControls.emit(ctrls, ipaContext); > > + setDelayedControls.emit(ctrls, params.ipaContext); > > setCameraTimeoutValue(); > > } > > > > @@ -951,8 +964,6 @@ void IpaBase::applyControls(const ControlList &controls) > > > > /* The control provides units of microseconds. */ > > agc->setFixedExposureTime(0, ctrl.second.get<int32_t>() * 1.0us); > > - > > - libcameraMetadata_.set(controls::ExposureTime, ctrl.second.get<int32_t>()); > > break; > > } > > > > @@ -976,9 +987,6 @@ void IpaBase::applyControls(const ControlList &controls) > > break; > > > > agc->setFixedGain(0, ctrl.second.get<float>()); > > - > > - libcameraMetadata_.set(controls::AnalogueGain, > > - ctrl.second.get<float>()); > > break; > > } > > > > diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp > > index f6ef8a3b..28f44753 100644 > > --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp > > +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp > > @@ -1528,4 +1528,50 @@ void CameraData::fillRequestMetadata(const ControlList &bufferControls, Request > > } > > } > > > > +static bool isControlDelayed(unsigned int id) > > +{ > > + return id == controls::ExposureTime || > > + id == controls::AnalogueGain || > > + id == controls::FrameDurationLimits || > > Fine with these that match a V4L2 control > > > + id == controls::AeEnable || > > + id == controls::ExposureTimeMode || > > + id == controls::AnalogueGainMode; > > But aren't these knobs that control the IPA behaviour rather than > controls for the HW (which take a delay to apply) ? That's true, but on the Pi, changing exposure or analogue gain values, even in manual mode, is always mediated by the AEC/AGC algorithm. So if someone sets (for example) ExposureTimeMode to Manual, and sends a shutter time, then we have to put the algorithm into the correct mode at the same moment, or the update will just get ignored. I guess you could ask what state the algorithm is really in when it's done something, but which hasn't happened yet. But I'll leave that question for Schrodinger and his cat! > > > +} > > + > > +void CameraData::handleControlLists(uint32_t delayContext) > > +{ > > + /* > > + * The delayContext is the sequence number after it's gone through the various > > + * pipeline delays, so that's what gets reported as the "ControlListSequence" > > + * in the metadata, being the sequence number of the request whose ControlList > > + * has just been applied. > > + */ > > + Request *request = requestQueue_.front(); > > + request->_d()->metadata().set(controls::rpi::ControlListSequence, delayContext); > > + > > + /* > > + * Controls that take effect immediately (typically ISP controls) have to be > > + * delayed so as to synchronise with those controls that do get delayed. So we > > + * must remove them from the current request, and push them onto a queue so > > + * that they can be used later. > > + */ > > + ControlList controls = std::move(request->controls()); > > + request->controls().clear(); > > I'm fine with the control list sequence number being reported through > controls::rpi::ControlListSequence. > > I'm less at ease with the idea we're re-constructing the ControlList > inside a Request. Any application that holds a reference to any of > those controls will fail if we're messing up the storage where those > controls live. > > I recall you said you don't really care about the controls of a > completed request as there's nothing interesting there. Does that mean > you would be fine simply reporting ControlListSequence and not > reworking the ControlList inside a Request ? I'll post a v4 to address this in just a moment. Thanks! David > > > > > + immediateControls_.push({ request->sequence(), {} }); > > + for (const auto &ctrl : controls) { > > + if (isControlDelayed(ctrl.first)) > > + request->controls().set(ctrl.first, ctrl.second); > > + else > > + immediateControls_.back().controls.set(ctrl.first, ctrl.second); > > + } > > + > > + /* "Immediate" controls that have become due are now merged back into this request. */ > > + while (!immediateControls_.empty() && > > + immediateControls_.front().controlListId <= delayContext) { > > + request->controls().merge(immediateControls_.front().controls, > > + ControlList::MergePolicy::OverwriteExisting); > > + immediateControls_.pop(); > > + } > > +} > > + > > } /* namespace libcamera */ > > diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h > > index 7bfac33e..d3f044a4 100644 > > --- a/src/libcamera/pipeline/rpi/common/pipeline_base.h > > +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h > > @@ -180,10 +180,18 @@ public: > > > > ClockRecovery wallClockRecovery_; > > > > + struct ImmediateControlsEntry { > > + uint64_t controlListId; > > + ControlList controls; > > + }; > > + std::queue<ImmediateControlsEntry> immediateControls_; > > + > > protected: > > void fillRequestMetadata(const ControlList &bufferControls, > > Request *request); > > > > + void handleControlLists(uint32_t delayContext); > > + > > virtual void tryRunPipeline() = 0; > > > > private: > > diff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp > > index c7799f2b..70bbac5a 100644 > > --- a/src/libcamera/pipeline/rpi/pisp/pisp.cpp > > +++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp > > @@ -2322,6 +2322,9 @@ void PiSPCameraData::tryRunPipeline() > > > > fillRequestMetadata(job.sensorControls, request); > > > > + /* This sorts out synchronisation with ControlLists in earlier requests. */ > > + handleControlLists(job.delayContext); > > + > > /* Set our state to say the pipeline is active. */ > > state_ = State::Busy; > > > > diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp > > index f99cfdbc..a1b44af1 100644 > > --- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp > > +++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp > > @@ -939,6 +939,9 @@ void Vc4CameraData::tryRunPipeline() > > > > fillRequestMetadata(bayerFrame.controls, request); > > > > + /* This sorts out synchronisation with ControlLists in earlier requests. */ > > + handleControlLists(bayerFrame.delayContext); > > + > > /* Set our state to say the pipeline is active. */ > > state_ = State::Busy; > > > > -- > > 2.47.3 > >
diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp index faa77719..dacafa57 100644 --- a/src/ipa/rpi/common/ipa_base.cpp +++ b/src/ipa/rpi/common/ipa_base.cpp @@ -431,6 +431,15 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms) rpiMetadata.clear(); fillDeviceStatus(params.sensorControls, ipaContext); + /* + * When there are controls, it's important that we don't skip running the + * IPAs, as that can mess with synchronisation. Crucially though, we need + * to know whether there were controls when this comes back as the + * _delayed_ metadata, hence why we flag this in the metadata itself. + */ + if (!params.requestControls.empty()) + rpiMetadata.set("ipa.request_controls", true); + if (params.buffers.embedded) { /* * Pipeline handler has supplied us with an embedded data buffer, @@ -451,7 +460,7 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms) */ AgcStatus agcStatus; bool hdrChange = false; - RPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext]; + RPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext % rpiMetadata_.size()]; if (!delayedMetadata.get<AgcStatus>("agc.status", agcStatus)) { rpiMetadata.set("agc.delayed_status", agcStatus); hdrChange = agcStatus.hdr.mode != hdrStatus_.mode; @@ -464,9 +473,13 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms) */ helper_->prepare(embeddedBuffer, rpiMetadata); + bool delayedRequestControls = false; + delayedMetadata.get<bool>("ipa.request_controls", delayedRequestControls); + /* Allow a 10% margin on the comparison below. */ Duration delta = (frameTimestamp - lastRunTimestamp_) * 1.0ns; - if (lastRunTimestamp_ && frameCount_ > invalidCount_ && + if (!delayedRequestControls && params.requestControls.empty() && + lastRunTimestamp_ && frameCount_ > invalidCount_ && delta < controllerMinFrameDuration_ * 0.9 && !hdrChange) { /* * Ensure we merge the previous frame's metadata with the current @@ -535,7 +548,7 @@ void IpaBase::processStats(const ProcessParams ¶ms) ControlList ctrls(sensorCtrls_); applyAGC(&agcStatus, ctrls); rpiMetadata.set("agc.status", agcStatus); - setDelayedControls.emit(ctrls, ipaContext); + setDelayedControls.emit(ctrls, params.ipaContext); setCameraTimeoutValue(); } @@ -951,8 +964,6 @@ void IpaBase::applyControls(const ControlList &controls) /* The control provides units of microseconds. */ agc->setFixedExposureTime(0, ctrl.second.get<int32_t>() * 1.0us); - - libcameraMetadata_.set(controls::ExposureTime, ctrl.second.get<int32_t>()); break; } @@ -976,9 +987,6 @@ void IpaBase::applyControls(const ControlList &controls) break; agc->setFixedGain(0, ctrl.second.get<float>()); - - libcameraMetadata_.set(controls::AnalogueGain, - ctrl.second.get<float>()); break; } diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp index f6ef8a3b..28f44753 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -1528,4 +1528,50 @@ void CameraData::fillRequestMetadata(const ControlList &bufferControls, Request } } +static bool isControlDelayed(unsigned int id) +{ + return id == controls::ExposureTime || + id == controls::AnalogueGain || + id == controls::FrameDurationLimits || + id == controls::AeEnable || + id == controls::ExposureTimeMode || + id == controls::AnalogueGainMode; +} + +void CameraData::handleControlLists(uint32_t delayContext) +{ + /* + * The delayContext is the sequence number after it's gone through the various + * pipeline delays, so that's what gets reported as the "ControlListSequence" + * in the metadata, being the sequence number of the request whose ControlList + * has just been applied. + */ + Request *request = requestQueue_.front(); + request->_d()->metadata().set(controls::rpi::ControlListSequence, delayContext); + + /* + * Controls that take effect immediately (typically ISP controls) have to be + * delayed so as to synchronise with those controls that do get delayed. So we + * must remove them from the current request, and push them onto a queue so + * that they can be used later. + */ + ControlList controls = std::move(request->controls()); + request->controls().clear(); + immediateControls_.push({ request->sequence(), {} }); + for (const auto &ctrl : controls) { + if (isControlDelayed(ctrl.first)) + request->controls().set(ctrl.first, ctrl.second); + else + immediateControls_.back().controls.set(ctrl.first, ctrl.second); + } + + /* "Immediate" controls that have become due are now merged back into this request. */ + while (!immediateControls_.empty() && + immediateControls_.front().controlListId <= delayContext) { + request->controls().merge(immediateControls_.front().controls, + ControlList::MergePolicy::OverwriteExisting); + immediateControls_.pop(); + } +} + } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h index 7bfac33e..d3f044a4 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.h +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h @@ -180,10 +180,18 @@ public: ClockRecovery wallClockRecovery_; + struct ImmediateControlsEntry { + uint64_t controlListId; + ControlList controls; + }; + std::queue<ImmediateControlsEntry> immediateControls_; + protected: void fillRequestMetadata(const ControlList &bufferControls, Request *request); + void handleControlLists(uint32_t delayContext); + virtual void tryRunPipeline() = 0; private: diff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp index c7799f2b..70bbac5a 100644 --- a/src/libcamera/pipeline/rpi/pisp/pisp.cpp +++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp @@ -2322,6 +2322,9 @@ void PiSPCameraData::tryRunPipeline() fillRequestMetadata(job.sensorControls, request); + /* This sorts out synchronisation with ControlLists in earlier requests. */ + handleControlLists(job.delayContext); + /* Set our state to say the pipeline is active. */ state_ = State::Busy; diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp index f99cfdbc..a1b44af1 100644 --- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp +++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp @@ -939,6 +939,9 @@ void Vc4CameraData::tryRunPipeline() fillRequestMetadata(bayerFrame.controls, request); + /* This sorts out synchronisation with ControlLists in earlier requests. */ + handleControlLists(bayerFrame.delayContext); + /* Set our state to say the pipeline is active. */ state_ = State::Busy;
When a request is about to be processed, this commit separates "delayed controls" (camera-related ones that take "a few frames" to apply) from "immediate controls" (ISP-related controls) that will happen instantly. The immediate controls are held back until the delayed controls that they were submitted with, have happened. This means all the controls submitted in a request happen atomically. We therefore already have the sequence number of the request whose controls have just been applied, so we additionally attach this to the current request as "ControlId" metadata. Signed-off-by: David Plowman <david.plowman@raspberrypi.com> --- src/ipa/rpi/common/ipa_base.cpp | 24 ++++++---- .../pipeline/rpi/common/pipeline_base.cpp | 46 +++++++++++++++++++ .../pipeline/rpi/common/pipeline_base.h | 8 ++++ src/libcamera/pipeline/rpi/pisp/pisp.cpp | 3 ++ src/libcamera/pipeline/rpi/vc4/vc4.cpp | 3 ++ 5 files changed, 76 insertions(+), 8 deletions(-)