Show a patch.

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

{
    "id": 23734,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/23734/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/23734/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/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": "<20250703114225.2074071-5-paul.elder@ideasonboard.com>",
    "date": "2025-07-03T11:42:19",
    "name": "[v2,4/8] libcamera: layer_manager: Add LayerManager implementation",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "90bc53cb47dec143a75260bc3b8abf0164d53089",
    "submitter": {
        "id": 17,
        "url": "https://patchwork.libcamera.org/api/1.1/people/17/?format=api",
        "name": "Paul Elder",
        "email": "paul.elder@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/23734/mbox/",
    "series": [
        {
            "id": 5267,
            "url": "https://patchwork.libcamera.org/api/1.1/series/5267/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5267",
            "date": "2025-07-03T11:42:15",
            "name": "Add Layers support",
            "version": 2,
            "mbox": "https://patchwork.libcamera.org/series/5267/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/23734/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/23734/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 80A5EC3293\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  3 Jul 2025 11:42:54 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id DDF3468E59;\n\tThu,  3 Jul 2025 13:42:53 +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 1BBF068E41\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  3 Jul 2025 13:42:52 +0200 (CEST)",
            "from neptunite.hamster-moth.ts.net (unknown\n\t[IPv6:2404:7a81:160:2100:c61b:f3bf:2578:6674])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id B5C7E593;\n\tThu,  3 Jul 2025 13:42:26 +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=\"ryKJzJ1S\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1751542948;\n\tbh=LB4Fi+5TVnj5LfL7dTH97XCzPLl0UKvl9mZCQw88JsE=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=ryKJzJ1SolvyvF1bsHTU0YyGXIRJNsfjOQE9bltMztoQsWbmytqhhNWAUfOI4jnWM\n\tnhlZb3zTNMVjnaQtg+4jfqS0nvCJCXaJWYFgpj61x2SK+qfOKp+gywROMKe6GmJLX/\n\tTj/eAkkNse9ZnfrFEoVcz8nqkpzaPRBjGIYWBNJA=",
        "From": "Paul Elder <paul.elder@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Paul Elder <paul.elder@ideasonboard.com>, kieran.bingham@ideasonboard.com,\n\tbarnabas.pocze@ideasonboard.com",
        "Subject": "[PATCH v2 4/8] libcamera: layer_manager: Add LayerManager\n\timplementation",
        "Date": "Thu,  3 Jul 2025 20:42:19 +0900",
        "Message-ID": "<20250703114225.2074071-5-paul.elder@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.47.2",
        "In-Reply-To": "<20250703114225.2074071-1-paul.elder@ideasonboard.com>",
        "References": "<20250703114225.2074071-1-paul.elder@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": "We want to be able to implement layers in libcamera, which conceptually\nsit in between the Camera class and the application. This can be useful\nfor implementing things that don't belong inside the Camera/IPA nor inside\nthe application, such as intercepting and translation the AeEnable\ncontrol, or implementing the Sync algorithm.\n\nTo achieve this, first add a LayerManager implementation, which searches\nfor and loads layers from shared object files, and orchestrates\nexecuting them. Actually calling into these functions from the Camera\nclass will be added in the following patch.\n\nSigned-off-by: Paul Elder <paul.elder@ideasonboard.com>\n\n---\nChanges in v2:\n- add closures to the layer interface\n- separate Layer into LayerInfo and LayerInterface, to separate out\n  layer data and the vtable\n- remove generateConfiguration() and streams() from the layer interface\n- add init() and terminate() to the layer interface, and make them\n  required (mainly for preparing and destroying the closure)\n- make LayerLoaded automatically dlclose on deconstruction, remove copy,\n  and implement move constructors\n- cache controls and properties in the LayerManager\n---\n include/libcamera/internal/layer_manager.h | 117 +++++++\n include/libcamera/internal/meson.build     |   1 +\n include/libcamera/layer.h                  |  54 +++\n include/libcamera/meson.build              |   1 +\n src/layer/meson.build                      |  10 +\n src/libcamera/layer_manager.cpp            | 383 +++++++++++++++++++++\n src/libcamera/meson.build                  |   1 +\n src/meson.build                            |   1 +\n 8 files changed, 568 insertions(+)\n create mode 100644 include/libcamera/internal/layer_manager.h\n create mode 100644 include/libcamera/layer.h\n create mode 100644 src/layer/meson.build\n create mode 100644 src/libcamera/layer_manager.cpp",
    "diff": "diff --git a/include/libcamera/internal/layer_manager.h b/include/libcamera/internal/layer_manager.h\nnew file mode 100644\nindex 000000000000..0d108bcddf3d\n--- /dev/null\n+++ b/include/libcamera/internal/layer_manager.h\n@@ -0,0 +1,117 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2025, Ideas On Board Oy\n+ *\n+ * Layer manager interface\n+ */\n+\n+#pragma once\n+\n+#include <deque>\n+#include <dlfcn.h>\n+#include <map>\n+#include <set>\n+#include <string>\n+#include <tuple>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/span.h>\n+\n+#include <libcamera/camera.h>\n+#include <libcamera/controls.h>\n+#include <libcamera/framebuffer.h>\n+#include <libcamera/layer.h>\n+#include <libcamera/request.h>\n+#include <libcamera/stream.h>\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(LayerManager)\n+\n+class LayerManager\n+{\n+public:\n+\tLayerManager();\n+\t~LayerManager() = default;\n+\n+\tvoid init(const Camera *camera, const ControlList &properties,\n+\t\t  const ControlInfoMap &controlInfoMap);\n+\tvoid terminate(const Camera *camera);\n+\n+\tvoid bufferCompleted(const Camera *camera,\n+\t\t\t     Request *request, FrameBuffer *buffer);\n+\tvoid requestCompleted(const Camera *camera, Request *request);\n+\tvoid disconnected(const Camera *camera);\n+\n+\tvoid acquire(const Camera *camera);\n+\tvoid release(const Camera *camera);\n+\n+\tconst ControlInfoMap &controls(const Camera *camera) const { return controls_.at(camera); }\n+\tconst ControlList &properties(const Camera *camera) const { return properties_.at(camera); }\n+\n+\tvoid configure(const Camera *camera, const CameraConfiguration *config,\n+\t\t       const ControlInfoMap &controlInfoMap);\n+\n+\tvoid createRequest(const Camera *camera,\n+\t\t\t   uint64_t cookie, const Request *request);\n+\n+\tvoid queueRequest(const Camera *camera, Request *request);\n+\n+\tvoid start(const Camera *camera, const ControlList *controls);\n+\tvoid stop(const Camera *camera);\n+\n+private:\n+\t/* Extend the layer with information specific to load-handling */\n+\tstruct LayerLoaded\n+\t{\n+\t\tLayerLoaded()\n+\t\t\t: info(nullptr), vtable(nullptr), dlHandle(nullptr)\n+\t\t{\n+\t\t}\n+\n+\t\tLayerLoaded(LayerLoaded &&other)\n+\t\t\t: info(other.info), vtable(other.vtable),\n+\t\t\t  dlHandle(other.dlHandle)\n+\t\t{\n+\t\t\tother.dlHandle = nullptr;\n+\t\t}\n+\n+\t\tLayerLoaded &operator=(LayerLoaded &&other)\n+\t\t{\n+\t\t\tinfo = other.info;\n+\t\t\tvtable = other.vtable;\n+\t\t\tdlHandle = other.dlHandle;\n+\t\t\tother.dlHandle = nullptr;\n+\t\t\treturn *this;\n+\t\t}\n+\n+\t\t~LayerLoaded()\n+\t\t{\n+\t\t\tif (dlHandle)\n+\t\t\t\tdlclose(dlHandle);\n+\t\t}\n+\n+\t\tLayerInfo *info;\n+\t\tLayerInterface *vtable;\n+\t\tvoid *dlHandle;\n+\n+\tprivate:\n+\t\tLIBCAMERA_DISABLE_COPY(LayerLoaded)\n+\t};\n+\n+\tusing ClosureKey = std::tuple<const Camera *, const LayerLoaded *>;\n+\n+\tvoid updateProperties(const Camera *camera,\n+\t\t\t      const ControlList &properties);\n+\tvoid updateControls(const Camera *camera,\n+\t\t\t    const ControlInfoMap &controlInfoMap);\n+\n+\tLayerLoaded createLayer(const std::string &file);\n+\tstd::deque<LayerLoaded> executionQueue_;\n+\tstd::map<ClosureKey, void *> closures_;\n+\n+\tstd::map<const Camera *, ControlInfoMap> controls_;\n+\tstd::map<const Camera *, ControlList> properties_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build\nindex 690f5c5ec9f6..20e6c295601f 100644\n--- a/include/libcamera/internal/meson.build\n+++ b/include/libcamera/internal/meson.build\n@@ -29,6 +29,7 @@ libcamera_internal_headers = files([\n     'ipa_proxy.h',\n     'ipc_pipe.h',\n     'ipc_unixsocket.h',\n+    'layer_manager.h',\n     'mapped_framebuffer.h',\n     'matrix.h',\n     'media_device.h',\ndiff --git a/include/libcamera/layer.h b/include/libcamera/layer.h\nnew file mode 100644\nindex 000000000000..cd0e26a3b72b\n--- /dev/null\n+++ b/include/libcamera/layer.h\n@@ -0,0 +1,54 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2025, Ideas On Board Oy\n+ *\n+ * Layer interface\n+ */\n+\n+#pragma once\n+\n+#include <set>\n+#include <stdint.h>\n+\n+#include <libcamera/base/span.h>\n+\n+#include <libcamera/controls.h>\n+\n+namespace libcamera {\n+\n+class CameraConfiguration;\n+class FrameBuffer;\n+class Request;\n+class Stream;\n+enum class StreamRole;\n+\n+struct LayerInfo {\n+\tconst char *name;\n+\tint layerAPIVersion;\n+};\n+\n+struct LayerInterface {\n+\tvoid *(*init)(const std::string &id);\n+\tvoid (*terminate)(void *);\n+\n+\tvoid (*bufferCompleted)(void *, Request *, FrameBuffer *);\n+\tvoid (*requestCompleted)(void *, Request *);\n+\tvoid (*disconnected)(void *);\n+\n+\tvoid (*acquire)(void *);\n+\tvoid (*release)(void *);\n+\n+\tControlInfoMap::Map (*controls)(void *, ControlInfoMap &);\n+\tControlList (*properties)(void *, ControlList &);\n+\n+\tvoid (*configure)(void *, const CameraConfiguration *);\n+\n+\tvoid (*createRequest)(void *, uint64_t, const Request *);\n+\n+\tvoid (*queueRequest)(void *, Request *);\n+\n+\tvoid (*start)(void *, const ControlList *);\n+\tvoid (*stop)(void *);\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/include/libcamera/meson.build b/include/libcamera/meson.build\nindex 30ea76f9470a..552af112abb5 100644\n--- a/include/libcamera/meson.build\n+++ b/include/libcamera/meson.build\n@@ -11,6 +11,7 @@ libcamera_public_headers = files([\n     'framebuffer.h',\n     'framebuffer_allocator.h',\n     'geometry.h',\n+    'layer.h',\n     'logging.h',\n     'orientation.h',\n     'pixel_format.h',\ndiff --git a/src/layer/meson.build b/src/layer/meson.build\nnew file mode 100644\nindex 000000000000..dee5e5ac5804\n--- /dev/null\n+++ b/src/layer/meson.build\n@@ -0,0 +1,10 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+layer_includes = [\n+    libcamera_includes,\n+]\n+\n+layer_install_dir = libcamera_libdir / 'layers'\n+\n+config_h.set('LAYER_DIR',\n+             '\"' + get_option('prefix') / layer_install_dir + '\"')\ndiff --git a/src/libcamera/layer_manager.cpp b/src/libcamera/layer_manager.cpp\nnew file mode 100644\nindex 000000000000..d707d4e12a53\n--- /dev/null\n+++ b/src/libcamera/layer_manager.cpp\n@@ -0,0 +1,383 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2025, Ideas On Board Oy\n+ *\n+ * Layer manager\n+ */\n+\n+#include \"libcamera/internal/layer_manager.h\"\n+\n+#include <algorithm>\n+#include <dirent.h>\n+#include <dlfcn.h>\n+#include <map>\n+#include <memory>\n+#include <set>\n+#include <string.h>\n+#include <string>\n+#include <sys/types.h>\n+#include <tuple>\n+\n+#include <libcamera/base/file.h>\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/utils.h>\n+#include <libcamera/base/span.h>\n+\n+#include <libcamera/control_ids.h>\n+#include <libcamera/layer.h>\n+\n+#include \"libcamera/internal/utils.h\"\n+\n+/**\n+ * \\file layer_manager.h\n+ * \\brief Layer manager\n+ */\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(LayerManager)\n+\n+/**\n+ * \\class LayerManager\n+ * \\brief Layer manager\n+ *\n+ * The Layer manager discovers layer implementations from disk, and creates\n+ * execution queues for every function that is implemented by each layer and\n+ * executes them. A layer is a layer that sits between libcamera and the\n+ * application, and hooks into the public Camera interface.\n+ */\n+\n+/**\n+ * \\brief Construct a LayerManager instance\n+ *\n+ * The LayerManager class is meant be instantiated by the Camera.\n+ */\n+LayerManager::LayerManager()\n+{\n+\tstd::map<std::string, LayerLoaded> layers;\n+\n+\t/* \\todo Implement built-in layers */\n+\n+\t/* This returns the number of \"modules\" successfully loaded */\n+\tstd::function<int(const std::string &)> addDirHandler =\n+\t[this, &layers](const std::string &file) {\n+\t\tLayerManager::LayerLoaded layer = createLayer(file);\n+\t\tif (!layer.info)\n+\t\t\treturn 0;\n+\n+\t\tLOG(LayerManager, Debug) << \"Loaded layer '\" << file << \"'\";\n+\n+\t\tlayers.emplace(std::string(layer.info->name), std::move(layer));\n+\n+\t\treturn 1;\n+\t};\n+\n+\t/* User-specified paths take precedence. */\n+\t/* \\todo Document this */\n+\tconst char *layerPaths = utils::secure_getenv(\"LIBCAMERA_LAYER_PATH\");\n+\tif (layerPaths) {\n+\t\tfor (const auto &dir : utils::split(layerPaths, \":\")) {\n+\t\t\tif (dir.empty())\n+\t\t\t\tcontinue;\n+\n+\t\t\t/*\n+\t\t\t * \\todo Move the shared objects into one directory\n+\t\t\t * instead of each in their own subdir\n+\t\t\t */\n+\t\t\tutils::addDir(dir.c_str(), 1, addDirHandler);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * When libcamera is used before it is installed, load layers from the\n+\t * same build directory as the libcamera library itself.\n+\t */\n+\tstd::string root = utils::libcameraBuildPath();\n+\tif (!root.empty()) {\n+\t\tstd::string layerBuildPath = root + \"src/layer\";\n+\t\tconstexpr int maxDepth = 2;\n+\n+\t\tLOG(LayerManager, Info)\n+\t\t\t<< \"libcamera is not installed. Adding '\"\n+\t\t\t<< layerBuildPath << \"' to the layer search path\";\n+\n+\t\tutils::addDir(layerBuildPath.c_str(), maxDepth, addDirHandler);\n+\t}\n+\n+\t/* Finally try to load layers from the installed system path. */\n+\tutils::addDir(LAYER_DIR, 1, addDirHandler);\n+\n+\t/* Order the layers */\n+\t/* \\todo Document this. First is closer to application, last is closer to libcamera */\n+\tconst char *layerList = utils::secure_getenv(\"LIBCAMERA_LAYERS_ENABLE\");\n+\tif (layerList) {\n+\t\tfor (const auto &layerName : utils::split(layerList, \":\")) {\n+\t\t\tif (layerName.empty())\n+\t\t\t\tcontinue;\n+\n+\t\t\tconst auto &it = layers.find(layerName);\n+\t\t\tif (it == layers.end())\n+\t\t\t\tcontinue;\n+\n+\t\t\texecutionQueue_.push_back(std::move(it->second));\n+\t\t}\n+\t}\n+}\n+\n+void LayerManager::init(const Camera *camera, const ControlList &properties,\n+\t\t\tconst ControlInfoMap &controlInfoMap)\n+{\n+\tfor (LayerManager::LayerLoaded &layer : executionQueue_) {\n+\t\tvoid *closure = layer.vtable->init(camera->id());\n+\t\tclosures_[std::make_tuple(camera, &layer)] = closure;\n+\t}\n+\n+\t/*\n+\t * We need to iterate over the layers individually to merge all of\n+\t * their controls, so we'll factor out updateControls() as it needs to be\n+\t * run again at configure().\n+\t */\n+\tupdateProperties(camera, properties);\n+\tupdateControls(camera, controlInfoMap);\n+}\n+\n+void LayerManager::terminate(const Camera *camera)\n+{\n+\tfor (LayerManager::LayerLoaded &layer : executionQueue_) {\n+\t\tvoid *closure = closures_.at(std::make_tuple(camera, &layer));\n+\t\tlayer.vtable->terminate(closure);\n+\t}\n+}\n+\n+LayerManager::LayerLoaded LayerManager::createLayer(const std::string &filename)\n+{\n+\tLayerLoaded layer;\n+\n+\tFile file{ filename };\n+\tif (!file.open(File::OpenModeFlag::ReadOnly)) {\n+\t\tLOG(LayerManager, Error) << \"Failed to open layer: \"\n+\t\t\t\t\t << strerror(-file.error());\n+\t\treturn layer;\n+\t}\n+\n+\tSpan<const uint8_t> data = file.map();\n+\tint ret = utils::elfVerifyIdent(data);\n+\tif (ret) {\n+\t\tLOG(LayerManager, Error) << \"Layer is not an ELF file\";\n+\t\treturn layer;\n+\t}\n+\n+\tSpan<const uint8_t> info = utils::elfLoadSymbol(data, \"layerInfo\");\n+\tif (info.size() < sizeof(LayerInfo)) {\n+\t\tLOG(LayerManager, Error) << \"Layer has no valid info\";\n+\t\treturn layer;\n+\t}\n+\n+\tvoid *dlHandle = dlopen(file.fileName().c_str(), RTLD_LAZY);\n+\tif (!dlHandle) {\n+\t\tLOG(LayerManager, Error)\n+\t\t\t<< \"Failed to open layer shared object: \"\n+\t\t\t<< dlerror();\n+\t\treturn layer;\n+\t}\n+\n+\tvoid *layerInfo = dlsym(dlHandle, \"layerInfo\");\n+\tif (!layerInfo) {\n+\t\tLOG(LayerManager, Error)\n+\t\t\t<< \"Failed to load layerInfo from layer shared object: \"\n+\t\t\t<< dlerror();\n+\t\tdlclose(dlHandle);\n+\t\treturn layer;\n+\t}\n+\n+\tvoid *vtable = dlsym(dlHandle, \"layerInterface\");\n+\tif (!vtable) {\n+\t\tLOG(LayerManager, Error)\n+\t\t\t<< \"Failed to load layerInterface from layer shared object: \"\n+\t\t\t<< dlerror();\n+\t\tdlclose(dlHandle);\n+\t\treturn layer;\n+\t}\n+\n+\tlayer.info = static_cast<LayerInfo *>(layerInfo);\n+\tlayer.vtable = static_cast<LayerInterface *>(vtable);\n+\tlayer.dlHandle = dlHandle;\n+\n+\t/*\n+\t * No need to dlclose after this as the LayerLoaded deconstructor will\n+\t * handle it\n+\t */\n+\n+\t/* \\todo Implement this. It should come from the libcamera version */\n+\tif (layer.info->layerAPIVersion != 1) {\n+\t\tLOG(LayerManager, Error) << \"Layer API version mismatch\";\n+\t\tlayer.info = nullptr;\n+\t\treturn layer;\n+\t}\n+\n+\t/* \\todo Document these requirements */\n+\tif (!layer.vtable->init) {\n+\t\tLOG(LayerManager, Error) << \"Layer doesn't implement init\";\n+\t\tlayer.info = nullptr;\n+\t\treturn layer;\n+\t}\n+\n+\t/* \\todo Document these requirements */\n+\tif (!layer.vtable->terminate) {\n+\t\tLOG(LayerManager, Error) << \"Layer doesn't implement terminate\";\n+\t\tlayer.info = nullptr;\n+\t\treturn layer;\n+\t}\n+\n+\t/* \\todo Validate the layer name. */\n+\n+\treturn layer;\n+}\n+\n+void LayerManager::bufferCompleted(const Camera *camera, Request *request, FrameBuffer *buffer)\n+{\n+\t/* Reverse order because this comes from a Signal emission */\n+\tfor (auto it = executionQueue_.rbegin();\n+\t     it != executionQueue_.rend(); it++) {\n+\t\tif ((*it).vtable->bufferCompleted) {\n+\t\t\tvoid *closure = closures_.at(std::make_tuple(camera, &(*it)));\n+\t\t\t(*it).vtable->bufferCompleted(closure, request, buffer);\n+\t\t}\n+\t}\n+}\n+\n+void LayerManager::requestCompleted(const Camera *camera, Request *request)\n+{\n+\t/* Reverse order because this comes from a Signal emission */\n+\tfor (auto it = executionQueue_.rbegin();\n+\t     it != executionQueue_.rend(); it++) {\n+\t\tif ((*it).vtable->requestCompleted) {\n+\t\t\tvoid *closure = closures_.at(std::make_tuple(camera, &(*it)));\n+\t\t\t(*it).vtable->requestCompleted(closure, request);\n+\t\t}\n+\t}\n+}\n+\n+void LayerManager::disconnected(const Camera *camera)\n+{\n+\t/* Reverse order because this comes from a Signal emission */\n+\tfor (auto it = executionQueue_.rbegin();\n+\t     it != executionQueue_.rend(); it++) {\n+\t\tif ((*it).vtable->disconnected) {\n+\t\t\tvoid *closure = closures_.at(std::make_tuple(camera, &(*it)));\n+\t\t\t(*it).vtable->disconnected(closure);\n+\t\t}\n+\t}\n+}\n+\n+void LayerManager::acquire(const Camera *camera)\n+{\n+\tfor (LayerManager::LayerLoaded &layer : executionQueue_) {\n+\t\tif (layer.vtable->acquire) {\n+\t\t\tvoid *closure = closures_.at(std::make_tuple(camera, &layer));\n+\t\t\tlayer.vtable->acquire(closure);\n+\t\t}\n+\t}\n+}\n+\n+void LayerManager::release(const Camera *camera)\n+{\n+\tfor (LayerManager::LayerLoaded &layer : executionQueue_) {\n+\t\tif (layer.vtable->release) {\n+\t\t\tvoid *closure = closures_.at(std::make_tuple(camera, &layer));\n+\t\t\tlayer.vtable->release(closure);\n+\t\t}\n+\t}\n+}\n+\n+void LayerManager::updateProperties(const Camera *camera,\n+\t\t\t\t    const ControlList &properties)\n+{\n+\tControlList props = properties;\n+\tfor (LayerManager::LayerLoaded &layer : executionQueue_) {\n+\t\tif (layer.vtable->properties) {\n+\t\t\tvoid *closure = closures_.at(std::make_tuple(camera, &layer));\n+\t\t\tControlList ret = layer.vtable->properties(closure, props);\n+\t\t\tprops.merge(ret, ControlList::MergePolicy::OverwriteExisting);\n+\t\t}\n+\t}\n+\tproperties_[camera] = props;\n+}\n+\n+void LayerManager::updateControls(const Camera *camera,\n+\t\t\t\t  const ControlInfoMap &controlInfoMap)\n+{\n+\tControlInfoMap infoMap = controlInfoMap;\n+\t/* \\todo Simplify this once ControlInfoMaps become easier to modify */\n+\tfor (LayerManager::LayerLoaded &layer : executionQueue_) {\n+\t\tif (layer.vtable->controls) {\n+\t\t\tvoid *closure = closures_.at(std::make_tuple(camera, &layer));\n+\t\t\tControlInfoMap::Map ret = layer.vtable->controls(closure, infoMap);\n+\t\t\tControlInfoMap::Map map;\n+\t\t\t/* Merge the layer's ret later so that layers can overwrite */\n+\t\t\tfor (auto &pair : infoMap)\n+\t\t\t\tmap.insert(pair);\n+\t\t\tfor (auto &pair : ret)\n+\t\t\t\tmap.insert(pair);\n+\t\t\tinfoMap = ControlInfoMap(std::move(map),\n+\t\t\t\t\t\t libcamera::controls::controls);\n+\t\t}\n+\t}\n+\tcontrols_[camera] = infoMap;\n+}\n+\n+void LayerManager::configure(const Camera *camera,\n+\t\t\t     const CameraConfiguration *config,\n+\t\t\t     const ControlInfoMap &controlInfoMap)\n+{\n+\tfor (LayerManager::LayerLoaded &layer : executionQueue_) {\n+\t\tif (layer.vtable->configure) {\n+\t\t\tvoid *closure = closures_.at(std::make_tuple(camera, &layer));\n+\t\t\tlayer.vtable->configure(closure, config);\n+\t\t}\n+\t}\n+\n+\tupdateControls(camera, controlInfoMap);\n+}\n+\n+void LayerManager::createRequest(const Camera *camera, uint64_t cookie, const Request *request)\n+{\n+\tfor (LayerManager::LayerLoaded &layer : executionQueue_) {\n+\t\tif (layer.vtable->createRequest) {\n+\t\t\tvoid *closure = closures_.at(std::make_tuple(camera, &layer));\n+\t\t\tlayer.vtable->createRequest(closure, cookie, request);\n+\t\t}\n+\t}\n+}\n+\n+void LayerManager::queueRequest(const Camera *camera, Request *request)\n+{\n+\tfor (LayerManager::LayerLoaded &layer : executionQueue_) {\n+\t\tif (layer.vtable->queueRequest) {\n+\t\t\tvoid *closure = closures_.at(std::make_tuple(camera, &layer));\n+\t\t\tlayer.vtable->queueRequest(closure, request);\n+\t\t}\n+\t}\n+}\n+\n+void LayerManager::start(const Camera *camera, const ControlList *controls)\n+{\n+\tfor (LayerManager::LayerLoaded &layer : executionQueue_) {\n+\t\tif (layer.vtable->start) {\n+\t\t\tvoid *closure = closures_.at(std::make_tuple(camera, &layer));\n+\t\t\tlayer.vtable->start(closure, controls);\n+\t\t}\n+\t}\n+}\n+\n+void LayerManager::stop(const Camera *camera)\n+{\n+\tfor (LayerManager::LayerLoaded &layer : executionQueue_) {\n+\t\tif (layer.vtable->stop) {\n+\t\t\tvoid *closure = closures_.at(std::make_tuple(camera, &layer));\n+\t\t\tlayer.vtable->stop(closure);\n+\t\t}\n+\t}\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\nindex 6a71b2903d27..0c2086a8399c 100644\n--- a/src/libcamera/meson.build\n+++ b/src/libcamera/meson.build\n@@ -40,6 +40,7 @@ libcamera_internal_sources = files([\n     'ipc_pipe.cpp',\n     'ipc_pipe_unixsocket.cpp',\n     'ipc_unixsocket.cpp',\n+    'layer_manager.cpp',\n     'mapped_framebuffer.cpp',\n     'matrix.cpp',\n     'media_device.cpp',\ndiff --git a/src/meson.build b/src/meson.build\nindex 8eb8f05b362f..37368b01cbf2 100644\n--- a/src/meson.build\n+++ b/src/meson.build\n@@ -63,6 +63,7 @@ subdir('libcamera')\n \n subdir('android')\n subdir('ipa')\n+subdir('layer')\n \n subdir('apps')\n \n",
    "prefixes": [
        "v2",
        "4/8"
    ]
}