{"id":23227,"url":"https://patchwork.libcamera.org/api/1.1/patches/23227/?format=json","web_url":"https://patchwork.libcamera.org/patch/23227/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20250422215920.4297-15-bryan.odonoghue@linaro.org>","date":"2025-04-22T21:59:07","name":"[14/27] libcamera: software_isp: egl: Introduce an eGL base helper class","commit_ref":null,"pull_url":null,"state":"rfc","archived":false,"hash":"52a12b8cda06fdb34512c9a65a7ab95212cff4d0","submitter":{"id":175,"url":"https://patchwork.libcamera.org/api/1.1/people/175/?format=json","name":"Bryan O'Donoghue","email":"bryan.odonoghue@linaro.org"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/23227/mbox/","series":[{"id":5142,"url":"https://patchwork.libcamera.org/api/1.1/series/5142/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5142","date":"2025-04-22T21:58:53","name":"RFC: Add in a eGL based GPUISP in libcamera","version":1,"mbox":"https://patchwork.libcamera.org/series/5142/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/23227/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/23227/checks/","tags":{},"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 60908C331E\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 22 Apr 2025 21:59:50 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8134C68B4A;\n\tTue, 22 Apr 2025 23:59:49 +0200 (CEST)","from mail-wm1-x333.google.com (mail-wm1-x333.google.com\n\t[IPv6:2a00:1450:4864:20::333])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 477A368ADB\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 22 Apr 2025 23:59:37 +0200 (CEST)","by mail-wm1-x333.google.com with SMTP id\n\t5b1f17b1804b1-43d04dc73b7so52250695e9.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 22 Apr 2025 14:59:37 -0700 (PDT)","from inspiron14p-linux.ht.home (188-141-3-146.dynamic.upc.ie.\n\t[188.141.3.146]) by smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-44092d2eccesm2726615e9.20.2025.04.22.14.59.35\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tTue, 22 Apr 2025 14:59:35 -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=\"YtR+0mZF\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=linaro.org; s=google; t=1745359177; x=1745963977;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=xk1344nZ8W/t2QKGcYFtSu2hlITWsBUFu9+PpTYsa9k=;\n\tb=YtR+0mZFBH22j8/i4ApfGnmJ/uM+5AqBDKsq6FYkyk0IEE6iNK1wHEq6sjz8iW4dab\n\tMvgKbKnyfFhE5tNeKSMVWdaEKasrQxTLl73JJwgEVnCYVoWhXGzBqHGq2/EzZiEo4sMp\n\tJnrU67Zk3Vsu4rFtz892e+X9vgVfGT2mDtqdgU7PmGePBGNcggNvTzfhfpz3i0dVceip\n\t9NoCUz3/hlZ9E8lF2pW5nbhJ9P8VfhhVWDZl87mBmJzZN+Nb55LMvUT7jVis4wSXXtCf\n\t2QCn6BvIu2FU48QHfj6I0/18y8usVJNVRiHwhzqg6wNJOA4xDgUowIDvOUzDwviNsofc\n\txDbw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1745359177; x=1745963977;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=xk1344nZ8W/t2QKGcYFtSu2hlITWsBUFu9+PpTYsa9k=;\n\tb=wNPHXqXIPpy2WBScv7fpBJLmuxPSxEm5GX5G/0ne7M7D6R5+Mq8AcqSKsXZdhLvsEA\n\t2OT2FAYPoDA0foNq2kvqYYE0qZk8kNLoQf6vjWRG7y9a1oOiX0QPcC5Hjd3VDVWGKqzw\n\tQmG9azWplgS+HVrTUf+EfRoEMFoxTia8s+Q+qo1XccaS/H5zv0IYT9wSiuEfiSdwANDX\n\t0Hr9HbyO7U4QRPYU1UNXAdenwPaeY6Rqgl9ea6S8FupMv5DawUe9Ky+Z4P17KI9/lZf8\n\tyOLPUQCRcxDifZyqZNwPq2ZenfmJZXMusNeP1Cc+Gx1lbQOzr3nlOrkk6giFyu13tXXY\n\t4EBg==","X-Gm-Message-State":"AOJu0YxtSXFc7MWam2LGnBot86j5sf6lF+nUeKJEqUxC6XjVgSak4RpO\n\twKUtHCZGtjtSyIfwBSlc5hFQL7ksmsSrgWPRfmv0uI4+gL/SvjVMpdRqcU+OGhw6D+5ULD6z90u\n\tlk+4=","X-Gm-Gg":"ASbGnct+wyZwWsTlmjbrOl0uWAj2XlF59x0N/d9GKBihDYTAIvwPwjmF2tqhRkjpwBh\n\tTGZo1kc6k6OotZqgvebnYCa22Z/WdTNJFSn7iDqxtw9XS5nOs2zVFZPDQBCwmUOkbBLAS7OXAqQ\n\tlrydquoEnSmKHGv/G8JmWK0OS2Wuzz5iRtyPw1sM/N9Y9d/g9Zq6Dr2nSVFr0OMe2PZ55q8SpVC\n\t/58C/a6Y5W7fZvie2EiB3w3g1NEf1skucnNrESV55YoMtZLRgkv9kAR84c1i1fAoVzk3IXHvYxN\n\tDcw5R/dRyXQQm+p7I1ymh7dK5aW5oosiJfuPmlwB8lFxWjQzHz1RtxEYN5iudxQQ3IpPt0uY6iK\n\tRS95CWl3/k2hutgz1MmIctq9YWl4Mm2g=","X-Google-Smtp-Source":"AGHT+IGxREPG94hstT9WkfsbJ3FRwL1My8OSde7/zXPcC10spvyfsf5DaIBLZH+LPFMKRpVAc/b3aw==","X-Received":"by 2002:a05:600c:a143:b0:43d:878c:7c40 with SMTP id\n\t5b1f17b1804b1-44076c9c6fbmr105466655e9.10.1745359176550; \n\tTue, 22 Apr 2025 14:59:36 -0700 (PDT)","From":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","To":"libcamera-devel@lists.libcamera.org","Cc":"hdegoede@redhat.com, mzamazal@redhat.com, bryan.odonoghue@linaro.org,\n\tbod.linux@nxsw.ie","Subject":"[PATCH 14/27] libcamera: software_isp: egl: Introduce an eGL base\n\thelper class","Date":"Tue, 22 Apr 2025 22:59:07 +0100","Message-ID":"<20250422215920.4297-15-bryan.odonoghue@linaro.org>","X-Mailer":"git-send-email 2.49.0","In-Reply-To":"<20250422215920.4297-1-bryan.odonoghue@linaro.org>","References":"<20250422215920.4297-1-bryan.odonoghue@linaro.org>","MIME-Version":"1.0","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>"},"content":"Introduce an eGL base helper class which provides an eGL context based on a\npassed width and height.\n\nThe initGLContext function could be overloaded to provide an interface to a\nreal display.\n\nA set of helper functions is provided to compile and link GLSL shaders.\nlinkShaderProgram currently compiles vertex/fragment pairs but could be\noverloaded or passed a parameter to link a compute shader instead.\n\nBreaking the eGL interface away from debayering - allows to use the eGL\ncontext inside of a dma-buf heap cleanly, reuse that context inside of a\ndebayer layer and conceivably reuse the context in a multi-stage shader\npass.\n\nSigned-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n---\n src/libcamera/software_isp/egl.cpp | 346 +++++++++++++++++++++++++++++\n src/libcamera/software_isp/egl.h   | 106 +++++++++\n 2 files changed, 452 insertions(+)\n create mode 100644 src/libcamera/software_isp/egl.cpp\n create mode 100644 src/libcamera/software_isp/egl.h","diff":"diff --git a/src/libcamera/software_isp/egl.cpp b/src/libcamera/software_isp/egl.cpp\nnew file mode 100644\nindex 00000000..c3eb8290\n--- /dev/null\n+++ b/src/libcamera/software_isp/egl.cpp\n@@ -0,0 +1,346 @@\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 <fcntl.h>\n+#include <unistd.h>\n+\n+#include <linux/dma-buf.h>\n+#include <linux/dma-heap.h>\n+#include <sys/ioctl.h>\n+#include <sys/mman.h>\n+\n+#include \"egl.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) << __func__ << \" stride \" << eglImage->stride_ << \" width \" << eglImage->width_ <<\n+\t\t\t\" height \" << eglImage->height_ << \" offset \" << eglImage->offset_ << \" framesize \" <<\n+\t\t\teglImage->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\t   EGL_LINUX_DMA_BUF_EXT,\n+\t\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+\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+\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+\teglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress(\"eglCreateImageKHR\");\n+\teglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress(\"eglDestroyImageKHR\");\n+\teglExportDMABUFImageMESA = (PFNEGLEXPORTDMABUFIMAGEMESAPROC)eglGetProcAddress(\"eglExportDMABUFImageMESA\");\n+\tglEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress(\"glEGLImageTargetTexture2DOES\");\n+\teglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress(\"eglClientWaitSyncKHR\");\n+\teglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress(\"eglCreateSyncKHR\");\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       unsigned int shaderDataLen,\n+\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       unsigned int shaderDataLen,\n+\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, Info) << \"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+}\ndiff --git a/src/libcamera/software_isp/egl.h b/src/libcamera/software_isp/egl.h\nnew file mode 100644\nindex 00000000..f3c5d50f\n--- /dev/null\n+++ b/src/libcamera/software_isp/egl.h\n@@ -0,0 +1,106 @@\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+#define GL_GLEXT_PROTOTYPES\n+#include <GL/gl.h>\n+#define EGL_EGLEXT_PROTOTYPES\n+#include <EGL/egl.h>\n+#include <EGL/eglext.h>\n+#include <GLES3/gl32.h>\n+\n+#include <libcamera/base/log.h>\n+\n+#include <unistd.h> // close\n+\n+#include \"gbm.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(eGL)\n+\n+class eGLImage {\n+public:\n+\teGLImage(uint32_t width, uint32_t height, uint32_t bpp) {\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+\n+\t\tglGenTextures(1, &texture_);\n+\t}\n+\n+\t~eGLImage() {\n+\t\tglDeleteTextures(1, &texture_);\n+\t};\n+\n+\tGLuint texture_;\n+\tEGLImageKHR image_;\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+};\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, uint32_t width, uint32_t height, void *data);\n+\tvoid createTexture1D(eGLImage *eglImage, 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  \tunsigned int shaderDataLen,\n+\t\t\t  \tstd::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+protected:\n+\n+};\n+\n+};\n","prefixes":["14/27"]}