[v6,2/4] libcamera: software_isp: Add support for monochrome formats - Stats
diff mbox series

Message ID 20261017111036.700859-3-alain.cousinie@laposte.net
State New
Headers show
Series
  • libcamera: software_isp: Add support for raw monochrome formats
Related show

Commit Message

Alain Cousinie Oct. 17, 2026, 11:10 a.m. UTC
Signed-off-by: Alain CousiniƩ <alain.cousinie@laposte.net>
---
 .../internal/software_isp/swstats_cpu.h       |  13 ++
 src/libcamera/software_isp/swstats_cpu.cpp    | 180 ++++++++++++++++++
 2 files changed, 193 insertions(+)

--
2.55.0

Patch
diff mbox series

diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h
index 551870921..f702cf126 100644
--- a/include/libcamera/internal/software_isp/swstats_cpu.h
+++ b/include/libcamera/internal/software_isp/swstats_cpu.h
@@ -101,8 +101,21 @@  private:
 	/* Bayer 12 bpp packed */
 	void statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats);
 	void statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats);
+	/* Mono 8 bpp unpacked */
+	void statsMono8Line0(const uint8_t *src[], SwIspStats &stats);
+	/* Mono 10 bpp unpacked */
+	void statsMono10Line0(const uint8_t *src[], SwIspStats &stats);
+	/* Mono 10 bpp packed */
+	void statsMono10PLine0(const uint8_t *src[], SwIspStats &stats);
+	/* Mono 12 bpp unpacked */
+	void statsMono12Line0(const uint8_t *src[], SwIspStats &stats);
+	/* Mono 12 bpp packed */
+	void statsMono12PLine0(const uint8_t *src[], SwIspStats &stats);
+	/* Mono 16 bpp unpacked */
+	void statsMono16Line0(const uint8_t *src[], SwIspStats &stats);

 	void processBayerFrame2(MappedFrameBuffer &in);
+	void processMonoFrame2(MappedFrameBuffer &in);

 	processFrameFn processFrame_;

diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
index 7fb77ce7d..f023563f1 100644
--- a/src/libcamera/software_isp/swstats_cpu.cpp
+++ b/src/libcamera/software_isp/swstats_cpu.cpp
@@ -375,6 +375,124 @@  void SwStatsCpu::statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats)
 	SWSTATS_FINISH_LINE_STATS()
 }

+/*
+ * Horizontal sub-sampling optimization: evaluates 1 pixel out of 4 (x += 4)
+ * to reduce CPU overhead. Each sample gets a weight of +4 to preserve global
+ * frame statistical density required by the Auto Exposure (AE) algorithm.
+ */
+inline void storeMonoAccumulation(SwIspStats &stats, uint64_t value)
+{
+	stats.sum_.r() += value;
+	stats.sum_.g() += value;
+	stats.sum_.b() += value;
+}
+
+void SwStatsCpu::statsMono8Line0(const uint8_t *src[], SwIspStats &stats)
+{
+	const uint8_t *cur = src[1] + window_.x;
+	uint64_t sum = 0;
+
+	for (unsigned int x = 0; x < window_.width; x += 4) {
+		sum += *cur;
+		stats.yHistogram[*cur >> 2] += 4;
+		cur += 4;
+	}
+	/* Process 4 pixels per iteration : x << 2 */
+	storeMonoAccumulation(stats,sum << 2);
+}
+
+void SwStatsCpu::statsMono10Line0(const uint8_t *src[], SwIspStats &stats)
+{
+	/*
+	 * Little-Endian optimization: Unpacked 10-bit format stores data
+	 * right-aligned in a 2-byte container. Reading via a 16-bit pointer
+	 * allows clean data access before applying the bit-shift.
+	 */
+	const uint16_t *cur = reinterpret_cast<const uint16_t *>(src[1] + window_.x * 2);
+	uint64_t sum = 0;
+
+	for (unsigned int x = 0; x < window_.width; x += 4) {
+		uint16_t val = *cur;
+		sum += val;
+		stats.yHistogram[(val >> 4) & 0x3F] += 4;
+		cur += 4;
+	}
+	/* Process 4 pixels per iteration : x 10 >> 2 to 8bits and x << 2 */
+	storeMonoAccumulation(stats,sum);
+}
+
+void SwStatsCpu::statsMono12Line0(const uint8_t *src[], SwIspStats &stats)
+{
+	/*
+	 * 12-bit unpacked processing. Data is read via a native 16-bit pointer
+	 * and right-shifted to fit the histogram range.
+	 */
+	const uint16_t *cur = reinterpret_cast<const uint16_t *>(src[1] + window_.x * 2);
+	uint64_t sum = 0;
+
+	for (unsigned int x = 0; x < window_.width; x += 4) {
+		uint16_t val = *cur;
+		sum += val;
+		stats.yHistogram[(val >> 6) & 0x3F] += 4;
+		cur += 4;
+	}
+	/* Process 4 pixels per iteration : 12 >> 4 to 8bits and x << 2 */
+	storeMonoAccumulation(stats,sum >> 2);
+}
+
+void SwStatsCpu::statsMono10PLine0(const uint8_t *src[], SwIspStats &stats)
+{
+	/*
+	 * MIPI CSI-2 10-bit packed: 4 pixels in 5 bytes. Stepping 5 bytes
+	 * evaluates the head MSB pixel of each block as a native 8-bit value.
+	 */
+	const uint8_t *cur = src[1] + window_.x * 5 / 4;
+	uint64_t sum = 0;
+
+	for (unsigned int x = 0; x < window_.width; x += 4) {
+		uint8_t val = *cur;
+		sum += val;
+		stats.yHistogram[val >> 2] += 4;
+		cur += 5;
+	}
+	/* Process 4 pixels per iteration : first byte x << 2 */
+	storeMonoAccumulation(stats,sum << 2);
+}
+
+void SwStatsCpu::statsMono12PLine0(const uint8_t *src[], SwIspStats &stats)
+{
+	/*
+	 * MIPI CSI-2 12-bit packed: 2 pixels in 3 bytes. Stepping 6 bytes
+	 * advances by two full blocks for optimal data alignment.
+	 */
+	const uint8_t *cur = src[1] + window_.x * 3 / 2;
+	uint64_t sum = 0;
+
+	for (unsigned int x = 0; x < window_.width; x += 4) {
+		uint8_t val = *cur;
+		sum += val;
+		stats.yHistogram[val >> 2] += 4;
+		cur += 6;
+	}
+	/* Process 4 pixels per iteration : first byte x << 2 */
+	storeMonoAccumulation(stats,sum << 2);
+}
+
+void SwStatsCpu::statsMono16Line0(const uint8_t *src[], SwIspStats &stats)
+{
+	const uint16_t *cur = reinterpret_cast<const uint16_t *>(src[1] + window_.x * 2);
+	uint64_t sum = 0;
+
+	for (unsigned int x = 0; x < window_.width; x += 4) {
+		uint16_t val = *cur;
+		sum += val;
+		stats.yHistogram[val >> 10] += 4;
+		cur += 4;
+	}
+	/* Process 4 pixels per iteration : 16 >> 8 to 8bits and x << 2 */
+	storeMonoAccumulation(stats,sum >> 6);
+}
+
 /**
  * \brief Reset state to start statistics gathering for a new frame
  * \param[in] frame The frame number
@@ -494,6 +612,49 @@  int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat

 	uint8_t bitDepth = bayerFormat.bitDepth;

+	if (bayerFormat.order == BayerFormat::Order::MONO) {
+		/* No skip line for mono */
+		ySkipMask_ = 0;
+		xShift_ = 0;
+		sumShift_ = 0;
+		processFrame_ = &SwStatsCpu::processMonoFrame2;
+
+		if (bayerFormat.packing == BayerFormat::Packing::None) {
+			patternSize_.width = 2;
+			patternSize_.height = 2;
+
+			switch (bitDepth) {
+			case 8:
+				stats0_ = &SwStatsCpu::statsMono8Line0;
+				return 0;
+			case 10:
+				stats0_ = &SwStatsCpu::statsMono10Line0;
+				return 0;
+			case 12:
+				stats0_ = &SwStatsCpu::statsMono12Line0;
+				return 0;
+			case 16:
+				stats0_ = &SwStatsCpu::statsMono16Line0;
+				return 0;
+			}
+		}
+		if (bayerFormat.packing == BayerFormat::Packing::CSI2) {
+			patternSize_.height = 2;
+
+			switch (bitDepth) {
+			case 10:
+				patternSize_.width = 4;
+				stats0_ = &SwStatsCpu::statsMono10PLine0;
+				return 0;
+			case 12:
+				patternSize_.width = 2;
+				stats0_ = &SwStatsCpu::statsMono12PLine0;
+				return 0;
+			}
+		}
+		return -EINVAL;
+	}
+
 	if ((bitDepth == 10 || bitDepth == 12) &&
 	    bayerFormat.packing == BayerFormat::Packing::CSI2) {
 		if (bitDepth == 10)
@@ -594,6 +755,25 @@  void SwStatsCpu::processBayerFrame2(MappedFrameBuffer &in)
 	}
 }

+void SwStatsCpu::processMonoFrame2(MappedFrameBuffer &in)
+{
+	const uint8_t *src = in.planes()[0].data();
+	const uint8_t *linePointers[3];
+
+	src += window_.y * stride_;
+
+	for (unsigned int y = 0; y < window_.height; y++) {
+		if (y & ySkipMask_) {
+			src += stride_;
+			continue;
+		}
+
+		linePointers[1] = src;
+		(this->*stats0_)(linePointers, stats_[0]);
+		src += stride_;
+	}
+}
+
 /**
  * \brief Calculate statistics for a frame in one go
  * \param[in] frame The frame number