Show a patch.

GET /api/patches/27101/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 27101,
    "url": "https://patchwork.libcamera.org/api/patches/27101/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/27101/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20260629163017.863145-23-barnabas.pocze@ideasonboard.com>",
    "date": "2026-06-29T16:29:45",
    "name": "[RFC,v1,22/54] libcamera: camera: Add buffer pool",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "bce49f0aa42bc6c48f552498a38194521096f0c3",
    "submitter": {
        "id": 216,
        "url": "https://patchwork.libcamera.org/api/people/216/?format=api",
        "name": "Barnabás Pőcze",
        "email": "barnabas.pocze@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/27101/mbox/",
    "series": [
        {
            "id": 6025,
            "url": "https://patchwork.libcamera.org/api/series/6025/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=6025",
            "date": "2026-06-29T16:29:23",
            "name": "libcamera: Split requests and buffers",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/6025/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/27101/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/27101/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>",
        "X-Original-To": "parsemail@patchwork.libcamera.org",
        "Delivered-To": "parsemail@patchwork.libcamera.org",
        "Received": [
            "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 17712C3261\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 29 Jun 2026 16:31:11 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id E34A965F8B;\n\tMon, 29 Jun 2026 18:31:06 +0200 (CEST)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 09AA565F47\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 29 Jun 2026 18:30:26 +0200 (CEST)",
            "from pb-laptop.local (185.221.140.128.nat.pool.zt.hu\n\t[185.221.140.128])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id D81B28D4\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 29 Jun 2026 18:29:42 +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=\"rkkJFwWP\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1782750582;\n\tbh=YmL2Qa3dHGwYVUaS/614a+cslVnODZwWtg0th56KayI=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=rkkJFwWPHuZx55kQxtvZsmXMbO15lJyijGFUZDVFIGDDyTT2YcPMjoRCth25dcX8U\n\txCRgov8sOlwJRrmBi4i8S23Ba3DVw4pGdEPlfE/vxj5+1dRuwYRYnlSuPop+fpVFO2\n\taVwguBhAXnTh7Ee1IfTYXpzIGOLq03iVOSVMdZ2A=",
        "From": "=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Subject": "[RFC PATCH v1 22/54] libcamera: camera: Add buffer pool",
        "Date": "Mon, 29 Jun 2026 18:29:45 +0200",
        "Message-ID": "<20260629163017.863145-23-barnabas.pocze@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.54.0",
        "In-Reply-To": "<20260629163017.863145-1-barnabas.pocze@ideasonboard.com>",
        "References": "<20260629163017.863145-1-barnabas.pocze@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=UTF-8",
        "Content-Transfer-Encoding": "8bit",
        "X-BeenThere": "libcamera-devel@lists.libcamera.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "<libcamera-devel.lists.libcamera.org>",
        "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>",
        "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>",
        "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>",
        "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>",
        "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Use the recently added `StreamData` to store a set of buffers for each\nstream. Introduce the `Camera::addBuffer()` method that can be used by\nan application to add buffers.\n\nSigned-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n---\n include/libcamera/camera.h                    |  3 +\n include/libcamera/internal/camera.h           | 15 ++++\n include/libcamera/internal/pipeline_handler.h |  5 ++\n src/libcamera/camera.cpp                      | 51 +++++++++++\n src/libcamera/pipeline_handler.cpp            | 88 +++++++++++++++++++\n 5 files changed, 162 insertions(+)",
    "diff": "diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\nindex 443116b588..471975a890 100644\n--- a/include/libcamera/camera.h\n+++ b/include/libcamera/camera.h\n@@ -28,6 +28,7 @@\n \n namespace libcamera {\n \n+class Fence;\n class FrameBuffer;\n class FrameBufferAllocator;\n class PipelineHandler;\n@@ -152,6 +153,8 @@ public:\n \tint start(const ControlList *controls = nullptr);\n \tint stop();\n \n+\tint addBuffer(const Stream *stream, FrameBuffer *buffer, std::unique_ptr<Fence> &&fence = {});\n+\n private:\n \tLIBCAMERA_DISABLE_COPY(Camera)\n \ndiff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h\nindex 43b6a6e4a2..6ba77eb882 100644\n--- a/include/libcamera/internal/camera.h\n+++ b/include/libcamera/internal/camera.h\n@@ -15,14 +15,17 @@\n #include <stdint.h>\n #include <string>\n #include <unordered_map>\n+#include <vector>\n \n #include <libcamera/base/class.h>\n+#include <libcamera/base/event_notifier.h>\n \n #include <libcamera/camera.h>\n \n namespace libcamera {\n \n class CameraControlValidator;\n+class Fence;\n class PipelineHandler;\n class Stream;\n \n@@ -57,6 +60,15 @@ private:\n \n \tstruct StreamData {\n \t\tbool active = false;\n+\t\tstd::vector<FrameBuffer *> buffers;\n+\t};\n+\n+\tstruct PendingFence {\n+\t\tEventNotifier notifier;\n+\t\tconst Stream *stream;\n+\t\tFrameBuffer *buffer;\n+\n+\t\tPendingFence(const Stream *s, FrameBuffer *b);\n \t};\n \n \tbool isAcquired() const;\n@@ -74,11 +86,14 @@ private:\n \tstd::string id_;\n \tstd::set<Stream *> streams_;\n \tstd::unordered_map<const Stream *, StreamData> streamData_;\n+\tstd::list<PendingFence> pendingFences_;\n \n \tbool disconnected_;\n \tstd::atomic<State> state_;\n \n \tstd::unique_ptr<CameraControlValidator> validator_;\n+\n+\tfriend PipelineHandler;\n };\n \n } /* namespace libcamera */\ndiff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h\nindex cc52c6b045..81ec359981 100644\n--- a/include/libcamera/internal/pipeline_handler.h\n+++ b/include/libcamera/internal/pipeline_handler.h\n@@ -27,6 +27,7 @@ class Camera;\n class CameraConfiguration;\n class DeviceEnumerator;\n class DeviceMatch;\n+class Fence;\n class FrameBuffer;\n class MediaDevice;\n class PipelineHandler;\n@@ -69,6 +70,10 @@ public:\n \tvoid completeRequest(Request *request);\n \tvoid cancelRequest(Request *request);\n \n+\tvoid addBuffer(Camera *camera,\n+\t\t       const Stream *stream, FrameBuffer *buffer,\n+\t\t       std::unique_ptr<Fence> &&fence);\n+\n \tstd::string configurationFile(const std::string &subdir,\n \t\t\t\t      const std::string &name,\n \t\t\t\t      bool silent = false) const;\ndiff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\nindex aa5682f9d7..31ab59e0b7 100644\n--- a/src/libcamera/camera.cpp\n+++ b/src/libcamera/camera.cpp\n@@ -27,6 +27,7 @@\n \n #include \"libcamera/internal/camera.h\"\n #include \"libcamera/internal/camera_controls.h\"\n+#include \"libcamera/internal/framebuffer.h\"\n #include \"libcamera/internal/pipeline_handler.h\"\n #include \"libcamera/internal/request.h\"\n \n@@ -746,6 +747,13 @@ void Camera::Private::setState(State state)\n {\n \tstate_.store(state, std::memory_order_release);\n }\n+\n+Camera::Private::PendingFence::PendingFence(const Stream *s, FrameBuffer *b)\n+\t: notifier(b->_d()->fence()->fd().get(), EventNotifier::Read),\n+\t  stream(s),\n+\t  buffer(b)\n+{\n+}\n #endif /* __DOXYGEN_PUBLIC__ */\n \n /**\n@@ -1061,6 +1069,9 @@ int Camera::release()\n \t\td->pipe_->invokeMethod(&PipelineHandler::release,\n \t\t\t\t       ConnectionTypeBlocking, this);\n \n+\t// \\todo clear buffer pool\n+\t// \\todo empty `pendingBuffers_` and report \"cancelled\" fences\n+\n \td->setState(Private::CameraAvailable);\n \n \treturn 0;\n@@ -1448,6 +1459,9 @@ int Camera::start(const ControlList *controls)\n  * This function stops capturing and processing requests immediately. All\n  * pending requests are cancelled and complete synchronously in an error state.\n  *\n+ * All buffers in the camera's buffer pool are returned via the Camera::bufferCompleted\n+ * event synchronously with the \\a request parameter equal to \\a nullptr.\n+ *\n  * \\context This function may be called in any camera state as defined in \\ref\n  * camera_operation, and shall be synchronized by the caller with other\n  * functions that affect the camera state. If called when the camera isn't\n@@ -1486,6 +1500,43 @@ int Camera::stop()\n \treturn 0;\n }\n \n+/**\n+ * \\fn Camera::addBuffer()\n+ * \\brief Add a buffer to buffer pool of the camera\n+ * \\param[in] stream The stream\n+ * \\param[in] buffer The buffer\n+ * \\param[in] fence The fence for \\a buffer\n+ *\n+ * \\context This function may only be called when the camera is\n+ * in the Running state as defined in \\ref camera_operation.\n+ *\n+ * \\return 0 on success or a negative error code otherwise\n+ *\n+ * \\internal\n+ * \\todo Add `addBuffers()` that accepts a list of (stream, buffer, fence) tuples.\n+ */\n+int Camera::addBuffer(const Stream *stream, FrameBuffer *buffer, std::unique_ptr<Fence> &&fence)\n+{\n+\tPrivate *const d = _d();\n+\n+\tint ret = d->isAccessAllowed(Private::CameraRunning);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tauto it = d->streamData_.find(stream);\n+\tif (it == d->streamData_.end() || !it->second.active)\n+\t\treturn -EINVAL;\n+\tif (!buffer)\n+\t\treturn -EINVAL;\n+\tif (buffer->_d()->fence())\n+\t\treturn -EINVAL;\n+\n+\td->pipe_->invokeMethod(&PipelineHandler::addBuffer, ConnectionTypeQueued,\n+\t\t\t       this, stream, buffer, std::move(fence));\n+\n+\treturn 0;\n+}\n+\n /**\n  * \\brief Handle request completion and notify application\n  * \\param[in] request The request that has completed\ndiff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\nindex 99f35d1e42..54ef8296a4 100644\n--- a/src/libcamera/pipeline_handler.cpp\n+++ b/src/libcamera/pipeline_handler.cpp\n@@ -22,6 +22,7 @@\n #include \"libcamera/internal/camera.h\"\n #include \"libcamera/internal/camera_manager.h\"\n #include \"libcamera/internal/device_enumerator.h\"\n+#include \"libcamera/internal/framebuffer.h\"\n #include \"libcamera/internal/media_device.h\"\n #include \"libcamera/internal/request.h\"\n #include \"libcamera/internal/tracepoints.h\"\n@@ -394,6 +395,24 @@ void PipelineHandler::stop(Camera *camera)\n \t\tdoQueueRequest(request);\n \t}\n \n+\tconst auto returnBuffer = [&](FrameBuffer *buffer) {\n+\t\tASSERT(!buffer->_d()->stream_);\n+\t\tbuffer->_d()->cancel();\n+\t\tcamera->bufferCompleted.emit(nullptr, buffer);\n+\t};\n+\n+\tfor (auto &pf : data->pendingFences_)\n+\t\treturnBuffer(pf.buffer);\n+\n+\tdata->pendingFences_.clear();\n+\n+\tfor (auto &[stream, streamData] : data->streamData_) {\n+\t\tfor (FrameBuffer *buffer : streamData.buffers)\n+\t\t\treturnBuffer(buffer);\n+\n+\t\tstreamData.buffers.clear();\n+\t}\n+\n \t/* Make sure no requests are pending. */\n \tASSERT(data->queuedRequests_.empty());\n \tASSERT(data->waitingRequests_.empty());\n@@ -611,6 +630,75 @@ void PipelineHandler::cancelRequest(Request *request)\n \tcompleteRequest(request);\n }\n \n+/**\n+ * \\fn PipelineHandler::addBuffer()\n+ * \\brief Add buffers to the buffer pool of the camera\n+ * \\param[in] camera The camera\n+ * \\param[in] stream The stream of \\a camera\n+ * \\param[in] buffer The buffer\n+ * \\param[in] fence The fence for \\a buffer\n+ *\n+ * \\context This function may only be called from the CameraManager thread.\n+ */\n+void PipelineHandler::addBuffer(Camera *camera,\n+\t\t\t\tconst Stream *stream, FrameBuffer *buffer,\n+\t\t\t\tstd::unique_ptr<Fence> &&fence)\n+{\n+\tCamera::Private *const d = camera->_d();\n+\n+\tauto it = d->streamData_.find(stream);\n+\tASSERT(it != d->streamData_.end());\n+\n+\t[[maybe_unused]] const auto checkUnique = [&] {\n+\t\tfor (const auto &[_, data] : d->streamData_) {\n+\t\t\tfor (const auto *b : data.buffers) {\n+\t\t\t\tif (b == buffer)\n+\t\t\t\t\treturn false;\n+\t\t\t}\n+\t\t}\n+\n+\t\tfor (const auto &pf : d->pendingFences_) {\n+\t\t\tif (pf.buffer == buffer)\n+\t\t\t\treturn false;\n+\n+\t\t\tif (fence && fence->isValid()) {\n+\t\t\t\tif (pf.buffer->_d()->fence()->fd().get() == fence->fd().get())\n+\t\t\t\t\treturn false;\n+\t\t\t}\n+\t\t}\n+\n+\t\treturn true;\n+\t};\n+\tASSERT(checkUnique() && \"buffer or fence is already present in the pool\");\n+\n+\tif (fence && fence->isValid()) {\n+\t\tbuffer->_d()->setFence(std::move(fence));\n+\n+\t\tauto it2 = d->pendingFences_.emplace(\n+\t\t\td->pendingFences_.end(),\n+\t\t\tstream,\n+\t\t\tbuffer\n+\t\t);\n+\n+\t\tLOG(Pipeline, Debug)\n+\t\t\t<< \"Waiting on fence:\" << buffer->_d()->fence()->fd().get()\n+\t\t\t<< \" for stream:\" << stream << \" buffer:\" << buffer;\n+\n+\t\tit2->notifier.activated.connect(this, [=] {\n+\t\t\tLOG(Pipeline, Debug)\n+\t\t\t\t<< \"Activated fence:\" << it2->buffer->_d()->fence()->fd().get()\n+\t\t\t\t<< \" for stream:\" << it2->stream << \" buffer:\" << it2->buffer;\n+\n+\t\t\tstd::ignore = it2->buffer->releaseFence();\n+\t\t\tit->second.buffers.push_back(it2->buffer);\n+\t\t\td->pendingFences_.erase(it2);\n+\t\t\t/* Lambda is now destroy, no captured variable should be accessed. */\n+\t\t});\n+\t} else {\n+\t\tit->second.buffers.push_back(buffer);\n+\t}\n+}\n+\n /**\n  * \\brief Retrieve the absolute path to a platform configuration file\n  * \\param[in] subdir The pipeline handler specific subdirectory name\n",
    "prefixes": [
        "RFC",
        "v1",
        "22/54"
    ]
}