blob: aaf77e547eb411032baf97ba7f459acb89052c8e [file] [log] [blame]
Pekka Paalanen26258812020-11-12 14:15:43 +02001/*
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 Paalanene70aa1f2021-04-16 17:42:40 +030028#include <math.h>
Pekka Paalanen26258812020-11-12 14:15:43 +020029
30#include "weston-test-client-helper.h"
31#include "weston-test-fixture-compositor.h"
32
33struct setup_args {
Pekka Paalanenef813882021-02-15 13:46:42 +020034 struct fixture_metadata meta;
Pekka Paalanen26258812020-11-12 14:15:43 +020035 enum renderer_type renderer;
36};
37
38static const int ALPHA_STEPS = 256;
39static const int BLOCK_WIDTH = 3;
40
41static const struct setup_args my_setup_args[] = {
Pekka Paalanenef813882021-02-15 13:46:42 +020042 {
43 .renderer = RENDERER_PIXMAN,
44 .meta.name = "pixman"
45 },
46 {
47 .renderer = RENDERER_GL,
48 .meta.name = "GL"
49 },
Pekka Paalanen26258812020-11-12 14:15:43 +020050};
51
52static enum test_result_code
53fixture_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 Paalanenef813882021-02-15 13:46:42 +020065DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args, meta);
Pekka Paalanen26258812020-11-12 14:15:43 +020066
67static void
68set_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
80static uint32_t
81premult_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
93static void
94fill_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 Paalanene70aa1f2021-04-16 17:42:40 +0300126struct color_float {
127 float r, g, b, a;
128};
129
130static struct color_float
131a8r8g8b8_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
143static void
144unpremult_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
157static 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
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
200static bool
201verify_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 Paalanen26258812020-11-12 14:15:43 +0200225static uint8_t
226red(uint32_t v)
227{
228 return (v >> 16) & 0xff;
229}
230
231static uint8_t
232blue(uint32_t v)
233{
234 return v & 0xff;
235}
236
237static bool
238pixels_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 Paalanen129bef52021-04-19 14:05:59 +0300255static void *
256get_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 Paalanen26258812020-11-12 14:15:43 +0200270static bool
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300271check_blend_pattern(struct buffer *bg, struct buffer *fg, struct buffer *shot)
Pekka Paalanen26258812020-11-12 14:15:43 +0200272{
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300273 uint32_t *bg_row = get_middle_row(bg);
274 uint32_t *fg_row = get_middle_row(fg);
Pekka Paalanen129bef52021-04-19 14:05:59 +0300275 uint32_t *shot_row = get_middle_row(shot);
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300276 struct color_float max_diff = { 0.0f, 0.0f, 0.0f, 0.0f };
Pekka Paalanen26258812020-11-12 14:15:43 +0200277 bool ret = true;
Pekka Paalanen26258812020-11-12 14:15:43 +0200278 int x;
Pekka Paalanen26258812020-11-12 14:15:43 +0200279
Pekka Paalanen129bef52021-04-19 14:05:59 +0300280 for (x = 0; x < BLOCK_WIDTH * ALPHA_STEPS - 1; x++) {
281 if (!pixels_monotonic(shot_row, x))
Pekka Paalanen26258812020-11-12 14:15:43 +0200282 ret = false;
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300283
284 if (!verify_sRGB_blend_a8r8g8b8(bg_row[x], fg_row[x],
285 shot_row[x], x, &max_diff))
286 ret = false;
Pekka Paalanen129bef52021-04-19 14:05:59 +0300287 }
Pekka Paalanen26258812020-11-12 14:15:43 +0200288
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300289 testlog("%s max diff: r=%f, g=%f, b=%f\n",
290 __func__, max_diff.r, max_diff.g, max_diff.b);
291
Pekka Paalanen26258812020-11-12 14:15:43 +0200292 return ret;
293}
294
295/*
296 * Test that alpha blending is roughly correct, and that an alpha ramp
Maxime Roussin-Bélanger4e8ea1f2020-12-17 17:07:49 -0500297 * results in a strictly monotonic color ramp. This should ensure that any
Pekka Paalanen26258812020-11-12 14:15:43 +0200298 * 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 Paalanene70aa1f2021-04-16 17:42:40 +0300313TEST(alpha_blend)
Pekka Paalanen26258812020-11-12 14:15:43 +0200314{
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 Paalanen129bef52021-04-19 14:05:59 +0300324 struct buffer *bg;
325 struct buffer *fg;
Pekka Paalanen26258812020-11-12 14:15:43 +0200326 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 Paalanen129bef52021-04-19 14:05:59 +0300336 bg = create_shm_buffer_a8r8g8b8(client, width, height);
337 fill_image_with_color(bg->image, &background_color);
Pekka Paalanen26258812020-11-12 14:15:43 +0200338
339 /* background window, main surface */
340 client->surface = create_test_surface(client);
341 client->surface->width = width;
342 client->surface->height = height;
Pekka Paalanen129bef52021-04-19 14:05:59 +0300343 client->surface->buffer = bg; /* pass ownership */
Pekka Paalanen26258812020-11-12 14:15:43 +0200344 set_opaque_rect(client, client->surface,
345 &(struct rectangle){ 0, 0, width, height });
346
347 /* foreground blended content */
Pekka Paalanen129bef52021-04-19 14:05:59 +0300348 fg = create_shm_buffer_a8r8g8b8(client, width, height);
349 fill_alpha_pattern(fg);
Pekka Paalanen26258812020-11-12 14:15:43 +0200350
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 Paalanen129bef52021-04-19 14:05:59 +0300355 wl_surface_attach(surf, fg->proxy, 0, 0);
Pekka Paalanen26258812020-11-12 14:15:43 +0200356 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 Paalanene70aa1f2021-04-16 17:42:40 +0300364 match = verify_image(shot, "alpha_blend", 0, NULL, 0);
Pekka Paalanen26258812020-11-12 14:15:43 +0200365 assert(match);
366
Pekka Paalanene70aa1f2021-04-16 17:42:40 +0300367 assert(check_blend_pattern(bg, fg, shot));
Pekka Paalanen26258812020-11-12 14:15:43 +0200368 buffer_destroy(shot);
369
370 wl_subsurface_destroy(sub);
371 wl_surface_destroy(surf);
Pekka Paalanen129bef52021-04-19 14:05:59 +0300372 buffer_destroy(fg);
Pekka Paalanen681db342021-05-14 17:06:27 +0300373 wl_subcompositor_destroy(subco);
Pekka Paalanen129bef52021-04-19 14:05:59 +0300374 client_destroy(client); /* destroys bg */
Pekka Paalanen26258812020-11-12 14:15:43 +0200375}