[igt-dev] [PATCH i-g-t 4/4] tests/amdgpu: Add HDR tests for amdgpu

Nicholas Kazlauskas nicholas.kazlauskas at amd.com
Wed Jun 5 14:44:32 UTC 2019


Tests various HDR10 usecases, such as:

- Transitions between 8bpc and 10bpc
- Transitions between SDR and HDR
- Fast static metadata switches
- Display level verification for infoframes using a luminance sensor

The tests require the "output_bpc" debugfs entry to read current
and maximum bpc.

Cc: Harry Wentland <harry.wentland at amd.com>
Cc: Leo Li <sunpeng.li at amd.com>
Signed-off-by: Nicholas Kazlauskas <nicholas.kazlauskas at amd.com>
---
 tests/Makefile.sources   |   1 +
 tests/amdgpu/amd_hdr.c   | 703 +++++++++++++++++++++++++++++++++++++++
 tests/amdgpu/meson.build |   1 +
 3 files changed, 705 insertions(+)
 create mode 100644 tests/amdgpu/amd_hdr.c

diff --git a/tests/Makefile.sources b/tests/Makefile.sources
index 027ed82f..38eae58d 100644
--- a/tests/Makefile.sources
+++ b/tests/Makefile.sources
@@ -8,6 +8,7 @@ AMDGPU_TESTS = \
 	amdgpu/amd_basic \
 	amdgpu/amd_color \
 	amdgpu/amd_cs_nop \
+	amdgpu/amd_hdr \
 	amdgpu/amd_prime \
 	amdgpu/amd_abm \
 	$(NULL)
diff --git a/tests/amdgpu/amd_hdr.c b/tests/amdgpu/amd_hdr.c
new file mode 100644
index 00000000..56eea547
--- /dev/null
+++ b/tests/amdgpu/amd_hdr.c
@@ -0,0 +1,703 @@
+/*
+ * Copyright 2019 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "igt.h"
+#include <fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+
+/* DRM HDR definitions. Not in the UAPI header, unfortunately. */
+
+enum hdmi_metadata_type {
+	HDMI_STATIC_METADATA_TYPE1 = 1,
+};
+
+enum hdmi_eotf {
+	HDMI_EOTF_TRADITIONAL_GAMMA_SDR,
+	HDMI_EOTF_TRADITIONAL_GAMMA_HDR,
+	HDMI_EOTF_SMPTE_ST2084,
+};
+
+/* Test flags. */
+enum {
+	TEST_NONE = 0,
+	TEST_DPMS = 1 << 0,
+	TEST_SUSPEND = 1 << 1,
+};
+
+/* BPC connector state. */
+typedef struct output_bpc {
+	unsigned int current;
+	unsigned int maximum;
+} output_bpc_t;
+
+/* Common test data. */
+typedef struct data {
+	igt_display_t display;
+	igt_plane_t *primary;
+	igt_output_t *output;
+	igt_pipe_t *pipe;
+	igt_pipe_crc_t *pipe_crc;
+	drmModeModeInfo *mode;
+	enum pipe pipe_id;
+	int fd;
+	int sensor_fd;
+	int w;
+	int h;
+} data_t;
+
+/* Converts a double to 861-G spec FP format. */
+static uint16_t calc_hdr_float(double val)
+{
+	return (uint16_t)(val * 50000.0);
+}
+
+/* PQ Inverse, L normalized luminance to signal value V. */
+static double calc_pq_inverse(double l)
+{
+	double c1 = 0.8359375;
+	double c2 = 18.8515625;
+	double c3 = 18.6875;
+	double m1 = 0.1593017578125;
+	double m2 = 78.84375;
+	double lm = pow(l, m1);
+
+	return pow((c1 + c2 * lm) / (1.0 + c3 * lm), m2);
+}
+
+/* Asserts that two given values in nits are equal within a given threshold. */
+static void assert_nits_equal(double n0, double n1, double threshold)
+{
+	double diff = fabs(n0 - n1);
+
+	igt_assert_f(diff <= threshold,
+		     "Nits not in threshold: | %.3f - %.3f | > %.3f\n",
+		     n0, n1, threshold);
+}
+
+/* Fills some test values for HDR metadata targeting SDR. */
+static void fill_hdr_output_metadata_sdr(struct hdr_output_metadata *meta)
+{
+	memset(meta, 0, sizeof(*meta));
+
+	meta->metadata_type = HDMI_STATIC_METADATA_TYPE1;
+	meta->hdmi_metadata_type1.eotf = HDMI_EOTF_TRADITIONAL_GAMMA_SDR;
+
+	/* Rec. 709 */
+	meta->hdmi_metadata_type1.display_primaries[0].x =
+		calc_hdr_float(0.640); /* Red */
+	meta->hdmi_metadata_type1.display_primaries[0].y =
+		calc_hdr_float(0.330);
+	meta->hdmi_metadata_type1.display_primaries[1].x =
+		calc_hdr_float(0.300); /* Green */
+	meta->hdmi_metadata_type1.display_primaries[1].y =
+		calc_hdr_float(0.600);
+	meta->hdmi_metadata_type1.display_primaries[2].x =
+		calc_hdr_float(0.150); /* Blue */
+	meta->hdmi_metadata_type1.display_primaries[2].y =
+		calc_hdr_float(0.006);
+	meta->hdmi_metadata_type1.white_point.x = calc_hdr_float(0.3127);
+	meta->hdmi_metadata_type1.white_point.y = calc_hdr_float(0.3290);
+
+	meta->hdmi_metadata_type1.max_display_mastering_luminance = 0;
+	meta->hdmi_metadata_type1.min_display_mastering_luminance = 0;
+	meta->hdmi_metadata_type1.max_fall = 0;
+	meta->hdmi_metadata_type1.max_cll = 0;
+}
+
+/* Fills some test values for ST2048 HDR output metadata.
+ *
+ * Note: there isn't really a standard for what the metadata is supposed
+ * to do on the display side of things. The display is free to ignore it
+ * and clip the output, use it to help tonemap to the content range,
+ * or do anything they want, really.
+ */
+static void fill_hdr_output_metadata_st2048(struct hdr_output_metadata *meta)
+{
+	memset(meta, 0, sizeof(*meta));
+
+	meta->metadata_type = HDMI_STATIC_METADATA_TYPE1;
+	meta->hdmi_metadata_type1.eotf = HDMI_EOTF_SMPTE_ST2084;
+
+	/* Rec. 2020 */
+	meta->hdmi_metadata_type1.display_primaries[0].x =
+		calc_hdr_float(0.708); /* Red */
+	meta->hdmi_metadata_type1.display_primaries[0].y =
+		calc_hdr_float(0.292);
+	meta->hdmi_metadata_type1.display_primaries[1].x =
+		calc_hdr_float(0.170); /* Green */
+	meta->hdmi_metadata_type1.display_primaries[1].y =
+		calc_hdr_float(0.797);
+	meta->hdmi_metadata_type1.display_primaries[2].x =
+		calc_hdr_float(0.131); /* Blue */
+	meta->hdmi_metadata_type1.display_primaries[2].y =
+		calc_hdr_float(0.046);
+	meta->hdmi_metadata_type1.white_point.x = calc_hdr_float(0.3127);
+	meta->hdmi_metadata_type1.white_point.y = calc_hdr_float(0.3290);
+
+	meta->hdmi_metadata_type1.max_display_mastering_luminance =
+		1000; /* 1000 nits */
+	meta->hdmi_metadata_type1.min_display_mastering_luminance =
+		500;				   /* 0.05 nits */
+	meta->hdmi_metadata_type1.max_fall = 1000; /* 1000 nits */
+	meta->hdmi_metadata_type1.max_cll = 500;   /* 500 nits */
+}
+
+/* Sets the HDR output metadata prop. */
+static void set_hdr_output_metadata(data_t *data,
+				    struct hdr_output_metadata const *meta)
+{
+	igt_output_replace_prop_blob(data->output,
+				     IGT_CONNECTOR_HDR_OUTPUT_METADATA, meta,
+				     meta ? sizeof(*meta) : 0);
+}
+
+/* Common test setup. */
+static void test_init(data_t *data)
+{
+	igt_display_t *display = &data->display;
+
+	/* It doesn't matter which pipe we choose on amdpgu. */
+	data->pipe_id = PIPE_A;
+	data->pipe = &data->display.pipes[data->pipe_id];
+
+	igt_display_reset(display);
+
+	/*
+	 * TODO: Look for an output that is HDR capable and favor that.
+	 * But for now just assume that the first connected display supports
+	 * HDR and 10bpc.
+	 */
+	data->output = igt_get_single_output_for_pipe(display, data->pipe_id);
+	igt_require(data->output);
+	igt_require(igt_output_has_prop(data->output, IGT_CONNECTOR_MAX_BPC));
+
+	data->mode = igt_output_get_mode(data->output);
+	igt_assert(data->mode);
+
+	data->primary =
+		igt_pipe_get_plane_type(data->pipe, DRM_PLANE_TYPE_PRIMARY);
+
+	data->pipe_crc = igt_pipe_crc_new(data->fd, data->pipe_id,
+					  INTEL_PIPE_CRC_SOURCE_AUTO);
+
+	igt_output_set_pipe(data->output, data->pipe_id);
+
+	data->w = data->mode->hdisplay;
+	data->h = data->mode->vdisplay;
+	data->sensor_fd = -1;
+}
+
+/* Common test cleanup. */
+static void test_fini(data_t *data)
+{
+	close(data->sensor_fd);
+	igt_pipe_crc_free(data->pipe_crc);
+	igt_display_reset(&data->display);
+}
+
+static void test_cycle_flags(data_t *data, uint32_t test_flags)
+{
+
+	if (test_flags & TEST_DPMS) {
+		kmstest_set_connector_dpms(data->fd,
+					   data->output->config.connector,
+					   DRM_MODE_DPMS_OFF);
+		kmstest_set_connector_dpms(data->fd,
+					   data->output->config.connector,
+					   DRM_MODE_DPMS_ON);
+	}
+
+	if (test_flags & TEST_SUSPEND)
+		igt_system_suspend_autoresume(SUSPEND_STATE_MEM,
+					      SUSPEND_TEST_NONE);
+}
+
+/* Returns the current and maximum bpc from the connector debugfs. */
+static output_bpc_t get_output_bpc(data_t *data)
+{
+	char buf[256];
+	char *start_loc;
+	int fd, res;
+	output_bpc_t info;
+
+	fd = igt_debugfs_connector_dir(data->fd, data->output->name, O_RDONLY);
+	igt_assert(fd >= 0);
+
+	res = igt_debugfs_simple_read(fd, "output_bpc", buf, sizeof(buf));
+	igt_require(res > 0);
+
+	close(fd);
+
+	igt_assert(start_loc = strstr(buf, "Current: "));
+	igt_assert_eq(sscanf(start_loc, "Current: %u", &info.current), 1);
+
+	igt_assert(start_loc = strstr(buf, "Maximum: "));
+	igt_assert_eq(sscanf(start_loc, "Maximum: %u", &info.maximum), 1);
+
+	return info;
+}
+
+/* Verifies that connector has the correct output bpc. */
+static void assert_output_bpc(data_t *data, unsigned int bpc)
+{
+	output_bpc_t info = get_output_bpc(data);
+
+	igt_require_f(info.maximum >= bpc,
+		      "Monitor doesn't support %u bpc, max is %u\n", bpc,
+		      info.maximum);
+
+	igt_assert_eq(info.current, bpc);
+}
+
+/*
+ * Loads the sensor if unloaded. The sensor is a serial to USB interface that
+ * prints the current measured luminance (nits) as a float, separated by a
+ * newline. Uses baudrate 9600.
+ */
+static void open_sensor(data_t *data)
+{
+	struct termios toptions;
+	int res;
+
+	/* Return if already loaded. */
+	if (data->sensor_fd >= 0)
+		return;
+
+	data->sensor_fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY);
+	if (data->sensor_fd < 0) {
+		igt_info("Luminance sensor not found.\n");
+		return;
+	}
+
+	res = tcgetattr(data->sensor_fd, &toptions);
+	igt_assert_lte(0, res);
+
+	cfsetispeed(&toptions, B9600);
+	cfsetospeed(&toptions, B9600);
+
+	toptions.c_cflag &= ~(PARENB | CSTOPB | CSIZE);
+	toptions.c_cflag |= (CS8 | CREAD | CLOCAL);
+	toptions.c_lflag |= ICANON;
+
+	cfmakeraw(&toptions);
+
+	res = tcsetattr(data->sensor_fd, TCSANOW, &toptions);
+	igt_assert_lte(0, res);
+
+	res = tcsetattr(data->sensor_fd, TCSAFLUSH, &toptions);
+	igt_assert_lte(0, res);
+}
+
+/* Reads a string from the sensor. */
+static void get_sensor_str(data_t *data, char *dst, int dst_bytes)
+{
+	char c = 0;
+	int i = 0;
+
+	igt_require(data->sensor_fd >= 0);
+	igt_set_timeout(3, "Waiting for sensor read\n");
+
+	dst_bytes -= 1;
+
+	while (c != '\n' && i < dst_bytes) {
+		int n = read(data->sensor_fd, &c, 1);
+		igt_assert_lte(0, n);
+
+		dst[i++] = c;
+	}
+
+	igt_reset_timeout();
+
+	if (dst_bytes > 0) {
+		dst[i] = 0;
+	}
+}
+
+/* Returns the current luminance reading from the sensor in cd/m^2. */
+static float get_sensor_nits(data_t *data)
+{
+	float nits = -1.0f;
+	char buf[32];
+
+	/* Sensor opening is deferred until we actually need it - here. */
+	open_sensor(data);
+
+	/* Flush old data from the buffer in case it's been a while. */
+	igt_require(data->sensor_fd >= 0);
+	tcflush(data->sensor_fd, TCIOFLUSH);
+
+	/* Read twice so we get a clean line. */
+	get_sensor_str(data, buf, ARRAY_SIZE(buf));
+	get_sensor_str(data, buf, ARRAY_SIZE(buf));
+
+	sscanf(buf, "%f", &nits);
+
+	return nits;
+}
+
+/* Logs the cursor sensor nits. */
+static void log_sensor_nits(double nits)
+{
+	igt_info("Sensor: %.3f nits\n", nits);
+}
+
+/*
+ * Waits for the monitor to light-up to a given threshold, useful for
+ * post-modeset measurement.
+ */
+static void wait_for_threshold(data_t *data, double threshold)
+{
+	/*
+	 * If we read too quick the sensor might still be lit up.
+	 * Easy hack: just wait a second.
+	 */
+	sleep(1);
+
+	/* Poll sensor until lightup. */
+	igt_set_timeout(5, "Waiting for sensor read\n");
+
+	for (;;) {
+		double nits = get_sensor_nits(data);
+
+		if (nits >= threshold)
+			break;
+	}
+
+	igt_reset_timeout();
+}
+
+/* Skips the test if the output isn't HDR. */
+static void test_require_hdr(data_t *data)
+{
+	igt_require(igt_output_has_prop(data->output,
+					IGT_CONNECTOR_HDR_OUTPUT_METADATA));
+}
+
+/* Fills the FB with a solid color given mapping to a light value in nits. */
+static void draw_light(igt_fb_t *fb, double nits)
+{
+	cairo_t *cr;
+	double l, v;
+	int x, y, w, h;
+
+	cr = igt_get_cairo_ctx(fb->fd, fb);
+
+	l = nits / 10000.0;
+	v = calc_pq_inverse(l);
+
+	/*
+	 * Draw a white rect in the bottom center of the screen for the sensor.
+	 * It's only 10% of the width and height of the screen since not every
+	 * monitor is capable of full HDR brightness for the whole screen.
+	 */
+	w = fb->width * 0.10;
+	h = fb->height * 0.10;
+	x = (fb->width - w) / 2;
+	y = (fb->height - h);
+
+	igt_paint_color(cr, 0, 0, fb->width, fb->height, 0.0, 0.0, 0.0);
+	igt_paint_color(cr, x, y, w, h, v, v, v);
+
+	igt_put_cairo_ctx(fb->fd, fb, cr);
+}
+
+/* Fills the FB with a test HDR pattern. */
+static void draw_hdr_pattern(igt_fb_t *fb)
+{
+	cairo_t *cr = igt_get_cairo_ctx(fb->fd, fb);
+
+	igt_paint_color(cr, 0, 0, fb->width, fb->height, 1.0, 1.0, 1.0);
+	igt_paint_test_pattern(cr, fb->width, fb->height);
+
+	igt_put_cairo_ctx(fb->fd, fb, cr);
+}
+
+/*
+ * Validates switching between different display output bpc modes.
+ * TODO: Hook up a debugfs entry to verify driver behavior beyond
+ * just checking if the commits succeeded.
+ */
+static void test_bpc_switch(data_t *data, uint32_t flags)
+{
+	igt_display_t *display = &data->display;
+	igt_crc_t ref_crc, new_crc;
+	igt_fb_t afb;
+
+	test_init(data);
+
+        /* 10-bit formats are slow, so limit the size. */
+        igt_create_fb(data->fd, 512, 512, DRM_FORMAT_XRGB2101010, 0, &afb);
+	draw_hdr_pattern(&afb);
+
+        /* Start in 8bpc. */
+	igt_plane_set_fb(data->primary, &afb);
+	igt_plane_set_size(data->primary, data->w, data->h);
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 8);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+	assert_output_bpc(data, 8);
+
+        /* Switch to 10bpc. */
+        igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 10);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+	assert_output_bpc(data, 10);
+
+	/* Verify that the CRC are equal after DPMS or suspend. */
+	igt_pipe_crc_collect_crc(data->pipe_crc, &ref_crc);
+	test_cycle_flags(data, flags);
+	igt_pipe_crc_collect_crc(data->pipe_crc, &new_crc);
+
+        /* Drop back to 8bpc. */
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 8);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+	assert_output_bpc(data, 8);
+
+	/* CRC capture is clamped to 8bpc, so capture should match. */
+	igt_assert_crc_equal(&ref_crc, &new_crc);
+
+	test_fini(data);
+        igt_remove_fb(data->fd, &afb);
+}
+
+/* Tests swapping static HDR metadata. */
+static void test_static_swap(data_t *data)
+{
+	igt_display_t *display = &data->display;
+	igt_crc_t ref_crc, new_crc;
+	igt_fb_t afb;
+	struct hdr_output_metadata hdr;
+
+	test_init(data);
+	test_require_hdr(data);
+
+	/* 10-bit formats are slow, so limit the size. */
+	igt_create_fb(data->fd, 512, 512, DRM_FORMAT_XRGB2101010, 0, &afb);
+	draw_hdr_pattern(&afb);
+
+	/* Start in SDR. */
+	igt_plane_set_fb(data->primary, &afb);
+	igt_plane_set_size(data->primary, data->w, data->h);
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 8);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+	assert_output_bpc(data, 8);
+
+	/* Enter HDR, a modeset is allowed here. */
+	fill_hdr_output_metadata_st2048(&hdr);
+	set_hdr_output_metadata(data, &hdr);
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 10);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+	assert_output_bpc(data, 10);
+
+	igt_pipe_crc_collect_crc(data->pipe_crc, &ref_crc);
+
+	/* Change the mastering information, no modeset allowed. */
+	hdr.hdmi_metadata_type1.max_display_mastering_luminance = 200;
+	hdr.hdmi_metadata_type1.max_fall = 200;
+	hdr.hdmi_metadata_type1.max_cll = 100;
+
+	set_hdr_output_metadata(data, &hdr);
+	igt_display_commit_atomic(display, 0, NULL);
+
+	/* Enter SDR via metadata, no modeset allowed. */
+	fill_hdr_output_metadata_sdr(&hdr);
+	set_hdr_output_metadata(data, &hdr);
+	igt_display_commit_atomic(display, 0, NULL);
+
+	igt_pipe_crc_collect_crc(data->pipe_crc, &new_crc);
+
+	/* Exit SDR and enter 8bpc, cleanup. */
+	set_hdr_output_metadata(data, NULL);
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 8);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+	assert_output_bpc(data, 8);
+
+	/* Verify that the CRC didn't change while cycling metadata. */
+	igt_assert_crc_equal(&ref_crc, &new_crc);
+
+	test_fini(data);
+	igt_remove_fb(data->fd, &afb);
+}
+
+/*
+ * Tests that setting the static metadata actually enters HDR mode.
+ *
+ * Note: This test is display specific in the sense that it requries
+ * a display that is capable of going above SDR brightness levels.
+ * Most HDR400 or higher certified displays should be capable of this.
+ *
+ * Some displays may require first limiting the output brightness
+ * in the OSD for this to work.
+ *
+ * Requires the luminance sensor to be attached to the test machine.
+ */
+static void test_static_output(data_t *data)
+{
+	igt_display_t *display = &data->display;
+	igt_fb_t afb;
+	struct hdr_output_metadata hdr;
+	double lightup = 100.0;
+	double threshold = 15.0;
+	double nits_sdr0, nits_sdr1, nits_sdr2, nits_hdr;
+
+	test_init(data);
+	test_require_hdr(data);
+
+	igt_create_fb(data->fd, data->w, data->h, DRM_FORMAT_XRGB2101010, 0, &afb);
+	draw_light(&afb, 10000.0);
+
+	igt_info("Test SDR, 8bpc\n");
+	igt_plane_set_fb(data->primary, &afb);
+	igt_plane_set_size(data->primary, data->w, data->h);
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 8);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+	assert_output_bpc(data, 8);
+
+	wait_for_threshold(data, lightup);
+	nits_sdr0 = get_sensor_nits(data);
+	log_sensor_nits(nits_sdr0);
+
+	igt_info("Test SDR, 10bpc\n");
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 10);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+	assert_output_bpc(data, 10);
+
+	wait_for_threshold(data, lightup);
+	nits_sdr1 = get_sensor_nits(data);
+	log_sensor_nits(nits_sdr1);
+
+	igt_info("Test HDR, 10bpc\n");
+	fill_hdr_output_metadata_st2048(&hdr);
+	set_hdr_output_metadata(data, &hdr);
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 10);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+
+	wait_for_threshold(data, lightup);
+	nits_hdr = get_sensor_nits(data);
+	log_sensor_nits(nits_hdr);
+
+	igt_info("Exit HDR into SDR, 8bpc\n");
+	set_hdr_output_metadata(data, NULL);
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 8);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+	assert_output_bpc(data, 8);
+
+	wait_for_threshold(data, lightup);
+	nits_sdr2 = get_sensor_nits(data);
+	log_sensor_nits(nits_sdr2);
+
+	/* Verify that all nit values were as expected. */
+	assert_nits_equal(nits_sdr0, nits_sdr1, threshold);
+	assert_nits_equal(nits_sdr0, nits_sdr2, threshold);
+
+	igt_assert_f(nits_hdr - nits_sdr0 > threshold * 2.0,
+		     "No measurable difference between SDR and HDR luminance: "
+		     "threshold=%.1f actual=%.1f\n",
+		     threshold * 2.0, nits_hdr - nits_sdr0);
+
+	test_fini(data);
+	igt_remove_fb(data->fd, &afb);
+}
+
+/* Tests entering and exiting HDR mode. */
+static void test_static_toggle(data_t *data, uint32_t flags)
+{
+	igt_display_t *display = &data->display;
+	igt_crc_t ref_crc, new_crc;
+	igt_fb_t afb;
+	struct hdr_output_metadata hdr;
+
+	test_init(data);
+	test_require_hdr(data);
+
+        /* 10-bit formats are slow, so limit the size. */
+	igt_create_fb(data->fd, 512, 512, DRM_FORMAT_XRGB2101010, 0, &afb);
+	draw_hdr_pattern(&afb);
+
+	fill_hdr_output_metadata_st2048(&hdr);
+
+        /* Start with no metadata. */
+	igt_plane_set_fb(data->primary, &afb);
+	igt_plane_set_size(data->primary, data->w, data->h);
+	set_hdr_output_metadata(data, NULL);
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 8);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+	assert_output_bpc(data, 8);
+
+        /* Apply HDR metadata and 10bpc. We expect a modeset for entering. */
+	set_hdr_output_metadata(data, &hdr);
+        igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 10);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+	assert_output_bpc(data, 10);
+
+	/* Verify that the CRC are equal after DPMS or suspend. */
+	igt_pipe_crc_collect_crc(data->pipe_crc, &ref_crc);
+	test_cycle_flags(data, flags);
+	igt_pipe_crc_collect_crc(data->pipe_crc, &new_crc);
+
+        /* Disable HDR metadata and drop back to 8bpc. We expect a modeset for exiting. */
+	set_hdr_output_metadata(data, NULL);
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 8);
+	igt_display_commit_atomic(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+	assert_output_bpc(data, 8);
+
+	igt_assert_crc_equal(&ref_crc, &new_crc);
+
+	test_fini(data);
+	igt_remove_fb(data->fd, &afb);
+}
+
+igt_main
+{
+	data_t data;
+
+	igt_skip_on_simulation();
+
+	memset(&data, 0, sizeof(data));
+
+	igt_fixture
+	{
+		data.fd = drm_open_driver_master(DRIVER_AMDGPU);
+
+		kmstest_set_vt_graphics_mode();
+
+		igt_display_require(&data.display, data.fd);
+		igt_require(data.display.is_atomic);
+		igt_display_require_output(&data.display);
+	}
+
+	igt_subtest("bpc-switch") test_bpc_switch(&data, 0);
+	igt_subtest("bpc-switch-dpms") test_bpc_switch(&data, TEST_DPMS);
+	igt_subtest("bpc-switch-suspend") test_bpc_switch(&data, TEST_SUSPEND);
+
+	igt_subtest("static-output") test_static_output(&data);
+	igt_subtest("static-swap") test_static_swap(&data);
+
+	igt_subtest("static-toggle") test_static_toggle(&data, 0);
+	igt_subtest("static-toggle-dpms") test_static_toggle(&data, TEST_DPMS);
+	igt_subtest("static-toggle-suspend")
+		test_static_toggle(&data, TEST_SUSPEND);
+
+	igt_fixture
+	{
+		igt_display_fini(&data.display);
+	}
+}
diff --git a/tests/amdgpu/meson.build b/tests/amdgpu/meson.build
index 42086374..9c6585bb 100644
--- a/tests/amdgpu/meson.build
+++ b/tests/amdgpu/meson.build
@@ -6,6 +6,7 @@ if libdrm_amdgpu.found()
 			  'amd_basic',
 			  'amd_color',
 			  'amd_cs_nop',
+			  'amd_hdr',
 			  'amd_prime',
 			]
 	amdgpu_deps += libdrm_amdgpu
-- 
2.17.1



More information about the igt-dev mailing list