Show a patch.

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

{
    "id": 24207,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/24207/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/24207/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/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": "<20250824-b4-v0-5-2-gpuisp-v2-a-v2-24-96f4576c814e@linaro.org>",
    "date": "2025-08-24T00:48:36",
    "name": "[v2,24/37] libcamera: software_isp: egl: Introduce an eGL base helper class",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "eb936a3a1c36e145217b171ed7331110f46e7f08",
    "submitter": {
        "id": 175,
        "url": "https://patchwork.libcamera.org/api/1.1/people/175/?format=api",
        "name": "Bryan O'Donoghue",
        "email": "bryan.odonoghue@linaro.org"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/24207/mbox/",
    "series": [
        {
            "id": 5400,
            "url": "https://patchwork.libcamera.org/api/1.1/series/5400/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5400",
            "date": "2025-08-24T00:48:12",
            "name": "Add GLES 2.0 GPUISP to libcamera",
            "version": 2,
            "mbox": "https://patchwork.libcamera.org/series/5400/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/24207/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/24207/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 67DC2BEFBE\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSun, 24 Aug 2025 00:49:29 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id F0C5969330;\n\tSun, 24 Aug 2025 02:49:28 +0200 (CEST)",
            "from mail-wr1-x42e.google.com (mail-wr1-x42e.google.com\n\t[IPv6:2a00:1450:4864:20::42e])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 45A4769301\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun, 24 Aug 2025 02:48:58 +0200 (CEST)",
            "by mail-wr1-x42e.google.com with SMTP id\n\tffacd0b85a97d-3c68ac7e238so724775f8f.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 23 Aug 2025 17:48:58 -0700 (PDT)",
            "from [192.168.0.13] (188-141-3-146.dynamic.upc.ie. [188.141.3.146])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-45b4e1d530esm69347225e9.0.2025.08.23.17.48.56\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tSat, 23 Aug 2025 17:48:57 -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=\"xTosWuPc\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=linaro.org; s=google; t=1755996538; x=1756601338;\n\tdarn=lists.libcamera.org; \n\th=cc:to:in-reply-to:references:message-id:content-transfer-encoding\n\t:mime-version:subject:date:from:from:to:cc:subject:date:message-id\n\t:reply-to; bh=1R6a5hzlND842KEEB7x7AFqaaRj4KgqmgTvlX61LMoE=;\n\tb=xTosWuPc02Ti1vy7GqcRaeB7ZsZn5rtO7p0xzuTV2uOLECeGdpiKLJn1ao9lTEDH1S\n\tzJO0qSbOcGFonmxgRFUNAW1VTduiKqqNU0KF+uG/kdrM8qR74zlek3QwYKYD/frLTSpY\n\t26VyeH9cZJy4u0RBXDr/iyvfb8YQHLG/xUwztPh3mm984pabrNWxjnB4fzZ9Wr+Tk9iA\n\t3wWkGHmig/FA/GEjHSg+r+AnRsauDsloNhOQ+GQfydDmfkeQHU8+07dHYFy9TUjRKZbX\n\tCMqAeHiohXnNMjFGboZG02WQab25UreJFCJFiHH4kZlbJtgska0MAQG3lCue3KErHGak\n\tizHw==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1755996538; x=1756601338;\n\th=cc:to:in-reply-to:references:message-id:content-transfer-encoding\n\t:mime-version:subject:date:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=1R6a5hzlND842KEEB7x7AFqaaRj4KgqmgTvlX61LMoE=;\n\tb=suYTnJFth8TQK5GJReo/VU03cDz+CrRKRdtXEag5Y/CFeHAtgJg/Ev6DPlSNsdKl6X\n\tY3ZOSmAcGO8SThficyac6u5qPVnzSL1O5Tl56hB86kXb8lFBUZvHDNDje5tg9IPN0kCw\n\tyidtb3r8Lj00utMlddacX3ezHxVLmCiXniCTPfpH49U9RfDzuOPpdiYogKf6aPtLjA0S\n\tXq0wXY5K243IP+AdszJd7Ynk1GR2IsE6CwLStRz4Cm27qSDbCAFvok7qfSjd/rG4ACqk\n\tbIcKecmVCLEAvO62j1mmQNRRS3cvcM74T5oMhuBZA6lCD45gTGp5VbKMQS3lA4lx42Cg\n\tW+hQ==",
        "X-Gm-Message-State": "AOJu0Yw8Xo0c8C/V+f5/vq48+iHNNHMWJ0Tv66LelCtMTBqPRieVaPgs\n\tOrf4yCqjXytBuTCfiKvKd1Fp9ar5iZ8SU+ML/bpw6LT5ur8RAkTTQAGEYSzrzHLMyIzlBGb2q0l\n\tqcYfvmAY=",
        "X-Gm-Gg": "ASbGnctLBOfs6RkW410bjILaR6CFwDwuPJ9O5sRKIX0tX7cC0gEXOIlfp3U1iv3ngr8\n\tJuTTsvd1bbVhXIYhG38+2YBzE4IceOi8P+REN7UpORWvUifyVQg9TAlMAasOvfH+XPQGTPAbDlJ\n\tqHTgJMpAe2Ah4Ht+KkZPK0Kql85AdG+HuDAyZFrBQTU3nXBfXHM3jGzR8bFmXsZmVWBp0RP9dj5\n\tmI87m57Zxc4z4XUNVDt1faDaxDOJRNoMpqw4fuydGknk7veMl5CHT5SzgtfzVDeQMMvDXOjztk5\n\taWVWvRMMxSolkMRKZmS314sqxZKT6Hh7QBnBZJe2bnxiGeaQiIeq0WsDmbhbnYLHDPi37ReLWuk\n\tuOKVip9ErX3cXzLRhjUms2S5uXSNztODk3cpnP4eo/b9dvdmjWBln8HIxjDx83kVRe8EhOyMtuh\n\toa/7DOk6ldOAC8NCYVgc2s",
        "X-Google-Smtp-Source": "AGHT+IG9I3yRfxUzzRuadmnNvxjFv9R9Ho+UznWVvvF/vvRTDr+l7YkH0CTLRmcqSprtcNi5wr5taA==",
        "X-Received": "by 2002:a05:6000:4205:b0:3c8:347e:b26c with SMTP id\n\tffacd0b85a97d-3c8347eb60emr911112f8f.38.1755996537621; \n\tSat, 23 Aug 2025 17:48:57 -0700 (PDT)",
        "From": "Bryan O'Donoghue <bryan.odonoghue@linaro.org>",
        "Date": "Sun, 24 Aug 2025 01:48:36 +0100",
        "Subject": "[PATCH v2 24/37] libcamera: software_isp: egl: Introduce an eGL\n\tbase helper class",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=\"utf-8\"",
        "Content-Transfer-Encoding": "7bit",
        "Message-Id": "<20250824-b4-v0-5-2-gpuisp-v2-a-v2-24-96f4576c814e@linaro.org>",
        "References": "<20250824-b4-v0-5-2-gpuisp-v2-a-v2-0-96f4576c814e@linaro.org>",
        "In-Reply-To": "<20250824-b4-v0-5-2-gpuisp-v2-a-v2-0-96f4576c814e@linaro.org>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Bryan O'Donoghue <bryan.odonoghue@linaro.org>",
        "X-Mailer": "b4 0.14.2",
        "X-Developer-Signature": "v=1; a=openpgp-sha256; l=17796;\n\ti=bryan.odonoghue@linaro.org; h=from:subject:message-id;\n\tbh=UppZEqSGkas9JB4z+6XYnxlO2Qcf71+kg3MOa90B8Io=;\n\tb=owEBbQKS/ZANAwAKASJxO7Ohjcg6AcsmYgBoqmFge2aN//0Aq8nXSpYtY4RAd+z9YKlb/uLeb\n\tFz3FH4AmMCJAjMEAAEKAB0WIQTmk/sqq6Nt4Rerb7QicTuzoY3IOgUCaKphYAAKCRAicTuzoY3I\n\tOhjvD/9tBoX6d9IfUJaZSVNcYWCaBsQxHYrDjDPYp5ENfvcx36C5eUNHKPMdSPYu17YaLZ9IJpM\n\tRpxDDz4XCgPcnkp0v6fRCXzefRT6fHW0i2CM2zTI2sCkOG5x5CSZ/3oPUChcf3t7HwQAytSSdeA\n\tdQCbqWYsa27/OKgHztG92QhKG3Q9x4DOPKqjRsxcWo8w4BHUDPH3YP1aB0Mk49yIen6Tf62SDfn\n\taO6/0KLC6zFsHSWFaSpP1DgOKYGPkNSlXSA6mgqUlizaWzoT1NATbxarSxbguXpnCo+RjU2bGGS\n\tBHQ06Pj7GQO4s7aqJRSEEz0szaVCTHchBsdOckgMVnrn0mokyjnZwVfnSYCgZaLhx+LJxbt3iEo\n\terEfVCLL5qFG5lq9Tv2SVqs3OniLocUQyAJNWCBReR8nyeX5GJKub6nIV4b0zbR93+KgoHyTMwr\n\t4yPtaxYrhKzpj+foU3/UWAtkHuK19y7d3o/rQZTT9w4F2P1XxQUxYylUXVlKUhQ124iX7a6lvg3\n\tw91ELLkNmZyYMs+sMN2t4Syg4+UgnndjfCyhXKQA3ERHJCQHmgxa5wtS+q41G+4Df3fk/hYnU/a\n\tMVo8pMctfkz4RqG6Bjps/5oI9O48IAO/YUUAZJftIzott8AtYhPHEpH09GmCGv48n2rMZg5aSNk\n\to4NdkGmEs6efaqQ==",
        "X-Developer-Key": "i=bryan.odonoghue@linaro.org; a=openpgp;\n\tfpr=E693FB2AABA36DE117AB6FB422713BB3A18DC83A",
        "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 | 133 +++++++++++++\n src/libcamera/egl.cpp            | 408 +++++++++++++++++++++++++++++++++++++++\n src/libcamera/meson.build        |  23 +++\n 3 files changed, 564 insertions(+)",
    "diff": "diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..2c48e28e14aaa1fa8029dd18662c8ce24e6a550c\n--- /dev/null\n+++ b/include/libcamera/internal/egl.h\n@@ -0,0 +1,133 @@\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, 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 = ALIGN_TO(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+\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+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+\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+\tint syncOutput();\n+\n+private:\n+\tint fd_;\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  std::vector<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 0000000000000000000000000000000000000000..b56c65a33837bb31a3e19613f4abb52d77880301\n--- /dev/null\n+++ b/src/libcamera/egl.cpp\n@@ -0,0 +1,408 @@\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 <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+}\n+\n+eGL::~eGL()\n+{\n+}\n+\n+int eGL::syncOutput(void)\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+\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_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// 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+\treturn createDMABufTexture2D(eglImage, fd, false);\n+}\n+int eGL::createOutputDMABufTexture2D(eGLImage *eglImage, int fd)\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+\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+\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+\tif (sync_) {\n+\t\tmakeCurrent();\n+\t\teglDestroySyncKHR(display_, sync_);\n+\t}\n+\n+}\n+void eGL::makeCurrent(void)\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::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\ndiff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\nindex 64270b571cea2ece8d4dfea8a23b8edf60de70d3..705c328a27f68377573916a77573335f4f4ab3cc 100644\n--- a/src/libcamera/meson.build\n+++ b/src/libcamera/meson.build\n@@ -78,6 +78,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@@ -191,7 +212,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": [
        "v2",
        "24/37"
    ]
}