Paul Kocialkowski | ee31e0b | 2017-07-19 16:46:07 +0300 | [diff] [blame] | 1 | /* |
| 2 | * Copyright © 2017 Intel Corporation |
| 3 | * |
| 4 | * Permission is hereby granted, free of charge, to any person obtaining a |
| 5 | * copy of this software and associated documentation files (the "Software"), |
| 6 | * to deal in the Software without restriction, including without limitation |
| 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| 8 | * and/or sell copies of the Software, and to permit persons to whom the |
| 9 | * Software is furnished to do so, subject to the following conditions: |
| 10 | * |
| 11 | * The above copyright notice and this permission notice (including the next |
| 12 | * paragraph) shall be included in all copies or substantial portions of the |
| 13 | * Software. |
| 14 | * |
| 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 21 | * IN THE SOFTWARE. |
| 22 | * |
| 23 | * Authors: |
| 24 | * Paul Kocialkowski <paul.kocialkowski@linux.intel.com> |
| 25 | */ |
| 26 | |
| 27 | #include "config.h" |
| 28 | |
| 29 | #include <fcntl.h> |
| 30 | #include <pixman.h> |
| 31 | #include <cairo.h> |
Paul Kocialkowski | 8cf32fe | 2017-07-20 18:13:36 +0300 | [diff] [blame] | 32 | #include <gsl/gsl_statistics_double.h> |
| 33 | #include <gsl/gsl_fit.h> |
Paul Kocialkowski | ee31e0b | 2017-07-19 16:46:07 +0300 | [diff] [blame] | 34 | |
Daniel Vetter | 8c1fcc6 | 2017-09-08 11:23:15 +0200 | [diff] [blame] | 35 | #include "igt_frame.h" |
| 36 | #include "igt_core.h" |
Paul Kocialkowski | ee31e0b | 2017-07-19 16:46:07 +0300 | [diff] [blame] | 37 | |
| 38 | /** |
| 39 | * SECTION:igt_frame |
| 40 | * @short_description: Library for frame-related tests |
| 41 | * @title: Frame |
| 42 | * @include: igt_frame.h |
| 43 | * |
| 44 | * This library contains helpers for frame-related tests. This includes common |
| 45 | * frame dumping as well as frame comparison helpers. |
| 46 | */ |
| 47 | |
| 48 | /** |
| 49 | * igt_frame_dump_is_enabled: |
| 50 | * |
| 51 | * Get whether frame dumping is enabled. |
| 52 | * |
| 53 | * Returns: A boolean indicating whether frame dumping is enabled |
| 54 | */ |
| 55 | bool igt_frame_dump_is_enabled(void) |
| 56 | { |
Daniel Vetter | a3a3e0f | 2017-09-05 14:36:08 +0200 | [diff] [blame] | 57 | return igt_frame_dump_path != NULL; |
Paul Kocialkowski | ee31e0b | 2017-07-19 16:46:07 +0300 | [diff] [blame] | 58 | } |
| 59 | |
| 60 | static void igt_write_frame_to_png(cairo_surface_t *surface, int fd, |
| 61 | const char *qualifier, const char *suffix) |
| 62 | { |
| 63 | char path[PATH_MAX]; |
| 64 | const char *test_name; |
| 65 | const char *subtest_name; |
| 66 | cairo_status_t status; |
| 67 | int index; |
| 68 | |
| 69 | test_name = igt_test_name(); |
| 70 | subtest_name = igt_subtest_name(); |
| 71 | |
| 72 | if (suffix) |
| 73 | snprintf(path, PATH_MAX, "%s/frame-%s-%s-%s-%s.png", |
Daniel Vetter | a3a3e0f | 2017-09-05 14:36:08 +0200 | [diff] [blame] | 74 | igt_frame_dump_path, test_name, subtest_name, qualifier, |
Paul Kocialkowski | ee31e0b | 2017-07-19 16:46:07 +0300 | [diff] [blame] | 75 | suffix); |
| 76 | else |
| 77 | snprintf(path, PATH_MAX, "%s/frame-%s-%s-%s.png", |
Daniel Vetter | a3a3e0f | 2017-09-05 14:36:08 +0200 | [diff] [blame] | 78 | igt_frame_dump_path, test_name, subtest_name, qualifier); |
Paul Kocialkowski | ee31e0b | 2017-07-19 16:46:07 +0300 | [diff] [blame] | 79 | |
| 80 | igt_debug("Dumping %s frame to %s...\n", qualifier, path); |
| 81 | |
| 82 | status = cairo_surface_write_to_png(surface, path); |
| 83 | |
| 84 | igt_assert_eq(status, CAIRO_STATUS_SUCCESS); |
| 85 | |
| 86 | index = strlen(path); |
| 87 | |
| 88 | if (fd >= 0 && index < (PATH_MAX - 1)) { |
| 89 | path[index++] = '\n'; |
| 90 | path[index] = '\0'; |
| 91 | |
| 92 | write(fd, path, strlen(path)); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | /** |
| 97 | * igt_write_compared_frames_to_png: |
| 98 | * @reference: The reference cairo surface |
| 99 | * @capture: The captured cairo surface |
| 100 | * @reference_suffix: The suffix to give to the reference png file |
| 101 | * @capture_suffix: The suffix to give to the capture png file |
| 102 | * |
| 103 | * Write previously compared frames to png files. |
| 104 | */ |
| 105 | void igt_write_compared_frames_to_png(cairo_surface_t *reference, |
| 106 | cairo_surface_t *capture, |
| 107 | const char *reference_suffix, |
| 108 | const char *capture_suffix) |
| 109 | { |
| 110 | char *id; |
| 111 | const char *test_name; |
| 112 | const char *subtest_name; |
| 113 | char path[PATH_MAX]; |
| 114 | int fd = -1; |
| 115 | |
| 116 | if (!igt_frame_dump_is_enabled()) |
| 117 | return; |
| 118 | |
| 119 | id = getenv("IGT_FRAME_DUMP_ID"); |
| 120 | |
| 121 | test_name = igt_test_name(); |
| 122 | subtest_name = igt_subtest_name(); |
| 123 | |
| 124 | if (id) |
| 125 | snprintf(path, PATH_MAX, "%s/frame-%s-%s-%s.txt", |
Daniel Vetter | a3a3e0f | 2017-09-05 14:36:08 +0200 | [diff] [blame] | 126 | igt_frame_dump_path, test_name, subtest_name, id); |
Paul Kocialkowski | ee31e0b | 2017-07-19 16:46:07 +0300 | [diff] [blame] | 127 | else |
| 128 | snprintf(path, PATH_MAX, "%s/frame-%s-%s.txt", |
Daniel Vetter | a3a3e0f | 2017-09-05 14:36:08 +0200 | [diff] [blame] | 129 | igt_frame_dump_path, test_name, subtest_name); |
Paul Kocialkowski | ee31e0b | 2017-07-19 16:46:07 +0300 | [diff] [blame] | 130 | |
| 131 | fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); |
| 132 | igt_assert(fd >= 0); |
| 133 | |
| 134 | igt_debug("Writing dump report to %s...\n", path); |
| 135 | |
| 136 | igt_write_frame_to_png(reference, fd, "reference", reference_suffix); |
| 137 | igt_write_frame_to_png(capture, fd, "capture", capture_suffix); |
| 138 | |
| 139 | close(fd); |
| 140 | } |
Paul Kocialkowski | 8cf32fe | 2017-07-20 18:13:36 +0300 | [diff] [blame] | 141 | |
| 142 | /** |
| 143 | * igt_check_analog_frame_match: |
| 144 | * @reference: The reference cairo surface |
| 145 | * @capture: The captured cairo surface |
| 146 | * |
| 147 | * Checks that the analog image contained in the chamelium frame dump matches |
| 148 | * the given framebuffer. |
| 149 | * |
| 150 | * In order to determine whether the frame matches the reference, the following |
| 151 | * reasoning is implemented: |
| 152 | * 1. The absolute error for each color value of the reference is collected. |
| 153 | * 2. The average absolute error is calculated for each color value of the |
| 154 | * reference and must not go above 60 (23.5 % of the total range). |
| 155 | * 3. A linear fit for the average absolute error from the pixel value is |
| 156 | * calculated, as a DAC-ADC chain is expected to have a linear error curve. |
| 157 | * 4. The linear fit is correlated with the actual average absolute error for |
| 158 | * the frame and the correlation coefficient is checked to be > 0.985, |
| 159 | * indicating a match with the expected error trend. |
| 160 | * |
| 161 | * Most errors (e.g. due to scaling, rotation, color space, etc) can be |
| 162 | * reliably detected this way, with a minimized number of false-positives. |
| 163 | * However, the brightest values (250 and up) are ignored as the error trend |
| 164 | * is often not linear there in practice due to clamping. |
| 165 | * |
| 166 | * Returns: a boolean indicating whether the frames match |
| 167 | */ |
| 168 | |
| 169 | bool igt_check_analog_frame_match(cairo_surface_t *reference, |
| 170 | cairo_surface_t *capture) |
| 171 | { |
| 172 | pixman_image_t *reference_src, *capture_src; |
| 173 | int w, h; |
| 174 | int error_count[3][256][2] = { 0 }; |
| 175 | double error_average[4][250]; |
| 176 | double error_trend[250]; |
| 177 | double c0, c1, cov00, cov01, cov11, sumsq; |
| 178 | double correlation; |
| 179 | unsigned char *reference_pixels, *capture_pixels; |
| 180 | unsigned char *p; |
| 181 | unsigned char *q; |
| 182 | bool match = true; |
| 183 | int diff; |
| 184 | int x, y; |
| 185 | int i, j; |
| 186 | |
| 187 | w = cairo_image_surface_get_width(reference); |
| 188 | h = cairo_image_surface_get_height(reference); |
| 189 | |
| 190 | reference_src = pixman_image_create_bits( |
| 191 | PIXMAN_x8r8g8b8, w, h, |
| 192 | (void*)cairo_image_surface_get_data(reference), |
| 193 | cairo_image_surface_get_stride(reference)); |
| 194 | reference_pixels = (unsigned char *) pixman_image_get_data(reference_src); |
| 195 | |
| 196 | capture_src = pixman_image_create_bits( |
| 197 | PIXMAN_x8r8g8b8, w, h, |
| 198 | (void*)cairo_image_surface_get_data(capture), |
| 199 | cairo_image_surface_get_stride(capture)); |
| 200 | capture_pixels = (unsigned char *) pixman_image_get_data(capture_src); |
| 201 | |
| 202 | /* Collect the absolute error for each color value */ |
| 203 | for (x = 0; x < w; x++) { |
| 204 | for (y = 0; y < h; y++) { |
| 205 | p = &capture_pixels[(x + y * w) * 4]; |
| 206 | q = &reference_pixels[(x + y * w) * 4]; |
| 207 | |
| 208 | for (i = 0; i < 3; i++) { |
| 209 | diff = (int) p[i] - q[i]; |
| 210 | if (diff < 0) |
| 211 | diff = -diff; |
| 212 | |
| 213 | error_count[i][q[i]][0] += diff; |
| 214 | error_count[i][q[i]][1]++; |
| 215 | } |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | /* Calculate the average absolute error for each color value */ |
| 220 | for (i = 0; i < 250; i++) { |
| 221 | error_average[0][i] = i; |
| 222 | |
| 223 | for (j = 1; j < 4; j++) { |
| 224 | error_average[j][i] = (double) error_count[j-1][i][0] / |
| 225 | error_count[j-1][i][1]; |
| 226 | |
| 227 | if (error_average[j][i] > 60) { |
| 228 | igt_warn("Error average too high (%f)\n", |
| 229 | error_average[j][i]); |
| 230 | |
| 231 | match = false; |
| 232 | goto complete; |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | /* |
| 238 | * Calculate error trend from linear fit. |
| 239 | * A DAC-ADC chain is expected to have a linear absolute error on |
| 240 | * most of its range |
| 241 | */ |
| 242 | for (i = 1; i < 4; i++) { |
| 243 | gsl_fit_linear((const double *) &error_average[0], 1, |
| 244 | (const double *) &error_average[i], 1, 250, |
| 245 | &c0, &c1, &cov00, &cov01, &cov11, &sumsq); |
| 246 | |
| 247 | for (j = 0; j < 250; j++) |
| 248 | error_trend[j] = c0 + j * c1; |
| 249 | |
| 250 | correlation = gsl_stats_correlation((const double *) &error_trend, |
| 251 | 1, |
| 252 | (const double *) &error_average[i], |
| 253 | 1, 250); |
| 254 | |
| 255 | if (correlation < 0.985) { |
| 256 | igt_warn("Error with reference not correlated (%f)\n", |
| 257 | correlation); |
| 258 | |
| 259 | match = false; |
| 260 | goto complete; |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | complete: |
| 265 | pixman_image_unref(reference_src); |
| 266 | pixman_image_unref(capture_src); |
| 267 | |
| 268 | return match; |
| 269 | } |