[v5,07/11] tests: Check the configured CPU image crop
diff mbox series

Message ID 20261029152058.896209-8-halfline@gmail.com
State Rejected
Headers show
Series
  • software-isp: Align metering and report the configured crop
Related show

Commit Message

Ray Strode Oct. 29, 2026, 3:20 p.m. UTC
From: Ray Strode <rstrode@redhat.com>

This commit checks the crop returned by CPU debayering for supported Bayer
input formats and both RGB channel orders.

Unpacked kernels may shift the first rendered pixel by one column depending
on the Bayer pattern and output channel order. The reported image region
must include that shift, while the specialized packed kernels retain their
unshifted origin.

The configuration-only cases cover all four standard Bayer orders at 8, 10
and 12 bits, including packed RAW10 and RAW12. Reusing the debayer across
configurations also checks that changing formats clears a previous phase
shift.

Signed-off-by: Ray Strode <rstrode@redhat.com>
---
 test/meson.build                   |   1 +
 test/software_isp/debayer-crop.cpp | 102 +++++++++++++++++++++++++++++
 test/software_isp/meson.build      |  20 ++++++
 3 files changed, 123 insertions(+)
 create mode 100644 test/software_isp/debayer-crop.cpp
 create mode 100644 test/software_isp/meson.build

Patch
diff mbox series

diff --git a/test/meson.build b/test/meson.build
index 5027b7b1..fc72d105 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -8,60 +8,61 @@  endif
 test_enabled = true
 
 # When ASan is enabled, find the path to the ASan runtime needed by multiple
 # tests. This currently works with gcc only, as clang uses different file names
 # depending on the compiler version and target architecture.
 asan_enabled = false
 asan_runtime_missing = false
 
 if get_option('b_sanitize').contains('address')
     asan_enabled = true
 
     if cc.get_id() == 'gcc'
         asan_runtime = run_command(cc, '-print-file-name=libasan.so', check : true).stdout().strip()
     else
         asan_runtime_missing = true
     endif
 endif
 
 subdir('libtest')
 
 subdir('camera')
 subdir('controls')
 subdir('gstreamer')
 subdir('ipa')
 subdir('ipc')
 subdir('log')
 subdir('media_device')
 subdir('process')
 subdir('py')
 subdir('serialization')
+subdir('software_isp')
 subdir('stream')
 subdir('v4l2_compat')
 subdir('v4l2_subdevice')
 subdir('v4l2_videodevice')
 
 public_tests = [
     {'name': 'color-space', 'sources': ['color-space.cpp']},
     {'name': 'geometry', 'sources': ['geometry.cpp']},
     {'name': 'public-api', 'sources': ['public-api.cpp']},
     {'name': 'signal', 'sources': ['signal.cpp']},
     {'name': 'transform', 'sources': ['transform.cpp']},
 ]
 
 internal_tests = [
     {'name': 'bayer-format', 'sources': ['bayer-format.cpp']},
     {'name': 'byte-stream-buffer', 'sources': ['byte-stream-buffer.cpp']},
     {'name': 'camera-sensor', 'sources': ['camera-sensor.cpp']},
     {'name': 'debayer-geometry', 'sources': ['debayer-geometry.cpp']},
     {'name': 'delayed_controls', 'sources': ['delayed_controls.cpp']},
     {'name': 'event', 'sources': ['event.cpp']},
     {'name': 'event-dispatcher', 'sources': ['event-dispatcher.cpp']},
     {'name': 'event-thread', 'sources': ['event-thread.cpp']},
     {'name': 'file', 'sources': ['file.cpp']},
     {'name': 'flags', 'sources': ['flags.cpp']},
     {'name': 'hotplug-cameras', 'sources': ['hotplug-cameras.cpp']},
     {'name': 'matrix', 'sources': ['matrix.cpp']},
     {'name': 'message', 'sources': ['message.cpp']},
     {'name': 'object', 'sources': ['object.cpp']},
     {'name': 'object-delete', 'sources': ['object-delete.cpp']},
     {'name': 'object-invoke', 'sources': ['object-invoke.cpp']},
diff --git a/test/software_isp/debayer-crop.cpp b/test/software_isp/debayer-crop.cpp
new file mode 100644
index 00000000..4f9f8b9a
--- /dev/null
+++ b/test/software_isp/debayer-crop.cpp
@@ -0,0 +1,102 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2026, Red Hat Inc.
+ *
+ * CPU debayer crop configuration tests
+ */
+
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include <libcamera/camera_manager.h>
+#include <libcamera/formats.h>
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/formats.h"
+#include "libcamera/internal/software_isp/swstats_cpu.h"
+
+#include "debayer_cpu.h"
+#include "test.h"
+
+using namespace libcamera;
+
+class DebayerCropTest : public Test
+{
+protected:
+	int run() override
+	{
+		CameraManager manager;
+		auto stats = std::make_unique<SwStatsCpu>(manager);
+		if (!stats->isValid())
+			return TestFail;
+
+		DebayerCpu debayer(std::move(stats), manager);
+		static const struct {
+			PixelFormat input;
+			int rgbShift;
+			int bgrShift;
+		} cases[] = {
+			{ formats::SBGGR8, 0, 1 },
+			{ formats::SGBRG8, 1, 0 },
+			{ formats::SGRBG8, 0, 1 },
+			{ formats::SRGGB8, 1, 0 },
+			{ formats::SBGGR10, 0, 1 },
+			{ formats::SGBRG10, 1, 0 },
+			{ formats::SGRBG10, 0, 1 },
+			{ formats::SRGGB10, 1, 0 },
+			{ formats::SBGGR12, 0, 1 },
+			{ formats::SGBRG12, 1, 0 },
+			{ formats::SGRBG12, 0, 1 },
+			{ formats::SRGGB12, 1, 0 },
+			/* Packed kernels use each Bayer order without a phase shift. */
+			{ formats::SBGGR10_CSI2P, 0, 0 },
+			{ formats::SGBRG10_CSI2P, 0, 0 },
+			{ formats::SGRBG10_CSI2P, 0, 0 },
+			{ formats::SRGGB10_CSI2P, 0, 0 },
+			{ formats::SBGGR12_CSI2P, 0, 0 },
+			{ formats::SGBRG12_CSI2P, 0, 0 },
+			{ formats::SGRBG12_CSI2P, 0, 0 },
+			{ formats::SRGGB12_CSI2P, 0, 0 },
+		};
+
+		for (const auto &entry : cases) {
+			StreamConfiguration input;
+			input.pixelFormat = entry.input;
+			input.size = { 1928, 1092 };
+			input.stride = PixelFormatInfo::info(entry.input).stride(input.size.width, 0);
+
+			for (PixelFormat format : { formats::RGB888, formats::BGR888,
+						    formats::XRGB8888, formats::XBGR8888 }) {
+				StreamConfiguration output;
+				output.pixelFormat = format;
+				output.size = { 1280, 720 };
+				std::tie(output.stride, output.frameSize) =
+					debayer.strideAndFrameSize(format, output.size);
+				const std::vector<std::reference_wrapper<const StreamConfiguration>> outputs{ output };
+				if (debayer.configure(input, outputs, false, false)) {
+					std::cerr << "Unable to configure " << entry.input << " -> " << format
+						  << std::endl;
+					return TestFail;
+				}
+
+				int shift = (format == formats::RGB888 || format == formats::XRGB8888)
+						    ? entry.rgbShift
+						    : entry.bgrShift;
+				Rectangle expected(324 + shift, 186, 1280, 720);
+				if (debayer.inputCrop() != expected) {
+					std::cerr << "Crop for " << entry.input << " -> " << format << " is "
+						  << debayer.inputCrop() << ", expected " << expected << std::endl;
+					return TestFail;
+				}
+			}
+		}
+
+		return TestPass;
+	}
+};
+
+TEST_REGISTER(DebayerCropTest)
diff --git a/test/software_isp/meson.build b/test/software_isp/meson.build
new file mode 100644
index 00000000..ebabde59
--- /dev/null
+++ b/test/software_isp/meson.build
@@ -0,0 +1,20 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+if not softisp_enabled
+    subdir_done()
+endif
+
+software_isp_tests = [
+    {'name': 'debayer-crop', 'sources': ['debayer-crop.cpp']},
+]
+
+foreach test : software_isp_tests
+    exe = executable(test['name'], test['sources'],
+                     dependencies : libcamera_private,
+                     implicit_include_directories : false,
+                     link_with : test_libraries,
+                     include_directories : [test_includes_internal,
+                                            '../../src/libcamera/software_isp/'])
+
+    test(test['name'], exe, suite : 'software_isp')
+endforeach