{"id":26278,"url":"https://patchwork.libcamera.org/api/1.1/patches/26278/?format=json","web_url":"https://patchwork.libcamera.org/patch/26278/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260312160009.18654-2-david.plowman@raspberrypi.com>","date":"2026-03-12T15:10:38","name":"[RFC,v2,1/1] libcamera: Add independent queue for ControlLists","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"9c994a133c845d97b47533da3b42ab307dd20ef2","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/1.1/people/42/?format=json","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/26278/mbox/","series":[{"id":5827,"url":"https://patchwork.libcamera.org/api/1.1/series/5827/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5827","date":"2026-03-12T15:10:37","name":"Add queueControls mechanism","version":2,"mbox":"https://patchwork.libcamera.org/series/5827/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/26278/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/26278/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id AC224C32B5\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 12 Mar 2026 16:00:19 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5787462663;\n\tThu, 12 Mar 2026 17:00:19 +0100 (CET)","from mail-wm1-x333.google.com (mail-wm1-x333.google.com\n\t[IPv6:2a00:1450:4864:20::333])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id BF8B86262E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 12 Mar 2026 17:00:16 +0100 (CET)","by mail-wm1-x333.google.com with SMTP id\n\t5b1f17b1804b1-48334ee0aeaso11220835e9.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 12 Mar 2026 09:00:16 -0700 (PDT)","from davidp-pi5.pitowers.org\n\t([2a00:1098:3142:1f:88ea:c658:5b20:5e46])\n\tby smtp.gmail.com with ESMTPSA id\n\tffacd0b85a97d-439fe1abf84sm9084111f8f.14.2026.03.12.09.00.14\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tThu, 12 Mar 2026 09:00:14 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"Xr8J3aGe\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1773331216; x=1773936016;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=c1qwLKbT2jFaV25UIuR5ZCzbWFQsDZVYMtj/3BK5ZBc=;\n\tb=Xr8J3aGeS8j5Pw0yKH9zCAYx3Yj0WHc+nhBArd7RcyaqqjfWekBcEUcfuyIOR9ZsBH\n\t6PNLMoaMafLTIOh2q3mI+ZncGmZTUSp6frJ7eMkaHJBHoHSGNVek9pdwRYJY5X39Tq3O\n\t+igqbiYaYOAXVEyVnHlqqVVr4UxAAvIVxizzDKqW/uHydMRwiTtuPdoVO8pIoxk+HU9d\n\tHOx5OcpnvutSd6ECo5TR45h41zZC+Qec08oiE/1X+a6Aq+hhQ3NlzZm6ld7TbC5TOFdO\n\ttpfQXaIAcJXxmRkLp2S7VbGGlnW6U1Tmqbv017y9qt00SxcXuXMN7FTSMHxHbWkh3g57\n\t6jag==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1773331216; x=1773936016;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n\t:to:cc:subject:date:message-id:reply-to;\n\tbh=c1qwLKbT2jFaV25UIuR5ZCzbWFQsDZVYMtj/3BK5ZBc=;\n\tb=iDzbwMOF+WXg6ksx2tx66gSNUyPLdD9DOFExsVQ/bUqJYPIjjSzPmSfhBlL05J+Gci\n\tEsMdoHNHAty3Uv6t0WiH+ibyhkz1PXN2CqiNAxMJ08tt4KcsTnqJCsVAFSducHxVEq0R\n\tqUDVFFM5tbVwb52bqq/WEABz3aF5MBd+vXYXJPU6t8HiJlcefaGOm2tVh9XCABe2Gtvz\n\tMFa7WuMsyavF0J+JJlZCHMjUQuY0wnIg2+QwHJk2Rqf3CsedkhSCJFXGgfook/8vIodZ\n\tcenkummP+SALZoQqCRygMEUke/PTth8Dw5Sn6WaM52iiM4vOn7EXB4Mi5j1VG5Qmo3xx\n\tvPQw==","X-Gm-Message-State":"AOJu0YzbJDmsl6oXr5g2O4Z0kpbEPuJhzXPYqeCzwziT1vaZyvZuIyvT\n\t3IXIgdBPPogRqZNxtfCBvh0o/3xTIBEl9vP4IPWbrO/vwtWYCsAGgJ0pHyz+Gn2oM+igHLbx+cq\n\tH3Mw2kDY=","X-Gm-Gg":"ATEYQzzwXbVUpkVs8KHArpXNLHRyiqfpYkBk4E0lVO7oHFUDu7XuAYVzhFEZ9TpIpGE\n\tlOOrJCW3DPDzUxPypZTApAZtwziWHEdYjUhipBL0Wzcm8vLJQutNxq2HRL4JcraV0mjSrDwFuCg\n\t8OSzFjytXDbjgX3PaPAwjBnWnVUCV4/Y/U6PI7NDl2GuhrOUt4xGmQz6rmly9iH/hT0E9WJ5lPD\n\tKC/7vtCOOteH+TjDZ2M9VqN/Ho89lBTV87Kw3YpRGErwlRl08R6Bf5XuYUCBtub3lHiJoUfsN1R\n\tnEg5c1LhGjsbjkXFPDHSNtLfPsYAVfG6ryvvm0GKN9xNL+O+dGfhOXeTpRRlhyEElqe6f7/Rt1I\n\trrxoul3SiQb4xbom+wu12PfIyVOiW5/fedXZoIWZjezPYfHMS14fa86/b4wONuuHm/EmRkgSFi0\n\tIKVyWR4FNd4oPRF1VLkBwohB+UWCMyeYETd+gQ9cvKrw5Mk3cxMeSPIAXY3lFQbs5jORnDud8cI\n\td9ffDqARbvecFOVYN+oNcOK45tfbxiyNemQUkjxFA==","X-Received":"by 2002:a05:600c:3b9f:b0:480:690e:f14a with SMTP id\n\t5b1f17b1804b1-4854b0d18e3mr122688975e9.14.1773331215489; \n\tThu, 12 Mar 2026 09:00:15 -0700 (PDT)","From":"David Plowman <david.plowman@raspberrypi.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"David Plowman <david.plowman@raspberrypi.com>","Subject":"[RFC PATCH v2 1/1] libcamera: Add independent queue for ControlLists","Date":"Thu, 12 Mar 2026 15:10:38 +0000","Message-ID":"<20260312160009.18654-2-david.plowman@raspberrypi.com>","X-Mailer":"git-send-email 2.47.3","In-Reply-To":"<20260312160009.18654-1-david.plowman@raspberrypi.com>","References":"<20260312160009.18654-1-david.plowman@raspberrypi.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Add `Camera::queueControls()` whose purpose is to apply controls as\nsoon as possible, without going through `Request::controls()`.\n\nA new virtual function `PipelineHandler::queueControlsDevice()` is\nprovided for pipeline handler to implement fast-tracked application of\ncontrols. If the pipeline handler does not implement that\nfunctionality, or it fails, then a fallback mechanism is used. The\ncontrols will be saved for later, and they will be merged into the\ncontrol list of the next available request sent to the pipeline\nhandler (`Camera::Private::waitingRequests_`).\n\nThis patch is derived directly from Barnabas's previous verion that\nimplemented the same idea but with a single ControlList, rather than\nallowing multiple ControlLists to be queued up for consecutive\nframes.\n\nSigned-off-by: David Plowman <david.plowman@raspberrypi.com>\n---\n include/libcamera/camera.h                    |  1 +\n include/libcamera/internal/camera.h           |  1 +\n include/libcamera/internal/pipeline_handler.h |  7 ++\n src/libcamera/camera.cpp                      | 61 +++++++++++++++\n src/libcamera/pipeline_handler.cpp            | 76 +++++++++++++++++++\n 5 files changed, 146 insertions(+)","diff":"diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\nindex b24a2974..93a484e4 100644\n--- a/include/libcamera/camera.h\n+++ b/include/libcamera/camera.h\n@@ -147,6 +147,7 @@ public:\n \n \tstd::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n \tint queueRequest(Request *request);\n+\tint queueControls(ControlList &&controls);\n \n \tint start(const ControlList *controls = nullptr);\n \tint stop();\ndiff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h\nindex 8a2e9ed5..17dda925 100644\n--- a/include/libcamera/internal/camera.h\n+++ b/include/libcamera/internal/camera.h\n@@ -38,6 +38,7 @@ public:\n \n \tstd::list<Request *> queuedRequests_;\n \tstd::queue<Request *> waitingRequests_;\n+\tstd::queue<ControlList> queuedControls_;\n \tControlInfoMap controlInfo_;\n \tControlList properties_;\n \ndiff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h\nindex b4f97477..c25213de 100644\n--- a/include/libcamera/internal/pipeline_handler.h\n+++ b/include/libcamera/internal/pipeline_handler.h\n@@ -57,6 +57,7 @@ public:\n \n \tvoid registerRequest(Request *request);\n \tvoid queueRequest(Request *request);\n+\tint queueControls(Camera *camera, ControlList controls);\n \n \tbool completeBuffer(Request *request, FrameBuffer *buffer);\n \tvoid completeRequest(Request *request);\n@@ -76,6 +77,12 @@ protected:\n \tunsigned int useCount() const { return useCount_; }\n \n \tvirtual int queueRequestDevice(Camera *camera, Request *request) = 0;\n+\n+\tvirtual int queueControlsDevice([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList &controls)\n+\t{\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n \tvirtual void stopDevice(Camera *camera) = 0;\n \n \tvirtual bool acquireDevice(Camera *camera);\ndiff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\nindex f724a1be..f0244707 100644\n--- a/src/libcamera/camera.cpp\n+++ b/src/libcamera/camera.cpp\n@@ -637,6 +637,16 @@ Camera::Private::~Private()\n  * queued requests was reached.\n  */\n \n+/**\n+ * \\var Camera::Private::queuedControls_\n+ * \\brief The queue of pending control lists\n+ *\n+ * This queue maintains a list of all the control lists that need to be sent\n+ * to the pipeline handler with subsequent requests. The top item in the queue\n+ * will always be sent with the next request going to\n+ * PipelineHandler::queueRequestDevice().\n+ */\n+\n /**\n  * \\var Camera::Private::controlInfo_\n  * \\brief The set of controls supported by the camera\n@@ -1378,6 +1388,57 @@ int Camera::queueRequest(Request *request)\n \treturn 0;\n }\n \n+/**\n+ * \\brief Queue controls to be applied as soon as possible\n+ * \\param[in] controls The list of controls to queue\n+ *\n+ * This function tries to ensure that the controls in \\a controls are applied\n+ * to the camera as soon as possible. If there are still pending controls waiting\n+ * to be applied (because of previous calls to Camera::queueControls), then\n+ * these controls will be applied as soon as possible on a frame after those.\n+ *\n+ * The exact guarantees are camera dependent, but it is guaranteed that the\n+ * controls will be applied no later than with the next \\ref Request\"request\"\n+ * that the application \\ref Camera::queueRequest() \"queues\" (after any requests\n+ * have been *used up\" for sending previously queued controls).\n+ *\n+ * \\context This function is \\threadsafe. It may only be called when the camera\n+ * is in the Running state as defined in \\ref camera_operation.\n+ *\n+ * \\return 0 on success or a negative error code otherwise\n+ * \\retval -ENODEV The camera has been disconnected from the system\n+ * \\retval -EACCES The camera is not running\n+ */\n+int Camera::queueControls(ControlList &&controls)\n+{\n+\tPrivate *const d = _d();\n+\n+\t/*\n+\t * Like requests, controls can't be queued if the camera is not running.\n+\t * Controls can be applied immediately when the camera starts using the\n+\t * Camera::Start method.\n+\t */\n+\n+\tint ret = d->isAccessAllowed(Private::CameraRunning);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\t/*\n+\t * We want to be able to queue empty control lists, as this gives a way of\n+\t * forcing another frame with the same controls as last time, before queueing\n+\t * another control list that might change them again.\n+\t */\n+\n+\tpatchControlList(controls);\n+\n+\t/*\n+\t * \\todo Or `ConnectionTypeBlocking` to get the return value?\n+\t */\n+\td->pipe_->invokeMethod(&PipelineHandler::queueControls, ConnectionTypeQueued, this, std::move(controls));\n+\n+\treturn 0;\n+}\n+\n /**\n  * \\brief Start capture from camera\n  * \\param[in] controls Controls to be applied before starting the Camera\ndiff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\nindex 5c469e5b..1a87b28c 100644\n--- a/src/libcamera/pipeline_handler.cpp\n+++ b/src/libcamera/pipeline_handler.cpp\n@@ -398,6 +398,12 @@ void PipelineHandler::stop(Camera *camera)\n \tASSERT(data->queuedRequests_.empty());\n \tASSERT(data->waitingRequests_.empty());\n \n+\t/*\n+\t * Clear out any unapplied controls. If an application wants to be\n+\t * sure controls have been applied, it should wait before stopping.\n+\t */\n+\tdata->queuedControls_ = {};\n+\n \tdata->requestSequence_ = 0;\n }\n \n@@ -477,6 +483,49 @@ void PipelineHandler::queueRequest(Request *request)\n \trequest->_d()->prepare(300ms);\n }\n \n+/**\n+ * \\brief Queue controls to apply as soon as possible\n+ * \\param[in] camera The camera\n+ * \\param[in] controls The controls to apply\n+ *\n+ * This function tries to queue \\a controls immediately to the device by\n+ * calling queueControlsDevice(). If that fails, then a fallback mechanism\n+ * is used to ensure that \\a controls will be merged into the control list\n+ * of the next available request submitted to the pipeline handler.\n+ *\n+ * \\context This function is called from the CameraManager thread.\n+ */\n+int PipelineHandler::queueControls(Camera *camera, ControlList controls)\n+{\n+\tCamera::Private *data = camera->_d();\n+\tint ret = queueControlsDevice(camera, controls);\n+\n+\t/*\n+\t * Don't worry about later request's controls overriding the ones\n+\t * sent here - the application needs to deal with that.\n+\t */\n+\n+\tif (ret == -EOPNOTSUPP) {\n+\t\t/*\n+\t\t * Fall back to adding the controls to the next request that enters the\n+\t\t * pipeline handler. See PipelineHandler::doQueueRequest().\n+\t\t */\n+\t\tdata->queuedControls_.push(std::move(controls));\n+\n+\t\t/* Counts as \"success\". */\n+\t\tret = 0;\n+\n+\t} else if (ret < 0) {\n+\t\t/*\n+\t\t * The pipeline handler is claiming to support queueControlsDevice,\n+\t\t * but it has failed. This is an error.\n+\t\t */\n+\t\tLOG(Pipeline, Debug) << \"Fast tracking controls failed: \" << res;\n+\t}\n+\n+\treturn ret;\n+}\n+\n /**\n  * \\brief Queue one requests to the device\n  */\n@@ -495,9 +544,21 @@ void PipelineHandler::doQueueRequest(Request *request)\n \t\treturn;\n \t}\n \n+\tif (!data->queuedControls_.empty()) {\n+\t\t/*\n+\t\t * Note that `ControlList::MergePolicy::KeepExisting` is used. This is\n+\t\t * needed to ensure that if `request` is newer than pendingControls_,\n+\t\t * then its controls take precedence.\n+\t\t */\n+\t\trequest->controls().merge(data->queuedControls_.front(),\n+\t\t\t\t\t  ControlList::MergePolicy::KeepExisting);\n+\t}\n+\n \tint ret = queueRequestDevice(camera, request);\n \tif (ret)\n \t\tcancelRequest(request);\n+\telse if (!data->queuedControls_.empty())\n+\t\tdata->queuedControls_.pop();\n }\n \n /**\n@@ -543,6 +604,21 @@ void PipelineHandler::doQueueRequests(Camera *camera)\n  * \\return 0 on success or a negative error code otherwise\n  */\n \n+/**\n+ * \\fn PipelineHandler::queueControlsDevice()\n+ * \\brief Queue controls to be applied as soon as possible\n+ * \\param[in] camera The camera\n+ * \\param[in] controls The controls to apply\n+ *\n+ * This function queues \\a controls to \\a camera so that they can be\n+ * applied as soon as possible\n+ *\n+ * \\context This function is called from the CameraManager thread.\n+ *\n+ * \\return 0 on success or a negative error code otherwise\n+ * \\return -EOPNOTSUPP if fast-tracking controls is not supported\n+ */\n+\n /**\n  * \\brief Complete a buffer for a request\n  * \\param[in] request The request the buffer belongs to\n","prefixes":["RFC","v2","1/1"]}