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

Nicholas Kazlauskas
2019-07-25T16:55:42Z

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.

v2: rebase

Cc: Harry Wentland <harry.wentland@amd.com>
Cc: Leo Li <sunpeng.li@amd.com>
Signed-off-by: Nicholas Kazlauskas <nicholas.kazlauskas@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 250dbd33..3a81a8ff 100644
--- a/tests/Makefile.sources
+++ b/tests/Makefile.sources
@@ -9,6 +9,7 @@ AMDGPU_TESTS = \
amdgpu/amd_bypass \
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 b7982291..6c6b7feb 100644
--- a/tests/amdgpu/meson.build
+++ b/tests/amdgpu/meson.build
@@ -7,6 +7,7 @@ if libdrm_amdgpu.found()
'amd_bypass',
'amd_color',
'amd_cs_nop',
+ 'amd_hdr',
'amd_prime',
]
amdgpu_deps += libdrm_amdgpu