diff options
Diffstat (limited to 'src/ipa/rkisp1')
38 files changed, 1797 insertions, 421 deletions
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 50e0690f..17d074d9 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -10,6 +10,8 @@ #include <algorithm> #include <chrono> #include <cmath> +#include <tuple> +#include <vector> #include <libcamera/base/log.h> #include <libcamera/base/utils.h> @@ -17,6 +19,8 @@ #include <libcamera/control_ids.h> #include <libcamera/ipa/core_ipa_interface.h> +#include "libcamera/internal/yaml_parser.h" + #include "libipa/histogram.h" /** @@ -36,6 +40,86 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Agc) +int Agc::parseMeteringModes(IPAContext &context, const YamlObject &tuningData) +{ + if (!tuningData.isDictionary()) + LOG(RkISP1Agc, Warning) + << "'AeMeteringMode' parameter not found in tuning file"; + + for (const auto &[key, value] : tuningData.asDict()) { + if (controls::AeMeteringModeNameValueMap.find(key) == + controls::AeMeteringModeNameValueMap.end()) { + LOG(RkISP1Agc, Warning) + << "Skipping unknown metering mode '" << key << "'"; + continue; + } + + std::vector<uint8_t> weights = + value.getList<uint8_t>().value_or(std::vector<uint8_t>{}); + if (weights.size() != context.hw->numHistogramWeights) { + LOG(RkISP1Agc, Warning) + << "Failed to read metering mode'" << key << "'"; + continue; + } + + meteringModes_[controls::AeMeteringModeNameValueMap.at(key)] = weights; + } + + if (meteringModes_.empty()) { + LOG(RkISP1Agc, Warning) + << "No metering modes read from tuning file; defaulting to matrix"; + int32_t meteringModeId = controls::AeMeteringModeNameValueMap.at("MeteringMatrix"); + std::vector<uint8_t> weights(context.hw->numHistogramWeights, 1); + + meteringModes_[meteringModeId] = weights; + } + + std::vector<ControlValue> meteringModes; + std::vector<int> meteringModeKeys = utils::map_keys(meteringModes_); + std::transform(meteringModeKeys.begin(), meteringModeKeys.end(), + std::back_inserter(meteringModes), + [](int x) { return ControlValue(x); }); + context.ctrlMap[&controls::AeMeteringMode] = ControlInfo(meteringModes); + + return 0; +} + +uint8_t Agc::computeHistogramPredivider(const Size &size, + enum rkisp1_cif_isp_histogram_mode mode) +{ + /* + * The maximum number of pixels that could potentially be in one bin is + * if all the pixels of the image are in it, multiplied by 3 for the + * three color channels. The counter for each bin is 16 bits wide, so + * `factor` thus contains the number of times we'd wrap around. This is + * obviously the number of pixels that we need to skip to make sure + * that we don't wrap around, but we compute the square root of it + * instead, as the skip that we need to program is for both the x and y + * directions. + * + * Even though it looks like dividing into a counter of 65536 would + * overflow by 1, this is apparently fine according to the hardware + * documentation, and this successfully gets the expected documented + * predivider size for cases where: + * (width / predivider) * (height / predivider) * 3 == 65536. + * + * There's a bit of extra rounding math to make sure the rounding goes + * the correct direction so that the square of the step is big enough + * to encompass the `factor` number of pixels that we need to skip. + * + * \todo Take into account weights. That is, if the weights are low + * enough we can potentially reduce the predivider to increase + * precision. This needs some investigation however, as this hardware + * behavior is undocumented and is only an educated guess. + */ + int count = mode == RKISP1_CIF_ISP_HISTOGRAM_MODE_RGB_COMBINED ? 3 : 1; + double factor = size.width * size.height * count / 65536.0; + double root = std::sqrt(factor); + uint8_t predivider = static_cast<uint8_t>(std::ceil(root)); + + return std::clamp<uint8_t>(predivider, 3, 127); +} + Agc::Agc() { supportsRaw_ = true; @@ -59,6 +143,12 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData) if (ret) return ret; + const YamlObject &yamlMeteringModes = tuningData["AeMeteringMode"]; + ret = parseMeteringModes(context, yamlMeteringModes); + if (ret) + return ret; + + context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); context.ctrlMap.merge(controls()); return 0; @@ -81,8 +171,19 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure; context.activeState.agc.autoEnabled = !context.configuration.raw; - context.activeState.agc.constraintMode = constraintModes().begin()->first; - context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first; + context.activeState.agc.constraintMode = + static_cast<controls::AeConstraintModeEnum>(constraintModes().begin()->first); + context.activeState.agc.exposureMode = + static_cast<controls::AeExposureModeEnum>(exposureModeHelpers().begin()->first); + context.activeState.agc.meteringMode = + static_cast<controls::AeMeteringModeEnum>(meteringModes_.begin()->first); + + /* + * \todo This should probably come from FrameDurationLimits instead, + * except it's computed in the IPA and not here so we'd have to + * recompute it. + */ + context.activeState.agc.maxFrameDuration = context.configuration.sensor.maxShutterSpeed; /* * Define the measurement window for AGC as a centered rectangle @@ -93,7 +194,6 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) context.configuration.agc.measureWindow.h_size = 3 * configInfo.outputSize.width / 4; context.configuration.agc.measureWindow.v_size = 3 * configInfo.outputSize.height / 4; - /* \todo Run this again when FrameDurationLimits is passed in */ setLimits(context.configuration.sensor.minShutterSpeed, context.configuration.sensor.maxShutterSpeed, context.configuration.sensor.minAnalogueGain, @@ -147,51 +247,83 @@ void Agc::queueRequest(IPAContext &context, frameContext.agc.exposure = agc.manual.exposure; frameContext.agc.gain = agc.manual.gain; } + + const auto &meteringMode = controls.get(controls::AeMeteringMode); + if (meteringMode) { + frameContext.agc.updateMetering = agc.meteringMode != *meteringMode; + agc.meteringMode = + static_cast<controls::AeMeteringModeEnum>(*meteringMode); + } + frameContext.agc.meteringMode = agc.meteringMode; + + const auto &exposureMode = controls.get(controls::AeExposureMode); + if (exposureMode) + agc.exposureMode = + static_cast<controls::AeExposureModeEnum>(*exposureMode); + frameContext.agc.exposureMode = agc.exposureMode; + + const auto &constraintMode = controls.get(controls::AeConstraintMode); + if (constraintMode) + agc.constraintMode = + static_cast<controls::AeConstraintModeEnum>(*constraintMode); + frameContext.agc.constraintMode = agc.constraintMode; + + const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); + if (frameDurationLimits) { + utils::Duration maxFrameDuration = + std::chrono::milliseconds((*frameDurationLimits).back()); + agc.maxFrameDuration = maxFrameDuration; + } + frameContext.agc.maxFrameDuration = agc.maxFrameDuration; } /** * \copydoc libcamera::ipa::Algorithm::prepare */ void Agc::prepare(IPAContext &context, const uint32_t frame, - IPAFrameContext &frameContext, rkisp1_params_cfg *params) + IPAFrameContext &frameContext, RkISP1Params *params) { if (frameContext.agc.autoEnabled) { frameContext.agc.exposure = context.activeState.agc.automatic.exposure; frameContext.agc.gain = context.activeState.agc.automatic.gain; } - if (frame > 0) + if (frame > 0 && !frameContext.agc.updateMetering) return; - /* Configure the measurement window. */ - params->meas.aec_config.meas_window = context.configuration.agc.measureWindow; - /* Use a continuous method for measure. */ - params->meas.aec_config.autostop = RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0; - /* Estimate Y as (R + G + B) x (85/256). */ - params->meas.aec_config.mode = RKISP1_CIF_ISP_EXP_MEASURING_MODE_1; - - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AEC; - params->module_ens |= RKISP1_CIF_ISP_MODULE_AEC; - params->module_en_update |= RKISP1_CIF_ISP_MODULE_AEC; - - /* Configure histogram. */ - params->meas.hst_config.meas_window = context.configuration.agc.measureWindow; - /* Produce the luminance histogram. */ - params->meas.hst_config.mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM; - /* Set an average weighted histogram. */ + /* + * Configure the AEC measurements. Set the window, measure + * continuously, and estimate Y as (R + G + B) x (85/256). + */ + auto aecConfig = params->block<BlockType::Aec>(); + aecConfig.setEnabled(true); + + aecConfig->meas_window = context.configuration.agc.measureWindow; + aecConfig->autostop = RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0; + aecConfig->mode = RKISP1_CIF_ISP_EXP_MEASURING_MODE_1; + + /* + * Configure the histogram measurement. Set the window, produce a + * luminance histogram, and set the weights and predivider. + */ + auto hstConfig = params->block<BlockType::Hst>(); + hstConfig.setEnabled(true); + + hstConfig->meas_window = context.configuration.agc.measureWindow; + hstConfig->mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM; + Span<uint8_t> weights{ - params->meas.hst_config.hist_weight, + hstConfig->hist_weight, context.hw->numHistogramWeights }; - std::fill(weights.begin(), weights.end(), 1); - /* Step size can't be less than 3. */ - params->meas.hst_config.histogram_predivider = 4; - - /* Update the configuration for histogram. */ - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_HST; - /* Enable the histogram measure unit. */ - params->module_ens |= RKISP1_CIF_ISP_MODULE_HST; - params->module_en_update |= RKISP1_CIF_ISP_MODULE_HST; + std::vector<uint8_t> &modeWeights = meteringModes_.at(frameContext.agc.meteringMode); + std::copy(modeWeights.begin(), modeWeights.end(), weights.begin()); + + struct rkisp1_cif_isp_window window = hstConfig->meas_window; + Size windowSize = { window.h_size, window.v_size }; + hstConfig->histogram_predivider = + computeHistogramPredivider(windowSize, + static_cast<rkisp1_cif_isp_histogram_mode>(hstConfig->mode)); } void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, @@ -201,6 +333,7 @@ void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, * frameContext.sensor.exposure; metadata.set(controls::AnalogueGain, frameContext.sensor.gain); metadata.set(controls::ExposureTime, exposureTime.get<std::micro>()); + metadata.set(controls::AeEnable, frameContext.agc.autoEnabled); /* \todo Use VBlank value calculated from each frame exposure. */ uint32_t vTotal = context.configuration.sensor.size.height @@ -208,6 +341,10 @@ void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, utils::Duration frameDuration = context.configuration.sensor.lineDuration * vTotal; metadata.set(controls::FrameDuration, frameDuration.get<std::micro>()); + + metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); + metadata.set(controls::AeExposureMode, frameContext.agc.exposureMode); + metadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode); } /** @@ -282,6 +419,15 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, [](uint32_t x) { return x >> 4; }); expMeans_ = { params->ae.exp_mean, context.hw->numAeCells }; + utils::Duration maxShutterSpeed = + std::clamp(frameContext.agc.maxFrameDuration, + context.configuration.sensor.minShutterSpeed, + context.configuration.sensor.maxShutterSpeed); + setLimits(context.configuration.sensor.minShutterSpeed, + maxShutterSpeed, + context.configuration.sensor.minAnalogueGain, + context.configuration.sensor.maxAnalogueGain); + /* * The Agc algorithm needs to know the effective exposure value that was * applied to the sensor when the statistics were collected. @@ -294,8 +440,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, utils::Duration shutterTime; double aGain, dGain; std::tie(shutterTime, aGain, dGain) = - calculateNewEv(context.activeState.agc.constraintMode, - context.activeState.agc.exposureMode, + calculateNewEv(frameContext.agc.constraintMode, + frameContext.agc.exposureMode, hist, effectiveExposureValue); LOG(RkISP1Agc, Debug) diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h index 04b3247e..aa86f2c5 100644 --- a/src/ipa/rkisp1/algorithms/agc.h +++ b/src/ipa/rkisp1/algorithms/agc.h @@ -15,7 +15,6 @@ #include <libcamera/geometry.h> #include "libipa/agc_mean_luminance.h" -#include "libipa/histogram.h" #include "algorithm.h" @@ -37,18 +36,24 @@ public: const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, ControlList &metadata) override; private: + int parseMeteringModes(IPAContext &context, const YamlObject &tuningData); + uint8_t computeHistogramPredivider(const Size &size, + enum rkisp1_cif_isp_histogram_mode mode); + void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, ControlList &metadata); double estimateLuminance(double gain) const override; Span<const uint8_t> expMeans_; + + std::map<int32_t, std::vector<uint8_t>> meteringModes_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index a01fe5d9..955a9ff4 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -8,12 +8,12 @@ #include "awb.h" #include <algorithm> -#include <cmath> -#include <iomanip> +#include <ios> #include <libcamera/base/log.h> #include <libcamera/control_ids.h> + #include <libcamera/ipa/core_ipa_interface.h> /** @@ -108,7 +108,7 @@ void Awb::queueRequest(IPAContext &context, * \copydoc libcamera::ipa::Algorithm::prepare */ void Awb::prepare(IPAContext &context, const uint32_t frame, - IPAFrameContext &frameContext, rkisp1_params_cfg *params) + IPAFrameContext &frameContext, RkISP1Params *params) { /* * This is the latest time we can read the active state. This is the @@ -120,29 +120,30 @@ void Awb::prepare(IPAContext &context, const uint32_t frame, frameContext.awb.gains.blue = context.activeState.awb.gains.automatic.blue; } - params->others.awb_gain_config.gain_green_b = 256 * frameContext.awb.gains.green; - params->others.awb_gain_config.gain_blue = 256 * frameContext.awb.gains.blue; - params->others.awb_gain_config.gain_red = 256 * frameContext.awb.gains.red; - params->others.awb_gain_config.gain_green_r = 256 * frameContext.awb.gains.green; + auto gainConfig = params->block<BlockType::AwbGain>(); + gainConfig.setEnabled(true); - /* Update the gains. */ - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AWB_GAIN; + gainConfig->gain_green_b = std::clamp<int>(256 * frameContext.awb.gains.green, 0, 0x3ff); + gainConfig->gain_blue = std::clamp<int>(256 * frameContext.awb.gains.blue, 0, 0x3ff); + gainConfig->gain_red = std::clamp<int>(256 * frameContext.awb.gains.red, 0, 0x3ff); + gainConfig->gain_green_r = std::clamp<int>(256 * frameContext.awb.gains.green, 0, 0x3ff); /* If we have already set the AWB measurement parameters, return. */ if (frame > 0) return; - rkisp1_cif_isp_awb_meas_config &awb_config = params->meas.awb_meas_config; + auto awbConfig = params->block<BlockType::Awb>(); + awbConfig.setEnabled(true); /* Configure the measure window for AWB. */ - awb_config.awb_wnd = context.configuration.awb.measureWindow; + awbConfig->awb_wnd = context.configuration.awb.measureWindow; /* Number of frames to use to estimate the means (0 means 1 frame). */ - awb_config.frames = 0; + awbConfig->frames = 0; /* Select RGB or YCbCr means measurement. */ if (rgbMode_) { - awb_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_RGB; + awbConfig->awb_mode = RKISP1_CIF_ISP_AWB_MODE_RGB; /* * For RGB-based measurements, pixels are selected with maximum @@ -150,19 +151,19 @@ void Awb::prepare(IPAContext &context, const uint32_t frame, * awb_ref_cr, awb_min_y and awb_ref_cb respectively. The other * values are not used, set them to 0. */ - awb_config.awb_ref_cr = 250; - awb_config.min_y = 250; - awb_config.awb_ref_cb = 250; + awbConfig->awb_ref_cr = 250; + awbConfig->min_y = 250; + awbConfig->awb_ref_cb = 250; - awb_config.max_y = 0; - awb_config.min_c = 0; - awb_config.max_csum = 0; + awbConfig->max_y = 0; + awbConfig->min_c = 0; + awbConfig->max_csum = 0; } else { - awb_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_YCBCR; + awbConfig->awb_mode = RKISP1_CIF_ISP_AWB_MODE_YCBCR; /* Set the reference Cr and Cb (AWB target) to white. */ - awb_config.awb_ref_cb = 128; - awb_config.awb_ref_cr = 128; + awbConfig->awb_ref_cb = 128; + awbConfig->awb_ref_cr = 128; /* * Filter out pixels based on luminance and chrominance values. @@ -170,20 +171,11 @@ void Awb::prepare(IPAContext &context, const uint32_t frame, * range, while the acceptable chroma values are specified with * a minimum of 16 and a maximum Cb+Cr sum of 250. */ - awb_config.min_y = 16; - awb_config.max_y = 250; - awb_config.min_c = 16; - awb_config.max_csum = 250; + awbConfig->min_y = 16; + awbConfig->max_y = 250; + awbConfig->min_c = 16; + awbConfig->max_csum = 250; } - - /* Enable the AWB gains. */ - params->module_en_update |= RKISP1_CIF_ISP_MODULE_AWB_GAIN; - params->module_ens |= RKISP1_CIF_ISP_MODULE_AWB_GAIN; - - /* Update the AWB measurement parameters and enable the AWB module. */ - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AWB; - params->module_en_update |= RKISP1_CIF_ISP_MODULE_AWB; - params->module_ens |= RKISP1_CIF_ISP_MODULE_AWB; } uint32_t Awb::estimateCCT(double red, double green, double blue) @@ -218,6 +210,12 @@ void Awb::process(IPAContext &context, double redMean; double blueMean; + metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled); + metadata.set(controls::ColourGains, { + static_cast<float>(frameContext.awb.gains.red), + static_cast<float>(frameContext.awb.gains.blue) + }); + if (rgbMode_) { greenMean = awb->awb_mean[0].mean_y_or_g; redMean = awb->awb_mean[0].mean_cr_or_r; @@ -273,12 +271,15 @@ void Awb::process(IPAContext &context, */ if (redMean < kMeanMinThreshold && greenMean < kMeanMinThreshold && blueMean < kMeanMinThreshold) { - frameContext.awb.temperatureK = activeState.awb.temperatureK; + metadata.set(controls::ColourTemperature, activeState.awb.temperatureK); return; } activeState.awb.temperatureK = estimateCCT(redMean, greenMean, blueMean); + /* Metadata shall contain the up to date measurement */ + metadata.set(controls::ColourTemperature, activeState.awb.temperatureK); + /* * Estimate the red and blue gains to apply in a grey world. The green * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the @@ -305,21 +306,13 @@ void Awb::process(IPAContext &context, activeState.awb.gains.automatic.blue = blueGain; activeState.awb.gains.automatic.green = 1.0; - frameContext.awb.temperatureK = activeState.awb.temperatureK; - - metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled); - metadata.set(controls::ColourGains, { - static_cast<float>(frameContext.awb.gains.red), - static_cast<float>(frameContext.awb.gains.blue) - }); - metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK); - - LOG(RkISP1Awb, Debug) << std::showpoint + LOG(RkISP1Awb, Debug) + << std::showpoint << "Means [" << redMean << ", " << greenMean << ", " << blueMean << "], gains [" << activeState.awb.gains.automatic.red << ", " << activeState.awb.gains.automatic.green << ", " << activeState.awb.gains.automatic.blue << "], temp " - << frameContext.awb.temperatureK << "K"; + << activeState.awb.temperatureK << "K"; } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h index 06c92896..b3b2c0bb 100644 --- a/src/ipa/rkisp1/algorithms/awb.h +++ b/src/ipa/rkisp1/algorithms/awb.h @@ -25,7 +25,7 @@ public: const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp index d2e74354..98cb7145 100644 --- a/src/ipa/rkisp1/algorithms/blc.cpp +++ b/src/ipa/rkisp1/algorithms/blc.cpp @@ -7,8 +7,12 @@ #include "blc.h" +#include <linux/videodev2.h> + #include <libcamera/base/log.h> +#include <libcamera/control_ids.h> + #include "libcamera/internal/yaml_parser.h" /** @@ -36,22 +40,62 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Blc) BlackLevelCorrection::BlackLevelCorrection() - : tuningParameters_(false) { + /* + * This is a bit of a hack. In raw mode no black level correction + * happens. This flag is used to ensure the metadata gets populated with + * the black level which is needed to capture proper raw images for + * tuning. + */ + supportsRaw_ = true; } /** * \copydoc libcamera::ipa::Algorithm::init */ -int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context, - const YamlObject &tuningData) +int BlackLevelCorrection::init(IPAContext &context, const YamlObject &tuningData) { - blackLevelRed_ = tuningData["R"].get<int16_t>(256); - blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256); - blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256); - blackLevelBlue_ = tuningData["B"].get<int16_t>(256); - - tuningParameters_ = true; + std::optional<int16_t> levelRed = tuningData["R"].get<int16_t>(); + std::optional<int16_t> levelGreenR = tuningData["Gr"].get<int16_t>(); + std::optional<int16_t> levelGreenB = tuningData["Gb"].get<int16_t>(); + std::optional<int16_t> levelBlue = tuningData["B"].get<int16_t>(); + bool tuningHasLevels = levelRed && levelGreenR && levelGreenB && levelBlue; + + auto blackLevel = context.camHelper->blackLevel(); + if (!blackLevel) { + /* + * Not all camera sensor helpers have been updated with black + * levels. Print a warning and fall back to the levels from the + * tuning data to preserve backward compatibility. This should + * be removed once all helpers provide the data. + */ + LOG(RkISP1Blc, Warning) + << "No black levels provided by camera sensor helper" + << ", please fix"; + + blackLevelRed_ = levelRed.value_or(4096); + blackLevelGreenR_ = levelGreenR.value_or(4096); + blackLevelGreenB_ = levelGreenB.value_or(4096); + blackLevelBlue_ = levelBlue.value_or(4096); + } else if (tuningHasLevels) { + /* + * If black levels are provided in the tuning file, use them to + * avoid breaking existing camera tuning. This is deprecated and + * will be removed. + */ + LOG(RkISP1Blc, Warning) + << "Deprecated: black levels overwritten by tuning file"; + + blackLevelRed_ = *levelRed; + blackLevelGreenR_ = *levelGreenR; + blackLevelGreenB_ = *levelGreenB; + blackLevelBlue_ = *levelBlue; + } else { + blackLevelRed_ = *blackLevel; + blackLevelGreenR_ = *blackLevel; + blackLevelGreenB_ = *blackLevel; + blackLevelBlue_ = *blackLevel; + } LOG(RkISP1Blc, Debug) << "Black levels: red " << blackLevelRed_ @@ -62,29 +106,80 @@ int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context, return 0; } +int BlackLevelCorrection::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + /* + * BLC on ISP versions that include the companding block requires usage + * of the extensible parameters format. + */ + supported_ = context.configuration.paramFormat == V4L2_META_FMT_RK_ISP1_EXT_PARAMS || + !context.hw->compand; + + if (!supported_) + LOG(RkISP1Blc, Warning) + << "BLC in companding block requires extensible parameters"; + + return 0; +} + /** * \copydoc libcamera::ipa::Algorithm::prepare */ -void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context, +void BlackLevelCorrection::prepare(IPAContext &context, const uint32_t frame, [[maybe_unused]] IPAFrameContext &frameContext, - rkisp1_params_cfg *params) + RkISP1Params *params) { + if (context.configuration.raw) + return; + if (frame > 0) return; - if (!tuningParameters_) + if (!supported_) return; - params->others.bls_config.enable_auto = 0; - params->others.bls_config.fixed_val.r = blackLevelRed_; - params->others.bls_config.fixed_val.gr = blackLevelGreenR_; - params->others.bls_config.fixed_val.gb = blackLevelGreenB_; - params->others.bls_config.fixed_val.b = blackLevelBlue_; + if (context.hw->compand) { + auto config = params->block<BlockType::CompandBls>(); + config.setEnabled(true); + + /* + * Scale up to the 20-bit black levels used by the companding + * block. + */ + config->r = blackLevelRed_ << 4; + config->gr = blackLevelGreenR_ << 4; + config->gb = blackLevelGreenB_ << 4; + config->b = blackLevelBlue_ << 4; + } else { + auto config = params->block<BlockType::Bls>(); + config.setEnabled(true); + + config->enable_auto = 0; + + /* Scale down to the 12-bit black levels used by the BLS block. */ + config->fixed_val.r = blackLevelRed_ >> 4; + config->fixed_val.gr = blackLevelGreenR_ >> 4; + config->fixed_val.gb = blackLevelGreenB_ >> 4; + config->fixed_val.b = blackLevelBlue_ >> 4; + } +} - params->module_en_update |= RKISP1_CIF_ISP_MODULE_BLS; - params->module_ens |= RKISP1_CIF_ISP_MODULE_BLS; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_BLS; +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void BlackLevelCorrection::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + [[maybe_unused]] const rkisp1_stat_buffer *stats, + ControlList &metadata) +{ + metadata.set(controls::SensorBlackLevels, + { static_cast<int32_t>(blackLevelRed_), + static_cast<int32_t>(blackLevelGreenR_), + static_cast<int32_t>(blackLevelGreenB_), + static_cast<int32_t>(blackLevelBlue_) }); } REGISTER_IPA_ALGORITHM(BlackLevelCorrection, "BlackLevelCorrection") diff --git a/src/ipa/rkisp1/algorithms/blc.h b/src/ipa/rkisp1/algorithms/blc.h index 460ebcc1..f797ae44 100644 --- a/src/ipa/rkisp1/algorithms/blc.h +++ b/src/ipa/rkisp1/algorithms/blc.h @@ -20,12 +20,19 @@ public: ~BlackLevelCorrection() = default; int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) override; private: - bool tuningParameters_; + bool supported_; + int16_t blackLevelRed_; int16_t blackLevelGreenR_; int16_t blackLevelGreenB_; diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp new file mode 100644 index 00000000..6b7d2e2c --- /dev/null +++ b/src/ipa/rkisp1/algorithms/ccm.cpp @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * RkISP1 Color Correction Matrix control algorithm + */ + +#include "ccm.h" + +#include <map> + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +#include <libcamera/control_ids.h> + +#include <libcamera/ipa/core_ipa_interface.h> + +#include "libcamera/internal/yaml_parser.h" + +#include "../utils.h" +#include "libipa/interpolator.h" + +/** + * \file ccm.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +/** + * \class Ccm + * \brief A color correction matrix algorithm + */ + +LOG_DEFINE_CATEGORY(RkISP1Ccm) + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) +{ + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); + if (ret < 0) { + LOG(RkISP1Ccm, Warning) + << "Failed to parse 'ccm' " + << "parameter from tuning file; falling back to unit matrix"; + ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } }); + } + + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); + if (ret < 0) { + LOG(RkISP1Ccm, Warning) + << "Failed to parse 'offsets' " + << "parameter from tuning file; falling back to zero offsets"; + + offsets_.setData({ { 0, Matrix<int16_t, 3, 1>({ 0, 0, 0 }) } }); + } + + return 0; +} + +void Ccm::setParameters(struct rkisp1_cif_isp_ctk_config &config, + const Matrix<float, 3, 3> &matrix, + const Matrix<int16_t, 3, 1> &offsets) +{ + /* + * 4 bit integer and 7 bit fractional, ranging from -8 (0x400) to + * +7.992 (0x3ff) + */ + for (unsigned int i = 0; i < 3; i++) { + for (unsigned int j = 0; j < 3; j++) + config.coeff[i][j] = + utils::floatingToFixedPoint<4, 7, uint16_t, double>(matrix[i][j]); + } + + for (unsigned int i = 0; i < 3; i++) + config.ct_offset[i] = offsets[i][0] & 0xfff; + + LOG(RkISP1Ccm, Debug) << "Setting matrix " << matrix; + LOG(RkISP1Ccm, Debug) << "Setting offsets " << offsets; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Ccm::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, RkISP1Params *params) +{ + uint32_t ct = context.activeState.awb.temperatureK; + + /* + * \todo The colour temperature will likely be noisy, add filtering to + * avoid updating the CCM matrix all the time. + */ + if (frame > 0 && ct == ct_) { + frameContext.ccm.ccm = context.activeState.ccm.ccm; + return; + } + + ct_ = ct; + Matrix<float, 3, 3> ccm = ccm_.getInterpolated(ct); + Matrix<int16_t, 3, 1> offsets = offsets_.getInterpolated(ct); + + context.activeState.ccm.ccm = ccm; + frameContext.ccm.ccm = ccm; + + auto config = params->block<BlockType::Ctk>(); + config.setEnabled(true); + setParameters(*config, ccm, offsets); +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Ccm::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const rkisp1_stat_buffer *stats, + ControlList &metadata) +{ + float m[9]; + for (unsigned int i = 0; i < 3; i++) { + for (unsigned int j = 0; j < 3; j++) + m[i * 3 + j] = frameContext.ccm.ccm[i][j]; + } + metadata.set(controls::ColourCorrectionMatrix, m); +} + +REGISTER_IPA_ALGORITHM(Ccm, "Ccm") + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/ccm.h b/src/ipa/rkisp1/algorithms/ccm.h new file mode 100644 index 00000000..46a1416e --- /dev/null +++ b/src/ipa/rkisp1/algorithms/ccm.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * RkISP1 Color Correction Matrix control algorithm + */ + +#pragma once + +#include <linux/rkisp1-config.h> + +#include "libipa/interpolator.h" +#include "libipa/matrix.h" + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +class Ccm : public Algorithm +{ +public: + Ccm() {} + ~Ccm() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RkISP1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) override; + +private: + void parseYaml(const YamlObject &tuningData); + void setParameters(struct rkisp1_cif_isp_ctk_config &config, + const Matrix<float, 3, 3> &matrix, + const Matrix<int16_t, 3, 1> &offsets); + + unsigned int ct_; + Interpolator<Matrix<float, 3, 3>> ccm_; + Interpolator<Matrix<int16_t, 3, 1>> offsets_; +}; + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp index 68bb8180..d1fff699 100644 --- a/src/ipa/rkisp1/algorithms/cproc.cpp +++ b/src/ipa/rkisp1/algorithms/cproc.cpp @@ -33,20 +33,71 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1CProc) +namespace { + +constexpr float kDefaultBrightness = 0.0f; +constexpr float kDefaultContrast = 1.0f; +constexpr float kDefaultSaturation = 1.0f; + +int convertBrightness(const float v) +{ + return std::clamp<int>(std::lround(v * 128), -128, 127); +} + +int convertContrastOrSaturation(const float v) +{ + return std::clamp<int>(std::lround(v * 128), 0, 255); +} + +} /* namespace */ + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int ColorProcessing::init(IPAContext &context, + [[maybe_unused]] const YamlObject &tuningData) +{ + auto &cmap = context.ctrlMap; + + cmap[&controls::Brightness] = ControlInfo(-1.0f, 0.993f, kDefaultBrightness); + cmap[&controls::Contrast] = ControlInfo(0.0f, 1.993f, kDefaultContrast); + cmap[&controls::Saturation] = ControlInfo(0.0f, 1.993f, kDefaultSaturation); + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int ColorProcessing::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + auto &cproc = context.activeState.cproc; + + cproc.brightness = convertBrightness(kDefaultBrightness); + cproc.contrast = convertContrastOrSaturation(kDefaultContrast); + cproc.saturation = convertContrastOrSaturation(kDefaultSaturation); + + return 0; +} + /** * \copydoc libcamera::ipa::Algorithm::queueRequest */ void ColorProcessing::queueRequest(IPAContext &context, - [[maybe_unused]] const uint32_t frame, + const uint32_t frame, IPAFrameContext &frameContext, const ControlList &controls) { auto &cproc = context.activeState.cproc; bool update = false; + if (frame == 0) + update = true; + const auto &brightness = controls.get(controls::Brightness); if (brightness) { - int value = std::clamp<int>(std::lround(*brightness * 128), -128, 127); + int value = convertBrightness(*brightness); if (cproc.brightness != value) { cproc.brightness = value; update = true; @@ -57,7 +108,7 @@ void ColorProcessing::queueRequest(IPAContext &context, const auto &contrast = controls.get(controls::Contrast); if (contrast) { - int value = std::clamp<int>(std::lround(*contrast * 128), 0, 255); + int value = convertContrastOrSaturation(*contrast); if (cproc.contrast != value) { cproc.contrast = value; update = true; @@ -68,7 +119,7 @@ void ColorProcessing::queueRequest(IPAContext &context, const auto saturation = controls.get(controls::Saturation); if (saturation) { - int value = std::clamp<int>(std::lround(*saturation * 128), 0, 255); + int value = convertContrastOrSaturation(*saturation); if (cproc.saturation != value) { cproc.saturation = value; update = true; @@ -89,19 +140,17 @@ void ColorProcessing::queueRequest(IPAContext &context, void ColorProcessing::prepare([[maybe_unused]] IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) + RkISP1Params *params) { /* Check if the algorithm configuration has been updated. */ if (!frameContext.cproc.update) return; - params->others.cproc_config.brightness = frameContext.cproc.brightness; - params->others.cproc_config.contrast = frameContext.cproc.contrast; - params->others.cproc_config.sat = frameContext.cproc.saturation; - - params->module_en_update |= RKISP1_CIF_ISP_MODULE_CPROC; - params->module_ens |= RKISP1_CIF_ISP_MODULE_CPROC; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_CPROC; + auto config = params->block<BlockType::Cproc>(); + config.setEnabled(true); + config->brightness = frameContext.cproc.brightness; + config->contrast = frameContext.cproc.contrast; + config->sat = frameContext.cproc.saturation; } REGISTER_IPA_ALGORITHM(ColorProcessing, "ColorProcessing") diff --git a/src/ipa/rkisp1/algorithms/cproc.h b/src/ipa/rkisp1/algorithms/cproc.h index bafba5cc..fd38fd17 100644 --- a/src/ipa/rkisp1/algorithms/cproc.h +++ b/src/ipa/rkisp1/algorithms/cproc.h @@ -21,12 +21,15 @@ public: ColorProcessing() = default; ~ColorProcessing() = default; + int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; void queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/dpcc.cpp b/src/ipa/rkisp1/algorithms/dpcc.cpp index b5a339e9..78946281 100644 --- a/src/ipa/rkisp1/algorithms/dpcc.cpp +++ b/src/ipa/rkisp1/algorithms/dpcc.cpp @@ -232,16 +232,14 @@ int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context, void DefectPixelClusterCorrection::prepare([[maybe_unused]] IPAContext &context, const uint32_t frame, [[maybe_unused]] IPAFrameContext &frameContext, - rkisp1_params_cfg *params) + RkISP1Params *params) { if (frame > 0) return; - params->others.dpcc_config = config_; - - params->module_en_update |= RKISP1_CIF_ISP_MODULE_DPCC; - params->module_ens |= RKISP1_CIF_ISP_MODULE_DPCC; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPCC; + auto config = params->block<BlockType::Dpcc>(); + config.setEnabled(true); + *config = config_; } REGISTER_IPA_ALGORITHM(DefectPixelClusterCorrection, "DefectPixelClusterCorrection") diff --git a/src/ipa/rkisp1/algorithms/dpcc.h b/src/ipa/rkisp1/algorithms/dpcc.h index d39b7bed..b77766c3 100644 --- a/src/ipa/rkisp1/algorithms/dpcc.h +++ b/src/ipa/rkisp1/algorithms/dpcc.h @@ -22,7 +22,7 @@ public: int init(IPAContext &context, const YamlObject &tuningData) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; private: rkisp1_cif_isp_dpcc_config config_; diff --git a/src/ipa/rkisp1/algorithms/dpf.cpp b/src/ipa/rkisp1/algorithms/dpf.cpp index abf95728..cb6095da 100644 --- a/src/ipa/rkisp1/algorithms/dpf.cpp +++ b/src/ipa/rkisp1/algorithms/dpf.cpp @@ -7,7 +7,9 @@ #include "dpf.h" -#include <cmath> +#include <algorithm> +#include <string> +#include <vector> #include <libcamera/base/log.h> @@ -215,15 +217,21 @@ void Dpf::queueRequest(IPAContext &context, * \copydoc libcamera::ipa::Algorithm::prepare */ void Dpf::prepare(IPAContext &context, const uint32_t frame, - IPAFrameContext &frameContext, rkisp1_params_cfg *params) + IPAFrameContext &frameContext, RkISP1Params *params) { - if (frame == 0) { - params->others.dpf_config = config_; - params->others.dpf_strength_config = strengthConfig_; + if (!frameContext.dpf.update && frame > 0) + return; + + auto config = params->block<BlockType::Dpf>(); + config.setEnabled(frameContext.dpf.denoise); + + if (frameContext.dpf.denoise) { + *config = config_; const auto &awb = context.configuration.awb; const auto &lsc = context.configuration.lsc; - auto &mode = params->others.dpf_config.gain.mode; + + auto &mode = config->gain.mode; /* * The DPF needs to take into account the total amount of @@ -241,15 +249,12 @@ void Dpf::prepare(IPAContext &context, const uint32_t frame, mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_LSC_GAINS; else mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_DISABLED; - - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPF | - RKISP1_CIF_ISP_MODULE_DPF_STRENGTH; } - if (frameContext.dpf.update) { - params->module_en_update |= RKISP1_CIF_ISP_MODULE_DPF; - if (frameContext.dpf.denoise) - params->module_ens |= RKISP1_CIF_ISP_MODULE_DPF; + if (frame == 0) { + auto strengthConfig = params->block<BlockType::DpfStrength>(); + strengthConfig.setEnabled(true); + *strengthConfig = strengthConfig_; } } diff --git a/src/ipa/rkisp1/algorithms/dpf.h b/src/ipa/rkisp1/algorithms/dpf.h index da0115ba..2dd8cd36 100644 --- a/src/ipa/rkisp1/algorithms/dpf.h +++ b/src/ipa/rkisp1/algorithms/dpf.h @@ -27,7 +27,7 @@ public: const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; private: struct rkisp1_cif_isp_dpf_config config_; diff --git a/src/ipa/rkisp1/algorithms/filter.cpp b/src/ipa/rkisp1/algorithms/filter.cpp index 9752248a..7598ef8a 100644 --- a/src/ipa/rkisp1/algorithms/filter.cpp +++ b/src/ipa/rkisp1/algorithms/filter.cpp @@ -104,7 +104,7 @@ void Filter::queueRequest(IPAContext &context, */ void Filter::prepare([[maybe_unused]] IPAContext &context, [[maybe_unused]] const uint32_t frame, - IPAFrameContext &frameContext, rkisp1_params_cfg *params) + IPAFrameContext &frameContext, RkISP1Params *params) { /* Check if the algorithm configuration has been updated. */ if (!frameContext.filter.update) @@ -160,23 +160,25 @@ void Filter::prepare([[maybe_unused]] IPAContext &context, uint8_t denoise = frameContext.filter.denoise; uint8_t sharpness = frameContext.filter.sharpness; - auto &flt_config = params->others.flt_config; - - flt_config.fac_sh0 = filt_fac_sh0[sharpness]; - flt_config.fac_sh1 = filt_fac_sh1[sharpness]; - flt_config.fac_mid = filt_fac_mid[sharpness]; - flt_config.fac_bl0 = filt_fac_bl0[sharpness]; - flt_config.fac_bl1 = filt_fac_bl1[sharpness]; - - flt_config.lum_weight = kFiltLumWeightDefault; - flt_config.mode = kFiltModeDefault; - flt_config.thresh_sh0 = filt_thresh_sh0[denoise]; - flt_config.thresh_sh1 = filt_thresh_sh1[denoise]; - flt_config.thresh_bl0 = filt_thresh_bl0[denoise]; - flt_config.thresh_bl1 = filt_thresh_bl1[denoise]; - flt_config.grn_stage1 = stage1_select[denoise]; - flt_config.chr_v_mode = filt_chr_v_mode[denoise]; - flt_config.chr_h_mode = filt_chr_h_mode[denoise]; + + auto config = params->block<BlockType::Flt>(); + config.setEnabled(true); + + config->fac_sh0 = filt_fac_sh0[sharpness]; + config->fac_sh1 = filt_fac_sh1[sharpness]; + config->fac_mid = filt_fac_mid[sharpness]; + config->fac_bl0 = filt_fac_bl0[sharpness]; + config->fac_bl1 = filt_fac_bl1[sharpness]; + + config->lum_weight = kFiltLumWeightDefault; + config->mode = kFiltModeDefault; + config->thresh_sh0 = filt_thresh_sh0[denoise]; + config->thresh_sh1 = filt_thresh_sh1[denoise]; + config->thresh_bl0 = filt_thresh_bl0[denoise]; + config->thresh_bl1 = filt_thresh_bl1[denoise]; + config->grn_stage1 = stage1_select[denoise]; + config->chr_v_mode = filt_chr_v_mode[denoise]; + config->chr_h_mode = filt_chr_h_mode[denoise]; /* * Combined high denoising and high sharpening requires some @@ -186,27 +188,23 @@ void Filter::prepare([[maybe_unused]] IPAContext &context, */ if (denoise == 9) { if (sharpness > 3) - flt_config.grn_stage1 = 2; + config->grn_stage1 = 2; } else if (denoise == 10) { if (sharpness > 5) - flt_config.grn_stage1 = 2; + config->grn_stage1 = 2; else if (sharpness > 3) - flt_config.grn_stage1 = 1; + config->grn_stage1 = 1; } if (denoise > 7) { if (sharpness > 7) { - flt_config.fac_bl0 /= 2; - flt_config.fac_bl1 /= 4; + config->fac_bl0 /= 2; + config->fac_bl1 /= 4; } else if (sharpness > 4) { - flt_config.fac_bl0 = flt_config.fac_bl0 * 3 / 4; - flt_config.fac_bl1 /= 2; + config->fac_bl0 = config->fac_bl0 * 3 / 4; + config->fac_bl1 /= 2; } } - - params->module_en_update |= RKISP1_CIF_ISP_MODULE_FLT; - params->module_ens |= RKISP1_CIF_ISP_MODULE_FLT; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_FLT; } REGISTER_IPA_ALGORITHM(Filter, "Filter") diff --git a/src/ipa/rkisp1/algorithms/filter.h b/src/ipa/rkisp1/algorithms/filter.h index d595811d..8f858e57 100644 --- a/src/ipa/rkisp1/algorithms/filter.h +++ b/src/ipa/rkisp1/algorithms/filter.h @@ -26,7 +26,7 @@ public: const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/goc.cpp b/src/ipa/rkisp1/algorithms/goc.cpp new file mode 100644 index 00000000..a9493678 --- /dev/null +++ b/src/ipa/rkisp1/algorithms/goc.cpp @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * RkISP1 Gamma out control + */ +#include "goc.h" + +#include <cmath> + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +#include <libcamera/control_ids.h> + +#include "libcamera/internal/yaml_parser.h" + +#include "linux/rkisp1-config.h" + +/** + * \file goc.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +/** + * \class GammaOutCorrection + * \brief RkISP1 Gamma out correction + * + * This algorithm implements the gamma out curve for the RkISP1. It defaults to + * a gamma value of 2.2. + * + * As gamma is internally represented as a piecewise linear function with only + * 17 knots, the difference between gamma=2.2 and sRGB gamma is minimal. + * Therefore sRGB gamma was not implemented as special case. + * + * Useful links: + * - https://www.cambridgeincolour.com/tutorials/gamma-correction.htm + * - https://en.wikipedia.org/wiki/SRGB + */ + +LOG_DEFINE_CATEGORY(RkISP1Gamma) + +const float kDefaultGamma = 2.2f; + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int GammaOutCorrection::init(IPAContext &context, const YamlObject &tuningData) +{ + if (context.hw->numGammaOutSamples != + RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10) { + LOG(RkISP1Gamma, Error) + << "Gamma is not implemented for RkISP1 V12"; + return -EINVAL; + } + + defaultGamma_ = tuningData["gamma"].get<double>(kDefaultGamma); + context.ctrlMap[&controls::Gamma] = ControlInfo(0.1f, 10.0f, defaultGamma_); + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int GammaOutCorrection::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + context.activeState.goc.gamma = defaultGamma_; + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void GammaOutCorrection::queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + if (frame == 0) + frameContext.goc.update = true; + + const auto &gamma = controls.get(controls::Gamma); + if (gamma) { + context.activeState.goc.gamma = *gamma; + frameContext.goc.update = true; + LOG(RkISP1Gamma, Debug) << "Set gamma to " << *gamma; + } + + frameContext.goc.gamma = context.activeState.goc.gamma; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void GammaOutCorrection::prepare(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + RkISP1Params *params) +{ + ASSERT(context.hw->numGammaOutSamples == + RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10); + + if (!frameContext.goc.update) + return; + + /* + * The logarithmic segments as specified in the reference. + * Plus an additional 0 to make the loop easier + */ + static constexpr std::array<unsigned int, RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10> segments = { + 64, 64, 64, 64, 128, 128, 128, 128, 256, + 256, 256, 512, 512, 512, 512, 512, 0 + }; + + auto config = params->block<BlockType::Goc>(); + config.setEnabled(true); + + __u16 *gamma_y = config->gamma_y; + + unsigned x = 0; + for (const auto [i, size] : utils::enumerate(segments)) { + gamma_y[i] = std::pow(x / 4096.0, 1.0 / frameContext.goc.gamma) * 1023.0; + x += size; + } + + config->mode = RKISP1_CIF_ISP_GOC_MODE_LOGARITHMIC; +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void GammaOutCorrection::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const rkisp1_stat_buffer *stats, + ControlList &metadata) +{ + metadata.set(controls::Gamma, frameContext.goc.gamma); +} + +REGISTER_IPA_ALGORITHM(GammaOutCorrection, "GammaOutCorrection") + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/goc.h b/src/ipa/rkisp1/algorithms/goc.h new file mode 100644 index 00000000..bb2ddfc9 --- /dev/null +++ b/src/ipa/rkisp1/algorithms/goc.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * RkISP1 Gamma out control + */ + +#pragma once + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +class GammaOutCorrection : public Algorithm +{ +public: + GammaOutCorrection() = default; + ~GammaOutCorrection() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RkISP1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) override; + +private: + float defaultGamma_; +}; + +} /* namespace ipa::rkisp1::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/gsl.cpp b/src/ipa/rkisp1/algorithms/gsl.cpp index 9b056c6e..9604c0ac 100644 --- a/src/ipa/rkisp1/algorithms/gsl.cpp +++ b/src/ipa/rkisp1/algorithms/gsl.cpp @@ -119,24 +119,20 @@ int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context, void GammaSensorLinearization::prepare([[maybe_unused]] IPAContext &context, const uint32_t frame, [[maybe_unused]] IPAFrameContext &frameContext, - rkisp1_params_cfg *params) + RkISP1Params *params) { if (frame > 0) return; - params->others.sdg_config.xa_pnts.gamma_dx0 = gammaDx_[0]; - params->others.sdg_config.xa_pnts.gamma_dx1 = gammaDx_[1]; + auto config = params->block<BlockType::Sdg>(); + config.setEnabled(true); - std::copy(curveYr_.begin(), curveYr_.end(), - params->others.sdg_config.curve_r.gamma_y); - std::copy(curveYg_.begin(), curveYg_.end(), - params->others.sdg_config.curve_g.gamma_y); - std::copy(curveYb_.begin(), curveYb_.end(), - params->others.sdg_config.curve_b.gamma_y); + config->xa_pnts.gamma_dx0 = gammaDx_[0]; + config->xa_pnts.gamma_dx1 = gammaDx_[1]; - params->module_en_update |= RKISP1_CIF_ISP_MODULE_SDG; - params->module_ens |= RKISP1_CIF_ISP_MODULE_SDG; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_SDG; + std::copy(curveYr_.begin(), curveYr_.end(), config->curve_r.gamma_y); + std::copy(curveYg_.begin(), curveYg_.end(), config->curve_g.gamma_y); + std::copy(curveYb_.begin(), curveYb_.end(), config->curve_b.gamma_y); } REGISTER_IPA_ALGORITHM(GammaSensorLinearization, "GammaSensorLinearization") diff --git a/src/ipa/rkisp1/algorithms/gsl.h b/src/ipa/rkisp1/algorithms/gsl.h index c404105e..91cf6efa 100644 --- a/src/ipa/rkisp1/algorithms/gsl.h +++ b/src/ipa/rkisp1/algorithms/gsl.h @@ -22,7 +22,7 @@ public: int init(IPAContext &context, const YamlObject &tuningData) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; private: uint32_t gammaDx_[2]; diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 161183fc..e47aa2f0 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -16,6 +16,7 @@ #include "libcamera/internal/yaml_parser.h" +#include "libipa/lsc_polynomial.h" #include "linux/rkisp1-config.h" /** @@ -24,6 +25,36 @@ namespace libcamera { +namespace ipa { + +constexpr int kColourTemperatureChangeThreshhold = 10; + +template<typename T> +void interpolateVector(const std::vector<T> &a, const std::vector<T> &b, + std::vector<T> &dest, double lambda) +{ + assert(a.size() == b.size()); + dest.resize(a.size()); + for (size_t i = 0; i < a.size(); i++) { + dest[i] = a[i] * (1.0 - lambda) + b[i] * lambda; + } +} + +template<> +void Interpolator<rkisp1::algorithms::LensShadingCorrection::Components>:: + interpolate(const rkisp1::algorithms::LensShadingCorrection::Components &a, + const rkisp1::algorithms::LensShadingCorrection::Components &b, + rkisp1::algorithms::LensShadingCorrection::Components &dest, + double lambda) +{ + interpolateVector(a.r, b.r, dest.r, lambda); + interpolateVector(a.gr, b.gr, dest.gr, lambda); + interpolateVector(a.gb, b.gb, dest.gb, lambda); + interpolateVector(a.b, b.b, dest.b, lambda); +} + +} /* namespace ipa */ + namespace ipa::rkisp1::algorithms { /** @@ -40,6 +71,200 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Lsc) +class LscPolynomialLoader +{ +public: + LscPolynomialLoader(const Size &sensorSize, + const Rectangle &cropRectangle, + const std::vector<double> &xSizes, + const std::vector<double> &ySizes) + : sensorSize_(sensorSize), + cropRectangle_(cropRectangle), + xSizes_(xSizes), + ySizes_(ySizes) + { + } + + int parseLscData(const YamlObject &yamlSets, + std::map<unsigned int, LensShadingCorrection::Components> &lscData) + { + const auto &sets = yamlSets.asList(); + for (const auto &yamlSet : sets) { + std::optional<LscPolynomial> pr, pgr, pgb, pb; + uint32_t ct = yamlSet["ct"].get<uint32_t>(0); + + if (lscData.count(ct)) { + LOG(RkISP1Lsc, Error) + << "Multiple sets found for " + << "color temperature " << ct; + return -EINVAL; + } + + LensShadingCorrection::Components &set = lscData[ct]; + pr = yamlSet["r"].get<LscPolynomial>(); + pgr = yamlSet["gr"].get<LscPolynomial>(); + pgb = yamlSet["gb"].get<LscPolynomial>(); + pb = yamlSet["b"].get<LscPolynomial>(); + + if (!(pr || pgr || pgb || pb)) { + LOG(RkISP1Lsc, Error) + << "Failed to parse polynomial for " + << "colour temperature " << ct; + return -EINVAL; + } + + set.ct = ct; + pr->setReferenceImageSize(sensorSize_); + pgr->setReferenceImageSize(sensorSize_); + pgb->setReferenceImageSize(sensorSize_); + pb->setReferenceImageSize(sensorSize_); + set.r = samplePolynomial(*pr); + set.gr = samplePolynomial(*pgr); + set.gb = samplePolynomial(*pgb); + set.b = samplePolynomial(*pb); + } + + if (lscData.empty()) { + LOG(RkISP1Lsc, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; + } + +private: + /* + * The lsc grid has custom spacing defined on half the range (see + * parseSizes() for details). For easier handling this function converts + * the spaces vector to positions and mirrors them. E.g.: + * + * input: | 0.2 | 0.3 | + * output: 0.0 0.2 0.5 0.8 1.0 + */ + std::vector<double> sizesListToPositions(const std::vector<double> &sizes) + { + const int half = sizes.size(); + std::vector<double> res(half * 2 + 1); + double x = 0.0; + + res[half] = 0.5; + for (int i = 1; i <= half; i++) { + x += sizes[half - i]; + res[half - i] = 0.5 - x; + res[half + i] = 0.5 + x; + } + + return res; + } + + std::vector<uint16_t> samplePolynomial(const LscPolynomial &poly) + { + constexpr int k = RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + + double m = poly.getM(); + double x0 = cropRectangle_.x / m; + double y0 = cropRectangle_.y / m; + double w = cropRectangle_.width / m; + double h = cropRectangle_.height / m; + std::vector<uint16_t> res; + + assert(xSizes_.size() * 2 + 1 == k); + assert(ySizes_.size() * 2 + 1 == k); + + res.reserve(k * k); + + std::vector<double> xPos(sizesListToPositions(xSizes_)); + std::vector<double> yPos(sizesListToPositions(ySizes_)); + + for (int y = 0; y < k; y++) { + for (int x = 0; x < k; x++) { + double xp = x0 + xPos[x] * w; + double yp = y0 + yPos[y] * h; + /* + * The hardware uses 2.10 fixed point format and + * limits the legal values to [1..3.999]. Scale + * and clamp the sampled value accordingly. + */ + int v = static_cast<int>( + poly.sampleAtNormalizedPixelPos(xp, yp) * + 1024); + v = std::min(std::max(v, 1024), 4095); + res.push_back(v); + } + } + return res; + } + + Size sensorSize_; + Rectangle cropRectangle_; + const std::vector<double> &xSizes_; + const std::vector<double> &ySizes_; +}; + +class LscTableLoader +{ +public: + int parseLscData(const YamlObject &yamlSets, + std::map<unsigned int, LensShadingCorrection::Components> &lscData) + { + const auto &sets = yamlSets.asList(); + + for (const auto &yamlSet : sets) { + uint32_t ct = yamlSet["ct"].get<uint32_t>(0); + + if (lscData.count(ct)) { + LOG(RkISP1Lsc, Error) + << "Multiple sets found for color temperature " + << ct; + return -EINVAL; + } + + LensShadingCorrection::Components &set = lscData[ct]; + + set.ct = ct; + set.r = parseTable(yamlSet, "r"); + set.gr = parseTable(yamlSet, "gr"); + set.gb = parseTable(yamlSet, "gb"); + set.b = parseTable(yamlSet, "b"); + + if (set.r.empty() || set.gr.empty() || + set.gb.empty() || set.b.empty()) { + LOG(RkISP1Lsc, Error) + << "Set for color temperature " << ct + << " is missing tables"; + return -EINVAL; + } + } + + if (lscData.empty()) { + LOG(RkISP1Lsc, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; + } + +private: + std::vector<uint16_t> parseTable(const YamlObject &tuningData, + const char *prop) + { + static constexpr unsigned int kLscNumSamples = + RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + + std::vector<uint16_t> table = + tuningData[prop].getList<uint16_t>().value_or(std::vector<uint16_t>{}); + if (table.size() != kLscNumSamples) { + LOG(RkISP1Lsc, Error) + << "Invalid '" << prop << "' values: expected " + << kLscNumSamples + << " elements, got " << table.size(); + return {}; + } + + return table; + } +}; + static std::vector<double> parseSizes(const YamlObject &tuningData, const char *prop) { @@ -70,28 +295,10 @@ static std::vector<double> parseSizes(const YamlObject &tuningData, return sizes; } -static std::vector<uint16_t> parseTable(const YamlObject &tuningData, - const char *prop) -{ - static constexpr unsigned int kLscNumSamples = - RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; - - std::vector<uint16_t> table = - tuningData[prop].getList<uint16_t>().value_or(std::vector<uint16_t>{}); - if (table.size() != kLscNumSamples) { - LOG(RkISP1Lsc, Error) - << "Invalid '" << prop << "' values: expected " - << kLscNumSamples - << " elements, got " << table.size(); - return {}; - } - - return table; -} - LensShadingCorrection::LensShadingCorrection() - : lastCt_({ 0, 0 }) + : lastAppliedCt_(0), lastAppliedQuantizedCt_(0) { + sets_.setQuantization(kColourTemperatureChangeThreshhold); } /** @@ -114,38 +321,30 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, return -EINVAL; } - const auto &sets = yamlSets.asList(); - for (const auto &yamlSet : sets) { - uint32_t ct = yamlSet["ct"].get<uint32_t>(0); - - if (sets_.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for color temperature " - << ct; - return -EINVAL; - } - - Components &set = sets_[ct]; - - set.ct = ct; - set.r = parseTable(yamlSet, "r"); - set.gr = parseTable(yamlSet, "gr"); - set.gb = parseTable(yamlSet, "gb"); - set.b = parseTable(yamlSet, "b"); - - if (set.r.empty() || set.gr.empty() || - set.gb.empty() || set.b.empty()) { - LOG(RkISP1Lsc, Error) - << "Set for color temperature " << ct - << " is missing tables"; - return -EINVAL; - } + std::map<unsigned int, Components> lscData; + int res = 0; + std::string type = tuningData["type"].get<std::string>("table"); + if (type == "table") { + LOG(RkISP1Lsc, Debug) << "Loading tabular LSC data."; + auto loader = LscTableLoader(); + res = loader.parseLscData(yamlSets, lscData); + } else if (type == "polynomial") { + LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data."; + auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize, + context.sensorInfo.analogCrop, + xSize_, + ySize_); + res = loader.parseLscData(yamlSets, lscData); + } else { + LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '" + << type << "'"; + res = -EINVAL; } - if (sets_.empty()) { - LOG(RkISP1Lsc, Error) << "Failed to load any sets"; - return -EINVAL; - } + if (res) + return res; + + sets_.setData(std::move(lscData)); return 0; } @@ -185,18 +384,12 @@ int LensShadingCorrection::configure(IPAContext &context, return 0; } -void LensShadingCorrection::setParameters(rkisp1_params_cfg *params) +void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) { - struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config; - memcpy(config.x_grad_tbl, xGrad_, sizeof(config.x_grad_tbl)); memcpy(config.y_grad_tbl, yGrad_, sizeof(config.y_grad_tbl)); memcpy(config.x_size_tbl, xSizes_, sizeof(config.x_size_tbl)); memcpy(config.y_size_tbl, ySizes_, sizeof(config.y_size_tbl)); - - params->module_en_update |= RKISP1_CIF_ISP_MODULE_LSC; - params->module_ens |= RKISP1_CIF_ISP_MODULE_LSC; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_LSC; } void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, @@ -208,131 +401,34 @@ void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, std::copy(set.b.begin(), set.b.end(), &config.b_data_tbl[0][0]); } -/* - * Interpolate LSC parameters based on color temperature value. - */ -void LensShadingCorrection::interpolateTable(rkisp1_cif_isp_lsc_config &config, - const Components &set0, - const Components &set1, - const uint32_t ct) -{ - double coeff0 = (set1.ct - ct) / static_cast<double>(set1.ct - set0.ct); - double coeff1 = (ct - set0.ct) / static_cast<double>(set1.ct - set0.ct); - - for (unsigned int i = 0; i < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++i) { - for (unsigned int j = 0; j < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++j) { - unsigned int sample = i * RKISP1_CIF_ISP_LSC_SAMPLES_MAX + j; - - config.r_data_tbl[i][j] = - set0.r[sample] * coeff0 + - set1.r[sample] * coeff1; - - config.gr_data_tbl[i][j] = - set0.gr[sample] * coeff0 + - set1.gr[sample] * coeff1; - - config.gb_data_tbl[i][j] = - set0.gb[sample] * coeff0 + - set1.gb[sample] * coeff1; - - config.b_data_tbl[i][j] = - set0.b[sample] * coeff0 + - set1.b[sample] * coeff1; - } - } -} - /** * \copydoc libcamera::ipa::Algorithm::prepare */ void LensShadingCorrection::prepare(IPAContext &context, - const uint32_t frame, + [[maybe_unused]] const uint32_t frame, [[maybe_unused]] IPAFrameContext &frameContext, - rkisp1_params_cfg *params) + RkISP1Params *params) { - struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config; - - /* - * If there is only one set, the configuration has already been done - * for first frame. - */ - if (sets_.size() == 1 && frame > 0) - return; - - /* - * If there is only one set, pick it. We can ignore lastCt_, as it will - * never be relevant. - */ - if (sets_.size() == 1) { - setParameters(params); - copyTable(config, sets_.cbegin()->second); - return; - } - uint32_t ct = context.activeState.awb.temperatureK; - ct = std::clamp(ct, sets_.cbegin()->first, sets_.crbegin()->first); - - /* - * If the original is the same, then it means the same adjustment would - * be made. If the adjusted is the same, then it means that it's the - * same as what was actually applied. Thus in these cases we can skip - * reprogramming the LSC. - * - * original == adjusted can only happen if an interpolation - * happened, or if original has an exact entry in sets_. This means - * that if original != adjusted, then original was adjusted to - * the nearest available entry in sets_, resulting in adjusted. - * Clearly, any ct value that is in between original and adjusted - * will be adjusted to the same adjusted value, so we can skip - * reprogramming the LSC table. - * - * We also skip updating the original value, as the last one had a - * larger bound and thus a larger range of ct values that will be - * adjusted to the same adjusted. - */ - if ((lastCt_.original <= ct && ct <= lastCt_.adjusted) || - (lastCt_.adjusted <= ct && ct <= lastCt_.original)) + if (std::abs(static_cast<int>(ct) - static_cast<int>(lastAppliedCt_)) < + kColourTemperatureChangeThreshhold) return; - - setParameters(params); - - /* - * The color temperature matches exactly one of the available LSC tables. - */ - if (sets_.count(ct)) { - copyTable(config, sets_[ct]); - lastCt_ = { ct, ct }; + unsigned int quantizedCt; + const Components &set = sets_.getInterpolated(ct, &quantizedCt); + if (lastAppliedQuantizedCt_ == quantizedCt) return; - } - /* No shortcuts left; we need to round or interpolate */ - auto iter = sets_.upper_bound(ct); - const Components &set1 = iter->second; - const Components &set0 = (--iter)->second; - uint32_t ct0 = set0.ct; - uint32_t ct1 = set1.ct; - uint32_t diff0 = ct - ct0; - uint32_t diff1 = ct1 - ct; - static constexpr double kThreshold = 0.1; - float threshold = kThreshold * (ct1 - ct0); - - if (diff0 < threshold || diff1 < threshold) { - const Components &set = diff0 < diff1 ? set0 : set1; - LOG(RkISP1Lsc, Debug) << "using LSC table for " << set.ct; - copyTable(config, set); - lastCt_ = { ct, set.ct }; - return; - } + auto config = params->block<BlockType::Lsc>(); + config.setEnabled(true); + setParameters(*config); + copyTable(*config, set); + + lastAppliedCt_ = ct; + lastAppliedQuantizedCt_ = quantizedCt; - /* - * ct is not within 10% of the difference between the neighbouring - * color temperatures, so we need to interpolate. - */ LOG(RkISP1Lsc, Debug) - << "ct is " << ct << ", interpolating between " - << ct0 << " and " << ct1; - interpolateTable(config, set0, set1, ct); - lastCt_ = { ct, ct }; + << "ct is " << ct << ", quantized to " + << quantizedCt; } REGISTER_IPA_ALGORITHM(LensShadingCorrection, "LensShadingCorrection") diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 5baf5927..5a0824e3 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -9,6 +9,8 @@ #include <map> +#include "libipa/interpolator.h" + #include "algorithm.h" namespace libcamera { @@ -25,9 +27,8 @@ public: int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; -private: struct Components { uint32_t ct; std::vector<uint16_t> r; @@ -36,23 +37,23 @@ private: std::vector<uint16_t> b; }; - void setParameters(rkisp1_params_cfg *params); +private: + void setParameters(rkisp1_cif_isp_lsc_config &config); void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0); void interpolateTable(rkisp1_cif_isp_lsc_config &config, const Components &set0, const Components &set1, const uint32_t ct); - std::map<uint32_t, Components> sets_; + ipa::Interpolator<Components> sets_; std::vector<double> xSize_; std::vector<double> ySize_; uint16_t xGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t yGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t xSizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t ySizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; - struct { - uint32_t original; - uint32_t adjusted; - } lastCt_; + + unsigned int lastAppliedCt_; + unsigned int lastAppliedQuantizedCt_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/meson.build b/src/ipa/rkisp1/algorithms/meson.build index 93a48329..1734a667 100644 --- a/src/ipa/rkisp1/algorithms/meson.build +++ b/src/ipa/rkisp1/algorithms/meson.build @@ -4,10 +4,12 @@ rkisp1_ipa_algorithms = files([ 'agc.cpp', 'awb.cpp', 'blc.cpp', + 'ccm.cpp', 'cproc.cpp', 'dpcc.cpp', 'dpf.cpp', 'filter.cpp', + 'goc.cpp', 'gsl.cpp', 'lsc.cpp', ]) diff --git a/src/ipa/rkisp1/data/imx219.yaml b/src/ipa/rkisp1/data/imx219.yaml index cbcc43b8..0d99cb52 100644 --- a/src/ipa/rkisp1/data/imx219.yaml +++ b/src/ipa/rkisp1/data/imx219.yaml @@ -6,10 +6,6 @@ algorithms: - Agc: - Awb: - BlackLevelCorrection: - R: 256 - Gr: 256 - Gb: 256 - B: 256 - LensShadingCorrection: x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ] y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ] diff --git a/src/ipa/rkisp1/data/imx258.yaml b/src/ipa/rkisp1/data/imx258.yaml index 43dddf20..202af36a 100644 --- a/src/ipa/rkisp1/data/imx258.yaml +++ b/src/ipa/rkisp1/data/imx258.yaml @@ -5,6 +5,7 @@ version: 1 algorithms: - Agc: - Awb: + - BlackLevelCorrection: - LensShadingCorrection: x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ] y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ] diff --git a/src/ipa/rkisp1/data/meson.build b/src/ipa/rkisp1/data/meson.build index 7150e155..1e3522b2 100644 --- a/src/ipa/rkisp1/data/meson.build +++ b/src/ipa/rkisp1/data/meson.build @@ -2,8 +2,12 @@ conf_files = files([ 'imx219.yaml', + 'imx258.yaml', + 'ov2685.yaml', 'ov4689.yaml', 'ov5640.yaml', + 'ov5695.yaml', + 'ov8858.yaml', 'uncalibrated.yaml', ]) diff --git a/src/ipa/rkisp1/data/ov4689.yaml b/src/ipa/rkisp1/data/ov4689.yaml index 2068684c..60901296 100644 --- a/src/ipa/rkisp1/data/ov4689.yaml +++ b/src/ipa/rkisp1/data/ov4689.yaml @@ -6,8 +6,4 @@ algorithms: - Agc: - Awb: - BlackLevelCorrection: - R: 66 - Gr: 66 - Gb: 66 - B: 66 ... diff --git a/src/ipa/rkisp1/data/ov5640.yaml b/src/ipa/rkisp1/data/ov5640.yaml index 897b83cb..4b21d412 100644 --- a/src/ipa/rkisp1/data/ov5640.yaml +++ b/src/ipa/rkisp1/data/ov5640.yaml @@ -6,10 +6,6 @@ algorithms: - Agc: - Awb: - BlackLevelCorrection: - R: 256 - Gr: 256 - Gb: 256 - B: 256 - ColorProcessing: - GammaSensorLinearization: x-intervals: [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ] diff --git a/src/ipa/rkisp1/data/uncalibrated.yaml b/src/ipa/rkisp1/data/uncalibrated.yaml index a7bbd8d8..60901296 100644 --- a/src/ipa/rkisp1/data/uncalibrated.yaml +++ b/src/ipa/rkisp1/data/uncalibrated.yaml @@ -5,4 +5,5 @@ version: 1 algorithms: - Agc: - Awb: + - BlackLevelCorrection: ... diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp index 283bc131..14d0c02a 100644 --- a/src/ipa/rkisp1/ipa_context.cpp +++ b/src/ipa/rkisp1/ipa_context.cpp @@ -106,6 +106,11 @@ namespace libcamera::ipa::rkisp1 { */ /** + * \var IPASessionConfiguration::paramFormat + * \brief The fourcc of the parameters buffers format + */ + +/** * \struct IPAActiveState * \brief Active state for algorithms * @@ -137,13 +142,43 @@ namespace libcamera::ipa::rkisp1 { * \var IPAActiveState::agc * \brief State for the Automatic Gain Control algorithm * - * The exposure and gain are the latest values computed by the AGC algorithm. + * The \a automatic variables track the latest values computed by algorithm + * based on the latest processed statistics. All other variables track the + * consolidated controls requested in queued requests. * - * \var IPAActiveState::agc.exposure - * \brief Exposure time expressed as a number of lines + * \struct IPAActiveState::agc.manual + * \brief Manual exposure time and analog gain (set through requests) * - * \var IPAActiveState::agc.gain - * \brief Analogue gain multiplier + * \var IPAActiveState::agc.manual.exposure + * \brief Manual exposure time expressed as a number of lines as set by the + * ExposureTime control + * + * \var IPAActiveState::agc.manual.gain + * \brief Manual analogue gain as set by the AnalogueGain control + * + * \struct IPAActiveState::agc.automatic + * \brief Automatic exposure time and analog gain (computed by the algorithm) + * + * \var IPAActiveState::agc.automatic.exposure + * \brief Automatic exposure time expressed as a number of lines + * + * \var IPAActiveState::agc.automatic.gain + * \brief Automatic analogue gain multiplier + * + * \var IPAActiveState::agc.autoEnabled + * \brief Manual/automatic AGC state as set by the AeEnable control + * + * \var IPAActiveState::agc.constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var IPAActiveState::agc.exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var IPAActiveState::agc.meteringMode + * \brief Metering mode as set by the AeMeteringMode control + * + * \var IPAActiveState::agc.maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control */ /** @@ -218,6 +253,14 @@ namespace libcamera::ipa::rkisp1 { */ /** + * \var IPAActiveState::goc + * \brief State for the goc algorithm + * + * \var IPAActiveState::goc.gamma + * \brief Gamma value applied as 1.0/gamma + */ + +/** * \struct IPAFrameContext * \brief Per-frame context for algorithms * @@ -257,12 +300,30 @@ namespace libcamera::ipa::rkisp1 { * applied to the sensor in order to take effect for this frame. * * \var IPAFrameContext::agc.exposure - * \brief Exposure time expressed as a number of lines + * \brief Exposure time expressed as a number of lines computed by the algorithm * * \var IPAFrameContext::agc.gain - * \brief Analogue gain multiplier + * \brief Analogue gain multiplier computed by the algorithm * * The gain should be adapted to the sensor specific gain code before applying. + * + * \var IPAFrameContext::agc.autoEnabled + * \brief Manual/automatic AGC state as set by the AeEnable control + * + * \var IPAFrameContext::agc.constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var IPAFrameContext::agc.exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var IPAFrameContext::agc.meteringMode + * \brief Metering mode as set by the AeMeteringMode control + * + * \var IPAFrameContext::agc.maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + * + * \var IPAFrameContext::agc.updateMetering + * \brief Indicate if new ISP AGC metering parameters need to be applied */ /** @@ -334,6 +395,18 @@ namespace libcamera::ipa::rkisp1 { */ /** + * \var IPAFrameContext::goc + * \brief Gamma out correction parameters for this frame + * + * \var IPAFrameContext::goc.gamma + * \brief Gamma value applied as 1.0/gamma + * + * \var IPAFrameContext::goc.update + * \brief Indicates if the goc parameters have been updated compared to the + * previous frame + */ + +/** * \var IPAFrameContext::sensor * \brief Sensor configuration that used been used for this frame * @@ -351,6 +424,9 @@ namespace libcamera::ipa::rkisp1 { * \var IPAContext::hw * \brief RkISP1 version-specific hardware parameters * + * \var IPAContext::sensorInfo + * \brief The IPA session sensorInfo, immutable during the session + * * \var IPAContext::configuration * \brief The IPA session configuration, immutable during the session * diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index bd02a7a2..e274d9b0 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -8,14 +8,20 @@ #pragma once +#include <memory> + #include <linux/rkisp1-config.h> #include <libcamera/base/utils.h> +#include <libcamera/control_ids.h> #include <libcamera/controls.h> #include <libcamera/geometry.h> +#include <libcamera/ipa/core_ipa_interface.h> +#include <libipa/camera_sensor_helper.h> #include <libipa/fc_queue.h> +#include <libipa/matrix.h> namespace libcamera { @@ -26,6 +32,7 @@ struct IPAHwSettings { unsigned int numHistogramBins; unsigned int numHistogramWeights; unsigned int numGammaOutSamples; + bool compand; }; struct IPASessionConfiguration { @@ -54,6 +61,7 @@ struct IPASessionConfiguration { } sensor; bool raw; + uint32_t paramFormat; }; struct IPAActiveState { @@ -68,8 +76,10 @@ struct IPAActiveState { } automatic; bool autoEnabled; - uint32_t constraintMode; - uint32_t exposureMode; + controls::AeConstraintModeEnum constraintMode; + controls::AeExposureModeEnum exposureMode; + controls::AeMeteringModeEnum meteringMode; + utils::Duration maxFrameDuration; } agc; struct { @@ -91,6 +101,10 @@ struct IPAActiveState { } awb; struct { + Matrix<float, 3, 3> ccm; + } ccm; + + struct { int8_t brightness; uint8_t contrast; uint8_t saturation; @@ -104,6 +118,10 @@ struct IPAActiveState { uint8_t denoise; uint8_t sharpness; } filter; + + struct { + double gamma; + } goc; }; struct IPAFrameContext : public FrameContext { @@ -111,6 +129,11 @@ struct IPAFrameContext : public FrameContext { uint32_t exposure; double gain; bool autoEnabled; + controls::AeConstraintModeEnum constraintMode; + controls::AeExposureModeEnum exposureMode; + controls::AeMeteringModeEnum meteringMode; + utils::Duration maxFrameDuration; + bool updateMetering; } agc; struct { @@ -120,7 +143,6 @@ struct IPAFrameContext : public FrameContext { double blue; } gains; - unsigned int temperatureK; bool autoEnabled; } awb; @@ -143,19 +165,32 @@ struct IPAFrameContext : public FrameContext { } filter; struct { + double gamma; + bool update; + } goc; + + struct { uint32_t exposure; double gain; } sensor; + + struct { + Matrix<float, 3, 3> ccm; + } ccm; }; struct IPAContext { const IPAHwSettings *hw; + IPACameraSensorInfo sensorInfo; IPASessionConfiguration configuration; IPAActiveState activeState; FCQueue<IPAFrameContext> frameContexts; ControlInfoMap::Map ctrlMap; + + /* Interface to the Camera Helper */ + std::unique_ptr<CameraSensorHelper> camHelper; }; } /* namespace ipa::rkisp1 */ diff --git a/src/ipa/rkisp1/meson.build b/src/ipa/rkisp1/meson.build index e813da53..34844f14 100644 --- a/src/ipa/rkisp1/meson.build +++ b/src/ipa/rkisp1/meson.build @@ -7,17 +7,17 @@ ipa_name = 'ipa_rkisp1' rkisp1_ipa_sources = files([ 'ipa_context.cpp', + 'params.cpp', 'rkisp1.cpp', + 'utils.cpp', ]) rkisp1_ipa_sources += rkisp1_ipa_algorithms -mod = shared_module(ipa_name, - [rkisp1_ipa_sources, libcamera_generated_ipa_headers], +mod = shared_module(ipa_name, rkisp1_ipa_sources, name_prefix : '', - include_directories : [ipa_includes, libipa_includes], - dependencies : libcamera_private, - link_with : libipa, + include_directories : [ipa_includes], + dependencies : [libcamera_private, libipa_dep], install : true, install_dir : ipa_install_dir) diff --git a/src/ipa/rkisp1/module.h b/src/ipa/rkisp1/module.h index 16c3e43e..69e9bc82 100644 --- a/src/ipa/rkisp1/module.h +++ b/src/ipa/rkisp1/module.h @@ -14,13 +14,14 @@ #include <libipa/module.h> #include "ipa_context.h" +#include "params.h" namespace libcamera { namespace ipa::rkisp1 { using Module = ipa::Module<IPAContext, IPAFrameContext, IPACameraSensorInfo, - rkisp1_params_cfg, rkisp1_stat_buffer>; + RkISP1Params, rkisp1_stat_buffer>; } /* namespace ipa::rkisp1 */ diff --git a/src/ipa/rkisp1/params.cpp b/src/ipa/rkisp1/params.cpp new file mode 100644 index 00000000..4c0b051c --- /dev/null +++ b/src/ipa/rkisp1/params.cpp @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * RkISP1 ISP Parameters + */ + +#include "params.h" + +#include <map> +#include <stddef.h> +#include <string.h> + +#include <linux/rkisp1-config.h> +#include <linux/videodev2.h> + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +namespace libcamera { + +LOG_DEFINE_CATEGORY(RkISP1Params) + +namespace ipa::rkisp1 { + +namespace { + +struct BlockTypeInfo { + enum rkisp1_ext_params_block_type type; + size_t size; + size_t offset; + uint32_t enableBit; +}; + +#define RKISP1_BLOCK_TYPE_ENTRY(block, id, type, category, bit) \ + { BlockType::block, { \ + RKISP1_EXT_PARAMS_BLOCK_TYPE_##id, \ + sizeof(struct rkisp1_cif_isp_##type##_config), \ + offsetof(struct rkisp1_params_cfg, category.type##_config), \ + RKISP1_CIF_ISP_MODULE_##bit, \ + } } + +#define RKISP1_BLOCK_TYPE_ENTRY_MEAS(block, id, type) \ + RKISP1_BLOCK_TYPE_ENTRY(block, id##_MEAS, type, meas, id) + +#define RKISP1_BLOCK_TYPE_ENTRY_OTHERS(block, id, type) \ + RKISP1_BLOCK_TYPE_ENTRY(block, id, type, others, id) + +#define RKISP1_BLOCK_TYPE_ENTRY_EXT(block, id, type) \ + { BlockType::block, { \ + RKISP1_EXT_PARAMS_BLOCK_TYPE_##id, \ + sizeof(struct rkisp1_cif_isp_##type##_config), \ + 0, 0, \ + } } + +const std::map<BlockType, BlockTypeInfo> kBlockTypeInfo = { + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Bls, BLS, bls), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Dpcc, DPCC, dpcc), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Sdg, SDG, sdg), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(AwbGain, AWB_GAIN, awb_gain), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Flt, FLT, flt), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Bdm, BDM, bdm), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Ctk, CTK, ctk), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Goc, GOC, goc), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Dpf, DPF, dpf), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(DpfStrength, DPF_STRENGTH, dpf_strength), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Cproc, CPROC, cproc), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Ie, IE, ie), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Lsc, LSC, lsc), + RKISP1_BLOCK_TYPE_ENTRY_MEAS(Awb, AWB, awb_meas), + RKISP1_BLOCK_TYPE_ENTRY_MEAS(Hst, HST, hst), + RKISP1_BLOCK_TYPE_ENTRY_MEAS(Aec, AEC, aec), + RKISP1_BLOCK_TYPE_ENTRY_MEAS(Afc, AFC, afc), + RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandBls, COMPAND_BLS, compand_bls), + RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandExpand, COMPAND_EXPAND, compand_curve), + RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandCompress, COMPAND_COMPRESS, compand_curve), +}; + +} /* namespace */ + +RkISP1ParamsBlockBase::RkISP1ParamsBlockBase(RkISP1Params *params, BlockType type, + const Span<uint8_t> &data) + : params_(params), type_(type) +{ + if (params_->format() == V4L2_META_FMT_RK_ISP1_EXT_PARAMS) { + header_ = data.subspan(0, sizeof(rkisp1_ext_params_block_header)); + data_ = data.subspan(sizeof(rkisp1_ext_params_block_header)); + } else { + data_ = data; + } +} + +void RkISP1ParamsBlockBase::setEnabled(bool enabled) +{ + /* + * For the legacy fixed format, blocks are enabled in the top-level + * header. Delegate to the RkISP1Params class. + */ + if (params_->format() == V4L2_META_FMT_RK_ISP1_PARAMS) + return params_->setBlockEnabled(type_, enabled); + + /* + * For the extensible format, set the enable and disable flags in the + * block header directly. + */ + struct rkisp1_ext_params_block_header *header = + reinterpret_cast<struct rkisp1_ext_params_block_header *>(header_.data()); + header->flags &= ~(RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE | + RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE); + header->flags |= enabled ? RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE + : RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE; +} + +RkISP1Params::RkISP1Params(uint32_t format, Span<uint8_t> data) + : format_(format), data_(data), used_(0) +{ + if (format_ == V4L2_META_FMT_RK_ISP1_EXT_PARAMS) { + struct rkisp1_ext_params_cfg *cfg = + reinterpret_cast<struct rkisp1_ext_params_cfg *>(data.data()); + + cfg->version = RKISP1_EXT_PARAM_BUFFER_V1; + cfg->data_size = 0; + + used_ += offsetof(struct rkisp1_ext_params_cfg, data); + } else { + memset(data.data(), 0, data.size()); + used_ = sizeof(struct rkisp1_params_cfg); + } +} + +void RkISP1Params::setBlockEnabled(BlockType type, bool enabled) +{ + const BlockTypeInfo &info = kBlockTypeInfo.at(type); + + struct rkisp1_params_cfg *cfg = + reinterpret_cast<struct rkisp1_params_cfg *>(data_.data()); + if (enabled) + cfg->module_ens |= info.enableBit; + else + cfg->module_ens &= ~info.enableBit; +} + +Span<uint8_t> RkISP1Params::block(BlockType type) +{ + auto infoIt = kBlockTypeInfo.find(type); + if (infoIt == kBlockTypeInfo.end()) { + LOG(RkISP1Params, Error) + << "Invalid parameters block type " + << utils::to_underlying(type); + return {}; + } + + const BlockTypeInfo &info = infoIt->second; + + /* + * For the legacy format, return a block referencing the fixed location + * of the data. + */ + if (format_ == V4L2_META_FMT_RK_ISP1_PARAMS) { + /* + * Blocks available only in extended parameters have an offset + * of 0. Return nullptr in that case. + */ + if (info.offset == 0) { + LOG(RkISP1Params, Error) + << "Block type " << utils::to_underlying(type) + << " unavailable in fixed parameters format"; + return {}; + } + + struct rkisp1_params_cfg *cfg = + reinterpret_cast<struct rkisp1_params_cfg *>(data_.data()); + + cfg->module_cfg_update |= info.enableBit; + cfg->module_en_update |= info.enableBit; + + return data_.subspan(info.offset, info.size); + } + + /* + * For the extensible format, allocate memory for the block, including + * the header. Look up the block in the cache first. If an algorithm + * requests the same block type twice, it should get the same block. + */ + auto cacheIt = blocks_.find(type); + if (cacheIt != blocks_.end()) + return cacheIt->second; + + /* Make sure we don't run out of space. */ + size_t size = sizeof(struct rkisp1_ext_params_block_header) + + ((info.size + 7) & ~7); + if (size > data_.size() - used_) { + LOG(RkISP1Params, Error) + << "Out of memory to allocate block type " + << utils::to_underlying(type); + return {}; + } + + /* Allocate a new block, clear its memory, and initialize its header. */ + Span<uint8_t> block = data_.subspan(used_, size); + used_ += size; + + struct rkisp1_ext_params_cfg *cfg = + reinterpret_cast<struct rkisp1_ext_params_cfg *>(data_.data()); + cfg->data_size += size; + + memset(block.data(), 0, block.size()); + + struct rkisp1_ext_params_block_header *header = + reinterpret_cast<struct rkisp1_ext_params_block_header *>(block.data()); + header->type = info.type; + header->size = block.size(); + + /* Update the cache. */ + blocks_[type] = block; + + return block; +} + +} /* namespace ipa::rkisp1 */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/params.h b/src/ipa/rkisp1/params.h new file mode 100644 index 00000000..40450e34 --- /dev/null +++ b/src/ipa/rkisp1/params.h @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * RkISP1 ISP Parameters + */ + +#pragma once + +#include <map> +#include <stdint.h> + +#include <linux/rkisp1-config.h> + +#include <libcamera/base/class.h> +#include <libcamera/base/span.h> + +namespace libcamera { + +namespace ipa::rkisp1 { + +enum class BlockType { + Bls, + Dpcc, + Sdg, + AwbGain, + Flt, + Bdm, + Ctk, + Goc, + Dpf, + DpfStrength, + Cproc, + Ie, + Lsc, + Awb, + Hst, + Aec, + Afc, + CompandBls, + CompandExpand, + CompandCompress, +}; + +namespace details { + +template<BlockType B> +struct block_type { +}; + +#define RKISP1_DEFINE_BLOCK_TYPE(blockType, blockStruct) \ +template<> \ +struct block_type<BlockType::blockType> { \ + using type = struct rkisp1_cif_isp_##blockStruct##_config; \ +}; + +RKISP1_DEFINE_BLOCK_TYPE(Bls, bls) +RKISP1_DEFINE_BLOCK_TYPE(Dpcc, dpcc) +RKISP1_DEFINE_BLOCK_TYPE(Sdg, sdg) +RKISP1_DEFINE_BLOCK_TYPE(AwbGain, awb_gain) +RKISP1_DEFINE_BLOCK_TYPE(Flt, flt) +RKISP1_DEFINE_BLOCK_TYPE(Bdm, bdm) +RKISP1_DEFINE_BLOCK_TYPE(Ctk, ctk) +RKISP1_DEFINE_BLOCK_TYPE(Goc, goc) +RKISP1_DEFINE_BLOCK_TYPE(Dpf, dpf) +RKISP1_DEFINE_BLOCK_TYPE(DpfStrength, dpf_strength) +RKISP1_DEFINE_BLOCK_TYPE(Cproc, cproc) +RKISP1_DEFINE_BLOCK_TYPE(Ie, ie) +RKISP1_DEFINE_BLOCK_TYPE(Lsc, lsc) +RKISP1_DEFINE_BLOCK_TYPE(Awb, awb_meas) +RKISP1_DEFINE_BLOCK_TYPE(Hst, hst) +RKISP1_DEFINE_BLOCK_TYPE(Aec, aec) +RKISP1_DEFINE_BLOCK_TYPE(Afc, afc) +RKISP1_DEFINE_BLOCK_TYPE(CompandBls, compand_bls) +RKISP1_DEFINE_BLOCK_TYPE(CompandExpand, compand_curve) +RKISP1_DEFINE_BLOCK_TYPE(CompandCompress, compand_curve) + +} /* namespace details */ + +class RkISP1Params; + +class RkISP1ParamsBlockBase +{ +public: + RkISP1ParamsBlockBase(RkISP1Params *params, BlockType type, + const Span<uint8_t> &data); + + Span<uint8_t> data() const { return data_; } + + void setEnabled(bool enabled); + +private: + LIBCAMERA_DISABLE_COPY(RkISP1ParamsBlockBase) + + RkISP1Params *params_; + BlockType type_; + Span<uint8_t> header_; + Span<uint8_t> data_; +}; + +template<BlockType B> +class RkISP1ParamsBlock : public RkISP1ParamsBlockBase +{ +public: + using Type = typename details::block_type<B>::type; + + RkISP1ParamsBlock(RkISP1Params *params, const Span<uint8_t> &data) + : RkISP1ParamsBlockBase(params, B, data) + { + } + + const Type *operator->() const + { + return reinterpret_cast<const Type *>(data().data()); + } + + Type *operator->() + { + return reinterpret_cast<Type *>(data().data()); + } + + const Type &operator*() const & + { + return *reinterpret_cast<const Type *>(data().data()); + } + + Type &operator*() & + { + return *reinterpret_cast<Type *>(data().data()); + } +}; + +class RkISP1Params +{ +public: + RkISP1Params(uint32_t format, Span<uint8_t> data); + + template<BlockType B> + RkISP1ParamsBlock<B> block() + { + return RkISP1ParamsBlock<B>(this, block(B)); + } + + uint32_t format() const { return format_; } + size_t size() const { return used_; } + +private: + friend class RkISP1ParamsBlockBase; + + Span<uint8_t> block(BlockType type); + void setBlockEnabled(BlockType type, bool enabled); + + uint32_t format_; + + Span<uint8_t> data_; + size_t used_; + + std::map<BlockType, Span<uint8_t>> blocks_; +}; + +} /* namespace ipa::rkisp1 */ + +} /* namespace libcamera*/ diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 6687c91e..9e161cab 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -6,8 +6,8 @@ */ #include <algorithm> -#include <math.h> -#include <queue> +#include <array> +#include <chrono> #include <stdint.h> #include <string.h> @@ -18,20 +18,22 @@ #include <libcamera/base/log.h> #include <libcamera/control_ids.h> +#include <libcamera/controls.h> #include <libcamera/framebuffer.h> +#include <libcamera/request.h> + #include <libcamera/ipa/ipa_interface.h> #include <libcamera/ipa/ipa_module_info.h> #include <libcamera/ipa/rkisp1_ipa_interface.h> -#include <libcamera/request.h> #include "libcamera/internal/formats.h" #include "libcamera/internal/mapped_framebuffer.h" #include "libcamera/internal/yaml_parser.h" #include "algorithms/algorithm.h" -#include "libipa/camera_sensor_helper.h" #include "ipa_context.h" +#include "params.h" namespace libcamera { @@ -81,9 +83,6 @@ private: ControlInfoMap sensorControls_; - /* Interface to the Camera Helper */ - std::unique_ptr<CameraSensorHelper> camHelper_; - /* Local parameter storage */ struct IPAContext context_; }; @@ -95,6 +94,15 @@ const IPAHwSettings ipaHwSettingsV10{ RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10, RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10, RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10, + false, +}; + +const IPAHwSettings ipaHwSettingsIMX8MP{ + RKISP1_CIF_ISP_AE_MEAN_MAX_V10, + RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10, + RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10, + RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10, + true, }; const IPAHwSettings ipaHwSettingsV12{ @@ -102,16 +110,13 @@ const IPAHwSettings ipaHwSettingsV12{ RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12, RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V12, RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V12, + false, }; /* List of controls handled by the RkISP1 IPA */ const ControlInfoMap::Map rkisp1Controls{ - { &controls::AeEnable, ControlInfo(false, true) }, { &controls::AwbEnable, ControlInfo(false, true) }, { &controls::ColourGains, ControlInfo(0.0f, 3.996f, 1.0f) }, - { &controls::Brightness, ControlInfo(-1.0f, 0.993f, 0.0f) }, - { &controls::Contrast, ControlInfo(0.0f, 1.993f, 1.0f) }, - { &controls::Saturation, ControlInfo(0.0f, 1.993f, 1.0f) }, { &controls::Sharpness, ControlInfo(0.0f, 10.0f, 1.0f) }, { &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) }, }; @@ -119,7 +124,7 @@ const ControlInfoMap::Map rkisp1Controls{ } /* namespace */ IPARkISP1::IPARkISP1() - : context_({ {}, {}, {}, { kMaxFrameContexts }, {} }) + : context_({ {}, {}, {}, {}, { kMaxFrameContexts }, {}, {} }) { } @@ -136,9 +141,11 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, /* \todo Add support for other revisions */ switch (hwRevision) { case RKISP1_V10: - case RKISP1_V_IMX8MP: context_.hw = &ipaHwSettingsV10; break; + case RKISP1_V_IMX8MP: + context_.hw = &ipaHwSettingsIMX8MP; + break; case RKISP1_V12: context_.hw = &ipaHwSettingsV12; break; @@ -151,16 +158,18 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, LOG(IPARkISP1, Debug) << "Hardware revision is " << hwRevision; - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); - if (!camHelper_) { + context_.sensorInfo = sensorInfo; + + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!context_.camHelper) { LOG(IPARkISP1, Error) << "Failed to create camera sensor helper for " << settings.sensorModel; return -ENODEV; } - context_.configuration.sensor.lineDuration = sensorInfo.minLineLength - * 1.0s / sensorInfo.pixelRate; + context_.configuration.sensor.lineDuration = + sensorInfo.minLineLength * 1.0s / sensorInfo.pixelRate; /* Load the tuning data file. */ File file(settings.configurationFile); @@ -234,6 +243,8 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.activeState = {}; context_.frameContexts.clear(); + context_.configuration.paramFormat = ipaConfig.paramFormat; + const IPACameraSensorInfo &info = ipaConfig.sensorInfo; const ControlInfo vBlank = sensorControls_.find(V4L2_CID_VBLANK)->second; context_.configuration.sensor.defVBlank = vBlank.def().get<int32_t>(); @@ -254,8 +265,10 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, minExposure * context_.configuration.sensor.lineDuration; context_.configuration.sensor.maxShutterSpeed = maxExposure * context_.configuration.sensor.lineDuration; - context_.configuration.sensor.minAnalogueGain = camHelper_->gain(minGain); - context_.configuration.sensor.maxAnalogueGain = camHelper_->gain(maxGain); + context_.configuration.sensor.minAnalogueGain = + context_.camHelper->gain(minGain); + context_.configuration.sensor.maxAnalogueGain = + context_.camHelper->gain(maxGain); context_.configuration.raw = std::any_of(streamConfig.begin(), streamConfig.end(), [](auto &cfg) -> bool { @@ -326,17 +339,13 @@ void IPARkISP1::fillParamsBuffer(const uint32_t frame, const uint32_t bufferId) { IPAFrameContext &frameContext = context_.frameContexts.get(frame); - rkisp1_params_cfg *params = - reinterpret_cast<rkisp1_params_cfg *>( - mappedBuffers_.at(bufferId).planes()[0].data()); - - /* Prepare parameters buffer. */ - memset(params, 0, sizeof(*params)); + RkISP1Params params(context_.configuration.paramFormat, + mappedBuffers_.at(bufferId).planes()[0]); for (auto const &algo : algorithms()) - algo->prepare(context_, frame, frameContext, params); + algo->prepare(context_, frame, frameContext, ¶ms); - paramsBufferReady.emit(frame); + paramsBufferReady.emit(frame, params.size()); } void IPARkISP1::processStatsBuffer(const uint32_t frame, const uint32_t bufferId, @@ -356,7 +365,7 @@ void IPARkISP1::processStatsBuffer(const uint32_t frame, const uint32_t bufferId frameContext.sensor.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>(); frameContext.sensor.gain = - camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); + context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); ControlList metadata(controls::controls); @@ -393,9 +402,9 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, /* Compute the analogue gain limits. */ const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>()); - float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>()); - float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>()); + float minGain = context_.camHelper->gain(v4l2Gain.min().get<int32_t>()); + float maxGain = context_.camHelper->gain(v4l2Gain.max().get<int32_t>()); + float defGain = context_.camHelper->gain(v4l2Gain.def().get<int32_t>()); ctrlMap.emplace(std::piecewise_construct, std::forward_as_tuple(&controls::AnalogueGain), std::forward_as_tuple(minGain, maxGain, defGain)); @@ -427,7 +436,7 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, frameDurations[1], frameDurations[2]); - ctrlMap.merge(context_.ctrlMap); + ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end()); *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); } @@ -440,7 +449,7 @@ void IPARkISP1::setControls(unsigned int frame) IPAFrameContext &frameContext = context_.frameContexts.get(frame); uint32_t exposure = frameContext.agc.exposure; - uint32_t gain = camHelper_->gainCode(frameContext.agc.gain); + uint32_t gain = context_.camHelper->gainCode(frameContext.agc.gain); ControlList ctrls(sensorControls_); ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure)); diff --git a/src/ipa/rkisp1/utils.cpp b/src/ipa/rkisp1/utils.cpp new file mode 100644 index 00000000..960ec64e --- /dev/null +++ b/src/ipa/rkisp1/utils.cpp @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Miscellaneous utility functions specific to rkisp1 + */ + +#include "utils.h" + +/** + * \file utils.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::utils { + +/** + * \fn R floatingToFixedPoint(T number) + * \brief Convert a floating point number to a fixed-point representation + * \tparam I Bit width of the integer part of the fixed-point + * \tparam F Bit width of the fractional part of the fixed-point + * \tparam R Return type of the fixed-point representation + * \tparam T Input type of the floating point representation + * \param number The floating point number to convert to fixed point + * \return The converted value + */ + +/** + * \fn R fixedToFloatingPoint(T number) + * \brief Convert a fixed-point number to a floating point representation + * \tparam I Bit width of the integer part of the fixed-point + * \tparam F Bit width of the fractional part of the fixed-point + * \tparam R Return type of the floating point representation + * \tparam T Input type of the fixed-point representation + * \param number The fixed point number to convert to floating point + * \return The converted value + */ + +} /* namespace ipa::rkisp1::utils */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/utils.h b/src/ipa/rkisp1/utils.h new file mode 100644 index 00000000..5f38b50b --- /dev/null +++ b/src/ipa/rkisp1/utils.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Miscellaneous utility functions specific to rkisp1 + */ + +#pragma once + +#include <cmath> +#include <type_traits> + +namespace libcamera { + +namespace ipa::rkisp1::utils { + +#ifndef __DOXYGEN__ +template<unsigned int I, unsigned int F, typename R, typename T, + std::enable_if_t<std::is_integral_v<R> && + std::is_floating_point_v<T>> * = nullptr> +#else +template<unsigned int I, unsigned int F, typename R, typename T> +#endif +constexpr R floatingToFixedPoint(T number) +{ + static_assert(sizeof(int) >= sizeof(R)); + static_assert(I + F <= sizeof(R) * 8); + + /* + * The intermediate cast to int is needed on arm platforms to properly + * cast negative values. See + * https://embeddeduse.com/2013/08/25/casting-a-negative-float-to-an-unsigned-int/ + */ + R mask = (1 << (F + I)) - 1; + R frac = static_cast<R>(static_cast<int>(std::round(number * (1 << F)))) & mask; + + return frac; +} + +#ifndef __DOXYGEN__ +template<unsigned int I, unsigned int F, typename R, typename T, + std::enable_if_t<std::is_floating_point_v<R> && + std::is_integral_v<T>> * = nullptr> +#else +template<unsigned int I, unsigned int F, typename R, typename T> +#endif +constexpr R fixedToFloatingPoint(T number) +{ + static_assert(sizeof(int) >= sizeof(T)); + static_assert(I + F <= sizeof(T) * 8); + + /* + * Recreate the upper bits in case of a negative number by shifting the sign + * bit from the fixed point to the first bit of the unsigned and then right shifting + * by the same amount which keeps the sign bit in place. + * This can be optimized by the compiler quite well. + */ + int remaining_bits = sizeof(int) * 8 - (I + F); + int t = static_cast<int>(static_cast<unsigned>(number) << remaining_bits) >> remaining_bits; + return static_cast<R>(t) / static_cast<R>(1 << F); +} + +} /* namespace ipa::rkisp1::utils */ + +} /* namespace libcamera */ |