Patch Detail
Show a patch.
GET /api/1.1/patches/23232/?format=api
{ "id": 23232, "url": "https://patchwork.libcamera.org/api/1.1/patches/23232/?format=api", "web_url": "https://patchwork.libcamera.org/patch/23232/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20250422215920.4297-20-bryan.odonoghue@linaro.org>", "date": "2025-04-22T21:59:12", "name": "[19/27] libcamera: software_isp: debayer_egl: Add an eGL debayer class", "commit_ref": null, "pull_url": null, "state": "rfc", "archived": false, "hash": "04b99136e2d94983a97a5c4301c44516be39cfd9", "submitter": { "id": 175, "url": "https://patchwork.libcamera.org/api/1.1/people/175/?format=api", "name": "Bryan O'Donoghue", "email": "bryan.odonoghue@linaro.org" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/23232/mbox/", "series": [ { "id": 5142, "url": "https://patchwork.libcamera.org/api/1.1/series/5142/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5142", "date": "2025-04-22T21:58:53", "name": "RFC: Add in a eGL based GPUISP in libcamera", "version": 1, "mbox": "https://patchwork.libcamera.org/series/5142/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/23232/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/23232/checks/", "tags": {}, "headers": { "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>", "X-Original-To": "parsemail@patchwork.libcamera.org", "Delivered-To": "parsemail@patchwork.libcamera.org", "Received": [ "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id E4895C327D\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 22 Apr 2025 21:59:57 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5FCE668AD9;\n\tTue, 22 Apr 2025 23:59:57 +0200 (CEST)", "from mail-wm1-x331.google.com (mail-wm1-x331.google.com\n\t[IPv6:2a00:1450:4864:20::331])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3666368B36\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 22 Apr 2025 23:59:42 +0200 (CEST)", "by mail-wm1-x331.google.com with SMTP id\n\t5b1f17b1804b1-43d04dc73b7so52251125e9.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 22 Apr 2025 14:59:42 -0700 (PDT)", "from inspiron14p-linux.ht.home (188-141-3-146.dynamic.upc.ie.\n\t[188.141.3.146]) by smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-44092d2eccesm2726615e9.20.2025.04.22.14.59.40\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tTue, 22 Apr 2025 14:59:40 -0700 (PDT)" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=linaro.org header.i=@linaro.org\n\theader.b=\"mKkGIi1l\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=linaro.org; s=google; t=1745359181; x=1745963981;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=rPosaOH1mtkxvmAKqwhdY3CYDHFWTfkbfbfqGtuksa4=;\n\tb=mKkGIi1lT7cMNuWX47AVqWn3elJoNBi2w/Ebg6DmkTawjN5nTvjqRobEeI89znvYNa\n\tWM2wdZeerhRlaBLf/8JyaHwrraeU63m2Ww8yMuPWzsJEIwLx7ptRkRrZme1D9UHcSfY0\n\tHONXJlNsGS44gWAC+8Ae0CU2jmMcVv1TL6grLb1L19YFUp8W4dFpc4BK3OCZXNl5t5QI\n\taMBrA0YNUTmGr2mnXmN+JGM/3ZWjcEvZApMy45pQBN5kmPprPIIGYX82NNCW3rCoiYkF\n\tzUCqI1rHw0UHp0anSabW60VvQBywC7tIqfr823WjxzabXWGAkdhGCXFmZGqDlv7nhALt\n\t0RwA==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1745359181; x=1745963981;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=rPosaOH1mtkxvmAKqwhdY3CYDHFWTfkbfbfqGtuksa4=;\n\tb=oumPGtAJlvySoXewSOktjByr3IiJ4PMaZtjDPzamB20yRG6ony3b2HXNlPlA79FTf5\n\t9AbXg4k3OGQhcYoP3G0L04fgqgsK2EIcX2S0SZ1a2SK/hshTgIJlkcP3I3rxVY1q8743\n\tXIaCp+tscRxkTrGhdA1TkC3wQhe9Zo+CCciUHYdMTx/TFqL5+QwErPbPqve0G9ylXtkB\n\tfIg82h4cklBOSJmsJ1ABuW9x6BlBusduppuiCHReUU40qdZ1AguVFdhBzOQzjU/bdK7u\n\t85w6iYBwVqDZr76Lfsf66lz4x6TloGUlXi+FPEW7kzOezi4oT9Mg8fh0AERI51GxdiWW\n\tq4CQ==", "X-Gm-Message-State": "AOJu0Yx3zDz11kBOqAine/VmGtJL9ld61xDGGiaRMjnI+qv2+uwWEZzz\n\tktZ171MKarIzWgEQafogJq7/PhB/wxh9zXoTMpn4vheUMIkgJm1hvn1/y6USX/33GRxolC5Mw93\n\tu5VU=", "X-Gm-Gg": "ASbGncv8qcG3c5Zm4JY4SN9fCWjvoGAWcuMc/zj6sFSGoVIy3tFKeb7Cke1LXzm1oKq\n\tcQtnVg4ADitwmF4RtUiJc/Nb/XA55aDtmp5sNO8fzz3qL+BfQ0/mHXr69Fd7fz4Cb0sydCSilI3\n\tqmp1yR0yLBKHGnE6x6SzKpt9VboMUcXnxkzeJomid0RY7+j7xu0QH/TDu+tOSkGWAMsgI/+UCXC\n\tXD+n3uOJ58QGq71gcodKD6oTWb4388wfDIB7Fo93NIEdzIWEGqQDhiC9NgQOo8t1ilToUvCL7/g\n\tO8hWiZo8LE9HSW0Cq5EC6d7HBI+oG7zxqZ6H3nUjVf4DrA8e/ngSqIzKt61RdBiQJqeIrtp70w5\n\to2oCP30KfyGLYyv0pdOHAQe5JCbZMeh8=", "X-Google-Smtp-Source": "AGHT+IHjuU3u6SF/3/4Ta9WlQP9bnpbH3jDhkuonLtp7sCPzymz6BASspe1bAuwohx5/+oOkubQ+TQ==", "X-Received": "by 2002:a05:600c:1e0c:b0:43d:fa59:bcee with SMTP id\n\t5b1f17b1804b1-4406ac1feebmr146436505e9.33.1745359181477; \n\tTue, 22 Apr 2025 14:59:41 -0700 (PDT)", "From": "Bryan O'Donoghue <bryan.odonoghue@linaro.org>", "To": "libcamera-devel@lists.libcamera.org", "Cc": "hdegoede@redhat.com, mzamazal@redhat.com, bryan.odonoghue@linaro.org,\n\tbod.linux@nxsw.ie", "Subject": "[PATCH 19/27] libcamera: software_isp: debayer_egl: Add an eGL\n\tdebayer class", "Date": "Tue, 22 Apr 2025 22:59:12 +0100", "Message-ID": "<20250422215920.4297-20-bryan.odonoghue@linaro.org>", "X-Mailer": "git-send-email 2.49.0", "In-Reply-To": "<20250422215920.4297-1-bryan.odonoghue@linaro.org>", "References": "<20250422215920.4297-1-bryan.odonoghue@linaro.org>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "X-BeenThere": "libcamera-devel@lists.libcamera.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "<libcamera-devel.lists.libcamera.org>", "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>", "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>", "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>", "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>", "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "Add a class to run the existing glsl debayer shaders on a GBM surface.\n\nSigned-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n---\n src/libcamera/software_isp/debayer_egl.cpp | 529 +++++++++++++++++++++\n src/libcamera/software_isp/debayer_egl.h | 168 +++++++\n src/libcamera/software_isp/meson.build | 30 ++\n 3 files changed, 727 insertions(+)\n create mode 100644 src/libcamera/software_isp/debayer_egl.cpp\n create mode 100644 src/libcamera/software_isp/debayer_egl.h", "diff": "diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp\nnew file mode 100644\nindex 00000000..008938f8\n--- /dev/null\n+++ b/src/libcamera/software_isp/debayer_egl.cpp\n@@ -0,0 +1,529 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Linaro Ltd.\n+ *\n+ * Authors:\n+ * Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n+ *\n+ * debayer_cpu.cpp - EGL based debayering class\n+ */\n+\n+#include <math.h>\n+#include <stdlib.h>\n+#include <time.h>\n+\n+#include <libcamera/formats.h>\n+\n+#include \"libcamera/internal/glsl_shaders.h\"\n+#include \"debayer_egl.h\"\n+\n+namespace libcamera {\n+\n+DebayerEGL::DebayerEGL(std::unique_ptr<SwStatsCpu> stats)\n+\t: stats_(std::move(stats))\n+{\n+}\n+\n+DebayerEGL::~DebayerEGL()\n+{\n+\tif (eglImageBayerIn_)\n+\t\tdelete eglImageBayerIn_;\n+}\n+\n+// 0xB0D make into public in the base\n+static bool isStandardBayerOrder(BayerFormat::Order order)\n+{\n+\treturn order == BayerFormat::BGGR || order == BayerFormat::GBRG ||\n+\t order == BayerFormat::GRBG || order == BayerFormat::RGGB;\n+}\n+\n+// 0xB0D make into public in the base\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::ARGB8888 });\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::ARGB8888 });\n+\t\treturn 0;\n+\t}\n+\n+\tLOG(Debayer, Info)\n+\t\t<< \"Unsupported input format \" << inputFormat.toString();\n+\treturn -EINVAL;\n+}\n+\n+// 0xB0D make into public in the base\n+int DebayerEGL::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config)\n+{\n+\tif (outputFormat == formats::ARGB8888) {\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+\ttextureUniformY_ = glGetUniformLocation(programId_, \"tex_y\");\n+\ttextureUniformU_ = glGetUniformLocation(programId_, \"tex_u\");\n+\ttextureUniformV_ = glGetUniformLocation(programId_, \"tex_v\");\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, Info) << \"vertexIn \" << attributeVertex_ << \" textureIn \" << attributeTexture_\n+\t\t\t << \" tex_y \" << textureUniformY_\n+\t\t\t << \" tex_u \" << textureUniformU_\n+\t\t\t << \" tex_v \" << textureUniformV_\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+\tswapRedBlueGains_ = false;\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+\tswitch (outputFormat) {\n+\tcase formats::ARGB8888:\n+\t\tbreak;\n+\tcase formats::BGRA8888:\n+\t\tswapRedBlueGains_ = true;\n+\t\tbreak;\n+\tdefault:\n+\t\tgoto invalid_fmt;\n+\t}\n+\n+\t// Pixel location parameters\n+\tswitch (inputFormat) {\n+\tcase libcamera::formats::SBGGR8:\n+\tcase libcamera::formats::SBGGR10_CSI2P:\n+\tcase libcamera::formats::SBGGR12_CSI2P:\n+\t\tfirstRed_x_ = 1.0;\n+\t\tfirstRed_y_ = 1.0;\n+\t\tbreak;\n+\tcase libcamera::formats::SGBRG8:\n+\tcase libcamera::formats::SGBRG10_CSI2P:\n+\tcase libcamera::formats::SGBRG12_CSI2P:\n+\t\tfirstRed_x_ = 0.0;\n+\t\tfirstRed_y_ = 1.0;\n+\t\tbreak;\n+\tcase libcamera::formats::SGRBG8:\n+\tcase libcamera::formats::SGRBG10_CSI2P:\n+\tcase libcamera::formats::SGRBG12_CSI2P:\n+\t\tfirstRed_x_ = 1.0;\n+\t\tfirstRed_y_ = 0.0;\n+\t\tbreak;\n+\tcase libcamera::formats::SRGGB8:\n+\tcase libcamera::formats::SRGGB10_CSI2P:\n+\tcase libcamera::formats::SRGGB12_CSI2P:\n+\t\tfirstRed_x_ = 0.0;\n+\t\tfirstRed_y_ = 0.0;\n+\t\tbreak;\n+\tdefault:\n+\t\tgoto invalid_fmt;\n+\t\tbreak;\n+\t};\n+\n+\t// Shader selection\n+\tswitch (inputFormat) {\n+\tcase libcamera::formats::SBGGR8:\n+\tcase libcamera::formats::SGBRG8:\n+\tcase libcamera::formats::SGRBG8:\n+\tcase libcamera::formats::SRGGB8:\n+\t\tfragmentShaderData = bayer_8_frag;\n+\t\tfragmentShaderDataLen = bayer_8_frag_len;\n+\t\tvertexShaderData = bayer_8_vert;\n+\t\tvertexShaderDataLen = bayer_8_vert_len;\n+\t\tbreak;\n+\tcase libcamera::formats::SBGGR10_CSI2P:\n+\tcase libcamera::formats::SGBRG10_CSI2P:\n+\tcase libcamera::formats::SGRBG10_CSI2P:\n+\tcase libcamera::formats::SRGGB10_CSI2P:\n+\t\tegl_.pushEnv(shaderEnv, \"#define RAW10P\");\n+\t\tfragmentShaderData = bayer_1x_packed_frag;\n+\t\tfragmentShaderDataLen = bayer_1x_packed_frag_len;\n+\t\tvertexShaderData = identity_vert;\n+\t\tvertexShaderDataLen = identity_vert_len;\n+\t\tbreak;\n+\tcase libcamera::formats::SBGGR12_CSI2P:\n+\tcase libcamera::formats::SGBRG12_CSI2P:\n+\tcase libcamera::formats::SGRBG12_CSI2P:\n+\tcase libcamera::formats::SRGGB12_CSI2P:\n+\t\tegl_.pushEnv(shaderEnv, \"#define RAW12P\");\n+\t\tfragmentShaderData = bayer_1x_packed_frag;\n+\t\tfragmentShaderDataLen = bayer_1x_packed_frag_len;\n+\t\tvertexShaderData = identity_vert;\n+\t\tvertexShaderDataLen = identity_vert_len;\n+\t\tbreak;\n+\tdefault:\n+\t\tgoto invalid_fmt;\n+\t\tbreak;\n+\t};\n+\n+\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+\n+compile_fail:\n+\tLOG(Debayer, Error) << \"Compile debayer shaders fail\";\n+\treturn -ENODEV;\n+\n+invalid_fmt:\n+\tLOG(Debayer, Error) << \"Unsupported input output format combination\";\n+\treturn -EINVAL;\n+}\n+\n+int DebayerEGL::configure(const StreamConfiguration &inputCfg,\n+\t\t\t const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,\n+\t\t\t bool ccmEnabled)\n+{\n+\tif (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0)\n+\t\treturn -EINVAL;\n+\n+\tif (stats_->configure(inputCfg) != 0)\n+\t\treturn -EINVAL;\n+\n+\tconst Size &stats_pattern_size = stats_->patternSize();\n+\tif (inputConfig_.patternSize.width != stats_pattern_size.width ||\n+\t inputConfig_.patternSize.height != stats_pattern_size.height) {\n+\t\tLOG(Debayer, Error)\n+\t\t\t<< \"mismatching stats and debayer pattern sizes for \"\n+\t\t\t<< inputCfg.pixelFormat.toString();\n+\t\treturn -EINVAL;\n+\t}\n+(void)ccmEnabled;\n+\tinputConfig_.stride = inputCfg.stride;\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+\tLOG(Debayer, Info) << \"Input size \" << inputCfg.size << \" stride \" << inputCfg.stride;\n+\n+\tif (gbmSurface_.initSurface(inputCfg.size.width, inputCfg.size.height))\n+\t\treturn -ENODEV;\n+\n+\tif (egl_.initEGLContext(&gbmSurface_))\n+\t\treturn -ENODEV;\n+\n+\teglImageBayerIn_ = new eGLImage(width_, height_, 32);\n+\n+\t// Create a single BO (calling gbm_surface_lock_front_buffer() again before gbm_surface_release_buffer() would create another BO)\n+\tif (gbmSurface_.mapSurface())\n+\t\treturn -ENODEV;\n+\n+\tStreamConfiguration &outputCfg = outputCfgs[0];\n+\tSizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size);\n+\n+\toutputConfig_.stride = gbmSurface_.getStride();\n+\toutputConfig_.frameSize = gbmSurface_.getFrameSize();\n+\n+\tLOG(Debayer, Debug) << \"Overriding stream config stride \"\n+\t\t\t << outputCfg.stride << \" with GBM surface stride \"\n+\t\t\t << outputConfig_.stride;\n+\toutputCfg.stride = outputConfig_.stride;\n+\n+\tif (!outSizeRange.contains(outputCfg.size) || outputConfig_.stride != outputCfg.stride) {\n+\t\tLOG(Debayer, Error)\n+\t\t\t<< \"Invalid output size/stride: \"\n+\t\t\t<< \"\\n \" << outputCfg.size << \" (\" << outSizeRange << \")\"\n+\t\t\t<< \"\\n \" << outputCfg.stride << \" (\" << outputConfig_.stride << \")\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\twindow_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) &\n+\t\t ~(inputConfig_.patternSize.width - 1);\n+\twindow_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) &\n+\t\t ~(inputConfig_.patternSize.height - 1);\n+\twindow_.width = outputCfg.size.width;\n+\twindow_.height = outputCfg.size.height;\n+\n+\t/* Don't pass x,y since process() already adjusts src before passing it */\n+\tstats_->setWindow(Rectangle(window_.size()));\n+\n+\tLOG(Debayer, Info) << \"Input width \" << inputCfg.size.width << \" height \" << inputCfg.size.height;\n+\tLOG(Debayer, Info) << \"Output width \" << outputCfg.size.width << \" height \" << outputCfg.size.height;\n+\tLOG(Debayer, Info) << \"Output stride \" << outputCfg.size.width << \" height \" << outputCfg.size.height;\n+\n+\tif (initBayerShaders(inputCfg.pixelFormat, outputCfg.pixelFormat))\n+\t\treturn -EINVAL;\n+\n+\treturn 0;\n+}\n+\n+Size DebayerEGL::patternSize(PixelFormat inputFormat)\n+{\n+\tDebayerEGL::DebayerInputConfig config;\n+\n+\tif (getInputConfig(inputFormat, config) != 0)\n+\t\treturn {};\n+\n+\treturn config.patternSize;\n+}\n+\n+std::vector<PixelFormat> DebayerEGL::formats(PixelFormat inputFormat)\n+{\n+\tDebayerEGL::DebayerInputConfig config;\n+\n+\tif (getInputConfig(inputFormat, config) != 0)\n+\t\treturn std::vector<PixelFormat>();\n+\n+\treturn config.outputFormats;\n+}\n+\n+std::tuple<unsigned int, unsigned int>\n+DebayerEGL::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)\n+{\n+\tDebayerEGL::DebayerOutputConfig config;\n+\n+\tif (getOutputConfig(outputFormat, config) != 0)\n+\t\treturn std::make_tuple(0, 0);\n+\n+\t/* round up to multiple of 8 for 64 bits alignment */\n+\tunsigned int stride = (size.width * config.bpp / 8 + 7) & ~7;\n+\n+\treturn std::make_tuple(stride, stride * size.height);\n+}\n+\n+void DebayerEGL::setShaderVariableValues(void)\n+{\n+\t/*\n+\t * Raw Bayer 8-bit, and packed raw Bayer 10-bit/12-bit formats\n+\t * are stored in a GL_LUMINANCE texture. The texture width is\n+\t * equal to the stride.\n+\t */\n+\tGLfloat firstRed[] = {firstRed_x_, firstRed_y_};\n+\tGLfloat imgSize[] = {(GLfloat)width_,\n+\t\t\t (GLfloat)height_};\n+\tGLfloat Step[] = {1.0f / (inputConfig_.stride - 1),\n+\t\t\t 1.0f / (height_ - 1)};\n+\tGLfloat Stride = 1.0f;\n+\tGLfloat projIdentityMatrix[] = {\n+\t\t1, 0, 0, 0,\n+\t\t0, 1, 0, 0,\n+\t\t0, 0, 1, 0,\n+\t\t0, 0, 0, 1\n+\t};\n+\n+\t// vertexIn - bayer_8.vert\n+\tglEnableVertexAttribArray(attributeVertex_);\n+\tglVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE,\n+\t\t\t 2 * sizeof(GLfloat), vcoordinates);\n+\n+\t// textureIn - bayer_8.vert\n+\tglEnableVertexAttribArray(attributeTexture_);\n+\tglVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE,\n+\t\t\t 2 * sizeof(GLfloat), tcoordinates);\n+\n+\tglUniform1i(textureUniformY_, 0);\t\t\t\t// tex_y - bayer_8.vert - set for no reason\n+\tglUniform2fv(textureUniformBayerFirstRed_, 1, firstRed);\t// tex_bayer_first_red - bayer_8.vert\n+\tglUniform2fv(textureUniformSize_, 1, imgSize);\t\t\t// tex_size - bayer_8.vert\n+\tglUniform2fv(textureUniformStep_, 1, Step);\t\t\t// tex_step - bayer_8.vert\n+\tglUniform1f(textureUniformStrideFactor_, Stride);\t\t// stride_factor - identity.vert\n+\tglUniformMatrix4fv(textureUniformProjMatrix_, 1,\n+\t\t\t GL_FALSE, projIdentityMatrix);\t\t// No scaling\n+\n+\tLOG(Debayer, Debug) << \"vertexIn \" << attributeVertex_ << \" textureIn \" << attributeTexture_\n+\t\t\t << \" tex_y \" << textureUniformY_\n+\t\t\t << \" tex_u \" << textureUniformU_\n+\t\t\t << \" tex_v \" << textureUniformV_\n+\t\t\t << \" tex_step \" << textureUniformStep_\n+\t\t\t << \" tex_size \" << textureUniformSize_\n+\t\t\t << \" stride_factor \" << textureUniformStrideFactor_\n+\t\t\t << \" tex_bayer_first_red \" << textureUniformBayerFirstRed_;\n+\n+\tLOG (Debayer, Debug) << \"textureUniformY_ = 0 \" <<\n+\t\t\t\t\" firstRed.x \" << firstRed[0] <<\n+\t\t\t\t\" firstRed.y \" << firstRed[1] <<\n+\t\t\t\t\" textureUniformSize_.width \" << imgSize[0] << \" \"\n+\t\t\t\t\" textureUniformSize_.height \" << imgSize[1] <<\n+\t\t\t\t\" textureUniformStep_.x \" << Step[0] <<\n+\t\t\t\t\" textureUniformStep_.y \" << Step[1] <<\n+\t\t\t\t\" textureUniformStrideFactor_ \" << Stride <<\n+\t\t\t\t\" textureUniformProjMatrix_ \" << textureUniformProjMatrix_;\n+\treturn;\n+}\n+\n+void DebayerEGL::debayerGPU(MappedFrameBuffer &in, MappedFrameBuffer &out)\n+{\n+\tLOG(Debayer, Debug)\n+\t\t<< \"Input height \" << height_\n+\t\t<< \" width \" << width_\n+\t\t<< \" fd \" << in.fds()[0];\n+\n+\t// eGL context switch\n+\tegl_.makeCurrent();\n+\n+\t// make texture unit 0 explicit this doesn't really matter probably remove ?\n+\tglActiveTexture(GL_TEXTURE0);\n+\n+\t// Greate a standard texture\n+\t// we will replace this with the DMA version at some point\n+\tegl_.createTexture2D(eglImageBayerIn_, inputConfig_.stride, height_, in.planes()[0].data());\n+\n+\t// Setup the scene\n+\tsetShaderVariableValues();\n+\tglViewport(0, 0, width_, height_);\n+\tglClear(GL_COLOR_BUFFER_BIT);\n+\tglDisable(GL_BLEND);\n+\n+\t// Draw the scene\n+\tglDrawArrays(GL_TRIANGLE_FAN, 0, DEBAYER_OPENGL_COORDS);\n+\n+\t// eglclientWaitScynKhr / eglwaitsynckr ?\n+\tegl_.swapBuffers();\n+\n+\t// Copy from the output GBM buffer to our output plane\n+\t// once we get render to texture working the\n+\t// explicit lock ioctl, memcpy and unlock ioctl won't be required\n+\tgbmSurface_.getFrameBufferData(out.planes()[0].data(), out.planes()[0].size());\n+}\n+\n+void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params)\n+{\n+\tbench_.startFrame();\n+\n+\tstd::vector<DmaSyncer> dmaSyncers;\n+\n+\tdmaSyncBegin(dmaSyncers, input, output);\n+\n+\tsetParams(params);\n+\n+\t/* Copy metadata from the input buffer */\n+\tFrameMetadata &metadata = output->_d()->metadata();\n+\tmetadata.status = input->metadata().status;\n+\tmetadata.sequence = input->metadata().sequence;\n+\tmetadata.timestamp = input->metadata().timestamp;\n+\n+\tMappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read);\n+\tMappedFrameBuffer out(output, MappedFrameBuffer::MapFlag::Write);\n+\tif (!in.isValid() || !out.isValid()) {\n+\t\tLOG(Debayer, Error) << \"mmap-ing buffer(s) failed\";\n+\t\tmetadata.status = FrameMetadata::FrameError;\n+\t\treturn;\n+\t}\n+\n+\tdebayerGPU(in, out);\n+\n+\tdmaSyncers.clear();\n+\n+\tbench_.finishFrame();\n+\n+\tmetadata.planes()[0].bytesused = out.planes()[0].size();\n+\n+\t// Calculate stats for the whole frame\n+\tstats_->processFrame(frame, 0, input);\n+\n+\toutputBufferReady.emit(output);\n+\tinputBufferReady.emit(input);\n+}\n+\n+SizeRange DebayerEGL::sizes(PixelFormat inputFormat, const Size &inputSize)\n+{\n+\tSize patternSize = this->patternSize(inputFormat);\n+\tunsigned int borderHeight = patternSize.height;\n+\n+\tif (patternSize.isNull())\n+\t\treturn {};\n+\n+\t/* No need for top/bottom border with a pattern height of 2 */\n+\tif (patternSize.height == 2)\n+\t\tborderHeight = 0;\n+\n+\t/*\n+\t * For debayer interpolation a border is kept around the entire image\n+\t * and the minimum output size is pattern-height x pattern-width.\n+\t */\n+\tif (inputSize.width < (3 * patternSize.width) ||\n+\t inputSize.height < (2 * borderHeight + patternSize.height)) {\n+\t\tLOG(Debayer, Warning)\n+\t\t\t<< \"Input format size too small: \" << inputSize.toString();\n+\t\treturn {};\n+\t}\n+\n+\treturn SizeRange(Size(patternSize.width, patternSize.height),\n+\t\t\t Size((inputSize.width - 2 * patternSize.width) & ~(patternSize.width - 1),\n+\t\t\t (inputSize.height - 2 * borderHeight) & ~(patternSize.height - 1)),\n+\t\t\t patternSize.width, patternSize.height);\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h\nnew file mode 100644\nindex 00000000..14f6997a\n--- /dev/null\n+++ b/src/libcamera/software_isp/debayer_egl.h\n@@ -0,0 +1,168 @@\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+#include <GL/gl.h>\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+#include \"egl.h\"\n+\n+#include \"libcamera/internal/bayer_format.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+/**\n+ * \\class DebayerEGL\n+ * \\brief Class for debayering using an EGL Shader\n+ *\n+ * Implements an EGL shader based debayering solution.\n+ */\n+class DebayerEGL : public Debayer\n+{\n+public:\n+\t/**\n+\t * \\brief Constructs a DebayerEGL object.\n+\t * \\param[in] stats Pointer to the stats object to use.\n+\t */\n+\tDebayerEGL(std::unique_ptr<SwStatsCpu> stats);\n+\t~DebayerEGL();\n+\n+\t/*\n+\t * Setup the Debayer object according to the passed in parameters.\n+\t * Return 0 on success, a negative errno value on failure\n+\t * (unsupported parameters).\n+\t */\n+\tint configure(const StreamConfiguration &inputCfg,\n+\t\t const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,\n+\t\t bool ccmEnabled);\n+\n+\t/*\n+\t * Get width and height at which the bayer-pattern repeats.\n+\t * Return pattern-size or an empty Size for an unsupported inputFormat.\n+\t */\n+\tSize patternSize(PixelFormat inputFormat);\n+\n+\tstd::vector<PixelFormat> formats(PixelFormat input);\n+\tstd::tuple<unsigned int, unsigned int>\n+\t\tstrideAndFrameSize(const PixelFormat &outputFormat, const Size &size);\n+\n+\tvoid process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params);\n+\n+\t/**\n+\t * \\brief Get the file descriptor for the statistics.\n+\t *\n+\t * \\return the file descriptor pointing to the statistics.\n+\t */\n+\tconst SharedFD &getStatsFD() { return stats_->getStatsFD(); }\n+\n+\t/**\n+\t * \\brief Get the output frame size.\n+\t *\n+\t * \\return The output frame size.\n+\t */\n+\tunsigned int frameSize() { return outputConfig_.frameSize; }\n+\n+\tSizeRange sizes(PixelFormat inputFormat, const Size &inputSize);\n+\n+\t/**\n+\t * \\brief EGL context is associated with a PID so don't moveToThread\n+\t *\n+\t * \\return nothing\n+\t */\n+\tvoid moveToThread(Thread *thread) { (void)thread; return; }\n+\n+private:\n+\n+\tint getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config);\n+\tint getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config);\n+\tint setupStandardBayerOrder(BayerFormat::Order order);\n+\tvoid pushEnv(std::vector<std::string> &shaderEnv, const char *str);\n+\tint initBayerShaders(PixelFormat inputFormat, PixelFormat outputFormat);\n+\tint initEGLContext();\n+\tint generateTextures();\n+\tint compileShaderProgram(GLuint &shaderId, GLenum shaderType,\n+\t\t\t\t unsigned char *shaderData, int shaderDataLen,\n+\t\t\t\t std::vector<std::string> shaderEnv);\n+\tint linkShaderProgram(void);\n+\tint getShaderVariableLocations();\n+\tvoid setShaderVariableValues(void);\n+\tvoid configureTexture(GLuint &texture);\n+\tvoid debayerGPU(MappedFrameBuffer &in, MappedFrameBuffer &out);\n+\n+\t// Shader program identifiers\n+\tGLuint vertexShaderId_;\n+\tGLuint fragmentShaderId_;\n+\tGLuint programId_;\n+\tenum {\n+\t\tBAYER_INPUT_INDEX = 0,\n+\t\tBAYER_OUTPUT_INDEX,\n+\t\tBAYER_BUF_NUM,\n+\t};\n+\n+\t// Pointer to object representing input texture\n+\teGLImage *eglImageBayerIn_;\n+\n+\t// Shader parameters\n+\tfloat firstRed_x_;\n+\tfloat firstRed_y_;\n+\tGLint attributeVertex_;\n+\tGLint attributeTexture_;\n+\tGLint textureUniformY_;\n+\tGLint textureUniformU_;\n+\tGLint textureUniformV_;\n+\tGLint textureUniformStep_;\n+\tGLint textureUniformSize_;\n+\tGLint textureUniformStrideFactor_;\n+\tGLint textureUniformBayerFirstRed_;\n+\tGLint textureUniformProjMatrix_;\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+\n+\t#define DEBAYER_OPENGL_COORDS 4\n+\n+\tGLfloat vcoordinates[DEBAYER_OPENGL_COORDS][2] = {\n+\t/* Vertex coordinates */\n+\t\t-1.0f, -1.0f,\n+\t\t-1.0f, +1.0f,\n+\t\t+1.0f, +1.0f,\n+\t\t+1.0f, -1.0f,\n+\t};\n+\n+\tGLfloat tcoordinates[DEBAYER_OPENGL_COORDS][2] = {\n+\t\t/* Texture coordinates */\n+\t\t0.0f, 1.0f,\n+\t\t0.0f, 0.0f,\n+\t\t1.0f, 0.0f,\n+\t\t1.0f, 1.0f,\n+\t};\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build\nindex 59fa5f02..9a7ac455 100644\n--- a/src/libcamera/software_isp/meson.build\n+++ b/src/libcamera/software_isp/meson.build\n@@ -14,3 +14,33 @@ libcamera_internal_sources += files([\n 'software_isp.cpp',\n 'swstats_cpu.cpp',\n ])\n+\n+libegl = cc.find_library('EGL', required : false)\n+libgbm = cc.find_library('gbm', required: false)\n+libglesv2 = cc.find_library('GLESv2', required : false)\n+\n+if libegl.found()\n+ config_h.set('HAVE_LIBEGL', 1)\n+endif\n+\n+if libgbm.found()\n+ config_h.set('HAVE_GBM', 1)\n+endif\n+\n+if libglesv2.found()\n+ config_h.set('HAVE_GLESV2', 1)\n+endif\n+\n+if libegl.found() and libgbm.found() and libglesv2.found()\n+libcamera_deps += [\n+ libegl,\n+ libgbm,\n+ libglesv2,\n+]\n+\n+libcamera_internal_sources += files([\n+ 'debayer_egl.cpp',\n+ 'egl.cpp',\n+ 'gbm.cpp',\n+])\n+endif\n", "prefixes": [ "19/27" ] }