Show a patch.

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

{
    "id": 25669,
    "url": "https://patchwork.libcamera.org/api/patches/25669/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/25669/",
    "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": "<20260106170059.56193-16-bryan.odonoghue@linaro.org>",
    "date": "2026-01-06T17:00:50",
    "name": "[v11,15/24] libcamera: software_isp: debayer_egl: Add an eGL Debayer class",
    "commit_ref": "f520b29fe9e65baf0173d027f819c2024245b860",
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "1c3cadee201e4f33e876cbcf43f74ca4d43ac271",
    "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/25669/mbox/",
    "series": [
        {
            "id": 5689,
            "url": "https://patchwork.libcamera.org/api/series/5689/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5689",
            "date": "2026-01-06T17:00:35",
            "name": "Add GLES 2.0 GPUISP to libcamera",
            "version": 10,
            "mbox": "https://patchwork.libcamera.org/series/5689/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/25669/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/25669/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 D8567C3213\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  6 Jan 2026 17:01:26 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8269B62022;\n\tTue,  6 Jan 2026 18:01:26 +0100 (CET)",
            "from mail-wr1-x443.google.com (mail-wr1-x443.google.com\n\t[IPv6:2a00:1450:4864:20::443])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 6D15961FD8\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  6 Jan 2026 18:01:24 +0100 (CET)",
            "by mail-wr1-x443.google.com with SMTP id\n\tffacd0b85a97d-42e2d02a3c9so808222f8f.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 06 Jan 2026 09:01:24 -0800 (PST)",
            "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\tffacd0b85a97d-432bd0dadcfsm5515322f8f.3.2026.01.06.09.01.21\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tTue, 06 Jan 2026 09:01:21 -0800 (PST)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=linaro.org header.i=@linaro.org\n\theader.b=\"uff4Kujo\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=linaro.org; s=google; t=1767718884; x=1768323684;\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=FlcsopdCqx0Vxy6Jo6h6qgZr9xc2hxHELOy7x3Ka3AA=;\n\tb=uff4Kujo176Gd4CYBAQ7UF88VERED2VtMYTg14Boq7g5IQ6NRCCgi7begry/lTa9SL\n\t1e0LYw/AMGp0I0VteueArmLdiUQYJ5ue1KYUtdGdHD1dlqnZ9c8XfCd5rlrEYLjmcfSM\n\trvfrGr+fckde8VcBtl0qLRcRPt1gZlFGtMPVUns6vgFkVXzcqZS0tzQvQRw0A8CBCSZj\n\t96bGq0KuX+HUX5KL2AlVhSo3HaS9O43PfnTd2GXzAXxN8BisEK+JUWbT2IfNB3/XTdep\n\t9k2TBRnkPAo2M84fYkOb7BO//anbKLukLHh2/tmn6OfogOmfowfguM7/kqTeFtc6mCpW\n\tBQ1Q==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1767718884; x=1768323684;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n\t:to:cc:subject:date:message-id:reply-to;\n\tbh=FlcsopdCqx0Vxy6Jo6h6qgZr9xc2hxHELOy7x3Ka3AA=;\n\tb=YhM1ND1wVUn4s5E1Mo4RtZ7y95rSsl4q+GaI6I09UjYCtSyv/hyGNbGm1jY3Gmiolh\n\tfmc4S89mw2h6aD90ZNoAmCMk3KEI0ro3pcGvKg27CIKf5KK2ZDHiITTk/1NRbxBhv7aZ\n\t1e1Np+Ef23V3dRkMOTCpLJnneYjYUeIYpBH75YfbWZ0jQ3kPcZuFT/WOPSbkvzEMOGtt\n\tliXBqe7aD6TWEmmd0OxwB5FWtMB/WdPlCc1yHWexh09x6Vbc+YqY9566DDIpwwcEcchy\n\ttyaeHB/xXMqUMKNVC5dKdD54p7r0zQV++YwpKKq57yqk9qbAZactdkYS0eRU7i2IkzF8\n\tkW+A==",
        "X-Gm-Message-State": "AOJu0Ywk0e3nY1CFy7G2iGRtdCH2iqsdJk1eUio+Yu4VxSkLoVEKItZe\n\t7BjoPL4jSs3pRwKIJLnWGEnM3qKiv+9ujPDo43pkoMX/HWo0ajpwqq7YojDEBXHxOv5V32J12N+\n\tLCG7I6ge/wg==",
        "X-Gm-Gg": "AY/fxX58vMte3gyeLivX8WZrn+sK7A+zAbPhnaPIC8yOTPuapDCep5DVwPiuytemi56\n\tHAxxI308IZlAR8DylGz+WOCA0Yevi7j4EIvkUxSuwOyF2dbnYSOfH6eKLs7Y4UTC9/WvuStmS1e\n\tVn/IeEJw1nRN58TOalnY0MwbzM0uWOC1kNIBRRp631keFZLt7P0MydJ/AShWYRQ/7jEI/UbJRWc\n\t7mzEQCb6suyKvP1pYpnGOuKiEFFRTPUCPthppBNmX9MLvyBQbfzaskZb39mWTqr/MStuS2wu5/r\n\tzXpSIJYx/fVUnHjSgoGaDv8GprxplSbCS6Sl9V2SSuyFBW3vKgwrAg+0k+qvSA3Hjv498bVzHUL\n\tTms5awC90Eqr1S74Xts4ItcMktb1dRWBYE9GtzThp/QiekHROBxKM+QoAXQHE+Qnt6NZQaTq1x+\n\ta+tjLpnoBzRpub2GoeqnzoUJKoSZXWohYWyPWOiKhpU/0yCSr02i3Bb1gNnSdNOO340CU=",
        "X-Google-Smtp-Source": "AGHT+IEg/eIbxWNvie5S1NjuEmS3bzJhS10nht8U8A59UE8VldKi7LLS4BvfebKAp8AZ78AMKt7wtg==",
        "X-Received": "by 2002:a05:6000:2c0f:b0:42b:396e:2817 with SMTP id\n\tffacd0b85a97d-432bc9f4634mr4944382f8f.40.1767718881965; \n\tTue, 06 Jan 2026 09:01:21 -0800 (PST)",
        "From": "Bryan O'Donoghue <bryan.odonoghue@linaro.org>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "pavel@ucw.cz, Bryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tRobert Mader <robert.mader@collabora.com>,\n\tMilan Zamazal <mzamazal@redhat.com>",
        "Subject": "[PATCH v11 15/24] libcamera: software_isp: debayer_egl: Add an eGL\n\tDebayer class",
        "Date": "Tue,  6 Jan 2026 17:00:50 +0000",
        "Message-ID": "<20260106170059.56193-16-bryan.odonoghue@linaro.org>",
        "X-Mailer": "git-send-email 2.52.0",
        "In-Reply-To": "<20260106170059.56193-1-bryan.odonoghue@linaro.org>",
        "References": "<20260106170059.56193-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": "Add a class to run the existing glsl debayer shaders on a GBM surface.\n\nSigned-off-by: Robert Mader <robert.mader@collabora.com>\nCo-developed-by: Robert Mader <robert.mader@collabora.com>\n[bod: took scaling and buffer size fixes from Robert]\n[bod: took fix for center byte calculation from Hans]\n[bod: took formatting fixes from Milan]\nSigned-off-by: Milan Zamazal <mzamazal@redhat.com>\nCo-developed-by: Milan Zamazal <mzamazal@redhat.com>\nReviewed-by: Robert Mader <robert.mader@collabora.com>\nSigned-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n---\n src/libcamera/software_isp/debayer_egl.cpp | 651 +++++++++++++++++++++\n src/libcamera/software_isp/debayer_egl.h   | 124 ++++\n src/libcamera/software_isp/meson.build     |   8 +\n 3 files changed, 783 insertions(+)\n create mode 100644 src/libcamera/software_isp/debayer_egl.cpp\n create mode 100644 src/libcamera/software_isp/debayer_egl.h",
    "diff": "diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp\nnew file mode 100644\nindex 000000000..8e0890323\n--- /dev/null\n+++ b/src/libcamera/software_isp/debayer_egl.cpp\n@@ -0,0 +1,651 @@\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+ */\n+\n+#include \"debayer_egl.h\"\n+\n+#include <cmath>\n+#include <stdlib.h>\n+#include <time.h>\n+\n+#include <libcamera/base/utils.h>\n+\n+#include <libcamera/formats.h>\n+\n+#include \"../glsl_shaders.h\"\n+\n+namespace libcamera {\n+\n+/**\n+ * \\class DebayerEGL\n+ * \\brief Class for debayering using an EGL Shader\n+ *\n+ * Implements an EGL shader based debayering solution.\n+ */\n+\n+/**\n+ * \\fn DebayerEGL::DebayerEGL(std::unique_ptr<SwStatsCpu> stats, const GlobalConfiguration &configuration)\n+ * \\brief Construct a DebayerEGL object\n+ * \\param[in] stats Statistics processing object\n+ * \\param[in] configuration Global configuration reference\n+ */\n+DebayerEGL::DebayerEGL(std::unique_ptr<SwStatsCpu> stats, const GlobalConfiguration &configuration)\n+\t: Debayer(configuration), stats_(std::move(stats))\n+{\n+}\n+\n+DebayerEGL::~DebayerEGL()\n+{\n+}\n+\n+int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config)\n+{\n+\tBayerFormat bayerFormat =\n+\t\tBayerFormat::fromPixelFormat(inputFormat);\n+\n+\tif ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10) &&\n+\t    bayerFormat.packing == BayerFormat::Packing::None &&\n+\t    isStandardBayerOrder(bayerFormat.order)) {\n+\t\tconfig.bpp = (bayerFormat.bitDepth + 7) & ~7;\n+\t\tconfig.patternSize.width = 2;\n+\t\tconfig.patternSize.height = 2;\n+\t\tconfig.outputFormats = std::vector<PixelFormat>({ formats::XRGB8888,\n+\t\t\t\t\t\t\t\t  formats::ARGB8888,\n+\t\t\t\t\t\t\t\t  formats::XBGR8888,\n+\t\t\t\t\t\t\t\t  formats::ABGR8888 });\n+\t\treturn 0;\n+\t}\n+\n+\tif (bayerFormat.bitDepth == 10 &&\n+\t    bayerFormat.packing == BayerFormat::Packing::CSI2 &&\n+\t    isStandardBayerOrder(bayerFormat.order)) {\n+\t\tconfig.bpp = 10;\n+\t\tconfig.patternSize.width = 4; /* 5 bytes per *4* pixels */\n+\t\tconfig.patternSize.height = 2;\n+\t\tconfig.outputFormats = std::vector<PixelFormat>({ formats::XRGB8888,\n+\t\t\t\t\t\t\t\t  formats::ARGB8888,\n+\t\t\t\t\t\t\t\t  formats::XBGR8888,\n+\t\t\t\t\t\t\t\t  formats::ABGR8888 });\n+\t\treturn 0;\n+\t}\n+\n+\tLOG(Debayer, Error)\n+\t\t<< \"Unsupported input format \" << inputFormat;\n+\n+\treturn -EINVAL;\n+}\n+\n+int DebayerEGL::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config)\n+{\n+\tif (outputFormat == formats::XRGB8888 || outputFormat == formats::ARGB8888 ||\n+\t    outputFormat == formats::XBGR8888 || outputFormat == formats::ABGR8888) {\n+\t\tconfig.bpp = 32;\n+\t\treturn 0;\n+\t}\n+\n+\tLOG(Debayer, Error)\n+\t\t<< \"Unsupported output format \" << outputFormat;\n+\n+\treturn -EINVAL;\n+}\n+\n+int DebayerEGL::getShaderVariableLocations(void)\n+{\n+\tattributeVertex_ = glGetAttribLocation(programId_, \"vertexIn\");\n+\tattributeTexture_ = glGetAttribLocation(programId_, \"textureIn\");\n+\n+\ttextureUniformBayerDataIn_ = glGetUniformLocation(programId_, \"tex_y\");\n+\tccmUniformDataIn_ = glGetUniformLocation(programId_, \"ccm\");\n+\tblackLevelUniformDataIn_ = glGetUniformLocation(programId_, \"blacklevel\");\n+\tgammaUniformDataIn_ = glGetUniformLocation(programId_, \"gamma\");\n+\tcontrastExpUniformDataIn_ = glGetUniformLocation(programId_, \"contrastExp\");\n+\n+\ttextureUniformStep_ = glGetUniformLocation(programId_, \"tex_step\");\n+\ttextureUniformSize_ = glGetUniformLocation(programId_, \"tex_size\");\n+\ttextureUniformStrideFactor_ = glGetUniformLocation(programId_, \"stride_factor\");\n+\ttextureUniformBayerFirstRed_ = glGetUniformLocation(programId_, \"tex_bayer_first_red\");\n+\ttextureUniformProjMatrix_ = glGetUniformLocation(programId_, \"proj_matrix\");\n+\n+\tLOG(Debayer, Debug) << \"vertexIn \" << attributeVertex_ << \" textureIn \" << attributeTexture_\n+\t\t\t    << \" tex_y \" << textureUniformBayerDataIn_\n+\t\t\t    << \" ccm \" << ccmUniformDataIn_\n+\t\t\t    << \" blacklevel \" << blackLevelUniformDataIn_\n+\t\t\t    << \" gamma \" << gammaUniformDataIn_\n+\t\t\t    << \" contrastExp \" << contrastExpUniformDataIn_\n+\t\t\t    << \" tex_step \" << textureUniformStep_\n+\t\t\t    << \" tex_size \" << textureUniformSize_\n+\t\t\t    << \" stride_factor \" << textureUniformStrideFactor_\n+\t\t\t    << \" tex_bayer_first_red \" << textureUniformBayerFirstRed_\n+\t\t\t    << \" proj_matrix \" << textureUniformProjMatrix_;\n+\treturn 0;\n+}\n+\n+int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputFormat)\n+{\n+\tstd::vector<std::string> shaderEnv;\n+\tunsigned int fragmentShaderDataLen = 0;\n+\tconst unsigned char *fragmentShaderData = 0;\n+\tunsigned int vertexShaderDataLen = 0;\n+\tconst unsigned char *vertexShaderData = 0;\n+\tGLenum err;\n+\n+\t/* Target gles 100 glsl requires \"#version x\" as first directive in shader */\n+\tegl_.pushEnv(shaderEnv, \"#version 100\");\n+\n+\t/* Specify GL_OES_EGL_image_external */\n+\tegl_.pushEnv(shaderEnv, \"#extension GL_OES_EGL_image_external: enable\");\n+\n+\t/*\n+\t * Tell shaders how to re-order output taking account of how the\n+\t * pixels are actually stored by GBM\n+\t */\n+\tswitch (outputFormat) {\n+\tcase formats::ARGB8888:\n+\tcase formats::XRGB8888:\n+\t\tbreak;\n+\tcase formats::ABGR8888:\n+\tcase formats::XBGR8888:\n+\t\tegl_.pushEnv(shaderEnv, \"#define SWAP_BLUE\");\n+\t\tbreak;\n+\tdefault:\n+\t\tLOG(Debayer, Error) << \"Unsupported output format\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* Pixel location parameters */\n+\tglFormat_ = GL_LUMINANCE;\n+\tbytesPerPixel_ = 1;\n+\tshaderStridePixels_ = inputConfig_.stride;\n+\n+\tswitch (inputFormat) {\n+\tcase libcamera::formats::SBGGR8:\n+\tcase libcamera::formats::SBGGR10_CSI2P:\n+\tcase libcamera::formats::SBGGR12_CSI2P:\n+\t\tfirstRed_x_ = 1.0;\n+\t\tfirstRed_y_ = 1.0;\n+\t\tbreak;\n+\tcase libcamera::formats::SGBRG8:\n+\tcase libcamera::formats::SGBRG10_CSI2P:\n+\tcase libcamera::formats::SGBRG12_CSI2P:\n+\t\tfirstRed_x_ = 0.0;\n+\t\tfirstRed_y_ = 1.0;\n+\t\tbreak;\n+\tcase libcamera::formats::SGRBG8:\n+\tcase libcamera::formats::SGRBG10_CSI2P:\n+\tcase libcamera::formats::SGRBG12_CSI2P:\n+\t\tfirstRed_x_ = 1.0;\n+\t\tfirstRed_y_ = 0.0;\n+\t\tbreak;\n+\tcase libcamera::formats::SRGGB8:\n+\tcase libcamera::formats::SRGGB10_CSI2P:\n+\tcase libcamera::formats::SRGGB12_CSI2P:\n+\t\tfirstRed_x_ = 0.0;\n+\t\tfirstRed_y_ = 0.0;\n+\t\tbreak;\n+\tdefault:\n+\t\tLOG(Debayer, Error) << \"Unsupported input format\";\n+\t\treturn -EINVAL;\n+\t};\n+\n+\t/* Shader selection */\n+\tswitch (inputFormat) {\n+\tcase libcamera::formats::SBGGR8:\n+\tcase libcamera::formats::SGBRG8:\n+\tcase libcamera::formats::SGRBG8:\n+\tcase libcamera::formats::SRGGB8:\n+\t\tfragmentShaderData = bayer_unpacked_frag;\n+\t\tfragmentShaderDataLen = bayer_unpacked_frag_len;\n+\t\tvertexShaderData = bayer_unpacked_vert;\n+\t\tvertexShaderDataLen = bayer_unpacked_vert_len;\n+\t\tbreak;\n+\tcase libcamera::formats::SBGGR10_CSI2P:\n+\tcase libcamera::formats::SGBRG10_CSI2P:\n+\tcase libcamera::formats::SGRBG10_CSI2P:\n+\tcase libcamera::formats::SRGGB10_CSI2P:\n+\t\tegl_.pushEnv(shaderEnv, \"#define RAW10P\");\n+\t\tif (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {\n+\t\t\tfragmentShaderData = bayer_unpacked_frag;\n+\t\t\tfragmentShaderDataLen = bayer_unpacked_frag_len;\n+\t\t\tvertexShaderData = bayer_unpacked_vert;\n+\t\t\tvertexShaderDataLen = bayer_unpacked_vert_len;\n+\t\t\tglFormat_ = GL_RG;\n+\t\t\tbytesPerPixel_ = 2;\n+\t\t} else {\n+\t\t\tfragmentShaderData = bayer_1x_packed_frag;\n+\t\t\tfragmentShaderDataLen = bayer_1x_packed_frag_len;\n+\t\t\tvertexShaderData = identity_vert;\n+\t\t\tvertexShaderDataLen = identity_vert_len;\n+\t\t\tshaderStridePixels_ = width_;\n+\t\t}\n+\t\tbreak;\n+\tcase libcamera::formats::SBGGR12_CSI2P:\n+\tcase libcamera::formats::SGBRG12_CSI2P:\n+\tcase libcamera::formats::SGRBG12_CSI2P:\n+\tcase libcamera::formats::SRGGB12_CSI2P:\n+\t\tegl_.pushEnv(shaderEnv, \"#define RAW12P\");\n+\t\tif (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {\n+\t\t\tfragmentShaderData = bayer_unpacked_frag;\n+\t\t\tfragmentShaderDataLen = bayer_unpacked_frag_len;\n+\t\t\tvertexShaderData = bayer_unpacked_vert;\n+\t\t\tvertexShaderDataLen = bayer_unpacked_vert_len;\n+\t\t\tglFormat_ = GL_RG;\n+\t\t\tbytesPerPixel_ = 2;\n+\t\t} else {\n+\t\t\tfragmentShaderData = bayer_1x_packed_frag;\n+\t\t\tfragmentShaderDataLen = bayer_1x_packed_frag_len;\n+\t\t\tvertexShaderData = identity_vert;\n+\t\t\tvertexShaderDataLen = identity_vert_len;\n+\t\t\tshaderStridePixels_ = width_;\n+\t\t}\n+\t\tbreak;\n+\t};\n+\n+\tif (egl_.compileVertexShader(vertexShaderId_, vertexShaderData, vertexShaderDataLen, shaderEnv)) {\n+\t\tLOG(Debayer, Error) << \"Compile vertex shader fail\";\n+\t\treturn -ENODEV;\n+\t}\n+\tutils::scope_exit vShaderGuard([&] { glDeleteShader(vertexShaderId_); });\n+\n+\tif (egl_.compileFragmentShader(fragmentShaderId_, fragmentShaderData, fragmentShaderDataLen, shaderEnv)) {\n+\t\tLOG(Debayer, Error) << \"Compile fragment shader fail\";\n+\t\treturn -ENODEV;\n+\t}\n+\tutils::scope_exit fShaderGuard([&] { glDeleteShader(fragmentShaderId_); });\n+\n+\tif (egl_.linkProgram(programId_, vertexShaderId_, fragmentShaderId_)) {\n+\t\tLOG(Debayer, Error) << \"Linking program fail\";\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tegl_.dumpShaderSource(vertexShaderId_);\n+\tegl_.dumpShaderSource(fragmentShaderId_);\n+\n+\t/* Ensure we set the programId_ */\n+\tegl_.useProgram(programId_);\n+\terr = glGetError();\n+\tif (err != GL_NO_ERROR) {\n+\t\tLOG(Debayer, Error) << \"Use program error \" << err;\n+\t\treturn -ENODEV;\n+\t}\n+\n+\treturn getShaderVariableLocations();\n+}\n+\n+/**\n+ * \\brief Get the output frame size\n+ *\n+ * \\return The output frame size\n+ */\n+unsigned int DebayerEGL::frameSize()\n+{\n+\treturn outputConfig_.frameSize;\n+}\n+\n+int DebayerEGL::configure(const StreamConfiguration &inputCfg,\n+\t\t\t  const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,\n+\t\t\t  bool ccmEnabled)\n+{\n+\tif (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0)\n+\t\treturn -EINVAL;\n+\n+\tif (stats_->configure(inputCfg) != 0)\n+\t\treturn -EINVAL;\n+\n+\tif (!ccmEnabled)\n+\t\treturn -EINVAL;\n+\n+\tconst Size &stats_pattern_size = stats_->patternSize();\n+\tif (inputConfig_.patternSize.width != stats_pattern_size.width ||\n+\t    inputConfig_.patternSize.height != stats_pattern_size.height) {\n+\t\tLOG(Debayer, Error)\n+\t\t\t<< \"mismatching stats and debayer pattern sizes for \"\n+\t\t\t<< inputCfg.pixelFormat;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tinputConfig_.stride = inputCfg.stride;\n+\tinputPixelFormat_ = inputCfg.pixelFormat;\n+\twidth_ = inputCfg.size.width;\n+\theight_ = inputCfg.size.height;\n+\n+\tif (outputCfgs.size() != 1) {\n+\t\tLOG(Debayer, Error)\n+\t\t\t<< \"Unsupported number of output streams: \"\n+\t\t\t<< outputCfgs.size();\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tStreamConfiguration &outputCfg = outputCfgs[0];\n+\tSizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size);\n+\tstd::tie(outputConfig_.stride, outputConfig_.frameSize) =\n+\t\tstrideAndFrameSize(outputCfg.pixelFormat, outputCfg.size);\n+\n+\tif (!outSizeRange.contains(outputCfg.size) || outputConfig_.stride != outputCfg.stride) {\n+\t\tLOG(Debayer, Error)\n+\t\t\t<< \"Invalid output size/stride: \"\n+\t\t\t<< \"\\n  \" << outputCfg.size << \" (\" << outSizeRange << \")\"\n+\t\t\t<< \"\\n  \" << outputCfg.stride << \" (\" << outputConfig_.stride << \")\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\toutputPixelFormat_ = outputCfg.pixelFormat;\n+\toutputSize_ = outputCfg.size;\n+\n+\twindow_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) &\n+\t\t    ~(inputConfig_.patternSize.width - 1);\n+\twindow_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) &\n+\t\t    ~(inputConfig_.patternSize.height - 1);\n+\twindow_.width = outputCfg.size.width;\n+\twindow_.height = outputCfg.size.height;\n+\n+\t/*\n+\t * Don't pass x,y from window_ since process() already adjusts for it.\n+\t * But crop the window to 2/3 of its width and height for speedup.\n+\t */\n+\tstats_->setWindow(Rectangle(window_.size()));\n+\n+\treturn 0;\n+}\n+\n+Size DebayerEGL::patternSize(PixelFormat inputFormat)\n+{\n+\tDebayerEGL::DebayerInputConfig config;\n+\n+\tif (getInputConfig(inputFormat, config) != 0)\n+\t\treturn {};\n+\n+\treturn config.patternSize;\n+}\n+\n+std::vector<PixelFormat> DebayerEGL::formats(PixelFormat inputFormat)\n+{\n+\tDebayerEGL::DebayerInputConfig config;\n+\n+\tif (getInputConfig(inputFormat, config) != 0)\n+\t\treturn std::vector<PixelFormat>();\n+\n+\treturn config.outputFormats;\n+}\n+\n+std::tuple<unsigned int, unsigned int>\n+DebayerEGL::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)\n+{\n+\tDebayerEGL::DebayerOutputConfig config;\n+\n+\tif (getOutputConfig(outputFormat, config) != 0)\n+\t\treturn std::make_tuple(0, 0);\n+\n+\t/* Align stride to 256 bytes as a generic GPU memory access alignment */\n+\tunsigned int stride = libcamera::utils::alignUp(size.width * config.bpp / 8, 256);\n+\n+\treturn std::make_tuple(stride, stride * size.height);\n+}\n+\n+void DebayerEGL::setShaderVariableValues(DebayerParams &params)\n+{\n+\t/*\n+\t * Raw Bayer 8-bit, and packed raw Bayer 10-bit/12-bit formats\n+\t * are stored in a GL_LUMINANCE texture. The texture width is\n+\t * equal to the stride.\n+\t */\n+\tGLfloat firstRed[] = { firstRed_x_, firstRed_y_ };\n+\tGLfloat imgSize[] = { (GLfloat)width_,\n+\t\t\t      (GLfloat)height_ };\n+\tGLfloat Step[] = { static_cast<float>(bytesPerPixel_) / (inputConfig_.stride - 1),\n+\t\t\t   1.0f / (height_ - 1) };\n+\tGLfloat Stride = (GLfloat)width_ / (shaderStridePixels_ / bytesPerPixel_);\n+\t/*\n+\t * Scale input to output size, keeping the aspect ratio and preferring\n+\t * cropping over black bars.\n+\t */\n+\tGLfloat scale = std::max((GLfloat)window_.width / width_,\n+\t\t\t\t (GLfloat)window_.height / height_);\n+\tGLfloat trans = -(1.0f - scale);\n+\tGLfloat projMatrix[] = {\n+\t\tscale, 0, 0, 0,\n+\t\t0, scale, 0, 0,\n+\t\t0, 0, 1, 0,\n+\t\ttrans, trans, 0, 1\n+\t};\n+\t/* Static const coordinates */\n+\tstatic const GLfloat vcoordinates[4][2] = {\n+\t\t{ -1.0f, -1.0f },\n+\t\t{ -1.0f, +1.0f },\n+\t\t{ +1.0f, +1.0f },\n+\t\t{ +1.0f, -1.0f },\n+\t};\n+\tstatic const GLfloat tcoordinates[4][2] = {\n+\t\t{ 0.0f, 0.0f },\n+\t\t{ 0.0f, 1.0f },\n+\t\t{ 1.0f, 1.0f },\n+\t\t{ 1.0f, 0.0f },\n+\t};\n+\n+\t/* vertexIn - bayer_8.vert */\n+\tglEnableVertexAttribArray(attributeVertex_);\n+\tglVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE,\n+\t\t\t      2 * sizeof(GLfloat), vcoordinates);\n+\n+\t/* textureIn - bayer_8.vert */\n+\tglEnableVertexAttribArray(attributeTexture_);\n+\tglVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE,\n+\t\t\t      2 * sizeof(GLfloat), tcoordinates);\n+\n+\t/*\n+\t * Set the sampler2D to the respective texture unit for each texutre\n+\t * To simultaneously sample multiple textures we need to use multiple\n+\t * texture units\n+\t */\n+\tglUniform1i(textureUniformBayerDataIn_, eglImageBayerIn_->texture_unit_uniform_id_);\n+\n+\t/*\n+\t * These values are:\n+\t * firstRed = tex_bayer_first_red - bayer_8.vert\n+\t * imgSize = tex_size - bayer_8.vert\n+\t * step = tex_step - bayer_8.vert\n+\t * Stride = stride_factor identity.vert\n+\t * textureUniformProjMatri = No scaling\n+\t */\n+\tglUniform2fv(textureUniformBayerFirstRed_, 1, firstRed);\n+\tglUniform2fv(textureUniformSize_, 1, imgSize);\n+\tglUniform2fv(textureUniformStep_, 1, Step);\n+\tglUniform1f(textureUniformStrideFactor_, Stride);\n+\tglUniformMatrix4fv(textureUniformProjMatrix_, 1, GL_FALSE, projMatrix);\n+\n+\tLOG(Debayer, Debug) << \"vertexIn \" << attributeVertex_ << \" textureIn \" << attributeTexture_\n+\t\t\t    << \" tex_y \" << textureUniformBayerDataIn_\n+\t\t\t    << \" tex_step \" << textureUniformStep_\n+\t\t\t    << \" tex_size \" << textureUniformSize_\n+\t\t\t    << \" stride_factor \" << textureUniformStrideFactor_\n+\t\t\t    << \" tex_bayer_first_red \" << textureUniformBayerFirstRed_;\n+\n+\tLOG(Debayer, Debug) << \"textureUniformY_ = 0 \"\n+\t\t\t    << \" firstRed.x \" << firstRed[0]\n+\t\t\t    << \" firstRed.y \" << firstRed[1]\n+\t\t\t    << \" textureUniformSize_.width \" << imgSize[0]\n+\t\t\t    << \" textureUniformSize_.height \" << imgSize[1]\n+\t\t\t    << \" textureUniformStep_.x \" << Step[0]\n+\t\t\t    << \" textureUniformStep_.y \" << Step[1]\n+\t\t\t    << \" textureUniformStrideFactor_ \" << Stride\n+\t\t\t    << \" textureUniformProjMatrix_ \" << textureUniformProjMatrix_;\n+\n+\tGLfloat ccm[9] = {\n+\t\tparams.ccm[0][0],\n+\t\tparams.ccm[0][1],\n+\t\tparams.ccm[0][2],\n+\t\tparams.ccm[1][0],\n+\t\tparams.ccm[1][1],\n+\t\tparams.ccm[1][2],\n+\t\tparams.ccm[2][0],\n+\t\tparams.ccm[2][1],\n+\t\tparams.ccm[2][2],\n+\t};\n+\tglUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm);\n+\tLOG(Debayer, Debug) << \" ccmUniformDataIn_ \" << ccmUniformDataIn_ << \" data \" << params.ccm;\n+\n+\t/*\n+\t * 0 = Red, 1 = Green, 2 = Blue\n+\t */\n+\tglUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]);\n+\tLOG(Debayer, Debug) << \" blackLevelUniformDataIn_ \" << blackLevelUniformDataIn_ << \" data \" << params.blackLevel;\n+\n+\t/*\n+\t * Gamma\n+\t */\n+\tglUniform1f(gammaUniformDataIn_, params.gamma);\n+\tLOG(Debayer, Debug) << \" gammaUniformDataIn_ \" << gammaUniformDataIn_ << \" data \" << params.gamma;\n+\n+\t/*\n+\t * Contrast\n+\t */\n+\tglUniform1f(contrastExpUniformDataIn_, params.contrastExp);\n+\tLOG(Debayer, Debug) << \" contrastExpUniformDataIn_ \" << contrastExpUniformDataIn_ << \" data \" << params.contrastExp;\n+\n+\treturn;\n+}\n+\n+int DebayerEGL::debayerGPU(MappedFrameBuffer &in, int out_fd, DebayerParams &params)\n+{\n+\t/* eGL context switch */\n+\tegl_.makeCurrent();\n+\n+\t/* Create a standard texture input */\n+\tegl_.createTexture2D(*eglImageBayerIn_, glFormat_, inputConfig_.stride / bytesPerPixel_, height_, in.planes()[0].data());\n+\n+\t/* Generate the output render framebuffer as render to texture */\n+\tegl_.createOutputDMABufTexture2D(*eglImageBayerOut_, out_fd);\n+\n+\tsetShaderVariableValues(params);\n+\tglViewport(0, 0, width_, height_);\n+\tglClear(GL_COLOR_BUFFER_BIT);\n+\tglDrawArrays(GL_TRIANGLE_FAN, 0, DEBAYER_OPENGL_COORDS);\n+\n+\tGLenum err = glGetError();\n+\tif (err != GL_NO_ERROR) {\n+\t\tLOG(eGL, Error) << \"Drawing scene fail \" << err;\n+\t\treturn -ENODEV;\n+\t} else {\n+\t\tegl_.syncOutput();\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params)\n+{\n+\tbench_.startFrame();\n+\n+\tstd::vector<DmaSyncer> dmaSyncers;\n+\n+\tdmaSyncBegin(dmaSyncers, input, nullptr);\n+\n+\tsetParams(params);\n+\n+\t/* Copy metadata from the input buffer */\n+\tFrameMetadata &metadata = output->_d()->metadata();\n+\tmetadata.status = input->metadata().status;\n+\tmetadata.sequence = input->metadata().sequence;\n+\tmetadata.timestamp = input->metadata().timestamp;\n+\n+\tMappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read);\n+\tif (!in.isValid()) {\n+\t\tLOG(Debayer, Error) << \"mmap-ing buffer(s) failed\";\n+\t\tgoto error;\n+\t}\n+\n+\tif (debayerGPU(in, output->planes()[0].fd.get(), params)) {\n+\t\tLOG(Debayer, Error) << \"debayerGPU failed\";\n+\t\tgoto error;\n+\t}\n+\n+\tbench_.finishFrame();\n+\n+\tmetadata.planes()[0].bytesused = output->planes()[0].length;\n+\n+\t/* Calculate stats for the whole frame */\n+\tstats_->processFrame(frame, 0, input);\n+\tdmaSyncers.clear();\n+\n+\toutputBufferReady.emit(output);\n+\tinputBufferReady.emit(input);\n+\n+\treturn;\n+\n+error:\n+\tbench_.finishFrame();\n+\tmetadata.status = FrameMetadata::FrameError;\n+\treturn;\n+}\n+\n+int DebayerEGL::start()\n+{\n+\tGLint maxTextureImageUnits;\n+\n+\tif (gbmSurface_.createDevice())\n+\t\treturn -ENODEV;\n+\n+\tif (egl_.initEGLContext(&gbmSurface_))\n+\t\treturn -ENODEV;\n+\n+\tglGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);\n+\n+\tLOG(Debayer, Debug) << \"Available fragment shader texture units \" << maxTextureImageUnits;\n+\n+\t/* Raw bayer input as texture */\n+\teglImageBayerIn_ = std::make_unique<eGLImage>(width_, height_, 32, inputConfig_.stride, GL_TEXTURE0, 0);\n+\n+\t/* Texture we will render to */\n+\teglImageBayerOut_ = std::make_unique<eGLImage>(outputSize_.width, outputSize_.height, 31, outputConfig_.stride, GL_TEXTURE1, 1);\n+\n+\tif (initBayerShaders(inputPixelFormat_, outputPixelFormat_))\n+\t\treturn -EINVAL;\n+\n+\treturn 0;\n+}\n+\n+void DebayerEGL::stop()\n+{\n+\teglImageBayerOut_.reset();\n+\teglImageBayerIn_.reset();\n+\n+\tif (programId_)\n+\t\tglDeleteProgram(programId_);\n+\n+\tegl_.cleanUp();\n+}\n+\n+SizeRange DebayerEGL::sizes(PixelFormat inputFormat, const Size &inputSize)\n+{\n+\tSize patternSize = this->patternSize(inputFormat);\n+\tunsigned int borderHeight = patternSize.height;\n+\n+\tif (patternSize.isNull())\n+\t\treturn {};\n+\n+\t/* No need for top/bottom border with a pattern height of 2 */\n+\tif (patternSize.height == 2)\n+\t\tborderHeight = 0;\n+\n+\t/*\n+\t * For debayer interpolation a border is kept around the entire image\n+\t * and the minimum output size is pattern-height x pattern-width.\n+\t */\n+\tif (inputSize.width < (3 * patternSize.width) ||\n+\t    inputSize.height < (2 * borderHeight + patternSize.height)) {\n+\t\tLOG(Debayer, Warning)\n+\t\t\t<< \"Input format size too small: \" << inputSize;\n+\t\treturn {};\n+\t}\n+\n+\treturn SizeRange(Size(patternSize.width, patternSize.height),\n+\t\t\t Size((inputSize.width - 2 * patternSize.width) & ~(patternSize.width - 1),\n+\t\t\t      (inputSize.height - 2 * borderHeight) & ~(patternSize.height - 1)),\n+\t\t\t patternSize.width, patternSize.height);\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h\nnew file mode 100644\nindex 000000000..a5033bc63\n--- /dev/null\n+++ b/src/libcamera/software_isp/debayer_egl.h\n@@ -0,0 +1,124 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2025, Bryan O'Donoghue.\n+ *\n+ * Authors:\n+ * Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n+ *\n+ */\n+\n+#pragma once\n+\n+#include <memory>\n+#include <stdint.h>\n+#include <vector>\n+\n+#define GL_GLEXT_PROTOTYPES\n+#define EGL_EGLEXT_PROTOTYPES\n+#include <libcamera/base/object.h>\n+\n+#include \"libcamera/internal/bayer_format.h\"\n+#include \"libcamera/internal/egl.h\"\n+#include \"libcamera/internal/framebuffer.h\"\n+#include \"libcamera/internal/mapped_framebuffer.h\"\n+#include \"libcamera/internal/software_isp/benchmark.h\"\n+#include \"libcamera/internal/software_isp/swstats_cpu.h\"\n+\n+#include <EGL/egl.h>\n+#include <EGL/eglext.h>\n+#include <GLES3/gl32.h>\n+\n+#include \"debayer.h\"\n+\n+namespace libcamera {\n+\n+#define DEBAYER_EGL_MIN_SIMPLE_RGB_GAIN_TEXTURE_UNITS 4\n+#define DEBAYER_OPENGL_COORDS 4\n+\n+class DebayerEGL : public Debayer\n+{\n+public:\n+\tDebayerEGL(std::unique_ptr<SwStatsCpu> stats, const GlobalConfiguration &configuration);\n+\t~DebayerEGL();\n+\n+\tint configure(const StreamConfiguration &inputCfg,\n+\t\t      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,\n+\t\t      bool ccmEnabled);\n+\n+\tSize patternSize(PixelFormat inputFormat);\n+\n+\tstd::vector<PixelFormat> formats(PixelFormat input);\n+\tstd::tuple<unsigned int, unsigned int> strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);\n+\n+\tvoid process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params);\n+\tint start();\n+\tvoid stop();\n+\n+\tconst SharedFD &getStatsFD() { return stats_->getStatsFD(); }\n+\tunsigned int frameSize();\n+\n+\tSizeRange sizes(PixelFormat inputFormat, const Size &inputSize);\n+\n+private:\n+\tstatic int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config);\n+\tstatic int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config);\n+\tint setupStandardBayerOrder(BayerFormat::Order order);\n+\tvoid pushEnv(std::vector<std::string> &shaderEnv, const char *str);\n+\tint initBayerShaders(PixelFormat inputFormat, PixelFormat outputFormat);\n+\tint initEGLContext();\n+\tint generateTextures();\n+\tint compileShaderProgram(GLuint &shaderId, GLenum shaderType,\n+\t\t\t\t unsigned char *shaderData, int shaderDataLen,\n+\t\t\t\t std::vector<std::string> shaderEnv);\n+\tint linkShaderProgram(void);\n+\tint getShaderVariableLocations();\n+\tvoid setShaderVariableValues(DebayerParams &params);\n+\tvoid configureTexture(GLuint &texture);\n+\tint debayerGPU(MappedFrameBuffer &in, int out_fd, DebayerParams &params);\n+\n+\t/* Shader program identifiers */\n+\tGLuint vertexShaderId_ = 0;\n+\tGLuint fragmentShaderId_ = 0;\n+\tGLuint programId_ = 0;\n+\n+\t/* Pointer to object representing input texture */\n+\tstd::unique_ptr<eGLImage> eglImageBayerIn_;\n+\tstd::unique_ptr<eGLImage> eglImageBayerOut_;\n+\n+\t/* Shader parameters */\n+\tfloat firstRed_x_;\n+\tfloat firstRed_y_;\n+\tGLint attributeVertex_;\n+\tGLint attributeTexture_;\n+\tGLint textureUniformStep_;\n+\tGLint textureUniformSize_;\n+\tGLint textureUniformStrideFactor_;\n+\tGLint textureUniformBayerFirstRed_;\n+\tGLint textureUniformProjMatrix_;\n+\n+\tGLint textureUniformBayerDataIn_;\n+\n+\t/* Represent per-frame CCM as a uniform vector of floats 3 x 3 */\n+\tGLint ccmUniformDataIn_;\n+\n+\t/* Black Level compensation */\n+\tGLint blackLevelUniformDataIn_;\n+\n+\t/* Gamma */\n+\tGLint gammaUniformDataIn_;\n+\n+\t/* Contrast */\n+\tGLint contrastExpUniformDataIn_;\n+\n+\tRectangle window_;\n+\tstd::unique_ptr<SwStatsCpu> stats_;\n+\teGL egl_;\n+\tGBM gbmSurface_;\n+\tuint32_t width_;\n+\tuint32_t height_;\n+\tGLint glFormat_;\n+\tunsigned int bytesPerPixel_;\n+\tuint32_t shaderStridePixels_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build\nindex 59fa5f02a..c61ac7d59 100644\n--- a/src/libcamera/software_isp/meson.build\n+++ b/src/libcamera/software_isp/meson.build\n@@ -2,6 +2,7 @@\n \n softisp_enabled = pipelines.contains('simple')\n summary({'SoftISP support' : softisp_enabled}, section : 'Configuration')\n+summary({'SoftISP GPU acceleration' : gles_headless_enabled}, section : 'Configuration')\n \n if not softisp_enabled\n     subdir_done()\n@@ -14,3 +15,10 @@ libcamera_internal_sources += files([\n     'software_isp.cpp',\n     'swstats_cpu.cpp',\n ])\n+\n+if softisp_enabled and gles_headless_enabled\n+    config_h.set('HAVE_DEBAYER_EGL', 1)\n+    libcamera_internal_sources += files([\n+        'debayer_egl.cpp',\n+    ])\n+endif\n",
    "prefixes": [
        "v11",
        "15/24"
    ]
}