From patchwork Mon Jun 29 16:29:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27101 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 17712C3261 for ; Mon, 29 Jun 2026 16:31:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E34A965F8B; Mon, 29 Jun 2026 18:31:06 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rkkJFwWP"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 09AA565F47 for ; Mon, 29 Jun 2026 18:30:26 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D81B28D4 for ; Mon, 29 Jun 2026 18:29:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1782750582; bh=YmL2Qa3dHGwYVUaS/614a+cslVnODZwWtg0th56KayI=; h=From:To:Subject:Date:In-Reply-To:References:From; b=rkkJFwWPHuZx55kQxtvZsmXMbO15lJyijGFUZDVFIGDDyTT2YcPMjoRCth25dcX8U xCRgov8sOlwJRrmBi4i8S23Ba3DVw4pGdEPlfE/vxj5+1dRuwYRYnlSuPop+fpVFO2 aVwguBhAXnTh7Ee1IfTYXpzIGOLq03iVOSVMdZ2A= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= 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 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Use the recently added `StreamData` to store a set of buffers for each stream. Introduce the `Camera::addBuffer()` method that can be used by an application to add buffers. Signed-off-by: Barnabás Pőcze --- include/libcamera/camera.h | 3 + include/libcamera/internal/camera.h | 15 ++++ include/libcamera/internal/pipeline_handler.h | 5 ++ src/libcamera/camera.cpp | 51 +++++++++++ src/libcamera/pipeline_handler.cpp | 88 +++++++++++++++++++ 5 files changed, 162 insertions(+) diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h index 443116b588..471975a890 100644 --- a/include/libcamera/camera.h +++ b/include/libcamera/camera.h @@ -28,6 +28,7 @@ namespace libcamera { +class Fence; class FrameBuffer; class FrameBufferAllocator; class PipelineHandler; @@ -152,6 +153,8 @@ public: int start(const ControlList *controls = nullptr); int stop(); + int addBuffer(const Stream *stream, FrameBuffer *buffer, std::unique_ptr &&fence = {}); + private: LIBCAMERA_DISABLE_COPY(Camera) diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h index 43b6a6e4a2..6ba77eb882 100644 --- a/include/libcamera/internal/camera.h +++ b/include/libcamera/internal/camera.h @@ -15,14 +15,17 @@ #include #include #include +#include #include +#include #include namespace libcamera { class CameraControlValidator; +class Fence; class PipelineHandler; class Stream; @@ -57,6 +60,15 @@ private: struct StreamData { bool active = false; + std::vector buffers; + }; + + struct PendingFence { + EventNotifier notifier; + const Stream *stream; + FrameBuffer *buffer; + + PendingFence(const Stream *s, FrameBuffer *b); }; bool isAcquired() const; @@ -74,11 +86,14 @@ private: std::string id_; std::set streams_; std::unordered_map streamData_; + std::list pendingFences_; bool disconnected_; std::atomic state_; std::unique_ptr validator_; + + friend PipelineHandler; }; } /* namespace libcamera */ diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h index cc52c6b045..81ec359981 100644 --- a/include/libcamera/internal/pipeline_handler.h +++ b/include/libcamera/internal/pipeline_handler.h @@ -27,6 +27,7 @@ class Camera; class CameraConfiguration; class DeviceEnumerator; class DeviceMatch; +class Fence; class FrameBuffer; class MediaDevice; class PipelineHandler; @@ -69,6 +70,10 @@ public: void completeRequest(Request *request); void cancelRequest(Request *request); + void addBuffer(Camera *camera, + const Stream *stream, FrameBuffer *buffer, + std::unique_ptr &&fence); + std::string configurationFile(const std::string &subdir, const std::string &name, bool silent = false) const; diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index aa5682f9d7..31ab59e0b7 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -27,6 +27,7 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/camera_controls.h" +#include "libcamera/internal/framebuffer.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/request.h" @@ -746,6 +747,13 @@ void Camera::Private::setState(State state) { state_.store(state, std::memory_order_release); } + +Camera::Private::PendingFence::PendingFence(const Stream *s, FrameBuffer *b) + : notifier(b->_d()->fence()->fd().get(), EventNotifier::Read), + stream(s), + buffer(b) +{ +} #endif /* __DOXYGEN_PUBLIC__ */ /** @@ -1061,6 +1069,9 @@ int Camera::release() d->pipe_->invokeMethod(&PipelineHandler::release, ConnectionTypeBlocking, this); + // \todo clear buffer pool + // \todo empty `pendingBuffers_` and report "cancelled" fences + d->setState(Private::CameraAvailable); return 0; @@ -1448,6 +1459,9 @@ int Camera::start(const ControlList *controls) * This function stops capturing and processing requests immediately. All * pending requests are cancelled and complete synchronously in an error state. * + * All buffers in the camera's buffer pool are returned via the Camera::bufferCompleted + * event synchronously with the \a request parameter equal to \a nullptr. + * * \context This function may be called in any camera state as defined in \ref * camera_operation, and shall be synchronized by the caller with other * functions that affect the camera state. If called when the camera isn't @@ -1486,6 +1500,43 @@ int Camera::stop() return 0; } +/** + * \fn Camera::addBuffer() + * \brief Add a buffer to buffer pool of the camera + * \param[in] stream The stream + * \param[in] buffer The buffer + * \param[in] fence The fence for \a buffer + * + * \context This function may only be called when the camera is + * in the Running state as defined in \ref camera_operation. + * + * \return 0 on success or a negative error code otherwise + * + * \internal + * \todo Add `addBuffers()` that accepts a list of (stream, buffer, fence) tuples. + */ +int Camera::addBuffer(const Stream *stream, FrameBuffer *buffer, std::unique_ptr &&fence) +{ + Private *const d = _d(); + + int ret = d->isAccessAllowed(Private::CameraRunning); + if (ret < 0) + return ret; + + auto it = d->streamData_.find(stream); + if (it == d->streamData_.end() || !it->second.active) + return -EINVAL; + if (!buffer) + return -EINVAL; + if (buffer->_d()->fence()) + return -EINVAL; + + d->pipe_->invokeMethod(&PipelineHandler::addBuffer, ConnectionTypeQueued, + this, stream, buffer, std::move(fence)); + + return 0; +} + /** * \brief Handle request completion and notify application * \param[in] request The request that has completed diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp index 99f35d1e42..54ef8296a4 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -22,6 +22,7 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/camera_manager.h" #include "libcamera/internal/device_enumerator.h" +#include "libcamera/internal/framebuffer.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/request.h" #include "libcamera/internal/tracepoints.h" @@ -394,6 +395,24 @@ void PipelineHandler::stop(Camera *camera) doQueueRequest(request); } + const auto returnBuffer = [&](FrameBuffer *buffer) { + ASSERT(!buffer->_d()->stream_); + buffer->_d()->cancel(); + camera->bufferCompleted.emit(nullptr, buffer); + }; + + for (auto &pf : data->pendingFences_) + returnBuffer(pf.buffer); + + data->pendingFences_.clear(); + + for (auto &[stream, streamData] : data->streamData_) { + for (FrameBuffer *buffer : streamData.buffers) + returnBuffer(buffer); + + streamData.buffers.clear(); + } + /* Make sure no requests are pending. */ ASSERT(data->queuedRequests_.empty()); ASSERT(data->waitingRequests_.empty()); @@ -611,6 +630,75 @@ void PipelineHandler::cancelRequest(Request *request) completeRequest(request); } +/** + * \fn PipelineHandler::addBuffer() + * \brief Add buffers to the buffer pool of the camera + * \param[in] camera The camera + * \param[in] stream The stream of \a camera + * \param[in] buffer The buffer + * \param[in] fence The fence for \a buffer + * + * \context This function may only be called from the CameraManager thread. + */ +void PipelineHandler::addBuffer(Camera *camera, + const Stream *stream, FrameBuffer *buffer, + std::unique_ptr &&fence) +{ + Camera::Private *const d = camera->_d(); + + auto it = d->streamData_.find(stream); + ASSERT(it != d->streamData_.end()); + + [[maybe_unused]] const auto checkUnique = [&] { + for (const auto &[_, data] : d->streamData_) { + for (const auto *b : data.buffers) { + if (b == buffer) + return false; + } + } + + for (const auto &pf : d->pendingFences_) { + if (pf.buffer == buffer) + return false; + + if (fence && fence->isValid()) { + if (pf.buffer->_d()->fence()->fd().get() == fence->fd().get()) + return false; + } + } + + return true; + }; + ASSERT(checkUnique() && "buffer or fence is already present in the pool"); + + if (fence && fence->isValid()) { + buffer->_d()->setFence(std::move(fence)); + + auto it2 = d->pendingFences_.emplace( + d->pendingFences_.end(), + stream, + buffer + ); + + LOG(Pipeline, Debug) + << "Waiting on fence:" << buffer->_d()->fence()->fd().get() + << " for stream:" << stream << " buffer:" << buffer; + + it2->notifier.activated.connect(this, [=] { + LOG(Pipeline, Debug) + << "Activated fence:" << it2->buffer->_d()->fence()->fd().get() + << " for stream:" << it2->stream << " buffer:" << it2->buffer; + + std::ignore = it2->buffer->releaseFence(); + it->second.buffers.push_back(it2->buffer); + d->pendingFences_.erase(it2); + /* Lambda is now destroy, no captured variable should be accessed. */ + }); + } else { + it->second.buffers.push_back(buffer); + } +} + /** * \brief Retrieve the absolute path to a platform configuration file * \param[in] subdir The pipeline handler specific subdirectory name