Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2020 Collabora, Ltd. |
| 3 | * |
| 4 | * Permission is hereby granted, free of charge, to any person obtaining |
| 5 | * a copy of this software and associated documentation files (the |
| 6 | * "Software"), to deal in the Software without restriction, including |
| 7 | * without limitation the rights to use, copy, modify, merge, publish, |
| 8 | * distribute, sublicense, and/or sell copies of the Software, and to |
| 9 | * permit persons to whom the Software is furnished to do so, subject to |
| 10 | * the following conditions: |
| 11 | * |
| 12 | * The above copyright notice and this permission notice (including the |
| 13 | * next paragraph) shall be included in all copies or substantial |
| 14 | * portions of the Software. |
| 15 | * |
| 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 23 | * SOFTWARE. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | |
Pekka Paalanen | e70aa1f | 2021-04-16 17:42:40 +0300 | [diff] [blame^] | 28 | #include <math.h> |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 29 | |
| 30 | #include "weston-test-client-helper.h" |
| 31 | #include "weston-test-fixture-compositor.h" |
| 32 | |
| 33 | struct setup_args { |
Pekka Paalanen | ef81388 | 2021-02-15 13:46:42 +0200 | [diff] [blame] | 34 | struct fixture_metadata meta; |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 35 | enum renderer_type renderer; |
| 36 | }; |
| 37 | |
| 38 | static const int ALPHA_STEPS = 256; |
| 39 | static const int BLOCK_WIDTH = 3; |
| 40 | |
| 41 | static const struct setup_args my_setup_args[] = { |
Pekka Paalanen | ef81388 | 2021-02-15 13:46:42 +0200 | [diff] [blame] | 42 | { |
| 43 | .renderer = RENDERER_PIXMAN, |
| 44 | .meta.name = "pixman" |
| 45 | }, |
| 46 | { |
| 47 | .renderer = RENDERER_GL, |
| 48 | .meta.name = "GL" |
| 49 | }, |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 50 | }; |
| 51 | |
| 52 | static enum test_result_code |
| 53 | fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) |
| 54 | { |
| 55 | struct compositor_setup setup; |
| 56 | |
| 57 | compositor_setup_defaults(&setup); |
| 58 | setup.renderer = arg->renderer; |
| 59 | setup.width = BLOCK_WIDTH * ALPHA_STEPS; |
| 60 | setup.height = 16; |
| 61 | setup.shell = SHELL_TEST_DESKTOP; |
| 62 | |
| 63 | return weston_test_harness_execute_as_client(harness, &setup); |
| 64 | } |
Pekka Paalanen | ef81388 | 2021-02-15 13:46:42 +0200 | [diff] [blame] | 65 | DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args, meta); |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 66 | |
| 67 | static void |
| 68 | set_opaque_rect(struct client *client, |
| 69 | struct surface *surface, |
| 70 | const struct rectangle *rect) |
| 71 | { |
| 72 | struct wl_region *region; |
| 73 | |
| 74 | region = wl_compositor_create_region(client->wl_compositor); |
| 75 | wl_region_add(region, rect->x, rect->y, rect->width, rect->height); |
| 76 | wl_surface_set_opaque_region(surface->wl_surface, region); |
| 77 | wl_region_destroy(region); |
| 78 | } |
| 79 | |
| 80 | static uint32_t |
| 81 | premult_color(uint32_t a, uint32_t r, uint32_t g, uint32_t b) |
| 82 | { |
| 83 | uint32_t c = 0; |
| 84 | |
| 85 | c |= a << 24; |
| 86 | c |= (a * r / 255) << 16; |
| 87 | c |= (a * g / 255) << 8; |
| 88 | c |= a * b / 255; |
| 89 | |
| 90 | return c; |
| 91 | } |
| 92 | |
| 93 | static void |
| 94 | fill_alpha_pattern(struct buffer *buf) |
| 95 | { |
| 96 | void *pixels; |
| 97 | int stride_bytes; |
| 98 | int w, h; |
| 99 | int y; |
| 100 | |
| 101 | assert(pixman_image_get_format(buf->image) == PIXMAN_a8r8g8b8); |
| 102 | |
| 103 | pixels = pixman_image_get_data(buf->image); |
| 104 | stride_bytes = pixman_image_get_stride(buf->image); |
| 105 | w = pixman_image_get_width(buf->image); |
| 106 | h = pixman_image_get_height(buf->image); |
| 107 | |
| 108 | assert(w == BLOCK_WIDTH * ALPHA_STEPS); |
| 109 | |
| 110 | for (y = 0; y < h; y++) { |
| 111 | uint32_t *row = pixels + y * stride_bytes; |
| 112 | uint32_t step; |
| 113 | |
| 114 | for (step = 0; step < (uint32_t)ALPHA_STEPS; step++) { |
| 115 | uint32_t alpha = step * 255 / (ALPHA_STEPS - 1); |
| 116 | uint32_t color; |
| 117 | int i; |
| 118 | |
| 119 | color = premult_color(alpha, 0, 255 - alpha, 255); |
| 120 | for (i = 0; i < BLOCK_WIDTH; i++) |
| 121 | *row++ = color; |
| 122 | } |
| 123 | } |
| 124 | } |
| 125 | |
Pekka Paalanen | e70aa1f | 2021-04-16 17:42:40 +0300 | [diff] [blame^] | 126 | struct color_float { |
| 127 | float r, g, b, a; |
| 128 | }; |
| 129 | |
| 130 | static struct color_float |
| 131 | a8r8g8b8_to_float(uint32_t v) |
| 132 | { |
| 133 | struct color_float cf; |
| 134 | |
| 135 | cf.a = ((v >> 24) & 0xff) / 255.f; |
| 136 | cf.r = ((v >> 16) & 0xff) / 255.f; |
| 137 | cf.g = ((v >> 8) & 0xff) / 255.f; |
| 138 | cf.b = ((v >> 0) & 0xff) / 255.f; |
| 139 | |
| 140 | return cf; |
| 141 | } |
| 142 | |
| 143 | static void |
| 144 | unpremult_float(struct color_float *cf) |
| 145 | { |
| 146 | if (cf->a == 0.0f) { |
| 147 | cf->r = 0.0f; |
| 148 | cf->g = 0.0f; |
| 149 | cf->b = 0.0f; |
| 150 | } else { |
| 151 | cf->r /= cf->a; |
| 152 | cf->g /= cf->a; |
| 153 | cf->b /= cf->a; |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | static bool |
| 158 | compare_float(float ref, float dst, int x, const char *chan, float *max_diff) |
| 159 | { |
| 160 | #if 0 |
| 161 | /* |
| 162 | * This file can be loaded in Octave for visualization. |
| 163 | * |
| 164 | * S = load('compare_float_dump.txt'); |
| 165 | * |
| 166 | * rvec = S(S(:,1)==114, 2:3); |
| 167 | * gvec = S(S(:,1)==103, 2:3); |
| 168 | * bvec = S(S(:,1)==98, 2:3); |
| 169 | * |
| 170 | * figure |
| 171 | * subplot(3, 1, 1); |
| 172 | * plot(rvec(:,1), rvec(:,2) .* 255, 'r'); |
| 173 | * subplot(3, 1, 2); |
| 174 | * plot(gvec(:,1), gvec(:,2) .* 255, 'g'); |
| 175 | * subplot(3, 1, 3); |
| 176 | * plot(bvec(:,1), bvec(:,2) .* 255, 'b'); |
| 177 | */ |
| 178 | static FILE *fp = NULL; |
| 179 | |
| 180 | if (!fp) |
| 181 | fp = fopen("compare_float_dump.txt", "w"); |
| 182 | fprintf(fp, "%d %d %f\n", chan[0], x, dst - ref); |
| 183 | fflush(fp); |
| 184 | #endif |
| 185 | |
| 186 | float diff = fabsf(ref - dst); |
| 187 | |
| 188 | if (diff > *max_diff) |
| 189 | *max_diff = diff; |
| 190 | |
| 191 | if (diff < 0.5f / 255.f) |
| 192 | return true; |
| 193 | |
| 194 | testlog("x=%d %s: ref %f != dst %f, delta %f\n", |
| 195 | x, chan, ref, dst, dst - ref); |
| 196 | |
| 197 | return false; |
| 198 | } |
| 199 | |
| 200 | static bool |
| 201 | verify_sRGB_blend_a8r8g8b8(uint32_t bg32, uint32_t fg32, uint32_t dst32, |
| 202 | int x, struct color_float *max_diff) |
| 203 | { |
| 204 | struct color_float bg = a8r8g8b8_to_float(bg32); |
| 205 | struct color_float fg = a8r8g8b8_to_float(fg32); |
| 206 | struct color_float dst = a8r8g8b8_to_float(dst32); |
| 207 | struct color_float ref; |
| 208 | bool ok = true; |
| 209 | |
| 210 | unpremult_float(&bg); |
| 211 | unpremult_float(&fg); |
| 212 | unpremult_float(&dst); |
| 213 | |
| 214 | ref.r = (1.0f - fg.a) * bg.r + fg.a * fg.r; |
| 215 | ref.g = (1.0f - fg.a) * bg.g + fg.a * fg.g; |
| 216 | ref.b = (1.0f - fg.a) * bg.b + fg.a * fg.b; |
| 217 | |
| 218 | ok = compare_float(ref.r, dst.r, x, "r", &max_diff->r) && ok; |
| 219 | ok = compare_float(ref.g, dst.g, x, "g", &max_diff->g) && ok; |
| 220 | ok = compare_float(ref.b, dst.b, x, "b", &max_diff->b) && ok; |
| 221 | |
| 222 | return ok; |
| 223 | } |
| 224 | |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 225 | static uint8_t |
| 226 | red(uint32_t v) |
| 227 | { |
| 228 | return (v >> 16) & 0xff; |
| 229 | } |
| 230 | |
| 231 | static uint8_t |
| 232 | blue(uint32_t v) |
| 233 | { |
| 234 | return v & 0xff; |
| 235 | } |
| 236 | |
| 237 | static bool |
| 238 | pixels_monotonic(const uint32_t *row, int x) |
| 239 | { |
| 240 | bool ret = true; |
| 241 | |
| 242 | if (red(row[x + 1]) > red(row[x])) { |
| 243 | testlog("pixel %d -> next: red value increases\n", x); |
| 244 | ret = false; |
| 245 | } |
| 246 | |
| 247 | if (blue(row[x + 1]) < blue(row[x])) { |
| 248 | testlog("pixel %d -> next: blue value decreases\n", x); |
| 249 | ret = false; |
| 250 | } |
| 251 | |
| 252 | return ret; |
| 253 | } |
| 254 | |
Pekka Paalanen | 129bef5 | 2021-04-19 14:05:59 +0300 | [diff] [blame] | 255 | static void * |
| 256 | get_middle_row(struct buffer *buf) |
| 257 | { |
| 258 | const int y = (BLOCK_WIDTH - 1) / 2; /* middle row */ |
| 259 | void *pixels; |
| 260 | int stride_bytes; |
| 261 | |
| 262 | assert(pixman_image_get_width(buf->image) >= BLOCK_WIDTH * ALPHA_STEPS); |
| 263 | assert(pixman_image_get_height(buf->image) >= BLOCK_WIDTH); |
| 264 | |
| 265 | pixels = pixman_image_get_data(buf->image); |
| 266 | stride_bytes = pixman_image_get_stride(buf->image); |
| 267 | return pixels + y * stride_bytes; |
| 268 | } |
| 269 | |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 270 | static bool |
Pekka Paalanen | e70aa1f | 2021-04-16 17:42:40 +0300 | [diff] [blame^] | 271 | check_blend_pattern(struct buffer *bg, struct buffer *fg, struct buffer *shot) |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 272 | { |
Pekka Paalanen | e70aa1f | 2021-04-16 17:42:40 +0300 | [diff] [blame^] | 273 | uint32_t *bg_row = get_middle_row(bg); |
| 274 | uint32_t *fg_row = get_middle_row(fg); |
Pekka Paalanen | 129bef5 | 2021-04-19 14:05:59 +0300 | [diff] [blame] | 275 | uint32_t *shot_row = get_middle_row(shot); |
Pekka Paalanen | e70aa1f | 2021-04-16 17:42:40 +0300 | [diff] [blame^] | 276 | struct color_float max_diff = { 0.0f, 0.0f, 0.0f, 0.0f }; |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 277 | bool ret = true; |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 278 | int x; |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 279 | |
Pekka Paalanen | 129bef5 | 2021-04-19 14:05:59 +0300 | [diff] [blame] | 280 | for (x = 0; x < BLOCK_WIDTH * ALPHA_STEPS - 1; x++) { |
| 281 | if (!pixels_monotonic(shot_row, x)) |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 282 | ret = false; |
Pekka Paalanen | e70aa1f | 2021-04-16 17:42:40 +0300 | [diff] [blame^] | 283 | |
| 284 | if (!verify_sRGB_blend_a8r8g8b8(bg_row[x], fg_row[x], |
| 285 | shot_row[x], x, &max_diff)) |
| 286 | ret = false; |
Pekka Paalanen | 129bef5 | 2021-04-19 14:05:59 +0300 | [diff] [blame] | 287 | } |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 288 | |
Pekka Paalanen | e70aa1f | 2021-04-16 17:42:40 +0300 | [diff] [blame^] | 289 | testlog("%s max diff: r=%f, g=%f, b=%f\n", |
| 290 | __func__, max_diff.r, max_diff.g, max_diff.b); |
| 291 | |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 292 | return ret; |
| 293 | } |
| 294 | |
| 295 | /* |
| 296 | * Test that alpha blending is roughly correct, and that an alpha ramp |
Maxime Roussin-Bélanger | 4e8ea1f | 2020-12-17 17:07:49 -0500 | [diff] [blame] | 297 | * results in a strictly monotonic color ramp. This should ensure that any |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 298 | * animation that varies alpha never goes "backwards" as that is easily |
| 299 | * noticeable. |
| 300 | * |
| 301 | * The background is a constant color. On top of that, there is an |
| 302 | * alpha-blended gradient with ramps in both alpha and color. Sub-surface |
| 303 | * ensures the correct positioning and stacking. |
| 304 | * |
| 305 | * The gradient consists of ALPHA_STEPS number of blocks. Block size is |
| 306 | * BLOCK_WIDTH x BLOCK_WIDTH and a block has a uniform color. |
| 307 | * |
| 308 | * In the blending result over x axis: |
| 309 | * - red goes from 1.0 to 0.0, monotonic |
| 310 | * - green is not monotonic |
| 311 | * - blue goes from 0.0 to 1.0, monotonic |
| 312 | */ |
Pekka Paalanen | e70aa1f | 2021-04-16 17:42:40 +0300 | [diff] [blame^] | 313 | TEST(alpha_blend) |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 314 | { |
| 315 | const int width = BLOCK_WIDTH * ALPHA_STEPS; |
| 316 | const int height = BLOCK_WIDTH; |
| 317 | const pixman_color_t background_color = { |
| 318 | .red = 0xffff, |
| 319 | .green = 0x8080, |
| 320 | .blue = 0x0000, |
| 321 | .alpha = 0xffff |
| 322 | }; |
| 323 | struct client *client; |
Pekka Paalanen | 129bef5 | 2021-04-19 14:05:59 +0300 | [diff] [blame] | 324 | struct buffer *bg; |
| 325 | struct buffer *fg; |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 326 | struct wl_subcompositor *subco; |
| 327 | struct wl_surface *surf; |
| 328 | struct wl_subsurface *sub; |
| 329 | struct buffer *shot; |
| 330 | bool match; |
| 331 | |
| 332 | client = create_client(); |
| 333 | subco = bind_to_singleton_global(client, &wl_subcompositor_interface, 1); |
| 334 | |
| 335 | /* background window content */ |
Pekka Paalanen | 129bef5 | 2021-04-19 14:05:59 +0300 | [diff] [blame] | 336 | bg = create_shm_buffer_a8r8g8b8(client, width, height); |
| 337 | fill_image_with_color(bg->image, &background_color); |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 338 | |
| 339 | /* background window, main surface */ |
| 340 | client->surface = create_test_surface(client); |
| 341 | client->surface->width = width; |
| 342 | client->surface->height = height; |
Pekka Paalanen | 129bef5 | 2021-04-19 14:05:59 +0300 | [diff] [blame] | 343 | client->surface->buffer = bg; /* pass ownership */ |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 344 | set_opaque_rect(client, client->surface, |
| 345 | &(struct rectangle){ 0, 0, width, height }); |
| 346 | |
| 347 | /* foreground blended content */ |
Pekka Paalanen | 129bef5 | 2021-04-19 14:05:59 +0300 | [diff] [blame] | 348 | fg = create_shm_buffer_a8r8g8b8(client, width, height); |
| 349 | fill_alpha_pattern(fg); |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 350 | |
| 351 | /* foreground window, sub-surface */ |
| 352 | surf = wl_compositor_create_surface(client->wl_compositor); |
| 353 | sub = wl_subcompositor_get_subsurface(subco, surf, client->surface->wl_surface); |
| 354 | /* sub-surface defaults to position 0, 0, top-most, synchronized */ |
Pekka Paalanen | 129bef5 | 2021-04-19 14:05:59 +0300 | [diff] [blame] | 355 | wl_surface_attach(surf, fg->proxy, 0, 0); |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 356 | wl_surface_damage(surf, 0, 0, width, height); |
| 357 | wl_surface_commit(surf); |
| 358 | |
| 359 | /* attach, damage, commit background window */ |
| 360 | move_client(client, 0, 0); |
| 361 | |
| 362 | shot = capture_screenshot_of_output(client); |
| 363 | assert(shot); |
Pekka Paalanen | e70aa1f | 2021-04-16 17:42:40 +0300 | [diff] [blame^] | 364 | match = verify_image(shot, "alpha_blend", 0, NULL, 0); |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 365 | assert(match); |
| 366 | |
Pekka Paalanen | e70aa1f | 2021-04-16 17:42:40 +0300 | [diff] [blame^] | 367 | assert(check_blend_pattern(bg, fg, shot)); |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 368 | buffer_destroy(shot); |
| 369 | |
| 370 | wl_subsurface_destroy(sub); |
| 371 | wl_surface_destroy(surf); |
Pekka Paalanen | 129bef5 | 2021-04-19 14:05:59 +0300 | [diff] [blame] | 372 | buffer_destroy(fg); |
Pekka Paalanen | 681db34 | 2021-05-14 17:06:27 +0300 | [diff] [blame] | 373 | wl_subcompositor_destroy(subco); |
Pekka Paalanen | 129bef5 | 2021-04-19 14:05:59 +0300 | [diff] [blame] | 374 | client_destroy(client); /* destroys bg */ |
Pekka Paalanen | 2625881 | 2020-11-12 14:15:43 +0200 | [diff] [blame] | 375 | } |