[{"id":36256,"web_url":"https://patchwork.libcamera.org/comment/36256/","msgid":"<bdd2c514-c27d-497a-b152-26ac063ca97b@collabora.com>","date":"2025-10-15T10:34:39","subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","submitter":{"id":140,"url":"https://patchwork.libcamera.org/api/people/140/","name":"Robert Mader","email":"robert.mader@collabora.com"},"content":"On 10/15/25 03:22, 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>\n> ---\n>   src/libcamera/software_isp/debayer_egl.cpp | 648 +++++++++++++++++++++\n>   src/libcamera/software_isp/debayer_egl.h   | 174 ++++++\n>   src/libcamera/software_isp/meson.build     |   8 +\n>   3 files changed, 830 insertions(+)\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/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp\n> new file mode 100644\n> index 00000000..a24dd067\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.cpp\n> @@ -0,0 +1,648 @@\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/base/utils.h\"\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, const GlobalConfiguration &configuration)\n> +\t: Debayer(configuration), stats_(std::move(stats))\n> +{\n> +\teglImageBayerIn_ = eglImageBayerOut_= 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 (eglImageBayerOut_)\n> +\t\tdelete eglImageBayerOut_;\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, Error)\n> +\t\t<< \"Unsupported input format \" << inputFormat.toString();\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.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> +\tccmUniformDataIn_ = glGetUniformLocation(programId_, \"ccm\");\n> +\tawbUniformDataIn_ = glGetUniformLocation(programId_, \"awb\");\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    << \" ccm \" << ccmUniformDataIn_\n> +\t\t\t    << \" awb \" << awbUniformDataIn_\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> +\tglFormat_ = GL_LUMINANCE;\n> +\tbytesPerPixel_ = 1;\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_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}\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}\n> +\t\tbreak;\n> +\tdefault:\n> +\t\tgoto invalid_fmt;\n> +\t\tbreak;\n> +\t};\n> +\n> +\tif (ccmEnabled_) {\n> +\t\t// Run the CCM if available\n> +\t\tegl_.pushEnv(shaderEnv, \"#define APPLY_CCM_PARAMETERS\");\n> +\t} else {\n> +\t\t// Flag to shaders that we have parameter gain tables\n> +\t\tegl_.pushEnv(shaderEnv, \"#define APPLY_RGB_PARAMETERS\");\n> +\t}\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> +\tGLint maxTextureImageUnits;\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;\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> +\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> +\tif (!ccmEnabled && 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> +\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> +\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> +\t// Raw bayer input as texture\n> +\teglImageBayerIn_ = new eGLImage(width_, height_, 32, GL_TEXTURE0, 0);\n\nAs mentioned in the call, I think want to pass in the actual stride here \ninstead of assuming a multiple of 256, as we don't control what we get \nfrom v4l2, i.e.:\n\neglImageBayerIn_ = new eGLImage(width_, height_, 32, inputCfg.stride, GL_TEXTURE0, 0);\n\nIIRC the value isn't yet later on, so this shouldn't change behavior for \nnow.\n\n> +\tif (!eglImageBayerIn_)\n> +\t\treturn -ENOMEM;\n> +\n> +\t// Only do the RGB lookup table textures if CCM is disabled\n> +\tif (!ccmEnabled_) {\n> +\n> +\t\t/// RGB correction tables as 2d textures\n> +\t\t// eGL doesn't support glTexImage1D so we do a little hack with 2D to compensate\n> +\t\teglImageRedLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE1, 1);\n> +\t\tif (!eglImageRedLookup_)\n> +\t\t\treturn -ENOMEM;\n> +\n> +\t\teglImageGreenLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE2, 2);\n> +\t\tif (!eglImageGreenLookup_)\n> +\t\t\treturn -ENOMEM;\n> +\n> +\t\teglImageBlueLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE3, 3);\n> +\t\tif (!eglImageBlueLookup_)\n> +\t\t\treturn -ENOMEM;\n> +\t}\n> +\n> +\teglImageBayerOut_ = new eGLImage(outputCfg.size.width, outputCfg.size.height, 32, outputCfg.stride, GL_TEXTURE4, 4);\n> +\tif (!eglImageBayerOut_)\n> +\t\treturn -ENOMEM;\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/* 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 = 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> +\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> +\n> +\tif (!ccmEnabled_) {\n> +\t\tglUniform1i(textureUniformRedLookupDataIn_, eglImageRedLookup_->texture_unit_uniform_id_);\n> +\t\tglUniform1i(textureUniformGreenLookupDataIn_, eglImageGreenLookup_->texture_unit_uniform_id_);\n> +\t\tglUniform1i(textureUniformBlueLookupDataIn_, eglImageBlueLookup_->texture_unit_uniform_id_);\n> +\n> +\t\tegl_.createTexture2D(eglImageRedLookup_, GL_LUMINANCE, DebayerParams::kRGBLookupSize, 1, &params.red);\n> +\t\tegl_.createTexture2D(eglImageGreenLookup_, GL_LUMINANCE, DebayerParams::kRGBLookupSize, 1, &params.green);\n> +\t\tegl_.createTexture2D(eglImageBlueLookup_, GL_LUMINANCE, DebayerParams::kRGBLookupSize, 1, &params.blue);\n> +\n> +\t\tLOG (Debayer, Debug) << \"textureUniformRedLookupDataIn_ \" << textureUniformRedLookupDataIn_\n> +\t\t\t\t     << \" textureUniformGreenLookupDataIn_ \" << textureUniformGreenLookupDataIn_\n> +\t\t\t\t     << \" textureUniformBlueLookupDataIn_ \" << textureUniformBlueLookupDataIn_;\n> +\t} else {\n> +\t\tGLfloat ccm[9] = {\n> +\t\t\tparams.ccm[0][0], params.ccm[0][1], params.ccm[0][2],\n> +\t\t\tparams.ccm[1][0], params.ccm[1][1], params.ccm[1][2],\n> +\t\t\tparams.ccm[2][0], params.ccm[2][1], params.ccm[2][2],\n> +\t\t};\n> +\t\tglUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm);\n> +\t\tLOG (Debayer, Debug) << \" ccmUniformDataIn_ \" << ccmUniformDataIn_ << \" data \" << params.ccm;\n> +\t}\n> +\n> +\t/* Apply the IPA calculated AWB */\n> +\tglUniform3f(awbUniformDataIn_, params.gains[0], params.gains[1], params.gains[2]);\n> +\tLOG (Debayer, Debug) << \" awbUniformDataIn_ \" << awbUniformDataIn_ << \" data \" << params.gains;\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/* Greate 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> +\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> +\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\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 = out.planes()[0].size();\n> +\n> +\t/* Calculate stats for the whole frame */\n> +\tdmaSyncBegin(dmaSyncers, input, nullptr);\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> +void DebayerEGL::stop()\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.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..500dea27\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.h\n> @@ -0,0 +1,174 @@\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, const GlobalConfiguration &configuration);\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> +\tvoid stop();\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(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_;\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> +\teGLImage *eglImageBayerOut_;\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> +\n> +\t// These textures will either point to simple RGB gains or to CCM lookup tables\n> +\tGLint textureUniformRedLookupDataIn_;\n> +\tGLint textureUniformGreenLookupDataIn_;\n> +\tGLint textureUniformBlueLookupDataIn_;\n> +\n> +\t// Represent per-frame CCM as a uniform vector of floats 3 x 3\n> +\tGLint ccmUniformDataIn_;\n> +\tbool ccmEnabled_;\n> +\n> +\t// Auto white Balance\n> +\tGLint awbUniformDataIn_;\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> +\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> +\tGLfloat tcoordinates[DEBAYER_OPENGL_COORDS][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> +\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 A9B05BF415\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 15 Oct 2025 10:34:52 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id C7A8260624;\n\tWed, 15 Oct 2025 12:34:51 +0200 (CEST)","from sender4-op-o12.zoho.com (sender4-op-o12.zoho.com\n\t[136.143.188.12])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 58B1060462\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 15 Oct 2025 12:34:49 +0200 (CEST)","by mx.zohomail.com with SMTPS id 1760524482486838.009530303409;\n\tWed, 15 Oct 2025 03:34:42 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=collabora.com\n\theader.i=robert.mader@collabora.com header.b=\"ATTkosKP\"; \n\tdkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1760524485; cv=none; \n\td=zohomail.com; s=zohoarc; \n\tb=WVYyhYiliviTID/tHrtJvZKELdsaa9mloNapBrBxPpvo22nEkJmOdJcgHKXbM0Gtu6RhvtV7vdXW6WWvhm5CoVPw4Biz+7bQhH38mYr+2doZOTl+xUyB4TF0GSH8oMwA7od7n898OsxV4X8zP11vDVSHl8fCbF0d/VnfuNFbAvU=","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; \n\ts=zohoarc; t=1760524485;\n\th=Content-Type:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To:Cc;\n\tbh=xS/8im8xAU4kV5skIrLLNj7CerC+XFIsvNt6WFFmKm8=; \n\tb=bHWn55rwGSeayS0MRiEpMFuV8jL2djRpFZ/oSoOr7+1kxiMx+jfg1TgDL9y2JroHlymdH3Z21EDdczO037iL9B8Te3y80/OoMjGnTAcPELAb1ImHbk08qJwGBCHHhy/Wrh5wCm8Ik5Jf/57UbTi93d1QmjgzUbV8y8OczAWp8WE=","ARC-Authentication-Results":"i=1; mx.zohomail.com;\n\tdkim=pass  header.i=collabora.com;\n\tspf=pass  smtp.mailfrom=robert.mader@collabora.com;\n\tdmarc=pass header.from=<robert.mader@collabora.com>","DKIM-Signature":"v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1760524485;\n\ts=zohomail; d=collabora.com; i=robert.mader@collabora.com;\n\th=Content-Type:Message-ID:Date:Date:MIME-Version:Subject:Subject:To:To:References:From:From:In-Reply-To:Message-Id:Reply-To:Cc;\n\tbh=xS/8im8xAU4kV5skIrLLNj7CerC+XFIsvNt6WFFmKm8=;\n\tb=ATTkosKPQ//r22rlNmOkEZC0S0+uh3y80l8SwFLLPQpey9QAJ5QlVRsQgTttPxcl\n\tMBZPnCohZHnrispO9DlGTiV07mWhEz0GVu8F0Ht2JMp+e/4O9swRF4zuh6G8V41EXpL\n\tgD8Ye/jrvqJZnPBuk5LcfpPIwLIH5sjjcFHyEbgI=","Content-Type":"multipart/alternative;\n\tboundary=\"------------2e5vOZNsmAIGRnIv2lDJtZjG\"","Message-ID":"<bdd2c514-c27d-497a-b152-26ac063ca97b@collabora.com>","Date":"Wed, 15 Oct 2025 12:34:39 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","To":"libcamera-devel@lists.libcamera.org","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-34-bryan.odonoghue@linaro.org>","Content-Language":"en-US, de-DE","From":"Robert Mader <robert.mader@collabora.com>","In-Reply-To":"<20251015012251.17508-34-bryan.odonoghue@linaro.org>","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":36257,"web_url":"https://patchwork.libcamera.org/comment/36257/","msgid":"<b1acfff2-37a8-4528-a520-16faad26c2ca@nxsw.ie>","date":"2025-10-15T13:52:05","subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","submitter":{"id":226,"url":"https://patchwork.libcamera.org/api/people/226/","name":"Bryan O'Donoghue","email":"bod.linux@nxsw.ie"},"content":"On 15/10/2025 11:34, Robert Mader wrote:\n> As mentioned in the call, I think want to pass in the actual stride here \n> instead of assuming a multiple of 256, as we don't control what we get \n> from v4l2, i.e.:\n> \n> eglImageBayerIn_ = new eGLImage(width_, height_, 32, inputCfg.stride, GL_TEXTURE0, 0);\n> \n> IIRC the value isn't yet later on, so this shouldn't change behavior for \n> now.\n> \n\nAh, you want to add the stride.\n\nSure, np.\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 53E0FBF415\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 15 Oct 2025 13:52:12 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D879E6061C;\n\tWed, 15 Oct 2025 15:52:10 +0200 (CEST)","from tor.source.kernel.org (tor.source.kernel.org [172.105.4.254])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9842F60462\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 15 Oct 2025 15:52:09 +0200 (CEST)","from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58])\n\tby tor.source.kernel.org (Postfix) with ESMTP id 12073625DF;\n\tWed, 15 Oct 2025 13:52:08 +0000 (UTC)","by smtp.kernel.org (Postfix) with ESMTPSA id 38577C4CEF8;\n\tWed, 15 Oct 2025 13:52:07 +0000 (UTC)"],"Message-ID":"<b1acfff2-37a8-4528-a520-16faad26c2ca@nxsw.ie>","Date":"Wed, 15 Oct 2025 14:52:05 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","To":"Robert Mader <robert.mader@collabora.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-34-bryan.odonoghue@linaro.org>\n\t<aCo1-f-ErZwFoY0bSU1crkth-2E_eMxNCOmt3w-t6b9c1shpo1aY6EWq1rz_NaP2BHRWDJCg5ds-zGRA0wACxA==@protonmail.internalid>\n\t<bdd2c514-c27d-497a-b152-26ac063ca97b@collabora.com>","From":"Bryan O'Donoghue <bod.linux@nxsw.ie>","Content-Language":"en-US","In-Reply-To":"<bdd2c514-c27d-497a-b152-26ac063ca97b@collabora.com>","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":36284,"web_url":"https://patchwork.libcamera.org/comment/36284/","msgid":"<176057088992.635357.14409638360975425453@ping.linuxembedded.co.uk>","date":"2025-10-15T23:28:09","subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Bryan O'Donoghue (2025-10-15 02:22:45)\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\nLooks like a squash happened here, so this might need a bit of cleanup.\n\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/software_isp/debayer_egl.cpp | 648 +++++++++++++++++++++\n>  src/libcamera/software_isp/debayer_egl.h   | 174 ++++++\n>  src/libcamera/software_isp/meson.build     |   8 +\n>  3 files changed, 830 insertions(+)\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/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp\n> new file mode 100644\n> index 00000000..a24dd067\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.cpp\n> @@ -0,0 +1,648 @@\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/base/utils.h\"\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, const GlobalConfiguration &configuration)\n> +       : Debayer(configuration), stats_(std::move(stats))\n> +{\n> +       eglImageBayerIn_ = eglImageBayerOut_= eglImageRedLookup_ = eglImageBlueLookup_ = eglImageGreenLookup_ = NULL;\n> +}\n> +\n> +DebayerEGL::~DebayerEGL()\n> +{\n> +       if (eglImageBlueLookup_)\n> +               delete eglImageBlueLookup_;\n> +\n> +       if (eglImageGreenLookup_)\n> +               delete eglImageGreenLookup_;\n> +\n> +       if (eglImageRedLookup_)\n> +               delete eglImageRedLookup_;\n> +\n> +       if (eglImageBayerOut_)\n> +               delete eglImageBayerOut_;\n> +\n> +       if (eglImageBayerIn_)\n> +               delete eglImageBayerIn_;\n\nPresumably there are some smart pointers that could handle memory to\nimprove here - but that's for 'on top'.\n\n\n> +}\n> +\n> +int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config)\n> +{\n> +       BayerFormat bayerFormat =\n> +               BayerFormat::fromPixelFormat(inputFormat);\n> +\n> +       if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10) &&\n> +           bayerFormat.packing == BayerFormat::Packing::None &&\n> +           isStandardBayerOrder(bayerFormat.order)) {\n> +               config.bpp = (bayerFormat.bitDepth + 7) & ~7;\n> +               config.patternSize.width = 2;\n> +               config.patternSize.height = 2;\n> +               config.outputFormats = std::vector<PixelFormat>({ formats::XRGB8888,\n> +                                                                 formats::ARGB8888,\n> +                                                                 formats::XBGR8888,\n> +                                                                 formats::ABGR8888 });\n> +               return 0;\n> +       }\n> +\n> +       if (bayerFormat.bitDepth == 10 &&\n> +           bayerFormat.packing == BayerFormat::Packing::CSI2 &&\n> +           isStandardBayerOrder(bayerFormat.order)) {\n> +               config.bpp = 10;\n> +               config.patternSize.width = 4; /* 5 bytes per *4* pixels */\n> +               config.patternSize.height = 2;\n> +               config.outputFormats = std::vector<PixelFormat>({ formats::XRGB8888,\n> +                                                                 formats::ARGB8888,\n> +                                                                 formats::XBGR8888,\n> +                                                                 formats::ABGR8888 });\n> +               return 0;\n> +       }\n> +\n> +       LOG(Debayer, Error)\n> +               << \"Unsupported input format \" << inputFormat.toString();\n> +\n> +       return -EINVAL;\n> +}\n> +\n> +int DebayerEGL::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config)\n> +{\n> +       if (outputFormat == formats::XRGB8888 || outputFormat == formats::ARGB8888 ||\n> +           outputFormat == formats::XBGR8888 || outputFormat == formats::ABGR8888) {\n> +               config.bpp = 32;\n> +               return 0;\n> +       }\n> +\n> +       LOG(Debayer, Error)\n> +               << \"Unsupported output format \" << outputFormat.toString();\n> +\n> +       return -EINVAL;\n> +}\n> +\n> +int DebayerEGL::getShaderVariableLocations(void)\n> +{\n> +       attributeVertex_ = glGetAttribLocation(programId_, \"vertexIn\");\n> +       attributeTexture_ = glGetAttribLocation(programId_, \"textureIn\");\n> +\n> +       textureUniformBayerDataIn_ = glGetUniformLocation(programId_, \"tex_y\");\n> +       textureUniformRedLookupDataIn_ = glGetUniformLocation(programId_, \"red_param\");\n> +       textureUniformGreenLookupDataIn_ = glGetUniformLocation(programId_, \"green_param\");\n> +       textureUniformBlueLookupDataIn_ = glGetUniformLocation(programId_, \"blue_param\");\n> +       ccmUniformDataIn_ = glGetUniformLocation(programId_, \"ccm\");\n> +       awbUniformDataIn_ = glGetUniformLocation(programId_, \"awb\");\n> +\n> +       textureUniformStep_ = glGetUniformLocation(programId_, \"tex_step\");\n> +       textureUniformSize_ = glGetUniformLocation(programId_, \"tex_size\");\n> +       textureUniformStrideFactor_ = glGetUniformLocation(programId_, \"stride_factor\");\n> +       textureUniformBayerFirstRed_ = glGetUniformLocation(programId_, \"tex_bayer_first_red\");\n> +       textureUniformProjMatrix_ = glGetUniformLocation(programId_, \"proj_matrix\");\n> +\n> +       LOG(Debayer, Debug) << \"vertexIn \" << attributeVertex_ << \" textureIn \" << attributeTexture_\n> +                           << \" tex_y \" << textureUniformBayerDataIn_\n> +                           << \" red_param \" << textureUniformRedLookupDataIn_\n> +                           << \" green_param \" << textureUniformGreenLookupDataIn_\n> +                           << \" blue_param \" << textureUniformBlueLookupDataIn_\n> +                           << \" ccm \" << ccmUniformDataIn_\n> +                           << \" awb \" << awbUniformDataIn_\n> +                           << \" tex_step \" << textureUniformStep_\n> +                           << \" tex_size \" << textureUniformSize_\n> +                           << \" stride_factor \" << textureUniformStrideFactor_\n> +                           << \" tex_bayer_first_red \" << textureUniformBayerFirstRed_\n> +                           << \" proj_matrix \" << textureUniformProjMatrix_;\n> +       return 0;\n> +}\n> +\n> +int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputFormat)\n> +{\n> +       std::vector<std::string> shaderEnv;\n> +       unsigned int fragmentShaderDataLen;\n> +       unsigned char *fragmentShaderData;\n> +       unsigned int vertexShaderDataLen;\n> +       unsigned char *vertexShaderData;\n> +       GLenum err;\n> +\n> +       // Target gles 100 glsl requires \"#version x\" as first directive in shader\n> +       egl_.pushEnv(shaderEnv, \"#version 100\");\n> +\n> +       // Specify GL_OES_EGL_image_external\n> +       egl_.pushEnv(shaderEnv, \"#extension GL_OES_EGL_image_external: enable\");\n> +\n> +       // Tell shaders how to re-order output taking account of how the\n> +       // pixels are actually stored by GBM\n> +       switch (outputFormat) {\n> +       case formats::ARGB8888:\n> +       case formats::XRGB8888:\n> +               break;\n> +       case formats::ABGR8888:\n> +       case formats::XBGR8888:\n> +               egl_.pushEnv(shaderEnv, \"#define SWAP_BLUE\");\n> +               break;\n> +       default:\n> +               goto invalid_fmt;\n> +       }\n> +\n> +       // Pixel location parameters\n> +       glFormat_ = GL_LUMINANCE;\n> +       bytesPerPixel_ = 1;\n> +       switch (inputFormat) {\n> +       case libcamera::formats::SBGGR8:\n> +       case libcamera::formats::SBGGR10_CSI2P:\n> +       case libcamera::formats::SBGGR12_CSI2P:\n> +               firstRed_x_ = 1.0;\n> +               firstRed_y_ = 1.0;\n> +               break;\n> +       case libcamera::formats::SGBRG8:\n> +       case libcamera::formats::SGBRG10_CSI2P:\n> +       case libcamera::formats::SGBRG12_CSI2P:\n> +               firstRed_x_ = 0.0;\n> +               firstRed_y_ = 1.0;\n> +               break;\n> +       case libcamera::formats::SGRBG8:\n> +       case libcamera::formats::SGRBG10_CSI2P:\n> +       case libcamera::formats::SGRBG12_CSI2P:\n> +               firstRed_x_ = 1.0;\n> +               firstRed_y_ = 0.0;\n> +               break;\n> +       case libcamera::formats::SRGGB8:\n> +       case libcamera::formats::SRGGB10_CSI2P:\n> +       case libcamera::formats::SRGGB12_CSI2P:\n> +               firstRed_x_ = 0.0;\n> +               firstRed_y_ = 0.0;\n> +               break;\n> +       default:\n> +               goto invalid_fmt;\n> +               break;\n> +       };\n> +\n> +       // Shader selection\n> +       switch (inputFormat) {\n> +       case libcamera::formats::SBGGR8:\n> +       case libcamera::formats::SGBRG8:\n> +       case libcamera::formats::SGRBG8:\n> +       case libcamera::formats::SRGGB8:\n> +               fragmentShaderData = bayer_unpacked_frag;\n> +               fragmentShaderDataLen = bayer_unpacked_frag_len;\n> +               vertexShaderData = bayer_unpacked_vert;\n> +               vertexShaderDataLen = bayer_unpacked_vert_len;\n> +               break;\n> +       case libcamera::formats::SBGGR10_CSI2P:\n> +       case libcamera::formats::SGBRG10_CSI2P:\n> +       case libcamera::formats::SGRBG10_CSI2P:\n> +       case libcamera::formats::SRGGB10_CSI2P:\n> +               egl_.pushEnv(shaderEnv, \"#define RAW10P\");\n> +               if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {\n> +                       fragmentShaderData = bayer_unpacked_frag;\n> +                       fragmentShaderDataLen = bayer_unpacked_frag_len;\n> +                       vertexShaderData = bayer_unpacked_vert;\n> +                       vertexShaderDataLen = bayer_unpacked_vert_len;\n> +                       glFormat_ = GL_RG;\n> +                       bytesPerPixel_ = 2;\n> +               } else {\n> +                       fragmentShaderData = bayer_1x_packed_frag;\n> +                       fragmentShaderDataLen = bayer_1x_packed_frag_len;\n> +                       vertexShaderData = identity_vert;\n> +                       vertexShaderDataLen = identity_vert_len;\n> +               }\n> +               break;\n> +       case libcamera::formats::SBGGR12_CSI2P:\n> +       case libcamera::formats::SGBRG12_CSI2P:\n> +       case libcamera::formats::SGRBG12_CSI2P:\n> +       case libcamera::formats::SRGGB12_CSI2P:\n> +               egl_.pushEnv(shaderEnv, \"#define RAW12P\");\n> +               if (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {\n> +                       fragmentShaderData = bayer_unpacked_frag;\n> +                       fragmentShaderDataLen = bayer_unpacked_frag_len;\n> +                       vertexShaderData = bayer_unpacked_vert;\n> +                       vertexShaderDataLen = bayer_unpacked_vert_len;\n> +                       glFormat_ = GL_RG;\n> +                       bytesPerPixel_ = 2;\n> +               } else {\n> +                       fragmentShaderData = bayer_1x_packed_frag;\n> +                       fragmentShaderDataLen = bayer_1x_packed_frag_len;\n> +                       vertexShaderData = identity_vert;\n> +                       vertexShaderDataLen = identity_vert_len;\n> +               }\n> +               break;\n> +       default:\n> +               goto invalid_fmt;\n> +               break;\n> +       };\n> +\n> +       if (ccmEnabled_) {\n> +               // Run the CCM if available\n> +               egl_.pushEnv(shaderEnv, \"#define APPLY_CCM_PARAMETERS\");\n> +       } else {\n> +               // Flag to shaders that we have parameter gain tables\n> +               egl_.pushEnv(shaderEnv, \"#define APPLY_RGB_PARAMETERS\");\n> +       }\n> +\n> +       if (egl_.compileVertexShader(vertexShaderId_, vertexShaderData, vertexShaderDataLen, shaderEnv))\n> +               goto compile_fail;\n> +\n> +       if (egl_.compileFragmentShader(fragmentShaderId_, fragmentShaderData, fragmentShaderDataLen, shaderEnv))\n> +               goto compile_fail;\n> +\n> +       if (egl_.linkProgram(programId_, vertexShaderId_, fragmentShaderId_))\n> +               goto link_fail;\n> +\n> +       egl_.dumpShaderSource(vertexShaderId_);\n> +       egl_.dumpShaderSource(fragmentShaderId_);\n> +\n> +       /* Ensure we set the programId_ */\n> +       egl_.useProgram(programId_);\n> +       err = glGetError();\n> +       if (err != GL_NO_ERROR)\n> +               goto program_fail;\n> +\n> +       if (getShaderVariableLocations())\n> +               goto parameters_fail;\n> +\n> +       return 0;\n> +\n> +parameters_fail:\n> +       LOG(Debayer, Error) << \"Program parameters fail\";\n> +       return -ENODEV;\n> +\n> +program_fail:\n> +       LOG(Debayer, Error) << \"Use program error \" << err;\n> +       return -ENODEV;\n> +\n> +link_fail:\n> +       LOG(Debayer, Error) << \"Linking program fail\";\n> +       return -ENODEV;\n> +\n> +compile_fail:\n> +       LOG(Debayer, Error) << \"Compile debayer shaders fail\";\n> +       return -ENODEV;\n> +\n> +invalid_fmt:\n> +       LOG(Debayer, Error) << \"Unsupported input output format combination\";\n> +       return -EINVAL;\n\nI don't understand why there's so much goto use here. I'd put these\ndirectly in the conditional after the statements.\n\nCould be a cleanup on top though.\n\nI don't know what else I can review here - and I think getting this in\nfor staging and wide testing is valuable so as long as CI is green:\n\nAcked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n\n> +}\n> +\n> +int DebayerEGL::configure(const StreamConfiguration &inputCfg,\n> +                         const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,\n> +                         bool ccmEnabled)\n> +{\n> +       GLint maxTextureImageUnits;\n> +\n> +       if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0)\n> +               return -EINVAL;\n> +\n> +       if (stats_->configure(inputCfg) != 0)\n> +               return -EINVAL;\n> +\n> +       const Size &stats_pattern_size = stats_->patternSize();\n> +       if (inputConfig_.patternSize.width != stats_pattern_size.width ||\n> +           inputConfig_.patternSize.height != stats_pattern_size.height) {\n> +               LOG(Debayer, Error)\n> +                       << \"mismatching stats and debayer pattern sizes for \"\n> +                       << inputCfg.pixelFormat.toString();\n> +               return -EINVAL;\n> +       }\n> +\n> +       inputConfig_.stride = inputCfg.stride;\n> +       width_ = inputCfg.size.width;\n> +       height_ = inputCfg.size.height;\n> +       ccmEnabled_ = ccmEnabled;\n> +\n> +       if (outputCfgs.size() != 1) {\n> +               LOG(Debayer, Error)\n> +                       << \"Unsupported number of output streams: \"\n> +                       << outputCfgs.size();\n> +               return -EINVAL;\n> +       }\n> +\n> +       if (gbmSurface_.createDevice())\n> +               return -ENODEV;\n> +\n> +       if (egl_.initEGLContext(&gbmSurface_))\n> +               return -ENODEV;\n> +\n> +       glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);\n> +\n> +       LOG(Debayer, Debug) << \"Available fragment shader texture units \" << maxTextureImageUnits;\n> +\n> +       if (!ccmEnabled && maxTextureImageUnits < DEBAYER_EGL_MIN_SIMPLE_RGB_GAIN_TEXTURE_UNITS) {\n> +               LOG(Debayer, Error) << \"Fragment shader texture unit count \" << maxTextureImageUnits\n> +                                   << \" required minimum for RGB gain table lookup \" << DEBAYER_EGL_MIN_SIMPLE_RGB_GAIN_TEXTURE_UNITS\n> +                                   << \" try using an identity CCM \";\n> +               return -ENODEV;\n> +       }\n> +\n> +       StreamConfiguration &outputCfg = outputCfgs[0];\n> +       SizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size);\n> +       std::tie(outputConfig_.stride, outputConfig_.frameSize) =\n> +               strideAndFrameSize(outputCfg.pixelFormat, outputCfg.size);\n> +\n> +       if (!outSizeRange.contains(outputCfg.size) || outputConfig_.stride != outputCfg.stride) {\n> +               LOG(Debayer, Error)\n> +                       << \"Invalid output size/stride: \"\n> +                       << \"\\n  \" << outputCfg.size << \" (\" << outSizeRange << \")\"\n> +                       << \"\\n  \" << outputCfg.stride << \" (\" << outputConfig_.stride << \")\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) &\n> +                   ~(inputConfig_.patternSize.width - 1);\n> +       window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) &\n> +                   ~(inputConfig_.patternSize.height - 1);\n> +       window_.width = outputCfg.size.width;\n> +       window_.height = outputCfg.size.height;\n> +\n> +       /*\n> +        * Don't pass x,y from window_ since process() already adjusts for it.\n> +        * But crop the window to 2/3 of its width and height for speedup.\n> +        */\n> +       stats_->setWindow(Rectangle(window_.size()));\n> +\n> +       // Raw bayer input as texture\n> +       eglImageBayerIn_ = new eGLImage(width_, height_, 32, GL_TEXTURE0, 0);\n> +       if (!eglImageBayerIn_)\n> +               return -ENOMEM;\n> +\n> +       // Only do the RGB lookup table textures if CCM is disabled\n> +       if (!ccmEnabled_) {\n> +\n> +               /// RGB correction tables as 2d textures\n> +               // eGL doesn't support glTexImage1D so we do a little hack with 2D to compensate\n> +               eglImageRedLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE1, 1);\n> +               if (!eglImageRedLookup_)\n> +                       return -ENOMEM;\n> +\n> +               eglImageGreenLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE2, 2);\n> +               if (!eglImageGreenLookup_)\n> +                       return -ENOMEM;\n> +\n> +               eglImageBlueLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE3, 3);\n> +               if (!eglImageBlueLookup_)\n> +                       return -ENOMEM;\n> +       }\n> +\n> +       eglImageBayerOut_ = new eGLImage(outputCfg.size.width, outputCfg.size.height, 32, outputCfg.stride, GL_TEXTURE4, 4);\n> +       if (!eglImageBayerOut_)\n> +               return -ENOMEM;\n> +\n> +       if (initBayerShaders(inputCfg.pixelFormat, outputCfg.pixelFormat))\n> +               return -EINVAL;\n> +\n> +       return 0;\n> +}\n> +\n> +Size DebayerEGL::patternSize(PixelFormat inputFormat)\n> +{\n> +       DebayerEGL::DebayerInputConfig config;\n> +\n> +       if (getInputConfig(inputFormat, config) != 0)\n> +               return {};\n> +\n> +       return config.patternSize;\n> +}\n> +\n> +std::vector<PixelFormat> DebayerEGL::formats(PixelFormat inputFormat)\n> +{\n> +       DebayerEGL::DebayerInputConfig config;\n> +\n> +       if (getInputConfig(inputFormat, config) != 0)\n> +               return std::vector<PixelFormat>();\n> +\n> +       return config.outputFormats;\n> +}\n> +\n> +std::tuple<unsigned int, unsigned int>\n> +DebayerEGL::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)\n> +{\n> +       DebayerEGL::DebayerOutputConfig config;\n> +\n> +       if (getOutputConfig(outputFormat, config) != 0)\n> +               return std::make_tuple(0, 0);\n> +\n> +       /* Align stride to 256 bytes as a generic GPU memory access alignment */\n> +       unsigned int stride = libcamera::utils::alignUp(size.width * config.bpp / 8, 256);\n> +\n> +       return std::make_tuple(stride, stride * size.height);\n> +}\n> +\n> +void DebayerEGL::setShaderVariableValues(DebayerParams &params)\n> +{\n> +       /*\n> +        * Raw Bayer 8-bit, and packed raw Bayer 10-bit/12-bit formats\n> +        * are stored in a GL_LUMINANCE texture. The texture width is\n> +        * equal to the stride.\n> +        */\n> +       GLfloat firstRed[] = { firstRed_x_, firstRed_y_ };\n> +       GLfloat imgSize[] = { (GLfloat)width_,\n> +                             (GLfloat)height_ };\n> +       GLfloat Step[] = { static_cast<float>(bytesPerPixel_) / (inputConfig_.stride - 1),\n> +                          1.0f / (height_ - 1) };\n> +       GLfloat Stride = 1.0f;\n> +       GLfloat projIdentityMatrix[] = {\n> +               1, 0, 0, 0,\n> +               0, 1, 0, 0,\n> +               0, 0, 1, 0,\n> +               0, 0, 0, 1\n> +       };\n> +\n> +       // vertexIn - bayer_8.vert\n> +       glEnableVertexAttribArray(attributeVertex_);\n> +       glVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE,\n> +                             2 * sizeof(GLfloat), vcoordinates);\n> +\n> +       // textureIn - bayer_8.vert\n> +       glEnableVertexAttribArray(attributeTexture_);\n> +       glVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE,\n> +                             2 * sizeof(GLfloat), tcoordinates);\n> +\n> +       // Set the sampler2D to the respective texture unit for each texutre\n> +       // To simultaneously sample multiple textures we need to use multiple\n> +       // texture units\n> +       glUniform1i(textureUniformBayerDataIn_, eglImageBayerIn_->texture_unit_uniform_id_);\n> +\n> +       // These values are:\n> +       // firstRed = tex_bayer_first_red - bayer_8.vert\n> +       // imgSize = tex_size - bayer_8.vert\n> +       // step = tex_step - bayer_8.vert\n> +       // Stride = stride_factor identity.vert\n> +       // textureUniformProjMatri = No scaling\n> +       glUniform2fv(textureUniformBayerFirstRed_, 1, firstRed);\n> +       glUniform2fv(textureUniformSize_, 1, imgSize);\n> +       glUniform2fv(textureUniformStep_, 1, Step);\n> +       glUniform1f(textureUniformStrideFactor_, Stride);\n> +       glUniformMatrix4fv(textureUniformProjMatrix_, 1,\n> +                          GL_FALSE, projIdentityMatrix);\n> +\n> +       LOG(Debayer, Debug) << \"vertexIn \" << attributeVertex_ << \" textureIn \" << attributeTexture_\n> +                           << \" tex_y \" << textureUniformBayerDataIn_\n> +                           << \" red_param \" << textureUniformRedLookupDataIn_\n> +                           << \" green_param \" << textureUniformGreenLookupDataIn_\n> +                           << \" blue_param \" << textureUniformBlueLookupDataIn_\n> +                           << \" tex_step \" << textureUniformStep_\n> +                           << \" tex_size \" << textureUniformSize_\n> +                           << \" stride_factor \" << textureUniformStrideFactor_\n> +                           << \" tex_bayer_first_red \" << textureUniformBayerFirstRed_;\n> +\n> +       LOG (Debayer, Debug) << \"textureUniformY_ = 0 \"\n> +                            << \" firstRed.x \" << firstRed[0]\n> +                            << \" firstRed.y \" << firstRed[1]\n> +                            << \" textureUniformSize_.width \" << imgSize[0]\n> +                            << \" textureUniformSize_.height \" << imgSize[1]\n> +                            << \" textureUniformStep_.x \" << Step[0]\n> +                            << \" textureUniformStep_.y \" << Step[1]\n> +                            << \" textureUniformStrideFactor_ \" << Stride\n> +                            << \" textureUniformProjMatrix_ \" << textureUniformProjMatrix_;\n> +\n> +       if (!ccmEnabled_) {\n> +               glUniform1i(textureUniformRedLookupDataIn_, eglImageRedLookup_->texture_unit_uniform_id_);\n> +               glUniform1i(textureUniformGreenLookupDataIn_, eglImageGreenLookup_->texture_unit_uniform_id_);\n> +               glUniform1i(textureUniformBlueLookupDataIn_, eglImageBlueLookup_->texture_unit_uniform_id_);\n> +\n> +               egl_.createTexture2D(eglImageRedLookup_, GL_LUMINANCE, DebayerParams::kRGBLookupSize, 1, &params.red);\n> +               egl_.createTexture2D(eglImageGreenLookup_, GL_LUMINANCE, DebayerParams::kRGBLookupSize, 1, &params.green);\n> +               egl_.createTexture2D(eglImageBlueLookup_, GL_LUMINANCE, DebayerParams::kRGBLookupSize, 1, &params.blue);\n> +\n> +               LOG (Debayer, Debug) << \"textureUniformRedLookupDataIn_ \" << textureUniformRedLookupDataIn_\n> +                                    << \" textureUniformGreenLookupDataIn_ \" << textureUniformGreenLookupDataIn_\n> +                                    << \" textureUniformBlueLookupDataIn_ \" << textureUniformBlueLookupDataIn_;\n> +       } else {\n> +               GLfloat ccm[9] = {\n> +                       params.ccm[0][0], params.ccm[0][1], params.ccm[0][2],\n> +                       params.ccm[1][0], params.ccm[1][1], params.ccm[1][2],\n> +                       params.ccm[2][0], params.ccm[2][1], params.ccm[2][2],\n> +               };\n> +               glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm);\n> +               LOG (Debayer, Debug) << \" ccmUniformDataIn_ \" << ccmUniformDataIn_ << \" data \" << params.ccm;\n> +       }\n> +\n> +       /* Apply the IPA calculated AWB */\n> +       glUniform3f(awbUniformDataIn_, params.gains[0], params.gains[1], params.gains[2]);\n> +       LOG (Debayer, Debug) << \" awbUniformDataIn_ \" << awbUniformDataIn_ << \" data \" << params.gains;\n> +\n> +       return;\n> +}\n> +\n> +int DebayerEGL::debayerGPU(MappedFrameBuffer &in, int out_fd, DebayerParams &params)\n> +{\n> +       /* eGL context switch */\n> +       egl_.makeCurrent();\n> +\n> +       /* Greate a standard texture input */\n> +       egl_.createTexture2D(eglImageBayerIn_, glFormat_, inputConfig_.stride / bytesPerPixel_, height_, in.planes()[0].data());\n> +\n> +       /* Generate the output render framebuffer as render to texture */\n> +       egl_.createOutputDMABufTexture2D(eglImageBayerOut_, out_fd);\n> +\n> +\n> +\n> +       setShaderVariableValues(params);\n> +       glViewport(0, 0, width_, height_);\n> +       glClear(GL_COLOR_BUFFER_BIT);\n> +       glDrawArrays(GL_TRIANGLE_FAN, 0, DEBAYER_OPENGL_COORDS);\n> +\n> +       GLenum err = glGetError();\n> +       if (err != GL_NO_ERROR) {\n> +               LOG(eGL, Error) << \"Drawing scene fail \" << err;\n> +               return -ENODEV;\n> +       } else {\n> +               egl_.syncOutput();\n> +       }\n> +\n> +       return 0;\n> +}\n> +\n> +void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params)\n> +{\n> +       bench_.startFrame();\n> +\n> +       std::vector<DmaSyncer> dmaSyncers;\n> +\n> +       setParams(params);\n> +\n> +       /* Copy metadata from the input buffer */\n> +       FrameMetadata &metadata = output->_d()->metadata();\n> +       metadata.status = input->metadata().status;\n> +       metadata.sequence = input->metadata().sequence;\n> +       metadata.timestamp = input->metadata().timestamp;\n> +\n> +       MappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read);\n> +       MappedFrameBuffer out(output, MappedFrameBuffer::MapFlag::Write);\n> +       if (!in.isValid() || !out.isValid()) {\n> +               LOG(Debayer, Error) << \"mmap-ing buffer(s) failed\";\n> +               goto error;\n> +       }\n> +\n> +       if (debayerGPU(in, output->planes()[0].fd.get(), params)) {\n> +               LOG(Debayer, Error) << \"debayerGPU failed\";\n> +               goto error;\n> +       }\n> +\n> +       bench_.finishFrame();\n> +\n> +       metadata.planes()[0].bytesused = out.planes()[0].size();\n> +\n> +       /* Calculate stats for the whole frame */\n> +       dmaSyncBegin(dmaSyncers, input, nullptr);\n> +       stats_->processFrame(frame, 0, input);\n> +       dmaSyncers.clear();\n> +\n> +       outputBufferReady.emit(output);\n> +       inputBufferReady.emit(input);\n> +\n> +       return;\n> +\n> +error:\n> +       bench_.finishFrame();\n> +       metadata.status = FrameMetadata::FrameError;\n> +       return;\n> +}\n> +\n> +void DebayerEGL::stop()\n> +{\n> +       egl_.cleanUp();\n> +}\n> +\n> +SizeRange DebayerEGL::sizes(PixelFormat inputFormat, const Size &inputSize)\n> +{\n> +       Size patternSize = this->patternSize(inputFormat);\n> +       unsigned int borderHeight = patternSize.height;\n> +\n> +       if (patternSize.isNull())\n> +               return {};\n> +\n> +       /* No need for top/bottom border with a pattern height of 2 */\n> +       if (patternSize.height == 2)\n> +               borderHeight = 0;\n> +\n> +       /*\n> +        * For debayer interpolation a border is kept around the entire image\n> +        * and the minimum output size is pattern-height x pattern-width.\n> +        */\n> +       if (inputSize.width < (3 * patternSize.width) ||\n> +           inputSize.height < (2 * borderHeight + patternSize.height)) {\n> +               LOG(Debayer, Warning)\n> +                       << \"Input format size too small: \" << inputSize.toString();\n> +               return {};\n> +       }\n> +\n> +       return SizeRange(Size(patternSize.width, patternSize.height),\n> +                        Size((inputSize.width - 2 * patternSize.width) & ~(patternSize.width - 1),\n> +                             (inputSize.height - 2 * borderHeight) & ~(patternSize.height - 1)),\n> +                        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..500dea27\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.h\n> @@ -0,0 +1,174 @@\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> +       /**\n> +        * \\brief Constructs a DebayerEGL object.\n> +        * \\param[in] stats Pointer to the stats object to use.\n> +        */\n> +       DebayerEGL(std::unique_ptr<SwStatsCpu> stats, const GlobalConfiguration &configuration);\n> +       ~DebayerEGL();\n> +\n> +       /*\n> +        * Setup the Debayer object according to the passed in parameters.\n> +        * Return 0 on success, a negative errno value on failure\n> +        * (unsupported parameters).\n> +        */\n> +       int configure(const StreamConfiguration &inputCfg,\n> +                     const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,\n> +                     bool ccmEnabled);\n> +\n> +       /*\n> +        * Get width and height at which the bayer-pattern repeats.\n> +        * Return pattern-size or an empty Size for an unsupported inputFormat.\n> +        */\n> +       Size patternSize(PixelFormat inputFormat);\n> +\n> +       std::vector<PixelFormat> formats(PixelFormat input);\n> +       std::tuple<unsigned int, unsigned int> strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);\n> +\n> +       void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params);\n> +       void stop();\n> +\n> +       /**\n> +        * \\brief Get the file descriptor for the statistics.\n> +        *\n> +        * \\return the file descriptor pointing to the statistics.\n> +        */\n> +       const SharedFD &getStatsFD() { return stats_->getStatsFD(); }\n> +\n> +       /**\n> +        * \\brief Get the output frame size.\n> +        *\n> +        * \\return The output frame size.\n> +        */\n> +       unsigned int frameSize() { return outputConfig_.frameSize; }\n> +\n> +       SizeRange sizes(PixelFormat inputFormat, const Size &inputSize);\n> +\n> +private:\n> +       static int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config);\n> +       static int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config);\n> +       int setupStandardBayerOrder(BayerFormat::Order order);\n> +       void pushEnv(std::vector<std::string> &shaderEnv, const char *str);\n> +       int initBayerShaders(PixelFormat inputFormat, PixelFormat outputFormat);\n> +       int initEGLContext();\n> +       int generateTextures();\n> +       int compileShaderProgram(GLuint &shaderId, GLenum shaderType,\n> +                                unsigned char *shaderData, int shaderDataLen,\n> +                                std::vector<std::string> shaderEnv);\n> +       int linkShaderProgram(void);\n> +       int getShaderVariableLocations();\n> +       void setShaderVariableValues(DebayerParams &params);\n> +       void configureTexture(GLuint &texture);\n> +       int debayerGPU(MappedFrameBuffer &in, int out_fd, DebayerParams &params);\n> +\n> +       // Shader program identifiers\n> +       GLuint vertexShaderId_;\n> +       GLuint fragmentShaderId_;\n> +       GLuint programId_;\n> +       enum {\n> +               BAYER_INPUT_INDEX = 0,\n> +               BAYER_OUTPUT_INDEX,\n> +               BAYER_BUF_NUM,\n> +       };\n> +\n> +       // Pointer to object representing input texture\n> +       eGLImage *eglImageBayerIn_;\n> +       eGLImage *eglImageBayerOut_;\n> +\n> +       eGLImage *eglImageRedLookup_;\n> +       eGLImage *eglImageGreenLookup_;\n> +       eGLImage *eglImageBlueLookup_;\n> +\n> +       // Shader parameters\n> +       float firstRed_x_;\n> +       float firstRed_y_;\n> +       GLint attributeVertex_;\n> +       GLint attributeTexture_;\n> +       GLint textureUniformStep_;\n> +       GLint textureUniformSize_;\n> +       GLint textureUniformStrideFactor_;\n> +       GLint textureUniformBayerFirstRed_;\n> +       GLint textureUniformProjMatrix_;\n> +\n> +       GLint textureUniformBayerDataIn_;\n> +\n> +       // These textures will either point to simple RGB gains or to CCM lookup tables\n> +       GLint textureUniformRedLookupDataIn_;\n> +       GLint textureUniformGreenLookupDataIn_;\n> +       GLint textureUniformBlueLookupDataIn_;\n> +\n> +       // Represent per-frame CCM as a uniform vector of floats 3 x 3\n> +       GLint ccmUniformDataIn_;\n> +       bool ccmEnabled_;\n> +\n> +       // Auto white Balance\n> +       GLint awbUniformDataIn_;\n> +\n> +       Rectangle window_;\n> +       std::unique_ptr<SwStatsCpu> stats_;\n> +       eGL egl_;\n> +       GBM gbmSurface_;\n> +       uint32_t width_;\n> +       uint32_t height_;\n> +       GLint glFormat_;\n> +       unsigned int bytesPerPixel_;\n> +       GLfloat vcoordinates[DEBAYER_OPENGL_COORDS][2] = {\n> +               { -1.0f, -1.0f },\n> +               { -1.0f, +1.0f },\n> +               { +1.0f, +1.0f },\n> +               { +1.0f, -1.0f },\n> +       };\n> +       GLfloat tcoordinates[DEBAYER_OPENGL_COORDS][2] = {\n> +               { 0.0f, 0.0f },\n> +               { 0.0f, 1.0f },\n> +               { 1.0f, 1.0f },\n> +               { 1.0f, 0.0f },\n> +       };\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\n> -- \n> 2.51.0\n>","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 5747CBE080\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 15 Oct 2025 23:28:16 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 9F6E360654;\n\tThu, 16 Oct 2025 01:28:15 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 68AF5605F3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 16 Oct 2025 01:28:13 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C8DFE1306;\n\tThu, 16 Oct 2025 01:26:33 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"uPTiEcxT\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1760570793;\n\tbh=4Gwa7D+bhL7nNFLv3DRFmnbfWLMeLG1X0TODJGz1Nnw=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=uPTiEcxTvZLjRYYCmTn1brPEAsu8Brnw2RMj0aMWQjtl9iEaxtb6zl/GLfZR/MwsM\n\tdoIJkqjVyCSux5H59ZNWo3lbTODC3tnSb2hEwrsJ3qJgsWZSI0jAo1U1N2yWkb8lYS\n\tzvU3SPZpAQj2uyghO6Ul0WOlMQ253eUiG0CiP3gM=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20251015012251.17508-34-bryan.odonoghue@linaro.org>","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-34-bryan.odonoghue@linaro.org>","Subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"hdegoede@redhat.com, mzamazal@redhat.com, bryan.odonoghue@linaro.org,\n\tbod.linux@nxsw.ie","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Thu, 16 Oct 2025 00:28:09 +0100","Message-ID":"<176057088992.635357.14409638360975425453@ping.linuxembedded.co.uk>","User-Agent":"alot/0.9.1","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":36320,"web_url":"https://patchwork.libcamera.org/comment/36320/","msgid":"<85v7kec032.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2025-10-16T15:56:01","subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Hi Bryan,\n\nmy bureaucratic bits:\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/software_isp/debayer_egl.cpp | 648 +++++++++++++++++++++\n>  src/libcamera/software_isp/debayer_egl.h   | 174 ++++++\n>  src/libcamera/software_isp/meson.build     |   8 +\n>  3 files changed, 830 insertions(+)\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/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp\n> new file mode 100644\n> index 00000000..a24dd067\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.cpp\n> @@ -0,0 +1,648 @@\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\nNo file name.\n\n> + */\n> +\n> +#include <math.h>\n> +#include <stdlib.h>\n> +#include <time.h>\n> +\n> +#include <libcamera/formats.h>\n> +\n> +#include \"libcamera/base/utils.h\"\n> +#include \"libcamera/internal/glsl_shaders.h\"\n> +#include \"debayer_egl.h\"\n\nI think the include's should be:\n\n#include \"debayer_egl.h\"\n\n#include <math.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include <libcamera/base/utils.h>\n\n#include <libcamera/formats.h>\n\n#include \"libcamera/internal/glsl_shaders.h\"\n\n> +\n> +namespace libcamera {\n> +\n> +DebayerEGL::DebayerEGL(std::unique_ptr<SwStatsCpu> stats, const GlobalConfiguration &configuration)\n> +\t: Debayer(configuration), stats_(std::move(stats))\n> +{\n> +\teglImageBayerIn_ = eglImageBayerOut_= eglImageRedLookup_ = eglImageBlueLookup_ = eglImageGreenLookup_ = NULL;\n\nMissing space after eglImageBayerOut_.\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 (eglImageBayerOut_)\n> +\t\tdelete eglImageBayerOut_;\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, Error)\n> +\t\t<< \"Unsupported input format \" << inputFormat.toString();\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.toString();\n> +\n> +\treturn -EINVAL;\n> +}\n> +\n> +int DebayerEGL::getShaderVariableLocations(void)\n\nNo `void'.\n\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> +\tccmUniformDataIn_ = glGetUniformLocation(programId_, \"ccm\");\n> +\tawbUniformDataIn_ = glGetUniformLocation(programId_, \"awb\");\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    << \" ccm \" << ccmUniformDataIn_\n> +\t\t\t    << \" awb \" << awbUniformDataIn_\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\n/* ... */\n\n(Also elsewhere.)\n\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> +\tglFormat_ = GL_LUMINANCE;\n> +\tbytesPerPixel_ = 1;\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_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}\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}\n> +\t\tbreak;\n> +\tdefault:\n> +\t\tgoto invalid_fmt;\n> +\t\tbreak;\n> +\t};\n> +\n> +\tif (ccmEnabled_) {\n> +\t\t// Run the CCM if available\n> +\t\tegl_.pushEnv(shaderEnv, \"#define APPLY_CCM_PARAMETERS\");\n> +\t} else {\n> +\t\t// Flag to shaders that we have parameter gain tables\n> +\t\tegl_.pushEnv(shaderEnv, \"#define APPLY_RGB_PARAMETERS\");\n> +\t}\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> +\tGLint maxTextureImageUnits;\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\nWould it be useful to log the values?\n\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;\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\nHow about something like\n\n  \"Only single output stream supported, \" << outputCfgs.size() << \" requested\"\n\n> +\t\treturn -EINVAL;\n> +\t}\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> +\tif (!ccmEnabled && 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> +\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> +\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\nLeftover from testing my window cropping patches?\n\n> +\t */\n> +\tstats_->setWindow(Rectangle(window_.size()));\n> +\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// Only do the RGB lookup table textures if CCM is disabled\n> +\tif (!ccmEnabled_) {\n> +\n\nUnnecessary blank line.\n\n> +\t\t/// RGB correction tables as 2d textures\n> +\t\t// eGL doesn't support glTexImage1D so we do a little hack with 2D to compensate\n> +\t\teglImageRedLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE1, 1);\n> +\t\tif (!eglImageRedLookup_)\n> +\t\t\treturn -ENOMEM;\n> +\n> +\t\teglImageGreenLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE2, 2);\n> +\t\tif (!eglImageGreenLookup_)\n> +\t\t\treturn -ENOMEM;\n> +\n> +\t\teglImageBlueLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE3, 3);\n> +\t\tif (!eglImageBlueLookup_)\n> +\t\t\treturn -ENOMEM;\n> +\t}\n> +\n> +\teglImageBayerOut_ = new eGLImage(outputCfg.size.width, outputCfg.size.height, 32, outputCfg.stride, GL_TEXTURE4, 4);\n> +\tif (!eglImageBayerOut_)\n> +\t\treturn -ENOMEM;\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/* 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 = 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\ns/texutre/texture/\n\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> +\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\nExtra space after LOG.  There are some more occurrences of this below.\n\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\nThere are tabs after all the initial `<<', spaces should be there instead.\n\n> +\n> +\tif (!ccmEnabled_) {\n> +\t\tglUniform1i(textureUniformRedLookupDataIn_, eglImageRedLookup_->texture_unit_uniform_id_);\n> +\t\tglUniform1i(textureUniformGreenLookupDataIn_, eglImageGreenLookup_->texture_unit_uniform_id_);\n> +\t\tglUniform1i(textureUniformBlueLookupDataIn_, eglImageBlueLookup_->texture_unit_uniform_id_);\n> +\n> +\t\tegl_.createTexture2D(eglImageRedLookup_, GL_LUMINANCE, DebayerParams::kRGBLookupSize, 1, &params.red);\n> +\t\tegl_.createTexture2D(eglImageGreenLookup_, GL_LUMINANCE, DebayerParams::kRGBLookupSize, 1, &params.green);\n> +\t\tegl_.createTexture2D(eglImageBlueLookup_, GL_LUMINANCE, DebayerParams::kRGBLookupSize, 1, &params.blue);\n> +\n> +\t\tLOG (Debayer, Debug) << \"textureUniformRedLookupDataIn_ \" << textureUniformRedLookupDataIn_\n> +\t\t\t\t     << \" textureUniformGreenLookupDataIn_ \" << textureUniformGreenLookupDataIn_\n> +\t\t\t\t     << \" textureUniformBlueLookupDataIn_ \" << textureUniformBlueLookupDataIn_;\n> +\t} else {\n> +\t\tGLfloat ccm[9] = {\n> +\t\t\tparams.ccm[0][0], params.ccm[0][1], params.ccm[0][2],\n> +\t\t\tparams.ccm[1][0], params.ccm[1][1], params.ccm[1][2],\n> +\t\t\tparams.ccm[2][0], params.ccm[2][1], params.ccm[2][2],\n\nI think this formatting is clearer but the auto-formatter insists on\nhaving each of the items on its own line.  Not sure how to handle it.\n\n> +\t\t};\n> +\t\tglUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm);\n> +\t\tLOG (Debayer, Debug) << \" ccmUniformDataIn_ \" << ccmUniformDataIn_ << \" data \" << params.ccm;\n> +\t}\n> +\n> +\t/* Apply the IPA calculated AWB */\n> +\tglUniform3f(awbUniformDataIn_, params.gains[0], params.gains[1], params.gains[2]);\n> +\tLOG (Debayer, Debug) << \" awbUniformDataIn_ \" << awbUniformDataIn_ << \" data \" << params.gains;\n\nNeeded?  See my comment to the preceding patch.\n\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/* Greate a standard texture input */\n\ns/Greate/Create/\n\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> +\n> +\n\nToo many blank lines.\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\ns/fail/failed/ ?\n\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> +\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\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 = out.planes()[0].size();\n> +\n> +\t/* Calculate stats for the whole frame */\n> +\tdmaSyncBegin(dmaSyncers, input, nullptr);\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> +void DebayerEGL::stop()\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.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..500dea27\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.h\n> @@ -0,0 +1,174 @@\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\nNo file name.\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 <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\nThis ^^ and ...\n\n> +#include \"libcamera/internal/mapped_framebuffer.h\"\n> +#include \"libcamera/internal/software_isp/benchmark.h\"\n\n... and this ^^ include's are redundant.\n\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\n+ \\param[in] configuration ...\n\n> +\t */\n> +\tDebayerEGL(std::unique_ptr<SwStatsCpu> stats, const GlobalConfiguration &configuration);\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> +\tvoid stop();\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\nNo `void'.\n\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_;\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> +\teGLImage *eglImageBayerOut_;\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> +\n> +\t// These textures will either point to simple RGB gains or to CCM lookup tables\n> +\tGLint textureUniformRedLookupDataIn_;\n> +\tGLint textureUniformGreenLookupDataIn_;\n> +\tGLint textureUniformBlueLookupDataIn_;\n> +\n> +\t// Represent per-frame CCM as a uniform vector of floats 3 x 3\n> +\tGLint ccmUniformDataIn_;\n> +\tbool ccmEnabled_;\n> +\n> +\t// Auto white Balance\n> +\tGLint awbUniformDataIn_;\n\nNeeded?  If yes, then s/Balance/balance/.\n\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> +\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> +\tGLfloat tcoordinates[DEBAYER_OPENGL_COORDS][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> +\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 3980EC3259\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 16 Oct 2025 15:56:11 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4BEB76068D;\n\tThu, 16 Oct 2025 17:56:10 +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 269B1605D7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 16 Oct 2025 17:56:08 +0200 (CEST)","from mail-wr1-f69.google.com (mail-wr1-f69.google.com\n\t[209.85.221.69]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-445-KHkoW8VCNNO4IAJNrdOhwg-1; Thu, 16 Oct 2025 11:56:05 -0400","by mail-wr1-f69.google.com with SMTP id\n\tffacd0b85a97d-3f384f10762so879448f8f.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 16 Oct 2025 08:56:05 -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\t5b1f17b1804b1-4710ed317c0sm23476095e9.3.2025.10.16.08.56.02\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tThu, 16 Oct 2025 08:56:02 -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=\"CUBagm9E\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1760630166;\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=9sAcTck4GOhpIDg2yifQrX0UfczjXGe4UY7x1KFiPUA=;\n\tb=CUBagm9EOkRsIqQv5TJqlIM5zGLv6QEBfeSEKAGUPdVNhWLpsmgC52vzXOyrp9H2w+W0jp\n\tc3VvIQZ9E0tTnVpe+z9MUWPrw3auQKgRnh85o10j5cfKmQsiT2R+drbiEk5IdglC1YfisQ\n\tbAvmoI0HJIBN2Ug/XCX/zDBGwc/+RKM=","X-MC-Unique":"KHkoW8VCNNO4IAJNrdOhwg-1","X-Mimecast-MFC-AGG-ID":"KHkoW8VCNNO4IAJNrdOhwg_1760630164","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1760630164; x=1761234964;\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=9sAcTck4GOhpIDg2yifQrX0UfczjXGe4UY7x1KFiPUA=;\n\tb=MyEluJ2lCa0hNlZ6MKtvS+ya/79lQvtNSqZpNt1o3Lq0aAxv4318xt2CLI2mcWK9Pd\n\tz6gKD89K5slG8J57minLW73wLe2hVcur2APwVlywcaIuL5yTC8VWIoabhQxfTqHOvEF7\n\tNme2MwxoZABIjKf+Y129zPPgo1RBk84WxdoGvd0PYzPINnb8kbzlsiq++y3Vd5e6lRTW\n\tAorjGtx7DAZq5syTy7JR1pkB2QAGvh40miboVh1xPOZOghAB8PuIIDK8EQRsi8zcrDYG\n\tXB367tibx1yVW+VXM7zo5ZzZUxJHXkgbW3DbOuNNVWEXGlXniAW46pVaOw9qbgARW1kj\n\tM8jA==","X-Gm-Message-State":"AOJu0Yz1BUylWLhVqWNwWeAglpgj0ZSbalmT/BZnmWbxr56X5iEQRBSP\n\taKjQltCsQC3WG7BARfwwOeZ0ehb1i2WydAGlxYVAr6STWrTyiW6P/ooiqFHOToV7pBp0YfsLY28\n\tuURbzurADPJ/buCdfINOhlDcX/0xKixpLk4U7TeRG0L2TbM/W8Hnr1i+90Q5AM9Fmlr/xFA9Yxu\n\to=","X-Gm-Gg":"ASbGnctkToFhXAoSIIeZArKwfp7My2PtjatqCgH0YsWZMEHLls7pNjkDaU/LAQujwfU\n\td3PFPDfMPskz5G3yaR7jeJhuKWOmqbAhJJX8Ee/fn2zg/BVJUqS3OrgE60dGq1hinZtsTQ81ANP\n\tEt1lfFXfCV7ZdeD+RIzXEfLAfqr8TZm7lY+3nJFvatZWn3kHFKH3vyWZOiFl2bI740iVXmkkNCT\n\tI5MHh5tv9ASo4xjWUq5MUO0tJJJ8Aj5z3OpciZGxryyb6lqTlNmiOT/FKHk6fFgV1jDhYaKKwAa\n\tI8goTnT+SO4FvN9riM1m9rYZs3Dh891AL4os+UyMGkHeThA0Qb2GL4xFjxvEmA6DcgfmVFxaLpl\n\tNg3ppPjbIsrCE6LYsUcw76qEqK1XLWrlmj4Y2yHXWPI6JukdG5qjZ","X-Received":["by 2002:a05:600c:a013:b0:46d:ba6d:65bb with SMTP id\n\t5b1f17b1804b1-47117919b6cmr4222895e9.31.1760630163660; \n\tThu, 16 Oct 2025 08:56:03 -0700 (PDT)","by 2002:a05:600c:a013:b0:46d:ba6d:65bb with SMTP id\n\t5b1f17b1804b1-47117919b6cmr4222475e9.31.1760630163035; \n\tThu, 16 Oct 2025 08:56:03 -0700 (PDT)"],"X-Google-Smtp-Source":"AGHT+IGW95zqUcULTqo1W2utNgCRTkMeEs8SXFgC7zRTOubOt/JQhaWAVw66iaj0qh8CMcF5+Ktr7g==","From":"Milan Zamazal <mzamazal@redhat.com>","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","Cc":"libcamera-devel@lists.libcamera.org,  hdegoede@redhat.com,\n\tbod.linux@nxsw.ie","Subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","In-Reply-To":"<20251015012251.17508-34-bryan.odonoghue@linaro.org> (Bryan\n\tO'Donoghue's message of \"Wed, 15 Oct 2025 02:22:45 +0100\")","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-34-bryan.odonoghue@linaro.org>","Date":"Thu, 16 Oct 2025 17:56:01 +0200","Message-ID":"<85v7kec032.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":"x2Caur4yWFOo5aRKy1Np1a8vVKRR6ftDez9_O9jCh2c_1760630164","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":36344,"web_url":"https://patchwork.libcamera.org/comment/36344/","msgid":"<e5b2976e-b1a3-4226-959b-2e3c320b8952@collabora.com>","date":"2025-10-18T16:34:55","subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","submitter":{"id":140,"url":"https://patchwork.libcamera.org/api/people/140/","name":"Robert Mader","email":"robert.mader@collabora.com"},"content":"Hi, a couple more comments below\n\nOn 15.10.25 03:22, 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>\n> ---\n>   src/libcamera/software_isp/debayer_egl.cpp | 648 +++++++++++++++++++++\n>   src/libcamera/software_isp/debayer_egl.h   | 174 ++++++\n>   src/libcamera/software_isp/meson.build     |   8 +\n>   3 files changed, 830 insertions(+)\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/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp\n> new file mode 100644\n> index 00000000..a24dd067\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.cpp\n> @@ -0,0 +1,648 @@\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/base/utils.h\"\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, const GlobalConfiguration &configuration)\n> +\t: Debayer(configuration), stats_(std::move(stats))\n> +{\n> +\teglImageBayerIn_ = eglImageBayerOut_= 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 (eglImageBayerOut_)\n> +\t\tdelete eglImageBayerOut_;\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, Error)\n> +\t\t<< \"Unsupported input format \" << inputFormat.toString();\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.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> +\tccmUniformDataIn_ = glGetUniformLocation(programId_, \"ccm\");\n> +\tawbUniformDataIn_ = glGetUniformLocation(programId_, \"awb\");\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    << \" ccm \" << ccmUniformDataIn_\n> +\t\t\t    << \" awb \" << awbUniformDataIn_\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> +\tglFormat_ = GL_LUMINANCE;\n> +\tbytesPerPixel_ = 1;\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_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}\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}\n> +\t\tbreak;\n> +\tdefault:\n> +\t\tgoto invalid_fmt;\n> +\t\tbreak;\n> +\t};\n> +\n> +\tif (ccmEnabled_) {\n> +\t\t// Run the CCM if available\n> +\t\tegl_.pushEnv(shaderEnv, \"#define APPLY_CCM_PARAMETERS\");\n> +\t} else {\n> +\t\t// Flag to shaders that we have parameter gain tables\n> +\t\tegl_.pushEnv(shaderEnv, \"#define APPLY_RGB_PARAMETERS\");\n> +\t}\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> +\tGLint maxTextureImageUnits;\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;\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> +\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> +\tif (!ccmEnabled && 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> +\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> +\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> +\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// Only do the RGB lookup table textures if CCM is disabled\n> +\tif (!ccmEnabled_) {\n> +\n> +\t\t/// RGB correction tables as 2d textures\n> +\t\t// eGL doesn't support glTexImage1D so we do a little hack with 2D to compensate\n> +\t\teglImageRedLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE1, 1);\n> +\t\tif (!eglImageRedLookup_)\n> +\t\t\treturn -ENOMEM;\n> +\n> +\t\teglImageGreenLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE2, 2);\n> +\t\tif (!eglImageGreenLookup_)\n> +\t\t\treturn -ENOMEM;\n> +\n> +\t\teglImageBlueLookup_ = new eGLImage(DebayerParams::kRGBLookupSize, 1, 32, GL_TEXTURE3, 3);\n> +\t\tif (!eglImageBlueLookup_)\n> +\t\t\treturn -ENOMEM;\n> +\t}\n> +\n> +\teglImageBayerOut_ = new eGLImage(outputCfg.size.width, outputCfg.size.height, 32, outputCfg.stride, GL_TEXTURE4, 4);\n> +\tif (!eglImageBayerOut_)\n> +\t\treturn -ENOMEM;\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/* 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 = 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> +\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> +\n> +\tif (!ccmEnabled_) {\n> +\t\tglUniform1i(textureUniformRedLookupDataIn_, eglImageRedLookup_->texture_unit_uniform_id_);\n> +\t\tglUniform1i(textureUniformGreenLookupDataIn_, eglImageGreenLookup_->texture_unit_uniform_id_);\n> +\t\tglUniform1i(textureUniformBlueLookupDataIn_, eglImageBlueLookup_->texture_unit_uniform_id_);\n> +\n> +\t\tegl_.createTexture2D(eglImageRedLookup_, GL_LUMINANCE, DebayerParams::kRGBLookupSize, 1, &params.red);\n> +\t\tegl_.createTexture2D(eglImageGreenLookup_, GL_LUMINANCE, DebayerParams::kRGBLookupSize, 1, &params.green);\n> +\t\tegl_.createTexture2D(eglImageBlueLookup_, GL_LUMINANCE, DebayerParams::kRGBLookupSize, 1, &params.blue);\n> +\n> +\t\tLOG (Debayer, Debug) << \"textureUniformRedLookupDataIn_ \" << textureUniformRedLookupDataIn_\n> +\t\t\t\t     << \" textureUniformGreenLookupDataIn_ \" << textureUniformGreenLookupDataIn_\n> +\t\t\t\t     << \" textureUniformBlueLookupDataIn_ \" << textureUniformBlueLookupDataIn_;\n> +\t} else {\n> +\t\tGLfloat ccm[9] = {\n> +\t\t\tparams.ccm[0][0], params.ccm[0][1], params.ccm[0][2],\n> +\t\t\tparams.ccm[1][0], params.ccm[1][1], params.ccm[1][2],\n> +\t\t\tparams.ccm[2][0], params.ccm[2][1], params.ccm[2][2],\n> +\t\t};\n> +\t\tglUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm);\n> +\t\tLOG (Debayer, Debug) << \" ccmUniformDataIn_ \" << ccmUniformDataIn_ << \" data \" << params.ccm;\n> +\t}\n> +\n> +\t/* Apply the IPA calculated AWB */\n> +\tglUniform3f(awbUniformDataIn_, params.gains[0], params.gains[1], params.gains[2]);\n> +\tLOG (Debayer, Debug) << \" awbUniformDataIn_ \" << awbUniformDataIn_ << \" data \" << params.gains;\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/* Greate a standard texture input */\n> +\tegl_.createTexture2D(eglImageBayerIn_, glFormat_, inputConfig_.stride / bytesPerPixel_, height_, in.planes()[0].data());\n\nI noticed one issue (regression compared to debayer_cpu) here in cases \nwhere inputCfg.size is smaller than outputCfg.size, i.e. where window_.x \nand window_.y are not zero. By uploading the texture from 0x0 (because \nwe use in.planes()[0].data() from the start), we effectively ignore \nwindow_.x / window_.y and thus zoom to the top-left instead of the center.\n\nThis is actually quite noticeable with some phone cameras, e.g. on the \nOnePlus6 where the sensor drivers currently only support one mode each \n(in the 4k size range).\n\nI think there are two ways to fix this:\n\n 1. Try to keep the zoom effect by calculating the data offset. I\n    suppose it would be something along the lines of\n    (inputConfig_.stride / bytesPerPixel_) * window_.y + window_x\n    * bytesPerPixel_). Keeping the stride as is should then result in\n    each line having the correct x-offset IIUC.\n 2. Just ditch the zoom-effect and use the whole input buffer, scaling\n    down to the output buffer size. This would also fix\n    https://bugs.libcamera.org/show_bug.cgi?id=284 (\"swISP: smaller\n    output sizes are always treated as center crop\"), which would\n    desirable for most clients - however at a performance cost. The\n    later can be avoided by making sensors support smaller\n    modes/resolutions, though.\n\nI personally would suggest to go for 2.\n\n> +\n> +\t/* Generate the output render framebuffer as render to texture */\n> +\tegl_.createOutputDMABufTexture2D(eglImageBayerOut_, out_fd);\n> +\n> +\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> +\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\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 = out.planes()[0].size();\n\nIf you replace this with\n\noutput.planes()[0].size()\n\ninstead you can drop\n\nMappedFrameBuffer out\n\nabove altogether and it would be more in line with\n\noutput->planes()[0].fd.get()\n\n> +\n> +\t/* Calculate stats for the whole frame */\n> +\tdmaSyncBegin(dmaSyncers, input, nullptr);\nThis\n\ndmaSyncBegin()\n\nneeds to be before\n\ndebayerGPU()\n\nbecause we unconditionally upload/copy data from CPU in\n\ncreateTexture2D()\n\n. Otherwise\n\nin.planes()[0].data()\n\nis not guaranteed to be concurrent/correct. I can actually observe this \non a PinePhone where it's causing glitches.\n\nIn the future we can optimize this and only call\n\ndmaSyncBegin()\n\n  if we need the CPU upload path *and*\n\nprocessFrame()\n\ndoes not skip processing for the current frame, see also my comment in \nhttps://gitlab.freedesktop.org/rmader/libcamera/-/commit/1f5c2764c4d42c916e52f9c06c44f4ae52519a78\n\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> +void DebayerEGL::stop()\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.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..500dea27\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.h\n> @@ -0,0 +1,174 @@\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, const GlobalConfiguration &configuration);\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> +\tvoid stop();\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(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_;\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> +\teGLImage *eglImageBayerOut_;\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> +\n> +\t// These textures will either point to simple RGB gains or to CCM lookup tables\n> +\tGLint textureUniformRedLookupDataIn_;\n> +\tGLint textureUniformGreenLookupDataIn_;\n> +\tGLint textureUniformBlueLookupDataIn_;\n> +\n> +\t// Represent per-frame CCM as a uniform vector of floats 3 x 3\n> +\tGLint ccmUniformDataIn_;\n> +\tbool ccmEnabled_;\n> +\n> +\t// Auto white Balance\n> +\tGLint awbUniformDataIn_;\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> +\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> +\tGLfloat tcoordinates[DEBAYER_OPENGL_COORDS][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> +\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 62F21BE080\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSat, 18 Oct 2025 16:35:13 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6BF57606E2;\n\tSat, 18 Oct 2025 18:35:12 +0200 (CEST)","from sender4-op-o12.zoho.com (sender4-op-o12.zoho.com\n\t[136.143.188.12])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 15A70606D6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 18 Oct 2025 18:35:09 +0200 (CEST)","by mx.zohomail.com with SMTPS id 1760805302908876.5852198338389;\n\tSat, 18 Oct 2025 09:35:02 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=collabora.com\n\theader.i=robert.mader@collabora.com header.b=\"Xmk2dw/o\"; \n\tdkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1760805305; cv=none; \n\td=zohomail.com; s=zohoarc; \n\tb=S+N6NJIkTKYKDmX8lunkHcg3HgQ/yi/N+pYR3nenPyD6ZVJ3Ky7BpBPWh4v6wmuKnCk8RgMoxK/h4QIGeM6UaYPbtpqO0gBUfPJ+g9Dvut+geyeOYIda8Sqe38+uc/DvpNX0ZwSYjgLpsEziH6nA325QYHSL+YyucwJRMVg6My8=","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; \n\ts=zohoarc; t=1760805305;\n\th=Content-Type:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To:Cc;\n\tbh=4BHokYh/3uPmVPvy7mWR8Mi65JNUBPGUJoVpnUql5nk=; \n\tb=bL3ggSt2JdHfeFCjI+NTSuIRoHpOAnqaMyiZWaMy6fKpONAVGRCBjpjlaPguV9ZGPqPyrDjeOeHqwGILtKPZlAXWG+R+pwFquWtAXnUgggdX/v1MLgjMGYh1Ro182Zwv1sVIjlmIGSZDOvQrn7w+0WV2nufucqTYA2o9yUEpIsg=","ARC-Authentication-Results":"i=1; mx.zohomail.com;\n\tdkim=pass  header.i=collabora.com;\n\tspf=pass  smtp.mailfrom=robert.mader@collabora.com;\n\tdmarc=pass header.from=<robert.mader@collabora.com>","DKIM-Signature":"v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1760805305;\n\ts=zohomail; d=collabora.com; i=robert.mader@collabora.com;\n\th=Content-Type:Message-ID:Date:Date:MIME-Version:Subject:Subject:To:To:References:From:From:In-Reply-To:Message-Id:Reply-To:Cc;\n\tbh=4BHokYh/3uPmVPvy7mWR8Mi65JNUBPGUJoVpnUql5nk=;\n\tb=Xmk2dw/obOI8LTucGLMVnGZLkOxgQTBiWUOyn5lkV3qMP0Lx5IHc6RZv1NIFruFx\n\tpexmesNi/ifE3lDPpSLpn8CQC2JsTGV0vqE107iM04jqamydLtMs+7COcqcaPaA1P6X\n\tmiV/Ol65lkzrAPvY1HY4jgaDlckfnc6qMiv+fobQ=","Content-Type":"multipart/alternative;\n\tboundary=\"------------7pO7HHUq6yQ2cZqxdYMEP0En\"","Message-ID":"<e5b2976e-b1a3-4226-959b-2e3c320b8952@collabora.com>","Date":"Sat, 18 Oct 2025 18:34:55 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","To":"libcamera-devel@lists.libcamera.org","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-34-bryan.odonoghue@linaro.org>","Content-Language":"en-US, de-DE, en-GB","From":"Robert Mader <robert.mader@collabora.com>","In-Reply-To":"<20251015012251.17508-34-bryan.odonoghue@linaro.org>","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":36346,"web_url":"https://patchwork.libcamera.org/comment/36346/","msgid":"<3ce28579-54d3-4645-a002-99f0c5f9ecb8@collabora.com>","date":"2025-10-19T15:11:55","subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","submitter":{"id":140,"url":"https://patchwork.libcamera.org/api/people/140/","name":"Robert Mader","email":"robert.mader@collabora.com"},"content":"For two of the issues mentioned I pushed fixup-commits to \nhttps://gitlab.freedesktop.org/rmader/libcamera/-/commits/v0.5.2-gpuisp-v3a-fixes \n- feel free to cherry-pick/squash.\n\nFor the third a small follow-up comment below:\n\nOn 18.10.25 18:34, Robert Mader wrote:\n>\n> Hi, a couple more comments below\n>\n> On 15.10.25 03:22, Bryan O'Donoghue wrote:\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/* Greate a standard texture input */\n>> +\tegl_.createTexture2D(eglImageBayerIn_, glFormat_, inputConfig_.stride / bytesPerPixel_, height_, in.planes()[0].data());\n>\n> I noticed one issue (regression compared to debayer_cpu) here in cases \n> where inputCfg.size is smaller than outputCfg.size, i.e. where \n> window_.x and window_.y are not zero. By uploading the texture from \n> 0x0 (because we use in.planes()[0].data() from the start), we \n> effectively ignore window_.x / window_.y and thus zoom to the top-left \n> instead of the center.\n>\n> This is actually quite noticeable with some phone cameras, e.g. on the \n> OnePlus6 where the sensor drivers currently only support one mode each \n> (in the 4k size range).\n>\n> I think there are two ways to fix this:\n>\n>  1. Try to keep the zoom effect by calculating the data offset. I\n>     suppose it would be something along the lines of\n>     (inputConfig_.stride / bytesPerPixel_) * window_.y + window_x\n>     * bytesPerPixel_). Keeping the stride as is should then result in\n>     each line having the correct x-offset IIUC.\n>  2. Just ditch the zoom-effect and use the whole input buffer, scaling\n>     down to the output buffer size. This would also fix\n>     https://bugs.libcamera.org/show_bug.cgi?id=284 (\"swISP: smaller\n>     output sizes are always treated as center crop\"), which would\n>     desirable for most clients - however at a performance cost. The\n>     later can be avoided by making sensors support smaller\n>     modes/resolutions, though.\n>\n> I personally would suggest to go for 2.\n>\nI noticed that 1. would likely be a bit more complex than just adding \nsome offset as we currently already upload the whole input buffer - and \nthe zoom effect IIUC is simply a result of the size of the output buffer.\n\nIMO that's more reasons to go for option 2.","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 DA33DC3259\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSun, 19 Oct 2025 15:12:07 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0FEA5606E8;\n\tSun, 19 Oct 2025 17:12:07 +0200 (CEST)","from sender4-op-o12.zoho.com (sender4-op-o12.zoho.com\n\t[136.143.188.12])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 92E16606AC\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun, 19 Oct 2025 17:12:05 +0200 (CEST)","by mx.zohomail.com with SMTPS id 1760886720460532.0106424596075;\n\tSun, 19 Oct 2025 08:12:00 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=collabora.com\n\theader.i=robert.mader@collabora.com header.b=\"LBKMatN5\"; \n\tdkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1760886723; cv=none; \n\td=zohomail.com; s=zohoarc; \n\tb=dI07vnuji1bA14aaPx9+nBCmwr+66tQ5S1yXIInk1dgDYFUWESD5RZMJijGzUXJSZgwQcI5l+8ls6BfD5piyVcNa2+hlhj03F0BOZS9CbdUKP3Ckli7KCmqYJqUqJ5V47jYrmSMB5/VZytnkqEc6PGS1/6Y7kJ6mXQxm1g3KNPg=","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; \n\ts=zohoarc; t=1760886723;\n\th=Content-Type:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To:Cc;\n\tbh=A7dgOB+/UA5sYrBOMD0SNnLkEfpX4epp00ctgl70T8o=; \n\tb=ffdQOoVrxEFXnjGvOhfWk1huvPZsiI1xrouZ1sCbR4zMEPcoIqVbmvovafBmq5oYSVlx92aXVFyELY8RrCaYAvSDB2srWoaAkUjSAwtQkhsNJFetcRhhEDMmCutoVDdUZVJib/dKjhl+EkwR6YV88waOJplOx/oL+/dzGMK0Wp0=","ARC-Authentication-Results":"i=1; mx.zohomail.com;\n\tdkim=pass  header.i=collabora.com;\n\tspf=pass  smtp.mailfrom=robert.mader@collabora.com;\n\tdmarc=pass header.from=<robert.mader@collabora.com>","DKIM-Signature":"v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1760886723;\n\ts=zohomail; d=collabora.com; i=robert.mader@collabora.com;\n\th=Content-Type:Message-ID:Date:Date:MIME-Version:Subject:Subject:To:To:References:From:From:In-Reply-To:Message-Id:Reply-To:Cc;\n\tbh=A7dgOB+/UA5sYrBOMD0SNnLkEfpX4epp00ctgl70T8o=;\n\tb=LBKMatN5jlHkejciTRKpKbkG1Owy+TwBp/0bTrsJpyQwCOoLgCc/fh43sxU1CDJF\n\twkjeLsONh0DLJej4bI9e+pRgEKdeN88NWIbTnd0b4RbqREae9lAyB4DjdR7PxW3OFs2\n\tP5eYUh3UwM9NhQBzq21yJ9SIT/aDOO4+pVejiOHI=","Content-Type":"multipart/alternative;\n\tboundary=\"------------5EKMJLqd4b0FXlRCdhZ9bTj3\"","Message-ID":"<3ce28579-54d3-4645-a002-99f0c5f9ecb8@collabora.com>","Date":"Sun, 19 Oct 2025 17:11:55 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","To":"libcamera-devel@lists.libcamera.org","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-34-bryan.odonoghue@linaro.org>\n\t<e5b2976e-b1a3-4226-959b-2e3c320b8952@collabora.com>","Content-Language":"en-US, de-DE, en-GB","From":"Robert Mader <robert.mader@collabora.com>","In-Reply-To":"<e5b2976e-b1a3-4226-959b-2e3c320b8952@collabora.com>","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":36473,"web_url":"https://patchwork.libcamera.org/comment/36473/","msgid":"<9359f33d-f95a-4132-af3d-07083b542d26@collabora.com>","date":"2025-10-27T01:06:52","subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","submitter":{"id":140,"url":"https://patchwork.libcamera.org/api/people/140/","name":"Robert Mader","email":"robert.mader@collabora.com"},"content":"Hey, I had a another look into the scaling issue and pushed a WIP patch \nhere: \nhttps://gitlab.freedesktop.org/rmader/libcamera/-/commit/048cc02e295fd82f6ecfb1d5c6280a21709cd0d5 \n/ \nhttps://gitlab.freedesktop.org/rmader/libcamera/-/commits/v0.5.2-gpuisp-v3a-fixes\n\nWith that one I get the expected full (or only cropped on one axis) \nimage. I tested on multiple devices, including the Pixel3a and OnePlus6 \nwhere the impact is very visible. With a solution like that we'd fix \nhttps://bugs.libcamera.org/show_bug.cgi?id=284\n\nOn 19.10.25 17:11, Robert Mader wrote:\n>\n> For two of the issues mentioned I pushed fixup-commits to \n> https://gitlab.freedesktop.org/rmader/libcamera/-/commits/v0.5.2-gpuisp-v3a-fixes \n> - feel free to cherry-pick/squash.\n>\n> For the third a small follow-up comment below:\n>\n> On 18.10.25 18:34, Robert Mader wrote:\n>>\n>> Hi, a couple more comments below\n>>\n>> On 15.10.25 03:22, Bryan O'Donoghue wrote:\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/* Greate a standard texture input */\n>>> +\tegl_.createTexture2D(eglImageBayerIn_, glFormat_, inputConfig_.stride / bytesPerPixel_, height_, in.planes()[0].data());\n>>\n>> I noticed one issue (regression compared to debayer_cpu) here in \n>> cases where inputCfg.size is smaller than outputCfg.size, i.e. where \n>> window_.x and window_.y are not zero. By uploading the texture from \n>> 0x0 (because we use in.planes()[0].data() from the start), we \n>> effectively ignore window_.x / window_.y and thus zoom to the \n>> top-left instead of the center.\n>>\n>> This is actually quite noticeable with some phone cameras, e.g. on \n>> the OnePlus6 where the sensor drivers currently only support one mode \n>> each (in the 4k size range).\n>>\n>> I think there are two ways to fix this:\n>>\n>>  1. Try to keep the zoom effect by calculating the data offset. I\n>>     suppose it would be something along the lines of\n>>     (inputConfig_.stride / bytesPerPixel_) * window_.y + window_x\n>>     * bytesPerPixel_). Keeping the stride as is should then result in\n>>     each line having the correct x-offset IIUC.\n>>  2. Just ditch the zoom-effect and use the whole input buffer,\n>>     scaling down to the output buffer size. This would also fix\n>>     https://bugs.libcamera.org/show_bug.cgi?id=284 (\"swISP: smaller\n>>     output sizes are always treated as center crop\"), which would\n>>     desirable for most clients - however at a performance cost. The\n>>     later can be avoided by making sensors support smaller\n>>     modes/resolutions, though.\n>>\n>> I personally would suggest to go for 2.\n>>\n> I noticed that 1. would likely be a bit more complex than just adding \n> some offset as we currently already upload the whole input buffer - \n> and the zoom effect IIUC is simply a result of the size of the output \n> buffer.\n>\n> IMO that's more reasons to go for option 2.\n>","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 74EB0C3259\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 27 Oct 2025 01:07:07 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0814060712;\n\tMon, 27 Oct 2025 02:07:06 +0100 (CET)","from sender4-op-o12.zoho.com (sender4-op-o12.zoho.com\n\t[136.143.188.12])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9128A606E6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 27 Oct 2025 02:07:03 +0100 (CET)","by mx.zohomail.com with SMTPS id 1761527214897385.8314255587759;\n\tSun, 26 Oct 2025 18:06:54 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=collabora.com\n\theader.i=robert.mader@collabora.com header.b=\"PydsBJwJ\"; \n\tdkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1761527217; cv=none; \n\td=zohomail.com; s=zohoarc; \n\tb=oBbCT/d/4J+JefKlIXYaXtwUi+3Kd4EbbgAQShFK1bN9cYUEYHxwfr3vgz2Zjy+CGoTvO4uoLiCR59gUs36J+vnVmi/0ZYPfmHkvH1o5NpyG56IcgjCXpg0mYIiJAbF6PjgePM7trCmqPDeWa2zLbgmaXDrlp56Mk+k9igMur7g=","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; \n\ts=zohoarc; t=1761527217;\n\th=Content-Type:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To:Cc;\n\tbh=Lk9QrklCtnV/M0iuJ7mJLL17lKmNJnr75QzulyZNUgU=; \n\tb=kfaBUIf9QVuVb3GqqlVRpGlzwgICV4q1K9tuXJyRxo6Zf29PDGMts4FvzHeLCXgOaeN+1BnBJ4bpfVx7Le793NxcZz6xq6JFG4SaqQAJypc86BAik9hMmaaN8EhJjgs7rm0NMIQkTfHRQj6weA3gsILSlMVZ9neJkkWqAPqbX9w=","ARC-Authentication-Results":"i=1; mx.zohomail.com;\n\tdkim=pass  header.i=collabora.com;\n\tspf=pass  smtp.mailfrom=robert.mader@collabora.com;\n\tdmarc=pass header.from=<robert.mader@collabora.com>","DKIM-Signature":"v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1761527217;\n\ts=zohomail; d=collabora.com; i=robert.mader@collabora.com;\n\th=Content-Type:Message-ID:Date:Date:MIME-Version:Subject:Subject:To:To:References:From:From:In-Reply-To:Message-Id:Reply-To:Cc;\n\tbh=Lk9QrklCtnV/M0iuJ7mJLL17lKmNJnr75QzulyZNUgU=;\n\tb=PydsBJwJC+1WKr1zWYlQtl0MmKdpf89pdmGqusf5ws/KEmt7Pj8h+omxsGQvZ7ot\n\tZxi413be0OtjXEhMEEdz2CTkulzaHtHjQRZP9MdoJAyaLXXA9RTnh88spP3DoudKmrc\n\t5UTIjZGDeEbJj4udTwpZFjrzzynt/4boqLnUuaX4=","Content-Type":"multipart/alternative;\n\tboundary=\"------------Zp20ICzQqrFgV43heX7WFTzp\"","Message-ID":"<9359f33d-f95a-4132-af3d-07083b542d26@collabora.com>","Date":"Mon, 27 Oct 2025 02:06:52 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","To":"libcamera-devel@lists.libcamera.org","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-34-bryan.odonoghue@linaro.org>\n\t<e5b2976e-b1a3-4226-959b-2e3c320b8952@collabora.com>\n\t<3ce28579-54d3-4645-a002-99f0c5f9ecb8@collabora.com>","Content-Language":"en-US, de-DE, en-GB","From":"Robert Mader <robert.mader@collabora.com>","In-Reply-To":"<3ce28579-54d3-4645-a002-99f0c5f9ecb8@collabora.com>","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":36488,"web_url":"https://patchwork.libcamera.org/comment/36488/","msgid":"<7aaad79c-5f21-400f-965b-64336891587d@nxsw.ie>","date":"2025-10-27T14:20:30","subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","submitter":{"id":226,"url":"https://patchwork.libcamera.org/api/people/226/","name":"Bryan O'Donoghue","email":"bod.linux@nxsw.ie"},"content":"On 27/10/2025 01:06, Robert Mader wrote:\n> Hey, I had a another look into the scaling issue and pushed a WIP patch \n> here: https://gitlab.freedesktop.org/rmader/libcamera/-/ \n> commit/048cc02e295fd82f6ecfb1d5c6280a21709cd0d5 / https:// \n> gitlab.freedesktop.org/rmader/libcamera/-/commits/v0.5.2-gpuisp-v3a-fixes\n> \n> With that one I get the expected full (or only cropped on one axis) \n> image. I tested on multiple devices, including the Pixel3a and OnePlus6 \n> where the impact is very visible. With a solution like that we'd fix \n> https://bugs.libcamera.org/show_bug.cgi?id=284\n> \n\nCool.\n\nI'll pick up that code as-is provided it passes ci and bash with a \nhammer otherwise.\n\nthx\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 B5DA2BE080\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 27 Oct 2025 14:20:38 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id BB34A60758;\n\tMon, 27 Oct 2025 15:20:37 +0100 (CET)","from sea.source.kernel.org (sea.source.kernel.org [172.234.252.31])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 60CAA6069A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 27 Oct 2025 15:20:35 +0100 (CET)","from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58])\n\tby sea.source.kernel.org (Postfix) with ESMTP id CFBF643CD8;\n\tMon, 27 Oct 2025 14:20:33 +0000 (UTC)","by smtp.kernel.org (Postfix) with ESMTPSA id 393EBC4CEF1;\n\tMon, 27 Oct 2025 14:20:33 +0000 (UTC)"],"Message-ID":"<7aaad79c-5f21-400f-965b-64336891587d@nxsw.ie>","Date":"Mon, 27 Oct 2025 14:20:30 +0000","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","To":"Robert Mader <robert.mader@collabora.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-34-bryan.odonoghue@linaro.org>\n\t<e5b2976e-b1a3-4226-959b-2e3c320b8952@collabora.com>\n\t<3ce28579-54d3-4645-a002-99f0c5f9ecb8@collabora.com>\n\t<mA7UILAys239nM5RrIzuHZNJelu98u_pbDu9ZFlR_DTdOh2aIuuhSBcaHmSLu12z7yhykx3SlXG7Z4FJ7_02qA==@protonmail.internalid>\n\t<9359f33d-f95a-4132-af3d-07083b542d26@collabora.com>","From":"Bryan O'Donoghue <bod.linux@nxsw.ie>","Content-Language":"en-US","In-Reply-To":"<9359f33d-f95a-4132-af3d-07083b542d26@collabora.com>","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":36537,"web_url":"https://patchwork.libcamera.org/comment/36537/","msgid":"<857bwdaix7.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2025-10-29T14:20:36","subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Hi Robert,\n\nRobert Mader <robert.mader@collabora.com> writes:\n\n> Hey, I had a another look into the scaling issue and pushed a WIP patch here:\n> https://gitlab.freedesktop.org/rmader/libcamera/-/commit/048cc02e295fd82f6ecfb1d5c6280a21709cd0d5 /\n> https://gitlab.freedesktop.org/rmader/libcamera/-/commits/v0.5.2-gpuisp-v3a-fixes\n>\n> With that one I get the expected full (or only cropped on one axis) image. I tested on multiple devices, including\n> the Pixel3a and OnePlus6 where the impact is very visible. With a solution like that we'd fix\n> https://bugs.libcamera.org/show_bug.cgi?id=284\n\nI tested it on TI AM69 with imx219 and different resolutions.  I used\nBryan's v3 with \"Fix neighbouring positions in 8-bit debayering\" patch\ndropped.  It seems to do the job but there are sometimes reddish\nhorizontal or vertical lines throughout the image (not only at the\nedges).","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 C06B8C3259\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 29 Oct 2025 14:20:45 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id EAA3460864;\n\tWed, 29 Oct 2025 15:20:44 +0100 (CET)","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 5243560453\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 29 Oct 2025 15:20:43 +0100 (CET)","from mail-ej1-f70.google.com (mail-ej1-f70.google.com\n\t[209.85.218.70]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-384-JP2c99J3PImD92Sog4fueg-1; Wed, 29 Oct 2025 10:20:40 -0400","by mail-ej1-f70.google.com with SMTP id\n\ta640c23a62f3a-b6d42c454c2so464252666b.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 29 Oct 2025 07:20:39 -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-b6d854550f2sm1420251066b.70.2025.10.29.07.20.36\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tWed, 29 Oct 2025 07:20:37 -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=\"FG4KXVeU\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1761747642;\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=586TuT84R9WWk8fng3ioilYo3tPQLaQWFRWKK8InFP0=;\n\tb=FG4KXVeUoAiWxIPwXznTXNWq17yBIv2E7pCxxz8snS8cxTp4F6BZTUBKmeg3IiPA0uE15V\n\tyQIW8goepvUQSurm9TNa19gctv3aG1PYGY4fYmb4l8LdOGvr0bTn7Iw/aZmC6sDJgOyKs5\n\tjJAgN1lblDvkPDhDiOluw1mPu99tT7U=","X-MC-Unique":"JP2c99J3PImD92Sog4fueg-1","X-Mimecast-MFC-AGG-ID":"JP2c99J3PImD92Sog4fueg_1761747639","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1761747638; x=1762352438;\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=586TuT84R9WWk8fng3ioilYo3tPQLaQWFRWKK8InFP0=;\n\tb=Z53yZNzwB2V7hNIAT9ml+SAGDlqKZOsrP3mjEnQ5icpIYJmgE7xT75FRJKIu1tGfEg\n\tECd5jlgI0N0cAaQfgdK/G5FqYlzWh+0AYNtoJRBTC76sWdlPFkzvSKyZIQ2Ogrso1ndS\n\tVM3XeBVbiXWKRBR8n/y45xzg8Xs2itrmwf2DmLdO1XhvnRb0GvQDOn1DnV4i06NWmT/i\n\toVA0/sQtAQfC329f8+A9lkqko9uvwrm3frILSxnnBXP8boRBdqCRXLDG6Ec7Hy44BLfA\n\tYUnxXaYwlrlFze1xZdB9aHpdZw11+h522pt/h77EjMpsV7qpexjxFtqzDv6kURr8ixQB\n\tR8sQ==","X-Gm-Message-State":"AOJu0YxbmjdsV5uH3psUR9CTt5UqRkWLajLKN606QWE3GGzPp2dtcWQ7\n\tdEQgSm/SAsNYvl8oeFs00dgpl3opfsLcjl1FAaANUw3fET3qYiS6l5swtYUpKykKumxJX3z9JSH\n\tA0rmbIPlrIpRv41sTHU3FHbUltglaJPbIq3Guv0KJN7FMPE/qE20VMuReoC844D8hD0bjAaUvls\n\teZ0+zJvnW8Qe8sGoqYUtkY1dxfTE4jPDh7o7xinGYOj45oPp8f/ByOue9kjSs=","X-Gm-Gg":"ASbGncuKt0bs+5VPhA17WkprcY58gNRlq93i2EquWT5kmrWVXarWrNbC4JPk2nPaKPG\n\tStB8ELdNSiBOeU4gp3yCw0MA5sZ+5jA9ObabDBqs/M44f8iZpCoUfS5DqIgTyjmNPDEUIa+FcRx\n\t+m/a8bSXohDSSNq1yqCJIsjcDUyQv6iAl/eUBNJaEToq3esd7ePibn4s9W/T+tV77Y1jYs1TZJz\n\ttK2+0rxlt9PlIDBnIrsugYl8zj61hds9uitnoMRPbzGCSWiWl7fx9nm6K7BZBkxh0PsTt04pljo\n\tRmr9CIf+YoG1W4i8hxyKfb/eC3rAznmHoVePC4pV46gbv7hD7SyrB/lVkVSjToOT0NNf2bwt8x5\n\tRvkPvosyBZJoWcvml1QgODvXxSrtm4MCzgCvcoRVa2aZLSm48OC/Y","X-Received":["by 2002:a17:907:3d45:b0:b6d:7d27:258b with SMTP id\n\ta640c23a62f3a-b703d2dae70mr343024366b.12.1761747638314; \n\tWed, 29 Oct 2025 07:20:38 -0700 (PDT)","by 2002:a17:907:3d45:b0:b6d:7d27:258b with SMTP id\n\ta640c23a62f3a-b703d2dae70mr343021166b.12.1761747637807; \n\tWed, 29 Oct 2025 07:20:37 -0700 (PDT)"],"X-Google-Smtp-Source":"AGHT+IHN93qec5wZbOxP8qYPfDWKFhwR1C+86k15wHCAyurbUDrrL3eS5HAMUxh7oax9xklKAhrgEA==","From":"Milan Zamazal <mzamazal@redhat.com>","To":"Robert Mader <robert.mader@collabora.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","In-Reply-To":"<9359f33d-f95a-4132-af3d-07083b542d26@collabora.com> (Robert\n\tMader's message of \"Mon, 27 Oct 2025 02:06:52 +0100\")","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-34-bryan.odonoghue@linaro.org>\n\t<e5b2976e-b1a3-4226-959b-2e3c320b8952@collabora.com>\n\t<3ce28579-54d3-4645-a002-99f0c5f9ecb8@collabora.com>\n\t<9359f33d-f95a-4132-af3d-07083b542d26@collabora.com>","Date":"Wed, 29 Oct 2025 15:20:36 +0100","Message-ID":"<857bwdaix7.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":"rzqRLRv4Y-jGRNb5kjPNh7URKVrfvlMrStnTH1o_yVk_1761747639","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":36538,"web_url":"https://patchwork.libcamera.org/comment/36538/","msgid":"<0941135c-b0dc-4564-bd15-9d7f2a5e075a@collabora.com>","date":"2025-10-29T14:43:16","subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","submitter":{"id":140,"url":"https://patchwork.libcamera.org/api/people/140/","name":"Robert Mader","email":"robert.mader@collabora.com"},"content":"Hi Milan,\n\nOn 10/29/25 15:20, Milan Zamazal wrote:\n> Hi Robert,\n>\n> Robert Mader <robert.mader@collabora.com> writes:\n>\n>> Hey, I had a another look into the scaling issue and pushed a WIP patch here:\n>> https://gitlab.freedesktop.org/rmader/libcamera/-/commit/048cc02e295fd82f6ecfb1d5c6280a21709cd0d5 /\n>> https://gitlab.freedesktop.org/rmader/libcamera/-/commits/v0.5.2-gpuisp-v3a-fixes\n>>\n>> With that one I get the expected full (or only cropped on one axis) image. I tested on multiple devices, including\n>> the Pixel3a and OnePlus6 where the impact is very visible. With a solution like that we'd fix\n>> https://bugs.libcamera.org/show_bug.cgi?id=284\n> I tested it on TI AM69 with imx219 and different resolutions.  I used\n> Bryan's v3 with \"Fix neighbouring positions in 8-bit debayering\" patch\n> dropped.  It seems to do the job but there are sometimes reddish\n> horizontal or vertical lines throughout the image (not only at the\n> edges).\n\nInteresting - not something I'd expect from the patch or saw myself yet. \nJust to be sure: did you also have \nhttps://gitlab.freedesktop.org/rmader/libcamera/-/commit/3cec858139bfae2d840821813ccd2bb5eefaf8ff \nfrom my \nhttps://gitlab.freedesktop.org/rmader/libcamera/-/commits/v0.5.2-gpuisp-v3a-fixes \nbranch applied? Because without that we'd expect random glitches on \narm64 devices.","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 F1979BE080\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 29 Oct 2025 14:43:30 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0812260865;\n\tWed, 29 Oct 2025 15:43:30 +0100 (CET)","from sender4-pp-f112.zoho.com (sender4-pp-f112.zoho.com\n\t[136.143.188.112])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0383360453\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 29 Oct 2025 15:43:27 +0100 (CET)","by mx.zohomail.com with SMTPS id 1761749001499106.57586867479927; \n\tWed, 29 Oct 2025 07:43:21 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=collabora.com\n\theader.i=robert.mader@collabora.com header.b=\"kRsJ2oTD\"; \n\tdkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1761749004; cv=none; \n\td=zohomail.com; s=zohoarc; \n\tb=l+NqL7z8FWqsvL+uovmBr9U0uDX6WTZ8OG4nC8ebsMFDtmi9syf+fG08SiRsEgX9jJDWIRsc4VuruOZvuHw/Ar/dmY6HCF+5xbHpvgX9xqR/q+OniwG52Vui8YESUDKKJUtiDM6bgscRShbA8242xZMrDuAKZfI8UVHOQUbIn2U=","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; \n\ts=zohoarc; t=1761749004;\n\th=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To;\n\tbh=CQmJfroObPTPXOAoOFFSeMWRU4sugZpVwVI1mkEekbw=; \n\tb=ZZhHpXLvs1LvHp6Ze9ciBiT25bIKiOEegtBi9sRLJSuoaXHGRDIu/tVFS1bTIiwuGdQhXED5v3Fe9lgWUuJ1llDVPxiBXSsNtB8Nc3xii9UYnGpofQYgXv8UPGZP2YzY0Rt43A7S/ifjlJT3Y5G5Xk28yxGu8b6UFJnD9XQRkHw=","ARC-Authentication-Results":"i=1; mx.zohomail.com;\n\tdkim=pass  header.i=collabora.com;\n\tspf=pass  smtp.mailfrom=robert.mader@collabora.com;\n\tdmarc=pass header.from=<robert.mader@collabora.com>","DKIM-Signature":"v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1761749004;\n\ts=zohomail; d=collabora.com; i=robert.mader@collabora.com;\n\th=Message-ID:Date:Date:MIME-Version:Subject:Subject:To:To:Cc:Cc:References:From:From:In-Reply-To:Content-Type:Content-Transfer-Encoding:Message-Id:Reply-To;\n\tbh=CQmJfroObPTPXOAoOFFSeMWRU4sugZpVwVI1mkEekbw=;\n\tb=kRsJ2oTDNZ+zLugY3MvFh37fyZHh9a+E/Jz25e3Ukt8bGzRO/oZjUNSWLbVaweLe\n\tykvOQ490kWazMjue6fWpITGgaTYQ+nYZ3jaO837LksXV3E2IEtBoEVMiM2Xd8AvCNDN\n\t7Tp2Xt+zCXgAbo3Brd3/6wXBGKk/gyyRWLW0zIsw=","Message-ID":"<0941135c-b0dc-4564-bd15-9d7f2a5e075a@collabora.com>","Date":"Wed, 29 Oct 2025 15:43:16 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","To":"Milan Zamazal <mzamazal@redhat.com>","Cc":"libcamera-devel@lists.libcamera.org","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-34-bryan.odonoghue@linaro.org>\n\t<e5b2976e-b1a3-4226-959b-2e3c320b8952@collabora.com>\n\t<3ce28579-54d3-4645-a002-99f0c5f9ecb8@collabora.com>\n\t<9359f33d-f95a-4132-af3d-07083b542d26@collabora.com>\n\t<857bwdaix7.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","Content-Language":"en-US, de-DE","From":"Robert Mader <robert.mader@collabora.com>","In-Reply-To":"<857bwdaix7.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>"}},{"id":36540,"web_url":"https://patchwork.libcamera.org/comment/36540/","msgid":"<85zf998zyx.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2025-10-29T15:55:18","subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Robert Mader <robert.mader@collabora.com> writes:\n\n> Hi Milan,\n>\n> On 10/29/25 15:20, Milan Zamazal wrote:\n>> Hi Robert,\n>>\n>> Robert Mader <robert.mader@collabora.com> writes:\n>>\n>>> Hey, I had a another look into the scaling issue and pushed a WIP patch here:\n>>> https://gitlab.freedesktop.org/rmader/libcamera/-/commit/048cc02e295fd82f6ecfb1d5c6280a21709cd0d5 /\n>>> https://gitlab.freedesktop.org/rmader/libcamera/-/commits/v0.5.2-gpuisp-v3a-fixes\n>>>\n>>> With that one I get the expected full (or only cropped on one axis) image. I tested on multiple\n>>> devices, including\n>>> the Pixel3a and OnePlus6 where the impact is very visible. With a solution like that we'd fix\n>>> https://bugs.libcamera.org/show_bug.cgi?id=284\n>> I tested it on TI AM69 with imx219 and different resolutions.  I used\n>> Bryan's v3 with \"Fix neighbouring positions in 8-bit debayering\" patch\n>> dropped.  It seems to do the job but there are sometimes reddish\n>> horizontal or vertical lines throughout the image (not only at the\n>> edges).\n>\n> Interesting - not something I'd expect from the patch or saw myself yet. Just to be sure: did you also\n> have https://gitlab.freedesktop.org/rmader/libcamera/-/commit/3cec858139bfae2d840821813ccd2bb5eefaf8ff\n> from my https://gitlab.freedesktop.org/rmader/libcamera/-/commits/v0.5.2-gpuisp-v3a-fixes branch applied?\n> Because without that we'd expect random glitches on arm64 devices.\n\nYes, I copied whole\nhttps://gitlab.freedesktop.org/rmader/libcamera/-/blob/048cc02e295fd82f6ecfb1d5c6280a21709cd0d5/src/libcamera/software_isp/debayer_egl.cpp\n\nAttaching a tiny crop showing a sample of a reddish horizontal line that\nI get when running `cam -s width=2000,height=1400' with a file output:\nI think I use to get something similar when debayering is one pixel off\nat the given place.  Note that I'm running on an unpacked input,\ni.e. bayer_unpacked.{vert,frag} shaders.","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 48EE0BE080\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 29 Oct 2025 15:55:29 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 63E0260869;\n\tWed, 29 Oct 2025 16:55:28 +0100 (CET)","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 66CDD60453\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 29 Oct 2025 16:55:26 +0100 (CET)","from mail-ed1-f70.google.com (mail-ed1-f70.google.com\n\t[209.85.208.70]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-50-Bv7Us9jEMZiwL77VIBkRlg-1; Wed, 29 Oct 2025 11:55:22 -0400","by mail-ed1-f70.google.com with SMTP id\n\t4fb4d7f45d1cf-63c169b6072so6808387a12.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 29 Oct 2025 08:55:22 -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\t4fb4d7f45d1cf-63e7ef6c0e9sm11929191a12.1.2025.10.29.08.55.19\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tWed, 29 Oct 2025 08:55:19 -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=\"IebK7EW/\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1761753324;\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=6N+fEKDbfxTm5G+G2nJRpJ4buHKg4TIkfMOPqEEBWko=;\n\tb=IebK7EW/Thy0Xc+HsbJ1r1/aDwyCRfXdOTYzHaHeenOpoAx1YxAQn+TGVycFk7tfwrZVPe\n\t7Iv1e4pXCgZsq9ga7V6G6Z7SKbl4tcwziezdlOAIC10RF6SDGC4hkcqJUjHrblmBamcg0S\n\t6CfsFVUKwknDgFIdfPxwgohB2Hmak9s=","X-MC-Unique":"Bv7Us9jEMZiwL77VIBkRlg-1","X-Mimecast-MFC-AGG-ID":"Bv7Us9jEMZiwL77VIBkRlg_1761753321","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1761753321; x=1762358121;\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=6N+fEKDbfxTm5G+G2nJRpJ4buHKg4TIkfMOPqEEBWko=;\n\tb=N3HaxSY2kab7dc4PW39+tclm+62GUGrltDY94J6/g2I8LUfmugChehrGkG8LOpIFQh\n\tO5u4Dq3Egwem60+3MZOGtdRO3IV/I9/gCZaLZI+uzHtourVLMgOTqyC0+wS+UX7PAb0d\n\tvhaW0HV0G4JbU4tHvCME5j73gO4k/Kr3Bt6Rqyl+CbXyXbzHiL5+cMV0FkjA3jQlzoia\n\t6CwZW0+W3hjcaU+r3Sl/N3q77gnsKswrpKBl2M+pKw7yo5V5lMMJT9IH3RhAp8SdkfPU\n\ta+XdRGOyycgURHpHjrK3+g4s+Ky59zRy9xVJDRop/xcuqbbGWBNGyjK2jbAPUb16ijsP\n\tWhdw==","X-Gm-Message-State":"AOJu0YwMZqkriK/cTMun5bSpqFCdoOOkUo3nGuDkCikjybTcztnJemZh\n\tER4DEjWpb+9DWz8RtrdiwO992x8yzP7TjYRrHOcfR7sVXuPHzQKX7nY6alFWPD2TGNKgiZKwngq\n\t6wX/HTWx3fFc2NCw8ANsRlYQXCB0Z4kJ4EqzxcnZgLKS1KtXVqMX+ktjR7UdwO2gnfrWVENGA6y\n\tQ9t4cyWzvLPJR1K8K/h02Rxb7jKj8rO+Uack9RO0Opixr9aGtiurEg/2L76Xg=","X-Gm-Gg":"ASbGnctn4xshJdfmtOIM0dOEe7A28lG7jvHcejJw2kY7LqR45W7/txr+Vqx5LRgZYgj\n\thueGBDjKOVZX8tfhFnaYZJFIsy7O9Ljr3WezuUlF/iRsfIX22Mkv9SQsAdPOGuXYfl6wIJw8B6p\n\torZQ2bJhFcrGtxqCbNgK8Qg3YXtaLbuLzPiFe7edpAXVXJnBloARjgAZJIR36kPDiTGQuntfp6F\n\t7GrnJEgnyQpul1pM9rZf5JuJROyBfYDlOXl4J7rptHQ0wl5xmESdp4wKLpSCnt9EGckyaGmxEz3\n\tMViduk+LJuPB/h2oeczOmQoAlvZO9sZ6/dD1X8TFErCNLSptmlb/n3Sk8IYOuZQP1O83p3PXR/C\n\t6dTyoKqdu/PlrDDbRvjrfCOUxmzXcoL9aa1gJsUHXrdz302qjbWMG","X-Received":["by 2002:a05:6402:35d5:b0:639:f54d:9274 with SMTP id\n\t4fb4d7f45d1cf-64044188b79mr2633318a12.2.1761753320645; \n\tWed, 29 Oct 2025 08:55:20 -0700 (PDT)","by 2002:a05:6402:35d5:b0:639:f54d:9274 with SMTP id\n\t4fb4d7f45d1cf-64044188b79mr2633296a12.2.1761753320042; \n\tWed, 29 Oct 2025 08:55:20 -0700 (PDT)"],"X-Google-Smtp-Source":"AGHT+IH3lbm4G0EFqzqWmwS0pKDw7AQktn5xRjo4EB0NlV9QP5swj35v1dN5dSmZa7bktXo3vX3dSg==","From":"Milan Zamazal <mzamazal@redhat.com>","To":"Robert Mader <robert.mader@collabora.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH v3 33/39] libcamera: software_isp: debayer_egl: Add an\n\teGL debayer class","In-Reply-To":"<0941135c-b0dc-4564-bd15-9d7f2a5e075a@collabora.com> (Robert\n\tMader's message of \"Wed, 29 Oct 2025 15:43:16 +0100\")","References":"<20251015012251.17508-1-bryan.odonoghue@linaro.org>\n\t<20251015012251.17508-34-bryan.odonoghue@linaro.org>\n\t<e5b2976e-b1a3-4226-959b-2e3c320b8952@collabora.com>\n\t<3ce28579-54d3-4645-a002-99f0c5f9ecb8@collabora.com>\n\t<9359f33d-f95a-4132-af3d-07083b542d26@collabora.com>\n\t<857bwdaix7.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>\n\t<0941135c-b0dc-4564-bd15-9d7f2a5e075a@collabora.com>","Date":"Wed, 29 Oct 2025 16:55:18 +0100","Message-ID":"<85zf998zyx.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":"ptwKcYVwBz1cxYpD_FO5itJoLu12CI4HsxhlfQlhpoM_1761753321","X-Mimecast-Originator":"redhat.com","Content-Type":"multipart/mixed; boundary=\"=-=-=\"","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>"}}]