[6/7] softisp: Split AWB from Combined Matrix
diff mbox series

Message ID 20260621-kbingham-awb-saturation-v1-6-b91ea59c6cfb@ideasonboard.com
State New
Headers show
Series
  • softisp: Fix Saturation and Black level handling
Related show

Commit Message

Kieran Bingham June 20, 2026, 11 p.m. UTC
The AWB and CCM are currently combined into a single matrix which gets
passed to the GPU and CPU ISP implementations. This unfortunately can
cause saturation bugs between the AWB and CCM in areas of high
brightness in a given scene, where one colour channel will saturate
before the others unevenly.

Remove AWB gains from the combined Matrix to allow a specific AWB Gain
stage to be applied, which is then clamped equally on all channels.

For CPU ISP the gains are applied and clamped before applying the CCM.

Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
---
 src/ipa/simple/algorithms/awb.cpp          | 16 +++-------------
 src/ipa/simple/ipa_context.h               |  5 +----
 src/libcamera/shaders/bayer_1x_packed.frag |  4 ++++
 src/libcamera/shaders/bayer_unpacked.frag  |  4 ++++
 src/libcamera/software_isp/debayer_cpu.cpp | 10 +++++-----
 src/libcamera/software_isp/debayer_egl.cpp |  5 +++++
 src/libcamera/software_isp/debayer_egl.h   |  3 +++
 7 files changed, 25 insertions(+), 22 deletions(-)

Patch
diff mbox series

diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp
index f5c88ea6f896ff8a7ffdc328e09a4d4aa99df5e4..05155c83d172d64609053ba940a4c12a2248bb04 100644
--- a/src/ipa/simple/algorithms/awb.cpp
+++ b/src/ipa/simple/algorithms/awb.cpp
@@ -38,15 +38,8 @@  void Awb::prepare(IPAContext &context,
 		  DebayerParams *params)
 {
 	auto &gains = context.activeState.awb.gains;
-	Matrix<float, 3, 3> gainMatrix = { { gains.r(), 0, 0,
-					     0, gains.g(), 0,
-					     0, 0, gains.b() } };
-	context.activeState.combinedMatrix =
-		gainMatrix * context.activeState.combinedMatrix;
-
-	frameContext.gains.red = gains.r();
-	frameContext.gains.blue = gains.b();
 
+	frameContext.gains = gains;
 	params->gains = gains;
 }
 
@@ -59,11 +52,8 @@  void Awb::process(IPAContext &context,
 	const SwIspStats::Histogram &histogram = stats->yHistogram;
 	const uint8_t blackLevel = context.activeState.blc.level;
 
-	const float mdGains[] = {
-		static_cast<float>(frameContext.gains.red),
-		static_cast<float>(frameContext.gains.blue)
-	};
-	metadata.set(controls::ColourGains, mdGains);
+	metadata.set(controls::ColourGains, { frameContext.gains.r(),
+					      frameContext.gains.b() });
 
 	if (!stats->valid)
 		return;
diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
index 34f7403a41d690cb3f0c271827ae2e915b6ea49d..8ccfacb46a59cedb5a0ad051d67f7c1f40af4b52 100644
--- a/src/ipa/simple/ipa_context.h
+++ b/src/ipa/simple/ipa_context.h
@@ -71,10 +71,7 @@  struct IPAFrameContext : public FrameContext {
 		double gain;
 	} sensor;
 
-	struct {
-		double red;
-		double blue;
-	} gains;
+	RGB<float> gains;
 
 	float gamma;
 	std::optional<float> contrast;
diff --git a/src/libcamera/shaders/bayer_1x_packed.frag b/src/libcamera/shaders/bayer_1x_packed.frag
index fbd15b32e5b3b510ba0dd5a75704f9ff3942e00c..6b3f7532c177f277e43c27e498dfadcb9cd4e26c 100644
--- a/src/libcamera/shaders/bayer_1x_packed.frag
+++ b/src/libcamera/shaders/bayer_1x_packed.frag
@@ -65,6 +65,7 @@  uniform vec2 tex_step;
 uniform vec2 tex_bayer_first_red;
 
 uniform sampler2D tex_y;
+uniform vec3 awb;
 uniform mat3 ccm;
 uniform vec3 blacklevel;
 uniform float gamma;
@@ -231,6 +232,9 @@  void main(void)
 	 */
 	rgb = (rgb - blacklevel) / (1.0 - blacklevel);
 
+	/* Apply AWB gains, and saturate each channel at sensor range */
+	rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0));
+
 	/*
 	 *   CCM is a 3x3 in the format
 	 *
diff --git a/src/libcamera/shaders/bayer_unpacked.frag b/src/libcamera/shaders/bayer_unpacked.frag
index 0f85e9a4ded7f43ce4fdabf1e045275ae2bc8f53..44535312125d0e7e5cca372f4e8e0f8ad9fce8b3 100644
--- a/src/libcamera/shaders/bayer_unpacked.frag
+++ b/src/libcamera/shaders/bayer_unpacked.frag
@@ -24,6 +24,7 @@  uniform sampler2D       tex_y;
 varying vec4            center;
 varying vec4            yCoord;
 varying vec4            xCoord;
+uniform vec3            awb;
 uniform mat3            ccm;
 uniform vec3            blacklevel;
 uniform float           gamma;
@@ -134,6 +135,9 @@  void main(void) {
      */
     rgb = (rgb - blacklevel) / (1.0 - blacklevel);
 
+    /* Apply AWB gains, and saturate each channel at sensor range */
+    rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0));
+
     /*
      *   CCM is a 3x3 in the format
      *
diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
index 9d6a08b3333f988570cb5d3f7cea4c117f7c2530..49382b4c2719bf02f6a806c9a9ac560c8daae866 100644
--- a/src/libcamera/software_isp/debayer_cpu.cpp
+++ b/src/libcamera/software_isp/debayer_cpu.cpp
@@ -1011,20 +1011,22 @@  void DebayerCpu::updateLookupTables(const DebayerParams &params)
 	const unsigned int gammaTableSize = gammaTable_.size();
 
 	const RGB<float> blackIndex = params.blackLevel * kRGBLookupSize;
+	const RGB<float> gains = params.gains;
 	const RGB<float> div = (RGB<float>(kRGBLookupSize) - blackIndex).max(1.0);
 
 	if (ccmEnabled_) {
 		if (gammaUpdateNeeded ||
-		    matrixChanged(params.combinedMatrix, params_.combinedMatrix)) {
+		    matrixChanged(params.combinedMatrix, params_.combinedMatrix) ||
+		    params.gains != params_.gains) {
 			auto &red = swapRedBlueGains_ ? blueCcm_ : redCcm_;
 			auto &green = greenCcm_;
 			auto &blue = swapRedBlueGains_ ? redCcm_ : blueCcm_;
 			const unsigned int redIndex = swapRedBlueGains_ ? 2 : 0;
 			const unsigned int greenIndex = 1;
 			const unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2;
-
 			for (unsigned int i = 0; i < kRGBLookupSize; i++) {
-				const RGB<float> rgb = ((RGB<float>(i) - blackIndex) * kRGBLookupSize / div).max(0.0);
+				const RGB<float> rgb = (gains * (RGB<float>(i) - blackIndex) * kRGBLookupSize / div)
+							       .clamp(0.0, kRGBLookupSize - 1);
 				red[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][0]);
 				red[i].g = std::round(rgb.r() * params.combinedMatrix[greenIndex][0]);
 				red[i].b = std::round(rgb.r() * params.combinedMatrix[blueIndex][0]);
@@ -1039,11 +1041,9 @@  void DebayerCpu::updateLookupTables(const DebayerParams &params)
 		}
 	} else {
 		if (gammaUpdateNeeded || params.gains != params_.gains) {
-			auto &gains = params.gains;
 			auto &red = swapRedBlueGains_ ? blue_ : red_;
 			auto &green = green_;
 			auto &blue = swapRedBlueGains_ ? red_ : blue_;
-
 			for (unsigned int i = 0; i < kRGBLookupSize; i++) {
 				const RGB<float> lutGains =
 					(gains * (RGB<float>(i) - blackIndex) * gammaTableSize / div)
diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
index 5213166578b6aadc26078443181b96a2c59fa9d5..6ba197966d85e1ab3bdc337b9e4ddeb20f1ec2fa 100644
--- a/src/libcamera/software_isp/debayer_egl.cpp
+++ b/src/libcamera/software_isp/debayer_egl.cpp
@@ -115,6 +115,7 @@  int DebayerEGL::getShaderVariableLocations(void)
 	attributeTexture_ = glGetAttribLocation(programId_, "textureIn");
 
 	textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y");
+	awbUniformDataIn_ = glGetUniformLocation(programId_, "awb");
 	ccmUniformDataIn_ = glGetUniformLocation(programId_, "ccm");
 	blackLevelUniformDataIn_ = glGetUniformLocation(programId_, "blacklevel");
 	gammaUniformDataIn_ = glGetUniformLocation(programId_, "gamma");
@@ -128,6 +129,7 @@  int DebayerEGL::getShaderVariableLocations(void)
 
 	LOG(Debayer, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_
 			    << " tex_y " << textureUniformBayerDataIn_
+			    << " awb " << awbUniformDataIn_
 			    << " ccm " << ccmUniformDataIn_
 			    << " blacklevel " << blackLevelUniformDataIn_
 			    << " gamma " << gammaUniformDataIn_
@@ -504,6 +506,9 @@  void DebayerEGL::setShaderVariableValues(const DebayerParams &params)
 	glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]);
 	LOG(Debayer, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel;
 
+	glUniform3f(awbUniformDataIn_, params.gains[0], params.gains[1], params.gains[2]);
+	LOG(Debayer, Debug) << " awbUniformDataIn_ " << awbUniformDataIn_ << " data " << params.gains;
+
 	/*
 	 * Gamma
 	 */
diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h
index fbd5430e4270678cf05d59cc98dff48315d2bb04..4c7c864726b7d5de1e92d4e74b0bc92860dc04c0 100644
--- a/src/libcamera/software_isp/debayer_egl.h
+++ b/src/libcamera/software_isp/debayer_egl.h
@@ -90,6 +90,9 @@  private:
 
 	GLint textureUniformBayerDataIn_;
 
+	/* Per-frame AWB gains */
+	GLint awbUniformDataIn_;
+
 	/* Represent per-frame CCM as a uniform vector of floats 3 x 3 */
 	GLint ccmUniformDataIn_;