[{"id":37069,"web_url":"https://patchwork.libcamera.org/comment/37069/","msgid":"<85ikexstor.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2025-11-25T21:12:20","subject":"Re: [PATCH v4 03/23] libcamera: software_isp: egl: Add a eGL base\n\thelper class","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Hi Bryan,\n\nBryan O'Donoghue <bryan.odonoghue@linaro.org> writes:\n\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> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> ---\n>  include/libcamera/internal/egl.h | 412 +++++++++++++++++++++++++++++\n>  src/libcamera/egl.cpp            | 436 +++++++++++++++++++++++++++++++\n>  src/libcamera/meson.build        |  23 ++\n>  3 files changed, 871 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..e61a394af\n> --- /dev/null\n> +++ b/include/libcamera/internal/egl.h\n> @@ -0,0 +1,412 @@\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> + * egl_context.cpp - Helper class for managing eGL interactions.\n> + */\n> +\n> +#pragma once\n> +\n> +#include <sys/types.h>\n> +#include <unistd.h>\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include \"libcamera/base/utils.h\"\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> +\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, 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_;\t\t\t/**< Image width in pixels */\n> +\tuint32_t height_;\t\t\t/**< Image height in pixels */\n> +\tuint32_t stride_;\t\t\t/**< Row stride in bytes */\n> +\tuint32_t offset_;\t\t\t/**< Buffer offset (reserved for future use) */\n> +\tuint32_t framesize_;\t\t\t/**< Total frame size in bytes (stride * height) */\n> +\tuint32_t bpp_;\t\t\t\t/**< Bytes per pixel */\n> +\tuint32_t texture_unit_uniform_id_;\t/**< Shader uniform id for texture unit */\n> +\tGLenum texture_unit_;\t\t\t/**< Texture unit associated with this image eg (GL_TEXTURE0) */\n> +\tGLuint texture_;\t\t\t/**< OpenGL texture object ID */\n> +\tGLuint fbo_;\t\t\t\t/**< OpenGL frame buffer object ID */\n> +\tEGLImageKHR image_;\t\t\t/**< 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> +/**\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> +class eGL\n> +{\n> +public:\n> +\t/**\n> +\t * \\brief Construct an EGL helper\n> +\t *\n> +\t * Creates an eGL instance with uninitialised context. Call initEGLContext()\n> +\t * to set up the EGL display, context, and load extension functions.\n> +\t */\n> +\teGL();\n> +\n> +\t/**\n> +\t * \\brief Destroy the EGL helper\n> +\t *\n> +\t * Destroys the EGL context and surface if they were successfully created.\n> +\t */\n> +\t~eGL();\n> +\n> +\t/**\n> +\t * \\brief Initialise the EGL context\n> +\t * \\param[in] gbmContext Pointer to initialised GBM context\n> +\t *\n> +\t * Sets up the EGL display from the GBM device, creates an OpenGL ES 2.0\n> +\t * context, and retrieves function pointers for required extensions\n> +\t * including:\n> +\t * - eglCreateImageKHR / eglDestroyImageKHR\n> +\t * - glEGLImageTargetTexture2DOES\n> +\t * - eglCreateSyncKHR / eglDestroySyncKHR / eglClientWaitSyncKHR\n> +\t *\n> +\t * \\return 0 on success, or -ENODEV on failure\n> +\t */\n> +\tint initEGLContext(GBM *gbmContext);\n> +\n> +\t/**\n> +\t * \\brief Clean up EGL resources\n> +\t *\n> +\t * Destroys the EGL sync object. Must be called from the same thread\n> +\t * that created the EGL context.\n> +\t */\n> +\tvoid cleanUp();\n> +\n> +\t/**\n> +\t * \\brief Create an input DMA-BUF backed texture\n> +\t * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n> +\t * \\param[in] fd DMA-BUF file descriptor\n> +\t *\n> +\t * Creates an EGL image from a DMA-BUF file descriptor and binds it to\n> +\t * a 2D texture for use as an input texture in shaders. The texture is\n> +\t * configured with nearest filtering and clamp-to-edge wrapping.\n> +\t *\n> +\t * \\return 0 on success, or -ENODEV on failure\n> +\t */\n> +\tint createInputDMABufTexture2D(eGLImage *eglImage, int fd);\n> +\n> +\t/**\n> +\t * \\brief Create an output DMA-BUF backed texture\n> +\t * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n> +\t * \\param[in] fd DMA-BUF file descriptor\n> +\t *\n> +\t * Creates an EGL image from a DMA-BUF file descriptor and binds it to\n> +\t * a 2D texture, then attaches it to a framebuffer object for use as a\n> +\t * render target. This enables zero-copy rendering directly to the\n> +\t * DMA-BUF.\n> +\t *\n> +\t * \\return 0 on success, or -ENODEV on failure\n> +\t */\n> +\tint createOutputDMABufTexture2D(eGLImage *eglImage, int fd);\n> +\n> +\t/**\n> +\t * \\brief Destroy a DMA-BUF texture's EGL image\n> +\t * \\param[in,out] eglImage EGL image to destroy\n> +\t *\n> +\t * Destroys the EGL image associated with a DMA-BUF texture. The OpenGL\n> +\t * texture and framebuffer objects are destroyed separately in the\n> +\t * eGLImage destructor.\n> +\t */\n> +\tvoid destroyDMABufTexture(eGLImage *eglImage);\n> +\n> +\t/**\n> +\t * \\brief Create a 2D texture from a memory buffer\n> +\t * \\param[in,out] eglImage EGL image to associate with the texture\n> +\t * \\param[in] format OpenGL internal format (e.g., GL_RGB, GL_RGBA)\n> +\t * \\param[in] width Texture width in pixels\n> +\t * \\param[in] height Texture height in pixels\n> +\t * \\param[in] data Pointer to pixel data, or nullptr for uninitialised texture\n> +\t *\n> +\t * Creates a 2D texture from a CPU-accessible memory buffer. The texture\n> +\t * is configured with nearest filtering and clamp-to-edge wrapping. This\n> +\t * is useful for uploading static data like lookup tables or uniform color\n> +\t * matrices to the GPU.\n> +\t */\n> +\tvoid createTexture2D(eGLImage *eglImage, GLint format, uint32_t width, uint32_t height, void *data);\n> +\n> +\t/**\n> +\t * \\brief Create a 1D texture from a memory buffer\n> +\t * \\param[in,out] eglImage EGL image to associate with the texture\n> +\t * \\param[in] format OpenGL internal format\n> +\t * \\param[in] width Texture width in pixels\n> +\t * \\param[in] data Pointer to pixel data\n> +\t *\n> +\t * Creates a 1D texture (implemented as a 2D texture with height=1) from\n> +\t * a CPU-accessible memory buffer. Useful for lookup tables in shaders.\n> +\t */\n> +\tvoid createTexture1D(eGLImage *eglImage, GLint format, uint32_t width, void *data);\n> +\n> +\t/**\n> +\t * \\brief Add a preprocessor definition to shader environment\n> +\t * \\param[in,out] shaderEnv Vector of shader environment strings\n> +\t * \\param[in] str Preprocessor definition string (e.g., \"#define APPLY_RGB_PARAMETERS\")\n> +\t *\n> +\t * Appends a preprocessor definition to the shader environment vector.\n> +\t * These definitions are prepended to shader source code during compilation.\n> +\t */\n> +\tvoid pushEnv(std::vector<std::string> &shaderEnv, const char *str);\n> +\n> +\t/**\n> +\t * \\brief Make the EGL context current for the calling thread\n> +\t *\n> +\t * Binds the EGL context to the current thread, allowing OpenGL ES\n> +\t * operations to be performed. Must be called from the thread that\n> +\t * will perform rendering operations.\n> +\t */\n> +\tvoid makeCurrent();\n> +\n> +\t/**\n> +\t * \\brief Compile a vertex shader\n> +\t * \\param[out] shaderId OpenGL shader object ID\n> +\t * \\param[in] shaderData Pointer to shader source code\n> +\t * \\param[in] shaderDataLen Length of shader source in bytes\n> +\t * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n> +\t *\n> +\t * Compiles a vertex shader from source code with optional preprocessor\n> +\t * definitions. On compilation failure, logs the shader info log.\n> +\t *\n> +\t * \\return 0 on success, or -EINVAL on compilation failure\n> +\t */\n> +\tint compileVertexShader(GLuint &shaderId, unsigned char *shaderData,\n> +\t\t\t\tunsigned int shaderDataLen,\n> +\t\t\t\tSpan<const std::string> shaderEnv);\n> +\n> +\t/**\n> +\t * \\brief Compile a fragment shader\n> +\t * \\param[out] shaderId OpenGL shader object ID\n> +\t * \\param[in] shaderData Pointer to shader source code\n> +\t * \\param[in] shaderDataLen Length of shader source in bytes\n> +\t * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n> +\t *\n> +\t * Compiles a fragment shader from source code with optional preprocessor\n> +\t * definitions. On compilation failure, logs the shader info log.\n> +\t *\n> +\t * \\return 0 on success, or -EINVAL on compilation failure\n> +\t */\n> +\tint compileFragmentShader(GLuint &shaderId, unsigned char *shaderData,\n> +\t\t\t\t  unsigned int shaderDataLen,\n> +\t\t\t\t  Span<const std::string> shaderEnv);\n> +\n> +\t/**\n> +\t * \\brief Link a shader program\n> +\t * \\param[out] programId OpenGL program object ID\n> +\t * \\param[in] fragmentshaderId Compiled fragment shader ID\n> +\t * \\param[in] vertexshaderId Compiled vertex shader ID\n> +\t *\n> +\t * Links vertex and fragment shaders into an executable shader program.\n> +\t * On link failure, logs the program info log and deletes the program.\n> +\t *\n> +\t * \\return 0 on success, or -ENODEV on link failure\n> +\t */\n> +\tint linkProgram(GLuint &programId, GLuint fragmentshaderId, GLuint vertexshaderId);\n> +\n> +\t/**\n> +\t * \\brief Dump shader source code to the log\n> +\t * \\param[in] shaderId OpenGL shader object ID\n> +\t *\n> +\t * Retrieves and logs the complete source code of a compiled shader.\n> +\t * Useful for debugging shader compilation issues.\n> +\t */\n> +\tvoid dumpShaderSource(GLuint shaderId);\n> +\n> +\t/**\n> +\t * \\brief Activate a shader program for rendering\n> +\t * \\param[in] programId OpenGL program object ID\n> +\t *\n> +\t * Sets the specified program as the current rendering program. All\n> +\t * subsequent draw calls will use this program's shaders.\n> +\t */\n> +\tvoid useProgram(GLuint programId);\n> +\n> +\t/**\n> +\t * \\brief Delete a shader program\n> +\t * \\param[in] programId OpenGL program object ID\n> +\t *\n> +\t * Deletes a shader program and frees associated resources. The program\n> +\t * must not be currently in use.\n> +\t */\n> +\tvoid deleteProgram(GLuint programId);\n> +\n> +\t/**\n> +\t * \\brief Synchronise rendering output\n> +\t *\n> +\t * Flushes OpenGL commands and waits for rendering to complete using an\n> +\t * EGL fence sync object. This ensures all rendering operations have\n> +\t * finished before the CPU accesses the output buffers.\n> +\t *\n> +\t * \\return 0 on success\n> +\t */\n> +\tint syncOutput();\n> +\n> +private:\n> +\tLIBCAMERA_DISABLE_COPY_AND_MOVE(eGL)\n> +\n> +\tint fd_;\t\t/**< File descriptor \\todo remove this */\n> +\tpid_t tid_;\t\t/**< Thread ID of the thread associated with this EGL context */\n> +\n> +\tEGLDisplay display_;\t/**< EGL display handle */\n> +\tEGLContext context_;\t/**< EGL context handle */\n> +\tEGLSurface surface_;\t/**< EGL sufrace handle */\n> +\tEGLSyncKHR sync_;\t/**< EGL sync object for output sychonisation */\n> +\n> +\t/**\n> +\t * \\brief Compile a shader of specified type\n> +\t * \\param[in] shaderType GL_VERTEX_SHADER or GL_FRAGMENT_SHADER\n> +\t * \\param[out] shaderId OpenGL shader object ID\n> +\t * \\param[in] shaderData Pointer to shader source code\n> +\t * \\param[in] shaderDataLen Length of shader source in bytes\n> +\t * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n> +\t *\n> +\t * Internal helper function for shader compilation. Prepends environment\n> +\t * definitions to the shader source and compiles the shader.\n> +\t *\n> +\t * \\return 0 on success, or -EINVAL on compilation failure\n> +\t */\n> +\tint compileShader(int shaderType, GLuint &shaderId, unsigned char *shaderData,\n> +\t\t\t  unsigned int shaderDataLen,\n> +\t\t\t  Span<const std::string> shaderEnv);\n> +\n> +\t/**\n> +\t * \\brief Create a DMA-BUF backed 2D texture\n> +\t * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n> +\t * \\param[in] fd DMA-BUF file descriptor\n> +\t * \\param[in] output If true, create framebuffer for render target\n> +\t *\n> +\t * Internal implementation for creating DMA-BUF textures. Creates an EGL\n> +\t * image from the DMA-BUF and binds it to a 2D texture. If output is true,\n> +\t * also creates and attaches a framebuffer object.\n> +\t *\n> +\t * \\return 0 on success, or -ENODEV on failure\n> +\t */\n> +\tint createDMABufTexture2D(eGLImage *eglImage, int fd, bool output);\n> +\n> +\tPFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA;\n> +\tPFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;\n> +\n> +\tPFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;\n> +\tPFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;\n> +\n> +\tPFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;\n> +\tPFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR;\n> +\tPFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;\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..52d6a6249\n> --- /dev/null\n> +++ b/src/libcamera/egl.cpp\n> @@ -0,0 +1,436 @@\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> + * egl.cpp - Helper class for managing eGL interactions.\n> + */\n> +\n> +#include \"libcamera/base/thread.h\"\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 <libdrm/drm_fourcc.h>\n> +#include <linux/dma-buf.h>\n> +#include <linux/dma-heap.h>\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(eGL)\n> +\n> +eGL::eGL()\n> +{\n> +\tcontext_ = EGL_NO_CONTEXT;\n> +\tsurface_ = EGL_NO_SURFACE;\n> +\tdisplay_ = EGL_NO_DISPLAY;\n> +}\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> +int eGL::syncOutput(void)\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\tglFlush();\n> +\teglClientWaitSyncKHR(display_, sync_, 0, EGL_FOREVER_KHR);\n> +\n> +\treturn 0;\n> +}\n> +\n> +// Create linear image attached to previous BO object\n> +int eGL::createDMABufTexture2D(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> +int eGL::createInputDMABufTexture2D(eGLImage *eglImage, int fd)\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\treturn createDMABufTexture2D(eglImage, fd, false);\n> +}\n> +int eGL::createOutputDMABufTexture2D(eGLImage *eglImage, int fd)\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\treturn createDMABufTexture2D(eglImage, fd, true);\n> +}\n> +\n> +void eGL::destroyDMABufTexture(eGLImage *eglImage)\n> +{\n> +\teglDestroyImage(display_, eglImage->image_);\n> +}\n> +\n> +// Generate a 2D texture from an input buffer directly\n> +void eGL::createTexture2D(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> +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> +\teglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress(\"eglCreateSyncKHR\");\n> +\tif (!eglCreateSyncKHR) {\n> +\t\tLOG(eGL, Error) << \"eglCreateSyncKHR not found\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\teglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC)eglGetProcAddress(\"eglDestroySyncKHR\");\n> +\tif (!eglDestroySyncKHR) {\n> +\t\tLOG(eGL, Error) << \"eglDestroySyncKHR not found\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\teglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress(\"eglClientWaitSyncKHR\");\n> +\tif (!eglClientWaitSyncKHR) {\n> +\t\tLOG(eGL, Error) << \"eglClientWaitSyncKHR 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> +\tsync_ = eglCreateSyncKHR(display_, EGL_SYNC_FENCE_KHR, NULL);\n\nCould you please elaborate a bit on the purpose and handling of `sync_'?\nI'm pretty ignorant about GL but I was exploring a problem with reported\nper-frame times and I also observed some tearing with v4e and I've hit\nthis.\n\nAccording to\nhttps://registry.khronos.org/EGL/sdk/docs/man/html/eglCreateSync.xhtml\n(it's sans-KHR but I suppose it's basically the same?):\n\n  When a fence sync object is created, eglCreateSync also inserts a\n  fence command into the command stream of the bound client API's\n  current context (i.e., the context returned by eglGetCurrentContext),\n  and associates it with the newly created sync object.\n\n  The only condition supported for fence sync objects is\n  EGL_SYNC_PRIOR_COMMANDS_COMPLETE, which is satisfied by completion of\n  the fence command corresponding to the sync object, and all preceding\n  commands in the associated client API context's command stream. The\n  sync object will not be signaled until all effects from these commands\n  on the client API's internal and framebuffer state are fully\n  realized. No other state is affected by execution of the fence\n  command.\n\nAs I understand it, calling eglCreateSyncKHR inserts a fence at a given\nGPU processing place and then eglClientWaitSyncKHR waits for completion\nof everything before the fence.\n\nBut here it seems that `sync_' is assigned only during configuration so\nI don't get its purpose and the purpose of eglClientWaitSyncKHR call in\nDebayerEGL::debayerGPU per each frame.  When I insert calls to\neglCreateSyncKHR + eglClientWaitSyncKHR + eglDestroySyncKHR all together\nafter glDrawArrays in DebayerEGL::debayerGPU, both my time measurement\nproblem and the image tearing disappear.  Which may possibly be just a\nworkaround for a different synchronization problem but then I still\ndon't understand what `sync_' is about.\n\n> +\tif (sync_ == EGL_NO_SYNC_KHR) {\n> +\t\tLOG(eGL, Error) << \"eglCreateSyncKHR fail\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\treturn 0;\n> +fail:\n> +\n> +\treturn -ENODEV;\n> +}\n> +\n> +void eGL::cleanUp(void)\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\tif (sync_) {\n> +\t\tmakeCurrent();\n> +\t\teglDestroySyncKHR(display_, sync_);\n> +\t}\n> +\n> +}\n> +\n> +void eGL::makeCurrent(void)\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> +void eGL::useProgram(GLuint programId)\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\tglUseProgram(programId);\n> +}\n> +\n> +void eGL::deleteProgram(GLuint programId)\n> +{\n> +\tASSERT(tid_ == Thread::currentId());\n> +\n> +\tglDeleteProgram(programId);\n> +}\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> +int eGL::compileVertexShader(GLuint &shaderId, 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> +int eGL::compileFragmentShader(GLuint &shaderId, 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> +int eGL::compileShader(int shaderType, GLuint &shaderId, 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> +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> +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 vertex 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 fe60c8752..b7be38a8f 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> @@ -186,7 +207,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 C247CC3334\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 25 Nov 2025 21:12:30 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A3BC260A85;\n\tTue, 25 Nov 2025 22:12:29 +0100 (CET)","from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.133.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4A1F7609D8\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 25 Nov 2025 22:12:27 +0100 (CET)","from mail-wm1-f72.google.com (mail-wm1-f72.google.com\n\t[209.85.128.72]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-504-Fjx_xGd7PJSYJwK11OnlsQ-1; Tue, 25 Nov 2025 16:12:24 -0500","by mail-wm1-f72.google.com with SMTP id\n\t5b1f17b1804b1-4775d8428e8so46977285e9.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 25 Nov 2025 13:12:24 -0800 (PST)","from mzamazal-thinkpadp1gen7.tpbc.csb\n\t(ip-77-48-47-2.net.vodafone.cz. [77.48.47.2])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-4790add608bsm8615365e9.5.2025.11.25.13.12.20\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tTue, 25 Nov 2025 13:12:21 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"O+WxyS6a\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1764105146;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=wveLXnqUDG0jGpio3ad7V3pqGbJaWi1quP5Nod4udN0=;\n\tb=O+WxyS6aQMeVj8GohwDcuVSjDnczIi6zOcI/dI0WikK98gxJywLFXqK4xbmtreCPIB//GT\n\tCB2PHBv+PUuypgFIMmXpQygJ57yPl7FRXKO5fEWgDOyaMBiSciU9lWpVlC7OEn958UI+H0\n\t9D1mxvWuEIAqK1mJYUcNRQzA3S93BqY=","X-MC-Unique":"Fjx_xGd7PJSYJwK11OnlsQ-1","X-Mimecast-MFC-AGG-ID":"Fjx_xGd7PJSYJwK11OnlsQ_1764105143","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1764105143; x=1764709943;\n\th=mime-version:user-agent:message-id:date:references:in-reply-to\n\t:subject:cc:to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject\n\t:date:message-id:reply-to;\n\tbh=wveLXnqUDG0jGpio3ad7V3pqGbJaWi1quP5Nod4udN0=;\n\tb=CRdE6WWrrH2REzsv7Y0MWIiwkmrygcP9jFZolcj2GxrbTiP1v9EiwogKV1HdyzMelR\n\toy2KxxW0Lt+9PZBVSgTL19k+kUevIcruZxzj9QmjTw05mF0cs9N39GlUlEJQ8Lg52vS2\n\tkxlBW/qs8/C8L7ZSxp1W4K6K+UMqaoWiXygy9kxgpbJRUGwntTE0pdfVHGaaI0768zJO\n\t62ScI/H2BCIWbcpFM3aM1s/FtMGUNtoQv5FFIZfmo5wbX8dyQ3Qp3hV333z/3KDbVjD/\n\toVZfUbwEaZvSz8Pa4sJr3DJ8tSLRcliZZM5F+POx/VTtSGWevIMxjGVw8Wz0tWSqOBhv\n\t2dGQ==","X-Gm-Message-State":"AOJu0YzkvfFGh2M6UF4JMwTluHI0ehIGbLizZov1HUfp2QOLuAQztIDn\n\tCgACaaQyTL2+Im770ic1NPn5UTKusaNs7al+iOHepWJmOP0lfiJUGb/yhbVV/bU2iTgW9Sl5CQx\n\t/x1RU9q5/ESkgWy8MbeTHhfHRD7jtQY9RRvhf7Lsoizg+Hk1v6S0NhYUvkSPh+vqUfC7+6T+QeE\n\t0=","X-Gm-Gg":"ASbGnctv5JOyDvig2d0V0WjzMDIKLmV10/nN95fAVPOa+G+D3SjguE5qDqN0H0dHjx8\n\tlDbQBigruT0N7wt7tFTCmfr+BYb0/3fOV0vjHdkSJgp/rIakN3dPOxVmRsJQ5hj27vRlERQqDg6\n\tSdDc3Knbqkv2+zjat2QtvmN8AbmumnGGInE69jCZ4ful7tGe6ogtFL9VM1ZU6z4Ad0v/6HzQUsY\n\tCCD4TB6wG8R6Q9kMEqkik/3klyeDaQMKfIUBOPqVl5KB+dUj8dVsJ5UEsWU30etvOhYLnvspdaP\n\tHD+8PDkElsmYh6afy/CA3JC2OOw7W24lKGu8zUnywomP45iRH1L9XoeQKM0AYoVmG2CttBwoman\n\tEnf/9SsGQuSxUzm4l2AVGE63HlwKUulkFXuxJt/eV64vCy+tt2HLSv/6QM8lsS0k=","X-Received":["by 2002:a05:600c:3b09:b0:471:13fa:1b84 with SMTP id\n\t5b1f17b1804b1-477c0185bc8mr205403485e9.12.1764105142892; \n\tTue, 25 Nov 2025 13:12:22 -0800 (PST)","by 2002:a05:600c:3b09:b0:471:13fa:1b84 with SMTP id\n\t5b1f17b1804b1-477c0185bc8mr205403255e9.12.1764105142276; \n\tTue, 25 Nov 2025 13:12:22 -0800 (PST)"],"X-Google-Smtp-Source":"AGHT+IGhk3dpijBBg2Xjxugvv3G7AkOa293zUBWm+eZr9D61TBlyVUqGh0efqElQRIxyn/MDMaADRQ==","From":"Milan Zamazal <mzamazal@redhat.com>","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","Cc":"libcamera-devel@lists.libcamera.org,  pavel@ucw.cz,  Kieran Bingham\n\t<kieran.bingham@ideasonboard.com>","Subject":"Re: [PATCH v4 03/23] libcamera: software_isp: egl: Add a eGL base\n\thelper class","In-Reply-To":"<20251120233347.5046-4-bryan.odonoghue@linaro.org> (Bryan\n\tO'Donoghue's message of \"Thu, 20 Nov 2025 23:33:27 +0000\")","References":"<20251120233347.5046-1-bryan.odonoghue@linaro.org>\n\t<20251120233347.5046-4-bryan.odonoghue@linaro.org>","Date":"Tue, 25 Nov 2025 22:12:20 +0100","Message-ID":"<85ikexstor.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","User-Agent":"Gnus/5.13 (Gnus v5.13)","MIME-Version":"1.0","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"dPcVZ8ejvcnu9vRX6ThOHSuB0NO97fk_Nq3w2fcXMI8_1764105143","X-Mimecast-Originator":"redhat.com","Content-Type":"text/plain","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":37077,"web_url":"https://patchwork.libcamera.org/comment/37077/","msgid":"<85ms49vtzr.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2025-11-26T12:49:44","subject":"Re: [PATCH v4 03/23] libcamera: software_isp: egl: Add a eGL base\n\thelper class","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Milan Zamazal <mzamazal@redhat.com> writes:\n\n> Hi Bryan,\n>\n> Bryan O'Donoghue <bryan.odonoghue@linaro.org> writes:\n>\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>> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n>> ---\n>>  include/libcamera/internal/egl.h | 412 +++++++++++++++++++++++++++++\n>>  src/libcamera/egl.cpp            | 436 +++++++++++++++++++++++++++++++\n>>  src/libcamera/meson.build        |  23 ++\n>>  3 files changed, 871 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..e61a394af\n>> --- /dev/null\n>> +++ b/include/libcamera/internal/egl.h\n>> @@ -0,0 +1,412 @@\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>> + * egl_context.cpp - Helper class for managing eGL interactions.\n>> + */\n>> +\n>> +#pragma once\n>> +\n>> +#include <sys/types.h>\n>> +#include <unistd.h>\n>> +\n>> +#include <libcamera/base/log.h>\n>> +\n>> +#include \"libcamera/base/utils.h\"\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>> + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t\n>> texture_unit_uniform_id)\n>> +\t{\n>> +\t\tinit(width, height, bpp, stride, texture_unit, texture_unit_uniform_id);\n>> +\t}\n>> +\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>> + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, GLenum texture_unit, uint32_t\n>> texture_unit_uniform_id)\n>> +\t{\n>> +\t\tuint32_t stride = libcamera::utils::alignUp(width * bpp, 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_;\t\t\t/**< Image width in pixels */\n>> +\tuint32_t height_;\t\t\t/**< Image height in pixels */\n>> +\tuint32_t stride_;\t\t\t/**< Row stride in bytes */\n>> +\tuint32_t offset_;\t\t\t/**< Buffer offset (reserved for future use) */\n>> +\tuint32_t framesize_;\t\t\t/**< Total frame size in bytes (stride * height) */\n>> +\tuint32_t bpp_;\t\t\t\t/**< Bytes per pixel */\n>> +\tuint32_t texture_unit_uniform_id_;\t/**< Shader uniform id for texture unit */\n>> + GLenum texture_unit_; /**< Texture unit associated with this image eg (GL_TEXTURE0) */\n>> +\tGLuint texture_;\t\t\t/**< OpenGL texture object ID */\n>> +\tGLuint fbo_;\t\t\t\t/**< OpenGL frame buffer object ID */\n>> +\tEGLImageKHR image_;\t\t\t/**< 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>> + void init(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit,\n>> 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>> +/**\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>> +class eGL\n>> +{\n>> +public:\n>> +\t/**\n>> +\t * \\brief Construct an EGL helper\n>> +\t *\n>> +\t * Creates an eGL instance with uninitialised context. Call initEGLContext()\n>> +\t * to set up the EGL display, context, and load extension functions.\n>> +\t */\n>> +\teGL();\n>> +\n>> +\t/**\n>> +\t * \\brief Destroy the EGL helper\n>> +\t *\n>> +\t * Destroys the EGL context and surface if they were successfully created.\n>> +\t */\n>> +\t~eGL();\n>> +\n>> +\t/**\n>> +\t * \\brief Initialise the EGL context\n>> +\t * \\param[in] gbmContext Pointer to initialised GBM context\n>> +\t *\n>> +\t * Sets up the EGL display from the GBM device, creates an OpenGL ES 2.0\n>> +\t * context, and retrieves function pointers for required extensions\n>> +\t * including:\n>> +\t * - eglCreateImageKHR / eglDestroyImageKHR\n>> +\t * - glEGLImageTargetTexture2DOES\n>> +\t * - eglCreateSyncKHR / eglDestroySyncKHR / eglClientWaitSyncKHR\n>> +\t *\n>> +\t * \\return 0 on success, or -ENODEV on failure\n>> +\t */\n>> +\tint initEGLContext(GBM *gbmContext);\n>> +\n>> +\t/**\n>> +\t * \\brief Clean up EGL resources\n>> +\t *\n>> +\t * Destroys the EGL sync object. Must be called from the same thread\n>> +\t * that created the EGL context.\n>> +\t */\n>> +\tvoid cleanUp();\n>> +\n>> +\t/**\n>> +\t * \\brief Create an input DMA-BUF backed texture\n>> +\t * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n>> +\t * \\param[in] fd DMA-BUF file descriptor\n>> +\t *\n>> +\t * Creates an EGL image from a DMA-BUF file descriptor and binds it to\n>> +\t * a 2D texture for use as an input texture in shaders. The texture is\n>> +\t * configured with nearest filtering and clamp-to-edge wrapping.\n>> +\t *\n>> +\t * \\return 0 on success, or -ENODEV on failure\n>> +\t */\n>> +\tint createInputDMABufTexture2D(eGLImage *eglImage, int fd);\n>> +\n>> +\t/**\n>> +\t * \\brief Create an output DMA-BUF backed texture\n>> +\t * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n>> +\t * \\param[in] fd DMA-BUF file descriptor\n>> +\t *\n>> +\t * Creates an EGL image from a DMA-BUF file descriptor and binds it to\n>> +\t * a 2D texture, then attaches it to a framebuffer object for use as a\n>> +\t * render target. This enables zero-copy rendering directly to the\n>> +\t * DMA-BUF.\n>> +\t *\n>> +\t * \\return 0 on success, or -ENODEV on failure\n>> +\t */\n>> +\tint createOutputDMABufTexture2D(eGLImage *eglImage, int fd);\n>> +\n>> +\t/**\n>> +\t * \\brief Destroy a DMA-BUF texture's EGL image\n>> +\t * \\param[in,out] eglImage EGL image to destroy\n>> +\t *\n>> +\t * Destroys the EGL image associated with a DMA-BUF texture. The OpenGL\n>> +\t * texture and framebuffer objects are destroyed separately in the\n>> +\t * eGLImage destructor.\n>> +\t */\n>> +\tvoid destroyDMABufTexture(eGLImage *eglImage);\n>> +\n>> +\t/**\n>> +\t * \\brief Create a 2D texture from a memory buffer\n>> +\t * \\param[in,out] eglImage EGL image to associate with the texture\n>> +\t * \\param[in] format OpenGL internal format (e.g., GL_RGB, GL_RGBA)\n>> +\t * \\param[in] width Texture width in pixels\n>> +\t * \\param[in] height Texture height in pixels\n>> +\t * \\param[in] data Pointer to pixel data, or nullptr for uninitialised texture\n>> +\t *\n>> +\t * Creates a 2D texture from a CPU-accessible memory buffer. The texture\n>> +\t * is configured with nearest filtering and clamp-to-edge wrapping. This\n>> +\t * is useful for uploading static data like lookup tables or uniform color\n>> +\t * matrices to the GPU.\n>> +\t */\n>> + void createTexture2D(eGLImage *eglImage, GLint format, uint32_t width, uint32_t height, void *data);\n>> +\n>> +\t/**\n>> +\t * \\brief Create a 1D texture from a memory buffer\n>> +\t * \\param[in,out] eglImage EGL image to associate with the texture\n>> +\t * \\param[in] format OpenGL internal format\n>> +\t * \\param[in] width Texture width in pixels\n>> +\t * \\param[in] data Pointer to pixel data\n>> +\t *\n>> +\t * Creates a 1D texture (implemented as a 2D texture with height=1) from\n>> +\t * a CPU-accessible memory buffer. Useful for lookup tables in shaders.\n>> +\t */\n>> +\tvoid createTexture1D(eGLImage *eglImage, GLint format, uint32_t width, void *data);\n>> +\n>> +\t/**\n>> +\t * \\brief Add a preprocessor definition to shader environment\n>> +\t * \\param[in,out] shaderEnv Vector of shader environment strings\n>> +\t * \\param[in] str Preprocessor definition string (e.g., \"#define APPLY_RGB_PARAMETERS\")\n>> +\t *\n>> +\t * Appends a preprocessor definition to the shader environment vector.\n>> +\t * These definitions are prepended to shader source code during compilation.\n>> +\t */\n>> +\tvoid pushEnv(std::vector<std::string> &shaderEnv, const char *str);\n>> +\n>> +\t/**\n>> +\t * \\brief Make the EGL context current for the calling thread\n>> +\t *\n>> +\t * Binds the EGL context to the current thread, allowing OpenGL ES\n>> +\t * operations to be performed. Must be called from the thread that\n>> +\t * will perform rendering operations.\n>> +\t */\n>> +\tvoid makeCurrent();\n>> +\n>> +\t/**\n>> +\t * \\brief Compile a vertex shader\n>> +\t * \\param[out] shaderId OpenGL shader object ID\n>> +\t * \\param[in] shaderData Pointer to shader source code\n>> +\t * \\param[in] shaderDataLen Length of shader source in bytes\n>> +\t * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n>> +\t *\n>> +\t * Compiles a vertex shader from source code with optional preprocessor\n>> +\t * definitions. On compilation failure, logs the shader info log.\n>> +\t *\n>> +\t * \\return 0 on success, or -EINVAL on compilation failure\n>> +\t */\n>> +\tint compileVertexShader(GLuint &shaderId, unsigned char *shaderData,\n>> +\t\t\t\tunsigned int shaderDataLen,\n>> +\t\t\t\tSpan<const std::string> shaderEnv);\n>> +\n>> +\t/**\n>> +\t * \\brief Compile a fragment shader\n>> +\t * \\param[out] shaderId OpenGL shader object ID\n>> +\t * \\param[in] shaderData Pointer to shader source code\n>> +\t * \\param[in] shaderDataLen Length of shader source in bytes\n>> +\t * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n>> +\t *\n>> +\t * Compiles a fragment shader from source code with optional preprocessor\n>> +\t * definitions. On compilation failure, logs the shader info log.\n>> +\t *\n>> +\t * \\return 0 on success, or -EINVAL on compilation failure\n>> +\t */\n>> +\tint compileFragmentShader(GLuint &shaderId, unsigned char *shaderData,\n>> +\t\t\t\t  unsigned int shaderDataLen,\n>> +\t\t\t\t  Span<const std::string> shaderEnv);\n>> +\n>> +\t/**\n>> +\t * \\brief Link a shader program\n>> +\t * \\param[out] programId OpenGL program object ID\n>> +\t * \\param[in] fragmentshaderId Compiled fragment shader ID\n>> +\t * \\param[in] vertexshaderId Compiled vertex shader ID\n>> +\t *\n>> +\t * Links vertex and fragment shaders into an executable shader program.\n>> +\t * On link failure, logs the program info log and deletes the program.\n>> +\t *\n>> +\t * \\return 0 on success, or -ENODEV on link failure\n>> +\t */\n>> +\tint linkProgram(GLuint &programId, GLuint fragmentshaderId, GLuint vertexshaderId);\n>> +\n>> +\t/**\n>> +\t * \\brief Dump shader source code to the log\n>> +\t * \\param[in] shaderId OpenGL shader object ID\n>> +\t *\n>> +\t * Retrieves and logs the complete source code of a compiled shader.\n>> +\t * Useful for debugging shader compilation issues.\n>> +\t */\n>> +\tvoid dumpShaderSource(GLuint shaderId);\n>> +\n>> +\t/**\n>> +\t * \\brief Activate a shader program for rendering\n>> +\t * \\param[in] programId OpenGL program object ID\n>> +\t *\n>> +\t * Sets the specified program as the current rendering program. All\n>> +\t * subsequent draw calls will use this program's shaders.\n>> +\t */\n>> +\tvoid useProgram(GLuint programId);\n>> +\n>> +\t/**\n>> +\t * \\brief Delete a shader program\n>> +\t * \\param[in] programId OpenGL program object ID\n>> +\t *\n>> +\t * Deletes a shader program and frees associated resources. The program\n>> +\t * must not be currently in use.\n>> +\t */\n>> +\tvoid deleteProgram(GLuint programId);\n>> +\n>> +\t/**\n>> +\t * \\brief Synchronise rendering output\n>> +\t *\n>> +\t * Flushes OpenGL commands and waits for rendering to complete using an\n>> +\t * EGL fence sync object. This ensures all rendering operations have\n>> +\t * finished before the CPU accesses the output buffers.\n>> +\t *\n>> +\t * \\return 0 on success\n>> +\t */\n>> +\tint syncOutput();\n>> +\n>> +private:\n>> +\tLIBCAMERA_DISABLE_COPY_AND_MOVE(eGL)\n>> +\n>> +\tint fd_;\t\t/**< File descriptor \\todo remove this */\n>> +\tpid_t tid_;\t\t/**< Thread ID of the thread associated with this EGL context */\n>> +\n>> +\tEGLDisplay display_;\t/**< EGL display handle */\n>> +\tEGLContext context_;\t/**< EGL context handle */\n>> +\tEGLSurface surface_;\t/**< EGL sufrace handle */\n>> +\tEGLSyncKHR sync_;\t/**< EGL sync object for output sychonisation */\n>> +\n>> +\t/**\n>> +\t * \\brief Compile a shader of specified type\n>> +\t * \\param[in] shaderType GL_VERTEX_SHADER or GL_FRAGMENT_SHADER\n>> +\t * \\param[out] shaderId OpenGL shader object ID\n>> +\t * \\param[in] shaderData Pointer to shader source code\n>> +\t * \\param[in] shaderDataLen Length of shader source in bytes\n>> +\t * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n>> +\t *\n>> +\t * Internal helper function for shader compilation. Prepends environment\n>> +\t * definitions to the shader source and compiles the shader.\n>> +\t *\n>> +\t * \\return 0 on success, or -EINVAL on compilation failure\n>> +\t */\n>> +\tint compileShader(int shaderType, GLuint &shaderId, unsigned char *shaderData,\n>> +\t\t\t  unsigned int shaderDataLen,\n>> +\t\t\t  Span<const std::string> shaderEnv);\n>> +\n>> +\t/**\n>> +\t * \\brief Create a DMA-BUF backed 2D texture\n>> +\t * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n>> +\t * \\param[in] fd DMA-BUF file descriptor\n>> +\t * \\param[in] output If true, create framebuffer for render target\n>> +\t *\n>> +\t * Internal implementation for creating DMA-BUF textures. Creates an EGL\n>> +\t * image from the DMA-BUF and binds it to a 2D texture. If output is true,\n>> +\t * also creates and attaches a framebuffer object.\n>> +\t *\n>> +\t * \\return 0 on success, or -ENODEV on failure\n>> +\t */\n>> +\tint createDMABufTexture2D(eGLImage *eglImage, int fd, bool output);\n>> +\n>> +\tPFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA;\n>> +\tPFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;\n>> +\n>> +\tPFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;\n>> +\tPFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;\n>> +\n>> +\tPFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;\n>> +\tPFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR;\n>> +\tPFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;\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..52d6a6249\n>> --- /dev/null\n>> +++ b/src/libcamera/egl.cpp\n>> @@ -0,0 +1,436 @@\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>> + * egl.cpp - Helper class for managing eGL interactions.\n>> + */\n>> +\n>> +#include \"libcamera/base/thread.h\"\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 <libdrm/drm_fourcc.h>\n>> +#include <linux/dma-buf.h>\n>> +#include <linux/dma-heap.h>\n>> +\n>> +namespace libcamera {\n>> +\n>> +LOG_DEFINE_CATEGORY(eGL)\n>> +\n>> +eGL::eGL()\n>> +{\n>> +\tcontext_ = EGL_NO_CONTEXT;\n>> +\tsurface_ = EGL_NO_SURFACE;\n>> +\tdisplay_ = EGL_NO_DISPLAY;\n>> +}\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>> +int eGL::syncOutput(void)\n>> +{\n>> +\tASSERT(tid_ == Thread::currentId());\n>> +\n>> +\tglFlush();\n>> +\teglClientWaitSyncKHR(display_, sync_, 0, EGL_FOREVER_KHR);\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +// Create linear image attached to previous BO object\n>> +int eGL::createDMABufTexture2D(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>> + glFramebufferTexture2D(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>> +int eGL::createInputDMABufTexture2D(eGLImage *eglImage, int fd)\n>> +{\n>> +\tASSERT(tid_ == Thread::currentId());\n>> +\n>> +\treturn createDMABufTexture2D(eglImage, fd, false);\n>> +}\n>> +int eGL::createOutputDMABufTexture2D(eGLImage *eglImage, int fd)\n>> +{\n>> +\tASSERT(tid_ == Thread::currentId());\n>> +\n>> +\treturn createDMABufTexture2D(eglImage, fd, true);\n>> +}\n>> +\n>> +void eGL::destroyDMABufTexture(eGLImage *eglImage)\n>> +{\n>> +\teglDestroyImage(display_, eglImage->image_);\n>> +}\n>> +\n>> +// Generate a 2D texture from an input buffer directly\n>> +void eGL::createTexture2D(eGLImage *eglImage, GLint format, uint32_t width, uint32_t height, void\n>> *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>> +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>> + glEGLImageTargetTexture2DOES =\n>> (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress(\"glEGLImageTargetTexture2DOES\");\n>> +\tif (!glEGLImageTargetTexture2DOES) {\n>> +\t\tLOG(eGL, Error) << \"glEGLImageTargetTexture2DOES not found\";\n>> +\t\tgoto fail;\n>> +\t}\n>> +\n>> +\teglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress(\"eglCreateSyncKHR\");\n>> +\tif (!eglCreateSyncKHR) {\n>> +\t\tLOG(eGL, Error) << \"eglCreateSyncKHR not found\";\n>> +\t\tgoto fail;\n>> +\t}\n>> +\n>> +\teglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC)eglGetProcAddress(\"eglDestroySyncKHR\");\n>> +\tif (!eglDestroySyncKHR) {\n>> +\t\tLOG(eGL, Error) << \"eglDestroySyncKHR not found\";\n>> +\t\tgoto fail;\n>> +\t}\n>> +\n>> +\teglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress(\"eglClientWaitSyncKHR\");\n>> +\tif (!eglClientWaitSyncKHR) {\n>> +\t\tLOG(eGL, Error) << \"eglClientWaitSyncKHR 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>> +\tsync_ = eglCreateSyncKHR(display_, EGL_SYNC_FENCE_KHR, NULL);\n>\n> Could you please elaborate a bit on the purpose and handling of `sync_'?\n> I'm pretty ignorant about GL but I was exploring a problem with reported\n> per-frame times and I also observed some tearing with v4e and I've hit\n> this.\n>\n> According to\n> https://registry.khronos.org/EGL/sdk/docs/man/html/eglCreateSync.xhtml\n> (it's sans-KHR but I suppose it's basically the same?):\n>\n>   When a fence sync object is created, eglCreateSync also inserts a\n>   fence command into the command stream of the bound client API's\n>   current context (i.e., the context returned by eglGetCurrentContext),\n>   and associates it with the newly created sync object.\n>\n>   The only condition supported for fence sync objects is\n>   EGL_SYNC_PRIOR_COMMANDS_COMPLETE, which is satisfied by completion of\n>   the fence command corresponding to the sync object, and all preceding\n>   commands in the associated client API context's command stream. The\n>   sync object will not be signaled until all effects from these commands\n>   on the client API's internal and framebuffer state are fully\n>   realized. No other state is affected by execution of the fence\n>   command.\n>\n> As I understand it, calling eglCreateSyncKHR inserts a fence at a given\n> GPU processing place and then eglClientWaitSyncKHR waits for completion\n> of everything before the fence.\n>\n> But here it seems that `sync_' is assigned only during configuration so\n> I don't get its purpose and the purpose of eglClientWaitSyncKHR call in\n> DebayerEGL::debayerGPU per each frame.  When I insert calls to\n> eglCreateSyncKHR + eglClientWaitSyncKHR + eglDestroySyncKHR all together\n> after glDrawArrays in DebayerEGL::debayerGPU, both my time measurement\n> problem and the image tearing disappear.  Which may possibly be just a\n> workaround for a different synchronization problem but then I still\n> don't understand what `sync_' is about.\n\nAs discussed at the meeting and suggested by Robert, I tested replacing\nsync_ use with glFinish call.  It works for me.  The corresponding patch\nis available here:\nhttps://gitlab.freedesktop.org/camera/libcamera-softisp/-/commits/pdm-gpu-sync?ref_type=heads\n\nThere are three additional patches on top of it, fixing various formal\nissues.  If possible, please use them too.\n\n>> +\tif (sync_ == EGL_NO_SYNC_KHR) {\n>> +\t\tLOG(eGL, Error) << \"eglCreateSyncKHR fail\";\n>> +\t\tgoto fail;\n>> +\t}\n>> +\n>> +\treturn 0;\n>> +fail:\n>> +\n>> +\treturn -ENODEV;\n>> +}\n>> +\n>> +void eGL::cleanUp(void)\n>> +{\n>> +\tASSERT(tid_ == Thread::currentId());\n>> +\n>> +\tif (sync_) {\n>> +\t\tmakeCurrent();\n>> +\t\teglDestroySyncKHR(display_, sync_);\n>> +\t}\n>> +\n>> +}\n>> +\n>> +void eGL::makeCurrent(void)\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>> +void eGL::useProgram(GLuint programId)\n>> +{\n>> +\tASSERT(tid_ == Thread::currentId());\n>> +\n>> +\tglUseProgram(programId);\n>> +}\n>> +\n>> +void eGL::deleteProgram(GLuint programId)\n>> +{\n>> +\tASSERT(tid_ == Thread::currentId());\n>> +\n>> +\tglDeleteProgram(programId);\n>> +}\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>> +int eGL::compileVertexShader(GLuint &shaderId, 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>> +int eGL::compileFragmentShader(GLuint &shaderId, 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>> +int eGL::compileShader(int shaderType, GLuint &shaderId, 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>> +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>> +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 vertex 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 fe60c8752..b7be38a8f 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>> @@ -186,7 +207,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 9F87AC32DE\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 26 Nov 2025 12:49:53 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D719C60A9E;\n\tWed, 26 Nov 2025 13:49:52 +0100 (CET)","from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.129.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 28F1C606D5\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 26 Nov 2025 13:49:51 +0100 (CET)","from mail-wm1-f71.google.com (mail-wm1-f71.google.com\n\t[209.85.128.71]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-541-IWp9t0S6MM6QMV-_eA6i4A-1; Wed, 26 Nov 2025 07:49:48 -0500","by mail-wm1-f71.google.com with SMTP id\n\t5b1f17b1804b1-47106a388cfso40765765e9.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 26 Nov 2025 04:49:48 -0800 (PST)","from mzamazal-thinkpadp1gen7.tpbc.csb\n\t(ip-77-48-47-2.net.vodafone.cz. [77.48.47.2])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-4790adf0b2asm40645405e9.11.2025.11.26.04.49.44\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tWed, 26 Nov 2025 04:49:45 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"QHvi3By0\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1764161389;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=aA/JpWcLJPfPtvorF2JcbBUk9c9LHzbAj5msY+X2TT0=;\n\tb=QHvi3By0LHwta/yt7pE1TPaGYexSqMJqe+xVNuPjBJOHOIbbNetB+1e/vhXbQ7egRldmtb\n\tSU6o7s++tiYLz2/rq2zzvLypJltw2qqpNgFJP1NLKCNp2Ehsca28jIYYBsqExx7WuEFJxc\n\tCbgIlFhKvrckh2hdBItstCmJ65wK9UA=","X-MC-Unique":"IWp9t0S6MM6QMV-_eA6i4A-1","X-Mimecast-MFC-AGG-ID":"IWp9t0S6MM6QMV-_eA6i4A_1764161387","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1764161387; x=1764766187;\n\th=mime-version:user-agent:message-id:date:references:in-reply-to\n\t:subject:cc:to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject\n\t:date:message-id:reply-to;\n\tbh=aA/JpWcLJPfPtvorF2JcbBUk9c9LHzbAj5msY+X2TT0=;\n\tb=JAon7OqN57Z8Ao0yacIAurSRYCCryI6+w0BWcgB70mHw6B47MHCgA6wH241P51wxRV\n\twZ9iWE5lh8EFWnI4rb9f6ExWc8JVOczARa8CYu9Qb6AvWboUUCmNdcxV/hd/LdewjQS8\n\tB2v5+jN11l+V7H4jMC/u/gYyePNi5lzWy/PNhsVfu+xuV9rb/YSPYGYgiYodqesAd8iR\n\tGEyjMwylJVfhKXv5JROawRok9EkfitW8UqeU3iMMQBtrBawDxRab5T7Lpacu1RegWzz9\n\tX4ziRqeAth0bO3BPg0BASw0vjKye1i2P75YzSpOnRYSDQYczS+zXCnrvLWrqnfXWIrdY\n\tdyWw==","X-Gm-Message-State":"AOJu0Ywum18jwSMj6yg4tmdlgQ7VP8JDcftcQPZFiqOaERE6HEHyHGF0\n\tI4k14yrJSCwiwstrC0Jq4D2U9LrBmS4VOJb4TgL/+SsgBJrSgAEfgsJovA7c4GfS1EMsPt1C1l5\n\th4R6jOK/b7XGtDH9NVrJRjOjWwqyPskTLhtYaOzmt6Z2HorC8Ix/hwlNWnZrVwp/yf0YPRAXVWl\n\tM=","X-Gm-Gg":"ASbGncs6JOqQ82LauDAwtyTrV0MiS3kFPtWS4hLKRblcBNapkrpD49cu8lYBHWd0+ZL\n\tAugC/zN3OFBnQSQO530dbvgMT2W6FtPl45TDk07yitHegnQexOxG53bL+zaEWTybcumhkNHnN3D\n\tzzoC3ls+/+PR/rKhZF9OWHtqZqjYKzsZjLjnfv6exVXo9onbc+6/XdbpYf+HMssKUBmk1vz50Md\n\t2/FllLU4sGvRftfwvchwp3y4HIjveJhO9Pk34wchRQBaUSumFD4/vMeUS9jQXbzj6fvZM46XVBl\n\tG5M0jtx9FHoSoOQoS3LvAQiVqQ4wOvSuYna1FHsig+YlWhHIUfqxvzudlZ2Q6tgkL2lq947btu3\n\tb0yS+SleDzI5Qou5oGBx/Fad+TTqcid8OyZ6yyTvIdcmbqCKW95dfnehY9My4ivQ=","X-Received":["by 2002:a05:600c:19ce:b0:46e:48fd:a1a9 with SMTP id\n\t5b1f17b1804b1-477c115dfaamr170989875e9.33.1764161386994; \n\tWed, 26 Nov 2025 04:49:46 -0800 (PST)","by 2002:a05:600c:19ce:b0:46e:48fd:a1a9 with SMTP id\n\t5b1f17b1804b1-477c115dfaamr170989475e9.33.1764161386307; \n\tWed, 26 Nov 2025 04:49:46 -0800 (PST)"],"X-Google-Smtp-Source":"AGHT+IGqLF1Bk8L8VGTv4+ILI5tlYVBhSSil4+iq0ZGGYk0/PqCDk7sRSE3bd+9FO1+EEJuSFySGjg==","From":"Milan Zamazal <mzamazal@redhat.com>","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","Cc":"libcamera-devel@lists.libcamera.org,  pavel@ucw.cz,  Kieran Bingham\n\t<kieran.bingham@ideasonboard.com>, Robert Mader\n\t<robert.mader@collabora.com>","Subject":"Re: [PATCH v4 03/23] libcamera: software_isp: egl: Add a eGL base\n\thelper class","In-Reply-To":"<85ikexstor.fsf@mzamazal-thinkpadp1gen7.tpbc.csb> (Milan\n\tZamazal's message of \"Tue, 25 Nov 2025 22:12:20 +0100\")","References":"<20251120233347.5046-1-bryan.odonoghue@linaro.org>\n\t<20251120233347.5046-4-bryan.odonoghue@linaro.org>\n\t<85ikexstor.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","Date":"Wed, 26 Nov 2025 13:49:44 +0100","Message-ID":"<85ms49vtzr.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","User-Agent":"Gnus/5.13 (Gnus v5.13)","MIME-Version":"1.0","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"-VIEtd_4cm6T2GLVwnyhta3BfzpP60dktvhk-JKqrPw_1764161387","X-Mimecast-Originator":"redhat.com","Content-Type":"text/plain","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":37084,"web_url":"https://patchwork.libcamera.org/comment/37084/","msgid":"<be383b9a-fbac-46e7-a078-a88274d117e3@linaro.org>","date":"2025-11-27T01:46:51","subject":"Re: [PATCH v4 03/23] libcamera: software_isp: egl: Add a eGL base\n\thelper class","submitter":{"id":175,"url":"https://patchwork.libcamera.org/api/people/175/","name":"Bryan O'Donoghue","email":"bryan.odonoghue@linaro.org"},"content":"On 26/11/2025 12:49, Milan Zamazal wrote:\n> Milan Zamazal <mzamazal@redhat.com> writes:\n> \n>> Hi Bryan,\n>>\n>> Bryan O'Donoghue <bryan.odonoghue@linaro.org> writes:\n>>\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>>> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n>>> ---\n>>>   include/libcamera/internal/egl.h | 412 +++++++++++++++++++++++++++++\n>>>   src/libcamera/egl.cpp            | 436 +++++++++++++++++++++++++++++++\n>>>   src/libcamera/meson.build        |  23 ++\n>>>   3 files changed, 871 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..e61a394af\n>>> --- /dev/null\n>>> +++ b/include/libcamera/internal/egl.h\n>>> @@ -0,0 +1,412 @@\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>>> + * egl_context.cpp - Helper class for managing eGL interactions.\n>>> + */\n>>> +\n>>> +#pragma once\n>>> +\n>>> +#include <sys/types.h>\n>>> +#include <unistd.h>\n>>> +\n>>> +#include <libcamera/base/log.h>\n>>> +\n>>> +#include \"libcamera/base/utils.h\"\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>>> + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t\n>>> texture_unit_uniform_id)\n>>> +\t{\n>>> +\t\tinit(width, height, bpp, stride, texture_unit, texture_unit_uniform_id);\n>>> +\t}\n>>> +\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>>> + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, GLenum texture_unit, uint32_t\n>>> texture_unit_uniform_id)\n>>> +\t{\n>>> +\t\tuint32_t stride = libcamera::utils::alignUp(width * bpp, 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_;\t\t\t/**< Image width in pixels */\n>>> +\tuint32_t height_;\t\t\t/**< Image height in pixels */\n>>> +\tuint32_t stride_;\t\t\t/**< Row stride in bytes */\n>>> +\tuint32_t offset_;\t\t\t/**< Buffer offset (reserved for future use) */\n>>> +\tuint32_t framesize_;\t\t\t/**< Total frame size in bytes (stride * height) */\n>>> +\tuint32_t bpp_;\t\t\t\t/**< Bytes per pixel */\n>>> +\tuint32_t texture_unit_uniform_id_;\t/**< Shader uniform id for texture unit */\n>>> + GLenum texture_unit_; /**< Texture unit associated with this image eg (GL_TEXTURE0) */\n>>> +\tGLuint texture_;\t\t\t/**< OpenGL texture object ID */\n>>> +\tGLuint fbo_;\t\t\t\t/**< OpenGL frame buffer object ID */\n>>> +\tEGLImageKHR image_;\t\t\t/**< 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>>> + void init(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit,\n>>> 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>>> +/**\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>>> +class eGL\n>>> +{\n>>> +public:\n>>> +\t/**\n>>> +\t * \\brief Construct an EGL helper\n>>> +\t *\n>>> +\t * Creates an eGL instance with uninitialised context. Call initEGLContext()\n>>> +\t * to set up the EGL display, context, and load extension functions.\n>>> +\t */\n>>> +\teGL();\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Destroy the EGL helper\n>>> +\t *\n>>> +\t * Destroys the EGL context and surface if they were successfully created.\n>>> +\t */\n>>> +\t~eGL();\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Initialise the EGL context\n>>> +\t * \\param[in] gbmContext Pointer to initialised GBM context\n>>> +\t *\n>>> +\t * Sets up the EGL display from the GBM device, creates an OpenGL ES 2.0\n>>> +\t * context, and retrieves function pointers for required extensions\n>>> +\t * including:\n>>> +\t * - eglCreateImageKHR / eglDestroyImageKHR\n>>> +\t * - glEGLImageTargetTexture2DOES\n>>> +\t * - eglCreateSyncKHR / eglDestroySyncKHR / eglClientWaitSyncKHR\n>>> +\t *\n>>> +\t * \\return 0 on success, or -ENODEV on failure\n>>> +\t */\n>>> +\tint initEGLContext(GBM *gbmContext);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Clean up EGL resources\n>>> +\t *\n>>> +\t * Destroys the EGL sync object. Must be called from the same thread\n>>> +\t * that created the EGL context.\n>>> +\t */\n>>> +\tvoid cleanUp();\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Create an input DMA-BUF backed texture\n>>> +\t * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n>>> +\t * \\param[in] fd DMA-BUF file descriptor\n>>> +\t *\n>>> +\t * Creates an EGL image from a DMA-BUF file descriptor and binds it to\n>>> +\t * a 2D texture for use as an input texture in shaders. The texture is\n>>> +\t * configured with nearest filtering and clamp-to-edge wrapping.\n>>> +\t *\n>>> +\t * \\return 0 on success, or -ENODEV on failure\n>>> +\t */\n>>> +\tint createInputDMABufTexture2D(eGLImage *eglImage, int fd);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Create an output DMA-BUF backed texture\n>>> +\t * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n>>> +\t * \\param[in] fd DMA-BUF file descriptor\n>>> +\t *\n>>> +\t * Creates an EGL image from a DMA-BUF file descriptor and binds it to\n>>> +\t * a 2D texture, then attaches it to a framebuffer object for use as a\n>>> +\t * render target. This enables zero-copy rendering directly to the\n>>> +\t * DMA-BUF.\n>>> +\t *\n>>> +\t * \\return 0 on success, or -ENODEV on failure\n>>> +\t */\n>>> +\tint createOutputDMABufTexture2D(eGLImage *eglImage, int fd);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Destroy a DMA-BUF texture's EGL image\n>>> +\t * \\param[in,out] eglImage EGL image to destroy\n>>> +\t *\n>>> +\t * Destroys the EGL image associated with a DMA-BUF texture. The OpenGL\n>>> +\t * texture and framebuffer objects are destroyed separately in the\n>>> +\t * eGLImage destructor.\n>>> +\t */\n>>> +\tvoid destroyDMABufTexture(eGLImage *eglImage);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Create a 2D texture from a memory buffer\n>>> +\t * \\param[in,out] eglImage EGL image to associate with the texture\n>>> +\t * \\param[in] format OpenGL internal format (e.g., GL_RGB, GL_RGBA)\n>>> +\t * \\param[in] width Texture width in pixels\n>>> +\t * \\param[in] height Texture height in pixels\n>>> +\t * \\param[in] data Pointer to pixel data, or nullptr for uninitialised texture\n>>> +\t *\n>>> +\t * Creates a 2D texture from a CPU-accessible memory buffer. The texture\n>>> +\t * is configured with nearest filtering and clamp-to-edge wrapping. This\n>>> +\t * is useful for uploading static data like lookup tables or uniform color\n>>> +\t * matrices to the GPU.\n>>> +\t */\n>>> + void createTexture2D(eGLImage *eglImage, GLint format, uint32_t width, uint32_t height, void *data);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Create a 1D texture from a memory buffer\n>>> +\t * \\param[in,out] eglImage EGL image to associate with the texture\n>>> +\t * \\param[in] format OpenGL internal format\n>>> +\t * \\param[in] width Texture width in pixels\n>>> +\t * \\param[in] data Pointer to pixel data\n>>> +\t *\n>>> +\t * Creates a 1D texture (implemented as a 2D texture with height=1) from\n>>> +\t * a CPU-accessible memory buffer. Useful for lookup tables in shaders.\n>>> +\t */\n>>> +\tvoid createTexture1D(eGLImage *eglImage, GLint format, uint32_t width, void *data);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Add a preprocessor definition to shader environment\n>>> +\t * \\param[in,out] shaderEnv Vector of shader environment strings\n>>> +\t * \\param[in] str Preprocessor definition string (e.g., \"#define APPLY_RGB_PARAMETERS\")\n>>> +\t *\n>>> +\t * Appends a preprocessor definition to the shader environment vector.\n>>> +\t * These definitions are prepended to shader source code during compilation.\n>>> +\t */\n>>> +\tvoid pushEnv(std::vector<std::string> &shaderEnv, const char *str);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Make the EGL context current for the calling thread\n>>> +\t *\n>>> +\t * Binds the EGL context to the current thread, allowing OpenGL ES\n>>> +\t * operations to be performed. Must be called from the thread that\n>>> +\t * will perform rendering operations.\n>>> +\t */\n>>> +\tvoid makeCurrent();\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Compile a vertex shader\n>>> +\t * \\param[out] shaderId OpenGL shader object ID\n>>> +\t * \\param[in] shaderData Pointer to shader source code\n>>> +\t * \\param[in] shaderDataLen Length of shader source in bytes\n>>> +\t * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n>>> +\t *\n>>> +\t * Compiles a vertex shader from source code with optional preprocessor\n>>> +\t * definitions. On compilation failure, logs the shader info log.\n>>> +\t *\n>>> +\t * \\return 0 on success, or -EINVAL on compilation failure\n>>> +\t */\n>>> +\tint compileVertexShader(GLuint &shaderId, unsigned char *shaderData,\n>>> +\t\t\t\tunsigned int shaderDataLen,\n>>> +\t\t\t\tSpan<const std::string> shaderEnv);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Compile a fragment shader\n>>> +\t * \\param[out] shaderId OpenGL shader object ID\n>>> +\t * \\param[in] shaderData Pointer to shader source code\n>>> +\t * \\param[in] shaderDataLen Length of shader source in bytes\n>>> +\t * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n>>> +\t *\n>>> +\t * Compiles a fragment shader from source code with optional preprocessor\n>>> +\t * definitions. On compilation failure, logs the shader info log.\n>>> +\t *\n>>> +\t * \\return 0 on success, or -EINVAL on compilation failure\n>>> +\t */\n>>> +\tint compileFragmentShader(GLuint &shaderId, unsigned char *shaderData,\n>>> +\t\t\t\t  unsigned int shaderDataLen,\n>>> +\t\t\t\t  Span<const std::string> shaderEnv);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Link a shader program\n>>> +\t * \\param[out] programId OpenGL program object ID\n>>> +\t * \\param[in] fragmentshaderId Compiled fragment shader ID\n>>> +\t * \\param[in] vertexshaderId Compiled vertex shader ID\n>>> +\t *\n>>> +\t * Links vertex and fragment shaders into an executable shader program.\n>>> +\t * On link failure, logs the program info log and deletes the program.\n>>> +\t *\n>>> +\t * \\return 0 on success, or -ENODEV on link failure\n>>> +\t */\n>>> +\tint linkProgram(GLuint &programId, GLuint fragmentshaderId, GLuint vertexshaderId);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Dump shader source code to the log\n>>> +\t * \\param[in] shaderId OpenGL shader object ID\n>>> +\t *\n>>> +\t * Retrieves and logs the complete source code of a compiled shader.\n>>> +\t * Useful for debugging shader compilation issues.\n>>> +\t */\n>>> +\tvoid dumpShaderSource(GLuint shaderId);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Activate a shader program for rendering\n>>> +\t * \\param[in] programId OpenGL program object ID\n>>> +\t *\n>>> +\t * Sets the specified program as the current rendering program. All\n>>> +\t * subsequent draw calls will use this program's shaders.\n>>> +\t */\n>>> +\tvoid useProgram(GLuint programId);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Delete a shader program\n>>> +\t * \\param[in] programId OpenGL program object ID\n>>> +\t *\n>>> +\t * Deletes a shader program and frees associated resources. The program\n>>> +\t * must not be currently in use.\n>>> +\t */\n>>> +\tvoid deleteProgram(GLuint programId);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Synchronise rendering output\n>>> +\t *\n>>> +\t * Flushes OpenGL commands and waits for rendering to complete using an\n>>> +\t * EGL fence sync object. This ensures all rendering operations have\n>>> +\t * finished before the CPU accesses the output buffers.\n>>> +\t *\n>>> +\t * \\return 0 on success\n>>> +\t */\n>>> +\tint syncOutput();\n>>> +\n>>> +private:\n>>> +\tLIBCAMERA_DISABLE_COPY_AND_MOVE(eGL)\n>>> +\n>>> +\tint fd_;\t\t/**< File descriptor \\todo remove this */\n>>> +\tpid_t tid_;\t\t/**< Thread ID of the thread associated with this EGL context */\n>>> +\n>>> +\tEGLDisplay display_;\t/**< EGL display handle */\n>>> +\tEGLContext context_;\t/**< EGL context handle */\n>>> +\tEGLSurface surface_;\t/**< EGL sufrace handle */\n>>> +\tEGLSyncKHR sync_;\t/**< EGL sync object for output sychonisation */\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Compile a shader of specified type\n>>> +\t * \\param[in] shaderType GL_VERTEX_SHADER or GL_FRAGMENT_SHADER\n>>> +\t * \\param[out] shaderId OpenGL shader object ID\n>>> +\t * \\param[in] shaderData Pointer to shader source code\n>>> +\t * \\param[in] shaderDataLen Length of shader source in bytes\n>>> +\t * \\param[in] shaderEnv Span of preprocessor definitions to prepend\n>>> +\t *\n>>> +\t * Internal helper function for shader compilation. Prepends environment\n>>> +\t * definitions to the shader source and compiles the shader.\n>>> +\t *\n>>> +\t * \\return 0 on success, or -EINVAL on compilation failure\n>>> +\t */\n>>> +\tint compileShader(int shaderType, GLuint &shaderId, unsigned char *shaderData,\n>>> +\t\t\t  unsigned int shaderDataLen,\n>>> +\t\t\t  Span<const std::string> shaderEnv);\n>>> +\n>>> +\t/**\n>>> +\t * \\brief Create a DMA-BUF backed 2D texture\n>>> +\t * \\param[in,out] eglImage EGL image to associate with the DMA-BUF\n>>> +\t * \\param[in] fd DMA-BUF file descriptor\n>>> +\t * \\param[in] output If true, create framebuffer for render target\n>>> +\t *\n>>> +\t * Internal implementation for creating DMA-BUF textures. Creates an EGL\n>>> +\t * image from the DMA-BUF and binds it to a 2D texture. If output is true,\n>>> +\t * also creates and attaches a framebuffer object.\n>>> +\t *\n>>> +\t * \\return 0 on success, or -ENODEV on failure\n>>> +\t */\n>>> +\tint createDMABufTexture2D(eGLImage *eglImage, int fd, bool output);\n>>> +\n>>> +\tPFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA;\n>>> +\tPFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;\n>>> +\n>>> +\tPFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;\n>>> +\tPFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;\n>>> +\n>>> +\tPFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;\n>>> +\tPFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR;\n>>> +\tPFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;\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..52d6a6249\n>>> --- /dev/null\n>>> +++ b/src/libcamera/egl.cpp\n>>> @@ -0,0 +1,436 @@\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>>> + * egl.cpp - Helper class for managing eGL interactions.\n>>> + */\n>>> +\n>>> +#include \"libcamera/base/thread.h\"\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 <libdrm/drm_fourcc.h>\n>>> +#include <linux/dma-buf.h>\n>>> +#include <linux/dma-heap.h>\n>>> +\n>>> +namespace libcamera {\n>>> +\n>>> +LOG_DEFINE_CATEGORY(eGL)\n>>> +\n>>> +eGL::eGL()\n>>> +{\n>>> +\tcontext_ = EGL_NO_CONTEXT;\n>>> +\tsurface_ = EGL_NO_SURFACE;\n>>> +\tdisplay_ = EGL_NO_DISPLAY;\n>>> +}\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>>> +int eGL::syncOutput(void)\n>>> +{\n>>> +\tASSERT(tid_ == Thread::currentId());\n>>> +\n>>> +\tglFlush();\n>>> +\teglClientWaitSyncKHR(display_, sync_, 0, EGL_FOREVER_KHR);\n>>> +\n>>> +\treturn 0;\n>>> +}\n>>> +\n>>> +// Create linear image attached to previous BO object\n>>> +int eGL::createDMABufTexture2D(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>>> + glFramebufferTexture2D(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>>> +int eGL::createInputDMABufTexture2D(eGLImage *eglImage, int fd)\n>>> +{\n>>> +\tASSERT(tid_ == Thread::currentId());\n>>> +\n>>> +\treturn createDMABufTexture2D(eglImage, fd, false);\n>>> +}\n>>> +int eGL::createOutputDMABufTexture2D(eGLImage *eglImage, int fd)\n>>> +{\n>>> +\tASSERT(tid_ == Thread::currentId());\n>>> +\n>>> +\treturn createDMABufTexture2D(eglImage, fd, true);\n>>> +}\n>>> +\n>>> +void eGL::destroyDMABufTexture(eGLImage *eglImage)\n>>> +{\n>>> +\teglDestroyImage(display_, eglImage->image_);\n>>> +}\n>>> +\n>>> +// Generate a 2D texture from an input buffer directly\n>>> +void eGL::createTexture2D(eGLImage *eglImage, GLint format, uint32_t width, uint32_t height, void\n>>> *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>>> +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>>> + glEGLImageTargetTexture2DOES =\n>>> (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress(\"glEGLImageTargetTexture2DOES\");\n>>> +\tif (!glEGLImageTargetTexture2DOES) {\n>>> +\t\tLOG(eGL, Error) << \"glEGLImageTargetTexture2DOES not found\";\n>>> +\t\tgoto fail;\n>>> +\t}\n>>> +\n>>> +\teglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress(\"eglCreateSyncKHR\");\n>>> +\tif (!eglCreateSyncKHR) {\n>>> +\t\tLOG(eGL, Error) << \"eglCreateSyncKHR not found\";\n>>> +\t\tgoto fail;\n>>> +\t}\n>>> +\n>>> +\teglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC)eglGetProcAddress(\"eglDestroySyncKHR\");\n>>> +\tif (!eglDestroySyncKHR) {\n>>> +\t\tLOG(eGL, Error) << \"eglDestroySyncKHR not found\";\n>>> +\t\tgoto fail;\n>>> +\t}\n>>> +\n>>> +\teglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress(\"eglClientWaitSyncKHR\");\n>>> +\tif (!eglClientWaitSyncKHR) {\n>>> +\t\tLOG(eGL, Error) << \"eglClientWaitSyncKHR 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>>> +\tsync_ = eglCreateSyncKHR(display_, EGL_SYNC_FENCE_KHR, NULL);\n>>\n>> Could you please elaborate a bit on the purpose and handling of `sync_'?\n>> I'm pretty ignorant about GL but I was exploring a problem with reported\n>> per-frame times and I also observed some tearing with v4e and I've hit\n>> this.\n>>\n>> According to\n>> https://registry.khronos.org/EGL/sdk/docs/man/html/eglCreateSync.xhtml\n>> (it's sans-KHR but I suppose it's basically the same?):\n>>\n>>    When a fence sync object is created, eglCreateSync also inserts a\n>>    fence command into the command stream of the bound client API's\n>>    current context (i.e., the context returned by eglGetCurrentContext),\n>>    and associates it with the newly created sync object.\n>>\n>>    The only condition supported for fence sync objects is\n>>    EGL_SYNC_PRIOR_COMMANDS_COMPLETE, which is satisfied by completion of\n>>    the fence command corresponding to the sync object, and all preceding\n>>    commands in the associated client API context's command stream. The\n>>    sync object will not be signaled until all effects from these commands\n>>    on the client API's internal and framebuffer state are fully\n>>    realized. No other state is affected by execution of the fence\n>>    command.\n>>\n>> As I understand it, calling eglCreateSyncKHR inserts a fence at a given\n>> GPU processing place and then eglClientWaitSyncKHR waits for completion\n>> of everything before the fence.\n>>\n>> But here it seems that `sync_' is assigned only during configuration so\n>> I don't get its purpose and the purpose of eglClientWaitSyncKHR call in\n>> DebayerEGL::debayerGPU per each frame.  When I insert calls to\n>> eglCreateSyncKHR + eglClientWaitSyncKHR + eglDestroySyncKHR all together\n>> after glDrawArrays in DebayerEGL::debayerGPU, both my time measurement\n>> problem and the image tearing disappear.  Which may possibly be just a\n>> workaround for a different synchronization problem but then I still\n>> don't understand what `sync_' is about.\n> \n> As discussed at the meeting and suggested by Robert, I tested replacing\n> sync_ use with glFinish call.  It works for me.  The corresponding patch\n> is available here:\n> https://gitlab.freedesktop.org/camera/libcamera-softisp/-/commits/pdm-gpu-sync?ref_type=heads\n> \n> There are three additional patches on top of it, fixing various formal\n> issues.  If possible, please use them too.\nDone","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 23041C3260\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 27 Nov 2025 01:46:56 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 16C1360A9E;\n\tThu, 27 Nov 2025 02:46:55 +0100 (CET)","from mail-wm1-x32a.google.com (mail-wm1-x32a.google.com\n\t[IPv6:2a00:1450:4864:20::32a])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 57378609D8\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 27 Nov 2025 02:46:53 +0100 (CET)","by mail-wm1-x32a.google.com with SMTP id\n\t5b1f17b1804b1-47118259fd8so2079475e9.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 26 Nov 2025 17:46:53 -0800 (PST)","from [192.168.0.27] (188-141-3-146.dynamic.upc.ie. [188.141.3.146])\n\tby smtp.gmail.com with ESMTPSA id\n\tffacd0b85a97d-42e1ca1a3e4sm447594f8f.25.2025.11.26.17.46.51\n\t(version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);\n\tWed, 26 Nov 2025 17:46:52 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=linaro.org header.i=@linaro.org\n\theader.b=\"Hu4ZrdLL\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=linaro.org; s=google; t=1764208013; x=1764812813;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:in-reply-to:content-language:from\n\t:references:cc:to:subject:user-agent:mime-version:date:message-id\n\t:from:to:cc:subject:date:message-id:reply-to;\n\tbh=+82UFUKAvRFjR+pV1mR5N7aC+C3O+79IhC6xlTE0fcs=;\n\tb=Hu4ZrdLLEhhxZzI4P3AWWxRNYj+rwfk2Tje098Ce4rbJMb8skQdTTNnbQzZNqFIK1r\n\tlbuXlzKHx8gwlqzaiyEMihJ364MQDlafUZXle34FOyW8iUc4oz+8pFA32PlLroDckLFx\n\tl5+0m92/gbqN+Fq4DC5JwSXHW2GI5KIPEUENJu124myqNp78OLnxTAHwz3wWlmCHAHc5\n\tVm24Aj2AiK+YgTU/Zz84QmqM28KYChPCvuiWfbRiT36V5jnGH0O2hcZ/RVR2eMwbyJQH\n\tYbRh5uM4ginA6Aw6CB5KOLWaNQLKPtzPTncg6eeC1GU1W9/+QkAC0oNi31YdOhpGvEJZ\n\taCPQ==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1764208013; x=1764812813;\n\th=content-transfer-encoding:in-reply-to:content-language:from\n\t:references:cc:to:subject:user-agent:mime-version:date:message-id\n\t:x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id\n\t:reply-to;\n\tbh=+82UFUKAvRFjR+pV1mR5N7aC+C3O+79IhC6xlTE0fcs=;\n\tb=SM+a3lWLU4KcFk8TnAqOS1gSjSv9rIzf0PvgBDsOP7jncNQZWJikYnxxNIMH/al8HB\n\tlncD4IpGzhtuqtpzcLDHvbBgSMCtKgE9mDnwdsXUN2MLK6q1SQ2DecW2vHeYlabP/QF9\n\tJKGbHBe/9CGRFnTomc6KpZz8tq7RwQa2J45a2QBjEKv5NMMAMcUOvvvHpagLgejnIs2C\n\t9dvPx4xNzYbz2T+7DFk9NKt8v2AJ0XTtgEDHWWV+76xujS20nvtckkZJE0yWyc22bfhO\n\ts1bQpOrBm2qVvBdcJl/49qpVLElDUk1/X42WUayqmUGyOkyiYc/yzsWVjs2h3rDFR3lg\n\t3v2A==","X-Gm-Message-State":"AOJu0YzMoVKi2jQIwwhku1jfkngBerri5rkpFeB6kqULBpe9vgr+9XLM\n\t9Y51yRu0XkiGsL0Dogz6XAFlvNOTFOF+h+NOCVJRU/7toSHNSIVLfqMvnqXs3zdHq0g=","X-Gm-Gg":"ASbGnct5u2AwunwackHN2i1l3o6ZoBpO22/t7JzrzKXZytQSaj+S42NfJZJxi219GUm\n\tq2nuYjr+qIE2FeCSPpCm2sj4qN5vtooFV8PuFBure0hrHw9oJfcZP/3iSQ4M5K7CBb/g2Z4/18x\n\tFa6xEhDyo54ioi+x9xPGDBHGq5bN83fEjEvQKzF8fbQLxRSoKTMXVeBqt/bpaxW89eyVPzzune1\n\txm7MxI1/dawVUma7ht0rjf/ycBo3l1cEpT4la70+xRaiZ88KXa5L+cOl0DTD76CMVnw1hlxNsw9\n\tRYf7wEFVeHlRolo7X7kJMJpJUmiN+cAg7Yo2ywvcd1Se93j18vX56zhvJhui4CkbQmUH8gsoA9s\n\t6G9ITUl7yexhHugli/eka7NPMwZX8Zg9FnIXTT81VzW5lUTyxeyDmrQy1sVePHsiv4tCaoIK1dG\n\tOiIwJg3qxNwEbIQJj79NaxLF4HPaYL+b8eY5D1Jpi4TlpKpGxM/dwf","X-Google-Smtp-Source":"AGHT+IF/ZrjpM4bzfW4iSg+58PBaPZ7hwrk4n9HXjvCPNs4fu6LBoWgCa3ftAXn5JcAWOVTBWldaOw==","X-Received":"by 2002:a05:600c:460a:b0:471:700:f281 with SMTP id\n\t5b1f17b1804b1-47904b1b2dfmr50564015e9.25.1764208012671; \n\tWed, 26 Nov 2025 17:46:52 -0800 (PST)","Message-ID":"<be383b9a-fbac-46e7-a078-a88274d117e3@linaro.org>","Date":"Thu, 27 Nov 2025 01:46:51 +0000","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v4 03/23] libcamera: software_isp: egl: Add a eGL base\n\thelper class","To":"Milan Zamazal <mzamazal@redhat.com>","Cc":"libcamera-devel@lists.libcamera.org, pavel@ucw.cz,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>,\n\tRobert Mader <robert.mader@collabora.com>","References":"<20251120233347.5046-1-bryan.odonoghue@linaro.org>\n\t<20251120233347.5046-4-bryan.odonoghue@linaro.org>\n\t<85ikexstor.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>\n\t<85ms49vtzr.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","From":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","Content-Language":"en-US","In-Reply-To":"<85ms49vtzr.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","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>"}}]