[{"id":34490,"web_url":"https://patchwork.libcamera.org/comment/34490/","msgid":"<416520dc-bf63-4634-aba5-1f074b5bed0b@linaro.org>","date":"2025-06-16T19:15:10","subject":"Re: [PATCH 22/35] libcamera: software_isp: egl: Introduce an eGL\n\tbase helper class","submitter":{"id":175,"url":"https://patchwork.libcamera.org/api/people/175/","name":"Bryan O'Donoghue","email":"bryan.odonoghue@linaro.org"},"content":"On 11/06/2025 02:32, Bryan O'Donoghue wrote:\n> Introduce an eGL base helper class which provides an eGL context based on a\n> passed width and height.\n> \n> The initGLContext function could be overloaded to provide an interface to a\n> real display.\n> \n> A set of helper functions is provided to compile and link GLSL shaders.\n> linkShaderProgram currently compiles vertex/fragment pairs but could be\n> overloaded or passed a parameter to link a compute shader instead.\n> \n> Breaking the eGL interface away from debayering - allows to use the eGL\n> context inside of a dma-buf heap cleanly, reuse that context inside of a\n> debayer layer and conceivably reuse the context in a multi-stage shader\n> pass.\n> \n> Small note the image_attrs[] array doesn't pass checkstyle.py however the\n> elements of the array are in pairs.\n> \n> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> ---\n>   include/libcamera/internal/egl.h | 110 +++++++++\n>   src/libcamera/egl.cpp            | 369 +++++++++++++++++++++++++++++++\n>   src/libcamera/meson.build        |  23 ++\n>   3 files changed, 502 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..04d637d8\n> --- /dev/null\n> +++ b/include/libcamera/internal/egl.h\n> @@ -0,0 +1,110 @@\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 <unistd.h>\n> +\n> +#include <libcamera/base/log.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> +class eGLImage\n> +{\n> +public:\n> +\teGLImage(uint32_t width, uint32_t height, uint32_t bpp, 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_ = width_ * bpp_ / 4;\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}\n> +\n> +\t~eGLImage()\n> +\t{\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> +\tGLuint texture_;\n> +\tEGLImageKHR image_;\n> +};\n> +\n> +class eGL\n> +{\n> +public:\n> +\teGL();\n> +\t~eGL();\n> +\n> +\tint initEGLContext(GBM *gbmContext);\n> +\tint createDMABufTexture2D(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> +\tvoid swapBuffers();\n> +\n> +\tint compileVertexShader(GLuint &shaderId, unsigned char *shaderData,\n> +\t\t\t\tunsigned int shaderDataLen,\n> +\t\t\t\tstd::vector<std::string> shaderEnv);\n> +\tint compileFragmentShader(GLuint &shaderId, unsigned char *shaderData,\n> +\t\t\t\t  unsigned int shaderDataLen,\n> +\t\t\t\t  std::vector<std::string> shaderEnv);\n> +\tint linkProgram(GLuint &programIdd, GLuint fragmentshaderId, GLuint vertexshaderId);\n> +\tvoid dumpShaderSource(GLuint shaderId);\n> +\tvoid useProgram(GLuint programId);\n> +\n> +private:\n> +\tint fd_;\n> +\n> +\tEGLDisplay display_;\n> +\tEGLContext context_;\n> +\tEGLSurface surface_;\n> +\n> +\tint compileShader(int shaderType, GLuint &shaderId, unsigned char *shaderData,\n> +\t\t\t  unsigned int shaderDataLen,\n> +\t\t\t  std::vector<std::string> shaderEnv);\n> +\n> +\tPFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA;\n> +\tPFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;\n> +\n> +\tPFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;\n> +\tPFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;\n> +\n> +\tPFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;\n> +\tPFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;\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..89ece148\n> --- /dev/null\n> +++ b/src/libcamera/egl.cpp\n> @@ -0,0 +1,369 @@\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/internal/egl.h\"\n> +\n> +#include <fcntl.h>\n> +#include <sys/ioctl.h>\n> +#include <sys/mman.h>\n> +#include <unistd.h>\n> +\n> +#include <linux/dma-buf.h>\n> +#include <linux/dma-heap.h>\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(eGL)\n> +\n> +eGL::eGL()\n> +{\n> +}\n> +\n> +eGL::~eGL()\n> +{\n> +}\n\nDefaults can be dropped.\n\n> +// Create linear image attached to previous BO object\n> +int eGL::createDMABufTexture2D(eGLImage *eglImage, int fd)\n> +{\n> +\tint ret = 0;\n> +\n> +\teglImage->stride_ = eglImage->width_ * eglImage->height_;\n> +\teglImage->offset_ = 0;\n> +\teglImage->framesize_ = eglImage->height_ * eglImage->stride_;\n> +\n> +\tLOG(eGL, Info)\n> +\t\t<< \" stride \" << eglImage->stride_ << \" width \" << eglImage->width_ << \" height \" << eglImage->height_ << \" offset \" << eglImage->offset_ << \" framesize \" << eglImage->framesize_;\n> +\n> +\t// TODO: use the dma buf handle from udma heap here directly\n> +\t// should work for both input and output with fencing\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, (int)GBM_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->framesize_,\n> +\t\tEGL_NONE, EGL_NONE,\t/* modifier lo */\n> +\t\tEGL_NONE, EGL_NONE,\t/* modifier hi */\n> +\t\tEGL_NONE,\n> +\t};\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// Generate texture, bind, associate image to texture, configure, unbind\n> +\tglEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage->image_);\n> +\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n> +\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n\nNot to self don't forget to set these parameters for GL_TEXTURE_1D.\n\n> +\n> +done:\n> +\treturn ret;\n> +}\n> +\n> +void eGL::destroyDMABufTexture(eGLImage *eglImage)\n> +{\n> +\teglDestroyImage(display_, eglImage->image_);\n> +}\n> +\n> +//\n> +// Generate a 2D texture from an input buffer directly\n> +void eGL::createTexture2D(eGLImage *eglImage, uint32_t width, uint32_t height, void *data)\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, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);\n\nLooks like squashing Milan's change for the format - GL_LUMINANCE hasn't \nmade it into this patch.\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> +\t//TODO: use optional eglGetPlatformDisplayEXT ?\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> +\t//TODO: interrogate strings to make sure we aren't hooking unsupported functions\n> +\t//      and remember to error out if a function we depend on isn't found.\n> +\t//      we don't use these functions right now but expect to for DMA backed\n> +\t//      texture generation and render-to-texture. One thing we can do is differentiate\n> +\t//      between DMA and non-DMA texture generation based on the presence of these functions\n> +\t//      In reality most - all ? - mesa implementations have these extensions so\n> +\t//      probably no fallback will be required\n> +\teglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress(\"eglCreateImageKHR\");\n> +\tif (!eglCreateImageKHR)\n> +\t\tLOG(eGL, Warning) << \"eglCreateImageKHR not found\";\n> +\n> +\teglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress(\"eglDestroyImageKHR\");\n> +\tif (!eglDestroyImageKHR)\n> +\t\tLOG(eGL, Warning) << \"eglDestroyImageKHR not found\";\n> +\n> +\teglExportDMABUFImageMESA = (PFNEGLEXPORTDMABUFIMAGEMESAPROC)eglGetProcAddress(\"eglExportDMABUFImageMESA\");\n> +\tif (!eglExportDMABUFImageMESA)\n> +\t\tLOG(eGL, Warning) << \"eglExportDMABUFImageMESA not found\";\n> +\n> +\tglEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress(\"glEGLImageTargetTexture2DOES\");\n> +\tif (!glEGLImageTargetTexture2DOES)\n> +\t\tLOG(eGL, Warning) << \"glEGLImageTargetTexture2DOES not found\";\n> +\n> +\teglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress(\"eglClientWaitSyncKHR\");\n> +\tif (!eglClientWaitSyncKHR)\n> +\t\tLOG(eGL, Warning) << \"eglClientWaitSyncKHR not found\";\n> +\n> +\teglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress(\"eglCreateSyncKHR\");\n> +\tif (!eglCreateSyncKHR)\n> +\t\tLOG(eGL, Warning) << \"eglCreateSyncKHR not found\";\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> +\tsurface_ = eglCreateWindowSurface(display_, config,\n> +\t\t\t\t\t  (EGLNativeWindowType)gbmContext->getSurface(),\n> +\t\t\t\t\t  NULL);\n> +\tif (surface_ == EGL_NO_SURFACE) {\n> +\t\tLOG(eGL, Error) << \"eglCreateWindowSurface fail\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\tmakeCurrent();\n> +\tswapBuffers();\n> +\n> +\treturn 0;\n> +fail:\n> +\n> +\treturn -ENODEV;\n> +}\n> +\n> +void eGL::makeCurrent(void)\n> +{\n> +\tif (eglMakeCurrent(display_, surface_, surface_, context_) != EGL_TRUE) {\n> +\t\tLOG(eGL, Error) << \"eglMakeCurrent fail\";\n> +\t}\n> +}\n> +\n> +void eGL::swapBuffers(void)\n> +{\n> +\tif (eglSwapBuffers(display_, surface_) != EGL_TRUE) {\n> +\t\tLOG(eGL, Error) << \"eglSwapBuffers fail\";\n> +\t}\n> +}\n> +\n> +void eGL::useProgram(GLuint programId)\n> +{\n> +\tglUseProgram(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(addStr);\n> +}\n> +\n> +int eGL::compileVertexShader(GLuint &shaderId, unsigned char *shaderData,\n> +\t\t\t     unsigned int shaderDataLen,\n> +\t\t\t     std::vector<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       std::vector<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       std::vector<std::string> shaderEnv)\n> +{\n> +\tGLchar **shaderSourceData;\n> +\tGLint *shaderDataLengths;\n> +\tGLint success;\n> +\tGLsizei count;\n> +\tsize_t i;\n> +\n> +\tcount = 1 + shaderEnv.size();\n> +\tshaderSourceData = new GLchar *[count];\n> +\tshaderDataLengths = new GLint[count];\n> +\n> +\t// Prefix defines before main body of shader\n> +\tfor (i = 0; i < shaderEnv.size(); i++) {\n> +\t\tshaderSourceData[i] = (GLchar *)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] = (GLchar *)shaderData;\n> +\tshaderDataLengths[i] = shaderDataLen;\n> +\n> +\t// And create the shader\n> +\tshaderId = glCreateShader(shaderType);\n> +\tglShaderSource(shaderId, count, shaderSourceData, shaderDataLengths);\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> +\t\tGLchar *infoLog;\n> +\n> +\t\tglGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &sizeLog);\n> +\t\tinfoLog = new GLchar[sizeLog];\n> +\n> +\t\tglGetShaderInfoLog(shaderId, sizeLog, &sizeLog, infoLog);\n> +\t\tLOG(eGL, Error) << infoLog;\n> +\n> +\t\tdelete[] infoLog;\n> +\t}\n> +\n> +\tdelete[] shaderSourceData;\n> +\tdelete[] shaderDataLengths;\n> +\n> +\treturn !(success == GL_TRUE);\n> +}\n> +\n> +void eGL::dumpShaderSource(GLuint shaderId)\n> +{\n> +\tGLint shaderLength = 0;\n> +\tGLchar *shaderSource;\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\tshaderSource = new GLchar[shaderLength];\n> +\t\tif (!shaderSource)\n> +\t\t\treturn;\n> +\n> +\t\tglGetShaderSource(shaderId, shaderLength, &shaderLength, shaderSource);\n> +\t\tif (shaderLength) {\n> +\t\t\tLOG(eGL, Debug) << \"Shader source = \" << shaderSource;\n> +\t\t}\n> +\t\tdelete[] shaderSource;\n> +\t}\n> +}\n> +\n> +int eGL::linkProgram(GLuint &programId, GLuint vertexshaderId, GLuint fragmentshaderId)\n> +{\n> +\tGLint success;\n> +\tGLenum err;\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> +\treturn -ENODEV;\n> +}\n> +} // namespace libcamera\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index 0d004694..491eb734 100644\n> --- a/src/libcamera/meson.build\n> +++ b/src/libcamera/meson.build\n> @@ -77,6 +77,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)\nRecall Laurent said dependency() was the preferred way.\n\n---\nbod","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 6A98ABDE6B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 16 Jun 2025 19:15:15 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A11EB68DC0;\n\tMon, 16 Jun 2025 21:15:14 +0200 (CEST)","from mail-wr1-x433.google.com (mail-wr1-x433.google.com\n\t[IPv6:2a00:1450:4864:20::433])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B2A6A68DC0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Jun 2025 21:15:12 +0200 (CEST)","by mail-wr1-x433.google.com with SMTP id\n\tffacd0b85a97d-3a50fc7ac4dso2866028f8f.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Jun 2025 12:15:12 -0700 (PDT)","from [192.168.0.35] (188-141-3-146.dynamic.upc.ie. [188.141.3.146])\n\tby smtp.gmail.com with ESMTPSA id\n\tffacd0b85a97d-3a568a7ddefsm11851366f8f.39.2025.06.16.12.15.11\n\t(version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);\n\tMon, 16 Jun 2025 12:15:11 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=linaro.org header.i=@linaro.org\n\theader.b=\"wKzu+1Aq\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=linaro.org; s=google; t=1750101312; x=1750706112;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:in-reply-to:from:content-language\n\t:references:to:subject:user-agent:mime-version:date:message-id:from\n\t:to:cc:subject:date:message-id:reply-to;\n\tbh=WMMhvwHg0jyClCK8+txB6Q6VuSJmogf8qo73Wd4H8W0=;\n\tb=wKzu+1Aqm8PZITuIdTxbP5TOz0ny7crPcAtdaRgjXpWOKFQOTukNgvVrP+2s7bzHi9\n\tvmbSchNj7ulrje1LUcHnoG8ak9g+oDUqGJQi6+BV1chvMzqftWlp/9qhwx8Oo28cP9Rn\n\thp4nVpmSgisRLplnKJQl3lzsuucT54cgx9+wNZ7BdO+PrFsaSuJEJnlhyUk5JxDrkof3\n\tzr1+IP/vxaXwoDqTa6gXC8ZDEFBPEfXfFf0tb0Ka1SLWTsUjHi7vBJXdMM13CiCAH50I\n\tbyx6u9Z7k09wW/cz2OenjBx5Qr69snpqupVE9F32x8aXU0rL9poa09wHFx9OR0WUxB4+\n\t6FHg==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1750101312; x=1750706112;\n\th=content-transfer-encoding:in-reply-to:from:content-language\n\t:references:to:subject:user-agent:mime-version:date:message-id\n\t:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to;\n\tbh=WMMhvwHg0jyClCK8+txB6Q6VuSJmogf8qo73Wd4H8W0=;\n\tb=TOcTBgbk7rCJLYvqD/Z5Tup+qQIc5oLpE+ymiZ96UImTvwq9VfA69aeO1m9G8nwC/F\n\t8sA2wN6fpNIRkLn0WV4v9TD0Hyrxy/sZ0bGnIbl5F4+q39PLxY1pitM/XmJGJcNofxLb\n\tbDyCruzUIafVxes42xwevp7x6PkQLSAwDSvUHIEGNz1kS6OBZUhEIl7hZo8IW/zFfnF6\n\tONXWEZP7idmp9UN0aSqkFiowI/ipdTYb4FLYDduzRyrZxAxYBmMe7PqPDJ87lWvdG076\n\tF4SBAGeAoN6hrBXSH6goYSXTZXGcFDG7ZYlqInYCP/AxnhDJYbTPuq5UblDgah24nDxH\n\tm4GQ==","X-Forwarded-Encrypted":"i=1;\n\tAJvYcCUnEfMy2exbjRZLoAqiOWO6MbswQgIYzJe5eEdPqtODCPT/wSFvF8wyt0aLGZZNMw4RBuAaLZ1G1JaYYVow1Eg=@lists.libcamera.org","X-Gm-Message-State":"AOJu0Yw0eZDFpKu8sovNr6Fs+2OS6Ydc2Y7HierEqOgDfH7wY82rZN61\n\tGZDz3LaF46xG+jEQvgwT4ioF+3njQUyKPleiscLwJwfmap9mEprH9mh9SIOrCCyq4ETtkLljfFy\n\tLvKEc6VQ=","X-Gm-Gg":"ASbGnctJaku82Kus2qNNUIck6idk86glWHAmxIJ07WXkxU5WRArgZ2MK7htkH44uFCM\n\t0krm6DzKK/5z8hqrtpkwcJt43Z5lquHiQoUHnMdVY73RSJVdj/5tye7qgLjKcErQ1zu6erlXjKS\n\tmxW/gg5eLsVmEiBdK24T9sADa+uWhBCMpVssRoy2v5wpV4IKRJWVW8FNxZg7TRvc/t8EpKGwGWK\n\trcEoslguyytfBrMn6d9BqujKtvlOEKoET3Is75XnncSaE3Wj11hU14T7fwnrc9Ah+cLNU+LDPex\n\tq9iYUMqnCE4PfkyEVpHhgxUgydGKyuWKPTNMnbp585a8yDA3X8EP+ok00ustZi8b+DwX1fNwAPs\n\t7nB46rnnGCzR21/48fg6wFlBMT5o=","X-Google-Smtp-Source":"AGHT+IHnEPB3Z4fthOTl24PLASTlJrNEuAin+uw4NEmEEvkRzBNWoWwf07tirW7w1xFToFy4sO7Yrw==","X-Received":"by 2002:a5d:5f8d:0:b0:3a5:5270:a52c with SMTP id\n\tffacd0b85a97d-3a57189727dmr9068496f8f.0.1750101312023; \n\tMon, 16 Jun 2025 12:15:12 -0700 (PDT)","Message-ID":"<416520dc-bf63-4634-aba5-1f074b5bed0b@linaro.org>","Date":"Mon, 16 Jun 2025 20:15:10 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 22/35] libcamera: software_isp: egl: Introduce an eGL\n\tbase helper class","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20250611013245.133785-1-bryan.odonoghue@linaro.org>\n\t<qOgRnWXNyjyX42CQu7ClGK5gV3n6UPa7DGIBMGqg6m_P5BF33kUkSbyw8F7taTy0M5jeu9G0qxOutzeHvdh95Q==@protonmail.internalid>\n\t<20250611013245.133785-23-bryan.odonoghue@linaro.org>","Content-Language":"en-US","From":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","In-Reply-To":"<20250611013245.133785-23-bryan.odonoghue@linaro.org>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":34521,"web_url":"https://patchwork.libcamera.org/comment/34521/","msgid":"<e696b0cc-c639-4469-ba9b-e6eff81729c9@ideasonboard.com>","date":"2025-06-17T12:33:06","subject":"Re: [PATCH 22/35] libcamera: software_isp: egl: Introduce an eGL\n\tbase helper class","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"Hi\n\n2025. 06. 11. 3:32 keltezéssel, Bryan O'Donoghue írta:\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 | 110 +++++++++\n>   src/libcamera/egl.cpp            | 369 +++++++++++++++++++++++++++++++\n>   src/libcamera/meson.build        |  23 ++\n>   3 files changed, 502 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..04d637d8\n> --- /dev/null\n> +++ b/include/libcamera/internal/egl.h\n> @@ -0,0 +1,110 @@\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 <unistd.h>\n> +\n> +#include <libcamera/base/log.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> +class eGLImage\n> +{\n> +public:\n> +\teGLImage(uint32_t width, uint32_t height, uint32_t bpp, GLenum texture_unit, uint32_t texture_unit_uniform_id)\n\nCould you use the member init list?\n\n\n> +\t{\n> +\t\timage_ = EGL_NO_IMAGE_KHR;\n> +\t\twidth_ = width;\n> +\t\theight_ = height;\n> +\t\tbpp_ = bpp;\n> +\t\tstride_ = width_ * bpp_ / 4;\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}\n> +\n> +\t~eGLImage()\n> +\t{\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> +\tGLuint texture_;\n> +\tEGLImageKHR image_;\n\n  = EGL_NO_IMAGE_KHR;\n\n\n\n> +};\n\nCopy ctor/assignment should be deleted; move ctor/assignment should be implemented or deleted (if not needed).\n\n\n> +\n> +class eGL\n> +{\n> +public:\n> +\teGL();\n> +\t~eGL();\n> +\n> +\tint initEGLContext(GBM *gbmContext);\n> +\tint createDMABufTexture2D(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> +\tvoid swapBuffers();\n> +\n> +\tint compileVertexShader(GLuint &shaderId, unsigned char *shaderData,\n> +\t\t\t\tunsigned int shaderDataLen,\n> +\t\t\t\tstd::vector<std::string> shaderEnv);\n\nSpan<const std::string> ?\n\n\n> +\tint compileFragmentShader(GLuint &shaderId, unsigned char *shaderData,\n> +\t\t\t\t  unsigned int shaderDataLen,\n> +\t\t\t\t  std::vector<std::string> shaderEnv);\n\nAnd here?\n\n\n> +\tint linkProgram(GLuint &programIdd, GLuint fragmentshaderId, GLuint vertexshaderId);\n\nprogramIdd -> programId\n\n\n> +\tvoid dumpShaderSource(GLuint shaderId);\n> +\tvoid useProgram(GLuint programId);\n> +\n> +private:\n> +\tint fd_;\n> +\n> +\tEGLDisplay display_;\n> +\tEGLContext context_;\n> +\tEGLSurface surface_;\n> +\n> +\tint compileShader(int shaderType, GLuint &shaderId, unsigned char *shaderData,\n> +\t\t\t  unsigned int shaderDataLen,\n> +\t\t\t  std::vector<std::string> shaderEnv);\n\nSpan<const std::string> here as well?\n\n\n> +\n> +\tPFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA;\n> +\tPFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;\n> +\n> +\tPFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;\n> +\tPFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;\n> +\n> +\tPFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;\n> +\tPFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;\n> +};\n\nCopy ctor/assignment should be deleted; move ctor/assignment should be implemented or deleted (if not needed).\n\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..89ece148\n> --- /dev/null\n> +++ b/src/libcamera/egl.cpp\n> @@ -0,0 +1,369 @@\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/internal/egl.h\"\n> +\n> +#include <fcntl.h>\n> +#include <sys/ioctl.h>\n> +#include <sys/mman.h>\n> +#include <unistd.h>\n> +\n> +#include <linux/dma-buf.h>\n> +#include <linux/dma-heap.h>\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(eGL)\n> +\n> +eGL::eGL()\n> +{\n> +}\n> +\n> +eGL::~eGL()\n> +{\n> +}\n> +\n> +// Create linear image attached to previous BO object\n> +int eGL::createDMABufTexture2D(eGLImage *eglImage, int fd)\n> +{\n> +\tint ret = 0;\n> +\n> +\teglImage->stride_ = eglImage->width_ * eglImage->height_;\n> +\teglImage->offset_ = 0;\n> +\teglImage->framesize_ = eglImage->height_ * eglImage->stride_;\n> +\n> +\tLOG(eGL, Info)\n> +\t\t<< \" stride \" << eglImage->stride_ << \" width \" << eglImage->width_ << \" height \" << eglImage->height_ << \" offset \" << eglImage->offset_ << \" framesize \" << eglImage->framesize_;\n> +\n> +\t// TODO: use the dma buf handle from udma heap here directly\n> +\t// should work for both input and output with fencing\n> +\tEGLint image_attrs[] = {> +\t\tEGL_WIDTH, (EGLint)eglImage->width_,\n> +\t\tEGL_HEIGHT, (EGLint)eglImage->height_,\n> +\t\tEGL_LINUX_DRM_FOURCC_EXT, (int)GBM_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->framesize_,\n> +\t\tEGL_NONE, EGL_NONE,\t/* modifier lo */\n> +\t\tEGL_NONE, EGL_NONE,\t/* modifier hi */\n> +\t\tEGL_NONE,\n> +\t};\n\n\nI think if you add `// clang-format off` and `// clang-format on` around it\nand then checkstyle.py should not complain.\n\n\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// Generate texture, bind, associate image to texture, configure, unbind\n> +\tglEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage->image_);\n> +\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n> +\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n> +\n> +done:\n> +\treturn ret;\n> +}\n> +\n> +void eGL::destroyDMABufTexture(eGLImage *eglImage)\n> +{\n> +\teglDestroyImage(display_, eglImage->image_);\n> +}\n> +\n> +//\n> +// Generate a 2D texture from an input buffer directly\n> +void eGL::createTexture2D(eGLImage *eglImage, uint32_t width, uint32_t height, void *data)\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, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, 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> +\t//TODO: use optional eglGetPlatformDisplayEXT ?\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> +\t//TODO: interrogate strings to make sure we aren't hooking unsupported functions\n> +\t//      and remember to error out if a function we depend on isn't found.\n> +\t//      we don't use these functions right now but expect to for DMA backed\n> +\t//      texture generation and render-to-texture. One thing we can do is differentiate\n> +\t//      between DMA and non-DMA texture generation based on the presence of these functions\n> +\t//      In reality most - all ? - mesa implementations have these extensions so\n> +\t//      probably no fallback will be required\n> +\teglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress(\"eglCreateImageKHR\");\n> +\tif (!eglCreateImageKHR)\n> +\t\tLOG(eGL, Warning) << \"eglCreateImageKHR not found\";\n> +\n> +\teglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress(\"eglDestroyImageKHR\");\n> +\tif (!eglDestroyImageKHR)\n> +\t\tLOG(eGL, Warning) << \"eglDestroyImageKHR not found\";\n> +\n> +\teglExportDMABUFImageMESA = (PFNEGLEXPORTDMABUFIMAGEMESAPROC)eglGetProcAddress(\"eglExportDMABUFImageMESA\");\n> +\tif (!eglExportDMABUFImageMESA)\n> +\t\tLOG(eGL, Warning) << \"eglExportDMABUFImageMESA not found\";\n> +\n> +\tglEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress(\"glEGLImageTargetTexture2DOES\");\n> +\tif (!glEGLImageTargetTexture2DOES)\n> +\t\tLOG(eGL, Warning) << \"glEGLImageTargetTexture2DOES not found\";\n> +\n> +\teglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress(\"eglClientWaitSyncKHR\");\n> +\tif (!eglClientWaitSyncKHR)\n> +\t\tLOG(eGL, Warning) << \"eglClientWaitSyncKHR not found\";\n> +\n> +\teglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress(\"eglCreateSyncKHR\");\n> +\tif (!eglCreateSyncKHR)\n> +\t\tLOG(eGL, Warning) << \"eglCreateSyncKHR not found\";\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\nDoes this not need `eglDestroyContext()` or similar in `~eGL()`?\n\n\n> +\tif (context_ == EGL_NO_CONTEXT) {\n> +\t\tLOG(eGL, Error) << \"eglContext returned EGL_NO_CONTEXT\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\tsurface_ = eglCreateWindowSurface(display_, config,\n> +\t\t\t\t\t  (EGLNativeWindowType)gbmContext->getSurface(),\n> +\t\t\t\t\t  NULL);\n\nSame question here with `eglDestroySurface()`?\n\n\n> +\tif (surface_ == EGL_NO_SURFACE) {\n> +\t\tLOG(eGL, Error) << \"eglCreateWindowSurface fail\";\n> +\t\tgoto fail;\n> +\t}\n> +\n> +\tmakeCurrent();\n> +\tswapBuffers();\n> +\n> +\treturn 0;\n> +fail:\n> +\n> +\treturn -ENODEV;\n> +}\n> +\n> +void eGL::makeCurrent(void)\n> +{\n> +\tif (eglMakeCurrent(display_, surface_, surface_, context_) != EGL_TRUE) {\n> +\t\tLOG(eGL, Error) << \"eglMakeCurrent fail\";\n> +\t}\n> +}\n> +\n> +void eGL::swapBuffers(void)\n> +{\n> +\tif (eglSwapBuffers(display_, surface_) != EGL_TRUE) {\n> +\t\tLOG(eGL, Error) << \"eglSwapBuffers fail\";\n> +\t}\n> +}\n> +\n> +void eGL::useProgram(GLuint programId)\n> +{\n> +\tglUseProgram(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(addStr);\n\nstd::move(addStr)\n\n\n> +}\n> +\n> +int eGL::compileVertexShader(GLuint &shaderId, unsigned char *shaderData,\n> +\t\t\t     unsigned int shaderDataLen,\n> +\t\t\t     std::vector<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       std::vector<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       std::vector<std::string> shaderEnv)\n> +{\n> +\tGLchar **shaderSourceData;\n> +\tGLint *shaderDataLengths;\n> +\tGLint success;\n> +\tGLsizei count;\n> +\tsize_t i;\n> +\n> +\tcount = 1 + shaderEnv.size();\n> +\tshaderSourceData = new GLchar *[count];\n> +\tshaderDataLengths = new GLint[count];\n\nauto shaderSourceData = std::make_unique<const GLchar *[]>(count);\netc.\n\n\n> +\n> +\t// Prefix defines before main body of shader\n> +\tfor (i = 0; i < shaderEnv.size(); i++) {\n> +\t\tshaderSourceData[i] = (GLchar *)shaderEnv[i].c_str();\n\nThe above cast shouldn't be needed if you change the type. `glShaderSource()`\nshould take `const GLchar * const *`, so if you have `const GLchar **`,\nthat should work.\n\n\n> +\t\tshaderDataLengths[i] = shaderEnv[i].length();\n> +\t}\n> +\n> +\t// Now the main body of the shader program\n> +\tshaderSourceData[i] = (GLchar *)shaderData;\n> +\tshaderDataLengths[i] = shaderDataLen;\n> +\n> +\t// And create the shader\n> +\tshaderId = glCreateShader(shaderType);\n> +\tglShaderSource(shaderId, count, shaderSourceData, shaderDataLengths);\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> +\t\tGLchar *infoLog;\n> +\n> +\t\tglGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &sizeLog);\n> +\t\tinfoLog = new GLchar[sizeLog];\n\nSame here.\n\n\n> +\n> +\t\tglGetShaderInfoLog(shaderId, sizeLog, &sizeLog, infoLog);\n> +\t\tLOG(eGL, Error) << infoLog;\n> +\n> +\t\tdelete[] infoLog;\n> +\t}\n> +\n> +\tdelete[] shaderSourceData;\n> +\tdelete[] shaderDataLengths;\n> +\n> +\treturn !(success == GL_TRUE);\n\nShould `glDeleteShader()` be called in case of failure?\n\n\n> +}\n> +\n> +void eGL::dumpShaderSource(GLuint shaderId)\n> +{\n> +\tGLint shaderLength = 0;\n> +\tGLchar *shaderSource;\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\tshaderSource = new GLchar[shaderLength];\n\nSame here.\n\n\n> +\t\tif (!shaderSource)\n> +\t\t\treturn;\n> +\n> +\t\tglGetShaderSource(shaderId, shaderLength, &shaderLength, shaderSource);\n> +\t\tif (shaderLength) {\n> +\t\t\tLOG(eGL, Debug) << \"Shader source = \" << shaderSource;\n> +\t\t}\n> +\t\tdelete[] shaderSource;\n> +\t}\n> +}\n> +\n> +int eGL::linkProgram(GLuint &programId, GLuint vertexshaderId, GLuint fragmentshaderId)\n> +{\n> +\tGLint success;\n> +\tGLenum err;\n> +\n> +\tprogramId = glCreateProgram();\n> +\tif (!programId)\n> +\t\tgoto fail;\n\nI think there should be another failure label that calls `glDeleteProgram()`.\n\n\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\nstd::unique_ptr<GLchar[]> here as well.\n\n\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> +\treturn -ENODEV;\n> +}\n> +} // namespace libcamera\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index 0d004694..491eb734 100644\n> --- a/src/libcamera/meson.build\n> +++ b/src/libcamera/meson.build\n> @@ -77,6 +77,27 @@ if libgbm.found() and gbm_works\n>       ])\n>   endif\n>   \n> +libegl = cc.find_library('EGL', required : false)\n\negl_dep = dependency('egl', required: false) ?\n\n\n> +libglesv2 = cc.find_library('GLESv2', required : false)\n> +mesa_works = cc.check_header('EGL/egl.h', required: false)\n\ncc.check_header(..., dependency: egl_dep) ?\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> @@ -198,7 +219,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\nRegards,\nBarnabás Pőcze","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 13E58BDE6B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 17 Jun 2025 12:33:15 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 20EBE68DB1;\n\tTue, 17 Jun 2025 14:33:14 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8A2CD68DB1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 17 Jun 2025 14:33:12 +0200 (CEST)","from [192.168.33.11] (185.221.143.107.nat.pool.zt.hu\n\t[185.221.143.107])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 6A8157E1;\n\tTue, 17 Jun 2025 14:32:59 +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=\"OEFOPILn\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1750163580;\n\tbh=q2YN0aeC50lvoYBlRzzs+DwXhXHUP0tmMuXNzZ8PXmA=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=OEFOPILnf1Hdv1SFh96y6S2+cJskCjB+jfcNRYhqFOQwjgGniRgwdE+qxMXIVZqBQ\n\tN53ZGSVXmOQRaLl0QGntTVdm/sKyTDD6J+R+rly5HvB1K11JGEUsYoNk0HDmWBf2Ik\n\tkhJDIYa3N4/fhHZwPCCXYzJ88YIiV5vFK5nXxKyI=","Message-ID":"<e696b0cc-c639-4469-ba9b-e6eff81729c9@ideasonboard.com>","Date":"Tue, 17 Jun 2025 14:33:06 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 22/35] libcamera: software_isp: egl: Introduce an eGL\n\tbase helper class","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20250611013245.133785-1-bryan.odonoghue@linaro.org>\n\t<20250611013245.133785-23-bryan.odonoghue@linaro.org>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<20250611013245.133785-23-bryan.odonoghue@linaro.org>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]