[{"id":37421,"web_url":"https://patchwork.libcamera.org/comment/37421/","msgid":"<6c10eea7-a460-4ac9-8596-123be691d7f9@collabora.com>","date":"2025-12-17T10:29:02","subject":"Re: [PATCH v9 02/26] libcamera: software_isp: egl: Add a eGL base\n\thelper class","submitter":{"id":140,"url":"https://patchwork.libcamera.org/api/people/140/","name":"Robert Mader","email":"robert.mader@collabora.com"},"content":"There are some possible clean-up left, but we can do that in a follow-up.\n\nReviewed-by: Robert Mader <robert.mader@collabora.com>\n\nOn 17.12.25 11:01, Bryan O'Donoghue wrote:\n> Introduce an eGL base helper class which provides an eGL context based on a\n> passed width and height.\n>\n> The initGLContext function could be overloaded to provide an interface to a\n> real display.\n>\n> A set of helper functions is provided to compile and link GLSL shaders.\n> linkShaderProgram currently compiles vertex/fragment pairs but could be\n> overloaded or passed a parameter to link a compute shader instead.\n>\n> Breaking the eGL interface away from debayering - allows to use the eGL\n> context inside of a dma-buf heap cleanly, reuse that context inside of a\n> debayer layer and conceivably reuse the context in a multi-stage shader\n> pass.\n>\n> Small note the image_attrs[] array doesn't pass checkstyle.py however the\n> elements of the array are in pairs.\n>\n> Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> [bod: Takes fix from Hans for constructor stride bpp]\n> [bod: Drops eglClientWaitSync in favour of glFinish Robert/Milan]\n> Co-developed-by: Hans de Goede <johannes.goede@oss.qualcomm.com>\n> Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>\n> Co-developed-by: Milan Zamazal <mzamazal@redhat.com>\n> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>\n> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> ---\n>   include/libcamera/internal/egl.h | 185 ++++++++++\n>   src/libcamera/egl.cpp            | 612 +++++++++++++++++++++++++++++++\n>   src/libcamera/meson.build        |  23 ++\n>   3 files changed, 820 insertions(+)\n>   create mode 100644 include/libcamera/internal/egl.h\n>   create mode 100644 src/libcamera/egl.cpp\n>\n> diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h\n> new file mode 100644\n> index 000000000..1ed93f3dc\n> --- /dev/null\n> +++ b/include/libcamera/internal/egl.h\n> @@ -0,0 +1,185 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Linaro Ltd.\n> + *\n> + * Authors:\n> + * Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> + *\n> + */\n> +\n> +#pragma once\n> +\n> +#include <sys/types.h>\n> +#include <unistd.h>\n> +\n> +#include <libcamera/base/log.h>\n> +#include <libcamera/base/span.h>\n> +#include <libcamera/base/utils.h>\n> +\n> +#include \"libcamera/internal/gbm.h\"\n> +\n> +#define EGL_EGLEXT_PROTOTYPES\n> +#include <EGL/egl.h>\n> +#include <EGL/eglext.h>\n> +#define GL_GLEXT_PROTOTYPES\n> +#include <GLES2/gl2.h>\n> +#include <GLES2/gl2ext.h>\n> +\n> +namespace libcamera {\n> +\n> +LOG_DECLARE_CATEGORY(eGL)\n> +\n> +/**\n> + * \\class eGLImage\n> + * \\brief Helper class for managing EGL image resources\n> + *\n> + * The eGLImage class encapsulates OpenGL ES texture and framebuffer objects\n> + * along with their associated EGL image. It aggregates handles, descriptors,\n> + * and routines for managing textures that can be associated with shader\n> + * uniform IDs.\n> + *\n> + * This class is particularly useful for managing DMA-BUF backed textures\n> + * in zero-copy rendering pipelines, where textures are bound to specific\n> + * texture units and can be used as both input textures and render targets.\n> + */\n> +class eGLImage\n> +{\n> +public:\n> +\t/**\n> +\t * \\brief Construct an eGLImage with explicit stride\n> +\t * \\param[in] width Image width in pixels\n> +\t * \\param[in] height Image height in pixels\n> +\t * \\param[in] bpp Bytes per pixel\n> +\t * \\param[in] stride Row stride in bytes\n> +\t * \\param[in] texture_unit OpenGL texture unit (e.g., GL_TEXTURE0)\n> +\t * \\param[in] texture_unit_uniform_id Shader uniform ID for this texture unit\n> +\t *\n> +\t * Creates an eGLImage with the specified dimensions and stride. The stride\n> +\t * may differ from width * bpp due to alignment.\n> +\t */\n> +\teGLImage(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t texture_unit_uniform_id)\n> +\t{\n> +\t\tinit(width, height, bpp, stride, texture_unit, texture_unit_uniform_id);\n> +\t}\n> +\t/**\n> +\t * \\brief Construct an eGLImage with automatic stride calculation\n> +\t * \\param[in] width Image width in pixels\n> +\t * \\param[in] height Image height in pixels\n> +\t * \\param[in] bpp Bytes per pixel\n> +\t * \\param[in] texture_unit OpenGL texture unit (e.g., GL_TEXTURE0)\n> +\t * \\param[in] texture_unit_uniform_id Shader uniform ID for this texture unit\n> +\t *\n> +\t * Creates an eGLImage with automatic stride calculation. The stride is\n> +\t * aligned to 256 bytes because 256 byte alignment is a common baseline alignment for GPUs.\n> +\t */\n> +\teGLImage(uint32_t width, uint32_t height, uint32_t bpp, GLenum texture_unit, uint32_t texture_unit_uniform_id)\n> +\t{\n> +\t\tuint32_t stride = libcamera::utils::alignUp(width * bpp / 8, 256);\n> +\n> +\t\tinit(width, height, bpp, stride, texture_unit, texture_unit_uniform_id);\n> +\t}\n> +\n> +\t/**\n> +\t * \\brief Destroy the eGLImage\n> +\t *\n> +\t * Cleans up OpenGL resources by deleting the framebuffer object and\n> +\t * texture.\n> +\t */\n> +\t~eGLImage()\n> +\t{\n> +\t\tglDeleteFramebuffers(1, &fbo_);\n> +\t\tglDeleteTextures(1, &texture_);\n> +\t}\n> +\n> +\tuint32_t width_; /**< Image width in pixels */\n> +\tuint32_t height_; /**< Image height in pixels */\n> +\tuint32_t stride_; /**< Row stride in bytes */\n> +\tuint32_t offset_; /**< Buffer offset (reserved for future use) */\n> +\tuint32_t framesize_; /**< Total frame size in bytes (stride * height) */\n> +\tuint32_t bpp_; /**< Bytes per pixel */\n> +\tuint32_t texture_unit_uniform_id_; /**< Shader uniform id for texture unit */\n> +\tGLenum texture_unit_; /**< Texture unit associated with this image eg (GL_TEXTURE0) */\n> +\tGLuint texture_; /**< OpenGL texture object ID */\n> +\tGLuint fbo_; /**< OpenGL frame buffer object ID */\n> +\tEGLImageKHR image_; /**< EGL Image handle */\n> +\n> +private:\n> +\tLIBCAMERA_DISABLE_COPY_AND_MOVE(eGLImage)\n> +\n> +\t/**\n> +\t * \\brief Initialise eGLImage state\n> +\t * \\param[in] width Image width in pixels\n> +\t * \\param[in] height Image height in pixels\n> +\t * \\param[in] bpp Bytes per pixel\n> +\t * \\param[in] stride Row stride in bytes\n> +\t * \\param[in] texture_unit OpenGL texture unit\n> +\t * \\param[in] texture_unit_uniform_id Shader uniform ID\n> +\t *\n> +\t * Common initialisation routine called by both constructors. Sets up\n> +\t * member variables and generates OpenGL texture and framebuffer objects.\n> +\t */\n> +\tvoid init(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t texture_unit_uniform_id)\n> +\t{\n> +\t\timage_ = EGL_NO_IMAGE_KHR;\n> +\t\twidth_ = width;\n> +\t\theight_ = height;\n> +\t\tbpp_ = bpp;\n> +\t\tstride_ = stride;\n> +\t\tframesize_ = stride_ * height_;\n> +\t\ttexture_unit_ = texture_unit;\n> +\t\ttexture_unit_uniform_id_ = texture_unit_uniform_id;\n> +\n> +\t\tglGenTextures(1, &texture_);\n> +\t\tglGenFramebuffers(1, &fbo_);\n> +\t}\n> +};\n> +\n> +class eGL\n> +{\n> +public:\n> +\teGL();\n> +\t~eGL();\n> +\n> +\tint initEGLContext(GBM *gbmContext);\n> +\tvoid cleanUp();\n> +\n> +\tint createInputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd);\n> +\tint createOutputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd);\n> +\tvoid destroyDMABufTexture(std::unique_ptr<eGLImage> &eglImage);\n> +\tvoid createTexture2D(std::unique_ptr<eGLImage> &eglImage, GLint format, uint32_t width, uint32_t height, void *data);\n> +\n> +\tvoid pushEnv(std::vector<std::string> &shaderEnv, const char *str);\n> +\tvoid makeCurrent();\n> +\n> +\tint compileVertexShader(GLuint &shaderId, const unsigned char *shaderData,\n> +\t\t\t\tunsigned int shaderDataLen,\n> +\t\t\t\tSpan<const std::string> shaderEnv);\n> +\tint compileFragmentShader(GLuint &shaderId, const unsigned char *shaderData,\n> +\t\t\t\t  unsigned int shaderDataLen,\n> +\t\t\t\t  Span<const std::string> shaderEnv);\n> +\tint linkProgram(GLuint &programId, GLuint fragmentshaderId, GLuint vertexshaderId);\n> +\tvoid dumpShaderSource(GLuint shaderId);\n> +\tvoid useProgram(GLuint programId);\n> +\tvoid deleteProgram(GLuint programId);\n> +\tvoid syncOutput();\n> +\n> +private:\n> +\tLIBCAMERA_DISABLE_COPY_AND_MOVE(eGL)\n> +\n> +\tpid_t tid_;\n> +\n> +\tEGLDisplay display_;\n> +\tEGLContext context_;\n> +\tEGLSurface surface_;\n> +\n> +\tint compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData,\n> +\t\t\t  unsigned int shaderDataLen,\n> +\t\t\t  Span<const std::string> shaderEnv);\n> +\n> +\tint createDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd, bool output);\n> +\n> +\tPFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;\n> +\tPFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;\n> +\tPFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;\n> +};\n> +} //namespace libcamera\n> diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp\n> new file mode 100644\n> index 000000000..c6fde5e42\n> --- /dev/null\n> +++ b/src/libcamera/egl.cpp\n> @@ -0,0 +1,612 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Linaro Ltd.\n> + *\n> + * Authors:\n> + * Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> + *\n> + */\n> +\n> +#include \"libcamera/internal/egl.h\"\n> +\n> +#include <fcntl.h>\n> +#include <sys/ioctl.h>\n> +#include <sys/mman.h>\n> +#include <unistd.h>\n> +\n> +#include <linux/dma-buf.h>\n> +#include <linux/dma-heap.h>\n> +\n> +#include <libcamera/base/thread.h>\n> +\n> +#include <libdrm/drm_fourcc.h>\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(eGL)\n> +\n> +/**\n> + * \\class eGL\n> + * \\brief Helper class for managing OpenGL ES operations\n> + *\n> + * It provides:\n> + *\n> + * - EGL context setup and management\n> + * - Extension function pointer retrieval\n> + * - Shader compilation and program linking\n> + * - DMA-BUF texture creation and management\n> + * - Synchronisation primitives\n> + *\n> + * This class is designed to work with zero-copy buffers via DMA-BUF file\n> + * descriptors.\n> + */\n> +\n> +/**\n> + *\\var eGL::tid_\n> + *\\brief Thread ID of the thread associated with this EGL context\n> + */\n> +\n> +/**\n> + *\\var eGL::display_\n> + *\\brief EGL display handle\n> + */\n> +\n> +/**\n> + *\\var eGL::context_\n> + *\\brief EGL context handle\n> + */\n> +\n> +/**\n> + *\\var eGL::surface_\n> + *\\brief EGL sufrace handle\n> + */\n> +\n> +/**\n> + * \\brief Construct an EGL helper\n> + *\n> + * Creates an eGL instance with uninitialised context. Call initEGLContext()\n> + * to set up the EGL display, context, and load extension functions.\n> + */\n> +eGL::eGL()\n> +{\n> +\tcontext_ = EGL_NO_CONTEXT;\n> +\tsurface_ = EGL_NO_SURFACE;\n> +\tdisplay_ = EGL_NO_DISPLAY;\n> +}\n> +\n> +/**\n> + * \\brief Destroy the EGL helper\n> + *\n> + * Destroys the EGL context and surface if they were successfully created.\n> + */\n> +eGL::~eGL()\n> +{\n> +\tif (context_ != EGL_NO_CONTEXT)\n> +\t\teglDestroyContext(display_, context_);\n> +\n> +\tif (surface_ != EGL_NO_SURFACE)\n> +\t\teglDestroySurface(display_, surface_);\n> +}\n> +\n> +/**\n> + * \\brief Synchronise rendering output\n> + *\n> + * Sychronise here. Calls glFinish() right now.\n> + *\n> + */\n> +void eGL::syncOutput()\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\tglFinish();\n> +}\n> +\n> +/**\n> + * \\brief Create a DMA-BUF backed 2D texture\n> + * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n> + * \\param[in] fd DMA-BUF file descriptor\n> + * \\param[in] output If true, create framebuffer for render target\n> + *\n> + * Internal implementation for creating DMA-BUF textures. Creates an EGL\n> + * image from the DMA-BUF and binds it to a 2D texture. If output is true,\n> + * also creates and attaches a framebuffer object.\n> + *\n> + * \\return 0 on success, or -ENODEV on failure\n> + */\n> +int eGL::createDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd, bool output)\n> +{\n> +\tint ret = 0;\n> +\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\t// clang-format off\n> +\tEGLint image_attrs[] = {\n> +\t\tEGL_WIDTH, (EGLint)eglImage->width_,\n> +\t\tEGL_HEIGHT, (EGLint)eglImage->height_,\n> +\t\tEGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_ARGB8888,\n> +\t\tEGL_DMA_BUF_PLANE0_FD_EXT, fd,\n> +\t\tEGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,\n> +\t\tEGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)eglImage->stride_,\n> +\t\tEGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, 0,\n> +\t\tEGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, 0,\n> +\t\tEGL_NONE,\n> +\t};\n> +\t// clang-format on\n> +\n> +\teglImage->image_ = eglCreateImageKHR(display_, EGL_NO_CONTEXT,\n> +\t\t\t\t\t     EGL_LINUX_DMA_BUF_EXT,\n> +\t\t\t\t\t     NULL, image_attrs);\n> +\n> +\tif (eglImage->image_ == EGL_NO_IMAGE_KHR) {\n> +\t\tLOG(eGL, Error) << \"eglCreateImageKHR fail\";\n> +\t\tret = -ENODEV;\n> +\t\tgoto done;\n> +\t}\n> +\n> +\t// Bind texture unit and texture\n> +\tglActiveTexture(eglImage->texture_unit_);\n> +\tglBindTexture(GL_TEXTURE_2D, eglImage->texture_);\n> +\n> +\t// Generate texture with filter semantics\n> +\tglEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage->image_);\n> +\n> +\t// Nearest filtering\n> +\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n> +\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n> +\n> +\t// Wrap to edge to avoid edge artifacts\n> +\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n> +\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n> +\n> +\tif (output) {\n> +\t\t// Generate a framebuffer from our texture direct to dma-buf handle buffer\n> +\t\tglBindFramebuffer(GL_FRAMEBUFFER, eglImage->fbo_);\n> +\t\tglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, eglImage->texture_, 0);\n> +\n> +\t\tGLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n> +\t\tif (err != GL_FRAMEBUFFER_COMPLETE) {\n> +\t\t\tLOG(eGL, Error) << \"glFrameBufferTexture2D error \" << err;\n> +\t\t\teglDestroyImageKHR(display_, eglImage->image_);\n> +\t\t\tret = -ENODEV;\n> +\t\t\tgoto done;\n> +\t\t}\n> +\t}\n> +done:\n> +\treturn ret;\n> +}\n> +\n> +/**\n> + * \\brief Create an input DMA-BUF backed texture\n> + * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n> + * \\param[in] fd DMA-BUF file descriptor\n> + *\n> + * Creates an EGL image from a DMA-BUF file descriptor and binds it to\n> + * a 2D texture for use as an input texture in shaders. The texture is\n> + * configured with nearest filtering and clamp-to-edge wrapping.\n> + *\n> + * \\return 0 on success, or -ENODEV on failure\n> + */\n> +int eGL::createInputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd)\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\treturn createDMABufTexture2D(eglImage, fd, false);\n> +}\n> +\n> +/**\n> + * \\brief Create an output DMA-BUF backed texture\n> + * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n> + * \\param[in] fd DMA-BUF file descriptor\n> + *\n> + * Creates an EGL image from a DMA-BUF file descriptor and binds it to\n> + * a 2D texture, then attaches it to a framebuffer object for use as a\n> + * render target. This enables zero-copy rendering directly to the\n> + * DMA-BUF.\n> + *\n> + * \\return 0 on success, or -ENODEV on failure\n> + */\n> +int eGL::createOutputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd)\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\treturn createDMABufTexture2D(eglImage, fd, true);\n> +}\n> +\n> +/**\n> + * \\brief Destroy a DMA-BUF texture's EGL image\n> + * \\param[in,out] eglImage EGL image to destroy\n> + *\n> + * Destroys the EGL image associated with a DMA-BUF texture. The OpenGL\n> + * texture and framebuffer objects are destroyed separately in the\n> + * eGLImage destructor.\n> + */\n> +void eGL::destroyDMABufTexture(std::unique_ptr<eGLImage> &eglImage)\n> +{\n> +\teglDestroyImage(display_, eglImage->image_);\n> +}\n> +\n> +/**\n> + * \\brief Create a 2D texture from a memory buffer\n> + * \\param[in,out] eglImage EGL image to associate with the texture\n> + * \\param[in] format OpenGL internal format (e.g., GL_RGB, GL_RGBA)\n> + * \\param[in] width Texture width in pixels\n> + * \\param[in] height Texture height in pixels\n> + * \\param[in] data Pointer to pixel data, or nullptr for uninitialised texture\n> + *\n> + * Creates a 2D texture from a CPU-accessible memory buffer. The texture\n> + * is configured with nearest filtering and clamp-to-edge wrapping. This\n> + * is useful for uploading static data like lookup tables or uniform color\n> + * matrices to the GPU.\n> + */\n> +void eGL::createTexture2D(std::unique_ptr<eGLImage> &eglImage, GLint format, uint32_t width, uint32_t height, void *data)\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\tglActiveTexture(eglImage->texture_unit_);\n> +\tglBindTexture(GL_TEXTURE_2D, eglImage->texture_);\n> +\n> +\t// Generate texture, bind, associate image to texture, configure, unbind\n> +\tglTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);\n> +\n> +\t// Nearest filtering\n> +\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n> +\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n> +\n> +\t// Wrap to edge to avoid edge artifacts\n> +\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n> +\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n> +}\n> +\n> +/**\n> + * \\brief Initialise the EGL context\n> + * \\param[in] gbmContext Pointer to initialised GBM context\n> + *\n> + * Sets up the EGL display from the GBM device, creates an OpenGL ES 2.0\n> + * context, and retrieves function pointers for required extensions\n> + * including:\n> + * - eglCreateImageKHR / eglDestroyImageKHR\n> + * - glEGLImageTargetTexture2DOES\n> + *\n> + * \\return 0 on success, or -ENODEV on failure\n> + */\n> +int eGL::initEGLContext(GBM *gbmContext)\n> +{\n> +\tEGLint configAttribs[] = {\n> +\t\tEGL_RED_SIZE, 8,\n> +\t\tEGL_GREEN_SIZE, 8,\n> +\t\tEGL_BLUE_SIZE, 8,\n> +\t\tEGL_ALPHA_SIZE, 8,\n> +\t\tEGL_SURFACE_TYPE, EGL_WINDOW_BIT,\n> +\t\tEGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,\n> +\t\tEGL_NONE\n> +\t};\n> +\n> +\tEGLint contextAttribs[] = {\n> +\t\tEGL_CONTEXT_MAJOR_VERSION, 2,\n> +\t\tEGL_NONE\n> +\t};\n> +\n> +\tEGLint numConfigs;\n> +\tEGLConfig config;\n> +\tEGLint major;\n> +\tEGLint minor;\n> +\n> +\tif (!eglBindAPI(EGL_OPENGL_ES_API)) {\n> +\t\tLOG(eGL, Error) << \"API bind fail\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\tdisplay_ = eglGetDisplay(gbmContext->getDevice());\n> +\tif (display_ == EGL_NO_DISPLAY) {\n> +\t\tLOG(eGL, Error) << \"Unable to get EGL display\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\tif (eglInitialize(display_, &major, &minor) != EGL_TRUE) {\n> +\t\tLOG(eGL, Error) << \"eglInitialize fail\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\tLOG(eGL, Info) << \"EGL: version \" << major << \".\" << minor;\n> +\tLOG(eGL, Info) << \"EGL: EGL_VERSION: \" << eglQueryString(display_, EGL_VERSION);\n> +\tLOG(eGL, Info) << \"EGL: EGL_VENDOR: \" << eglQueryString(display_, EGL_VENDOR);\n> +\tLOG(eGL, Info) << \"EGL: EGL_CLIENT_APIS: \" << eglQueryString(display_, EGL_CLIENT_APIS);\n> +\tLOG(eGL, Info) << \"EGL: EGL_EXTENSIONS: \" << eglQueryString(display_, EGL_EXTENSIONS);\n> +\n> +\teglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress(\"eglCreateImageKHR\");\n> +\tif (!eglCreateImageKHR) {\n> +\t\tLOG(eGL, Error) << \"eglCreateImageKHR not found\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\teglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress(\"eglDestroyImageKHR\");\n> +\tif (!eglDestroyImageKHR) {\n> +\t\tLOG(eGL, Error) << \"eglDestroyImageKHR not found\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\tglEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress(\"glEGLImageTargetTexture2DOES\");\n> +\tif (!glEGLImageTargetTexture2DOES) {\n> +\t\tLOG(eGL, Error) << \"glEGLImageTargetTexture2DOES not found\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\tif (eglChooseConfig(display_, configAttribs, &config, 1, &numConfigs) != EGL_TRUE) {\n> +\t\tLOG(eGL, Error) << \"eglChooseConfig fail\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\tcontext_ = eglCreateContext(display_, config, EGL_NO_CONTEXT, contextAttribs);\n> +\tif (context_ == EGL_NO_CONTEXT) {\n> +\t\tLOG(eGL, Error) << \"eglContext returned EGL_NO_CONTEXT\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\ttid_ = Thread::currentId();\n> +\n> +\tmakeCurrent();\n> +\n> +\treturn 0;\n> +fail:\n> +\n> +\treturn -ENODEV;\n> +}\n> +\n> +/**\n> + * \\brief Clean up EGL resources\n> + *\n> + * Destroys the EGL sync object. Must be called from the same thread\n> + * that created the EGL context.\n> + */\n> +void eGL::cleanUp()\n> +{\n> +}\n> +\n> +/**\n> + * \\brief Make the EGL context current for the calling thread\n> + *\n> + * Binds the EGL context to the current thread, allowing OpenGL ES\n> + * operations to be performed. Must be called from the thread that\n> + * will perform rendering operations.\n> + */\n> +void eGL::makeCurrent()\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\tif (eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, context_) != EGL_TRUE) {\n> +\t\tLOG(eGL, Error) << \"eglMakeCurrent fail\";\n> +\t}\n> +}\n> +\n> +/**\n> + * \\brief Activate a shader program for rendering\n> + * \\param[in] programId OpenGL program object ID\n> + *\n> + * Sets the specified program as the current rendering program. All\n> + * subsequent draw calls will use this program's shaders.\n> + */\n> +void eGL::useProgram(GLuint programId)\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\tglUseProgram(programId);\n> +}\n> +\n> +/**\n> + * \\brief Delete a shader program\n> + * \\param[in] programId OpenGL program object ID\n> + *\n> + * Deletes a shader program and frees associated resources. The program\n> + * must not be currently in use.\n> + */\n> +void eGL::deleteProgram(GLuint programId)\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\tglDeleteProgram(programId);\n> +}\n> +\n> +/**\n> + * \\brief Add a preprocessor definition to shader environment\n> + * \\param[in,out] shaderEnv Vector of shader environment strings\n> + * \\param[in] str Preprocessor definition string (e.g., \"#define APPLY_RGB_PARAMETERS\")\n> + *\n> + * Appends a preprocessor definition to the shader environment vector.\n> + * These definitions are prepended to shader source code during compilation.\n> + */\n> +void eGL::pushEnv(std::vector<std::string> &shaderEnv, const char *str)\n> +{\n> +\tstd::string addStr = str;\n> +\n> +\taddStr.push_back('\\n');\n> +\tshaderEnv.push_back(std::move(addStr));\n> +}\n> +\n> +/**\n> + * \\brief Compile a vertex shader\n> + * \\param[out] shaderId OpenGL shader object ID\n> + * \\param[in] shaderData Pointer to shader source code\n> + * \\param[in] shaderDataLen Length of shader source in bytes\n> + * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n> + *\n> + * Compiles a vertex shader from source code with optional preprocessor\n> + * definitions. On compilation failure, logs the shader info log.\n> + *\n> + * \\return 0 on success, or -EINVAL on compilation failure\n> + */\n> +int eGL::compileVertexShader(GLuint &shaderId, const unsigned char *shaderData,\n> +\t\t\t     unsigned int shaderDataLen,\n> +\t\t\t     Span<const std::string> shaderEnv)\n> +{\n> +\treturn compileShader(GL_VERTEX_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv);\n> +}\n> +\n> +/**\n> + * \\brief Compile a fragment shader\n> + * \\param[out] shaderId OpenGL shader object ID\n> + * \\param[in] shaderData Pointer to shader source code\n> + * \\param[in] shaderDataLen Length of shader source in bytes\n> + * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n> + *\n> + * Compiles a fragment shader from source code with optional preprocessor\n> + * definitions. On compilation failure, logs the shader info log.\n> + *\n> + * \\return 0 on success, or -EINVAL on compilation failure\n> + */\n> +int eGL::compileFragmentShader(GLuint &shaderId, const unsigned char *shaderData,\n> +\t\t\t       unsigned int shaderDataLen,\n> +\t\t\t       Span<const std::string> shaderEnv)\n> +{\n> +\treturn compileShader(GL_FRAGMENT_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv);\n> +}\n> +\n> +/**\n> + * \\brief Compile a shader of specified type\n> + * \\param[in] shaderType GL_VERTEX_SHADER or GL_FRAGMENT_SHADER\n> + * \\param[out] shaderId OpenGL shader object ID\n> + * \\param[in] shaderData Pointer to shader source code\n> + * \\param[in] shaderDataLen Length of shader source in bytes\n> + * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n> + *\n> + * Internal helper function for shader compilation. Prepends environment\n> + * definitions to the shader source and compiles the shader.\n> + *\n> + * \\return 0 on success, or -EINVAL on compilation failure\n> + */\n> +int eGL::compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData,\n> +\t\t       unsigned int shaderDataLen,\n> +\t\t       Span<const std::string> shaderEnv)\n> +{\n> +\tGLint success;\n> +\tsize_t i;\n> +\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\tauto count = 1 + shaderEnv.size();\n> +\tauto shaderSourceData = std::make_unique<const GLchar *[]>(count);\n> +\tauto shaderDataLengths = std::make_unique<GLint[]>(count);\n> +\n> +\t// Prefix defines before main body of shader\n> +\tfor (i = 0; i < shaderEnv.size(); i++) {\n> +\t\tshaderSourceData[i] = shaderEnv[i].c_str();\n> +\t\tshaderDataLengths[i] = shaderEnv[i].length();\n> +\t}\n> +\n> +\t// Now the main body of the shader program\n> +\tshaderSourceData[i] = reinterpret_cast<const GLchar *>(shaderData);\n> +\tshaderDataLengths[i] = shaderDataLen;\n> +\n> +\t// And create the shader\n> +\tshaderId = glCreateShader(shaderType);\n> +\tglShaderSource(shaderId, count, shaderSourceData.get(), shaderDataLengths.get());\n> +\tglCompileShader(shaderId);\n> +\n> +\t// Check status\n> +\tglGetShaderiv(shaderId, GL_COMPILE_STATUS, &success);\n> +\tif (success == GL_FALSE) {\n> +\t\tGLint sizeLog = 0;\n> +\n> +\t\tglGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &sizeLog);\n> +\t\tauto infoLog = std::make_unique<GLchar[]>(sizeLog);\n> +\n> +\t\tglGetShaderInfoLog(shaderId, sizeLog, &sizeLog, infoLog.get());\n> +\t\tLOG(eGL, Error) << infoLog.get();\n> +\t}\n> +\n> +\treturn (success == GL_TRUE) ? 0 : -EINVAL;\n> +}\n> +\n> +/**\n> + * \\brief Dump shader source code to the log\n> + * \\param[in] shaderId OpenGL shader object ID\n> + *\n> + * Retrieves and logs the complete source code of a compiled shader.\n> + * Useful for debugging shader compilation issues.\n> + */\n> +void eGL::dumpShaderSource(GLuint shaderId)\n> +{\n> +\tGLint shaderLength = 0;\n> +\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\tglGetShaderiv(shaderId, GL_SHADER_SOURCE_LENGTH, &shaderLength);\n> +\n> +\tLOG(eGL, Debug) << \"Shader length is \" << shaderLength;\n> +\n> +\tif (shaderLength > 0) {\n> +\t\tauto shaderSource = std::make_unique<GLchar[]>(shaderLength);\n> +\n> +\t\tglGetShaderSource(shaderId, shaderLength, &shaderLength, shaderSource.get());\n> +\t\tif (shaderLength) {\n> +\t\t\tLOG(eGL, Debug) << \"Shader source = \" << shaderSource.get();\n> +\t\t}\n> +\t}\n> +}\n> +\n> +/**\n> + * \\brief Link a shader program\n> + * \\param[out] programId OpenGL program object ID\n> + * \\param[in] fragmentshaderId Compiled fragment shader ID\n> + * \\param[in] vertexshaderId Compiled vertex shader ID\n> + *\n> + * Links vertex and fragment shaders into an executable shader program.\n> + * On link failure, logs the program info log and deletes the program.\n> + *\n> + * \\return 0 on success, or -ENODEV on link failure\n> + */\n> +int eGL::linkProgram(GLuint &programId, GLuint vertexshaderId, GLuint fragmentshaderId)\n> +{\n> +\tGLint success;\n> +\tGLenum err;\n> +\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\tprogramId = glCreateProgram();\n> +\tif (!programId)\n> +\t\tgoto fail;\n> +\n> +\tglAttachShader(programId, vertexshaderId);\n> +\tif ((err = glGetError()) != GL_NO_ERROR) {\n> +\t\tLOG(eGL, Error) << \"Attach compute vertex shader fail\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\tglAttachShader(programId, fragmentshaderId);\n> +\tif ((err = glGetError()) != GL_NO_ERROR) {\n> +\t\tLOG(eGL, Error) << \"Attach compute fragment shader fail\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\tglLinkProgram(programId);\n> +\tif ((err = glGetError()) != GL_NO_ERROR) {\n> +\t\tLOG(eGL, Error) << \"Link program fail\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\tglDetachShader(programId, fragmentshaderId);\n> +\tglDetachShader(programId, vertexshaderId);\n> +\n> +\t// Check status\n> +\tglGetProgramiv(programId, GL_LINK_STATUS, &success);\n> +\tif (success == GL_FALSE) {\n> +\t\tGLint sizeLog = 0;\n> +\t\tGLchar *infoLog;\n> +\n> +\t\tglGetProgramiv(programId, GL_INFO_LOG_LENGTH, &sizeLog);\n> +\t\tinfoLog = new GLchar[sizeLog];\n> +\n> +\t\tglGetProgramInfoLog(programId, sizeLog, &sizeLog, infoLog);\n> +\t\tLOG(eGL, Error) << infoLog;\n> +\n> +\t\tdelete[] infoLog;\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\treturn 0;\n> +fail:\n> +\tif (programId)\n> +\t\tglDeleteProgram(programId);\n> +\n> +\treturn -ENODEV;\n> +}\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index e5b5330a8..60f036da4 100644\n> --- a/src/libcamera/meson.build\n> +++ b/src/libcamera/meson.build\n> @@ -79,6 +79,27 @@ if libgbm.found() and gbm_works\n>       ])\n>   endif\n>   \n> +libegl = cc.find_library('EGL', required : false)\n> +libglesv2 = cc.find_library('GLESv2', required : false)\n> +mesa_works = cc.check_header('EGL/egl.h', required: false)\n> +\n> +if libegl.found() and mesa_works\n> +    config_h.set('HAVE_LIBEGL', 1)\n> +endif\n> +\n> +if libglesv2.found() and mesa_works\n> +    config_h.set('HAVE_GLESV2', 1)\n> +endif\n> +\n> +if mesa_works and gbm_works\n> +    libcamera_internal_sources += files([\n> +        'egl.cpp',\n> +    ])\n> +    gles_headless_enabled = true\n> +else\n> +    gles_headless_enabled = false\n> +endif\n> +\n>   subdir('base')\n>   subdir('converter')\n>   subdir('ipa')\n> @@ -187,7 +208,9 @@ libcamera_deps += [\n>       libcamera_base_private,\n>       libcrypto,\n>       libdl,\n> +    libegl,\n>       libgbm,\n> +    libglesv2,\n>       liblttng,\n>       libudev,\n>       libyaml,","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 56ABEC3257\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 17 Dec 2025 10:29:13 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4E60261A35;\n\tWed, 17 Dec 2025 11:29:12 +0100 (CET)","from sender4-op-o12.zoho.com (sender4-op-o12.zoho.com\n\t[136.143.188.12])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3AE7C61A2C\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 17 Dec 2025 11:29:10 +0100 (CET)","by mx.zohomail.com with SMTPS id 1765967344750561.2815090578681;\n\tWed, 17 Dec 2025 02:29:04 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=collabora.com\n\theader.i=robert.mader@collabora.com header.b=\"TqV8Kvxt\"; \n\tdkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1765967347; cv=none; \n\td=zohomail.com; s=zohoarc; \n\tb=hzsPgcN5jya/kSD71Kka/yLroBAknUS5yWqHE8kcNmqgtP1Sy8NWakLNFyuIePPaXeU5poDc1xOM9eerIINU/3+N+GYzd4ROEkZCIt6a7iku1gg1zUu6TZTUSPbFQ/e6ak4ZZYL7UxS098F8IrcVBTghstc9XvZj9H2ck3+gMag=","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; \n\ts=zohoarc; t=1765967347;\n\th=Content-Type:Content-Transfer-Encoding:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To:Cc;\n\tbh=3qduRJG3OGAuPe5W9jdOETEJcdqDAgJi+Vxt2M1z/Aw=; \n\tb=ja3ccFPjO6zTa0c8v1X4C0bIKtlyIZU7PlfMjxGe2voLqPCYWUEBPCisl7sPxcHkguVh44KEOuimXirOFZuQgs3QONtsauPtlkeWswHUZDh6mftX1cfSc38weHFO1+8It2F0CrRZfFr2XCIshh87+0IKWBueOnBQy2m4MLmr+4g=","ARC-Authentication-Results":"i=1; mx.zohomail.com;\n\tdkim=pass  header.i=collabora.com;\n\tspf=pass  smtp.mailfrom=robert.mader@collabora.com;\n\tdmarc=pass header.from=<robert.mader@collabora.com>","DKIM-Signature":"v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1765967347;\n\ts=zohomail; d=collabora.com; i=robert.mader@collabora.com;\n\th=Message-ID:Date:Date:MIME-Version:Subject:Subject:To:To:References:From:From:In-Reply-To:Content-Type:Content-Transfer-Encoding:Message-Id:Reply-To:Cc;\n\tbh=3qduRJG3OGAuPe5W9jdOETEJcdqDAgJi+Vxt2M1z/Aw=;\n\tb=TqV8KvxtFrw2OqylxtPAB24Q8YQE4LIlIo7ctWajlwMfl6gUb6vlUh7ugLXxnPkb\n\thV3cHcXr9GvJxUpW01Cl3XfQptuWPqzatmWf7gYOOtw6/lXOJI+hYKrqj+tHK2GjQh8\n\tmj5659+yKiugCSE5A4Syo6FD309+UtciPBdWOtIU=","Message-ID":"<6c10eea7-a460-4ac9-8596-123be691d7f9@collabora.com>","Date":"Wed, 17 Dec 2025 11:29:02 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v9 02/26] libcamera: software_isp: egl: Add a eGL base\n\thelper class","To":"libcamera-devel@lists.libcamera.org","References":"<20251217100138.82525-1-bryan.odonoghue@linaro.org>\n\t<20251217100138.82525-3-bryan.odonoghue@linaro.org>","Content-Language":"en-US, de-DE","From":"Robert Mader <robert.mader@collabora.com>","In-Reply-To":"<20251217100138.82525-3-bryan.odonoghue@linaro.org>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","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":37425,"web_url":"https://patchwork.libcamera.org/comment/37425/","msgid":"<176597223925.3401191.15862807731593663211@ping.linuxembedded.co.uk>","date":"2025-12-17T11:50:39","subject":"Re: [PATCH v9 02/26] libcamera: software_isp: egl: Add a eGL base\n\thelper class","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Hi Bryan,\n\nQuoting Bryan O'Donoghue (2025-12-17 10:01:14)\n> Introduce an eGL base helper class which provides an eGL context based on a\n> passed width and height.\n> \n> The initGLContext function could be overloaded to provide an interface to a\n> real display.\n> \n> A set of helper functions is provided to compile and link GLSL shaders.\n> linkShaderProgram currently compiles vertex/fragment pairs but could be\n> overloaded or passed a parameter to link a compute shader instead.\n> \n> Breaking the eGL interface away from debayering - allows to use the eGL\n> context inside of a dma-buf heap cleanly, reuse that context inside of a\n> debayer layer and conceivably reuse the context in a multi-stage shader\n> pass.\n> \n> Small note the image_attrs[] array doesn't pass checkstyle.py however the\n> elements of the array are in pairs.\n> \n> Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> [bod: Takes fix from Hans for constructor stride bpp]\n> [bod: Drops eglClientWaitSync in favour of glFinish Robert/Milan]\n> Co-developed-by: Hans de Goede <johannes.goede@oss.qualcomm.com>\n> Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>\n> Co-developed-by: Milan Zamazal <mzamazal@redhat.com>\n> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>\n> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> ---\n>  include/libcamera/internal/egl.h | 185 ++++++++++\n\nThe linter spotted something here:\n\n--------------------------------------------------------------------------------------------------\ne178c47f2d66b92005dcd2b44cb7e8e12bbbdb34 libcamera: software_isp: egl: Add a eGL base helper class\n--------------------------------------------------------------------------------------------------\nHeader include/libcamera/internal/egl.h added without corresponding update to include/libcamera/internal/meson.build\n\n\nPlease make sure you add egl.h to\ninclude/libcamera/internal/meson.build.\n\nThat's important as it ties in to the meson dependencies.\n\nWith that,\n\n\nAcked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n--\nKieran\n\n\n>  src/libcamera/egl.cpp            | 612 +++++++++++++++++++++++++++++++\n>  src/libcamera/meson.build        |  23 ++\n\n\n\n>  3 files changed, 820 insertions(+)\n>  create mode 100644 include/libcamera/internal/egl.h\n>  create mode 100644 src/libcamera/egl.cpp\n> \n> diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h\n> new file mode 100644\n> index 000000000..1ed93f3dc\n> --- /dev/null\n> +++ b/include/libcamera/internal/egl.h\n> @@ -0,0 +1,185 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Linaro Ltd.\n> + *\n> + * Authors:\n> + * Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> + *\n> + */\n> +\n> +#pragma once\n> +\n> +#include <sys/types.h>\n> +#include <unistd.h>\n> +\n> +#include <libcamera/base/log.h>\n> +#include <libcamera/base/span.h>\n> +#include <libcamera/base/utils.h>\n> +\n> +#include \"libcamera/internal/gbm.h\"\n> +\n> +#define EGL_EGLEXT_PROTOTYPES\n> +#include <EGL/egl.h>\n> +#include <EGL/eglext.h>\n> +#define GL_GLEXT_PROTOTYPES\n> +#include <GLES2/gl2.h>\n> +#include <GLES2/gl2ext.h>\n> +\n> +namespace libcamera {\n> +\n> +LOG_DECLARE_CATEGORY(eGL)\n> +\n> +/**\n> + * \\class eGLImage\n> + * \\brief Helper class for managing EGL image resources\n> + *\n> + * The eGLImage class encapsulates OpenGL ES texture and framebuffer objects\n> + * along with their associated EGL image. It aggregates handles, descriptors,\n> + * and routines for managing textures that can be associated with shader\n> + * uniform IDs.\n> + *\n> + * This class is particularly useful for managing DMA-BUF backed textures\n> + * in zero-copy rendering pipelines, where textures are bound to specific\n> + * texture units and can be used as both input textures and render targets.\n> + */\n> +class eGLImage\n> +{\n> +public:\n> +       /**\n> +        * \\brief Construct an eGLImage with explicit stride\n> +        * \\param[in] width Image width in pixels\n> +        * \\param[in] height Image height in pixels\n> +        * \\param[in] bpp Bytes per pixel\n> +        * \\param[in] stride Row stride in bytes\n> +        * \\param[in] texture_unit OpenGL texture unit (e.g., GL_TEXTURE0)\n> +        * \\param[in] texture_unit_uniform_id Shader uniform ID for this texture unit\n> +        *\n> +        * Creates an eGLImage with the specified dimensions and stride. The stride\n> +        * may differ from width * bpp due to alignment.\n> +        */\n> +       eGLImage(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t texture_unit_uniform_id)\n> +       {\n> +               init(width, height, bpp, stride, texture_unit, texture_unit_uniform_id);\n> +       }\n> +       /**\n> +        * \\brief Construct an eGLImage with automatic stride calculation\n> +        * \\param[in] width Image width in pixels\n> +        * \\param[in] height Image height in pixels\n> +        * \\param[in] bpp Bytes per pixel\n> +        * \\param[in] texture_unit OpenGL texture unit (e.g., GL_TEXTURE0)\n> +        * \\param[in] texture_unit_uniform_id Shader uniform ID for this texture unit\n> +        *\n> +        * Creates an eGLImage with automatic stride calculation. The stride is\n> +        * aligned to 256 bytes because 256 byte alignment is a common baseline alignment for GPUs.\n> +        */\n> +       eGLImage(uint32_t width, uint32_t height, uint32_t bpp, GLenum texture_unit, uint32_t texture_unit_uniform_id)\n> +       {\n> +               uint32_t stride = libcamera::utils::alignUp(width * bpp / 8, 256);\n> +\n> +               init(width, height, bpp, stride, texture_unit, texture_unit_uniform_id);\n> +       }\n> +\n> +       /**\n> +        * \\brief Destroy the eGLImage\n> +        *\n> +        * Cleans up OpenGL resources by deleting the framebuffer object and\n> +        * texture.\n> +        */\n> +       ~eGLImage()\n> +       {\n> +               glDeleteFramebuffers(1, &fbo_);\n> +               glDeleteTextures(1, &texture_);\n> +       }\n> +\n> +       uint32_t width_; /**< Image width in pixels */\n> +       uint32_t height_; /**< Image height in pixels */\n> +       uint32_t stride_; /**< Row stride in bytes */\n> +       uint32_t offset_; /**< Buffer offset (reserved for future use) */\n> +       uint32_t framesize_; /**< Total frame size in bytes (stride * height) */\n> +       uint32_t bpp_; /**< Bytes per pixel */\n> +       uint32_t texture_unit_uniform_id_; /**< Shader uniform id for texture unit */\n> +       GLenum texture_unit_; /**< Texture unit associated with this image eg (GL_TEXTURE0) */\n> +       GLuint texture_; /**< OpenGL texture object ID */\n> +       GLuint fbo_; /**< OpenGL frame buffer object ID */\n> +       EGLImageKHR image_; /**< EGL Image handle */\n> +\n> +private:\n> +       LIBCAMERA_DISABLE_COPY_AND_MOVE(eGLImage)\n> +\n> +       /**\n> +        * \\brief Initialise eGLImage state\n> +        * \\param[in] width Image width in pixels\n> +        * \\param[in] height Image height in pixels\n> +        * \\param[in] bpp Bytes per pixel\n> +        * \\param[in] stride Row stride in bytes\n> +        * \\param[in] texture_unit OpenGL texture unit\n> +        * \\param[in] texture_unit_uniform_id Shader uniform ID\n> +        *\n> +        * Common initialisation routine called by both constructors. Sets up\n> +        * member variables and generates OpenGL texture and framebuffer objects.\n> +        */\n> +       void init(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t texture_unit_uniform_id)\n> +       {\n> +               image_ = EGL_NO_IMAGE_KHR;\n> +               width_ = width;\n> +               height_ = height;\n> +               bpp_ = bpp;\n> +               stride_ = stride;\n> +               framesize_ = stride_ * height_;\n> +               texture_unit_ = texture_unit;\n> +               texture_unit_uniform_id_ = texture_unit_uniform_id;\n> +\n> +               glGenTextures(1, &texture_);\n> +               glGenFramebuffers(1, &fbo_);\n> +       }\n> +};\n> +\n> +class eGL\n> +{\n> +public:\n> +       eGL();\n> +       ~eGL();\n> +\n> +       int initEGLContext(GBM *gbmContext);\n> +       void cleanUp();\n> +\n> +       int createInputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd);\n> +       int createOutputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd);\n> +       void destroyDMABufTexture(std::unique_ptr<eGLImage> &eglImage);\n> +       void createTexture2D(std::unique_ptr<eGLImage> &eglImage, GLint format, uint32_t width, uint32_t height, void *data);\n> +\n> +       void pushEnv(std::vector<std::string> &shaderEnv, const char *str);\n> +       void makeCurrent();\n> +\n> +       int compileVertexShader(GLuint &shaderId, const unsigned char *shaderData,\n> +                               unsigned int shaderDataLen,\n> +                               Span<const std::string> shaderEnv);\n> +       int compileFragmentShader(GLuint &shaderId, const unsigned char *shaderData,\n> +                                 unsigned int shaderDataLen,\n> +                                 Span<const std::string> shaderEnv);\n> +       int linkProgram(GLuint &programId, GLuint fragmentshaderId, GLuint vertexshaderId);\n> +       void dumpShaderSource(GLuint shaderId);\n> +       void useProgram(GLuint programId);\n> +       void deleteProgram(GLuint programId);\n> +       void syncOutput();\n> +\n> +private:\n> +       LIBCAMERA_DISABLE_COPY_AND_MOVE(eGL)\n> +\n> +       pid_t tid_;\n> +\n> +       EGLDisplay display_;\n> +       EGLContext context_;\n> +       EGLSurface surface_;\n> +\n> +       int compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData,\n> +                         unsigned int shaderDataLen,\n> +                         Span<const std::string> shaderEnv);\n> +\n> +       int createDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd, bool output);\n> +\n> +       PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;\n> +       PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;\n> +       PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;\n> +};\n> +} //namespace libcamera\n> diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp\n> new file mode 100644\n> index 000000000..c6fde5e42\n> --- /dev/null\n> +++ b/src/libcamera/egl.cpp\n> @@ -0,0 +1,612 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Linaro Ltd.\n> + *\n> + * Authors:\n> + * Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> + *\n> + */\n> +\n> +#include \"libcamera/internal/egl.h\"\n> +\n> +#include <fcntl.h>\n> +#include <sys/ioctl.h>\n> +#include <sys/mman.h>\n> +#include <unistd.h>\n> +\n> +#include <linux/dma-buf.h>\n> +#include <linux/dma-heap.h>\n> +\n> +#include <libcamera/base/thread.h>\n> +\n> +#include <libdrm/drm_fourcc.h>\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(eGL)\n> +\n> +/**\n> + * \\class eGL\n> + * \\brief Helper class for managing OpenGL ES operations\n> + *\n> + * It provides:\n> + *\n> + * - EGL context setup and management\n> + * - Extension function pointer retrieval\n> + * - Shader compilation and program linking\n> + * - DMA-BUF texture creation and management\n> + * - Synchronisation primitives\n> + *\n> + * This class is designed to work with zero-copy buffers via DMA-BUF file\n> + * descriptors.\n> + */\n> +\n> +/**\n> + *\\var eGL::tid_\n> + *\\brief Thread ID of the thread associated with this EGL context\n> + */\n> +\n> +/**\n> + *\\var eGL::display_\n> + *\\brief EGL display handle\n> + */\n> +\n> +/**\n> + *\\var eGL::context_\n> + *\\brief EGL context handle\n> + */\n> +\n> +/**\n> + *\\var eGL::surface_\n> + *\\brief EGL sufrace handle\n> + */\n> +\n> +/**\n> + * \\brief Construct an EGL helper\n> + *\n> + * Creates an eGL instance with uninitialised context. Call initEGLContext()\n> + * to set up the EGL display, context, and load extension functions.\n> + */\n> +eGL::eGL()\n> +{\n> +       context_ = EGL_NO_CONTEXT;\n> +       surface_ = EGL_NO_SURFACE;\n> +       display_ = EGL_NO_DISPLAY;\n> +}\n> +\n> +/**\n> + * \\brief Destroy the EGL helper\n> + *\n> + * Destroys the EGL context and surface if they were successfully created.\n> + */\n> +eGL::~eGL()\n> +{\n> +       if (context_ != EGL_NO_CONTEXT)\n> +               eglDestroyContext(display_, context_);\n> +\n> +       if (surface_ != EGL_NO_SURFACE)\n> +               eglDestroySurface(display_, surface_);\n> +}\n> +\n> +/**\n> + * \\brief Synchronise rendering output\n> + *\n> + * Sychronise here. Calls glFinish() right now.\n> + *\n> + */\n> +void eGL::syncOutput()\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       glFinish();\n> +}\n> +\n> +/**\n> + * \\brief Create a DMA-BUF backed 2D texture\n> + * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n> + * \\param[in] fd DMA-BUF file descriptor\n> + * \\param[in] output If true, create framebuffer for render target\n> + *\n> + * Internal implementation for creating DMA-BUF textures. Creates an EGL\n> + * image from the DMA-BUF and binds it to a 2D texture. If output is true,\n> + * also creates and attaches a framebuffer object.\n> + *\n> + * \\return 0 on success, or -ENODEV on failure\n> + */\n> +int eGL::createDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd, bool output)\n> +{\n> +       int ret = 0;\n> +\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       // clang-format off\n> +       EGLint image_attrs[] = {\n> +               EGL_WIDTH, (EGLint)eglImage->width_,\n> +               EGL_HEIGHT, (EGLint)eglImage->height_,\n> +               EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_ARGB8888,\n> +               EGL_DMA_BUF_PLANE0_FD_EXT, fd,\n> +               EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,\n> +               EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)eglImage->stride_,\n> +               EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, 0,\n> +               EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, 0,\n> +               EGL_NONE,\n> +       };\n> +       // clang-format on\n> +\n> +       eglImage->image_ = eglCreateImageKHR(display_, EGL_NO_CONTEXT,\n> +                                            EGL_LINUX_DMA_BUF_EXT,\n> +                                            NULL, image_attrs);\n> +\n> +       if (eglImage->image_ == EGL_NO_IMAGE_KHR) {\n> +               LOG(eGL, Error) << \"eglCreateImageKHR fail\";\n> +               ret = -ENODEV;\n> +               goto done;\n> +       }\n> +\n> +       // Bind texture unit and texture\n> +       glActiveTexture(eglImage->texture_unit_);\n> +       glBindTexture(GL_TEXTURE_2D, eglImage->texture_);\n> +\n> +       // Generate texture with filter semantics\n> +       glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage->image_);\n> +\n> +       // Nearest filtering\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n> +\n> +       // Wrap to edge to avoid edge artifacts\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n> +\n> +       if (output) {\n> +               // Generate a framebuffer from our texture direct to dma-buf handle buffer\n> +               glBindFramebuffer(GL_FRAMEBUFFER, eglImage->fbo_);\n> +               glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, eglImage->texture_, 0);\n> +\n> +               GLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n> +               if (err != GL_FRAMEBUFFER_COMPLETE) {\n> +                       LOG(eGL, Error) << \"glFrameBufferTexture2D error \" << err;\n> +                       eglDestroyImageKHR(display_, eglImage->image_);\n> +                       ret = -ENODEV;\n> +                       goto done;\n> +               }\n> +       }\n> +done:\n> +       return ret;\n> +}\n> +\n> +/**\n> + * \\brief Create an input DMA-BUF backed texture\n> + * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n> + * \\param[in] fd DMA-BUF file descriptor\n> + *\n> + * Creates an EGL image from a DMA-BUF file descriptor and binds it to\n> + * a 2D texture for use as an input texture in shaders. The texture is\n> + * configured with nearest filtering and clamp-to-edge wrapping.\n> + *\n> + * \\return 0 on success, or -ENODEV on failure\n> + */\n> +int eGL::createInputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd)\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       return createDMABufTexture2D(eglImage, fd, false);\n> +}\n> +\n> +/**\n> + * \\brief Create an output DMA-BUF backed texture\n> + * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n> + * \\param[in] fd DMA-BUF file descriptor\n> + *\n> + * Creates an EGL image from a DMA-BUF file descriptor and binds it to\n> + * a 2D texture, then attaches it to a framebuffer object for use as a\n> + * render target. This enables zero-copy rendering directly to the\n> + * DMA-BUF.\n> + *\n> + * \\return 0 on success, or -ENODEV on failure\n> + */\n> +int eGL::createOutputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd)\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       return createDMABufTexture2D(eglImage, fd, true);\n> +}\n> +\n> +/**\n> + * \\brief Destroy a DMA-BUF texture's EGL image\n> + * \\param[in,out] eglImage EGL image to destroy\n> + *\n> + * Destroys the EGL image associated with a DMA-BUF texture. The OpenGL\n> + * texture and framebuffer objects are destroyed separately in the\n> + * eGLImage destructor.\n> + */\n> +void eGL::destroyDMABufTexture(std::unique_ptr<eGLImage> &eglImage)\n> +{\n> +       eglDestroyImage(display_, eglImage->image_);\n> +}\n> +\n> +/**\n> + * \\brief Create a 2D texture from a memory buffer\n> + * \\param[in,out] eglImage EGL image to associate with the texture\n> + * \\param[in] format OpenGL internal format (e.g., GL_RGB, GL_RGBA)\n> + * \\param[in] width Texture width in pixels\n> + * \\param[in] height Texture height in pixels\n> + * \\param[in] data Pointer to pixel data, or nullptr for uninitialised texture\n> + *\n> + * Creates a 2D texture from a CPU-accessible memory buffer. The texture\n> + * is configured with nearest filtering and clamp-to-edge wrapping. This\n> + * is useful for uploading static data like lookup tables or uniform color\n> + * matrices to the GPU.\n> + */\n> +void eGL::createTexture2D(std::unique_ptr<eGLImage> &eglImage, GLint format, uint32_t width, uint32_t height, void *data)\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       glActiveTexture(eglImage->texture_unit_);\n> +       glBindTexture(GL_TEXTURE_2D, eglImage->texture_);\n> +\n> +       // Generate texture, bind, associate image to texture, configure, unbind\n> +       glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);\n> +\n> +       // Nearest filtering\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n> +\n> +       // Wrap to edge to avoid edge artifacts\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n> +}\n> +\n> +/**\n> + * \\brief Initialise the EGL context\n> + * \\param[in] gbmContext Pointer to initialised GBM context\n> + *\n> + * Sets up the EGL display from the GBM device, creates an OpenGL ES 2.0\n> + * context, and retrieves function pointers for required extensions\n> + * including:\n> + * - eglCreateImageKHR / eglDestroyImageKHR\n> + * - glEGLImageTargetTexture2DOES\n> + *\n> + * \\return 0 on success, or -ENODEV on failure\n> + */\n> +int eGL::initEGLContext(GBM *gbmContext)\n> +{\n> +       EGLint configAttribs[] = {\n> +               EGL_RED_SIZE, 8,\n> +               EGL_GREEN_SIZE, 8,\n> +               EGL_BLUE_SIZE, 8,\n> +               EGL_ALPHA_SIZE, 8,\n> +               EGL_SURFACE_TYPE, EGL_WINDOW_BIT,\n> +               EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,\n> +               EGL_NONE\n> +       };\n> +\n> +       EGLint contextAttribs[] = {\n> +               EGL_CONTEXT_MAJOR_VERSION, 2,\n> +               EGL_NONE\n> +       };\n> +\n> +       EGLint numConfigs;\n> +       EGLConfig config;\n> +       EGLint major;\n> +       EGLint minor;\n> +\n> +       if (!eglBindAPI(EGL_OPENGL_ES_API)) {\n> +               LOG(eGL, Error) << \"API bind fail\";\n> +               goto fail;\n> +       }\n> +\n> +       display_ = eglGetDisplay(gbmContext->getDevice());\n> +       if (display_ == EGL_NO_DISPLAY) {\n> +               LOG(eGL, Error) << \"Unable to get EGL display\";\n> +               goto fail;\n> +       }\n> +\n> +       if (eglInitialize(display_, &major, &minor) != EGL_TRUE) {\n> +               LOG(eGL, Error) << \"eglInitialize fail\";\n> +               goto fail;\n> +       }\n> +\n> +       LOG(eGL, Info) << \"EGL: version \" << major << \".\" << minor;\n> +       LOG(eGL, Info) << \"EGL: EGL_VERSION: \" << eglQueryString(display_, EGL_VERSION);\n> +       LOG(eGL, Info) << \"EGL: EGL_VENDOR: \" << eglQueryString(display_, EGL_VENDOR);\n> +       LOG(eGL, Info) << \"EGL: EGL_CLIENT_APIS: \" << eglQueryString(display_, EGL_CLIENT_APIS);\n> +       LOG(eGL, Info) << \"EGL: EGL_EXTENSIONS: \" << eglQueryString(display_, EGL_EXTENSIONS);\n> +\n> +       eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress(\"eglCreateImageKHR\");\n> +       if (!eglCreateImageKHR) {\n> +               LOG(eGL, Error) << \"eglCreateImageKHR not found\";\n> +               goto fail;\n> +       }\n> +\n> +       eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress(\"eglDestroyImageKHR\");\n> +       if (!eglDestroyImageKHR) {\n> +               LOG(eGL, Error) << \"eglDestroyImageKHR not found\";\n> +               goto fail;\n> +       }\n> +\n> +       glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress(\"glEGLImageTargetTexture2DOES\");\n> +       if (!glEGLImageTargetTexture2DOES) {\n> +               LOG(eGL, Error) << \"glEGLImageTargetTexture2DOES not found\";\n> +               goto fail;\n> +       }\n> +\n> +       if (eglChooseConfig(display_, configAttribs, &config, 1, &numConfigs) != EGL_TRUE) {\n> +               LOG(eGL, Error) << \"eglChooseConfig fail\";\n> +               goto fail;\n> +       }\n> +\n> +       context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT, contextAttribs);\n> +       if (context_ == EGL_NO_CONTEXT) {\n> +               LOG(eGL, Error) << \"eglContext returned EGL_NO_CONTEXT\";\n> +               goto fail;\n> +       }\n> +\n> +       tid_ = Thread::currentId();\n> +\n> +       makeCurrent();\n> +\n> +       return 0;\n> +fail:\n> +\n> +       return -ENODEV;\n> +}\n> +\n> +/**\n> + * \\brief Clean up EGL resources\n> + *\n> + * Destroys the EGL sync object. Must be called from the same thread\n> + * that created the EGL context.\n> + */\n> +void eGL::cleanUp()\n> +{\n> +}\n> +\n> +/**\n> + * \\brief Make the EGL context current for the calling thread\n> + *\n> + * Binds the EGL context to the current thread, allowing OpenGL ES\n> + * operations to be performed. Must be called from the thread that\n> + * will perform rendering operations.\n> + */\n> +void eGL::makeCurrent()\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       if (eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, context_) != EGL_TRUE) {\n> +               LOG(eGL, Error) << \"eglMakeCurrent fail\";\n> +       }\n> +}\n> +\n> +/**\n> + * \\brief Activate a shader program for rendering\n> + * \\param[in] programId OpenGL program object ID\n> + *\n> + * Sets the specified program as the current rendering program. All\n> + * subsequent draw calls will use this program's shaders.\n> + */\n> +void eGL::useProgram(GLuint programId)\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       glUseProgram(programId);\n> +}\n> +\n> +/**\n> + * \\brief Delete a shader program\n> + * \\param[in] programId OpenGL program object ID\n> + *\n> + * Deletes a shader program and frees associated resources. The program\n> + * must not be currently in use.\n> + */\n> +void eGL::deleteProgram(GLuint programId)\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       glDeleteProgram(programId);\n> +}\n> +\n> +/**\n> + * \\brief Add a preprocessor definition to shader environment\n> + * \\param[in,out] shaderEnv Vector of shader environment strings\n> + * \\param[in] str Preprocessor definition string (e.g., \"#define APPLY_RGB_PARAMETERS\")\n> + *\n> + * Appends a preprocessor definition to the shader environment vector.\n> + * These definitions are prepended to shader source code during compilation.\n> + */\n> +void eGL::pushEnv(std::vector<std::string> &shaderEnv, const char *str)\n> +{\n> +       std::string addStr = str;\n> +\n> +       addStr.push_back('\\n');\n> +       shaderEnv.push_back(std::move(addStr));\n> +}\n> +\n> +/**\n> + * \\brief Compile a vertex shader\n> + * \\param[out] shaderId OpenGL shader object ID\n> + * \\param[in] shaderData Pointer to shader source code\n> + * \\param[in] shaderDataLen Length of shader source in bytes\n> + * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n> + *\n> + * Compiles a vertex shader from source code with optional preprocessor\n> + * definitions. On compilation failure, logs the shader info log.\n> + *\n> + * \\return 0 on success, or -EINVAL on compilation failure\n> + */\n> +int eGL::compileVertexShader(GLuint &shaderId, const unsigned char *shaderData,\n> +                            unsigned int shaderDataLen,\n> +                            Span<const std::string> shaderEnv)\n> +{\n> +       return compileShader(GL_VERTEX_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv);\n> +}\n> +\n> +/**\n> + * \\brief Compile a fragment shader\n> + * \\param[out] shaderId OpenGL shader object ID\n> + * \\param[in] shaderData Pointer to shader source code\n> + * \\param[in] shaderDataLen Length of shader source in bytes\n> + * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n> + *\n> + * Compiles a fragment shader from source code with optional preprocessor\n> + * definitions. On compilation failure, logs the shader info log.\n> + *\n> + * \\return 0 on success, or -EINVAL on compilation failure\n> + */\n> +int eGL::compileFragmentShader(GLuint &shaderId, const unsigned char *shaderData,\n> +                              unsigned int shaderDataLen,\n> +                              Span<const std::string> shaderEnv)\n> +{\n> +       return compileShader(GL_FRAGMENT_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv);\n> +}\n> +\n> +/**\n> + * \\brief Compile a shader of specified type\n> + * \\param[in] shaderType GL_VERTEX_SHADER or GL_FRAGMENT_SHADER\n> + * \\param[out] shaderId OpenGL shader object ID\n> + * \\param[in] shaderData Pointer to shader source code\n> + * \\param[in] shaderDataLen Length of shader source in bytes\n> + * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n> + *\n> + * Internal helper function for shader compilation. Prepends environment\n> + * definitions to the shader source and compiles the shader.\n> + *\n> + * \\return 0 on success, or -EINVAL on compilation failure\n> + */\n> +int eGL::compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData,\n> +                      unsigned int shaderDataLen,\n> +                      Span<const std::string> shaderEnv)\n> +{\n> +       GLint success;\n> +       size_t i;\n> +\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       auto count = 1 + shaderEnv.size();\n> +       auto shaderSourceData = std::make_unique<const GLchar *[]>(count);\n> +       auto shaderDataLengths = std::make_unique<GLint[]>(count);\n> +\n> +       // Prefix defines before main body of shader\n> +       for (i = 0; i < shaderEnv.size(); i++) {\n> +               shaderSourceData[i] = shaderEnv[i].c_str();\n> +               shaderDataLengths[i] = shaderEnv[i].length();\n> +       }\n> +\n> +       // Now the main body of the shader program\n> +       shaderSourceData[i] = reinterpret_cast<const GLchar *>(shaderData);\n> +       shaderDataLengths[i] = shaderDataLen;\n> +\n> +       // And create the shader\n> +       shaderId = glCreateShader(shaderType);\n> +       glShaderSource(shaderId, count, shaderSourceData.get(), shaderDataLengths.get());\n> +       glCompileShader(shaderId);\n> +\n> +       // Check status\n> +       glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success);\n> +       if (success == GL_FALSE) {\n> +               GLint sizeLog = 0;\n> +\n> +               glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &sizeLog);\n> +               auto infoLog = std::make_unique<GLchar[]>(sizeLog);\n> +\n> +               glGetShaderInfoLog(shaderId, sizeLog, &sizeLog, infoLog.get());\n> +               LOG(eGL, Error) << infoLog.get();\n> +       }\n> +\n> +       return (success == GL_TRUE) ? 0 : -EINVAL;\n> +}\n> +\n> +/**\n> + * \\brief Dump shader source code to the log\n> + * \\param[in] shaderId OpenGL shader object ID\n> + *\n> + * Retrieves and logs the complete source code of a compiled shader.\n> + * Useful for debugging shader compilation issues.\n> + */\n> +void eGL::dumpShaderSource(GLuint shaderId)\n> +{\n> +       GLint shaderLength = 0;\n> +\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       glGetShaderiv(shaderId, GL_SHADER_SOURCE_LENGTH, &shaderLength);\n> +\n> +       LOG(eGL, Debug) << \"Shader length is \" << shaderLength;\n> +\n> +       if (shaderLength > 0) {\n> +               auto shaderSource = std::make_unique<GLchar[]>(shaderLength);\n> +\n> +               glGetShaderSource(shaderId, shaderLength, &shaderLength, shaderSource.get());\n> +               if (shaderLength) {\n> +                       LOG(eGL, Debug) << \"Shader source = \" << shaderSource.get();\n> +               }\n> +       }\n> +}\n> +\n> +/**\n> + * \\brief Link a shader program\n> + * \\param[out] programId OpenGL program object ID\n> + * \\param[in] fragmentshaderId Compiled fragment shader ID\n> + * \\param[in] vertexshaderId Compiled vertex shader ID\n> + *\n> + * Links vertex and fragment shaders into an executable shader program.\n> + * On link failure, logs the program info log and deletes the program.\n> + *\n> + * \\return 0 on success, or -ENODEV on link failure\n> + */\n> +int eGL::linkProgram(GLuint &programId, GLuint vertexshaderId, GLuint fragmentshaderId)\n> +{\n> +       GLint success;\n> +       GLenum err;\n> +\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       programId = glCreateProgram();\n> +       if (!programId)\n> +               goto fail;\n> +\n> +       glAttachShader(programId, vertexshaderId);\n> +       if ((err = glGetError()) != GL_NO_ERROR) {\n> +               LOG(eGL, Error) << \"Attach compute vertex shader fail\";\n> +               goto fail;\n> +       }\n> +\n> +       glAttachShader(programId, fragmentshaderId);\n> +       if ((err = glGetError()) != GL_NO_ERROR) {\n> +               LOG(eGL, Error) << \"Attach compute fragment shader fail\";\n> +               goto fail;\n> +       }\n> +\n> +       glLinkProgram(programId);\n> +       if ((err = glGetError()) != GL_NO_ERROR) {\n> +               LOG(eGL, Error) << \"Link program fail\";\n> +               goto fail;\n> +       }\n> +\n> +       glDetachShader(programId, fragmentshaderId);\n> +       glDetachShader(programId, vertexshaderId);\n> +\n> +       // Check status\n> +       glGetProgramiv(programId, GL_LINK_STATUS, &success);\n> +       if (success == GL_FALSE) {\n> +               GLint sizeLog = 0;\n> +               GLchar *infoLog;\n> +\n> +               glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &sizeLog);\n> +               infoLog = new GLchar[sizeLog];\n> +\n> +               glGetProgramInfoLog(programId, sizeLog, &sizeLog, infoLog);\n> +               LOG(eGL, Error) << infoLog;\n> +\n> +               delete[] infoLog;\n> +               goto fail;\n> +       }\n> +\n> +       return 0;\n> +fail:\n> +       if (programId)\n> +               glDeleteProgram(programId);\n> +\n> +       return -ENODEV;\n> +}\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index e5b5330a8..60f036da4 100644\n> --- a/src/libcamera/meson.build\n> +++ b/src/libcamera/meson.build\n> @@ -79,6 +79,27 @@ if libgbm.found() and gbm_works\n>      ])\n>  endif\n>  \n> +libegl = cc.find_library('EGL', required : false)\n> +libglesv2 = cc.find_library('GLESv2', required : false)\n> +mesa_works = cc.check_header('EGL/egl.h', required: false)\n> +\n> +if libegl.found() and mesa_works\n> +    config_h.set('HAVE_LIBEGL', 1)\n> +endif\n> +\n> +if libglesv2.found() and mesa_works\n> +    config_h.set('HAVE_GLESV2', 1)\n> +endif\n> +\n> +if mesa_works and gbm_works\n> +    libcamera_internal_sources += files([\n> +        'egl.cpp',\n> +    ])\n> +    gles_headless_enabled = true\n> +else\n> +    gles_headless_enabled = false\n> +endif\n> +\n>  subdir('base')\n>  subdir('converter')\n>  subdir('ipa')\n> @@ -187,7 +208,9 @@ libcamera_deps += [\n>      libcamera_base_private,\n>      libcrypto,\n>      libdl,\n> +    libegl,\n>      libgbm,\n> +    libglesv2,\n>      liblttng,\n>      libudev,\n>      libyaml,\n> -- \n> 2.52.0\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 6E68AC3257\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 17 Dec 2025 11:50:45 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7862461A60;\n\tWed, 17 Dec 2025 12:50:44 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7212E606D5\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 17 Dec 2025 12:50:42 +0100 (CET)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id B647AB3;\n\tWed, 17 Dec 2025 12:50:35 +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=\"a5QTyC4a\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1765972235;\n\tbh=GfAf5zAt345h9G/sWHdpmsuJRlzLl75LYb38xz3Rf1g=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=a5QTyC4avb182FhLbHUgcY2aO7e0fPp5ZQddn35RAGGCAE6Mna8pSr8mymQ4/uUjQ\n\tFHdUUyNuwO4ppCpHoFejOEQD6wIqeogjrzrVHGby5+DyUfX94fak156rdrl4XZO4s4\n\ttsQRRiJdVMpFBUYbdCTblJCJuiDpF8x40rhPyIeM=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20251217100138.82525-3-bryan.odonoghue@linaro.org>","References":"<20251217100138.82525-1-bryan.odonoghue@linaro.org>\n\t<20251217100138.82525-3-bryan.odonoghue@linaro.org>","Subject":"Re: [PATCH v9 02/26] libcamera: software_isp: egl: Add a eGL base\n\thelper class","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"pavel@ucw.cz, Bryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tHans de Goede <johannes.goede@oss.qualcomm.com>,\n\tMilan Zamazal <mzamazal@redhat.com>","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Wed, 17 Dec 2025 11:50:39 +0000","Message-ID":"<176597223925.3401191.15862807731593663211@ping.linuxembedded.co.uk>","User-Agent":"alot/0.9.1","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]