[RFC,v2,1/1] libcamera: Add independent queue for ControlLists
diff mbox series

Message ID 20260312160009.18654-2-david.plowman@raspberrypi.com
State New
Headers show
Series
  • Add queueControls mechanism
Related show

Commit Message

David Plowman March 12, 2026, 3:10 p.m. UTC
Add `Camera::queueControls()` whose purpose is to apply controls as
soon as possible, without going through `Request::controls()`.

A new virtual function `PipelineHandler::queueControlsDevice()` is
provided for pipeline handler to implement fast-tracked application of
controls. If the pipeline handler does not implement that
functionality, or it fails, then a fallback mechanism is used. The
controls will be saved for later, and they will be merged into the
control list of the next available request sent to the pipeline
handler (`Camera::Private::waitingRequests_`).

This patch is derived directly from Barnabas's previous verion that
implemented the same idea but with a single ControlList, rather than
allowing multiple ControlLists to be queued up for consecutive
frames.

Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
---
 include/libcamera/camera.h                    |  1 +
 include/libcamera/internal/camera.h           |  1 +
 include/libcamera/internal/pipeline_handler.h |  7 ++
 src/libcamera/camera.cpp                      | 61 +++++++++++++++
 src/libcamera/pipeline_handler.cpp            | 76 +++++++++++++++++++
 5 files changed, 146 insertions(+)

Patch
diff mbox series

diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h
index b24a2974..93a484e4 100644
--- a/include/libcamera/camera.h
+++ b/include/libcamera/camera.h
@@ -147,6 +147,7 @@  public:
 
 	std::unique_ptr<Request> createRequest(uint64_t cookie = 0);
 	int queueRequest(Request *request);
+	int queueControls(ControlList &&controls);
 
 	int start(const ControlList *controls = nullptr);
 	int stop();
diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h
index 8a2e9ed5..17dda925 100644
--- a/include/libcamera/internal/camera.h
+++ b/include/libcamera/internal/camera.h
@@ -38,6 +38,7 @@  public:
 
 	std::list<Request *> queuedRequests_;
 	std::queue<Request *> waitingRequests_;
+	std::queue<ControlList> queuedControls_;
 	ControlInfoMap controlInfo_;
 	ControlList properties_;
 
diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h
index b4f97477..c25213de 100644
--- a/include/libcamera/internal/pipeline_handler.h
+++ b/include/libcamera/internal/pipeline_handler.h
@@ -57,6 +57,7 @@  public:
 
 	void registerRequest(Request *request);
 	void queueRequest(Request *request);
+	int queueControls(Camera *camera, ControlList controls);
 
 	bool completeBuffer(Request *request, FrameBuffer *buffer);
 	void completeRequest(Request *request);
@@ -76,6 +77,12 @@  protected:
 	unsigned int useCount() const { return useCount_; }
 
 	virtual int queueRequestDevice(Camera *camera, Request *request) = 0;
+
+	virtual int queueControlsDevice([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList &controls)
+	{
+		return -EOPNOTSUPP;
+	}
+
 	virtual void stopDevice(Camera *camera) = 0;
 
 	virtual bool acquireDevice(Camera *camera);
diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp
index f724a1be..f0244707 100644
--- a/src/libcamera/camera.cpp
+++ b/src/libcamera/camera.cpp
@@ -637,6 +637,16 @@  Camera::Private::~Private()
  * queued requests was reached.
  */
 
+/**
+ * \var Camera::Private::queuedControls_
+ * \brief The queue of pending control lists
+ *
+ * This queue maintains a list of all the control lists that need to be sent
+ * to the pipeline handler with subsequent requests. The top item in the queue
+ * will always be sent with the next request going to
+ * PipelineHandler::queueRequestDevice().
+ */
+
 /**
  * \var Camera::Private::controlInfo_
  * \brief The set of controls supported by the camera
@@ -1378,6 +1388,57 @@  int Camera::queueRequest(Request *request)
 	return 0;
 }
 
+/**
+ * \brief Queue controls to be applied as soon as possible
+ * \param[in] controls The list of controls to queue
+ *
+ * This function tries to ensure that the controls in \a controls are applied
+ * to the camera as soon as possible. If there are still pending controls waiting
+ * to be applied (because of previous calls to Camera::queueControls), then
+ * these controls will be applied as soon as possible on a frame after those.
+ *
+ * The exact guarantees are camera dependent, but it is guaranteed that the
+ * controls will be applied no later than with the next \ref Request"request"
+ * that the application \ref Camera::queueRequest() "queues" (after any requests
+ * have been *used up" for sending previously queued controls).
+ *
+ * \context This function is \threadsafe. It may only be called when the camera
+ * is in the Running state as defined in \ref camera_operation.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -ENODEV The camera has been disconnected from the system
+ * \retval -EACCES The camera is not running
+ */
+int Camera::queueControls(ControlList &&controls)
+{
+	Private *const d = _d();
+
+	/*
+	 * Like requests, controls can't be queued if the camera is not running.
+	 * Controls can be applied immediately when the camera starts using the
+	 * Camera::Start method.
+	 */
+
+	int ret = d->isAccessAllowed(Private::CameraRunning);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * We want to be able to queue empty control lists, as this gives a way of
+	 * forcing another frame with the same controls as last time, before queueing
+	 * another control list that might change them again.
+	 */
+
+	patchControlList(controls);
+
+	/*
+	 * \todo Or `ConnectionTypeBlocking` to get the return value?
+	 */
+	d->pipe_->invokeMethod(&PipelineHandler::queueControls, ConnectionTypeQueued, this, std::move(controls));
+
+	return 0;
+}
+
 /**
  * \brief Start capture from camera
  * \param[in] controls Controls to be applied before starting the Camera
diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp
index 5c469e5b..1a87b28c 100644
--- a/src/libcamera/pipeline_handler.cpp
+++ b/src/libcamera/pipeline_handler.cpp
@@ -398,6 +398,12 @@  void PipelineHandler::stop(Camera *camera)
 	ASSERT(data->queuedRequests_.empty());
 	ASSERT(data->waitingRequests_.empty());
 
+	/*
+	 * Clear out any unapplied controls. If an application wants to be
+	 * sure controls have been applied, it should wait before stopping.
+	 */
+	data->queuedControls_ = {};
+
 	data->requestSequence_ = 0;
 }
 
@@ -477,6 +483,49 @@  void PipelineHandler::queueRequest(Request *request)
 	request->_d()->prepare(300ms);
 }
 
+/**
+ * \brief Queue controls to apply as soon as possible
+ * \param[in] camera The camera
+ * \param[in] controls The controls to apply
+ *
+ * This function tries to queue \a controls immediately to the device by
+ * calling queueControlsDevice(). If that fails, then a fallback mechanism
+ * is used to ensure that \a controls will be merged into the control list
+ * of the next available request submitted to the pipeline handler.
+ *
+ * \context This function is called from the CameraManager thread.
+ */
+int PipelineHandler::queueControls(Camera *camera, ControlList controls)
+{
+	Camera::Private *data = camera->_d();
+	int ret = queueControlsDevice(camera, controls);
+
+	/*
+	 * Don't worry about later request's controls overriding the ones
+	 * sent here - the application needs to deal with that.
+	 */
+
+	if (ret == -EOPNOTSUPP) {
+		/*
+		 * Fall back to adding the controls to the next request that enters the
+		 * pipeline handler. See PipelineHandler::doQueueRequest().
+		 */
+		data->queuedControls_.push(std::move(controls));
+
+		/* Counts as "success". */
+		ret = 0;
+
+	} else if (ret < 0) {
+		/*
+		 * The pipeline handler is claiming to support queueControlsDevice,
+		 * but it has failed. This is an error.
+		 */
+		LOG(Pipeline, Debug) << "Fast tracking controls failed: " << res;
+	}
+
+	return ret;
+}
+
 /**
  * \brief Queue one requests to the device
  */
@@ -495,9 +544,21 @@  void PipelineHandler::doQueueRequest(Request *request)
 		return;
 	}
 
+	if (!data->queuedControls_.empty()) {
+		/*
+		 * Note that `ControlList::MergePolicy::KeepExisting` is used. This is
+		 * needed to ensure that if `request` is newer than pendingControls_,
+		 * then its controls take precedence.
+		 */
+		request->controls().merge(data->queuedControls_.front(),
+					  ControlList::MergePolicy::KeepExisting);
+	}
+
 	int ret = queueRequestDevice(camera, request);
 	if (ret)
 		cancelRequest(request);
+	else if (!data->queuedControls_.empty())
+		data->queuedControls_.pop();
 }
 
 /**
@@ -543,6 +604,21 @@  void PipelineHandler::doQueueRequests(Camera *camera)
  * \return 0 on success or a negative error code otherwise
  */
 
+/**
+ * \fn PipelineHandler::queueControlsDevice()
+ * \brief Queue controls to be applied as soon as possible
+ * \param[in] camera The camera
+ * \param[in] controls The controls to apply
+ *
+ * This function queues \a controls to \a camera so that they can be
+ * applied as soon as possible
+ *
+ * \context This function is called from the CameraManager thread.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \return -EOPNOTSUPP if fast-tracking controls is not supported
+ */
+
 /**
  * \brief Complete a buffer for a request
  * \param[in] request The request the buffer belongs to