Show a patch.

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

{
    "id": 24098,
    "url": "https://patchwork.libcamera.org/api/patches/24098/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/24098/",
    "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": "<20250813102501.1645940-3-barnabas.pocze@ideasonboard.com>",
    "date": "2025-08-13T10:25:01",
    "name": "[RFC,v2,2/2] libcamera: pipeline: virtual: Move image generation to separate thread",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "2ddff544d8f00cc0f98e3cb47354de32a06d894a",
    "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/24098/mbox/",
    "series": [
        {
            "id": 5371,
            "url": "https://patchwork.libcamera.org/api/series/5371/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5371",
            "date": "2025-08-13T10:24:59",
            "name": "libcamera: pipeline: virtual: Move image generation to separate thread",
            "version": 2,
            "mbox": "https://patchwork.libcamera.org/series/5371/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/24098/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/24098/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 BBED3C3295\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 13 Aug 2025 10:25:10 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5DE1369253;\n\tWed, 13 Aug 2025 12:25:08 +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 3831D6923C\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Aug 2025 12:25:05 +0200 (CEST)",
            "from pb-laptop.local (185.221.141.188.nat.pool.zt.hu\n\t[185.221.141.188])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id BDCC5351\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Aug 2025 12:24:11 +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=\"Q1b5glAw\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1755080651;\n\tbh=J4gwGlh2r6YAzia66QDh6qgPCGZW/+A8kiIyyEu3Zo0=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=Q1b5glAwQwzp1IMdniD+QsMkvOJr804EzPvGQf4noeG/sHzjr7uJJ9FvSFFXtFafH\n\tb/hGpoBRwncSqHQr6U5v8mhCR0L56tiCu1cbmURKNU48C/+yQVrFCjwq2GckUDIe1K\n\tx+FKzO9DDw3wGycnVd1Ve/AVia5emjc9x/eIjFHs=",
        "From": "=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Subject": "[RFC PATCH v2 2/2] libcamera: pipeline: virtual: Move image\n\tgeneration to separate thread",
        "Date": "Wed, 13 Aug 2025 12:25:01 +0200",
        "Message-ID": "<20250813102501.1645940-3-barnabas.pocze@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.50.1",
        "In-Reply-To": "<20250813102501.1645940-1-barnabas.pocze@ideasonboard.com>",
        "References": "<20250813102501.1645940-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>\n---\n src/libcamera/pipeline/virtual/virtual.cpp | 87 ++++++++++++++--------\n src/libcamera/pipeline/virtual/virtual.h   |  9 ++-\n 2 files changed, 66 insertions(+), 30 deletions(-)",
    "diff": "diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp\nindex 049ebcba5..48857491d 100644\n--- a/src/libcamera/pipeline/virtual/virtual.cpp\n+++ b/src/libcamera/pipeline/virtual/virtual.cpp\n@@ -107,6 +107,14 @@ private:\n \n \tbool initFrameGenerator(Camera *camera);\n \n+\tvoid onBufferCompleted(FrameBuffer *buffer)\n+\t{\n+\t\tRequest *request = buffer->request();\n+\n+\t\tif (completeBuffer(request, buffer))\n+\t\t\tcompleteRequest(request);\n+\t}\n+\n \tDmaBufAllocator dmaBufAllocator_;\n \n \tbool resetCreated_ = false;\n@@ -129,6 +137,38 @@ 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::queueRequest(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\tfor (const auto [i, p] : utils::enumerate(buffer->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 +331,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::onBufferCompleted);\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,35 +360,8 @@ 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\tfor (const auto [i, p] : utils::enumerate(buffer->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::queueRequest, ConnectionTypeQueued, request);\n \n \treturn 0;\n }\ndiff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h\nindex 683cb82b4..2d83dfe54 100644\n--- a/src/libcamera/pipeline/virtual/virtual.h\n+++ b/src/libcamera/pipeline/virtual/virtual.h\n@@ -11,6 +11,8 @@\n #include <variant>\n #include <vector>\n \n+#include <libcamera/base/object.h>\n+#include <libcamera/base/thread.h>\n #include <libcamera/geometry.h>\n #include <libcamera/stream.h>\n \n@@ -25,7 +27,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 +58,12 @@ public:\n \n \t~VirtualCameraData() = default;\n \n+\tvoid queueRequest(Request *request);\n+\n \tConfiguration config_;\n \n \tstd::vector<StreamConfig> streamConfigs_;\n+\tSignal<FrameBuffer *> bufferCompleted;\n };\n \n } /* namespace libcamera */\n",
    "prefixes": [
        "RFC",
        "v2",
        "2/2"
    ]
}