[{"id":37885,"web_url":"https://patchwork.libcamera.org/comment/37885/","msgid":"<55308b0c-4856-45e2-810c-77e39ab1581f@collabora.com>","date":"2026-01-22T16:58:32","subject":"Re: [PATCH v4 14/15] libcamera: ipa: simple: Remove Lut algorithm","submitter":{"id":140,"url":"https://patchwork.libcamera.org/api/people/140/","name":"Robert Mader","email":"robert.mader@collabora.com"},"content":"Hi, thanks for the series!\n\nTrying this commit (on top of current master) on a FP5 (qcom) makes the \nsoft-isp fail with\n\nERROR IPAModuleAlgo module.h:91 IPASoft: Algorithm 'Lut' not found\nERROR SoftwareIsp software_isp.cpp:157 IPA init failed\nWARN SimplePipeline simple.cpp:620 Failed to create software ISP, disabling software debayering\n\nfor me. It's printed for each sensor/camera when running `cam -l`.\n\nOn 22.01.26 17:19, Milan Zamazal wrote:\n> The Lut algorithm is not really an algorithm.  Moreover, algorithms may\n> be enabled or disabled but with Lut disabled, nothing will work.\n>\n> Let's move the construction of lookup tables to CPU debayering, where it\n> is used.  The implied and related changes are:\n>\n> - DebayerParams is changed to contain the real params rather than lookup\n>    tables.\n> - contrastExp parameter introduced by GPU ISP is used for CPU ISP too.\n> - The params must be initialised so that debayering gets meaningful\n>    parameter values even when some algorithms are disabled.\n> - combinedMatrix must be put to params everywhere where it is modified.\n> - Matrix changes needn't be tracked in the algorithms any more.\n> - CPU debayering must watch for changes of the corresponding parameters\n>    to update the lookup tables when and only when needed.\n> - Swapping red and blue is integrated into lookup table constructions.\n> - gpuIspEnabled flags are removed as they are not needed any more.\n>\n> Signed-off-by: Milan Zamazal<mzamazal@redhat.com>\n> ---\n>   .../internal/software_isp/debayer_params.h    |  43 +----\n>   include/libcamera/ipa/soft.mojom              |   3 +-\n>   src/ipa/simple/algorithms/adjust.cpp          |  17 +-\n>   src/ipa/simple/algorithms/adjust.h            |   4 -\n>   src/ipa/simple/algorithms/awb.cpp             |   6 +-\n>   src/ipa/simple/algorithms/ccm.cpp             |   1 -\n>   src/ipa/simple/algorithms/lut.cpp             | 140 --------------\n>   src/ipa/simple/algorithms/lut.h               |  35 ----\n>   src/ipa/simple/algorithms/meson.build         |   1 -\n>   src/ipa/simple/data/uncalibrated.yaml         |   1 -\n>   src/ipa/simple/ipa_context.h                  |  11 --\n>   src/ipa/simple/soft_simple.cpp                |  11 +-\n>   src/libcamera/software_isp/debayer.cpp        | 172 +-----------------\n>   src/libcamera/software_isp/debayer.h          |   8 -\n>   src/libcamera/software_isp/debayer_cpu.cpp    | 136 ++++++++++++--\n>   src/libcamera/software_isp/debayer_cpu.h      |  26 ++-\n>   src/libcamera/software_isp/debayer_egl.cpp    |  22 +--\n>   src/libcamera/software_isp/software_isp.cpp   |  29 +--\n>   18 files changed, 188 insertions(+), 478 deletions(-)\n>   delete mode 100644 src/ipa/simple/algorithms/lut.cpp\n>   delete mode 100644 src/ipa/simple/algorithms/lut.h\n>\n> diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h\n> index 2d69bd295..1c0412d75 100644\n> --- a/include/libcamera/internal/software_isp/debayer_params.h\n> +++ b/include/libcamera/internal/software_isp/debayer_params.h\n> @@ -1,6 +1,6 @@\n>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>   /*\n> - * Copyright (C) 2023-2025 Red Hat Inc.\n> + * Copyright (C) 2023-2026 Red Hat Inc.\n>    *\n>    * Authors:\n>    * Hans de Goede<hdegoede@redhat.com>\n> @@ -10,7 +10,6 @@\n>   \n>   #pragma once\n>   \n> -#include <array>\n>   #include <stdint.h>\n>   \n>   #include \"libcamera/internal/matrix.h\"\n> @@ -19,47 +18,11 @@\n>   namespace libcamera {\n>   \n>   struct DebayerParams {\n> -\tstatic constexpr unsigned int kRGBLookupSize = 256;\n> -\n> -\tstruct CcmColumn {\n> -\t\tint16_t r;\n> -\t\tint16_t g;\n> -\t\tint16_t b;\n> -\t};\n> -\n> -\tusing LookupTable = std::array<uint8_t, kRGBLookupSize>;\n> -\tusing CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>;\n> -\n> -\t/*\n> -\t * Color lookup tables when CCM is not used.\n> -\t *\n> -\t * Each color of a debayered pixel is amended by the corresponding\n> -\t * value in the given table.\n> -\t */\n> -\tLookupTable red;\n> -\tLookupTable green;\n> -\tLookupTable blue;\n> -\n> -\t/*\n> -\t * Color and gamma lookup tables when CCM is used.\n> -\t *\n> -\t * Each of the CcmLookupTable's corresponds to a CCM column; together they\n> -\t * make a complete 3x3 CCM lookup table. The CCM is applied on debayered\n> -\t * pixels and then the gamma lookup table is used to set the resulting\n> -\t * values of all the three colors.\n> -\t */\n> -\tCcmLookupTable redCcm;\n> -\tCcmLookupTable greenCcm;\n> -\tCcmLookupTable blueCcm;\n> -\tLookupTable gammaLut;\n> -\n> -\t/*\n> -\t * Per frame corrections as calculated by the IPA\n> -\t */\n> -\tMatrix<float, 3, 3> ccm;\n> +\tMatrix<float, 3, 3> combinedMatrix;\n>   \tRGB<float> blackLevel;\n>   \tfloat gamma;\n>   \tfloat contrastExp;\n> +\tRGB<float> gains;\n>   };\n>   \n>   } /* namespace libcamera */\n> diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom\n> index aff8fcbd3..77328c5fd 100644\n> --- a/include/libcamera/ipa/soft.mojom\n> +++ b/include/libcamera/ipa/soft.mojom\n> @@ -17,8 +17,7 @@ interface IPASoftInterface {\n>   \t     libcamera.SharedFD fdStats,\n>   \t     libcamera.SharedFD fdParams,\n>   \t     libcamera.IPACameraSensorInfo sensorInfo,\n> -\t     libcamera.ControlInfoMap sensorControls,\n> -\t     bool gpuIspEnabled)\n> +\t     libcamera.ControlInfoMap sensorControls)\n>   \t\t=> (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled);\n>   \tstart() => (int32 ret);\n>   \tstop();\n> diff --git a/src/ipa/simple/algorithms/adjust.cpp b/src/ipa/simple/algorithms/adjust.cpp\n> index acdd3f741..068e98404 100644\n> --- a/src/ipa/simple/algorithms/adjust.cpp\n> +++ b/src/ipa/simple/algorithms/adjust.cpp\n> @@ -95,23 +95,20 @@ void Adjust::applySaturation(Matrix<float, 3, 3> &matrix, float saturation)\n>   void Adjust::prepare(IPAContext &context,\n>   \t\t     [[maybe_unused]] const uint32_t frame,\n>   \t\t     IPAFrameContext &frameContext,\n> -\t\t     [[maybe_unused]] DebayerParams *params)\n> +\t\t     DebayerParams *params)\n>   {\n>   \tframeContext.gamma = context.activeState.knobs.gamma;\n>   \tframeContext.contrast = context.activeState.knobs.contrast;\n>   \n> -\tif (!context.ccmEnabled)\n> -\t\treturn;\n> -\n>   \tauto &saturation = context.activeState.knobs.saturation;\n> -\tframeContext.saturation = saturation;\n> -\tif (saturation)\n> +\tif (context.ccmEnabled && saturation) {\n>   \t\tapplySaturation(context.activeState.combinedMatrix, saturation.value());\n> -\n> -\tif (saturation != lastSaturation_) {\n> -\t\tcontext.activeState.matrixChanged = true;\n> -\t\tlastSaturation_ = saturation;\n> +\t\tframeContext.saturation = saturation;\n>   \t}\n> +\n> +\tparams->gamma = 1.0 / context.activeState.knobs.gamma;\n> +\tconst float contrast = context.activeState.knobs.contrast.value_or(kDefaultContrast);\n> +\tparams->contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001));\n>   }\n>   \n>   void Adjust::process([[maybe_unused]] IPAContext &context,\n> diff --git a/src/ipa/simple/algorithms/adjust.h b/src/ipa/simple/algorithms/adjust.h\n> index 7644138ff..fb133b140 100644\n> --- a/src/ipa/simple/algorithms/adjust.h\n> +++ b/src/ipa/simple/algorithms/adjust.h\n> @@ -7,8 +7,6 @@\n>   \n>   #pragma once\n>   \n> -#include <optional>\n> -\n>   #include \"libcamera/internal/matrix.h\"\n>   \n>   #include <libipa/interpolator.h>\n> @@ -45,8 +43,6 @@ public:\n>   \n>   private:\n>   \tvoid applySaturation(Matrix<float, 3, 3> &ccm, float saturation);\n> -\n> -\tstd::optional<float> lastSaturation_;\n>   };\n>   \n>   } /* namespace ipa::soft::algorithms */\n> diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp\n> index 4d2f1df15..d2c1b87f0 100644\n> --- a/src/ipa/simple/algorithms/awb.cpp\n> +++ b/src/ipa/simple/algorithms/awb.cpp\n> @@ -37,7 +37,7 @@ int Awb::configure(IPAContext &context,\n>   void Awb::prepare(IPAContext &context,\n>   \t\t  [[maybe_unused]] const uint32_t frame,\n>   \t\t  IPAFrameContext &frameContext,\n> -\t\t  [[maybe_unused]] DebayerParams *params)\n> +\t\t  DebayerParams *params)\n>   {\n>   \tauto &gains = context.activeState.awb.gains;\n>   \tMatrix<float, 3, 3> gainMatrix = { { gains.r(), 0, 0,\n> @@ -45,9 +45,11 @@ void Awb::prepare(IPAContext &context,\n>   \t\t\t\t\t     0, 0, gains.b() } };\n>   \tcontext.activeState.combinedMatrix =\n>   \t\tcontext.activeState.combinedMatrix * gainMatrix;\n> -\t/* Just report, the gains are applied in LUT algorithm. */\n> +\n>   \tframeContext.gains.red = gains.r();\n>   \tframeContext.gains.blue = gains.b();\n> +\n> +\tparams->gains = gains;\n>   }\n>   \n>   void Awb::process(IPAContext &context,\n> diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp\n> index 5576a301f..911a5af2c 100644\n> --- a/src/ipa/simple/algorithms/ccm.cpp\n> +++ b/src/ipa/simple/algorithms/ccm.cpp\n> @@ -51,7 +51,6 @@ void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>   \t    utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) {\n>   \t\tcurrentCcm_ = ccm_.getInterpolated(ct);\n>   \t\tlastCt_ = ct;\n> -\t\tcontext.activeState.matrixChanged = true;\n>   \t}\n>   \n>   \tcontext.activeState.combinedMatrix =\n> diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp\n> deleted file mode 100644\n> index fd442259a..000000000\n> --- a/src/ipa/simple/algorithms/lut.cpp\n> +++ /dev/null\n> @@ -1,140 +0,0 @@\n> -/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> -/*\n> - * Copyright (C) 2024-2026, Red Hat Inc.\n> - *\n> - * Color lookup tables construction\n> - */\n> -\n> -#include \"lut.h\"\n> -\n> -#include <algorithm>\n> -#include <cmath>\n> -#include <optional>\n> -#include <stdint.h>\n> -\n> -#include <libcamera/base/log.h>\n> -\n> -#include <libcamera/control_ids.h>\n> -\n> -#include \"simple/ipa_context.h\"\n> -\n> -#include \"adjust.h\"\n> -\n> -namespace libcamera {\n> -\n> -LOG_DEFINE_CATEGORY(IPASoftLut)\n> -\n> -namespace ipa::soft::algorithms {\n> -\n> -int Lut::configure(IPAContext &context,\n> -\t\t   [[maybe_unused]] const IPAConfigInfo &configInfo)\n> -{\n> -\tupdateGammaTable(context);\n> -\n> -\treturn 0;\n> -}\n> -\n> -void Lut::updateGammaTable(IPAContext &context)\n> -{\n> -\tconst auto blackLevel = context.activeState.blc.level;\n> -\tconst auto gamma = 1.0 / context.activeState.knobs.gamma;\n> -\tconst auto contrast = context.activeState.knobs.contrast.value_or(1.0);\n> -\t/* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */\n> -\tfloat contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001));\n> -\n> -\tif (!context.gpuIspEnabled) {\n> -\t\tauto &gammaTable = context.activeState.gamma.gammaTable;\n> -\t\tconst unsigned int blackIndex = blackLevel * gammaTable.size() / 256;\n> -\t\tconst float divisor = gammaTable.size() - blackIndex - 1.0;\n> -\t\tfor (unsigned int i = blackIndex; i < gammaTable.size(); i++) {\n> -\t\t\tdouble normalized = (i - blackIndex) / divisor;\n> -\t\t\t/* Apply simple S-curve */\n> -\t\t\tif (normalized < 0.5)\n> -\t\t\t\tnormalized = 0.5 * std::pow(normalized / 0.5, contrastExp);\n> -\t\t\telse\n> -\t\t\t\tnormalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp);\n> -\t\t\tgammaTable[i] = UINT8_MAX * std::pow(normalized, gamma);\n> -\t\t}\n> -\t\t/*\n> -\t\t * Due to CCM operations, the table lookup may reach indices below the black\n> -\t\t * level. Let's set the table values below black level to the minimum\n> -\t\t * non-black value to prevent problems when the minimum value is\n> -\t\t * significantly non-zero (for example, when the image should be all grey).\n> -\t\t */\n> -\t\tstd::fill(gammaTable.begin(), gammaTable.begin() + blackIndex,\n> -\t\t\t  gammaTable[blackIndex]);\n> -\t}\n> -\n> -\tcontext.activeState.gamma.gamma = gamma;\n> -\tcontext.activeState.gamma.blackLevel = blackLevel;\n> -\tcontext.activeState.gamma.contrastExp = contrastExp;\n> -}\n> -\n> -int16_t Lut::matrixValue(unsigned int i, float ccm) const\n> -{\n> -\treturn std::round(i * ccm);\n> -}\n> -\n> -void Lut::prepare(IPAContext &context,\n> -\t\t  [[maybe_unused]] const uint32_t frame,\n> -\t\t  [[maybe_unused]] IPAFrameContext &frameContext,\n> -\t\t  DebayerParams *params)\n> -{\n> -\t/*\n> -\t * Update the gamma table if needed. This means if black level changes\n> -\t * and since the black level gets updated only if a lower value is\n> -\t * observed, it's not permanently prone to minor fluctuations or\n> -\t * rounding errors.\n> -\t */\n> -\tconst bool gammaUpdateNeeded =\n> -\t\tcontext.activeState.gamma.blackLevel != context.activeState.blc.level ||\n> -\t\tcontext.activeState.gamma.contrast != context.activeState.knobs.contrast;\n> -\tif (gammaUpdateNeeded)\n> -\t\tupdateGammaTable(context);\n> -\n> -\tauto &gains = context.activeState.awb.gains;\n> -\tauto &gammaTable = context.activeState.gamma.gammaTable;\n> -\tconst unsigned int gammaTableSize = gammaTable.size();\n> -\tconst double div = static_cast<double>(DebayerParams::kRGBLookupSize) /\n> -\t\t\t   gammaTableSize;\n> -\n> -\tif (!context.ccmEnabled) {\n> -\t\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n> -\t\t\t/* Apply gamma after gain! */\n> -\t\t\tconst RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);\n> -\t\t\tparams->red[i] = gammaTable[static_cast<unsigned int>(lutGains.r())];\n> -\t\t\tparams->green[i] = gammaTable[static_cast<unsigned int>(lutGains.g())];\n> -\t\t\tparams->blue[i] = gammaTable[static_cast<unsigned int>(lutGains.b())];\n> -\t\t}\n> -\t} else if (context.activeState.matrixChanged || gammaUpdateNeeded) {\n> -\t\tauto &matrix = context.activeState.combinedMatrix;\n> -\t\tauto &red = params->redCcm;\n> -\t\tauto &green = params->greenCcm;\n> -\t\tauto &blue = params->blueCcm;\n> -\t\tparams->ccm = matrix;\n> -\t\tif (!context.gpuIspEnabled) {\n> -\t\t\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n> -\t\t\t\tred[i].r = matrixValue(i, matrix[0][0]);\n> -\t\t\t\tred[i].g = matrixValue(i, matrix[1][0]);\n> -\t\t\t\tred[i].b = matrixValue(i, matrix[2][0]);\n> -\t\t\t\tgreen[i].r = matrixValue(i, matrix[0][1]);\n> -\t\t\t\tgreen[i].g = matrixValue(i, matrix[1][1]);\n> -\t\t\t\tgreen[i].b = matrixValue(i, matrix[2][1]);\n> -\t\t\t\tblue[i].r = matrixValue(i, matrix[0][2]);\n> -\t\t\t\tblue[i].g = matrixValue(i, matrix[1][2]);\n> -\t\t\t\tblue[i].b = matrixValue(i, matrix[2][2]);\n> -\t\t\t\tparams->gammaLut[i] = gammaTable[i / div];\n> -\t\t\t}\n> -\t\t}\n> -\t\tcontext.activeState.matrixChanged = false;\n> -\t}\n> -\n> -\tparams->gamma = context.activeState.gamma.gamma;\n> -\tparams->contrastExp = context.activeState.gamma.contrastExp;\n> -}\n> -\n> -REGISTER_IPA_ALGORITHM(Lut, \"Lut\") - -} /* namespace ipa::soft::algorithms */ - -} /* namespace \n> libcamera */ diff --git a/src/ipa/simple/algorithms/lut.h \n> b/src/ipa/simple/algorithms/lut.h deleted file mode 100644 index \n> ad16d1e8e..000000000 --- a/src/ipa/simple/algorithms/lut.h +++ \n> /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: \n> LGPL-2.1-or-later */ -/* - * Copyright (C) 2024, Red Hat Inc. - * - * \n> Color lookup tables construction - */ - -#pragma once - -#include \"algorithm.h\"\n> -\n> -namespace libcamera {\n> -\n> -namespace ipa::soft::algorithms {\n> -\n> -class Lut : public Algorithm\n> -{\n> -public:\n> -\tLut() = default;\n> -\t~Lut() = default;\n> -\n> -\tint configure(IPAContext &context, const IPAConfigInfo &configInfo) override;\n> -\tvoid prepare(IPAContext &context,\n> -\t\t     const uint32_t frame,\n> -\t\t     IPAFrameContext &frameContext,\n> -\t\t     DebayerParams *params) override;\n> -\n> -private:\n> -\tvoid updateGammaTable(IPAContext &context);\n> -\tint16_t matrixValue(unsigned int i, float ccm) const;\n> -};\n> -\n> -} /* namespace ipa::soft::algorithms */\n> -\n> -} /* namespace libcamera */\n> diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build\n> index ebe9f20dd..73c637220 100644\n> --- a/src/ipa/simple/algorithms/meson.build\n> +++ b/src/ipa/simple/algorithms/meson.build\n> @@ -6,5 +6,4 @@ soft_simple_ipa_algorithms = files([\n>       'agc.cpp',\n>       'blc.cpp',\n>       'ccm.cpp',\n> -    'lut.cpp',\n>   ])\n> diff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml\n> index e389e0588..c6feda36d 100644\n> --- a/src/ipa/simple/data/uncalibrated.yaml\n> +++ b/src/ipa/simple/data/uncalibrated.yaml\n> @@ -15,6 +15,5 @@ algorithms:\n>                    0, 1, 0,\n>                    0, 0, 1]\n>     - Adjust:\n> -  - Lut:\n>     - Agc:\n>   ...\n> diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h\n> index 293e35b71..34f7403a4 100644\n> --- a/src/ipa/simple/ipa_context.h\n> +++ b/src/ipa/simple/ipa_context.h\n> @@ -53,17 +53,7 @@ struct IPAActiveState {\n>   \t\tunsigned int temperatureK;\n>   \t} awb;\n>   \n> -\tstatic constexpr unsigned int kGammaLookupSize = 1024;\n> -\tstruct {\n> -\t\tstd::array<double, kGammaLookupSize> gammaTable;\n> -\t\tuint8_t blackLevel;\n> -\t\tfloat gamma;\n> -\t\tfloat contrast;\n> -\t\tfloat contrastExp;\n> -\t} gamma;\n> -\n>   \tMatrix<float, 3, 3> combinedMatrix;\n> -\tbool matrixChanged = false;\n>   \n>   \tstruct {\n>   \t\tfloat gamma;\n> @@ -103,7 +93,6 @@ struct IPAContext {\n>   \tFCQueue<IPAFrameContext> frameContexts;\n>   \tControlInfoMap::Map ctrlMap;\n>   \tbool ccmEnabled = false;\n> -\tbool gpuIspEnabled = false;\n>   };\n>   \n>   } /* namespace ipa::soft */\n> diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp\n> index 732e82510..6bef597c8 100644\n> --- a/src/ipa/simple/soft_simple.cpp\n> +++ b/src/ipa/simple/soft_simple.cpp\n> @@ -26,6 +26,7 @@\n>   #include \"libcamera/internal/software_isp/swisp_stats.h\"\n>   #include \"libcamera/internal/yaml_parser.h\"\n>   \n> +#include \"algorithms/adjust.h\"\n>   #include \"libipa/camera_sensor_helper.h\"\n>   \n>   #include \"module.h\"\n> @@ -55,7 +56,6 @@ public:\n>   \t\t const SharedFD &fdParams,\n>   \t\t const IPACameraSensorInfo &sensorInfo,\n>   \t\t const ControlInfoMap &sensorControls,\n> -\t\t bool gpuIspEnabled,\n>   \t\t ControlInfoMap *ipaControls,\n>   \t\t bool *ccmEnabled) override;\n>   \tint configure(const IPAConfigInfo &configInfo) override;\n> @@ -96,7 +96,6 @@ int IPASoftSimple::init(const IPASettings &settings,\n>   \t\t\tconst SharedFD &fdParams,\n>   \t\t\tconst IPACameraSensorInfo &sensorInfo,\n>   \t\t\tconst ControlInfoMap &sensorControls,\n> -\t\t\tbool gpuIspEnabled,\n>   \t\t\tControlInfoMap *ipaControls,\n>   \t\t\tbool *ccmEnabled)\n>   {\n> @@ -108,7 +107,6 @@ int IPASoftSimple::init(const IPASettings &settings,\n>   \t}\n>   \n>   \tcontext_.sensorInfo = sensorInfo;\n> -\tcontext_.gpuIspEnabled = gpuIspEnabled;\n>   \n>   \t/* Load the tuning data file */\n>   \tFile file(settings.configurationFile);\n> @@ -161,6 +159,11 @@ int IPASoftSimple::init(const IPASettings &settings,\n>   \t\t}\n>   \n>   \t\tparams_ = static_cast<DebayerParams *>(mem);\n> +\t\tparams_->blackLevel = { { 0.0, 0.0, 0.0 } };\n> +\t\tparams_->gamma = 1.0 / algorithms::kDefaultGamma;\n> +\t\tparams_->contrastExp = 1.0;\n> +\t\tparams_->gains = { { 1.0, 1.0, 1.0 } };\n> +\t\t/* combinedMatrix is reset for each frame. */\n>   \t}\n>   \n>   \t{\n> @@ -287,6 +290,8 @@ void IPASoftSimple::computeParams(const uint32_t frame)\n>   \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n>   \tfor (auto const &algo : algorithms())\n>   \t\talgo->prepare(context_, frame, frameContext, params_);\n> +\tparams_->combinedMatrix = context_.activeState.combinedMatrix;\n> +\n>   \tsetIspParams.emit();\n>   }\n>   \n> diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp\n> index 65a1762dd..dccdd86b4 100644\n> --- a/src/libcamera/software_isp/debayer.cpp\n> +++ b/src/libcamera/software_isp/debayer.cpp\n> @@ -1,7 +1,7 @@\n>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>   /*\n>    * Copyright (C) 2023, Linaro Ltd\n> - * Copyright (C) 2023-2025 Red Hat Inc.\n> + * Copyright (C) 2023-2026 Red Hat Inc.\n>    *\n>    * Authors:\n>    * Hans de Goede<hdegoede@redhat.com>\n> @@ -25,99 +25,28 @@ namespace libcamera {\n>    */\n>   \n>   /**\n> - * \\var DebayerParams::kRGBLookupSize\n> - * \\brief Size of a color lookup table\n> + * \\var DebayerParams::gains\n> + * \\brief Colour channel gains\n>    */\n>   \n>   /**\n> - * \\struct DebayerParams::CcmColumn\n> - * \\brief Type of a single column of a color correction matrix (CCM)\n> - *\n> - * When multiplying an input pixel, columns in the CCM correspond to the red,\n> - * green or blue component of input pixel values, while rows correspond to the\n> - * red, green or blue components of the output pixel values. The members of the\n> - * CcmColumn structure are named after the colour components of the output pixel\n> - * values they correspond to.\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::CcmColumn::r\n> - * \\brief Red (first) component of a CCM column\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::CcmColumn::g\n> - * \\brief Green (second) component of a CCM column\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::CcmColumn::b\n> - * \\brief Blue (third) component of a CCM column\n> - */\n> -\n> -/**\n> - * \\typedef DebayerParams::LookupTable\n> - * \\brief Type of the lookup tables for single lookup values\n> - */\n> -\n> -/**\n> - * \\typedef DebayerParams::CcmLookupTable\n> - * \\brief Type of the CCM lookup tables for red, green, blue values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::red\n> - * \\brief Lookup table for red color, mapping input values to output values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::green\n> - * \\brief Lookup table for green color, mapping input values to output values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::blue\n> - * \\brief Lookup table for blue color, mapping input values to output values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::redCcm\n> - * \\brief Lookup table for the CCM red column, mapping input values to output values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::greenCcm\n> - * \\brief Lookup table for the CCM green column, mapping input values to output values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::blueCcm\n> - * \\brief Lookup table for the CCM blue column, mapping input values to output values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::gammaLut\n> - * \\brief Gamma lookup table used with color correction matrix\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::ccm\n> - * \\brief Per frame colour correction matrix for GPUISP\n> + * \\var DebayerParams::combinedMatrix\n> + * \\brief Colour correction matrix, including other adjustments\n>    */\n>   \n>   /**\n>    * \\var DebayerParams::blackLevel\n> - * \\brief Blacklevel gains for the GPUISP\n> + * \\brief Black level values\n>    */\n>   \n>   /**\n>    * \\var DebayerParams::gamma\n> - * \\brief Gamma value for the GPUISP\n> + * \\brief Gamma value, e.g. 1/2.2\n>    */\n>   \n>   /**\n>    * \\var DebayerParams::contrastExp\n> - * \\brief Contrast value for GPUISP\n> + * \\brief Contrast value to be used as an exponent\n>    */\n>   \n>   /**\n> @@ -131,13 +60,6 @@ LOG_DEFINE_CATEGORY(Debayer)\n>   \n>   Debayer::Debayer(const GlobalConfiguration &configuration) : bench_(configuration)\n>   {\n> -\t/* Initialize color lookup tables */\n> -\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n> -\t\tred_[i] = green_[i] = blue_[i] = i;\n> -\t\tredCcm_[i] = { static_cast<int16_t>(i), 0, 0 };\n> -\t\tgreenCcm_[i] = { 0, static_cast<int16_t>(i), 0 };\n> -\t\tblueCcm_[i] = { 0, 0, static_cast<int16_t>(i) };\n> -\t}\n>   }\n>   \n>   Debayer::~Debayer()\n> @@ -305,56 +227,6 @@ Debayer::~Debayer()\n>    * \\brief Output size object\n>    */\n>   \n> -/**\n> - * \\var Debayer::red_\n> - * \\brief Lookup table for red channel gain and correction values\n> - *\n> - * This table provides precomputed per-pixel or per-intensity\n> - * correction values for the red color channel used during debayering.\n> - */\n> -\n> -/**\n> - * \\var Debayer::green_\n> - * \\brief Lookup table for green channel gain and correction values\n> - *\n> - * This table provides precomputed per-pixel or per-intensity\n> - * correction values for the green color channel used during debayering.\n> - */\n> -\n> -/**\n> - * \\var Debayer::blue_\n> - * \\brief Lookup table for blue channel gain and correction values\n> - *\n> - * This table provides precomputed per-pixel or per-intensity\n> - * correction values for the blue color channel used during debayering.\n> - */\n> -\n> -/**\n> - * \\var Debayer::redCcm_\n> - * \\brief Red channel Color Correction Matrix (CCM) lookup table\n> - *\n> - * Contains coefficients for green channel color correction.\n> - */\n> -\n> -/**\n> - * \\var Debayer::greenCcm_\n> - * \\brief Green channel Color Correction Matrix (CCM) lookup table\n> - *\n> - * Contains coefficients for green channel color correction.\n> - */\n> -\n> -/**\n> - * \\var Debayer::blueCcm_\n> - * \\brief Blue channel Color Correction Matrix (CCM) lookup table\n> - *\n> - * Contains coefficients for blue channel color correction.\n> - */\n> -\n> -/**\n> - * \\var Debayer::gammaLut_\n> - * \\brief Gamma correction lookup table\n> - */\n> -\n>   /**\n>    * \\var Debayer::swapRedBlueGains_\n>    * \\brief Flag indicating whether red and blue channel gains should be swapped\n> @@ -396,34 +268,6 @@ Debayer::~Debayer()\n>    * DebayerEGL::start.\n>    */\n>   \n> -/**\n> - * \\fn void Debayer::setParams(DebayerParams &params)\n> - * \\brief Select the bayer params to use for the next frame debayer\n> - * \\param[in] params The parameters to be used in debayering\n> - */\n> -void Debayer::setParams(DebayerParams &params)\n> -{\n> -\tgreen_ = params.green;\n> -\tgreenCcm_ = params.greenCcm;\n> -\tif (swapRedBlueGains_) {\n> -\t\tred_ = params.blue;\n> -\t\tblue_ = params.red;\n> -\t\tredCcm_ = params.blueCcm;\n> -\t\tblueCcm_ = params.redCcm;\n> -\t\tfor (unsigned int i = 0; i < 256; i++) {\n> -\t\t\tstd::swap(redCcm_[i].r, redCcm_[i].b);\n> -\t\t\tstd::swap(greenCcm_[i].r, greenCcm_[i].b);\n> -\t\t\tstd::swap(blueCcm_[i].r, blueCcm_[i].b);\n> -\t\t}\n> -\t} else {\n> -\t\tred_ = params.red;\n> -\t\tblue_ = params.blue;\n> -\t\tredCcm_ = params.redCcm;\n> -\t\tblueCcm_ = params.blueCcm;\n> -\t}\n> -\tgammaLut_ = params.gammaLut;\n> -}\n> -\n>   /**\n>    * \\fn void Debayer::dmaSyncBegin(DebayerParams &params)\n>    * \\brief Common CPU/GPU Dma Sync Buffer begin\n> diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h\n> index cd2db9930..652cff4cc 100644\n> --- a/src/libcamera/software_isp/debayer.h\n> +++ b/src/libcamera/software_isp/debayer.h\n> @@ -78,13 +78,6 @@ public:\n>   \tSize outputSize_;\n>   \tPixelFormat inputPixelFormat_;\n>   \tPixelFormat outputPixelFormat_;\n> -\tDebayerParams::LookupTable red_;\n> -\tDebayerParams::LookupTable green_;\n> -\tDebayerParams::LookupTable blue_;\n> -\tDebayerParams::CcmLookupTable redCcm_;\n> -\tDebayerParams::CcmLookupTable greenCcm_;\n> -\tDebayerParams::CcmLookupTable blueCcm_;\n> -\tDebayerParams::LookupTable gammaLut_;\n>   \tbool swapRedBlueGains_;\n>   \tBenchmark bench_;\n>   \n> @@ -92,7 +85,6 @@ private:\n>   \tvirtual Size patternSize(PixelFormat inputFormat) = 0;\n>   \n>   protected:\n> -\tvoid setParams(DebayerParams &params);\n>   \tvoid dmaSyncBegin(std::vector<DmaSyncer> &dmaSyncers, FrameBuffer *input, FrameBuffer *output);\n>   \tstatic bool isStandardBayerOrder(BayerFormat::Order order);\n>   };\n> diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp\n> index 00738c56b..af7af0a8d 100644\n> --- a/src/libcamera/software_isp/debayer_cpu.cpp\n> +++ b/src/libcamera/software_isp/debayer_cpu.cpp\n> @@ -1,7 +1,7 @@\n>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>   /*\n>    * Copyright (C) 2023, Linaro Ltd\n> - * Copyright (C) 2023-2025 Red Hat Inc.\n> + * Copyright (C) 2023-2026 Red Hat Inc.\n>    *\n>    * Authors:\n>    * Hans de Goede<hdegoede@redhat.com>\n> @@ -68,21 +68,21 @@ DebayerCpu::~DebayerCpu() = default;\n>   #define GAMMA(value) \\\n>   \t*dst++ = gammaLut_[std::clamp(value, 0, static_cast<int>(gammaLut_.size()) - 1)]\n>   \n> -#define STORE_PIXEL(b_, g_, r_)                                        \\\n> -\tif constexpr (ccmEnabled) {                                    \\\n> -\t\tconst DebayerParams::CcmColumn &blue = blueCcm_[b_];   \\\n> -\t\tconst DebayerParams::CcmColumn &green = greenCcm_[g_]; \\\n> -\t\tconst DebayerParams::CcmColumn &red = redCcm_[r_];     \\\n> -\t\tGAMMA(blue.b + green.b + red.b);                       \\\n> -\t\tGAMMA(blue.g + green.g + red.g);                       \\\n> -\t\tGAMMA(blue.r + green.r + red.r);                       \\\n> -\t} else {                                                       \\\n> -\t\t*dst++ = blue_[b_];                                    \\\n> -\t\t*dst++ = green_[g_];                                   \\\n> -\t\t*dst++ = red_[r_];                                     \\\n> -\t}                                                              \\\n> -\tif constexpr (addAlphaByte)                                    \\\n> -\t\t*dst++ = 255;                                          \\\n> +#define STORE_PIXEL(b_, g_, r_)                         \\\n> +\tif constexpr (ccmEnabled) {                     \\\n> +\t\tconst CcmColumn &blue = blueCcm_[b_];   \\\n> +\t\tconst CcmColumn &green = greenCcm_[g_]; \\\n> +\t\tconst CcmColumn &red = redCcm_[r_];     \\\n> +\t\tGAMMA(blue.b + green.b + red.b);        \\\n> +\t\tGAMMA(blue.g + green.g + red.g);        \\\n> +\t\tGAMMA(blue.r + green.r + red.r);        \\\n> +\t} else {                                        \\\n> +\t\t*dst++ = blue_[b_];                     \\\n> +\t\t*dst++ = green_[g_];                    \\\n> +\t\t*dst++ = red_[r_];                      \\\n> +\t}                                               \\\n> +\tif constexpr (addAlphaByte)                     \\\n> +\t\t*dst++ = 255;                           \\\n>   \tx++;\n>   \n>   /*\n> @@ -525,6 +525,16 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg,\n>   \tif (ret != 0)\n>   \t\treturn -EINVAL;\n>   \n> +\tccmEnabled_ = ccmEnabled;\n> +\n> +\t/*\n> +\t * Lookup tables must be initialized because the initial value is used for\n> +\t * the first two frames, i.e. until stats processing starts providing its\n> +\t * own parameters. Let's enforce recomputing lookup tables by setting the\n> +\t * stored last used gamma to an out-of-range value.\n> +\t */\n> +\tparams_.gamma = 1.0;\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> @@ -740,6 +750,98 @@ void DebayerCpu::process4(uint32_t frame, const uint8_t *src, uint8_t *dst)\n>   \t}\n>   }\n>   \n> +void DebayerCpu::updateGammaTable(DebayerParams &params)\n> +{\n> +\tconst RGB<float> blackLevel = params.blackLevel;\n> +\t/* Take let's say the green channel black level */\n> +\tconst unsigned int blackIndex = blackLevel[1] * gammaTable_.size();\n> +\tconst float gamma = params.gamma;\n> +\tconst float contrastExp = params.contrastExp;\n> +\n> +\tconst float divisor = gammaTable_.size() - blackIndex - 1.0;\n> +\tfor (unsigned int i = blackIndex; i < gammaTable_.size(); i++) {\n> +\t\tfloat normalized = (i - blackIndex) / divisor;\n> +\t\t/* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */\n> +\t\t/* Apply simple S-curve */\n> +\t\tif (normalized < 0.5)\n> +\t\t\tnormalized = 0.5 * std::pow(normalized / 0.5, contrastExp);\n> +\t\telse\n> +\t\t\tnormalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp);\n> +\t\tgammaTable_[i] = UINT8_MAX *\n> +\t\t\t\t std::pow(normalized, gamma);\n> +\t}\n> +\t/*\n> +\t * Due to CCM operations, the table lookup may reach indices below the black\n> +\t * level. Let's set the table values below black level to the minimum\n> +\t * non-black value to prevent problems when the minimum value is\n> +\t * significantly non-zero (for example, when the image should be all grey).\n> +\t */\n> +\tstd::fill(gammaTable_.begin(), gammaTable_.begin() + blackIndex,\n> +\t\t  gammaTable_[blackIndex]);\n> +}\n> +\n> +void DebayerCpu::updateLookupTables(DebayerParams &params)\n> +{\n> +\tconst bool gammaUpdateNeeded =\n> +\t\tparams.gamma != params_.gamma ||\n> +\t\tparams.blackLevel != params_.blackLevel ||\n> +\t\tparams.contrastExp != params_.contrastExp;\n> +\tif (gammaUpdateNeeded)\n> +\t\tupdateGammaTable(params);\n> +\n> +\tauto matrixChanged = [](const Matrix<float, 3, 3> &m1, const Matrix<float, 3, 3> &m2) -> bool {\n> +\t\treturn !std::equal(m1.data().begin(), m1.data().end(), m2.data().begin());\n> +\t};\n> +\tconst unsigned int gammaTableSize = gammaTable_.size();\n> +\tconst double div = static_cast<double>(kRGBLookupSize) / gammaTableSize;\n> +\tif (ccmEnabled_) {\n> +\t\tif (gammaUpdateNeeded ||\n> +\t\t    matrixChanged(params.combinedMatrix, params_.combinedMatrix)) {\n> +\t\t\tauto &red = swapRedBlueGains_ ? blueCcm_ : redCcm_;\n> +\t\t\tauto &green = greenCcm_;\n> +\t\t\tauto &blue = swapRedBlueGains_ ? redCcm_ : blueCcm_;\n> +\t\t\tconst unsigned int redIndex = swapRedBlueGains_ ? 2 : 0;\n> +\t\t\tconst unsigned int greenIndex = 1;\n> +\t\t\tconst unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2;\n> +\t\t\tfor (unsigned int i = 0; i < kRGBLookupSize; i++) {\n> +\t\t\t\tred[i].r = std::round(i * params.combinedMatrix[redIndex][0]);\n> +\t\t\t\tred[i].g = std::round(i * params.combinedMatrix[greenIndex][0]);\n> +\t\t\t\tred[i].b = std::round(i * params.combinedMatrix[blueIndex][0]);\n> +\t\t\t\tgreen[i].r = std::round(i * params.combinedMatrix[redIndex][1]);\n> +\t\t\t\tgreen[i].g = std::round(i * params.combinedMatrix[greenIndex][1]);\n> +\t\t\t\tgreen[i].b = std::round(i * params.combinedMatrix[blueIndex][1]);\n> +\t\t\t\tblue[i].r = std::round(i * params.combinedMatrix[redIndex][2]);\n> +\t\t\t\tblue[i].g = std::round(i * params.combinedMatrix[greenIndex][2]);\n> +\t\t\t\tblue[i].b = std::round(i * params.combinedMatrix[blueIndex][2]);\n> +\t\t\t\tgammaLut_[i] = gammaTable_[i / div];\n> +\t\t\t}\n> +\t\t}\n> +\t} else {\n> +\t\tif (gammaUpdateNeeded || params.gains != params_.gains) {\n> +\t\t\tauto &gains = params.gains;\n> +\t\t\tauto &red = swapRedBlueGains_ ? blue_ : red_;\n> +\t\t\tauto &green = green_;\n> +\t\t\tauto &blue = swapRedBlueGains_ ? red_ : blue_;\n> +\t\t\tfor (unsigned int i = 0; i < kRGBLookupSize; i++) {\n> +\t\t\t\t/* Apply gamma after gain! */\n> +\t\t\t\tconst RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);\n> +\t\t\t\tred[i] = gammaTable_[static_cast<unsigned int>(lutGains.r())];\n> +\t\t\t\tgreen[i] = gammaTable_[static_cast<unsigned int>(lutGains.g())];\n> +\t\t\t\tblue[i] = gammaTable_[static_cast<unsigned int>(lutGains.b())];\n> +\t\t\t}\n> +\t\t}\n> +\t}\n> +\n> +\tLOG(Debayer, Debug)\n> +\t\t<< \"Debayer parameters: blackLevel=\" << params.blackLevel\n> +\t\t<< \"; gamma=\" << params.gamma\n> +\t\t<< \"; contrastExp=\" << params.contrastExp\n> +\t\t<< \"; gains=\" << params.gains\n> +\t\t<< \"; matrix=\" << params.combinedMatrix;\n> +\n> +\tparams_ = params;\n> +}\n> +\n>   void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params)\n>   {\n>   \tbench_.startFrame();\n> @@ -748,7 +850,7 @@ void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output\n>   \n>   \tdmaSyncBegin(dmaSyncers, input, output);\n>   \n> -\tsetParams(params);\n> +\tupdateLookupTables(params);\n>   \n>   \t/* Copy metadata from the input buffer */\n>   \tFrameMetadata &metadata = output->_d()->metadata();\n> diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h\n> index 67df2b93a..b5cbb5bd2 100644\n> --- a/src/libcamera/software_isp/debayer_cpu.h\n> +++ b/src/libcamera/software_isp/debayer_cpu.h\n> @@ -1,7 +1,7 @@\n>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>   /*\n>    * Copyright (C) 2023, Linaro Ltd\n> - * Copyright (C) 2023-2025 Red Hat Inc.\n> + * Copyright (C) 2023-2026 Red Hat Inc.\n>    *\n>    * Authors:\n>    * Hans de Goede<hdegoede@redhat.com>\n> @@ -18,6 +18,8 @@\n>   #include <libcamera/base/object.h>\n>   \n>   #include \"libcamera/internal/bayer_format.h\"\n> +#include \"libcamera/internal/global_configuration.h\"\n> +#include \"libcamera/internal/software_isp/debayer_params.h\"\n>   #include \"libcamera/internal/software_isp/swstats_cpu.h\"\n>   \n>   #include \"debayer.h\"\n> @@ -108,10 +110,32 @@ private:\n>   \tvoid memcpyNextLine(const uint8_t *linePointers[]);\n>   \tvoid process2(uint32_t frame, const uint8_t *src, uint8_t *dst);\n>   \tvoid process4(uint32_t frame, const uint8_t *src, uint8_t *dst);\n> +\tvoid updateGammaTable(DebayerParams &params);\n> +\tvoid updateLookupTables(DebayerParams &params);\n>   \n>   \t/* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */\n>   \tstatic constexpr unsigned int kMaxLineBuffers = 5;\n>   \n> +\tstatic constexpr unsigned int kRGBLookupSize = 256;\n> +\tstatic constexpr unsigned int kGammaLookupSize = 1024;\n> +\tstruct CcmColumn {\n> +\t\tint16_t r;\n> +\t\tint16_t g;\n> +\t\tint16_t b;\n> +\t};\n> +\tusing LookupTable = std::array<uint8_t, kRGBLookupSize>;\n> +\tusing CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>;\n> +\tLookupTable red_;\n> +\tLookupTable green_;\n> +\tLookupTable blue_;\n> +\tCcmLookupTable redCcm_;\n> +\tCcmLookupTable greenCcm_;\n> +\tCcmLookupTable blueCcm_;\n> +\tstd::array<double, kGammaLookupSize> gammaTable_;\n> +\tLookupTable gammaLut_;\n> +\tbool ccmEnabled_;\n> +\tDebayerParams params_;\n> +\n>   \tdebayerFn debayer0_;\n>   \tdebayerFn debayer1_;\n>   \tdebayerFn debayer2_;\n> diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp\n> index 9693d7252..af04d60ca 100644\n> --- a/src/libcamera/software_isp/debayer_egl.cpp\n> +++ b/src/libcamera/software_isp/debayer_egl.cpp\n> @@ -475,18 +475,18 @@ void DebayerEGL::setShaderVariableValues(DebayerParams &params)\n>   \t\t\t    << \" textureUniformProjMatrix_ \" << textureUniformProjMatrix_;\n>   \n>   \tGLfloat ccm[9] = {\n> -\t\tparams.ccm[0][0],\n> -\t\tparams.ccm[0][1],\n> -\t\tparams.ccm[0][2],\n> -\t\tparams.ccm[1][0],\n> -\t\tparams.ccm[1][1],\n> -\t\tparams.ccm[1][2],\n> -\t\tparams.ccm[2][0],\n> -\t\tparams.ccm[2][1],\n> -\t\tparams.ccm[2][2],\n> +\t\tparams.combinedMatrix[0][0],\n> +\t\tparams.combinedMatrix[0][1],\n> +\t\tparams.combinedMatrix[0][2],\n> +\t\tparams.combinedMatrix[1][0],\n> +\t\tparams.combinedMatrix[1][1],\n> +\t\tparams.combinedMatrix[1][2],\n> +\t\tparams.combinedMatrix[2][0],\n> +\t\tparams.combinedMatrix[2][1],\n> +\t\tparams.combinedMatrix[2][2],\n>   \t};\n>   \tglUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm);\n> -\tLOG(Debayer, Debug) << \" ccmUniformDataIn_ \" << ccmUniformDataIn_ << \" data \" << params.ccm;\n> +\tLOG(Debayer, Debug) << \" ccmUniformDataIn_ \" << ccmUniformDataIn_ << \" data \" << params.combinedMatrix;\n>   \n>   \t/*\n>   \t * 0 = Red, 1 = Green, 2 = Blue\n> @@ -544,8 +544,6 @@ void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output\n>   \n>   \tdmaSyncBegin(dmaSyncers, input, nullptr);\n>   \n> -\tsetParams(params);\n> -\n>   \t/* Copy metadata from the input buffer */\n>   \tFrameMetadata &metadata = output->_d()->metadata();\n>   \tmetadata.status = input->metadata().status;\n> diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp\n> index 7ad3511db..a83986b78 100644\n> --- a/src/libcamera/software_isp/software_isp.cpp\n> +++ b/src/libcamera/software_isp/software_isp.cpp\n> @@ -84,23 +84,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n>   \t\t   DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |\n>   \t\t   DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)\n>   {\n> -\t/*\n> -\t * debayerParams_ must be initialized because the initial value is used for\n> -\t * the first two frames, i.e. until stats processing starts providing its\n> -\t * own parameters.\n> -\t *\n> -\t * \\todo This should be handled in the same place as the related\n> -\t * operations, in the IPA module.\n> -\t */\n> -\tstd::array<uint8_t, 256> gammaTable;\n> -\tfor (unsigned int i = 0; i < 256; i++)\n> -\t\tgammaTable[i] = UINT8_MAX * std::pow(i / 256.0, 0.5);\n> -\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n> -\t\tdebayerParams_.red[i] = gammaTable[i];\n> -\t\tdebayerParams_.green[i] = gammaTable[i];\n> -\t\tdebayerParams_.blue[i] = gammaTable[i];\n> -\t}\n> -\n>   \tif (!dmaHeap_.isValid()) {\n>   \t\tLOG(SoftwareIsp, Error) << \"Failed to create DmaBufAllocator object\";\n>   \t\treturn;\n> @@ -121,8 +104,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n>   \t}\n>   \tstats->statsReady.connect(this, &SoftwareIsp::statsReady);\n>   \n> -\tbool gpuIspEnabled;\n> -\n>   #if HAVE_DEBAYER_EGL\n>   \tstd::optional<std::string> softISPMode = configuration.envOption(\"LIBCAMERA_SOFTISP_MODE\", { \"software_isp\", \"mode\" });\n>   \tif (softISPMode) {\n> @@ -133,15 +114,12 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n>   \t\t}\n>   \t}\n>   \n> -\tif (!softISPMode || softISPMode == \"gpu\") {\n> +\tif (!softISPMode || softISPMode == \"gpu\")\n>   \t\tdebayer_ = std::make_unique<DebayerEGL>(std::move(stats), configuration);\n> -\t\tgpuIspEnabled = true;\n> -\t}\n> +\n>   #endif\n> -\tif (!debayer_) {\n> +\tif (!debayer_)\n>   \t\tdebayer_ = std::make_unique<DebayerCpu>(std::move(stats), configuration);\n> -\t\tgpuIspEnabled = false;\n> -\t}\n>   \n>   \tdebayer_->inputBufferReady.connect(this, &SoftwareIsp::inputReady);\n>   \tdebayer_->outputBufferReady.connect(this, &SoftwareIsp::outputReady);\n> @@ -173,7 +151,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n>   \t\t\t sharedParams_.fd(),\n>   \t\t\t sensorInfo,\n>   \t\t\t sensor->controls(),\n> -\t\t\t gpuIspEnabled,\n>   \t\t\t ipaControls,\n>   \t\t\t &ccmEnabled_);\n>   \tif (ret) {","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 055ABC3220\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 22 Jan 2026 16:58:45 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 453E761FBB;\n\tThu, 22 Jan 2026 17:58:44 +0100 (CET)","from sender4-op-o12.zoho.com (sender4-op-o12.zoho.com\n\t[136.143.188.12])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9658361F84\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 22 Jan 2026 17:58:41 +0100 (CET)","by mx.zohomail.com with SMTPS id 1769101115428862.3954356718662;\n\tThu, 22 Jan 2026 08:58:35 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=collabora.com\n\theader.i=robert.mader@collabora.com header.b=\"hW4rUwO+\"; \n\tdkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1769101118; cv=none; \n\td=zohomail.com; s=zohoarc; \n\tb=NJR4J0k+yO8e3J46JfeXtMtBvbcmESuzpve5ckVCxo+07awK9e7Zc9aqPQ2Xdkoy9dsWScdZzHZQg5GiMuW9TFmWFT3Ypdhp/MtO1JCcvKzkixFeOTcdtZSIDAG2iMdaRv3VIPuzHmIZqZLakzW0/60Pu1UGmakf8J0rkBhkEzU=","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; \n\ts=zohoarc; t=1769101118;\n\th=Content-Type:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To:Cc;\n\tbh=n01Po6pbC1WaPbf9YpjErdjE+feKI0FMZiVqWmxaUrs=; \n\tb=nH8ZetRUv4rGWJFSLqagdv+A3C5OIuKWQ++at57udsj294SJkgSxceHXBJTH9sV3ZISnBsxNkaQXJ5DjvY+d6WvvgnPtwsr8GVmrdXL+kwIRScGI9Vz6DjCioaOXME+lInkOoL2gVyVzTRrenMXLGZeCCzsOpCYQ1M1b72Lr2Zk=","ARC-Authentication-Results":"i=1; mx.zohomail.com;\n\tdkim=pass  header.i=collabora.com;\n\tspf=pass  smtp.mailfrom=robert.mader@collabora.com;\n\tdmarc=pass header.from=<robert.mader@collabora.com>","DKIM-Signature":"v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1769101118;\n\ts=zohomail; d=collabora.com; i=robert.mader@collabora.com;\n\th=Content-Type:Message-ID:Date:Date:MIME-Version:Subject:Subject:To:To:References:From:From:In-Reply-To:Message-Id:Reply-To:Cc;\n\tbh=n01Po6pbC1WaPbf9YpjErdjE+feKI0FMZiVqWmxaUrs=;\n\tb=hW4rUwO+nr4ELQVNRCVCj8ezZsdx/wpzVEbmdUKbJ1lf7qvHY/6zV/Tujxno7lQR\n\t66+ryxKH7cGEWsD/zV+ZVtUfl0C0drHtgleUeVBCwcl8C2bGUCN+gZ1FcSRV/GuuQKf\n\tyesN+2ek4tKAu7uNCWUX3VwsiuJG/NcEm0dRIoDI=","Content-Type":"multipart/alternative;\n\tboundary=\"------------skACIP2uv0V0PUcHNBAeoY5q\"","Message-ID":"<55308b0c-4856-45e2-810c-77e39ab1581f@collabora.com>","Date":"Thu, 22 Jan 2026 17:58:32 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v4 14/15] libcamera: ipa: simple: Remove Lut algorithm","To":"libcamera-devel@lists.libcamera.org","References":"<20260122161935.208562-1-mzamazal@redhat.com>\n\t<20260122161935.208562-15-mzamazal@redhat.com>","Content-Language":"en-US, de-DE","From":"Robert Mader <robert.mader@collabora.com>","In-Reply-To":"<20260122161935.208562-15-mzamazal@redhat.com>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":37886,"web_url":"https://patchwork.libcamera.org/comment/37886/","msgid":"<ebf14c35-3f2d-4dc4-b95a-181c117d7e09@ideasonboard.com>","date":"2026-01-22T17:01:09","subject":"Re: [PATCH v4 14/15] libcamera: ipa: simple: Remove Lut algorithm","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"2026. 01. 22. 17:58 keltezéssel, Robert Mader írta:\n> Hi, thanks for the series!\n> \n> Trying this commit (on top of current master) on a FP5 (qcom) makes the soft-isp fail with\n> \n> ERROR IPAModuleAlgo module.h:91 IPASoft: Algorithm 'Lut' not found\n> ERROR SoftwareIsp software_isp.cpp:157 IPA init failed\n> WARN SimplePipeline simple.cpp:620 Failed to create software ISP, disabling software debayering\n> \n> for me. It's printed for each sensor/camera when running `cam -l`.\n\nMaybe old tuning files are loaded? This patch definitely removes it from\nuncalibrated.yaml but maybe you have sensor specific ones locally.\n\n\n> \n> On 22.01.26 17:19, Milan Zamazal wrote:\n>> The Lut algorithm is not really an algorithm.  Moreover, algorithms may\n>> be enabled or disabled but with Lut disabled, nothing will work.\n>>\n>> Let's move the construction of lookup tables to CPU debayering, where it\n>> is used.  The implied and related changes are:\n>>\n>> - DebayerParams is changed to contain the real params rather than lookup\n>>    tables.\n>> - contrastExp parameter introduced by GPU ISP is used for CPU ISP too.\n>> - The params must be initialised so that debayering gets meaningful\n>>    parameter values even when some algorithms are disabled.\n>> - combinedMatrix must be put to params everywhere where it is modified.\n>> - Matrix changes needn't be tracked in the algorithms any more.\n>> - CPU debayering must watch for changes of the corresponding parameters\n>>    to update the lookup tables when and only when needed.\n>> - Swapping red and blue is integrated into lookup table constructions.\n>> - gpuIspEnabled flags are removed as they are not needed any more.\n>>\n>> Signed-off-by: Milan Zamazal<mzamazal@redhat.com>\n>> ---\n>>   .../internal/software_isp/debayer_params.h    |  43 +----\n>>   include/libcamera/ipa/soft.mojom              |   3 +-\n>>   src/ipa/simple/algorithms/adjust.cpp          |  17 +-\n>>   src/ipa/simple/algorithms/adjust.h            |   4 -\n>>   src/ipa/simple/algorithms/awb.cpp             |   6 +-\n>>   src/ipa/simple/algorithms/ccm.cpp             |   1 -\n>>   src/ipa/simple/algorithms/lut.cpp             | 140 --------------\n>>   src/ipa/simple/algorithms/lut.h               |  35 ----\n>>   src/ipa/simple/algorithms/meson.build         |   1 -\n>>   src/ipa/simple/data/uncalibrated.yaml         |   1 -\n>>   src/ipa/simple/ipa_context.h                  |  11 --\n>>   src/ipa/simple/soft_simple.cpp                |  11 +-\n>>   src/libcamera/software_isp/debayer.cpp        | 172 +-----------------\n>>   src/libcamera/software_isp/debayer.h          |   8 -\n>>   src/libcamera/software_isp/debayer_cpu.cpp    | 136 ++++++++++++--\n>>   src/libcamera/software_isp/debayer_cpu.h      |  26 ++-\n>>   src/libcamera/software_isp/debayer_egl.cpp    |  22 +--\n>>   src/libcamera/software_isp/software_isp.cpp   |  29 +--\n>>   18 files changed, 188 insertions(+), 478 deletions(-)\n>>   delete mode 100644 src/ipa/simple/algorithms/lut.cpp\n>>   delete mode 100644 src/ipa/simple/algorithms/lut.h\n>>\n>> diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h\n>> index 2d69bd295..1c0412d75 100644\n>> --- a/include/libcamera/internal/software_isp/debayer_params.h\n>> +++ b/include/libcamera/internal/software_isp/debayer_params.h\n>> @@ -1,6 +1,6 @@\n>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>   /*\n>> - * Copyright (C) 2023-2025 Red Hat Inc.\n>> + * Copyright (C) 2023-2026 Red Hat Inc.\n>>    *\n>>    * Authors:\n>>    * Hans de Goede<hdegoede@redhat.com>\n>> @@ -10,7 +10,6 @@\n>>   \n>>   #pragma once\n>>   \n>> -#include <array>\n>>   #include <stdint.h>\n>>   \n>>   #include \"libcamera/internal/matrix.h\"\n>> @@ -19,47 +18,11 @@\n>>   namespace libcamera {\n>>   \n>>   struct DebayerParams {\n>> -\tstatic constexpr unsigned int kRGBLookupSize = 256;\n>> -\n>> -\tstruct CcmColumn {\n>> -\t\tint16_t r;\n>> -\t\tint16_t g;\n>> -\t\tint16_t b;\n>> -\t};\n>> -\n>> -\tusing LookupTable = std::array<uint8_t, kRGBLookupSize>;\n>> -\tusing CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>;\n>> -\n>> -\t/*\n>> -\t * Color lookup tables when CCM is not used.\n>> -\t *\n>> -\t * Each color of a debayered pixel is amended by the corresponding\n>> -\t * value in the given table.\n>> -\t */\n>> -\tLookupTable red;\n>> -\tLookupTable green;\n>> -\tLookupTable blue;\n>> -\n>> -\t/*\n>> -\t * Color and gamma lookup tables when CCM is used.\n>> -\t *\n>> -\t * Each of the CcmLookupTable's corresponds to a CCM column; together they\n>> -\t * make a complete 3x3 CCM lookup table. The CCM is applied on debayered\n>> -\t * pixels and then the gamma lookup table is used to set the resulting\n>> -\t * values of all the three colors.\n>> -\t */\n>> -\tCcmLookupTable redCcm;\n>> -\tCcmLookupTable greenCcm;\n>> -\tCcmLookupTable blueCcm;\n>> -\tLookupTable gammaLut;\n>> -\n>> -\t/*\n>> -\t * Per frame corrections as calculated by the IPA\n>> -\t */\n>> -\tMatrix<float, 3, 3> ccm;\n>> +\tMatrix<float, 3, 3> combinedMatrix;\n>>   \tRGB<float> blackLevel;\n>>   \tfloat gamma;\n>>   \tfloat contrastExp;\n>> +\tRGB<float> gains;\n>>   };\n>>   \n>>   } /* namespace libcamera */\n>> diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom\n>> index aff8fcbd3..77328c5fd 100644\n>> --- a/include/libcamera/ipa/soft.mojom\n>> +++ b/include/libcamera/ipa/soft.mojom\n>> @@ -17,8 +17,7 @@ interface IPASoftInterface {\n>>   \t     libcamera.SharedFD fdStats,\n>>   \t     libcamera.SharedFD fdParams,\n>>   \t     libcamera.IPACameraSensorInfo sensorInfo,\n>> -\t     libcamera.ControlInfoMap sensorControls,\n>> -\t     bool gpuIspEnabled)\n>> +\t     libcamera.ControlInfoMap sensorControls)\n>>   \t\t=> (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled);\n>>   \tstart() => (int32 ret);\n>>   \tstop();\n>> diff --git a/src/ipa/simple/algorithms/adjust.cpp b/src/ipa/simple/algorithms/adjust.cpp\n>> index acdd3f741..068e98404 100644\n>> --- a/src/ipa/simple/algorithms/adjust.cpp\n>> +++ b/src/ipa/simple/algorithms/adjust.cpp\n>> @@ -95,23 +95,20 @@ void Adjust::applySaturation(Matrix<float, 3, 3> &matrix, float saturation)\n>>   void Adjust::prepare(IPAContext &context,\n>>   \t\t     [[maybe_unused]] const uint32_t frame,\n>>   \t\t     IPAFrameContext &frameContext,\n>> -\t\t     [[maybe_unused]] DebayerParams *params)\n>> +\t\t     DebayerParams *params)\n>>   {\n>>   \tframeContext.gamma = context.activeState.knobs.gamma;\n>>   \tframeContext.contrast = context.activeState.knobs.contrast;\n>>   \n>> -\tif (!context.ccmEnabled)\n>> -\t\treturn;\n>> -\n>>   \tauto &saturation = context.activeState.knobs.saturation;\n>> -\tframeContext.saturation = saturation;\n>> -\tif (saturation)\n>> +\tif (context.ccmEnabled && saturation) {\n>>   \t\tapplySaturation(context.activeState.combinedMatrix, saturation.value());\n>> -\n>> -\tif (saturation != lastSaturation_) {\n>> -\t\tcontext.activeState.matrixChanged = true;\n>> -\t\tlastSaturation_ = saturation;\n>> +\t\tframeContext.saturation = saturation;\n>>   \t}\n>> +\n>> +\tparams->gamma = 1.0 / context.activeState.knobs.gamma;\n>> +\tconst float contrast = context.activeState.knobs.contrast.value_or(kDefaultContrast);\n>> +\tparams->contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001));\n>>   }\n>>   \n>>   void Adjust::process([[maybe_unused]] IPAContext &context,\n>> diff --git a/src/ipa/simple/algorithms/adjust.h b/src/ipa/simple/algorithms/adjust.h\n>> index 7644138ff..fb133b140 100644\n>> --- a/src/ipa/simple/algorithms/adjust.h\n>> +++ b/src/ipa/simple/algorithms/adjust.h\n>> @@ -7,8 +7,6 @@\n>>   \n>>   #pragma once\n>>   \n>> -#include <optional>\n>> -\n>>   #include \"libcamera/internal/matrix.h\"\n>>   \n>>   #include <libipa/interpolator.h>\n>> @@ -45,8 +43,6 @@ public:\n>>   \n>>   private:\n>>   \tvoid applySaturation(Matrix<float, 3, 3> &ccm, float saturation);\n>> -\n>> -\tstd::optional<float> lastSaturation_;\n>>   };\n>>   \n>>   } /* namespace ipa::soft::algorithms */\n>> diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp\n>> index 4d2f1df15..d2c1b87f0 100644\n>> --- a/src/ipa/simple/algorithms/awb.cpp\n>> +++ b/src/ipa/simple/algorithms/awb.cpp\n>> @@ -37,7 +37,7 @@ int Awb::configure(IPAContext &context,\n>>   void Awb::prepare(IPAContext &context,\n>>   \t\t  [[maybe_unused]] const uint32_t frame,\n>>   \t\t  IPAFrameContext &frameContext,\n>> -\t\t  [[maybe_unused]] DebayerParams *params)\n>> +\t\t  DebayerParams *params)\n>>   {\n>>   \tauto &gains = context.activeState.awb.gains;\n>>   \tMatrix<float, 3, 3> gainMatrix = { { gains.r(), 0, 0,\n>> @@ -45,9 +45,11 @@ void Awb::prepare(IPAContext &context,\n>>   \t\t\t\t\t     0, 0, gains.b() } };\n>>   \tcontext.activeState.combinedMatrix =\n>>   \t\tcontext.activeState.combinedMatrix * gainMatrix;\n>> -\t/* Just report, the gains are applied in LUT algorithm. */\n>> +\n>>   \tframeContext.gains.red = gains.r();\n>>   \tframeContext.gains.blue = gains.b();\n>> +\n>> +\tparams->gains = gains;\n>>   }\n>>   \n>>   void Awb::process(IPAContext &context,\n>> diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp\n>> index 5576a301f..911a5af2c 100644\n>> --- a/src/ipa/simple/algorithms/ccm.cpp\n>> +++ b/src/ipa/simple/algorithms/ccm.cpp\n>> @@ -51,7 +51,6 @@ void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>>   \t    utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) {\n>>   \t\tcurrentCcm_ = ccm_.getInterpolated(ct);\n>>   \t\tlastCt_ = ct;\n>> -\t\tcontext.activeState.matrixChanged = true;\n>>   \t}\n>>   \n>>   \tcontext.activeState.combinedMatrix =\n>> diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp\n>> deleted file mode 100644\n>> index fd442259a..000000000\n>> --- a/src/ipa/simple/algorithms/lut.cpp\n>> +++ /dev/null\n>> @@ -1,140 +0,0 @@\n>> -/* SPDX-License-Identifier: LGPL-2.1-or-later */\n>> -/*\n>> - * Copyright (C) 2024-2026, Red Hat Inc.\n>> - *\n>> - * Color lookup tables construction\n>> - */\n>> -\n>> -#include \"lut.h\"\n>> -\n>> -#include <algorithm>\n>> -#include <cmath>\n>> -#include <optional>\n>> -#include <stdint.h>\n>> -\n>> -#include <libcamera/base/log.h>\n>> -\n>> -#include <libcamera/control_ids.h>\n>> -\n>> -#include \"simple/ipa_context.h\"\n>> -\n>> -#include \"adjust.h\"\n>> -\n>> -namespace libcamera {\n>> -\n>> -LOG_DEFINE_CATEGORY(IPASoftLut)\n>> -\n>> -namespace ipa::soft::algorithms {\n>> -\n>> -int Lut::configure(IPAContext &context,\n>> -\t\t   [[maybe_unused]] const IPAConfigInfo &configInfo)\n>> -{\n>> -\tupdateGammaTable(context);\n>> -\n>> -\treturn 0;\n>> -}\n>> -\n>> -void Lut::updateGammaTable(IPAContext &context)\n>> -{\n>> -\tconst auto blackLevel = context.activeState.blc.level;\n>> -\tconst auto gamma = 1.0 / context.activeState.knobs.gamma;\n>> -\tconst auto contrast = context.activeState.knobs.contrast.value_or(1.0);\n>> -\t/* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */\n>> -\tfloat contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001));\n>> -\n>> -\tif (!context.gpuIspEnabled) {\n>> -\t\tauto &gammaTable = context.activeState.gamma.gammaTable;\n>> -\t\tconst unsigned int blackIndex = blackLevel * gammaTable.size() / 256;\n>> -\t\tconst float divisor = gammaTable.size() - blackIndex - 1.0;\n>> -\t\tfor (unsigned int i = blackIndex; i < gammaTable.size(); i++) {\n>> -\t\t\tdouble normalized = (i - blackIndex) / divisor;\n>> -\t\t\t/* Apply simple S-curve */\n>> -\t\t\tif (normalized < 0.5)\n>> -\t\t\t\tnormalized = 0.5 * std::pow(normalized / 0.5, contrastExp);\n>> -\t\t\telse\n>> -\t\t\t\tnormalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp);\n>> -\t\t\tgammaTable[i] = UINT8_MAX * std::pow(normalized, gamma);\n>> -\t\t}\n>> -\t\t/*\n>> -\t\t * Due to CCM operations, the table lookup may reach indices below the black\n>> -\t\t * level. Let's set the table values below black level to the minimum\n>> -\t\t * non-black value to prevent problems when the minimum value is\n>> -\t\t * significantly non-zero (for example, when the image should be all grey).\n>> -\t\t */\n>> -\t\tstd::fill(gammaTable.begin(), gammaTable.begin() + blackIndex,\n>> -\t\t\t  gammaTable[blackIndex]);\n>> -\t}\n>> -\n>> -\tcontext.activeState.gamma.gamma = gamma;\n>> -\tcontext.activeState.gamma.blackLevel = blackLevel;\n>> -\tcontext.activeState.gamma.contrastExp = contrastExp;\n>> -}\n>> -\n>> -int16_t Lut::matrixValue(unsigned int i, float ccm) const\n>> -{\n>> -\treturn std::round(i * ccm);\n>> -}\n>> -\n>> -void Lut::prepare(IPAContext &context,\n>> -\t\t  [[maybe_unused]] const uint32_t frame,\n>> -\t\t  [[maybe_unused]] IPAFrameContext &frameContext,\n>> -\t\t  DebayerParams *params)\n>> -{\n>> -\t/*\n>> -\t * Update the gamma table if needed. This means if black level changes\n>> -\t * and since the black level gets updated only if a lower value is\n>> -\t * observed, it's not permanently prone to minor fluctuations or\n>> -\t * rounding errors.\n>> -\t */\n>> -\tconst bool gammaUpdateNeeded =\n>> -\t\tcontext.activeState.gamma.blackLevel != context.activeState.blc.level ||\n>> -\t\tcontext.activeState.gamma.contrast != context.activeState.knobs.contrast;\n>> -\tif (gammaUpdateNeeded)\n>> -\t\tupdateGammaTable(context);\n>> -\n>> -\tauto &gains = context.activeState.awb.gains;\n>> -\tauto &gammaTable = context.activeState.gamma.gammaTable;\n>> -\tconst unsigned int gammaTableSize = gammaTable.size();\n>> -\tconst double div = static_cast<double>(DebayerParams::kRGBLookupSize) /\n>> -\t\t\t   gammaTableSize;\n>> -\n>> -\tif (!context.ccmEnabled) {\n>> -\t\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n>> -\t\t\t/* Apply gamma after gain! */\n>> -\t\t\tconst RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);\n>> -\t\t\tparams->red[i] = gammaTable[static_cast<unsigned int>(lutGains.r())];\n>> -\t\t\tparams->green[i] = gammaTable[static_cast<unsigned int>(lutGains.g())];\n>> -\t\t\tparams->blue[i] = gammaTable[static_cast<unsigned int>(lutGains.b())];\n>> -\t\t}\n>> -\t} else if (context.activeState.matrixChanged || gammaUpdateNeeded) {\n>> -\t\tauto &matrix = context.activeState.combinedMatrix;\n>> -\t\tauto &red = params->redCcm;\n>> -\t\tauto &green = params->greenCcm;\n>> -\t\tauto &blue = params->blueCcm;\n>> -\t\tparams->ccm = matrix;\n>> -\t\tif (!context.gpuIspEnabled) {\n>> -\t\t\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n>> -\t\t\t\tred[i].r = matrixValue(i, matrix[0][0]);\n>> -\t\t\t\tred[i].g = matrixValue(i, matrix[1][0]);\n>> -\t\t\t\tred[i].b = matrixValue(i, matrix[2][0]);\n>> -\t\t\t\tgreen[i].r = matrixValue(i, matrix[0][1]);\n>> -\t\t\t\tgreen[i].g = matrixValue(i, matrix[1][1]);\n>> -\t\t\t\tgreen[i].b = matrixValue(i, matrix[2][1]);\n>> -\t\t\t\tblue[i].r = matrixValue(i, matrix[0][2]);\n>> -\t\t\t\tblue[i].g = matrixValue(i, matrix[1][2]);\n>> -\t\t\t\tblue[i].b = matrixValue(i, matrix[2][2]);\n>> -\t\t\t\tparams->gammaLut[i] = gammaTable[i / div];\n>> -\t\t\t}\n>> -\t\t}\n>> -\t\tcontext.activeState.matrixChanged = false;\n>> -\t}\n>> -\n>> -\tparams->gamma = context.activeState.gamma.gamma;\n>> -\tparams->contrastExp = context.activeState.gamma.contrastExp;\n>> -}\n>> -\n>> -REGISTER_IPA_ALGORITHM(Lut, \"Lut\") - -} /* namespace ipa::soft::algorithms */ - -} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/lut.h b/src/ipa/simple/algorithms/lut.h deleted file mode 100644 index ad16d1e8e..000000000 --- a/src/ipa/simple/algorithms/lut.h +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2024, Red Hat Inc. - * - * Color lookup tables construction - */ - -#pragma once - -#include \"algorithm.h\"\n>> -\n>> -namespace libcamera {\n>> -\n>> -namespace ipa::soft::algorithms {\n>> -\n>> -class Lut : public Algorithm\n>> -{\n>> -public:\n>> -\tLut() = default;\n>> -\t~Lut() = default;\n>> -\n>> -\tint configure(IPAContext &context, const IPAConfigInfo &configInfo) override;\n>> -\tvoid prepare(IPAContext &context,\n>> -\t\t     const uint32_t frame,\n>> -\t\t     IPAFrameContext &frameContext,\n>> -\t\t     DebayerParams *params) override;\n>> -\n>> -private:\n>> -\tvoid updateGammaTable(IPAContext &context);\n>> -\tint16_t matrixValue(unsigned int i, float ccm) const;\n>> -};\n>> -\n>> -} /* namespace ipa::soft::algorithms */\n>> -\n>> -} /* namespace libcamera */\n>> diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build\n>> index ebe9f20dd..73c637220 100644\n>> --- a/src/ipa/simple/algorithms/meson.build\n>> +++ b/src/ipa/simple/algorithms/meson.build\n>> @@ -6,5 +6,4 @@ soft_simple_ipa_algorithms = files([\n>>       'agc.cpp',\n>>       'blc.cpp',\n>>       'ccm.cpp',\n>> -    'lut.cpp',\n>>   ])\n>> diff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml\n>> index e389e0588..c6feda36d 100644\n>> --- a/src/ipa/simple/data/uncalibrated.yaml\n>> +++ b/src/ipa/simple/data/uncalibrated.yaml\n>> @@ -15,6 +15,5 @@ algorithms:\n>>                    0, 1, 0,\n>>                    0, 0, 1]\n>>     - Adjust:\n>> -  - Lut:\n>>     - Agc:\n>>   ...\n>> diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h\n>> index 293e35b71..34f7403a4 100644\n>> --- a/src/ipa/simple/ipa_context.h\n>> +++ b/src/ipa/simple/ipa_context.h\n>> @@ -53,17 +53,7 @@ struct IPAActiveState {\n>>   \t\tunsigned int temperatureK;\n>>   \t} awb;\n>>   \n>> -\tstatic constexpr unsigned int kGammaLookupSize = 1024;\n>> -\tstruct {\n>> -\t\tstd::array<double, kGammaLookupSize> gammaTable;\n>> -\t\tuint8_t blackLevel;\n>> -\t\tfloat gamma;\n>> -\t\tfloat contrast;\n>> -\t\tfloat contrastExp;\n>> -\t} gamma;\n>> -\n>>   \tMatrix<float, 3, 3> combinedMatrix;\n>> -\tbool matrixChanged = false;\n>>   \n>>   \tstruct {\n>>   \t\tfloat gamma;\n>> @@ -103,7 +93,6 @@ struct IPAContext {\n>>   \tFCQueue<IPAFrameContext> frameContexts;\n>>   \tControlInfoMap::Map ctrlMap;\n>>   \tbool ccmEnabled = false;\n>> -\tbool gpuIspEnabled = false;\n>>   };\n>>   \n>>   } /* namespace ipa::soft */\n>> diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp\n>> index 732e82510..6bef597c8 100644\n>> --- a/src/ipa/simple/soft_simple.cpp\n>> +++ b/src/ipa/simple/soft_simple.cpp\n>> @@ -26,6 +26,7 @@\n>>   #include \"libcamera/internal/software_isp/swisp_stats.h\"\n>>   #include \"libcamera/internal/yaml_parser.h\"\n>>   \n>> +#include \"algorithms/adjust.h\"\n>>   #include \"libipa/camera_sensor_helper.h\"\n>>   \n>>   #include \"module.h\"\n>> @@ -55,7 +56,6 @@ public:\n>>   \t\t const SharedFD &fdParams,\n>>   \t\t const IPACameraSensorInfo &sensorInfo,\n>>   \t\t const ControlInfoMap &sensorControls,\n>> -\t\t bool gpuIspEnabled,\n>>   \t\t ControlInfoMap *ipaControls,\n>>   \t\t bool *ccmEnabled) override;\n>>   \tint configure(const IPAConfigInfo &configInfo) override;\n>> @@ -96,7 +96,6 @@ int IPASoftSimple::init(const IPASettings &settings,\n>>   \t\t\tconst SharedFD &fdParams,\n>>   \t\t\tconst IPACameraSensorInfo &sensorInfo,\n>>   \t\t\tconst ControlInfoMap &sensorControls,\n>> -\t\t\tbool gpuIspEnabled,\n>>   \t\t\tControlInfoMap *ipaControls,\n>>   \t\t\tbool *ccmEnabled)\n>>   {\n>> @@ -108,7 +107,6 @@ int IPASoftSimple::init(const IPASettings &settings,\n>>   \t}\n>>   \n>>   \tcontext_.sensorInfo = sensorInfo;\n>> -\tcontext_.gpuIspEnabled = gpuIspEnabled;\n>>   \n>>   \t/* Load the tuning data file */\n>>   \tFile file(settings.configurationFile);\n>> @@ -161,6 +159,11 @@ int IPASoftSimple::init(const IPASettings &settings,\n>>   \t\t}\n>>   \n>>   \t\tparams_ = static_cast<DebayerParams *>(mem);\n>> +\t\tparams_->blackLevel = { { 0.0, 0.0, 0.0 } };\n>> +\t\tparams_->gamma = 1.0 / algorithms::kDefaultGamma;\n>> +\t\tparams_->contrastExp = 1.0;\n>> +\t\tparams_->gains = { { 1.0, 1.0, 1.0 } };\n>> +\t\t/* combinedMatrix is reset for each frame. */\n>>   \t}\n>>   \n>>   \t{\n>> @@ -287,6 +290,8 @@ void IPASoftSimple::computeParams(const uint32_t frame)\n>>   \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n>>   \tfor (auto const &algo : algorithms())\n>>   \t\talgo->prepare(context_, frame, frameContext, params_);\n>> +\tparams_->combinedMatrix = context_.activeState.combinedMatrix;\n>> +\n>>   \tsetIspParams.emit();\n>>   }\n>>   \n>> diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp\n>> index 65a1762dd..dccdd86b4 100644\n>> --- a/src/libcamera/software_isp/debayer.cpp\n>> +++ b/src/libcamera/software_isp/debayer.cpp\n>> @@ -1,7 +1,7 @@\n>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>   /*\n>>    * Copyright (C) 2023, Linaro Ltd\n>> - * Copyright (C) 2023-2025 Red Hat Inc.\n>> + * Copyright (C) 2023-2026 Red Hat Inc.\n>>    *\n>>    * Authors:\n>>    * Hans de Goede<hdegoede@redhat.com>\n>> @@ -25,99 +25,28 @@ namespace libcamera {\n>>    */\n>>   \n>>   /**\n>> - * \\var DebayerParams::kRGBLookupSize\n>> - * \\brief Size of a color lookup table\n>> + * \\var DebayerParams::gains\n>> + * \\brief Colour channel gains\n>>    */\n>>   \n>>   /**\n>> - * \\struct DebayerParams::CcmColumn\n>> - * \\brief Type of a single column of a color correction matrix (CCM)\n>> - *\n>> - * When multiplying an input pixel, columns in the CCM correspond to the red,\n>> - * green or blue component of input pixel values, while rows correspond to the\n>> - * red, green or blue components of the output pixel values. The members of the\n>> - * CcmColumn structure are named after the colour components of the output pixel\n>> - * values they correspond to.\n>> - */\n>> -\n>> -/**\n>> - * \\var DebayerParams::CcmColumn::r\n>> - * \\brief Red (first) component of a CCM column\n>> - */\n>> -\n>> -/**\n>> - * \\var DebayerParams::CcmColumn::g\n>> - * \\brief Green (second) component of a CCM column\n>> - */\n>> -\n>> -/**\n>> - * \\var DebayerParams::CcmColumn::b\n>> - * \\brief Blue (third) component of a CCM column\n>> - */\n>> -\n>> -/**\n>> - * \\typedef DebayerParams::LookupTable\n>> - * \\brief Type of the lookup tables for single lookup values\n>> - */\n>> -\n>> -/**\n>> - * \\typedef DebayerParams::CcmLookupTable\n>> - * \\brief Type of the CCM lookup tables for red, green, blue values\n>> - */\n>> -\n>> -/**\n>> - * \\var DebayerParams::red\n>> - * \\brief Lookup table for red color, mapping input values to output values\n>> - */\n>> -\n>> -/**\n>> - * \\var DebayerParams::green\n>> - * \\brief Lookup table for green color, mapping input values to output values\n>> - */\n>> -\n>> -/**\n>> - * \\var DebayerParams::blue\n>> - * \\brief Lookup table for blue color, mapping input values to output values\n>> - */\n>> -\n>> -/**\n>> - * \\var DebayerParams::redCcm\n>> - * \\brief Lookup table for the CCM red column, mapping input values to output values\n>> - */\n>> -\n>> -/**\n>> - * \\var DebayerParams::greenCcm\n>> - * \\brief Lookup table for the CCM green column, mapping input values to output values\n>> - */\n>> -\n>> -/**\n>> - * \\var DebayerParams::blueCcm\n>> - * \\brief Lookup table for the CCM blue column, mapping input values to output values\n>> - */\n>> -\n>> -/**\n>> - * \\var DebayerParams::gammaLut\n>> - * \\brief Gamma lookup table used with color correction matrix\n>> - */\n>> -\n>> -/**\n>> - * \\var DebayerParams::ccm\n>> - * \\brief Per frame colour correction matrix for GPUISP\n>> + * \\var DebayerParams::combinedMatrix\n>> + * \\brief Colour correction matrix, including other adjustments\n>>    */\n>>   \n>>   /**\n>>    * \\var DebayerParams::blackLevel\n>> - * \\brief Blacklevel gains for the GPUISP\n>> + * \\brief Black level values\n>>    */\n>>   \n>>   /**\n>>    * \\var DebayerParams::gamma\n>> - * \\brief Gamma value for the GPUISP\n>> + * \\brief Gamma value, e.g. 1/2.2\n>>    */\n>>   \n>>   /**\n>>    * \\var DebayerParams::contrastExp\n>> - * \\brief Contrast value for GPUISP\n>> + * \\brief Contrast value to be used as an exponent\n>>    */\n>>   \n>>   /**\n>> @@ -131,13 +60,6 @@ LOG_DEFINE_CATEGORY(Debayer)\n>>   \n>>   Debayer::Debayer(const GlobalConfiguration &configuration) : bench_(configuration)\n>>   {\n>> -\t/* Initialize color lookup tables */\n>> -\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n>> -\t\tred_[i] = green_[i] = blue_[i] = i;\n>> -\t\tredCcm_[i] = { static_cast<int16_t>(i), 0, 0 };\n>> -\t\tgreenCcm_[i] = { 0, static_cast<int16_t>(i), 0 };\n>> -\t\tblueCcm_[i] = { 0, 0, static_cast<int16_t>(i) };\n>> -\t}\n>>   }\n>>   \n>>   Debayer::~Debayer()\n>> @@ -305,56 +227,6 @@ Debayer::~Debayer()\n>>    * \\brief Output size object\n>>    */\n>>   \n>> -/**\n>> - * \\var Debayer::red_\n>> - * \\brief Lookup table for red channel gain and correction values\n>> - *\n>> - * This table provides precomputed per-pixel or per-intensity\n>> - * correction values for the red color channel used during debayering.\n>> - */\n>> -\n>> -/**\n>> - * \\var Debayer::green_\n>> - * \\brief Lookup table for green channel gain and correction values\n>> - *\n>> - * This table provides precomputed per-pixel or per-intensity\n>> - * correction values for the green color channel used during debayering.\n>> - */\n>> -\n>> -/**\n>> - * \\var Debayer::blue_\n>> - * \\brief Lookup table for blue channel gain and correction values\n>> - *\n>> - * This table provides precomputed per-pixel or per-intensity\n>> - * correction values for the blue color channel used during debayering.\n>> - */\n>> -\n>> -/**\n>> - * \\var Debayer::redCcm_\n>> - * \\brief Red channel Color Correction Matrix (CCM) lookup table\n>> - *\n>> - * Contains coefficients for green channel color correction.\n>> - */\n>> -\n>> -/**\n>> - * \\var Debayer::greenCcm_\n>> - * \\brief Green channel Color Correction Matrix (CCM) lookup table\n>> - *\n>> - * Contains coefficients for green channel color correction.\n>> - */\n>> -\n>> -/**\n>> - * \\var Debayer::blueCcm_\n>> - * \\brief Blue channel Color Correction Matrix (CCM) lookup table\n>> - *\n>> - * Contains coefficients for blue channel color correction.\n>> - */\n>> -\n>> -/**\n>> - * \\var Debayer::gammaLut_\n>> - * \\brief Gamma correction lookup table\n>> - */\n>> -\n>>   /**\n>>    * \\var Debayer::swapRedBlueGains_\n>>    * \\brief Flag indicating whether red and blue channel gains should be swapped\n>> @@ -396,34 +268,6 @@ Debayer::~Debayer()\n>>    * DebayerEGL::start.\n>>    */\n>>   \n>> -/**\n>> - * \\fn void Debayer::setParams(DebayerParams &params)\n>> - * \\brief Select the bayer params to use for the next frame debayer\n>> - * \\param[in] params The parameters to be used in debayering\n>> - */\n>> -void Debayer::setParams(DebayerParams &params)\n>> -{\n>> -\tgreen_ = params.green;\n>> -\tgreenCcm_ = params.greenCcm;\n>> -\tif (swapRedBlueGains_) {\n>> -\t\tred_ = params.blue;\n>> -\t\tblue_ = params.red;\n>> -\t\tredCcm_ = params.blueCcm;\n>> -\t\tblueCcm_ = params.redCcm;\n>> -\t\tfor (unsigned int i = 0; i < 256; i++) {\n>> -\t\t\tstd::swap(redCcm_[i].r, redCcm_[i].b);\n>> -\t\t\tstd::swap(greenCcm_[i].r, greenCcm_[i].b);\n>> -\t\t\tstd::swap(blueCcm_[i].r, blueCcm_[i].b);\n>> -\t\t}\n>> -\t} else {\n>> -\t\tred_ = params.red;\n>> -\t\tblue_ = params.blue;\n>> -\t\tredCcm_ = params.redCcm;\n>> -\t\tblueCcm_ = params.blueCcm;\n>> -\t}\n>> -\tgammaLut_ = params.gammaLut;\n>> -}\n>> -\n>>   /**\n>>    * \\fn void Debayer::dmaSyncBegin(DebayerParams &params)\n>>    * \\brief Common CPU/GPU Dma Sync Buffer begin\n>> diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h\n>> index cd2db9930..652cff4cc 100644\n>> --- a/src/libcamera/software_isp/debayer.h\n>> +++ b/src/libcamera/software_isp/debayer.h\n>> @@ -78,13 +78,6 @@ public:\n>>   \tSize outputSize_;\n>>   \tPixelFormat inputPixelFormat_;\n>>   \tPixelFormat outputPixelFormat_;\n>> -\tDebayerParams::LookupTable red_;\n>> -\tDebayerParams::LookupTable green_;\n>> -\tDebayerParams::LookupTable blue_;\n>> -\tDebayerParams::CcmLookupTable redCcm_;\n>> -\tDebayerParams::CcmLookupTable greenCcm_;\n>> -\tDebayerParams::CcmLookupTable blueCcm_;\n>> -\tDebayerParams::LookupTable gammaLut_;\n>>   \tbool swapRedBlueGains_;\n>>   \tBenchmark bench_;\n>>   \n>> @@ -92,7 +85,6 @@ private:\n>>   \tvirtual Size patternSize(PixelFormat inputFormat) = 0;\n>>   \n>>   protected:\n>> -\tvoid setParams(DebayerParams &params);\n>>   \tvoid dmaSyncBegin(std::vector<DmaSyncer> &dmaSyncers, FrameBuffer *input, FrameBuffer *output);\n>>   \tstatic bool isStandardBayerOrder(BayerFormat::Order order);\n>>   };\n>> diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp\n>> index 00738c56b..af7af0a8d 100644\n>> --- a/src/libcamera/software_isp/debayer_cpu.cpp\n>> +++ b/src/libcamera/software_isp/debayer_cpu.cpp\n>> @@ -1,7 +1,7 @@\n>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>   /*\n>>    * Copyright (C) 2023, Linaro Ltd\n>> - * Copyright (C) 2023-2025 Red Hat Inc.\n>> + * Copyright (C) 2023-2026 Red Hat Inc.\n>>    *\n>>    * Authors:\n>>    * Hans de Goede<hdegoede@redhat.com>\n>> @@ -68,21 +68,21 @@ DebayerCpu::~DebayerCpu() = default;\n>>   #define GAMMA(value) \\\n>>   \t*dst++ = gammaLut_[std::clamp(value, 0, static_cast<int>(gammaLut_.size()) - 1)]\n>>   \n>> -#define STORE_PIXEL(b_, g_, r_)                                        \\\n>> -\tif constexpr (ccmEnabled) {                                    \\\n>> -\t\tconst DebayerParams::CcmColumn &blue = blueCcm_[b_];   \\\n>> -\t\tconst DebayerParams::CcmColumn &green = greenCcm_[g_]; \\\n>> -\t\tconst DebayerParams::CcmColumn &red = redCcm_[r_];     \\\n>> -\t\tGAMMA(blue.b + green.b + red.b);                       \\\n>> -\t\tGAMMA(blue.g + green.g + red.g);                       \\\n>> -\t\tGAMMA(blue.r + green.r + red.r);                       \\\n>> -\t} else {                                                       \\\n>> -\t\t*dst++ = blue_[b_];                                    \\\n>> -\t\t*dst++ = green_[g_];                                   \\\n>> -\t\t*dst++ = red_[r_];                                     \\\n>> -\t}                                                              \\\n>> -\tif constexpr (addAlphaByte)                                    \\\n>> -\t\t*dst++ = 255;                                          \\\n>> +#define STORE_PIXEL(b_, g_, r_)                         \\\n>> +\tif constexpr (ccmEnabled) {                     \\\n>> +\t\tconst CcmColumn &blue = blueCcm_[b_];   \\\n>> +\t\tconst CcmColumn &green = greenCcm_[g_]; \\\n>> +\t\tconst CcmColumn &red = redCcm_[r_];     \\\n>> +\t\tGAMMA(blue.b + green.b + red.b);        \\\n>> +\t\tGAMMA(blue.g + green.g + red.g);        \\\n>> +\t\tGAMMA(blue.r + green.r + red.r);        \\\n>> +\t} else {                                        \\\n>> +\t\t*dst++ = blue_[b_];                     \\\n>> +\t\t*dst++ = green_[g_];                    \\\n>> +\t\t*dst++ = red_[r_];                      \\\n>> +\t}                                               \\\n>> +\tif constexpr (addAlphaByte)                     \\\n>> +\t\t*dst++ = 255;                           \\\n>>   \tx++;\n>>   \n>>   /*\n>> @@ -525,6 +525,16 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg,\n>>   \tif (ret != 0)\n>>   \t\treturn -EINVAL;\n>>   \n>> +\tccmEnabled_ = ccmEnabled;\n>> +\n>> +\t/*\n>> +\t * Lookup tables must be initialized because the initial value is used for\n>> +\t * the first two frames, i.e. until stats processing starts providing its\n>> +\t * own parameters. Let's enforce recomputing lookup tables by setting the\n>> +\t * stored last used gamma to an out-of-range value.\n>> +\t */\n>> +\tparams_.gamma = 1.0;\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>> @@ -740,6 +750,98 @@ void DebayerCpu::process4(uint32_t frame, const uint8_t *src, uint8_t *dst)\n>>   \t}\n>>   }\n>>   \n>> +void DebayerCpu::updateGammaTable(DebayerParams &params)\n>> +{\n>> +\tconst RGB<float> blackLevel = params.blackLevel;\n>> +\t/* Take let's say the green channel black level */\n>> +\tconst unsigned int blackIndex = blackLevel[1] * gammaTable_.size();\n>> +\tconst float gamma = params.gamma;\n>> +\tconst float contrastExp = params.contrastExp;\n>> +\n>> +\tconst float divisor = gammaTable_.size() - blackIndex - 1.0;\n>> +\tfor (unsigned int i = blackIndex; i < gammaTable_.size(); i++) {\n>> +\t\tfloat normalized = (i - blackIndex) / divisor;\n>> +\t\t/* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */\n>> +\t\t/* Apply simple S-curve */\n>> +\t\tif (normalized < 0.5)\n>> +\t\t\tnormalized = 0.5 * std::pow(normalized / 0.5, contrastExp);\n>> +\t\telse\n>> +\t\t\tnormalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp);\n>> +\t\tgammaTable_[i] = UINT8_MAX *\n>> +\t\t\t\t std::pow(normalized, gamma);\n>> +\t}\n>> +\t/*\n>> +\t * Due to CCM operations, the table lookup may reach indices below the black\n>> +\t * level. Let's set the table values below black level to the minimum\n>> +\t * non-black value to prevent problems when the minimum value is\n>> +\t * significantly non-zero (for example, when the image should be all grey).\n>> +\t */\n>> +\tstd::fill(gammaTable_.begin(), gammaTable_.begin() + blackIndex,\n>> +\t\t  gammaTable_[blackIndex]);\n>> +}\n>> +\n>> +void DebayerCpu::updateLookupTables(DebayerParams &params)\n>> +{\n>> +\tconst bool gammaUpdateNeeded =\n>> +\t\tparams.gamma != params_.gamma ||\n>> +\t\tparams.blackLevel != params_.blackLevel ||\n>> +\t\tparams.contrastExp != params_.contrastExp;\n>> +\tif (gammaUpdateNeeded)\n>> +\t\tupdateGammaTable(params);\n>> +\n>> +\tauto matrixChanged = [](const Matrix<float, 3, 3> &m1, const Matrix<float, 3, 3> &m2) -> bool {\n>> +\t\treturn !std::equal(m1.data().begin(), m1.data().end(), m2.data().begin());\n>> +\t};\n>> +\tconst unsigned int gammaTableSize = gammaTable_.size();\n>> +\tconst double div = static_cast<double>(kRGBLookupSize) / gammaTableSize;\n>> +\tif (ccmEnabled_) {\n>> +\t\tif (gammaUpdateNeeded ||\n>> +\t\t    matrixChanged(params.combinedMatrix, params_.combinedMatrix)) {\n>> +\t\t\tauto &red = swapRedBlueGains_ ? blueCcm_ : redCcm_;\n>> +\t\t\tauto &green = greenCcm_;\n>> +\t\t\tauto &blue = swapRedBlueGains_ ? redCcm_ : blueCcm_;\n>> +\t\t\tconst unsigned int redIndex = swapRedBlueGains_ ? 2 : 0;\n>> +\t\t\tconst unsigned int greenIndex = 1;\n>> +\t\t\tconst unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2;\n>> +\t\t\tfor (unsigned int i = 0; i < kRGBLookupSize; i++) {\n>> +\t\t\t\tred[i].r = std::round(i * params.combinedMatrix[redIndex][0]);\n>> +\t\t\t\tred[i].g = std::round(i * params.combinedMatrix[greenIndex][0]);\n>> +\t\t\t\tred[i].b = std::round(i * params.combinedMatrix[blueIndex][0]);\n>> +\t\t\t\tgreen[i].r = std::round(i * params.combinedMatrix[redIndex][1]);\n>> +\t\t\t\tgreen[i].g = std::round(i * params.combinedMatrix[greenIndex][1]);\n>> +\t\t\t\tgreen[i].b = std::round(i * params.combinedMatrix[blueIndex][1]);\n>> +\t\t\t\tblue[i].r = std::round(i * params.combinedMatrix[redIndex][2]);\n>> +\t\t\t\tblue[i].g = std::round(i * params.combinedMatrix[greenIndex][2]);\n>> +\t\t\t\tblue[i].b = std::round(i * params.combinedMatrix[blueIndex][2]);\n>> +\t\t\t\tgammaLut_[i] = gammaTable_[i / div];\n>> +\t\t\t}\n>> +\t\t}\n>> +\t} else {\n>> +\t\tif (gammaUpdateNeeded || params.gains != params_.gains) {\n>> +\t\t\tauto &gains = params.gains;\n>> +\t\t\tauto &red = swapRedBlueGains_ ? blue_ : red_;\n>> +\t\t\tauto &green = green_;\n>> +\t\t\tauto &blue = swapRedBlueGains_ ? red_ : blue_;\n>> +\t\t\tfor (unsigned int i = 0; i < kRGBLookupSize; i++) {\n>> +\t\t\t\t/* Apply gamma after gain! */\n>> +\t\t\t\tconst RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);\n>> +\t\t\t\tred[i] = gammaTable_[static_cast<unsigned int>(lutGains.r())];\n>> +\t\t\t\tgreen[i] = gammaTable_[static_cast<unsigned int>(lutGains.g())];\n>> +\t\t\t\tblue[i] = gammaTable_[static_cast<unsigned int>(lutGains.b())];\n>> +\t\t\t}\n>> +\t\t}\n>> +\t}\n>> +\n>> +\tLOG(Debayer, Debug)\n>> +\t\t<< \"Debayer parameters: blackLevel=\" << params.blackLevel\n>> +\t\t<< \"; gamma=\" << params.gamma\n>> +\t\t<< \"; contrastExp=\" << params.contrastExp\n>> +\t\t<< \"; gains=\" << params.gains\n>> +\t\t<< \"; matrix=\" << params.combinedMatrix;\n>> +\n>> +\tparams_ = params;\n>> +}\n>> +\n>>   void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params)\n>>   {\n>>   \tbench_.startFrame();\n>> @@ -748,7 +850,7 @@ void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output\n>>   \n>>   \tdmaSyncBegin(dmaSyncers, input, output);\n>>   \n>> -\tsetParams(params);\n>> +\tupdateLookupTables(params);\n>>   \n>>   \t/* Copy metadata from the input buffer */\n>>   \tFrameMetadata &metadata = output->_d()->metadata();\n>> diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h\n>> index 67df2b93a..b5cbb5bd2 100644\n>> --- a/src/libcamera/software_isp/debayer_cpu.h\n>> +++ b/src/libcamera/software_isp/debayer_cpu.h\n>> @@ -1,7 +1,7 @@\n>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>   /*\n>>    * Copyright (C) 2023, Linaro Ltd\n>> - * Copyright (C) 2023-2025 Red Hat Inc.\n>> + * Copyright (C) 2023-2026 Red Hat Inc.\n>>    *\n>>    * Authors:\n>>    * Hans de Goede<hdegoede@redhat.com>\n>> @@ -18,6 +18,8 @@\n>>   #include <libcamera/base/object.h>\n>>   \n>>   #include \"libcamera/internal/bayer_format.h\"\n>> +#include \"libcamera/internal/global_configuration.h\"\n>> +#include \"libcamera/internal/software_isp/debayer_params.h\"\n>>   #include \"libcamera/internal/software_isp/swstats_cpu.h\"\n>>   \n>>   #include \"debayer.h\"\n>> @@ -108,10 +110,32 @@ private:\n>>   \tvoid memcpyNextLine(const uint8_t *linePointers[]);\n>>   \tvoid process2(uint32_t frame, const uint8_t *src, uint8_t *dst);\n>>   \tvoid process4(uint32_t frame, const uint8_t *src, uint8_t *dst);\n>> +\tvoid updateGammaTable(DebayerParams &params);\n>> +\tvoid updateLookupTables(DebayerParams &params);\n>>   \n>>   \t/* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */\n>>   \tstatic constexpr unsigned int kMaxLineBuffers = 5;\n>>   \n>> +\tstatic constexpr unsigned int kRGBLookupSize = 256;\n>> +\tstatic constexpr unsigned int kGammaLookupSize = 1024;\n>> +\tstruct CcmColumn {\n>> +\t\tint16_t r;\n>> +\t\tint16_t g;\n>> +\t\tint16_t b;\n>> +\t};\n>> +\tusing LookupTable = std::array<uint8_t, kRGBLookupSize>;\n>> +\tusing CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>;\n>> +\tLookupTable red_;\n>> +\tLookupTable green_;\n>> +\tLookupTable blue_;\n>> +\tCcmLookupTable redCcm_;\n>> +\tCcmLookupTable greenCcm_;\n>> +\tCcmLookupTable blueCcm_;\n>> +\tstd::array<double, kGammaLookupSize> gammaTable_;\n>> +\tLookupTable gammaLut_;\n>> +\tbool ccmEnabled_;\n>> +\tDebayerParams params_;\n>> +\n>>   \tdebayerFn debayer0_;\n>>   \tdebayerFn debayer1_;\n>>   \tdebayerFn debayer2_;\n>> diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp\n>> index 9693d7252..af04d60ca 100644\n>> --- a/src/libcamera/software_isp/debayer_egl.cpp\n>> +++ b/src/libcamera/software_isp/debayer_egl.cpp\n>> @@ -475,18 +475,18 @@ void DebayerEGL::setShaderVariableValues(DebayerParams &params)\n>>   \t\t\t    << \" textureUniformProjMatrix_ \" << textureUniformProjMatrix_;\n>>   \n>>   \tGLfloat ccm[9] = {\n>> -\t\tparams.ccm[0][0],\n>> -\t\tparams.ccm[0][1],\n>> -\t\tparams.ccm[0][2],\n>> -\t\tparams.ccm[1][0],\n>> -\t\tparams.ccm[1][1],\n>> -\t\tparams.ccm[1][2],\n>> -\t\tparams.ccm[2][0],\n>> -\t\tparams.ccm[2][1],\n>> -\t\tparams.ccm[2][2],\n>> +\t\tparams.combinedMatrix[0][0],\n>> +\t\tparams.combinedMatrix[0][1],\n>> +\t\tparams.combinedMatrix[0][2],\n>> +\t\tparams.combinedMatrix[1][0],\n>> +\t\tparams.combinedMatrix[1][1],\n>> +\t\tparams.combinedMatrix[1][2],\n>> +\t\tparams.combinedMatrix[2][0],\n>> +\t\tparams.combinedMatrix[2][1],\n>> +\t\tparams.combinedMatrix[2][2],\n>>   \t};\n>>   \tglUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm);\n>> -\tLOG(Debayer, Debug) << \" ccmUniformDataIn_ \" << ccmUniformDataIn_ << \" data \" << params.ccm;\n>> +\tLOG(Debayer, Debug) << \" ccmUniformDataIn_ \" << ccmUniformDataIn_ << \" data \" << params.combinedMatrix;\n>>   \n>>   \t/*\n>>   \t * 0 = Red, 1 = Green, 2 = Blue\n>> @@ -544,8 +544,6 @@ void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output\n>>   \n>>   \tdmaSyncBegin(dmaSyncers, input, nullptr);\n>>   \n>> -\tsetParams(params);\n>> -\n>>   \t/* Copy metadata from the input buffer */\n>>   \tFrameMetadata &metadata = output->_d()->metadata();\n>>   \tmetadata.status = input->metadata().status;\n>> diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp\n>> index 7ad3511db..a83986b78 100644\n>> --- a/src/libcamera/software_isp/software_isp.cpp\n>> +++ b/src/libcamera/software_isp/software_isp.cpp\n>> @@ -84,23 +84,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n>>   \t\t   DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |\n>>   \t\t   DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)\n>>   {\n>> -\t/*\n>> -\t * debayerParams_ must be initialized because the initial value is used for\n>> -\t * the first two frames, i.e. until stats processing starts providing its\n>> -\t * own parameters.\n>> -\t *\n>> -\t * \\todo This should be handled in the same place as the related\n>> -\t * operations, in the IPA module.\n>> -\t */\n>> -\tstd::array<uint8_t, 256> gammaTable;\n>> -\tfor (unsigned int i = 0; i < 256; i++)\n>> -\t\tgammaTable[i] = UINT8_MAX * std::pow(i / 256.0, 0.5);\n>> -\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n>> -\t\tdebayerParams_.red[i] = gammaTable[i];\n>> -\t\tdebayerParams_.green[i] = gammaTable[i];\n>> -\t\tdebayerParams_.blue[i] = gammaTable[i];\n>> -\t}\n>> -\n>>   \tif (!dmaHeap_.isValid()) {\n>>   \t\tLOG(SoftwareIsp, Error) << \"Failed to create DmaBufAllocator object\";\n>>   \t\treturn;\n>> @@ -121,8 +104,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n>>   \t}\n>>   \tstats->statsReady.connect(this, &SoftwareIsp::statsReady);\n>>   \n>> -\tbool gpuIspEnabled;\n>> -\n>>   #if HAVE_DEBAYER_EGL\n>>   \tstd::optional<std::string> softISPMode = configuration.envOption(\"LIBCAMERA_SOFTISP_MODE\", { \"software_isp\", \"mode\" });\n>>   \tif (softISPMode) {\n>> @@ -133,15 +114,12 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n>>   \t\t}\n>>   \t}\n>>   \n>> -\tif (!softISPMode || softISPMode == \"gpu\") {\n>> +\tif (!softISPMode || softISPMode == \"gpu\")\n>>   \t\tdebayer_ = std::make_unique<DebayerEGL>(std::move(stats), configuration);\n>> -\t\tgpuIspEnabled = true;\n>> -\t}\n>> +\n>>   #endif\n>> -\tif (!debayer_) {\n>> +\tif (!debayer_)\n>>   \t\tdebayer_ = std::make_unique<DebayerCpu>(std::move(stats), configuration);\n>> -\t\tgpuIspEnabled = false;\n>> -\t}\n>>   \n>>   \tdebayer_->inputBufferReady.connect(this, &SoftwareIsp::inputReady);\n>>   \tdebayer_->outputBufferReady.connect(this, &SoftwareIsp::outputReady);\n>> @@ -173,7 +151,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n>>   \t\t\t sharedParams_.fd(),\n>>   \t\t\t sensorInfo,\n>>   \t\t\t sensor->controls(),\n>> -\t\t\t gpuIspEnabled,\n>>   \t\t\t ipaControls,\n>>   \t\t\t &ccmEnabled_);\n>>   \tif (ret) {\n> \n> -- \n> Robert Mader\n> Consultant Software Developer\n> \n> Collabora Ltd.\n> Platinum Building, St John's Innovation Park, Cambridge CB4 0DS, UK\n> Registered in England & Wales, no. 5513718\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id CDAF0BDCBF\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 22 Jan 2026 17:01:18 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id F24B361FC5;\n\tThu, 22 Jan 2026 18:01:17 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 03BF361F84\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 22 Jan 2026 18:01:16 +0100 (CET)","from [192.168.33.25] (185.221.143.114.nat.pool.zt.hu\n\t[185.221.143.114])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 6C2C7324;\n\tThu, 22 Jan 2026 18:00:40 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"UowcVmuZ\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1769101244;\n\tbh=tBIWnienQHNnGJvUE0KtE5GZxNcIxEYzYiD4spqrU/w=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=UowcVmuZ5vsVxLH/dUJ+WSKIWznDQZ4YKlJb+AQbj1UlDyUiqgOIpsv48BTfVGZ6R\n\tBno1+KowH0EKvcDO6l4lG8m+QMp9Ki6z2MiZlX5HaqY/pEuwbaFey06M42L5nCY1GE\n\tpyQnzlAnhPmFG0mMrwiQ+RcPfmJDt4lUv93blrlY=","Message-ID":"<ebf14c35-3f2d-4dc4-b95a-181c117d7e09@ideasonboard.com>","Date":"Thu, 22 Jan 2026 18:01:09 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v4 14/15] libcamera: ipa: simple: Remove Lut algorithm","To":"Robert Mader <robert.mader@collabora.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20260122161935.208562-1-mzamazal@redhat.com>\n\t<20260122161935.208562-15-mzamazal@redhat.com>\n\t<55308b0c-4856-45e2-810c-77e39ab1581f@collabora.com>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<55308b0c-4856-45e2-810c-77e39ab1581f@collabora.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","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>"}},{"id":37887,"web_url":"https://patchwork.libcamera.org/comment/37887/","msgid":"<084751fd-bf7b-4647-a932-c31614f0f902@collabora.com>","date":"2026-01-22T17:04:55","subject":"Re: [PATCH v4 14/15] libcamera: ipa: simple: Remove Lut algorithm","submitter":{"id":140,"url":"https://patchwork.libcamera.org/api/people/140/","name":"Robert Mader","email":"robert.mader@collabora.com"},"content":"On 22.01.26 18:01, Barnabás Pőcze wrote:\n> 2026. 01. 22. 17:58 keltezéssel, Robert Mader írta:\n>> Hi, thanks for the series!\n>>\n>> Trying this commit (on top of current master) on a FP5 (qcom) makes \n>> the soft-isp fail with\n>>\n>> ERROR IPAModuleAlgo module.h:91 IPASoft: Algorithm 'Lut' not found\n>> ERROR SoftwareIsp software_isp.cpp:157 IPA init failed\n>> WARN SimplePipeline simple.cpp:620 Failed to create software ISP, \n>> disabling software debayering\n>>\n>> for me. It's printed for each sensor/camera when running `cam -l`.\n>\n> Maybe old tuning files are loaded? This patch definitely removes it from\n> uncalibrated.yaml but maybe you have sensor specific ones locally.\nUh, right, that was it! With that cleaned up it works as expected, thanks.\n\n>\n>>\n>> On 22.01.26 17:19, Milan Zamazal wrote:\n>>> The Lut algorithm is not really an algorithm.  Moreover, algorithms may\n>>> be enabled or disabled but with Lut disabled, nothing will work.\n>>>\n>>> Let's move the construction of lookup tables to CPU debayering, \n>>> where it\n>>> is used.  The implied and related changes are:\n>>>\n>>> - DebayerParams is changed to contain the real params rather than \n>>> lookup\n>>>    tables.\n>>> - contrastExp parameter introduced by GPU ISP is used for CPU ISP too.\n>>> - The params must be initialised so that debayering gets meaningful\n>>>    parameter values even when some algorithms are disabled.\n>>> - combinedMatrix must be put to params everywhere where it is modified.\n>>> - Matrix changes needn't be tracked in the algorithms any more.\n>>> - CPU debayering must watch for changes of the corresponding parameters\n>>>    to update the lookup tables when and only when needed.\n>>> - Swapping red and blue is integrated into lookup table constructions.\n>>> - gpuIspEnabled flags are removed as they are not needed any more.\n>>>\n>>> Signed-off-by: Milan Zamazal<mzamazal@redhat.com>\n>>> ---\n>>>   .../internal/software_isp/debayer_params.h    |  43 +----\n>>>   include/libcamera/ipa/soft.mojom              |   3 +-\n>>>   src/ipa/simple/algorithms/adjust.cpp          |  17 +-\n>>>   src/ipa/simple/algorithms/adjust.h            |   4 -\n>>>   src/ipa/simple/algorithms/awb.cpp             |   6 +-\n>>>   src/ipa/simple/algorithms/ccm.cpp             |   1 -\n>>>   src/ipa/simple/algorithms/lut.cpp             | 140 --------------\n>>>   src/ipa/simple/algorithms/lut.h               |  35 ----\n>>>   src/ipa/simple/algorithms/meson.build         |   1 -\n>>>   src/ipa/simple/data/uncalibrated.yaml         |   1 -\n>>>   src/ipa/simple/ipa_context.h                  |  11 --\n>>>   src/ipa/simple/soft_simple.cpp                |  11 +-\n>>>   src/libcamera/software_isp/debayer.cpp        | 172 \n>>> +-----------------\n>>>   src/libcamera/software_isp/debayer.h          |   8 -\n>>>   src/libcamera/software_isp/debayer_cpu.cpp    | 136 ++++++++++++--\n>>>   src/libcamera/software_isp/debayer_cpu.h      |  26 ++-\n>>>   src/libcamera/software_isp/debayer_egl.cpp    |  22 +--\n>>>   src/libcamera/software_isp/software_isp.cpp   |  29 +--\n>>>   18 files changed, 188 insertions(+), 478 deletions(-)\n>>>   delete mode 100644 src/ipa/simple/algorithms/lut.cpp\n>>>   delete mode 100644 src/ipa/simple/algorithms/lut.h\n>>>\n>>> diff --git \n>>> a/include/libcamera/internal/software_isp/debayer_params.h \n>>> b/include/libcamera/internal/software_isp/debayer_params.h\n>>> index 2d69bd295..1c0412d75 100644\n>>> --- a/include/libcamera/internal/software_isp/debayer_params.h\n>>> +++ b/include/libcamera/internal/software_isp/debayer_params.h\n>>> @@ -1,6 +1,6 @@\n>>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>>   /*\n>>> - * Copyright (C) 2023-2025 Red Hat Inc.\n>>> + * Copyright (C) 2023-2026 Red Hat Inc.\n>>>    *\n>>>    * Authors:\n>>>    * Hans de Goede<hdegoede@redhat.com>\n>>> @@ -10,7 +10,6 @@\n>>>     #pragma once\n>>>   -#include <array>\n>>>   #include <stdint.h>\n>>>     #include \"libcamera/internal/matrix.h\"\n>>> @@ -19,47 +18,11 @@\n>>>   namespace libcamera {\n>>>     struct DebayerParams {\n>>> -    static constexpr unsigned int kRGBLookupSize = 256;\n>>> -\n>>> -    struct CcmColumn {\n>>> -        int16_t r;\n>>> -        int16_t g;\n>>> -        int16_t b;\n>>> -    };\n>>> -\n>>> -    using LookupTable = std::array<uint8_t, kRGBLookupSize>;\n>>> -    using CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>;\n>>> -\n>>> -    /*\n>>> -     * Color lookup tables when CCM is not used.\n>>> -     *\n>>> -     * Each color of a debayered pixel is amended by the corresponding\n>>> -     * value in the given table.\n>>> -     */\n>>> -    LookupTable red;\n>>> -    LookupTable green;\n>>> -    LookupTable blue;\n>>> -\n>>> -    /*\n>>> -     * Color and gamma lookup tables when CCM is used.\n>>> -     *\n>>> -     * Each of the CcmLookupTable's corresponds to a CCM column; \n>>> together they\n>>> -     * make a complete 3x3 CCM lookup table. The CCM is applied on \n>>> debayered\n>>> -     * pixels and then the gamma lookup table is used to set the \n>>> resulting\n>>> -     * values of all the three colors.\n>>> -     */\n>>> -    CcmLookupTable redCcm;\n>>> -    CcmLookupTable greenCcm;\n>>> -    CcmLookupTable blueCcm;\n>>> -    LookupTable gammaLut;\n>>> -\n>>> -    /*\n>>> -     * Per frame corrections as calculated by the IPA\n>>> -     */\n>>> -    Matrix<float, 3, 3> ccm;\n>>> +    Matrix<float, 3, 3> combinedMatrix;\n>>>       RGB<float> blackLevel;\n>>>       float gamma;\n>>>       float contrastExp;\n>>> +    RGB<float> gains;\n>>>   };\n>>>     } /* namespace libcamera */\n>>> diff --git a/include/libcamera/ipa/soft.mojom \n>>> b/include/libcamera/ipa/soft.mojom\n>>> index aff8fcbd3..77328c5fd 100644\n>>> --- a/include/libcamera/ipa/soft.mojom\n>>> +++ b/include/libcamera/ipa/soft.mojom\n>>> @@ -17,8 +17,7 @@ interface IPASoftInterface {\n>>>            libcamera.SharedFD fdStats,\n>>>            libcamera.SharedFD fdParams,\n>>>            libcamera.IPACameraSensorInfo sensorInfo,\n>>> -         libcamera.ControlInfoMap sensorControls,\n>>> -         bool gpuIspEnabled)\n>>> +         libcamera.ControlInfoMap sensorControls)\n>>>           => (int32 ret, libcamera.ControlInfoMap ipaControls, bool \n>>> ccmEnabled);\n>>>       start() => (int32 ret);\n>>>       stop();\n>>> diff --git a/src/ipa/simple/algorithms/adjust.cpp \n>>> b/src/ipa/simple/algorithms/adjust.cpp\n>>> index acdd3f741..068e98404 100644\n>>> --- a/src/ipa/simple/algorithms/adjust.cpp\n>>> +++ b/src/ipa/simple/algorithms/adjust.cpp\n>>> @@ -95,23 +95,20 @@ void Adjust::applySaturation(Matrix<float, 3, 3> \n>>> &matrix, float saturation)\n>>>   void Adjust::prepare(IPAContext &context,\n>>>                [[maybe_unused]] const uint32_t frame,\n>>>                IPAFrameContext &frameContext,\n>>> -             [[maybe_unused]] DebayerParams *params)\n>>> +             DebayerParams *params)\n>>>   {\n>>>       frameContext.gamma = context.activeState.knobs.gamma;\n>>>       frameContext.contrast = context.activeState.knobs.contrast;\n>>>   -    if (!context.ccmEnabled)\n>>> -        return;\n>>> -\n>>>       auto &saturation = context.activeState.knobs.saturation;\n>>> -    frameContext.saturation = saturation;\n>>> -    if (saturation)\n>>> +    if (context.ccmEnabled && saturation) {\n>>>           applySaturation(context.activeState.combinedMatrix, \n>>> saturation.value());\n>>> -\n>>> -    if (saturation != lastSaturation_) {\n>>> -        context.activeState.matrixChanged = true;\n>>> -        lastSaturation_ = saturation;\n>>> +        frameContext.saturation = saturation;\n>>>       }\n>>> +\n>>> +    params->gamma = 1.0 / context.activeState.knobs.gamma;\n>>> +    const float contrast = \n>>> context.activeState.knobs.contrast.value_or(kDefaultContrast);\n>>> +    params->contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, \n>>> M_PI_2 - 0.00001));\n>>>   }\n>>>     void Adjust::process([[maybe_unused]] IPAContext &context,\n>>> diff --git a/src/ipa/simple/algorithms/adjust.h \n>>> b/src/ipa/simple/algorithms/adjust.h\n>>> index 7644138ff..fb133b140 100644\n>>> --- a/src/ipa/simple/algorithms/adjust.h\n>>> +++ b/src/ipa/simple/algorithms/adjust.h\n>>> @@ -7,8 +7,6 @@\n>>>     #pragma once\n>>>   -#include <optional>\n>>> -\n>>>   #include \"libcamera/internal/matrix.h\"\n>>>     #include <libipa/interpolator.h>\n>>> @@ -45,8 +43,6 @@ public:\n>>>     private:\n>>>       void applySaturation(Matrix<float, 3, 3> &ccm, float saturation);\n>>> -\n>>> -    std::optional<float> lastSaturation_;\n>>>   };\n>>>     } /* namespace ipa::soft::algorithms */\n>>> diff --git a/src/ipa/simple/algorithms/awb.cpp \n>>> b/src/ipa/simple/algorithms/awb.cpp\n>>> index 4d2f1df15..d2c1b87f0 100644\n>>> --- a/src/ipa/simple/algorithms/awb.cpp\n>>> +++ b/src/ipa/simple/algorithms/awb.cpp\n>>> @@ -37,7 +37,7 @@ int Awb::configure(IPAContext &context,\n>>>   void Awb::prepare(IPAContext &context,\n>>>             [[maybe_unused]] const uint32_t frame,\n>>>             IPAFrameContext &frameContext,\n>>> -          [[maybe_unused]] DebayerParams *params)\n>>> +          DebayerParams *params)\n>>>   {\n>>>       auto &gains = context.activeState.awb.gains;\n>>>       Matrix<float, 3, 3> gainMatrix = { { gains.r(), 0, 0,\n>>> @@ -45,9 +45,11 @@ void Awb::prepare(IPAContext &context,\n>>>                            0, 0, gains.b() } };\n>>>       context.activeState.combinedMatrix =\n>>>           context.activeState.combinedMatrix * gainMatrix;\n>>> -    /* Just report, the gains are applied in LUT algorithm. */\n>>> +\n>>>       frameContext.gains.red = gains.r();\n>>>       frameContext.gains.blue = gains.b();\n>>> +\n>>> +    params->gains = gains;\n>>>   }\n>>>     void Awb::process(IPAContext &context,\n>>> diff --git a/src/ipa/simple/algorithms/ccm.cpp \n>>> b/src/ipa/simple/algorithms/ccm.cpp\n>>> index 5576a301f..911a5af2c 100644\n>>> --- a/src/ipa/simple/algorithms/ccm.cpp\n>>> +++ b/src/ipa/simple/algorithms/ccm.cpp\n>>> @@ -51,7 +51,6 @@ void Ccm::prepare(IPAContext &context, \n>>> [[maybe_unused]] const uint32_t frame,\n>>>           utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) {\n>>>           currentCcm_ = ccm_.getInterpolated(ct);\n>>>           lastCt_ = ct;\n>>> -        context.activeState.matrixChanged = true;\n>>>       }\n>>>         context.activeState.combinedMatrix =\n>>> diff --git a/src/ipa/simple/algorithms/lut.cpp \n>>> b/src/ipa/simple/algorithms/lut.cpp\n>>> deleted file mode 100644\n>>> index fd442259a..000000000\n>>> --- a/src/ipa/simple/algorithms/lut.cpp\n>>> +++ /dev/null\n>>> @@ -1,140 +0,0 @@\n>>> -/* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>> -/*\n>>> - * Copyright (C) 2024-2026, Red Hat Inc.\n>>> - *\n>>> - * Color lookup tables construction\n>>> - */\n>>> -\n>>> -#include \"lut.h\"\n>>> -\n>>> -#include <algorithm>\n>>> -#include <cmath>\n>>> -#include <optional>\n>>> -#include <stdint.h>\n>>> -\n>>> -#include <libcamera/base/log.h>\n>>> -\n>>> -#include <libcamera/control_ids.h>\n>>> -\n>>> -#include \"simple/ipa_context.h\"\n>>> -\n>>> -#include \"adjust.h\"\n>>> -\n>>> -namespace libcamera {\n>>> -\n>>> -LOG_DEFINE_CATEGORY(IPASoftLut)\n>>> -\n>>> -namespace ipa::soft::algorithms {\n>>> -\n>>> -int Lut::configure(IPAContext &context,\n>>> -           [[maybe_unused]] const IPAConfigInfo &configInfo)\n>>> -{\n>>> -    updateGammaTable(context);\n>>> -\n>>> -    return 0;\n>>> -}\n>>> -\n>>> -void Lut::updateGammaTable(IPAContext &context)\n>>> -{\n>>> -    const auto blackLevel = context.activeState.blc.level;\n>>> -    const auto gamma = 1.0 / context.activeState.knobs.gamma;\n>>> -    const auto contrast = \n>>> context.activeState.knobs.contrast.value_or(1.0);\n>>> -    /* Convert 0..2 to 0..infinity; avoid actual inifinity at \n>>> tan(pi/2) */\n>>> -    float contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, \n>>> M_PI_2 - 0.00001));\n>>> -\n>>> -    if (!context.gpuIspEnabled) {\n>>> -        auto &gammaTable = context.activeState.gamma.gammaTable;\n>>> -        const unsigned int blackIndex = blackLevel * \n>>> gammaTable.size() / 256;\n>>> -        const float divisor = gammaTable.size() - blackIndex - 1.0;\n>>> -        for (unsigned int i = blackIndex; i < gammaTable.size(); \n>>> i++) {\n>>> -            double normalized = (i - blackIndex) / divisor;\n>>> -            /* Apply simple S-curve */\n>>> -            if (normalized < 0.5)\n>>> -                normalized = 0.5 * std::pow(normalized / 0.5, \n>>> contrastExp);\n>>> -            else\n>>> -                normalized = 1.0 - 0.5 * std::pow((1.0 - \n>>> normalized) / 0.5, contrastExp);\n>>> -            gammaTable[i] = UINT8_MAX * std::pow(normalized, gamma);\n>>> -        }\n>>> -        /*\n>>> -         * Due to CCM operations, the table lookup may reach \n>>> indices below the black\n>>> -         * level. Let's set the table values below black level to \n>>> the minimum\n>>> -         * non-black value to prevent problems when the minimum \n>>> value is\n>>> -         * significantly non-zero (for example, when the image \n>>> should be all grey).\n>>> -         */\n>>> -        std::fill(gammaTable.begin(), gammaTable.begin() + blackIndex,\n>>> -              gammaTable[blackIndex]);\n>>> -    }\n>>> -\n>>> -    context.activeState.gamma.gamma = gamma;\n>>> -    context.activeState.gamma.blackLevel = blackLevel;\n>>> -    context.activeState.gamma.contrastExp = contrastExp;\n>>> -}\n>>> -\n>>> -int16_t Lut::matrixValue(unsigned int i, float ccm) const\n>>> -{\n>>> -    return std::round(i * ccm);\n>>> -}\n>>> -\n>>> -void Lut::prepare(IPAContext &context,\n>>> -          [[maybe_unused]] const uint32_t frame,\n>>> -          [[maybe_unused]] IPAFrameContext &frameContext,\n>>> -          DebayerParams *params)\n>>> -{\n>>> -    /*\n>>> -     * Update the gamma table if needed. This means if black level \n>>> changes\n>>> -     * and since the black level gets updated only if a lower value is\n>>> -     * observed, it's not permanently prone to minor fluctuations or\n>>> -     * rounding errors.\n>>> -     */\n>>> -    const bool gammaUpdateNeeded =\n>>> -        context.activeState.gamma.blackLevel != \n>>> context.activeState.blc.level ||\n>>> -        context.activeState.gamma.contrast != \n>>> context.activeState.knobs.contrast;\n>>> -    if (gammaUpdateNeeded)\n>>> -        updateGammaTable(context);\n>>> -\n>>> -    auto &gains = context.activeState.awb.gains;\n>>> -    auto &gammaTable = context.activeState.gamma.gammaTable;\n>>> -    const unsigned int gammaTableSize = gammaTable.size();\n>>> -    const double div = \n>>> static_cast<double>(DebayerParams::kRGBLookupSize) /\n>>> -               gammaTableSize;\n>>> -\n>>> -    if (!context.ccmEnabled) {\n>>> -        for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; \n>>> i++) {\n>>> -            /* Apply gamma after gain! */\n>>> -            const RGB<float> lutGains = (gains * i / \n>>> div).min(gammaTableSize - 1);\n>>> -            params->red[i] = gammaTable[static_cast<unsigned \n>>> int>(lutGains.r())];\n>>> -            params->green[i] = gammaTable[static_cast<unsigned \n>>> int>(lutGains.g())];\n>>> -            params->blue[i] = gammaTable[static_cast<unsigned \n>>> int>(lutGains.b())];\n>>> -        }\n>>> -    } else if (context.activeState.matrixChanged || \n>>> gammaUpdateNeeded) {\n>>> -        auto &matrix = context.activeState.combinedMatrix;\n>>> -        auto &red = params->redCcm;\n>>> -        auto &green = params->greenCcm;\n>>> -        auto &blue = params->blueCcm;\n>>> -        params->ccm = matrix;\n>>> -        if (!context.gpuIspEnabled) {\n>>> -            for (unsigned int i = 0; i < \n>>> DebayerParams::kRGBLookupSize; i++) {\n>>> -                red[i].r = matrixValue(i, matrix[0][0]);\n>>> -                red[i].g = matrixValue(i, matrix[1][0]);\n>>> -                red[i].b = matrixValue(i, matrix[2][0]);\n>>> -                green[i].r = matrixValue(i, matrix[0][1]);\n>>> -                green[i].g = matrixValue(i, matrix[1][1]);\n>>> -                green[i].b = matrixValue(i, matrix[2][1]);\n>>> -                blue[i].r = matrixValue(i, matrix[0][2]);\n>>> -                blue[i].g = matrixValue(i, matrix[1][2]);\n>>> -                blue[i].b = matrixValue(i, matrix[2][2]);\n>>> -                params->gammaLut[i] = gammaTable[i / div];\n>>> -            }\n>>> -        }\n>>> -        context.activeState.matrixChanged = false;\n>>> -    }\n>>> -\n>>> -    params->gamma = context.activeState.gamma.gamma;\n>>> -    params->contrastExp = context.activeState.gamma.contrastExp;\n>>> -}\n>>> -\n>>> -REGISTER_IPA_ALGORITHM(Lut, \"Lut\") - -} /* namespace \n>>> ipa::soft::algorithms */ - -} /* namespace libcamera */ diff --git \n>>> a/src/ipa/simple/algorithms/lut.h b/src/ipa/simple/algorithms/lut.h \n>>> deleted file mode 100644 index ad16d1e8e..000000000 --- \n>>> a/src/ipa/simple/algorithms/lut.h +++ /dev/null @@ -1,35 +0,0 @@ -/* \n>>> SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) \n>>> 2024, Red Hat Inc. - * - * Color lookup tables construction - */ - \n>>> -#pragma once - -#include \"algorithm.h\"\n>>> -\n>>> -namespace libcamera {\n>>> -\n>>> -namespace ipa::soft::algorithms {\n>>> -\n>>> -class Lut : public Algorithm\n>>> -{\n>>> -public:\n>>> -    Lut() = default;\n>>> -    ~Lut() = default;\n>>> -\n>>> -    int configure(IPAContext &context, const IPAConfigInfo \n>>> &configInfo) override;\n>>> -    void prepare(IPAContext &context,\n>>> -             const uint32_t frame,\n>>> -             IPAFrameContext &frameContext,\n>>> -             DebayerParams *params) override;\n>>> -\n>>> -private:\n>>> -    void updateGammaTable(IPAContext &context);\n>>> -    int16_t matrixValue(unsigned int i, float ccm) const;\n>>> -};\n>>> -\n>>> -} /* namespace ipa::soft::algorithms */\n>>> -\n>>> -} /* namespace libcamera */\n>>> diff --git a/src/ipa/simple/algorithms/meson.build \n>>> b/src/ipa/simple/algorithms/meson.build\n>>> index ebe9f20dd..73c637220 100644\n>>> --- a/src/ipa/simple/algorithms/meson.build\n>>> +++ b/src/ipa/simple/algorithms/meson.build\n>>> @@ -6,5 +6,4 @@ soft_simple_ipa_algorithms = files([\n>>>       'agc.cpp',\n>>>       'blc.cpp',\n>>>       'ccm.cpp',\n>>> -    'lut.cpp',\n>>>   ])\n>>> diff --git a/src/ipa/simple/data/uncalibrated.yaml \n>>> b/src/ipa/simple/data/uncalibrated.yaml\n>>> index e389e0588..c6feda36d 100644\n>>> --- a/src/ipa/simple/data/uncalibrated.yaml\n>>> +++ b/src/ipa/simple/data/uncalibrated.yaml\n>>> @@ -15,6 +15,5 @@ algorithms:\n>>>                    0, 1, 0,\n>>>                    0, 0, 1]\n>>>     - Adjust:\n>>> -  - Lut:\n>>>     - Agc:\n>>>   ...\n>>> diff --git a/src/ipa/simple/ipa_context.h \n>>> b/src/ipa/simple/ipa_context.h\n>>> index 293e35b71..34f7403a4 100644\n>>> --- a/src/ipa/simple/ipa_context.h\n>>> +++ b/src/ipa/simple/ipa_context.h\n>>> @@ -53,17 +53,7 @@ struct IPAActiveState {\n>>>           unsigned int temperatureK;\n>>>       } awb;\n>>>   -    static constexpr unsigned int kGammaLookupSize = 1024;\n>>> -    struct {\n>>> -        std::array<double, kGammaLookupSize> gammaTable;\n>>> -        uint8_t blackLevel;\n>>> -        float gamma;\n>>> -        float contrast;\n>>> -        float contrastExp;\n>>> -    } gamma;\n>>> -\n>>>       Matrix<float, 3, 3> combinedMatrix;\n>>> -    bool matrixChanged = false;\n>>>         struct {\n>>>           float gamma;\n>>> @@ -103,7 +93,6 @@ struct IPAContext {\n>>>       FCQueue<IPAFrameContext> frameContexts;\n>>>       ControlInfoMap::Map ctrlMap;\n>>>       bool ccmEnabled = false;\n>>> -    bool gpuIspEnabled = false;\n>>>   };\n>>>     } /* namespace ipa::soft */\n>>> diff --git a/src/ipa/simple/soft_simple.cpp \n>>> b/src/ipa/simple/soft_simple.cpp\n>>> index 732e82510..6bef597c8 100644\n>>> --- a/src/ipa/simple/soft_simple.cpp\n>>> +++ b/src/ipa/simple/soft_simple.cpp\n>>> @@ -26,6 +26,7 @@\n>>>   #include \"libcamera/internal/software_isp/swisp_stats.h\"\n>>>   #include \"libcamera/internal/yaml_parser.h\"\n>>>   +#include \"algorithms/adjust.h\"\n>>>   #include \"libipa/camera_sensor_helper.h\"\n>>>     #include \"module.h\"\n>>> @@ -55,7 +56,6 @@ public:\n>>>            const SharedFD &fdParams,\n>>>            const IPACameraSensorInfo &sensorInfo,\n>>>            const ControlInfoMap &sensorControls,\n>>> -         bool gpuIspEnabled,\n>>>            ControlInfoMap *ipaControls,\n>>>            bool *ccmEnabled) override;\n>>>       int configure(const IPAConfigInfo &configInfo) override;\n>>> @@ -96,7 +96,6 @@ int IPASoftSimple::init(const IPASettings &settings,\n>>>               const SharedFD &fdParams,\n>>>               const IPACameraSensorInfo &sensorInfo,\n>>>               const ControlInfoMap &sensorControls,\n>>> -            bool gpuIspEnabled,\n>>>               ControlInfoMap *ipaControls,\n>>>               bool *ccmEnabled)\n>>>   {\n>>> @@ -108,7 +107,6 @@ int IPASoftSimple::init(const IPASettings \n>>> &settings,\n>>>       }\n>>>         context_.sensorInfo = sensorInfo;\n>>> -    context_.gpuIspEnabled = gpuIspEnabled;\n>>>         /* Load the tuning data file */\n>>>       File file(settings.configurationFile);\n>>> @@ -161,6 +159,11 @@ int IPASoftSimple::init(const IPASettings \n>>> &settings,\n>>>           }\n>>>             params_ = static_cast<DebayerParams *>(mem);\n>>> +        params_->blackLevel = { { 0.0, 0.0, 0.0 } };\n>>> +        params_->gamma = 1.0 / algorithms::kDefaultGamma;\n>>> +        params_->contrastExp = 1.0;\n>>> +        params_->gains = { { 1.0, 1.0, 1.0 } };\n>>> +        /* combinedMatrix is reset for each frame. */\n>>>       }\n>>>         {\n>>> @@ -287,6 +290,8 @@ void IPASoftSimple::computeParams(const uint32_t \n>>> frame)\n>>>       IPAFrameContext &frameContext = \n>>> context_.frameContexts.get(frame);\n>>>       for (auto const &algo : algorithms())\n>>>           algo->prepare(context_, frame, frameContext, params_);\n>>> +    params_->combinedMatrix = context_.activeState.combinedMatrix;\n>>> +\n>>>       setIspParams.emit();\n>>>   }\n>>>   diff --git a/src/libcamera/software_isp/debayer.cpp \n>>> b/src/libcamera/software_isp/debayer.cpp\n>>> index 65a1762dd..dccdd86b4 100644\n>>> --- a/src/libcamera/software_isp/debayer.cpp\n>>> +++ b/src/libcamera/software_isp/debayer.cpp\n>>> @@ -1,7 +1,7 @@\n>>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>>   /*\n>>>    * Copyright (C) 2023, Linaro Ltd\n>>> - * Copyright (C) 2023-2025 Red Hat Inc.\n>>> + * Copyright (C) 2023-2026 Red Hat Inc.\n>>>    *\n>>>    * Authors:\n>>>    * Hans de Goede<hdegoede@redhat.com>\n>>> @@ -25,99 +25,28 @@ namespace libcamera {\n>>>    */\n>>>     /**\n>>> - * \\var DebayerParams::kRGBLookupSize\n>>> - * \\brief Size of a color lookup table\n>>> + * \\var DebayerParams::gains\n>>> + * \\brief Colour channel gains\n>>>    */\n>>>     /**\n>>> - * \\struct DebayerParams::CcmColumn\n>>> - * \\brief Type of a single column of a color correction matrix (CCM)\n>>> - *\n>>> - * When multiplying an input pixel, columns in the CCM correspond \n>>> to the red,\n>>> - * green or blue component of input pixel values, while rows \n>>> correspond to the\n>>> - * red, green or blue components of the output pixel values. The \n>>> members of the\n>>> - * CcmColumn structure are named after the colour components of the \n>>> output pixel\n>>> - * values they correspond to.\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var DebayerParams::CcmColumn::r\n>>> - * \\brief Red (first) component of a CCM column\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var DebayerParams::CcmColumn::g\n>>> - * \\brief Green (second) component of a CCM column\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var DebayerParams::CcmColumn::b\n>>> - * \\brief Blue (third) component of a CCM column\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\typedef DebayerParams::LookupTable\n>>> - * \\brief Type of the lookup tables for single lookup values\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\typedef DebayerParams::CcmLookupTable\n>>> - * \\brief Type of the CCM lookup tables for red, green, blue values\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var DebayerParams::red\n>>> - * \\brief Lookup table for red color, mapping input values to \n>>> output values\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var DebayerParams::green\n>>> - * \\brief Lookup table for green color, mapping input values to \n>>> output values\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var DebayerParams::blue\n>>> - * \\brief Lookup table for blue color, mapping input values to \n>>> output values\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var DebayerParams::redCcm\n>>> - * \\brief Lookup table for the CCM red column, mapping input values \n>>> to output values\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var DebayerParams::greenCcm\n>>> - * \\brief Lookup table for the CCM green column, mapping input \n>>> values to output values\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var DebayerParams::blueCcm\n>>> - * \\brief Lookup table for the CCM blue column, mapping input \n>>> values to output values\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var DebayerParams::gammaLut\n>>> - * \\brief Gamma lookup table used with color correction matrix\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var DebayerParams::ccm\n>>> - * \\brief Per frame colour correction matrix for GPUISP\n>>> + * \\var DebayerParams::combinedMatrix\n>>> + * \\brief Colour correction matrix, including other adjustments\n>>>    */\n>>>     /**\n>>>    * \\var DebayerParams::blackLevel\n>>> - * \\brief Blacklevel gains for the GPUISP\n>>> + * \\brief Black level values\n>>>    */\n>>>     /**\n>>>    * \\var DebayerParams::gamma\n>>> - * \\brief Gamma value for the GPUISP\n>>> + * \\brief Gamma value, e.g. 1/2.2\n>>>    */\n>>>     /**\n>>>    * \\var DebayerParams::contrastExp\n>>> - * \\brief Contrast value for GPUISP\n>>> + * \\brief Contrast value to be used as an exponent\n>>>    */\n>>>     /**\n>>> @@ -131,13 +60,6 @@ LOG_DEFINE_CATEGORY(Debayer)\n>>>     Debayer::Debayer(const GlobalConfiguration &configuration) : \n>>> bench_(configuration)\n>>>   {\n>>> -    /* Initialize color lookup tables */\n>>> -    for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n>>> -        red_[i] = green_[i] = blue_[i] = i;\n>>> -        redCcm_[i] = { static_cast<int16_t>(i), 0, 0 };\n>>> -        greenCcm_[i] = { 0, static_cast<int16_t>(i), 0 };\n>>> -        blueCcm_[i] = { 0, 0, static_cast<int16_t>(i) };\n>>> -    }\n>>>   }\n>>>     Debayer::~Debayer()\n>>> @@ -305,56 +227,6 @@ Debayer::~Debayer()\n>>>    * \\brief Output size object\n>>>    */\n>>>   -/**\n>>> - * \\var Debayer::red_\n>>> - * \\brief Lookup table for red channel gain and correction values\n>>> - *\n>>> - * This table provides precomputed per-pixel or per-intensity\n>>> - * correction values for the red color channel used during debayering.\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var Debayer::green_\n>>> - * \\brief Lookup table for green channel gain and correction values\n>>> - *\n>>> - * This table provides precomputed per-pixel or per-intensity\n>>> - * correction values for the green color channel used during \n>>> debayering.\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var Debayer::blue_\n>>> - * \\brief Lookup table for blue channel gain and correction values\n>>> - *\n>>> - * This table provides precomputed per-pixel or per-intensity\n>>> - * correction values for the blue color channel used during \n>>> debayering.\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var Debayer::redCcm_\n>>> - * \\brief Red channel Color Correction Matrix (CCM) lookup table\n>>> - *\n>>> - * Contains coefficients for green channel color correction.\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var Debayer::greenCcm_\n>>> - * \\brief Green channel Color Correction Matrix (CCM) lookup table\n>>> - *\n>>> - * Contains coefficients for green channel color correction.\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var Debayer::blueCcm_\n>>> - * \\brief Blue channel Color Correction Matrix (CCM) lookup table\n>>> - *\n>>> - * Contains coefficients for blue channel color correction.\n>>> - */\n>>> -\n>>> -/**\n>>> - * \\var Debayer::gammaLut_\n>>> - * \\brief Gamma correction lookup table\n>>> - */\n>>> -\n>>>   /**\n>>>    * \\var Debayer::swapRedBlueGains_\n>>>    * \\brief Flag indicating whether red and blue channel gains \n>>> should be swapped\n>>> @@ -396,34 +268,6 @@ Debayer::~Debayer()\n>>>    * DebayerEGL::start.\n>>>    */\n>>>   -/**\n>>> - * \\fn void Debayer::setParams(DebayerParams &params)\n>>> - * \\brief Select the bayer params to use for the next frame debayer\n>>> - * \\param[in] params The parameters to be used in debayering\n>>> - */\n>>> -void Debayer::setParams(DebayerParams &params)\n>>> -{\n>>> -    green_ = params.green;\n>>> -    greenCcm_ = params.greenCcm;\n>>> -    if (swapRedBlueGains_) {\n>>> -        red_ = params.blue;\n>>> -        blue_ = params.red;\n>>> -        redCcm_ = params.blueCcm;\n>>> -        blueCcm_ = params.redCcm;\n>>> -        for (unsigned int i = 0; i < 256; i++) {\n>>> -            std::swap(redCcm_[i].r, redCcm_[i].b);\n>>> -            std::swap(greenCcm_[i].r, greenCcm_[i].b);\n>>> -            std::swap(blueCcm_[i].r, blueCcm_[i].b);\n>>> -        }\n>>> -    } else {\n>>> -        red_ = params.red;\n>>> -        blue_ = params.blue;\n>>> -        redCcm_ = params.redCcm;\n>>> -        blueCcm_ = params.blueCcm;\n>>> -    }\n>>> -    gammaLut_ = params.gammaLut;\n>>> -}\n>>> -\n>>>   /**\n>>>    * \\fn void Debayer::dmaSyncBegin(DebayerParams &params)\n>>>    * \\brief Common CPU/GPU Dma Sync Buffer begin\n>>> diff --git a/src/libcamera/software_isp/debayer.h \n>>> b/src/libcamera/software_isp/debayer.h\n>>> index cd2db9930..652cff4cc 100644\n>>> --- a/src/libcamera/software_isp/debayer.h\n>>> +++ b/src/libcamera/software_isp/debayer.h\n>>> @@ -78,13 +78,6 @@ public:\n>>>       Size outputSize_;\n>>>       PixelFormat inputPixelFormat_;\n>>>       PixelFormat outputPixelFormat_;\n>>> -    DebayerParams::LookupTable red_;\n>>> -    DebayerParams::LookupTable green_;\n>>> -    DebayerParams::LookupTable blue_;\n>>> -    DebayerParams::CcmLookupTable redCcm_;\n>>> -    DebayerParams::CcmLookupTable greenCcm_;\n>>> -    DebayerParams::CcmLookupTable blueCcm_;\n>>> -    DebayerParams::LookupTable gammaLut_;\n>>>       bool swapRedBlueGains_;\n>>>       Benchmark bench_;\n>>>   @@ -92,7 +85,6 @@ private:\n>>>       virtual Size patternSize(PixelFormat inputFormat) = 0;\n>>>     protected:\n>>> -    void setParams(DebayerParams &params);\n>>>       void dmaSyncBegin(std::vector<DmaSyncer> &dmaSyncers, \n>>> FrameBuffer *input, FrameBuffer *output);\n>>>       static bool isStandardBayerOrder(BayerFormat::Order order);\n>>>   };\n>>> diff --git a/src/libcamera/software_isp/debayer_cpu.cpp \n>>> b/src/libcamera/software_isp/debayer_cpu.cpp\n>>> index 00738c56b..af7af0a8d 100644\n>>> --- a/src/libcamera/software_isp/debayer_cpu.cpp\n>>> +++ b/src/libcamera/software_isp/debayer_cpu.cpp\n>>> @@ -1,7 +1,7 @@\n>>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>>   /*\n>>>    * Copyright (C) 2023, Linaro Ltd\n>>> - * Copyright (C) 2023-2025 Red Hat Inc.\n>>> + * Copyright (C) 2023-2026 Red Hat Inc.\n>>>    *\n>>>    * Authors:\n>>>    * Hans de Goede<hdegoede@redhat.com>\n>>> @@ -68,21 +68,21 @@ DebayerCpu::~DebayerCpu() = default;\n>>>   #define GAMMA(value) \\\n>>>       *dst++ = gammaLut_[std::clamp(value, 0, \n>>> static_cast<int>(gammaLut_.size()) - 1)]\n>>>   -#define STORE_PIXEL(b_, g_, \n>>> r_)                                        \\\n>>> -    if constexpr (ccmEnabled) {                                    \\\n>>> -        const DebayerParams::CcmColumn &blue = blueCcm_[b_];   \\\n>>> -        const DebayerParams::CcmColumn &green = greenCcm_[g_]; \\\n>>> -        const DebayerParams::CcmColumn &red = redCcm_[r_];     \\\n>>> -        GAMMA(blue.b + green.b + red.b);                       \\\n>>> -        GAMMA(blue.g + green.g + red.g);                       \\\n>>> -        GAMMA(blue.r + green.r + red.r);                       \\\n>>> -    } else {                                                       \\\n>>> -        *dst++ = blue_[b_];                                    \\\n>>> -        *dst++ = green_[g_];                                   \\\n>>> -        *dst++ = red_[r_];                                     \\\n>>> - } \\\n>>> -    if constexpr (addAlphaByte)                                    \\\n>>> -        *dst++ = 255;                                          \\\n>>> +#define STORE_PIXEL(b_, g_, r_)                         \\\n>>> +    if constexpr (ccmEnabled) {                     \\\n>>> +        const CcmColumn &blue = blueCcm_[b_];   \\\n>>> +        const CcmColumn &green = greenCcm_[g_]; \\\n>>> +        const CcmColumn &red = redCcm_[r_];     \\\n>>> +        GAMMA(blue.b + green.b + red.b);        \\\n>>> +        GAMMA(blue.g + green.g + red.g);        \\\n>>> +        GAMMA(blue.r + green.r + red.r);        \\\n>>> +    } else {                                        \\\n>>> +        *dst++ = blue_[b_];                     \\\n>>> +        *dst++ = green_[g_];                    \\\n>>> +        *dst++ = red_[r_];                      \\\n>>> +    }                                               \\\n>>> +    if constexpr (addAlphaByte)                     \\\n>>> +        *dst++ = 255;                           \\\n>>>       x++;\n>>>     /*\n>>> @@ -525,6 +525,16 @@ int DebayerCpu::configure(const \n>>> StreamConfiguration &inputCfg,\n>>>       if (ret != 0)\n>>>           return -EINVAL;\n>>>   +    ccmEnabled_ = ccmEnabled;\n>>> +\n>>> +    /*\n>>> +     * Lookup tables must be initialized because the initial value \n>>> is used for\n>>> +     * the first two frames, i.e. until stats processing starts \n>>> providing its\n>>> +     * own parameters. Let's enforce recomputing lookup tables by \n>>> setting the\n>>> +     * stored last used gamma to an out-of-range value.\n>>> +     */\n>>> +    params_.gamma = 1.0;\n>>> +\n>>>       window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) &\n>>>               ~(inputConfig_.patternSize.width - 1);\n>>>       window_.y = ((inputCfg.size.height - outputCfg.size.height) / \n>>> 2) &\n>>> @@ -740,6 +750,98 @@ void DebayerCpu::process4(uint32_t frame, const \n>>> uint8_t *src, uint8_t *dst)\n>>>       }\n>>>   }\n>>>   +void DebayerCpu::updateGammaTable(DebayerParams &params)\n>>> +{\n>>> +    const RGB<float> blackLevel = params.blackLevel;\n>>> +    /* Take let's say the green channel black level */\n>>> +    const unsigned int blackIndex = blackLevel[1] * \n>>> gammaTable_.size();\n>>> +    const float gamma = params.gamma;\n>>> +    const float contrastExp = params.contrastExp;\n>>> +\n>>> +    const float divisor = gammaTable_.size() - blackIndex - 1.0;\n>>> +    for (unsigned int i = blackIndex; i < gammaTable_.size(); i++) {\n>>> +        float normalized = (i - blackIndex) / divisor;\n>>> +        /* Convert 0..2 to 0..infinity; avoid actual inifinity at \n>>> tan(pi/2) */\n>>> +        /* Apply simple S-curve */\n>>> +        if (normalized < 0.5)\n>>> +            normalized = 0.5 * std::pow(normalized / 0.5, \n>>> contrastExp);\n>>> +        else\n>>> +            normalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / \n>>> 0.5, contrastExp);\n>>> +        gammaTable_[i] = UINT8_MAX *\n>>> +                 std::pow(normalized, gamma);\n>>> +    }\n>>> +    /*\n>>> +     * Due to CCM operations, the table lookup may reach indices \n>>> below the black\n>>> +     * level. Let's set the table values below black level to the \n>>> minimum\n>>> +     * non-black value to prevent problems when the minimum value is\n>>> +     * significantly non-zero (for example, when the image should \n>>> be all grey).\n>>> +     */\n>>> +    std::fill(gammaTable_.begin(), gammaTable_.begin() + blackIndex,\n>>> +          gammaTable_[blackIndex]);\n>>> +}\n>>> +\n>>> +void DebayerCpu::updateLookupTables(DebayerParams &params)\n>>> +{\n>>> +    const bool gammaUpdateNeeded =\n>>> +        params.gamma != params_.gamma ||\n>>> +        params.blackLevel != params_.blackLevel ||\n>>> +        params.contrastExp != params_.contrastExp;\n>>> +    if (gammaUpdateNeeded)\n>>> +        updateGammaTable(params);\n>>> +\n>>> +    auto matrixChanged = [](const Matrix<float, 3, 3> &m1, const \n>>> Matrix<float, 3, 3> &m2) -> bool {\n>>> +        return !std::equal(m1.data().begin(), m1.data().end(), \n>>> m2.data().begin());\n>>> +    };\n>>> +    const unsigned int gammaTableSize = gammaTable_.size();\n>>> +    const double div = static_cast<double>(kRGBLookupSize) / \n>>> gammaTableSize;\n>>> +    if (ccmEnabled_) {\n>>> +        if (gammaUpdateNeeded ||\n>>> +            matrixChanged(params.combinedMatrix, \n>>> params_.combinedMatrix)) {\n>>> +            auto &red = swapRedBlueGains_ ? blueCcm_ : redCcm_;\n>>> +            auto &green = greenCcm_;\n>>> +            auto &blue = swapRedBlueGains_ ? redCcm_ : blueCcm_;\n>>> +            const unsigned int redIndex = swapRedBlueGains_ ? 2 : 0;\n>>> +            const unsigned int greenIndex = 1;\n>>> +            const unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2;\n>>> +            for (unsigned int i = 0; i < kRGBLookupSize; i++) {\n>>> +                red[i].r = std::round(i * \n>>> params.combinedMatrix[redIndex][0]);\n>>> +                red[i].g = std::round(i * \n>>> params.combinedMatrix[greenIndex][0]);\n>>> +                red[i].b = std::round(i * \n>>> params.combinedMatrix[blueIndex][0]);\n>>> +                green[i].r = std::round(i * \n>>> params.combinedMatrix[redIndex][1]);\n>>> +                green[i].g = std::round(i * \n>>> params.combinedMatrix[greenIndex][1]);\n>>> +                green[i].b = std::round(i * \n>>> params.combinedMatrix[blueIndex][1]);\n>>> +                blue[i].r = std::round(i * \n>>> params.combinedMatrix[redIndex][2]);\n>>> +                blue[i].g = std::round(i * \n>>> params.combinedMatrix[greenIndex][2]);\n>>> +                blue[i].b = std::round(i * \n>>> params.combinedMatrix[blueIndex][2]);\n>>> +                gammaLut_[i] = gammaTable_[i / div];\n>>> +            }\n>>> +        }\n>>> +    } else {\n>>> +        if (gammaUpdateNeeded || params.gains != params_.gains) {\n>>> +            auto &gains = params.gains;\n>>> +            auto &red = swapRedBlueGains_ ? blue_ : red_;\n>>> +            auto &green = green_;\n>>> +            auto &blue = swapRedBlueGains_ ? red_ : blue_;\n>>> +            for (unsigned int i = 0; i < kRGBLookupSize; i++) {\n>>> +                /* Apply gamma after gain! */\n>>> +                const RGB<float> lutGains = (gains * i / \n>>> div).min(gammaTableSize - 1);\n>>> +                red[i] = gammaTable_[static_cast<unsigned \n>>> int>(lutGains.r())];\n>>> +                green[i] = gammaTable_[static_cast<unsigned \n>>> int>(lutGains.g())];\n>>> +                blue[i] = gammaTable_[static_cast<unsigned \n>>> int>(lutGains.b())];\n>>> +            }\n>>> +        }\n>>> +    }\n>>> +\n>>> +    LOG(Debayer, Debug)\n>>> +        << \"Debayer parameters: blackLevel=\" << params.blackLevel\n>>> +        << \"; gamma=\" << params.gamma\n>>> +        << \"; contrastExp=\" << params.contrastExp\n>>> +        << \"; gains=\" << params.gains\n>>> +        << \"; matrix=\" << params.combinedMatrix;\n>>> +\n>>> +    params_ = params;\n>>> +}\n>>> +\n>>>   void DebayerCpu::process(uint32_t frame, FrameBuffer *input, \n>>> FrameBuffer *output, DebayerParams params)\n>>>   {\n>>>       bench_.startFrame();\n>>> @@ -748,7 +850,7 @@ void DebayerCpu::process(uint32_t frame, \n>>> FrameBuffer *input, FrameBuffer *output\n>>>         dmaSyncBegin(dmaSyncers, input, output);\n>>>   -    setParams(params);\n>>> +    updateLookupTables(params);\n>>>         /* Copy metadata from the input buffer */\n>>>       FrameMetadata &metadata = output->_d()->metadata();\n>>> diff --git a/src/libcamera/software_isp/debayer_cpu.h \n>>> b/src/libcamera/software_isp/debayer_cpu.h\n>>> index 67df2b93a..b5cbb5bd2 100644\n>>> --- a/src/libcamera/software_isp/debayer_cpu.h\n>>> +++ b/src/libcamera/software_isp/debayer_cpu.h\n>>> @@ -1,7 +1,7 @@\n>>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>>   /*\n>>>    * Copyright (C) 2023, Linaro Ltd\n>>> - * Copyright (C) 2023-2025 Red Hat Inc.\n>>> + * Copyright (C) 2023-2026 Red Hat Inc.\n>>>    *\n>>>    * Authors:\n>>>    * Hans de Goede<hdegoede@redhat.com>\n>>> @@ -18,6 +18,8 @@\n>>>   #include <libcamera/base/object.h>\n>>>     #include \"libcamera/internal/bayer_format.h\"\n>>> +#include \"libcamera/internal/global_configuration.h\"\n>>> +#include \"libcamera/internal/software_isp/debayer_params.h\"\n>>>   #include \"libcamera/internal/software_isp/swstats_cpu.h\"\n>>>     #include \"debayer.h\"\n>>> @@ -108,10 +110,32 @@ private:\n>>>       void memcpyNextLine(const uint8_t *linePointers[]);\n>>>       void process2(uint32_t frame, const uint8_t *src, uint8_t *dst);\n>>>       void process4(uint32_t frame, const uint8_t *src, uint8_t *dst);\n>>> +    void updateGammaTable(DebayerParams &params);\n>>> +    void updateLookupTables(DebayerParams &params);\n>>>         /* Max. supported Bayer pattern height is 4, debayering this \n>>> requires 5 lines */\n>>>       static constexpr unsigned int kMaxLineBuffers = 5;\n>>>   +    static constexpr unsigned int kRGBLookupSize = 256;\n>>> +    static constexpr unsigned int kGammaLookupSize = 1024;\n>>> +    struct CcmColumn {\n>>> +        int16_t r;\n>>> +        int16_t g;\n>>> +        int16_t b;\n>>> +    };\n>>> +    using LookupTable = std::array<uint8_t, kRGBLookupSize>;\n>>> +    using CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>;\n>>> +    LookupTable red_;\n>>> +    LookupTable green_;\n>>> +    LookupTable blue_;\n>>> +    CcmLookupTable redCcm_;\n>>> +    CcmLookupTable greenCcm_;\n>>> +    CcmLookupTable blueCcm_;\n>>> +    std::array<double, kGammaLookupSize> gammaTable_;\n>>> +    LookupTable gammaLut_;\n>>> +    bool ccmEnabled_;\n>>> +    DebayerParams params_;\n>>> +\n>>>       debayerFn debayer0_;\n>>>       debayerFn debayer1_;\n>>>       debayerFn debayer2_;\n>>> diff --git a/src/libcamera/software_isp/debayer_egl.cpp \n>>> b/src/libcamera/software_isp/debayer_egl.cpp\n>>> index 9693d7252..af04d60ca 100644\n>>> --- a/src/libcamera/software_isp/debayer_egl.cpp\n>>> +++ b/src/libcamera/software_isp/debayer_egl.cpp\n>>> @@ -475,18 +475,18 @@ void \n>>> DebayerEGL::setShaderVariableValues(DebayerParams &params)\n>>>                   << \" textureUniformProjMatrix_ \" << \n>>> textureUniformProjMatrix_;\n>>>         GLfloat ccm[9] = {\n>>> -        params.ccm[0][0],\n>>> -        params.ccm[0][1],\n>>> -        params.ccm[0][2],\n>>> -        params.ccm[1][0],\n>>> -        params.ccm[1][1],\n>>> -        params.ccm[1][2],\n>>> -        params.ccm[2][0],\n>>> -        params.ccm[2][1],\n>>> -        params.ccm[2][2],\n>>> +        params.combinedMatrix[0][0],\n>>> +        params.combinedMatrix[0][1],\n>>> +        params.combinedMatrix[0][2],\n>>> +        params.combinedMatrix[1][0],\n>>> +        params.combinedMatrix[1][1],\n>>> +        params.combinedMatrix[1][2],\n>>> +        params.combinedMatrix[2][0],\n>>> +        params.combinedMatrix[2][1],\n>>> +        params.combinedMatrix[2][2],\n>>>       };\n>>>       glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm);\n>>> -    LOG(Debayer, Debug) << \" ccmUniformDataIn_ \" << \n>>> ccmUniformDataIn_ << \" data \" << params.ccm;\n>>> +    LOG(Debayer, Debug) << \" ccmUniformDataIn_ \" << \n>>> ccmUniformDataIn_ << \" data \" << params.combinedMatrix;\n>>>         /*\n>>>        * 0 = Red, 1 = Green, 2 = Blue\n>>> @@ -544,8 +544,6 @@ void DebayerEGL::process(uint32_t frame, \n>>> FrameBuffer *input, FrameBuffer *output\n>>>         dmaSyncBegin(dmaSyncers, input, nullptr);\n>>>   -    setParams(params);\n>>> -\n>>>       /* Copy metadata from the input buffer */\n>>>       FrameMetadata &metadata = output->_d()->metadata();\n>>>       metadata.status = input->metadata().status;\n>>> diff --git a/src/libcamera/software_isp/software_isp.cpp \n>>> b/src/libcamera/software_isp/software_isp.cpp\n>>> index 7ad3511db..a83986b78 100644\n>>> --- a/src/libcamera/software_isp/software_isp.cpp\n>>> +++ b/src/libcamera/software_isp/software_isp.cpp\n>>> @@ -84,23 +84,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, \n>>> const CameraSensor *sensor,\n>>>              DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |\n>>>              DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)\n>>>   {\n>>> -    /*\n>>> -     * debayerParams_ must be initialized because the initial value \n>>> is used for\n>>> -     * the first two frames, i.e. until stats processing starts \n>>> providing its\n>>> -     * own parameters.\n>>> -     *\n>>> -     * \\todo This should be handled in the same place as the related\n>>> -     * operations, in the IPA module.\n>>> -     */\n>>> -    std::array<uint8_t, 256> gammaTable;\n>>> -    for (unsigned int i = 0; i < 256; i++)\n>>> -        gammaTable[i] = UINT8_MAX * std::pow(i / 256.0, 0.5);\n>>> -    for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n>>> -        debayerParams_.red[i] = gammaTable[i];\n>>> -        debayerParams_.green[i] = gammaTable[i];\n>>> -        debayerParams_.blue[i] = gammaTable[i];\n>>> -    }\n>>> -\n>>>       if (!dmaHeap_.isValid()) {\n>>>           LOG(SoftwareIsp, Error) << \"Failed to create \n>>> DmaBufAllocator object\";\n>>>           return;\n>>> @@ -121,8 +104,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, \n>>> const CameraSensor *sensor,\n>>>       }\n>>>       stats->statsReady.connect(this, &SoftwareIsp::statsReady);\n>>>   -    bool gpuIspEnabled;\n>>> -\n>>>   #if HAVE_DEBAYER_EGL\n>>>       std::optional<std::string> softISPMode = \n>>> configuration.envOption(\"LIBCAMERA_SOFTISP_MODE\", { \"software_isp\", \n>>> \"mode\" });\n>>>       if (softISPMode) {\n>>> @@ -133,15 +114,12 @@ SoftwareIsp::SoftwareIsp(PipelineHandler \n>>> *pipe, const CameraSensor *sensor,\n>>>           }\n>>>       }\n>>>   -    if (!softISPMode || softISPMode == \"gpu\") {\n>>> +    if (!softISPMode || softISPMode == \"gpu\")\n>>>           debayer_ = std::make_unique<DebayerEGL>(std::move(stats), \n>>> configuration);\n>>> -        gpuIspEnabled = true;\n>>> -    }\n>>> +\n>>>   #endif\n>>> -    if (!debayer_) {\n>>> +    if (!debayer_)\n>>>           debayer_ = std::make_unique<DebayerCpu>(std::move(stats), \n>>> configuration);\n>>> -        gpuIspEnabled = false;\n>>> -    }\n>>>         debayer_->inputBufferReady.connect(this, \n>>> &SoftwareIsp::inputReady);\n>>>       debayer_->outputBufferReady.connect(this, \n>>> &SoftwareIsp::outputReady);\n>>> @@ -173,7 +151,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, \n>>> const CameraSensor *sensor,\n>>>                sharedParams_.fd(),\n>>>                sensorInfo,\n>>>                sensor->controls(),\n>>> -             gpuIspEnabled,\n>>>                ipaControls,\n>>>                &ccmEnabled_);\n>>>       if (ret) {\n>>\n>> -- \n>> Robert Mader\n>> Consultant Software Developer\n>>\n>> Collabora Ltd.\n>> Platinum Building, St John's Innovation Park, Cambridge CB4 0DS, UK\n>> Registered in England & Wales, no. 5513718\n>>\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id B851FC3220\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 22 Jan 2026 17:05:06 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id B966561FCE;\n\tThu, 22 Jan 2026 18:05:05 +0100 (CET)","from sender4-pp-f112.zoho.com (sender4-pp-f112.zoho.com\n\t[136.143.188.112])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id CF01B61FC3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 22 Jan 2026 18:05:03 +0100 (CET)","by mx.zohomail.com with SMTPS id 1769101498697369.45598814766345; \n\tThu, 22 Jan 2026 09:04:58 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=collabora.com\n\theader.i=robert.mader@collabora.com header.b=\"gJHsJVFU\"; \n\tdkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1769101501; cv=none; \n\td=zohomail.com; s=zohoarc; \n\tb=EbSARMLcVFWVwWUdxcQcE7Q8XiTvQlR55x8Y2w2dRaT/pijTZCyoAqaUvTkn7NnZif+JRWuQ0D2tkOiVije+J6l7dVYRp45lBewhEGQ+vI1VXxugZy9QHT0YCyDZ+Ag2X0wK3KXa8BVJH/lDwnB5HOaIC3g5Fa+5bRRz7DUw0Qk=","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; \n\ts=zohoarc; t=1769101501;\n\th=Content-Type:Content-Transfer-Encoding:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To:Cc;\n\tbh=b4cmSjHQp1FkX+5vZ/RROyUOpBYwLijzh8heE67tD3k=; \n\tb=PoC0lXZctiLLZxul2YsjN0/HYnYJH0zLkc1KzNB8jp3t/2LlRZdRucpcWy1UGPgbBQzEfcA7pcdhlbmDLTrSvPTrNklyw9uBzkcSJeymt8/hJkHdt/JroODEilxh79e1/y1r6QlZ4THOKTYj1qlbTVvg7I3ADm27x2VwtrG4HUg=","ARC-Authentication-Results":"i=1; mx.zohomail.com;\n\tdkim=pass  header.i=collabora.com;\n\tspf=pass  smtp.mailfrom=robert.mader@collabora.com;\n\tdmarc=pass header.from=<robert.mader@collabora.com>","DKIM-Signature":"v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1769101501;\n\ts=zohomail; d=collabora.com; i=robert.mader@collabora.com;\n\th=Message-ID:Date:Date:MIME-Version:Subject:Subject:To:To:References:From:From:In-Reply-To:Content-Type:Content-Transfer-Encoding:Message-Id:Reply-To:Cc;\n\tbh=b4cmSjHQp1FkX+5vZ/RROyUOpBYwLijzh8heE67tD3k=;\n\tb=gJHsJVFUD7iC6AnwR9e7HERFiNKqnLT8EU8O7IBow+IcJ1+au9MJSSVWcyrAamff\n\t/bgknZGbT7ztr0ZZ+0oMkTjKX8i4MvRosvkBwBAg/rXD9KaGFOX2rfzPxgyaWedcG29\n\tdC80r498vPrnUHbicK87aVNSqaQkPZB/eIiCxL/c=","Message-ID":"<084751fd-bf7b-4647-a932-c31614f0f902@collabora.com>","Date":"Thu, 22 Jan 2026 18:04:55 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v4 14/15] libcamera: ipa: simple: Remove Lut algorithm","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20260122161935.208562-1-mzamazal@redhat.com>\n\t<20260122161935.208562-15-mzamazal@redhat.com>\n\t<55308b0c-4856-45e2-810c-77e39ab1581f@collabora.com>\n\t<ebf14c35-3f2d-4dc4-b95a-181c117d7e09@ideasonboard.com>","Content-Language":"en-US, de-DE","From":"Robert Mader <robert.mader@collabora.com>","In-Reply-To":"<ebf14c35-3f2d-4dc4-b95a-181c117d7e09@ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","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>"}},{"id":37954,"web_url":"https://patchwork.libcamera.org/comment/37954/","msgid":"<7e6cc110-c142-45ea-9fbb-5ff80e76cd39@collabora.com>","date":"2026-01-26T15:56:35","subject":"Re: [PATCH v4 14/15] libcamera: ipa: simple: Remove Lut algorithm","submitter":{"id":140,"url":"https://patchwork.libcamera.org/api/people/140/","name":"Robert Mader","email":"robert.mader@collabora.com"},"content":"Reviewed-by: Robert Mader <robert.mader@collabora.com>\n\nOn 22.01.26 17:19, Milan Zamazal wrote:\n> The Lut algorithm is not really an algorithm.  Moreover, algorithms may\n> be enabled or disabled but with Lut disabled, nothing will work.\n>\n> Let's move the construction of lookup tables to CPU debayering, where it\n> is used.  The implied and related changes are:\n>\n> - DebayerParams is changed to contain the real params rather than lookup\n>    tables.\n> - contrastExp parameter introduced by GPU ISP is used for CPU ISP too.\n> - The params must be initialised so that debayering gets meaningful\n>    parameter values even when some algorithms are disabled.\n> - combinedMatrix must be put to params everywhere where it is modified.\n> - Matrix changes needn't be tracked in the algorithms any more.\n> - CPU debayering must watch for changes of the corresponding parameters\n>    to update the lookup tables when and only when needed.\n> - Swapping red and blue is integrated into lookup table constructions.\n> - gpuIspEnabled flags are removed as they are not needed any more.\n>\n> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>\n> ---\n>   .../internal/software_isp/debayer_params.h    |  43 +----\n>   include/libcamera/ipa/soft.mojom              |   3 +-\n>   src/ipa/simple/algorithms/adjust.cpp          |  17 +-\n>   src/ipa/simple/algorithms/adjust.h            |   4 -\n>   src/ipa/simple/algorithms/awb.cpp             |   6 +-\n>   src/ipa/simple/algorithms/ccm.cpp             |   1 -\n>   src/ipa/simple/algorithms/lut.cpp             | 140 --------------\n>   src/ipa/simple/algorithms/lut.h               |  35 ----\n>   src/ipa/simple/algorithms/meson.build         |   1 -\n>   src/ipa/simple/data/uncalibrated.yaml         |   1 -\n>   src/ipa/simple/ipa_context.h                  |  11 --\n>   src/ipa/simple/soft_simple.cpp                |  11 +-\n>   src/libcamera/software_isp/debayer.cpp        | 172 +-----------------\n>   src/libcamera/software_isp/debayer.h          |   8 -\n>   src/libcamera/software_isp/debayer_cpu.cpp    | 136 ++++++++++++--\n>   src/libcamera/software_isp/debayer_cpu.h      |  26 ++-\n>   src/libcamera/software_isp/debayer_egl.cpp    |  22 +--\n>   src/libcamera/software_isp/software_isp.cpp   |  29 +--\n>   18 files changed, 188 insertions(+), 478 deletions(-)\n>   delete mode 100644 src/ipa/simple/algorithms/lut.cpp\n>   delete mode 100644 src/ipa/simple/algorithms/lut.h\n>\n> diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h\n> index 2d69bd295..1c0412d75 100644\n> --- a/include/libcamera/internal/software_isp/debayer_params.h\n> +++ b/include/libcamera/internal/software_isp/debayer_params.h\n> @@ -1,6 +1,6 @@\n>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>   /*\n> - * Copyright (C) 2023-2025 Red Hat Inc.\n> + * Copyright (C) 2023-2026 Red Hat Inc.\n>    *\n>    * Authors:\n>    * Hans de Goede <hdegoede@redhat.com>\n> @@ -10,7 +10,6 @@\n>   \n>   #pragma once\n>   \n> -#include <array>\n>   #include <stdint.h>\n>   \n>   #include \"libcamera/internal/matrix.h\"\n> @@ -19,47 +18,11 @@\n>   namespace libcamera {\n>   \n>   struct DebayerParams {\n> -\tstatic constexpr unsigned int kRGBLookupSize = 256;\n> -\n> -\tstruct CcmColumn {\n> -\t\tint16_t r;\n> -\t\tint16_t g;\n> -\t\tint16_t b;\n> -\t};\n> -\n> -\tusing LookupTable = std::array<uint8_t, kRGBLookupSize>;\n> -\tusing CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>;\n> -\n> -\t/*\n> -\t * Color lookup tables when CCM is not used.\n> -\t *\n> -\t * Each color of a debayered pixel is amended by the corresponding\n> -\t * value in the given table.\n> -\t */\n> -\tLookupTable red;\n> -\tLookupTable green;\n> -\tLookupTable blue;\n> -\n> -\t/*\n> -\t * Color and gamma lookup tables when CCM is used.\n> -\t *\n> -\t * Each of the CcmLookupTable's corresponds to a CCM column; together they\n> -\t * make a complete 3x3 CCM lookup table. The CCM is applied on debayered\n> -\t * pixels and then the gamma lookup table is used to set the resulting\n> -\t * values of all the three colors.\n> -\t */\n> -\tCcmLookupTable redCcm;\n> -\tCcmLookupTable greenCcm;\n> -\tCcmLookupTable blueCcm;\n> -\tLookupTable gammaLut;\n> -\n> -\t/*\n> -\t * Per frame corrections as calculated by the IPA\n> -\t */\n> -\tMatrix<float, 3, 3> ccm;\n> +\tMatrix<float, 3, 3> combinedMatrix;\n>   \tRGB<float> blackLevel;\n>   \tfloat gamma;\n>   \tfloat contrastExp;\n> +\tRGB<float> gains;\n>   };\n>   \n>   } /* namespace libcamera */\n> diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom\n> index aff8fcbd3..77328c5fd 100644\n> --- a/include/libcamera/ipa/soft.mojom\n> +++ b/include/libcamera/ipa/soft.mojom\n> @@ -17,8 +17,7 @@ interface IPASoftInterface {\n>   \t     libcamera.SharedFD fdStats,\n>   \t     libcamera.SharedFD fdParams,\n>   \t     libcamera.IPACameraSensorInfo sensorInfo,\n> -\t     libcamera.ControlInfoMap sensorControls,\n> -\t     bool gpuIspEnabled)\n> +\t     libcamera.ControlInfoMap sensorControls)\n>   \t\t=> (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled);\n>   \tstart() => (int32 ret);\n>   \tstop();\n> diff --git a/src/ipa/simple/algorithms/adjust.cpp b/src/ipa/simple/algorithms/adjust.cpp\n> index acdd3f741..068e98404 100644\n> --- a/src/ipa/simple/algorithms/adjust.cpp\n> +++ b/src/ipa/simple/algorithms/adjust.cpp\n> @@ -95,23 +95,20 @@ void Adjust::applySaturation(Matrix<float, 3, 3> &matrix, float saturation)\n>   void Adjust::prepare(IPAContext &context,\n>   \t\t     [[maybe_unused]] const uint32_t frame,\n>   \t\t     IPAFrameContext &frameContext,\n> -\t\t     [[maybe_unused]] DebayerParams *params)\n> +\t\t     DebayerParams *params)\n>   {\n>   \tframeContext.gamma = context.activeState.knobs.gamma;\n>   \tframeContext.contrast = context.activeState.knobs.contrast;\n>   \n> -\tif (!context.ccmEnabled)\n> -\t\treturn;\n> -\n>   \tauto &saturation = context.activeState.knobs.saturation;\n> -\tframeContext.saturation = saturation;\n> -\tif (saturation)\n> +\tif (context.ccmEnabled && saturation) {\n>   \t\tapplySaturation(context.activeState.combinedMatrix, saturation.value());\n> -\n> -\tif (saturation != lastSaturation_) {\n> -\t\tcontext.activeState.matrixChanged = true;\n> -\t\tlastSaturation_ = saturation;\n> +\t\tframeContext.saturation = saturation;\n>   \t}\n> +\n> +\tparams->gamma = 1.0 / context.activeState.knobs.gamma;\n> +\tconst float contrast = context.activeState.knobs.contrast.value_or(kDefaultContrast);\n> +\tparams->contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001));\n>   }\n>   \n>   void Adjust::process([[maybe_unused]] IPAContext &context,\n> diff --git a/src/ipa/simple/algorithms/adjust.h b/src/ipa/simple/algorithms/adjust.h\n> index 7644138ff..fb133b140 100644\n> --- a/src/ipa/simple/algorithms/adjust.h\n> +++ b/src/ipa/simple/algorithms/adjust.h\n> @@ -7,8 +7,6 @@\n>   \n>   #pragma once\n>   \n> -#include <optional>\n> -\n>   #include \"libcamera/internal/matrix.h\"\n>   \n>   #include <libipa/interpolator.h>\n> @@ -45,8 +43,6 @@ public:\n>   \n>   private:\n>   \tvoid applySaturation(Matrix<float, 3, 3> &ccm, float saturation);\n> -\n> -\tstd::optional<float> lastSaturation_;\n>   };\n>   \n>   } /* namespace ipa::soft::algorithms */\n> diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp\n> index 4d2f1df15..d2c1b87f0 100644\n> --- a/src/ipa/simple/algorithms/awb.cpp\n> +++ b/src/ipa/simple/algorithms/awb.cpp\n> @@ -37,7 +37,7 @@ int Awb::configure(IPAContext &context,\n>   void Awb::prepare(IPAContext &context,\n>   \t\t  [[maybe_unused]] const uint32_t frame,\n>   \t\t  IPAFrameContext &frameContext,\n> -\t\t  [[maybe_unused]] DebayerParams *params)\n> +\t\t  DebayerParams *params)\n>   {\n>   \tauto &gains = context.activeState.awb.gains;\n>   \tMatrix<float, 3, 3> gainMatrix = { { gains.r(), 0, 0,\n> @@ -45,9 +45,11 @@ void Awb::prepare(IPAContext &context,\n>   \t\t\t\t\t     0, 0, gains.b() } };\n>   \tcontext.activeState.combinedMatrix =\n>   \t\tcontext.activeState.combinedMatrix * gainMatrix;\n> -\t/* Just report, the gains are applied in LUT algorithm. */\n> +\n>   \tframeContext.gains.red = gains.r();\n>   \tframeContext.gains.blue = gains.b();\n> +\n> +\tparams->gains = gains;\n>   }\n>   \n>   void Awb::process(IPAContext &context,\n> diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp\n> index 5576a301f..911a5af2c 100644\n> --- a/src/ipa/simple/algorithms/ccm.cpp\n> +++ b/src/ipa/simple/algorithms/ccm.cpp\n> @@ -51,7 +51,6 @@ void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>   \t    utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) {\n>   \t\tcurrentCcm_ = ccm_.getInterpolated(ct);\n>   \t\tlastCt_ = ct;\n> -\t\tcontext.activeState.matrixChanged = true;\n>   \t}\n>   \n>   \tcontext.activeState.combinedMatrix =\n> diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp\n> deleted file mode 100644\n> index fd442259a..000000000\n> --- a/src/ipa/simple/algorithms/lut.cpp\n> +++ /dev/null\n> @@ -1,140 +0,0 @@\n> -/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> -/*\n> - * Copyright (C) 2024-2026, Red Hat Inc.\n> - *\n> - * Color lookup tables construction\n> - */\n> -\n> -#include \"lut.h\"\n> -\n> -#include <algorithm>\n> -#include <cmath>\n> -#include <optional>\n> -#include <stdint.h>\n> -\n> -#include <libcamera/base/log.h>\n> -\n> -#include <libcamera/control_ids.h>\n> -\n> -#include \"simple/ipa_context.h\"\n> -\n> -#include \"adjust.h\"\n> -\n> -namespace libcamera {\n> -\n> -LOG_DEFINE_CATEGORY(IPASoftLut)\n> -\n> -namespace ipa::soft::algorithms {\n> -\n> -int Lut::configure(IPAContext &context,\n> -\t\t   [[maybe_unused]] const IPAConfigInfo &configInfo)\n> -{\n> -\tupdateGammaTable(context);\n> -\n> -\treturn 0;\n> -}\n> -\n> -void Lut::updateGammaTable(IPAContext &context)\n> -{\n> -\tconst auto blackLevel = context.activeState.blc.level;\n> -\tconst auto gamma = 1.0 / context.activeState.knobs.gamma;\n> -\tconst auto contrast = context.activeState.knobs.contrast.value_or(1.0);\n> -\t/* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */\n> -\tfloat contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001));\n> -\n> -\tif (!context.gpuIspEnabled) {\n> -\t\tauto &gammaTable = context.activeState.gamma.gammaTable;\n> -\t\tconst unsigned int blackIndex = blackLevel * gammaTable.size() / 256;\n> -\t\tconst float divisor = gammaTable.size() - blackIndex - 1.0;\n> -\t\tfor (unsigned int i = blackIndex; i < gammaTable.size(); i++) {\n> -\t\t\tdouble normalized = (i - blackIndex) / divisor;\n> -\t\t\t/* Apply simple S-curve */\n> -\t\t\tif (normalized < 0.5)\n> -\t\t\t\tnormalized = 0.5 * std::pow(normalized / 0.5, contrastExp);\n> -\t\t\telse\n> -\t\t\t\tnormalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp);\n> -\t\t\tgammaTable[i] = UINT8_MAX * std::pow(normalized, gamma);\n> -\t\t}\n> -\t\t/*\n> -\t\t * Due to CCM operations, the table lookup may reach indices below the black\n> -\t\t * level. Let's set the table values below black level to the minimum\n> -\t\t * non-black value to prevent problems when the minimum value is\n> -\t\t * significantly non-zero (for example, when the image should be all grey).\n> -\t\t */\n> -\t\tstd::fill(gammaTable.begin(), gammaTable.begin() + blackIndex,\n> -\t\t\t  gammaTable[blackIndex]);\n> -\t}\n> -\n> -\tcontext.activeState.gamma.gamma = gamma;\n> -\tcontext.activeState.gamma.blackLevel = blackLevel;\n> -\tcontext.activeState.gamma.contrastExp = contrastExp;\n> -}\n> -\n> -int16_t Lut::matrixValue(unsigned int i, float ccm) const\n> -{\n> -\treturn std::round(i * ccm);\n> -}\n> -\n> -void Lut::prepare(IPAContext &context,\n> -\t\t  [[maybe_unused]] const uint32_t frame,\n> -\t\t  [[maybe_unused]] IPAFrameContext &frameContext,\n> -\t\t  DebayerParams *params)\n> -{\n> -\t/*\n> -\t * Update the gamma table if needed. This means if black level changes\n> -\t * and since the black level gets updated only if a lower value is\n> -\t * observed, it's not permanently prone to minor fluctuations or\n> -\t * rounding errors.\n> -\t */\n> -\tconst bool gammaUpdateNeeded =\n> -\t\tcontext.activeState.gamma.blackLevel != context.activeState.blc.level ||\n> -\t\tcontext.activeState.gamma.contrast != context.activeState.knobs.contrast;\n> -\tif (gammaUpdateNeeded)\n> -\t\tupdateGammaTable(context);\n> -\n> -\tauto &gains = context.activeState.awb.gains;\n> -\tauto &gammaTable = context.activeState.gamma.gammaTable;\n> -\tconst unsigned int gammaTableSize = gammaTable.size();\n> -\tconst double div = static_cast<double>(DebayerParams::kRGBLookupSize) /\n> -\t\t\t   gammaTableSize;\n> -\n> -\tif (!context.ccmEnabled) {\n> -\t\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n> -\t\t\t/* Apply gamma after gain! */\n> -\t\t\tconst RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);\n> -\t\t\tparams->red[i] = gammaTable[static_cast<unsigned int>(lutGains.r())];\n> -\t\t\tparams->green[i] = gammaTable[static_cast<unsigned int>(lutGains.g())];\n> -\t\t\tparams->blue[i] = gammaTable[static_cast<unsigned int>(lutGains.b())];\n> -\t\t}\n> -\t} else if (context.activeState.matrixChanged || gammaUpdateNeeded) {\n> -\t\tauto &matrix = context.activeState.combinedMatrix;\n> -\t\tauto &red = params->redCcm;\n> -\t\tauto &green = params->greenCcm;\n> -\t\tauto &blue = params->blueCcm;\n> -\t\tparams->ccm = matrix;\n> -\t\tif (!context.gpuIspEnabled) {\n> -\t\t\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n> -\t\t\t\tred[i].r = matrixValue(i, matrix[0][0]);\n> -\t\t\t\tred[i].g = matrixValue(i, matrix[1][0]);\n> -\t\t\t\tred[i].b = matrixValue(i, matrix[2][0]);\n> -\t\t\t\tgreen[i].r = matrixValue(i, matrix[0][1]);\n> -\t\t\t\tgreen[i].g = matrixValue(i, matrix[1][1]);\n> -\t\t\t\tgreen[i].b = matrixValue(i, matrix[2][1]);\n> -\t\t\t\tblue[i].r = matrixValue(i, matrix[0][2]);\n> -\t\t\t\tblue[i].g = matrixValue(i, matrix[1][2]);\n> -\t\t\t\tblue[i].b = matrixValue(i, matrix[2][2]);\n> -\t\t\t\tparams->gammaLut[i] = gammaTable[i / div];\n> -\t\t\t}\n> -\t\t}\n> -\t\tcontext.activeState.matrixChanged = false;\n> -\t}\n> -\n> -\tparams->gamma = context.activeState.gamma.gamma;\n> -\tparams->contrastExp = context.activeState.gamma.contrastExp;\n> -}\n> -\n> -REGISTER_IPA_ALGORITHM(Lut, \"Lut\")\n> -\n> -} /* namespace ipa::soft::algorithms */\n> -\n> -} /* namespace libcamera */\n> diff --git a/src/ipa/simple/algorithms/lut.h b/src/ipa/simple/algorithms/lut.h\n> deleted file mode 100644\n> index ad16d1e8e..000000000\n> --- a/src/ipa/simple/algorithms/lut.h\n> +++ /dev/null\n> @@ -1,35 +0,0 @@\n> -/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> -/*\n> - * Copyright (C) 2024, Red Hat Inc.\n> - *\n> - * Color lookup tables construction\n> - */\n> -\n> -#pragma once\n> -\n> -#include \"algorithm.h\"\n> -\n> -namespace libcamera {\n> -\n> -namespace ipa::soft::algorithms {\n> -\n> -class Lut : public Algorithm\n> -{\n> -public:\n> -\tLut() = default;\n> -\t~Lut() = default;\n> -\n> -\tint configure(IPAContext &context, const IPAConfigInfo &configInfo) override;\n> -\tvoid prepare(IPAContext &context,\n> -\t\t     const uint32_t frame,\n> -\t\t     IPAFrameContext &frameContext,\n> -\t\t     DebayerParams *params) override;\n> -\n> -private:\n> -\tvoid updateGammaTable(IPAContext &context);\n> -\tint16_t matrixValue(unsigned int i, float ccm) const;\n> -};\n> -\n> -} /* namespace ipa::soft::algorithms */\n> -\n> -} /* namespace libcamera */\n> diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build\n> index ebe9f20dd..73c637220 100644\n> --- a/src/ipa/simple/algorithms/meson.build\n> +++ b/src/ipa/simple/algorithms/meson.build\n> @@ -6,5 +6,4 @@ soft_simple_ipa_algorithms = files([\n>       'agc.cpp',\n>       'blc.cpp',\n>       'ccm.cpp',\n> -    'lut.cpp',\n>   ])\n> diff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml\n> index e389e0588..c6feda36d 100644\n> --- a/src/ipa/simple/data/uncalibrated.yaml\n> +++ b/src/ipa/simple/data/uncalibrated.yaml\n> @@ -15,6 +15,5 @@ algorithms:\n>                    0, 1, 0,\n>                    0, 0, 1]\n>     - Adjust:\n> -  - Lut:\n>     - Agc:\n>   ...\n> diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h\n> index 293e35b71..34f7403a4 100644\n> --- a/src/ipa/simple/ipa_context.h\n> +++ b/src/ipa/simple/ipa_context.h\n> @@ -53,17 +53,7 @@ struct IPAActiveState {\n>   \t\tunsigned int temperatureK;\n>   \t} awb;\n>   \n> -\tstatic constexpr unsigned int kGammaLookupSize = 1024;\n> -\tstruct {\n> -\t\tstd::array<double, kGammaLookupSize> gammaTable;\n> -\t\tuint8_t blackLevel;\n> -\t\tfloat gamma;\n> -\t\tfloat contrast;\n> -\t\tfloat contrastExp;\n> -\t} gamma;\n> -\n>   \tMatrix<float, 3, 3> combinedMatrix;\n> -\tbool matrixChanged = false;\n>   \n>   \tstruct {\n>   \t\tfloat gamma;\n> @@ -103,7 +93,6 @@ struct IPAContext {\n>   \tFCQueue<IPAFrameContext> frameContexts;\n>   \tControlInfoMap::Map ctrlMap;\n>   \tbool ccmEnabled = false;\n> -\tbool gpuIspEnabled = false;\n>   };\n>   \n>   } /* namespace ipa::soft */\n> diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp\n> index 732e82510..6bef597c8 100644\n> --- a/src/ipa/simple/soft_simple.cpp\n> +++ b/src/ipa/simple/soft_simple.cpp\n> @@ -26,6 +26,7 @@\n>   #include \"libcamera/internal/software_isp/swisp_stats.h\"\n>   #include \"libcamera/internal/yaml_parser.h\"\n>   \n> +#include \"algorithms/adjust.h\"\n>   #include \"libipa/camera_sensor_helper.h\"\n>   \n>   #include \"module.h\"\n> @@ -55,7 +56,6 @@ public:\n>   \t\t const SharedFD &fdParams,\n>   \t\t const IPACameraSensorInfo &sensorInfo,\n>   \t\t const ControlInfoMap &sensorControls,\n> -\t\t bool gpuIspEnabled,\n>   \t\t ControlInfoMap *ipaControls,\n>   \t\t bool *ccmEnabled) override;\n>   \tint configure(const IPAConfigInfo &configInfo) override;\n> @@ -96,7 +96,6 @@ int IPASoftSimple::init(const IPASettings &settings,\n>   \t\t\tconst SharedFD &fdParams,\n>   \t\t\tconst IPACameraSensorInfo &sensorInfo,\n>   \t\t\tconst ControlInfoMap &sensorControls,\n> -\t\t\tbool gpuIspEnabled,\n>   \t\t\tControlInfoMap *ipaControls,\n>   \t\t\tbool *ccmEnabled)\n>   {\n> @@ -108,7 +107,6 @@ int IPASoftSimple::init(const IPASettings &settings,\n>   \t}\n>   \n>   \tcontext_.sensorInfo = sensorInfo;\n> -\tcontext_.gpuIspEnabled = gpuIspEnabled;\n>   \n>   \t/* Load the tuning data file */\n>   \tFile file(settings.configurationFile);\n> @@ -161,6 +159,11 @@ int IPASoftSimple::init(const IPASettings &settings,\n>   \t\t}\n>   \n>   \t\tparams_ = static_cast<DebayerParams *>(mem);\n> +\t\tparams_->blackLevel = { { 0.0, 0.0, 0.0 } };\n> +\t\tparams_->gamma = 1.0 / algorithms::kDefaultGamma;\n> +\t\tparams_->contrastExp = 1.0;\n> +\t\tparams_->gains = { { 1.0, 1.0, 1.0 } };\n> +\t\t/* combinedMatrix is reset for each frame. */\n>   \t}\n>   \n>   \t{\n> @@ -287,6 +290,8 @@ void IPASoftSimple::computeParams(const uint32_t frame)\n>   \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n>   \tfor (auto const &algo : algorithms())\n>   \t\talgo->prepare(context_, frame, frameContext, params_);\n> +\tparams_->combinedMatrix = context_.activeState.combinedMatrix;\n> +\n>   \tsetIspParams.emit();\n>   }\n>   \n> diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp\n> index 65a1762dd..dccdd86b4 100644\n> --- a/src/libcamera/software_isp/debayer.cpp\n> +++ b/src/libcamera/software_isp/debayer.cpp\n> @@ -1,7 +1,7 @@\n>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>   /*\n>    * Copyright (C) 2023, Linaro Ltd\n> - * Copyright (C) 2023-2025 Red Hat Inc.\n> + * Copyright (C) 2023-2026 Red Hat Inc.\n>    *\n>    * Authors:\n>    * Hans de Goede <hdegoede@redhat.com>\n> @@ -25,99 +25,28 @@ namespace libcamera {\n>    */\n>   \n>   /**\n> - * \\var DebayerParams::kRGBLookupSize\n> - * \\brief Size of a color lookup table\n> + * \\var DebayerParams::gains\n> + * \\brief Colour channel gains\n>    */\n>   \n>   /**\n> - * \\struct DebayerParams::CcmColumn\n> - * \\brief Type of a single column of a color correction matrix (CCM)\n> - *\n> - * When multiplying an input pixel, columns in the CCM correspond to the red,\n> - * green or blue component of input pixel values, while rows correspond to the\n> - * red, green or blue components of the output pixel values. The members of the\n> - * CcmColumn structure are named after the colour components of the output pixel\n> - * values they correspond to.\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::CcmColumn::r\n> - * \\brief Red (first) component of a CCM column\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::CcmColumn::g\n> - * \\brief Green (second) component of a CCM column\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::CcmColumn::b\n> - * \\brief Blue (third) component of a CCM column\n> - */\n> -\n> -/**\n> - * \\typedef DebayerParams::LookupTable\n> - * \\brief Type of the lookup tables for single lookup values\n> - */\n> -\n> -/**\n> - * \\typedef DebayerParams::CcmLookupTable\n> - * \\brief Type of the CCM lookup tables for red, green, blue values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::red\n> - * \\brief Lookup table for red color, mapping input values to output values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::green\n> - * \\brief Lookup table for green color, mapping input values to output values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::blue\n> - * \\brief Lookup table for blue color, mapping input values to output values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::redCcm\n> - * \\brief Lookup table for the CCM red column, mapping input values to output values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::greenCcm\n> - * \\brief Lookup table for the CCM green column, mapping input values to output values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::blueCcm\n> - * \\brief Lookup table for the CCM blue column, mapping input values to output values\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::gammaLut\n> - * \\brief Gamma lookup table used with color correction matrix\n> - */\n> -\n> -/**\n> - * \\var DebayerParams::ccm\n> - * \\brief Per frame colour correction matrix for GPUISP\n> + * \\var DebayerParams::combinedMatrix\n> + * \\brief Colour correction matrix, including other adjustments\n>    */\n>   \n>   /**\n>    * \\var DebayerParams::blackLevel\n> - * \\brief Blacklevel gains for the GPUISP\n> + * \\brief Black level values\n>    */\n>   \n>   /**\n>    * \\var DebayerParams::gamma\n> - * \\brief Gamma value for the GPUISP\n> + * \\brief Gamma value, e.g. 1/2.2\n>    */\n>   \n>   /**\n>    * \\var DebayerParams::contrastExp\n> - * \\brief Contrast value for GPUISP\n> + * \\brief Contrast value to be used as an exponent\n>    */\n>   \n>   /**\n> @@ -131,13 +60,6 @@ LOG_DEFINE_CATEGORY(Debayer)\n>   \n>   Debayer::Debayer(const GlobalConfiguration &configuration) : bench_(configuration)\n>   {\n> -\t/* Initialize color lookup tables */\n> -\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n> -\t\tred_[i] = green_[i] = blue_[i] = i;\n> -\t\tredCcm_[i] = { static_cast<int16_t>(i), 0, 0 };\n> -\t\tgreenCcm_[i] = { 0, static_cast<int16_t>(i), 0 };\n> -\t\tblueCcm_[i] = { 0, 0, static_cast<int16_t>(i) };\n> -\t}\n>   }\n>   \n>   Debayer::~Debayer()\n> @@ -305,56 +227,6 @@ Debayer::~Debayer()\n>    * \\brief Output size object\n>    */\n>   \n> -/**\n> - * \\var Debayer::red_\n> - * \\brief Lookup table for red channel gain and correction values\n> - *\n> - * This table provides precomputed per-pixel or per-intensity\n> - * correction values for the red color channel used during debayering.\n> - */\n> -\n> -/**\n> - * \\var Debayer::green_\n> - * \\brief Lookup table for green channel gain and correction values\n> - *\n> - * This table provides precomputed per-pixel or per-intensity\n> - * correction values for the green color channel used during debayering.\n> - */\n> -\n> -/**\n> - * \\var Debayer::blue_\n> - * \\brief Lookup table for blue channel gain and correction values\n> - *\n> - * This table provides precomputed per-pixel or per-intensity\n> - * correction values for the blue color channel used during debayering.\n> - */\n> -\n> -/**\n> - * \\var Debayer::redCcm_\n> - * \\brief Red channel Color Correction Matrix (CCM) lookup table\n> - *\n> - * Contains coefficients for green channel color correction.\n> - */\n> -\n> -/**\n> - * \\var Debayer::greenCcm_\n> - * \\brief Green channel Color Correction Matrix (CCM) lookup table\n> - *\n> - * Contains coefficients for green channel color correction.\n> - */\n> -\n> -/**\n> - * \\var Debayer::blueCcm_\n> - * \\brief Blue channel Color Correction Matrix (CCM) lookup table\n> - *\n> - * Contains coefficients for blue channel color correction.\n> - */\n> -\n> -/**\n> - * \\var Debayer::gammaLut_\n> - * \\brief Gamma correction lookup table\n> - */\n> -\n>   /**\n>    * \\var Debayer::swapRedBlueGains_\n>    * \\brief Flag indicating whether red and blue channel gains should be swapped\n> @@ -396,34 +268,6 @@ Debayer::~Debayer()\n>    * DebayerEGL::start.\n>    */\n>   \n> -/**\n> - * \\fn void Debayer::setParams(DebayerParams &params)\n> - * \\brief Select the bayer params to use for the next frame debayer\n> - * \\param[in] params The parameters to be used in debayering\n> - */\n> -void Debayer::setParams(DebayerParams &params)\n> -{\n> -\tgreen_ = params.green;\n> -\tgreenCcm_ = params.greenCcm;\n> -\tif (swapRedBlueGains_) {\n> -\t\tred_ = params.blue;\n> -\t\tblue_ = params.red;\n> -\t\tredCcm_ = params.blueCcm;\n> -\t\tblueCcm_ = params.redCcm;\n> -\t\tfor (unsigned int i = 0; i < 256; i++) {\n> -\t\t\tstd::swap(redCcm_[i].r, redCcm_[i].b);\n> -\t\t\tstd::swap(greenCcm_[i].r, greenCcm_[i].b);\n> -\t\t\tstd::swap(blueCcm_[i].r, blueCcm_[i].b);\n> -\t\t}\n> -\t} else {\n> -\t\tred_ = params.red;\n> -\t\tblue_ = params.blue;\n> -\t\tredCcm_ = params.redCcm;\n> -\t\tblueCcm_ = params.blueCcm;\n> -\t}\n> -\tgammaLut_ = params.gammaLut;\n> -}\n> -\n>   /**\n>    * \\fn void Debayer::dmaSyncBegin(DebayerParams &params)\n>    * \\brief Common CPU/GPU Dma Sync Buffer begin\n> diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h\n> index cd2db9930..652cff4cc 100644\n> --- a/src/libcamera/software_isp/debayer.h\n> +++ b/src/libcamera/software_isp/debayer.h\n> @@ -78,13 +78,6 @@ public:\n>   \tSize outputSize_;\n>   \tPixelFormat inputPixelFormat_;\n>   \tPixelFormat outputPixelFormat_;\n> -\tDebayerParams::LookupTable red_;\n> -\tDebayerParams::LookupTable green_;\n> -\tDebayerParams::LookupTable blue_;\n> -\tDebayerParams::CcmLookupTable redCcm_;\n> -\tDebayerParams::CcmLookupTable greenCcm_;\n> -\tDebayerParams::CcmLookupTable blueCcm_;\n> -\tDebayerParams::LookupTable gammaLut_;\n>   \tbool swapRedBlueGains_;\n>   \tBenchmark bench_;\n>   \n> @@ -92,7 +85,6 @@ private:\n>   \tvirtual Size patternSize(PixelFormat inputFormat) = 0;\n>   \n>   protected:\n> -\tvoid setParams(DebayerParams &params);\n>   \tvoid dmaSyncBegin(std::vector<DmaSyncer> &dmaSyncers, FrameBuffer *input, FrameBuffer *output);\n>   \tstatic bool isStandardBayerOrder(BayerFormat::Order order);\n>   };\n> diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp\n> index 00738c56b..af7af0a8d 100644\n> --- a/src/libcamera/software_isp/debayer_cpu.cpp\n> +++ b/src/libcamera/software_isp/debayer_cpu.cpp\n> @@ -1,7 +1,7 @@\n>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>   /*\n>    * Copyright (C) 2023, Linaro Ltd\n> - * Copyright (C) 2023-2025 Red Hat Inc.\n> + * Copyright (C) 2023-2026 Red Hat Inc.\n>    *\n>    * Authors:\n>    * Hans de Goede <hdegoede@redhat.com>\n> @@ -68,21 +68,21 @@ DebayerCpu::~DebayerCpu() = default;\n>   #define GAMMA(value) \\\n>   \t*dst++ = gammaLut_[std::clamp(value, 0, static_cast<int>(gammaLut_.size()) - 1)]\n>   \n> -#define STORE_PIXEL(b_, g_, r_)                                        \\\n> -\tif constexpr (ccmEnabled) {                                    \\\n> -\t\tconst DebayerParams::CcmColumn &blue = blueCcm_[b_];   \\\n> -\t\tconst DebayerParams::CcmColumn &green = greenCcm_[g_]; \\\n> -\t\tconst DebayerParams::CcmColumn &red = redCcm_[r_];     \\\n> -\t\tGAMMA(blue.b + green.b + red.b);                       \\\n> -\t\tGAMMA(blue.g + green.g + red.g);                       \\\n> -\t\tGAMMA(blue.r + green.r + red.r);                       \\\n> -\t} else {                                                       \\\n> -\t\t*dst++ = blue_[b_];                                    \\\n> -\t\t*dst++ = green_[g_];                                   \\\n> -\t\t*dst++ = red_[r_];                                     \\\n> -\t}                                                              \\\n> -\tif constexpr (addAlphaByte)                                    \\\n> -\t\t*dst++ = 255;                                          \\\n> +#define STORE_PIXEL(b_, g_, r_)                         \\\n> +\tif constexpr (ccmEnabled) {                     \\\n> +\t\tconst CcmColumn &blue = blueCcm_[b_];   \\\n> +\t\tconst CcmColumn &green = greenCcm_[g_]; \\\n> +\t\tconst CcmColumn &red = redCcm_[r_];     \\\n> +\t\tGAMMA(blue.b + green.b + red.b);        \\\n> +\t\tGAMMA(blue.g + green.g + red.g);        \\\n> +\t\tGAMMA(blue.r + green.r + red.r);        \\\n> +\t} else {                                        \\\n> +\t\t*dst++ = blue_[b_];                     \\\n> +\t\t*dst++ = green_[g_];                    \\\n> +\t\t*dst++ = red_[r_];                      \\\n> +\t}                                               \\\n> +\tif constexpr (addAlphaByte)                     \\\n> +\t\t*dst++ = 255;                           \\\n>   \tx++;\n>   \n>   /*\n> @@ -525,6 +525,16 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg,\n>   \tif (ret != 0)\n>   \t\treturn -EINVAL;\n>   \n> +\tccmEnabled_ = ccmEnabled;\n> +\n> +\t/*\n> +\t * Lookup tables must be initialized because the initial value is used for\n> +\t * the first two frames, i.e. until stats processing starts providing its\n> +\t * own parameters. Let's enforce recomputing lookup tables by setting the\n> +\t * stored last used gamma to an out-of-range value.\n> +\t */\n> +\tparams_.gamma = 1.0;\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> @@ -740,6 +750,98 @@ void DebayerCpu::process4(uint32_t frame, const uint8_t *src, uint8_t *dst)\n>   \t}\n>   }\n>   \n> +void DebayerCpu::updateGammaTable(DebayerParams &params)\n> +{\n> +\tconst RGB<float> blackLevel = params.blackLevel;\n> +\t/* Take let's say the green channel black level */\n> +\tconst unsigned int blackIndex = blackLevel[1] * gammaTable_.size();\n> +\tconst float gamma = params.gamma;\n> +\tconst float contrastExp = params.contrastExp;\n> +\n> +\tconst float divisor = gammaTable_.size() - blackIndex - 1.0;\n> +\tfor (unsigned int i = blackIndex; i < gammaTable_.size(); i++) {\n> +\t\tfloat normalized = (i - blackIndex) / divisor;\n> +\t\t/* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */\n> +\t\t/* Apply simple S-curve */\n> +\t\tif (normalized < 0.5)\n> +\t\t\tnormalized = 0.5 * std::pow(normalized / 0.5, contrastExp);\n> +\t\telse\n> +\t\t\tnormalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp);\n> +\t\tgammaTable_[i] = UINT8_MAX *\n> +\t\t\t\t std::pow(normalized, gamma);\n> +\t}\n> +\t/*\n> +\t * Due to CCM operations, the table lookup may reach indices below the black\n> +\t * level. Let's set the table values below black level to the minimum\n> +\t * non-black value to prevent problems when the minimum value is\n> +\t * significantly non-zero (for example, when the image should be all grey).\n> +\t */\n> +\tstd::fill(gammaTable_.begin(), gammaTable_.begin() + blackIndex,\n> +\t\t  gammaTable_[blackIndex]);\n> +}\n> +\n> +void DebayerCpu::updateLookupTables(DebayerParams &params)\n> +{\n> +\tconst bool gammaUpdateNeeded =\n> +\t\tparams.gamma != params_.gamma ||\n> +\t\tparams.blackLevel != params_.blackLevel ||\n> +\t\tparams.contrastExp != params_.contrastExp;\n> +\tif (gammaUpdateNeeded)\n> +\t\tupdateGammaTable(params);\n> +\n> +\tauto matrixChanged = [](const Matrix<float, 3, 3> &m1, const Matrix<float, 3, 3> &m2) -> bool {\n> +\t\treturn !std::equal(m1.data().begin(), m1.data().end(), m2.data().begin());\n> +\t};\n> +\tconst unsigned int gammaTableSize = gammaTable_.size();\n> +\tconst double div = static_cast<double>(kRGBLookupSize) / gammaTableSize;\n> +\tif (ccmEnabled_) {\n> +\t\tif (gammaUpdateNeeded ||\n> +\t\t    matrixChanged(params.combinedMatrix, params_.combinedMatrix)) {\n> +\t\t\tauto &red = swapRedBlueGains_ ? blueCcm_ : redCcm_;\n> +\t\t\tauto &green = greenCcm_;\n> +\t\t\tauto &blue = swapRedBlueGains_ ? redCcm_ : blueCcm_;\n> +\t\t\tconst unsigned int redIndex = swapRedBlueGains_ ? 2 : 0;\n> +\t\t\tconst unsigned int greenIndex = 1;\n> +\t\t\tconst unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2;\n> +\t\t\tfor (unsigned int i = 0; i < kRGBLookupSize; i++) {\n> +\t\t\t\tred[i].r = std::round(i * params.combinedMatrix[redIndex][0]);\n> +\t\t\t\tred[i].g = std::round(i * params.combinedMatrix[greenIndex][0]);\n> +\t\t\t\tred[i].b = std::round(i * params.combinedMatrix[blueIndex][0]);\n> +\t\t\t\tgreen[i].r = std::round(i * params.combinedMatrix[redIndex][1]);\n> +\t\t\t\tgreen[i].g = std::round(i * params.combinedMatrix[greenIndex][1]);\n> +\t\t\t\tgreen[i].b = std::round(i * params.combinedMatrix[blueIndex][1]);\n> +\t\t\t\tblue[i].r = std::round(i * params.combinedMatrix[redIndex][2]);\n> +\t\t\t\tblue[i].g = std::round(i * params.combinedMatrix[greenIndex][2]);\n> +\t\t\t\tblue[i].b = std::round(i * params.combinedMatrix[blueIndex][2]);\n> +\t\t\t\tgammaLut_[i] = gammaTable_[i / div];\n> +\t\t\t}\n> +\t\t}\n> +\t} else {\n> +\t\tif (gammaUpdateNeeded || params.gains != params_.gains) {\n> +\t\t\tauto &gains = params.gains;\n> +\t\t\tauto &red = swapRedBlueGains_ ? blue_ : red_;\n> +\t\t\tauto &green = green_;\n> +\t\t\tauto &blue = swapRedBlueGains_ ? red_ : blue_;\n> +\t\t\tfor (unsigned int i = 0; i < kRGBLookupSize; i++) {\n> +\t\t\t\t/* Apply gamma after gain! */\n> +\t\t\t\tconst RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);\n> +\t\t\t\tred[i] = gammaTable_[static_cast<unsigned int>(lutGains.r())];\n> +\t\t\t\tgreen[i] = gammaTable_[static_cast<unsigned int>(lutGains.g())];\n> +\t\t\t\tblue[i] = gammaTable_[static_cast<unsigned int>(lutGains.b())];\n> +\t\t\t}\n> +\t\t}\n> +\t}\n> +\n> +\tLOG(Debayer, Debug)\n> +\t\t<< \"Debayer parameters: blackLevel=\" << params.blackLevel\n> +\t\t<< \"; gamma=\" << params.gamma\n> +\t\t<< \"; contrastExp=\" << params.contrastExp\n> +\t\t<< \"; gains=\" << params.gains\n> +\t\t<< \"; matrix=\" << params.combinedMatrix;\n> +\n> +\tparams_ = params;\n> +}\n> +\n>   void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params)\n>   {\n>   \tbench_.startFrame();\n> @@ -748,7 +850,7 @@ void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output\n>   \n>   \tdmaSyncBegin(dmaSyncers, input, output);\n>   \n> -\tsetParams(params);\n> +\tupdateLookupTables(params);\n>   \n>   \t/* Copy metadata from the input buffer */\n>   \tFrameMetadata &metadata = output->_d()->metadata();\n> diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h\n> index 67df2b93a..b5cbb5bd2 100644\n> --- a/src/libcamera/software_isp/debayer_cpu.h\n> +++ b/src/libcamera/software_isp/debayer_cpu.h\n> @@ -1,7 +1,7 @@\n>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>   /*\n>    * Copyright (C) 2023, Linaro Ltd\n> - * Copyright (C) 2023-2025 Red Hat Inc.\n> + * Copyright (C) 2023-2026 Red Hat Inc.\n>    *\n>    * Authors:\n>    * Hans de Goede <hdegoede@redhat.com>\n> @@ -18,6 +18,8 @@\n>   #include <libcamera/base/object.h>\n>   \n>   #include \"libcamera/internal/bayer_format.h\"\n> +#include \"libcamera/internal/global_configuration.h\"\n> +#include \"libcamera/internal/software_isp/debayer_params.h\"\n>   #include \"libcamera/internal/software_isp/swstats_cpu.h\"\n>   \n>   #include \"debayer.h\"\n> @@ -108,10 +110,32 @@ private:\n>   \tvoid memcpyNextLine(const uint8_t *linePointers[]);\n>   \tvoid process2(uint32_t frame, const uint8_t *src, uint8_t *dst);\n>   \tvoid process4(uint32_t frame, const uint8_t *src, uint8_t *dst);\n> +\tvoid updateGammaTable(DebayerParams &params);\n> +\tvoid updateLookupTables(DebayerParams &params);\n>   \n>   \t/* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */\n>   \tstatic constexpr unsigned int kMaxLineBuffers = 5;\n>   \n> +\tstatic constexpr unsigned int kRGBLookupSize = 256;\n> +\tstatic constexpr unsigned int kGammaLookupSize = 1024;\n> +\tstruct CcmColumn {\n> +\t\tint16_t r;\n> +\t\tint16_t g;\n> +\t\tint16_t b;\n> +\t};\n> +\tusing LookupTable = std::array<uint8_t, kRGBLookupSize>;\n> +\tusing CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>;\n> +\tLookupTable red_;\n> +\tLookupTable green_;\n> +\tLookupTable blue_;\n> +\tCcmLookupTable redCcm_;\n> +\tCcmLookupTable greenCcm_;\n> +\tCcmLookupTable blueCcm_;\n> +\tstd::array<double, kGammaLookupSize> gammaTable_;\n> +\tLookupTable gammaLut_;\n> +\tbool ccmEnabled_;\n> +\tDebayerParams params_;\n> +\n>   \tdebayerFn debayer0_;\n>   \tdebayerFn debayer1_;\n>   \tdebayerFn debayer2_;\n> diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp\n> index 9693d7252..af04d60ca 100644\n> --- a/src/libcamera/software_isp/debayer_egl.cpp\n> +++ b/src/libcamera/software_isp/debayer_egl.cpp\n> @@ -475,18 +475,18 @@ void DebayerEGL::setShaderVariableValues(DebayerParams &params)\n>   \t\t\t    << \" textureUniformProjMatrix_ \" << textureUniformProjMatrix_;\n>   \n>   \tGLfloat ccm[9] = {\n> -\t\tparams.ccm[0][0],\n> -\t\tparams.ccm[0][1],\n> -\t\tparams.ccm[0][2],\n> -\t\tparams.ccm[1][0],\n> -\t\tparams.ccm[1][1],\n> -\t\tparams.ccm[1][2],\n> -\t\tparams.ccm[2][0],\n> -\t\tparams.ccm[2][1],\n> -\t\tparams.ccm[2][2],\n> +\t\tparams.combinedMatrix[0][0],\n> +\t\tparams.combinedMatrix[0][1],\n> +\t\tparams.combinedMatrix[0][2],\n> +\t\tparams.combinedMatrix[1][0],\n> +\t\tparams.combinedMatrix[1][1],\n> +\t\tparams.combinedMatrix[1][2],\n> +\t\tparams.combinedMatrix[2][0],\n> +\t\tparams.combinedMatrix[2][1],\n> +\t\tparams.combinedMatrix[2][2],\n>   \t};\n>   \tglUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm);\n> -\tLOG(Debayer, Debug) << \" ccmUniformDataIn_ \" << ccmUniformDataIn_ << \" data \" << params.ccm;\n> +\tLOG(Debayer, Debug) << \" ccmUniformDataIn_ \" << ccmUniformDataIn_ << \" data \" << params.combinedMatrix;\n>   \n>   \t/*\n>   \t * 0 = Red, 1 = Green, 2 = Blue\n> @@ -544,8 +544,6 @@ void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output\n>   \n>   \tdmaSyncBegin(dmaSyncers, input, nullptr);\n>   \n> -\tsetParams(params);\n> -\n>   \t/* Copy metadata from the input buffer */\n>   \tFrameMetadata &metadata = output->_d()->metadata();\n>   \tmetadata.status = input->metadata().status;\n> diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp\n> index 7ad3511db..a83986b78 100644\n> --- a/src/libcamera/software_isp/software_isp.cpp\n> +++ b/src/libcamera/software_isp/software_isp.cpp\n> @@ -84,23 +84,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n>   \t\t   DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |\n>   \t\t   DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)\n>   {\n> -\t/*\n> -\t * debayerParams_ must be initialized because the initial value is used for\n> -\t * the first two frames, i.e. until stats processing starts providing its\n> -\t * own parameters.\n> -\t *\n> -\t * \\todo This should be handled in the same place as the related\n> -\t * operations, in the IPA module.\n> -\t */\n> -\tstd::array<uint8_t, 256> gammaTable;\n> -\tfor (unsigned int i = 0; i < 256; i++)\n> -\t\tgammaTable[i] = UINT8_MAX * std::pow(i / 256.0, 0.5);\n> -\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n> -\t\tdebayerParams_.red[i] = gammaTable[i];\n> -\t\tdebayerParams_.green[i] = gammaTable[i];\n> -\t\tdebayerParams_.blue[i] = gammaTable[i];\n> -\t}\n> -\n>   \tif (!dmaHeap_.isValid()) {\n>   \t\tLOG(SoftwareIsp, Error) << \"Failed to create DmaBufAllocator object\";\n>   \t\treturn;\n> @@ -121,8 +104,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n>   \t}\n>   \tstats->statsReady.connect(this, &SoftwareIsp::statsReady);\n>   \n> -\tbool gpuIspEnabled;\n> -\n>   #if HAVE_DEBAYER_EGL\n>   \tstd::optional<std::string> softISPMode = configuration.envOption(\"LIBCAMERA_SOFTISP_MODE\", { \"software_isp\", \"mode\" });\n>   \tif (softISPMode) {\n> @@ -133,15 +114,12 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n>   \t\t}\n>   \t}\n>   \n> -\tif (!softISPMode || softISPMode == \"gpu\") {\n> +\tif (!softISPMode || softISPMode == \"gpu\")\n>   \t\tdebayer_ = std::make_unique<DebayerEGL>(std::move(stats), configuration);\n> -\t\tgpuIspEnabled = true;\n> -\t}\n> +\n>   #endif\n> -\tif (!debayer_) {\n> +\tif (!debayer_)\n>   \t\tdebayer_ = std::make_unique<DebayerCpu>(std::move(stats), configuration);\n> -\t\tgpuIspEnabled = false;\n> -\t}\n>   \n>   \tdebayer_->inputBufferReady.connect(this, &SoftwareIsp::inputReady);\n>   \tdebayer_->outputBufferReady.connect(this, &SoftwareIsp::outputReady);\n> @@ -173,7 +151,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n>   \t\t\t sharedParams_.fd(),\n>   \t\t\t sensorInfo,\n>   \t\t\t sensor->controls(),\n> -\t\t\t gpuIspEnabled,\n>   \t\t\t ipaControls,\n>   \t\t\t &ccmEnabled_);\n>   \tif (ret) {","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 27CE4C3200\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 26 Jan 2026 15:56:45 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 58B4461FC4;\n\tMon, 26 Jan 2026 16:56:44 +0100 (CET)","from sender4-op-o12.zoho.com (sender4-op-o12.zoho.com\n\t[136.143.188.12])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 437D461A35\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 26 Jan 2026 16:56:42 +0100 (CET)","by mx.zohomail.com with SMTPS id 1769442998095395.4134746547136;\n\tMon, 26 Jan 2026 07:56:38 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=collabora.com\n\theader.i=robert.mader@collabora.com header.b=\"cdec5aOn\"; \n\tdkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1769443000; cv=none; \n\td=zohomail.com; s=zohoarc; \n\tb=g9sJL2um6rfsAxV/Pu8XFqvdSDRym6VWFGLyvIiHbiUVDoKpktTMsRysy79hZHIVTxcpJIMeNoU9B3SpMMo6JU8vZZTOAOoWjKM/PIWJQjQKC+BFcXt7HJaCV3e4GuBA3JswDUT501PcaddtwjLRYd/WJIL3cv4bAxP5D8i3XfI=","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; \n\ts=zohoarc; t=1769443000;\n\th=Content-Type:Content-Transfer-Encoding:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To:Cc;\n\tbh=lvMJ/TjhrOGvfLCmOZZzijecfS4KO3EliZydQYzPmlo=; \n\tb=EOAeDfiRr5n2o4L7Z0V/ISWas2OV1FWSKm0l4Ch33acGNh1aK3z1HxGf2P9B8vBY5KyXPypIVVTjzwEDi7ikh/z6itBcZ1ERYYudCeQpIaG8wCJJhXroJSIZg09G7Gvt20qv83+0KG3T8CqAYYqU5DObyVwrocd057miitAYlRE=","ARC-Authentication-Results":"i=1; mx.zohomail.com;\n\tdkim=pass  header.i=collabora.com;\n\tspf=pass  smtp.mailfrom=robert.mader@collabora.com;\n\tdmarc=pass header.from=<robert.mader@collabora.com>","DKIM-Signature":"v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1769443000;\n\ts=zohomail; d=collabora.com; i=robert.mader@collabora.com;\n\th=Message-ID:Date:Date:MIME-Version:Subject:Subject:To:To:References:From:From:In-Reply-To:Content-Type:Content-Transfer-Encoding:Message-Id:Reply-To:Cc;\n\tbh=lvMJ/TjhrOGvfLCmOZZzijecfS4KO3EliZydQYzPmlo=;\n\tb=cdec5aOnRwdzB/DnvYRQ8cJB3Bwly3gRRgZPNzocW9iNz9/mlKgXMepUHmFneaBl\n\tpyt56wz4ERdvvdZdLrTwD+PHVCbORvRQsBFsU7A+Dw8UT0Ff2ZyJHYAOPIgzvHD1WZO\n\tCw21qXcGy5n6hXrRw/2R/jXRvIw9KHyLzLkfiJNQ=","Message-ID":"<7e6cc110-c142-45ea-9fbb-5ff80e76cd39@collabora.com>","Date":"Mon, 26 Jan 2026 16:56:35 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v4 14/15] libcamera: ipa: simple: Remove Lut algorithm","To":"libcamera-devel@lists.libcamera.org","References":"<20260122161935.208562-1-mzamazal@redhat.com>\n\t<20260122161935.208562-15-mzamazal@redhat.com>","Content-Language":"en-US, de-DE","From":"Robert Mader <robert.mader@collabora.com>","In-Reply-To":"<20260122161935.208562-15-mzamazal@redhat.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]