[{"id":38289,"web_url":"https://patchwork.libcamera.org/comment/38289/","msgid":"<177194896117.1513537.12265013306022510835@t16>","date":"2026-02-24T16:02:41","subject":"Re: [PATCH v6 4/8] libcamera: layer_manager: Add Layer handling\n\timplementation","submitter":{"id":215,"url":"https://patchwork.libcamera.org/api/people/215/","name":"Isaac Scott","email":"isaac.scott@ideasonboard.com"},"content":"Quoting Paul Elder (2026-01-29 08:28:10)\n> We want to be able to implement layers in libcamera, which conceptually\n> sit in between the Camera class and the application. This can be useful\n> for implementing things that don't belong inside the Camera/IPA nor inside\n> the application, such as intercepting and translating the AeEnable\n> control, or implementing the Sync algorithm.\n> \n> To achieve this, add:\n> - a LayerManager, which searches for and loads layers from shared object\n>   files\n> - a LayerController, which belongs to a Camera and orchestrates\n>   selecting layers for the Camera and executing them.\n> \n> Actually calling into these functions from the Camera class will be\n> added in the following patch.\n> \n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> \n> ---\n> Changes in v6:\n> - make requestCompleted return metadata, so that layers can set\n>   metadata (since Request::metadata() now returns a const)\n> \n> Changes in v5:\n> - add documentation to LayerInstance\n> \n> Changes in v4:\n> - add LayerInstance to wrap LayerLoaded and closure to remove the need\n>   for a map to store the closures in LayerController\n>   - Also add a level of indirection in LayerInstance to ease calling\n>     into the Layer functions from LayerController and remove the need to\n>     get and pass the closures at the LayerController level\n> - make LayerManager::createController() const\n> - add LIBCAMERA_LAYER_PATH to meson devenv\n> \n> Changes in v3:\n> - change start() signature so that the layers can modify the initial\n>   control list\n>   - this is done by the LayerManager having it's own copy of the control\n>     list that is modified by the layers, and it returns it to the Camera\n>     on its hook\n> - expand documentation about the LayerManager\n> - add documentation for all the LayerManager and Layer functions\n> - clear properties_ and controls_ on terminate()\n> - add LayerController which belongs to a Camera, to remove from\n>   LayerManager having to manage a map of camera-layer keys to the\n>   closures for each layer\n> \n> Changes 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 | 205 +++++++\n>  include/libcamera/internal/meson.build     |   1 +\n>  include/libcamera/layer.h                  |  54 ++\n>  include/libcamera/meson.build              |   1 +\n>  src/layer/meson.build                      |  14 +\n>  src/libcamera/layer.cpp                    | 179 ++++++\n>  src/libcamera/layer_manager.cpp            | 612 +++++++++++++++++++++\n>  src/libcamera/meson.build                  |   2 +\n>  src/meson.build                            |   1 +\n>  9 files changed, 1069 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.cpp\n>  create mode 100644 src/libcamera/layer_manager.cpp\n> \n> diff --git a/include/libcamera/internal/layer_manager.h b/include/libcamera/internal/layer_manager.h\n> new file mode 100644\n> index 000000000000..8427ec3eb5d9\n> --- /dev/null\n> +++ b/include/libcamera/internal/layer_manager.h\n> @@ -0,0 +1,205 @@\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 <memory>\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/control_ids.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(LayerLoaded)\n> +LOG_DECLARE_CATEGORY(LayerController)\n> +LOG_DECLARE_CATEGORY(LayerManager)\n> +\n> +/* Extend the layer with information specific to load-handling */\n> +struct LayerLoaded {\n> +       LayerLoaded() = default;\n> +\n> +       LayerLoaded(const std::string &file);\n> +\n> +       LayerLoaded(LayerLoaded &&other)\n> +               : info(other.info), vtable(other.vtable),\n> +                 dlHandle(other.dlHandle), valid(other.valid)\n> +       {\n> +               other.dlHandle = nullptr;\n> +       }\n> +\n> +       LayerLoaded &operator=(LayerLoaded &&other)\n> +       {\n> +               info = other.info;\n> +               vtable = other.vtable;\n> +               dlHandle = other.dlHandle;\n> +               other.dlHandle = nullptr;\n> +               valid = other.valid;\n> +               return *this;\n> +       }\n> +\n> +       ~LayerLoaded()\n> +       {\n> +               if (dlHandle)\n> +                       dlclose(dlHandle);\n> +       }\n> +\n> +       LayerInfo *info = nullptr;\n> +       LayerInterface *vtable = nullptr;\n\nI may be missing something here, but what is vtable? Variable table?\n\n> +       void *dlHandle = nullptr;\n> +       bool valid = false;\n> +\n> +private:\n> +       LIBCAMERA_DISABLE_COPY(LayerLoaded)\n> +};\n> +\n> +#ifndef __DOXYGEN__\n> +#define _ARG_PARAMS2(type1, type2) type1 arg1, type2 arg2\n> +#define _ARG_NAMES2(type1, type2) arg1, arg2\n> +\n> +#define _ARG_PARAMS1(type1) type1 arg1\n> +#define _ARG_NAMES1(type1) arg1\n> +\n> +#define _ARG_PARAMS0()\n> +#define _ARG_NAMES0()\n> +\n> +#define _GET_OVERRIDE(_1, _2, _3, NAME, ...) NAME\n> +\n> +#define ARG_PARAMS(...) _GET_OVERRIDE(\"ignored\", __VA_ARGS__ __VA_OPT__(,) \\\n> +                   _ARG_PARAMS2, _ARG_PARAMS1, _ARG_PARAMS0)(__VA_ARGS__)\n> +\n> +#define ARG_NAMES(...) _GET_OVERRIDE(\"ignored\", __VA_ARGS__ __VA_OPT__(,) \\\n> +                   _ARG_NAMES2, _ARG_NAMES1, _ARG_NAMES0)(__VA_ARGS__)\n> +#endif /* __DOXYGEN__ */\n> +\n> +#define LAYER_INSTANCE_CALL(func, ...) \\\n> +       void func(ARG_PARAMS(__VA_ARGS__)) \\\n> +       { \\\n> +               if (layer->vtable->func) \\\n> +                       layer->vtable->func(closure __VA_OPT__(,) ARG_NAMES(__VA_ARGS__)); \\\n> +       }\n> +\n> +struct LayerInstance {\n> +       LayerInstance(const std::shared_ptr<LayerLoaded> &l)\n> +               : layer(l)\n> +       {\n> +       }\n> +\n> +       void init(const std::string &id)\n> +       {\n> +               closure = layer->vtable->init(id);\n> +       }\n> +\n> +       void terminate()\n> +       {\n> +               layer->vtable->terminate(closure);\n> +       }\n> +\n> +       LAYER_INSTANCE_CALL(bufferCompleted, Request *, FrameBuffer *)\n> +       LAYER_INSTANCE_CALL(disconnected)\n> +       LAYER_INSTANCE_CALL(acquire)\n> +       LAYER_INSTANCE_CALL(release)\n> +       LAYER_INSTANCE_CALL(configure, const CameraConfiguration *)\n> +       LAYER_INSTANCE_CALL(createRequest, uint64_t, const Request *)\n> +       LAYER_INSTANCE_CALL(queueRequest, Request *)\n> +       LAYER_INSTANCE_CALL(start, ControlList &)\n> +       LAYER_INSTANCE_CALL(stop)\n> +\n> +       ControlList requestCompleted(Request *request)\n> +       {\n> +               if (!layer->vtable->requestCompleted)\n> +                       return ControlList(controls::controls);\n> +               return layer->vtable->requestCompleted(closure, request);\n> +       }\n> +\n> +       ControlInfoMap::Map controls(ControlInfoMap &infoMap)\n> +       {\n> +               if (!layer->vtable->controls)\n> +                       return ControlInfoMap::Map();\n> +               return layer->vtable->controls(closure, infoMap);\n> +       }\n> +\n> +       ControlList properties(ControlList &props)\n> +       {\n> +               if (!layer->vtable->properties)\n> +                       return ControlList(controls::controls);\n> +               return layer->vtable->properties(closure, props);\n> +       }\n> +\n> +       const std::shared_ptr<LayerLoaded> layer;\n> +       void *closure = nullptr;\n> +};\n> +\n> +class LayerController\n> +{\n> +public:\n> +       LayerController(const Camera *camera, const ControlList &properties,\n> +                       const ControlInfoMap &controlInfoMap,\n> +                       const std::map<std::string, std::shared_ptr<LayerLoaded>> &layers);\n> +       ~LayerController();\n> +\n> +       void bufferCompleted(Request *request, FrameBuffer *buffer);\n> +       ControlList requestCompleted(Request *request);\n> +       void disconnected();\n> +\n> +       void acquire();\n> +       void release();\n> +\n> +       const ControlInfoMap &controls() const { return controls_; }\n> +       const ControlList &properties() const { return properties_; }\n> +\n> +       void configure(const CameraConfiguration *config,\n> +                      const ControlInfoMap &controlInfoMap);\n> +\n> +       void createRequest(uint64_t cookie, const Request *request);\n> +\n> +       void queueRequest(Request *request);\n> +\n> +       ControlList *start(const ControlList *controls);\n> +       void stop();\n> +\n> +private:\n> +       void updateProperties(const ControlList &properties);\n> +       void updateControls(const ControlInfoMap &controlInfoMap);\n> +\n> +       std::deque<std::unique_ptr<LayerInstance>> executionQueue_;\n> +\n> +       ControlInfoMap controls_;\n> +       ControlList properties_;\n> +\n> +       ControlList startControls_ = ControlList(controls::controls);\n> +};\n> +\n> +class LayerManager\n> +{\n> +public:\n> +       LayerManager();\n> +       ~LayerManager() = default;\n> +\n> +       std::unique_ptr<LayerController>\n> +       createController(const Camera *camera,\n> +                        const ControlList &properties,\n> +                        const ControlInfoMap &controlInfoMap) const;\n> +\n> +private:\n> +       std::map<std::string, std::shared_ptr<LayerLoaded>> layers_;\n> +};\n> +\n> +} /* namespace libcamera */\n> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build\n> index ed145a6dd7cd..ca96754f087e 100644\n> --- a/include/libcamera/internal/meson.build\n> +++ b/include/libcamera/internal/meson.build\n> @@ -31,6 +31,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',\n> diff --git a/include/libcamera/layer.h b/include/libcamera/layer.h\n> new file mode 100644\n> index 000000000000..1ec37256ad8d\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> +       const char *name;\n> +       int layerAPIVersion;\n> +};\n> +\n> +struct LayerInterface {\n> +       void *(*init)(const std::string &);\n> +       void (*terminate)(void *);\n> +\n> +       void (*bufferCompleted)(void *, Request *, FrameBuffer *);\n> +       ControlList (*requestCompleted)(void *, Request *);\n> +       void (*disconnected)(void *);\n> +\n> +       void (*acquire)(void *);\n> +       void (*release)(void *);\n> +\n> +       ControlInfoMap::Map (*controls)(void *, ControlInfoMap &);\n> +       ControlList (*properties)(void *, ControlList &);\n> +\n> +       void (*configure)(void *, const CameraConfiguration *);\n> +\n> +       void (*createRequest)(void *, uint64_t, const Request *);\n> +\n> +       void (*queueRequest)(void *, Request *);\n> +\n> +       void (*start)(void *, ControlList &);\n> +       void (*stop)(void *);\n> +};\n> +\n> +} /* namespace libcamera */\n> diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build\n> index 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',\n> diff --git a/src/layer/meson.build b/src/layer/meson.build\n> new file mode 100644\n> index 000000000000..45dded512a13\n> --- /dev/null\n> +++ b/src/layer/meson.build\n> @@ -0,0 +1,14 @@\n> +# SPDX-License-Identifier: CC0-1.0\n> +\n> +layer_includes = [\n> +    libcamera_includes,\n> +]\n> +\n> +layer_install_dir = libcamera_libdir / 'layer'\n> +\n> +config_h.set('LAYER_DIR',\n> +             '\"' + get_option('prefix') / layer_install_dir + '\"')\n> +\n> +layers_env = environment()\n> +layers_env.set('LIBCAMERA_LAYER_PATH', meson.current_build_dir())\n> +meson.add_devenv(layers_env)\n> diff --git a/src/libcamera/layer.cpp b/src/libcamera/layer.cpp\n> new file mode 100644\n> index 000000000000..64debfb0c9e2\n> --- /dev/null\n> +++ b/src/libcamera/layer.cpp\n> @@ -0,0 +1,179 @@\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> +#include <libcamera/layer.h>\n> +\n> +/**\n> + * \\file layer.h\n> + * \\brief Layer interface\n> + *\n> + * Layers are a construct that lives in between the application and libcamera.\n> + * They are hooked into select calls to and from Camera, and each one is\n\nI would use \"to and from the Camera class\" to be more explicit, but\nthat's perhaps just a nitpick that you needn't worry about :-)\n\n> + * executed in order.\n> + *\n> + * \\todo Expand on this, and decide on more concrete naming like module vs implementation\n> + */\n> +\n> +namespace libcamera {\n> +\n> +/**\n> + * \\struct LayerInfo\n> + * \\brief Information about a Layer implementation\n> + *\n> + * This struct gives information about a layer implementation, such as name and\n> + * API version. It must be exposed, named 'layerInfo', by the layer\n> + * implementation shared object to identify itself to the LayerManager.\n> + */\n> +\n> +/**\n> + * \\var LayerInfo::name\n> + * \\brief Name of the Layer module\n> + */\n> +\n> +/**\n> + * \\var LayerInfo::layerAPIVersion\n> + * \\brief API version of the Layer implementation\n> + */\n> +\n> +/**\n> + * \\struct LayerInterface\n> + * \\brief The function table of the Layer implementation\n> + *\n> + * This struct is the function table of a layer implementation. Any functions\n> + * that the layer implements should be filled in here, and any functions that\n> + * are not implemented must be set to nullptr. This struct, named\n> + * 'layerInterface', must be exposed by the layer implementation shared object.\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::init\n> + * \\brief Initialize the layer\n> + * \\param[in] name Name of the camera\n> + *\n> + * This function is a required function for layer implementations.\n> + *\n> + * This function is called on Camera construction, and is where the layer\n> + * implementation should allocate and initialize its closure and anything else\n> + * required to run.\n> + *\n> + * \\return A closure\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::terminate\n> + * \\brief Terminate the layer\n> + * \\param[in] closure The closure that was allocated at init()\n> + *\n> + * This function is a required function for layer implementations.\n> + *\n> + * This function is called on Camera deconstruction, and is where the layer\n> + * should free its closure and anything else that was allocated at\n> + * initialization.\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::bufferCompleted\n> + * \\brief Hook for Camera::bufferCompleted\n> + * \\param[in] closure The closure of the layer\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::requestCompleted\n> + * \\brief Hook for Camera::requestCompleted\n> + * \\param[in] closure The closure of the layer\n> + * \\return Metadata that the layer wants to set\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::disconnected\n> + * \\brief Hook for Camera::disconnected\n> + * \\param[in] closure The closure of the layer\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::acquire\n> + * \\brief Hook for Camera::acquire\n> + * \\param[in] closure The closure of the layer\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::release\n> + * \\brief Hook for Camera::release\n> + * \\param[in] closure The closure of the layer\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::controls\n> + * \\brief Declare the controls supported by the Layer\n> + * \\param[in] closure The closure of the layer\n> + * \\param[in] controlInfoMap The cumulative ControlInfoMap of supported controls of the Camera and any previous layers\n> + *\n> + * This function is for the layer implementation to declare the controls that\n> + * it supports. This will be called by the LayerManager at Camera::init() time\n> + * (after LayerInterface::init()), and at Camera::configure() time. The latter\n> + * gives a chance for the controls to be updated if the configuration changes\n> + * them.\n> + *\n> + * The controls that are returned by this function will overwrite any\n> + * duplicates that were in the input parameter controls.\n> + *\n> + * \\return The additional controls that this Layer implements\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::properties\n> + * \\brief Declare the properties supported by the Layer\n> + * \\param[in] closure The closure of the layer\n> + * \\param[in] controlList The cumulative properties of the Camera and any previous layers\n> + *\n> + * This function is for the layer implementation to declare the properies that\n> + * it wants to declare. This will be called by the LayerManager once at\n> + * Camera::init() time (after LayerInterface::init(), and before\n> + * LayerInterface::controls()).\n> + *\n> + * The properties that are returned by this function will overwrite any\n> + * duplicates that were in the input parameter properties.\n> + *\n> + * \\return The additional properties that this Layer declares\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::configure\n> + * \\brief Hook for Camera::configure\n> + * \\param[in] closure The closure of the layer\n> + * \\param[in] cameraConfiguration The camera configuration\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::createRequest\n> + * \\brief Hook for Camera::createRequest\n> + * \\param[in] closure The closure of the layer\n> + * \\param[in] cookie An opaque cookie for the application\n> + * \\param[in] request The request that was just created by the Camera\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::queueRequest\n> + * \\brief Hook for Camera::queueRequest\n> + * \\param[in] closure The closure of the layer\n> + * \\param[in] request The request that was queued\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::start\n> + * \\brief Hook for Camera::start\n> + * \\param[in] closure The closure of the layer\n> + * \\param[in] controls The controls to be set before starting capture\n> + */\n> +\n> +/**\n> + * \\var LayerInterface::stop\n> + * \\brief Hook for Camera::stop\n> + * \\param[in] closure The closure of the layer\n> + */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/layer_manager.cpp b/src/libcamera/layer_manager.cpp\n> new file mode 100644\n> index 000000000000..bef69f6bd04b\n> --- /dev/null\n> +++ b/src/libcamera/layer_manager.cpp\n> @@ -0,0 +1,612 @@\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> +\n> +#include <libcamera/base/file.h>\n> +#include <libcamera/base/log.h>\n> +#include <libcamera/base/span.h>\n> +#include <libcamera/base/utils.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(LayerLoaded)\n> +LOG_DEFINE_CATEGORY(LayerController)\n> +LOG_DEFINE_CATEGORY(LayerManager)\n> +\n> +/**\n> + * \\class LayerLoaded\n> + * \\brief A wrapper class for a Layer shared object that has been loaded\n> + *\n> + * This class wraps a Layer shared object that has been loaded, managing the\n> + * lifetime and management of the dlopened handle as well as organizing access\n> + * to the function table and layer info.\n> + *\n> + * Instances of this class shall not be used directly. LayerInstance, which\n> + * wraps LayerLoaded, shall be used instead.\n> + *\n> + * \\sa LayerInstance\n> + */\n> +\n> +/**\n> + * \\var LayerLoaded::info\n> + * \\brief Information about the Layer\n> + */\n> +\n> +/**\n> + * \\var LayerLoaded::vtable\n> + * \\brief The function table of the layer\n\nAh, here it is; why not ftable?\n\nBest wishes,\nIsaac\n> + */\n> +\n> +/**\n> + * \\var LayerLoaded::dlHandle\n> + * \\brief The handle as returned by dlopen for the layer shared object\n> + */\n> +\n> +/**\n> + * \\var LayerLoaded::valid\n> + * \\brief Whether or not the loaded layer is valid\n> + *\n> + * Instances that failed to load due to error, or that were constructed with no\n> + * parameters will be invalid.\n> + */\n> +\n> +/**\n> + * \\brief Load a Layer from a shared object file\n> + */\n> +LayerLoaded::LayerLoaded(const std::string &filename)\n> +{\n> +       File file{ filename };\n> +       if (!file.open(File::OpenModeFlag::ReadOnly)) {\n> +               LOG(LayerLoaded, Error) << \"Failed to open layer: \"\n> +                                       << strerror(-file.error());\n> +               return;\n> +       }\n> +\n> +       Span<const uint8_t> data = file.map();\n> +       int ret = utils::elfVerifyIdent(data);\n> +       if (ret) {\n> +               LOG(LayerLoaded, Error) << \"Layer is not an ELF file\";\n> +               return;\n> +       }\n> +\n> +       Span<const uint8_t> layerInfoSym = utils::elfLoadSymbol(data, \"layerInfo\");\n> +       if (layerInfoSym.size() < sizeof(LayerInfo)) {\n> +               LOG(LayerLoaded, Error) << \"Layer has no valid layerInfoSym\";\n> +               return;\n> +       }\n> +\n> +       void *dlh = dlopen(file.fileName().c_str(), RTLD_LAZY);\n> +       if (!dlh) {\n> +               LOG(LayerLoaded, Error)\n> +                       << \"Failed to open layer shared object: \"\n> +                       << dlerror();\n> +               return;\n> +       }\n> +\n> +       /* No need to dlclose as the deconstructor will handle it */\n> +\n> +       void *layerInfoDl = dlsym(dlh, \"layerInfo\");\n> +       if (!layerInfoDl) {\n> +               LOG(LayerLoaded, Error)\n> +                       << \"Failed to load layerInfo from layer shared object: \"\n> +                       << dlerror();\n> +               return;\n> +       }\n> +\n> +       void *vtableSym = dlsym(dlh, \"layerInterface\");\n> +       if (!vtableSym) {\n> +               LOG(LayerLoaded, Error)\n> +                       << \"Failed to load layerInterface from layer shared object: \"\n> +                       << dlerror();\n> +               return;\n> +       }\n> +\n> +       info = static_cast<LayerInfo *>(layerInfoDl);\n> +       vtable = static_cast<LayerInterface *>(vtableSym);\n> +       dlHandle = dlh;\n> +\n> +       /* \\todo Implement this. It should come from the libcamera version */\n> +       if (info->layerAPIVersion != 1) {\n> +               LOG(LayerLoaded, Error) << \"Layer '\" << info->name\n> +                                       << \"' API version mismatch\";\n> +               return;\n> +       }\n> +\n> +       /* \\todo Document these requirements */\n> +       if (!vtable->init) {\n> +               LOG(LayerLoaded, Error) << \"Layer '\" << info->name\n> +                                       << \"' doesn't implement init\";\n> +               return;\n> +       }\n> +\n> +       /* \\todo Document these requirements */\n> +       if (!vtable->terminate) {\n> +               LOG(LayerLoaded, Error) << \"Layer '\" << info->name\n> +                                       << \"' doesn't implement terminate\";\n> +               return;\n> +       }\n> +\n> +       /* \\todo Validate the layer name. */\n> +\n> +       valid = true;\n> +\n> +       return;\n> +}\n> +\n> +/**\n> + * \\fn LayerLoaded::LayerLoaded(LayerLoaded &&other)\n> + * \\brief Move constructor\n> + */\n> +\n> +/**\n> + * \\fn LayerLoaded &LayerLoaded::operator=(LayerLoaded &&other)\n> + * \\brief Move assignment operator\n> + */\n> +\n> +/**\n> + * \\def LAYER_INSTANCE_CALL\n> + * \\brief Call a function from the layer's vtable\n> + * \\param func The function name to call from the vtable\n> + *\n> + * This is a convenience function to reduce code duplication for checking that\n> + * the function exists in the layer's vtable before calling it.\n> + */\n> +\n> +/**\n> + * \\struct LayerInstance\n> + * \\brief A Layer that has been instantiated\n> + *\n> + * This struct represents a Layer that has been instantiated. It wraps an\n> + * instance-agnostic LayerLoaded (which notably contains the layer vtable) with\n> + * instance-specific data (in a closure).\n> + *\n> + * All functions (except for the constructor, controls(), and properties()) are\n> + * simply a redirection into the underlying LayerLoaded's vtable, plus a check\n> + * (if appropriate) that the function exists in the vtable.\n> + */\n> +\n> +/**\n> + * \\fn LayerInstance::LayerInstance(const std::shared_ptr<LayerLoaded> &l)\n> + * \\brief Construct a LayerInstance from a LayerLoaded\n> + * \\param l LayerLoaded from which to construct a LayerInstance\n> + */\n> +\n> +/**\n> + * \\fn LayerInstance::init(const std::string &id)\n> + * \\brief Initialize the layer\n> + * \\param id Name of the camera\n> + *\n> + * This function calls init() of the wrapped LayerLoaded, and saves the closure\n> + * that is returned by LayerLoaded::init().\n> + *\n> + * \\sa LayerLoaded::init()\n> + */\n> +\n> +/**\n> + * \\fn LayerInstance::terminate()\n> + * \\brief Terminate the layer\n> + *\n> + * This function calls terminate() of the wrapped LayerLoaded.\n> + *\n> + * \\sa LayerLoaded::terminate()\n> + */\n> +\n> +/**\n> + * \\fn LayerInstance::requestCompleted(Request *request)\n> + * \\brief Handler for when a Request completes\n> + * \\param request The request that has completed\n> + * \\return The metadata that this Layer reports for the completed \\a request\n> + */\n> +\n> +/**\n> + * \\fn LayerInstance::controls(ControlInfoMap &infoMap)\n> + * \\brief Declare the controls supported by the Layer\n> + * \\param infoMap The cumulative ControlInfoMap of supported controls of the\n> + * Camera and any previous layers\n> + *\n> + * This function calls controls() of the wrapped LayerLoaded, if controls()\n> + * exists in the vtable. If it does not then an empty ControlInfoMap::Map is\n> + * returned.\n> + *\n> + * \\sa LayerLoaded::controls()\n> + *\n> + * \\return The additional controls that this Layer implements, or an empty\n> + * ControlInfoMap::Map if controls() is not implemented by the Layer\n> + */\n> +\n> +/**\n> + * \\fn LayerInstance::properties(ControlList &props)\n> + * \\brief Declare the properties supported by the Layer\n> + * \\param props The cumulative properties of the Camera and any previous layers\n> + *\n> + * This function calls properties() of the wrapped LayerLoaded, if properties()\n> + * exists in the vtable. If it does not then an empty ControlList (made from\n> + * controls::controls) is returned.\n> + *\n> + * \\sa LayerLoaded::properties()\n> + *\n> + * \\return The additional properties that this Layer implements, or an empty\n> + * ControlList if properties() is not implemented by the Layer\n> + */\n> +\n> +/**\n> + * \\var LayerInstance::layer\n> + * \\brief The LayerLoaded that this LayerInstance wraps\n> + */\n> +\n> +/**\n> + * \\var LayerInstance::closure\n> + * \\brief The closure of this LayerInstance\n> + */\n> +\n> +/**\n> + * \\class LayerController\n> + * \\brief Per-Camera instance of a layer manager\n> + *\n> + * Conceptually this class is an instantiation of the LayerManager for each\n> + * Camera instance. It contains the closure for of each layer specific to the\n> + * Camera, as well as the queue of layers to execute for each Camera.\n> + */\n> +\n> +/**\n> + * \\brief Initialize the Layers\n> + * \\param[in] camera The Camera for whom to initialize layers\n> + * \\param[in] properties The Camera properties\n> + * \\param[in] controlInfoMap The Camera controls\n> + * \\param[in] layers Map of available layers\n> + *\n> + * This is called by the Camera at construction time via\n> + * LayerManager::createController. The LayerManager feeds the list of layers\n> + * that are available, and the LayerController can then create its own\n> + * execution queue and initialize all the layers for its Camera.\n> + *\n> + * \\a properties and \\a controlInfoMap are passed in so that the Layers can\n> + * modify them, although they will be cached in an internal copy that can be\n> + * efficiently returned at properties() and controls(), respectively.\n> + */\n> +LayerController::LayerController(const Camera *camera,\n> +                                const ControlList &properties,\n> +                                const ControlInfoMap &controlInfoMap,\n> +                                const std::map<std::string, std::shared_ptr<LayerLoaded>> &layers)\n> +{\n> +       /* Order the layers */\n> +       /* \\todo Document this. First is closer to application, last is closer to libcamera */\n> +       /* \\todo Get this from configuration file */\n> +       const char *layerList = utils::secure_getenv(\"LIBCAMERA_LAYERS_ENABLE\");\n> +       if (layerList) {\n> +               for (const auto &layerName : utils::split(layerList, \",\")) {\n> +                       if (layerName.empty())\n> +                               continue;\n> +\n> +                       const auto &it = layers.find(layerName);\n> +                       if (it == layers.end()) {\n> +                               LOG(LayerController, Warning)\n> +                                       << \"Requested layer '\" << layerName\n> +                                       << \"' not found\";\n> +                               continue;\n> +                       }\n> +\n> +                       executionQueue_.emplace_back(std::make_unique<LayerInstance>(it->second));\n> +               }\n> +       }\n> +\n> +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> +               layer->init(camera->id());\n> +\n> +       /*\n> +        * We need to iterate over the layers individually to merge all of\n> +        * their controls, so we'll factor out updateControls() as it needs to be\n> +        * run again at configure().\n> +        */\n> +       updateProperties(properties);\n> +       updateControls(controlInfoMap);\n> +}\n> +\n> +/**\n> + * \\brief Terminate the Layers\n> + *\n> + * This is called by the Camera at deconstruction time. The LayerController\n> + * instructs all Layer instances to release the resources that they allocated\n> + * for this specific \\a camera.\n> + */\n> +LayerController::~LayerController()\n> +{\n> +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> +               layer->terminate();\n> +}\n> +\n> +/**\n> + * \\brief Hook for Camera::bufferCompleted\n> + * \\param[in] request The request whose buffer completed\n> + * \\param[in] buffer The buffer that completed\n> + */\n> +void LayerController::bufferCompleted(Request *request, FrameBuffer *buffer)\n> +{\n> +       /* Reverse order because this comes from a Signal emission */\n> +       for (auto it = executionQueue_.rbegin();\n> +            it != executionQueue_.rend(); it++) {\n> +               (*it)->bufferCompleted(request, buffer);\n> +       }\n> +}\n> +\n> +/**\n> + * \\brief Hook for Camera::requestCompleted\n> + * \\param[in] request The request that completed\n> + * \\return metadata set by all the layers\n> + */\n> +ControlList LayerController::requestCompleted(Request *request)\n> +{\n> +       ControlList ret(controls::controls);\n> +\n> +       /* Reverse order because this comes from a Signal emission */\n> +       for (auto it = executionQueue_.rbegin();\n> +            it != executionQueue_.rend(); it++) {\n> +               ControlList metadata = (*it)->requestCompleted(request);\n> +               ret.merge(metadata, ControlList::MergePolicy::OverwriteExisting);\n> +       }\n> +\n> +       return ret;\n> +}\n> +\n> +/**\n> + * \\brief Hook for Camera::disconnected\n> + */\n> +void LayerController::disconnected()\n> +{\n> +       /* Reverse order because this comes from a Signal emission */\n> +       for (auto it = executionQueue_.rbegin();\n> +            it != executionQueue_.rend(); it++) {\n> +               (*it)->disconnected();\n> +       }\n> +}\n> +\n> +/**\n> + * \\brief Hook for Camera::acquire\n> + */\n> +void LayerController::acquire()\n> +{\n> +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> +               layer->acquire();\n> +}\n> +\n> +/**\n> + * \\brief Hook for Camera::release\n> + */\n> +void LayerController::release()\n> +{\n> +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> +               layer->release();\n> +}\n> +\n> +/**\n> + * \\fn LayerController::controls\n> + * \\brief Hook for Camera::controls\n> + * \\return A ControlInfoMap that merges the Camera's controls() with the ones\n> + * declared by the layers\n> + */\n> +\n> +/**\n> + * \\fn LayerController::properties\n> + * \\brief Hook for Camera::properties\n> + * \\return A properties list that merges the Camera's properties() with the\n> + * ones declared by the layers\n> + */\n> +\n> +void LayerController::updateProperties(const ControlList &properties)\n> +{\n> +       ControlList props = properties;\n> +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_) {\n> +               ControlList ret = layer->properties(props);\n> +               props.merge(ret, ControlList::MergePolicy::OverwriteExisting);\n> +       }\n> +       properties_ = props;\n> +}\n> +\n> +void LayerController::updateControls(const ControlInfoMap &controlInfoMap)\n> +{\n> +       ControlInfoMap infoMap = controlInfoMap;\n> +       /* \\todo Simplify this once ControlInfoMaps become easier to modify */\n> +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_) {\n> +               ControlInfoMap::Map ret = layer->controls(infoMap);\n> +               ControlInfoMap::Map map;\n> +               /* Merge the layer's ret first since insert doesn't overwrite */\n> +               map.insert(ret.begin(), ret.end());\n> +               map.insert(infoMap.begin(), infoMap.end());\n> +               infoMap = ControlInfoMap(std::move(map),\n> +                                        libcamera::controls::controls);\n> +       }\n> +       controls_ = infoMap;\n> +}\n> +\n> +/**\n> + * \\brief Hook for Camera::configure\n> + * \\param[in] config The configuration\n> + * \\param[in] controlInfoMap The ControlInfoMap of the controls that \\a camera supports\n> + *\n> + * \\a controlInfoMap is passed in as this is a potential point where the limits\n> + * of controls could change, so this gives a chance for the Layers to update\n> + * the ControlInfoMap that will be returned by LayerController::controls().\n> + */\n> +void LayerController::configure(const CameraConfiguration *config,\n> +                               const ControlInfoMap &controlInfoMap)\n> +{\n> +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> +               layer->configure(config);\n> +\n> +       updateControls(controlInfoMap);\n> +}\n> +\n> +/**\n> + * \\brief Hook for Camera::createRequest\n> + * \\param[in] cookie An opaque cookie for the application\n> + * \\param[in] request The request that was created\n> + */\n> +void LayerController::createRequest(uint64_t cookie, const Request *request)\n> +{\n> +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> +               layer->createRequest(cookie, request);\n> +}\n> +\n> +/**\n> + * \\brief Hook for Camera::queueRequest\n> + * \\param[in] request The request that is being queued\n> + */\n> +void LayerController::queueRequest(Request *request)\n> +{\n> +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> +               layer->queueRequest(request);\n> +}\n> +\n> +/**\n> + * \\brief Hook for Camera::start\n> + * \\param[in] controls The controls to be applied before starting the capture\n> + * \\return A ControlList that merges controls set by the layers and \\a controls\n> + */\n> +ControlList *LayerController::start(const ControlList *controls)\n> +{\n> +       if (controls) {\n> +               /* Clear any leftover start controls from a previous run */\n> +               startControls_.clear();\n> +               startControls_.merge(*controls);\n> +       }\n> +\n> +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> +               layer->start(startControls_);\n> +\n> +       return &startControls_;\n> +}\n> +\n> +/**\n> + * \\brief Hook for Camera::stop\n> + */\n> +void LayerController::stop()\n> +{\n> +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> +               layer->stop();\n> +}\n> +\n> +/**\n> + * \\class LayerManager\n> + * \\brief Layer manager\n> + *\n> + * The Layer manager discovers layer implementations from the filesystem, and\n> + * creates LayerLoaded instances for each one.\n> + *\n> + * The LayerManager itself is instantiated by the CameraManager, and each\n> + * Camera interacts with the LayerManager by passing itself in to\n> + * LayerManager::createController. The LayerManager internally maps each Camera\n> + * to a list of Layer instances that it calls sequentially for each hook.\n> + */\n> +\n> +/**\n> + * \\brief Construct a LayerManager instance\n> + *\n> + * The LayerManager class is meant be instantiated by the CameraManager.\n> + *\n> + * This function simply loads all available layers and stores them. The\n> + * LayerController is responsible for organizing them into queues to be\n> + * executed, and for managing closures for each Camera that they belong to.\n> + */\n> +LayerManager::LayerManager()\n> +{\n> +       /* This is so that we can capture it in the lambda below */\n> +       std::map<std::string, std::shared_ptr<LayerLoaded>> &layers = layers_;\n> +\n> +       /* \\todo Implement built-in layers */\n> +\n> +       /* This returns the number of \"modules\" successfully loaded */\n> +       std::function<int(const std::string &)> soHandler =\n> +       [&layers](const std::string &file) {\n> +               std::shared_ptr<LayerLoaded> layer = std::make_shared<LayerLoaded>(file);\n> +               if (!layer->valid)\n> +                       return 0;\n> +\n> +               LOG(LayerManager, Debug) << \"Loaded layer '\" << file << \"'\";\n> +\n> +               auto [it, inserted] =\n> +                       layers.try_emplace(std::string(layer->info->name),\n> +                                          std::move(layer));\n> +               if (!inserted)\n> +                       LOG(LayerManager, Warning)\n> +                               << \"Not adding duplicate layer '\"\n> +                               << layer->info->name << \"'\";\n> +\n> +               return 1;\n> +       };\n> +\n> +       /* User-specified paths take precedence. */\n> +       /* \\todo Document this */\n> +       const char *layerPaths = utils::secure_getenv(\"LIBCAMERA_LAYER_PATH\");\n> +       if (layerPaths) {\n> +               for (const auto &dir : utils::split(layerPaths, \":\")) {\n> +                       if (dir.empty())\n> +                               continue;\n> +\n> +                       /*\n> +                        * \\todo Move the shared objects into one directory\n> +                        * instead of each in their own subdir\n> +                        */\n> +                       utils::findSharedObjects(dir.c_str(), 1, soHandler);\n> +               }\n> +       }\n> +\n> +       /*\n> +        * When libcamera is used before it is installed, load layers from the\n> +        * same build directory as the libcamera library itself.\n> +        */\n> +       std::string root = utils::libcameraBuildPath();\n> +       if (!root.empty()) {\n> +               std::string layerBuildPath = root + \"src/layer\";\n> +               constexpr int maxDepth = 2;\n> +\n> +               LOG(LayerManager, Info)\n> +                       << \"libcamera is not installed. Adding '\"\n> +                       << layerBuildPath << \"' to the layer search path\";\n> +\n> +               utils::findSharedObjects(layerBuildPath.c_str(), maxDepth, soHandler);\n> +       }\n> +\n> +       /* Finally try to load layers from the installed system path. */\n> +       utils::findSharedObjects(LAYER_DIR, 1, soHandler);\n> +}\n> +\n> +/**\n> + * \\brief Create a LayerController instance\n> + * \\param[in] camera The Camera instance for whom to create a LayerController\n> + * \\param[in] properties The Camera properties\n> + * \\param[in] controlInfoMap The Camera controls\n> + */\n> +std::unique_ptr<LayerController>\n> +LayerManager::createController(const Camera *camera,\n> +                              const ControlList &properties,\n> +                              const ControlInfoMap &controlInfoMap) const\n> +{\n> +       return std::make_unique<LayerController>(camera, properties, controlInfoMap, layers_);\n> +}\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index 186d5c48ccd5..076cfda66060 100644\n> --- a/src/libcamera/meson.build\n> +++ b/src/libcamera/meson.build\n> @@ -41,6 +41,8 @@ libcamera_internal_sources = files([\n>      'ipc_pipe.cpp',\n>      'ipc_pipe_unixsocket.cpp',\n>      'ipc_unixsocket.cpp',\n> +    'layer.cpp',\n> +    'layer_manager.cpp',\n>      'mapped_framebuffer.cpp',\n>      'matrix.cpp',\n>      'media_device.cpp',\n> diff --git a/src/meson.build b/src/meson.build\n> index 9b63c8e845d8..ef296382718c 100644\n> --- a/src/meson.build\n> +++ b/src/meson.build\n> @@ -77,6 +77,7 @@ subdir('libcamera')\n>  \n>  subdir('android')\n>  subdir('ipa')\n> +subdir('layer')\n>  \n>  subdir('apps')\n>  \n> -- \n> 2.47.2\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 E770CC0DA4\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 24 Feb 2026 16:02:46 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 3DBFF6229F;\n\tTue, 24 Feb 2026 17:02:46 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id A34AB62080\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 24 Feb 2026 17:02:44 +0100 (CET)","from thinkpad.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 97CB5AB4;\n\tTue, 24 Feb 2026 17:01:47 +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=\"LE6aHsv9\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1771948907;\n\tbh=uhvQY+Jd7ITg1+we9BYEcsXAxQy1IDvgNTq1ZQGeQgM=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=LE6aHsv9j+EZRLgFvkb29lkWnkXGsljxQ9iPo0/Y7SqxJUnJM+XS6zQTM5MJDFfT5\n\tLZGbVKKIifI8YIDMrrQ8B5frP6i7T+2hiCiNxpEJ6gdEgjAW3vWX1IWlKAmBO8rccR\n\tDtvp+AXDF0qOqagIIM+i+zoBHfVCB/3RRP1fjP5w=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20260129082814.1777779-5-paul.elder@ideasonboard.com>","References":"<20260129082814.1777779-1-paul.elder@ideasonboard.com>\n\t<20260129082814.1777779-5-paul.elder@ideasonboard.com>","Subject":"Re: [PATCH v6 4/8] libcamera: layer_manager: Add Layer handling\n\timplementation","From":"Isaac Scott <isaac.scott@ideasonboard.com>","Cc":"Paul Elder <paul.elder@ideasonboard.com>","To":"Paul Elder <paul.elder@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Tue, 24 Feb 2026 16:02:41 +0000","Message-ID":"<177194896117.1513537.12265013306022510835@t16>","User-Agent":"alot/0.10","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>"}},{"id":38297,"web_url":"https://patchwork.libcamera.org/comment/38297/","msgid":"<177198712030.607498.11650178776796019669@neptunite.rasen.tech>","date":"2026-02-25T02:38:40","subject":"Re: [PATCH v6 4/8] libcamera: layer_manager: Add Layer handling\n\timplementation","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"Hi Isaac,\n\nThanks for the review.\n\nQuoting Isaac Scott (2026-02-25 01:02:41)\n> Quoting Paul Elder (2026-01-29 08:28:10)\n> > We want to be able to implement layers in libcamera, which conceptually\n> > sit in between the Camera class and the application. This can be useful\n> > for implementing things that don't belong inside the Camera/IPA nor inside\n> > the application, such as intercepting and translating the AeEnable\n> > control, or implementing the Sync algorithm.\n> > \n> > To achieve this, add:\n> > - a LayerManager, which searches for and loads layers from shared object\n> >   files\n> > - a LayerController, which belongs to a Camera and orchestrates\n> >   selecting layers for the Camera and executing them.\n> > \n> > Actually calling into these functions from the Camera class will be\n> > added in the following patch.\n> > \n> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> > \n> > ---\n> > Changes in v6:\n> > - make requestCompleted return metadata, so that layers can set\n> >   metadata (since Request::metadata() now returns a const)\n> > \n> > Changes in v5:\n> > - add documentation to LayerInstance\n> > \n> > Changes in v4:\n> > - add LayerInstance to wrap LayerLoaded and closure to remove the need\n> >   for a map to store the closures in LayerController\n> >   - Also add a level of indirection in LayerInstance to ease calling\n> >     into the Layer functions from LayerController and remove the need to\n> >     get and pass the closures at the LayerController level\n> > - make LayerManager::createController() const\n> > - add LIBCAMERA_LAYER_PATH to meson devenv\n> > \n> > Changes in v3:\n> > - change start() signature so that the layers can modify the initial\n> >   control list\n> >   - this is done by the LayerManager having it's own copy of the control\n> >     list that is modified by the layers, and it returns it to the Camera\n> >     on its hook\n> > - expand documentation about the LayerManager\n> > - add documentation for all the LayerManager and Layer functions\n> > - clear properties_ and controls_ on terminate()\n> > - add LayerController which belongs to a Camera, to remove from\n> >   LayerManager having to manage a map of camera-layer keys to the\n> >   closures for each layer\n> > \n> > Changes 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 | 205 +++++++\n> >  include/libcamera/internal/meson.build     |   1 +\n> >  include/libcamera/layer.h                  |  54 ++\n> >  include/libcamera/meson.build              |   1 +\n> >  src/layer/meson.build                      |  14 +\n> >  src/libcamera/layer.cpp                    | 179 ++++++\n> >  src/libcamera/layer_manager.cpp            | 612 +++++++++++++++++++++\n> >  src/libcamera/meson.build                  |   2 +\n> >  src/meson.build                            |   1 +\n> >  9 files changed, 1069 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.cpp\n> >  create mode 100644 src/libcamera/layer_manager.cpp\n> > \n> > diff --git a/include/libcamera/internal/layer_manager.h b/include/libcamera/internal/layer_manager.h\n> > new file mode 100644\n> > index 000000000000..8427ec3eb5d9\n> > --- /dev/null\n> > +++ b/include/libcamera/internal/layer_manager.h\n> > @@ -0,0 +1,205 @@\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 <memory>\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/control_ids.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(LayerLoaded)\n> > +LOG_DECLARE_CATEGORY(LayerController)\n> > +LOG_DECLARE_CATEGORY(LayerManager)\n> > +\n> > +/* Extend the layer with information specific to load-handling */\n> > +struct LayerLoaded {\n> > +       LayerLoaded() = default;\n> > +\n> > +       LayerLoaded(const std::string &file);\n> > +\n> > +       LayerLoaded(LayerLoaded &&other)\n> > +               : info(other.info), vtable(other.vtable),\n> > +                 dlHandle(other.dlHandle), valid(other.valid)\n> > +       {\n> > +               other.dlHandle = nullptr;\n> > +       }\n> > +\n> > +       LayerLoaded &operator=(LayerLoaded &&other)\n> > +       {\n> > +               info = other.info;\n> > +               vtable = other.vtable;\n> > +               dlHandle = other.dlHandle;\n> > +               other.dlHandle = nullptr;\n> > +               valid = other.valid;\n> > +               return *this;\n> > +       }\n> > +\n> > +       ~LayerLoaded()\n> > +       {\n> > +               if (dlHandle)\n> > +                       dlclose(dlHandle);\n> > +       }\n> > +\n> > +       LayerInfo *info = nullptr;\n> > +       LayerInterface *vtable = nullptr;\n> \n> I may be missing something here, but what is vtable? Variable table?\n\nApparently it's virtual method table. C++ uses them for class virtual functions\niirc.\n\n> \n> > +       void *dlHandle = nullptr;\n> > +       bool valid = false;\n> > +\n> > +private:\n> > +       LIBCAMERA_DISABLE_COPY(LayerLoaded)\n> > +};\n> > +\n> > +#ifndef __DOXYGEN__\n> > +#define _ARG_PARAMS2(type1, type2) type1 arg1, type2 arg2\n> > +#define _ARG_NAMES2(type1, type2) arg1, arg2\n> > +\n> > +#define _ARG_PARAMS1(type1) type1 arg1\n> > +#define _ARG_NAMES1(type1) arg1\n> > +\n> > +#define _ARG_PARAMS0()\n> > +#define _ARG_NAMES0()\n> > +\n> > +#define _GET_OVERRIDE(_1, _2, _3, NAME, ...) NAME\n> > +\n> > +#define ARG_PARAMS(...) _GET_OVERRIDE(\"ignored\", __VA_ARGS__ __VA_OPT__(,) \\\n> > +                   _ARG_PARAMS2, _ARG_PARAMS1, _ARG_PARAMS0)(__VA_ARGS__)\n> > +\n> > +#define ARG_NAMES(...) _GET_OVERRIDE(\"ignored\", __VA_ARGS__ __VA_OPT__(,) \\\n> > +                   _ARG_NAMES2, _ARG_NAMES1, _ARG_NAMES0)(__VA_ARGS__)\n> > +#endif /* __DOXYGEN__ */\n> > +\n> > +#define LAYER_INSTANCE_CALL(func, ...) \\\n> > +       void func(ARG_PARAMS(__VA_ARGS__)) \\\n> > +       { \\\n> > +               if (layer->vtable->func) \\\n> > +                       layer->vtable->func(closure __VA_OPT__(,) ARG_NAMES(__VA_ARGS__)); \\\n> > +       }\n> > +\n> > +struct LayerInstance {\n> > +       LayerInstance(const std::shared_ptr<LayerLoaded> &l)\n> > +               : layer(l)\n> > +       {\n> > +       }\n> > +\n> > +       void init(const std::string &id)\n> > +       {\n> > +               closure = layer->vtable->init(id);\n> > +       }\n> > +\n> > +       void terminate()\n> > +       {\n> > +               layer->vtable->terminate(closure);\n> > +       }\n> > +\n> > +       LAYER_INSTANCE_CALL(bufferCompleted, Request *, FrameBuffer *)\n> > +       LAYER_INSTANCE_CALL(disconnected)\n> > +       LAYER_INSTANCE_CALL(acquire)\n> > +       LAYER_INSTANCE_CALL(release)\n> > +       LAYER_INSTANCE_CALL(configure, const CameraConfiguration *)\n> > +       LAYER_INSTANCE_CALL(createRequest, uint64_t, const Request *)\n> > +       LAYER_INSTANCE_CALL(queueRequest, Request *)\n> > +       LAYER_INSTANCE_CALL(start, ControlList &)\n> > +       LAYER_INSTANCE_CALL(stop)\n> > +\n> > +       ControlList requestCompleted(Request *request)\n> > +       {\n> > +               if (!layer->vtable->requestCompleted)\n> > +                       return ControlList(controls::controls);\n> > +               return layer->vtable->requestCompleted(closure, request);\n> > +       }\n> > +\n> > +       ControlInfoMap::Map controls(ControlInfoMap &infoMap)\n> > +       {\n> > +               if (!layer->vtable->controls)\n> > +                       return ControlInfoMap::Map();\n> > +               return layer->vtable->controls(closure, infoMap);\n> > +       }\n> > +\n> > +       ControlList properties(ControlList &props)\n> > +       {\n> > +               if (!layer->vtable->properties)\n> > +                       return ControlList(controls::controls);\n> > +               return layer->vtable->properties(closure, props);\n> > +       }\n> > +\n> > +       const std::shared_ptr<LayerLoaded> layer;\n> > +       void *closure = nullptr;\n> > +};\n> > +\n> > +class LayerController\n> > +{\n> > +public:\n> > +       LayerController(const Camera *camera, const ControlList &properties,\n> > +                       const ControlInfoMap &controlInfoMap,\n> > +                       const std::map<std::string, std::shared_ptr<LayerLoaded>> &layers);\n> > +       ~LayerController();\n> > +\n> > +       void bufferCompleted(Request *request, FrameBuffer *buffer);\n> > +       ControlList requestCompleted(Request *request);\n> > +       void disconnected();\n> > +\n> > +       void acquire();\n> > +       void release();\n> > +\n> > +       const ControlInfoMap &controls() const { return controls_; }\n> > +       const ControlList &properties() const { return properties_; }\n> > +\n> > +       void configure(const CameraConfiguration *config,\n> > +                      const ControlInfoMap &controlInfoMap);\n> > +\n> > +       void createRequest(uint64_t cookie, const Request *request);\n> > +\n> > +       void queueRequest(Request *request);\n> > +\n> > +       ControlList *start(const ControlList *controls);\n> > +       void stop();\n> > +\n> > +private:\n> > +       void updateProperties(const ControlList &properties);\n> > +       void updateControls(const ControlInfoMap &controlInfoMap);\n> > +\n> > +       std::deque<std::unique_ptr<LayerInstance>> executionQueue_;\n> > +\n> > +       ControlInfoMap controls_;\n> > +       ControlList properties_;\n> > +\n> > +       ControlList startControls_ = ControlList(controls::controls);\n> > +};\n> > +\n> > +class LayerManager\n> > +{\n> > +public:\n> > +       LayerManager();\n> > +       ~LayerManager() = default;\n> > +\n> > +       std::unique_ptr<LayerController>\n> > +       createController(const Camera *camera,\n> > +                        const ControlList &properties,\n> > +                        const ControlInfoMap &controlInfoMap) const;\n> > +\n> > +private:\n> > +       std::map<std::string, std::shared_ptr<LayerLoaded>> layers_;\n> > +};\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build\n> > index ed145a6dd7cd..ca96754f087e 100644\n> > --- a/include/libcamera/internal/meson.build\n> > +++ b/include/libcamera/internal/meson.build\n> > @@ -31,6 +31,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',\n> > diff --git a/include/libcamera/layer.h b/include/libcamera/layer.h\n> > new file mode 100644\n> > index 000000000000..1ec37256ad8d\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> > +       const char *name;\n> > +       int layerAPIVersion;\n> > +};\n> > +\n> > +struct LayerInterface {\n> > +       void *(*init)(const std::string &);\n> > +       void (*terminate)(void *);\n> > +\n> > +       void (*bufferCompleted)(void *, Request *, FrameBuffer *);\n> > +       ControlList (*requestCompleted)(void *, Request *);\n> > +       void (*disconnected)(void *);\n> > +\n> > +       void (*acquire)(void *);\n> > +       void (*release)(void *);\n> > +\n> > +       ControlInfoMap::Map (*controls)(void *, ControlInfoMap &);\n> > +       ControlList (*properties)(void *, ControlList &);\n> > +\n> > +       void (*configure)(void *, const CameraConfiguration *);\n> > +\n> > +       void (*createRequest)(void *, uint64_t, const Request *);\n> > +\n> > +       void (*queueRequest)(void *, Request *);\n> > +\n> > +       void (*start)(void *, ControlList &);\n> > +       void (*stop)(void *);\n> > +};\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build\n> > index 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',\n> > diff --git a/src/layer/meson.build b/src/layer/meson.build\n> > new file mode 100644\n> > index 000000000000..45dded512a13\n> > --- /dev/null\n> > +++ b/src/layer/meson.build\n> > @@ -0,0 +1,14 @@\n> > +# SPDX-License-Identifier: CC0-1.0\n> > +\n> > +layer_includes = [\n> > +    libcamera_includes,\n> > +]\n> > +\n> > +layer_install_dir = libcamera_libdir / 'layer'\n> > +\n> > +config_h.set('LAYER_DIR',\n> > +             '\"' + get_option('prefix') / layer_install_dir + '\"')\n> > +\n> > +layers_env = environment()\n> > +layers_env.set('LIBCAMERA_LAYER_PATH', meson.current_build_dir())\n> > +meson.add_devenv(layers_env)\n> > diff --git a/src/libcamera/layer.cpp b/src/libcamera/layer.cpp\n> > new file mode 100644\n> > index 000000000000..64debfb0c9e2\n> > --- /dev/null\n> > +++ b/src/libcamera/layer.cpp\n> > @@ -0,0 +1,179 @@\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> > +#include <libcamera/layer.h>\n> > +\n> > +/**\n> > + * \\file layer.h\n> > + * \\brief Layer interface\n> > + *\n> > + * Layers are a construct that lives in between the application and libcamera.\n> > + * They are hooked into select calls to and from Camera, and each one is\n> \n> I would use \"to and from the Camera class\" to be more explicit, but\n> that's perhaps just a nitpick that you needn't worry about :-)\n\nExplicit might actually be better :)\n\n> \n> > + * executed in order.\n> > + *\n> > + * \\todo Expand on this, and decide on more concrete naming like module vs implementation\n> > + */\n> > +\n> > +namespace libcamera {\n> > +\n> > +/**\n> > + * \\struct LayerInfo\n> > + * \\brief Information about a Layer implementation\n> > + *\n> > + * This struct gives information about a layer implementation, such as name and\n> > + * API version. It must be exposed, named 'layerInfo', by the layer\n> > + * implementation shared object to identify itself to the LayerManager.\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInfo::name\n> > + * \\brief Name of the Layer module\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInfo::layerAPIVersion\n> > + * \\brief API version of the Layer implementation\n> > + */\n> > +\n> > +/**\n> > + * \\struct LayerInterface\n> > + * \\brief The function table of the Layer implementation\n> > + *\n> > + * This struct is the function table of a layer implementation. Any functions\n> > + * that the layer implements should be filled in here, and any functions that\n> > + * are not implemented must be set to nullptr. This struct, named\n> > + * 'layerInterface', must be exposed by the layer implementation shared object.\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::init\n> > + * \\brief Initialize the layer\n> > + * \\param[in] name Name of the camera\n> > + *\n> > + * This function is a required function for layer implementations.\n> > + *\n> > + * This function is called on Camera construction, and is where the layer\n> > + * implementation should allocate and initialize its closure and anything else\n> > + * required to run.\n> > + *\n> > + * \\return A closure\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::terminate\n> > + * \\brief Terminate the layer\n> > + * \\param[in] closure The closure that was allocated at init()\n> > + *\n> > + * This function is a required function for layer implementations.\n> > + *\n> > + * This function is called on Camera deconstruction, and is where the layer\n> > + * should free its closure and anything else that was allocated at\n> > + * initialization.\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::bufferCompleted\n> > + * \\brief Hook for Camera::bufferCompleted\n> > + * \\param[in] closure The closure of the layer\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::requestCompleted\n> > + * \\brief Hook for Camera::requestCompleted\n> > + * \\param[in] closure The closure of the layer\n> > + * \\return Metadata that the layer wants to set\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::disconnected\n> > + * \\brief Hook for Camera::disconnected\n> > + * \\param[in] closure The closure of the layer\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::acquire\n> > + * \\brief Hook for Camera::acquire\n> > + * \\param[in] closure The closure of the layer\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::release\n> > + * \\brief Hook for Camera::release\n> > + * \\param[in] closure The closure of the layer\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::controls\n> > + * \\brief Declare the controls supported by the Layer\n> > + * \\param[in] closure The closure of the layer\n> > + * \\param[in] controlInfoMap The cumulative ControlInfoMap of supported controls of the Camera and any previous layers\n> > + *\n> > + * This function is for the layer implementation to declare the controls that\n> > + * it supports. This will be called by the LayerManager at Camera::init() time\n> > + * (after LayerInterface::init()), and at Camera::configure() time. The latter\n> > + * gives a chance for the controls to be updated if the configuration changes\n> > + * them.\n> > + *\n> > + * The controls that are returned by this function will overwrite any\n> > + * duplicates that were in the input parameter controls.\n> > + *\n> > + * \\return The additional controls that this Layer implements\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::properties\n> > + * \\brief Declare the properties supported by the Layer\n> > + * \\param[in] closure The closure of the layer\n> > + * \\param[in] controlList The cumulative properties of the Camera and any previous layers\n> > + *\n> > + * This function is for the layer implementation to declare the properies that\n> > + * it wants to declare. This will be called by the LayerManager once at\n> > + * Camera::init() time (after LayerInterface::init(), and before\n> > + * LayerInterface::controls()).\n> > + *\n> > + * The properties that are returned by this function will overwrite any\n> > + * duplicates that were in the input parameter properties.\n> > + *\n> > + * \\return The additional properties that this Layer declares\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::configure\n> > + * \\brief Hook for Camera::configure\n> > + * \\param[in] closure The closure of the layer\n> > + * \\param[in] cameraConfiguration The camera configuration\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::createRequest\n> > + * \\brief Hook for Camera::createRequest\n> > + * \\param[in] closure The closure of the layer\n> > + * \\param[in] cookie An opaque cookie for the application\n> > + * \\param[in] request The request that was just created by the Camera\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::queueRequest\n> > + * \\brief Hook for Camera::queueRequest\n> > + * \\param[in] closure The closure of the layer\n> > + * \\param[in] request The request that was queued\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::start\n> > + * \\brief Hook for Camera::start\n> > + * \\param[in] closure The closure of the layer\n> > + * \\param[in] controls The controls to be set before starting capture\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInterface::stop\n> > + * \\brief Hook for Camera::stop\n> > + * \\param[in] closure The closure of the layer\n> > + */\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/layer_manager.cpp b/src/libcamera/layer_manager.cpp\n> > new file mode 100644\n> > index 000000000000..bef69f6bd04b\n> > --- /dev/null\n> > +++ b/src/libcamera/layer_manager.cpp\n> > @@ -0,0 +1,612 @@\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> > +\n> > +#include <libcamera/base/file.h>\n> > +#include <libcamera/base/log.h>\n> > +#include <libcamera/base/span.h>\n> > +#include <libcamera/base/utils.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(LayerLoaded)\n> > +LOG_DEFINE_CATEGORY(LayerController)\n> > +LOG_DEFINE_CATEGORY(LayerManager)\n> > +\n> > +/**\n> > + * \\class LayerLoaded\n> > + * \\brief A wrapper class for a Layer shared object that has been loaded\n> > + *\n> > + * This class wraps a Layer shared object that has been loaded, managing the\n> > + * lifetime and management of the dlopened handle as well as organizing access\n> > + * to the function table and layer info.\n> > + *\n> > + * Instances of this class shall not be used directly. LayerInstance, which\n> > + * wraps LayerLoaded, shall be used instead.\n> > + *\n> > + * \\sa LayerInstance\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerLoaded::info\n> > + * \\brief Information about the Layer\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerLoaded::vtable\n> > + * \\brief The function table of the layer\n> \n> Ah, here it is; why not ftable?\n\niirc it's C++ terminology.\n\nhttps://en.wikipedia.org/wiki/Virtual_method_table\n\n\nThanks,\n\nPaul\n\n> \n> Best wishes,\n> Isaac\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerLoaded::dlHandle\n> > + * \\brief The handle as returned by dlopen for the layer shared object\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerLoaded::valid\n> > + * \\brief Whether or not the loaded layer is valid\n> > + *\n> > + * Instances that failed to load due to error, or that were constructed with no\n> > + * parameters will be invalid.\n> > + */\n> > +\n> > +/**\n> > + * \\brief Load a Layer from a shared object file\n> > + */\n> > +LayerLoaded::LayerLoaded(const std::string &filename)\n> > +{\n> > +       File file{ filename };\n> > +       if (!file.open(File::OpenModeFlag::ReadOnly)) {\n> > +               LOG(LayerLoaded, Error) << \"Failed to open layer: \"\n> > +                                       << strerror(-file.error());\n> > +               return;\n> > +       }\n> > +\n> > +       Span<const uint8_t> data = file.map();\n> > +       int ret = utils::elfVerifyIdent(data);\n> > +       if (ret) {\n> > +               LOG(LayerLoaded, Error) << \"Layer is not an ELF file\";\n> > +               return;\n> > +       }\n> > +\n> > +       Span<const uint8_t> layerInfoSym = utils::elfLoadSymbol(data, \"layerInfo\");\n> > +       if (layerInfoSym.size() < sizeof(LayerInfo)) {\n> > +               LOG(LayerLoaded, Error) << \"Layer has no valid layerInfoSym\";\n> > +               return;\n> > +       }\n> > +\n> > +       void *dlh = dlopen(file.fileName().c_str(), RTLD_LAZY);\n> > +       if (!dlh) {\n> > +               LOG(LayerLoaded, Error)\n> > +                       << \"Failed to open layer shared object: \"\n> > +                       << dlerror();\n> > +               return;\n> > +       }\n> > +\n> > +       /* No need to dlclose as the deconstructor will handle it */\n> > +\n> > +       void *layerInfoDl = dlsym(dlh, \"layerInfo\");\n> > +       if (!layerInfoDl) {\n> > +               LOG(LayerLoaded, Error)\n> > +                       << \"Failed to load layerInfo from layer shared object: \"\n> > +                       << dlerror();\n> > +               return;\n> > +       }\n> > +\n> > +       void *vtableSym = dlsym(dlh, \"layerInterface\");\n> > +       if (!vtableSym) {\n> > +               LOG(LayerLoaded, Error)\n> > +                       << \"Failed to load layerInterface from layer shared object: \"\n> > +                       << dlerror();\n> > +               return;\n> > +       }\n> > +\n> > +       info = static_cast<LayerInfo *>(layerInfoDl);\n> > +       vtable = static_cast<LayerInterface *>(vtableSym);\n> > +       dlHandle = dlh;\n> > +\n> > +       /* \\todo Implement this. It should come from the libcamera version */\n> > +       if (info->layerAPIVersion != 1) {\n> > +               LOG(LayerLoaded, Error) << \"Layer '\" << info->name\n> > +                                       << \"' API version mismatch\";\n> > +               return;\n> > +       }\n> > +\n> > +       /* \\todo Document these requirements */\n> > +       if (!vtable->init) {\n> > +               LOG(LayerLoaded, Error) << \"Layer '\" << info->name\n> > +                                       << \"' doesn't implement init\";\n> > +               return;\n> > +       }\n> > +\n> > +       /* \\todo Document these requirements */\n> > +       if (!vtable->terminate) {\n> > +               LOG(LayerLoaded, Error) << \"Layer '\" << info->name\n> > +                                       << \"' doesn't implement terminate\";\n> > +               return;\n> > +       }\n> > +\n> > +       /* \\todo Validate the layer name. */\n> > +\n> > +       valid = true;\n> > +\n> > +       return;\n> > +}\n> > +\n> > +/**\n> > + * \\fn LayerLoaded::LayerLoaded(LayerLoaded &&other)\n> > + * \\brief Move constructor\n> > + */\n> > +\n> > +/**\n> > + * \\fn LayerLoaded &LayerLoaded::operator=(LayerLoaded &&other)\n> > + * \\brief Move assignment operator\n> > + */\n> > +\n> > +/**\n> > + * \\def LAYER_INSTANCE_CALL\n> > + * \\brief Call a function from the layer's vtable\n> > + * \\param func The function name to call from the vtable\n> > + *\n> > + * This is a convenience function to reduce code duplication for checking that\n> > + * the function exists in the layer's vtable before calling it.\n> > + */\n> > +\n> > +/**\n> > + * \\struct LayerInstance\n> > + * \\brief A Layer that has been instantiated\n> > + *\n> > + * This struct represents a Layer that has been instantiated. It wraps an\n> > + * instance-agnostic LayerLoaded (which notably contains the layer vtable) with\n> > + * instance-specific data (in a closure).\n> > + *\n> > + * All functions (except for the constructor, controls(), and properties()) are\n> > + * simply a redirection into the underlying LayerLoaded's vtable, plus a check\n> > + * (if appropriate) that the function exists in the vtable.\n> > + */\n> > +\n> > +/**\n> > + * \\fn LayerInstance::LayerInstance(const std::shared_ptr<LayerLoaded> &l)\n> > + * \\brief Construct a LayerInstance from a LayerLoaded\n> > + * \\param l LayerLoaded from which to construct a LayerInstance\n> > + */\n> > +\n> > +/**\n> > + * \\fn LayerInstance::init(const std::string &id)\n> > + * \\brief Initialize the layer\n> > + * \\param id Name of the camera\n> > + *\n> > + * This function calls init() of the wrapped LayerLoaded, and saves the closure\n> > + * that is returned by LayerLoaded::init().\n> > + *\n> > + * \\sa LayerLoaded::init()\n> > + */\n> > +\n> > +/**\n> > + * \\fn LayerInstance::terminate()\n> > + * \\brief Terminate the layer\n> > + *\n> > + * This function calls terminate() of the wrapped LayerLoaded.\n> > + *\n> > + * \\sa LayerLoaded::terminate()\n> > + */\n> > +\n> > +/**\n> > + * \\fn LayerInstance::requestCompleted(Request *request)\n> > + * \\brief Handler for when a Request completes\n> > + * \\param request The request that has completed\n> > + * \\return The metadata that this Layer reports for the completed \\a request\n> > + */\n> > +\n> > +/**\n> > + * \\fn LayerInstance::controls(ControlInfoMap &infoMap)\n> > + * \\brief Declare the controls supported by the Layer\n> > + * \\param infoMap The cumulative ControlInfoMap of supported controls of the\n> > + * Camera and any previous layers\n> > + *\n> > + * This function calls controls() of the wrapped LayerLoaded, if controls()\n> > + * exists in the vtable. If it does not then an empty ControlInfoMap::Map is\n> > + * returned.\n> > + *\n> > + * \\sa LayerLoaded::controls()\n> > + *\n> > + * \\return The additional controls that this Layer implements, or an empty\n> > + * ControlInfoMap::Map if controls() is not implemented by the Layer\n> > + */\n> > +\n> > +/**\n> > + * \\fn LayerInstance::properties(ControlList &props)\n> > + * \\brief Declare the properties supported by the Layer\n> > + * \\param props The cumulative properties of the Camera and any previous layers\n> > + *\n> > + * This function calls properties() of the wrapped LayerLoaded, if properties()\n> > + * exists in the vtable. If it does not then an empty ControlList (made from\n> > + * controls::controls) is returned.\n> > + *\n> > + * \\sa LayerLoaded::properties()\n> > + *\n> > + * \\return The additional properties that this Layer implements, or an empty\n> > + * ControlList if properties() is not implemented by the Layer\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInstance::layer\n> > + * \\brief The LayerLoaded that this LayerInstance wraps\n> > + */\n> > +\n> > +/**\n> > + * \\var LayerInstance::closure\n> > + * \\brief The closure of this LayerInstance\n> > + */\n> > +\n> > +/**\n> > + * \\class LayerController\n> > + * \\brief Per-Camera instance of a layer manager\n> > + *\n> > + * Conceptually this class is an instantiation of the LayerManager for each\n> > + * Camera instance. It contains the closure for of each layer specific to the\n> > + * Camera, as well as the queue of layers to execute for each Camera.\n> > + */\n> > +\n> > +/**\n> > + * \\brief Initialize the Layers\n> > + * \\param[in] camera The Camera for whom to initialize layers\n> > + * \\param[in] properties The Camera properties\n> > + * \\param[in] controlInfoMap The Camera controls\n> > + * \\param[in] layers Map of available layers\n> > + *\n> > + * This is called by the Camera at construction time via\n> > + * LayerManager::createController. The LayerManager feeds the list of layers\n> > + * that are available, and the LayerController can then create its own\n> > + * execution queue and initialize all the layers for its Camera.\n> > + *\n> > + * \\a properties and \\a controlInfoMap are passed in so that the Layers can\n> > + * modify them, although they will be cached in an internal copy that can be\n> > + * efficiently returned at properties() and controls(), respectively.\n> > + */\n> > +LayerController::LayerController(const Camera *camera,\n> > +                                const ControlList &properties,\n> > +                                const ControlInfoMap &controlInfoMap,\n> > +                                const std::map<std::string, std::shared_ptr<LayerLoaded>> &layers)\n> > +{\n> > +       /* Order the layers */\n> > +       /* \\todo Document this. First is closer to application, last is closer to libcamera */\n> > +       /* \\todo Get this from configuration file */\n> > +       const char *layerList = utils::secure_getenv(\"LIBCAMERA_LAYERS_ENABLE\");\n> > +       if (layerList) {\n> > +               for (const auto &layerName : utils::split(layerList, \",\")) {\n> > +                       if (layerName.empty())\n> > +                               continue;\n> > +\n> > +                       const auto &it = layers.find(layerName);\n> > +                       if (it == layers.end()) {\n> > +                               LOG(LayerController, Warning)\n> > +                                       << \"Requested layer '\" << layerName\n> > +                                       << \"' not found\";\n> > +                               continue;\n> > +                       }\n> > +\n> > +                       executionQueue_.emplace_back(std::make_unique<LayerInstance>(it->second));\n> > +               }\n> > +       }\n> > +\n> > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > +               layer->init(camera->id());\n> > +\n> > +       /*\n> > +        * We need to iterate over the layers individually to merge all of\n> > +        * their controls, so we'll factor out updateControls() as it needs to be\n> > +        * run again at configure().\n> > +        */\n> > +       updateProperties(properties);\n> > +       updateControls(controlInfoMap);\n> > +}\n> > +\n> > +/**\n> > + * \\brief Terminate the Layers\n> > + *\n> > + * This is called by the Camera at deconstruction time. The LayerController\n> > + * instructs all Layer instances to release the resources that they allocated\n> > + * for this specific \\a camera.\n> > + */\n> > +LayerController::~LayerController()\n> > +{\n> > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > +               layer->terminate();\n> > +}\n> > +\n> > +/**\n> > + * \\brief Hook for Camera::bufferCompleted\n> > + * \\param[in] request The request whose buffer completed\n> > + * \\param[in] buffer The buffer that completed\n> > + */\n> > +void LayerController::bufferCompleted(Request *request, FrameBuffer *buffer)\n> > +{\n> > +       /* Reverse order because this comes from a Signal emission */\n> > +       for (auto it = executionQueue_.rbegin();\n> > +            it != executionQueue_.rend(); it++) {\n> > +               (*it)->bufferCompleted(request, buffer);\n> > +       }\n> > +}\n> > +\n> > +/**\n> > + * \\brief Hook for Camera::requestCompleted\n> > + * \\param[in] request The request that completed\n> > + * \\return metadata set by all the layers\n> > + */\n> > +ControlList LayerController::requestCompleted(Request *request)\n> > +{\n> > +       ControlList ret(controls::controls);\n> > +\n> > +       /* Reverse order because this comes from a Signal emission */\n> > +       for (auto it = executionQueue_.rbegin();\n> > +            it != executionQueue_.rend(); it++) {\n> > +               ControlList metadata = (*it)->requestCompleted(request);\n> > +               ret.merge(metadata, ControlList::MergePolicy::OverwriteExisting);\n> > +       }\n> > +\n> > +       return ret;\n> > +}\n> > +\n> > +/**\n> > + * \\brief Hook for Camera::disconnected\n> > + */\n> > +void LayerController::disconnected()\n> > +{\n> > +       /* Reverse order because this comes from a Signal emission */\n> > +       for (auto it = executionQueue_.rbegin();\n> > +            it != executionQueue_.rend(); it++) {\n> > +               (*it)->disconnected();\n> > +       }\n> > +}\n> > +\n> > +/**\n> > + * \\brief Hook for Camera::acquire\n> > + */\n> > +void LayerController::acquire()\n> > +{\n> > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > +               layer->acquire();\n> > +}\n> > +\n> > +/**\n> > + * \\brief Hook for Camera::release\n> > + */\n> > +void LayerController::release()\n> > +{\n> > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > +               layer->release();\n> > +}\n> > +\n> > +/**\n> > + * \\fn LayerController::controls\n> > + * \\brief Hook for Camera::controls\n> > + * \\return A ControlInfoMap that merges the Camera's controls() with the ones\n> > + * declared by the layers\n> > + */\n> > +\n> > +/**\n> > + * \\fn LayerController::properties\n> > + * \\brief Hook for Camera::properties\n> > + * \\return A properties list that merges the Camera's properties() with the\n> > + * ones declared by the layers\n> > + */\n> > +\n> > +void LayerController::updateProperties(const ControlList &properties)\n> > +{\n> > +       ControlList props = properties;\n> > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_) {\n> > +               ControlList ret = layer->properties(props);\n> > +               props.merge(ret, ControlList::MergePolicy::OverwriteExisting);\n> > +       }\n> > +       properties_ = props;\n> > +}\n> > +\n> > +void LayerController::updateControls(const ControlInfoMap &controlInfoMap)\n> > +{\n> > +       ControlInfoMap infoMap = controlInfoMap;\n> > +       /* \\todo Simplify this once ControlInfoMaps become easier to modify */\n> > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_) {\n> > +               ControlInfoMap::Map ret = layer->controls(infoMap);\n> > +               ControlInfoMap::Map map;\n> > +               /* Merge the layer's ret first since insert doesn't overwrite */\n> > +               map.insert(ret.begin(), ret.end());\n> > +               map.insert(infoMap.begin(), infoMap.end());\n> > +               infoMap = ControlInfoMap(std::move(map),\n> > +                                        libcamera::controls::controls);\n> > +       }\n> > +       controls_ = infoMap;\n> > +}\n> > +\n> > +/**\n> > + * \\brief Hook for Camera::configure\n> > + * \\param[in] config The configuration\n> > + * \\param[in] controlInfoMap The ControlInfoMap of the controls that \\a camera supports\n> > + *\n> > + * \\a controlInfoMap is passed in as this is a potential point where the limits\n> > + * of controls could change, so this gives a chance for the Layers to update\n> > + * the ControlInfoMap that will be returned by LayerController::controls().\n> > + */\n> > +void LayerController::configure(const CameraConfiguration *config,\n> > +                               const ControlInfoMap &controlInfoMap)\n> > +{\n> > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > +               layer->configure(config);\n> > +\n> > +       updateControls(controlInfoMap);\n> > +}\n> > +\n> > +/**\n> > + * \\brief Hook for Camera::createRequest\n> > + * \\param[in] cookie An opaque cookie for the application\n> > + * \\param[in] request The request that was created\n> > + */\n> > +void LayerController::createRequest(uint64_t cookie, const Request *request)\n> > +{\n> > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > +               layer->createRequest(cookie, request);\n> > +}\n> > +\n> > +/**\n> > + * \\brief Hook for Camera::queueRequest\n> > + * \\param[in] request The request that is being queued\n> > + */\n> > +void LayerController::queueRequest(Request *request)\n> > +{\n> > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > +               layer->queueRequest(request);\n> > +}\n> > +\n> > +/**\n> > + * \\brief Hook for Camera::start\n> > + * \\param[in] controls The controls to be applied before starting the capture\n> > + * \\return A ControlList that merges controls set by the layers and \\a controls\n> > + */\n> > +ControlList *LayerController::start(const ControlList *controls)\n> > +{\n> > +       if (controls) {\n> > +               /* Clear any leftover start controls from a previous run */\n> > +               startControls_.clear();\n> > +               startControls_.merge(*controls);\n> > +       }\n> > +\n> > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > +               layer->start(startControls_);\n> > +\n> > +       return &startControls_;\n> > +}\n> > +\n> > +/**\n> > + * \\brief Hook for Camera::stop\n> > + */\n> > +void LayerController::stop()\n> > +{\n> > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > +               layer->stop();\n> > +}\n> > +\n> > +/**\n> > + * \\class LayerManager\n> > + * \\brief Layer manager\n> > + *\n> > + * The Layer manager discovers layer implementations from the filesystem, and\n> > + * creates LayerLoaded instances for each one.\n> > + *\n> > + * The LayerManager itself is instantiated by the CameraManager, and each\n> > + * Camera interacts with the LayerManager by passing itself in to\n> > + * LayerManager::createController. The LayerManager internally maps each Camera\n> > + * to a list of Layer instances that it calls sequentially for each hook.\n> > + */\n> > +\n> > +/**\n> > + * \\brief Construct a LayerManager instance\n> > + *\n> > + * The LayerManager class is meant be instantiated by the CameraManager.\n> > + *\n> > + * This function simply loads all available layers and stores them. The\n> > + * LayerController is responsible for organizing them into queues to be\n> > + * executed, and for managing closures for each Camera that they belong to.\n> > + */\n> > +LayerManager::LayerManager()\n> > +{\n> > +       /* This is so that we can capture it in the lambda below */\n> > +       std::map<std::string, std::shared_ptr<LayerLoaded>> &layers = layers_;\n> > +\n> > +       /* \\todo Implement built-in layers */\n> > +\n> > +       /* This returns the number of \"modules\" successfully loaded */\n> > +       std::function<int(const std::string &)> soHandler =\n> > +       [&layers](const std::string &file) {\n> > +               std::shared_ptr<LayerLoaded> layer = std::make_shared<LayerLoaded>(file);\n> > +               if (!layer->valid)\n> > +                       return 0;\n> > +\n> > +               LOG(LayerManager, Debug) << \"Loaded layer '\" << file << \"'\";\n> > +\n> > +               auto [it, inserted] =\n> > +                       layers.try_emplace(std::string(layer->info->name),\n> > +                                          std::move(layer));\n> > +               if (!inserted)\n> > +                       LOG(LayerManager, Warning)\n> > +                               << \"Not adding duplicate layer '\"\n> > +                               << layer->info->name << \"'\";\n> > +\n> > +               return 1;\n> > +       };\n> > +\n> > +       /* User-specified paths take precedence. */\n> > +       /* \\todo Document this */\n> > +       const char *layerPaths = utils::secure_getenv(\"LIBCAMERA_LAYER_PATH\");\n> > +       if (layerPaths) {\n> > +               for (const auto &dir : utils::split(layerPaths, \":\")) {\n> > +                       if (dir.empty())\n> > +                               continue;\n> > +\n> > +                       /*\n> > +                        * \\todo Move the shared objects into one directory\n> > +                        * instead of each in their own subdir\n> > +                        */\n> > +                       utils::findSharedObjects(dir.c_str(), 1, soHandler);\n> > +               }\n> > +       }\n> > +\n> > +       /*\n> > +        * When libcamera is used before it is installed, load layers from the\n> > +        * same build directory as the libcamera library itself.\n> > +        */\n> > +       std::string root = utils::libcameraBuildPath();\n> > +       if (!root.empty()) {\n> > +               std::string layerBuildPath = root + \"src/layer\";\n> > +               constexpr int maxDepth = 2;\n> > +\n> > +               LOG(LayerManager, Info)\n> > +                       << \"libcamera is not installed. Adding '\"\n> > +                       << layerBuildPath << \"' to the layer search path\";\n> > +\n> > +               utils::findSharedObjects(layerBuildPath.c_str(), maxDepth, soHandler);\n> > +       }\n> > +\n> > +       /* Finally try to load layers from the installed system path. */\n> > +       utils::findSharedObjects(LAYER_DIR, 1, soHandler);\n> > +}\n> > +\n> > +/**\n> > + * \\brief Create a LayerController instance\n> > + * \\param[in] camera The Camera instance for whom to create a LayerController\n> > + * \\param[in] properties The Camera properties\n> > + * \\param[in] controlInfoMap The Camera controls\n> > + */\n> > +std::unique_ptr<LayerController>\n> > +LayerManager::createController(const Camera *camera,\n> > +                              const ControlList &properties,\n> > +                              const ControlInfoMap &controlInfoMap) const\n> > +{\n> > +       return std::make_unique<LayerController>(camera, properties, controlInfoMap, layers_);\n> > +}\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> > index 186d5c48ccd5..076cfda66060 100644\n> > --- a/src/libcamera/meson.build\n> > +++ b/src/libcamera/meson.build\n> > @@ -41,6 +41,8 @@ libcamera_internal_sources = files([\n> >      'ipc_pipe.cpp',\n> >      'ipc_pipe_unixsocket.cpp',\n> >      'ipc_unixsocket.cpp',\n> > +    'layer.cpp',\n> > +    'layer_manager.cpp',\n> >      'mapped_framebuffer.cpp',\n> >      'matrix.cpp',\n> >      'media_device.cpp',\n> > diff --git a/src/meson.build b/src/meson.build\n> > index 9b63c8e845d8..ef296382718c 100644\n> > --- a/src/meson.build\n> > +++ b/src/meson.build\n> > @@ -77,6 +77,7 @@ subdir('libcamera')\n> >  \n> >  subdir('android')\n> >  subdir('ipa')\n> > +subdir('layer')\n> >  \n> >  subdir('apps')\n> >  \n> > -- \n> > 2.47.2\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 9D8CFC3237\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 25 Feb 2026 02:38:50 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id B0103622AA;\n\tWed, 25 Feb 2026 03:38:49 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8DF69621CE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 25 Feb 2026 03:38:48 +0100 (CET)","from neptunite.rasen.tech (unknown\n\t[IPv6:2404:7a81:160:2100:4acc:2a06:1e26:a070])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 7D6D0E47;\n\tWed, 25 Feb 2026 03:37:50 +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=\"U2d0ve5L\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1771987071;\n\tbh=vKTY1CNuBW1VVosg31P/klWpD3mDjM1HK2Ff3+1vKN0=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=U2d0ve5Lvl8WVfOnRbib4uiGxUXLpkTid/zBp4ySz8CE+Lyfi/8m8Hjk71TJ+u0wm\n\tNyCnYjLCAAws5clMnfNyQo5KlGOmWf9+D3RjS0znbfL57LT3RE9B1SSDPkqGohUsmF\n\trSzfBZ6sTtvMoXgN/CPQzUSlLHVHjQudFb9xHhUQ=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<177194896117.1513537.12265013306022510835@t16>","References":"<20260129082814.1777779-1-paul.elder@ideasonboard.com>\n\t<20260129082814.1777779-5-paul.elder@ideasonboard.com>\n\t<177194896117.1513537.12265013306022510835@t16>","Subject":"Re: [PATCH v6 4/8] libcamera: layer_manager: Add Layer handling\n\timplementation","From":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"","To":"Isaac Scott <isaac.scott@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Wed, 25 Feb 2026 11:38:40 +0900","Message-ID":"<177198712030.607498.11650178776796019669@neptunite.rasen.tech>","User-Agent":"alot/0.0.0","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>"}},{"id":38299,"web_url":"https://patchwork.libcamera.org/comment/38299/","msgid":"<177201589319.1569069.4342313288859198340@t16>","date":"2026-02-25T10:38:13","subject":"Re: [PATCH v6 4/8] libcamera: layer_manager: Add Layer handling\n\timplementation","submitter":{"id":215,"url":"https://patchwork.libcamera.org/api/people/215/","name":"Isaac Scott","email":"isaac.scott@ideasonboard.com"},"content":"Hi Paul,\n\nQuoting Paul Elder (2026-02-25 02:38:40)\n> Hi Isaac,\n> \n> Thanks for the review.\n> \n> Quoting Isaac Scott (2026-02-25 01:02:41)\n> > Quoting Paul Elder (2026-01-29 08:28:10)\n> > > We want to be able to implement layers in libcamera, which conceptually\n> > > sit in between the Camera class and the application. This can be useful\n> > > for implementing things that don't belong inside the Camera/IPA nor inside\n> > > the application, such as intercepting and translating the AeEnable\n> > > control, or implementing the Sync algorithm.\n> > > \n> > > To achieve this, add:\n> > > - a LayerManager, which searches for and loads layers from shared object\n\n<snip>\n\n> > \n> > I may be missing something here, but what is vtable? Variable table?\n> \n> Apparently it's virtual method table. C++ uses them for class virtual functions\n> iirc.\n> \n\nAh okay, that makes sense then. Thanks for clearing that up!\n\nReviewed-by: Isaac Scott <isaac.scott@ideasonboard.com>\n\nBest wishes,\nIsaac\n\n> > \n> > > +       void *dlHandle = nullptr;\n> > > +       bool valid = false;\n> > > +\n> > > +private:\n> > > +       LIBCAMERA_DISABLE_COPY(LayerLoaded)\n> > > +};\n> > > +\n> > > +#ifndef __DOXYGEN__\n> > > +#define _ARG_PARAMS2(type1, type2) type1 arg1, type2 arg2\n> > > +#define _ARG_NAMES2(type1, type2) arg1, arg2\n> > > +\n> > > +#define _ARG_PARAMS1(type1) type1 arg1\n> > > +#define _ARG_NAMES1(type1) arg1\n> > > +\n> > > +#define _ARG_PARAMS0()\n> > > +#define _ARG_NAMES0()\n> > > +\n> > > +#define _GET_OVERRIDE(_1, _2, _3, NAME, ...) NAME\n> > > +\n> > > +#define ARG_PARAMS(...) _GET_OVERRIDE(\"ignored\", __VA_ARGS__ __VA_OPT__(,) \\\n> > > +                   _ARG_PARAMS2, _ARG_PARAMS1, _ARG_PARAMS0)(__VA_ARGS__)\n> > > +\n> > > +#define ARG_NAMES(...) _GET_OVERRIDE(\"ignored\", __VA_ARGS__ __VA_OPT__(,) \\\n> > > +                   _ARG_NAMES2, _ARG_NAMES1, _ARG_NAMES0)(__VA_ARGS__)\n> > > +#endif /* __DOXYGEN__ */\n> > > +\n> > > +#define LAYER_INSTANCE_CALL(func, ...) \\\n> > > +       void func(ARG_PARAMS(__VA_ARGS__)) \\\n> > > +       { \\\n> > > +               if (layer->vtable->func) \\\n> > > +                       layer->vtable->func(closure __VA_OPT__(,) ARG_NAMES(__VA_ARGS__)); \\\n> > > +       }\n> > > +\n> > > +struct LayerInstance {\n> > > +       LayerInstance(const std::shared_ptr<LayerLoaded> &l)\n> > > +               : layer(l)\n> > > +       {\n> > > +       }\n> > > +\n> > > +       void init(const std::string &id)\n> > > +       {\n> > > +               closure = layer->vtable->init(id);\n> > > +       }\n> > > +\n> > > +       void terminate()\n> > > +       {\n> > > +               layer->vtable->terminate(closure);\n> > > +       }\n> > > +\n> > > +       LAYER_INSTANCE_CALL(bufferCompleted, Request *, FrameBuffer *)\n> > > +       LAYER_INSTANCE_CALL(disconnected)\n> > > +       LAYER_INSTANCE_CALL(acquire)\n> > > +       LAYER_INSTANCE_CALL(release)\n> > > +       LAYER_INSTANCE_CALL(configure, const CameraConfiguration *)\n> > > +       LAYER_INSTANCE_CALL(createRequest, uint64_t, const Request *)\n> > > +       LAYER_INSTANCE_CALL(queueRequest, Request *)\n> > > +       LAYER_INSTANCE_CALL(start, ControlList &)\n> > > +       LAYER_INSTANCE_CALL(stop)\n> > > +\n> > > +       ControlList requestCompleted(Request *request)\n> > > +       {\n> > > +               if (!layer->vtable->requestCompleted)\n> > > +                       return ControlList(controls::controls);\n> > > +               return layer->vtable->requestCompleted(closure, request);\n> > > +       }\n> > > +\n> > > +       ControlInfoMap::Map controls(ControlInfoMap &infoMap)\n> > > +       {\n> > > +               if (!layer->vtable->controls)\n> > > +                       return ControlInfoMap::Map();\n> > > +               return layer->vtable->controls(closure, infoMap);\n> > > +       }\n> > > +\n> > > +       ControlList properties(ControlList &props)\n> > > +       {\n> > > +               if (!layer->vtable->properties)\n> > > +                       return ControlList(controls::controls);\n> > > +               return layer->vtable->properties(closure, props);\n> > > +       }\n> > > +\n> > > +       const std::shared_ptr<LayerLoaded> layer;\n> > > +       void *closure = nullptr;\n> > > +};\n> > > +\n> > > +class LayerController\n> > > +{\n> > > +public:\n> > > +       LayerController(const Camera *camera, const ControlList &properties,\n> > > +                       const ControlInfoMap &controlInfoMap,\n> > > +                       const std::map<std::string, std::shared_ptr<LayerLoaded>> &layers);\n> > > +       ~LayerController();\n> > > +\n> > > +       void bufferCompleted(Request *request, FrameBuffer *buffer);\n> > > +       ControlList requestCompleted(Request *request);\n> > > +       void disconnected();\n> > > +\n> > > +       void acquire();\n> > > +       void release();\n> > > +\n> > > +       const ControlInfoMap &controls() const { return controls_; }\n> > > +       const ControlList &properties() const { return properties_; }\n> > > +\n> > > +       void configure(const CameraConfiguration *config,\n> > > +                      const ControlInfoMap &controlInfoMap);\n> > > +\n> > > +       void createRequest(uint64_t cookie, const Request *request);\n> > > +\n> > > +       void queueRequest(Request *request);\n> > > +\n> > > +       ControlList *start(const ControlList *controls);\n> > > +       void stop();\n> > > +\n> > > +private:\n> > > +       void updateProperties(const ControlList &properties);\n> > > +       void updateControls(const ControlInfoMap &controlInfoMap);\n> > > +\n> > > +       std::deque<std::unique_ptr<LayerInstance>> executionQueue_;\n> > > +\n> > > +       ControlInfoMap controls_;\n> > > +       ControlList properties_;\n> > > +\n> > > +       ControlList startControls_ = ControlList(controls::controls);\n> > > +};\n> > > +\n> > > +class LayerManager\n> > > +{\n> > > +public:\n> > > +       LayerManager();\n> > > +       ~LayerManager() = default;\n> > > +\n> > > +       std::unique_ptr<LayerController>\n> > > +       createController(const Camera *camera,\n> > > +                        const ControlList &properties,\n> > > +                        const ControlInfoMap &controlInfoMap) const;\n> > > +\n> > > +private:\n> > > +       std::map<std::string, std::shared_ptr<LayerLoaded>> layers_;\n> > > +};\n> > > +\n> > > +} /* namespace libcamera */\n> > > diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build\n> > > index ed145a6dd7cd..ca96754f087e 100644\n> > > --- a/include/libcamera/internal/meson.build\n> > > +++ b/include/libcamera/internal/meson.build\n> > > @@ -31,6 +31,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',\n> > > diff --git a/include/libcamera/layer.h b/include/libcamera/layer.h\n> > > new file mode 100644\n> > > index 000000000000..1ec37256ad8d\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> > > +       const char *name;\n> > > +       int layerAPIVersion;\n> > > +};\n> > > +\n> > > +struct LayerInterface {\n> > > +       void *(*init)(const std::string &);\n> > > +       void (*terminate)(void *);\n> > > +\n> > > +       void (*bufferCompleted)(void *, Request *, FrameBuffer *);\n> > > +       ControlList (*requestCompleted)(void *, Request *);\n> > > +       void (*disconnected)(void *);\n> > > +\n> > > +       void (*acquire)(void *);\n> > > +       void (*release)(void *);\n> > > +\n> > > +       ControlInfoMap::Map (*controls)(void *, ControlInfoMap &);\n> > > +       ControlList (*properties)(void *, ControlList &);\n> > > +\n> > > +       void (*configure)(void *, const CameraConfiguration *);\n> > > +\n> > > +       void (*createRequest)(void *, uint64_t, const Request *);\n> > > +\n> > > +       void (*queueRequest)(void *, Request *);\n> > > +\n> > > +       void (*start)(void *, ControlList &);\n> > > +       void (*stop)(void *);\n> > > +};\n> > > +\n> > > +} /* namespace libcamera */\n> > > diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build\n> > > index 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',\n> > > diff --git a/src/layer/meson.build b/src/layer/meson.build\n> > > new file mode 100644\n> > > index 000000000000..45dded512a13\n> > > --- /dev/null\n> > > +++ b/src/layer/meson.build\n> > > @@ -0,0 +1,14 @@\n> > > +# SPDX-License-Identifier: CC0-1.0\n> > > +\n> > > +layer_includes = [\n> > > +    libcamera_includes,\n> > > +]\n> > > +\n> > > +layer_install_dir = libcamera_libdir / 'layer'\n> > > +\n> > > +config_h.set('LAYER_DIR',\n> > > +             '\"' + get_option('prefix') / layer_install_dir + '\"')\n> > > +\n> > > +layers_env = environment()\n> > > +layers_env.set('LIBCAMERA_LAYER_PATH', meson.current_build_dir())\n> > > +meson.add_devenv(layers_env)\n> > > diff --git a/src/libcamera/layer.cpp b/src/libcamera/layer.cpp\n> > > new file mode 100644\n> > > index 000000000000..64debfb0c9e2\n> > > --- /dev/null\n> > > +++ b/src/libcamera/layer.cpp\n> > > @@ -0,0 +1,179 @@\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> > > +#include <libcamera/layer.h>\n> > > +\n> > > +/**\n> > > + * \\file layer.h\n> > > + * \\brief Layer interface\n> > > + *\n> > > + * Layers are a construct that lives in between the application and libcamera.\n> > > + * They are hooked into select calls to and from Camera, and each one is\n> > \n> > I would use \"to and from the Camera class\" to be more explicit, but\n> > that's perhaps just a nitpick that you needn't worry about :-)\n> \n> Explicit might actually be better :)\n> \n> > \n> > > + * executed in order.\n> > > + *\n> > > + * \\todo Expand on this, and decide on more concrete naming like module vs implementation\n> > > + */\n> > > +\n> > > +namespace libcamera {\n> > > +\n> > > +/**\n> > > + * \\struct LayerInfo\n> > > + * \\brief Information about a Layer implementation\n> > > + *\n> > > + * This struct gives information about a layer implementation, such as name and\n> > > + * API version. It must be exposed, named 'layerInfo', by the layer\n> > > + * implementation shared object to identify itself to the LayerManager.\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInfo::name\n> > > + * \\brief Name of the Layer module\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInfo::layerAPIVersion\n> > > + * \\brief API version of the Layer implementation\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\struct LayerInterface\n> > > + * \\brief The function table of the Layer implementation\n> > > + *\n> > > + * This struct is the function table of a layer implementation. Any functions\n> > > + * that the layer implements should be filled in here, and any functions that\n> > > + * are not implemented must be set to nullptr. This struct, named\n> > > + * 'layerInterface', must be exposed by the layer implementation shared object.\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::init\n> > > + * \\brief Initialize the layer\n> > > + * \\param[in] name Name of the camera\n> > > + *\n> > > + * This function is a required function for layer implementations.\n> > > + *\n> > > + * This function is called on Camera construction, and is where the layer\n> > > + * implementation should allocate and initialize its closure and anything else\n> > > + * required to run.\n> > > + *\n> > > + * \\return A closure\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::terminate\n> > > + * \\brief Terminate the layer\n> > > + * \\param[in] closure The closure that was allocated at init()\n> > > + *\n> > > + * This function is a required function for layer implementations.\n> > > + *\n> > > + * This function is called on Camera deconstruction, and is where the layer\n> > > + * should free its closure and anything else that was allocated at\n> > > + * initialization.\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::bufferCompleted\n> > > + * \\brief Hook for Camera::bufferCompleted\n> > > + * \\param[in] closure The closure of the layer\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::requestCompleted\n> > > + * \\brief Hook for Camera::requestCompleted\n> > > + * \\param[in] closure The closure of the layer\n> > > + * \\return Metadata that the layer wants to set\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::disconnected\n> > > + * \\brief Hook for Camera::disconnected\n> > > + * \\param[in] closure The closure of the layer\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::acquire\n> > > + * \\brief Hook for Camera::acquire\n> > > + * \\param[in] closure The closure of the layer\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::release\n> > > + * \\brief Hook for Camera::release\n> > > + * \\param[in] closure The closure of the layer\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::controls\n> > > + * \\brief Declare the controls supported by the Layer\n> > > + * \\param[in] closure The closure of the layer\n> > > + * \\param[in] controlInfoMap The cumulative ControlInfoMap of supported controls of the Camera and any previous layers\n> > > + *\n> > > + * This function is for the layer implementation to declare the controls that\n> > > + * it supports. This will be called by the LayerManager at Camera::init() time\n> > > + * (after LayerInterface::init()), and at Camera::configure() time. The latter\n> > > + * gives a chance for the controls to be updated if the configuration changes\n> > > + * them.\n> > > + *\n> > > + * The controls that are returned by this function will overwrite any\n> > > + * duplicates that were in the input parameter controls.\n> > > + *\n> > > + * \\return The additional controls that this Layer implements\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::properties\n> > > + * \\brief Declare the properties supported by the Layer\n> > > + * \\param[in] closure The closure of the layer\n> > > + * \\param[in] controlList The cumulative properties of the Camera and any previous layers\n> > > + *\n> > > + * This function is for the layer implementation to declare the properies that\n> > > + * it wants to declare. This will be called by the LayerManager once at\n> > > + * Camera::init() time (after LayerInterface::init(), and before\n> > > + * LayerInterface::controls()).\n> > > + *\n> > > + * The properties that are returned by this function will overwrite any\n> > > + * duplicates that were in the input parameter properties.\n> > > + *\n> > > + * \\return The additional properties that this Layer declares\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::configure\n> > > + * \\brief Hook for Camera::configure\n> > > + * \\param[in] closure The closure of the layer\n> > > + * \\param[in] cameraConfiguration The camera configuration\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::createRequest\n> > > + * \\brief Hook for Camera::createRequest\n> > > + * \\param[in] closure The closure of the layer\n> > > + * \\param[in] cookie An opaque cookie for the application\n> > > + * \\param[in] request The request that was just created by the Camera\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::queueRequest\n> > > + * \\brief Hook for Camera::queueRequest\n> > > + * \\param[in] closure The closure of the layer\n> > > + * \\param[in] request The request that was queued\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::start\n> > > + * \\brief Hook for Camera::start\n> > > + * \\param[in] closure The closure of the layer\n> > > + * \\param[in] controls The controls to be set before starting capture\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInterface::stop\n> > > + * \\brief Hook for Camera::stop\n> > > + * \\param[in] closure The closure of the layer\n> > > + */\n> > > +\n> > > +} /* namespace libcamera */\n> > > diff --git a/src/libcamera/layer_manager.cpp b/src/libcamera/layer_manager.cpp\n> > > new file mode 100644\n> > > index 000000000000..bef69f6bd04b\n> > > --- /dev/null\n> > > +++ b/src/libcamera/layer_manager.cpp\n> > > @@ -0,0 +1,612 @@\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> > > +\n> > > +#include <libcamera/base/file.h>\n> > > +#include <libcamera/base/log.h>\n> > > +#include <libcamera/base/span.h>\n> > > +#include <libcamera/base/utils.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(LayerLoaded)\n> > > +LOG_DEFINE_CATEGORY(LayerController)\n> > > +LOG_DEFINE_CATEGORY(LayerManager)\n> > > +\n> > > +/**\n> > > + * \\class LayerLoaded\n> > > + * \\brief A wrapper class for a Layer shared object that has been loaded\n> > > + *\n> > > + * This class wraps a Layer shared object that has been loaded, managing the\n> > > + * lifetime and management of the dlopened handle as well as organizing access\n> > > + * to the function table and layer info.\n> > > + *\n> > > + * Instances of this class shall not be used directly. LayerInstance, which\n> > > + * wraps LayerLoaded, shall be used instead.\n> > > + *\n> > > + * \\sa LayerInstance\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerLoaded::info\n> > > + * \\brief Information about the Layer\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerLoaded::vtable\n> > > + * \\brief The function table of the layer\n> > \n> > Ah, here it is; why not ftable?\n> \n> iirc it's C++ terminology.\n> \n> https://en.wikipedia.org/wiki/Virtual_method_table\n> \n> \n> Thanks,\n> \n> Paul\n> \n> > \n> > Best wishes,\n> > Isaac\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerLoaded::dlHandle\n> > > + * \\brief The handle as returned by dlopen for the layer shared object\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerLoaded::valid\n> > > + * \\brief Whether or not the loaded layer is valid\n> > > + *\n> > > + * Instances that failed to load due to error, or that were constructed with no\n> > > + * parameters will be invalid.\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\brief Load a Layer from a shared object file\n> > > + */\n> > > +LayerLoaded::LayerLoaded(const std::string &filename)\n> > > +{\n> > > +       File file{ filename };\n> > > +       if (!file.open(File::OpenModeFlag::ReadOnly)) {\n> > > +               LOG(LayerLoaded, Error) << \"Failed to open layer: \"\n> > > +                                       << strerror(-file.error());\n> > > +               return;\n> > > +       }\n> > > +\n> > > +       Span<const uint8_t> data = file.map();\n> > > +       int ret = utils::elfVerifyIdent(data);\n> > > +       if (ret) {\n> > > +               LOG(LayerLoaded, Error) << \"Layer is not an ELF file\";\n> > > +               return;\n> > > +       }\n> > > +\n> > > +       Span<const uint8_t> layerInfoSym = utils::elfLoadSymbol(data, \"layerInfo\");\n> > > +       if (layerInfoSym.size() < sizeof(LayerInfo)) {\n> > > +               LOG(LayerLoaded, Error) << \"Layer has no valid layerInfoSym\";\n> > > +               return;\n> > > +       }\n> > > +\n> > > +       void *dlh = dlopen(file.fileName().c_str(), RTLD_LAZY);\n> > > +       if (!dlh) {\n> > > +               LOG(LayerLoaded, Error)\n> > > +                       << \"Failed to open layer shared object: \"\n> > > +                       << dlerror();\n> > > +               return;\n> > > +       }\n> > > +\n> > > +       /* No need to dlclose as the deconstructor will handle it */\n> > > +\n> > > +       void *layerInfoDl = dlsym(dlh, \"layerInfo\");\n> > > +       if (!layerInfoDl) {\n> > > +               LOG(LayerLoaded, Error)\n> > > +                       << \"Failed to load layerInfo from layer shared object: \"\n> > > +                       << dlerror();\n> > > +               return;\n> > > +       }\n> > > +\n> > > +       void *vtableSym = dlsym(dlh, \"layerInterface\");\n> > > +       if (!vtableSym) {\n> > > +               LOG(LayerLoaded, Error)\n> > > +                       << \"Failed to load layerInterface from layer shared object: \"\n> > > +                       << dlerror();\n> > > +               return;\n> > > +       }\n> > > +\n> > > +       info = static_cast<LayerInfo *>(layerInfoDl);\n> > > +       vtable = static_cast<LayerInterface *>(vtableSym);\n> > > +       dlHandle = dlh;\n> > > +\n> > > +       /* \\todo Implement this. It should come from the libcamera version */\n> > > +       if (info->layerAPIVersion != 1) {\n> > > +               LOG(LayerLoaded, Error) << \"Layer '\" << info->name\n> > > +                                       << \"' API version mismatch\";\n> > > +               return;\n> > > +       }\n> > > +\n> > > +       /* \\todo Document these requirements */\n> > > +       if (!vtable->init) {\n> > > +               LOG(LayerLoaded, Error) << \"Layer '\" << info->name\n> > > +                                       << \"' doesn't implement init\";\n> > > +               return;\n> > > +       }\n> > > +\n> > > +       /* \\todo Document these requirements */\n> > > +       if (!vtable->terminate) {\n> > > +               LOG(LayerLoaded, Error) << \"Layer '\" << info->name\n> > > +                                       << \"' doesn't implement terminate\";\n> > > +               return;\n> > > +       }\n> > > +\n> > > +       /* \\todo Validate the layer name. */\n> > > +\n> > > +       valid = true;\n> > > +\n> > > +       return;\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\fn LayerLoaded::LayerLoaded(LayerLoaded &&other)\n> > > + * \\brief Move constructor\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\fn LayerLoaded &LayerLoaded::operator=(LayerLoaded &&other)\n> > > + * \\brief Move assignment operator\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\def LAYER_INSTANCE_CALL\n> > > + * \\brief Call a function from the layer's vtable\n> > > + * \\param func The function name to call from the vtable\n> > > + *\n> > > + * This is a convenience function to reduce code duplication for checking that\n> > > + * the function exists in the layer's vtable before calling it.\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\struct LayerInstance\n> > > + * \\brief A Layer that has been instantiated\n> > > + *\n> > > + * This struct represents a Layer that has been instantiated. It wraps an\n> > > + * instance-agnostic LayerLoaded (which notably contains the layer vtable) with\n> > > + * instance-specific data (in a closure).\n> > > + *\n> > > + * All functions (except for the constructor, controls(), and properties()) are\n> > > + * simply a redirection into the underlying LayerLoaded's vtable, plus a check\n> > > + * (if appropriate) that the function exists in the vtable.\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\fn LayerInstance::LayerInstance(const std::shared_ptr<LayerLoaded> &l)\n> > > + * \\brief Construct a LayerInstance from a LayerLoaded\n> > > + * \\param l LayerLoaded from which to construct a LayerInstance\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\fn LayerInstance::init(const std::string &id)\n> > > + * \\brief Initialize the layer\n> > > + * \\param id Name of the camera\n> > > + *\n> > > + * This function calls init() of the wrapped LayerLoaded, and saves the closure\n> > > + * that is returned by LayerLoaded::init().\n> > > + *\n> > > + * \\sa LayerLoaded::init()\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\fn LayerInstance::terminate()\n> > > + * \\brief Terminate the layer\n> > > + *\n> > > + * This function calls terminate() of the wrapped LayerLoaded.\n> > > + *\n> > > + * \\sa LayerLoaded::terminate()\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\fn LayerInstance::requestCompleted(Request *request)\n> > > + * \\brief Handler for when a Request completes\n> > > + * \\param request The request that has completed\n> > > + * \\return The metadata that this Layer reports for the completed \\a request\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\fn LayerInstance::controls(ControlInfoMap &infoMap)\n> > > + * \\brief Declare the controls supported by the Layer\n> > > + * \\param infoMap The cumulative ControlInfoMap of supported controls of the\n> > > + * Camera and any previous layers\n> > > + *\n> > > + * This function calls controls() of the wrapped LayerLoaded, if controls()\n> > > + * exists in the vtable. If it does not then an empty ControlInfoMap::Map is\n> > > + * returned.\n> > > + *\n> > > + * \\sa LayerLoaded::controls()\n> > > + *\n> > > + * \\return The additional controls that this Layer implements, or an empty\n> > > + * ControlInfoMap::Map if controls() is not implemented by the Layer\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\fn LayerInstance::properties(ControlList &props)\n> > > + * \\brief Declare the properties supported by the Layer\n> > > + * \\param props The cumulative properties of the Camera and any previous layers\n> > > + *\n> > > + * This function calls properties() of the wrapped LayerLoaded, if properties()\n> > > + * exists in the vtable. If it does not then an empty ControlList (made from\n> > > + * controls::controls) is returned.\n> > > + *\n> > > + * \\sa LayerLoaded::properties()\n> > > + *\n> > > + * \\return The additional properties that this Layer implements, or an empty\n> > > + * ControlList if properties() is not implemented by the Layer\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInstance::layer\n> > > + * \\brief The LayerLoaded that this LayerInstance wraps\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\var LayerInstance::closure\n> > > + * \\brief The closure of this LayerInstance\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\class LayerController\n> > > + * \\brief Per-Camera instance of a layer manager\n> > > + *\n> > > + * Conceptually this class is an instantiation of the LayerManager for each\n> > > + * Camera instance. It contains the closure for of each layer specific to the\n> > > + * Camera, as well as the queue of layers to execute for each Camera.\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\brief Initialize the Layers\n> > > + * \\param[in] camera The Camera for whom to initialize layers\n> > > + * \\param[in] properties The Camera properties\n> > > + * \\param[in] controlInfoMap The Camera controls\n> > > + * \\param[in] layers Map of available layers\n> > > + *\n> > > + * This is called by the Camera at construction time via\n> > > + * LayerManager::createController. The LayerManager feeds the list of layers\n> > > + * that are available, and the LayerController can then create its own\n> > > + * execution queue and initialize all the layers for its Camera.\n> > > + *\n> > > + * \\a properties and \\a controlInfoMap are passed in so that the Layers can\n> > > + * modify them, although they will be cached in an internal copy that can be\n> > > + * efficiently returned at properties() and controls(), respectively.\n> > > + */\n> > > +LayerController::LayerController(const Camera *camera,\n> > > +                                const ControlList &properties,\n> > > +                                const ControlInfoMap &controlInfoMap,\n> > > +                                const std::map<std::string, std::shared_ptr<LayerLoaded>> &layers)\n> > > +{\n> > > +       /* Order the layers */\n> > > +       /* \\todo Document this. First is closer to application, last is closer to libcamera */\n> > > +       /* \\todo Get this from configuration file */\n> > > +       const char *layerList = utils::secure_getenv(\"LIBCAMERA_LAYERS_ENABLE\");\n> > > +       if (layerList) {\n> > > +               for (const auto &layerName : utils::split(layerList, \",\")) {\n> > > +                       if (layerName.empty())\n> > > +                               continue;\n> > > +\n> > > +                       const auto &it = layers.find(layerName);\n> > > +                       if (it == layers.end()) {\n> > > +                               LOG(LayerController, Warning)\n> > > +                                       << \"Requested layer '\" << layerName\n> > > +                                       << \"' not found\";\n> > > +                               continue;\n> > > +                       }\n> > > +\n> > > +                       executionQueue_.emplace_back(std::make_unique<LayerInstance>(it->second));\n> > > +               }\n> > > +       }\n> > > +\n> > > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > > +               layer->init(camera->id());\n> > > +\n> > > +       /*\n> > > +        * We need to iterate over the layers individually to merge all of\n> > > +        * their controls, so we'll factor out updateControls() as it needs to be\n> > > +        * run again at configure().\n> > > +        */\n> > > +       updateProperties(properties);\n> > > +       updateControls(controlInfoMap);\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Terminate the Layers\n> > > + *\n> > > + * This is called by the Camera at deconstruction time. The LayerController\n> > > + * instructs all Layer instances to release the resources that they allocated\n> > > + * for this specific \\a camera.\n> > > + */\n> > > +LayerController::~LayerController()\n> > > +{\n> > > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > > +               layer->terminate();\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Hook for Camera::bufferCompleted\n> > > + * \\param[in] request The request whose buffer completed\n> > > + * \\param[in] buffer The buffer that completed\n> > > + */\n> > > +void LayerController::bufferCompleted(Request *request, FrameBuffer *buffer)\n> > > +{\n> > > +       /* Reverse order because this comes from a Signal emission */\n> > > +       for (auto it = executionQueue_.rbegin();\n> > > +            it != executionQueue_.rend(); it++) {\n> > > +               (*it)->bufferCompleted(request, buffer);\n> > > +       }\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Hook for Camera::requestCompleted\n> > > + * \\param[in] request The request that completed\n> > > + * \\return metadata set by all the layers\n> > > + */\n> > > +ControlList LayerController::requestCompleted(Request *request)\n> > > +{\n> > > +       ControlList ret(controls::controls);\n> > > +\n> > > +       /* Reverse order because this comes from a Signal emission */\n> > > +       for (auto it = executionQueue_.rbegin();\n> > > +            it != executionQueue_.rend(); it++) {\n> > > +               ControlList metadata = (*it)->requestCompleted(request);\n> > > +               ret.merge(metadata, ControlList::MergePolicy::OverwriteExisting);\n> > > +       }\n> > > +\n> > > +       return ret;\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Hook for Camera::disconnected\n> > > + */\n> > > +void LayerController::disconnected()\n> > > +{\n> > > +       /* Reverse order because this comes from a Signal emission */\n> > > +       for (auto it = executionQueue_.rbegin();\n> > > +            it != executionQueue_.rend(); it++) {\n> > > +               (*it)->disconnected();\n> > > +       }\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Hook for Camera::acquire\n> > > + */\n> > > +void LayerController::acquire()\n> > > +{\n> > > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > > +               layer->acquire();\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Hook for Camera::release\n> > > + */\n> > > +void LayerController::release()\n> > > +{\n> > > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > > +               layer->release();\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\fn LayerController::controls\n> > > + * \\brief Hook for Camera::controls\n> > > + * \\return A ControlInfoMap that merges the Camera's controls() with the ones\n> > > + * declared by the layers\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\fn LayerController::properties\n> > > + * \\brief Hook for Camera::properties\n> > > + * \\return A properties list that merges the Camera's properties() with the\n> > > + * ones declared by the layers\n> > > + */\n> > > +\n> > > +void LayerController::updateProperties(const ControlList &properties)\n> > > +{\n> > > +       ControlList props = properties;\n> > > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_) {\n> > > +               ControlList ret = layer->properties(props);\n> > > +               props.merge(ret, ControlList::MergePolicy::OverwriteExisting);\n> > > +       }\n> > > +       properties_ = props;\n> > > +}\n> > > +\n> > > +void LayerController::updateControls(const ControlInfoMap &controlInfoMap)\n> > > +{\n> > > +       ControlInfoMap infoMap = controlInfoMap;\n> > > +       /* \\todo Simplify this once ControlInfoMaps become easier to modify */\n> > > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_) {\n> > > +               ControlInfoMap::Map ret = layer->controls(infoMap);\n> > > +               ControlInfoMap::Map map;\n> > > +               /* Merge the layer's ret first since insert doesn't overwrite */\n> > > +               map.insert(ret.begin(), ret.end());\n> > > +               map.insert(infoMap.begin(), infoMap.end());\n> > > +               infoMap = ControlInfoMap(std::move(map),\n> > > +                                        libcamera::controls::controls);\n> > > +       }\n> > > +       controls_ = infoMap;\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Hook for Camera::configure\n> > > + * \\param[in] config The configuration\n> > > + * \\param[in] controlInfoMap The ControlInfoMap of the controls that \\a camera supports\n> > > + *\n> > > + * \\a controlInfoMap is passed in as this is a potential point where the limits\n> > > + * of controls could change, so this gives a chance for the Layers to update\n> > > + * the ControlInfoMap that will be returned by LayerController::controls().\n> > > + */\n> > > +void LayerController::configure(const CameraConfiguration *config,\n> > > +                               const ControlInfoMap &controlInfoMap)\n> > > +{\n> > > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > > +               layer->configure(config);\n> > > +\n> > > +       updateControls(controlInfoMap);\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Hook for Camera::createRequest\n> > > + * \\param[in] cookie An opaque cookie for the application\n> > > + * \\param[in] request The request that was created\n> > > + */\n> > > +void LayerController::createRequest(uint64_t cookie, const Request *request)\n> > > +{\n> > > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > > +               layer->createRequest(cookie, request);\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Hook for Camera::queueRequest\n> > > + * \\param[in] request The request that is being queued\n> > > + */\n> > > +void LayerController::queueRequest(Request *request)\n> > > +{\n> > > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > > +               layer->queueRequest(request);\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Hook for Camera::start\n> > > + * \\param[in] controls The controls to be applied before starting the capture\n> > > + * \\return A ControlList that merges controls set by the layers and \\a controls\n> > > + */\n> > > +ControlList *LayerController::start(const ControlList *controls)\n> > > +{\n> > > +       if (controls) {\n> > > +               /* Clear any leftover start controls from a previous run */\n> > > +               startControls_.clear();\n> > > +               startControls_.merge(*controls);\n> > > +       }\n> > > +\n> > > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > > +               layer->start(startControls_);\n> > > +\n> > > +       return &startControls_;\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Hook for Camera::stop\n> > > + */\n> > > +void LayerController::stop()\n> > > +{\n> > > +       for (std::unique_ptr<LayerInstance> &layer : executionQueue_)\n> > > +               layer->stop();\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\class LayerManager\n> > > + * \\brief Layer manager\n> > > + *\n> > > + * The Layer manager discovers layer implementations from the filesystem, and\n> > > + * creates LayerLoaded instances for each one.\n> > > + *\n> > > + * The LayerManager itself is instantiated by the CameraManager, and each\n> > > + * Camera interacts with the LayerManager by passing itself in to\n> > > + * LayerManager::createController. The LayerManager internally maps each Camera\n> > > + * to a list of Layer instances that it calls sequentially for each hook.\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\brief Construct a LayerManager instance\n> > > + *\n> > > + * The LayerManager class is meant be instantiated by the CameraManager.\n> > > + *\n> > > + * This function simply loads all available layers and stores them. The\n> > > + * LayerController is responsible for organizing them into queues to be\n> > > + * executed, and for managing closures for each Camera that they belong to.\n> > > + */\n> > > +LayerManager::LayerManager()\n> > > +{\n> > > +       /* This is so that we can capture it in the lambda below */\n> > > +       std::map<std::string, std::shared_ptr<LayerLoaded>> &layers = layers_;\n> > > +\n> > > +       /* \\todo Implement built-in layers */\n> > > +\n> > > +       /* This returns the number of \"modules\" successfully loaded */\n> > > +       std::function<int(const std::string &)> soHandler =\n> > > +       [&layers](const std::string &file) {\n> > > +               std::shared_ptr<LayerLoaded> layer = std::make_shared<LayerLoaded>(file);\n> > > +               if (!layer->valid)\n> > > +                       return 0;\n> > > +\n> > > +               LOG(LayerManager, Debug) << \"Loaded layer '\" << file << \"'\";\n> > > +\n> > > +               auto [it, inserted] =\n> > > +                       layers.try_emplace(std::string(layer->info->name),\n> > > +                                          std::move(layer));\n> > > +               if (!inserted)\n> > > +                       LOG(LayerManager, Warning)\n> > > +                               << \"Not adding duplicate layer '\"\n> > > +                               << layer->info->name << \"'\";\n> > > +\n> > > +               return 1;\n> > > +       };\n> > > +\n> > > +       /* User-specified paths take precedence. */\n> > > +       /* \\todo Document this */\n> > > +       const char *layerPaths = utils::secure_getenv(\"LIBCAMERA_LAYER_PATH\");\n> > > +       if (layerPaths) {\n> > > +               for (const auto &dir : utils::split(layerPaths, \":\")) {\n> > > +                       if (dir.empty())\n> > > +                               continue;\n> > > +\n> > > +                       /*\n> > > +                        * \\todo Move the shared objects into one directory\n> > > +                        * instead of each in their own subdir\n> > > +                        */\n> > > +                       utils::findSharedObjects(dir.c_str(), 1, soHandler);\n> > > +               }\n> > > +       }\n> > > +\n> > > +       /*\n> > > +        * When libcamera is used before it is installed, load layers from the\n> > > +        * same build directory as the libcamera library itself.\n> > > +        */\n> > > +       std::string root = utils::libcameraBuildPath();\n> > > +       if (!root.empty()) {\n> > > +               std::string layerBuildPath = root + \"src/layer\";\n> > > +               constexpr int maxDepth = 2;\n> > > +\n> > > +               LOG(LayerManager, Info)\n> > > +                       << \"libcamera is not installed. Adding '\"\n> > > +                       << layerBuildPath << \"' to the layer search path\";\n> > > +\n> > > +               utils::findSharedObjects(layerBuildPath.c_str(), maxDepth, soHandler);\n> > > +       }\n> > > +\n> > > +       /* Finally try to load layers from the installed system path. */\n> > > +       utils::findSharedObjects(LAYER_DIR, 1, soHandler);\n> > > +}\n> > > +\n> > > +/**\n> > > + * \\brief Create a LayerController instance\n> > > + * \\param[in] camera The Camera instance for whom to create a LayerController\n> > > + * \\param[in] properties The Camera properties\n> > > + * \\param[in] controlInfoMap The Camera controls\n> > > + */\n> > > +std::unique_ptr<LayerController>\n> > > +LayerManager::createController(const Camera *camera,\n> > > +                              const ControlList &properties,\n> > > +                              const ControlInfoMap &controlInfoMap) const\n> > > +{\n> > > +       return std::make_unique<LayerController>(camera, properties, controlInfoMap, layers_);\n> > > +}\n> > > +\n> > > +} /* namespace libcamera */\n> > > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> > > index 186d5c48ccd5..076cfda66060 100644\n> > > --- a/src/libcamera/meson.build\n> > > +++ b/src/libcamera/meson.build\n> > > @@ -41,6 +41,8 @@ libcamera_internal_sources = files([\n> > >      'ipc_pipe.cpp',\n> > >      'ipc_pipe_unixsocket.cpp',\n> > >      'ipc_unixsocket.cpp',\n> > > +    'layer.cpp',\n> > > +    'layer_manager.cpp',\n> > >      'mapped_framebuffer.cpp',\n> > >      'matrix.cpp',\n> > >      'media_device.cpp',\n> > > diff --git a/src/meson.build b/src/meson.build\n> > > index 9b63c8e845d8..ef296382718c 100644\n> > > --- a/src/meson.build\n> > > +++ b/src/meson.build\n> > > @@ -77,6 +77,7 @@ subdir('libcamera')\n> > >  \n> > >  subdir('android')\n> > >  subdir('ipa')\n> > > +subdir('layer')\n> > >  \n> > >  subdir('apps')\n> > >  \n> > > -- \n> > > 2.47.2\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 08591C3237\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 25 Feb 2026 10:38:20 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 33CA9622AD;\n\tWed, 25 Feb 2026 11:38:19 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id BF12F620C9\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 25 Feb 2026 11:38:16 +0100 (CET)","from thinkpad.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 34D9CF52;\n\tWed, 25 Feb 2026 11:37:19 +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=\"ae1Deez8\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1772015839;\n\tbh=I7mvvObMsYk8OKa68KZbuAMz+VKYdvpIYYZKg3NZ3gc=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=ae1Deez8mTWHYbnzpWhH5RPuMlAB5zN1EcuPJA0wG9shhIOSg+lQTZFD/oz/ZaSnz\n\ttZF/4Jo1vVtp5kSmI0lSiBJ61/uJfddtD6iI0/EJVIwdKsaiG/vBnvwq0Arnhw6N4x\n\tl5UhsgtsGlizyLSJXk+DFr/6YaEgOFXnPXsAzBIQ=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<177198712030.607498.11650178776796019669@neptunite.rasen.tech>","References":"<20260129082814.1777779-1-paul.elder@ideasonboard.com>\n\t<20260129082814.1777779-5-paul.elder@ideasonboard.com>\n\t<177194896117.1513537.12265013306022510835@t16>\n\t<177198712030.607498.11650178776796019669@neptunite.rasen.tech>","Subject":"Re: [PATCH v6 4/8] libcamera: layer_manager: Add Layer handling\n\timplementation","From":"Isaac Scott <isaac.scott@ideasonboard.com>","Cc":"","To":"Paul Elder <paul.elder@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Wed, 25 Feb 2026 10:38:13 +0000","Message-ID":"<177201589319.1569069.4342313288859198340@t16>","User-Agent":"alot/0.10","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>"}}]