From patchwork Sat Oct 17 11:10:31 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alain Cousinie X-Patchwork-Id: 28496 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id B6933C3316 for ; Sat, 17 Oct 2026 11:11:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C2C5368E7C; Sat, 17 Oct 2026 13:11:55 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=laposte.net header.i=@laposte.net header.b="tRr5L3d9"; dkim-atps=neutral Received: from smtp-outgoing-1902.laposte.net (smtp-outgoing-1902.laposte.net [160.92.124.106]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A7B6968E45 for ; Sat, 17 Oct 2026 13:11:50 +0200 (CEST) X-mail-filterd: {"version":"1.9.7", "queueID":"4j6JzP50G7zdZLZ", "contextId": "1df45f25-2715-4e6c-8e36-89e9d8a8f5a4"} Received: from outgoing-mail.laposte.net (localhost.localdomain [127.0.0.1]) by mlpnf0106.laposte.net (SMTP Server) with ESMTP id 4j6JzP50G7zdZLZ; Sat, 17 Oct 2026 13:11:45 +0200 (CEST) X-mail-filterd: {"version":"1.9.7", "queueID":"4j6JzP2mCczdZLg", "contextId": "e4f4da21-80b2-4294-9050-715ac1adeaff"} X-lpn-mailing: LEGIT X-lpn-spamrating: 36 X-lpn-spamlevel: not-spam Received: from debian.home (m-dj.hd.free.fr [82.67.82.126]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mlpnf0106.laposte.net (SMTP Server) with ESMTPSA id 4j6JzP2mCczdZLg; Sat, 17 Oct 2026 13:11:45 +0200 (CEST) From: alain.cousinie@laposte.net To: libcamera-devel@lists.libcamera.org Cc: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= , Milan Zamazal , Hans de Goede , Bryan O'Donoghue , =?utf-8?q?Alain_Cousini=C3=A9?= Subject: [PATCH v6 4/4] libcamera: software_isp: Add support for monochrome formats - GPU Date: Sat, 17 Oct 2026 13:10:31 +0200 Message-ID: <20261017111036.700859-5-alain.cousinie@laposte.net> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261017111036.700859-1-alain.cousinie@laposte.net> References: <10d396ff-2270-46ef-a641-967df76917be@laposte.net> <20261017111036.700859-1-alain.cousinie@laposte.net> MIME-Version: 1.0 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=laposte.net; s=lpn-wlmd; t=1792235510; bh=DafMqYJPAjYNkutn+wnCe5vukaKl+efH2punKeN05fM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Type:Content-Transfer-Encoding; b=tRr5L3d995+jrGDTGESS51OljrG0c1Xz6NBq6ecEvJAUSTAVWksbTeDQNt1tCNyNyfAvl/bbEVLntbynQ1OaB9WDyPC8ADZ6UMNi2pI3dywL/Cw5ZKireDIawAC1ri1ELVTmOgZFvbZEGWeoZwY5cwT90Se/unHp5RCxZ5uETR1d8C/GsimtyCUY3crD9FTHVRf5rFhgmtkg6OXfvHtdkX9CfTlKrGCIfoDGg9o5/Vwy/6xeAvDhqxhH8x9qxONgeV6eNDAUqUMQPZaqq3BHfojD/+X4/5PbWt/tS38EL9YrYI85e3Ta7U4AfpbivL10XAzt2yjx5WJ6SVtVb8wg7w==; X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Signed-off-by: Alain CousiniƩ --- src/libcamera/software_isp/debayer_egl.cpp | 137 +++++++++++++++++++-- src/libcamera/software_isp/debayer_egl.h | 2 + 2 files changed, 127 insertions(+), 12 deletions(-) -- 2.55.0 diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 300822f9d..00c680d78 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -61,6 +61,21 @@ int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf formats::XBGR8888, formats::ABGR8888 }; + if (bayerFormat.order == BayerFormat::Order::MONO) { + if (bayerFormat.packing == BayerFormat::Packing::None) { + config.bpp = (bayerFormat.bitDepth + 7) & ~7; + config.patternSize.width = 2; + config.patternSize.height = 2; + } else if (bayerFormat.packing == BayerFormat::Packing::CSI2) { + config.bpp = bayerFormat.bitDepth; + config.patternSize.width = (bayerFormat.bitDepth == 10) ? 4 : 2; + config.patternSize.height = 2; + } + + config.outputFormats = outputFormats; + return 0; + } + if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10) && bayerFormat.packing == BayerFormat::Packing::None && isStandardBayerOrder(bayerFormat.order)) { @@ -166,6 +181,13 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm shaderStridePixels_ = inputConfig_.stride; switch (inputFormat) { + case libcamera::formats::R8: + case libcamera::formats::R10: + case libcamera::formats::R12: + case libcamera::formats::R16: + firstRed_x_ = 0.0; + firstRed_y_ = 0.0; + break; case libcamera::formats::SBGGR8: case libcamera::formats::SBGGR10_CSI2P: case libcamera::formats::SBGGR12_CSI2P: @@ -197,6 +219,74 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm /* Shader selection */ switch (inputFormat) { + case libcamera::formats::R8: + egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 255.0)"); + egl_.pushEnv(shaderEnv, "#define RAW8"); + fragmentShaderData = mono_unpacked_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_LUMINANCE; + bytesPerPixel_ = 1; + break; + case libcamera::formats::R10: { + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + + egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 1023.0)"); + if (bayerFormat.packing == BayerFormat::Packing::None) { + egl_.pushEnv(shaderEnv, "#define RAW10"); + fragmentShaderData = mono_unpacked_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_RG; + bytesPerPixel_ = 2; + } else { + egl_.pushEnv(shaderEnv, "#define BLOCK_PIXELS 4.0"); + egl_.pushEnv(shaderEnv, "#define BLOCK_BYTES 5.0"); + egl_.pushEnv(shaderEnv, "#define LSB_SIZE 2.0"); + egl_.pushEnv(shaderEnv, "#define LSB_MASK_VAL 4.0"); + egl_.pushEnv(shaderEnv, (std::string("#define TEX_WIDTH ") + std::to_string(inputConfig_.stride) + ".0").c_str()); + egl_.pushEnv(shaderEnv, (std::string("#define TEX_HEIGHT ") + std::to_string(height_) + ".0").c_str()); + + fragmentShaderData = mono_1x_packed_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_LUMINANCE; + bytesPerPixel_ = 1; + shaderStridePixels_ = inputConfig_.stride; + } + break; + } + case libcamera::formats::R12: { + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + + egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 4095.0)"); + if (bayerFormat.packing == BayerFormat::Packing::None) { + egl_.pushEnv(shaderEnv, "#define RAW12"); + fragmentShaderData = mono_unpacked_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_RG; + bytesPerPixel_ = 2; + } else { + egl_.pushEnv(shaderEnv, "#define BLOCK_PIXELS 2.0"); + egl_.pushEnv(shaderEnv, "#define BLOCK_BYTES 3.0"); + egl_.pushEnv(shaderEnv, "#define LSB_SIZE 4.0"); + egl_.pushEnv(shaderEnv, "#define LSB_MASK_VAL 16.0"); + egl_.pushEnv(shaderEnv, (std::string("#define TEX_WIDTH ") + std::to_string(inputConfig_.stride) + ".0").c_str()); + egl_.pushEnv(shaderEnv, (std::string("#define TEX_HEIGHT ") + std::to_string(height_) + ".0").c_str()); + + fragmentShaderData = mono_1x_packed_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_LUMINANCE; + bytesPerPixel_ = 1; + shaderStridePixels_ = inputConfig_.stride; + } + break; + } + case libcamera::formats::R16: + egl_.pushEnv(shaderEnv, "#define INV_MAX_VAL (1.0 / 65535.0)"); + egl_.pushEnv(shaderEnv, "#define RAW16"); + fragmentShaderData = mono_unpacked_frag; + vertexShaderData = mono_vert; + glFormat_ = GL_RG; + bytesPerPixel_ = 2; + break; case libcamera::formats::SBGGR8: case libcamera::formats::SGBRG8: case libcamera::formats::SGRBG8: @@ -273,7 +363,7 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm int DebayerEGL::configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, - [[maybe_unused]] bool ccmEnabled) + bool ccmEnabled) { if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0) { LOG(Debayer, Error) @@ -293,6 +383,15 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, return -EINVAL; } + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputCfg.pixelFormat); + if (bayerFormat.order == BayerFormat::Order::MONO) { + /* \todo Disable CCM via IPA; force false here to prevent monochrome coloration */ + ccmEnabled = false; + } else { + /* Force ccmEnabled to true as it is not yet populated. */ + ccmEnabled = true; + } + inputConfig_.stride = inputCfg.stride; inputPixelFormat_ = inputCfg.pixelFormat; width_ = inputCfg.size.width; @@ -322,10 +421,16 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, outputSize_ = outputCfg.size; nativeOutputSize_ = outSizeRange.max; - window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & - ~(inputConfig_.patternSize.width - 1); - window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & - ~(inputConfig_.patternSize.height - 1); + /* Setup the window geometry according to the format type */ + if (bayerFormat.order == BayerFormat::Order::MONO) { + window_.x = 0; + window_.y = 0; + } else { + window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & + ~(inputConfig_.patternSize.width - 1); + window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & + ~(inputConfig_.patternSize.height - 1); + } window_.width = outputCfg.size.width; window_.height = outputCfg.size.height; @@ -338,6 +443,8 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, inputBufferCount_ = inputCfg.bufferCount; outputBufferCount_ = outputCfg.bufferCount; + ccmEnabled_ = ccmEnabled; + return 0; } @@ -465,9 +572,11 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara << " textureUniformProjMatrix_ " << textureUniformProjMatrix_; /* Pre-transpose matrix for GLES 2.0. */ - Matrix ccm = params.combinedMatrix.transpose(); - glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm.data().data()); - LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; + if (ccmEnabled_) { + Matrix ccm = params.combinedMatrix.transpose(); + glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm.data().data()); + LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; + } /* * 0 = Red, 1 = Green, 2 = Blue @@ -692,11 +801,12 @@ void DebayerEGL::stop() SizeRange DebayerEGL::sizes(PixelFormat inputFormat, const Size &inputSize) { Size patternSize = this->patternSize(inputFormat); - unsigned int borderHeight = patternSize.height; - if (patternSize.isNull()) return {}; + /* FIX: borderHeight is now safely initialized after the isNull() check */ + unsigned int borderHeight = patternSize.height; + /* No need for top/bottom border with a pattern height of 2 */ if (patternSize.height == 2) borderHeight = 0; @@ -712,9 +822,12 @@ SizeRange DebayerEGL::sizes(PixelFormat inputFormat, const Size &inputSize) return {}; } + BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + bool localIsMono = (bayerFormat.order == BayerFormat::Order::MONO); + return SizeRange(Size(patternSize.width, patternSize.height), - Size((inputSize.width - 2 * patternSize.width) & ~(patternSize.width - 1), - (inputSize.height - 2 * borderHeight) & ~(patternSize.height - 1)), + Size((inputSize.width - (localIsMono ? 0 : 2 * patternSize.width)) & ~(patternSize.width - 1), + (inputSize.height - (localIsMono ? 0 : 2 * borderHeight)) & ~(patternSize.height - 1)), patternSize.width, patternSize.height); } diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h index 30e51a477..197a2b6f1 100644 --- a/src/libcamera/software_isp/debayer_egl.h +++ b/src/libcamera/software_isp/debayer_egl.h @@ -119,6 +119,8 @@ private: GLint glFormat_; unsigned int bytesPerPixel_; uint32_t shaderStridePixels_; + + bool ccmEnabled_; }; } /* namespace libcamera */