[13/14] ipa: rppx1: Add AWB algorithm
diff mbox series

Message ID 20260618-rppx1-ipa-v1-13-32337264cfcd@ideasonboard.com
State New
Headers show
Series
  • libcamera: Add support for R-Car Gen4 and RPP-X1 ISP
Related show

Commit Message

Jacopo Mondi June 18, 2026, 10:18 a.m. UTC
Add Awb algorithm to the RPP-X1 IPA.

The implementation is based on libIPA AwbAlgorithm.

Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
 src/ipa/rppx1/algorithms/awb.cpp     | 298 +++++++++++++++++++++++++++++++++++
 src/ipa/rppx1/algorithms/awb.h       |  48 ++++++
 src/ipa/rppx1/algorithms/meson.build |   5 +
 src/ipa/rppx1/ipa_context.cpp        |  30 ++++
 src/ipa/rppx1/ipa_context.h          |  10 ++
 src/ipa/rppx1/meson.build            |   4 +
 6 files changed, 395 insertions(+)

Patch
diff mbox series

diff --git a/src/ipa/rppx1/algorithms/awb.cpp b/src/ipa/rppx1/algorithms/awb.cpp
new file mode 100644
index 000000000000..6316606b4b0f
--- /dev/null
+++ b/src/ipa/rppx1/algorithms/awb.cpp
@@ -0,0 +1,298 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026, Ideas On Board
+ *
+ * RPP-X1 AWB control algorithm
+ */
+
+
+#include "awb.h"
+
+#include <libcamera/base/log.h>
+#include <libcamera/internal/vector.h>
+
+/**
+ * \file awb.h
+ * \brief RPP-X1 Auto White Balance
+ */
+
+namespace libcamera {
+
+namespace ipa::rppx1::algorithms {
+
+/* \todo RPP-X1 doesn't support the Lux algorithm. */
+static constexpr unsigned int kDefaultLux = 500;
+
+LOG_DEFINE_CATEGORY(RppX1Awb)
+
+namespace {
+
+/* Quantize a gain to the UQ6.12 register format. */
+constexpr uint32_t quantizeGain(double gain)
+{
+	return UQ<6, 12>(static_cast<float>(gain)).quantized();
+}
+
+}; /* namespace */
+
+/*
+ * The RPP-X1 ISP has a bus width of 24 bits and all the measurement limits are
+ * expressed as 24 bit values. In order to make it simpler to express limits
+ * in code use an 8-bit value and left-shift by 16 to match the 24 bit width of
+ * the ISP processing pipeline.
+ */
+constexpr unsigned int kRPPX1BusWidthShift = 16;
+
+class RppX1AwbStats final : public AwbStats
+{
+public:
+	RppX1AwbStats() {}
+	RppX1AwbStats(const RGB<double> &rgbMeans)
+		: AwbStats(rgbMeans)
+	{
+	}
+
+	/* Minimum mean value below which AWB can't operate. */
+	double minColourValue() const override
+	{
+		return 2.0;
+	}
+};
+
+/**
+ * \class Awb
+ * \brief RPP-X1 white balance correction algorithm
+ */
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::init
+ */
+int Awb::init(IPAContext &context, const ValueNode &tuningData)
+{
+	return awbAlgo_.init(tuningData, context.ctrlMap);
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::configure
+ */
+int Awb::configure(IPAContext &context,
+		   const IPACameraSensorInfo &configInfo)
+{
+	awbAlgo_.configure(context.activeState.awb);
+
+	/*
+	 * Define the measurement window for AWB as a centered rectangle
+	 * covering 3/4 of the image width and height.
+	 */
+	context.configuration.awb.measureWindow.h_offs = configInfo.outputSize.width / 8;
+	context.configuration.awb.measureWindow.v_offs = configInfo.outputSize.height / 8;
+	context.configuration.awb.measureWindow.h_size = 3 * configInfo.outputSize.width / 4;
+	context.configuration.awb.measureWindow.v_size = 3 * configInfo.outputSize.height / 4;
+
+	context.configuration.awb.enabled = true;
+
+	return 0;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::queueRequest
+ */
+void Awb::queueRequest(IPAContext &context, const uint32_t frame,
+		       IPAFrameContext &frameContext,
+		       const ControlList &controls)
+{
+	awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,
+			      controls);
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void Awb::prepare(IPAContext &context, const uint32_t frame,
+		  IPAFrameContext &frameContext, RppX1Params *params)
+{
+	awbAlgo_.prepare(context.activeState.awb, frameContext.awb);
+
+	auto gainConfig = params->block<BlockType::AwbgPre1>();
+	gainConfig.setEnabled(true);
+
+	gainConfig->gain_green_b = quantizeGain(frameContext.awb.gains.g());
+	gainConfig->gain_blue = quantizeGain(frameContext.awb.gains.b());
+	gainConfig->gain_red = quantizeGain(frameContext.awb.gains.r());
+	gainConfig->gain_green_r = quantizeGain(frameContext.awb.gains.g());
+
+	if (frame > 0)
+		return;
+
+	/*
+	 * If this is the first frame, program the white balance measurement
+	 * engine.
+	 */
+
+	auto awbConfig = params->block<BlockType::WbmeasPost>();
+	awbConfig.setEnabled(true);
+
+	/* Configure the measure window for AWB. */
+	awbConfig->wnd = context.configuration.awb.measureWindow;
+
+	/* Number of frames to use to estimate the means (0 means 1 frame). */
+	awbConfig->frames = 0;
+
+	/* Use YCbCr measurement mode. */
+	awbConfig->mode = RPPX1_WBMEAS_MODE_YCBCR;
+
+	/* Set the reference Cr and Cb (AWB target) to white. */
+	awbConfig->ref_cb_max_b = 128 << kRPPX1BusWidthShift;
+	awbConfig->ref_cr_max_r = 128 << kRPPX1BusWidthShift;
+
+	/*
+	 * Filter out pixels based on luminance and chrominance values.
+	 * The acceptable luma values are specified as a [16, 250]
+	 * range, while the acceptable chroma values are specified
+	 * with a minimum of 16 and a maximum Cb+Cr sum of 250;
+	 */
+	awbConfig->min_y_max_g = 16 << kRPPX1BusWidthShift;
+	awbConfig->max_y = 250 << kRPPX1BusWidthShift;
+	awbConfig->min_c = 16 << kRPPX1BusWidthShift;
+	awbConfig->max_csum = 250 << kRPPX1BusWidthShift;
+
+	/* \todo Disable ymax_cmp even if we program 'max_y' */
+	awbConfig->ymax_cmp = 0;
+
+	/*
+	 * Program coefficients and offsets to perform RGB-to-YCbCr
+	 * conversion according to the BT.601 specification for limited
+	 * range YUV.
+	 *
+	 *  Y = 16 + 0.2500 R + 0.5000 G + 0.1094 B
+	 *  Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
+	 *  Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
+	 *
+	 * which resuls in the following register values:
+	 *
+	 * - coefficients are signed Q4.12 format
+	 * - their numerical value is then 'reg / 2^12'
+	 * - negative numbers need to reverse the 2's complement
+	 *   (!(reg & !BIT(16)) + 1) / 2^12
+	 *
+	 * coeff G0 0x00000800; = 0.500
+	 * coeff B0 0x000001c0; = 0.1094
+	 * coeff R0 0x00000400; = 0.2500
+	 * coeff G1 0x0000fb40; = -0.2969
+	 * coeff B1 0x00000700; = 0.4375
+	 * coeff R1 0x0000fdc0; = -0.1406
+	 * coeff G2 0x0000fa00; = -0.3750
+	 * coeff B2 0x0000ff00; = -0.0625
+	 * coeff R2 0x00000700; = 0.4375
+	 * offset R 0x00100000; offset_r = (16 << 16)
+	 * offset G 0x00800000; offset_g = (128 << 16)
+	 * offset B 0x00800000; offset_b = (128 << 16)
+	 *
+	 * Use the inverse of this matrix in calculateRgbMeans() to
+	 * reverse the colorspace conversion.
+	 */
+	awbConfig->ccor_coeff[0][0] = 0x00000800;
+	awbConfig->ccor_coeff[0][1] = 0x000001c0;
+	awbConfig->ccor_coeff[0][2] = 0x00000400;
+	awbConfig->ccor_coeff[1][0] = 0x0000fb40;
+	awbConfig->ccor_coeff[1][1] = 0x00000700;
+	awbConfig->ccor_coeff[1][2] = 0x0000fdc0;
+	awbConfig->ccor_coeff[2][0] = 0x0000fa00;
+	awbConfig->ccor_coeff[2][1] = 0x0000ff00;
+	awbConfig->ccor_coeff[2][2] = 0x00000700;
+
+	awbConfig->ccor_offs[0] = 16 << kRPPX1BusWidthShift;
+	awbConfig->ccor_offs[1] = 128 << kRPPX1BusWidthShift;
+	awbConfig->ccor_offs[2] = 128 << kRPPX1BusWidthShift;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::process
+ */
+void Awb::process(IPAContext &context,
+		  [[maybe_unused]] const uint32_t frame,
+		  IPAFrameContext &frameContext,
+		  const RppX1Stats *stats,
+		  ControlList &metadata)
+{
+	RppX1AwbStats awbStats = calculateRgbMeans(frameContext, stats);
+
+	awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,
+			 kDefaultLux, metadata);
+}
+
+RppX1AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext,
+				     const RppX1Stats *stats) const
+{
+	if (!stats)
+		return {};
+
+	const auto awb = stats->block<StatsType::WbmeasPost>();
+	if (!awb)
+		return {};
+
+	if (awb->cnt == 0) {
+		LOG(RppX1Awb, Debug) << "AWB statistics are empty";
+		return {};
+	}
+
+	Vector<double, 3> rgbMeans;
+
+	/* Get the YCbCr mean values: we use YCbCr measurement mode. */
+	Vector<double, 3> yuvMeans({
+		static_cast<double>(awb->mean_y_or_g),
+		static_cast<double>(awb->mean_cb_or_b),
+		static_cast<double>(awb->mean_cr_or_r)
+	});
+
+	/*
+	 * Convert from YCbCr to RGB. The statistics engine has been
+	 * programmed with the following matrix:
+	 *
+	 * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B
+	 * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B
+	 * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B
+	 *
+	 * Use the inverse matrix here.
+	 */
+	static const Matrix<double, 3, 3> yuv2rgbMatrix({
+		1.1636, -0.0623,  1.6008,
+		1.1636, -0.4045, -0.7949,
+		1.1636,  1.9912, -0.0250
+	});
+	static const Vector<double, 3> yuv2rgbOffset({
+		16, 128, 128
+	});
+
+	rgbMeans = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);
+
+	/*
+	 * Due to hardware rounding errors in the YCbCr means, the
+	 * calculated RGB means may be negative. This would lead to
+	 * negative gains, messing up calculation. Prevent this by
+	 * clamping the means to positive values.
+	 */
+	rgbMeans = rgbMeans.max(0.0);
+
+	/*
+	 * \todo
+	 * The ISP computes the AWB means after applying the CCM. Apply
+	 * the inverse as we want to get the raw means before the colour gains.
+	 * rgbMeans = frameContext.ccm.ccm.inverse() * rgbMeans;
+	 */
+
+	/*
+	 * The ISP computes the AWB means after applying the colour gains,
+	 * divide by the gains that were used to get the raw means from the
+	 * sensor. Apply a minimum value to avoid divisions by near-zero.
+	 */
+	rgbMeans /= frameContext.awb.gains.max(0.01);
+
+	return RppX1AwbStats(rgbMeans);
+}
+
+REGISTER_IPA_ALGORITHM(Awb, "Awb")
+
+} /* namespace ipa::rppx1::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rppx1/algorithms/awb.h b/src/ipa/rppx1/algorithms/awb.h
new file mode 100644
index 000000000000..2e4f67f11bc0
--- /dev/null
+++ b/src/ipa/rppx1/algorithms/awb.h
@@ -0,0 +1,48 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026, Ideas On Board
+ *
+ * AWB control algorithm
+ */
+
+#pragma once
+
+#include "libipa/awb.h"
+#include "libipa/fixedpoint.h"
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::rppx1::algorithms {
+
+class RppX1AwbStats;
+
+class Awb : public Algorithm
+{
+public:
+	Awb() = default;
+	~Awb() = default;
+
+	int init(IPAContext &context, const ValueNode &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,
+		     RppX1Params *params) override;
+	void process(IPAContext &context, const uint32_t frame,
+		     IPAFrameContext &frameContext,
+		     const RppX1Stats *stats,
+		     ControlList &metadata) override;
+
+private:
+	RppX1AwbStats calculateRgbMeans(const IPAFrameContext &frameContext,
+					const RppX1Stats *stats) const;
+
+	AwbAlgorithm<UQ<6, 12>> awbAlgo_;
+};
+
+} /* namespace ipa::rppx1::algorithms */
+} /* namespace libcamera */
diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build
new file mode 100644
index 000000000000..70368ad9bac9
--- /dev/null
+++ b/src/ipa/rppx1/algorithms/meson.build
@@ -0,0 +1,5 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+rppx1_ipa_algorithms = files([
+    'awb.cpp',
+])
diff --git a/src/ipa/rppx1/ipa_context.cpp b/src/ipa/rppx1/ipa_context.cpp
index 5ac60dfc7bf6..ea7008f5685d 100644
--- a/src/ipa/rppx1/ipa_context.cpp
+++ b/src/ipa/rppx1/ipa_context.cpp
@@ -16,21 +16,51 @@ 
 
 namespace libcamera::ipa::rppx1 {
 
+/**
+ * \struct RppX1AwbSession
+ * \brief RPP-X1 Awb session configuration
+ */
+
+/**
+ * \var RppX1AwbSession::measureWindow
+ * \brief Awb measurement window
+ */
+
+/**
+ * \var RppX1AwbSession::enabled
+ * \brief Awb enabled flag
+ */
+
 /**
  * \struct IPASessionConfiguration
  * \brief Session configuration for the IPA module
  */
 
+/**
+ * \var IPASessionConfiguration::awb
+ * \brief Awb session configuration
+ */
+
 /**
  * \struct IPAActiveState
  * \brief Active state for algorithms
  */
 
+/**
+ * \var IPAActiveState::awb
+ * \copydoc ipa::awb::ActiveState
+ */
+
 /**
  * \struct IPAFrameContext
  * \brief Per-frame context for algorithms
  */
 
+/**
+ * \var IPAFrameContext::awb
+ * \copydoc ipa::awb::FrameContext
+ */
+
 /**
  * \struct IPAContext
  * \brief Global IPA context data shared between all algorithms
diff --git a/src/ipa/rppx1/ipa_context.h b/src/ipa/rppx1/ipa_context.h
index f268a35081bc..57197d865d3f 100644
--- a/src/ipa/rppx1/ipa_context.h
+++ b/src/ipa/rppx1/ipa_context.h
@@ -20,17 +20,27 @@ 
 #include <libipa/camera_sensor_helper.h>
 #include <libipa/fc_queue.h>
 
+#include "libipa/awb.h"
+
 namespace libcamera {
 
 namespace ipa::rppx1 {
 
+struct RppX1AwbSession {
+	struct rppx1_window measureWindow;
+	bool enabled;
+};
+
 struct IPASessionConfiguration {
+	struct RppX1AwbSession awb;
 };
 
 struct IPAActiveState {
+	ipa::awb::ActiveState awb;
 };
 
 struct IPAFrameContext : public FrameContext {
+	ipa::awb::FrameContext awb;
 };
 
 struct IPAContext {
diff --git a/src/ipa/rppx1/meson.build b/src/ipa/rppx1/meson.build
index 8034fe24d241..8b8c79e997c1 100644
--- a/src/ipa/rppx1/meson.build
+++ b/src/ipa/rppx1/meson.build
@@ -1,4 +1,6 @@ 
 # SPDX-License-Identifier: CC0-1.0
+#
+subdir('algorithms')
 
 ipa_name = 'ipa_rppx1'
 
@@ -7,6 +9,8 @@  rppx1_ipa_sources = files([
     'rppx1.cpp',
 ])
 
+rppx1_ipa_sources += rppx1_ipa_algorithms
+
 mod = shared_module(ipa_name, rppx1_ipa_sources,
                     name_prefix : '',
                     include_directories : [ipa_includes],