From 541e046d72ea60671e91a2c2eea30624eaffc0b2 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sun, 29 Mar 2026 11:48:28 +0200 Subject: [PATCH] tests: Initial wlr_surface unit test A small unit test of wlr_surface, currently testing surface and buffer damage with scaling, transform and viewport, including interaction with surface state locking. --- test/meson.build | 13 ++ test/test_surface.c | 293 +++++++++++++++++++++++++++++++++++++++++ types/wlr_compositor.c | 29 ++-- 3 files changed, 320 insertions(+), 15 deletions(-) create mode 100644 test/test_surface.c diff --git a/test/meson.build b/test/meson.build index f51b2c02c..13854b426 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,8 +1,21 @@ +wayland_client = dependency('wayland-client', kwargs: wayland_kwargs) + test( 'box', executable('test-box', 'test_box.c', dependencies: wlroots), ) +test( + 'surface', + executable('test-surface', + [ + 'test_surface.c', + '../util/shm.c', + protocols_code['viewporter'], + ], + dependencies: [wlroots, wayland_client]), +) + benchmark( 'scene', executable('bench-scene', 'bench_scene.c', dependencies: wlroots), diff --git a/test/test_surface.c b/test/test_surface.c new file mode 100644 index 000000000..bd558b429 --- /dev/null +++ b/test/test_surface.c @@ -0,0 +1,293 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "util/shm.h" +#include "viewporter-client-protocol.h" + +#define BUF_WIDTH 24 +#define BUF_HEIGHT 12 +#define BUF_STRIDE (BUF_WIDTH * 4) +#define BUF_SIZE (BUF_STRIDE * BUF_HEIGHT) + +struct test_context { + struct wl_display *server_display; + struct wlr_compositor *compositor; + struct wlr_shm *shm; + struct wlr_viewporter *viewporter; + struct wlr_surface *wlr_surface; + struct wl_listener new_surface; + + struct wl_display *client_display; + struct wl_compositor *client_compositor; + struct wl_shm *client_shm; + struct wp_viewporter *client_viewporter; + struct wl_surface *client_surface; + struct wl_buffer *client_buffer; +}; + +static void handle_new_surface(struct wl_listener *listener, void *data) { + struct test_context *ctx = + wl_container_of(listener, ctx, new_surface); + ctx->wlr_surface = data; +} + +static void registry_handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + struct test_context *ctx = data; + if (strcmp(interface, wl_compositor_interface.name) == 0) { + ctx->client_compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, version); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + ctx->client_shm = wl_registry_bind(registry, name, + &wl_shm_interface, version); + } else if (strcmp(interface, wp_viewporter_interface.name) == 0) { + ctx->client_viewporter = wl_registry_bind(registry, name, + &wp_viewporter_interface, version); + } +} + +static void registry_handle_global_remove(void *data, + struct wl_registry *registry, uint32_t name) { +} + +static const struct wl_registry_listener registry_listener = { + .global = registry_handle_global, + .global_remove = registry_handle_global_remove, +}; + +static void client_roundtrip(struct test_context *ctx) { + assert(wl_display_flush(ctx->client_display) >= 0); + assert(wl_event_loop_dispatch( + wl_display_get_event_loop(ctx->server_display), -1) == 0); +} + +static void full_roundtrip(struct test_context *ctx) { + assert(wl_display_flush(ctx->client_display) >= 0); + assert(wl_event_loop_dispatch( + wl_display_get_event_loop(ctx->server_display), -1) == 0); + wl_display_flush_clients(ctx->server_display); + assert(wl_display_dispatch(ctx->client_display) != -1); +} + +static bool region_is_empty(pixman_region32_t *region) { + return !pixman_region32_not_empty(region); +} + +static bool region_is_rect(pixman_region32_t *region, + int x, int y, int w, int h) { + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(region, &nrects); + return nrects == 1 && + rects[0].x1 == x && rects[0].y1 == y && + rects[0].x2 == x + w && rects[0].y2 == y + h; +} + +static void setup(struct test_context *ctx) { + memset(ctx, 0, sizeof(*ctx)); + + ctx->server_display = wl_display_create(); + assert(ctx->server_display); + ctx->compositor = wlr_compositor_create(ctx->server_display, 6, NULL); + assert(ctx->compositor); + uint32_t fmts[] = { DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888 }; + ctx->shm = wlr_shm_create(ctx->server_display, 1, fmts, 2); + assert(ctx->shm); + ctx->viewporter = wlr_viewporter_create(ctx->server_display); + assert(ctx->viewporter); + + ctx->new_surface.notify = handle_new_surface; + wl_signal_add(&ctx->compositor->events.new_surface, &ctx->new_surface); + + int sv[2]; + assert(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == 0); + assert(fcntl(sv[0], F_SETFD, FD_CLOEXEC) == 0); + assert(fcntl(sv[1], F_SETFD, FD_CLOEXEC) == 0); + assert(wl_client_create(ctx->server_display, sv[0])); + + ctx->client_display = wl_display_connect_to_fd(sv[1]); + assert(ctx->client_display); + + struct wl_registry *registry = + wl_display_get_registry(ctx->client_display); + wl_registry_add_listener(registry, ®istry_listener, ctx); + full_roundtrip(ctx); + full_roundtrip(ctx); + assert(ctx->client_compositor); + assert(ctx->client_shm); + assert(ctx->client_viewporter); + + ctx->client_surface = + wl_compositor_create_surface(ctx->client_compositor); + assert(ctx->client_surface); + client_roundtrip(ctx); + assert(ctx->wlr_surface); + + int fd = allocate_shm_file(BUF_SIZE); + assert(fd >= 0); + struct wl_shm_pool *pool = + wl_shm_create_pool(ctx->client_shm, fd, BUF_SIZE); + assert(pool); + ctx->client_buffer = wl_shm_pool_create_buffer(pool, 0, + BUF_WIDTH, BUF_HEIGHT, BUF_STRIDE, WL_SHM_FORMAT_ARGB8888); + assert(ctx->client_buffer); + wl_shm_pool_destroy(pool); + close(fd); + + wl_registry_destroy(registry); +} + +static void teardown(struct test_context *ctx) { + wl_list_remove(&ctx->new_surface.link); + wl_buffer_destroy(ctx->client_buffer); + wl_surface_destroy(ctx->client_surface); + wp_viewporter_destroy(ctx->client_viewporter); + wl_compositor_destroy(ctx->client_compositor); + wl_shm_destroy(ctx->client_shm); + wl_display_disconnect(ctx->client_display); + wl_display_destroy(ctx->server_display); +} + +static void test_surface_damage(void) { + struct test_context ctx; + setup(&ctx); + + wl_surface_attach(ctx.client_surface, ctx.client_buffer, 0, 0); + wl_surface_set_buffer_scale(ctx.client_surface, 2); + wl_surface_damage(ctx.client_surface, 0, 0, 6, 3); + wl_surface_commit(ctx.client_surface); + client_roundtrip(&ctx); + + assert(region_is_rect(&ctx.wlr_surface->buffer_damage, 0, 0, 12, 6)); + + teardown(&ctx); +} + +static void test_buffer_damage(void) { + struct test_context ctx; + setup(&ctx); + + wl_surface_attach(ctx.client_surface, ctx.client_buffer, 0, 0); + wl_surface_set_buffer_scale(ctx.client_surface, 2); + wl_surface_damage_buffer(ctx.client_surface, 0, 0, 10, 5); + wl_surface_commit(ctx.client_surface); + client_roundtrip(&ctx); + + assert(region_is_rect(&ctx.wlr_surface->buffer_damage, 0, 0, 10, 5)); + + teardown(&ctx); +} + +static void test_surface_damage_transform(void) { + struct test_context ctx; + setup(&ctx); + + wl_surface_attach(ctx.client_surface, ctx.client_buffer, 0, 0); + wl_surface_set_buffer_transform(ctx.client_surface, + WL_OUTPUT_TRANSFORM_90); + wl_surface_damage(ctx.client_surface, 0, 0, 6, 12); + wl_surface_commit(ctx.client_surface); + client_roundtrip(&ctx); + + assert(region_is_rect(&ctx.wlr_surface->buffer_damage, 0, 6, 12, 6)); + + teardown(&ctx); +} + +static void test_surface_damage_viewport(void) { + struct test_context ctx; + setup(&ctx); + + struct wp_viewport *viewport = wp_viewporter_get_viewport( + ctx.client_viewporter, ctx.client_surface); + + wl_surface_attach(ctx.client_surface, ctx.client_buffer, 0, 0); + wp_viewport_set_destination(viewport, 6, 3); + wl_surface_damage(ctx.client_surface, 0, 0, 3, 3); + wl_surface_commit(ctx.client_surface); + client_roundtrip(&ctx); + + assert(region_is_rect(&ctx.wlr_surface->buffer_damage, 0, 0, 12, 12)); + + wp_viewport_destroy(viewport); + teardown(&ctx); +} + +static void test_surface_damage_all(void) { + struct test_context ctx; + setup(&ctx); + + struct wp_viewport *viewport = wp_viewporter_get_viewport( + ctx.client_viewporter, ctx.client_surface); + + wl_surface_attach(ctx.client_surface, ctx.client_buffer, 0, 0); + wl_surface_set_buffer_scale(ctx.client_surface, 2); + wl_surface_set_buffer_transform(ctx.client_surface, + WL_OUTPUT_TRANSFORM_90); + wp_viewport_set_destination(viewport, 3, 6); + wl_surface_damage(ctx.client_surface, 0, 0, 1, 2); + wl_surface_commit(ctx.client_surface); + client_roundtrip(&ctx); + + assert(region_is_rect(&ctx.wlr_surface->buffer_damage, 0, 8, 8, 4)); + + wp_viewport_destroy(viewport); + teardown(&ctx); +} + +static void test_cached_surface_damage(void) { + struct test_context ctx; + setup(&ctx); + + struct wp_viewport *viewport = wp_viewporter_get_viewport( + ctx.client_viewporter, ctx.client_surface); + + wl_surface_attach(ctx.client_surface, ctx.client_buffer, 0, 0); + wl_surface_set_buffer_scale(ctx.client_surface, 2); + wl_surface_set_buffer_transform(ctx.client_surface, + WL_OUTPUT_TRANSFORM_90); + wp_viewport_set_destination(viewport, 3, 6); + wl_surface_commit(ctx.client_surface); + client_roundtrip(&ctx); + + assert(ctx.wlr_surface->current.scale == 2); + assert(region_is_empty(&ctx.wlr_surface->buffer_damage)); + + uint32_t seq = wlr_surface_lock_pending(ctx.wlr_surface); + + wl_surface_damage(ctx.client_surface, 0, 0, 1, 2); + wl_surface_commit(ctx.client_surface); + client_roundtrip(&ctx); + + assert(region_is_empty(&ctx.wlr_surface->buffer_damage)); + + wlr_surface_unlock_cached(ctx.wlr_surface, seq); + assert(region_is_rect(&ctx.wlr_surface->buffer_damage, 0, 8, 8, 4)); + + wp_viewport_destroy(viewport); + teardown(&ctx); +} + +int main(void) { +#ifdef NDEBUG + fprintf(stderr, "NDEBUG must be disabled for tests\n"); + return 1; +#endif + + test_surface_damage(); + test_buffer_damage(); + test_surface_damage_transform(); + test_surface_damage_viewport(); + test_surface_damage_all(); + test_cached_surface_damage(); + return 0; +} diff --git a/types/wlr_compositor.c b/types/wlr_compositor.c index 6b31ab857..75faed190 100644 --- a/types/wlr_compositor.c +++ b/types/wlr_compositor.c @@ -246,40 +246,40 @@ static void surface_finalize_pending(struct wlr_surface *surface) { } static void surface_update_damage(pixman_region32_t *buffer_damage, - struct wlr_surface_state *current, struct wlr_surface_state *pending) { + struct wlr_surface_state *state) { pixman_region32_clear(buffer_damage); // Copy over surface damage + buffer damage pixman_region32_t surface_damage; pixman_region32_init(&surface_damage); - pixman_region32_copy(&surface_damage, &pending->surface_damage); + pixman_region32_copy(&surface_damage, &state->surface_damage); - if (pending->viewport.has_dst) { + if (state->viewport.has_dst) { int src_width, src_height; - surface_state_viewport_src_size(pending, &src_width, &src_height); - float scale_x = (float)pending->viewport.dst_width / src_width; - float scale_y = (float)pending->viewport.dst_height / src_height; + surface_state_viewport_src_size(state, &src_width, &src_height); + float scale_x = (float)state->viewport.dst_width / src_width; + float scale_y = (float)state->viewport.dst_height / src_height; wlr_region_scale_xy(&surface_damage, &surface_damage, 1.0 / scale_x, 1.0 / scale_y); } - if (pending->viewport.has_src) { + if (state->viewport.has_src) { // This is lossy: do a best-effort conversion pixman_region32_translate(&surface_damage, - floor(pending->viewport.src.x), - floor(pending->viewport.src.y)); + floor(state->viewport.src.x), + floor(state->viewport.src.y)); } - wlr_region_scale(&surface_damage, &surface_damage, pending->scale); + wlr_region_scale(&surface_damage, &surface_damage, state->scale); int width, height; - surface_state_transformed_buffer_size(pending, &width, &height); + surface_state_transformed_buffer_size(state, &width, &height); wlr_region_transform(&surface_damage, &surface_damage, - wlr_output_transform_invert(pending->transform), + wlr_output_transform_invert(state->transform), width, height); pixman_region32_union(buffer_damage, - &pending->buffer_damage, &surface_damage); + &state->buffer_damage, &surface_damage); pixman_region32_fini(&surface_damage); } @@ -521,8 +521,6 @@ static void surface_commit_state(struct wlr_surface *surface, surface->unmap_commit = false; } - surface_update_damage(&surface->buffer_damage, &surface->current, next); - surface->previous.scale = surface->current.scale; surface->previous.transform = surface->current.transform; surface->previous.width = surface->current.width; @@ -531,6 +529,7 @@ static void surface_commit_state(struct wlr_surface *surface, surface->previous.buffer_height = surface->current.buffer_height; surface_state_move(&surface->current, next, surface); + surface_update_damage(&surface->buffer_damage, &surface->current); if (invalid_buffer) { surface_apply_damage(surface);