{"id":27101,"url":"https://patchwork.libcamera.org/api/1.1/patches/27101/?format=json","web_url":"https://patchwork.libcamera.org/patch/27101/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<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/1.1/people/216/?format=json","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/1.1/series/6025/?format=json","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"]}