blob: e2916be977a72328e6fea465ce7e3b62db82807a [file] [log] [blame]
Pekka Paalanen26258812020-11-12 14:15:43 +02001/*
2 * Copyright 2020 Collabora, Ltd.
Vitaly Prosyak15d75462021-11-09 14:02:01 -05003 * Copyright 2021 Advanced Micro Devices, Inc.
Pekka Paalanen26258812020-11-12 14:15:43 +02004 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the
14 * next paragraph) shall be included in all copies or substantial
15 * portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26
27#include "config.h"
28
Pekka Paalanene70aa1f2021-04-16 17:42:40 +030029#include <math.h>
Pekka Paalanen26258812020-11-12 14:15:43 +020030
31#include "weston-test-client-helper.h"
32#include "weston-test-fixture-compositor.h"
Vitaly Prosyak15d75462021-11-09 14:02:01 -050033#include "color_util.h"
Pekka Paalanen26258812020-11-12 14:15:43 +020034
35struct setup_args {
Pekka Paalanenef813882021-02-15 13:46:42 +020036 struct fixture_metadata meta;
Pekka Paalanen26258812020-11-12 14:15:43 +020037 enum renderer_type renderer;
Pekka Paalanen35cae562021-04-16 17:42:40 +030038 bool color_management;
Pekka Paalanen26258812020-11-12 14:15:43 +020039};
40
41static const int ALPHA_STEPS = 256;
42static const int BLOCK_WIDTH = 3;
43
44static const struct setup_args my_setup_args[] = {
Pekka Paalanenef813882021-02-15 13:46:42 +020045 {
46 .renderer = RENDERER_PIXMAN,
Pekka Paalanen35cae562021-04-16 17:42:40 +030047 .color_management = false,
Pekka Paalanenef813882021-02-15 13:46:42 +020048 .meta.name = "pixman"
49 },
50 {
51 .renderer = RENDERER_GL,
Pekka Paalanen35cae562021-04-16 17:42:40 +030052 .color_management = false,
Pekka Paalanenef813882021-02-15 13:46:42 +020053 .meta.name = "GL"
54 },
Pekka Paalanen35cae562021-04-16 17:42:40 +030055 {
56 .renderer = RENDERER_GL,
57 .color_management = true,
58 .meta.name = "GL sRGB EOTF"
59 },
Pekka Paalanen26258812020-11-12 14:15:43 +020060};
61
62static enum test_result_code
63fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg)
64{
65 struct compositor_setup setup;
66
67 compositor_setup_defaults(&setup);
68 setup.renderer = arg->renderer;
69 setup.width = BLOCK_WIDTH * ALPHA_STEPS;
70 setup.height = 16;
71 setup.shell = SHELL_TEST_DESKTOP;
72
Pekka Paalanen35cae562021-04-16 17:42:40 +030073 if (arg->color_management) {
74 weston_ini_setup(&setup,
75 cfgln("[core]"),
76 cfgln("color-management=true"));
77 }
78
Pekka Paalanen26258812020-11-12 14:15:43 +020079 return weston_test_harness_execute_as_client(harness, &setup);
80}
Pekka Paalanenef813882021-02-15 13:46:42 +020081DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args, meta);
Pekka Paalanen26258812020-11-12 14:15:43 +020082
83static void
84set_opaque_rect(struct client *client,
85 struct surface *surface,
86 const struct rectangle *rect)
87{
88 struct wl_region *region;
89
90 region = wl_compositor_create_region(client->wl_compositor);
91 wl_region_add(region, rect->x, rect->y, rect->width, rect->height);
92 wl_surface_set_opaque_region(surface->wl_surface, region);
93 wl_region_destroy(region);
94}
95
96static uint32_t
97premult_color(uint32_t a, uint32_t r, uint32_t g, uint32_t b)
98{
99 uint32_t c = 0;
100
101 c |= a << 24;
102 c |= (a * r / 255) << 16;
103 c |= (a * g / 255) << 8;
104 c |= a * b / 255;
105
106 return c;
107}
108
109static void
Vitaly Prosyak15d75462021-11-09 14:02:01 -0500110unpremult_float(struct color_float *cf)
111{
112 if (cf->a == 0.0f) {
113 cf->r = 0.0f;
114 cf->g = 0.0f;
115 cf->b = 0.0f;
116 } else {
117 cf->r /= cf->a;
118 cf->g /= cf->a;
119 cf->b /= cf->a;
120 }
121}
122
123static void
Pekka Paalanen26258812020-11-12 14:15:43 +0200124fill_alpha_pattern(struct buffer *buf)
125{
126 void *pixels;
127 int stride_bytes;
128 int w, h;
129 int y;
130
131 assert(pixman_image_get_format(buf->image) == PIXMAN_a8r8g8b8);
132
133 pixels = pixman_image_get_data(buf->image);
134 stride_bytes = pixman_image_get_stride(buf->image);
135 w = pixman_image_get_width(buf->image);
136 h = pixman_image_get_height(buf->image);
137
138 assert(w == BLOCK_WIDTH * ALPHA_STEPS);
139
140 for (y = 0; y < h; y++) {
141 uint32_t *row = pixels + y * stride_bytes;
142 uint32_t step;
143
144 for (step = 0; step < (uint32_t)ALPHA_STEPS; step++) {
145 uint32_t alpha = step * 255 / (ALPHA_STEPS - 1);
146 uint32_t color;
147 int i;
148
149 color = premult_color(alpha, 0, 255 - alpha, 255);
150 for (i = 0; i < BLOCK_WIDTH; i++)
151 *row++ = color;
152 }
153 }
154}
155
Pekka Paalanen35cae562021-04-16 17:42:40 +0300156
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300157static bool
158compare_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
Pekka Paalanen35cae562021-04-16 17:42:40 +0300191 /*
192 * Allow for +/- 1.5 code points of error in non-linear 8-bit channel
193 * value. This is necessary for the BLEND_LINEAR case.
194 *
195 * With llvmpipe, we could go as low as +/- 0.65 code points of error
196 * and still pass.
197 *
198 * AMD Polaris 11 would be ok with +/- 1.0 code points error threshold
199 * if not for one particular case of blending (a=254, r=0) into r=255,
200 * which results in error of 1.29 code points.
201 */
202 if (diff < 1.5f / 255.f)
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300203 return true;
204
205 testlog("x=%d %s: ref %f != dst %f, delta %f\n",
206 x, chan, ref, dst, dst - ref);
207
208 return false;
209}
210
Pekka Paalanen35cae562021-04-16 17:42:40 +0300211enum blend_space {
212 BLEND_NONLINEAR,
213 BLEND_LINEAR,
214};
215
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300216static bool
217verify_sRGB_blend_a8r8g8b8(uint32_t bg32, uint32_t fg32, uint32_t dst32,
Pekka Paalanen35cae562021-04-16 17:42:40 +0300218 int x, struct color_float *max_diff,
219 enum blend_space space)
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300220{
221 struct color_float bg = a8r8g8b8_to_float(bg32);
222 struct color_float fg = a8r8g8b8_to_float(fg32);
223 struct color_float dst = a8r8g8b8_to_float(dst32);
224 struct color_float ref;
225 bool ok = true;
226
227 unpremult_float(&bg);
228 unpremult_float(&fg);
229 unpremult_float(&dst);
230
Pekka Paalanen35cae562021-04-16 17:42:40 +0300231 if (space == BLEND_LINEAR) {
232 sRGB_linearize(&bg);
233 sRGB_linearize(&fg);
234 }
235
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300236 ref.r = (1.0f - fg.a) * bg.r + fg.a * fg.r;
237 ref.g = (1.0f - fg.a) * bg.g + fg.a * fg.g;
238 ref.b = (1.0f - fg.a) * bg.b + fg.a * fg.b;
239
Pekka Paalanen35cae562021-04-16 17:42:40 +0300240 if (space == BLEND_LINEAR)
241 sRGB_delinearize(&ref);
242
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300243 ok = compare_float(ref.r, dst.r, x, "r", &max_diff->r) && ok;
244 ok = compare_float(ref.g, dst.g, x, "g", &max_diff->g) && ok;
245 ok = compare_float(ref.b, dst.b, x, "b", &max_diff->b) && ok;
246
247 return ok;
248}
249
Pekka Paalanen26258812020-11-12 14:15:43 +0200250static uint8_t
251red(uint32_t v)
252{
253 return (v >> 16) & 0xff;
254}
255
256static uint8_t
257blue(uint32_t v)
258{
259 return v & 0xff;
260}
261
262static bool
263pixels_monotonic(const uint32_t *row, int x)
264{
265 bool ret = true;
266
267 if (red(row[x + 1]) > red(row[x])) {
268 testlog("pixel %d -> next: red value increases\n", x);
269 ret = false;
270 }
271
272 if (blue(row[x + 1]) < blue(row[x])) {
273 testlog("pixel %d -> next: blue value decreases\n", x);
274 ret = false;
275 }
276
277 return ret;
278}
279
Pekka Paalanen129bef52021-04-19 14:05:59 +0300280static void *
281get_middle_row(struct buffer *buf)
282{
283 const int y = (BLOCK_WIDTH - 1) / 2; /* middle row */
284 void *pixels;
285 int stride_bytes;
286
287 assert(pixman_image_get_width(buf->image) >= BLOCK_WIDTH * ALPHA_STEPS);
288 assert(pixman_image_get_height(buf->image) >= BLOCK_WIDTH);
289
290 pixels = pixman_image_get_data(buf->image);
291 stride_bytes = pixman_image_get_stride(buf->image);
292 return pixels + y * stride_bytes;
293}
294
Pekka Paalanen26258812020-11-12 14:15:43 +0200295static bool
Pekka Paalanen35cae562021-04-16 17:42:40 +0300296check_blend_pattern(struct buffer *bg, struct buffer *fg, struct buffer *shot,
297 enum blend_space space)
Pekka Paalanen26258812020-11-12 14:15:43 +0200298{
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300299 uint32_t *bg_row = get_middle_row(bg);
300 uint32_t *fg_row = get_middle_row(fg);
Pekka Paalanen129bef52021-04-19 14:05:59 +0300301 uint32_t *shot_row = get_middle_row(shot);
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300302 struct color_float max_diff = { 0.0f, 0.0f, 0.0f, 0.0f };
Pekka Paalanen26258812020-11-12 14:15:43 +0200303 bool ret = true;
Pekka Paalanen26258812020-11-12 14:15:43 +0200304 int x;
Pekka Paalanen26258812020-11-12 14:15:43 +0200305
Pekka Paalanen129bef52021-04-19 14:05:59 +0300306 for (x = 0; x < BLOCK_WIDTH * ALPHA_STEPS - 1; x++) {
307 if (!pixels_monotonic(shot_row, x))
Pekka Paalanen26258812020-11-12 14:15:43 +0200308 ret = false;
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300309
310 if (!verify_sRGB_blend_a8r8g8b8(bg_row[x], fg_row[x],
Pekka Paalanen35cae562021-04-16 17:42:40 +0300311 shot_row[x], x, &max_diff,
312 space))
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300313 ret = false;
Pekka Paalanen129bef52021-04-19 14:05:59 +0300314 }
Pekka Paalanen26258812020-11-12 14:15:43 +0200315
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300316 testlog("%s max diff: r=%f, g=%f, b=%f\n",
317 __func__, max_diff.r, max_diff.g, max_diff.b);
318
Pekka Paalanen26258812020-11-12 14:15:43 +0200319 return ret;
320}
321
322/*
323 * Test that alpha blending is roughly correct, and that an alpha ramp
Maxime Roussin-Bélanger4e8ea1f2020-12-17 17:07:49 -0500324 * results in a strictly monotonic color ramp. This should ensure that any
Pekka Paalanen26258812020-11-12 14:15:43 +0200325 * animation that varies alpha never goes "backwards" as that is easily
326 * noticeable.
327 *
328 * The background is a constant color. On top of that, there is an
329 * alpha-blended gradient with ramps in both alpha and color. Sub-surface
330 * ensures the correct positioning and stacking.
331 *
332 * The gradient consists of ALPHA_STEPS number of blocks. Block size is
333 * BLOCK_WIDTH x BLOCK_WIDTH and a block has a uniform color.
334 *
335 * In the blending result over x axis:
336 * - red goes from 1.0 to 0.0, monotonic
337 * - green is not monotonic
338 * - blue goes from 0.0 to 1.0, monotonic
Pekka Paalanen35cae562021-04-16 17:42:40 +0300339 *
340 * This test has two modes: BLEND_NONLINEAR and BLEND_LINEAR.
341 *
342 * BLEND_NONLINEAR does blending with pixel values as is, which are non-linear,
343 * and therefore result in "physically incorrect" blending result. Yet, people
344 * have accustomed to seeing this effect. This mode hits pipeline_premult()
345 * in fragment.glsl.
346 *
347 * BLEND_LINEAR has sRGB encoded pixels (non-linear). These are converted to
348 * linear light (optical) values, blended, and converted back to non-linear
349 * (electrical) values. This results in "physically more correct" blending
350 * result for some value of "physical". This mode hits pipeline_straight()
351 * in fragment.glsl, and tests even more things:
352 * - gl-renderer implementation of 1D LUT is correct
353 * - color-lcms instantiates the correct sRGB EOTF and inverse LUTs
354 * - color space conversions do not happen when both content and output are
355 * using their default color spaces
356 * - blending through gl-renderer shadow framebuffer
Pekka Paalanen26258812020-11-12 14:15:43 +0200357 */
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300358TEST(alpha_blend)
Pekka Paalanen26258812020-11-12 14:15:43 +0200359{
360 const int width = BLOCK_WIDTH * ALPHA_STEPS;
361 const int height = BLOCK_WIDTH;
362 const pixman_color_t background_color = {
363 .red = 0xffff,
364 .green = 0x8080,
365 .blue = 0x0000,
366 .alpha = 0xffff
367 };
Pekka Paalanen35cae562021-04-16 17:42:40 +0300368 const struct setup_args *args;
Pekka Paalanen26258812020-11-12 14:15:43 +0200369 struct client *client;
Pekka Paalanen129bef52021-04-19 14:05:59 +0300370 struct buffer *bg;
371 struct buffer *fg;
Pekka Paalanen26258812020-11-12 14:15:43 +0200372 struct wl_subcompositor *subco;
373 struct wl_surface *surf;
374 struct wl_subsurface *sub;
375 struct buffer *shot;
376 bool match;
Pekka Paalanen35cae562021-04-16 17:42:40 +0300377 int seq_no;
378 enum blend_space space;
379
380 args = &my_setup_args[get_test_fixture_index()];
381 if (args->color_management) {
382 seq_no = 1;
383 space = BLEND_LINEAR;
384 } else {
385 seq_no = 0;
386 space = BLEND_NONLINEAR;
387 }
Pekka Paalanen26258812020-11-12 14:15:43 +0200388
389 client = create_client();
390 subco = bind_to_singleton_global(client, &wl_subcompositor_interface, 1);
391
392 /* background window content */
Pekka Paalanen129bef52021-04-19 14:05:59 +0300393 bg = create_shm_buffer_a8r8g8b8(client, width, height);
394 fill_image_with_color(bg->image, &background_color);
Pekka Paalanen26258812020-11-12 14:15:43 +0200395
396 /* background window, main surface */
397 client->surface = create_test_surface(client);
398 client->surface->width = width;
399 client->surface->height = height;
Pekka Paalanen129bef52021-04-19 14:05:59 +0300400 client->surface->buffer = bg; /* pass ownership */
Pekka Paalanen26258812020-11-12 14:15:43 +0200401 set_opaque_rect(client, client->surface,
402 &(struct rectangle){ 0, 0, width, height });
403
404 /* foreground blended content */
Pekka Paalanen129bef52021-04-19 14:05:59 +0300405 fg = create_shm_buffer_a8r8g8b8(client, width, height);
406 fill_alpha_pattern(fg);
Pekka Paalanen26258812020-11-12 14:15:43 +0200407
408 /* foreground window, sub-surface */
409 surf = wl_compositor_create_surface(client->wl_compositor);
410 sub = wl_subcompositor_get_subsurface(subco, surf, client->surface->wl_surface);
411 /* sub-surface defaults to position 0, 0, top-most, synchronized */
Pekka Paalanen129bef52021-04-19 14:05:59 +0300412 wl_surface_attach(surf, fg->proxy, 0, 0);
Pekka Paalanen26258812020-11-12 14:15:43 +0200413 wl_surface_damage(surf, 0, 0, width, height);
414 wl_surface_commit(surf);
415
416 /* attach, damage, commit background window */
417 move_client(client, 0, 0);
418
419 shot = capture_screenshot_of_output(client);
420 assert(shot);
Pekka Paalanen35cae562021-04-16 17:42:40 +0300421 match = verify_image(shot, "alpha_blend", seq_no, NULL, seq_no);
422 assert(check_blend_pattern(bg, fg, shot, space));
Pekka Paalanen26258812020-11-12 14:15:43 +0200423 assert(match);
424
Pekka Paalanen26258812020-11-12 14:15:43 +0200425 buffer_destroy(shot);
426
427 wl_subsurface_destroy(sub);
428 wl_surface_destroy(surf);
Pekka Paalanen129bef52021-04-19 14:05:59 +0300429 buffer_destroy(fg);
Pekka Paalanen681db342021-05-14 17:06:27 +0300430 wl_subcompositor_destroy(subco);
Pekka Paalanen129bef52021-04-19 14:05:59 +0300431 client_destroy(client); /* destroys bg */
Pekka Paalanen26258812020-11-12 14:15:43 +0200432}