From patchwork Mon Aug 11 09:49:25 2025 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: 24089 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 55531BDCC1 for ; Mon, 11 Aug 2025 09:49:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C864969230; Mon, 11 Aug 2025 11:49:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BU5JXXQZ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CAFB761464 for ; Mon, 11 Aug 2025 11:49:29 +0200 (CEST) Received: from pb-laptop.local (185.221.140.182.nat.pool.zt.hu [185.221.140.182]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C997C10D4 for ; Mon, 11 Aug 2025 11:48:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1754905717; bh=+aU5QbLxj/hcBEzsskTkS6MZzydYuIlSDc5zFp7SyGc=; h=From:To:Subject:Date:In-Reply-To:References:From; b=BU5JXXQZGQJgfOscnKjEcmUQ2HbUfuTYN+wLBpRJBVCNB+gY1TOHV+hufPQGtTD/4 mGr3LGaP8RmJopGgUtTTxTG9QILoZ+fJ4dApmma/q6IWR9xUtuJeitZvXkYNCtLZWD HMee7xxhc98IlidXfSM7vi2I91bvKi6w82AIEhmQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 1/2] libcamera: base: object: Add `invokeMethod` for lamdas Date: Mon, 11 Aug 2025 11:49:25 +0200 Message-ID: <20250811094926.1308259-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250811094926.1308259-1-barnabas.pocze@ideasonboard.com> References: <20250811094926.1308259-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" Add an overload of `invokeMethod` that works the same way as the already existing version but instead of invoking a particular member function, it runs a lambda. Signed-off-by: Barnabás Pőcze --- include/libcamera/base/object.h | 10 ++++++++++ src/libcamera/base/object.cpp | 24 +++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/include/libcamera/base/object.h b/include/libcamera/base/object.h index a24f84ff9..3f004499f 100644 --- a/include/libcamera/base/object.h +++ b/include/libcamera/base/object.h @@ -43,6 +43,16 @@ public: return method->activate(std::forward(args)..., true); } + template> * = nullptr> + auto invokeMethod(Func func, ConnectionType type) + -> std::invoke_result_t + { + using R = std::invoke_result_t; + auto *method = new BoundMethodFunctor(this, this, std::move(func), type); + return method->activate(true); + } + Thread *thread() const { return thread_; } void moveToThread(Thread *thread); diff --git a/src/libcamera/base/object.cpp b/src/libcamera/base/object.cpp index 37d133cc7..98937784c 100644 --- a/src/libcamera/base/object.cpp +++ b/src/libcamera/base/object.cpp @@ -255,7 +255,7 @@ bool Object::assertThreadBound(const char *message) } /** - * \fn R Object::invokeMethod() + * \fn Object::invokeMethod(R (T::*func)(FuncArgs...), ConnectionType type, Args &&...args) * \brief Invoke a method asynchronously on an Object instance * \param[in] func The object method to invoke * \param[in] type Connection type for method invocation @@ -280,6 +280,28 @@ bool Object::assertThreadBound(const char *message) * connection type ConnectionTypeQueued, return a default-constructed R value. */ +/** + * \fn Object::invokeMethod(Func func, ConnectionType type) + * \brief Invoke a lambda in the thread of an Object instance + * \param[in] func The object method to invoke + * \param[in] type Connection type for method invocation + * + * This function invokes the lambda \a func without arguments, based on the + * connection \a type wrt. the Object instance. Depending on the type, the method + * will be called synchronously in the same thread or asynchronously in the + * object's thread. + * + * Due to the asynchronous nature of threads, asynchronous invocations with the + * ConnectionTypeQueued type are not guaranteed to be called before the thread + * is stopped. See \ref thread-stop for additional information. + * + * \context This function is \threadsafe. + * + * \return For connection types ConnectionTypeDirect and + * ConnectionTypeBlocking, return the return value of the invoked method. For + * connection type ConnectionTypeQueued, return a default-constructed R value. + */ + /** * \fn Object::thread() * \brief Retrieve the thread the object is bound to From patchwork Mon Aug 11 09:49:26 2025 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: 24090 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 479E5C3295 for ; Mon, 11 Aug 2025 09:49:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9E8146145E; Mon, 11 Aug 2025 11:49:32 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="jqPWbOkz"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0ADFC6922A for ; Mon, 11 Aug 2025 11:49:30 +0200 (CEST) Received: from pb-laptop.local (185.221.140.182.nat.pool.zt.hu [185.221.140.182]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 162854A4 for ; Mon, 11 Aug 2025 11:48:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1754905718; bh=wFL6fTxVFkofj93mVSP5Hr6wNTuL7h4DvXhDxNkhHqA=; h=From:To:Subject:Date:In-Reply-To:References:From; b=jqPWbOkz/BO9gMNR/s4PJdlmaD4qzqTgTxccAzUeN3saeniYY0ag7vj7ecMfkUtjC vg1I7R0mvsWphE8/jhOo9N9Vn7bYecv0zGRPM+PttGlC/7Fs+5qo1xV0vl2AVLi9zq Ari3Yc3KRUph0/ryK6py9PKGh0AxVGvyaPRC5D7s= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 2/2] libcamera: pipeline: virtual: Move image generation to separate thread Date: Mon, 11 Aug 2025 11:49:26 +0200 Message-ID: <20250811094926.1308259-3-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250811094926.1308259-1-barnabas.pocze@ideasonboard.com> References: <20250811094926.1308259-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" Currently the virtual pipeline generates the images synchronously. This is not ideal because it blocks the camera manager's internal thread, and because its behaviour is different from other existing pipeline handlers, all of which complete requests asynchronously. So move the image generation to a separate thread by deriving `VirtualCameraData` from `Thread`, as well as `Object` and using the existing asynchronous signal and method call mechanism. Signed-off-by: Barnabás Pőcze --- src/libcamera/pipeline/virtual/virtual.cpp | 80 ++++++++++++++-------- src/libcamera/pipeline/virtual/virtual.h | 8 ++- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index 049ebcba5..2ad32ec0a 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -129,6 +129,38 @@ VirtualCameraData::VirtualCameraData(PipelineHandler *pipe, /* \todo Support multiple streams and pass multi_stream_test */ streamConfigs_.resize(kMaxStream); + + moveToThread(this); +} + +void VirtualCameraData::queueRequest(Request *request) +{ + for (auto const &[stream, buffer] : request->buffers()) { + bool found = false; + /* map buffer and fill test patterns */ + for (auto &streamConfig : streamConfigs_) { + if (stream == &streamConfig.stream) { + FrameMetadata &fmd = buffer->_d()->metadata(); + + fmd.status = FrameMetadata::Status::FrameSuccess; + fmd.sequence = streamConfig.seq++; + fmd.timestamp = currentTimestamp(); + + for (const auto [i, p] : utils::enumerate(buffer->planes())) + fmd.planes()[i].bytesused = p.length; + + found = true; + + if (streamConfig.frameGenerator->generateFrame( + stream->configuration().size, buffer)) + fmd.status = FrameMetadata::Status::FrameError; + + bufferCompleted.emit(request, buffer); + break; + } + } + ASSERT(found); + } } VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data) @@ -291,11 +323,28 @@ int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera, for (auto &s : data->streamConfigs_) s.seq = 0; + data->bufferCompleted.connect(this, [&](Request *request, FrameBuffer *buffer) { + if (completeBuffer(request, buffer)) + completeRequest(request); + }); + + data->start(); + return 0; } -void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera) +void PipelineHandlerVirtual::stopDevice(Camera *camera) { + VirtualCameraData *data = cameraData(camera); + + /* Flush all work. */ + data->invokeMethod([] { }, ConnectionTypeBlocking); + data->exit(); + data->wait(); + + /* Process queued `bufferCompleted` signal emissions. */ + Thread::current()->dispatchMessages(Message::Type::InvokeMessage, this); + data->bufferCompleted.disconnect(this); } int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, @@ -304,35 +353,8 @@ int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, VirtualCameraData *data = cameraData(camera); const auto timestamp = currentTimestamp(); - for (auto const &[stream, buffer] : request->buffers()) { - bool found = false; - /* map buffer and fill test patterns */ - for (auto &streamConfig : data->streamConfigs_) { - if (stream == &streamConfig.stream) { - FrameMetadata &fmd = buffer->_d()->metadata(); - - fmd.status = FrameMetadata::Status::FrameSuccess; - fmd.sequence = streamConfig.seq++; - fmd.timestamp = timestamp; - - for (const auto [i, p] : utils::enumerate(buffer->planes())) - fmd.planes()[i].bytesused = p.length; - - found = true; - - if (streamConfig.frameGenerator->generateFrame( - stream->configuration().size, buffer)) - fmd.status = FrameMetadata::Status::FrameError; - - completeBuffer(request, buffer); - break; - } - } - ASSERT(found); - } - request->metadata().set(controls::SensorTimestamp, timestamp); - completeRequest(request); + data->invokeMethod(&VirtualCameraData::queueRequest, ConnectionTypeQueued, request); return 0; } diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h index 683cb82b4..0832fd80c 100644 --- a/src/libcamera/pipeline/virtual/virtual.h +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -25,7 +26,9 @@ namespace libcamera { using VirtualFrame = std::variant; -class VirtualCameraData : public Camera::Private +class VirtualCameraData : public Camera::Private, + public Thread, + public Object { public: const static unsigned int kMaxStream = 3; @@ -54,9 +57,12 @@ public: ~VirtualCameraData() = default; + void queueRequest(Request *request); + Configuration config_; std::vector streamConfigs_; + Signal bufferCompleted; }; } /* namespace libcamera */