[{"id":36014,"web_url":"https://patchwork.libcamera.org/comment/36014/","msgid":"<175905726808.1131930.8663677982854084595@ping.linuxembedded.co.uk>","date":"2025-09-28T11:01:08","subject":"Re: [PATCH v3 2/2] libcamera: pipeline: virtual: Move image\n\tgeneration to separate thread","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Barnabás Pőcze (2025-09-26 12:07:08)\n> Currently the virtual pipeline generates the images synchronously. This is not\n> ideal because it blocks the camera manager's internal thread, and because its\n> behaviour is different from other existing pipeline handlers, all of which\n> complete requests asynchronously.\n> \n> So move the image generation to a separate thread by deriving `VirtualCameraData`\n> from `Thread`, as well as `Object` and using the existing asynchronous signal\n> and method call mechanism.\n> \n> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\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(-)\n> \n> diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp\n> index 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>         }\n>  \n>         bool initFrameGenerator(Camera *camera);\n> +       void bufferCompleted(FrameBuffer *buffer);\n>  \n>         DmaBufAllocator dmaBufAllocator_;\n>  \n> @@ -129,6 +130,39 @@ VirtualCameraData::VirtualCameraData(PipelineHandler *pipe,\n>  \n>         /* \\todo Support multiple streams and pass multi_stream_test */\n>         streamConfigs_.resize(kMaxStream);\n> +\n> +       moveToThread(this);\n> +}\n> +\n> +void VirtualCameraData::processRequest(Request *request)\n> +{\n> +       for (auto const &[stream, buffer] : request->buffers()) {\n> +               bool found = false;\n> +               /* map buffer and fill test patterns */\n> +               for (auto &streamConfig : streamConfigs_) {\n> +                       if (stream == &streamConfig.stream) {\n> +                               FrameMetadata &fmd = buffer->_d()->metadata();\n> +\n> +                               fmd.status = FrameMetadata::Status::FrameSuccess;\n> +                               fmd.sequence = streamConfig.seq++;\n> +                               fmd.timestamp = currentTimestamp();\n> +\n> +                               Span<const FrameBuffer::Plane> planes = buffer->planes();\n> +                               for (const auto [i, p] : utils::enumerate(planes))\n> +                                       fmd.planes()[i].bytesused = p.length;\n> +\n> +                               found = true;\n> +\n> +                               if (streamConfig.frameGenerator->generateFrame(\n> +                                           stream->configuration().size, buffer))\n> +                                       fmd.status = FrameMetadata::Status::FrameError;\n> +\n> +                               bufferCompleted.emit(buffer);\n> +                               break;\n> +                       }\n> +               }\n> +               ASSERT(found);\n> +       }\n>  }\n>  \n>  VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data)\n> @@ -291,11 +325,27 @@ int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera,\n>         for (auto &s : data->streamConfigs_)\n>                 s.seq = 0;\n>  \n> +       data->bufferCompleted.connect(this, &PipelineHandlerVirtual::bufferCompleted);\n> +       data->start();\n> +\n>         return 0;\n>  }\n>  \n> -void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera)\n> +void PipelineHandlerVirtual::stopDevice(Camera *camera)\n>  {\n> +       VirtualCameraData *data = cameraData(camera);\n> +\n> +       /* Cancel pending work. */\n> +       data->exit();\n> +       data->wait();\n> +       data->removeMessages(data);\n> +\n> +       /* Process pending `bufferCompleted` signals. */\n> +       thread()->dispatchMessages(Message::Type::InvokeMessage, this);\n> +       data->bufferCompleted.disconnect(this);\n> +\n> +       while (!data->queuedRequests_.empty())\n> +               cancelRequest(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>         VirtualCameraData *data = cameraData(camera);\n>         const auto timestamp = currentTimestamp();\n>  \n> -       for (auto const &[stream, buffer] : request->buffers()) {\n> -               bool found = false;\n> -               /* map buffer and fill test patterns */\n> -               for (auto &streamConfig : data->streamConfigs_) {\n> -                       if (stream == &streamConfig.stream) {\n> -                               FrameMetadata &fmd = buffer->_d()->metadata();\n> -\n> -                               fmd.status = FrameMetadata::Status::FrameSuccess;\n> -                               fmd.sequence = streamConfig.seq++;\n> -                               fmd.timestamp = timestamp;\n> -\n> -                               Span<const FrameBuffer::Plane> planes = buffer->planes();\n> -                               for (const auto [i, p] : utils::enumerate(planes))\n> -                                       fmd.planes()[i].bytesused = p.length;\n> -\n> -                               found = true;\n> -\n> -                               if (streamConfig.frameGenerator->generateFrame(\n> -                                           stream->configuration().size, buffer))\n> -                                       fmd.status = FrameMetadata::Status::FrameError;\n> -\n> -                               completeBuffer(request, buffer);\n> -                               break;\n> -                       }\n> -               }\n> -               ASSERT(found);\n> -       }\n> -\n>         request->metadata().set(controls::SensorTimestamp, timestamp);\n> -       completeRequest(request);\n> +       data->invokeMethod(&VirtualCameraData::processRequest,\n> +                          ConnectionTypeQueued, request);\n>  \n>         return 0;\n>  }\n> @@ -415,6 +438,14 @@ bool PipelineHandlerVirtual::initFrameGenerator(Camera *camera)\n>         return true;\n>  }\n>  \n> +void PipelineHandlerVirtual::bufferCompleted(FrameBuffer *buffer)\n> +{\n> +       Request *request = buffer->request();\n> +\n> +       if (completeBuffer(request, buffer))\n> +               completeRequest(request);\n> +}\n> +\n>  REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, \"virtual\")\n>  \n>  } /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h\n> index 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> +                         public Thread,\n> +                         public Object\n>  {\n>  public:\n>         const static unsigned int kMaxStream = 3;\n> @@ -54,9 +60,12 @@ public:\n>  \n>         ~VirtualCameraData() = default;\n>  \n> +       void processRequest(Request *request);\n> +\n>         Configuration config_;\n>  \n>         std::vector<StreamConfig> streamConfigs_;\n> +       Signal<FrameBuffer *> bufferCompleted;\n>  };\n>  \n>  } /* namespace libcamera */\n> -- \n> 2.51.0\n>","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 2B963C328C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSun, 28 Sep 2025 11:01:14 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D33296B5AA;\n\tSun, 28 Sep 2025 13:01:13 +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 6B1026B5AA\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun, 28 Sep 2025 13:01:11 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id AC5E2725;\n\tSun, 28 Sep 2025 12:59:44 +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=\"DvUgeWSw\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1759057184;\n\tbh=vp0pQbPD6yCvy7U9zWJWPcuPs+6lRZsndNNM0OV1w74=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=DvUgeWSwKnWx3xX7HKj9Dh1OioZChZQ8UUIOuldmuHb5JfnvE3+IKT5WdU1OHbo1q\n\tQFksCpq4vMNr6wk3evUh5jrtiLFHlnXF5hjbSDbkFICGN/PeBXISvc2FtiQ3740cdb\n\tX8UVsvD703MgYB3cMbd1YUGyi4uq6qAdY/dpSQro=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20250926110708.94112-3-barnabas.pocze@ideasonboard.com>","References":"<20250926110708.94112-1-barnabas.pocze@ideasonboard.com>\n\t<20250926110708.94112-3-barnabas.pocze@ideasonboard.com>","Subject":"Re: [PATCH v3 2/2] libcamera: pipeline: virtual: Move image\n\tgeneration to separate thread","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Sun, 28 Sep 2025 12:01:08 +0100","Message-ID":"<175905726808.1131930.8663677982854084595@ping.linuxembedded.co.uk>","User-Agent":"alot/0.9.1","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>"}}]