| Message ID | 20260128114402.31570-15-mzamazal@redhat.com |
|---|---|
| State | Accepted |
| Commit | 3ffaa4c0e29a7ece2dcce4e94d3447932f7a3a65 |
| Headers | show |
| Series |
|
| Related | show |
Quoting Milan Zamazal (2026-01-28 11:44:01) > The Lut algorithm is not really an algorithm. Moreover, algorithms may > be enabled or disabled but with Lut disabled, nothing will work. > > Let's move the construction of lookup tables to CPU debayering, where it > is used. The implied and related changes are: > > - DebayerParams is changed to contain the real params rather than lookup > tables. > - contrastExp parameter introduced by GPU ISP is used for CPU ISP too. > - The params must be initialised so that debayering gets meaningful > parameter values even when some algorithms are disabled. > - combinedMatrix must be put to params everywhere where it is modified. > - Matrix changes needn't be tracked in the algorithms any more. > - CPU debayering must watch for changes of the corresponding parameters > to update the lookup tables when and only when needed. > - Swapping red and blue is integrated into lookup table constructions. > - gpuIspEnabled flags are removed as they are not needed any more. There's a lot going on in there... but I think this is all helping progress the development, and CI is green so: Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > Reviewed-by: Robert Mader <robert.mader@collabora.com> > Signed-off-by: Milan Zamazal <mzamazal@redhat.com> > --- > .../internal/software_isp/debayer_params.h | 43 +---- > include/libcamera/ipa/soft.mojom | 3 +- > src/ipa/simple/algorithms/adjust.cpp | 17 +- > src/ipa/simple/algorithms/adjust.h | 4 - > src/ipa/simple/algorithms/awb.cpp | 6 +- > src/ipa/simple/algorithms/ccm.cpp | 1 - > src/ipa/simple/algorithms/lut.cpp | 140 -------------- > src/ipa/simple/algorithms/lut.h | 35 ---- > src/ipa/simple/algorithms/meson.build | 1 - > src/ipa/simple/data/uncalibrated.yaml | 1 - > src/ipa/simple/ipa_context.h | 11 -- > src/ipa/simple/soft_simple.cpp | 11 +- > src/libcamera/software_isp/debayer.cpp | 172 +----------------- > src/libcamera/software_isp/debayer.h | 8 - > src/libcamera/software_isp/debayer_cpu.cpp | 136 ++++++++++++-- > src/libcamera/software_isp/debayer_cpu.h | 26 ++- > src/libcamera/software_isp/debayer_egl.cpp | 22 +-- > src/libcamera/software_isp/software_isp.cpp | 29 +-- > 18 files changed, 188 insertions(+), 478 deletions(-) > delete mode 100644 src/ipa/simple/algorithms/lut.cpp > delete mode 100644 src/ipa/simple/algorithms/lut.h > > diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h > index 2d69bd295..1c0412d75 100644 > --- a/include/libcamera/internal/software_isp/debayer_params.h > +++ b/include/libcamera/internal/software_isp/debayer_params.h > @@ -1,6 +1,6 @@ > /* SPDX-License-Identifier: LGPL-2.1-or-later */ > /* > - * Copyright (C) 2023-2025 Red Hat Inc. > + * Copyright (C) 2023-2026 Red Hat Inc. > * > * Authors: > * Hans de Goede <hdegoede@redhat.com> > @@ -10,7 +10,6 @@ > > #pragma once > > -#include <array> > #include <stdint.h> > > #include "libcamera/internal/matrix.h" > @@ -19,47 +18,11 @@ > namespace libcamera { > > struct DebayerParams { > - static constexpr unsigned int kRGBLookupSize = 256; > - > - struct CcmColumn { > - int16_t r; > - int16_t g; > - int16_t b; > - }; > - > - using LookupTable = std::array<uint8_t, kRGBLookupSize>; > - using CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>; > - > - /* > - * Color lookup tables when CCM is not used. > - * > - * Each color of a debayered pixel is amended by the corresponding > - * value in the given table. > - */ > - LookupTable red; > - LookupTable green; > - LookupTable blue; > - > - /* > - * Color and gamma lookup tables when CCM is used. > - * > - * Each of the CcmLookupTable's corresponds to a CCM column; together they > - * make a complete 3x3 CCM lookup table. The CCM is applied on debayered > - * pixels and then the gamma lookup table is used to set the resulting > - * values of all the three colors. > - */ > - CcmLookupTable redCcm; > - CcmLookupTable greenCcm; > - CcmLookupTable blueCcm; > - LookupTable gammaLut; > - > - /* > - * Per frame corrections as calculated by the IPA > - */ > - Matrix<float, 3, 3> ccm; > + Matrix<float, 3, 3> combinedMatrix; > RGB<float> blackLevel; > float gamma; > float contrastExp; > + RGB<float> gains; > }; > > } /* namespace libcamera */ > diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom > index aff8fcbd3..77328c5fd 100644 > --- a/include/libcamera/ipa/soft.mojom > +++ b/include/libcamera/ipa/soft.mojom > @@ -17,8 +17,7 @@ interface IPASoftInterface { > libcamera.SharedFD fdStats, > libcamera.SharedFD fdParams, > libcamera.IPACameraSensorInfo sensorInfo, > - libcamera.ControlInfoMap sensorControls, > - bool gpuIspEnabled) > + libcamera.ControlInfoMap sensorControls) > => (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled); > start() => (int32 ret); > stop(); > diff --git a/src/ipa/simple/algorithms/adjust.cpp b/src/ipa/simple/algorithms/adjust.cpp > index acdd3f741..068e98404 100644 > --- a/src/ipa/simple/algorithms/adjust.cpp > +++ b/src/ipa/simple/algorithms/adjust.cpp > @@ -95,23 +95,20 @@ void Adjust::applySaturation(Matrix<float, 3, 3> &matrix, float saturation) > void Adjust::prepare(IPAContext &context, > [[maybe_unused]] const uint32_t frame, > IPAFrameContext &frameContext, > - [[maybe_unused]] DebayerParams *params) > + DebayerParams *params) > { > frameContext.gamma = context.activeState.knobs.gamma; > frameContext.contrast = context.activeState.knobs.contrast; > > - if (!context.ccmEnabled) > - return; > - > auto &saturation = context.activeState.knobs.saturation; > - frameContext.saturation = saturation; > - if (saturation) > + if (context.ccmEnabled && saturation) { > applySaturation(context.activeState.combinedMatrix, saturation.value()); > - > - if (saturation != lastSaturation_) { > - context.activeState.matrixChanged = true; > - lastSaturation_ = saturation; > + frameContext.saturation = saturation; > } > + > + params->gamma = 1.0 / context.activeState.knobs.gamma; > + const float contrast = context.activeState.knobs.contrast.value_or(kDefaultContrast); > + params->contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001)); > } > > void Adjust::process([[maybe_unused]] IPAContext &context, > diff --git a/src/ipa/simple/algorithms/adjust.h b/src/ipa/simple/algorithms/adjust.h > index 7644138ff..fb133b140 100644 > --- a/src/ipa/simple/algorithms/adjust.h > +++ b/src/ipa/simple/algorithms/adjust.h > @@ -7,8 +7,6 @@ > > #pragma once > > -#include <optional> > - > #include "libcamera/internal/matrix.h" > > #include <libipa/interpolator.h> > @@ -45,8 +43,6 @@ public: > > private: > void applySaturation(Matrix<float, 3, 3> &ccm, float saturation); > - > - std::optional<float> lastSaturation_; > }; > > } /* namespace ipa::soft::algorithms */ > diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp > index 31726658a..6fdaacaba 100644 > --- a/src/ipa/simple/algorithms/awb.cpp > +++ b/src/ipa/simple/algorithms/awb.cpp > @@ -37,7 +37,7 @@ int Awb::configure(IPAContext &context, > void Awb::prepare(IPAContext &context, > [[maybe_unused]] const uint32_t frame, > IPAFrameContext &frameContext, > - [[maybe_unused]] DebayerParams *params) > + DebayerParams *params) > { > auto &gains = context.activeState.awb.gains; > Matrix<float, 3, 3> gainMatrix = { { gains.r(), 0, 0, > @@ -45,9 +45,11 @@ void Awb::prepare(IPAContext &context, > 0, 0, gains.b() } }; > context.activeState.combinedMatrix = > context.activeState.combinedMatrix * gainMatrix; > - /* Just report, the gains are applied in LUT algorithm. */ > + > frameContext.gains.red = gains.r(); > frameContext.gains.blue = gains.b(); > + > + params->gains = gains; > } > > void Awb::process(IPAContext &context, > diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp > index 5576a301f..911a5af2c 100644 > --- a/src/ipa/simple/algorithms/ccm.cpp > +++ b/src/ipa/simple/algorithms/ccm.cpp > @@ -51,7 +51,6 @@ void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, > utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) { > currentCcm_ = ccm_.getInterpolated(ct); > lastCt_ = ct; > - context.activeState.matrixChanged = true; > } > > context.activeState.combinedMatrix = > diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp > deleted file mode 100644 > index fd442259a..000000000 > --- a/src/ipa/simple/algorithms/lut.cpp > +++ /dev/null > @@ -1,140 +0,0 @@ > -/* SPDX-License-Identifier: LGPL-2.1-or-later */ > -/* > - * Copyright (C) 2024-2026, Red Hat Inc. > - * > - * Color lookup tables construction > - */ > - > -#include "lut.h" > - > -#include <algorithm> > -#include <cmath> > -#include <optional> > -#include <stdint.h> > - > -#include <libcamera/base/log.h> > - > -#include <libcamera/control_ids.h> > - > -#include "simple/ipa_context.h" > - > -#include "adjust.h" > - > -namespace libcamera { > - > -LOG_DEFINE_CATEGORY(IPASoftLut) > - > -namespace ipa::soft::algorithms { > - > -int Lut::configure(IPAContext &context, > - [[maybe_unused]] const IPAConfigInfo &configInfo) > -{ > - updateGammaTable(context); > - > - return 0; > -} > - > -void Lut::updateGammaTable(IPAContext &context) > -{ > - const auto blackLevel = context.activeState.blc.level; > - const auto gamma = 1.0 / context.activeState.knobs.gamma; > - const auto contrast = context.activeState.knobs.contrast.value_or(1.0); > - /* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */ > - float contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001)); > - > - if (!context.gpuIspEnabled) { > - auto &gammaTable = context.activeState.gamma.gammaTable; > - const unsigned int blackIndex = blackLevel * gammaTable.size() / 256; > - const float divisor = gammaTable.size() - blackIndex - 1.0; > - for (unsigned int i = blackIndex; i < gammaTable.size(); i++) { > - double normalized = (i - blackIndex) / divisor; > - /* Apply simple S-curve */ > - if (normalized < 0.5) > - normalized = 0.5 * std::pow(normalized / 0.5, contrastExp); > - else > - normalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp); > - gammaTable[i] = UINT8_MAX * std::pow(normalized, gamma); > - } > - /* > - * Due to CCM operations, the table lookup may reach indices below the black > - * level. Let's set the table values below black level to the minimum > - * non-black value to prevent problems when the minimum value is > - * significantly non-zero (for example, when the image should be all grey). > - */ > - std::fill(gammaTable.begin(), gammaTable.begin() + blackIndex, > - gammaTable[blackIndex]); > - } > - > - context.activeState.gamma.gamma = gamma; > - context.activeState.gamma.blackLevel = blackLevel; > - context.activeState.gamma.contrastExp = contrastExp; > -} > - > -int16_t Lut::matrixValue(unsigned int i, float ccm) const > -{ > - return std::round(i * ccm); > -} > - > -void Lut::prepare(IPAContext &context, > - [[maybe_unused]] const uint32_t frame, > - [[maybe_unused]] IPAFrameContext &frameContext, > - DebayerParams *params) > -{ > - /* > - * Update the gamma table if needed. This means if black level changes > - * and since the black level gets updated only if a lower value is > - * observed, it's not permanently prone to minor fluctuations or > - * rounding errors. > - */ > - const bool gammaUpdateNeeded = > - context.activeState.gamma.blackLevel != context.activeState.blc.level || > - context.activeState.gamma.contrast != context.activeState.knobs.contrast; > - if (gammaUpdateNeeded) > - updateGammaTable(context); > - > - auto &gains = context.activeState.awb.gains; > - auto &gammaTable = context.activeState.gamma.gammaTable; > - const unsigned int gammaTableSize = gammaTable.size(); > - const double div = static_cast<double>(DebayerParams::kRGBLookupSize) / > - gammaTableSize; > - > - if (!context.ccmEnabled) { > - for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) { > - /* Apply gamma after gain! */ > - const RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1); > - params->red[i] = gammaTable[static_cast<unsigned int>(lutGains.r())]; > - params->green[i] = gammaTable[static_cast<unsigned int>(lutGains.g())]; > - params->blue[i] = gammaTable[static_cast<unsigned int>(lutGains.b())]; > - } > - } else if (context.activeState.matrixChanged || gammaUpdateNeeded) { > - auto &matrix = context.activeState.combinedMatrix; > - auto &red = params->redCcm; > - auto &green = params->greenCcm; > - auto &blue = params->blueCcm; > - params->ccm = matrix; > - if (!context.gpuIspEnabled) { > - for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) { > - red[i].r = matrixValue(i, matrix[0][0]); > - red[i].g = matrixValue(i, matrix[1][0]); > - red[i].b = matrixValue(i, matrix[2][0]); > - green[i].r = matrixValue(i, matrix[0][1]); > - green[i].g = matrixValue(i, matrix[1][1]); > - green[i].b = matrixValue(i, matrix[2][1]); > - blue[i].r = matrixValue(i, matrix[0][2]); > - blue[i].g = matrixValue(i, matrix[1][2]); > - blue[i].b = matrixValue(i, matrix[2][2]); > - params->gammaLut[i] = gammaTable[i / div]; > - } > - } > - context.activeState.matrixChanged = false; > - } > - > - params->gamma = context.activeState.gamma.gamma; > - params->contrastExp = context.activeState.gamma.contrastExp; > -} > - > -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" > - > -namespace libcamera { > - > -namespace ipa::soft::algorithms { > - > -class Lut : public Algorithm > -{ > -public: > - Lut() = default; > - ~Lut() = default; > - > - int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; > - void prepare(IPAContext &context, > - const uint32_t frame, > - IPAFrameContext &frameContext, > - DebayerParams *params) override; > - > -private: > - void updateGammaTable(IPAContext &context); > - int16_t matrixValue(unsigned int i, float ccm) const; > -}; > - > -} /* namespace ipa::soft::algorithms */ > - > -} /* namespace libcamera */ > diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build > index ebe9f20dd..73c637220 100644 > --- a/src/ipa/simple/algorithms/meson.build > +++ b/src/ipa/simple/algorithms/meson.build > @@ -6,5 +6,4 @@ soft_simple_ipa_algorithms = files([ > 'agc.cpp', > 'blc.cpp', > 'ccm.cpp', > - 'lut.cpp', > ]) > diff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml > index e389e0588..c6feda36d 100644 > --- a/src/ipa/simple/data/uncalibrated.yaml > +++ b/src/ipa/simple/data/uncalibrated.yaml > @@ -15,6 +15,5 @@ algorithms: > 0, 1, 0, > 0, 0, 1] > - Adjust: > - - Lut: > - Agc: > ... > diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h > index 293e35b71..34f7403a4 100644 > --- a/src/ipa/simple/ipa_context.h > +++ b/src/ipa/simple/ipa_context.h > @@ -53,17 +53,7 @@ struct IPAActiveState { > unsigned int temperatureK; > } awb; > > - static constexpr unsigned int kGammaLookupSize = 1024; > - struct { > - std::array<double, kGammaLookupSize> gammaTable; > - uint8_t blackLevel; > - float gamma; > - float contrast; > - float contrastExp; > - } gamma; > - > Matrix<float, 3, 3> combinedMatrix; > - bool matrixChanged = false; > > struct { > float gamma; > @@ -103,7 +93,6 @@ struct IPAContext { > FCQueue<IPAFrameContext> frameContexts; > ControlInfoMap::Map ctrlMap; > bool ccmEnabled = false; > - bool gpuIspEnabled = false; > }; > > } /* namespace ipa::soft */ > diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp > index 732e82510..6bef597c8 100644 > --- a/src/ipa/simple/soft_simple.cpp > +++ b/src/ipa/simple/soft_simple.cpp > @@ -26,6 +26,7 @@ > #include "libcamera/internal/software_isp/swisp_stats.h" > #include "libcamera/internal/yaml_parser.h" > > +#include "algorithms/adjust.h" > #include "libipa/camera_sensor_helper.h" > > #include "module.h" > @@ -55,7 +56,6 @@ public: > const SharedFD &fdParams, > const IPACameraSensorInfo &sensorInfo, > const ControlInfoMap &sensorControls, > - bool gpuIspEnabled, > ControlInfoMap *ipaControls, > bool *ccmEnabled) override; > int configure(const IPAConfigInfo &configInfo) override; > @@ -96,7 +96,6 @@ int IPASoftSimple::init(const IPASettings &settings, > const SharedFD &fdParams, > const IPACameraSensorInfo &sensorInfo, > const ControlInfoMap &sensorControls, > - bool gpuIspEnabled, > ControlInfoMap *ipaControls, > bool *ccmEnabled) > { > @@ -108,7 +107,6 @@ int IPASoftSimple::init(const IPASettings &settings, > } > > context_.sensorInfo = sensorInfo; > - context_.gpuIspEnabled = gpuIspEnabled; > > /* Load the tuning data file */ > File file(settings.configurationFile); > @@ -161,6 +159,11 @@ int IPASoftSimple::init(const IPASettings &settings, > } > > params_ = static_cast<DebayerParams *>(mem); > + params_->blackLevel = { { 0.0, 0.0, 0.0 } }; > + params_->gamma = 1.0 / algorithms::kDefaultGamma; > + params_->contrastExp = 1.0; > + params_->gains = { { 1.0, 1.0, 1.0 } }; > + /* combinedMatrix is reset for each frame. */ > } > > { > @@ -287,6 +290,8 @@ void IPASoftSimple::computeParams(const uint32_t frame) > IPAFrameContext &frameContext = context_.frameContexts.get(frame); > for (auto const &algo : algorithms()) > algo->prepare(context_, frame, frameContext, params_); > + params_->combinedMatrix = context_.activeState.combinedMatrix; > + > setIspParams.emit(); > } > > diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp > index 65a1762dd..dccdd86b4 100644 > --- a/src/libcamera/software_isp/debayer.cpp > +++ b/src/libcamera/software_isp/debayer.cpp > @@ -1,7 +1,7 @@ > /* SPDX-License-Identifier: LGPL-2.1-or-later */ > /* > * Copyright (C) 2023, Linaro Ltd > - * Copyright (C) 2023-2025 Red Hat Inc. > + * Copyright (C) 2023-2026 Red Hat Inc. > * > * Authors: > * Hans de Goede <hdegoede@redhat.com> > @@ -25,99 +25,28 @@ namespace libcamera { > */ > > /** > - * \var DebayerParams::kRGBLookupSize > - * \brief Size of a color lookup table > + * \var DebayerParams::gains > + * \brief Colour channel gains > */ > > /** > - * \struct DebayerParams::CcmColumn > - * \brief Type of a single column of a color correction matrix (CCM) > - * > - * When multiplying an input pixel, columns in the CCM correspond to the red, > - * green or blue component of input pixel values, while rows correspond to the > - * red, green or blue components of the output pixel values. The members of the > - * CcmColumn structure are named after the colour components of the output pixel > - * values they correspond to. > - */ > - > -/** > - * \var DebayerParams::CcmColumn::r > - * \brief Red (first) component of a CCM column > - */ > - > -/** > - * \var DebayerParams::CcmColumn::g > - * \brief Green (second) component of a CCM column > - */ > - > -/** > - * \var DebayerParams::CcmColumn::b > - * \brief Blue (third) component of a CCM column > - */ > - > -/** > - * \typedef DebayerParams::LookupTable > - * \brief Type of the lookup tables for single lookup values > - */ > - > -/** > - * \typedef DebayerParams::CcmLookupTable > - * \brief Type of the CCM lookup tables for red, green, blue values > - */ > - > -/** > - * \var DebayerParams::red > - * \brief Lookup table for red color, mapping input values to output values > - */ > - > -/** > - * \var DebayerParams::green > - * \brief Lookup table for green color, mapping input values to output values > - */ > - > -/** > - * \var DebayerParams::blue > - * \brief Lookup table for blue color, mapping input values to output values > - */ > - > -/** > - * \var DebayerParams::redCcm > - * \brief Lookup table for the CCM red column, mapping input values to output values > - */ > - > -/** > - * \var DebayerParams::greenCcm > - * \brief Lookup table for the CCM green column, mapping input values to output values > - */ > - > -/** > - * \var DebayerParams::blueCcm > - * \brief Lookup table for the CCM blue column, mapping input values to output values > - */ > - > -/** > - * \var DebayerParams::gammaLut > - * \brief Gamma lookup table used with color correction matrix > - */ > - > -/** > - * \var DebayerParams::ccm > - * \brief Per frame colour correction matrix for GPUISP > + * \var DebayerParams::combinedMatrix > + * \brief Colour correction matrix, including other adjustments > */ > > /** > * \var DebayerParams::blackLevel > - * \brief Blacklevel gains for the GPUISP > + * \brief Black level values > */ > > /** > * \var DebayerParams::gamma > - * \brief Gamma value for the GPUISP > + * \brief Gamma value, e.g. 1/2.2 > */ > > /** > * \var DebayerParams::contrastExp > - * \brief Contrast value for GPUISP > + * \brief Contrast value to be used as an exponent > */ > > /** > @@ -131,13 +60,6 @@ LOG_DEFINE_CATEGORY(Debayer) > > Debayer::Debayer(const GlobalConfiguration &configuration) : bench_(configuration) > { > - /* Initialize color lookup tables */ > - for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) { > - red_[i] = green_[i] = blue_[i] = i; > - redCcm_[i] = { static_cast<int16_t>(i), 0, 0 }; > - greenCcm_[i] = { 0, static_cast<int16_t>(i), 0 }; > - blueCcm_[i] = { 0, 0, static_cast<int16_t>(i) }; > - } > } > > Debayer::~Debayer() > @@ -305,56 +227,6 @@ Debayer::~Debayer() > * \brief Output size object > */ > > -/** > - * \var Debayer::red_ > - * \brief Lookup table for red channel gain and correction values > - * > - * This table provides precomputed per-pixel or per-intensity > - * correction values for the red color channel used during debayering. > - */ > - > -/** > - * \var Debayer::green_ > - * \brief Lookup table for green channel gain and correction values > - * > - * This table provides precomputed per-pixel or per-intensity > - * correction values for the green color channel used during debayering. > - */ > - > -/** > - * \var Debayer::blue_ > - * \brief Lookup table for blue channel gain and correction values > - * > - * This table provides precomputed per-pixel or per-intensity > - * correction values for the blue color channel used during debayering. > - */ > - > -/** > - * \var Debayer::redCcm_ > - * \brief Red channel Color Correction Matrix (CCM) lookup table > - * > - * Contains coefficients for green channel color correction. > - */ > - > -/** > - * \var Debayer::greenCcm_ > - * \brief Green channel Color Correction Matrix (CCM) lookup table > - * > - * Contains coefficients for green channel color correction. > - */ > - > -/** > - * \var Debayer::blueCcm_ > - * \brief Blue channel Color Correction Matrix (CCM) lookup table > - * > - * Contains coefficients for blue channel color correction. > - */ > - > -/** > - * \var Debayer::gammaLut_ > - * \brief Gamma correction lookup table > - */ > - > /** > * \var Debayer::swapRedBlueGains_ > * \brief Flag indicating whether red and blue channel gains should be swapped > @@ -396,34 +268,6 @@ Debayer::~Debayer() > * DebayerEGL::start. > */ > > -/** > - * \fn void Debayer::setParams(DebayerParams ¶ms) > - * \brief Select the bayer params to use for the next frame debayer > - * \param[in] params The parameters to be used in debayering > - */ > -void Debayer::setParams(DebayerParams ¶ms) > -{ > - green_ = params.green; > - greenCcm_ = params.greenCcm; > - if (swapRedBlueGains_) { > - red_ = params.blue; > - blue_ = params.red; > - redCcm_ = params.blueCcm; > - blueCcm_ = params.redCcm; > - for (unsigned int i = 0; i < 256; i++) { > - std::swap(redCcm_[i].r, redCcm_[i].b); > - std::swap(greenCcm_[i].r, greenCcm_[i].b); > - std::swap(blueCcm_[i].r, blueCcm_[i].b); > - } > - } else { > - red_ = params.red; > - blue_ = params.blue; > - redCcm_ = params.redCcm; > - blueCcm_ = params.blueCcm; > - } > - gammaLut_ = params.gammaLut; > -} > - > /** > * \fn void Debayer::dmaSyncBegin(DebayerParams ¶ms) > * \brief Common CPU/GPU Dma Sync Buffer begin > diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h > index cd2db9930..652cff4cc 100644 > --- a/src/libcamera/software_isp/debayer.h > +++ b/src/libcamera/software_isp/debayer.h > @@ -78,13 +78,6 @@ public: > Size outputSize_; > PixelFormat inputPixelFormat_; > PixelFormat outputPixelFormat_; > - DebayerParams::LookupTable red_; > - DebayerParams::LookupTable green_; > - DebayerParams::LookupTable blue_; > - DebayerParams::CcmLookupTable redCcm_; > - DebayerParams::CcmLookupTable greenCcm_; > - DebayerParams::CcmLookupTable blueCcm_; > - DebayerParams::LookupTable gammaLut_; > bool swapRedBlueGains_; > Benchmark bench_; > > @@ -92,7 +85,6 @@ private: > virtual Size patternSize(PixelFormat inputFormat) = 0; > > protected: > - void setParams(DebayerParams ¶ms); > void dmaSyncBegin(std::vector<DmaSyncer> &dmaSyncers, FrameBuffer *input, FrameBuffer *output); > static bool isStandardBayerOrder(BayerFormat::Order order); > }; > diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp > index 00738c56b..af7af0a8d 100644 > --- a/src/libcamera/software_isp/debayer_cpu.cpp > +++ b/src/libcamera/software_isp/debayer_cpu.cpp > @@ -1,7 +1,7 @@ > /* SPDX-License-Identifier: LGPL-2.1-or-later */ > /* > * Copyright (C) 2023, Linaro Ltd > - * Copyright (C) 2023-2025 Red Hat Inc. > + * Copyright (C) 2023-2026 Red Hat Inc. > * > * Authors: > * Hans de Goede <hdegoede@redhat.com> > @@ -68,21 +68,21 @@ DebayerCpu::~DebayerCpu() = default; > #define GAMMA(value) \ > *dst++ = gammaLut_[std::clamp(value, 0, static_cast<int>(gammaLut_.size()) - 1)] > > -#define STORE_PIXEL(b_, g_, r_) \ > - if constexpr (ccmEnabled) { \ > - const DebayerParams::CcmColumn &blue = blueCcm_[b_]; \ > - const DebayerParams::CcmColumn &green = greenCcm_[g_]; \ > - const DebayerParams::CcmColumn &red = redCcm_[r_]; \ > - GAMMA(blue.b + green.b + red.b); \ > - GAMMA(blue.g + green.g + red.g); \ > - GAMMA(blue.r + green.r + red.r); \ > - } else { \ > - *dst++ = blue_[b_]; \ > - *dst++ = green_[g_]; \ > - *dst++ = red_[r_]; \ > - } \ > - if constexpr (addAlphaByte) \ > - *dst++ = 255; \ > +#define STORE_PIXEL(b_, g_, r_) \ > + if constexpr (ccmEnabled) { \ > + const CcmColumn &blue = blueCcm_[b_]; \ > + const CcmColumn &green = greenCcm_[g_]; \ > + const CcmColumn &red = redCcm_[r_]; \ > + GAMMA(blue.b + green.b + red.b); \ > + GAMMA(blue.g + green.g + red.g); \ > + GAMMA(blue.r + green.r + red.r); \ > + } else { \ > + *dst++ = blue_[b_]; \ > + *dst++ = green_[g_]; \ > + *dst++ = red_[r_]; \ > + } \ > + if constexpr (addAlphaByte) \ > + *dst++ = 255; \ > x++; > > /* > @@ -525,6 +525,16 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg, > if (ret != 0) > return -EINVAL; > > + ccmEnabled_ = ccmEnabled; > + > + /* > + * Lookup tables must be initialized because the initial value is used for > + * the first two frames, i.e. until stats processing starts providing its > + * own parameters. Let's enforce recomputing lookup tables by setting the > + * stored last used gamma to an out-of-range value. > + */ > + params_.gamma = 1.0; > + > window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & > ~(inputConfig_.patternSize.width - 1); > window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & > @@ -740,6 +750,98 @@ void DebayerCpu::process4(uint32_t frame, const uint8_t *src, uint8_t *dst) > } > } > > +void DebayerCpu::updateGammaTable(DebayerParams ¶ms) > +{ > + const RGB<float> blackLevel = params.blackLevel; > + /* Take let's say the green channel black level */ > + const unsigned int blackIndex = blackLevel[1] * gammaTable_.size(); > + const float gamma = params.gamma; > + const float contrastExp = params.contrastExp; > + > + const float divisor = gammaTable_.size() - blackIndex - 1.0; > + for (unsigned int i = blackIndex; i < gammaTable_.size(); i++) { > + float normalized = (i - blackIndex) / divisor; > + /* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */ > + /* Apply simple S-curve */ > + if (normalized < 0.5) > + normalized = 0.5 * std::pow(normalized / 0.5, contrastExp); > + else > + normalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp); > + gammaTable_[i] = UINT8_MAX * > + std::pow(normalized, gamma); > + } > + /* > + * Due to CCM operations, the table lookup may reach indices below the black > + * level. Let's set the table values below black level to the minimum > + * non-black value to prevent problems when the minimum value is > + * significantly non-zero (for example, when the image should be all grey). > + */ > + std::fill(gammaTable_.begin(), gammaTable_.begin() + blackIndex, > + gammaTable_[blackIndex]); > +} > + > +void DebayerCpu::updateLookupTables(DebayerParams ¶ms) > +{ > + const bool gammaUpdateNeeded = > + params.gamma != params_.gamma || > + params.blackLevel != params_.blackLevel || > + params.contrastExp != params_.contrastExp; > + if (gammaUpdateNeeded) > + updateGammaTable(params); > + > + auto matrixChanged = [](const Matrix<float, 3, 3> &m1, const Matrix<float, 3, 3> &m2) -> bool { > + return !std::equal(m1.data().begin(), m1.data().end(), m2.data().begin()); > + }; > + const unsigned int gammaTableSize = gammaTable_.size(); > + const double div = static_cast<double>(kRGBLookupSize) / gammaTableSize; > + if (ccmEnabled_) { > + if (gammaUpdateNeeded || > + matrixChanged(params.combinedMatrix, params_.combinedMatrix)) { > + auto &red = swapRedBlueGains_ ? blueCcm_ : redCcm_; > + auto &green = greenCcm_; > + auto &blue = swapRedBlueGains_ ? redCcm_ : blueCcm_; > + const unsigned int redIndex = swapRedBlueGains_ ? 2 : 0; > + const unsigned int greenIndex = 1; > + const unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2; > + for (unsigned int i = 0; i < kRGBLookupSize; i++) { > + red[i].r = std::round(i * params.combinedMatrix[redIndex][0]); > + red[i].g = std::round(i * params.combinedMatrix[greenIndex][0]); > + red[i].b = std::round(i * params.combinedMatrix[blueIndex][0]); > + green[i].r = std::round(i * params.combinedMatrix[redIndex][1]); > + green[i].g = std::round(i * params.combinedMatrix[greenIndex][1]); > + green[i].b = std::round(i * params.combinedMatrix[blueIndex][1]); > + blue[i].r = std::round(i * params.combinedMatrix[redIndex][2]); > + blue[i].g = std::round(i * params.combinedMatrix[greenIndex][2]); > + blue[i].b = std::round(i * params.combinedMatrix[blueIndex][2]); > + gammaLut_[i] = gammaTable_[i / div]; > + } > + } > + } else { > + if (gammaUpdateNeeded || params.gains != params_.gains) { > + auto &gains = params.gains; > + auto &red = swapRedBlueGains_ ? blue_ : red_; > + auto &green = green_; > + auto &blue = swapRedBlueGains_ ? red_ : blue_; > + for (unsigned int i = 0; i < kRGBLookupSize; i++) { > + /* Apply gamma after gain! */ > + const RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1); > + red[i] = gammaTable_[static_cast<unsigned int>(lutGains.r())]; > + green[i] = gammaTable_[static_cast<unsigned int>(lutGains.g())]; > + blue[i] = gammaTable_[static_cast<unsigned int>(lutGains.b())]; > + } > + } > + } > + > + LOG(Debayer, Debug) > + << "Debayer parameters: blackLevel=" << params.blackLevel > + << "; gamma=" << params.gamma > + << "; contrastExp=" << params.contrastExp > + << "; gains=" << params.gains > + << "; matrix=" << params.combinedMatrix; > + > + params_ = params; > +} > + > void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params) > { > bench_.startFrame(); > @@ -748,7 +850,7 @@ void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output > > dmaSyncBegin(dmaSyncers, input, output); > > - setParams(params); > + updateLookupTables(params); > > /* Copy metadata from the input buffer */ > FrameMetadata &metadata = output->_d()->metadata(); > diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h > index 67df2b93a..b5cbb5bd2 100644 > --- a/src/libcamera/software_isp/debayer_cpu.h > +++ b/src/libcamera/software_isp/debayer_cpu.h > @@ -1,7 +1,7 @@ > /* SPDX-License-Identifier: LGPL-2.1-or-later */ > /* > * Copyright (C) 2023, Linaro Ltd > - * Copyright (C) 2023-2025 Red Hat Inc. > + * Copyright (C) 2023-2026 Red Hat Inc. > * > * Authors: > * Hans de Goede <hdegoede@redhat.com> > @@ -18,6 +18,8 @@ > #include <libcamera/base/object.h> > > #include "libcamera/internal/bayer_format.h" > +#include "libcamera/internal/global_configuration.h" > +#include "libcamera/internal/software_isp/debayer_params.h" > #include "libcamera/internal/software_isp/swstats_cpu.h" > > #include "debayer.h" > @@ -108,10 +110,32 @@ private: > void memcpyNextLine(const uint8_t *linePointers[]); > void process2(uint32_t frame, const uint8_t *src, uint8_t *dst); > void process4(uint32_t frame, const uint8_t *src, uint8_t *dst); > + void updateGammaTable(DebayerParams ¶ms); > + void updateLookupTables(DebayerParams ¶ms); > > /* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */ > static constexpr unsigned int kMaxLineBuffers = 5; > > + static constexpr unsigned int kRGBLookupSize = 256; > + static constexpr unsigned int kGammaLookupSize = 1024; > + struct CcmColumn { > + int16_t r; > + int16_t g; > + int16_t b; > + }; > + using LookupTable = std::array<uint8_t, kRGBLookupSize>; > + using CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>; > + LookupTable red_; > + LookupTable green_; > + LookupTable blue_; > + CcmLookupTable redCcm_; > + CcmLookupTable greenCcm_; > + CcmLookupTable blueCcm_; > + std::array<double, kGammaLookupSize> gammaTable_; > + LookupTable gammaLut_; > + bool ccmEnabled_; > + DebayerParams params_; > + > debayerFn debayer0_; > debayerFn debayer1_; > debayerFn debayer2_; > diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp > index 37c44e8b1..722778103 100644 > --- a/src/libcamera/software_isp/debayer_egl.cpp > +++ b/src/libcamera/software_isp/debayer_egl.cpp > @@ -475,18 +475,18 @@ void DebayerEGL::setShaderVariableValues(DebayerParams ¶ms) > << " textureUniformProjMatrix_ " << textureUniformProjMatrix_; > > GLfloat ccm[9] = { > - params.ccm[0][0], > - params.ccm[0][1], > - params.ccm[0][2], > - params.ccm[1][0], > - params.ccm[1][1], > - params.ccm[1][2], > - params.ccm[2][0], > - params.ccm[2][1], > - params.ccm[2][2], > + params.combinedMatrix[0][0], > + params.combinedMatrix[0][1], > + params.combinedMatrix[0][2], > + params.combinedMatrix[1][0], > + params.combinedMatrix[1][1], > + params.combinedMatrix[1][2], > + params.combinedMatrix[2][0], > + params.combinedMatrix[2][1], > + params.combinedMatrix[2][2], > }; > glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm); > - LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.ccm; > + LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; > > /* > * 0 = Red, 1 = Green, 2 = Blue > @@ -544,8 +544,6 @@ void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output > > dmaSyncBegin(dmaSyncers, input, nullptr); > > - setParams(params); > - > /* Copy metadata from the input buffer */ > FrameMetadata &metadata = output->_d()->metadata(); > metadata.status = input->metadata().status; > diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp > index 7ad3511db..a83986b78 100644 > --- a/src/libcamera/software_isp/software_isp.cpp > +++ b/src/libcamera/software_isp/software_isp.cpp > @@ -84,23 +84,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, > DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap | > DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf) > { > - /* > - * debayerParams_ must be initialized because the initial value is used for > - * the first two frames, i.e. until stats processing starts providing its > - * own parameters. > - * > - * \todo This should be handled in the same place as the related > - * operations, in the IPA module. > - */ > - std::array<uint8_t, 256> gammaTable; > - for (unsigned int i = 0; i < 256; i++) > - gammaTable[i] = UINT8_MAX * std::pow(i / 256.0, 0.5); > - for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) { > - debayerParams_.red[i] = gammaTable[i]; > - debayerParams_.green[i] = gammaTable[i]; > - debayerParams_.blue[i] = gammaTable[i]; > - } > - > if (!dmaHeap_.isValid()) { > LOG(SoftwareIsp, Error) << "Failed to create DmaBufAllocator object"; > return; > @@ -121,8 +104,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, > } > stats->statsReady.connect(this, &SoftwareIsp::statsReady); > > - bool gpuIspEnabled; > - > #if HAVE_DEBAYER_EGL > std::optional<std::string> softISPMode = configuration.envOption("LIBCAMERA_SOFTISP_MODE", { "software_isp", "mode" }); > if (softISPMode) { > @@ -133,15 +114,12 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, > } > } > > - if (!softISPMode || softISPMode == "gpu") { > + if (!softISPMode || softISPMode == "gpu") > debayer_ = std::make_unique<DebayerEGL>(std::move(stats), configuration); > - gpuIspEnabled = true; > - } > + > #endif > - if (!debayer_) { > + if (!debayer_) > debayer_ = std::make_unique<DebayerCpu>(std::move(stats), configuration); > - gpuIspEnabled = false; > - } > > debayer_->inputBufferReady.connect(this, &SoftwareIsp::inputReady); > debayer_->outputBufferReady.connect(this, &SoftwareIsp::outputReady); > @@ -173,7 +151,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, > sharedParams_.fd(), > sensorInfo, > sensor->controls(), > - gpuIspEnabled, > ipaControls, > &ccmEnabled_); > if (ret) { > -- > 2.52.0 >
diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h index 2d69bd295..1c0412d75 100644 --- a/include/libcamera/internal/software_isp/debayer_params.h +++ b/include/libcamera/internal/software_isp/debayer_params.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* - * Copyright (C) 2023-2025 Red Hat Inc. + * Copyright (C) 2023-2026 Red Hat Inc. * * Authors: * Hans de Goede <hdegoede@redhat.com> @@ -10,7 +10,6 @@ #pragma once -#include <array> #include <stdint.h> #include "libcamera/internal/matrix.h" @@ -19,47 +18,11 @@ namespace libcamera { struct DebayerParams { - static constexpr unsigned int kRGBLookupSize = 256; - - struct CcmColumn { - int16_t r; - int16_t g; - int16_t b; - }; - - using LookupTable = std::array<uint8_t, kRGBLookupSize>; - using CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>; - - /* - * Color lookup tables when CCM is not used. - * - * Each color of a debayered pixel is amended by the corresponding - * value in the given table. - */ - LookupTable red; - LookupTable green; - LookupTable blue; - - /* - * Color and gamma lookup tables when CCM is used. - * - * Each of the CcmLookupTable's corresponds to a CCM column; together they - * make a complete 3x3 CCM lookup table. The CCM is applied on debayered - * pixels and then the gamma lookup table is used to set the resulting - * values of all the three colors. - */ - CcmLookupTable redCcm; - CcmLookupTable greenCcm; - CcmLookupTable blueCcm; - LookupTable gammaLut; - - /* - * Per frame corrections as calculated by the IPA - */ - Matrix<float, 3, 3> ccm; + Matrix<float, 3, 3> combinedMatrix; RGB<float> blackLevel; float gamma; float contrastExp; + RGB<float> gains; }; } /* namespace libcamera */ diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom index aff8fcbd3..77328c5fd 100644 --- a/include/libcamera/ipa/soft.mojom +++ b/include/libcamera/ipa/soft.mojom @@ -17,8 +17,7 @@ interface IPASoftInterface { libcamera.SharedFD fdStats, libcamera.SharedFD fdParams, libcamera.IPACameraSensorInfo sensorInfo, - libcamera.ControlInfoMap sensorControls, - bool gpuIspEnabled) + libcamera.ControlInfoMap sensorControls) => (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled); start() => (int32 ret); stop(); diff --git a/src/ipa/simple/algorithms/adjust.cpp b/src/ipa/simple/algorithms/adjust.cpp index acdd3f741..068e98404 100644 --- a/src/ipa/simple/algorithms/adjust.cpp +++ b/src/ipa/simple/algorithms/adjust.cpp @@ -95,23 +95,20 @@ void Adjust::applySaturation(Matrix<float, 3, 3> &matrix, float saturation) void Adjust::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, - [[maybe_unused]] DebayerParams *params) + DebayerParams *params) { frameContext.gamma = context.activeState.knobs.gamma; frameContext.contrast = context.activeState.knobs.contrast; - if (!context.ccmEnabled) - return; - auto &saturation = context.activeState.knobs.saturation; - frameContext.saturation = saturation; - if (saturation) + if (context.ccmEnabled && saturation) { applySaturation(context.activeState.combinedMatrix, saturation.value()); - - if (saturation != lastSaturation_) { - context.activeState.matrixChanged = true; - lastSaturation_ = saturation; + frameContext.saturation = saturation; } + + params->gamma = 1.0 / context.activeState.knobs.gamma; + const float contrast = context.activeState.knobs.contrast.value_or(kDefaultContrast); + params->contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001)); } void Adjust::process([[maybe_unused]] IPAContext &context, diff --git a/src/ipa/simple/algorithms/adjust.h b/src/ipa/simple/algorithms/adjust.h index 7644138ff..fb133b140 100644 --- a/src/ipa/simple/algorithms/adjust.h +++ b/src/ipa/simple/algorithms/adjust.h @@ -7,8 +7,6 @@ #pragma once -#include <optional> - #include "libcamera/internal/matrix.h" #include <libipa/interpolator.h> @@ -45,8 +43,6 @@ public: private: void applySaturation(Matrix<float, 3, 3> &ccm, float saturation); - - std::optional<float> lastSaturation_; }; } /* namespace ipa::soft::algorithms */ diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp index 31726658a..6fdaacaba 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -37,7 +37,7 @@ int Awb::configure(IPAContext &context, void Awb::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, - [[maybe_unused]] DebayerParams *params) + DebayerParams *params) { auto &gains = context.activeState.awb.gains; Matrix<float, 3, 3> gainMatrix = { { gains.r(), 0, 0, @@ -45,9 +45,11 @@ void Awb::prepare(IPAContext &context, 0, 0, gains.b() } }; context.activeState.combinedMatrix = context.activeState.combinedMatrix * gainMatrix; - /* Just report, the gains are applied in LUT algorithm. */ + frameContext.gains.red = gains.r(); frameContext.gains.blue = gains.b(); + + params->gains = gains; } void Awb::process(IPAContext &context, diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp index 5576a301f..911a5af2c 100644 --- a/src/ipa/simple/algorithms/ccm.cpp +++ b/src/ipa/simple/algorithms/ccm.cpp @@ -51,7 +51,6 @@ void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) { currentCcm_ = ccm_.getInterpolated(ct); lastCt_ = ct; - context.activeState.matrixChanged = true; } context.activeState.combinedMatrix = diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp deleted file mode 100644 index fd442259a..000000000 --- a/src/ipa/simple/algorithms/lut.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2024-2026, Red Hat Inc. - * - * Color lookup tables construction - */ - -#include "lut.h" - -#include <algorithm> -#include <cmath> -#include <optional> -#include <stdint.h> - -#include <libcamera/base/log.h> - -#include <libcamera/control_ids.h> - -#include "simple/ipa_context.h" - -#include "adjust.h" - -namespace libcamera { - -LOG_DEFINE_CATEGORY(IPASoftLut) - -namespace ipa::soft::algorithms { - -int Lut::configure(IPAContext &context, - [[maybe_unused]] const IPAConfigInfo &configInfo) -{ - updateGammaTable(context); - - return 0; -} - -void Lut::updateGammaTable(IPAContext &context) -{ - const auto blackLevel = context.activeState.blc.level; - const auto gamma = 1.0 / context.activeState.knobs.gamma; - const auto contrast = context.activeState.knobs.contrast.value_or(1.0); - /* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */ - float contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001)); - - if (!context.gpuIspEnabled) { - auto &gammaTable = context.activeState.gamma.gammaTable; - const unsigned int blackIndex = blackLevel * gammaTable.size() / 256; - const float divisor = gammaTable.size() - blackIndex - 1.0; - for (unsigned int i = blackIndex; i < gammaTable.size(); i++) { - double normalized = (i - blackIndex) / divisor; - /* Apply simple S-curve */ - if (normalized < 0.5) - normalized = 0.5 * std::pow(normalized / 0.5, contrastExp); - else - normalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp); - gammaTable[i] = UINT8_MAX * std::pow(normalized, gamma); - } - /* - * Due to CCM operations, the table lookup may reach indices below the black - * level. Let's set the table values below black level to the minimum - * non-black value to prevent problems when the minimum value is - * significantly non-zero (for example, when the image should be all grey). - */ - std::fill(gammaTable.begin(), gammaTable.begin() + blackIndex, - gammaTable[blackIndex]); - } - - context.activeState.gamma.gamma = gamma; - context.activeState.gamma.blackLevel = blackLevel; - context.activeState.gamma.contrastExp = contrastExp; -} - -int16_t Lut::matrixValue(unsigned int i, float ccm) const -{ - return std::round(i * ccm); -} - -void Lut::prepare(IPAContext &context, - [[maybe_unused]] const uint32_t frame, - [[maybe_unused]] IPAFrameContext &frameContext, - DebayerParams *params) -{ - /* - * Update the gamma table if needed. This means if black level changes - * and since the black level gets updated only if a lower value is - * observed, it's not permanently prone to minor fluctuations or - * rounding errors. - */ - const bool gammaUpdateNeeded = - context.activeState.gamma.blackLevel != context.activeState.blc.level || - context.activeState.gamma.contrast != context.activeState.knobs.contrast; - if (gammaUpdateNeeded) - updateGammaTable(context); - - auto &gains = context.activeState.awb.gains; - auto &gammaTable = context.activeState.gamma.gammaTable; - const unsigned int gammaTableSize = gammaTable.size(); - const double div = static_cast<double>(DebayerParams::kRGBLookupSize) / - gammaTableSize; - - if (!context.ccmEnabled) { - for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) { - /* Apply gamma after gain! */ - const RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1); - params->red[i] = gammaTable[static_cast<unsigned int>(lutGains.r())]; - params->green[i] = gammaTable[static_cast<unsigned int>(lutGains.g())]; - params->blue[i] = gammaTable[static_cast<unsigned int>(lutGains.b())]; - } - } else if (context.activeState.matrixChanged || gammaUpdateNeeded) { - auto &matrix = context.activeState.combinedMatrix; - auto &red = params->redCcm; - auto &green = params->greenCcm; - auto &blue = params->blueCcm; - params->ccm = matrix; - if (!context.gpuIspEnabled) { - for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) { - red[i].r = matrixValue(i, matrix[0][0]); - red[i].g = matrixValue(i, matrix[1][0]); - red[i].b = matrixValue(i, matrix[2][0]); - green[i].r = matrixValue(i, matrix[0][1]); - green[i].g = matrixValue(i, matrix[1][1]); - green[i].b = matrixValue(i, matrix[2][1]); - blue[i].r = matrixValue(i, matrix[0][2]); - blue[i].g = matrixValue(i, matrix[1][2]); - blue[i].b = matrixValue(i, matrix[2][2]); - params->gammaLut[i] = gammaTable[i / div]; - } - } - context.activeState.matrixChanged = false; - } - - params->gamma = context.activeState.gamma.gamma; - params->contrastExp = context.activeState.gamma.contrastExp; -} - -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" - -namespace libcamera { - -namespace ipa::soft::algorithms { - -class Lut : public Algorithm -{ -public: - Lut() = default; - ~Lut() = default; - - int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; - void prepare(IPAContext &context, - const uint32_t frame, - IPAFrameContext &frameContext, - DebayerParams *params) override; - -private: - void updateGammaTable(IPAContext &context); - int16_t matrixValue(unsigned int i, float ccm) const; -}; - -} /* namespace ipa::soft::algorithms */ - -} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build index ebe9f20dd..73c637220 100644 --- a/src/ipa/simple/algorithms/meson.build +++ b/src/ipa/simple/algorithms/meson.build @@ -6,5 +6,4 @@ soft_simple_ipa_algorithms = files([ 'agc.cpp', 'blc.cpp', 'ccm.cpp', - 'lut.cpp', ]) diff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml index e389e0588..c6feda36d 100644 --- a/src/ipa/simple/data/uncalibrated.yaml +++ b/src/ipa/simple/data/uncalibrated.yaml @@ -15,6 +15,5 @@ algorithms: 0, 1, 0, 0, 0, 1] - Adjust: - - Lut: - Agc: ... diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 293e35b71..34f7403a4 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -53,17 +53,7 @@ struct IPAActiveState { unsigned int temperatureK; } awb; - static constexpr unsigned int kGammaLookupSize = 1024; - struct { - std::array<double, kGammaLookupSize> gammaTable; - uint8_t blackLevel; - float gamma; - float contrast; - float contrastExp; - } gamma; - Matrix<float, 3, 3> combinedMatrix; - bool matrixChanged = false; struct { float gamma; @@ -103,7 +93,6 @@ struct IPAContext { FCQueue<IPAFrameContext> frameContexts; ControlInfoMap::Map ctrlMap; bool ccmEnabled = false; - bool gpuIspEnabled = false; }; } /* namespace ipa::soft */ diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 732e82510..6bef597c8 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -26,6 +26,7 @@ #include "libcamera/internal/software_isp/swisp_stats.h" #include "libcamera/internal/yaml_parser.h" +#include "algorithms/adjust.h" #include "libipa/camera_sensor_helper.h" #include "module.h" @@ -55,7 +56,6 @@ public: const SharedFD &fdParams, const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, - bool gpuIspEnabled, ControlInfoMap *ipaControls, bool *ccmEnabled) override; int configure(const IPAConfigInfo &configInfo) override; @@ -96,7 +96,6 @@ int IPASoftSimple::init(const IPASettings &settings, const SharedFD &fdParams, const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, - bool gpuIspEnabled, ControlInfoMap *ipaControls, bool *ccmEnabled) { @@ -108,7 +107,6 @@ int IPASoftSimple::init(const IPASettings &settings, } context_.sensorInfo = sensorInfo; - context_.gpuIspEnabled = gpuIspEnabled; /* Load the tuning data file */ File file(settings.configurationFile); @@ -161,6 +159,11 @@ int IPASoftSimple::init(const IPASettings &settings, } params_ = static_cast<DebayerParams *>(mem); + params_->blackLevel = { { 0.0, 0.0, 0.0 } }; + params_->gamma = 1.0 / algorithms::kDefaultGamma; + params_->contrastExp = 1.0; + params_->gains = { { 1.0, 1.0, 1.0 } }; + /* combinedMatrix is reset for each frame. */ } { @@ -287,6 +290,8 @@ void IPASoftSimple::computeParams(const uint32_t frame) IPAFrameContext &frameContext = context_.frameContexts.get(frame); for (auto const &algo : algorithms()) algo->prepare(context_, frame, frameContext, params_); + params_->combinedMatrix = context_.activeState.combinedMatrix; + setIspParams.emit(); } diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp index 65a1762dd..dccdd86b4 100644 --- a/src/libcamera/software_isp/debayer.cpp +++ b/src/libcamera/software_isp/debayer.cpp @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2023, Linaro Ltd - * Copyright (C) 2023-2025 Red Hat Inc. + * Copyright (C) 2023-2026 Red Hat Inc. * * Authors: * Hans de Goede <hdegoede@redhat.com> @@ -25,99 +25,28 @@ namespace libcamera { */ /** - * \var DebayerParams::kRGBLookupSize - * \brief Size of a color lookup table + * \var DebayerParams::gains + * \brief Colour channel gains */ /** - * \struct DebayerParams::CcmColumn - * \brief Type of a single column of a color correction matrix (CCM) - * - * When multiplying an input pixel, columns in the CCM correspond to the red, - * green or blue component of input pixel values, while rows correspond to the - * red, green or blue components of the output pixel values. The members of the - * CcmColumn structure are named after the colour components of the output pixel - * values they correspond to. - */ - -/** - * \var DebayerParams::CcmColumn::r - * \brief Red (first) component of a CCM column - */ - -/** - * \var DebayerParams::CcmColumn::g - * \brief Green (second) component of a CCM column - */ - -/** - * \var DebayerParams::CcmColumn::b - * \brief Blue (third) component of a CCM column - */ - -/** - * \typedef DebayerParams::LookupTable - * \brief Type of the lookup tables for single lookup values - */ - -/** - * \typedef DebayerParams::CcmLookupTable - * \brief Type of the CCM lookup tables for red, green, blue values - */ - -/** - * \var DebayerParams::red - * \brief Lookup table for red color, mapping input values to output values - */ - -/** - * \var DebayerParams::green - * \brief Lookup table for green color, mapping input values to output values - */ - -/** - * \var DebayerParams::blue - * \brief Lookup table for blue color, mapping input values to output values - */ - -/** - * \var DebayerParams::redCcm - * \brief Lookup table for the CCM red column, mapping input values to output values - */ - -/** - * \var DebayerParams::greenCcm - * \brief Lookup table for the CCM green column, mapping input values to output values - */ - -/** - * \var DebayerParams::blueCcm - * \brief Lookup table for the CCM blue column, mapping input values to output values - */ - -/** - * \var DebayerParams::gammaLut - * \brief Gamma lookup table used with color correction matrix - */ - -/** - * \var DebayerParams::ccm - * \brief Per frame colour correction matrix for GPUISP + * \var DebayerParams::combinedMatrix + * \brief Colour correction matrix, including other adjustments */ /** * \var DebayerParams::blackLevel - * \brief Blacklevel gains for the GPUISP + * \brief Black level values */ /** * \var DebayerParams::gamma - * \brief Gamma value for the GPUISP + * \brief Gamma value, e.g. 1/2.2 */ /** * \var DebayerParams::contrastExp - * \brief Contrast value for GPUISP + * \brief Contrast value to be used as an exponent */ /** @@ -131,13 +60,6 @@ LOG_DEFINE_CATEGORY(Debayer) Debayer::Debayer(const GlobalConfiguration &configuration) : bench_(configuration) { - /* Initialize color lookup tables */ - for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) { - red_[i] = green_[i] = blue_[i] = i; - redCcm_[i] = { static_cast<int16_t>(i), 0, 0 }; - greenCcm_[i] = { 0, static_cast<int16_t>(i), 0 }; - blueCcm_[i] = { 0, 0, static_cast<int16_t>(i) }; - } } Debayer::~Debayer() @@ -305,56 +227,6 @@ Debayer::~Debayer() * \brief Output size object */ -/** - * \var Debayer::red_ - * \brief Lookup table for red channel gain and correction values - * - * This table provides precomputed per-pixel or per-intensity - * correction values for the red color channel used during debayering. - */ - -/** - * \var Debayer::green_ - * \brief Lookup table for green channel gain and correction values - * - * This table provides precomputed per-pixel or per-intensity - * correction values for the green color channel used during debayering. - */ - -/** - * \var Debayer::blue_ - * \brief Lookup table for blue channel gain and correction values - * - * This table provides precomputed per-pixel or per-intensity - * correction values for the blue color channel used during debayering. - */ - -/** - * \var Debayer::redCcm_ - * \brief Red channel Color Correction Matrix (CCM) lookup table - * - * Contains coefficients for green channel color correction. - */ - -/** - * \var Debayer::greenCcm_ - * \brief Green channel Color Correction Matrix (CCM) lookup table - * - * Contains coefficients for green channel color correction. - */ - -/** - * \var Debayer::blueCcm_ - * \brief Blue channel Color Correction Matrix (CCM) lookup table - * - * Contains coefficients for blue channel color correction. - */ - -/** - * \var Debayer::gammaLut_ - * \brief Gamma correction lookup table - */ - /** * \var Debayer::swapRedBlueGains_ * \brief Flag indicating whether red and blue channel gains should be swapped @@ -396,34 +268,6 @@ Debayer::~Debayer() * DebayerEGL::start. */ -/** - * \fn void Debayer::setParams(DebayerParams ¶ms) - * \brief Select the bayer params to use for the next frame debayer - * \param[in] params The parameters to be used in debayering - */ -void Debayer::setParams(DebayerParams ¶ms) -{ - green_ = params.green; - greenCcm_ = params.greenCcm; - if (swapRedBlueGains_) { - red_ = params.blue; - blue_ = params.red; - redCcm_ = params.blueCcm; - blueCcm_ = params.redCcm; - for (unsigned int i = 0; i < 256; i++) { - std::swap(redCcm_[i].r, redCcm_[i].b); - std::swap(greenCcm_[i].r, greenCcm_[i].b); - std::swap(blueCcm_[i].r, blueCcm_[i].b); - } - } else { - red_ = params.red; - blue_ = params.blue; - redCcm_ = params.redCcm; - blueCcm_ = params.blueCcm; - } - gammaLut_ = params.gammaLut; -} - /** * \fn void Debayer::dmaSyncBegin(DebayerParams ¶ms) * \brief Common CPU/GPU Dma Sync Buffer begin diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h index cd2db9930..652cff4cc 100644 --- a/src/libcamera/software_isp/debayer.h +++ b/src/libcamera/software_isp/debayer.h @@ -78,13 +78,6 @@ public: Size outputSize_; PixelFormat inputPixelFormat_; PixelFormat outputPixelFormat_; - DebayerParams::LookupTable red_; - DebayerParams::LookupTable green_; - DebayerParams::LookupTable blue_; - DebayerParams::CcmLookupTable redCcm_; - DebayerParams::CcmLookupTable greenCcm_; - DebayerParams::CcmLookupTable blueCcm_; - DebayerParams::LookupTable gammaLut_; bool swapRedBlueGains_; Benchmark bench_; @@ -92,7 +85,6 @@ private: virtual Size patternSize(PixelFormat inputFormat) = 0; protected: - void setParams(DebayerParams ¶ms); void dmaSyncBegin(std::vector<DmaSyncer> &dmaSyncers, FrameBuffer *input, FrameBuffer *output); static bool isStandardBayerOrder(BayerFormat::Order order); }; diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp index 00738c56b..af7af0a8d 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2023, Linaro Ltd - * Copyright (C) 2023-2025 Red Hat Inc. + * Copyright (C) 2023-2026 Red Hat Inc. * * Authors: * Hans de Goede <hdegoede@redhat.com> @@ -68,21 +68,21 @@ DebayerCpu::~DebayerCpu() = default; #define GAMMA(value) \ *dst++ = gammaLut_[std::clamp(value, 0, static_cast<int>(gammaLut_.size()) - 1)] -#define STORE_PIXEL(b_, g_, r_) \ - if constexpr (ccmEnabled) { \ - const DebayerParams::CcmColumn &blue = blueCcm_[b_]; \ - const DebayerParams::CcmColumn &green = greenCcm_[g_]; \ - const DebayerParams::CcmColumn &red = redCcm_[r_]; \ - GAMMA(blue.b + green.b + red.b); \ - GAMMA(blue.g + green.g + red.g); \ - GAMMA(blue.r + green.r + red.r); \ - } else { \ - *dst++ = blue_[b_]; \ - *dst++ = green_[g_]; \ - *dst++ = red_[r_]; \ - } \ - if constexpr (addAlphaByte) \ - *dst++ = 255; \ +#define STORE_PIXEL(b_, g_, r_) \ + if constexpr (ccmEnabled) { \ + const CcmColumn &blue = blueCcm_[b_]; \ + const CcmColumn &green = greenCcm_[g_]; \ + const CcmColumn &red = redCcm_[r_]; \ + GAMMA(blue.b + green.b + red.b); \ + GAMMA(blue.g + green.g + red.g); \ + GAMMA(blue.r + green.r + red.r); \ + } else { \ + *dst++ = blue_[b_]; \ + *dst++ = green_[g_]; \ + *dst++ = red_[r_]; \ + } \ + if constexpr (addAlphaByte) \ + *dst++ = 255; \ x++; /* @@ -525,6 +525,16 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg, if (ret != 0) return -EINVAL; + ccmEnabled_ = ccmEnabled; + + /* + * Lookup tables must be initialized because the initial value is used for + * the first two frames, i.e. until stats processing starts providing its + * own parameters. Let's enforce recomputing lookup tables by setting the + * stored last used gamma to an out-of-range value. + */ + params_.gamma = 1.0; + window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & ~(inputConfig_.patternSize.width - 1); window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & @@ -740,6 +750,98 @@ void DebayerCpu::process4(uint32_t frame, const uint8_t *src, uint8_t *dst) } } +void DebayerCpu::updateGammaTable(DebayerParams ¶ms) +{ + const RGB<float> blackLevel = params.blackLevel; + /* Take let's say the green channel black level */ + const unsigned int blackIndex = blackLevel[1] * gammaTable_.size(); + const float gamma = params.gamma; + const float contrastExp = params.contrastExp; + + const float divisor = gammaTable_.size() - blackIndex - 1.0; + for (unsigned int i = blackIndex; i < gammaTable_.size(); i++) { + float normalized = (i - blackIndex) / divisor; + /* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */ + /* Apply simple S-curve */ + if (normalized < 0.5) + normalized = 0.5 * std::pow(normalized / 0.5, contrastExp); + else + normalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp); + gammaTable_[i] = UINT8_MAX * + std::pow(normalized, gamma); + } + /* + * Due to CCM operations, the table lookup may reach indices below the black + * level. Let's set the table values below black level to the minimum + * non-black value to prevent problems when the minimum value is + * significantly non-zero (for example, when the image should be all grey). + */ + std::fill(gammaTable_.begin(), gammaTable_.begin() + blackIndex, + gammaTable_[blackIndex]); +} + +void DebayerCpu::updateLookupTables(DebayerParams ¶ms) +{ + const bool gammaUpdateNeeded = + params.gamma != params_.gamma || + params.blackLevel != params_.blackLevel || + params.contrastExp != params_.contrastExp; + if (gammaUpdateNeeded) + updateGammaTable(params); + + auto matrixChanged = [](const Matrix<float, 3, 3> &m1, const Matrix<float, 3, 3> &m2) -> bool { + return !std::equal(m1.data().begin(), m1.data().end(), m2.data().begin()); + }; + const unsigned int gammaTableSize = gammaTable_.size(); + const double div = static_cast<double>(kRGBLookupSize) / gammaTableSize; + if (ccmEnabled_) { + if (gammaUpdateNeeded || + matrixChanged(params.combinedMatrix, params_.combinedMatrix)) { + auto &red = swapRedBlueGains_ ? blueCcm_ : redCcm_; + auto &green = greenCcm_; + auto &blue = swapRedBlueGains_ ? redCcm_ : blueCcm_; + const unsigned int redIndex = swapRedBlueGains_ ? 2 : 0; + const unsigned int greenIndex = 1; + const unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2; + for (unsigned int i = 0; i < kRGBLookupSize; i++) { + red[i].r = std::round(i * params.combinedMatrix[redIndex][0]); + red[i].g = std::round(i * params.combinedMatrix[greenIndex][0]); + red[i].b = std::round(i * params.combinedMatrix[blueIndex][0]); + green[i].r = std::round(i * params.combinedMatrix[redIndex][1]); + green[i].g = std::round(i * params.combinedMatrix[greenIndex][1]); + green[i].b = std::round(i * params.combinedMatrix[blueIndex][1]); + blue[i].r = std::round(i * params.combinedMatrix[redIndex][2]); + blue[i].g = std::round(i * params.combinedMatrix[greenIndex][2]); + blue[i].b = std::round(i * params.combinedMatrix[blueIndex][2]); + gammaLut_[i] = gammaTable_[i / div]; + } + } + } else { + if (gammaUpdateNeeded || params.gains != params_.gains) { + auto &gains = params.gains; + auto &red = swapRedBlueGains_ ? blue_ : red_; + auto &green = green_; + auto &blue = swapRedBlueGains_ ? red_ : blue_; + for (unsigned int i = 0; i < kRGBLookupSize; i++) { + /* Apply gamma after gain! */ + const RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1); + red[i] = gammaTable_[static_cast<unsigned int>(lutGains.r())]; + green[i] = gammaTable_[static_cast<unsigned int>(lutGains.g())]; + blue[i] = gammaTable_[static_cast<unsigned int>(lutGains.b())]; + } + } + } + + LOG(Debayer, Debug) + << "Debayer parameters: blackLevel=" << params.blackLevel + << "; gamma=" << params.gamma + << "; contrastExp=" << params.contrastExp + << "; gains=" << params.gains + << "; matrix=" << params.combinedMatrix; + + params_ = params; +} + void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params) { bench_.startFrame(); @@ -748,7 +850,7 @@ void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output dmaSyncBegin(dmaSyncers, input, output); - setParams(params); + updateLookupTables(params); /* Copy metadata from the input buffer */ FrameMetadata &metadata = output->_d()->metadata(); diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h index 67df2b93a..b5cbb5bd2 100644 --- a/src/libcamera/software_isp/debayer_cpu.h +++ b/src/libcamera/software_isp/debayer_cpu.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2023, Linaro Ltd - * Copyright (C) 2023-2025 Red Hat Inc. + * Copyright (C) 2023-2026 Red Hat Inc. * * Authors: * Hans de Goede <hdegoede@redhat.com> @@ -18,6 +18,8 @@ #include <libcamera/base/object.h> #include "libcamera/internal/bayer_format.h" +#include "libcamera/internal/global_configuration.h" +#include "libcamera/internal/software_isp/debayer_params.h" #include "libcamera/internal/software_isp/swstats_cpu.h" #include "debayer.h" @@ -108,10 +110,32 @@ private: void memcpyNextLine(const uint8_t *linePointers[]); void process2(uint32_t frame, const uint8_t *src, uint8_t *dst); void process4(uint32_t frame, const uint8_t *src, uint8_t *dst); + void updateGammaTable(DebayerParams ¶ms); + void updateLookupTables(DebayerParams ¶ms); /* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */ static constexpr unsigned int kMaxLineBuffers = 5; + static constexpr unsigned int kRGBLookupSize = 256; + static constexpr unsigned int kGammaLookupSize = 1024; + struct CcmColumn { + int16_t r; + int16_t g; + int16_t b; + }; + using LookupTable = std::array<uint8_t, kRGBLookupSize>; + using CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>; + LookupTable red_; + LookupTable green_; + LookupTable blue_; + CcmLookupTable redCcm_; + CcmLookupTable greenCcm_; + CcmLookupTable blueCcm_; + std::array<double, kGammaLookupSize> gammaTable_; + LookupTable gammaLut_; + bool ccmEnabled_; + DebayerParams params_; + debayerFn debayer0_; debayerFn debayer1_; debayerFn debayer2_; diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 37c44e8b1..722778103 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -475,18 +475,18 @@ void DebayerEGL::setShaderVariableValues(DebayerParams ¶ms) << " textureUniformProjMatrix_ " << textureUniformProjMatrix_; GLfloat ccm[9] = { - params.ccm[0][0], - params.ccm[0][1], - params.ccm[0][2], - params.ccm[1][0], - params.ccm[1][1], - params.ccm[1][2], - params.ccm[2][0], - params.ccm[2][1], - params.ccm[2][2], + params.combinedMatrix[0][0], + params.combinedMatrix[0][1], + params.combinedMatrix[0][2], + params.combinedMatrix[1][0], + params.combinedMatrix[1][1], + params.combinedMatrix[1][2], + params.combinedMatrix[2][0], + params.combinedMatrix[2][1], + params.combinedMatrix[2][2], }; glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm); - LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.ccm; + LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; /* * 0 = Red, 1 = Green, 2 = Blue @@ -544,8 +544,6 @@ void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output dmaSyncBegin(dmaSyncers, input, nullptr); - setParams(params); - /* Copy metadata from the input buffer */ FrameMetadata &metadata = output->_d()->metadata(); metadata.status = input->metadata().status; diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp index 7ad3511db..a83986b78 100644 --- a/src/libcamera/software_isp/software_isp.cpp +++ b/src/libcamera/software_isp/software_isp.cpp @@ -84,23 +84,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap | DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf) { - /* - * debayerParams_ must be initialized because the initial value is used for - * the first two frames, i.e. until stats processing starts providing its - * own parameters. - * - * \todo This should be handled in the same place as the related - * operations, in the IPA module. - */ - std::array<uint8_t, 256> gammaTable; - for (unsigned int i = 0; i < 256; i++) - gammaTable[i] = UINT8_MAX * std::pow(i / 256.0, 0.5); - for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) { - debayerParams_.red[i] = gammaTable[i]; - debayerParams_.green[i] = gammaTable[i]; - debayerParams_.blue[i] = gammaTable[i]; - } - if (!dmaHeap_.isValid()) { LOG(SoftwareIsp, Error) << "Failed to create DmaBufAllocator object"; return; @@ -121,8 +104,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, } stats->statsReady.connect(this, &SoftwareIsp::statsReady); - bool gpuIspEnabled; - #if HAVE_DEBAYER_EGL std::optional<std::string> softISPMode = configuration.envOption("LIBCAMERA_SOFTISP_MODE", { "software_isp", "mode" }); if (softISPMode) { @@ -133,15 +114,12 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, } } - if (!softISPMode || softISPMode == "gpu") { + if (!softISPMode || softISPMode == "gpu") debayer_ = std::make_unique<DebayerEGL>(std::move(stats), configuration); - gpuIspEnabled = true; - } + #endif - if (!debayer_) { + if (!debayer_) debayer_ = std::make_unique<DebayerCpu>(std::move(stats), configuration); - gpuIspEnabled = false; - } debayer_->inputBufferReady.connect(this, &SoftwareIsp::inputReady); debayer_->outputBufferReady.connect(this, &SoftwareIsp::outputReady); @@ -173,7 +151,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, sharedParams_.fd(), sensorInfo, sensor->controls(), - gpuIspEnabled, ipaControls, &ccmEnabled_); if (ret) {