{"id":25195,"url":"https://patchwork.libcamera.org/api/patches/25195/?format=json","web_url":"https://patchwork.libcamera.org/patch/25195/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20251125162851.2301793-17-stefan.klug@ideasonboard.com>","date":"2025-11-25T16:28:28","name":"[v3,16/29] libcamera: converter: Add dw100 converter module","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"e2a36fff2cdbe2b113a33eb806ece7fda5e21fd9","submitter":{"id":184,"url":"https://patchwork.libcamera.org/api/people/184/?format=json","name":"Stefan Klug","email":"stefan.klug@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/25195/mbox/","series":[{"id":5613,"url":"https://patchwork.libcamera.org/api/series/5613/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5613","date":"2025-11-25T16:28:12","name":"Full dewarper support on imx8mp","version":3,"mbox":"https://patchwork.libcamera.org/series/5613/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/25195/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/25195/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 16C40C333C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 25 Nov 2025 16:29:46 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id CDA4160AA0;\n\tTue, 25 Nov 2025 17:29:45 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7FA2C60AC8\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 25 Nov 2025 17:29:42 +0100 (CET)","from ideasonboard.com (unknown\n\t[IPv6:2a00:6020:448c:6c00:bae1:340c:573c:570b])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 7610B6AF;\n\tTue, 25 Nov 2025 17:27:33 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"YMnraqZz\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1764088053;\n\tbh=mtMNb6yc2/K9lvSTs1b9y0wcotfqjFOwFgN75HTWh5A=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=YMnraqZz3UZLz8wox2aQH/B9kxyqqYQ0iuXE+yg5sbVXYlwNkJ4tLnd2fhG9u7RZk\n\ttjS85wjCrWVucM3HMm3HQl8GBoQnfHodykXGqOKYEPc5MNawTsr5Fvclx88mPv1xB3\n\tF/G6SHxhQVHBTxOjSn/b5AvvGOOBW3rMa3XY1WUU=","From":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Stefan Klug <stefan.klug@ideasonboard.com>","Subject":"[PATCH v3 16/29] libcamera: converter: Add dw100 converter module","Date":"Tue, 25 Nov 2025 17:28:28 +0100","Message-ID":"<20251125162851.2301793-17-stefan.klug@ideasonboard.com>","X-Mailer":"git-send-email 2.51.0","In-Reply-To":"<20251125162851.2301793-1-stefan.klug@ideasonboard.com>","References":"<20251125162851.2301793-1-stefan.klug@ideasonboard.com>","MIME-Version":"1.0","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":"The DW100 Dewarp engine is present on i.MX8MP SoC and possibly others.\nThis patch provides a dedicated converter module that allows easy\nintegration of such a dewarper into a pipeline handler.\n\nIn this patch only the ScalerCrop control is implemented. Support for\nadditional functionality will be added in later patches.\n\nSigned-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n\n---\n\nChnages in v3:\n- Major rewrite to implement the libcamera controls in the converter class\n  instead of leaving that to the pipeline handler.\n- Moved V4L2 requests handling into the class instead of leaving that to\n  the pipeline handler\n\nChanges in v2:\n- Drop validateSize() as it is not used anywhere\n\nChanges in v0.9\n- Use shared_ptr in constructor\n---\n .../internal/converter/converter_dw100.h      |  85 ++++\n .../libcamera/internal/converter/meson.build  |   1 +\n src/libcamera/converter/converter_dw100.cpp   | 413 ++++++++++++++++++\n src/libcamera/converter/meson.build           |   1 +\n 4 files changed, 500 insertions(+)\n create mode 100644 include/libcamera/internal/converter/converter_dw100.h\n create mode 100644 src/libcamera/converter/converter_dw100.cpp","diff":"diff --git a/include/libcamera/internal/converter/converter_dw100.h b/include/libcamera/internal/converter/converter_dw100.h\nnew file mode 100644\nindex 000000000000..8dd21a6228f1\n--- /dev/null\n+++ b/include/libcamera/internal/converter/converter_dw100.h\n@@ -0,0 +1,85 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2024, Ideas On Board Oy\n+ *\n+ * i.MX8MP Dewarp Engine integration\n+ */\n+\n+#pragma once\n+\n+#include <memory>\n+#include <queue>\n+\n+#include <libcamera/control_ids.h>\n+#include <libcamera/controls.h>\n+#include <libcamera/framebuffer.h>\n+\n+#include \"libcamera/internal/converter/converter_dw100_vertexmap.h\"\n+#include \"libcamera/internal/converter/converter_v4l2_m2m.h\"\n+#include \"libcamera/internal/device_enumerator.h\"\n+\n+namespace libcamera {\n+\n+class MediaDevice;\n+class Rectangle;\n+class Stream;\n+\n+class ConverterDW100Module\n+{\n+public:\n+\tvirtual ~ConverterDW100Module() = default;\n+\n+\tstatic std::unique_ptr<ConverterDW100Module> createModule(DeviceEnumerator *enumerator);\n+\n+\tint configure(const StreamConfiguration &inputCfg,\n+\t\t      const std::vector<std::reference_wrapper<StreamConfiguration>>\n+\t\t\t      &outputCfg);\n+\tbool isConfigured(const Stream *stream) const;\n+\n+\tSize adjustInputSize(const PixelFormat &pixFmt, const Size &size,\n+\t\t\t     Converter::Alignment align = Converter::Alignment::Down);\n+\tSize adjustOutputSize(const PixelFormat &pixFmt, const Size &size,\n+\t\t\t      Converter::Alignment align = Converter::Alignment::Down);\n+\n+\tint exportBuffers(const Stream *stream, unsigned int count,\n+\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n+\tint validateOutput(StreamConfiguration *cfg, bool *adjusted,\n+\t\t\t   Converter::Alignment align = Converter::Alignment::Down);\n+\tint queueBuffers(FrameBuffer *input,\n+\t\t\t const std::map<const Stream *, FrameBuffer *> &outputs);\n+\n+\tint start();\n+\tvoid stop();\n+\n+\tvoid updateControlInfos(const Stream *stream, ControlInfoMap::Map &infos);\n+\tvoid setControls(const Stream *stream, const ControlList &controls);\n+\tvoid populateMetadata(const Stream *stream, ControlList &meta);\n+\n+\tvoid setSensorCrop(const Rectangle &rect);\n+\tvoid setTransform(const Stream *stream, const Transform &transform);\n+\n+\tSignal<FrameBuffer *> inputBufferReady;\n+\tSignal<FrameBuffer *> outputBufferReady;\n+\n+private:\n+\tConverterDW100Module(std::shared_ptr<MediaDevice> media);\n+\n+\tint applyControls(const Stream *stream, const V4L2Request *request);\n+\tvoid reinitRequest(V4L2Request *request);\n+\n+\tstruct VertexMapInfo {\n+\t\tDw100VertexMap map;\n+\t\tbool update;\n+\t};\n+\n+\tstd::map<const Stream *, VertexMapInfo> vertexMaps_;\n+\tunsigned int inputBufferCount_;\n+\tV4L2M2MConverter converter_;\n+\tRectangle sensorCrop_;\n+\tbool running_;\n+\n+\tstd::vector<std::unique_ptr<V4L2Request>> requests_;\n+\tstd::queue<V4L2Request *> availableRequests_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/include/libcamera/internal/converter/meson.build b/include/libcamera/internal/converter/meson.build\nindex 9d586293f63a..128c644cb73f 100644\n--- a/include/libcamera/internal/converter/meson.build\n+++ b/include/libcamera/internal/converter/meson.build\n@@ -1,6 +1,7 @@\n # SPDX-License-Identifier: CC0-1.0\n \n libcamera_internal_headers += files([\n+    'converter_dw100.h',\n     'converter_dw100_vertexmap.h',\n     'converter_v4l2_m2m.h',\n ])\ndiff --git a/src/libcamera/converter/converter_dw100.cpp b/src/libcamera/converter/converter_dw100.cpp\nnew file mode 100644\nindex 000000000000..cba7cc9f709b\n--- /dev/null\n+++ b/src/libcamera/converter/converter_dw100.cpp\n@@ -0,0 +1,413 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2024, Ideas On Board Oy\n+ *\n+ * i.MX8MP Dewarp Engine integration\n+ */\n+\n+#include \"libcamera/internal/converter/converter_dw100.h\"\n+\n+#include <linux/dw100.h>\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/geometry.h>\n+#include <libcamera/stream.h>\n+\n+#include \"libcamera/internal/converter.h\"\n+#include \"libcamera/internal/converter/converter_v4l2_m2m.h\"\n+#include \"libcamera/internal/media_device.h\"\n+#include \"libcamera/internal/v4l2_videodevice.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(Converter)\n+\n+/**\n+ * \\class libcamera::ConverterDW100Module\n+ * \\brief A converter module for the dw100 dewarper\n+ *\n+ * This class implements a converter module with direct support for libcamera\n+ * controls. Functionality wise it closely resembles the libcamera::Converter\n+ * interface. The main difference is that V4L2 requests are handled internally\n+ * and it has direct support for libcamera controls.\n+ */\n+\n+ConverterDW100Module::ConverterDW100Module(std::shared_ptr<MediaDevice> media)\n+\t: converter_(media), running_(false)\n+{\n+\tconverter_.outputBufferReady.connect(&this->outputBufferReady, &Signal<FrameBuffer *>::emit);\n+\tconverter_.inputBufferReady.connect(&this->inputBufferReady, &Signal<FrameBuffer *>::emit);\n+}\n+\n+/**\n+ * \\brief Create a ConverterDW100Module\n+ * \\param[in] enumerator The enumerator\n+ *\n+ * Static factory function that searches for the dw100 device using the provided\n+ * \\a enumerator. If found, a ConverterDW100Module is instantiated and returned.\n+ *\n+ * \\return A ConverterDW100Module or null if no converter was found\n+ */\n+std::unique_ptr<ConverterDW100Module>\n+ConverterDW100Module::createModule(DeviceEnumerator *enumerator)\n+{\n+\tDeviceMatch dwp(\"dw100\");\n+\tdwp.add(\"dw100-source\");\n+\tdwp.add(\"dw100-sink\");\n+\n+\tstd::shared_ptr<MediaDevice> dwpMediaDevice = enumerator->search(dwp);\n+\tif (!dwpMediaDevice)\n+\t\treturn {};\n+\n+\tstd::unique_ptr<ConverterDW100Module> dwpModule{ new ConverterDW100Module(dwpMediaDevice) };\n+\tif (dwpModule->converter_.isValid())\n+\t\treturn dwpModule;\n+\n+\tLOG(Converter, Warning)\n+\t\t<< \"Found DW100 dewarper \" << dwpMediaDevice->deviceNode()\n+\t\t<< \" but invalid\";\n+\treturn {};\n+}\n+\n+/**\n+ * \\copydoc libcamera::V4L2M2MConverter::configure\n+ */\n+int ConverterDW100Module::configure(const StreamConfiguration &inputCfg,\n+\t\t\t\t    const std::vector<std::reference_wrapper<StreamConfiguration>>\n+\t\t\t\t\t    &outputCfgs)\n+{\n+\tint ret;\n+\n+\tvertexMaps_.clear();\n+\tret = converter_.configure(inputCfg, outputCfgs);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tinputBufferCount_ = inputCfg.bufferCount;\n+\n+\tfor (auto &ref : outputCfgs) {\n+\t\tconst auto &outputCfg = ref.get();\n+\t\tauto &info = vertexMaps_[outputCfg.stream()];\n+\t\tauto &vertexMap = info.map;\n+\t\tvertexMap.setInputSize(inputCfg.size);\n+\t\tvertexMap.setOutputSize(outputCfg.size);\n+\t\tvertexMap.setSensorCrop(sensorCrop_);\n+\t\tinfo.update = true;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * \\copydoc libcamera::V4L2M2MConverter::isConfigured\n+ */\n+bool ConverterDW100Module::isConfigured(const Stream *stream) const\n+{\n+\treturn vertexMaps_.find(stream) != vertexMaps_.end();\n+}\n+\n+/**\n+ * \\copydoc libcamera::V4L2M2MConverter::adjustInputSize\n+ */\n+Size ConverterDW100Module::adjustInputSize(const PixelFormat &pixFmt,\n+\t\t\t\t\t   const Size &size,\n+\t\t\t\t\t   Converter::Alignment align)\n+{\n+\treturn converter_.adjustInputSize(pixFmt, size, align);\n+}\n+\n+/**\n+ * \\copydoc libcamera::V4L2M2MConverter::adjustOutputSize\n+ */\n+Size ConverterDW100Module::adjustOutputSize(const PixelFormat &pixFmt,\n+\t\t\t\t\t    const Size &size,\n+\t\t\t\t\t    Converter::Alignment align)\n+{\n+\treturn converter_.adjustOutputSize(pixFmt, size, align);\n+}\n+\n+/**\n+ * \\copydoc libcamera::V4L2M2MConverter::exportBuffers\n+ */\n+int ConverterDW100Module::exportBuffers(const Stream *stream, unsigned int count,\n+\t\t\t\t\tstd::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\treturn converter_.exportBuffers(stream, count, buffers);\n+}\n+\n+/**\n+ * \\copydoc libcamera::V4L2M2MConverter::validateOutput\n+ */\n+int ConverterDW100Module::validateOutput(StreamConfiguration *cfg,\n+\t\t\t\t\t bool *adjusted,\n+\t\t\t\t\t Converter::Alignment align)\n+{\n+\treturn converter_.validateOutput(cfg, adjusted, align);\n+}\n+\n+/**\n+ * \\brief Queue buffers to converter device\n+ * \\param[in] input The frame buffer to apply the conversion\n+ * \\param[out] outputs The container holding the output stream pointers and\n+ * their respective frame buffer outputs.\n+ *\n+ * This function queues the \\a input frame buffer and the output frame buffers\n+ * contained in \\a outputs to the device for processing.\n+ *\n+ * Controls are automatically applied to the device before queuing buffers. V4L2\n+ * requests are used to atomically apply the controls if the kernel supports it.\n+ *\n+ * \\return 0 on success or a negative error code otherwise\n+ */\n+int ConverterDW100Module::queueBuffers(FrameBuffer *input,\n+\t\t\t\t       const std::map<const Stream *, FrameBuffer *> &outputs)\n+{\n+\tint ret;\n+\n+\tV4L2Request *request = nullptr;\n+\tif (!requests_.empty()) {\n+\t\t/* If we have requests support, there must be one available */\n+\t\tASSERT(!availableRequests_.empty());\n+\t\trequest = availableRequests_.front();\n+\t\tavailableRequests_.pop();\n+\t}\n+\n+\tfor (auto &[stream, buffer] : outputs) {\n+\t\tret = applyControls(stream, request);\n+\t\tif (ret) {\n+\t\t\treinitRequest(request);\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tret = converter_.queueBuffers(input, outputs, request);\n+\tif (ret) {\n+\t\treinitRequest(request);\n+\t\treturn ret;\n+\t}\n+\n+\tif (!request)\n+\t\treturn 0;\n+\n+\tret = request->queue();\n+\tif (ret < 0) {\n+\t\tLOG(Converter, Error) << \"Failed to queue dewarp request: -\"\n+\t\t\t\t      << strerror(-ret);\n+\t\t/* Push it back into the queue. */\n+\t\treinitRequest(request);\n+\t}\n+\n+\treturn ret;\n+}\n+\n+/**\n+ * \\copydoc libcamera::V4L2M2MConverter::start\n+ */\n+int ConverterDW100Module::start()\n+{\n+\tint ret;\n+\n+\tif (converter_.supportsRequests()) {\n+\t\tret = converter_.allocateRequests(inputBufferCount_,\n+\t\t\t\t\t\t  &requests_);\n+\t\tif (ret < 0) {\n+\t\t\tLOG(Converter, Error) << \"Failed to allocate requests.\";\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tfor (std::unique_ptr<V4L2Request> &request : requests_) {\n+\t\trequest->requestDone.connect(this, &ConverterDW100Module::reinitRequest);\n+\t\tavailableRequests_.push(request.get());\n+\t}\n+\n+\t/*\n+\t * Apply controls on all streams, to support older kernels without\n+\t * request and dynamic vertex map support.\n+\t */\n+\tfor (auto &[stream, info] : vertexMaps_)\n+\t\tapplyControls(stream, nullptr);\n+\n+\tret = converter_.start();\n+\tif (!ret) {\n+\t\trunning_ = true;\n+\t\treturn 0;\n+\t}\n+\n+\tavailableRequests_ = {};\n+\trequests_.clear();\n+\treturn ret;\n+}\n+\n+/**\n+ * \\copydoc libcamera::V4L2M2MConverter::stop\n+ */\n+void ConverterDW100Module::stop()\n+{\n+\trunning_ = false;\n+\tconverter_.stop();\n+\tavailableRequests_ = {};\n+\trequests_.clear();\n+}\n+\n+/**\n+ * \\brief Update the controls\n+ * \\param[in] stream The stream\n+ * \\param[inout] controls The controls info map to update\n+ *\n+ * Updated the \\a controls map with all the controls and limits provided by this\n+ * class.\n+ */\n+void ConverterDW100Module::updateControlInfos(const Stream *stream, ControlInfoMap::Map &controls)\n+{\n+\tControlValue scalerCropDefault = sensorCrop_;\n+\n+\tif (isConfigured(stream)) {\n+\t\tauto &info = vertexMaps_[stream];\n+\t\tinfo.map.applyLimits();\n+\t\tscalerCropDefault = info.map.effectiveScalerCrop();\n+\t}\n+\n+\tcontrols[&controls::ScalerCrop] = ControlInfo(Rectangle(sensorCrop_.x, sensorCrop_.y, 1, 1),\n+\t\t\t\t\t\t      sensorCrop_, sensorCrop_);\n+\n+\tif (!converter_.supportsRequests())\n+\t\tLOG(Converter, Warning)\n+\t\t\t<< \"dw100 kernel driver has no requests support.\"\n+\t\t\t   \" Dynamic configuration is not possible.\";\n+}\n+\n+/**\n+ * \\brief Set libcamera controls\n+ * \\param[in] stream The stream to update\n+ * \\param[in] controls The controls\n+ *\n+ * Looks up all supported controls in \\a controls and sets them on stream \\a\n+ * stream. The controls will be applied to the device on the next call to\n+ * queueBuffers().\n+ */\n+void ConverterDW100Module::setControls(const Stream *stream, const ControlList &controls)\n+{\n+\tif (!isConfigured(stream))\n+\t\treturn;\n+\n+\tauto &info = vertexMaps_[stream];\n+\tauto &vertexMap = info.map;\n+\n+\tconst auto &crop = controls.get(controls::ScalerCrop);\n+\tif (crop) {\n+\t\tvertexMap.setScalerCrop(*crop);\n+\t\tinfo.update = true;\n+\t}\n+\n+\tif (info.update && running_ && !converter_.supportsRequests())\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Dynamically setting dw100 specific controls requires\"\n+\t\t\t   \" a dw100 kernel driver with requests support\";\n+}\n+\n+/**\n+ * \\brief Retrieve updated metadata\n+ * \\param[in] stream The stream\n+ * \\param[in] meta The metadata list\n+ *\n+ * This function retrieves the metadata for the provided \\a stream and writes it\n+ * to \\a list. It shall be called after queueBuffers().\n+ */\n+void ConverterDW100Module::populateMetadata(const Stream *stream, ControlList &meta)\n+{\n+\tif (!isConfigured(stream))\n+\t\treturn;\n+\n+\tauto &vertexMap = vertexMaps_[stream].map;\n+\n+\tmeta.set(controls::ScalerCrop, vertexMap.effectiveScalerCrop());\n+}\n+\n+/**\n+ * \\var ConverterDW100Module::inputBufferReady\n+ * \\brief A signal emitted when the input frame buffer completes\n+ */\n+\n+/**\n+ * \\var ConverterDW100Module::outputBufferReady\n+ * \\brief A signal emitted on each frame buffer completion of the output queue\n+ */\n+\n+/**\n+ * \\brief Set sensor crop rectangle\n+ * \\param[in] rect The crop rectangle\n+ *\n+ * Set the sensor crop rectangle to \\a rect. This rectangle describes the area\n+ * covered by the input buffers in sensor coordinates. It is used internally\n+ * to handle the ScalerCrop control and related metadata.\n+ */\n+void ConverterDW100Module::setSensorCrop(const Rectangle &rect)\n+{\n+\tsensorCrop_ = rect;\n+\tfor (auto &[stream, vertexMap] : vertexMaps_) {\n+\t\tvertexMap.map.setSensorCrop(rect);\n+\t\tvertexMap.update = true;\n+\t}\n+}\n+\n+/**\n+ * \\brief Set transform\n+ * \\param[in] stream The stream\n+ * \\param[in] transform The transform\n+ *\n+ * Set the transform that shall be applied by the dewarper on the given stream.\n+ * As orientation is a property of libcamera::CameraConfiguration, the transform\n+ * needs to be set at configure time.\n+ */\n+void ConverterDW100Module::setTransform(const Stream *stream, const Transform &transform)\n+{\n+\tif (!isConfigured(stream))\n+\t\treturn;\n+\n+\tvertexMaps_[stream].map.setTransform(transform);\n+}\n+\n+void ConverterDW100Module::reinitRequest(V4L2Request *request)\n+{\n+\tif (!request)\n+\t\treturn;\n+\n+\trequest->reinit();\n+\tavailableRequests_.push(request);\n+}\n+\n+/**\n+ * \\brief Apply the vertex map for a given stream\n+ * \\param[in] stream The stream to update\n+ * \\param[in] request An optional request\n+ *\n+ * This function updates the vertex map on the stream \\a stream. If \\a request\n+ * is provided, the updated happens in that request.\n+ *\n+ * \\return 0 on success or a negative error code otherwise\n+ */\n+int ConverterDW100Module::applyControls(const Stream *stream, const V4L2Request *request)\n+{\n+\tif (!isConfigured(stream))\n+\t\treturn -EINVAL;\n+\n+\tauto &info = vertexMaps_[stream];\n+\tif (!info.update)\n+\t\treturn 0;\n+\n+\tstd::vector<uint32_t> map = info.map.getVertexMap();\n+\tauto value = Span<const int32_t>(reinterpret_cast<const int32_t *>(&map[0]), map.size());\n+\n+\tControlList ctrls;\n+\tctrls.set(V4L2_CID_DW100_DEWARPING_16x16_VERTEX_MAP, value);\n+\n+\tint ret = converter_.applyControls(stream, ctrls, request);\n+\tif (!ret)\n+\t\tinfo.update = false;\n+\n+\treturn ret;\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build\nindex 558d63a1bdd4..48f27cad5997 100644\n--- a/src/libcamera/converter/meson.build\n+++ b/src/libcamera/converter/meson.build\n@@ -1,6 +1,7 @@\n # SPDX-License-Identifier: CC0-1.0\n \n libcamera_internal_sources += files([\n+        'converter_dw100.cpp',\n         'converter_dw100_vertexmap.cpp',\n         'converter_v4l2_m2m.cpp'\n ])\n","prefixes":["v3","16/29"]}