Show a patch.

GET /api/patches/24671/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 24671,
    "url": "https://patchwork.libcamera.org/api/patches/24671/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/24671/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20251015012251.17508-27-bryan.odonoghue@linaro.org>",
    "date": "2025-10-15T01:22:38",
    "name": "[v3,26/39] libcamera: software_isp: egl: Introduce an eGL base helper class",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "9627a7d57ee624b2f6b5f89e514ef9463ef8dc53",
    "submitter": {
        "id": 175,
        "url": "https://patchwork.libcamera.org/api/people/175/?format=api",
        "name": "Bryan O'Donoghue",
        "email": "bryan.odonoghue@linaro.org"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/24671/mbox/",
    "series": [
        {
            "id": 5503,
            "url": "https://patchwork.libcamera.org/api/series/5503/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5503",
            "date": "2025-10-15T01:22:12",
            "name": "Add GLES 2.0 GPUISP to libcamera",
            "version": 3,
            "mbox": "https://patchwork.libcamera.org/series/5503/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/24671/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/24671/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 9CB0AC3335\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 15 Oct 2025 01:23:36 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 326E96066A;\n\tWed, 15 Oct 2025 03:23:36 +0200 (CEST)",
            "from mail-ed1-x531.google.com (mail-ed1-x531.google.com\n\t[IPv6:2a00:1450:4864:20::531])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0A0686064C\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 15 Oct 2025 03:23:22 +0200 (CEST)",
            "by mail-ed1-x531.google.com with SMTP id\n\t4fb4d7f45d1cf-63b9da57cecso5187805a12.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 14 Oct 2025 18:23:22 -0700 (PDT)",
            "from inspiron14p-linux.nxsw.local (188-141-3-146.dynamic.upc.ie.\n\t[188.141.3.146]) by smtp.gmail.com with ESMTPSA id\n\ta640c23a62f3a-b5ccd7b202dsm98348466b.82.2025.10.14.18.23.19\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tTue, 14 Oct 2025 18:23:20 -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=\"DasP0w70\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=linaro.org; s=google; t=1760491401; x=1761096201;\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=XOWqBm0QMLRhHmCkYqOM/ZUTCSHw7t28B8nnYsbLPSM=;\n\tb=DasP0w706aTApLuQybNcm7hXHGlnDBIePz0tbGj0xd3RoFV74UKTeruZxEeU6bDLoo\n\ttjAOQFxq+F+MtAV3r5kxN0Fl3x7fYaxR7iTGy6go3C3AhFjaUfqCYIvfveyT7wf1++V8\n\tmLtDWPS4mljGBEyTRIOVL3APGxpB6bydbxaxawkMB0vhf+UVlWIEutn/FSKabliJI7fV\n\tmTJ9Xzoq/Ffx1icrcI0yaqNjItQsTcXQkAfV2UoCZeKb9o9pVB9F/jyMPsz6wsigUK4L\n\tQuTmwUVSxbPEpByADKKOUc7RqWJeUEA8oypTPj2It0wriVLutz5/6iwvPGRoWHPAMgDc\n\tlh/A==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1760491401; x=1761096201;\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=XOWqBm0QMLRhHmCkYqOM/ZUTCSHw7t28B8nnYsbLPSM=;\n\tb=uClnkPvSWmcTWsOwSuKTi3dYzPadQujsoADNqhgr632sEQaq3iNRjxzcnrpWUD0Evd\n\tcqnj5gjSNAaGfqJoIVGh1KIHm+jh2SLJUuloh0acXZjFJGKZ+pE0hb+ndxpdU1rjc5sP\n\tTMx3Zbisb1AlZ76oWWWUWBduGPglBc2RLSNte15eURVFfROhmaFD4aqZmIUxP0lY2WXC\n\tblIj8pqYx9xji09gv5UiICCNNHDN8jocWmznPkOBCmPsEYsdYd7fNTEZGrDpSxd917dS\n\tU7HfwNvjC/t5S2Nl7FkxSp06WGG0/7ESZqXoD09boXii2veefEAinM8EPn7/j+brYJZe\n\tVZng==",
        "X-Gm-Message-State": "AOJu0YzDTOhgkrTt5Iq9WwJ/n4ACV9+8Xry+oiQo/jGStthiRjpVl36l\n\tabI62IhqGLHuSErIl+jOt3z1RjQk4bxqGfdkaB6yuOiKBu0+PRGLyZne1fvWn7WNESOKaq1jRvs\n\tHdyUw",
        "X-Gm-Gg": "ASbGnctHxhDcftjT58lC+ld+4peVMVIY/mKmUixZpJNfzQO4A3gy5CwSn30LlU3gXPC\n\tcgG0RUxpxgtnxiglRk5/WGKjIspb7/j122InvBDsx4BAMr2a+DzEMR6iu/+t9/79HAz21axTTHp\n\tAma9DydaSWoWJtFcwPUbpbLupa2fczqvJ/vnqJylQ8NlrcpkKOHWrLeMJ7hfT/u9k0p+1/2Eta6\n\tuPQhAdbRdu7UnU3oeQLn/z4sfpyw0kUztcNEY66JVDdE7oqy658ocsyLL0vKcvHBOqLi5nNeelu\n\tr9sBfa3/AmOVjd5491Q83dnVJvBQ29oJ7QBoIPZpnFWApbjUyc5MK3FYbkhZL3+Tla8nX2IYuqH\n\tTcz2r7ww7D0mJRURUOLGIRSuFIwwEuOpFyEZgdQZ19wc68urN2/ujBziKyDSY9VoJOMWU6kK96v\n\tkf+u+SIRc2/ihyKD86HKmqVle15cOUOz5dqOGAnnB4",
        "X-Google-Smtp-Source": "AGHT+IGwUXmzAwFdMIWczlXZk4awLtk3PyKvkmAqvBR31K9nXxhbK5ZhK78OpsJUwonwSH8+40mGgA==",
        "X-Received": "by 2002:a17:906:ef02:b0:b3f:f66b:268a with SMTP id\n\ta640c23a62f3a-b50aa08ef47mr3037260466b.19.1760491401199; \n\tTue, 14 Oct 2025 18:23:21 -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 v3 26/39] libcamera: software_isp: egl: Introduce an eGL base\n\thelper class",
        "Date": "Wed, 15 Oct 2025 02:22:38 +0100",
        "Message-ID": "<20251015012251.17508-27-bryan.odonoghue@linaro.org>",
        "X-Mailer": "git-send-email 2.51.0",
        "In-Reply-To": "<20251015012251.17508-1-bryan.odonoghue@linaro.org>",
        "References": "<20251015012251.17508-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\nSmall note the image_attrs[] array doesn't pass checkstyle.py however the\nelements of the array are in pairs.\n\nSigned-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",
    "diff": "diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h\nnew file mode 100644\nindex 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+ */\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+\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+\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\ndiff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp\nnew file mode 100644\nindex 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+\tcontext_ = EGL_NO_CONTEXT;\n+\tsurface_ = EGL_NO_SURFACE;\n+\tdisplay_ = EGL_NO_DISPLAY;\n+}\n+\n+eGL::~eGL()\n+{\n+\tif (context_ != EGL_NO_CONTEXT)\n+\t\teglDestroyContext(display_, context_);\n+\n+\tif (surface_ != EGL_NO_SURFACE)\n+\t\teglDestroySurface(display_, surface_);\n+\n+}\n+\n+int eGL::syncOutput(void)\n+{\n+\tASSERT(tid_ == Thread::currentId());\n+\n+\tglFlush();\n+\teglClientWaitSyncKHR(display_, sync_, 0, EGL_FOREVER_KHR);\n+\n+\treturn 0;\n+}\n+\n+// Create linear image attached to previous BO object\n+int eGL::createDMABufTexture2D(eGLImage *eglImage, int fd, bool output)\n+{\n+\tint ret = 0;\n+\n+\tASSERT(tid_ == Thread::currentId());\n+\n+\t// clang-format off\n+\tEGLint image_attrs[] = {\n+\t\tEGL_WIDTH, (EGLint)eglImage->width_,\n+\t\tEGL_HEIGHT, (EGLint)eglImage->height_,\n+\t\tEGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_ARGB8888,\n+\t\tEGL_DMA_BUF_PLANE0_FD_EXT, fd,\n+\t\tEGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,\n+\t\tEGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)eglImage->stride_,\n+\t\tEGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, 0,\n+\t\tEGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, 0,\n+\t\tEGL_NONE,\n+\t};\n+\t// clang-format on\n+\n+\teglImage->image_ = eglCreateImageKHR(display_, EGL_NO_CONTEXT,\n+\t\t\t\t\t     EGL_LINUX_DMA_BUF_EXT,\n+\t\t\t\t\t     NULL, image_attrs);\n+\n+\tif (eglImage->image_ == EGL_NO_IMAGE_KHR) {\n+\t\tLOG(eGL, Error) << \"eglCreateImageKHR fail\";\n+\t\tret = -ENODEV;\n+\t\tgoto done;\n+\t}\n+\n+\t// Bind texture unit and texture\n+\tglActiveTexture(eglImage->texture_unit_);\n+\tglBindTexture(GL_TEXTURE_2D, eglImage->texture_);\n+\n+\t// Generate texture with filter semantics\n+\tglEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage->image_);\n+\n+\t// Nearest filtering\n+\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n+\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n+\n+\t// Wrap to edge to avoid edge artifacts\n+\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n+\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n+\n+\tif (output) {\n+\t\t// Generate a framebuffer from our texture direct to dma-buf handle buffer\n+\t\tglBindFramebuffer(GL_FRAMEBUFFER, eglImage->fbo_);\n+\t\tglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, eglImage->texture_, 0);\n+\n+\t\tGLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n+\t\tif (err!= GL_FRAMEBUFFER_COMPLETE) {\n+\t\t\tLOG(eGL, Error) << \"glFrameBufferTexture2D error \" << err;\n+\t\t\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+}\n+\n+void eGL::makeCurrent(void)\n+{\n+\tASSERT(tid_ == Thread::currentId());\n+\n+\tif (eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, context_) != EGL_TRUE) {\n+\t\tLOG(eGL, Error) << \"eglMakeCurrent fail\";\n+\t}\n+}\n+\n+void eGL::useProgram(GLuint programId)\n+{\n+\tASSERT(tid_ == Thread::currentId());\n+\n+\tglUseProgram(programId);\n+}\n+\n+void eGL::deleteProgram(GLuint programId)\n+{\n+\tASSERT(tid_ == Thread::currentId());\n+\n+\tglDeleteProgram(programId);\n+}\n+\n+void eGL::pushEnv(std::vector<std::string> &shaderEnv, const char *str)\n+{\n+\tstd::string addStr = str;\n+\n+\taddStr.push_back('\\n');\n+\tshaderEnv.push_back(std::move(addStr));\n+}\n+\n+int eGL::compileVertexShader(GLuint &shaderId, unsigned char *shaderData,\n+\t\t\t     unsigned int shaderDataLen,\n+\t\t\t     Span<const std::string> shaderEnv)\n+{\n+\treturn compileShader(GL_VERTEX_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv);\n+}\n+\n+int eGL::compileFragmentShader(GLuint &shaderId, unsigned char *shaderData,\n+\t\t\t       unsigned int shaderDataLen,\n+\t\t\t       Span<const std::string> shaderEnv)\n+{\n+\treturn compileShader(GL_FRAGMENT_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv);\n+}\n+\n+int eGL::compileShader(int shaderType, GLuint &shaderId, unsigned char *shaderData,\n+\t\t       unsigned int shaderDataLen,\n+\t\t       Span<const std::string> shaderEnv)\n+{\n+\tGLint success;\n+\tsize_t i;\n+\n+\tASSERT(tid_ == Thread::currentId());\n+\n+\tauto count = 1 + shaderEnv.size();\n+\tauto shaderSourceData = std::make_unique<const GLchar*[]>(count);\n+\tauto shaderDataLengths = std::make_unique<GLint[]>(count);\n+\n+\t// Prefix defines before main body of shader\n+\tfor (i = 0; i < shaderEnv.size(); i++) {\n+\t\tshaderSourceData[i] = shaderEnv[i].c_str();\n+\t\tshaderDataLengths[i] = shaderEnv[i].length();\n+\t}\n+\n+\t// Now the main body of the shader program\n+\tshaderSourceData[i] = reinterpret_cast<const GLchar*>(shaderData);\n+\tshaderDataLengths[i] = shaderDataLen;\n+\n+\t// And create the shader\n+\tshaderId = glCreateShader(shaderType);\n+\tglShaderSource(shaderId, count, shaderSourceData.get(), shaderDataLengths.get());\n+\tglCompileShader(shaderId);\n+\n+\t// Check status\n+\tglGetShaderiv(shaderId, GL_COMPILE_STATUS, &success);\n+\tif (success == GL_FALSE) {\n+\t\tGLint sizeLog = 0;\n+\n+\t\tglGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &sizeLog);\n+\t\tauto infoLog = std::make_unique<GLchar[]>(sizeLog);\n+\n+\t\tglGetShaderInfoLog(shaderId, sizeLog, &sizeLog, infoLog.get());\n+\t\tLOG(eGL, Error) << infoLog.get();\n+\t}\n+\n+\treturn !(success == GL_TRUE);\n+}\n+\n+void eGL::dumpShaderSource(GLuint shaderId)\n+{\n+\tGLint shaderLength = 0;\n+\n+\tASSERT(tid_ == Thread::currentId());\n+\n+\tglGetShaderiv(shaderId, GL_SHADER_SOURCE_LENGTH, &shaderLength);\n+\n+\tLOG(eGL, Debug) << \"Shader length is \" << shaderLength;\n+\n+\tif (shaderLength > 0) {\n+\t\tauto shaderSource = std::make_unique<GLchar []>(shaderLength);\n+\n+\t\tglGetShaderSource(shaderId, shaderLength, &shaderLength, shaderSource.get());\n+\t\tif (shaderLength) {\n+\t\t\tLOG(eGL, Debug) << \"Shader source = \" << shaderSource.get();\n+\t\t}\n+\t}\n+}\n+\n+int eGL::linkProgram(GLuint &programId, GLuint vertexshaderId, GLuint fragmentshaderId)\n+{\n+\tGLint success;\n+\tGLenum err;\n+\n+\tASSERT(tid_ == Thread::currentId());\n+\n+\tprogramId = glCreateProgram();\n+\tif (!programId)\n+\t\tgoto fail;\n+\n+\tglAttachShader(programId, vertexshaderId);\n+\tif ((err = glGetError()) != GL_NO_ERROR) {\n+\t\tLOG(eGL, Error) << \"Attach compute vertex shader fail\";\n+\t\tgoto fail;\n+\t}\n+\n+\tglAttachShader(programId, fragmentshaderId);\n+\tif ((err = glGetError()) != GL_NO_ERROR) {\n+\t\tLOG(eGL, Error) << \"Attach compute vertex shader fail\";\n+\t\tgoto fail;\n+\t}\n+\n+\tglLinkProgram(programId);\n+\tif ((err = glGetError()) != GL_NO_ERROR) {\n+\t\tLOG(eGL, Error) << \"Link program fail\";\n+\t\tgoto fail;\n+\t}\n+\n+\tglDetachShader(programId, fragmentshaderId);\n+\tglDetachShader(programId, vertexshaderId);\n+\n+\t// Check status\n+\tglGetProgramiv(programId, GL_LINK_STATUS, &success);\n+\tif (success == GL_FALSE) {\n+\t\tGLint sizeLog = 0;\n+\t\tGLchar *infoLog;\n+\n+\t\tglGetProgramiv(programId, GL_INFO_LOG_LENGTH, &sizeLog);\n+\t\tinfoLog = new GLchar[sizeLog];\n+\n+\t\tglGetProgramInfoLog(programId, sizeLog, &sizeLog, infoLog);\n+\t\tLOG(eGL, Error) << infoLog;\n+\n+\t\tdelete[] infoLog;\n+\t\tgoto fail;\n+\t}\n+\n+\treturn 0;\n+fail:\n+\tif (programId)\n+\t\tglDeleteProgram(programId);\n+\n+\treturn -ENODEV;\n+}\n+} // namespace libcamera\ndiff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\nindex 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,\n",
    "prefixes": [
        "v3",
        "26/39"
    ]
}