[{"id":35995,"web_url":"https://patchwork.libcamera.org/comment/35995/","msgid":"<CAHW6GYJy=C5zEy+HQiWy3FMUp+EOH=OX37x16RUTWf8ciaBXEw@mail.gmail.com>","date":"2025-09-26T08:46:01","subject":"Re: [RFC PATCH v1 6/7] libcamera: camera: Add `applyControls()`","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/people/42/","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"content":"Hi Barnabas\n\nThanks very much for sending this. I'm very excited to see the\nquestion of controls and how they relate to requests move forward!\n\nOn Wed, 24 Sept 2025 at 13:47, Barnabás Pőcze\n<barnabas.pocze@ideasonboard.com> wrote:\n>\n> Add `Camera::applyControls()` whose purpose is to apply controls as soon\n> as possible, without going through `Request::controls()`.\n>\n> A new virtual function `PipelineHandler::applyControlsDevice()` is provided\n> for pipeline handler to implement fast-tracked application of controls. If\n> the pipeline handler does not implement that functionality, or it fails,\n> then a fallback mechanism is used. The controls will be saved for later,\n> and they will be merged into the control list of the next request sent to\n> the pipeline handler (`Camera::Private::waitingRequests_`).\n\nFirst of all, and I don't know if you're aware of this, at Raspberry\nPi we did quite a lot of work on this question so perhaps it's worth\ncovering that first. We implemented our scheme and have demonstrated\nit on and off over the last few years, most recently at the Nice\nworkshop.\n\nOur implementation didn't present a separate mechanism to submit\ncontrols, instead we still relied on them being passed in the\nrequest. Though internally, we effectively ran a separate queue\ncontaining only the non-empty control lists, so that all the requests\nwithout a control list didn't get in the way.\n\nHaving said that, I like the suggestion of the separate API that you\npresented, and I think it would work well for us so long as it's\nbacked by a queue. Then we'd be able, for example, to queue up\nmultiple different exposure values and have them applied on\nconsecutive frames.\n\nThe other feature of our scheme was that every control list was\nassigned an id number, starting at 1 and then incrementing by 1 with\nevery new control list (not request). This id number would be reported\nback in a completed request, telling you exactly which the most\nrecent control list was that had been applied. It was also valuable\nfor detecting when things went wrong (via skipped or duplicated id\nnumbers).\n\nFinally, those taking part in the Kamaros working group will\nappreciate that the above, with the queue mechanism, is very close to\nwhat's in the Kamaros spec. The principal difference is that Kamaros\nlets you have more than one such queue. For example, you might have\nsome part of your system pushing AGC/AEC type controls into one\nqueue, and possibly keeping it quite full, while another part of the\nsystem responds quickly to user input (perhaps wanting an auto-focus\ncycle).\n\nBest regards\nDavid\n\n>\n> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.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                      |  53 +++++++++\n>  src/libcamera/pipeline_handler.cpp            | 110 +++++++++++++++++-\n>  5 files changed, 171 insertions(+), 1 deletion(-)\n>\n> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> index b24a29740..94f940496 100644\n> --- a/include/libcamera/camera.h\n> +++ b/include/libcamera/camera.h\n> @@ -147,6 +147,7 @@ public:\n>\n>         std::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n>         int queueRequest(Request *request);\n> +       int applyControls(ControlList &&controls);\n>\n>         int start(const ControlList *controls = nullptr);\n>         int stop();\n> diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h\n> index 0f50c14a5..5a77298b3 100644\n> --- a/include/libcamera/internal/camera.h\n> +++ b/include/libcamera/internal/camera.h\n> @@ -38,6 +38,7 @@ public:\n>\n>         std::list<Request *> queuedRequests_;\n>         std::deque<Request *> waitingRequests_;\n> +       ControlList pendingControls_;\n>         ControlInfoMap controlInfo_;\n>         ControlList properties_;\n>\n> diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h\n> index e89d6a33e..16ff54bab 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>         void registerRequest(Request *request);\n>         void queueRequest(Request *request);\n> +       int applyControls(Camera *camera, ControlList &&controls);\n>\n>         bool completeBuffer(Request *request, FrameBuffer *buffer);\n>         void completeRequest(Request *request);\n> @@ -75,6 +76,12 @@ protected:\n>         void hotplugMediaDevice(MediaDevice *media);\n>\n>         virtual int queueRequestDevice(Camera *camera, Request *request) = 0;\n> +\n> +       virtual int applyControlsDevice([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList &controls)\n> +       {\n> +               return -EOPNOTSUPP;\n> +       }\n> +\n>         virtual void stopDevice(Camera *camera) = 0;\n>\n>         virtual bool acquireDevice(Camera *camera);\n> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> index 2e1e146a2..778445e36 100644\n> --- a/src/libcamera/camera.cpp\n> +++ b/src/libcamera/camera.cpp\n> @@ -637,6 +637,14 @@ Camera::Private::~Private()\n>   * queued requests was reached.\n>   */\n>\n> +/**\n> + * \\var Camera::Private::pendingControls_\n> + * \\brief The list of pending controls\n> + *\n> + * This list tracks the controls that need to be applied to the device with the\n> + * next request going to PipelineHandler::queueRequestDevice().\n> + */\n> +\n>  /**\n>   * \\var Camera::Private::controlInfo_\n>   * \\brief The set of controls supported by the camera\n> @@ -1374,6 +1382,51 @@ int Camera::queueRequest(Request *request)\n>         return 0;\n>  }\n>\n> +/**\n> + * \\brief Apply controls immediately\n> + * \\param[in] controls The list of controls to apply\n> + *\n> + * This function tries to ensure that the controls in \\a controls are applied\n> + * to the camera as soon as possible.\n> + *\n> + * The exact guarantees are camera dependent, but it is guaranteed that the\n> + * controls will be applied no later than if they were part of the next \\ref Request \"request\"\n> + * that the application \\ref Camera::queueRequest() \"queues\".\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::applyControls(ControlList &&controls)\n> +{\n> +       Private *const d = _d();\n> +\n> +       /*\n> +        * \\todo What to do if not running? Should it be stored and merged into the \"start\" `ControlList`?\n> +        *       If yes, should that list of pending controls be overwritten/updated when stopped?\n> +        *       And should it be cleared in `Camera::release()`?\n> +        */\n> +\n> +       int ret = d->isAccessAllowed(Private::CameraRunning);\n> +       if (ret < 0)\n> +               return ret;\n> +\n> +       if (controls.empty())\n> +               return 0;\n> +\n> +       patchControlList(controls);\n> +\n> +       /*\n> +        * \\todo Or `ConnectionTypeBlocking` to get the return value?\n> +        */\n> +       d->pipe_->invokeMethod(&PipelineHandler::applyControls, ConnectionTypeQueued, this, std::move(controls));\n> +\n> +       return 0;\n> +}\n> +\n>  /**\n>   * \\brief Start capture from camera\n>   * \\param[in] controls Controls to be applied before starting the Camera\n> diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> index d0d3fbc79..99bb2193f 100644\n> --- a/src/libcamera/pipeline_handler.cpp\n> +++ b/src/libcamera/pipeline_handler.cpp\n> @@ -387,6 +387,12 @@ void PipelineHandler::stop(Camera *camera)\n>         ASSERT(data->queuedRequests_.empty());\n>         ASSERT(data->waitingRequests_.empty());\n>\n> +       /*\n> +        * \\todo Or not? This probably needs discussions regarding\n> +        * the expected state of controls after a stop-start sequence.\n> +        */\n> +       data->pendingControls_.clear();\n> +\n>         data->requestSequence_ = 0;\n>  }\n>\n> @@ -466,6 +472,54 @@ void PipelineHandler::queueRequest(Request *request)\n>         request->_d()->prepare(300ms);\n>  }\n>\n> +/**\n> + * \\brief Apply controls immediately\n> + * \\param[in] camera The camera\n> + * \\param[in] controls The controls to apply\n> + *\n> + * This function tries to apply \\a controls immediately to the device by\n> + * calling applyControlsDevice(). 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 request submitted to the pipeline handler.\n> + *\n> + * This function also ensures that requests in Camera::Private::waitingRequests_\n> + * will not override the fast-tracked controls.\n> + *\n> + * \\context This function is called from the CameraManager thread.\n> + */\n> +int PipelineHandler::applyControls(Camera *camera, ControlList &&controls)\n> +{\n> +       Camera::Private *data = camera->_d();\n> +       int res = applyControlsDevice(camera, controls);\n> +\n> +       /*\n> +        * Prevent later requests from overriding the fast-tracked controls.\n> +        * \\todo This is unfortunately slow.\n> +        */\n> +       for (const auto &[k, v] : controls) {\n> +               for (Request *r : data->waitingRequests_)\n> +                       r->controls().erase(k);\n> +       }\n> +\n> +       if (res < 0) {\n> +               /*\n> +                * \\todo Always fall back or only if EOPNOTSUPP is returned?\n> +                *       Possibly there could be some way for the user to influence\n> +                *       the fallback logic (e.g. `flags` argument for `Camera::applyControls()`).\n> +                */\n> +               if (res != -EOPNOTSUPP)\n> +                       LOG(Pipeline, Debug) << \"Fast tracking controls failed: \" << res;\n> +\n> +               /*\n> +                * Fall back to adding the controls to the next request that enters the\n> +                * pipeline handler. See PipelineHandler::doQueueRequest().\n> +                */\n> +               data->pendingControls_.merge(std::move(controls), ControlList::MergePolicy::OverwriteExisting);\n> +       }\n> +\n> +       return 0;\n> +}\n> +\n>  /**\n>   * \\brief Queue one requests to the device\n>   */\n> @@ -484,9 +538,49 @@ void PipelineHandler::doQueueRequest(Request *request)\n>                 return;\n>         }\n>\n> +       if (!data->pendingControls_.empty()) {\n> +               /*\n> +                * Note that `ControlList::MergePolicy::KeepExisting` is used. This is\n> +                * needed to ensure that if `request` is newer than pendingControls_,\n> +                * then its controls take precedence.\n> +                *\n> +                * For older requests, `PipelineHandler::applyControls()` has already removed\n> +                * the controls that should be overridden.\n> +                *\n> +                * \\todo How to handle (if at all) conflicting controls?\n> +                * \\todo How to handle failure of `queueRequestDevice()`?\n> +                *       After the merge it becomes impossible to retrieve the pending controls\n> +                *       from `request->controls()`. Making a copy in such a hot-path is not ideal.\n> +                */\n> +               request->controls().merge(std::move(data->pendingControls_), ControlList::MergePolicy::KeepExisting);\n> +               data->pendingControls_.clear();\n> +       }\n> +\n>         int ret = queueRequestDevice(camera, request);\n> -       if (ret)\n> +       if (ret) {\n> +               /*\n> +                * \\todo What to do now?\n> +                *\n> +                * The original `pendingControls_` is not easily recoverable.\n> +                *\n> +                * Initially\n> +                *     request->controls() = A u B\n> +                *     pendingControls_ = A' u C\n> +                *\n> +                * then after the above merge\n> +                *     request->controls() = A u B u C\n> +                *     pendingControls_ = A'\n> +                *\n> +                * so recovering `pendingControls_` without a copy of at least\n> +                * `B` or `C` does not appear easy.\n> +                *\n> +                * But sometimes recovering is not even desirable: assume that the controls\n> +                * in `pendingControls_` causes the failure. In that case keeping them around\n> +                * will cause every single subsequent request to fail.\n> +                */\n> +\n>                 cancelRequest(request);\n> +       }\n>  }\n>\n>  /**\n> @@ -532,6 +626,20 @@ void PipelineHandler::doQueueRequests(Camera *camera)\n>   * \\return 0 on success or a negative error code otherwise\n>   */\n>\n> +/**\n> + * \\fn PipelineHandler::applyControlsDevice()\n> + * \\brief Apply controls immediately\n> + * \\param[in] camera The camera\n> + * \\param[in] controls The controls to apply\n> + *\n> + * This function applies \\a controls to \\a camera immediately.\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> --\n> 2.51.0\n>","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 E9BD8C328C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 26 Sep 2025 08:46:17 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6E7996B5F3;\n\tFri, 26 Sep 2025 10:46:16 +0200 (CEST)","from mail-qv1-xf30.google.com (mail-qv1-xf30.google.com\n\t[IPv6:2607:f8b0:4864:20::f30])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D12176936E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 26 Sep 2025 10:46:13 +0200 (CEST)","by mail-qv1-xf30.google.com with SMTP id\n\t6a1803df08f44-7960d69f14bso9931286d6.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 26 Sep 2025 01:46:13 -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=\"hxMwXsTT\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1758876372; x=1759481172;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:cc:to:subject:message-id:date:from\n\t:in-reply-to:references:mime-version:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=vJndWE6MM81F9sZZT/7OPS7C0JapRe2bOZdkm519khA=;\n\tb=hxMwXsTTgOErgJv8TCehNVG2tixn64Lx9XmKhRUgqbjl0h+SLYoszNgaaSXDIIylnq\n\tlWwBPpINaXtDWpDUezLMHhulY0LFgBNN35KO2r3wzd8AuHkbE95pLF5ThFqhixDgb6zm\n\t6CM+dzkYcpyZIBqdFX/a1MrVZeepTIry9OeUtt6BYVOMr1tWPnfV3zd2P14m+s/oNr3v\n\tFRaIGtnuLY4dbadfrkIv+sVe52mh4FUo1AmwiLZCwGB8Z91r54N1r++93RkNYRXUgQx6\n\tj0xCxCFK5ZsTyNzTvr6n+5Qdp2DxwEWuIXFwKLORHZMZbwjs9DhjCnkqlKsIqUh7A9ql\n\t5k0A==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1758876372; x=1759481172;\n\th=content-transfer-encoding:cc:to:subject:message-id:date:from\n\t:in-reply-to:references:mime-version:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=vJndWE6MM81F9sZZT/7OPS7C0JapRe2bOZdkm519khA=;\n\tb=JKJpZFWAnt+9twgvxI0fEmL6Vb0U6tfoTBoNyaagv/xddxLXgTCAIeyqeZ0msOiB6L\n\tSHHkz0fEFQgGoLnufW7Vz8YfzFpJsBFJIBBFSKKaIB0p2jktZsKMTE1/MATHEJt8j1pH\n\tGNET5vKPe8jG5Dyu/dNJ5Sng5KxJinqMhzRqa3uMKfa4SKCX/tXRKmb9+Y2I2Gkoy0rS\n\tnbjar2bDe+H9ljdmc6MJ5BQQEhjSU4n54SdjADk4pADKVm0oipQNsQcA9v1RvDIYlQDr\n\tJ3inZEsm3m7bCksOwMh3zYgLrqfUFzF8L2Qm5UsfzCwqxKpr6CjcRTX0e6D6YGiNQvUn\n\t3/4Q==","X-Gm-Message-State":"AOJu0YyXIQV+5jPK2X4Ot6kb+I0xcsn/8aR9g4L9qMZCtC+43ya6m52U\n\txeFRsE62Fo6H9uu/n914Vk7+mkB9eD+QhBJUwWhhoo+Ls/Op75pQG1akOqHNvsCYKDTYybYikg4\n\tROYrqiugC66YLTZn7mAUwmQJAiPeoarN66qLxZEqGUR4NK+5JmscSahQ=","X-Gm-Gg":"ASbGncuVG6cO/sE0th+jnM9psmf63mgVZts/ap4YgfNAuYb3fSo1cUF7t5KduYceo6o\n\tVUwlu6srMB2rxdpryCo6jFPhTtUTGda8bmjjaT3e8Xjt7hplQvyiX/dMG32+3qx97eluCxdcJ7p\n\tB2WsG3rQG0IxQY/WZfSlV+QRb1uLY/dc3DErwvNIYUZZLUcRnt9Q5W8AR7VeFc7rwXOXv9TCPOJ\n\tTn+KsCMD6KO1Zpyemp+3FDpgiKfDtBO86eQu8oA2YuXeMb7","X-Google-Smtp-Source":"AGHT+IGkMtwVofDzF5+Ggmg29RtVenUwgGbaMedhHKM89B2BY7TbIwCqG2qiMLzzUKduvSpcWWigl/8/AimCqJgrm08=","X-Received":"by 2002:a05:6214:769:b0:79b:d17b:b693 with SMTP id\n\t6a1803df08f44-7fc414d118bmr98701076d6.60.1758876372254;\n\tFri, 26 Sep 2025 01:46:12 -0700 (PDT)","MIME-Version":"1.0","References":"<20250924124713.3361707-1-barnabas.pocze@ideasonboard.com>\n\t<20250924124713.3361707-7-barnabas.pocze@ideasonboard.com>","In-Reply-To":"<20250924124713.3361707-7-barnabas.pocze@ideasonboard.com>","From":"David Plowman <david.plowman@raspberrypi.com>","Date":"Fri, 26 Sep 2025 09:46:01 +0100","X-Gm-Features":"AS18NWAaHkBvbD3MFU0T0732s5ekIy37Ob4O6m_nzmOxrpuYd2RWS40KCujOhEg","Message-ID":"<CAHW6GYJy=C5zEy+HQiWy3FMUp+EOH=OX37x16RUTWf8ciaBXEw@mail.gmail.com>","Subject":"Re: [RFC PATCH v1 6/7] libcamera: camera: Add `applyControls()`","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org, \n\tKieran Bingham <kieran.bingham@ideasonboard.com>","Content-Type":"text/plain; charset=\"UTF-8\"","Content-Transfer-Encoding":"quoted-printable","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>"}},{"id":35996,"web_url":"https://patchwork.libcamera.org/comment/35996/","msgid":"<932da8e0-6728-4d21-ad9e-bf43a74b4f57@ideasonboard.com>","date":"2025-09-26T10:21:24","subject":"Re: [RFC PATCH v1 6/7] libcamera: camera: Add `applyControls()`","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"Hi\n\n2025. 09. 26. 10:46 keltezéssel, David Plowman írta:\n> Hi Barnabas\n> \n> Thanks very much for sending this. I'm very excited to see the\n> question of controls and how they relate to requests move forward!\n> \n> On Wed, 24 Sept 2025 at 13:47, Barnabás Pőcze\n> <barnabas.pocze@ideasonboard.com> wrote:\n>>\n>> Add `Camera::applyControls()` whose purpose is to apply controls as soon\n>> as possible, without going through `Request::controls()`.\n>>\n>> A new virtual function `PipelineHandler::applyControlsDevice()` is provided\n>> for pipeline handler to implement fast-tracked application of controls. If\n>> the pipeline handler does not implement that functionality, or it fails,\n>> then a fallback mechanism is used. The controls will be saved for later,\n>> and they will be merged into the control list of the next request sent to\n>> the pipeline handler (`Camera::Private::waitingRequests_`).\n> \n> First of all, and I don't know if you're aware of this, at Raspberry\n> Pi we did quite a lot of work on this question so perhaps it's worth\n> covering that first. We implemented our scheme and have demonstrated\n> it on and off over the last few years, most recently at the Nice\n> workshop.\n\nYes, I think I have seen it.\n\n\n> \n> Our implementation didn't present a separate mechanism to submit\n> controls, instead we still relied on them being passed in the\n> request. Though internally, we effectively ran a separate queue\n> containing only the non-empty control lists, so that all the requests\n> without a control list didn't get in the way.\n\nBypassing the request mechanism is kind of the intention here. Specifically,\nthe idea is to support applications that keep many requests queued at any given\ntime, but still want to be able to update controls \"immediately\". In that case\nsetting the desired controls as part of the next request causes unnecessary latency.\n\nAn alternative option is to let the application be responsible for keeping \"just enough\"\nrequests queued at any given moment, and keep an application-side queue for the \"rest\"\nof the ready request. In that case it can set the controls on the first request in the\napplication-side queue, reducing the latency. However, this queue would essentially be\nthe same as `Camera::Private::waitingRequests_`, so the idea here is to try to provide\na simpler mechanism.\n\nI have briefly looked at https://github.com/raspberrypi/libcamera/tree/pfc_2025 (I assume\nthis is the one), and I could be misremembering, but I feel like the proposed mechanism here\naddresses a use-case somewhat different from what your scheme addresses? Or I could be missing\na more fundamental connection somewhere?\n\n\nRegards,\nBarnabás Pőcze\n\n> \n> Having said that, I like the suggestion of the separate API that you\n> presented, and I think it would work well for us so long as it's\n> backed by a queue. Then we'd be able, for example, to queue up\n> multiple different exposure values and have them applied on\n> consecutive frames.\n> \n> The other feature of our scheme was that every control list was\n> assigned an id number, starting at 1 and then incrementing by 1 with\n> every new control list (not request). This id number would be reported\n> back in a completed request, telling you exactly which the most\n> recent control list was that had been applied. It was also valuable\n> for detecting when things went wrong (via skipped or duplicated id\n> numbers).\n> \n> Finally, those taking part in the Kamaros working group will\n> appreciate that the above, with the queue mechanism, is very close to\n> what's in the Kamaros spec. The principal difference is that Kamaros\n> lets you have more than one such queue. For example, you might have\n> some part of your system pushing AGC/AEC type controls into one\n> queue, and possibly keeping it quite full, while another part of the\n> system responds quickly to user input (perhaps wanting an auto-focus\n> cycle).\n> \n> Best regards\n> David\n> \n>>\n>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.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                      |  53 +++++++++\n>>   src/libcamera/pipeline_handler.cpp            | 110 +++++++++++++++++-\n>>   5 files changed, 171 insertions(+), 1 deletion(-)\n>>\n>> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n>> index b24a29740..94f940496 100644\n>> --- a/include/libcamera/camera.h\n>> +++ b/include/libcamera/camera.h\n>> @@ -147,6 +147,7 @@ public:\n>>\n>>          std::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n>>          int queueRequest(Request *request);\n>> +       int applyControls(ControlList &&controls);\n>>\n>>          int start(const ControlList *controls = nullptr);\n>>          int stop();\n>> diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h\n>> index 0f50c14a5..5a77298b3 100644\n>> --- a/include/libcamera/internal/camera.h\n>> +++ b/include/libcamera/internal/camera.h\n>> @@ -38,6 +38,7 @@ public:\n>>\n>>          std::list<Request *> queuedRequests_;\n>>          std::deque<Request *> waitingRequests_;\n>> +       ControlList pendingControls_;\n>>          ControlInfoMap controlInfo_;\n>>          ControlList properties_;\n>>\n>> diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h\n>> index e89d6a33e..16ff54bab 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>>          void registerRequest(Request *request);\n>>          void queueRequest(Request *request);\n>> +       int applyControls(Camera *camera, ControlList &&controls);\n>>\n>>          bool completeBuffer(Request *request, FrameBuffer *buffer);\n>>          void completeRequest(Request *request);\n>> @@ -75,6 +76,12 @@ protected:\n>>          void hotplugMediaDevice(MediaDevice *media);\n>>\n>>          virtual int queueRequestDevice(Camera *camera, Request *request) = 0;\n>> +\n>> +       virtual int applyControlsDevice([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList &controls)\n>> +       {\n>> +               return -EOPNOTSUPP;\n>> +       }\n>> +\n>>          virtual void stopDevice(Camera *camera) = 0;\n>>\n>>          virtual bool acquireDevice(Camera *camera);\n>> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n>> index 2e1e146a2..778445e36 100644\n>> --- a/src/libcamera/camera.cpp\n>> +++ b/src/libcamera/camera.cpp\n>> @@ -637,6 +637,14 @@ Camera::Private::~Private()\n>>    * queued requests was reached.\n>>    */\n>>\n>> +/**\n>> + * \\var Camera::Private::pendingControls_\n>> + * \\brief The list of pending controls\n>> + *\n>> + * This list tracks the controls that need to be applied to the device with the\n>> + * next request going to PipelineHandler::queueRequestDevice().\n>> + */\n>> +\n>>   /**\n>>    * \\var Camera::Private::controlInfo_\n>>    * \\brief The set of controls supported by the camera\n>> @@ -1374,6 +1382,51 @@ int Camera::queueRequest(Request *request)\n>>          return 0;\n>>   }\n>>\n>> +/**\n>> + * \\brief Apply controls immediately\n>> + * \\param[in] controls The list of controls to apply\n>> + *\n>> + * This function tries to ensure that the controls in \\a controls are applied\n>> + * to the camera as soon as possible.\n>> + *\n>> + * The exact guarantees are camera dependent, but it is guaranteed that the\n>> + * controls will be applied no later than if they were part of the next \\ref Request \"request\"\n>> + * that the application \\ref Camera::queueRequest() \"queues\".\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::applyControls(ControlList &&controls)\n>> +{\n>> +       Private *const d = _d();\n>> +\n>> +       /*\n>> +        * \\todo What to do if not running? Should it be stored and merged into the \"start\" `ControlList`?\n>> +        *       If yes, should that list of pending controls be overwritten/updated when stopped?\n>> +        *       And should it be cleared in `Camera::release()`?\n>> +        */\n>> +\n>> +       int ret = d->isAccessAllowed(Private::CameraRunning);\n>> +       if (ret < 0)\n>> +               return ret;\n>> +\n>> +       if (controls.empty())\n>> +               return 0;\n>> +\n>> +       patchControlList(controls);\n>> +\n>> +       /*\n>> +        * \\todo Or `ConnectionTypeBlocking` to get the return value?\n>> +        */\n>> +       d->pipe_->invokeMethod(&PipelineHandler::applyControls, ConnectionTypeQueued, this, std::move(controls));\n>> +\n>> +       return 0;\n>> +}\n>> +\n>>   /**\n>>    * \\brief Start capture from camera\n>>    * \\param[in] controls Controls to be applied before starting the Camera\n>> diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n>> index d0d3fbc79..99bb2193f 100644\n>> --- a/src/libcamera/pipeline_handler.cpp\n>> +++ b/src/libcamera/pipeline_handler.cpp\n>> @@ -387,6 +387,12 @@ void PipelineHandler::stop(Camera *camera)\n>>          ASSERT(data->queuedRequests_.empty());\n>>          ASSERT(data->waitingRequests_.empty());\n>>\n>> +       /*\n>> +        * \\todo Or not? This probably needs discussions regarding\n>> +        * the expected state of controls after a stop-start sequence.\n>> +        */\n>> +       data->pendingControls_.clear();\n>> +\n>>          data->requestSequence_ = 0;\n>>   }\n>>\n>> @@ -466,6 +472,54 @@ void PipelineHandler::queueRequest(Request *request)\n>>          request->_d()->prepare(300ms);\n>>   }\n>>\n>> +/**\n>> + * \\brief Apply controls immediately\n>> + * \\param[in] camera The camera\n>> + * \\param[in] controls The controls to apply\n>> + *\n>> + * This function tries to apply \\a controls immediately to the device by\n>> + * calling applyControlsDevice(). 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 request submitted to the pipeline handler.\n>> + *\n>> + * This function also ensures that requests in Camera::Private::waitingRequests_\n>> + * will not override the fast-tracked controls.\n>> + *\n>> + * \\context This function is called from the CameraManager thread.\n>> + */\n>> +int PipelineHandler::applyControls(Camera *camera, ControlList &&controls)\n>> +{\n>> +       Camera::Private *data = camera->_d();\n>> +       int res = applyControlsDevice(camera, controls);\n>> +\n>> +       /*\n>> +        * Prevent later requests from overriding the fast-tracked controls.\n>> +        * \\todo This is unfortunately slow.\n>> +        */\n>> +       for (const auto &[k, v] : controls) {\n>> +               for (Request *r : data->waitingRequests_)\n>> +                       r->controls().erase(k);\n>> +       }\n>> +\n>> +       if (res < 0) {\n>> +               /*\n>> +                * \\todo Always fall back or only if EOPNOTSUPP is returned?\n>> +                *       Possibly there could be some way for the user to influence\n>> +                *       the fallback logic (e.g. `flags` argument for `Camera::applyControls()`).\n>> +                */\n>> +               if (res != -EOPNOTSUPP)\n>> +                       LOG(Pipeline, Debug) << \"Fast tracking controls failed: \" << res;\n>> +\n>> +               /*\n>> +                * Fall back to adding the controls to the next request that enters the\n>> +                * pipeline handler. See PipelineHandler::doQueueRequest().\n>> +                */\n>> +               data->pendingControls_.merge(std::move(controls), ControlList::MergePolicy::OverwriteExisting);\n>> +       }\n>> +\n>> +       return 0;\n>> +}\n>> +\n>>   /**\n>>    * \\brief Queue one requests to the device\n>>    */\n>> @@ -484,9 +538,49 @@ void PipelineHandler::doQueueRequest(Request *request)\n>>                  return;\n>>          }\n>>\n>> +       if (!data->pendingControls_.empty()) {\n>> +               /*\n>> +                * Note that `ControlList::MergePolicy::KeepExisting` is used. This is\n>> +                * needed to ensure that if `request` is newer than pendingControls_,\n>> +                * then its controls take precedence.\n>> +                *\n>> +                * For older requests, `PipelineHandler::applyControls()` has already removed\n>> +                * the controls that should be overridden.\n>> +                *\n>> +                * \\todo How to handle (if at all) conflicting controls?\n>> +                * \\todo How to handle failure of `queueRequestDevice()`?\n>> +                *       After the merge it becomes impossible to retrieve the pending controls\n>> +                *       from `request->controls()`. Making a copy in such a hot-path is not ideal.\n>> +                */\n>> +               request->controls().merge(std::move(data->pendingControls_), ControlList::MergePolicy::KeepExisting);\n>> +               data->pendingControls_.clear();\n>> +       }\n>> +\n>>          int ret = queueRequestDevice(camera, request);\n>> -       if (ret)\n>> +       if (ret) {\n>> +               /*\n>> +                * \\todo What to do now?\n>> +                *\n>> +                * The original `pendingControls_` is not easily recoverable.\n>> +                *\n>> +                * Initially\n>> +                *     request->controls() = A u B\n>> +                *     pendingControls_ = A' u C\n>> +                *\n>> +                * then after the above merge\n>> +                *     request->controls() = A u B u C\n>> +                *     pendingControls_ = A'\n>> +                *\n>> +                * so recovering `pendingControls_` without a copy of at least\n>> +                * `B` or `C` does not appear easy.\n>> +                *\n>> +                * But sometimes recovering is not even desirable: assume that the controls\n>> +                * in `pendingControls_` causes the failure. In that case keeping them around\n>> +                * will cause every single subsequent request to fail.\n>> +                */\n>> +\n>>                  cancelRequest(request);\n>> +       }\n>>   }\n>>\n>>   /**\n>> @@ -532,6 +626,20 @@ void PipelineHandler::doQueueRequests(Camera *camera)\n>>    * \\return 0 on success or a negative error code otherwise\n>>    */\n>>\n>> +/**\n>> + * \\fn PipelineHandler::applyControlsDevice()\n>> + * \\brief Apply controls immediately\n>> + * \\param[in] camera The camera\n>> + * \\param[in] controls The controls to apply\n>> + *\n>> + * This function applies \\a controls to \\a camera immediately.\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>> --\n>> 2.51.0\n>>","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 3BBCBBDB1C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 26 Sep 2025 10:21:31 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 296476B5F3;\n\tFri, 26 Sep 2025 12:21:30 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id C911E613AB\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 26 Sep 2025 12:21:28 +0200 (CEST)","from [192.168.33.19] (185.221.140.70.nat.pool.zt.hu\n\t[185.221.140.70])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 527A31980;\n\tFri, 26 Sep 2025 12:20:01 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"GercoMHq\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1758882003;\n\tbh=WhJ+vytvKPJstlJnK2QXg4J1eKQ5hEf1kFahEHqsEjY=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=GercoMHqO4K9UmUENKRIyqgY1bFjcuV42t1SVMMVvA/JJjlElRBSeAIV+lYcr3fbh\n\toBBEgoCZHTUk1W7FL3mfpoY06rowzhh4EnrHapdObcj/RaalcNzWWlYpWNnONEsB7u\n\t0rY8SFe+PtZTKl9qm2P/rC9HuhUpmmDR6jJVWf2Y=","Message-ID":"<932da8e0-6728-4d21-ad9e-bf43a74b4f57@ideasonboard.com>","Date":"Fri, 26 Sep 2025 12:21:24 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [RFC PATCH v1 6/7] libcamera: camera: Add `applyControls()`","To":"David Plowman <david.plowman@raspberrypi.com>","Cc":"libcamera-devel@lists.libcamera.org,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>","References":"<20250924124713.3361707-1-barnabas.pocze@ideasonboard.com>\n\t<20250924124713.3361707-7-barnabas.pocze@ideasonboard.com>\n\t<CAHW6GYJy=C5zEy+HQiWy3FMUp+EOH=OX37x16RUTWf8ciaBXEw@mail.gmail.com>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<CAHW6GYJy=C5zEy+HQiWy3FMUp+EOH=OX37x16RUTWf8ciaBXEw@mail.gmail.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","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>"}},{"id":36062,"web_url":"https://patchwork.libcamera.org/comment/36062/","msgid":"<CAHW6GYLoks+xDOcQJCFjH_zq2WAfMGjJEA_k37Ev9QAnv2jXTQ@mail.gmail.com>","date":"2025-09-30T14:59:53","subject":"Re: [RFC PATCH v1 6/7] libcamera: camera: Add `applyControls()`","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/people/42/","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"content":"Hi Barnabás, and thanks for the reply.\n\nOn Fri, 26 Sept 2025 at 11:21, Barnabás Pőcze\n<barnabas.pocze@ideasonboard.com> wrote:\n>\n> Hi\n>\n> 2025. 09. 26. 10:46 keltezéssel, David Plowman írta:\n> > Hi Barnabas\n> >\n> > Thanks very much for sending this. I'm very excited to see the\n> > question of controls and how they relate to requests move forward!\n> >\n> > On Wed, 24 Sept 2025 at 13:47, Barnabás Pőcze\n> > <barnabas.pocze@ideasonboard.com> wrote:\n> >>\n> >> Add `Camera::applyControls()` whose purpose is to apply controls as soon\n> >> as possible, without going through `Request::controls()`.\n> >>\n> >> A new virtual function `PipelineHandler::applyControlsDevice()` is provided\n> >> for pipeline handler to implement fast-tracked application of controls. If\n> >> the pipeline handler does not implement that functionality, or it fails,\n> >> then a fallback mechanism is used. The controls will be saved for later,\n> >> and they will be merged into the control list of the next request sent to\n> >> the pipeline handler (`Camera::Private::waitingRequests_`).\n> >\n> > First of all, and I don't know if you're aware of this, at Raspberry\n> > Pi we did quite a lot of work on this question so perhaps it's worth\n> > covering that first. We implemented our scheme and have demonstrated\n> > it on and off over the last few years, most recently at the Nice\n> > workshop.\n>\n> Yes, I think I have seen it.\n>\n>\n> >\n> > Our implementation didn't present a separate mechanism to submit\n> > controls, instead we still relied on them being passed in the\n> > request. Though internally, we effectively ran a separate queue\n> > containing only the non-empty control lists, so that all the requests\n> > without a control list didn't get in the way.\n>\n> Bypassing the request mechanism is kind of the intention here. Specifically,\n> the idea is to support applications that keep many requests queued at any given\n> time, but still want to be able to update controls \"immediately\". In that case\n> setting the desired controls as part of the next request causes unnecessary latency.\n\nYes, this is exactly the use case we were addressing, though we fixed it in a\ndifferent way. However, I prefer the external mechanism that you have\n(so long as\nI can queue up control lists for several consecutive frames).\n\n>\n> An alternative option is to let the application be responsible for keeping \"just enough\"\n> requests queued at any given moment, and keep an application-side queue for the \"rest\"\n> of the ready request. In that case it can set the controls on the first request in the\n> application-side queue, reducing the latency. However, this queue would essentially be\n> the same as `Camera::Private::waitingRequests_`, so the idea here is to try to provide\n> a simpler mechanism.\n>\n> I have briefly looked at https://github.com/raspberrypi/libcamera/tree/pfc_2025 (I assume\n> this is the one), and I could be misremembering, but I feel like the proposed mechanism here\n> addresses a use-case somewhat different from what your scheme addresses? Or I could be missing\n> a more fundamental connection somewhere?\n\nThe by-passing of the requests in the request queue is essentially for\nexactly the same purpose.\nBut I agree that identifying which controls have been applied is\nlargely orthogonal. However,\nthere could be some interaction, so there may be some value in\nconsidering the two together.\n\nFor example, perhaps sending a control list to the external queue\ngives you back an id number,\nwhich you would then look for in the request when it completes, so\nthat's a (very small!) API change.\n(Obviously, figuring out when controls are applied is more difficult,\nand was probably what a lot\nof the code in that branch was dealing with...)\n\nBest regards\nDavid\n\n>\n>\n> Regards,\n> Barnabás Pőcze\n>\n> >\n> > Having said that, I like the suggestion of the separate API that you\n> > presented, and I think it would work well for us so long as it's\n> > backed by a queue. Then we'd be able, for example, to queue up\n> > multiple different exposure values and have them applied on\n> > consecutive frames.\n> >\n> > The other feature of our scheme was that every control list was\n> > assigned an id number, starting at 1 and then incrementing by 1 with\n> > every new control list (not request). This id number would be reported\n> > back in a completed request, telling you exactly which the most\n> > recent control list was that had been applied. It was also valuable\n> > for detecting when things went wrong (via skipped or duplicated id\n> > numbers).\n> >\n> > Finally, those taking part in the Kamaros working group will\n> > appreciate that the above, with the queue mechanism, is very close to\n> > what's in the Kamaros spec. The principal difference is that Kamaros\n> > lets you have more than one such queue. For example, you might have\n> > some part of your system pushing AGC/AEC type controls into one\n> > queue, and possibly keeping it quite full, while another part of the\n> > system responds quickly to user input (perhaps wanting an auto-focus\n> > cycle).\n> >\n> > Best regards\n> > David\n> >\n> >>\n> >> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.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                      |  53 +++++++++\n> >>   src/libcamera/pipeline_handler.cpp            | 110 +++++++++++++++++-\n> >>   5 files changed, 171 insertions(+), 1 deletion(-)\n> >>\n> >> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> >> index b24a29740..94f940496 100644\n> >> --- a/include/libcamera/camera.h\n> >> +++ b/include/libcamera/camera.h\n> >> @@ -147,6 +147,7 @@ public:\n> >>\n> >>          std::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n> >>          int queueRequest(Request *request);\n> >> +       int applyControls(ControlList &&controls);\n> >>\n> >>          int start(const ControlList *controls = nullptr);\n> >>          int stop();\n> >> diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h\n> >> index 0f50c14a5..5a77298b3 100644\n> >> --- a/include/libcamera/internal/camera.h\n> >> +++ b/include/libcamera/internal/camera.h\n> >> @@ -38,6 +38,7 @@ public:\n> >>\n> >>          std::list<Request *> queuedRequests_;\n> >>          std::deque<Request *> waitingRequests_;\n> >> +       ControlList pendingControls_;\n> >>          ControlInfoMap controlInfo_;\n> >>          ControlList properties_;\n> >>\n> >> diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h\n> >> index e89d6a33e..16ff54bab 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> >>          void registerRequest(Request *request);\n> >>          void queueRequest(Request *request);\n> >> +       int applyControls(Camera *camera, ControlList &&controls);\n> >>\n> >>          bool completeBuffer(Request *request, FrameBuffer *buffer);\n> >>          void completeRequest(Request *request);\n> >> @@ -75,6 +76,12 @@ protected:\n> >>          void hotplugMediaDevice(MediaDevice *media);\n> >>\n> >>          virtual int queueRequestDevice(Camera *camera, Request *request) = 0;\n> >> +\n> >> +       virtual int applyControlsDevice([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList &controls)\n> >> +       {\n> >> +               return -EOPNOTSUPP;\n> >> +       }\n> >> +\n> >>          virtual void stopDevice(Camera *camera) = 0;\n> >>\n> >>          virtual bool acquireDevice(Camera *camera);\n> >> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> >> index 2e1e146a2..778445e36 100644\n> >> --- a/src/libcamera/camera.cpp\n> >> +++ b/src/libcamera/camera.cpp\n> >> @@ -637,6 +637,14 @@ Camera::Private::~Private()\n> >>    * queued requests was reached.\n> >>    */\n> >>\n> >> +/**\n> >> + * \\var Camera::Private::pendingControls_\n> >> + * \\brief The list of pending controls\n> >> + *\n> >> + * This list tracks the controls that need to be applied to the device with the\n> >> + * next request going to PipelineHandler::queueRequestDevice().\n> >> + */\n> >> +\n> >>   /**\n> >>    * \\var Camera::Private::controlInfo_\n> >>    * \\brief The set of controls supported by the camera\n> >> @@ -1374,6 +1382,51 @@ int Camera::queueRequest(Request *request)\n> >>          return 0;\n> >>   }\n> >>\n> >> +/**\n> >> + * \\brief Apply controls immediately\n> >> + * \\param[in] controls The list of controls to apply\n> >> + *\n> >> + * This function tries to ensure that the controls in \\a controls are applied\n> >> + * to the camera as soon as possible.\n> >> + *\n> >> + * The exact guarantees are camera dependent, but it is guaranteed that the\n> >> + * controls will be applied no later than if they were part of the next \\ref Request \"request\"\n> >> + * that the application \\ref Camera::queueRequest() \"queues\".\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::applyControls(ControlList &&controls)\n> >> +{\n> >> +       Private *const d = _d();\n> >> +\n> >> +       /*\n> >> +        * \\todo What to do if not running? Should it be stored and merged into the \"start\" `ControlList`?\n> >> +        *       If yes, should that list of pending controls be overwritten/updated when stopped?\n> >> +        *       And should it be cleared in `Camera::release()`?\n> >> +        */\n> >> +\n> >> +       int ret = d->isAccessAllowed(Private::CameraRunning);\n> >> +       if (ret < 0)\n> >> +               return ret;\n> >> +\n> >> +       if (controls.empty())\n> >> +               return 0;\n> >> +\n> >> +       patchControlList(controls);\n> >> +\n> >> +       /*\n> >> +        * \\todo Or `ConnectionTypeBlocking` to get the return value?\n> >> +        */\n> >> +       d->pipe_->invokeMethod(&PipelineHandler::applyControls, ConnectionTypeQueued, this, std::move(controls));\n> >> +\n> >> +       return 0;\n> >> +}\n> >> +\n> >>   /**\n> >>    * \\brief Start capture from camera\n> >>    * \\param[in] controls Controls to be applied before starting the Camera\n> >> diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> >> index d0d3fbc79..99bb2193f 100644\n> >> --- a/src/libcamera/pipeline_handler.cpp\n> >> +++ b/src/libcamera/pipeline_handler.cpp\n> >> @@ -387,6 +387,12 @@ void PipelineHandler::stop(Camera *camera)\n> >>          ASSERT(data->queuedRequests_.empty());\n> >>          ASSERT(data->waitingRequests_.empty());\n> >>\n> >> +       /*\n> >> +        * \\todo Or not? This probably needs discussions regarding\n> >> +        * the expected state of controls after a stop-start sequence.\n> >> +        */\n> >> +       data->pendingControls_.clear();\n> >> +\n> >>          data->requestSequence_ = 0;\n> >>   }\n> >>\n> >> @@ -466,6 +472,54 @@ void PipelineHandler::queueRequest(Request *request)\n> >>          request->_d()->prepare(300ms);\n> >>   }\n> >>\n> >> +/**\n> >> + * \\brief Apply controls immediately\n> >> + * \\param[in] camera The camera\n> >> + * \\param[in] controls The controls to apply\n> >> + *\n> >> + * This function tries to apply \\a controls immediately to the device by\n> >> + * calling applyControlsDevice(). 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 request submitted to the pipeline handler.\n> >> + *\n> >> + * This function also ensures that requests in Camera::Private::waitingRequests_\n> >> + * will not override the fast-tracked controls.\n> >> + *\n> >> + * \\context This function is called from the CameraManager thread.\n> >> + */\n> >> +int PipelineHandler::applyControls(Camera *camera, ControlList &&controls)\n> >> +{\n> >> +       Camera::Private *data = camera->_d();\n> >> +       int res = applyControlsDevice(camera, controls);\n> >> +\n> >> +       /*\n> >> +        * Prevent later requests from overriding the fast-tracked controls.\n> >> +        * \\todo This is unfortunately slow.\n> >> +        */\n> >> +       for (const auto &[k, v] : controls) {\n> >> +               for (Request *r : data->waitingRequests_)\n> >> +                       r->controls().erase(k);\n> >> +       }\n> >> +\n> >> +       if (res < 0) {\n> >> +               /*\n> >> +                * \\todo Always fall back or only if EOPNOTSUPP is returned?\n> >> +                *       Possibly there could be some way for the user to influence\n> >> +                *       the fallback logic (e.g. `flags` argument for `Camera::applyControls()`).\n> >> +                */\n> >> +               if (res != -EOPNOTSUPP)\n> >> +                       LOG(Pipeline, Debug) << \"Fast tracking controls failed: \" << res;\n> >> +\n> >> +               /*\n> >> +                * Fall back to adding the controls to the next request that enters the\n> >> +                * pipeline handler. See PipelineHandler::doQueueRequest().\n> >> +                */\n> >> +               data->pendingControls_.merge(std::move(controls), ControlList::MergePolicy::OverwriteExisting);\n> >> +       }\n> >> +\n> >> +       return 0;\n> >> +}\n> >> +\n> >>   /**\n> >>    * \\brief Queue one requests to the device\n> >>    */\n> >> @@ -484,9 +538,49 @@ void PipelineHandler::doQueueRequest(Request *request)\n> >>                  return;\n> >>          }\n> >>\n> >> +       if (!data->pendingControls_.empty()) {\n> >> +               /*\n> >> +                * Note that `ControlList::MergePolicy::KeepExisting` is used. This is\n> >> +                * needed to ensure that if `request` is newer than pendingControls_,\n> >> +                * then its controls take precedence.\n> >> +                *\n> >> +                * For older requests, `PipelineHandler::applyControls()` has already removed\n> >> +                * the controls that should be overridden.\n> >> +                *\n> >> +                * \\todo How to handle (if at all) conflicting controls?\n> >> +                * \\todo How to handle failure of `queueRequestDevice()`?\n> >> +                *       After the merge it becomes impossible to retrieve the pending controls\n> >> +                *       from `request->controls()`. Making a copy in such a hot-path is not ideal.\n> >> +                */\n> >> +               request->controls().merge(std::move(data->pendingControls_), ControlList::MergePolicy::KeepExisting);\n> >> +               data->pendingControls_.clear();\n> >> +       }\n> >> +\n> >>          int ret = queueRequestDevice(camera, request);\n> >> -       if (ret)\n> >> +       if (ret) {\n> >> +               /*\n> >> +                * \\todo What to do now?\n> >> +                *\n> >> +                * The original `pendingControls_` is not easily recoverable.\n> >> +                *\n> >> +                * Initially\n> >> +                *     request->controls() = A u B\n> >> +                *     pendingControls_ = A' u C\n> >> +                *\n> >> +                * then after the above merge\n> >> +                *     request->controls() = A u B u C\n> >> +                *     pendingControls_ = A'\n> >> +                *\n> >> +                * so recovering `pendingControls_` without a copy of at least\n> >> +                * `B` or `C` does not appear easy.\n> >> +                *\n> >> +                * But sometimes recovering is not even desirable: assume that the controls\n> >> +                * in `pendingControls_` causes the failure. In that case keeping them around\n> >> +                * will cause every single subsequent request to fail.\n> >> +                */\n> >> +\n> >>                  cancelRequest(request);\n> >> +       }\n> >>   }\n> >>\n> >>   /**\n> >> @@ -532,6 +626,20 @@ void PipelineHandler::doQueueRequests(Camera *camera)\n> >>    * \\return 0 on success or a negative error code otherwise\n> >>    */\n> >>\n> >> +/**\n> >> + * \\fn PipelineHandler::applyControlsDevice()\n> >> + * \\brief Apply controls immediately\n> >> + * \\param[in] camera The camera\n> >> + * \\param[in] controls The controls to apply\n> >> + *\n> >> + * This function applies \\a controls to \\a camera immediately.\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> >> --\n> >> 2.51.0\n> >>\n>","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 A5C1BC324C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 30 Sep 2025 15:00:11 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id E9B066B599;\n\tTue, 30 Sep 2025 17:00:09 +0200 (CEST)","from mail-qk1-x729.google.com (mail-qk1-x729.google.com\n\t[IPv6:2607:f8b0:4864:20::729])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D01DF62C35\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 30 Sep 2025 17:00:06 +0200 (CEST)","by mail-qk1-x729.google.com with SMTP id\n\taf79cd13be357-86420079b01so382580985a.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 30 Sep 2025 08:00:06 -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=\"MvQtehNf\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1759244405; x=1759849205;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:cc:to:subject:message-id:date:from\n\t:in-reply-to:references:mime-version:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=cxPf4irTQ5Z7Gjz3y7kfNw+UC5EK7LpNsrgW9MUvG4c=;\n\tb=MvQtehNfyj/3+R3xmJ9xJWtJzIGKKO1CqjslB1XgTxQ/m4X7n4gjq671qWxXDd1x4+\n\tzfSedEssFVU4Y8ttHnz5Cn0oNGyacBXA83bLS3b4xP559Ub0RTbmp1gRCpTLHLhtPxiX\n\tFM7T4Ye9vi/+IrvsLqjRRfGmJJJFMxnQJ/WXY8g5sm6AAFSUNuTFc4XWWq5VrXPTma0y\n\t6zESTXwmI+QAEmfud3i4I/j9emzokc07CNYbViGzf2RSHNKyW88SFYcMnkVGbDriVRKK\n\txxzWFKxu38wccZhP+wI9z+S6rVoB6mMiKs5fzbXzXuhpU7ZmqHE9Yb5D4jR+Rcxv72Hi\n\tG9WQ==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1759244405; x=1759849205;\n\th=content-transfer-encoding:cc:to:subject:message-id:date:from\n\t:in-reply-to:references:mime-version:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=cxPf4irTQ5Z7Gjz3y7kfNw+UC5EK7LpNsrgW9MUvG4c=;\n\tb=f3bYbqZ0SJGaahbA3Ct6mMxD5lfLbL8WSZg7/FRUyw16AHytq4yEoyO1qhkH+62Dn3\n\tjF3wn++VnlHvfD8k+pwuas6jbGFerHRJWRqhVLRBBM8dB85XHa9rw8/cP0oCGTt0cJv/\n\tNluSQqvjj4ra5Uz+BY1HjGhpIWeSQ2drQ3MQnKUl1TA2PrMfjutt3Ki7bj/jxNYgBUNs\n\tgXHtfQov81BaEI/B3f/ks0GPtPdrX8ZnzpDnzQG5qSD0kz5bxa7+ZBD+hWBCwsn4LwDB\n\tsnaDglDVa5M4CiZJavCL8BHW9ojhVR23HJzgt+mmkdPwthuslkb6hpnGBP5X/T+AYL8w\n\tZvmA==","X-Gm-Message-State":"AOJu0YxO3FWM7E7KVnp1mM2j7hB7/ZSS5G6QvU16VFAX2dJoTB/7x68U\n\t4rtgzGnf9CQ2OupTTll+inXM35Y9JbWb3LsJL/MuuUXn+p6KBl9iU5eurq8xOW2ilaRX7ehZhlR\n\tpBJPFAiRAscdnQD+Ysgvz/ns6kYBjZKCOVLjK/QEIoQ==","X-Gm-Gg":"ASbGncvShyOJFkiokZZiq798SSgDPnVnuty6m56odpy+g39eNI39s3cQZsp0tKM93VZ\n\t9PpmGWNpO93VpzXEQ8TveJ0YZZR/upTwi3GeDYTMCwChV88mFNypKkHYLRntn5NrBOeFsbIqE0k\n\tX9Y3IeLjufNNn2caCk8lEiA0PeVEMDtTMSSdEeBbRWhhLEUxsIQGiWJwCstBlMrn49oRyIZ4MRZ\n\t57Jk8bZnMPwIJgMBsFdQpEGC7B1y7DnsLk8jG2O1N7fUxy7JE6bIIdlvpEMqXI=","X-Google-Smtp-Source":"AGHT+IFCJ7D+ZLlxMU+zGgdn5b3GqyfF2CeI9h5eWxf0vKRtAaQ65UaNz+rQIQLI7IQevqWG3HhvyLY515x2BBubDhE=","X-Received":"by 2002:a05:620a:4723:b0:83b:5f20:41a6 with SMTP id\n\taf79cd13be357-8737021b00emr13022785a.2.1759244405202; Tue, 30 Sep 2025\n\t08:00:05 -0700 (PDT)","MIME-Version":"1.0","References":"<20250924124713.3361707-1-barnabas.pocze@ideasonboard.com>\n\t<20250924124713.3361707-7-barnabas.pocze@ideasonboard.com>\n\t<CAHW6GYJy=C5zEy+HQiWy3FMUp+EOH=OX37x16RUTWf8ciaBXEw@mail.gmail.com>\n\t<932da8e0-6728-4d21-ad9e-bf43a74b4f57@ideasonboard.com>","In-Reply-To":"<932da8e0-6728-4d21-ad9e-bf43a74b4f57@ideasonboard.com>","From":"David Plowman <david.plowman@raspberrypi.com>","Date":"Tue, 30 Sep 2025 15:59:53 +0100","X-Gm-Features":"AS18NWBPOv74WuXaBR9eq_PDObSjBjsInoizOzUt07lC1cZePVF-ygn83CVj80k","Message-ID":"<CAHW6GYLoks+xDOcQJCFjH_zq2WAfMGjJEA_k37Ev9QAnv2jXTQ@mail.gmail.com>","Subject":"Re: [RFC PATCH v1 6/7] libcamera: camera: Add `applyControls()`","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org, \n\tKieran Bingham <kieran.bingham@ideasonboard.com>","Content-Type":"text/plain; charset=\"UTF-8\"","Content-Transfer-Encoding":"quoted-printable","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>"}},{"id":36149,"web_url":"https://patchwork.libcamera.org/comment/36149/","msgid":"<175975900382.3214037.5815584853617386394@localhost>","date":"2025-10-06T13:56:43","subject":"Re: [RFC PATCH v1 6/7] libcamera: camera: Add `applyControls()`","submitter":{"id":184,"url":"https://patchwork.libcamera.org/api/people/184/","name":"Stefan Klug","email":"stefan.klug@ideasonboard.com"},"content":"Hi Barnabás,\n\nThank you for the patch.\n\nQuoting Barnabás Pőcze (2025-09-24 14:47:11)\n> Add `Camera::applyControls()` whose purpose is to apply controls as soon\n> as possible, without going through `Request::controls()`.\n> \n> A new virtual function `PipelineHandler::applyControlsDevice()` is provided\n> for pipeline handler to implement fast-tracked application of controls. If\n> the pipeline handler does not implement that functionality, or it fails,\n> then a fallback mechanism is used. The controls will be saved for later,\n> and they will be merged into the control list of the next request sent to\n> the pipeline handler (`Camera::Private::waitingRequests_`).\n> \n> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.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                      |  53 +++++++++\n>  src/libcamera/pipeline_handler.cpp            | 110 +++++++++++++++++-\n>  5 files changed, 171 insertions(+), 1 deletion(-)\n> \n> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> index b24a29740..94f940496 100644\n> --- a/include/libcamera/camera.h\n> +++ b/include/libcamera/camera.h\n> @@ -147,6 +147,7 @@ public:\n>  \n>         std::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n>         int queueRequest(Request *request);\n> +       int applyControls(ControlList &&controls);\n>  \n>         int start(const ControlList *controls = nullptr);\n>         int stop();\n> diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h\n> index 0f50c14a5..5a77298b3 100644\n> --- a/include/libcamera/internal/camera.h\n> +++ b/include/libcamera/internal/camera.h\n> @@ -38,6 +38,7 @@ public:\n>  \n>         std::list<Request *> queuedRequests_;\n>         std::deque<Request *> waitingRequests_;\n> +       ControlList pendingControls_;\n>         ControlInfoMap controlInfo_;\n>         ControlList properties_;\n>  \n> diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h\n> index e89d6a33e..16ff54bab 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>         void registerRequest(Request *request);\n>         void queueRequest(Request *request);\n> +       int applyControls(Camera *camera, ControlList &&controls);\n>  \n>         bool completeBuffer(Request *request, FrameBuffer *buffer);\n>         void completeRequest(Request *request);\n> @@ -75,6 +76,12 @@ protected:\n>         void hotplugMediaDevice(MediaDevice *media);\n>  \n>         virtual int queueRequestDevice(Camera *camera, Request *request) = 0;\n> +\n> +       virtual int applyControlsDevice([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList &controls)\n> +       {\n> +               return -EOPNOTSUPP;\n> +       }\n> +\n>         virtual void stopDevice(Camera *camera) = 0;\n>  \n>         virtual bool acquireDevice(Camera *camera);\n> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> index 2e1e146a2..778445e36 100644\n> --- a/src/libcamera/camera.cpp\n> +++ b/src/libcamera/camera.cpp\n> @@ -637,6 +637,14 @@ Camera::Private::~Private()\n>   * queued requests was reached.\n>   */\n>  \n> +/**\n> + * \\var Camera::Private::pendingControls_\n> + * \\brief The list of pending controls\n> + *\n> + * This list tracks the controls that need to be applied to the device with the\n> + * next request going to PipelineHandler::queueRequestDevice().\n> + */\n> +\n>  /**\n>   * \\var Camera::Private::controlInfo_\n>   * \\brief The set of controls supported by the camera\n> @@ -1374,6 +1382,51 @@ int Camera::queueRequest(Request *request)\n>         return 0;\n>  }\n>  \n> +/**\n> + * \\brief Apply controls immediately\n> + * \\param[in] controls The list of controls to apply\n> + *\n> + * This function tries to ensure that the controls in \\a controls are applied\n> + * to the camera as soon as possible.\n> + *\n> + * The exact guarantees are camera dependent, but it is guaranteed that the\n> + * controls will be applied no later than if they were part of the next \\ref Request \"request\"\n> + * that the application \\ref Camera::queueRequest() \"queues\".\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::applyControls(ControlList &&controls)\n> +{\n> +       Private *const d = _d();\n> +\n> +       /*\n> +        * \\todo What to do if not running? Should it be stored and merged into the \"start\" `ControlList`?\n> +        *       If yes, should that list of pending controls be overwritten/updated when stopped?\n> +        *       And should it be cleared in `Camera::release()`?\n\nTo keep things simple I would keep start() and applyControls() separate.\nAlthough it seems to be tempting to have applyControls as single entry\npoint for simple usecases. Oh my :-)\n\n> +        */\n> +\n> +       int ret = d->isAccessAllowed(Private::CameraRunning);\n> +       if (ret < 0)\n> +               return ret;\n> +\n> +       if (controls.empty())\n> +               return 0;\n> +\n> +       patchControlList(controls);\n> +\n> +       /*\n> +        * \\todo Or `ConnectionTypeBlocking` to get the return value?\n> +        */\n> +       d->pipe_->invokeMethod(&PipelineHandler::applyControls, ConnectionTypeQueued, this, std::move(controls));\n> +\n> +       return 0;\n> +}\n> +\n>  /**\n>   * \\brief Start capture from camera\n>   * \\param[in] controls Controls to be applied before starting the Camera\n> diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> index d0d3fbc79..99bb2193f 100644\n> --- a/src/libcamera/pipeline_handler.cpp\n> +++ b/src/libcamera/pipeline_handler.cpp\n> @@ -387,6 +387,12 @@ void PipelineHandler::stop(Camera *camera)\n>         ASSERT(data->queuedRequests_.empty());\n>         ASSERT(data->waitingRequests_.empty());\n>  \n> +       /*\n> +        * \\todo Or not? This probably needs discussions regarding\n> +        * the expected state of controls after a stop-start sequence.\n> +        */\n> +       data->pendingControls_.clear();\n> +\n>         data->requestSequence_ = 0;\n>  }\n>  \n> @@ -466,6 +472,54 @@ void PipelineHandler::queueRequest(Request *request)\n>         request->_d()->prepare(300ms);\n>  }\n>  \n> +/**\n> + * \\brief Apply controls immediately\n> + * \\param[in] camera The camera\n> + * \\param[in] controls The controls to apply\n> + *\n> + * This function tries to apply \\a controls immediately to the device by\n> + * calling applyControlsDevice(). 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 request submitted to the pipeline handler.\n> + *\n> + * This function also ensures that requests in Camera::Private::waitingRequests_\n> + * will not override the fast-tracked controls.\n> + *\n> + * \\context This function is called from the CameraManager thread.\n> + */\n> +int PipelineHandler::applyControls(Camera *camera, ControlList &&controls)\n> +{\n> +       Camera::Private *data = camera->_d();\n> +       int res = applyControlsDevice(camera, controls);\n> +\n> +       /*\n> +        * Prevent later requests from overriding the fast-tracked controls.\n> +        * \\todo This is unfortunately slow.\n\nI thought about that for a while. I think we will have enough issues\ndefining the behavior for inter requests controls in cases with queue\nunderruns. My take would be to simply document that as undefined/racy\nbehavior and instruct the user to either use applyControls() or\nqueueRequest() for the same control, but not both. This keeps the code\nand path simple without many downsides.\n\n> +        */\n> +       for (const auto &[k, v] : controls) {\n> +               for (Request *r : data->waitingRequests_)\n> +                       r->controls().erase(k);\n> +       }\n> +\n> +       if (res < 0) {\n> +               /*\n> +                * \\todo Always fall back or only if EOPNOTSUPP is returned?\n> +                *       Possibly there could be some way for the user to influence\n> +                *       the fallback logic (e.g. `flags` argument for `Camera::applyControls()`).\n> +                */\n> +               if (res != -EOPNOTSUPP)\n> +                       LOG(Pipeline, Debug) << \"Fast tracking controls failed: \" << res;\n> +\n> +               /*\n> +                * Fall back to adding the controls to the next request that enters the\n> +                * pipeline handler. See PipelineHandler::doQueueRequest().\n> +                */\n> +               data->pendingControls_.merge(std::move(controls), ControlList::MergePolicy::OverwriteExisting);\n> +       }\n\nYou could also move this code into the default applyControlsDevice()\nimplementation. Then a pipeline handler can either use the default or\nimplement it's own. No need to handle a mixture of both.\n\n> +\n> +       return 0;\n> +}\n> +\n>  /**\n>   * \\brief Queue one requests to the device\n>   */\n> @@ -484,9 +538,49 @@ void PipelineHandler::doQueueRequest(Request *request)\n>                 return;\n>         }\n>  \n> +       if (!data->pendingControls_.empty()) {\n> +               /*\n> +                * Note that `ControlList::MergePolicy::KeepExisting` is used. This is\n> +                * needed to ensure that if `request` is newer than pendingControls_,\n> +                * then its controls take precedence.\n> +                *\n> +                * For older requests, `PipelineHandler::applyControls()` has already removed\n> +                * the controls that should be overridden.\n> +                *\n> +                * \\todo How to handle (if at all) conflicting controls?\n\nAs noted above I don't think we should bother too much :-)\n\n> +                * \\todo How to handle failure of `queueRequestDevice()`?\n> +                *       After the merge it becomes impossible to retrieve the pending controls\n> +                *       from `request->controls()`. Making a copy in such a hot-path is not ideal.\n\nThat is correct. But I have a hard time to come up with a use case where\nthis is problematic. And controls is reset internally by\nrequest->reuse() so I think this is acceptable.\n\n> +                */\n> +               request->controls().merge(std::move(data->pendingControls_), ControlList::MergePolicy::KeepExisting);\n> +               data->pendingControls_.clear();\n> +       }\n> +\n>         int ret = queueRequestDevice(camera, request);\n> -       if (ret)\n> +       if (ret) {\n> +               /*\n> +                * \\todo What to do now?\n> +                *\n> +                * The original `pendingControls_` is not easily recoverable.\n> +                *\n> +                * Initially\n> +                *     request->controls() = A u B\n> +                *     pendingControls_ = A' u C\n> +                *\n> +                * then after the above merge\n> +                *     request->controls() = A u B u C\n> +                *     pendingControls_ = A'\n\nIs that still the case? As you cleared pendingControls_ before.\n\n> +                *\n> +                * so recovering `pendingControls_` without a copy of at least\n> +                * `B` or `C` does not appear easy.\n> +                *\n> +                * But sometimes recovering is not even desirable: assume that the controls\n> +                * in `pendingControls_` causes the failure. In that case keeping them around\n> +                * will cause every single subsequent request to fail.\n\nDo we expect cameras to regularly fail on queueRequest()? I think it is\nok to have \"lost\" the pending controls in this case.\n\nBest regards,\nStefan\n\n> +                */\n> +\n>                 cancelRequest(request);\n> +       }\n>  }\n>  \n>  /**\n> @@ -532,6 +626,20 @@ void PipelineHandler::doQueueRequests(Camera *camera)\n>   * \\return 0 on success or a negative error code otherwise\n>   */\n>  \n> +/**\n> + * \\fn PipelineHandler::applyControlsDevice()\n> + * \\brief Apply controls immediately\n> + * \\param[in] camera The camera\n> + * \\param[in] controls The controls to apply\n> + *\n> + * This function applies \\a controls to \\a camera immediately.\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> -- \n> 2.51.0\n>","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 2A825BF415\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  6 Oct 2025 13:56:51 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id C3B726B5F3;\n\tMon,  6 Oct 2025 15:56:49 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E892C6936E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  6 Oct 2025 15:56:46 +0200 (CEST)","from ideasonboard.com (unknown\n\t[IPv6:2a00:6020:448c:6c00:2e2:f331:f320:caae])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 51C821741; \n\tMon,  6 Oct 2025 15:55:14 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"LE2Tmv+U\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1759758914;\n\tbh=bUBUCJpUWeyBPMSnzMgRCBMW5UbeUi0lFwKBBE7JIRc=;\n\th=In-Reply-To:References:Subject:From:To:Date:From;\n\tb=LE2Tmv+UtYw0DJNhCdW9I8VRnSmmQhOVljPG0g84OV4/eqdjoHSJ6En81HvKLrpvr\n\tD+Gecf+ZoMkdthgsYjZT3q7s9hOmw53CwKIfEPdikt38AYjtZ6cU8dZ94kGDBflG9k\n\tcHyqQgltTObw/H1Zw7ELOmrdCKI59lVDO1rIjGDc=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20250924124713.3361707-7-barnabas.pocze@ideasonboard.com>","References":"<20250924124713.3361707-1-barnabas.pocze@ideasonboard.com>\n\t<20250924124713.3361707-7-barnabas.pocze@ideasonboard.com>","Subject":"Re: [RFC PATCH v1 6/7] libcamera: camera: Add `applyControls()`","From":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>, \n\tlibcamera-devel@lists.libcamera.org","Date":"Mon, 06 Oct 2025 15:56:43 +0200","Message-ID":"<175975900382.3214037.5815584853617386394@localhost>","User-Agent":"alot/0.12.dev8+g2c003385c862.d20250602","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>"}},{"id":36173,"web_url":"https://patchwork.libcamera.org/comment/36173/","msgid":"<CAHW6GYLMG7QFjFyAT6BBgNgtnXsQWwJZheSRCmEocR8XO6Uz8A@mail.gmail.com>","date":"2025-10-08T08:41:37","subject":"Re: [RFC PATCH v1 6/7] libcamera: camera: Add `applyControls()`","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/people/42/","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"content":"Hi again\n\nOn Mon, 6 Oct 2025 at 14:56, Stefan Klug <stefan.klug@ideasonboard.com> wrote:\n>\n> Hi Barnabás,\n>\n> Thank you for the patch.\n>\n> Quoting Barnabás Pőcze (2025-09-24 14:47:11)\n> > Add `Camera::applyControls()` whose purpose is to apply controls as soon\n> > as possible, without going through `Request::controls()`.\n> >\n> > A new virtual function `PipelineHandler::applyControlsDevice()` is provided\n> > for pipeline handler to implement fast-tracked application of controls. If\n> > the pipeline handler does not implement that functionality, or it fails,\n> > then a fallback mechanism is used. The controls will be saved for later,\n> > and they will be merged into the control list of the next request sent to\n> > the pipeline handler (`Camera::Private::waitingRequests_`).\n> >\n> > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.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                      |  53 +++++++++\n> >  src/libcamera/pipeline_handler.cpp            | 110 +++++++++++++++++-\n> >  5 files changed, 171 insertions(+), 1 deletion(-)\n> >\n> > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > index b24a29740..94f940496 100644\n> > --- a/include/libcamera/camera.h\n> > +++ b/include/libcamera/camera.h\n> > @@ -147,6 +147,7 @@ public:\n> >\n> >         std::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n> >         int queueRequest(Request *request);\n> > +       int applyControls(ControlList &&controls);\n> >\n> >         int start(const ControlList *controls = nullptr);\n> >         int stop();\n> > diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h\n> > index 0f50c14a5..5a77298b3 100644\n> > --- a/include/libcamera/internal/camera.h\n> > +++ b/include/libcamera/internal/camera.h\n> > @@ -38,6 +38,7 @@ public:\n> >\n> >         std::list<Request *> queuedRequests_;\n> >         std::deque<Request *> waitingRequests_;\n> > +       ControlList pendingControls_;\n> >         ControlInfoMap controlInfo_;\n> >         ControlList properties_;\n> >\n> > diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h\n> > index e89d6a33e..16ff54bab 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> >         void registerRequest(Request *request);\n> >         void queueRequest(Request *request);\n> > +       int applyControls(Camera *camera, ControlList &&controls);\n> >\n> >         bool completeBuffer(Request *request, FrameBuffer *buffer);\n> >         void completeRequest(Request *request);\n> > @@ -75,6 +76,12 @@ protected:\n> >         void hotplugMediaDevice(MediaDevice *media);\n> >\n> >         virtual int queueRequestDevice(Camera *camera, Request *request) = 0;\n> > +\n> > +       virtual int applyControlsDevice([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList &controls)\n> > +       {\n> > +               return -EOPNOTSUPP;\n> > +       }\n> > +\n> >         virtual void stopDevice(Camera *camera) = 0;\n> >\n> >         virtual bool acquireDevice(Camera *camera);\n> > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > index 2e1e146a2..778445e36 100644\n> > --- a/src/libcamera/camera.cpp\n> > +++ b/src/libcamera/camera.cpp\n> > @@ -637,6 +637,14 @@ Camera::Private::~Private()\n> >   * queued requests was reached.\n> >   */\n> >\n> > +/**\n> > + * \\var Camera::Private::pendingControls_\n> > + * \\brief The list of pending controls\n> > + *\n> > + * This list tracks the controls that need to be applied to the device with the\n> > + * next request going to PipelineHandler::queueRequestDevice().\n> > + */\n> > +\n> >  /**\n> >   * \\var Camera::Private::controlInfo_\n> >   * \\brief The set of controls supported by the camera\n> > @@ -1374,6 +1382,51 @@ int Camera::queueRequest(Request *request)\n> >         return 0;\n> >  }\n> >\n> > +/**\n> > + * \\brief Apply controls immediately\n> > + * \\param[in] controls The list of controls to apply\n> > + *\n> > + * This function tries to ensure that the controls in \\a controls are applied\n> > + * to the camera as soon as possible.\n> > + *\n> > + * The exact guarantees are camera dependent, but it is guaranteed that the\n> > + * controls will be applied no later than if they were part of the next \\ref Request \"request\"\n> > + * that the application \\ref Camera::queueRequest() \"queues\".\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::applyControls(ControlList &&controls)\n> > +{\n> > +       Private *const d = _d();\n> > +\n> > +       /*\n> > +        * \\todo What to do if not running? Should it be stored and merged into the \"start\" `ControlList`?\n> > +        *       If yes, should that list of pending controls be overwritten/updated when stopped?\n> > +        *       And should it be cleared in `Camera::release()`?\n>\n> To keep things simple I would keep start() and applyControls() separate.\n> Although it seems to be tempting to have applyControls as single entry\n> point for simple usecases. Oh my :-)\n>\n> > +        */\n> > +\n> > +       int ret = d->isAccessAllowed(Private::CameraRunning);\n> > +       if (ret < 0)\n> > +               return ret;\n> > +\n> > +       if (controls.empty())\n> > +               return 0;\n> > +\n> > +       patchControlList(controls);\n> > +\n> > +       /*\n> > +        * \\todo Or `ConnectionTypeBlocking` to get the return value?\n> > +        */\n> > +       d->pipe_->invokeMethod(&PipelineHandler::applyControls, ConnectionTypeQueued, this, std::move(controls));\n> > +\n> > +       return 0;\n> > +}\n> > +\n> >  /**\n> >   * \\brief Start capture from camera\n> >   * \\param[in] controls Controls to be applied before starting the Camera\n> > diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> > index d0d3fbc79..99bb2193f 100644\n> > --- a/src/libcamera/pipeline_handler.cpp\n> > +++ b/src/libcamera/pipeline_handler.cpp\n> > @@ -387,6 +387,12 @@ void PipelineHandler::stop(Camera *camera)\n> >         ASSERT(data->queuedRequests_.empty());\n> >         ASSERT(data->waitingRequests_.empty());\n> >\n> > +       /*\n> > +        * \\todo Or not? This probably needs discussions regarding\n> > +        * the expected state of controls after a stop-start sequence.\n> > +        */\n> > +       data->pendingControls_.clear();\n> > +\n> >         data->requestSequence_ = 0;\n> >  }\n> >\n> > @@ -466,6 +472,54 @@ void PipelineHandler::queueRequest(Request *request)\n> >         request->_d()->prepare(300ms);\n> >  }\n> >\n> > +/**\n> > + * \\brief Apply controls immediately\n> > + * \\param[in] camera The camera\n> > + * \\param[in] controls The controls to apply\n> > + *\n> > + * This function tries to apply \\a controls immediately to the device by\n> > + * calling applyControlsDevice(). 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 request submitted to the pipeline handler.\n> > + *\n> > + * This function also ensures that requests in Camera::Private::waitingRequests_\n> > + * will not override the fast-tracked controls.\n> > + *\n> > + * \\context This function is called from the CameraManager thread.\n> > + */\n> > +int PipelineHandler::applyControls(Camera *camera, ControlList &&controls)\n> > +{\n> > +       Camera::Private *data = camera->_d();\n> > +       int res = applyControlsDevice(camera, controls);\n> > +\n> > +       /*\n> > +        * Prevent later requests from overriding the fast-tracked controls.\n> > +        * \\todo This is unfortunately slow.\n>\n> I thought about that for a while. I think we will have enough issues\n> defining the behavior for inter requests controls in cases with queue\n> underruns. My take would be to simply document that as undefined/racy\n> behavior and instruct the user to either use applyControls() or\n> queueRequest() for the same control, but not both. This keeps the code\n> and path simple without many downsides.\n\nThis brings to mind another related discussion we've had over the\nyears as to whether the controls in a request are expected to be\napplied exactly in time for that request, or not.\n\nPersonally I've never been a big fan of that because (a) we can't\nguarantee to achieve it, (b) it relies on having \"enough\" requests\nalready in the queue so that those controls can be grabbed early\nenough. All of which means the controls take longer to apply, and you\nhave to check what control list was applied on that request (when it\ncompletes) anyway. But as part of our per-frame-control work, we did\nimplement this scheme too.\n\nHowever, it leads me to the thought that the controls in a request\n_could_ behave in this way, and then the controls in the external\ncontrol list queue are applied \"as soon as possible\". We Raspberry Pi\nfolks would only use the external queue, as that has the behaviour we\nwant. Other folks could use the controls in the requests if they\nprefer. Or is this all over-complicated?\n\nI don't think we need to worry too much about merging control lists\nfrom the two sources. I can't imagine any sensible use case where an\napplication might put the same control into both places - it feels a\ntotally confused thing to be doing. It would be fine to flag it as an\nerror and choose any arbitrary outcome for it.\n\nDavid\n\n>\n> > +        */\n> > +       for (const auto &[k, v] : controls) {\n> > +               for (Request *r : data->waitingRequests_)\n> > +                       r->controls().erase(k);\n> > +       }\n> > +\n> > +       if (res < 0) {\n> > +               /*\n> > +                * \\todo Always fall back or only if EOPNOTSUPP is returned?\n> > +                *       Possibly there could be some way for the user to influence\n> > +                *       the fallback logic (e.g. `flags` argument for `Camera::applyControls()`).\n> > +                */\n> > +               if (res != -EOPNOTSUPP)\n> > +                       LOG(Pipeline, Debug) << \"Fast tracking controls failed: \" << res;\n> > +\n> > +               /*\n> > +                * Fall back to adding the controls to the next request that enters the\n> > +                * pipeline handler. See PipelineHandler::doQueueRequest().\n> > +                */\n> > +               data->pendingControls_.merge(std::move(controls), ControlList::MergePolicy::OverwriteExisting);\n> > +       }\n>\n> You could also move this code into the default applyControlsDevice()\n> implementation. Then a pipeline handler can either use the default or\n> implement it's own. No need to handle a mixture of both.\n>\n> > +\n> > +       return 0;\n> > +}\n> > +\n> >  /**\n> >   * \\brief Queue one requests to the device\n> >   */\n> > @@ -484,9 +538,49 @@ void PipelineHandler::doQueueRequest(Request *request)\n> >                 return;\n> >         }\n> >\n> > +       if (!data->pendingControls_.empty()) {\n> > +               /*\n> > +                * Note that `ControlList::MergePolicy::KeepExisting` is used. This is\n> > +                * needed to ensure that if `request` is newer than pendingControls_,\n> > +                * then its controls take precedence.\n> > +                *\n> > +                * For older requests, `PipelineHandler::applyControls()` has already removed\n> > +                * the controls that should be overridden.\n> > +                *\n> > +                * \\todo How to handle (if at all) conflicting controls?\n>\n> As noted above I don't think we should bother too much :-)\n>\n> > +                * \\todo How to handle failure of `queueRequestDevice()`?\n> > +                *       After the merge it becomes impossible to retrieve the pending controls\n> > +                *       from `request->controls()`. Making a copy in such a hot-path is not ideal.\n>\n> That is correct. But I have a hard time to come up with a use case where\n> this is problematic. And controls is reset internally by\n> request->reuse() so I think this is acceptable.\n>\n> > +                */\n> > +               request->controls().merge(std::move(data->pendingControls_), ControlList::MergePolicy::KeepExisting);\n> > +               data->pendingControls_.clear();\n> > +       }\n> > +\n> >         int ret = queueRequestDevice(camera, request);\n> > -       if (ret)\n> > +       if (ret) {\n> > +               /*\n> > +                * \\todo What to do now?\n> > +                *\n> > +                * The original `pendingControls_` is not easily recoverable.\n> > +                *\n> > +                * Initially\n> > +                *     request->controls() = A u B\n> > +                *     pendingControls_ = A' u C\n> > +                *\n> > +                * then after the above merge\n> > +                *     request->controls() = A u B u C\n> > +                *     pendingControls_ = A'\n>\n> Is that still the case? As you cleared pendingControls_ before.\n>\n> > +                *\n> > +                * so recovering `pendingControls_` without a copy of at least\n> > +                * `B` or `C` does not appear easy.\n> > +                *\n> > +                * But sometimes recovering is not even desirable: assume that the controls\n> > +                * in `pendingControls_` causes the failure. In that case keeping them around\n> > +                * will cause every single subsequent request to fail.\n>\n> Do we expect cameras to regularly fail on queueRequest()? I think it is\n> ok to have \"lost\" the pending controls in this case.\n>\n> Best regards,\n> Stefan\n>\n> > +                */\n> > +\n> >                 cancelRequest(request);\n> > +       }\n> >  }\n> >\n> >  /**\n> > @@ -532,6 +626,20 @@ void PipelineHandler::doQueueRequests(Camera *camera)\n> >   * \\return 0 on success or a negative error code otherwise\n> >   */\n> >\n> > +/**\n> > + * \\fn PipelineHandler::applyControlsDevice()\n> > + * \\brief Apply controls immediately\n> > + * \\param[in] camera The camera\n> > + * \\param[in] controls The controls to apply\n> > + *\n> > + * This function applies \\a controls to \\a camera immediately.\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> > --\n> > 2.51.0\n> >","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 72B32C324C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  8 Oct 2025 08:41:54 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 438036B5AA;\n\tWed,  8 Oct 2025 10:41:53 +0200 (CEST)","from mail-qk1-x72d.google.com (mail-qk1-x72d.google.com\n\t[IPv6:2607:f8b0:4864:20::72d])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4F0286B5A2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  8 Oct 2025 10:41:50 +0200 (CEST)","by mail-qk1-x72d.google.com with SMTP id\n\taf79cd13be357-856701dc22aso743204485a.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 08 Oct 2025 01:41:50 -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=\"rzkRB52h\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1759912909; x=1760517709;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:cc:to:subject:message-id:date:from\n\t:in-reply-to:references:mime-version:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=bNL77zM3xCDDLFXBwaBD7S28OCmGEBCtEnhHewRD73E=;\n\tb=rzkRB52hGBWmYOPi7F/qAimJ56nJ2yHr6g6eXYRexW+p6v5n4uBPr+GqWV46X3xh4o\n\tOd5ZwXf3h1pkzGh09hkfeVMzXbS0ITsOHM9BtFD3YLZrzzeagVxYKAIZ7lz5Ar2FmWUP\n\tTZS5pz4iQaFOQCpuqxelR6Au9LYhq41nq3WgAn0CdVXvv4sOR+YWTFSYHDbTtrcNbN/f\n\tI4uXu7meXZ+Gmsw1BqDM24Gv/So/cvNjPMfd//q6hh/gF5fvjNw0DmQI2X43zc9Uzqyb\n\tAx5rA4TURhLTyaz6gI4Oi/pg9oASQfvi7KdPWalz2zQRnYVQkm2Hh6PzMZt7PiM01J3c\n\tVWTA==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1759912909; x=1760517709;\n\th=content-transfer-encoding:cc:to:subject:message-id:date:from\n\t:in-reply-to:references:mime-version:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=bNL77zM3xCDDLFXBwaBD7S28OCmGEBCtEnhHewRD73E=;\n\tb=b/s3gUbCfMiHHdMCzyrYqUzG+eNMvtHalR1wNzHPaD8WnTEJmLRVxnsC6h7w/DBUZF\n\tZhLr1dEK70QsgnQfYFdxdSYSpI+9IblfaY0Cx8V3siR69FcTQ0SpXEYZhYt6BgAyQNn0\n\tjM5a6W3/vjEN4wYDNpQbrpr6i3QhiISYowJRF9l7VyR37nhR5jBw1jTEGnbZK7kct9xu\n\tJNojeIgpo59BT2DiNNy40vDdvi7eRuo/2jGpVrcg3ssGuKg9XXx8QL+RtC4YzCj6Aw18\n\thqY9H8+KOf24qHg3ffpRwCo6iksXFWJnNotCRRNdhpqJLfPKIDFvWpF5wPPScX449mTW\n\tQs1g==","X-Forwarded-Encrypted":"i=1;\n\tAJvYcCWQLPrnnCSJVLRJRvxSvMACauIxCaIJmNMQBwmC0TAbiuhmkwcfSIYg30rf88CQsRLktNmeUcL9YwmZ5n+ALNE=@lists.libcamera.org","X-Gm-Message-State":"AOJu0YylbgJkiflB37A75bSY7qx8ywxVvzQ02iVjhnmznf8DQAOglvwv\n\tP5Cgq9anAH/RXP8s6wh1B5j1LZlZtu8ceiKokFh0rAvhtCanezbg4pR/c8mNnp/mbuLLYYswWWT\n\t6qZiMwgVksnPhec6rGX6vdSj1M+yReT0+NnZJC2jAlxjdNg5cULk/","X-Gm-Gg":"ASbGnctwPM0tbsC2wp9E2ZQQwreMtU9r9QVkWa+GnqgAbGwl4K0KwaypcEwGL5l3gqY\n\t2AEMkvdgMDwvv6OdcC4o5NPYn0/rDPsTWoUlQOkfyEwQNh/eMRrbIbVtY+gzn2X8wgg1km4IVKp\n\tg9o3xQf7tx247tGqPCBkAqmMwgmDMHRXbDXeHubh+C3NS3yqHlZOaro+5V5mTJGFHVfltOsCxEy\n\tkuS6GhGiENpc8M83fv7SWN3+eWcW4vOQnwcCbgw6LLH9gJMqnUF54dd+yONAwTQ1Umtrx6WeQ==","X-Google-Smtp-Source":"AGHT+IGYBYXUWYPYpXy4b8DZ3N9xUyU9D30sKButZdm4EwuZLqw3abVgQV1FfMC3i3DyuZ8XXebuiIxO1Q3d3C6noBM=","X-Received":"by 2002:a05:620a:1a13:b0:85c:d089:41d8 with SMTP id\n\taf79cd13be357-883504a9b03mr474505285a.23.1759912908836;\n\tWed, 08 Oct 2025 01:41:48 -0700 (PDT)","MIME-Version":"1.0","References":"<20250924124713.3361707-1-barnabas.pocze@ideasonboard.com>\n\t<20250924124713.3361707-7-barnabas.pocze@ideasonboard.com>\n\t<175975900382.3214037.5815584853617386394@localhost>","In-Reply-To":"<175975900382.3214037.5815584853617386394@localhost>","From":"David Plowman <david.plowman@raspberrypi.com>","Date":"Wed, 8 Oct 2025 09:41:37 +0100","X-Gm-Features":"AS18NWBeX1MWaSpR1SGDOwUBlgmrhAmwrBPYMZsFeB4RQsajrg0UhsKUKgDeumE","Message-ID":"<CAHW6GYLMG7QFjFyAT6BBgNgtnXsQWwJZheSRCmEocR8XO6Uz8A@mail.gmail.com>","Subject":"Re: [RFC PATCH v1 6/7] libcamera: camera: Add `applyControls()`","To":"Stefan Klug <stefan.klug@ideasonboard.com>","Cc":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>, \n\tlibcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"UTF-8\"","Content-Transfer-Encoding":"quoted-printable","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>"}},{"id":38254,"web_url":"https://patchwork.libcamera.org/comment/38254/","msgid":"<CAHW6GYLJWKUxNS78a79pqg0XW9_4r_OHGzW=rASSJH3x9tcRtg@mail.gmail.com>","date":"2026-02-19T14:14:47","subject":"Re: [RFC PATCH v1 6/7] libcamera: camera: Add `applyControls()`","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/people/42/","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"content":"Hi everyone\n\nI was wondering whether we might restart this discussion as it's all\ngone a bit quiet. To recap briefly:\n\nFirstly, I'm very keen to be able to apply controls independently of\nthe request queue.\n\nI also like the idea of a queue of ControlLists, so that you could\nqueue up multiple ControlLists and they would be applied on\nback-to-back frames. (Exposure bracketing being the most obvious use\ncase.)\n\nI would want something (counters, cookies, whatever...) to identify\nthe first completed request that had applied each of these\nControlLists.\n\nI'm aware that the existence of applyControlsDevice() means we (RPi)\ncould implement just such a queue for ourselves in our PH. Though I\nimagine we might want to define the behaviour more precisely, for\napplication portability. (And possibly queueControls becomes a better\nname than applyControls.)\n\nMost of the other theoretical concerns, e,g, the control value being\noverwritten by a later request, a request failing etc. were, I\nthought, not things that would be a genuine concern in a debugged and\nreal application. I think that mostly echoes what Stefan said? (For\ninstance, why would an application be trying to set the same control\nin different ways? If they do, and mess it up, that's their problem.)\n\nI thought it might be \"Nice\" to get some of this settled at, if not\nbefore, the next libcamera workshop!\n\nAny opinions?\n\nThanks\nDavid\n\nOn Wed, 8 Oct 2025 at 09:41, David Plowman\n<david.plowman@raspberrypi.com> wrote:\n>\n> Hi again\n>\n> On Mon, 6 Oct 2025 at 14:56, Stefan Klug <stefan.klug@ideasonboard.com> wrote:\n> >\n> > Hi Barnabás,\n> >\n> > Thank you for the patch.\n> >\n> > Quoting Barnabás Pőcze (2025-09-24 14:47:11)\n> > > Add `Camera::applyControls()` whose purpose is to apply controls as soon\n> > > as possible, without going through `Request::controls()`.\n> > >\n> > > A new virtual function `PipelineHandler::applyControlsDevice()` is provided\n> > > for pipeline handler to implement fast-tracked application of controls. If\n> > > the pipeline handler does not implement that functionality, or it fails,\n> > > then a fallback mechanism is used. The controls will be saved for later,\n> > > and they will be merged into the control list of the next request sent to\n> > > the pipeline handler (`Camera::Private::waitingRequests_`).\n> > >\n> > > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.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                      |  53 +++++++++\n> > >  src/libcamera/pipeline_handler.cpp            | 110 +++++++++++++++++-\n> > >  5 files changed, 171 insertions(+), 1 deletion(-)\n> > >\n> > > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > > index b24a29740..94f940496 100644\n> > > --- a/include/libcamera/camera.h\n> > > +++ b/include/libcamera/camera.h\n> > > @@ -147,6 +147,7 @@ public:\n> > >\n> > >         std::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n> > >         int queueRequest(Request *request);\n> > > +       int applyControls(ControlList &&controls);\n> > >\n> > >         int start(const ControlList *controls = nullptr);\n> > >         int stop();\n> > > diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h\n> > > index 0f50c14a5..5a77298b3 100644\n> > > --- a/include/libcamera/internal/camera.h\n> > > +++ b/include/libcamera/internal/camera.h\n> > > @@ -38,6 +38,7 @@ public:\n> > >\n> > >         std::list<Request *> queuedRequests_;\n> > >         std::deque<Request *> waitingRequests_;\n> > > +       ControlList pendingControls_;\n> > >         ControlInfoMap controlInfo_;\n> > >         ControlList properties_;\n> > >\n> > > diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h\n> > > index e89d6a33e..16ff54bab 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> > >         void registerRequest(Request *request);\n> > >         void queueRequest(Request *request);\n> > > +       int applyControls(Camera *camera, ControlList &&controls);\n> > >\n> > >         bool completeBuffer(Request *request, FrameBuffer *buffer);\n> > >         void completeRequest(Request *request);\n> > > @@ -75,6 +76,12 @@ protected:\n> > >         void hotplugMediaDevice(MediaDevice *media);\n> > >\n> > >         virtual int queueRequestDevice(Camera *camera, Request *request) = 0;\n> > > +\n> > > +       virtual int applyControlsDevice([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList &controls)\n> > > +       {\n> > > +               return -EOPNOTSUPP;\n> > > +       }\n> > > +\n> > >         virtual void stopDevice(Camera *camera) = 0;\n> > >\n> > >         virtual bool acquireDevice(Camera *camera);\n> > > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > > index 2e1e146a2..778445e36 100644\n> > > --- a/src/libcamera/camera.cpp\n> > > +++ b/src/libcamera/camera.cpp\n> > > @@ -637,6 +637,14 @@ Camera::Private::~Private()\n> > >   * queued requests was reached.\n> > >   */\n> > >\n> > > +/**\n> > > + * \\var Camera::Private::pendingControls_\n> > > + * \\brief The list of pending controls\n> > > + *\n> > > + * This list tracks the controls that need to be applied to the device with the\n> > > + * next request going to PipelineHandler::queueRequestDevice().\n> > > + */\n> > > +\n> > >  /**\n> > >   * \\var Camera::Private::controlInfo_\n> > >   * \\brief The set of controls supported by the camera\n> > > @@ -1374,6 +1382,51 @@ int Camera::queueRequest(Request *request)\n> > >         return 0;\n> > >  }\n> > >\n> > > +/**\n> > > + * \\brief Apply controls immediately\n> > > + * \\param[in] controls The list of controls to apply\n> > > + *\n> > > + * This function tries to ensure that the controls in \\a controls are applied\n> > > + * to the camera as soon as possible.\n> > > + *\n> > > + * The exact guarantees are camera dependent, but it is guaranteed that the\n> > > + * controls will be applied no later than if they were part of the next \\ref Request \"request\"\n> > > + * that the application \\ref Camera::queueRequest() \"queues\".\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::applyControls(ControlList &&controls)\n> > > +{\n> > > +       Private *const d = _d();\n> > > +\n> > > +       /*\n> > > +        * \\todo What to do if not running? Should it be stored and merged into the \"start\" `ControlList`?\n> > > +        *       If yes, should that list of pending controls be overwritten/updated when stopped?\n> > > +        *       And should it be cleared in `Camera::release()`?\n> >\n> > To keep things simple I would keep start() and applyControls() separate.\n> > Although it seems to be tempting to have applyControls as single entry\n> > point for simple usecases. Oh my :-)\n> >\n> > > +        */\n> > > +\n> > > +       int ret = d->isAccessAllowed(Private::CameraRunning);\n> > > +       if (ret < 0)\n> > > +               return ret;\n> > > +\n> > > +       if (controls.empty())\n> > > +               return 0;\n> > > +\n> > > +       patchControlList(controls);\n> > > +\n> > > +       /*\n> > > +        * \\todo Or `ConnectionTypeBlocking` to get the return value?\n> > > +        */\n> > > +       d->pipe_->invokeMethod(&PipelineHandler::applyControls, ConnectionTypeQueued, this, std::move(controls));\n> > > +\n> > > +       return 0;\n> > > +}\n> > > +\n> > >  /**\n> > >   * \\brief Start capture from camera\n> > >   * \\param[in] controls Controls to be applied before starting the Camera\n> > > diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> > > index d0d3fbc79..99bb2193f 100644\n> > > --- a/src/libcamera/pipeline_handler.cpp\n> > > +++ b/src/libcamera/pipeline_handler.cpp\n> > > @@ -387,6 +387,12 @@ void PipelineHandler::stop(Camera *camera)\n> > >         ASSERT(data->queuedRequests_.empty());\n> > >         ASSERT(data->waitingRequests_.empty());\n> > >\n> > > +       /*\n> > > +        * \\todo Or not? This probably needs discussions regarding\n> > > +        * the expected state of controls after a stop-start sequence.\n> > > +        */\n> > > +       data->pendingControls_.clear();\n> > > +\n> > >         data->requestSequence_ = 0;\n> > >  }\n> > >\n> > > @@ -466,6 +472,54 @@ void PipelineHandler::queueRequest(Request *request)\n> > >         request->_d()->prepare(300ms);\n> > >  }\n> > >\n> > > +/**\n> > > + * \\brief Apply controls immediately\n> > > + * \\param[in] camera The camera\n> > > + * \\param[in] controls The controls to apply\n> > > + *\n> > > + * This function tries to apply \\a controls immediately to the device by\n> > > + * calling applyControlsDevice(). 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 request submitted to the pipeline handler.\n> > > + *\n> > > + * This function also ensures that requests in Camera::Private::waitingRequests_\n> > > + * will not override the fast-tracked controls.\n> > > + *\n> > > + * \\context This function is called from the CameraManager thread.\n> > > + */\n> > > +int PipelineHandler::applyControls(Camera *camera, ControlList &&controls)\n> > > +{\n> > > +       Camera::Private *data = camera->_d();\n> > > +       int res = applyControlsDevice(camera, controls);\n> > > +\n> > > +       /*\n> > > +        * Prevent later requests from overriding the fast-tracked controls.\n> > > +        * \\todo This is unfortunately slow.\n> >\n> > I thought about that for a while. I think we will have enough issues\n> > defining the behavior for inter requests controls in cases with queue\n> > underruns. My take would be to simply document that as undefined/racy\n> > behavior and instruct the user to either use applyControls() or\n> > queueRequest() for the same control, but not both. This keeps the code\n> > and path simple without many downsides.\n>\n> This brings to mind another related discussion we've had over the\n> years as to whether the controls in a request are expected to be\n> applied exactly in time for that request, or not.\n>\n> Personally I've never been a big fan of that because (a) we can't\n> guarantee to achieve it, (b) it relies on having \"enough\" requests\n> already in the queue so that those controls can be grabbed early\n> enough. All of which means the controls take longer to apply, and you\n> have to check what control list was applied on that request (when it\n> completes) anyway. But as part of our per-frame-control work, we did\n> implement this scheme too.\n>\n> However, it leads me to the thought that the controls in a request\n> _could_ behave in this way, and then the controls in the external\n> control list queue are applied \"as soon as possible\". We Raspberry Pi\n> folks would only use the external queue, as that has the behaviour we\n> want. Other folks could use the controls in the requests if they\n> prefer. Or is this all over-complicated?\n>\n> I don't think we need to worry too much about merging control lists\n> from the two sources. I can't imagine any sensible use case where an\n> application might put the same control into both places - it feels a\n> totally confused thing to be doing. It would be fine to flag it as an\n> error and choose any arbitrary outcome for it.\n>\n> David\n>\n> >\n> > > +        */\n> > > +       for (const auto &[k, v] : controls) {\n> > > +               for (Request *r : data->waitingRequests_)\n> > > +                       r->controls().erase(k);\n> > > +       }\n> > > +\n> > > +       if (res < 0) {\n> > > +               /*\n> > > +                * \\todo Always fall back or only if EOPNOTSUPP is returned?\n> > > +                *       Possibly there could be some way for the user to influence\n> > > +                *       the fallback logic (e.g. `flags` argument for `Camera::applyControls()`).\n> > > +                */\n> > > +               if (res != -EOPNOTSUPP)\n> > > +                       LOG(Pipeline, Debug) << \"Fast tracking controls failed: \" << res;\n> > > +\n> > > +               /*\n> > > +                * Fall back to adding the controls to the next request that enters the\n> > > +                * pipeline handler. See PipelineHandler::doQueueRequest().\n> > > +                */\n> > > +               data->pendingControls_.merge(std::move(controls), ControlList::MergePolicy::OverwriteExisting);\n> > > +       }\n> >\n> > You could also move this code into the default applyControlsDevice()\n> > implementation. Then a pipeline handler can either use the default or\n> > implement it's own. No need to handle a mixture of both.\n> >\n> > > +\n> > > +       return 0;\n> > > +}\n> > > +\n> > >  /**\n> > >   * \\brief Queue one requests to the device\n> > >   */\n> > > @@ -484,9 +538,49 @@ void PipelineHandler::doQueueRequest(Request *request)\n> > >                 return;\n> > >         }\n> > >\n> > > +       if (!data->pendingControls_.empty()) {\n> > > +               /*\n> > > +                * Note that `ControlList::MergePolicy::KeepExisting` is used. This is\n> > > +                * needed to ensure that if `request` is newer than pendingControls_,\n> > > +                * then its controls take precedence.\n> > > +                *\n> > > +                * For older requests, `PipelineHandler::applyControls()` has already removed\n> > > +                * the controls that should be overridden.\n> > > +                *\n> > > +                * \\todo How to handle (if at all) conflicting controls?\n> >\n> > As noted above I don't think we should bother too much :-)\n> >\n> > > +                * \\todo How to handle failure of `queueRequestDevice()`?\n> > > +                *       After the merge it becomes impossible to retrieve the pending controls\n> > > +                *       from `request->controls()`. Making a copy in such a hot-path is not ideal.\n> >\n> > That is correct. But I have a hard time to come up with a use case where\n> > this is problematic. And controls is reset internally by\n> > request->reuse() so I think this is acceptable.\n> >\n> > > +                */\n> > > +               request->controls().merge(std::move(data->pendingControls_), ControlList::MergePolicy::KeepExisting);\n> > > +               data->pendingControls_.clear();\n> > > +       }\n> > > +\n> > >         int ret = queueRequestDevice(camera, request);\n> > > -       if (ret)\n> > > +       if (ret) {\n> > > +               /*\n> > > +                * \\todo What to do now?\n> > > +                *\n> > > +                * The original `pendingControls_` is not easily recoverable.\n> > > +                *\n> > > +                * Initially\n> > > +                *     request->controls() = A u B\n> > > +                *     pendingControls_ = A' u C\n> > > +                *\n> > > +                * then after the above merge\n> > > +                *     request->controls() = A u B u C\n> > > +                *     pendingControls_ = A'\n> >\n> > Is that still the case? As you cleared pendingControls_ before.\n> >\n> > > +                *\n> > > +                * so recovering `pendingControls_` without a copy of at least\n> > > +                * `B` or `C` does not appear easy.\n> > > +                *\n> > > +                * But sometimes recovering is not even desirable: assume that the controls\n> > > +                * in `pendingControls_` causes the failure. In that case keeping them around\n> > > +                * will cause every single subsequent request to fail.\n> >\n> > Do we expect cameras to regularly fail on queueRequest()? I think it is\n> > ok to have \"lost\" the pending controls in this case.\n> >\n> > Best regards,\n> > Stefan\n> >\n> > > +                */\n> > > +\n> > >                 cancelRequest(request);\n> > > +       }\n> > >  }\n> > >\n> > >  /**\n> > > @@ -532,6 +626,20 @@ void PipelineHandler::doQueueRequests(Camera *camera)\n> > >   * \\return 0 on success or a negative error code otherwise\n> > >   */\n> > >\n> > > +/**\n> > > + * \\fn PipelineHandler::applyControlsDevice()\n> > > + * \\brief Apply controls immediately\n> > > + * \\param[in] camera The camera\n> > > + * \\param[in] controls The controls to apply\n> > > + *\n> > > + * This function applies \\a controls to \\a camera immediately.\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> > > --\n> > > 2.51.0\n> > >","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 13970C31E9\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 19 Feb 2026 14:15:03 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 53E096224B;\n\tThu, 19 Feb 2026 15:15:02 +0100 (CET)","from mail-qv1-xf2d.google.com (mail-qv1-xf2d.google.com\n\t[IPv6:2607:f8b0:4864:20::f2d])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id AC888620C9\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 19 Feb 2026 15:14:59 +0100 (CET)","by mail-qv1-xf2d.google.com with SMTP id\n\t6a1803df08f44-89577f866d6so13666646d6.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 19 Feb 2026 06:14:59 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"KcRlJRbv\"; dkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1771510498; cv=none;\n\td=google.com; s=arc-20240605;\n\tb=Bn2mSUNbAvhcUc9wzbpG/oG2t4xFLU8fC8OBEnY3fXeOhLfz6kHL76upex6Z98Mab5\n\tnVZWJoKisG7c1Q9dVpXzHw5FuysUxqWPumcMFAg6XnJqCWfUeuSiPW8k8NBCM3cdGgT5\n\tOj6JAhXTLfFhqVNCHSH8KefdgEVQ9SKLFFetE2Ppb/GxlY/W8fqRnbjiHf8N9w+SBXHg\n\toGd8uvRmMLm/Onc23YBOZBWQKqnfoecHgkT5JsjqZA7JmHXqXw3CUjnKQ4Uv74uIP4C+\n\tHG6d5mC1b4hch1ZhG+WUxp/qe6RCzW/lRiyBvcJ/lmCFRK/qNNYGw1Er9xW6XhaCSjbQ\n\tuNKA==","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com;\n\ts=arc-20240605; \n\th=content-transfer-encoding:cc:to:subject:message-id:date:from\n\t:in-reply-to:references:mime-version:dkim-signature;\n\tbh=zSYhXoas6aKEQ/dJ1pBcbeiWjmBzHSYoxo9FSlKlgS0=;\n\tfh=GVm9bpJXlr8Xlci4BiwhiX+8hacARUQoecEtYy/lO+Q=;\n\tb=GFyRBwq1XEZPpsb17zK4feoCc+Y/KiCnLmk0D6o83nBDDqr3O2OqX2wGx9jqVULQwO\n\tFoAdJhCmDcrbpx8wCLtnEJCZYGr7WE924c4k71syJ01Lg4dOef34KP69sz7b0l6m5WNU\n\tHFPKwLCYY/uozc69gUD9K7B+w72wmJUvjzheF85pGmbsmQW+69PwKh+1nSm4BlZqLpdO\n\tEDiOV1s7ar5df1tnP5NJyh2PWXf4NaLqZ2xZFAX9dARhs4Y51OfjlFIr7L4BG1pURCTI\n\t8u5bymZ3dJRXfNf+AcA4jscz578lN13dMiqqGWXBFfuS32o/rOVmJI+uCCfP2BuI19a0\n\twWwg==; darn=lists.libcamera.org","ARC-Authentication-Results":"i=1; mx.google.com; arc=none","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1771510498; x=1772115298;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:cc:to:subject:message-id:date:from\n\t:in-reply-to:references:mime-version:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=zSYhXoas6aKEQ/dJ1pBcbeiWjmBzHSYoxo9FSlKlgS0=;\n\tb=KcRlJRbvJsNL7kknwud0ykMoSnHn/zzmIrvGjBpJ5cNwN7SDKETw4i7J2c/6wnRP+0\n\tkn6Xyt1YbI1NxqQA55uIIlCO/GG2vJSdhKo3osgxUiSCElhtWg1AKRbkION/n/hvsqFF\n\t+Tp4BfkR2Fqs3GwHWyfmX68Spv+BYPRoMvNnfgCDkeEEzJeV2sEjRRVmBS57EZFpoG5B\n\ti78BivMazVhzTB7bqD1mc+Mny6M1TfkkexHXDMtCwxR3vsYZOuZHztGn8QovDhuykelb\n\trieoD7yZCP3XGxlEoO7V3c2cx9Ysa+zMw0BvBkmCtIbw6/8WFFv4FyHE8Bo1PJwIbZnz\n\t280Q==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1771510498; x=1772115298;\n\th=content-transfer-encoding:cc:to:subject:message-id:date:from\n\t:in-reply-to:references:mime-version:x-gm-gg:x-gm-message-state:from\n\t:to:cc:subject:date:message-id:reply-to;\n\tbh=zSYhXoas6aKEQ/dJ1pBcbeiWjmBzHSYoxo9FSlKlgS0=;\n\tb=a0ywZywIR7msN4M8is3pnHyFZB/gT+8E4LtgxCwyyFYJOYx1sIAvUxTm8cO5Sr6kNB\n\tNM04YkRB8fAmb7QVo7GVLn2834ArYuynqrPpoNgEp/lYqx8yEVoKJmxenp+qlzgcxmw4\n\tnX5FbGzagV7rbnJZlVKcj2LToqKPFYIOBD0+In9EPP8+zK2FQfDvkajT5irGut78GWx+\n\trF6zPk/eUeaVqOSalpOz3iRVqJss8FbLI2O/4ndRlEZPmH34/I6AtgnULpkbetJbZ9jC\n\tfRhPhVCMy329B7ayrFfHOagefEqHip5VluZyVUcMIqZeJN66c9FutcMl6wZq2oGVy+y8\n\tInKg==","X-Forwarded-Encrypted":"i=1;\n\tAJvYcCXbBBKNvf9tEkrsR/iiM417gxnTAmEefcKNb2VyLivvtBZ+K3/WLZh8wrNwYfYCDceiF69NYxLSVPtXAVBSPoI=@lists.libcamera.org","X-Gm-Message-State":"AOJu0YyFTQZ2mHsFkJn+G1lUdG1sb42DRFJPyZl5IpeNBL6zOBwsSsCi\n\txaf1dhQNhLY01fD2UM8oUyH2O6GvjJVRDguXFH2GaVKBELZ23YQa4pNV5JolfXFnsW4s6hrqs4Q\n\tTyINKdKj2knlvkKPUsXbiavkIDMn+THAji7PTWtTPGA==","X-Gm-Gg":"AZuq6aLRkJxF9O/U+shXyPkQwEPgvrmysbYNVoK5948GXQaZpsx3R7mhBP22zamwETV\n\tMIIsjh2oDiFQ+iitwAr28i1q4ZseIPsmhezanb55h8gmY3ovqcLlVEJi7UMGnxqbz/eHwhL9c1F\n\t+0z+pyWn6Xs6luN07nAuVdB16Be5csDXdcTX0U9IyVCan59Tb4zwxA4jnVKVeyWmSPyd0h51pg/\n\tBdD6jhs3YMl57GUgbriAlFS6oOTBYSuujpn5H3ZMG+2ZqxauDfqeBj1VucrJ6g7OyQgJhhyKshw\n\t2/27wmA315Y/oRP9vSpGi97ImWHGBedAiZ8DvtRGh8NVs82cGfmrw3viTi/qVnWZ7th5ZAvtg4Y\n\tzd9w/zNEjVzUoFt5YnwA2Py3oFb2esSui+wo=","X-Received":"by 2002:a05:6214:27ca:b0:895:4d9b:a436 with SMTP id\n\t6a1803df08f44-89965c77c2fmr23798156d6.30.1771510498265;\n\tThu, 19 Feb 2026 06:14:58 -0800 (PST)","MIME-Version":"1.0","References":"<20250924124713.3361707-1-barnabas.pocze@ideasonboard.com>\n\t<20250924124713.3361707-7-barnabas.pocze@ideasonboard.com>\n\t<175975900382.3214037.5815584853617386394@localhost>\n\t<CAHW6GYLMG7QFjFyAT6BBgNgtnXsQWwJZheSRCmEocR8XO6Uz8A@mail.gmail.com>","In-Reply-To":"<CAHW6GYLMG7QFjFyAT6BBgNgtnXsQWwJZheSRCmEocR8XO6Uz8A@mail.gmail.com>","From":"David Plowman <david.plowman@raspberrypi.com>","Date":"Thu, 19 Feb 2026 14:14:47 +0000","X-Gm-Features":"AaiRm51psR-rUiSbJWyHapdH2RDFagW8NpxKW0IjSMI2HFyvce4RwBwDpHDsdmo","Message-ID":"<CAHW6GYLJWKUxNS78a79pqg0XW9_4r_OHGzW=rASSJH3x9tcRtg@mail.gmail.com>","Subject":"Re: [RFC PATCH v1 6/7] libcamera: camera: Add `applyControls()`","To":"Stefan Klug <stefan.klug@ideasonboard.com>","Cc":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>, \n\tlibcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"UTF-8\"","Content-Transfer-Encoding":"quoted-printable","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>"}}]