[{"id":34491,"web_url":"https://patchwork.libcamera.org/comment/34491/","msgid":"<766ad95b-191d-426d-98dd-f2c2ec9daab2@linaro.org>","date":"2025-06-16T19:16:07","subject":"Re: [PATCH 23/35] libcamera: software_isp: debayer_egl: Add an eGL\n\tdebayer class","submitter":{"id":175,"url":"https://patchwork.libcamera.org/api/people/175/","name":"Bryan O'Donoghue","email":"bryan.odonoghue@linaro.org"},"content":"On 11/06/2025 02:32, Bryan O'Donoghue wrote:\n> Add a class to run the existing glsl debayer shaders on a GBM surface.\n> \n> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> \n> libcamera: software_isp: debayer_egl: Extend logic to enable application of softISP RGB debayer params\n> \n> The existing SoftISP calculates RGB gain values as a lookup table of 256\n> values which shifts for each frame depending on the required correction.\n> \n> We can pass the required tables into the debayer shaders as textures, one\n> texture for R, G and B respectively.\n> \n> The debayer shader will do its debayer interpolation and then if the\n> appropriate define is specified use the calculated R, G and B values as\n> indexes into our bayer colour gain table.\n> \n> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\nDon't send email at 2:30 am.\n\nThis is what happens. Break these patches apart.\n\n---\nbod","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 9D7F6C3237\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 16 Jun 2025 19:16:12 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 57B9268DCF;\n\tMon, 16 Jun 2025 21:16:12 +0200 (CEST)","from mail-wm1-x335.google.com (mail-wm1-x335.google.com\n\t[IPv6:2a00:1450:4864:20::335])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id C3EC868DC0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Jun 2025 21:16:10 +0200 (CEST)","by mail-wm1-x335.google.com with SMTP id\n\t5b1f17b1804b1-43ea40a6e98so60699295e9.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Jun 2025 12:16:10 -0700 (PDT)","from [192.168.0.35] (188-141-3-146.dynamic.upc.ie. [188.141.3.146])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-4532e2449f1sm156802195e9.23.2025.06.16.12.16.07\n\t(version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);\n\tMon, 16 Jun 2025 12:16:08 -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=\"yds1zZIO\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=linaro.org; s=google; t=1750101370; x=1750706170;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:in-reply-to:from:content-language\n\t:references:to:subject:user-agent:mime-version:date:message-id:from\n\t:to:cc:subject:date:message-id:reply-to;\n\tbh=BpYzrXTqM781MmNpgKhi8JQ9M4RA6a+Qgiq5kWGXrxo=;\n\tb=yds1zZIObh7+A5Qo3AqHwH9+Ul1BkdzDIU+otgwdFNUUHrq+NXtp/gCHjXFUkJgI7i\n\tvrnbcWcd0DaWVJpXarvctzxQcZnGyUJWabjM3bmZXjQNkBytQzygbu341YP7SvqLY6xG\n\t998bWjak+hOMmk5zXGymLLqK8/w863K0PJ0/ilplnpUQpdssDhfPnEPLj231qMg9XrMM\n\tEsMtHJv6li1RBybHhYpIbVIdorvhvzfiET82xciu6+7h3x3yophE4B7wtCWNJ+w/FGD7\n\tRNooDvZkpUL0A+fMpQMEAyJ5pyH8dU2lms24yaP8cQoTVA5V6noYYMLc7hj9fsaO8/Sx\n\tvTeg==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1750101370; x=1750706170;\n\th=content-transfer-encoding:in-reply-to:from:content-language\n\t:references:to:subject:user-agent:mime-version:date:message-id\n\t:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to;\n\tbh=BpYzrXTqM781MmNpgKhi8JQ9M4RA6a+Qgiq5kWGXrxo=;\n\tb=AoQPtYIOdHI9A/a1Pw/XjdDQV6c4NvbwaWkp+Ws5hM658ozsNpsrKIm8hlPQYstXfI\n\tHlQ6kiQGtNf/vhpdAI41LL6JqkcVZap+NBdBec/HFqmorlsJZCmFEsP0psgYtwlWfAVO\n\tWJg0Zgs4zPP4EFOjWIg+LmZolc64I3P9IKRyzSBCVRxKbngFaIa4rVv7eT4IzbrDOVF1\n\t39qi9ubi6yJAk2FTD1iNhcnasLzz4P8O2IfshefiV+6o3kzh46waz1+A802MLFqwdj4r\n\toFZT7FTyuYB3S7IerJjVjkbDP9Y0DiasJsqo5EYUW+TYYHYZ5wDMfxSL2DiVtM1ZVUTU\n\trWVA==","X-Forwarded-Encrypted":"i=1;\n\tAJvYcCWYQWS6fvZjYx5FCZ/Q+alyuMzRZN8Zp6qUzcsec9SLpXnEOfVVRlZ5E/bbEMeHCncsN45EwKaJ1faBt5SyMZE=@lists.libcamera.org","X-Gm-Message-State":"AOJu0YyQWmZ2jTM8c0RnUZK2UQtWXGBtHdPuLJbg7+oGWnU4Rfao0KAS\n\t5La6wzwbREHuvsPndnkKMKTm5SGdj+VhB9l12mHhESaDCX0CTup2OWdKsIHlzLFxPVozTdmwRDu\n\t3tznI8t4=","X-Gm-Gg":"ASbGncv/Po+wQHH8Ro/Ga95EXZIHlb6xTq1U8C7YHE2i/eZEDnc/4CAGGlyLGnpB3E2\n\tGGKYdHfvcxaPrBhGZ+KbKBtef3UlHEW5UEQ08pRe4p1u1JO4RnQjH7mmcJWY0EGtBeTHL9yHqWG\n\tZS0pVsDBvb+BsK2oMzLZtpPilN2I+3GmD3ITMLBuCapZHvMcldqSS307tPgW1UxEuluOtp91b4+\n\tGB2RSnwV3/UE9kUnm5WtlB2gouiDRt7hbnw86pfqmWw01JsCgGgPcbL4vbp7RtYqMnc+0a2qZtC\n\tvKjWAstOSsNSqhXra0REWgqbkpIhh3/LMC6HkAynklxjJNMVjeRgF4ilm0C5MxKegdSEgNG2Vv2\n\t0ygEpEHZx64kGVcEN8nspPSKtw/k=","X-Google-Smtp-Source":"AGHT+IHnd8EwebqGBPgZc0Jd5VvUjqsFMdcX69ngrSlywTNP9BeqK7m2T8Ayc2iK1dGm7ugS0ICNqg==","X-Received":"by 2002:a5d:584d:0:b0:3a5:243c:6042 with SMTP id\n\tffacd0b85a97d-3a572398e5cmr9462115f8f.2.1750101370232; \n\tMon, 16 Jun 2025 12:16:10 -0700 (PDT)","Message-ID":"<766ad95b-191d-426d-98dd-f2c2ec9daab2@linaro.org>","Date":"Mon, 16 Jun 2025 20:16:07 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 23/35] libcamera: software_isp: debayer_egl: Add an eGL\n\tdebayer class","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20250611013245.133785-1-bryan.odonoghue@linaro.org>\n\t<WQQoB50SohQPLMJoeXt45mZy9Oa89Wdsol-qEbWIU3JPfDd9h7yEH6XqYGy-o2IprK2HYlq3ISsSt3yhgMyMFw==@protonmail.internalid>\n\t<20250611013245.133785-24-bryan.odonoghue@linaro.org>","Content-Language":"en-US","From":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","In-Reply-To":"<20250611013245.133785-24-bryan.odonoghue@linaro.org>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":34533,"web_url":"https://patchwork.libcamera.org/comment/34533/","msgid":"<85ldppijk2.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2025-06-18T07:40:45","subject":"Re: [PATCH 23/35] libcamera: software_isp: debayer_egl: Add an eGL\n\tdebayer class","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Hi Bryan,\n\nnot commenting on GL stuff, just a few general comments (regardless how\nthe patches are split).\n\nBryan O'Donoghue <bryan.odonoghue@linaro.org> writes:\n\n> Add a class to run the existing glsl debayer shaders on a GBM surface.\n>\n> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n>\n> libcamera: software_isp: debayer_egl: Extend logic to enable application of softISP RGB debayer params\n>\n> The existing SoftISP calculates RGB gain values as a lookup table of 256\n> values which shifts for each frame depending on the required correction.\n>\n> We can pass the required tables into the debayer shaders as textures, one\n> texture for R, G and B respectively.\n>\n> The debayer shader will do its debayer interpolation and then if the\n> appropriate define is specified use the calculated R, G and B values as\n> indexes into our bayer colour gain table.\n>\n> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> ---\n>  src/libcamera/egl.cpp                      |   4 +-\n>  src/libcamera/software_isp/debayer_egl.cpp | 587 +++++++++++++++++++++\n>  src/libcamera/software_isp/debayer_egl.h   | 164 ++++++\n>  src/libcamera/software_isp/meson.build     |   8 +\n>  4 files changed, 761 insertions(+), 2 deletions(-)\n>  create mode 100644 src/libcamera/software_isp/debayer_egl.cpp\n>  create mode 100644 src/libcamera/software_isp/debayer_egl.h\n>\n> diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp\n> index 89ece148..c6b0f9a5 100644\n> --- a/src/libcamera/egl.cpp\n> +++ b/src/libcamera/egl.cpp\n> @@ -82,13 +82,13 @@ void eGL::destroyDMABufTexture(eGLImage *eglImage)\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> +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, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);\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> diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp\n> new file mode 100644\n> index 00000000..3fb15511\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.cpp\n> @@ -0,0 +1,587 @@\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> + * debayer_cpu.cpp - EGL based debayering class\n> + */\n> +\n> +#include <math.h>\n> +#include <stdlib.h>\n> +#include <time.h>\n> +\n> +#include <libcamera/formats.h>\n> +\n> +#include \"libcamera/internal/glsl_shaders.h\"\n> +#include \"debayer_egl.h\"\n> +\n> +namespace libcamera {\n> +\n> +DebayerEGL::DebayerEGL(std::unique_ptr<SwStatsCpu> stats)\n> +\t: Debayer(), stats_(std::move(stats))\n> +{\n> +\teglImageBayerIn_ = eglImageRedLookup_ = eglImageBlueLookup_ = eglImageGreenLookup_ = NULL;\n\ns/NULL/nullptr/\n\n> +}\n> +\n> +DebayerEGL::~DebayerEGL()\n> +{\n> +\tif (eglImageBlueLookup_)\n> +\t\tdelete eglImageBlueLookup_;\n> +\n> +\tif (eglImageGreenLookup_)\n> +\t\tdelete eglImageGreenLookup_;\n> +\n> +\tif (eglImageRedLookup_)\n> +\t\tdelete eglImageRedLookup_;\n> +\n> +\tif (eglImageBayerIn_)\n> +\t\tdelete eglImageBayerIn_;\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, Info)\n> +\t\t<< \"Unsupported input format \" << inputFormat.toString();\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.toString();\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> +\ttextureUniformRedLookupDataIn_ = glGetUniformLocation(programId_, \"red_param\");\n> +\ttextureUniformGreenLookupDataIn_ = glGetUniformLocation(programId_, \"green_param\");\n> +\ttextureUniformBlueLookupDataIn_ = glGetUniformLocation(programId_, \"blue_param\");\n\nShould we really use lookup tables or rather apply the gains directly or\nvia CCM?\n\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    << \" red_param \" << textureUniformRedLookupDataIn_\n> +\t\t\t    << \" green_param \" << textureUniformGreenLookupDataIn_\n> +\t\t\t    << \" blue_param \" << textureUniformBlueLookupDataIn_\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;\n> +\tunsigned char *fragmentShaderData;\n> +\tunsigned int vertexShaderDataLen;\n> +\tunsigned char *vertexShaderData;\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// Tell shaders how to re-order output taking account of how the\n> +\t// pixels are actually stored by GBM\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\nThe trick looks much less confusing here ATM than in the CPU version but\nI think it still deserves a comment in a prominent place how it is\nsupposed to work -- e.g. that we pretend that blue is red and vice versa\ntill the very end and how this must be considered in CCMs for sensors or\nfor contingent colour corrections.\n\nAlso, there is a question whether performing the swap in another way,\ne.g. via CCM, would make things less or more confusing or complicated.\n\nThis all should be also discussed in the commit message.\n\n> +\t\tbreak;\n> +\tdefault:\n> +\t\tgoto invalid_fmt;\n> +\t}\n> +\n> +\t// Pixel location parameters\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\tgoto invalid_fmt;\n> +\t\tbreak;\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_8_frag;\n> +\t\tfragmentShaderDataLen = bayer_8_frag_len;\n> +\t\tvertexShaderData = bayer_8_vert;\n> +\t\tvertexShaderDataLen = bayer_8_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\tfragmentShaderData = bayer_1x_packed_frag;\n> +\t\tfragmentShaderDataLen = bayer_1x_packed_frag_len;\n> +\t\tvertexShaderData = identity_vert;\n> +\t\tvertexShaderDataLen = identity_vert_len;\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\tfragmentShaderData = bayer_1x_packed_frag;\n> +\t\tfragmentShaderDataLen = bayer_1x_packed_frag_len;\n> +\t\tvertexShaderData = identity_vert;\n> +\t\tvertexShaderDataLen = identity_vert_len;\n> +\t\tbreak;\n> +\tdefault:\n> +\t\tgoto invalid_fmt;\n> +\t\tbreak;\n> +\t};\n> +\n> +\t// Flag to shaders that we have parameter gain tables\n> +\tegl_.pushEnv(shaderEnv, \"#define APPLY_RGB_PARAMETERS\");\n> +\n> +\tif (egl_.compileVertexShader(vertexShaderId_, vertexShaderData, vertexShaderDataLen, shaderEnv))\n> +\t\tgoto compile_fail;\n> +\n> +\tif (egl_.compileFragmentShader(fragmentShaderId_, fragmentShaderData, fragmentShaderDataLen, shaderEnv))\n> +\t\tgoto compile_fail;\n> +\n> +\tif (egl_.linkProgram(programId_, vertexShaderId_, fragmentShaderId_))\n> +\t\tgoto link_fail;\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\tgoto program_fail;\n> +\n> +\tif (getShaderVariableLocations())\n> +\t\tgoto parameters_fail;\n> +\n> +\treturn 0;\n> +\n> +parameters_fail:\n> +\tLOG(Debayer, Error) << \"Program parameters fail\";\n> +\treturn -ENODEV;\n> +\n> +program_fail:\n> +\tLOG(Debayer, Error) << \"Use program error \" << err;\n> +\treturn -ENODEV;\n> +\n> +link_fail:\n> +\tLOG(Debayer, Error) << \"Linking program fail\";\n> +\treturn -ENODEV;\n> +\n> +compile_fail:\n> +\tLOG(Debayer, Error) << \"Compile debayer shaders fail\";\n> +\treturn -ENODEV;\n> +\n> +invalid_fmt:\n> +\tLOG(Debayer, Error) << \"Unsupported input output format combination\";\n> +\treturn -EINVAL;\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> +\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.toString();\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tinputConfig_.stride = inputCfg.stride;\n> +\twidth_ = inputCfg.size.width;\n> +\theight_ = inputCfg.size.height;\n> +\tccmEnabled_ = ccmEnabled = false;\n\nDo we have any reason to not use CCM?\n\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> +\tLOG(Debayer, Info) << \"Input size \" << inputCfg.size << \" stride \" << inputCfg.stride;\n> +\n> +\tif (gbmSurface_.initSurface(inputCfg.size.width, inputCfg.size.height))\n> +\t\treturn -ENODEV;\n> +\n> +\tif (egl_.initEGLContext(&gbmSurface_))\n> +\t\treturn -ENODEV;\n> +\n> +\tglGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);\n> +\tLOG(Debayer, Debug) << \"Fragment shader maximum texture units \" << maxTextureImageUnits;\n> +\n> +\tif (maxTextureImageUnits < DEBAYER_EGL_MIN_SIMPLE_RGB_GAIN_TEXTURE_UNITS) {\n> +\t\tLOG(Debayer, Error) << \"Fragment shader texture unit count \" << maxTextureImageUnits\n> +\t\t\t\t    << \" required minimum for RGB gain table lookup \" << DEBAYER_EGL_MIN_SIMPLE_RGB_GAIN_TEXTURE_UNITS\n> +\t\t\t\t    << \" try using an identity CCM \";\n> +\t\treturn -ENODEV;\n> +\t}\n> +\t// Raw bayer input as texture\n> +\teglImageBayerIn_ = new eGLImage(width_, height_, 32, GL_TEXTURE0, 0);\n> +\tif (!eglImageBayerIn_)\n> +\t\treturn -ENOMEM;\n> +\n> +\t/// RGB correction tables as 2d textures\n> +\t// eGL doesn't support glTexImage1D so we do a little hack with 2D to compensate\n> +\teglImageRedLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE1, 1);\n> +\tif (!eglImageRedLookup_)\n> +\t\treturn -ENOMEM;\n> +\n> +\teglImageGreenLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE2, 2);\n> +\tif (!eglImageGreenLookup_)\n> +\t\treturn -ENOMEM;\n> +\n> +\teglImageBlueLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE3, 3);\n> +\tif (!eglImageBlueLookup_)\n> +\t\treturn -ENOMEM;\n> +\n> +\t// Create a single BO (calling gbm_surface_lock_front_buffer() again before gbm_surface_release_buffer() would create another BO)\n> +\tif (gbmSurface_.mapSurface())\n> +\t\treturn -ENODEV;\n> +\n> +\tStreamConfiguration &outputCfg = outputCfgs[0];\n> +\tSizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size);\n> +\n> +\toutputConfig_.stride = gbmSurface_.getStride();\n> +\toutputConfig_.frameSize = gbmSurface_.getFrameSize();\n> +\n> +\tLOG(Debayer, Debug) << \"Overriding stream config stride \"\n> +\t\t\t    << outputCfg.stride << \" with GBM surface stride \"\n> +\t\t\t    << outputConfig_.stride;\n> +\toutputCfg.stride = outputConfig_.stride;\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> +\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/* Don't pass x,y since process() already adjusts src before passing it */\n> +\tstats_->setWindow(Rectangle(window_.size()));\n> +\n> +\tLOG(Debayer, Debug) << \"Input width \" << inputCfg.size.width << \" height \" << inputCfg.size.height;\n> +\tLOG(Debayer, Debug) << \"Output width \" << outputCfg.size.width << \" height \" << outputCfg.size.height;\n> +\tLOG(Debayer, Debug) << \"Output stride \" << outputCfg.size.width << \" height \" << outputCfg.size.height;\n> +\n> +\tif (initBayerShaders(inputCfg.pixelFormat, outputCfg.pixelFormat))\n> +\t\treturn -EINVAL;\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/* round up to multiple of 8 for 64 bits alignment */\n> +\tunsigned int stride = (size.width * config.bpp / 8 + 7) & ~7;\n> +\n> +\treturn std::make_tuple(stride, stride * size.height);\n> +}\n> +\n> +void DebayerEGL::setShaderVariableValues(void)\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\nstatic_cast<GLfloat>(width_)\n\n> +\tGLfloat Step[] = { 1.0f / (inputConfig_.stride - 1),\n> +\t\t\t   1.0f / (height_ - 1) };\n> +\tGLfloat Stride = 1.0f;\n> +\tGLfloat projIdentityMatrix[] = {\n> +\t\t1, 0, 0, 0,\n> +\t\t0, 1, 0, 0,\n> +\t\t0, 0, 1, 0,\n> +\t\t0, 0, 0, 1\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// 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> +\tglUniform1i(textureUniformBayerDataIn_, eglImageBayerIn_->texture_unit_uniform_id_);\n> +\tglUniform1i(textureUniformRedLookupDataIn_, eglImageRedLookup_->texture_unit_uniform_id_);\n> +\tglUniform1i(textureUniformGreenLookupDataIn_, eglImageGreenLookup_->texture_unit_uniform_id_);\n> +\tglUniform1i(textureUniformBlueLookupDataIn_, eglImageBlueLookup_->texture_unit_uniform_id_);\n> +\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> +\tglUniform2fv(textureUniformBayerFirstRed_, 1, firstRed);\n> +\tglUniform2fv(textureUniformSize_, 1, imgSize);\n> +\tglUniform2fv(textureUniformStep_, 1, Step);\n> +\tglUniform1f(textureUniformStrideFactor_, Stride);\n> +\tglUniformMatrix4fv(textureUniformProjMatrix_, 1,\n> +\t\t\t   GL_FALSE, projIdentityMatrix);\n> +\n> +\tLOG(Debayer, Debug) << \"vertexIn \" << attributeVertex_ << \" textureIn \" << attributeTexture_\n> +\t\t\t    << \" tex_y \" << textureUniformBayerDataIn_\n> +\t\t\t    << \" red_param \" << textureUniformRedLookupDataIn_\n> +\t\t\t    << \" green_param \" << textureUniformGreenLookupDataIn_\n> +\t\t\t    << \" blue_param \" << textureUniformBlueLookupDataIn_\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\t\" firstRed.x \" << firstRed[0] <<\n> +\t\t\t\t\" firstRed.y \" << firstRed[1] <<\n> +\t\t\t\t\" textureUniformSize_.width \" << imgSize[0] << \" \"\n> +\t\t\t\t\" textureUniformSize_.height \" << imgSize[1] <<\n> +\t\t\t\t\" textureUniformStep_.x \" << Step[0] <<\n> +\t\t\t\t\" textureUniformStep_.y \" << Step[1] <<\n> +\t\t\t\t\" textureUniformStrideFactor_ \" << Stride <<\n> +\t\t\t\t\" textureUniformProjMatrix_ \" << textureUniformProjMatrix_;\n> +\treturn;\n> +}\n> +\n> +void DebayerEGL::debayerGPU(MappedFrameBuffer &in, MappedFrameBuffer &out, DebayerParams &params)\n> +{\n> +\tLOG(Debayer, Debug)\n> +\t\t<< \"Input height \" << height_\n> +\t\t<< \" width \" << width_\n> +\t\t<< \" fd \" << in.getPlaneFD(0);\n> +\n> +\t// eGL context switch\n> +\tegl_.makeCurrent();\n> +\n> +\t// Greate a standard texture\n> +\t// we will replace this with the DMA version at some point\n> +\tegl_.createTexture2D(eglImageBayerIn_, inputConfig_.stride, height_, in.planes()[0].data());\n> +\n> +\t// Populate bayer parameters\n> +\tegl_.createTexture2D(eglImageRedLookup_, DebayerParams::kRGBLookupSize, 1, &params.red);\n> +\tegl_.createTexture2D(eglImageGreenLookup_, DebayerParams::kRGBLookupSize, 1, &params.green);\n> +\tegl_.createTexture2D(eglImageBlueLookup_, DebayerParams::kRGBLookupSize, 1, &params.blue);\n> +\n> +\t// Setup the scene\n> +\tsetShaderVariableValues();\n> +\tglViewport(0, 0, width_, height_);\n> +\tglClear(GL_COLOR_BUFFER_BIT);\n> +\tglDisable(GL_BLEND);\n> +\n> +\t// Draw the scene\n> +\tglDrawArrays(GL_TRIANGLE_FAN, 0, DEBAYER_OPENGL_COORDS);\n> +\n> +\t// eglclientWaitScynKhr / eglwaitsynckr ?\n> +\tegl_.swapBuffers();\n> +\n> +\t// Copy from the output GBM buffer to our output plane\n> +\t// once we get render to texture working the\n> +\t// explicit lock ioctl, memcpy and unlock ioctl won't be required\n> +\tgbmSurface_.getFrameBufferData(out.planes()[0].data(), out.planes()[0].size());\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, output);\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> +\tMappedFrameBuffer out(output, MappedFrameBuffer::MapFlag::Write);\n> +\tif (!in.isValid() || !out.isValid()) {\n> +\t\tLOG(Debayer, Error) << \"mmap-ing buffer(s) failed\";\n> +\t\tmetadata.status = FrameMetadata::FrameError;\n> +\t\treturn;\n> +\t}\n> +\n> +\tdebayerGPU(in, out, params);\n> +\n> +\tdmaSyncers.clear();\n> +\n> +\tbench_.finishFrame();\n> +\n> +\tmetadata.planes()[0].bytesused = out.planes()[0].size();\n> +\n> +\t// Calculate stats for the whole frame\n> +\tstats_->processFrame(frame, 0, input);\n> +\n> +\toutputBufferReady.emit(output);\n> +\tinputBufferReady.emit(input);\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.toString();\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 */\n> diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h\n> new file mode 100644\n> index 00000000..c0fc220f\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.h\n> @@ -0,0 +1,164 @@\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> + * debayer_opengl.h - EGL debayer header\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 <EGL/egl.h>\n> +#include <EGL/eglext.h>\n> +#include <GLES3/gl32.h>\n> +\n> +#include <libcamera/base/object.h>\n> +\n> +#include \"debayer.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> +namespace libcamera {\n> +\n> +#define DEBAYER_EGL_MIN_SIMPLE_RGB_GAIN_TEXTURE_UNITS 4\n> +#define DEBAYER_OPENGL_COORDS 4\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> +class DebayerEGL : public Debayer\n> +{\n> +public:\n> +\t/**\n> +\t * \\brief Constructs a DebayerEGL object.\n> +\t * \\param[in] stats Pointer to the stats object to use.\n> +\t */\n> +\tDebayerEGL(std::unique_ptr<SwStatsCpu> stats);\n> +\t~DebayerEGL();\n> +\n> +\t/*\n> +\t * Setup the Debayer object according to the passed in parameters.\n> +\t * Return 0 on success, a negative errno value on failure\n> +\t * (unsupported parameters).\n> +\t */\n> +\tint configure(const StreamConfiguration &inputCfg,\n> +\t\t      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,\n> +\t\t      bool ccmEnabled);\n> +\n> +\t/*\n> +\t * Get width and height at which the bayer-pattern repeats.\n> +\t * Return pattern-size or an empty Size for an unsupported inputFormat.\n> +\t */\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> +\n> +\t/**\n> +\t * \\brief Get the file descriptor for the statistics.\n> +\t *\n> +\t * \\return the file descriptor pointing to the statistics.\n> +\t */\n> +\tconst SharedFD &getStatsFD() { return stats_->getStatsFD(); }\n> +\n> +\t/**\n> +\t * \\brief Get the output frame size.\n> +\t *\n> +\t * \\return The output frame size.\n> +\t */\n> +\tunsigned int frameSize() { return outputConfig_.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(void);\n> +\tvoid configureTexture(GLuint &texture);\n> +\tvoid debayerGPU(MappedFrameBuffer &in, MappedFrameBuffer &out, DebayerParams &params);\n> +\n> +\t// Shader program identifiers\n> +\tGLuint vertexShaderId_;\n> +\tGLuint fragmentShaderId_;\n> +\tGLuint programId_;\n> +\tenum {\n> +\t\tBAYER_INPUT_INDEX = 0,\n> +\t\tBAYER_OUTPUT_INDEX,\n> +\t\tBAYER_BUF_NUM,\n> +\t};\n> +\n> +\t// Pointer to object representing input texture\n> +\teGLImage *eglImageBayerIn_;\n> +\n> +\teGLImage *eglImageRedLookup_;\n> +\teGLImage *eglImageGreenLookup_;\n> +\teGLImage *eglImageBlueLookup_;\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> +\tGLint textureUniformRedLookupDataIn_;\n> +\tGLint textureUniformGreenLookupDataIn_;\n> +\tGLint textureUniformBlueLookupDataIn_;\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> +\tbool ccmEnabled_;\n> +\n> +\tGLfloat vcoordinates[DEBAYER_OPENGL_COORDS][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> +\n> +\tGLfloat tcoordinates[DEBAYER_OPENGL_COORDS][2] = {\n> +\t\t{ 0.0f, 1.0f },\n> +\t\t{ 0.0f, 0.0f },\n> +\t\t{ 1.0f, 0.0f },\n> +\t\t{ 1.0f, 1.0f },\n> +\t};\n> +};\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build\n> index 59fa5f02..c61ac7d5 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","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 14B92C3237\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 18 Jun 2025 07:40:56 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 67F7B68DCC;\n\tWed, 18 Jun 2025 09:40:54 +0200 (CEST)","from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.133.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7F35261548\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 18 Jun 2025 09:40:52 +0200 (CEST)","from mail-wr1-f70.google.com (mail-wr1-f70.google.com\n\t[209.85.221.70]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-320-Svkb-8r6MA-I_UC59Lr9nA-1; Wed, 18 Jun 2025 03:40:49 -0400","by mail-wr1-f70.google.com with SMTP id\n\tffacd0b85a97d-3a4fabcafecso3124553f8f.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 18 Jun 2025 00:40:49 -0700 (PDT)","from mzamazal-thinkpadp1gen7.tpbc.csb\n\t(ip-77-48-47-2.net.vodafone.cz. [77.48.47.2])\n\tby smtp.gmail.com with ESMTPSA id\n\tffacd0b85a97d-3a568a54d98sm15967678f8f.7.2025.06.18.00.40.45\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tWed, 18 Jun 2025 00:40:46 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"Ijx4x7Lo\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1750232451;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=s8KyvlYhkKMlB5t7T79+MEXW/qJWObbV9zDtPeMf9/8=;\n\tb=Ijx4x7Lo0aO58muInmsLuaVxdnQ1gCUmOR+uVlTPiNBe/fv6zvy+7VUrY0KkCN6kyzMhPj\n\tlIym964AFiPQ6CJ1CbXK04vdCiOLPguPLYavn7BB56051zcd7HGnCOTrW+MaVM07KjON+h\n\t65uPwzIMEmbSahgGJf0G42uEr/bb++I=","X-MC-Unique":"Svkb-8r6MA-I_UC59Lr9nA-1","X-Mimecast-MFC-AGG-ID":"Svkb-8r6MA-I_UC59Lr9nA_1750232448","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1750232448; x=1750837248;\n\th=mime-version:user-agent:message-id:date:references:in-reply-to\n\t:subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=s8KyvlYhkKMlB5t7T79+MEXW/qJWObbV9zDtPeMf9/8=;\n\tb=M3UynMLLxKL8KRx5xOQ6Fw/Mehth8AUc+lIqT0NItwwU6j7urluxxprww94mPpU1f1\n\tYy4mwBgGcmKCC7vmD8796KDQHLOw4tPw/NqMVIwp+ST0MKiltCuFUZK0X+nw2CsHQlC5\n\teiKVHdff7lUQb1XW/FVK7cHlNu//MDZqGbsxVXz2DKe8+9+VkuLQ1eT4Ju188lIqlsSj\n\tymqJGY6ECxrwnt6rK06ywwRe4+8gXQ38NlLI59/DwhfIVaLb3Qfenhd8U1qjq77ah+4A\n\tkaHH1nQR8n3QdUxvgUXsUiAc6E7gUgVn1y2Oz45ZiUp6NCsOI7Qc7OrrRiO2cLQCcI6Q\n\tLjRA==","X-Gm-Message-State":"AOJu0YxVSCRCiCJIpL9iG6j2FVYBuVBrRTafTNcsQPlxKeofCcEnU145\n\tsv6qoRBh18G2w5vNh+gW1MsNL1NVPtDht9G9SGU8T7uzaHeiZFa5Ij4m9au7TDt+eTDY7cW5NSB\n\tRWF/4ZLv6u2HY9HK01SJwf1Pgugt9kG7jKGO4YFa1O1XXlP8am6RIdpnZZA7IQSATEbCfk7FL60\n\tK9zcumlZ5MjbqXQf41GYDO5CdQJENJMvaNctuRUHGFak1bvtc8jAXtnsLV8NM=","X-Gm-Gg":"ASbGncuEmpt/fxZqbxNsFBBhD7c/sRnn1zpGYBgljlmg53oDb/MSFcVwkGJ4ARK14Lk\n\t65gCr9isRngfiNiBu4fNrRzNIBSY+A3NvmTBE6NEZg8FecPRcqOXq8wdy++mBKbh5gVqA/LVEDp\n\t/XTXYqkXQiCQc7UbqlyNZp/Au56/1QRzifPryCWIknEHreuRnJz7V7Pnl50XGWbaVt5CH9a2zEQ\n\tEzM2zDFikAuwnfDvRxG1YJemaCLeNZRD/WzXvlVBACnjdJNUbIrOKd9c51+C1L16YScXcEw1YRn\n\tF1hnhTFmMPj2bNgTAIDe+cdKjTMTuUl4yNww2d44rmHffUMqfyfiY+5Fl3o11Miz2ZcA9ffU+FE\n\t=","X-Received":["by 2002:a05:6000:2484:b0:3a4:f7ae:77e8 with SMTP id\n\tffacd0b85a97d-3a5723720e3mr13791548f8f.15.1750232447625; \n\tWed, 18 Jun 2025 00:40:47 -0700 (PDT)","by 2002:a05:6000:2484:b0:3a4:f7ae:77e8 with SMTP id\n\tffacd0b85a97d-3a5723720e3mr13791521f8f.15.1750232446961; \n\tWed, 18 Jun 2025 00:40:46 -0700 (PDT)"],"X-Google-Smtp-Source":"AGHT+IE2lRvVSzjsIz6Ijp1TYeyJFVXXpaD3nZh7UBgHrb5rjcIE40iQo8mDuqFmyae2Y9zePwjGYA==","From":"Milan Zamazal <mzamazal@redhat.com>","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 23/35] libcamera: software_isp: debayer_egl: Add an eGL\n\tdebayer class","In-Reply-To":"<20250611013245.133785-24-bryan.odonoghue@linaro.org> (Bryan\n\tO'Donoghue's message of \"Wed, 11 Jun 2025 02:32:33 +0100\")","References":"<20250611013245.133785-1-bryan.odonoghue@linaro.org>\n\t<20250611013245.133785-24-bryan.odonoghue@linaro.org>","Date":"Wed, 18 Jun 2025 09:40:45 +0200","Message-ID":"<85ldppijk2.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","User-Agent":"Gnus/5.13 (Gnus v5.13)","MIME-Version":"1.0","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"Fs9T9ZZTwWzK3662ZaL9FPsg1kxKev2OqexTa4jRhLY_1750232448","X-Mimecast-Originator":"redhat.com","Content-Type":"text/plain","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":34792,"web_url":"https://patchwork.libcamera.org/comment/34792/","msgid":"<85ldp0mi0n.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2025-07-07T10:07:04","subject":"Re: [PATCH 23/35] libcamera: software_isp: debayer_egl: Add an eGL\n\tdebayer class","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Bryan O'Donoghue <bryan.odonoghue@linaro.org> writes:\n\n> Add a class to run the existing glsl debayer shaders on a GBM surface.\n>\n> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n>\n> libcamera: software_isp: debayer_egl: Extend logic to enable application of softISP RGB debayer params\n>\n> The existing SoftISP calculates RGB gain values as a lookup table of 256\n> values which shifts for each frame depending on the required correction.\n>\n> We can pass the required tables into the debayer shaders as textures, one\n> texture for R, G and B respectively.\n>\n> The debayer shader will do its debayer interpolation and then if the\n> appropriate define is specified use the calculated R, G and B values as\n> indexes into our bayer colour gain table.\n>\n> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> ---\n>  src/libcamera/egl.cpp                      |   4 +-\n>  src/libcamera/software_isp/debayer_egl.cpp | 587 +++++++++++++++++++++\n>  src/libcamera/software_isp/debayer_egl.h   | 164 ++++++\n>  src/libcamera/software_isp/meson.build     |   8 +\n>  4 files changed, 761 insertions(+), 2 deletions(-)\n>  create mode 100644 src/libcamera/software_isp/debayer_egl.cpp\n>  create mode 100644 src/libcamera/software_isp/debayer_egl.h\n>\n> diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp\n> index 89ece148..c6b0f9a5 100644\n> --- a/src/libcamera/egl.cpp\n> +++ b/src/libcamera/egl.cpp\n> @@ -82,13 +82,13 @@ void eGL::destroyDMABufTexture(eGLImage *eglImage)\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> +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, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);\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> diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp\n> new file mode 100644\n> index 00000000..3fb15511\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.cpp\n> @@ -0,0 +1,587 @@\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> + * debayer_cpu.cpp - EGL based debayering class\n> + */\n> +\n> +#include <math.h>\n> +#include <stdlib.h>\n> +#include <time.h>\n> +\n> +#include <libcamera/formats.h>\n> +\n> +#include \"libcamera/internal/glsl_shaders.h\"\n> +#include \"debayer_egl.h\"\n> +\n> +namespace libcamera {\n> +\n> +DebayerEGL::DebayerEGL(std::unique_ptr<SwStatsCpu> stats)\n> +\t: Debayer(), stats_(std::move(stats))\n> +{\n> +\teglImageBayerIn_ = eglImageRedLookup_ = eglImageBlueLookup_ = eglImageGreenLookup_ = NULL;\n> +}\n> +\n> +DebayerEGL::~DebayerEGL()\n> +{\n> +\tif (eglImageBlueLookup_)\n> +\t\tdelete eglImageBlueLookup_;\n> +\n> +\tif (eglImageGreenLookup_)\n> +\t\tdelete eglImageGreenLookup_;\n> +\n> +\tif (eglImageRedLookup_)\n> +\t\tdelete eglImageRedLookup_;\n> +\n> +\tif (eglImageBayerIn_)\n> +\t\tdelete eglImageBayerIn_;\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, Info)\n> +\t\t<< \"Unsupported input format \" << inputFormat.toString();\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.toString();\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> +\ttextureUniformRedLookupDataIn_ = glGetUniformLocation(programId_, \"red_param\");\n> +\ttextureUniformGreenLookupDataIn_ = glGetUniformLocation(programId_, \"green_param\");\n> +\ttextureUniformBlueLookupDataIn_ = glGetUniformLocation(programId_, \"blue_param\");\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    << \" red_param \" << textureUniformRedLookupDataIn_\n> +\t\t\t    << \" green_param \" << textureUniformGreenLookupDataIn_\n> +\t\t\t    << \" blue_param \" << textureUniformBlueLookupDataIn_\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;\n> +\tunsigned char *fragmentShaderData;\n> +\tunsigned int vertexShaderDataLen;\n> +\tunsigned char *vertexShaderData;\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// Tell shaders how to re-order output taking account of how the\n> +\t// pixels are actually stored by GBM\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\tgoto invalid_fmt;\n> +\t}\n> +\n> +\t// Pixel location parameters\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\tgoto invalid_fmt;\n> +\t\tbreak;\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_8_frag;\n> +\t\tfragmentShaderDataLen = bayer_8_frag_len;\n> +\t\tvertexShaderData = bayer_8_vert;\n> +\t\tvertexShaderDataLen = bayer_8_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\tfragmentShaderData = bayer_1x_packed_frag;\n> +\t\tfragmentShaderDataLen = bayer_1x_packed_frag_len;\n> +\t\tvertexShaderData = identity_vert;\n> +\t\tvertexShaderDataLen = identity_vert_len;\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\tfragmentShaderData = bayer_1x_packed_frag;\n> +\t\tfragmentShaderDataLen = bayer_1x_packed_frag_len;\n> +\t\tvertexShaderData = identity_vert;\n> +\t\tvertexShaderDataLen = identity_vert_len;\n> +\t\tbreak;\n> +\tdefault:\n> +\t\tgoto invalid_fmt;\n> +\t\tbreak;\n> +\t};\n> +\n> +\t// Flag to shaders that we have parameter gain tables\n> +\tegl_.pushEnv(shaderEnv, \"#define APPLY_RGB_PARAMETERS\");\n> +\n> +\tif (egl_.compileVertexShader(vertexShaderId_, vertexShaderData, vertexShaderDataLen, shaderEnv))\n> +\t\tgoto compile_fail;\n> +\n> +\tif (egl_.compileFragmentShader(fragmentShaderId_, fragmentShaderData, fragmentShaderDataLen, shaderEnv))\n> +\t\tgoto compile_fail;\n> +\n> +\tif (egl_.linkProgram(programId_, vertexShaderId_, fragmentShaderId_))\n> +\t\tgoto link_fail;\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\tgoto program_fail;\n> +\n> +\tif (getShaderVariableLocations())\n> +\t\tgoto parameters_fail;\n> +\n> +\treturn 0;\n> +\n> +parameters_fail:\n> +\tLOG(Debayer, Error) << \"Program parameters fail\";\n> +\treturn -ENODEV;\n> +\n> +program_fail:\n> +\tLOG(Debayer, Error) << \"Use program error \" << err;\n> +\treturn -ENODEV;\n> +\n> +link_fail:\n> +\tLOG(Debayer, Error) << \"Linking program fail\";\n> +\treturn -ENODEV;\n> +\n> +compile_fail:\n> +\tLOG(Debayer, Error) << \"Compile debayer shaders fail\";\n> +\treturn -ENODEV;\n> +\n> +invalid_fmt:\n> +\tLOG(Debayer, Error) << \"Unsupported input output format combination\";\n> +\treturn -EINVAL;\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> +\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.toString();\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tinputConfig_.stride = inputCfg.stride;\n> +\twidth_ = inputCfg.size.width;\n> +\theight_ = inputCfg.size.height;\n> +\tccmEnabled_ = ccmEnabled = false;\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> +\tLOG(Debayer, Info) << \"Input size \" << inputCfg.size << \" stride \" << inputCfg.stride;\n> +\n> +\tif (gbmSurface_.initSurface(inputCfg.size.width, inputCfg.size.height))\n> +\t\treturn -ENODEV;\n> +\n> +\tif (egl_.initEGLContext(&gbmSurface_))\n> +\t\treturn -ENODEV;\n> +\n> +\tglGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);\n> +\tLOG(Debayer, Debug) << \"Fragment shader maximum texture units \" << maxTextureImageUnits;\n> +\n> +\tif (maxTextureImageUnits < DEBAYER_EGL_MIN_SIMPLE_RGB_GAIN_TEXTURE_UNITS) {\n> +\t\tLOG(Debayer, Error) << \"Fragment shader texture unit count \" << maxTextureImageUnits\n> +\t\t\t\t    << \" required minimum for RGB gain table lookup \" << DEBAYER_EGL_MIN_SIMPLE_RGB_GAIN_TEXTURE_UNITS\n> +\t\t\t\t    << \" try using an identity CCM \";\n> +\t\treturn -ENODEV;\n> +\t}\n> +\t// Raw bayer input as texture\n> +\teglImageBayerIn_ = new eGLImage(width_, height_, 32, GL_TEXTURE0, 0);\n> +\tif (!eglImageBayerIn_)\n> +\t\treturn -ENOMEM;\n> +\n> +\t/// RGB correction tables as 2d textures\n> +\t// eGL doesn't support glTexImage1D so we do a little hack with 2D to compensate\n> +\teglImageRedLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE1, 1);\n> +\tif (!eglImageRedLookup_)\n> +\t\treturn -ENOMEM;\n> +\n> +\teglImageGreenLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE2, 2);\n> +\tif (!eglImageGreenLookup_)\n> +\t\treturn -ENOMEM;\n> +\n> +\teglImageBlueLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE3, 3);\n> +\tif (!eglImageBlueLookup_)\n> +\t\treturn -ENOMEM;\n> +\n> +\t// Create a single BO (calling gbm_surface_lock_front_buffer() again before gbm_surface_release_buffer() would create another BO)\n> +\tif (gbmSurface_.mapSurface())\n> +\t\treturn -ENODEV;\n> +\n> +\tStreamConfiguration &outputCfg = outputCfgs[0];\n> +\tSizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size);\n> +\n> +\toutputConfig_.stride = gbmSurface_.getStride();\n> +\toutputConfig_.frameSize = gbmSurface_.getFrameSize();\n> +\n> +\tLOG(Debayer, Debug) << \"Overriding stream config stride \"\n> +\t\t\t    << outputCfg.stride << \" with GBM surface stride \"\n> +\t\t\t    << outputConfig_.stride;\n> +\toutputCfg.stride = outputConfig_.stride;\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> +\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/* Don't pass x,y since process() already adjusts src before passing it */\n> +\tstats_->setWindow(Rectangle(window_.size()));\n> +\n> +\tLOG(Debayer, Debug) << \"Input width \" << inputCfg.size.width << \" height \" << inputCfg.size.height;\n> +\tLOG(Debayer, Debug) << \"Output width \" << outputCfg.size.width << \" height \" << outputCfg.size.height;\n> +\tLOG(Debayer, Debug) << \"Output stride \" << outputCfg.size.width << \" height \" << outputCfg.size.height;\n> +\n> +\tif (initBayerShaders(inputCfg.pixelFormat, outputCfg.pixelFormat))\n> +\t\treturn -EINVAL;\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/* round up to multiple of 8 for 64 bits alignment */\n> +\tunsigned int stride = (size.width * config.bpp / 8 + 7) & ~7;\n> +\n> +\treturn std::make_tuple(stride, stride * size.height);\n> +}\n> +\n> +void DebayerEGL::setShaderVariableValues(void)\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[] = { 1.0f / (inputConfig_.stride - 1),\n> +\t\t\t   1.0f / (height_ - 1) };\n> +\tGLfloat Stride = 1.0f;\n> +\tGLfloat projIdentityMatrix[] = {\n> +\t\t1, 0, 0, 0,\n> +\t\t0, 1, 0, 0,\n> +\t\t0, 0, 1, 0,\n> +\t\t0, 0, 0, 1\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// 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> +\tglUniform1i(textureUniformBayerDataIn_, eglImageBayerIn_->texture_unit_uniform_id_);\n> +\tglUniform1i(textureUniformRedLookupDataIn_, eglImageRedLookup_->texture_unit_uniform_id_);\n> +\tglUniform1i(textureUniformGreenLookupDataIn_, eglImageGreenLookup_->texture_unit_uniform_id_);\n> +\tglUniform1i(textureUniformBlueLookupDataIn_, eglImageBlueLookup_->texture_unit_uniform_id_);\n> +\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> +\tglUniform2fv(textureUniformBayerFirstRed_, 1, firstRed);\n> +\tglUniform2fv(textureUniformSize_, 1, imgSize);\n> +\tglUniform2fv(textureUniformStep_, 1, Step);\n> +\tglUniform1f(textureUniformStrideFactor_, Stride);\n> +\tglUniformMatrix4fv(textureUniformProjMatrix_, 1,\n> +\t\t\t   GL_FALSE, projIdentityMatrix);\n> +\n> +\tLOG(Debayer, Debug) << \"vertexIn \" << attributeVertex_ << \" textureIn \" << attributeTexture_\n> +\t\t\t    << \" tex_y \" << textureUniformBayerDataIn_\n> +\t\t\t    << \" red_param \" << textureUniformRedLookupDataIn_\n> +\t\t\t    << \" green_param \" << textureUniformGreenLookupDataIn_\n> +\t\t\t    << \" blue_param \" << textureUniformBlueLookupDataIn_\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\t\" firstRed.x \" << firstRed[0] <<\n> +\t\t\t\t\" firstRed.y \" << firstRed[1] <<\n> +\t\t\t\t\" textureUniformSize_.width \" << imgSize[0] << \" \"\n> +\t\t\t\t\" textureUniformSize_.height \" << imgSize[1] <<\n> +\t\t\t\t\" textureUniformStep_.x \" << Step[0] <<\n> +\t\t\t\t\" textureUniformStep_.y \" << Step[1] <<\n> +\t\t\t\t\" textureUniformStrideFactor_ \" << Stride <<\n> +\t\t\t\t\" textureUniformProjMatrix_ \" << textureUniformProjMatrix_;\n> +\treturn;\n> +}\n> +\n> +void DebayerEGL::debayerGPU(MappedFrameBuffer &in, MappedFrameBuffer &out, DebayerParams &params)\n> +{\n> +\tLOG(Debayer, Debug)\n> +\t\t<< \"Input height \" << height_\n> +\t\t<< \" width \" << width_\n> +\t\t<< \" fd \" << in.getPlaneFD(0);\n> +\n> +\t// eGL context switch\n> +\tegl_.makeCurrent();\n> +\n> +\t// Greate a standard texture\n> +\t// we will replace this with the DMA version at some point\n> +\tegl_.createTexture2D(eglImageBayerIn_, inputConfig_.stride, height_, in.planes()[0].data());\n> +\n> +\t// Populate bayer parameters\n> +\tegl_.createTexture2D(eglImageRedLookup_, DebayerParams::kRGBLookupSize, 1, &params.red);\n> +\tegl_.createTexture2D(eglImageGreenLookup_, DebayerParams::kRGBLookupSize, 1, &params.green);\n> +\tegl_.createTexture2D(eglImageBlueLookup_, DebayerParams::kRGBLookupSize, 1, &params.blue);\n> +\n> +\t// Setup the scene\n> +\tsetShaderVariableValues();\n> +\tglViewport(0, 0, width_, height_);\n> +\tglClear(GL_COLOR_BUFFER_BIT);\n> +\tglDisable(GL_BLEND);\n> +\n> +\t// Draw the scene\n> +\tglDrawArrays(GL_TRIANGLE_FAN, 0, DEBAYER_OPENGL_COORDS);\n> +\n> +\t// eglclientWaitScynKhr / eglwaitsynckr ?\n> +\tegl_.swapBuffers();\n\nSome sync is definitely needed here (or a mechanism other than buffer\nswapping).  I get occasional artefacts where a part of the output image\nis replaced by a different part of a (next/previous?) image, sometimes\nadditionally with a strong purple cast.\n\nIf I add a sleep call like:\n\n  usleep(50000);\n  egl_.swapBuffers();\n\nthe artefacts are gone.  If I add it after:\n\n  egl_.swapBuffers();\n  usleep(50000);\n\nthey are present.\n\n> +\n> +\t// Copy from the output GBM buffer to our output plane\n> +\t// once we get render to texture working the\n> +\t// explicit lock ioctl, memcpy and unlock ioctl won't be required\n> +\tgbmSurface_.getFrameBufferData(out.planes()[0].data(), out.planes()[0].size());\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, output);\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> +\tMappedFrameBuffer out(output, MappedFrameBuffer::MapFlag::Write);\n> +\tif (!in.isValid() || !out.isValid()) {\n> +\t\tLOG(Debayer, Error) << \"mmap-ing buffer(s) failed\";\n> +\t\tmetadata.status = FrameMetadata::FrameError;\n> +\t\treturn;\n> +\t}\n> +\n> +\tdebayerGPU(in, out, params);\n> +\n> +\tdmaSyncers.clear();\n> +\n> +\tbench_.finishFrame();\n> +\n> +\tmetadata.planes()[0].bytesused = out.planes()[0].size();\n> +\n> +\t// Calculate stats for the whole frame\n> +\tstats_->processFrame(frame, 0, input);\n> +\n> +\toutputBufferReady.emit(output);\n> +\tinputBufferReady.emit(input);\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.toString();\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 */\n> diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h\n> new file mode 100644\n> index 00000000..c0fc220f\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.h\n> @@ -0,0 +1,164 @@\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> + * debayer_opengl.h - EGL debayer header\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 <EGL/egl.h>\n> +#include <EGL/eglext.h>\n> +#include <GLES3/gl32.h>\n> +\n> +#include <libcamera/base/object.h>\n> +\n> +#include \"debayer.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> +namespace libcamera {\n> +\n> +#define DEBAYER_EGL_MIN_SIMPLE_RGB_GAIN_TEXTURE_UNITS 4\n> +#define DEBAYER_OPENGL_COORDS 4\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> +class DebayerEGL : public Debayer\n> +{\n> +public:\n> +\t/**\n> +\t * \\brief Constructs a DebayerEGL object.\n> +\t * \\param[in] stats Pointer to the stats object to use.\n> +\t */\n> +\tDebayerEGL(std::unique_ptr<SwStatsCpu> stats);\n> +\t~DebayerEGL();\n> +\n> +\t/*\n> +\t * Setup the Debayer object according to the passed in parameters.\n> +\t * Return 0 on success, a negative errno value on failure\n> +\t * (unsupported parameters).\n> +\t */\n> +\tint configure(const StreamConfiguration &inputCfg,\n> +\t\t      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,\n> +\t\t      bool ccmEnabled);\n> +\n> +\t/*\n> +\t * Get width and height at which the bayer-pattern repeats.\n> +\t * Return pattern-size or an empty Size for an unsupported inputFormat.\n> +\t */\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> +\n> +\t/**\n> +\t * \\brief Get the file descriptor for the statistics.\n> +\t *\n> +\t * \\return the file descriptor pointing to the statistics.\n> +\t */\n> +\tconst SharedFD &getStatsFD() { return stats_->getStatsFD(); }\n> +\n> +\t/**\n> +\t * \\brief Get the output frame size.\n> +\t *\n> +\t * \\return The output frame size.\n> +\t */\n> +\tunsigned int frameSize() { return outputConfig_.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(void);\n> +\tvoid configureTexture(GLuint &texture);\n> +\tvoid debayerGPU(MappedFrameBuffer &in, MappedFrameBuffer &out, DebayerParams &params);\n> +\n> +\t// Shader program identifiers\n> +\tGLuint vertexShaderId_;\n> +\tGLuint fragmentShaderId_;\n> +\tGLuint programId_;\n> +\tenum {\n> +\t\tBAYER_INPUT_INDEX = 0,\n> +\t\tBAYER_OUTPUT_INDEX,\n> +\t\tBAYER_BUF_NUM,\n> +\t};\n> +\n> +\t// Pointer to object representing input texture\n> +\teGLImage *eglImageBayerIn_;\n> +\n> +\teGLImage *eglImageRedLookup_;\n> +\teGLImage *eglImageGreenLookup_;\n> +\teGLImage *eglImageBlueLookup_;\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> +\tGLint textureUniformRedLookupDataIn_;\n> +\tGLint textureUniformGreenLookupDataIn_;\n> +\tGLint textureUniformBlueLookupDataIn_;\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> +\tbool ccmEnabled_;\n> +\n> +\tGLfloat vcoordinates[DEBAYER_OPENGL_COORDS][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> +\n> +\tGLfloat tcoordinates[DEBAYER_OPENGL_COORDS][2] = {\n> +\t\t{ 0.0f, 1.0f },\n> +\t\t{ 0.0f, 0.0f },\n> +\t\t{ 1.0f, 0.0f },\n> +\t\t{ 1.0f, 1.0f },\n> +\t};\n> +};\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build\n> index 59fa5f02..c61ac7d5 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","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 49F71C3237\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  7 Jul 2025 10:07:14 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 3E60068E8F;\n\tMon,  7 Jul 2025 12:07:13 +0200 (CEST)","from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.129.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4DF8768E74\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  7 Jul 2025 12:07:11 +0200 (CEST)","from mail-ej1-f69.google.com (mail-ej1-f69.google.com\n\t[209.85.218.69]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-627-u5jPrs9DOj68ceYdfQ610Q-1; Mon, 07 Jul 2025 06:07:08 -0400","by mail-ej1-f69.google.com with SMTP id\n\ta640c23a62f3a-adf58589c90so391632866b.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 07 Jul 2025 03:07:08 -0700 (PDT)","from mzamazal-thinkpadp1gen7.tpbc.csb\n\t(ip-77-48-47-2.net.vodafone.cz. [77.48.47.2])\n\tby smtp.gmail.com with ESMTPSA id\n\ta640c23a62f3a-ae3f69134c0sm666444266b.40.2025.07.07.03.07.04\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 07 Jul 2025 03:07:05 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"jG4dw9ah\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1751882830;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=HVUcD7GyQANoOWgkOy5PCFjdFPxQADhucU/gFriN7Gc=;\n\tb=jG4dw9ah15OyWq51+dvWGrUuHe2TLDnxwrpmNBbeDmGNi5CURT5of7gNmoIVZX7Rfoqq83\n\ttv9T6+8zn7yMSbGqx9ecLJGjXgDfsjrjfYGaj5jLN48Z4tgMWgaAEcOEUcMGojK0UYmExz\n\ttzQza4AOlpuu4fnLnyhJRQZOmY4UZGM=","X-MC-Unique":"u5jPrs9DOj68ceYdfQ610Q-1","X-Mimecast-MFC-AGG-ID":"u5jPrs9DOj68ceYdfQ610Q_1751882827","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1751882827; x=1752487627;\n\th=mime-version:user-agent:message-id:date:references:in-reply-to\n\t:subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=HVUcD7GyQANoOWgkOy5PCFjdFPxQADhucU/gFriN7Gc=;\n\tb=MIhH1t7MMuhnc6IY9VqrXxe6gFRYKWqtIElecrinvxQdgURc+9fbL2w3FLzfm8xxhO\n\tHcK5BdT948a8HULJCcS+zBVTT87puqaUaKfy2mHzei4K+PsxA++E3hO5IfRroUuhUbkG\n\tAOKWYiVi+NRGyxn6omrP1F8c3SHWFAP3jp5PKMFDo3qpwLbyuv2lABNnvIl3Kppry3t/\n\tjGZL7I8tkKvs9V4UmHr7+0Zvl4RYTMgxn+A/cBT0VS49iaQgRrNeKyQGul9ScMtfV3xL\n\tAOheiIKjMGpPw88V+y7PdPpdrUVZqOsflQ9DE7LUoP+6/sL37sz9r/euge2Od+hLnWXp\n\tVqhg==","X-Gm-Message-State":"AOJu0YxeMApU6wSCMZgSeA4k8dqbJssJIjJ4eX2p/3PdtTwLOM8AXlFC\n\tt2gjxsRAmmdsL0Wkb4EnDMSXXFYO0dqhysYDMt+i8NeHnyBVOZ1YXrVHqQL7ecnJfc5vk62I/1G\n\thWZvrTPgkDLZlQMd+OkiZdDKxAzQdGNLDkjCL4jLa/d5De+/aiBT9u6VhIpJX9Oxq/U0AiJtz/b\n\tKPRfNOW/MpTi75cEyt7g+sYoYmiRpW7jXqmDol8v/0i+g48t2bxo8JqTdvFj0=","X-Gm-Gg":"ASbGnctKTHnFsbpibU9Cj8oRM482z0TRa2CNhd5YA38yGeFpz+8TQ0fRivkpK7OCeWw\n\t+dYdUKrpdZjPKmOgIlrLMbiVph0lrXK1MW87eRc/ZnXMFxLaE5RK5IbXL7/KShyg3tWW51OfPWb\n\thsSOEQfPWhxgfIgAqoJjkVd0roilp404tSnfYEW8K8R07s9lDHQ9fiMfMv4grN+tG3HzHyRPLqg\n\t9tW0asFkeS/9dO7v7vAcUAZwTQrMavP58L8MUQNgEOoVOfaICuxWCCnReFdvyB0ofogtXSPbrDZ\n\tyo7vuu2uPjv80z2BCq9CPGzdJLGcDII900HIq/pjj8tfY80PixdStz6786AP/GSSxbssUQfzA5w\n\t=","X-Received":["by 2002:a17:907:9487:b0:acb:37ae:619c with SMTP id\n\ta640c23a62f3a-ae3f9ccc41bmr1320231666b.15.1751882826644; \n\tMon, 07 Jul 2025 03:07:06 -0700 (PDT)","by 2002:a17:907:9487:b0:acb:37ae:619c with SMTP id\n\ta640c23a62f3a-ae3f9ccc41bmr1320226366b.15.1751882825798; \n\tMon, 07 Jul 2025 03:07:05 -0700 (PDT)"],"X-Google-Smtp-Source":"AGHT+IF3uLbgq6aAWQUUsvnESMuOsnTFek/j0ghQ3AYHayxbkitfLrj45O8mhhCZL3MytPhbb6LzHw==","From":"Milan Zamazal <mzamazal@redhat.com>","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 23/35] libcamera: software_isp: debayer_egl: Add an eGL\n\tdebayer class","In-Reply-To":"<20250611013245.133785-24-bryan.odonoghue@linaro.org> (Bryan\n\tO'Donoghue's message of \"Wed, 11 Jun 2025 02:32:33 +0100\")","References":"<20250611013245.133785-1-bryan.odonoghue@linaro.org>\n\t<20250611013245.133785-24-bryan.odonoghue@linaro.org>","Date":"Mon, 07 Jul 2025 12:07:04 +0200","Message-ID":"<85ldp0mi0n.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","User-Agent":"Gnus/5.13 (Gnus v5.13)","MIME-Version":"1.0","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"oDZ0H4CckP4RvJPl0fR1eshwG2X5Jl0s8gEx2OG9HX4_1751882827","X-Mimecast-Originator":"redhat.com","Content-Type":"text/plain","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":34797,"web_url":"https://patchwork.libcamera.org/comment/34797/","msgid":"<400b309d-4867-43fb-8e8b-56b795f29dde@linaro.org>","date":"2025-07-07T10:37:28","subject":"Re: [PATCH 23/35] libcamera: software_isp: debayer_egl: Add an eGL\n\tdebayer class","submitter":{"id":175,"url":"https://patchwork.libcamera.org/api/people/175/","name":"Bryan O'Donoghue","email":"bryan.odonoghue@linaro.org"},"content":"On 07/07/2025 11:07, Milan Zamazal wrote:\n>> +\tegl_.swapBuffers();\n> Some sync is definitely needed here (or a mechanism other than buffer\n> swapping).  I get occasional artefacts where a part of the output image\n> is replaced by a different part of a (next/previous?) image, sometimes\n> additionally with a strong purple cast.\n> \n> If I add a sleep call like:\n> \n>    usleep(50000);\n>    egl_.swapBuffers();\n> \n> the artefacts are gone.  If I add it after:\n> \n>    egl_.swapBuffers();\n>    usleep(50000);\n> \n> they are present.\n\nack\n\n---\nbod","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id C1127C0DA4\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  7 Jul 2025 10:37:32 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7808E68EA6;\n\tMon,  7 Jul 2025 12:37:32 +0200 (CEST)","from mail-wr1-x435.google.com (mail-wr1-x435.google.com\n\t[IPv6:2a00:1450:4864:20::435])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 1474D68E92\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  7 Jul 2025 12:37:30 +0200 (CEST)","by mail-wr1-x435.google.com with SMTP id\n\tffacd0b85a97d-3a54700a46eso1458292f8f.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 07 Jul 2025 03:37:30 -0700 (PDT)","from [192.168.0.35] (188-141-3-146.dynamic.upc.ie. [188.141.3.146])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-454b1890626sm110568095e9.40.2025.07.07.03.37.28\n\t(version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);\n\tMon, 07 Jul 2025 03:37:29 -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=\"Gqo9Y+pR\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=linaro.org; s=google; t=1751884649; x=1752489449;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:in-reply-to:content-language:from\n\t:references:cc:to:subject:user-agent:mime-version:date:message-id\n\t:from:to:cc:subject:date:message-id:reply-to;\n\tbh=e4+mpsxb2CBeHO677RQr2+uRTXr3VHz4m/bOCfAss6Q=;\n\tb=Gqo9Y+pRIIaNqDx/Kl8lQcuSYp+SANlfqi/oWBJUiK6fJCofdA11ekXeZ36crFdD2j\n\tza8XArhQbx6MAbX0nrtOxBXv7yPvGEuTEQsZ+w5rLjEeaRxpucVUqm7/jSilcDg0Nddf\n\tmWktRFy6Y8w7HaVAGdOmwx4HY8DW+PfE/CoReKyW3ydx0SkTmC3JITavOS8CSmqo/jNG\n\tKRw9ZKWCI5+uB6OMtrAT5EmruMbf8iLa6f00cFcyWdemf96g+51tm232MUXNTfq8etAP\n\tcOdVrc+zUlt0DqrgPfOd0SvHTInauEQUyTG7/uk343kVVyRuz3N/6ef2bAjj7CLeaUEB\n\tr/RA==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1751884649; x=1752489449;\n\th=content-transfer-encoding:in-reply-to:content-language:from\n\t:references:cc:to:subject:user-agent:mime-version:date:message-id\n\t:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to;\n\tbh=e4+mpsxb2CBeHO677RQr2+uRTXr3VHz4m/bOCfAss6Q=;\n\tb=ZzV1B3kSUomQcL3GbsGvpHwbH+6VIvWNtNtWQ+OVrwbyZBj2U0J/dcqDZ7EyVGj8V+\n\t4M4nDnOJWcMyrbUSXD7XAzrlTCCBKzRbh8PsobEkDA2M6nHY5XSNanFgO+Jc6hqotu95\n\t2fuOrB2KwA2o1ZjWhK3uy3Tfr0/GJmEB/65ezyBjTdBcDQejXsbi/Q5BHzR9mEReqG+c\n\tw6DnpIFhEolVWpHp1X3IJTKKpQCuJ9Ek2eYbV3xfrHQn+2bOWmfuVfQ61NI0Kb7DWz90\n\t+4BE7SUftiYQX04m1apwHMkqDthBW+DsPFEbqcj1Kk4ErtUGoz2a+uifT8X9KlfCA/F8\n\tsjlg==","X-Gm-Message-State":"AOJu0Yyl3hxi/fTx0uP3QeyKfnQmVohawSYS/bSGFakuDNQbZCp8WKuN\n\tVNtaoRyVGlIb4b49wj/FK197QdfeUnV/4U+DLH7g/Si8nA9GxiEz1aWQl3L9VSu6A+T0p52KGw2\n\tyv5ZBb00=","X-Gm-Gg":"ASbGncvwQ1wRl/jqKNU5h5gWd9B0D2pE4cHuBInT/Pdcwn9nbwxmUg0fZ0D9/RBAK8H\n\tQGKkc6y04FZc3CsEjVMWyf8UTydou6UVQbZri7IcTzbmi4CtKnpX9duKIYwT/IMqMg+Y62pJy3w\n\t4QkpBxUPE/dVO8M61rVR0Ss/VK2wnn7AHHl+5kXqndL1e7h2VJynEEjNXNFmOh0zP0t9N9e22lc\n\tr59cjY5zH5gvb2f/UdU+YMEqWRmO8hWQ8BayjBlw4Sk197rE6JOtM2YCl+5sWAphnSbbYMKsKj0\n\tGm81RuBAUZ1q4RCzxAH2tG1SyZCdHkZmOnoeT/OMOzeJ7i0ntSlYm/Ti3CmBQu6yTEyEvisGj7u\n\trxoWv9hu3nwAY2ehOGVFkhGMRzNE=","X-Google-Smtp-Source":"AGHT+IHSTw7owQBfTb4mdxt0rouK+29istxXf9I9cwNWXsuiNKXrrmtgS7Nal40a/wapSs3sFJwTmw==","X-Received":"by 2002:a05:6000:2c0e:b0:3a4:e844:745d with SMTP id\n\tffacd0b85a97d-3b49aabc786mr6398028f8f.56.1751884649568; \n\tMon, 07 Jul 2025 03:37:29 -0700 (PDT)","Message-ID":"<400b309d-4867-43fb-8e8b-56b795f29dde@linaro.org>","Date":"Mon, 7 Jul 2025 11:37:28 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 23/35] libcamera: software_isp: debayer_egl: Add an eGL\n\tdebayer class","To":"Milan Zamazal <mzamazal@redhat.com>","Cc":"libcamera-devel@lists.libcamera.org","References":"<20250611013245.133785-1-bryan.odonoghue@linaro.org>\n\t<20250611013245.133785-24-bryan.odonoghue@linaro.org>\n\t<85ldp0mi0n.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","From":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","Content-Language":"en-US","In-Reply-To":"<85ldp0mi0n.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]