From patchwork Fri Jul 31 21:10:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Milan Zamazal X-Patchwork-Id: 27533 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 3D150BDE4C for ; Fri, 31 Jul 2026 21:11:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E622F67FE8; Fri, 31 Jul 2026 23:11:04 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="ZAyvtmNM"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6556E65F9F for ; Fri, 31 Jul 2026 23:11:03 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1785532262; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=4NbgSXicJ8fdZv812b+IZvsdLkImFs3E9nWaFrHqESA=; b=ZAyvtmNMZDwbCp9OPEMgkpZoDLFyFOCNgJxx8RBAC/Lr1RFGIbOdmdOT/AEnTH+ZKWhCSP oCOlAR5VNkUcegzbYV4IKL/W48R+BJbq7UMGUtYte7Q95+KnuIzhpTQ1kLiF4Q2k0wPtQW R+YKl2+MFQVyqNqJbK8BIcLDpb07fcM= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-247-lM2GLOHTN4-g-VHyb_Sc9A-1; Fri, 31 Jul 2026 17:11:00 -0400 X-MC-Unique: lM2GLOHTN4-g-VHyb_Sc9A-1 X-Mimecast-MFC-AGG-ID: lM2GLOHTN4-g-VHyb_Sc9A_1785532259 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (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 mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 8999F19560A6; Fri, 31 Jul 2026 21:10:58 +0000 (UTC) Received: from mzamazal-thinkpadp1gen7.tpbc.com (unknown [10.44.32.42]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id E229E18004AD; Fri, 31 Jul 2026 21:10:54 +0000 (UTC) From: Milan Zamazal To: libcamera-devel@lists.libcamera.org Cc: Xander Pronk , Bryan O'Donoghue , Hans de Goede , Laurent Pinchart , Robert Mader , Bryan O'Donoghue , Rick ten Wolde , Milan Zamazal Subject: [RFC PATCH v10 1/6] libcamera: software_isp: egl: Add filter parameter to createTexture2D() Date: Fri, 31 Jul 2026 23:10:38 +0200 Message-ID: <20260731211044.135970-2-mzamazal@redhat.com> In-Reply-To: <20260731211044.135970-1-mzamazal@redhat.com> References: <20260731211044.135970-1-mzamazal@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: StGpHb-U9Zx8HAAHZdaTWx4_b0cr9nxMTSobKP31JsY_1785532259 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true 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" From: Xander Pronk Add `filter' parameter to createTexture2D() to allow overriding the currently hardcoded GL_NEAREST value. This is needed for grid-based lens shading interpolation, which is implemented in followup patches. Reviewed-by: Bryan O'Donoghue Co-developed-by: Rick ten Wolde Signed-off-by: Rick ten Wolde Signed-off-by: Xander Pronk Signed-off-by: Milan Zamazal --- include/libcamera/internal/egl.h | 2 +- src/libcamera/egl.cpp | 11 ++++++----- src/libcamera/software_isp/debayer_egl.cpp | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h index 1e31d490b..9edc28ea4 100644 --- a/include/libcamera/internal/egl.h +++ b/include/libcamera/internal/egl.h @@ -107,7 +107,7 @@ public: int createInputDMABufTexture2D(eGLImage &eglImage, int fd); int createOutputDMABufTexture2D(eGLImage &eglImage, int fd); - void createTexture2D(eGLImage &eglImage, void *data); + void createTexture2D(eGLImage &eglImage, void *data, GLint filter); void updateTexture2D(eGLImage &eglImage, void *data); void createOutputTexture2D(eGLImage &eglImage); diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp index 7ec7a654d..53390f370 100644 --- a/src/libcamera/egl.cpp +++ b/src/libcamera/egl.cpp @@ -275,13 +275,14 @@ int eGL::createOutputDMABufTexture2D(eGLImage &eglImage, int fd) * \brief Create a 2D texture from a memory buffer * \param[in,out] eglImage EGL image to associate with the texture * \param[in] data Pointer to pixel data, or nullptr for uninitialised texture + * \param[in] filter GL texture filter setting * * Creates a 2D texture from a CPU-accessible memory buffer. The texture - * is configured with nearest filtering and clamp-to-edge wrapping. This + * is configured the specified filtering and clamp-to-edge wrapping. This * is useful for uploading static data like lookup tables or uniform color * matrices to the GPU. */ -void eGL::createTexture2D(eGLImage &eglImage, void *data) +void eGL::createTexture2D(eGLImage &eglImage, void *data, GLint filter) { ASSERT(tid_ == Thread::currentId()); @@ -291,8 +292,8 @@ void eGL::createTexture2D(eGLImage &eglImage, void *data) glTexImage2D(GL_TEXTURE_2D, 0, eglImage.format_, eglImage.width_, eglImage.height_, 0, eglImage.format_, GL_UNSIGNED_BYTE, data); // Nearest filtering - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); // Wrap to edge to avoid edge artifacts glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); @@ -369,7 +370,7 @@ void eGL::updateTexture2D(eGLImage &eglImage, void *data) */ void eGL::createOutputTexture2D(eGLImage &eglImage) { - createTexture2D(eglImage, NULL); + createTexture2D(eglImage, NULL, GL_NEAREST); attachTextureToFBO(eglImage); } diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 02651fe87..7e08cae71 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -549,7 +549,7 @@ eGLImage *DebayerEGL::getCachedInputFrameBuffer(FrameBuffer *input, std::optiona return nullptr; } if (cache_miss) - egl_.createTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); + egl_.createTexture2D(*eglImageIn, inMapped->value().planes()[0].data(), GL_NEAREST); else egl_.updateTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); From patchwork Fri Jul 31 21:10:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Milan Zamazal X-Patchwork-Id: 27534 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 12381BDE4C for ; Fri, 31 Jul 2026 21:11:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C3CB067FE8; Fri, 31 Jul 2026 23:11:08 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="ZQsGgCAe"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id F256065F9F for ; Fri, 31 Jul 2026 23:11:06 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1785532265; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=+Uk1mBF1U+cbKzx4PEbbM2mpHmG7ZgIxcK9OtM4LK6A=; b=ZQsGgCAeWpXFbd7iKbtoXbmSJFCcZzIWDYd+ZE4uZW6Y9Kn6Q9Gue7qrdE0W9YrjC04K6r +5+Ns1WIiWg/qOCY3J4NCF5ZY5C+rfcWANso+nWt5A8f7NIrUdJeB1UmpuFZCcT6TrMW3n EDuQSqM8zwobMLO+qgab5WVoWd8o0EA= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-368-8zyGAFvgPuCrgTyOFRjhHw-1; Fri, 31 Jul 2026 17:11:04 -0400 X-MC-Unique: 8zyGAFvgPuCrgTyOFRjhHw-1 X-Mimecast-MFC-AGG-ID: 8zyGAFvgPuCrgTyOFRjhHw_1785532263 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (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 mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B5BA6180056E; Fri, 31 Jul 2026 21:11:02 +0000 (UTC) Received: from mzamazal-thinkpadp1gen7.tpbc.com (unknown [10.44.32.42]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 3649B180036E; Fri, 31 Jul 2026 21:10:58 +0000 (UTC) From: Milan Zamazal To: libcamera-devel@lists.libcamera.org Cc: Xander Pronk , Bryan O'Donoghue , Hans de Goede , Laurent Pinchart , Robert Mader , Rick ten Wolde , Milan Zamazal Subject: [RFC PATCH v10 2/6] libcamera: software_isp: Add LSC data to DebayerParams Date: Fri, 31 Jul 2026 23:10:39 +0200 Message-ID: <20260731211044.135970-3-mzamazal@redhat.com> In-Reply-To: <20260731211044.135970-1-mzamazal@redhat.com> References: <20260731211044.135970-1-mzamazal@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: 2lV7ioYF9Cww8oziYqKD8msxvajdcRgML1cjjTIv6EQ_1785532263 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true 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" From: Xander Pronk Add a lookup table for grid-based lens shading correction to debayer params. The lookup table is in the format of an RGBA texture to be passed to debayering. The alpha value serves only for padding and can be arbitrary; OpenGL wants by default rows aligned to 4 bytes and using RGBA format is the simplest way to achieve it. The parameter is filled in by Lsc algorithm implemented in a follow-up patch. If the algorithm is not enabled, the table is filled in with values corresponding to identity multiplication (1.0). In order the image processing would be able to update its internal data (e.g. the GPU texture) on and only on the lookup table changes, lscLutVersion parameter is added, which is incremented on each lookup table change. The parameters are currently unused, their handling is implemented in follow-up patches. The grid size is selected to be the same as in the tuning files already present in rkisp1. Another popular grid size in other pipelines is 32. Co-developed-by: Rick ten Wolde Signed-off-by: Rick ten Wolde Signed-off-by: Xander Pronk Signed-off-by: Milan Zamazal --- .../internal/software_isp/debayer_params.h | 12 ++++++++ src/libcamera/software_isp/debayer.cpp | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h index 1074720d7..93dcf42e6 100644 --- a/include/libcamera/internal/software_isp/debayer_params.h +++ b/include/libcamera/internal/software_isp/debayer_params.h @@ -10,6 +10,7 @@ #pragma once +#include #include #include "libcamera/internal/matrix.h" @@ -25,6 +26,17 @@ struct DebayerParams { float gamma = 1.0; float contrastExp = 1.0; RGB gains = RGB({ 1.0, 1.0, 1.0 }); + + /** + * To prevent OpenGL alignment issues, the number of bytes in each row + * should be a multiple of 4. + **/ + static constexpr unsigned int kLscGridSize = 17; + static constexpr unsigned int kLscValuesPerCell = 4; + using LscLookupTable = + std::array; + LscLookupTable lscLut{}; + uint64_t lscLutVersion = 0; }; } /* namespace libcamera */ diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp index 56446f55d..b1565307a 100644 --- a/src/libcamera/software_isp/debayer.cpp +++ b/src/libcamera/software_isp/debayer.cpp @@ -43,6 +43,34 @@ namespace libcamera { * \brief Contrast value to be used as an exponent */ +/** + * \var DebayerParams::kLscGridSize + * \brief Number of lens shading grid areas in one direction + */ + +/** + * \var DebayerParams::kLscValuesPerCell + * \brief Number of pixel values per each of the lens shading grid areas + */ + +/** + * \typedef DebayerParams::LscLookupTable + * \brief Lookup table for lens shading correction + * + * It's an array of values to be later used as a texture. + * The values are in row - column - RGB order. + */ + +/** + * \var DebayerParams::lscLut + * \brief Lens shading lookup table + */ + +/** + * \var DebayerParams::lscLutVersion + * \brief Incremented on each \a lscLut change + */ + /** * \class Debayer * \brief Base debayering class From patchwork Fri Jul 31 21:10:40 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Milan Zamazal X-Patchwork-Id: 27535 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 8FA2BBDE4C for ; Fri, 31 Jul 2026 21:11:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 57BC867FE8; Fri, 31 Jul 2026 23:11:14 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="Pf6POwK7"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CA4C465F9F for ; Fri, 31 Jul 2026 23:11:12 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1785532271; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=oLvxlfLFiPFKN2HKLVuoZCKUn58qcf5Zo1Yx+J1QdVc=; b=Pf6POwK7oue5P4BVByoCCfyjHHuQuI+E/hiKHYwmb1UGDL7q/6Hr2DIoZPmp0ZXxaJjtrG eqSMbbBb7+/W+90/gNvoI/9KRH/oM40ZAUEjp/+PnO7lRT15qgC1h+6CBchMomniEHxAo0 lugVbMXQTyiIjxWPEx1Je1+6yZzKWjw= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-228-L-Cz3m9uOJCb7dVwOB4Byw-1; Fri, 31 Jul 2026 17:11:08 -0400 X-MC-Unique: L-Cz3m9uOJCb7dVwOB4Byw-1 X-Mimecast-MFC-AGG-ID: L-Cz3m9uOJCb7dVwOB4Byw_1785532266 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (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 mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 70CA11800348; Fri, 31 Jul 2026 21:11:06 +0000 (UTC) Received: from mzamazal-thinkpadp1gen7.tpbc.com (unknown [10.44.32.42]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 2721D180056E; Fri, 31 Jul 2026 21:11:02 +0000 (UTC) From: Milan Zamazal To: libcamera-devel@lists.libcamera.org Cc: Xander Pronk , Bryan O'Donoghue , Hans de Goede , Laurent Pinchart , Robert Mader , Rick ten Wolde , Milan Zamazal Subject: [RFC PATCH v10 3/6] ipa: simple: Add LSC algorithm Date: Fri, 31 Jul 2026 23:10:40 +0200 Message-ID: <20260731211044.135970-4-mzamazal@redhat.com> In-Reply-To: <20260731211044.135970-1-mzamazal@redhat.com> References: <20260731211044.135970-1-mzamazal@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: F2T_inbyoqbX8JWAH3Mn7_DYognrNBG8-bChz9EtZG4_1785532266 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true 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" From: Xander Pronk The algorithm is based on the common libipa lens shading correction implementation. The grid values obtained from the libipa algorithm are passed to the debayer algorithm as an array and used as an an RGB texture. Notes on the implementation: - The overall idea is to keep things simple, to not make the LSC computation unnecessarily expensive. - LscAlgorithm accepts only quantised types. UQ<2,6> is used, to be converted to float in debayering. - The limit of 100 degrees to consider a temperature change noticeable is arbitrary. Co-developed-by: Rick ten Wolde Signed-off-by: Rick ten Wolde Signed-off-by: Xander Pronk Signed-off-by: Milan Zamazal --- src/ipa/simple/algorithms/lsc.cpp | 99 +++++++++++++++++++++++++++ src/ipa/simple/algorithms/lsc.h | 82 ++++++++++++++++++++++ src/ipa/simple/algorithms/meson.build | 1 + src/ipa/simple/ipa_context.h | 5 ++ 4 files changed, 187 insertions(+) create mode 100644 src/ipa/simple/algorithms/lsc.cpp create mode 100644 src/ipa/simple/algorithms/lsc.h diff --git a/src/ipa/simple/algorithms/lsc.cpp b/src/ipa/simple/algorithms/lsc.cpp new file mode 100644 index 000000000..618d2135c --- /dev/null +++ b/src/ipa/simple/algorithms/lsc.cpp @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Lens shading correction + */ + +#include "lsc.h" + +#include + +namespace libcamera { + +namespace ipa::soft::algorithms { + +LOG_DEFINE_CATEGORY(IPASoftLsc) + +int Lsc::init(IPAContext &context, const ValueNode &tuningData) +{ + static constexpr unsigned int kGridSize = DebayerParams::kLscGridSize; + + for (unsigned int i = 0; i < kGridSize; i++) + gridPos_.push_back(static_cast(i) / (kGridSize - 1)); + + return lscAlgo_.init(tuningData, context.ctrlMap, + { .keys = { "r", "g", "b" }, + .numHSamples = kGridSize, + .numVSamples = kGridSize, + .sensorSize = context.sensorInfo.activeAreaSize }); +} + +int Lsc::configure(IPAContext &context, + [[maybe_unused]] const IPAConfigInfo &configInfo) +{ + return lscAlgo_.configure(context.activeState.lsc, + context.sensorInfo.analogCrop, + gridPos_, gridPos_); +} + +void Lsc::prepare([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + DebayerParams *params) +{ + unsigned int ct = frameContext.awb.colourTemperature; + constexpr unsigned int minTemperatureChange = 100; + + if (!frameContext.lsc.enabled) { + if (lastAppliedCt_ != 0 || params->lscLutVersion == 0) { + params->lscLut.fill(64); /* UQ<2, 6>(64) == 1.0 */ + params->lscLutVersion++; + lastAppliedCt_ = 0; + } + return; + } + + if (utils::abs_diff(ct, lastAppliedCt_) < minTemperatureChange) + return; + + const SimpleLscAlgorithm::Components &set = + lscAlgo_.getInterpolator().getInterpolated(ct); + + const auto &red = set.at("r"); + const auto &green = set.at("g"); + const auto &blue = set.at("b"); + + DebayerParams::LscLookupTable lut; + constexpr unsigned int gridSize = DebayerParams::kLscGridSize; + for (unsigned int i = 0, j = 0; i < gridSize * gridSize; i++) { + lut[j++] = red[i]; + lut[j++] = green[i]; + lut[j++] = blue[i]; + lut[j++] = 0; /* padding */ + } + params->lscLut = lut; + params->lscLutVersion++; + + lastAppliedCt_ = ct; +} + +void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) +{ + lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc, + controls); +} + +void Lsc::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const SwIspStats *stats, + ControlList &metadata) +{ + lscAlgo_.process(frameContext.lsc, metadata); +} + +REGISTER_IPA_ALGORITHM(Lsc, "Lsc") + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/lsc.h b/src/ipa/simple/algorithms/lsc.h new file mode 100644 index 000000000..981aaef53 --- /dev/null +++ b/src/ipa/simple/algorithms/lsc.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Lens shading correction + */ + +#pragma once + +#include + +#include "libipa/fixedpoint.h" +#include "libipa/lsc.h" + +#include "algorithm.h" +#include "ipa_context.h" + +namespace libcamera { + +namespace ipa { + +namespace soft::algorithms { + +using SimpleLscAlgorithm = LscAlgorithm>; +using SimpleComponents = SimpleLscAlgorithm::Components; +using SimpleComponentsMap = SimpleLscAlgorithm::ComponentsMap; + +class Lsc : public Algorithm +{ +public: + Lsc() = default; + ~Lsc() = default; + + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, + const IPAConfigInfo &configInfo) override; + void queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) override; + void prepare(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + DebayerParams *params) override; + void process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const SwIspStats *stats, + ControlList &metadata) override; + +private: + SimpleLscAlgorithm lscAlgo_; + std::vector gridPos_; + + unsigned int lastAppliedCt_ = 0; +}; + +} /* namespace soft::algorithms */ + +#ifndef __DOXYGEN__ +template +void interpolateVector(const std::vector &a, + const std::vector &b, + std::vector &dest, double lambda) +{ + ASSERT(a.size() == b.size()); + dest.resize(a.size()); + for (size_t i = 0; i < a.size(); i++) + dest[i] = a[i] * (1.0 - lambda) + b[i] * lambda; +} + +template<> +void Interpolator:: + interpolate(const soft::algorithms::SimpleComponents &a, + const soft::algorithms::SimpleComponents &b, + soft::algorithms::SimpleComponents &dest, + double lambda) +{ + for (auto const &[k, v] : a) + interpolateVector(v, b.at(k), dest[k], lambda); +} +#endif /* __DOXYGEN__ */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build index 73c637220..c9f6e5590 100644 --- a/src/ipa/simple/algorithms/meson.build +++ b/src/ipa/simple/algorithms/meson.build @@ -6,4 +6,5 @@ soft_simple_ipa_algorithms = files([ 'agc.cpp', 'blc.cpp', 'ccm.cpp', + 'lsc.cpp', ]) diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index ff312ae8f..23c2cfd0a 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -19,6 +19,7 @@ #include #include #include +#include "libipa/lsc.h" #include "core_ipa_interface.h" @@ -61,6 +62,8 @@ struct IPAActiveState { std::optional contrast; std::optional saturation; } knobs; + + ipa::lsc::ActiveState lsc; }; struct IPAFrameContext : public FrameContext { @@ -75,6 +78,7 @@ struct IPAFrameContext : public FrameContext { float gamma; std::optional contrast; std::optional saturation; + ipa::lsc::FrameContext lsc; }; struct IPAContext { @@ -89,6 +93,7 @@ struct IPAContext { FCQueue frameContexts; ControlInfoMap::Map ctrlMap; bool ccmEnabled = false; + ipa::lsc::ActiveState lsc; }; } /* namespace ipa::soft */ From patchwork Fri Jul 31 21:10:41 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Milan Zamazal X-Patchwork-Id: 27536 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 2EAAABDE4C for ; Fri, 31 Jul 2026 21:11:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DB96667FF7; Fri, 31 Jul 2026 23:11:17 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="MNoT6sbC"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8CCC665F9F for ; Fri, 31 Jul 2026 23:11:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1785532275; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=XCLpKcs91jMicTKWtWmaiC1wmcx0TjaG3ltJWpZep0Q=; b=MNoT6sbCe3VOpZM5AmbkHbpkenkgekgBlKT5MLRmdjWnCjfOEiPwHtt/XVI79SrV2dW42L f0BvCx3t1RpXlzKECHqsALeMwtfp+PA9jC+6TTT3kNlrvtbMqmclgaKBHTLOrD95UN2/ak VMj5qjQDZ3ZkLITj2qnVYCZ5FTu4UHo= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-534-T00zBtpJMdSGuDOT3HmBtw-1; Fri, 31 Jul 2026 17:11:12 -0400 X-MC-Unique: T00zBtpJMdSGuDOT3HmBtw-1 X-Mimecast-MFC-AGG-ID: T00zBtpJMdSGuDOT3HmBtw_1785532270 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (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 mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 34D4F18002C3; Fri, 31 Jul 2026 21:11:10 +0000 (UTC) Received: from mzamazal-thinkpadp1gen7.tpbc.com (unknown [10.44.32.42]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id E0EB8180036E; Fri, 31 Jul 2026 21:11:06 +0000 (UTC) From: Milan Zamazal To: libcamera-devel@lists.libcamera.org Cc: Xander Pronk , Bryan O'Donoghue , Hans de Goede , Laurent Pinchart , Robert Mader , Rick ten Wolde , Milan Zamazal , Bryan O'Donoghue Subject: [RFC PATCH v10 4/6] libcamera: shaders: Add LSC support Date: Fri, 31 Jul 2026 23:10:41 +0200 Message-ID: <20260731211044.135970-5-mzamazal@redhat.com> In-Reply-To: <20260731211044.135970-1-mzamazal@redhat.com> References: <20260731211044.135970-1-mzamazal@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: FaBp3UWBrcqSIRc8jzNYfvCHw-4Syflskpkcv70Yvck_1785532270 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true 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" From: Xander Pronk Lens shading correction should be applied after black level subtraction (in order to make the computations with meaningful values) and before white balance (especially before white balance stats are computed). Note that lens shading correction depends on temperature, which is computed from the preceding, rather than current, frame (this is due to how white balance is currently computed). The shaders are compiled on initialisation, while lens shading correction can be enabled or disabled dynamically, using the corresponding control. The whole correction in the shader is wrapped by a conditional macro to be able to disable it and all the related overheads if Lsc algorithm is not enabled at all. If Lsc algorithm is enabled, the correction is always applied, whether the given control is enabled or not. If it is not, 1.0 multipliers are expected to be provided. This is because lens shading correction is supposed to be rarely switched off using the runtime control when Lsc algorithm is enabled, e.g. only for demoing or debugging purposes. We prefer accepting the performance penalty of applying a no-op correction in such cases rather than disturbing GPU computation in the regular flow by introducing a runtime `if'. lsc_tex texture contains 8-bit UQ<2, 6> quantized data as provided by the IPA algorithm. It is converted to float in the shader by multiplying it by 4.0. Co-developed-by: Rick ten Wolde Signed-off-by: Rick ten Wolde Signed-off-by: Xander Pronk Signed-off-by: Milan Zamazal Reviewed-by: Bryan O'Donoghue --- src/libcamera/shaders/bayer_1x_packed.frag | 9 +++++++++ src/libcamera/shaders/bayer_unpacked.frag | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/libcamera/shaders/bayer_1x_packed.frag b/src/libcamera/shaders/bayer_1x_packed.frag index 0b641a5f3..765fc8e88 100644 --- a/src/libcamera/shaders/bayer_1x_packed.frag +++ b/src/libcamera/shaders/bayer_1x_packed.frag @@ -71,6 +71,10 @@ uniform vec3 blacklevel; uniform float gamma; uniform float contrastExp; +#if defined(APPLY_LSC) +uniform sampler2D lsc_tex; +#endif + float apply_contrast(float value) { // Apply simple S-curve @@ -232,6 +236,11 @@ void main(void) */ rgb = (rgb - blacklevel) / (1.0 - blacklevel); +#if defined(APPLY_LSC) + /* Multiple by 4.0 for UQ<2, 6> -> float conversion */ + rgb = rgb * (texture2D(lsc_tex, textureOut).rgb * 4.0); +#endif + /* Apply AWB gains, and saturate each channel at sensor range */ rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0)); diff --git a/src/libcamera/shaders/bayer_unpacked.frag b/src/libcamera/shaders/bayer_unpacked.frag index 10c5e941b..be2f89dd0 100644 --- a/src/libcamera/shaders/bayer_unpacked.frag +++ b/src/libcamera/shaders/bayer_unpacked.frag @@ -30,6 +30,10 @@ uniform vec3 blacklevel; uniform float gamma; uniform float contrastExp; +#if defined(APPLY_LSC) +uniform sampler2D lsc_tex; +#endif + float apply_contrast(float value) { // Apply simple S-curve @@ -135,6 +139,11 @@ void main(void) { */ rgb = (rgb - blacklevel) / (1.0 - blacklevel); +#if defined(APPLY_LSC) + /* Multiple by 4.0 for UQ<2, 6> -> float conversion */ + rgb = rgb * (texture2D(lsc_tex, center.xy).rgb * 4.0); +#endif + /* Apply AWB gains, and saturate each channel at sensor range */ rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0)); From patchwork Fri Jul 31 21:10:42 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Milan Zamazal X-Patchwork-Id: 27537 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 D570EBDE4C for ; Fri, 31 Jul 2026 21:11:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8C71867FFA; Fri, 31 Jul 2026 23:11:22 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="Ebv2Le29"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4342965F9F for ; Fri, 31 Jul 2026 23:11:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1785532280; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=RjXg4U0+dcYLeMbmoydrLG92WxjbOUVK+gIPrOa5/TA=; b=Ebv2Le29GZVYYeEOx2uP7CILSH6GVdWqKzO1B21GaE93BsJJ1g6e0NkKPpukcclVDRS9vq ZXaKmaYud4ySIP7acKNtwGUMVhG0MNHMYyweiLbFBTVuM4GIcmqEbo85t6kXSE/Duv+b9b CeZcjmzCMl+cBEJMgPxe5qsjgksOZWw= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-600-v08gdMftN2Gwrkj8W69wYQ-1; Fri, 31 Jul 2026 17:11:14 -0400 X-MC-Unique: v08gdMftN2Gwrkj8W69wYQ-1 X-Mimecast-MFC-AGG-ID: v08gdMftN2Gwrkj8W69wYQ_1785532273 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (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 mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 47A551956094; Fri, 31 Jul 2026 21:11:13 +0000 (UTC) Received: from mzamazal-thinkpadp1gen7.tpbc.com (unknown [10.44.32.42]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id C6305180036E; Fri, 31 Jul 2026 21:11:10 +0000 (UTC) From: Milan Zamazal To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Bryan O'Donoghue , Hans de Goede , Laurent Pinchart , Robert Mader Subject: [RFC PATCH v10 5/6] libcamera: software_isp: Pass LSC availability to debayering Date: Fri, 31 Jul 2026 23:10:42 +0200 Message-ID: <20260731211044.135970-6-mzamazal@redhat.com> In-Reply-To: <20260731211044.135970-1-mzamazal@redhat.com> References: <20260731211044.135970-1-mzamazal@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: TN1odXH4b5fIU93jaDNdDddfltJBOTa6yD9qU9r-R-w_1785532273 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true 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" The lens shading algorithm may or may not be enabled in the tuning file. Debayering configuration needs this information to arrange things accordingly, e.g. to set up shader parameters. Let's add a corresponding flag that is set in the LSC algorithm (if present) and passed to Debayer::configure. This is similar to what we already do with CCM availability. The flag is ignored by CPU ISP, where LSC is not going to be implemented for now. Reviewed-by: Bryan O'Donoghue Signed-off-by: Milan Zamazal --- include/libcamera/internal/software_isp/software_isp.h | 1 + include/libcamera/ipa/soft.mojom | 4 +++- src/ipa/simple/algorithms/lsc.cpp | 8 +++++++- src/ipa/simple/ipa_context.h | 1 + src/ipa/simple/soft_simple.cpp | 7 +++++-- src/libcamera/software_isp/debayer.cpp | 1 + src/libcamera/software_isp/debayer.h | 3 ++- src/libcamera/software_isp/debayer_cpu.cpp | 3 ++- src/libcamera/software_isp/debayer_cpu.h | 3 ++- src/libcamera/software_isp/debayer_egl.cpp | 5 ++++- src/libcamera/software_isp/debayer_egl.h | 4 +++- src/libcamera/software_isp/software_isp.cpp | 5 +++-- 12 files changed, 34 insertions(+), 11 deletions(-) diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h index 4f72dce9b..f24c04890 100644 --- a/include/libcamera/internal/software_isp/software_isp.h +++ b/include/libcamera/internal/software_isp/software_isp.h @@ -100,6 +100,7 @@ private: DebayerParams debayerParams_; DmaBufAllocator dmaHeap_; bool ccmEnabled_; + bool lscEnabled_; std::unique_ptr ipa_; std::deque queuedInputBuffers_; diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom index 77328c5fd..aaefd50c4 100644 --- a/include/libcamera/ipa/soft.mojom +++ b/include/libcamera/ipa/soft.mojom @@ -18,7 +18,9 @@ interface IPASoftInterface { libcamera.SharedFD fdParams, libcamera.IPACameraSensorInfo sensorInfo, libcamera.ControlInfoMap sensorControls) - => (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled); + => (int32 ret, + libcamera.ControlInfoMap ipaControls, + bool ccmEnabled, bool lscEnabled); start() => (int32 ret); stop(); configure(IPAConfigInfo configInfo) diff --git a/src/ipa/simple/algorithms/lsc.cpp b/src/ipa/simple/algorithms/lsc.cpp index 618d2135c..01781933e 100644 --- a/src/ipa/simple/algorithms/lsc.cpp +++ b/src/ipa/simple/algorithms/lsc.cpp @@ -20,11 +20,17 @@ int Lsc::init(IPAContext &context, const ValueNode &tuningData) for (unsigned int i = 0; i < kGridSize; i++) gridPos_.push_back(static_cast(i) / (kGridSize - 1)); - return lscAlgo_.init(tuningData, context.ctrlMap, + int ret = lscAlgo_.init(tuningData, context.ctrlMap, { .keys = { "r", "g", "b" }, .numHSamples = kGridSize, .numVSamples = kGridSize, .sensorSize = context.sensorInfo.activeAreaSize }); + if (ret) + return ret; + + context.lscEnabled = true; + + return 0; } int Lsc::configure(IPAContext &context, diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 23c2cfd0a..0ac36a4a8 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -93,6 +93,7 @@ struct IPAContext { FCQueue frameContexts; ControlInfoMap::Map ctrlMap; bool ccmEnabled = false; + bool lscEnabled = false; ipa::lsc::ActiveState lsc; }; diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 629e1a32d..4c724183a 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -57,7 +57,8 @@ public: const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls, - bool *ccmEnabled) override; + bool *ccmEnabled, + bool *lscEnabled) override; int configure(const IPAConfigInfo &configInfo) override; int start() override; @@ -97,7 +98,8 @@ int IPASoftSimple::init(const IPASettings &settings, const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls, - bool *ccmEnabled) + bool *ccmEnabled, + bool *lscEnabled) { camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); if (!camHelper_) { @@ -136,6 +138,7 @@ int IPASoftSimple::init(const IPASettings &settings, return ret; *ccmEnabled = context_.ccmEnabled; + *lscEnabled = context_.lscEnabled; params_ = nullptr; stats_ = nullptr; diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp index b1565307a..ce99e3c03 100644 --- a/src/libcamera/software_isp/debayer.cpp +++ b/src/libcamera/software_isp/debayer.cpp @@ -99,6 +99,7 @@ Debayer::~Debayer() * \param[in] inputCfg The input configuration * \param[in] outputCfgs The output configurations * \param[in] ccmEnabled Whether a color correction matrix is applied + * \param[in] lscEnabled Whether lens shading correction grid is provided * * \return 0 on success, a negative errno on failure */ diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h index 28f1b857b..d6dc24d29 100644 --- a/src/libcamera/software_isp/debayer.h +++ b/src/libcamera/software_isp/debayer.h @@ -40,7 +40,8 @@ public: virtual int configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, - bool ccmEnabled) = 0; + bool ccmEnabled, + bool lscEnabled) = 0; virtual std::vector formats(PixelFormat inputFormat) = 0; diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp index ab22635fd..d3812f182 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -641,7 +641,8 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, int DebayerCpu::configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, - bool ccmEnabled) + bool ccmEnabled, + [[maybe_unused]] bool lscEnabled) { if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0) return -EINVAL; diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h index 5281c65cb..a3c24d06b 100644 --- a/src/libcamera/software_isp/debayer_cpu.h +++ b/src/libcamera/software_isp/debayer_cpu.h @@ -37,7 +37,8 @@ public: int configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, - bool ccmEnabled) override; + bool ccmEnabled, + bool lscEnabled) override; Size patternSize(PixelFormat inputFormat) override; std::vector formats(PixelFormat input) override; std::tuple diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 7e08cae71..7cb0ee6ff 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -286,7 +286,8 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm int DebayerEGL::configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, - [[maybe_unused]] bool ccmEnabled) + [[maybe_unused]] bool ccmEnabled, + bool lscEnabled) { if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0) return -EINVAL; @@ -303,6 +304,8 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, return -EINVAL; } + lscEnabled_ = lscEnabled; + inputConfig_.stride = inputCfg.stride; inputPixelFormat_ = inputCfg.pixelFormat; width_ = inputCfg.size.width; diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h index d6223f3a9..26b009da4 100644 --- a/src/libcamera/software_isp/debayer_egl.h +++ b/src/libcamera/software_isp/debayer_egl.h @@ -45,7 +45,8 @@ public: int configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, - bool ccmEnabled) override; + bool ccmEnabled, + bool lscEnabled) override; Size patternSize(PixelFormat inputFormat) override; @@ -83,6 +84,7 @@ private: unsigned int inputBufferCount_; unsigned int outputBufferCount_; + bool lscEnabled_; /* Shader parameters */ float firstRed_x_; float firstRed_y_; diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp index c73a16ce0..a72d9c82c 100644 --- a/src/libcamera/software_isp/software_isp.cpp +++ b/src/libcamera/software_isp/software_isp.cpp @@ -163,7 +163,8 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, sensorInfo, sensor->controls(), ipaControls, - &ccmEnabled_); + &ccmEnabled_, + &lscEnabled_); if (ret) { LOG(SoftwareIsp, Error) << "IPA init failed"; debayer_.reset(); @@ -292,7 +293,7 @@ int SoftwareIsp::configure(const StreamConfiguration &inputCfg, if (ret < 0) return ret; - ret = debayer_->configure(inputCfg, outputCfgs, ccmEnabled_); + ret = debayer_->configure(inputCfg, outputCfgs, ccmEnabled_, lscEnabled_); if (ret < 0) return ret; From patchwork Fri Jul 31 21:10:43 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Milan Zamazal X-Patchwork-Id: 27538 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 BA0C8BDE4C for ; Fri, 31 Jul 2026 21:11:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 67DC167FFA; Fri, 31 Jul 2026 23:11:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="KDtBsSX6"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2A41865F9F for ; Fri, 31 Jul 2026 23:11:51 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1785532310; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=VQKWvInvOT+qLPjkSFMFNwBt8jZS9WKvVgBmh7GnY2Y=; b=KDtBsSX6jO+3ViQUHhKUxIt/tTYjtTF+zqhuL/2DltPOghDcNA1XQCJl6K4x2x6IMES9YM II+tQeHdkeAkxamguaRXwnQvKgBdn1SPGTcSLdVPHQX+SGqet0wWsWrarn4gaLx0pN0twc xLMSGyH/4dNGg6haL2qntthDXcS5o6g= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-618-Qww88_pQOtOOHLhtq3g7NQ-1; Fri, 31 Jul 2026 17:11:18 -0400 X-MC-Unique: Qww88_pQOtOOHLhtq3g7NQ-1 X-Mimecast-MFC-AGG-ID: Qww88_pQOtOOHLhtq3g7NQ_1785532277 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (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 mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 12F271800345; Fri, 31 Jul 2026 21:11:17 +0000 (UTC) Received: from mzamazal-thinkpadp1gen7.tpbc.com (unknown [10.44.32.42]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id ACD011800370; Fri, 31 Jul 2026 21:11:13 +0000 (UTC) From: Milan Zamazal To: libcamera-devel@lists.libcamera.org Cc: Xander Pronk , Bryan O'Donoghue , Hans de Goede , Laurent Pinchart , Robert Mader , Rick ten Wolde , Milan Zamazal Subject: [RFC PATCH v10 6/6] libcamera: software_isp: debayer_egl: Add LSC support Date: Fri, 31 Jul 2026 23:10:43 +0200 Message-ID: <20260731211044.135970-7-mzamazal@redhat.com> In-Reply-To: <20260731211044.135970-1-mzamazal@redhat.com> References: <20260731211044.135970-1-mzamazal@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: Ly5f_5JhupyLNdAeOwkf4T93h7lQXIZl3FwZeIdxEX8_1785532277 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true 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" From: Xander Pronk Add support for passing the LSC table from debayerParams to the shaders. We use 8-bit texture with the original UQ<2, 6> values to avoid trouble with floats. 32-bit floats don't work in my environment and 16-bit floats may not be supported in all OpenGL environments. When LSC is disabled by the runtime control, we provide a constant 1.0 correction table, i.e. a no-op table. This is preferred to having a runtime flag, to not disturb GPU computation with runtime switches, assuming that disabling LSC in runtime is not a common use case. The LSC texture is created once and then updated. The texture creation cannot be done in `configure', because of a different thread. Co-developed-by: Rick ten Wolde Signed-off-by: Rick ten Wolde Signed-off-by: Xander Pronk Signed-off-by: Milan Zamazal --- include/libcamera/internal/egl.h | 8 +++- .../internal/software_isp/debayer_params.h | 2 + src/libcamera/egl.cpp | 14 +++++-- src/libcamera/software_isp/debayer.cpp | 5 +++ src/libcamera/software_isp/debayer_egl.cpp | 42 ++++++++++++++++++- src/libcamera/software_isp/debayer_egl.h | 8 ++++ 6 files changed, 71 insertions(+), 8 deletions(-) diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h index 9edc28ea4..164b919e9 100644 --- a/include/libcamera/internal/egl.h +++ b/include/libcamera/internal/egl.h @@ -107,8 +107,12 @@ public: int createInputDMABufTexture2D(eGLImage &eglImage, int fd); int createOutputDMABufTexture2D(eGLImage &eglImage, int fd); - void createTexture2D(eGLImage &eglImage, void *data, GLint filter); - void updateTexture2D(eGLImage &eglImage, void *data); + void createTexture2D(eGLImage &eglImage, + GLint internalFormat, + GLenum type, + const void *data, + GLint filter); + void updateTexture2D(eGLImage &eglImage, const void *data); void createOutputTexture2D(eGLImage &eglImage); int attachTextureToFBO(eGLImage &eglImage); diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h index 93dcf42e6..ae51c3271 100644 --- a/include/libcamera/internal/software_isp/debayer_params.h +++ b/include/libcamera/internal/software_isp/debayer_params.h @@ -33,6 +33,8 @@ struct DebayerParams { **/ static constexpr unsigned int kLscGridSize = 17; static constexpr unsigned int kLscValuesPerCell = 4; + static constexpr unsigned int kLscBytesPerCell = + kLscValuesPerCell * sizeof(uint8_t); using LscLookupTable = std::array; LscLookupTable lscLut{}; diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp index 53390f370..cea6676ff 100644 --- a/src/libcamera/egl.cpp +++ b/src/libcamera/egl.cpp @@ -274,6 +274,8 @@ int eGL::createOutputDMABufTexture2D(eGLImage &eglImage, int fd) /** * \brief Create a 2D texture from a memory buffer * \param[in,out] eglImage EGL image to associate with the texture + * \param[in] internalFormat OpenGL internal storage format (e.g., GL_RGB8, GL_RGBA8) + * \param[in] type OpenGL pixel data type (e.g., GL_UNSIGNED_BYTE, GL_FLOAT) * \param[in] data Pointer to pixel data, or nullptr for uninitialised texture * \param[in] filter GL texture filter setting * @@ -282,14 +284,18 @@ int eGL::createOutputDMABufTexture2D(eGLImage &eglImage, int fd) * is useful for uploading static data like lookup tables or uniform color * matrices to the GPU. */ -void eGL::createTexture2D(eGLImage &eglImage, void *data, GLint filter) +void eGL::createTexture2D(eGLImage &eglImage, + GLint internalFormat, + GLenum type, + const void *data, + GLint filter) { ASSERT(tid_ == Thread::currentId()); activateBindTexture(eglImage); // Generate texture, bind, associate image to texture, configure, unbind - glTexImage2D(GL_TEXTURE_2D, 0, eglImage.format_, eglImage.width_, eglImage.height_, 0, eglImage.format_, GL_UNSIGNED_BYTE, data); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, eglImage.width_, eglImage.height_, 0, eglImage.format_, type, data); // Nearest filtering glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); @@ -351,7 +357,7 @@ bool eGL::isAvailable() * * Updates a 2D texture in VRAM. */ -void eGL::updateTexture2D(eGLImage &eglImage, void *data) +void eGL::updateTexture2D(eGLImage &eglImage, const void *data) { ASSERT(tid_ == Thread::currentId()); @@ -370,7 +376,7 @@ void eGL::updateTexture2D(eGLImage &eglImage, void *data) */ void eGL::createOutputTexture2D(eGLImage &eglImage) { - createTexture2D(eglImage, NULL, GL_NEAREST); + createTexture2D(eglImage, eglImage.format_, GL_UNSIGNED_BYTE, NULL, GL_NEAREST); attachTextureToFBO(eglImage); } diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp index ce99e3c03..522c22391 100644 --- a/src/libcamera/software_isp/debayer.cpp +++ b/src/libcamera/software_isp/debayer.cpp @@ -53,6 +53,11 @@ namespace libcamera { * \brief Number of pixel values per each of the lens shading grid areas */ +/** + * \var DebayerParams::kLscBytesPerCell + * \brief Number of bytes per each of the lens shading grid areas + */ + /** * \typedef DebayerParams::LscLookupTable * \brief Lookup table for lens shading correction diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 7cb0ee6ff..4f4eb56d0 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -128,6 +128,8 @@ int DebayerEGL::getShaderVariableLocations(void) textureUniformBayerFirstRed_ = glGetUniformLocation(programId_, "tex_bayer_first_red"); textureUniformProjMatrix_ = glGetUniformLocation(programId_, "proj_matrix"); + textureUniformLsc_ = glGetUniformLocation(programId_, "lsc_tex"); + LOG(Debayer, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_ << " tex_y " << textureUniformBayerDataIn_ << " awb " << awbUniformDataIn_ @@ -139,7 +141,8 @@ int DebayerEGL::getShaderVariableLocations(void) << " tex_size " << textureUniformSize_ << " stride_factor " << textureUniformStrideFactor_ << " tex_bayer_first_red " << textureUniformBayerFirstRed_ - << " proj_matrix " << textureUniformProjMatrix_; + << " proj_matrix " << textureUniformProjMatrix_ + << " tex_lsc " << textureUniformLsc_; return 0; } @@ -156,6 +159,9 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm /* Specify GL_OES_EGL_image_external */ egl_.pushEnv(shaderEnv, "#extension GL_OES_EGL_image_external: enable"); + if (lscEnabled_) + egl_.pushEnv(shaderEnv, "#define APPLY_LSC"); + /* * Tell shaders how to re-order output taking account of how the pixels * are actually stored by EGL. @@ -351,6 +357,18 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, inputBufferCount_ = inputCfg.bufferCount; outputBufferCount_ = outputCfg.bufferCount; + if (lscEnabled_) { + constexpr unsigned int gridSize = DebayerParams::kLscGridSize; + const unsigned int stride = gridSize * DebayerParams::kLscBytesPerCell; + eglImageLscLookup_ = + std::make_unique(GL_RGBA, + gridSize, + gridSize, + stride, + GL_TEXTURE2, + 2); + } + return 0; } @@ -486,6 +504,21 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_TRUE, params.combinedMatrix.data().data()); LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; + if (lscEnabled_) { + glUniform1i(textureUniformLsc_, eglImageLscLookup_->texture_unit_uniform_id_); + if (params.lscLutVersion != lscLutVersion_) { + if (lscTextureCreated_) { + egl_.updateTexture2D(*eglImageLscLookup_, params.lscLut.data()); + } else { + egl_.createTexture2D(*eglImageLscLookup_, eglImageLscLookup_->format_, GL_UNSIGNED_BYTE, + params.lscLut.data(), GL_LINEAR); + lscTextureCreated_ = true; + } + + lscLutVersion_ = params.lscLutVersion; + } + } + /* * 0 = Red, 1 = Green, 2 = Blue */ @@ -552,7 +585,11 @@ eGLImage *DebayerEGL::getCachedInputFrameBuffer(FrameBuffer *input, std::optiona return nullptr; } if (cache_miss) - egl_.createTexture2D(*eglImageIn, inMapped->value().planes()[0].data(), GL_NEAREST); + egl_.createTexture2D(*eglImageIn, + eglImageIn->format_, + GL_UNSIGNED_BYTE, + inMapped->value().planes()[0].data(), + GL_NEAREST); else egl_.updateTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); @@ -698,6 +735,7 @@ void DebayerEGL::stop() { eglImageOutCache_.clear(); eglImageInCache_.clear(); + lscTextureCreated_ = false; if (programId_) glDeleteProgram(programId_); diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h index 26b009da4..9798dd559 100644 --- a/src/libcamera/software_isp/debayer_egl.h +++ b/src/libcamera/software_isp/debayer_egl.h @@ -84,7 +84,12 @@ private: unsigned int inputBufferCount_; unsigned int outputBufferCount_; + /* LSC lookup table */ + std::unique_ptr eglImageLscLookup_; bool lscEnabled_; + bool lscTextureCreated_ = false; + uint64_t lscLutVersion_ = 0; + /* Shader parameters */ float firstRed_x_; float firstRed_y_; @@ -101,6 +106,9 @@ private: /* Per-frame AWB gains */ GLint awbUniformDataIn_; + /* Lens shading correction */ + GLint textureUniformLsc_; + /* Represent per-frame CCM as a uniform vector of floats 3 x 3 */ GLint ccmUniformDataIn_;