[igt-dev] [RESEND,v3 5/5] kms_plane: Add clipping subtests

Gwan-gyeong Mun
2018-09-06T07:24:14Z

From: Imre Deak <imre.deak@intel.com>

Add plane clipping subtests displaying a single clipped plane, with the
following test cases:
a) plane covering the whole screen, so that clipping is done at all 4
screen edges
b) plane at either of the 4 corners of the screen clipped, so that a
4x4 pixel part of the plane is visible
c) plane at either of the 4 corners of the screen clipped, so that a
2x2 pixel part of the plane is visible

Each of the above cases are tested with all supported tiling modes,
rotation degrees, differing pixel format sizes (only 16 bpp and 32 bpp
for now) and certain planes. While the a) and b) cases above are
supported on all platforms c) is not fully supported on GLK and CNL
(which was the primary motivation for this testcase).

v2:
- Add missing reset for the output pipe after finishing the test for a
given output, fixing the
"DP-1 and eDP-1 are both trying to use pipe A" type of errors.
- Use -ERANGE instead of -EINVAL to check the return code for
unsupported plane X positions, based on the latest kernel code.
- Add comment explaining the dependencies when doing a universal
commit.

v3: (Gwan-gyeong)
- Add missing release of framebuffer on create_fb_for_mode__clipping_display()
function.
- Add using multi planes per single display commit on clipping test of
4 corners. (Daniel)
It enables the improving of test speed. If number of planes is over
than 4, it uses four planes from first plane to next four planes.
(4 planes make testing of 4 corners possible as 1 display commit.)
If number of planes is over than 2 and less than 4, it uses two planes
from first plane to next two planes. (2 planes make testing of 4 corners
possible as 2 display commit.) Rest of cases it uses only first plane.
(1 plane makes testing of 4 corners possible as 4 display commit.)
It changes to using of certain planes from using of all supported planes.
And a cursor plane can be one of test planes.

Signed-off-by: Imre Deak <imre.deak@intel.com>
Signed-off-by: Gwan-gyeong Mun <gwan-gyeong.mun@intel.com>
Cc: Mika Kahola <mika.kahola@intel.com>
Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: Juha-Pekka Heikkilä <juha-pekka.heikkila@intel.com>
Reviewed-by: Arkadiusz Hiler <arkadiusz.hiler@intel.com> (v1)
---
tests/kms_plane.c | 529 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 529 insertions(+)

diff --git a/tests/kms_plane.c b/tests/kms_plane.c
index 3d08893f..ea62efed 100644
--- a/tests/kms_plane.c
+++ b/tests/kms_plane.c
@@ -41,11 +41,40 @@ typedef struct {
int drm_fd;
igt_display_t display;
igt_pipe_crc_t *pipe_crc;
+ uint32_t devid;
+ uint64_t max_curw;
+ uint64_t max_curh;
} data_t;

+typedef struct {
+ int x;
+ int y;
+ int size;
+} square_t;
+
static color_t red = { 1.0f, 0.0f, 0.0f };
static color_t green = { 0.0f, 1.0f, 0.0f };
+static color_t yellow = { 1.0f, 1.0f, 0.0f };
static color_t blue = { 0.0f, 0.0f, 1.0f };
+static color_t white = { 1.0f, 1.0f, 1.0f };
+static square_t clip_squares[4];
+
+/*
+ * Size of a square plane used to test clipping at the 4 courners of the
+ * display.
+ */
+#define CLIPPED_PLANE_SMALL_SIZE 64
+
+/*
+ * Visible plane size after clipping that works on all platforms for all plane
+ * positions.
+ * The exceptions are GLK/CNL where there must be at least this many pixels
+ * visible from the plane after it's clipped to the left/right edge of the
+ * screen. Not meeting this condition may trigger FIFO underflows and screen
+ * corruption. The cursor plane is an exception that doesn't have this problem
+ * even on GLK/CNL.
+ */
+#define CLIPPED_PLANE_MIN_VALID 4

/*
* Common code across all tests, acting on data_t
@@ -151,6 +180,69 @@ create_fb_for_mode__position(data_t *data, drmModeModeInfo *mode,
igt_put_cairo_ctx(data->drm_fd, fb, cr);
}

+/*
+ * Create a square FB for the plane in the clipping test, divided into 4
+ * quarters solid filled with different colors. Use the given tiling, format
+ * and size and rotate the FB clockwise with the given rotation degrees, so
+ * that the counterclockwise rotation with the same degrees done by the HW
+ * will always result in the same reference FB image.
+ */
+static void
+create_fb_for_mode__clipping_plane(data_t *data, igt_rotation_t rotation,
+ uint64_t tiling,
+ uint32_t format,
+ int size,
+ struct igt_fb *fb /* out */)
+{
+ color_t corners[] = { red, white, yellow, blue };
+ color_t color;
+ unsigned int fb_id;
+ cairo_t *cr;
+ const int qsize = size / 2;
+ int idx;
+
+ fb_id = igt_create_fb(data->drm_fd, size, size, format, tiling, fb);
+ igt_assert(fb_id);
+
+ cr = igt_get_cairo_ctx(data->drm_fd, fb);
+
+ switch (rotation) {
+ case IGT_ROTATION_0:
+ idx = 0;
+ break;
+ case IGT_ROTATION_90:
+ idx = 3;
+ break;
+ case IGT_ROTATION_180:
+ idx = 2;
+ break;
+ case IGT_ROTATION_270:
+ idx = 1;
+ break;
+ default:
+ igt_assert(0);
+ }
+
+ color = corners[idx];
+ igt_paint_color(cr, 0, 0, qsize, qsize,
+ color.red, color.green, color.blue);
+
+ color = corners[(++idx) % 4];
+ igt_paint_color(cr, qsize, 0, qsize, qsize,
+ color.red, color.green, color.blue);
+
+ color = corners[(++idx) % 4];
+ igt_paint_color(cr, qsize, qsize, qsize, qsize,
+ color.red, color.green, color.blue);
+
+ color = corners[(++idx) % 4];
+ igt_paint_color(cr, 0, qsize, qsize, qsize,
+ color.red, color.green, color.blue);
+
+ igt_assert(cairo_status(cr) == 0);
+ cairo_destroy(cr);
+}
+
enum {
TEST_POSITION_FULLY_COVERED = 1 << 0,
TEST_DPMS = 1 << 1,
@@ -554,6 +646,427 @@ test_pixel_formats(data_t *data, enum pipe pipe)
}
}

+static bool
+bogus_plane_conf(uint32_t devid, igt_plane_t *plane_obj,
+ int hdisplay, int plane_x, int plane_width)
+{
+ if (!(IS_GEMINILAKE(devid) || IS_CANNONLAKE(devid)))
+ return false;
+
+ if (plane_obj->type == DRM_PLANE_TYPE_CURSOR)
+ return false;
+
+ if (plane_x + plane_width >= CLIPPED_PLANE_MIN_VALID &&
+ plane_x <= hdisplay - CLIPPED_PLANE_MIN_VALID)
+ return false;
+
+ return true;
+}
+
+static bool
+supported_plane_conf(data_t *data, int plane_type, uint64_t tiling,
+ uint32_t format, igt_rotation_t rotation,
+ drmModeModeInfo *mode,
+ int plane_x, int plane_y, int plane_size)
+{
+ if (intel_gen(data->devid) < 9 &&
+ tiling != LOCAL_DRM_FORMAT_MOD_NONE)
+ return false;
+
+ /* On GEN<9 the primary plane must cover the full screen. */
+ if (intel_gen(data->devid) < 9 &&
+ plane_type == DRM_PLANE_TYPE_PRIMARY &&
+ (plane_x > 0 || plane_y > 0 ||
+ (plane_x + plane_size < mode->hdisplay) ||
+ (plane_y + plane_size < mode->vdisplay)))
+ return false;
+
+ if (plane_type == DRM_PLANE_TYPE_CURSOR) {
+ /* For cursor planes only linear/alpha format is supported. */
+ if (tiling != LOCAL_DRM_FORMAT_MOD_NONE ||
+ format != DRM_FORMAT_ARGB8888)
+ return false;
+
+ if (plane_size > data->max_curw || plane_size > data->max_curh)
+ return false;
+ } else {
+ /*
+ * For non-cursor planes formats with alpha may result in
+ * undeterministic CRCs, we use the same sized
+ * non-alpha XRGB8888 format instead.
+ */
+ if (format == DRM_FORMAT_ARGB8888)
+ return false;
+
+ if (format == DRM_FORMAT_RGB565 &&
+ intel_gen(data->devid) < 9 &&
+ !IS_VALLEYVIEW(data->devid) && !IS_CHERRYVIEW(data->devid))
+ return false;
+ }
+
+ /* RGB565 with rotation is not supported for now. */
+ if (format == DRM_FORMAT_RGB565 && rotation != IGT_ROTATION_0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Create a square reference FB for the whole screen in the clipping test,
+ * with the given test plane position and size. See
+ * create_fb_for_mode__clipping_plane() for the layout of the test plane.
+ */
+static void
+create_fb_for_mode__clipping_display(data_t *data, drmModeModeInfo *mode,
+ square_t *clip_squares,
+ int clip_squares_cnt,
+ struct igt_fb *fb /* out */)
+{
+ int plane;
+ struct igt_fb plane_fbs[4];
+ unsigned int fb_id;
+ cairo_t *cr;
+ cairo_surface_t *srcs[4];
+
+ fb_id = igt_create_fb(data->drm_fd,
+ mode->hdisplay, mode->vdisplay,
+ DRM_FORMAT_XRGB8888,
+ LOCAL_DRM_FORMAT_MOD_NONE,
+ fb);
+ igt_assert(fb_id);
+
+ cr = igt_get_cairo_ctx(data->drm_fd, fb);
+ igt_paint_color(cr, 0, 0, mode->hdisplay, mode->vdisplay,
+ 0, 0, 0);
+
+ for (plane = 0; plane < clip_squares_cnt; plane++) {
+ create_fb_for_mode__clipping_plane(data, IGT_ROTATION_0,
+ LOCAL_DRM_FORMAT_MOD_NONE,
+ DRM_FORMAT_XRGB8888,
+ clip_squares[plane].size,
+ &plane_fbs[plane]);
+
+ srcs[plane] = igt_get_cairo_surface(data->drm_fd,
+ &plane_fbs[plane]);
+ cairo_set_source_surface(cr, srcs[plane],
+ clip_squares[plane].x,
+ clip_squares[plane].y);
+ cairo_rectangle(cr,
+ clip_squares[plane].x,
+ clip_squares[plane].y,
+ clip_squares[plane].size,
+ clip_squares[plane].size);
+ cairo_fill(cr);
+ }
+
+ igt_assert(cairo_status(cr) == 0);
+ cairo_destroy(cr);
+
+ for (plane = 0; plane < clip_squares_cnt; plane++) {
+ cairo_surface_destroy(srcs[plane]);
+ igt_remove_fb(data->drm_fd, &plane_fbs[plane]);
+ }
+}
+
+static void
+test_plane_clipping_format(data_t *data,
+ enum pipe pipe,
+ igt_output_t *output,
+ drmModeModeInfo *mode,
+ square_t *clip_squares,
+ int clip_squares_cnt,
+ uint64_t tiling,
+ igt_rotation_t rotation,
+ uint32_t format,
+ igt_crc_t *reference_crc)
+{
+ int plane;
+ igt_plane_t *plane_objs[4];
+ struct igt_fb plane_fbs[4];
+ igt_crc_t crc;
+ int ret;
+ bool bogus_planes = false;
+
+ memset(plane_objs, 0, sizeof(plane_objs));
+
+ igt_debug("rotation %s tiling %s format %s\n",
+ igt_rotation_degrees_str(rotation),
+ igt_tiling_str(tiling),
+ igt_format_str(format));
+
+ igt_output_set_pipe(output, pipe);
+
+ for (plane = 0; plane < clip_squares_cnt ; plane++) {
+ igt_plane_t *obj = igt_output_get_plane(output, plane);
+ struct igt_fb *fb = &plane_fbs[plane];
+ int x = clip_squares[plane].x;
+ int y = clip_squares[plane].y;
+ int size = clip_squares[plane].size;
+ uint64_t _tiling = tiling;
+ uint32_t _format = format;
+
+ if (obj->type == DRM_PLANE_TYPE_CURSOR) {
+ _tiling = LOCAL_DRM_FORMAT_MOD_NONE;
+ _format = DRM_FORMAT_ARGB8888;
+ }
+
+ if (!supported_plane_conf(data, obj->type, _tiling, _format,
+ rotation, mode, x, y, size))
+ goto out;
+
+ create_fb_for_mode__clipping_plane(data, rotation,_tiling,
+ _format, size, fb);
+
+ igt_plane_set_fb(obj, fb);
+ igt_fb_set_position(fb, obj, 0, 0);
+
+ igt_plane_set_size(obj, size, size);
+ igt_plane_set_rotation(obj, rotation);
+ igt_plane_set_position(obj, x, y);
+
+ plane_objs[plane] = obj;
+ }
+
+ /*
+ * Note that a universal commit doesn't support full modesets, so we
+ * have to make sure that the following only needs to commit changes
+ * that are compatible with a fastset. This should be guaranteed,
+ * since before calling this function we took the reference CRC which
+ * left the display enabled with the mode we require here and
+ * afterwards we only change plane parameters (FB, position, rotation
+ * etc.).
+ */
+ ret = igt_display_try_commit2(&data->display, COMMIT_UNIVERSAL);
+
+ for (plane = 0; plane < clip_squares_cnt; plane++) {
+ if(!bogus_plane_conf(data->devid, plane_objs[plane],
+ mode->hdisplay, clip_squares[plane].x,
+ clip_squares[plane].size)) {
+ bogus_planes = true;
+ break;
+ }
+ }
+
+ if (bogus_planes) {
+ igt_assert(ret == 0);
+ igt_pipe_crc_collect_crc(data->pipe_crc, &crc);
+ igt_assert_crc_equal(reference_crc, &crc);
+ } else {
+ igt_assert(ret == -ERANGE);
+ }
+
+out:
+ for (plane = 0; plane < clip_squares_cnt; plane++) {
+ if (!plane_objs[plane])
+ continue;
+ igt_plane_set_fb(plane_objs[plane], NULL);
+ igt_plane_set_rotation(plane_objs[plane], IGT_ROTATION_0);
+ igt_plane_set_position(plane_objs[plane], 0, 0);
+
+ igt_remove_fb(data->drm_fd, &plane_fbs[plane]);
+ }
+
+ igt_output_set_pipe(output, PIPE_ANY);
+}
+
+static void
+test_plane_clipping_square(data_t *data, enum pipe pipe,
+ igt_output_t *output, drmModeModeInfo *mode,
+ square_t *clip_squares, int clip_squares_cnt)
+{
+ const struct {
+ uint64_t tiling;
+ igt_rotation_t rotation;
+ } rotations[] = {
+ { LOCAL_DRM_FORMAT_MOD_NONE, IGT_ROTATION_0 },
+
+ { LOCAL_I915_FORMAT_MOD_X_TILED, IGT_ROTATION_0 },
+
+ { LOCAL_I915_FORMAT_MOD_Y_TILED, IGT_ROTATION_0 },
+ { LOCAL_I915_FORMAT_MOD_Y_TILED, IGT_ROTATION_90 },
+ { LOCAL_I915_FORMAT_MOD_Y_TILED, IGT_ROTATION_180 },
+ { LOCAL_I915_FORMAT_MOD_Y_TILED, IGT_ROTATION_270 },
+
+ { LOCAL_I915_FORMAT_MOD_Yf_TILED, IGT_ROTATION_0 },
+ { LOCAL_I915_FORMAT_MOD_Yf_TILED, IGT_ROTATION_90 },
+ { LOCAL_I915_FORMAT_MOD_Yf_TILED, IGT_ROTATION_180 },
+ { LOCAL_I915_FORMAT_MOD_Yf_TILED, IGT_ROTATION_270 },
+ };
+ const uint32_t formats[] = {
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ };
+
+ igt_fb_t reference_fb;
+ igt_crc_t reference_crc;
+
+ for (int plane = 0; plane < clip_squares_cnt; plane++) {
+ igt_info("Testing connector %s mode %dx%d using pipe %s: %dx%d@%d,%d\n",
+ igt_output_name(output),
+ mode->hdisplay, mode->vdisplay,
+ kmstest_pipe_name(pipe),
+ clip_squares[plane].size, clip_squares[plane].size,
+ clip_squares[plane].x, clip_squares[plane].y);
+ }
+
+ test_init(data, pipe);
+
+ create_fb_for_mode__clipping_display(data, mode,
+ clip_squares,
+ clip_squares_cnt,
+ &reference_fb);
+
+ test_grab_crc_for_fb2(data, output, pipe, &reference_fb,
+ &reference_crc, COMMIT_UNIVERSAL);
+
+ igt_remove_fb(data->drm_fd, &reference_fb);
+
+ for (int rotation = 0; rotation < ARRAY_SIZE(rotations); rotation++)
+ for (int format = 0; format < ARRAY_SIZE(formats); format++)
+ test_plane_clipping_format(data, pipe, output,
+ mode,
+ clip_squares,
+ clip_squares_cnt,
+ rotations[rotation].tiling,
+ rotations[rotation].rotation,
+ formats[format],
+ &reference_crc);
+
+ test_fini(data);
+}
+
+static void
+test_plane_clipping_squares(data_t *data, enum pipe pipe,
+ igt_output_t *output, drmModeModeInfo *mode,
+ square_t *clip_squares, int clip_squares_cnt)
+{
+ switch (clip_squares_cnt) {
+ case 4:
+ test_plane_clipping_square(data, pipe, output, mode,
+ clip_squares, clip_squares_cnt);
+ break;
+ case 2:
+ test_plane_clipping_square(data, pipe, output, mode,
+ clip_squares, clip_squares_cnt);
+ test_plane_clipping_square(data, pipe, output, mode,
+ clip_squares + clip_squares_cnt,
+ clip_squares_cnt);
+ break;
+ case 1:
+ default:
+ for (int i = 0; i < 4; i++) {
+ test_plane_clipping_square(data, pipe, output, mode,
+ clip_squares + i,
+ clip_squares_cnt);
+ }
+ break;
+ }
+}
+
+static void
+test_plane_clipping(data_t *data, enum pipe pipe)
+{
+ igt_output_t *output;
+ int connected_outs = 0;
+
+ for_each_valid_output_on_pipe(&data->display, pipe, output) {
+ drmModeModeInfo *mode;
+ int sq_size;
+ int max_planes = data->display.pipes[pipe].n_planes;
+ int clip_squares_cnt;
+
+ clip_squares_cnt = max_planes >= 4 ?
+ 4 : max_planes >= 2 ? 2 : 1;
+
+ igt_output_set_pipe(output, pipe);
+ mode = igt_output_get_mode(output);
+
+ /*
+ * Test with a square plane bigger than either the width or
+ * height of the mode. This should pass on all platforms.
+ */
+ sq_size = mode->hdisplay > mode->vdisplay ?
+ mode->hdisplay : mode->vdisplay;
+
+ clip_squares[0].x = -2;
+ clip_squares[0].y = -2;
+ clip_squares[0].size = sq_size + 4;
+
+ test_plane_clipping_square(data, pipe, output, mode,
+ clip_squares, 1);
+
+ /*
+ * Test positions in the 4 corners of the screen with a
+ * CLIPPED_PLANE_MIN_VALID x CLIPPED_PLANE_MIN_VALID square
+ * visible from the plane. These should pass on all platforms.
+ */
+ clip_squares[0].x = -CLIPPED_PLANE_SMALL_SIZE +
+ CLIPPED_PLANE_MIN_VALID;
+ clip_squares[0].y = -CLIPPED_PLANE_SMALL_SIZE +
+ CLIPPED_PLANE_MIN_VALID;
+ clip_squares[0].size = CLIPPED_PLANE_SMALL_SIZE;
+
+ clip_squares[1].x = -CLIPPED_PLANE_SMALL_SIZE +
+ CLIPPED_PLANE_MIN_VALID;
+ clip_squares[1].y = mode->vdisplay -
+ CLIPPED_PLANE_MIN_VALID;
+ clip_squares[1].size = CLIPPED_PLANE_SMALL_SIZE;
+
+ clip_squares[2].x = mode->hdisplay -
+ CLIPPED_PLANE_MIN_VALID;
+ clip_squares[2].y = -CLIPPED_PLANE_SMALL_SIZE +
+ CLIPPED_PLANE_MIN_VALID;
+ clip_squares[2].size = CLIPPED_PLANE_SMALL_SIZE;
+
+ clip_squares[3].x = mode->hdisplay -
+ CLIPPED_PLANE_MIN_VALID;
+ clip_squares[3].y = mode->vdisplay -
+ CLIPPED_PLANE_MIN_VALID;
+ clip_squares[3].size = CLIPPED_PLANE_SMALL_SIZE;
+
+ test_plane_clipping_squares(data, pipe, output, mode,
+ clip_squares, clip_squares_cnt);
+
+ /*
+ * Test positions in the 4 corners of the screen with a
+ * 2 x 2 square visible from the plane. These are valid on all
+ * platforms except on GLK/CNL where less than
+ * CLIPPED_PLANE_MIN_VALID pixels visible on the left/right
+ * edges of the screen may cause FIFO underflow and display
+ * corruption.
+ *
+ * The cursor plane is an exception without this problem.
+ *
+ * Use 2 x 2 size as other (odd) sizes may result in an
+ * incorrect CRC for the cursor plane even though it displays
+ * correctly and causes no underflow.
+ */
+ clip_squares[0].x = -CLIPPED_PLANE_SMALL_SIZE + 2;
+ clip_squares[0].y = -CLIPPED_PLANE_SMALL_SIZE + 2;
+ clip_squares[0].size = CLIPPED_PLANE_SMALL_SIZE;
+
+ clip_squares[1].x = -CLIPPED_PLANE_SMALL_SIZE + 2;
+ clip_squares[1].y = mode->vdisplay - 2;
+ clip_squares[1].size = CLIPPED_PLANE_SMALL_SIZE;
+
+ clip_squares[2].x = mode->hdisplay - 2;
+ clip_squares[2].y = -CLIPPED_PLANE_SMALL_SIZE + 2;
+ clip_squares[2].size = CLIPPED_PLANE_SMALL_SIZE;
+
+ clip_squares[3].x = mode->hdisplay - 2;
+ clip_squares[3].y = mode->vdisplay - 2;
+ clip_squares[3].size = CLIPPED_PLANE_SMALL_SIZE;
+
+ test_plane_clipping_squares(data, pipe, output, mode,
+ clip_squares, clip_squares_cnt);
+
+ connected_outs++;
+ }
+
+ igt_skip_on(connected_outs == 0);
+}
+
static void
run_tests_for_pipe_plane(data_t *data, enum pipe pipe)
{
@@ -590,6 +1103,9 @@ run_tests_for_pipe_plane(data_t *data, enum pipe pipe)
kmstest_pipe_name(pipe))
test_plane_panning(data, pipe, TEST_PANNING_BOTTOM_RIGHT |
TEST_SUSPEND_RESUME);
+ igt_subtest_f("plane-clipping-pipe-%s-planes",
+ kmstest_pipe_name(pipe))
+ test_plane_clipping(data, pipe);
}

@@ -602,8 +1118,21 @@ igt_main
igt_skip_on_simulation();

igt_fixture {
+ int ret;
+
data.drm_fd = drm_open_driver_master(DRIVER_INTEL);

+ data.devid = intel_get_drm_devid(data.drm_fd);
+
+ data.max_curw = 64;
+ data.max_curh = 64;
+ ret = drmGetCap(data.drm_fd, DRM_CAP_CURSOR_WIDTH,
+ &data.max_curw);
+ igt_assert(ret == 0 || errno == EINVAL);
+ ret = drmGetCap(data.drm_fd, DRM_CAP_CURSOR_HEIGHT,
+ &data.max_curh);
+ igt_assert(ret == 0 || errno == EINVAL);
+
kmstest_set_vt_graphics_mode();

igt_require_pipe_crc(data.drm_fd);