[{"id":37423,"web_url":"https://patchwork.libcamera.org/comment/37423/","msgid":"<b569d8ea-1ba7-4d16-b9db-035c841afad3@collabora.com>","date":"2025-12-17T10:34:10","subject":"Re: [PATCH v9 14/26] 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":"Reviewed-by: Robert Mader <robert.mader@collabora.com>\n\nOn 17.12.25 11:01, Bryan O'Donoghue wrote:\n> Add a class to run the existing glsl debayer shaders on a GBM surface.\n>\n> Signed-off-by: Robert Mader <robert.mader@collabora.com>\n> Co-developed-by: Robert Mader <robert.mader@collabora.com>\n> [bod: took scaling and buffer size fixes from Robert]\n> [bod: took fix for center byte calculation from Hans]\n> [bod: took formatting fixes from Milan]\n> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>\n> Co-developed-by: Milan Zamazal <mzamazal@redhat.com>\n> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> ---\n>   src/libcamera/software_isp/debayer_egl.cpp | 650 +++++++++++++++++++++\n>   src/libcamera/software_isp/debayer_egl.h   | 141 +++++\n>   src/libcamera/software_isp/meson.build     |   8 +\n>   3 files changed, 799 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 000000000..9d7134f2a\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.cpp\n> @@ -0,0 +1,650 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Linaro Ltd.\n> + *\n> + * Authors:\n> + * Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> + *\n> + */\n> +\n> +#include \"debayer_egl.h\"\n> +\n> +#include <cmath>\n> +#include <stdlib.h>\n> +#include <time.h>\n> +\n> +#include <libcamera/base/utils.h>\n> +\n> +#include <libcamera/formats.h>\n> +\n> +#include \"../glsl_shaders.h\"\n> +\n> +namespace libcamera {\n> +\n> +/**\n> + * \\class DebayerEGL\n> + * \\brief Class for debayering using an EGL Shader\n> + *\n> + * Implements an EGL shader based debayering solution.\n> + */\n> +\n> +/**\n> + * \\fn DebayerEGL::DebayerEGL(std::unique_ptr<SwStatsCpu> stats, const GlobalConfiguration &configuration)\n> + * \\brief Construct a DebayerEGL object\n> + * \\param[in] stats Statistics processing object\n> + * \\param[in] configuration Global configuration reference\n> + */\n> +DebayerEGL::DebayerEGL(std::unique_ptr<SwStatsCpu> stats, const GlobalConfiguration &configuration)\n> +\t: Debayer(configuration), stats_(std::move(stats))\n> +{\n> +}\n> +\n> +DebayerEGL::~DebayerEGL()\n> +{\n> +}\n> +\n> +int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config)\n> +{\n> +\tBayerFormat bayerFormat =\n> +\t\tBayerFormat::fromPixelFormat(inputFormat);\n> +\n> +\tif ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10) &&\n> +\t    bayerFormat.packing == BayerFormat::Packing::None &&\n> +\t    isStandardBayerOrder(bayerFormat.order)) {\n> +\t\tconfig.bpp = (bayerFormat.bitDepth + 7) & ~7;\n> +\t\tconfig.patternSize.width = 2;\n> +\t\tconfig.patternSize.height = 2;\n> +\t\tconfig.outputFormats = std::vector<PixelFormat>({ formats::XRGB8888,\n> +\t\t\t\t\t\t\t\t  formats::ARGB8888,\n> +\t\t\t\t\t\t\t\t  formats::XBGR8888,\n> +\t\t\t\t\t\t\t\t  formats::ABGR8888 });\n> +\t\treturn 0;\n> +\t}\n> +\n> +\tif (bayerFormat.bitDepth == 10 &&\n> +\t    bayerFormat.packing == BayerFormat::Packing::CSI2 &&\n> +\t    isStandardBayerOrder(bayerFormat.order)) {\n> +\t\tconfig.bpp = 10;\n> +\t\tconfig.patternSize.width = 4; /* 5 bytes per *4* pixels */\n> +\t\tconfig.patternSize.height = 2;\n> +\t\tconfig.outputFormats = std::vector<PixelFormat>({ formats::XRGB8888,\n> +\t\t\t\t\t\t\t\t  formats::ARGB8888,\n> +\t\t\t\t\t\t\t\t  formats::XBGR8888,\n> +\t\t\t\t\t\t\t\t  formats::ABGR8888 });\n> +\t\treturn 0;\n> +\t}\n> +\n> +\tLOG(Debayer, Error)\n> +\t\t<< \"Unsupported input format \" << inputFormat.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> +\tccmUniformDataIn_ = glGetUniformLocation(programId_, \"ccm\");\n> +\tblackLevelUniformDataIn_ = glGetUniformLocation(programId_, \"blacklevel\");\n> +\tgammaUniformDataIn_ = glGetUniformLocation(programId_, \"gamma\");\n> +\tcontrastUniformDataIn_ = glGetUniformLocation(programId_, \"contrast\");\n> +\n> +\ttextureUniformStep_ = glGetUniformLocation(programId_, \"tex_step\");\n> +\ttextureUniformSize_ = glGetUniformLocation(programId_, \"tex_size\");\n> +\ttextureUniformStrideFactor_ = glGetUniformLocation(programId_, \"stride_factor\");\n> +\ttextureUniformBayerFirstRed_ = glGetUniformLocation(programId_, \"tex_bayer_first_red\");\n> +\ttextureUniformProjMatrix_ = glGetUniformLocation(programId_, \"proj_matrix\");\n> +\n> +\tLOG(Debayer, Debug) << \"vertexIn \" << attributeVertex_ << \" textureIn \" << attributeTexture_\n> +\t\t\t    << \" tex_y \" << textureUniformBayerDataIn_\n> +\t\t\t    << \" ccm \" << ccmUniformDataIn_\n> +\t\t\t    << \" blacklevel \" << blackLevelUniformDataIn_\n> +\t\t\t    << \" gamma \" << gammaUniformDataIn_\n> +\t\t\t    << \" contrast \" << contrastUniformDataIn_\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> +\tconst unsigned char *fragmentShaderData;\n> +\tunsigned int vertexShaderDataLen;\n> +\tconst unsigned 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/*\n> +\t * Tell shaders how to re-order output taking account of how the\n> +\t * pixels are actually stored by GBM\n> +\t */\n> +\tswitch (outputFormat) {\n> +\tcase formats::ARGB8888:\n> +\tcase formats::XRGB8888:\n> +\t\tbreak;\n> +\tcase formats::ABGR8888:\n> +\tcase formats::XBGR8888:\n> +\t\tegl_.pushEnv(shaderEnv, \"#define SWAP_BLUE\");\n> +\t\tbreak;\n> +\tdefault:\n> +\t\tgoto invalid_fmt;\n> +\t}\n> +\n> +\t/* Pixel location parameters */\n> +\tglFormat_ = GL_LUMINANCE;\n> +\tbytesPerPixel_ = 1;\n> +\tshaderStridePixels_ = inputConfig_.stride;\n> +\n> +\tswitch (inputFormat) {\n> +\tcase libcamera::formats::SBGGR8:\n> +\tcase libcamera::formats::SBGGR10_CSI2P:\n> +\tcase libcamera::formats::SBGGR12_CSI2P:\n> +\t\tfirstRed_x_ = 1.0;\n> +\t\tfirstRed_y_ = 1.0;\n> +\t\tbreak;\n> +\tcase libcamera::formats::SGBRG8:\n> +\tcase libcamera::formats::SGBRG10_CSI2P:\n> +\tcase libcamera::formats::SGBRG12_CSI2P:\n> +\t\tfirstRed_x_ = 0.0;\n> +\t\tfirstRed_y_ = 1.0;\n> +\t\tbreak;\n> +\tcase libcamera::formats::SGRBG8:\n> +\tcase libcamera::formats::SGRBG10_CSI2P:\n> +\tcase libcamera::formats::SGRBG12_CSI2P:\n> +\t\tfirstRed_x_ = 1.0;\n> +\t\tfirstRed_y_ = 0.0;\n> +\t\tbreak;\n> +\tcase libcamera::formats::SRGGB8:\n> +\tcase libcamera::formats::SRGGB10_CSI2P:\n> +\tcase libcamera::formats::SRGGB12_CSI2P:\n> +\t\tfirstRed_x_ = 0.0;\n> +\t\tfirstRed_y_ = 0.0;\n> +\t\tbreak;\n> +\tdefault:\n> +\t\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\tshaderStridePixels_ = width_;\n> +\t\t}\n> +\t\tbreak;\n> +\tcase libcamera::formats::SBGGR12_CSI2P:\n> +\tcase libcamera::formats::SGBRG12_CSI2P:\n> +\tcase libcamera::formats::SGRBG12_CSI2P:\n> +\tcase libcamera::formats::SRGGB12_CSI2P:\n> +\t\tegl_.pushEnv(shaderEnv, \"#define RAW12P\");\n> +\t\tif (BayerFormat::fromPixelFormat(inputFormat).packing == BayerFormat::Packing::None) {\n> +\t\t\tfragmentShaderData = bayer_unpacked_frag;\n> +\t\t\tfragmentShaderDataLen = bayer_unpacked_frag_len;\n> +\t\t\tvertexShaderData = bayer_unpacked_vert;\n> +\t\t\tvertexShaderDataLen = bayer_unpacked_vert_len;\n> +\t\t\tglFormat_ = GL_RG;\n> +\t\t\tbytesPerPixel_ = 2;\n> +\t\t} else {\n> +\t\t\tfragmentShaderData = bayer_1x_packed_frag;\n> +\t\t\tfragmentShaderDataLen = bayer_1x_packed_frag_len;\n> +\t\t\tvertexShaderData = identity_vert;\n> +\t\t\tvertexShaderDataLen = identity_vert_len;\n> +\t\t\tshaderStridePixels_ = width_;\n> +\t\t}\n> +\t\tbreak;\n> +\tdefault:\n> +\t\tgoto invalid_fmt;\n> +\t\tbreak;\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> +/**\n> + * \\brief Get the output frame size\n> + *\n> + * \\return The output frame size\n> + */\n> +unsigned int DebayerEGL::frameSize()\n> +{\n> +\treturn outputConfig_.frameSize;\n> +}\n> +\n> +int DebayerEGL::configure(const StreamConfiguration &inputCfg,\n> +\t\t\t  const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,\n> +\t\t\t  bool ccmEnabled)\n> +{\n> +\tif (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0)\n> +\t\treturn -EINVAL;\n> +\n> +\tif (stats_->configure(inputCfg) != 0)\n> +\t\treturn -EINVAL;\n> +\n> +\tif (!ccmEnabled)\n> +\t\treturn -EINVAL;\n> +\n> +\tconst Size &stats_pattern_size = stats_->patternSize();\n> +\tif (inputConfig_.patternSize.width != stats_pattern_size.width ||\n> +\t    inputConfig_.patternSize.height != stats_pattern_size.height) {\n> +\t\tLOG(Debayer, Error)\n> +\t\t\t<< \"mismatching stats and debayer pattern sizes for \"\n> +\t\t\t<< inputCfg.pixelFormat.toString();\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tinputConfig_.stride = inputCfg.stride;\n> +\tinputPixelFormat_ = inputCfg.pixelFormat;\n> +\twidth_ = inputCfg.size.width;\n> +\theight_ = inputCfg.size.height;\n> +\n> +\tif (outputCfgs.size() != 1) {\n> +\t\tLOG(Debayer, Error)\n> +\t\t\t<< \"Unsupported number of output streams: \"\n> +\t\t\t<< outputCfgs.size();\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tStreamConfiguration &outputCfg = outputCfgs[0];\n> +\tSizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size);\n> +\tstd::tie(outputConfig_.stride, outputConfig_.frameSize) =\n> +\t\tstrideAndFrameSize(outputCfg.pixelFormat, outputCfg.size);\n> +\n> +\tif (!outSizeRange.contains(outputCfg.size) || outputConfig_.stride != outputCfg.stride) {\n> +\t\tLOG(Debayer, Error)\n> +\t\t\t<< \"Invalid output size/stride: \"\n> +\t\t\t<< \"\\n  \" << outputCfg.size << \" (\" << outSizeRange << \")\"\n> +\t\t\t<< \"\\n  \" << outputCfg.stride << \" (\" << outputConfig_.stride << \")\";\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\toutputPixelFormat_ = outputCfg.pixelFormat;\n> +\toutputSize_ = outputCfg.size;\n> +\n> +\twindow_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) &\n> +\t\t    ~(inputConfig_.patternSize.width - 1);\n> +\twindow_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) &\n> +\t\t    ~(inputConfig_.patternSize.height - 1);\n> +\twindow_.width = outputCfg.size.width;\n> +\twindow_.height = outputCfg.size.height;\n> +\n> +\t/*\n> +\t * Don't pass x,y from window_ since process() already adjusts for it.\n> +\t * But crop the window to 2/3 of its width and height for speedup.\n> +\t */\n> +\tstats_->setWindow(Rectangle(window_.size()));\n> +\n> +\treturn 0;\n> +}\n> +\n> +Size DebayerEGL::patternSize(PixelFormat inputFormat)\n> +{\n> +\tDebayerEGL::DebayerInputConfig config;\n> +\n> +\tif (getInputConfig(inputFormat, config) != 0)\n> +\t\treturn {};\n> +\n> +\treturn config.patternSize;\n> +}\n> +\n> +std::vector<PixelFormat> DebayerEGL::formats(PixelFormat inputFormat)\n> +{\n> +\tDebayerEGL::DebayerInputConfig config;\n> +\n> +\tif (getInputConfig(inputFormat, config) != 0)\n> +\t\treturn std::vector<PixelFormat>();\n> +\n> +\treturn config.outputFormats;\n> +}\n> +\n> +std::tuple<unsigned int, unsigned int>\n> +DebayerEGL::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)\n> +{\n> +\tDebayerEGL::DebayerOutputConfig config;\n> +\n> +\tif (getOutputConfig(outputFormat, config) != 0)\n> +\t\treturn std::make_tuple(0, 0);\n> +\n> +\t/* Align stride to 256 bytes as a generic GPU memory access alignment */\n> +\tunsigned int stride = libcamera::utils::alignUp(size.width * config.bpp / 8, 256);\n> +\n> +\treturn std::make_tuple(stride, stride * size.height);\n> +}\n> +\n> +void DebayerEGL::setShaderVariableValues(DebayerParams &params)\n> +{\n> +\t/*\n> +\t * Raw Bayer 8-bit, and packed raw Bayer 10-bit/12-bit formats\n> +\t * are stored in a GL_LUMINANCE texture. The texture width is\n> +\t * equal to the stride.\n> +\t */\n> +\tGLfloat firstRed[] = { firstRed_x_, firstRed_y_ };\n> +\tGLfloat imgSize[] = { (GLfloat)width_,\n> +\t\t\t      (GLfloat)height_ };\n> +\tGLfloat Step[] = { static_cast<float>(bytesPerPixel_) / (inputConfig_.stride - 1),\n> +\t\t\t   1.0f / (height_ - 1) };\n> +\tGLfloat Stride = (GLfloat)width_ / (shaderStridePixels_ / bytesPerPixel_);\n> +\t/*\n> +\t * Scale input to output size, keeping the aspect ratio and preferring\n> +\t * cropping over black bars.\n> +\t */\n> +\tGLfloat scale = std::max((GLfloat)window_.width / width_,\n> +\t\t\t\t (GLfloat)window_.height / height_);\n> +\tGLfloat trans = -(1.0f - scale);\n> +\tGLfloat projMatrix[] = {\n> +\t\tscale, 0, 0, 0,\n> +\t\t0, scale, 0, 0,\n> +\t\t0, 0, 1, 0,\n> +\t\ttrans, trans, 0, 1\n> +\t};\n> +\n> +\t/* vertexIn - bayer_8.vert */\n> +\tglEnableVertexAttribArray(attributeVertex_);\n> +\tglVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE,\n> +\t\t\t      2 * sizeof(GLfloat), vcoordinates);\n> +\n> +\t/* textureIn - bayer_8.vert */\n> +\tglEnableVertexAttribArray(attributeTexture_);\n> +\tglVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE,\n> +\t\t\t      2 * sizeof(GLfloat), tcoordinates);\n> +\n> +\t/*\n> +\t * Set the sampler2D to the respective texture unit for each texutre\n> +\t * To simultaneously sample multiple textures we need to use multiple\n> +\t * texture units\n> +\t */\n> +\tglUniform1i(textureUniformBayerDataIn_, eglImageBayerIn_->texture_unit_uniform_id_);\n> +\n> +\t/*\n> +\t * These values are:\n> +\t * firstRed = tex_bayer_first_red - bayer_8.vert\n> +\t * imgSize = tex_size - bayer_8.vert\n> +\t * step = tex_step - bayer_8.vert\n> +\t * Stride = stride_factor identity.vert\n> +\t * textureUniformProjMatri = No scaling\n> +\t */\n> +\tglUniform2fv(textureUniformBayerFirstRed_, 1, firstRed);\n> +\tglUniform2fv(textureUniformSize_, 1, imgSize);\n> +\tglUniform2fv(textureUniformStep_, 1, Step);\n> +\tglUniform1f(textureUniformStrideFactor_, Stride);\n> +\tglUniformMatrix4fv(textureUniformProjMatrix_, 1, GL_FALSE, projMatrix);\n> +\n> +\tLOG(Debayer, Debug) << \"vertexIn \" << attributeVertex_ << \" textureIn \" << attributeTexture_\n> +\t\t\t    << \" tex_y \" << textureUniformBayerDataIn_\n> +\t\t\t    << \" tex_step \" << textureUniformStep_\n> +\t\t\t    << \" tex_size \" << textureUniformSize_\n> +\t\t\t    << \" stride_factor \" << textureUniformStrideFactor_\n> +\t\t\t    << \" tex_bayer_first_red \" << textureUniformBayerFirstRed_;\n> +\n> +\tLOG(Debayer, Debug) << \"textureUniformY_ = 0 \"\n> +\t\t\t    << \" firstRed.x \" << firstRed[0]\n> +\t\t\t    << \" firstRed.y \" << firstRed[1]\n> +\t\t\t    << \" textureUniformSize_.width \" << imgSize[0]\n> +\t\t\t    << \" textureUniformSize_.height \" << imgSize[1]\n> +\t\t\t    << \" textureUniformStep_.x \" << Step[0]\n> +\t\t\t    << \" textureUniformStep_.y \" << Step[1]\n> +\t\t\t    << \" textureUniformStrideFactor_ \" << Stride\n> +\t\t\t    << \" textureUniformProjMatrix_ \" << textureUniformProjMatrix_;\n> +\n> +\tGLfloat ccm[9] = {\n> +\t\tparams.ccm[0][0],\n> +\t\tparams.ccm[0][1],\n> +\t\tparams.ccm[0][2],\n> +\t\tparams.ccm[1][0],\n> +\t\tparams.ccm[1][1],\n> +\t\tparams.ccm[1][2],\n> +\t\tparams.ccm[2][0],\n> +\t\tparams.ccm[2][1],\n> +\t\tparams.ccm[2][2],\n> +\t};\n> +\tglUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm);\n> +\tLOG(Debayer, Debug) << \" ccmUniformDataIn_ \" << ccmUniformDataIn_ << \" data \" << params.ccm;\n> +\n> +\t/*\n> +\t * 0 = Red, 1 = Green, 2 = Blue\n> +\t */\n> +\tglUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]);\n> +\tLOG(Debayer, Debug) << \" blackLevelUniformDataIn_ \" << blackLevelUniformDataIn_ << \" data \" << params.blackLevel;\n> +\n> +\t/*\n> +\t * Gamma\n> +\t */\n> +\tglUniform1f(gammaUniformDataIn_, params.gamma);\n> +\tLOG(Debayer, Debug) << \" gammaUniformDataIn_ \" << gammaUniformDataIn_ << \" data \" << params.gamma;\n> +\n> +\t/*\n> +\t * Contrast\n> +\t */\n> +\tglUniform1f(contrastUniformDataIn_, params.contrast);\n> +\tLOG(Debayer, Debug) << \" contrastUniformDataIn_ \" << contrastUniformDataIn_ << \" data \" << params.contrast;\n> +\n> +\treturn;\n> +}\n> +\n> +int DebayerEGL::debayerGPU(MappedFrameBuffer &in, int out_fd, DebayerParams &params)\n> +{\n> +\t/* eGL context switch */\n> +\tegl_.makeCurrent();\n> +\n> +\t/* Create a standard texture input */\n> +\tegl_.createTexture2D(eglImageBayerIn_, glFormat_, inputConfig_.stride / bytesPerPixel_, height_, in.planes()[0].data());\n> +\n> +\t/* Generate the output render framebuffer as render to texture */\n> +\tegl_.createOutputDMABufTexture2D(eglImageBayerOut_, out_fd);\n> +\n> +\tsetShaderVariableValues(params);\n> +\tglViewport(0, 0, width_, height_);\n> +\tglClear(GL_COLOR_BUFFER_BIT);\n> +\tglDrawArrays(GL_TRIANGLE_FAN, 0, DEBAYER_OPENGL_COORDS);\n> +\n> +\tGLenum err = glGetError();\n> +\tif (err != GL_NO_ERROR) {\n> +\t\tLOG(eGL, Error) << \"Drawing scene fail \" << err;\n> +\t\treturn -ENODEV;\n> +\t} else {\n> +\t\tegl_.syncOutput();\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params)\n> +{\n> +\tbench_.startFrame();\n> +\n> +\tstd::vector<DmaSyncer> dmaSyncers;\n> +\n> +\tdmaSyncBegin(dmaSyncers, input, nullptr);\n> +\n> +\tsetParams(params);\n> +\n> +\t/* Copy metadata from the input buffer */\n> +\tFrameMetadata &metadata = output->_d()->metadata();\n> +\tmetadata.status = input->metadata().status;\n> +\tmetadata.sequence = input->metadata().sequence;\n> +\tmetadata.timestamp = input->metadata().timestamp;\n> +\n> +\tMappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read);\n> +\tif (!in.isValid()) {\n> +\t\tLOG(Debayer, Error) << \"mmap-ing buffer(s) failed\";\n> +\t\tgoto error;\n> +\t}\n> +\n> +\tif (debayerGPU(in, output->planes()[0].fd.get(), params)) {\n> +\t\tLOG(Debayer, Error) << \"debayerGPU failed\";\n> +\t\tgoto error;\n> +\t}\n> +\n> +\tbench_.finishFrame();\n> +\n> +\tmetadata.planes()[0].bytesused = output->planes()[0].length;\n> +\n> +\t/* Calculate stats for the whole frame */\n> +\tstats_->processFrame(frame, 0, input);\n> +\tdmaSyncers.clear();\n> +\n> +\toutputBufferReady.emit(output);\n> +\tinputBufferReady.emit(input);\n> +\n> +\treturn;\n> +\n> +error:\n> +\tbench_.finishFrame();\n> +\tmetadata.status = FrameMetadata::FrameError;\n> +\treturn;\n> +}\n> +\n> +int DebayerEGL::start()\n> +{\n> +\tGLint maxTextureImageUnits;\n> +\n> +\tif (gbmSurface_.createDevice())\n> +\t\treturn -ENODEV;\n> +\n> +\tif (egl_.initEGLContext(&gbmSurface_))\n> +\t\treturn -ENODEV;\n> +\n> +\tglGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);\n> +\n> +\tLOG(Debayer, Debug) << \"Available fragment shader texture units \" << maxTextureImageUnits;\n> +\n> +\t/* Raw bayer input as texture */\n> +\teglImageBayerIn_ = std::make_unique<eGLImage>(width_, height_, 32, inputConfig_.stride, GL_TEXTURE0, 0);\n> +\n> +\t/* Texture we will render to */\n> +\teglImageBayerOut_ = std::make_unique<eGLImage>(outputSize_.width, outputSize_.height, 31, outputConfig_.stride, GL_TEXTURE1, 1);\n> +\n> +\tif (initBayerShaders(inputPixelFormat_, outputPixelFormat_))\n> +\t\treturn -EINVAL;\n> +\n> +\treturn 0;\n> +}\n> +\n> +void DebayerEGL::stop()\n> +{\n> +\teglImageBayerOut_.release();\n> +\teglImageBayerIn_.release();\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 000000000..c0a68fd35\n> --- /dev/null\n> +++ b/src/libcamera/software_isp/debayer_egl.h\n> @@ -0,0 +1,141 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2025, Bryan O'Donoghue.\n> + *\n> + * Authors:\n> + * Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> + *\n> + */\n> +\n> +#pragma once\n> +\n> +#include <memory>\n> +#include <stdint.h>\n> +#include <vector>\n> +\n> +#define GL_GLEXT_PROTOTYPES\n> +#define EGL_EGLEXT_PROTOTYPES\n> +#include <libcamera/base/object.h>\n> +\n> +#include \"libcamera/internal/bayer_format.h\"\n> +#include \"libcamera/internal/egl.h\"\n> +#include \"libcamera/internal/framebuffer.h\"\n> +#include \"libcamera/internal/mapped_framebuffer.h\"\n> +#include \"libcamera/internal/software_isp/benchmark.h\"\n> +#include \"libcamera/internal/software_isp/swstats_cpu.h\"\n> +\n> +#include <EGL/egl.h>\n> +#include <EGL/eglext.h>\n> +#include <GLES3/gl32.h>\n> +\n> +#include \"debayer.h\"\n> +\n> +namespace libcamera {\n> +\n> +#define DEBAYER_EGL_MIN_SIMPLE_RGB_GAIN_TEXTURE_UNITS 4\n> +#define DEBAYER_OPENGL_COORDS 4\n> +\n> +class DebayerEGL : public Debayer\n> +{\n> +public:\n> +\tDebayerEGL(std::unique_ptr<SwStatsCpu> stats, const GlobalConfiguration &configuration);\n> +\t~DebayerEGL();\n> +\n> +\tint configure(const StreamConfiguration &inputCfg,\n> +\t\t      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,\n> +\t\t      bool ccmEnabled);\n> +\n> +\tSize patternSize(PixelFormat inputFormat);\n> +\n> +\tstd::vector<PixelFormat> formats(PixelFormat input);\n> +\tstd::tuple<unsigned int, unsigned int> strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);\n> +\n> +\tvoid process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params);\n> +\tint start();\n> +\tvoid stop();\n> +\n> +\tconst SharedFD &getStatsFD() { return stats_->getStatsFD(); }\n> +\tunsigned int frameSize();\n> +\n> +\tSizeRange sizes(PixelFormat inputFormat, const Size &inputSize);\n> +\n> +private:\n> +\tstatic int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config);\n> +\tstatic int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config);\n> +\tint setupStandardBayerOrder(BayerFormat::Order order);\n> +\tvoid pushEnv(std::vector<std::string> &shaderEnv, const char *str);\n> +\tint initBayerShaders(PixelFormat inputFormat, PixelFormat outputFormat);\n> +\tint initEGLContext();\n> +\tint generateTextures();\n> +\tint compileShaderProgram(GLuint &shaderId, GLenum shaderType,\n> +\t\t\t\t unsigned char *shaderData, int shaderDataLen,\n> +\t\t\t\t std::vector<std::string> shaderEnv);\n> +\tint linkShaderProgram(void);\n> +\tint getShaderVariableLocations();\n> +\tvoid setShaderVariableValues(DebayerParams &params);\n> +\tvoid configureTexture(GLuint &texture);\n> +\tint debayerGPU(MappedFrameBuffer &in, int out_fd, DebayerParams &params);\n> +\n> +\t/* Shader program identifiers */\n> +\tGLuint vertexShaderId_;\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> +\tstd::unique_ptr<eGLImage> eglImageBayerIn_;\n> +\tstd::unique_ptr<eGLImage> eglImageBayerOut_;\n> +\n> +\t/* Shader parameters */\n> +\tfloat firstRed_x_;\n> +\tfloat firstRed_y_;\n> +\tGLint attributeVertex_;\n> +\tGLint attributeTexture_;\n> +\tGLint textureUniformStep_;\n> +\tGLint textureUniformSize_;\n> +\tGLint textureUniformStrideFactor_;\n> +\tGLint textureUniformBayerFirstRed_;\n> +\tGLint textureUniformProjMatrix_;\n> +\n> +\tGLint textureUniformBayerDataIn_;\n> +\n> +\t/* Represent per-frame CCM as a uniform vector of floats 3 x 3 */\n> +\tGLint ccmUniformDataIn_;\n> +\n> +\t/* Black Level compensation */\n> +\tGLint blackLevelUniformDataIn_;\n> +\n> +\t/* Gamma */\n> +\tGLint gammaUniformDataIn_;\n> +\n> +\t/* Contrast */\n> +\tGLint contrastUniformDataIn_;\n> +\n> +\tRectangle window_;\n> +\tstd::unique_ptr<SwStatsCpu> stats_;\n> +\teGL egl_;\n> +\tGBM gbmSurface_;\n> +\tuint32_t width_;\n> +\tuint32_t height_;\n> +\tGLint glFormat_;\n> +\tunsigned int bytesPerPixel_;\n> +\tuint32_t shaderStridePixels_;\n> +\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 59fa5f02a..c61ac7d59 100644\n> --- a/src/libcamera/software_isp/meson.build\n> +++ b/src/libcamera/software_isp/meson.build\n> @@ -2,6 +2,7 @@\n>   \n>   softisp_enabled = pipelines.contains('simple')\n>   summary({'SoftISP support' : softisp_enabled}, section : 'Configuration')\n> +summary({'SoftISP GPU acceleration' : gles_headless_enabled}, section : 'Configuration')\n>   \n>   if not softisp_enabled\n>       subdir_done()\n> @@ -14,3 +15,10 @@ libcamera_internal_sources += files([\n>       'software_isp.cpp',\n>       'swstats_cpu.cpp',\n>   ])\n> +\n> +if softisp_enabled and gles_headless_enabled\n> +    config_h.set('HAVE_DEBAYER_EGL', 1)\n> +    libcamera_internal_sources += files([\n> +        'debayer_egl.cpp',\n> +    ])\n> +endif","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 44983BD7D8\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 17 Dec 2025 10:34:22 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 2D71861A40;\n\tWed, 17 Dec 2025 11:34:21 +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 7527F61A2C\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 17 Dec 2025 11:34:19 +0100 (CET)","by mx.zohomail.com with SMTPS id 1765967653726809.2692993597782;\n\tWed, 17 Dec 2025 02:34:13 -0800 (PST)"],"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=\"DIQdH5eL\"; \n\tdkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1765967656; cv=none; \n\td=zohomail.com; s=zohoarc; \n\tb=PoVJm46R/aM1B7l7zllNgd9W2Ts7xNN1TG1EUnmjaW5vOlqLFDbw+CFb6M8BcZskp/jXYUxdR6IYzvUV30AfUfuxKw7605lD6xCHegMqgnVMXKcsB2pKDVZWhYkmebg/pCjHOYWnHgcCMYp5zz0obj8CiBtyXxzNZmBdjGg3KXE=","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; \n\ts=zohoarc; t=1765967656;\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=OE4bqopTmDVMbjPm/09gEHE8lU4qO2PUBrRpRtEuGN0=; \n\tb=I/bY+I/P+iXldaEkU54uSlwrDSaxGbrgNTbq48t9NQSBPvx2f0Er1FxIeb5wjlOwHRgsRfAu/VDiYEhgaa6NP3FcDS/95L8xNGigr6KCQVqAvdFlsb60x6KuPJNIqGYywj2ljTKWakaq7KcQX1lQsmdNqiCv+BMK3mwonyI3EIw=","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=1765967656;\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=OE4bqopTmDVMbjPm/09gEHE8lU4qO2PUBrRpRtEuGN0=;\n\tb=DIQdH5eLtos7HUxKhYn0rjeGTfgtwrq5xGXcaG8OKhE73rtaDFsw9h/GG+svNpw2\n\tz4iF1SkVIvaz71BJIy62bouFT/xJJVcD+iOqZrbud+d1s1fSMNcnVM2vD0OEe26KODV\n\trh9V/LByM8dmkCLkso4y0y7Q1x8OWsBrJfFxW5eI=","Message-ID":"<b569d8ea-1ba7-4d16-b9db-035c841afad3@collabora.com>","Date":"Wed, 17 Dec 2025 11:34:10 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v9 14/26] libcamera: software_isp: debayer_egl: Add an\n\teGL Debayer class","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tlibcamera-devel@lists.libcamera.org","Cc":"pavel@ucw.cz, Milan Zamazal <mzamazal@redhat.com>","References":"<20251217100138.82525-1-bryan.odonoghue@linaro.org>\n\t<20251217100138.82525-15-bryan.odonoghue@linaro.org>","Content-Language":"en-US, de-DE","From":"Robert Mader <robert.mader@collabora.com>","In-Reply-To":"<20251217100138.82525-15-bryan.odonoghue@linaro.org>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]