[{"id":36281,"web_url":"https://patchwork.libcamera.org/comment/36281/","msgid":"<176056987244.635357.439082412023106643@ping.linuxembedded.co.uk>","date":"2025-10-15T23:11:12","subject":"Re: [PATCH v3 26/39] libcamera: software_isp: egl: Introduce an eGL\n\tbase helper class","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Bryan O'Donoghue (2025-10-15 02:22:38)\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\nI'd put that comment after the --- rather than in the commit directly.\n\nIn this file - I don't think I can comment much on the egl\nimplementation - and this is a new file - that we want to get in and\nstart building on top of so already perhaps more of an \n\n\nAcked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\nstylistic review comments below - which if you can tackle would save a\npatch on top, but I wouldn't block on those if it's getting past CI.\n\nlibcamera coding style avoids // though - so that cleanup would be\nappreciated below.\n\n\n\n> \n> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> ---\n>  include/libcamera/internal/egl.h | 162 ++++++++++++\n>  src/libcamera/egl.cpp            | 435 +++++++++++++++++++++++++++++++\n>  src/libcamera/meson.build        |  23 ++\n>  3 files changed, 620 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 00000000..3021578b\n> --- /dev/null\n> +++ b/include/libcamera/internal/egl.h\n> @@ -0,0 +1,162 @@\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> + * \\brief Helper class for an eGLImage.\n> + *\n> + * There are a few handles, descriptors and routines that it makes sense to\n> + * aggregate together. For example generating a GL texture and\n> + * frame-buffer-object and being about to assocate that with the\n> + * texture_uniform_id for a given shader.\n> + *\n\nThat empty line can go\n\n> + */\n> +class eGLImage\n> +{\n> +public:\n> +       eGLImage(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t texture_unit_uniform_id)\n> +       {\n> +               init(width, height, bpp, stride, texture_unit, texture_unit_uniform_id);\n> +       }\n> +\n> +       eGLImage(uint32_t width, uint32_t height, uint32_t bpp, GLenum texture_unit, uint32_t texture_unit_uniform_id)\n> +       {\n> +               uint32_t stride = libcamera::utils::alignUp(width * bpp, 256);\n> +\n> +               init(width, height, bpp, stride, texture_unit, texture_unit_uniform_id);\n> +       }\n> +\n> +       ~eGLImage()\n> +       {\n> +               glDeleteFramebuffers(1, &fbo_);\n> +               glDeleteTextures(1, &texture_);\n> +       }\n> +\n> +       uint32_t width_;\n> +       uint32_t height_;\n> +       uint32_t stride_;\n> +       uint32_t offset_;\n> +       uint32_t framesize_;\n> +       uint32_t bpp_;\n> +       uint32_t texture_unit_uniform_id_;\n> +       GLenum texture_unit_;\n> +       GLuint texture_;\n> +       GLuint fbo_;\n> +       EGLImageKHR image_;\n> +\n> +private:\n> +       LIBCAMERA_DISABLE_COPY_AND_MOVE(eGLImage)\n> +\n> +       void init(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t texture_unit_uniform_id)\n> +       {\n> +               image_ = EGL_NO_IMAGE_KHR;\n> +               width_ = width;\n> +               height_ = height;\n> +               bpp_ = bpp;\n> +               stride_ = stride;\n> +               framesize_ = stride_ * height_;\n> +               texture_unit_ = texture_unit;\n> +               texture_unit_uniform_id_ = texture_unit_uniform_id;\n> +\n> +               glGenTextures(1, &texture_);\n> +               glGenFramebuffers(1, &fbo_);\n> +       }\n> +};\n> +\n> +/**\n> + * \\brief The main eGL object\n> + *\n> + * This class buries away eGL specific setup and manipulation.\n> + *\n> + * Setting up the eGL context.\n> + * Getting function pointers.\n> + * Generating an eGL program from environment variables.\n> + * Link that program.\n> + * Generating textures for upload.\n> + * Render to texture for output.\n> + *\n> + */\n> +class eGL\n> +{\n> +public:\n> +       eGL();\n> +       ~eGL();\n> +\n> +       int initEGLContext(GBM *gbmContext);\n> +       void cleanUp();\n> +       int createInputDMABufTexture2D(eGLImage *eglImage, int fd);\n> +       int createOutputDMABufTexture2D(eGLImage *eglImage, int fd);\n> +       void destroyDMABufTexture(eGLImage *eglImage);\n> +       void createTexture2D(eGLImage *eglImage, GLint format, uint32_t width, uint32_t height, void *data);\n> +       void createTexture1D(eGLImage *eglImage, GLint format, uint32_t width, void *data);\n> +\n> +       void pushEnv(std::vector<std::string> &shaderEnv, const char *str);\n> +       void makeCurrent();\n> +\n> +       int compileVertexShader(GLuint &shaderId, unsigned char *shaderData,\n> +                               unsigned int shaderDataLen,\n> +                               Span<const std::string> shaderEnv);\n> +       int compileFragmentShader(GLuint &shaderId, unsigned char *shaderData,\n> +                                 unsigned int shaderDataLen,\n> +                                 Span<const std::string> shaderEnv);\n> +       int linkProgram(GLuint &programId, GLuint fragmentshaderId, GLuint vertexshaderId);\n> +       void dumpShaderSource(GLuint shaderId);\n> +       void useProgram(GLuint programId);\n> +       void deleteProgram(GLuint programId);\n> +       int syncOutput();\n> +\n> +private:\n> +       LIBCAMERA_DISABLE_COPY_AND_MOVE(eGL)\n> +\n> +       int fd_;\n> +       pid_t tid_;\n> +\n> +       EGLDisplay display_;\n> +       EGLContext context_;\n> +       EGLSurface surface_;\n> +       EGLSyncKHR sync_;\n> +\n> +       int compileShader(int shaderType, GLuint &shaderId, unsigned char *shaderData,\n> +                         unsigned int shaderDataLen,\n> +                         Span<const std::string> shaderEnv);\n> +\n> +       int createDMABufTexture2D(eGLImage *eglImage, int fd, bool output);\n> +\n> +       PFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA;\n> +       PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;\n> +\n> +       PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;\n> +       PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;\n> +\n> +       PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;\n> +       PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR;\n> +       PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;\n> +};\n> +} //namespace libcamera\n\n\n} /* namespace libcamera */\nto match all the other instances.\n\nCould you do a quick sweep on this file to convert // to /* \n\n\n> diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp\n> new file mode 100644\n> index 00000000..d5cb2df5\n> --- /dev/null\n> +++ b/src/libcamera/egl.cpp\n> @@ -0,0 +1,435 @@\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> +       context_ = EGL_NO_CONTEXT;\n> +       surface_ = EGL_NO_SURFACE;\n> +       display_ = EGL_NO_DISPLAY;\n> +}\n> +\n> +eGL::~eGL()\n> +{\n> +       if (context_ != EGL_NO_CONTEXT)\n> +               eglDestroyContext(display_, context_);\n> +\n> +       if (surface_ != EGL_NO_SURFACE)\n> +               eglDestroySurface(display_, surface_);\n> +\n> +}\n> +\n> +int eGL::syncOutput(void)\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       glFlush();\n> +       eglClientWaitSyncKHR(display_, sync_, 0, EGL_FOREVER_KHR);\n> +\n> +       return 0;\n> +}\n> +\n> +// Create linear image attached to previous BO object\n> +int eGL::createDMABufTexture2D(eGLImage *eglImage, int fd, bool output)\n> +{\n> +       int ret = 0;\n> +\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       // clang-format off\n\nAck.\n\n> +       EGLint image_attrs[] = {\n> +               EGL_WIDTH, (EGLint)eglImage->width_,\n> +               EGL_HEIGHT, (EGLint)eglImage->height_,\n> +               EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_ARGB8888,\n> +               EGL_DMA_BUF_PLANE0_FD_EXT, fd,\n> +               EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,\n> +               EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)eglImage->stride_,\n> +               EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, 0,\n> +               EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, 0,\n> +               EGL_NONE,\n> +       };\n> +       // clang-format on\n> +\n> +       eglImage->image_ = eglCreateImageKHR(display_, EGL_NO_CONTEXT,\n> +                                            EGL_LINUX_DMA_BUF_EXT,\n> +                                            NULL, image_attrs);\n> +\n> +       if (eglImage->image_ == EGL_NO_IMAGE_KHR) {\n> +               LOG(eGL, Error) << \"eglCreateImageKHR fail\";\n> +               ret = -ENODEV;\n> +               goto done;\n> +       }\n> +\n> +       // Bind texture unit and texture\n> +       glActiveTexture(eglImage->texture_unit_);\n> +       glBindTexture(GL_TEXTURE_2D, eglImage->texture_);\n> +\n> +       // Generate texture with filter semantics\n> +       glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage->image_);\n> +\n> +       // Nearest filtering\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n> +\n> +       // Wrap to edge to avoid edge artifacts\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n> +\n> +       if (output) {\n> +               // Generate a framebuffer from our texture direct to dma-buf handle buffer\n> +               glBindFramebuffer(GL_FRAMEBUFFER, eglImage->fbo_);\n> +               glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, eglImage->texture_, 0);\n> +\n> +               GLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n> +               if (err!= GL_FRAMEBUFFER_COMPLETE) {\n> +                       LOG(eGL, Error) << \"glFrameBufferTexture2D error \" << err;\n> +                       ret = -ENODEV;\n> +                       goto done;\n> +               }\n> +       }\n> +done:\n> +       return ret;\n> +}\n> +\n> +int eGL::createInputDMABufTexture2D(eGLImage *eglImage, int fd)\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       return createDMABufTexture2D(eglImage, fd, false);\n> +}\n> +int eGL::createOutputDMABufTexture2D(eGLImage *eglImage, int fd)\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       return createDMABufTexture2D(eglImage, fd, true);\n> +}\n> +\n> +void eGL::destroyDMABufTexture(eGLImage *eglImage)\n> +{\n> +       eglDestroyImage(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> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       glActiveTexture(eglImage->texture_unit_);\n> +       glBindTexture(GL_TEXTURE_2D, eglImage->texture_);\n> +\n> +       // Generate texture, bind, associate image to texture, configure, unbind\n> +       glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);\n> +\n> +       // Nearest filtering\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n> +\n> +       // Wrap to edge to avoid edge artifacts\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n> +       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n> +}\n> +\n> +int eGL::initEGLContext(GBM *gbmContext)\n> +{\n> +       EGLint configAttribs[] = {\n> +               EGL_RED_SIZE, 8,\n> +               EGL_GREEN_SIZE, 8,\n> +               EGL_BLUE_SIZE, 8,\n> +               EGL_ALPHA_SIZE, 8,\n> +               EGL_SURFACE_TYPE, EGL_WINDOW_BIT,\n> +               EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,\n> +               EGL_NONE\n> +       };\n> +\n> +       EGLint contextAttribs[] = {\n> +               EGL_CONTEXT_MAJOR_VERSION, 2,\n> +               EGL_NONE\n> +       };\n> +\n> +       EGLint numConfigs;\n> +       EGLConfig config;\n> +       EGLint major;\n> +       EGLint minor;\n> +\n> +       if (!eglBindAPI(EGL_OPENGL_ES_API)) {\n> +               LOG(eGL, Error) << \"API bind fail\";\n> +               goto fail;\n> +       }\n> +\n> +       display_ = eglGetDisplay(gbmContext->getDevice());\n> +       if (display_ == EGL_NO_DISPLAY) {\n> +               LOG(eGL, Error) << \"Unable to get EGL display\";\n> +               goto fail;\n> +       }\n> +\n> +       if (eglInitialize(display_, &major, &minor) != EGL_TRUE) {\n> +               LOG(eGL, Error) << \"eglInitialize fail\";\n> +               goto fail;\n> +       }\n> +\n> +       LOG(eGL, Info) << \"EGL: version \" << major << \".\" << minor;\n> +       LOG(eGL, Info) << \"EGL: EGL_VERSION: \" << eglQueryString(display_, EGL_VERSION);\n> +       LOG(eGL, Info) << \"EGL: EGL_VENDOR: \" << eglQueryString(display_, EGL_VENDOR);\n> +       LOG(eGL, Info) << \"EGL: EGL_CLIENT_APIS: \" << eglQueryString(display_, EGL_CLIENT_APIS);\n> +       LOG(eGL, Info) << \"EGL: EGL_EXTENSIONS: \" << eglQueryString(display_, EGL_EXTENSIONS);\n\nSometime we might want to lower this level - but I think it's fine now.\n\nAck\n\n> +\n> +       eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress(\"eglCreateImageKHR\");\n> +       if (!eglCreateImageKHR) {\n> +               LOG(eGL, Error) << \"eglCreateImageKHR not found\";\n> +               goto fail;\n> +       }\n> +\n> +       eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress(\"eglDestroyImageKHR\");\n> +       if (!eglDestroyImageKHR) {\n> +               LOG(eGL, Error) << \"eglDestroyImageKHR not found\";\n> +               goto fail;\n> +       }\n> +\n> +       glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress(\"glEGLImageTargetTexture2DOES\");\n\nWOW_WHATADELIGHTFULAPI(\"soEasyOnTheEyes\")\n\nGlad this all gets wrapped into a class for us here :D\n\n> +       if (!glEGLImageTargetTexture2DOES) {\n> +               LOG(eGL, Error) << \"glEGLImageTargetTexture2DOES not found\";\n> +               goto fail;\n> +       }\n> +\n> +       eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress(\"eglCreateSyncKHR\");\n> +       if (!eglCreateSyncKHR) {\n> +               LOG(eGL, Error) << \"eglCreateSyncKHR not found\";\n> +               goto fail;\n> +       }\n> +\n> +       eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC)eglGetProcAddress(\"eglDestroySyncKHR\");\n> +       if (!eglDestroySyncKHR) {\n> +               LOG(eGL, Error) << \"eglDestroySyncKHR not found\";\n> +               goto fail;\n> +       }\n> +\n> +       eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress(\"eglClientWaitSyncKHR\");\n> +       if (!eglClientWaitSyncKHR) {\n> +               LOG(eGL, Error) << \"eglClientWaitSyncKHR not found\";\n> +               goto fail;\n> +       }\n> +\n> +       if (eglChooseConfig(display_, configAttribs, &config, 1, &numConfigs) != EGL_TRUE) {\n> +               LOG(eGL, Error) << \"eglChooseConfig fail\";\n> +               goto fail;\n> +       }\n> +\n> +       context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT, contextAttribs);\n> +       if (context_ == EGL_NO_CONTEXT) {\n> +               LOG(eGL, Error) << \"eglContext returned EGL_NO_CONTEXT\";\n> +               goto fail;\n> +       }\n> +\n> +       tid_ = Thread::currentId();\n> +\n> +       makeCurrent();\n> +\n> +       sync_ = eglCreateSyncKHR(display_, EGL_SYNC_FENCE_KHR, NULL);\n> +       if (sync_ == EGL_NO_SYNC_KHR) {\n> +               LOG(eGL, Error) << \"eglCreateSyncKHR fail\";\n> +               goto fail;\n> +       }\n> +\n> +       return 0;\n> +fail:\n\n> +       return -ENODEV;\n> +}\n> +\n> +void eGL::cleanUp(void)\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       if (sync_) {\n> +               makeCurrent();\n> +               eglDestroySyncKHR(display_, sync_);\n> +       }\n> +\n> +}\n> +\n> +void eGL::makeCurrent(void)\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       if (eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, context_) != EGL_TRUE) {\n> +               LOG(eGL, Error) << \"eglMakeCurrent fail\";\n> +       }\n> +}\n> +\n> +void eGL::useProgram(GLuint programId)\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       glUseProgram(programId);\n> +}\n> +\n> +void eGL::deleteProgram(GLuint programId)\n> +{\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       glDeleteProgram(programId);\n> +}\n> +\n> +void eGL::pushEnv(std::vector<std::string> &shaderEnv, const char *str)\n> +{\n> +       std::string addStr = str;\n> +\n> +       addStr.push_back('\\n');\n> +       shaderEnv.push_back(std::move(addStr));\n> +}\n> +\n> +int eGL::compileVertexShader(GLuint &shaderId, unsigned char *shaderData,\n> +                            unsigned int shaderDataLen,\n> +                            Span<const std::string> shaderEnv)\n> +{\n> +       return compileShader(GL_VERTEX_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv);\n> +}\n> +\n> +int eGL::compileFragmentShader(GLuint &shaderId, unsigned char *shaderData,\n> +                              unsigned int shaderDataLen,\n> +                              Span<const std::string> shaderEnv)\n> +{\n> +       return compileShader(GL_FRAGMENT_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv);\n> +}\n> +\n> +int eGL::compileShader(int shaderType, GLuint &shaderId, unsigned char *shaderData,\n> +                      unsigned int shaderDataLen,\n> +                      Span<const std::string> shaderEnv)\n> +{\n> +       GLint success;\n> +       size_t i;\n> +\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       auto count = 1 + shaderEnv.size();\n> +       auto shaderSourceData = std::make_unique<const GLchar*[]>(count);\n> +       auto shaderDataLengths = std::make_unique<GLint[]>(count);\n> +\n> +       // Prefix defines before main body of shader\n> +       for (i = 0; i < shaderEnv.size(); i++) {\n> +               shaderSourceData[i] = shaderEnv[i].c_str();\n> +               shaderDataLengths[i] = shaderEnv[i].length();\n> +       }\n> +\n> +       // Now the main body of the shader program\n> +       shaderSourceData[i] = reinterpret_cast<const GLchar*>(shaderData);\n> +       shaderDataLengths[i] = shaderDataLen;\n> +\n> +       // And create the shader\n> +       shaderId = glCreateShader(shaderType);\n> +       glShaderSource(shaderId, count, shaderSourceData.get(), shaderDataLengths.get());\n> +       glCompileShader(shaderId);\n> +\n> +       // Check status\n> +       glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success);\n> +       if (success == GL_FALSE) {\n> +               GLint sizeLog = 0;\n> +\n> +               glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &sizeLog);\n> +               auto infoLog = std::make_unique<GLchar[]>(sizeLog);\n> +\n> +               glGetShaderInfoLog(shaderId, sizeLog, &sizeLog, infoLog.get());\n> +               LOG(eGL, Error) << infoLog.get();\n> +       }\n> +\n> +       return !(success == GL_TRUE);\n> +}\n> +\n> +void eGL::dumpShaderSource(GLuint shaderId)\n> +{\n> +       GLint shaderLength = 0;\n> +\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       glGetShaderiv(shaderId, GL_SHADER_SOURCE_LENGTH, &shaderLength);\n> +\n> +       LOG(eGL, Debug) << \"Shader length is \" << shaderLength;\n> +\n> +       if (shaderLength > 0) {\n> +               auto shaderSource = std::make_unique<GLchar []>(shaderLength);\n> +\n> +               glGetShaderSource(shaderId, shaderLength, &shaderLength, shaderSource.get());\n> +               if (shaderLength) {\n> +                       LOG(eGL, Debug) << \"Shader source = \" << shaderSource.get();\n> +               }\n> +       }\n> +}\n> +\n> +int eGL::linkProgram(GLuint &programId, GLuint vertexshaderId, GLuint fragmentshaderId)\n> +{\n> +       GLint success;\n> +       GLenum err;\n> +\n> +       ASSERT(tid_ == Thread::currentId());\n> +\n> +       programId = glCreateProgram();\n> +       if (!programId)\n> +               goto fail;\n> +\n> +       glAttachShader(programId, vertexshaderId);\n> +       if ((err = glGetError()) != GL_NO_ERROR) {\n> +               LOG(eGL, Error) << \"Attach compute vertex shader fail\";\n> +               goto fail;\n> +       }\n> +\n> +       glAttachShader(programId, fragmentshaderId);\n> +       if ((err = glGetError()) != GL_NO_ERROR) {\n> +               LOG(eGL, Error) << \"Attach compute vertex shader fail\";\n> +               goto fail;\n> +       }\n> +\n> +       glLinkProgram(programId);\n> +       if ((err = glGetError()) != GL_NO_ERROR) {\n> +               LOG(eGL, Error) << \"Link program fail\";\n> +               goto fail;\n> +       }\n> +\n> +       glDetachShader(programId, fragmentshaderId);\n> +       glDetachShader(programId, vertexshaderId);\n> +\n> +       // Check status\n> +       glGetProgramiv(programId, GL_LINK_STATUS, &success);\n> +       if (success == GL_FALSE) {\n> +               GLint sizeLog = 0;\n> +               GLchar *infoLog;\n> +\n> +               glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &sizeLog);\n> +               infoLog = new GLchar[sizeLog];\n> +\n> +               glGetProgramInfoLog(programId, sizeLog, &sizeLog, infoLog);\n> +               LOG(eGL, Error) << infoLog;\n> +\n> +               delete[] infoLog;\n> +               goto fail;\n> +       }\n> +\n> +       return 0;\n> +fail:\n> +       if (programId)\n> +               glDeleteProgram(programId);\n> +\n> +       return -ENODEV;\n> +}\n> +} // namespace libcamera\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index fe60c875..b7be38a8 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\nIdeally this patch would update README.rst with the new dependency\nrequirements too - but that can be a patch on top or separate too.\n\n\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,\n> -- \n> 2.51.0\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 10B64C3259\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 15 Oct 2025 23:11:18 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4486560643;\n\tThu, 16 Oct 2025 01:11:17 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E2625605F3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 16 Oct 2025 01:11:14 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 7046CC72;\n\tThu, 16 Oct 2025 01:09:35 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"ATL8pV9H\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1760569775;\n\tbh=fTbXyt/wyNnW6rEWyW0kplhKLUWQqUxYLNbdWKwLSqk=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=ATL8pV9HBtXQcI14GHaPKE+eoDb69/SYZ0vtHgZgxvVKhojnTkFx4+bY50aU7q7Mm\n\tH03jyJHdoxt7jQ/7rF+vRV5znwtZ4dssbPGkXE5+1Daqx8PWxA1roP2o090zh/VOKn\n\t563O5HGEinPdgbFcbhKCCxwlkcYcp+BtZxOHQwxA=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20251015012251.17508-27-bryan.odonoghue@linaro.org>","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-27-bryan.odonoghue@linaro.org>","Subject":"Re: [PATCH v3 26/39] libcamera: software_isp: egl: Introduce an eGL\n\tbase helper class","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"hdegoede@redhat.com, mzamazal@redhat.com, bryan.odonoghue@linaro.org,\n\tbod.linux@nxsw.ie","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Thu, 16 Oct 2025 00:11:12 +0100","Message-ID":"<176056987244.635357.439082412023106643@ping.linuxembedded.co.uk>","User-Agent":"alot/0.9.1","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":36314,"web_url":"https://patchwork.libcamera.org/comment/36314/","msgid":"<85bjm7dntk.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2025-10-16T12:37:59","subject":"Re: [PATCH v3 26/39] libcamera: software_isp: egl: Introduce an eGL\n\tbase helper class","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Hi Bryan,\n\nin addition to Kieran's comments:\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> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> ---\n>  include/libcamera/internal/egl.h | 162 ++++++++++++\n>  src/libcamera/egl.cpp            | 435 +++++++++++++++++++++++++++++++\n>  src/libcamera/meson.build        |  23 ++\n>  3 files changed, 620 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 00000000..3021578b\n> --- /dev/null\n> +++ b/include/libcamera/internal/egl.h\n> @@ -0,0 +1,162 @@\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\nNo file name (even correct one :-)) in the comment.\n\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\n#include <libcamera/base/utils.h>\n\n> +#include \"libcamera/internal/gbm.h\"\n> +\n> +#define EGL_EGLEXT_PROTOTYPES\n> +#include <EGL/egl.h>\n> +#include <EGL/eglext.h>\n> +#define GL_GLEXT_PROTOTYPES\n> +#include <GLES2/gl2.h>\n> +#include <GLES2/gl2ext.h>\n> +\n> +namespace libcamera {\n> +\n> +LOG_DECLARE_CATEGORY(eGL)\n> +\n> +/**\n> + * \\brief Helper class for an eGLImage.\n> + *\n> + * There are a few handles, descriptors and routines that it makes sense to\n> + * aggregate together. For example generating a GL texture and\n> + * frame-buffer-object and being about to assocate that with the\n\ns/assocate/associate/\n\n> + * texture_uniform_id for a given shader.\n> + *\n> + */\n> +class eGLImage\n> +{\n> +public:\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> +\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~eGLImage()\n> +\t{\n> +\t\tglDeleteFramebuffers(1, &fbo_);\n> +\t\tglDeleteTextures(1, &texture_);\n> +\t}\n> +\n> +\tuint32_t width_;\n> +\tuint32_t height_;\n> +\tuint32_t stride_;\n> +\tuint32_t offset_;\n> +\tuint32_t framesize_;\n> +\tuint32_t bpp_;\n> +\tuint32_t texture_unit_uniform_id_;\n> +\tGLenum texture_unit_;\n\ncamelCase (all the related identifiers).\n\n> +\tGLuint texture_;\n> +\tGLuint fbo_;\n> +\tEGLImageKHR image_;\n> +\n> +private:\n> +\tLIBCAMERA_DISABLE_COPY_AND_MOVE(eGLImage)\n> +\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> + * \\brief The main eGL object\n> + *\n> + * This class buries away eGL specific setup and manipulation.\n> + *\n> + * Setting up the eGL context.\n> + * Getting function pointers.\n> + * Generating an eGL program from environment variables.\n> + * Link that program.\n> + * Generating textures for upload.\n> + * Render to texture for output.\n> + *\n> + */\n> +class eGL\n> +{\n> +public:\n> +\teGL();\n> +\t~eGL();\n> +\n> +\tint initEGLContext(GBM *gbmContext);\n> +\tvoid cleanUp();\n> +\tint createInputDMABufTexture2D(eGLImage *eglImage, int fd);\n> +\tint createOutputDMABufTexture2D(eGLImage *eglImage, int fd);\n> +\tvoid destroyDMABufTexture(eGLImage *eglImage);\n> +\tvoid createTexture2D(eGLImage *eglImage, GLint format, uint32_t width, uint32_t height, void *data);\n> +\tvoid createTexture1D(eGLImage *eglImage, GLint format, uint32_t width, void *data);\n> +\n> +\tvoid pushEnv(std::vector<std::string> &shaderEnv, const char *str);\n> +\tvoid makeCurrent();\n> +\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> +\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> +\tint linkProgram(GLuint &programId, GLuint fragmentshaderId, GLuint vertexshaderId);\n\nThe shader arguments are swapped compared to the definition.\n\n> +\tvoid dumpShaderSource(GLuint shaderId);\n> +\tvoid useProgram(GLuint programId);\n> +\tvoid deleteProgram(GLuint programId);\n> +\tint syncOutput();\n> +\n> +private:\n> +\tLIBCAMERA_DISABLE_COPY_AND_MOVE(eGL)\n> +\n> +\tint fd_;\n> +\tpid_t tid_;\n> +\n> +\tEGLDisplay display_;\n> +\tEGLContext context_;\n> +\tEGLSurface surface_;\n> +\tEGLSyncKHR sync_;\n> +\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> +\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 00000000..d5cb2df5\n> --- /dev/null\n> +++ b/src/libcamera/egl.cpp\n> @@ -0,0 +1,435 @@\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\nNo file name.\n\n> + */\n> +\n> +#include \"libcamera/base/thread.h\"\n\n<libcamera/base/thread.h>\n\n> +#include \"libcamera/internal/egl.h\"\n> +\n> +#include <fcntl.h>\n> +#include <sys/ioctl.h>\n> +#include <sys/mman.h>\n> +#include <unistd.h>\n> +\n> +#include <libdrm/drm_fourcc.h>\n\nThe order of the includes should be:\n\n#include <linux/dma-buf.h>\n#include <linux/dma-heap.h>\n\n#include <libcamera/base/thread.h>\n\n#include <libdrm/drm_fourcc.h>\n\n(according to .clang-format I think).\n\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\nThe blank line should be removed.\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\n/* ... */\n\n(Also other comments below.)\n\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\nstatic_cast<EGLint>(...)\n\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\nMissing space before `!='.\n\n> +\t\t\tLOG(eGL, Error) << \"glFrameBufferTexture2D error \" << err;\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> +\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\nThe blank line should be removed.\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\nMissing space before `*'.\n\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\nMissing space before `*'.\n\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);\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\nExtra space before `[]'.\n\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\ns/vertex/fragment/\n\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 fe60c875..b7be38a8 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 0B61AC3259\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 16 Oct 2025 12:38:09 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 1D28F6067D;\n\tThu, 16 Oct 2025 14:38:08 +0200 (CEST)","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 05B3A605D7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 16 Oct 2025 14:38:05 +0200 (CEST)","from mail-ed1-f71.google.com (mail-ed1-f71.google.com\n\t[209.85.208.71]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-554-A-FaajVJNPyYbAFyKraWEQ-1; Thu, 16 Oct 2025 08:38:03 -0400","by mail-ed1-f71.google.com with SMTP id\n\t4fb4d7f45d1cf-62fcb646334so968822a12.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 16 Oct 2025 05:38:03 -0700 (PDT)","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\t4fb4d7f45d1cf-63bdfec82ebsm5292888a12.24.2025.10.16.05.38.00\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tThu, 16 Oct 2025 05:38:00 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"GgDs4Mql\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1760618284;\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=Pvmp5V4OSVS3e7lAX3RrRC2w+8YrTGFt+dT0YMqsYh0=;\n\tb=GgDs4MqlFqsR0KpoQ6LTcJT2edOX2bGhZRmNgE3uB+vDDeyiTG7eJYgE24F2gsMvqh8BsC\n\teUUsTTBWlYUkLZTNnGIMmUxpysAcb2iPwa8bBa21ZkDpXJ9BdQp/oA0Kn/PYgPZqTXNlnv\n\t4GtoOyuGxbyWDmbUcWavrcSl6qtMsm4=","X-MC-Unique":"A-FaajVJNPyYbAFyKraWEQ-1","X-Mimecast-MFC-AGG-ID":"A-FaajVJNPyYbAFyKraWEQ_1760618282","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1760618282; x=1761223082;\n\th=mime-version:user-agent:message-id:date:references:in-reply-to\n\t:subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=Pvmp5V4OSVS3e7lAX3RrRC2w+8YrTGFt+dT0YMqsYh0=;\n\tb=o0kPZzOOV4Pyk976f0VAvOMaRuMDSbaL98CyhxmTHp+BcLYfi5zW9qIuZKUuGda1TU\n\tWucKk9APK5GnbeBvjo8jmIHB8jR3aFW3WsX0uHFKhu3SnonALpuu2ZWsnP95TJ2hVyT2\n\tdsDgZVznWreF9rntdxzrewAgxQiAb4tmcInb+b3IueqSLEhnlQcothizzo7IlgFDzvjE\n\t6xMCmYZMjY4VvIt0iwYS0LuzOtvEfTBBYlfXxOxujdS77n4i2tnxnF3y6lmtsQHXLc2D\n\tTih8gC+neAxxDaoNYLAnkeBMtsP4XBsEsqQ2rup/4RIHS4cZ9BPmnGufdibcWgMUDl3I\n\tFe2Q==","X-Gm-Message-State":"AOJu0Yz1gKYfCjU9rA12ZggppbhA0DEdLvsoW1XkVXWhj4H8PSYiMxw9\n\tcctBcjdxpBgZVB0lAinWQRbXIaHRFCyII66ms9B9YpTnsWwc2KBCZIyyl2r8mI0NvnrA5BdC92D\n\tNK7iea4Gjx25wSvtX/KTU9MTG84qgqk9wSgMv2Pek3nQD3LwCKw7qdLZqf/0FE65XLHO4qUGiQB\n\t8=","X-Gm-Gg":"ASbGnct91bugL9JEOc8LOnJvMUhqcxa+XA9aKAIbw/z21PCpqwIhxDNYKXJpfJkS3HV\n\tlzQvSmGlpQx+AvwcMkBbQmFqQaLJnVGGcy6pTTAoUMX808ml4Y75XBp8Zs+gI0Po9UAGIafL+i4\n\t4PVyVncjt3tGoxSw27wv7xa6ye4rv3cRHcfjuFIYybDyt7vJ+Wa6Zzgwky9mKOoR1EPZQ46A969\n\tcJ7ns5WlxviS4545XJi20IVt+XyI8JYuS2HG8o6YQi+/n1jPYVPaZPNQKuZajNae8mZLOgpEJCX\n\t5YYNz5B4Js4toS4X/sUWsSTCHKR9jJ7D8y1rtPRqybBuY2hkEixD6CpggEPES0Al+FEjgBIDtAy\n\tfTcvBVpZRE2TNTImKQzpsxOR5WD80HrIS3lhycuhzxCnyAVFUYPo/","X-Received":["by 2002:a05:6402:26ca:b0:63b:f67b:3776 with SMTP id\n\t4fb4d7f45d1cf-63bf67b39bbmr5155162a12.12.1760618281940; \n\tThu, 16 Oct 2025 05:38:01 -0700 (PDT)","by 2002:a05:6402:26ca:b0:63b:f67b:3776 with SMTP id\n\t4fb4d7f45d1cf-63bf67b39bbmr5155123a12.12.1760618281118; \n\tThu, 16 Oct 2025 05:38:01 -0700 (PDT)"],"X-Google-Smtp-Source":"AGHT+IGL7ZewT67PNTFwY3fT7D4yDgtsNLCmiji8sEv4nPKN7841asni4BuodSAr2Tfu1YqIVIE+yg==","From":"Milan Zamazal <mzamazal@redhat.com>","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","Cc":"libcamera-devel@lists.libcamera.org,  hdegoede@redhat.com,\n\tbod.linux@nxsw.ie","Subject":"Re: [PATCH v3 26/39] libcamera: software_isp: egl: Introduce an eGL\n\tbase helper class","In-Reply-To":"<20251015012251.17508-27-bryan.odonoghue@linaro.org> (Bryan\n\tO'Donoghue's message of \"Wed, 15 Oct 2025 02:22:38 +0100\")","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-27-bryan.odonoghue@linaro.org>","Date":"Thu, 16 Oct 2025 14:37:59 +0200","Message-ID":"<85bjm7dntk.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":"ObCM8sY8Sq_6oTZ87ZSTJ1irR0QnjiTTQP2ZV5kVXJk_1760618282","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":36810,"web_url":"https://patchwork.libcamera.org/comment/36810/","msgid":"<aRcYo41GKrdyF6T6@duo.ucw.cz>","date":"2025-11-14T11:55:15","subject":"Re: [PATCH v3 26/39] libcamera: software_isp: egl: Introduce an eGL\n\tbase helper class","submitter":{"id":49,"url":"https://patchwork.libcamera.org/api/people/49/","name":"Pavel Machek","email":"pavel@ucw.cz"},"content":"Hi!\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> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue at linaro.org>\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> +\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\tret = -ENODEV;\n> +\t\t\tgoto done;\n\nShould we free the image?\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);\n> +}\n\n(success == GL_TRUE) ? 0 : -EINVAL;\n\n?\n\nBest regards,\n\t\t\t\t\t\t\t\t\tPavel","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 A912DC3241\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 14 Nov 2025 11:55:17 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id DFB3560A80;\n\tFri, 14 Nov 2025 12:55:16 +0100 (CET)","from jabberwock.ucw.cz (jabberwock.ucw.cz [46.255.230.98])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 083EA606E6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 14 Nov 2025 12:55:16 +0100 (CET)","by jabberwock.ucw.cz (Postfix, from userid 1017)\n\tid 93D6C1C00AB; Fri, 14 Nov 2025 12:55:15 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ucw.cz header.i=@ucw.cz header.b=\"nxBUQ8Ed\";\n\tdkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=ucw.cz; s=gen1;\n\tt=1763121315;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:mime-version:mime-version:content-type:content-type:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=gW0OWIVGZffZcgb0g6o8BGfTa81NBTMBEcKgo3+67Ts=;\n\tb=nxBUQ8EdiCMfoJVgb8NAgGKD8NazaTjz92eMKH8vK3INQwZUhRzoWEsc/Z2tppTyVUcIf/\n\tVe/+IvMGhxo9pwlpf/Gv5BewTTrCgETxiazuYdSJzVfcekhCgAJvAUvnIWFyTdNsE/A2qH\n\tBnORmuf2Tu6bOXMKl8Zc17kjKmoBLEs=","Date":"Fri, 14 Nov 2025 12:55:15 +0100","From":"Pavel Machek <pavel@ucw.cz>","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tlibcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH v3 26/39] libcamera: software_isp: egl: Introduce an eGL\n\tbase helper class","Message-ID":"<aRcYo41GKrdyF6T6@duo.ucw.cz>","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-27-bryan.odonoghue@linaro.org>","MIME-Version":"1.0","Content-Type":"multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\"; boundary=\"GhLlpWrMaB9WNt++\"","Content-Disposition":"inline","In-Reply-To":"<20251015012251.17508-27-bryan.odonoghue@linaro.org>","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>"}}]