This adds support for VGA frame comparison check. Since VGA uses a DAC-ADC chain, its data cannot be expected to be pixel perfect. Thus, it is impossible to uses a CRC check and full frames have to be analyzed instead. Such an analysis is implemented, based on both an absolute error threshold and a correlation with the expected error trend for a DAC-ADC chain. It was tested with a couple encoders and provides reliable error detection with few false positives. In case the frame does not match, it is dumped to a file in a similar fashion as CRC tests. Signed-off-by: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxxxxxx> --- configure.ac | 5 +- lib/Makefile.am | 2 + lib/igt_chamelium.c | 235 +++++++++++++++++++++++++++++++++++++++++++++++++++- lib/igt_chamelium.h | 6 +- tests/chamelium.c | 82 ++++++++++++++++++ 5 files changed, 325 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index 0418e605..e7c3603b 100644 --- a/configure.ac +++ b/configure.ac @@ -182,8 +182,9 @@ PKG_CHECK_MODULES(GLIB, glib-2.0) # for chamelium PKG_CHECK_MODULES(XMLRPC, xmlrpc_client, [xmlrpc=yes], [xmlrpc=no]) PKG_CHECK_MODULES(PIXMAN, pixman-1, [pixman=yes], [pixman=no]) -AM_CONDITIONAL(HAVE_CHAMELIUM, [test "x$xmlrpc$pixman" = xyesyes]) -if test x"$xmlrpc$pixman" = xyesyes; then +PKG_CHECK_MODULES(GSL, gsl, [gsl=yes], [gsl=no]) +AM_CONDITIONAL(HAVE_CHAMELIUM, [test "x$xmlrpc$pixman$gsl" = xyesyesyes]) +if test x"$xmlrpc$pixman$gsl" = xyesyesyes; then AC_DEFINE(HAVE_CHAMELIUM, 1, [Enable Chamelium support]) fi diff --git a/lib/Makefile.am b/lib/Makefile.am index d4f41128..fb922ced 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -35,6 +35,7 @@ AM_CFLAGS = \ $(DRM_CFLAGS) \ $(PCIACCESS_CFLAGS) \ $(LIBUNWIND_CFLAGS) \ + $(GSL_CFLAGS) \ $(KMOD_CFLAGS) \ $(PROCPS_CFLAGS) \ $(DEBUG_CFLAGS) \ @@ -54,6 +55,7 @@ libintel_tools_la_LIBADD = \ $(DRM_LIBS) \ $(PCIACCESS_LIBS) \ $(PROCPS_LIBS) \ + $(GSL_LIBS) \ $(KMOD_LIBS) \ $(CAIRO_LIBS) \ $(LIBUDEV_LIBS) \ diff --git a/lib/igt_chamelium.c b/lib/igt_chamelium.c index 9aca6842..79ea8b4f 100644 --- a/lib/igt_chamelium.c +++ b/lib/igt_chamelium.c @@ -34,6 +34,8 @@ #include <glib.h> #include <pixman.h> #include <cairo.h> +#include <gsl/gsl_statistics_double.h> +#include <gsl/gsl_fit.h> #include "igt.h" @@ -1035,6 +1037,235 @@ void chamelium_write_frame_to_png(const struct chamelium *chamelium, } /** + * chamelium_analogue_frame_crop: + * @chamelium: The Chamelium instance to use + * @dump: The chamelium frame dump to crop + * @width: The cropped frame width + * @height: The cropped frame height + * + * Detects the corners of a chamelium frame and crops it to the requested + * width/height. This is useful for VGA frame dumps that also contain the + * pixels dumped during the blanking intervals. + * + * The detection is done on a brightness-threshold-basis, that is adapted + * to the reference frame used by i-g-t. It may not be as relevant for other + * frames. + */ +void chamelium_crop_analogue_frame(struct chamelium_frame_dump *dump, int width, + int height) +{ + unsigned char *bgr; + unsigned char *p; + unsigned char *q; + int top, left; + int x, y, xx, yy; + int score; + + if (dump->width == width && dump->height == height) + return; + + /* Start with the most bottom-right position. */ + top = dump->height - height; + left = dump->width - width; + + igt_assert(top >= 0 && left >= 0); + + igt_debug("Cropping analogue frame from %dx%d to %dx%d\n", dump->width, + dump->height, width, height); + + /* Detect the top-left corner of the frame. */ + for (x = 0; x < dump->width; x++) { + for (y = 0; y < dump->height; y++) { + p = &dump->bgr[(x + y * dump->width) * 3]; + + /* Detect significantly bright pixels. */ + if (p[0] < 50 && p[1] < 50 && p[2] < 50) + continue; + + /* + * Make sure close-by pixels are also significantly + * bright. + */ + score = 0; + for (xx = x; xx < x + 10; xx++) { + for (yy = y; yy < y + 10; yy++) { + p = &dump->bgr[(xx + yy * dump->width) * 3]; + + if (p[0] > 50 && p[1] > 50 && p[2] > 50) + score++; + } + } + + /* Not enough pixels are significantly bright. */ + if (score < 25) + continue; + + if (x < left) + left = x; + + if (y < top) + top = y; + + if (left == x || top == y) + continue; + } + } + + igt_debug("Detected analogue frame edges at %dx%d\n", left, top); + + /* Crop the frame given the detected top-left corner. */ + bgr = malloc(width * height * 3); + + for (y = 0; y < height; y++) { + p = &dump->bgr[(left + (top + y) * dump->width) * 3]; + q = &bgr[(y * width) * 3]; + memcpy(q, p, width * 3); + } + + free(dump->bgr); + dump->width = width; + dump->height = height; + dump->bgr = bgr; +} + +/** + * chamelium_check_analogue_frame_eq: + * @chamelium: The Chamelium instance to use + * @dump: The chamelium frame dump to check + * @fb: The framebuffer to check against + * + * Checks that the analogue image contained in the chamelium frame dump matches + * the given framebuffer. + * + * In order to determine whether the frame matches the reference, the following + * reasoning is implemented: + * 1. The absolute error for each color value of the reference is collected. + * 2. The average absolute error is calculated for each color value of the + * reference and must not go above 60 (23.5 % of the total range). + * 3. A linear fit for the average absolute error from the pixel value is + * calculated, as a DAC-ADC chain is expected to have a linear error curve. + * 4. The linear fit is correlated with the actual average absolute error for + * the frame and the correlation coefficient is checked to be > 0.985, + * indicating a match with the expected error trend. + * + * Most errors (e.g. due to scaling, rotation, color space, etc) can be + * reliably detected this way, with a minimized number of false-positives. + * However, the brightest values (250 and up) are ignored as the error trend + * is often not linear there in practice due to clamping. + * + * Returns: a boolean indicating whether the frames match + */ +bool chamelium_check_analogue_frame_match(const struct chamelium *chamelium, + const struct chamelium_frame_dump *dump, + struct igt_fb *fb) +{ + cairo_t *cr; + cairo_surface_t *fb_surface; + pixman_image_t *reference_src, *reference_bgr; + int w = dump->width, h = dump->height; + int error_count[3][256][2] = { 0 }; + double error_average[4][250]; + double error_trend[250]; + double c0, c1, cov00, cov01, cov11, sumsq; + double correlation; + unsigned char *bgr; + unsigned char *p; + unsigned char *q; + bool match = true; + int diff; + int x, y; + int i, j; + + /* Get the cairo surface for the framebuffer */ + cr = igt_get_cairo_ctx(chamelium->drm_fd, fb); + fb_surface = cairo_get_target(cr); + cairo_surface_reference(fb_surface); + cairo_destroy(cr); + + /* + * Convert the reference image into the same format as the chamelium + * image + */ + reference_src = pixman_image_create_bits( + PIXMAN_x8r8g8b8, w, h, + (void*)cairo_image_surface_get_data(fb_surface), + cairo_image_surface_get_stride(fb_surface)); + reference_bgr = convert_frame_format(reference_src, PIXMAN_b8g8r8); + pixman_image_unref(reference_src); + + bgr = (unsigned char *) pixman_image_get_data(reference_bgr); + + /* Collect the absolute error for each color value */ + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + p = &dump->bgr[(x + y * w) * 3]; + q = &bgr[(x + y * w) * 3]; + + for (i = 0; i < 3; i++) { + diff = (int) p[i] - q[i]; + if (diff < 0) + diff = -diff; + + error_count[i][q[i]][0] += diff; + error_count[i][q[i]][1]++; + } + } + } + + /* Calculate the average absolute error for each color value */ + for (i = 0; i < 250; i++) { + error_average[0][i] = i; + + for (j = 1; j < 4; j++) { + error_average[j][i] = (double) error_count[j-1][i][0] / + error_count[j-1][i][1]; + + if (error_average[j][i] > 60) { + igt_warn("Error average too high (%f)\n", + error_average[j][i]); + + match = false; + goto complete; + } + } + } + + /* + * Calculate error trend from linear fit. + * A DAC-ADC chain is expected to have a linear absolute error on + * most of its range + */ + for (i = 1; i < 4; i++) { + gsl_fit_linear((const double *) &error_average[0], 1, + (const double *) &error_average[i], 1, 250, + &c0, &c1, &cov00, &cov01, &cov11, &sumsq); + + for (j = 0; j < 250; j++) + error_trend[j] = c0 + j * c1; + + correlation = gsl_stats_correlation((const double *) &error_trend, + 1, + (const double *) &error_average[i], + 1, 250); + + if (correlation < 0.985) { + igt_warn("Error with reference not correlated (%f)\n", + correlation); + + match = false; + goto complete; + } + } + +complete: + + pixman_image_unref(reference_bgr); + cairo_surface_destroy(fb_surface); + + return match; +} + +/** * chamelium_get_frame_limit: * @chamelium: The Chamelium instance to use * @port: The port to check the frame limit on @@ -1482,7 +1713,7 @@ igt_constructor { /* Frame dumps can be large, so we need to be able to handle very large * responses * - * Limit here is 10MB + * Limit here is 15MB */ - xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, 10485760); + xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, 15728640); } diff --git a/lib/igt_chamelium.h b/lib/igt_chamelium.h index aa881971..df1ebd12 100644 --- a/lib/igt_chamelium.h +++ b/lib/igt_chamelium.h @@ -102,13 +102,17 @@ int chamelium_get_captured_frame_count(struct chamelium *chamelium); int chamelium_get_frame_limit(struct chamelium *chamelium, struct chamelium_port *port, int w, int h); - void chamelium_assert_frame_eq(const struct chamelium *chamelium, const struct chamelium_frame_dump *dump, struct igt_fb *fb); void chamelium_write_frame_to_png(const struct chamelium *chamelium, const struct chamelium_frame_dump *dump, const char *filename); +void chamelium_crop_analogue_frame(struct chamelium_frame_dump *dump, int width, + int height); +bool chamelium_check_analogue_frame_match(const struct chamelium *chamelium, + const struct chamelium_frame_dump *dump, + struct igt_fb *fb); void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump); #endif /* IGT_CHAMELIUM_H */ diff --git a/tests/chamelium.c b/tests/chamelium.c index 3d95c05c..5ccdee7c 100644 --- a/tests/chamelium.c +++ b/tests/chamelium.c @@ -360,6 +360,10 @@ enable_output(data_t *data, BROADCAST_RGB_FULL); igt_display_commit(display); + + if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_VGA) + usleep(250000); + chamelium_port_wait_video_input_stable(data->chamelium, port, HOTPLUG_TIMEOUT); @@ -470,6 +474,81 @@ test_display_crc(data_t *data, struct chamelium_port *port, int count) } static void +test_analogue_frame_dump(data_t *data, struct chamelium_port *port) +{ + igt_display_t display; + igt_output_t *output; + igt_plane_t *primary; + struct igt_fb fb; + struct chamelium_frame_dump *frame; + drmModeModeInfo *mode; + drmModeConnector *connector; + int fb_id, i; + const char *connector_name; + char *frame_dump_path; + char path[PATH_MAX]; + bool eq; + + output = prepare_output(data, &display, port); + connector = chamelium_port_get_connector(data->chamelium, port, false); + primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); + igt_assert(primary); + + connector_name = kmstest_connector_type_str(connector->connector_type); + frame_dump_path = chamelium_get_frame_dump_path(data->chamelium); + + for (i = 0; i < connector->count_modes; i++) { + mode = &connector->modes[i]; + + fb_id = igt_create_color_pattern_fb(data->drm_fd, + mode->hdisplay, mode->vdisplay, + DRM_FORMAT_XRGB8888, + LOCAL_DRM_FORMAT_MOD_NONE, + 0, 0, 0, &fb); + igt_assert(fb_id > 0); + + enable_output(data, port, output, mode, &fb); + + igt_debug("Reading frame dumps from Chamelium...\n"); + + frame = chamelium_port_dump_pixels(data->chamelium, port, 0, 0, + 0, 0); + + chamelium_crop_analogue_frame(frame, mode->hdisplay, + mode->vdisplay); + + eq = chamelium_check_analogue_frame_match(data->chamelium, + frame, &fb); + if (!eq && frame_dump_path) { + igt_debug("Dumping reference and chamelium frames to %s...\n", + frame_dump_path); + + snprintf(path, PATH_MAX, "%s/frame-reference-%s.png", + frame_dump_path, connector_name); + igt_write_fb_to_png(data->drm_fd, &fb, path); + + snprintf(path, PATH_MAX, "%s/frame-chamelium-%s.png", + frame_dump_path, connector_name); + chamelium_write_frame_to_png(data->chamelium, + frame, path); + + chamelium_destroy_frame_dump(frame); + } + + igt_fail_on_f(!eq, + "Chamelium analogue frame mismatch with reference\n"); + + chamelium_destroy_frame_dump(frame); + + disable_output(data, port, output); + igt_remove_fb(data->drm_fd, &fb); + } + + drmModeFreeConnector(connector); + igt_display_fini(&display); +} + +static void test_hpd_without_ddc(data_t *data, struct chamelium_port *port) { struct udev_monitor *mon = igt_watch_hotplug(); @@ -706,6 +785,9 @@ igt_main connector_subtest("vga-hpd-without-ddc", VGA) test_hpd_without_ddc(&data, port); + + connector_subtest("vga-frame-dump", VGA) + test_analogue_frame_dump(&data, port); } igt_subtest_group { -- 2.13.2 _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/intel-gfx