In anticipation of invasive future work on color management, add an alpha blending test to make sure we don't break alpha blending. The idea for doing a monotonicity test came from glennk on #dri-devel in Freenode IRC. Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.com>dev
							parent
							
								
									f8d170c4e7
								
							
						
					
					
						commit
						26258817fc
					
				@ -0,0 +1,253 @@ | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2020 Collabora, Ltd. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * Permission is hereby granted, free of charge, to any person obtaining | 
				
			||||||
 | 
					 * a copy of this software and associated documentation files (the | 
				
			||||||
 | 
					 * "Software"), to deal in the Software without restriction, including | 
				
			||||||
 | 
					 * without limitation the rights to use, copy, modify, merge, publish, | 
				
			||||||
 | 
					 * distribute, sublicense, and/or sell copies of the Software, and to | 
				
			||||||
 | 
					 * permit persons to whom the Software is furnished to do so, subject to | 
				
			||||||
 | 
					 * the following conditions: | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * The above copyright notice and this permission notice (including the | 
				
			||||||
 | 
					 * next paragraph) shall be included in all copies or substantial | 
				
			||||||
 | 
					 * portions of the Software. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | 
				
			||||||
 | 
					 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | 
				
			||||||
 | 
					 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | 
				
			||||||
 | 
					 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | 
				
			||||||
 | 
					 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | 
				
			||||||
 | 
					 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | 
				
			||||||
 | 
					 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | 
				
			||||||
 | 
					 * SOFTWARE. | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "config.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdio.h> | 
				
			||||||
 | 
					#include <string.h> | 
				
			||||||
 | 
					#include <sys/mman.h> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "weston-test-client-helper.h" | 
				
			||||||
 | 
					#include "weston-test-fixture-compositor.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct setup_args { | 
				
			||||||
 | 
						enum renderer_type renderer; | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const int ALPHA_STEPS = 256; | 
				
			||||||
 | 
					static const int BLOCK_WIDTH = 3; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct setup_args my_setup_args[] = { | 
				
			||||||
 | 
						{ RENDERER_PIXMAN }, | 
				
			||||||
 | 
						{ RENDERER_GL }, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static enum test_result_code | 
				
			||||||
 | 
					fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						struct compositor_setup setup; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						compositor_setup_defaults(&setup); | 
				
			||||||
 | 
						setup.renderer = arg->renderer; | 
				
			||||||
 | 
						setup.width = BLOCK_WIDTH * ALPHA_STEPS; | 
				
			||||||
 | 
						setup.height = 16; | 
				
			||||||
 | 
						setup.shell = SHELL_TEST_DESKTOP; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return weston_test_harness_execute_as_client(harness, &setup); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void | 
				
			||||||
 | 
					set_opaque_rect(struct client *client, | 
				
			||||||
 | 
							struct surface *surface, | 
				
			||||||
 | 
							const struct rectangle *rect) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						struct wl_region *region; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						region = wl_compositor_create_region(client->wl_compositor); | 
				
			||||||
 | 
						wl_region_add(region, rect->x, rect->y, rect->width, rect->height); | 
				
			||||||
 | 
						wl_surface_set_opaque_region(surface->wl_surface, region); | 
				
			||||||
 | 
						wl_region_destroy(region); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static uint32_t | 
				
			||||||
 | 
					premult_color(uint32_t a, uint32_t r, uint32_t g, uint32_t b) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						uint32_t c = 0; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c |= a << 24; | 
				
			||||||
 | 
						c |= (a * r / 255) << 16; | 
				
			||||||
 | 
						c |= (a * g / 255) << 8; | 
				
			||||||
 | 
						c |= a * b / 255; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void | 
				
			||||||
 | 
					fill_alpha_pattern(struct buffer *buf) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						void *pixels; | 
				
			||||||
 | 
						int stride_bytes; | 
				
			||||||
 | 
						int w, h; | 
				
			||||||
 | 
						int y; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert(pixman_image_get_format(buf->image) == PIXMAN_a8r8g8b8); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pixels = pixman_image_get_data(buf->image); | 
				
			||||||
 | 
						stride_bytes = pixman_image_get_stride(buf->image); | 
				
			||||||
 | 
						w = pixman_image_get_width(buf->image); | 
				
			||||||
 | 
						h = pixman_image_get_height(buf->image); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert(w == BLOCK_WIDTH * ALPHA_STEPS); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (y = 0; y < h; y++) { | 
				
			||||||
 | 
							uint32_t *row = pixels + y * stride_bytes; | 
				
			||||||
 | 
							uint32_t step; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (step = 0; step < (uint32_t)ALPHA_STEPS; step++) { | 
				
			||||||
 | 
								uint32_t alpha = step * 255 / (ALPHA_STEPS - 1); | 
				
			||||||
 | 
								uint32_t color; | 
				
			||||||
 | 
								int i; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								color = premult_color(alpha, 0, 255 - alpha, 255); | 
				
			||||||
 | 
								for (i = 0; i < BLOCK_WIDTH; i++) | 
				
			||||||
 | 
									*row++ = color; | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static uint8_t | 
				
			||||||
 | 
					red(uint32_t v) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						return (v >> 16) & 0xff; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static uint8_t | 
				
			||||||
 | 
					blue(uint32_t v) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						return v & 0xff; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool | 
				
			||||||
 | 
					pixels_monotonic(const uint32_t *row, int x) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						bool ret = true; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (red(row[x + 1]) > red(row[x])) { | 
				
			||||||
 | 
							testlog("pixel %d -> next: red value increases\n", x); | 
				
			||||||
 | 
							ret = false; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (blue(row[x + 1]) < blue(row[x])) { | 
				
			||||||
 | 
							testlog("pixel %d -> next: blue value decreases\n", x); | 
				
			||||||
 | 
							ret = false; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool | 
				
			||||||
 | 
					check_blend_pattern(struct buffer *shot) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						bool ret = true; | 
				
			||||||
 | 
						const int y = (BLOCK_WIDTH - 1) / 2; /* middle row */ | 
				
			||||||
 | 
						int x; | 
				
			||||||
 | 
						void *pixels; | 
				
			||||||
 | 
						int stride_bytes; | 
				
			||||||
 | 
						uint32_t *row; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert(pixman_image_get_width(shot->image) >= BLOCK_WIDTH * ALPHA_STEPS); | 
				
			||||||
 | 
						assert(pixman_image_get_height(shot->image) >= BLOCK_WIDTH); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pixels = pixman_image_get_data(shot->image); | 
				
			||||||
 | 
						stride_bytes = pixman_image_get_stride(shot->image); | 
				
			||||||
 | 
						row = pixels + y * stride_bytes; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (x = 0; x < BLOCK_WIDTH * ALPHA_STEPS - 1; x++) | 
				
			||||||
 | 
							if (!pixels_monotonic(row, x)) | 
				
			||||||
 | 
								ret = false; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Test that alpha blending is roughly correct, and that an alpha ramp | 
				
			||||||
 | 
					 * results in a stricly monotonic color ramp. This should ensure that any | 
				
			||||||
 | 
					 * animation that varies alpha never goes "backwards" as that is easily | 
				
			||||||
 | 
					 * noticeable. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * The background is a constant color. On top of that, there is an | 
				
			||||||
 | 
					 * alpha-blended gradient with ramps in both alpha and color. Sub-surface | 
				
			||||||
 | 
					 * ensures the correct positioning and stacking. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * The gradient consists of ALPHA_STEPS number of blocks. Block size is | 
				
			||||||
 | 
					 * BLOCK_WIDTH x BLOCK_WIDTH and a block has a uniform color. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * In the blending result over x axis: | 
				
			||||||
 | 
					 * - red goes from 1.0 to 0.0, monotonic | 
				
			||||||
 | 
					 * - green is not monotonic | 
				
			||||||
 | 
					 * - blue goes from 0.0 to 1.0, monotonic | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					TEST(alpha_blend_monotonic) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
						const int width = BLOCK_WIDTH * ALPHA_STEPS; | 
				
			||||||
 | 
						const int height = BLOCK_WIDTH; | 
				
			||||||
 | 
						const pixman_color_t background_color = { | 
				
			||||||
 | 
							.red   = 0xffff, | 
				
			||||||
 | 
							.green = 0x8080, | 
				
			||||||
 | 
							.blue  = 0x0000, | 
				
			||||||
 | 
							.alpha = 0xffff | 
				
			||||||
 | 
						}; | 
				
			||||||
 | 
						struct client *client; | 
				
			||||||
 | 
						struct buffer *buf; | 
				
			||||||
 | 
						struct wl_subcompositor *subco; | 
				
			||||||
 | 
						struct wl_surface *surf; | 
				
			||||||
 | 
						struct wl_subsurface *sub; | 
				
			||||||
 | 
						struct buffer *shot; | 
				
			||||||
 | 
						bool match; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client = create_client(); | 
				
			||||||
 | 
						subco = bind_to_singleton_global(client, &wl_subcompositor_interface, 1); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* background window content */ | 
				
			||||||
 | 
						buf = create_shm_buffer_a8r8g8b8(client, width, height); | 
				
			||||||
 | 
						fill_image_with_color(buf->image, &background_color); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* background window, main surface */ | 
				
			||||||
 | 
						client->surface = create_test_surface(client); | 
				
			||||||
 | 
						client->surface->width = width; | 
				
			||||||
 | 
						client->surface->height = height; | 
				
			||||||
 | 
						client->surface->buffer = buf; /* pass ownership */ | 
				
			||||||
 | 
						set_opaque_rect(client, client->surface, | 
				
			||||||
 | 
								&(struct rectangle){ 0, 0, width, height }); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* foreground blended content */ | 
				
			||||||
 | 
						buf = create_shm_buffer_a8r8g8b8(client, width, height); | 
				
			||||||
 | 
						fill_alpha_pattern(buf); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* foreground window, sub-surface */ | 
				
			||||||
 | 
						surf = wl_compositor_create_surface(client->wl_compositor); | 
				
			||||||
 | 
						sub = wl_subcompositor_get_subsurface(subco, surf, client->surface->wl_surface); | 
				
			||||||
 | 
						/* sub-surface defaults to position 0, 0, top-most, synchronized */ | 
				
			||||||
 | 
						wl_surface_attach(surf, buf->proxy, 0, 0); | 
				
			||||||
 | 
						wl_surface_damage(surf, 0, 0, width, height); | 
				
			||||||
 | 
						wl_surface_commit(surf); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* attach, damage, commit background window */ | 
				
			||||||
 | 
						move_client(client, 0, 0); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						shot = capture_screenshot_of_output(client); | 
				
			||||||
 | 
						assert(shot); | 
				
			||||||
 | 
						match = verify_image(shot, "alpha_blend_monotonic", 0, NULL, 0); | 
				
			||||||
 | 
						assert(match); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert(check_blend_pattern(shot)); | 
				
			||||||
 | 
						buffer_destroy(shot); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wl_subsurface_destroy(sub); | 
				
			||||||
 | 
						wl_surface_destroy(surf); | 
				
			||||||
 | 
						buffer_destroy(buf); | 
				
			||||||
 | 
						client_destroy(client); | 
				
			||||||
 | 
					} | 
				
			||||||
| 
		 After Width: | Height: | Size: 299 B  | 
					Loading…
					
					
				
		Reference in new issue