Patch Detail
Show a patch.
GET /api/patches/24471/?format=api
{ "id": 24471, "url": "https://patchwork.libcamera.org/api/patches/24471/?format=api", "web_url": "https://patchwork.libcamera.org/patch/24471/", "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": "<20250926110708.94112-3-barnabas.pocze@ideasonboard.com>", "date": "2025-09-26T11:07:08", "name": "[v3,2/2] libcamera: pipeline: virtual: Move image generation to separate thread", "commit_ref": null, "pull_url": null, "state": "accepted", "archived": false, "hash": "aaba34733105f17d93cf343bbffd10b50f59b4ba", "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/24471/mbox/", "series": [ { "id": 5460, "url": "https://patchwork.libcamera.org/api/series/5460/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5460", "date": "2025-09-26T11:07:06", "name": "libcamera: pipeline: virtual: Move image generation to separate thread", "version": 3, "mbox": "https://patchwork.libcamera.org/series/5460/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/24471/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/24471/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 E1D29C328C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 26 Sep 2025 11:07:21 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 311F46B5F9;\n\tFri, 26 Sep 2025 13:07:19 +0200 (CEST)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9282D6B5C2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 26 Sep 2025 13:07:13 +0200 (CEST)", "from pb-laptop.local (185.221.140.70.nat.pool.zt.hu\n\t[185.221.140.70])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 1E6B41E5D;\n\tFri, 26 Sep 2025 13:05:48 +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=\"VtJcv2Rx\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1758884748;\n\tbh=Y9lCZ+P5KnXYeK5LtyttfTfvkbfBHGWMK0emYPBFSME=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=VtJcv2Rx4SBVlKvxMMFi/lY6NfcrPSZiF2HFyapbfOeEsdIwoDIQHAj8X4JVjsS62\n\tmVjnWWQRDKFafGGukHfD+IMP5wT0a+wMzX5QqPdiYy+uXpQE/XzbGVwVI62D7booY8\n\ttxCkin7c2tBbYKWNhC/ICDNE376dGKOar1HBO7Wg=", "From": "=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>", "To": "libcamera-devel@lists.libcamera.org", "Cc": "Laurent Pinchart <laurent.pinchart@ideasonboard.com>", "Subject": "[PATCH v3 2/2] libcamera: pipeline: virtual: Move image generation\n\tto separate thread", "Date": "Fri, 26 Sep 2025 13:07:08 +0200", "Message-ID": "<20250926110708.94112-3-barnabas.pocze@ideasonboard.com>", "X-Mailer": "git-send-email 2.51.0", "In-Reply-To": "<20250926110708.94112-1-barnabas.pocze@ideasonboard.com>", "References": "<20250926110708.94112-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": "Currently the virtual pipeline generates the images synchronously. This is not\nideal because it blocks the camera manager's internal thread, and because its\nbehaviour is different from other existing pipeline handlers, all of which\ncomplete requests asynchronously.\n\nSo move the image generation to a separate thread by deriving `VirtualCameraData`\nfrom `Thread`, as well as `Object` and using the existing asynchronous signal\nand method call mechanism.\n\nSigned-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\nReviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n---\n src/libcamera/pipeline/virtual/virtual.cpp | 91 +++++++++++++++-------\n src/libcamera/pipeline/virtual/virtual.h | 11 ++-\n 2 files changed, 71 insertions(+), 31 deletions(-)", "diff": "diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp\nindex f9538129c..23eae852f 100644\n--- a/src/libcamera/pipeline/virtual/virtual.cpp\n+++ b/src/libcamera/pipeline/virtual/virtual.cpp\n@@ -106,6 +106,7 @@ private:\n \t}\n \n \tbool initFrameGenerator(Camera *camera);\n+\tvoid bufferCompleted(FrameBuffer *buffer);\n \n \tDmaBufAllocator dmaBufAllocator_;\n \n@@ -129,6 +130,39 @@ VirtualCameraData::VirtualCameraData(PipelineHandler *pipe,\n \n \t/* \\todo Support multiple streams and pass multi_stream_test */\n \tstreamConfigs_.resize(kMaxStream);\n+\n+\tmoveToThread(this);\n+}\n+\n+void VirtualCameraData::processRequest(Request *request)\n+{\n+\tfor (auto const &[stream, buffer] : request->buffers()) {\n+\t\tbool found = false;\n+\t\t/* map buffer and fill test patterns */\n+\t\tfor (auto &streamConfig : streamConfigs_) {\n+\t\t\tif (stream == &streamConfig.stream) {\n+\t\t\t\tFrameMetadata &fmd = buffer->_d()->metadata();\n+\n+\t\t\t\tfmd.status = FrameMetadata::Status::FrameSuccess;\n+\t\t\t\tfmd.sequence = streamConfig.seq++;\n+\t\t\t\tfmd.timestamp = currentTimestamp();\n+\n+\t\t\t\tSpan<const FrameBuffer::Plane> planes = buffer->planes();\n+\t\t\t\tfor (const auto [i, p] : utils::enumerate(planes))\n+\t\t\t\t\tfmd.planes()[i].bytesused = p.length;\n+\n+\t\t\t\tfound = true;\n+\n+\t\t\t\tif (streamConfig.frameGenerator->generateFrame(\n+\t\t\t\t\t stream->configuration().size, buffer))\n+\t\t\t\t\tfmd.status = FrameMetadata::Status::FrameError;\n+\n+\t\t\t\tbufferCompleted.emit(buffer);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\tASSERT(found);\n+\t}\n }\n \n VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data)\n@@ -291,11 +325,27 @@ int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera,\n \tfor (auto &s : data->streamConfigs_)\n \t\ts.seq = 0;\n \n+\tdata->bufferCompleted.connect(this, &PipelineHandlerVirtual::bufferCompleted);\n+\tdata->start();\n+\n \treturn 0;\n }\n \n-void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera)\n+void PipelineHandlerVirtual::stopDevice(Camera *camera)\n {\n+\tVirtualCameraData *data = cameraData(camera);\n+\n+\t/* Cancel pending work. */\n+\tdata->exit();\n+\tdata->wait();\n+\tdata->removeMessages(data);\n+\n+\t/* Process pending `bufferCompleted` signals. */\n+\tthread()->dispatchMessages(Message::Type::InvokeMessage, this);\n+\tdata->bufferCompleted.disconnect(this);\n+\n+\twhile (!data->queuedRequests_.empty())\n+\t\tcancelRequest(data->queuedRequests_.front());\n }\n \n int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,\n@@ -304,36 +354,9 @@ int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,\n \tVirtualCameraData *data = cameraData(camera);\n \tconst auto timestamp = currentTimestamp();\n \n-\tfor (auto const &[stream, buffer] : request->buffers()) {\n-\t\tbool found = false;\n-\t\t/* map buffer and fill test patterns */\n-\t\tfor (auto &streamConfig : data->streamConfigs_) {\n-\t\t\tif (stream == &streamConfig.stream) {\n-\t\t\t\tFrameMetadata &fmd = buffer->_d()->metadata();\n-\n-\t\t\t\tfmd.status = FrameMetadata::Status::FrameSuccess;\n-\t\t\t\tfmd.sequence = streamConfig.seq++;\n-\t\t\t\tfmd.timestamp = timestamp;\n-\n-\t\t\t\tSpan<const FrameBuffer::Plane> planes = buffer->planes();\n-\t\t\t\tfor (const auto [i, p] : utils::enumerate(planes))\n-\t\t\t\t\tfmd.planes()[i].bytesused = p.length;\n-\n-\t\t\t\tfound = true;\n-\n-\t\t\t\tif (streamConfig.frameGenerator->generateFrame(\n-\t\t\t\t\t stream->configuration().size, buffer))\n-\t\t\t\t\tfmd.status = FrameMetadata::Status::FrameError;\n-\n-\t\t\t\tcompleteBuffer(request, buffer);\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t}\n-\t\tASSERT(found);\n-\t}\n-\n \trequest->metadata().set(controls::SensorTimestamp, timestamp);\n-\tcompleteRequest(request);\n+\tdata->invokeMethod(&VirtualCameraData::processRequest,\n+\t\t\t ConnectionTypeQueued, request);\n \n \treturn 0;\n }\n@@ -415,6 +438,14 @@ bool PipelineHandlerVirtual::initFrameGenerator(Camera *camera)\n \treturn true;\n }\n \n+void PipelineHandlerVirtual::bufferCompleted(FrameBuffer *buffer)\n+{\n+\tRequest *request = buffer->request();\n+\n+\tif (completeBuffer(request, buffer))\n+\t\tcompleteRequest(request);\n+}\n+\n REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, \"virtual\")\n \n } /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h\nindex 683cb82b4..215e56fa3 100644\n--- a/src/libcamera/pipeline/virtual/virtual.h\n+++ b/src/libcamera/pipeline/virtual/virtual.h\n@@ -11,6 +11,10 @@\n #include <variant>\n #include <vector>\n \n+#include <libcamera/base/object.h>\n+#include <libcamera/base/signal.h>\n+#include <libcamera/base/thread.h>\n+\n #include <libcamera/geometry.h>\n #include <libcamera/stream.h>\n \n@@ -25,7 +29,9 @@ namespace libcamera {\n \n using VirtualFrame = std::variant<TestPattern, ImageFrames>;\n \n-class VirtualCameraData : public Camera::Private\n+class VirtualCameraData : public Camera::Private,\n+\t\t\t public Thread,\n+\t\t\t public Object\n {\n public:\n \tconst static unsigned int kMaxStream = 3;\n@@ -54,9 +60,12 @@ public:\n \n \t~VirtualCameraData() = default;\n \n+\tvoid processRequest(Request *request);\n+\n \tConfiguration config_;\n \n \tstd::vector<StreamConfig> streamConfigs_;\n+\tSignal<FrameBuffer *> bufferCompleted;\n };\n \n } /* namespace libcamera */\n", "prefixes": [ "v3", "2/2" ] }