diff --git a/include/wlr/types/wlr_compositor.h b/include/wlr/types/wlr_compositor.h index eb870b7b..94262b4b 100644 --- a/include/wlr/types/wlr_compositor.h +++ b/include/wlr/types/wlr_compositor.h @@ -69,6 +69,9 @@ struct wlr_surface_state { // Number of locks that prevent this surface state from being committed. size_t cached_state_locks; struct wl_list cached_state_link; // wlr_surface.cached + + // Sync'ed object states, one per struct wlr_surface_synced + struct wl_array synced; // void * }; struct wlr_surface_role { @@ -222,6 +225,9 @@ struct wlr_surface { int32_t preferred_buffer_scale; bool preferred_buffer_transform_sent; enum wl_output_transform preferred_buffer_transform; + + struct wl_list synced; // wlr_surface_synced.link + size_t synced_len; }; struct wlr_renderer; @@ -430,6 +436,63 @@ void wlr_surface_set_preferred_buffer_scale(struct wlr_surface *surface, void wlr_surface_set_preferred_buffer_transform(struct wlr_surface *surface, enum wl_output_transform transform); +/** + * Implementation for struct wlr_surface_synced. + * + * struct wlr_surface takes care of allocating the sync'ed object state. + * + * The only mandatory field is state_size. + */ +struct wlr_surface_synced_impl { + // Size in bytes of the state struct. + size_t state_size; + // Initialize a state. If NULL, this is a no-op. + void (*init_state)(void *state); + // Finish a state. If NULL, this is a no-op. + void (*finish_state)(void *state); + // Move a state. If NULL, memcpy() is used. + void (*move_state)(void *dst, void *src); +}; + +/** + * An object synchronized with a surface. + * + * This is typically used by surface add-ons which integrate with the surface + * commit mechanism. + * + * A sync'ed object maintains state whose lifecycle is managed by + * struct wlr_surface_synced_impl. Clients make requests to mutate the pending + * state, then clients commit the pending state via wl_surface.commit. The + * pending state may become cached, then becomes current when it's applied. + */ +struct wlr_surface_synced { + struct wlr_surface *surface; + const struct wlr_surface_synced_impl *impl; + struct wl_list link; // wlr_surface.synced + size_t index; +}; + +/** + * Initialize a sync'ed object. + * + * pending and current must be pointers to the sync'ed object's state. This + * function will initialize them. + */ +bool wlr_surface_synced_init(struct wlr_surface_synced *synced, + struct wlr_surface *surface, const struct wlr_surface_synced_impl *impl, + void *pending, void *current); +/** + * Finish a sync'ed object. + * + * This must be called before the struct wlr_surface is destroyed. + */ +void wlr_surface_synced_finish(struct wlr_surface_synced *synced); +/** + * Obtain a sync'ed object state. + */ +void *wlr_surface_synced_get_state(struct wlr_surface_synced *synced, + const struct wlr_surface_state *state); + /** * Get a Pixman region from a wl_region resource. */ diff --git a/types/wlr_compositor.c b/types/wlr_compositor.c index 91996037..d9c7b3c1 100644 --- a/types/wlr_compositor.c +++ b/types/wlr_compositor.c @@ -12,6 +12,7 @@ #include "types/wlr_buffer.h" #include "types/wlr_region.h" #include "types/wlr_subcompositor.h" +#include "util/array.h" #include "util/time.h" #define COMPOSITOR_VERSION 6 @@ -272,11 +273,42 @@ static void surface_update_damage(pixman_region32_t *buffer_damage, } } +static void *surface_synced_create_state(struct wlr_surface_synced *synced) { + void *state = calloc(1, synced->impl->state_size); + if (state == NULL) { + return NULL; + } + if (synced->impl->init_state) { + synced->impl->init_state(state); + } + return state; +} + +static void surface_synced_destroy_state(struct wlr_surface_synced *synced, + void *state) { + if (state == NULL) { + return; + } + if (synced->impl->finish_state) { + synced->impl->finish_state(state); + } + free(state); +} + +static void surface_synced_move_state(struct wlr_surface_synced *synced, + void *dst, void *src) { + if (synced->impl->move_state) { + synced->impl->move_state(dst, src); + } else { + memcpy(dst, src, synced->impl->state_size); + } +} + /** * Overwrite state with a copy of the next state, then clear the next state. */ static void surface_state_move(struct wlr_surface_state *state, - struct wlr_surface_state *next) { + struct wlr_surface_state *next, struct wlr_surface *surface) { state->width = next->width; state->height = next->height; state->buffer_width = next->buffer_width; @@ -331,6 +363,14 @@ static void surface_state_move(struct wlr_surface_state *state, wl_list_init(&next->frame_callback_list); } + void **state_synced = state->synced.data; + void **next_synced = next->synced.data; + struct wlr_surface_synced *synced; + wl_list_for_each(synced, &surface->synced, link) { + surface_synced_move_state(synced, + state_synced[synced->index], next_synced[synced->index]); + } + state->committed = next->committed; next->committed = 0; @@ -404,21 +444,44 @@ static void surface_update_input_region(struct wlr_surface *surface) { 0, 0, surface->current.width, surface->current.height); } -static void surface_state_init(struct wlr_surface_state *state); +static bool surface_state_init(struct wlr_surface_state *state, + struct wlr_surface *surface); +static void surface_state_finish(struct wlr_surface_state *state); static void surface_cache_pending(struct wlr_surface *surface) { struct wlr_surface_state *cached = calloc(1, sizeof(*cached)); if (!cached) { - wl_resource_post_no_memory(surface->resource); - return; + goto error; + } + + if (!surface_state_init(cached, surface)) { + goto error_cached; } - surface_state_init(cached); - surface_state_move(cached, &surface->pending); + void **cached_synced = cached->synced.data; + struct wlr_surface_synced *synced; + wl_list_for_each(synced, &surface->synced, link) { + void *synced_state = surface_synced_create_state(synced); + if (synced_state == NULL) { + goto error_state; + } + cached_synced[synced->index] = synced_state; + } + + surface_state_move(cached, &surface->pending, surface); wl_list_insert(surface->cached.prev, &cached->cached_state_link); surface->pending.seq++; + + return; + +error_state: + surface_state_finish(cached); +error_cached: + free(cached); +error: + wl_resource_post_no_memory(surface->resource); } static void surface_commit_state(struct wlr_surface *surface, @@ -452,7 +515,7 @@ static void surface_commit_state(struct wlr_surface *surface, surface->previous.buffer_width = surface->current.buffer_width; surface->previous.buffer_height = surface->current.buffer_height; - surface_state_move(&surface->current, next); + surface_state_move(&surface->current, next, surface); if (invalid_buffer) { surface_apply_damage(surface); @@ -579,7 +642,8 @@ struct wlr_surface *wlr_surface_from_resource(struct wl_resource *resource) { return wl_resource_get_user_data(resource); } -static void surface_state_init(struct wlr_surface_state *state) { +static bool surface_state_init(struct wlr_surface_state *state, + struct wlr_surface *surface) { *state = (struct wlr_surface_state){ .scale = 1, .transform = WL_OUTPUT_TRANSFORM_NORMAL, @@ -595,6 +659,10 @@ static void surface_state_init(struct wlr_surface_state *state) { pixman_region32_init(&state->opaque); pixman_region32_init_rect(&state->input, INT32_MIN, INT32_MIN, UINT32_MAX, UINT32_MAX); + + wl_array_init(&state->synced); + void *ptr = wl_array_add(&state->synced, surface->synced_len * sizeof(void *)); + return ptr != NULL; } static void surface_state_finish(struct wlr_surface_state *state) { @@ -609,9 +677,18 @@ static void surface_state_finish(struct wlr_surface_state *state) { pixman_region32_fini(&state->buffer_damage); pixman_region32_fini(&state->opaque); pixman_region32_fini(&state->input); + + wl_array_release(&state->synced); } -static void surface_state_destroy_cached(struct wlr_surface_state *state) { +static void surface_state_destroy_cached(struct wlr_surface_state *state, + struct wlr_surface *surface) { + void **synced_states = state->synced.data; + struct wlr_surface_synced *synced; + wl_list_for_each(synced, &surface->synced, link) { + surface_synced_destroy_state(synced, synced_states[synced->index]); + } + surface_state_finish(state); wl_list_remove(&state->cached_state_link); free(state); @@ -634,10 +711,11 @@ static void surface_handle_resource_destroy(struct wl_resource *resource) { wl_signal_emit_mutable(&surface->events.destroy, surface); wlr_addon_set_finish(&surface->addons); + assert(wl_list_empty(&surface->synced)); struct wlr_surface_state *cached, *cached_tmp; wl_list_for_each_safe(cached, cached_tmp, &surface->cached, cached_state_link) { - surface_state_destroy_cached(cached); + surface_state_destroy_cached(cached, surface); } wl_list_remove(&surface->renderer_destroy.link); @@ -682,8 +760,8 @@ static struct wlr_surface *surface_create(struct wl_client *client, surface->renderer = renderer; - surface_state_init(&surface->current); - surface_state_init(&surface->pending); + surface_state_init(&surface->current, surface); + surface_state_init(&surface->pending, surface); surface->pending.seq = 1; wl_signal_init(&surface->events.client_commit); @@ -699,6 +777,7 @@ static struct wlr_surface *surface_create(struct wl_client *client, pixman_region32_init(&surface->opaque_region); pixman_region32_init(&surface->input_region); wlr_addon_set_init(&surface->addons); + wl_list_init(&surface->synced); if (renderer != NULL) { wl_signal_add(&renderer->events.destroy, &surface->renderer_destroy); @@ -860,7 +939,7 @@ void wlr_surface_unlock_cached(struct wlr_surface *surface, uint32_t seq) { } surface_commit_state(surface, next); - surface_state_destroy_cached(next); + surface_state_destroy_cached(next, surface); } } @@ -1258,3 +1337,125 @@ struct wlr_compositor *wlr_compositor_create(struct wl_display *display, return compositor; } + +static bool surface_state_add_synced(struct wlr_surface_state *state, void *value) { + void **ptr = wl_array_add(&state->synced, sizeof(void *)); + if (ptr == NULL) { + return false; + } + *ptr = value; + return true; +} + +static void *surface_state_remove_synced(struct wlr_surface_state *state, + struct wlr_surface_synced *synced) { + void **synced_states = state->synced.data; + void *synced_state = synced_states[synced->index]; + array_remove_at(&state->synced, synced->index * sizeof(void *), sizeof(void *)); + return synced_state; +} + +static void surface_state_remove_and_destroy_synced(struct wlr_surface_state *state, + struct wlr_surface_synced *synced) { + void *synced_state = surface_state_remove_synced(state, synced); + surface_synced_destroy_state(synced, synced_state); +} + +bool wlr_surface_synced_init(struct wlr_surface_synced *synced, + struct wlr_surface *surface, const struct wlr_surface_synced_impl *impl, + void *pending, void *current) { + assert(impl->state_size > 0); + + struct wlr_surface_synced *other; + wl_list_for_each(other, &surface->synced, link) { + assert(synced != other); + } + + memset(pending, 0, impl->state_size); + memset(current, 0, impl->state_size); + if (impl->init_state) { + impl->init_state(pending); + impl->init_state(current); + } + if (!surface_state_add_synced(&surface->pending, pending)) { + goto error_init; + } + if (!surface_state_add_synced(&surface->current, current)) { + goto error_pending; + } + + *synced = (struct wlr_surface_synced){ + .surface = surface, + .impl = impl, + .index = surface->synced_len, + }; + + struct wlr_surface_state *cached; + wl_list_for_each(cached, &surface->cached, cached_state_link) { + void *synced_state = surface_synced_create_state(synced); + if (synced_state == NULL || + !surface_state_add_synced(cached, synced_state)) { + surface_synced_destroy_state(synced, synced_state); + goto error_cached; + } + } + + wl_list_insert(&surface->synced, &synced->link); + surface->synced_len++; + + return true; + +error_cached:; + struct wlr_surface_state *failed_at = cached; + wl_list_for_each(cached, &surface->cached, cached_state_link) { + if (cached == failed_at) { + break; + } + surface_state_remove_and_destroy_synced(cached, synced); + } + surface_state_remove_synced(&surface->current, synced); +error_pending: + surface_state_remove_synced(&surface->pending, synced); +error_init: + if (synced->impl->finish_state) { + synced->impl->finish_state(pending); + synced->impl->finish_state(current); + } + return false; +} + +void wlr_surface_synced_finish(struct wlr_surface_synced *synced) { + struct wlr_surface *surface = synced->surface; + + bool found = false; + struct wlr_surface_synced *other; + wl_list_for_each(other, &surface->synced, link) { + if (other == synced) { + found = true; + } else if (other->index > synced->index) { + other->index--; + } + } + assert(found); + + struct wlr_surface_state *cached; + wl_list_for_each(cached, &surface->cached, cached_state_link) { + surface_state_remove_and_destroy_synced(cached, synced); + } + + void *pending = surface_state_remove_synced(&surface->pending, synced); + void *current = surface_state_remove_synced(&surface->current, synced); + if (synced->impl->finish_state) { + synced->impl->finish_state(pending); + synced->impl->finish_state(current); + } + + wl_list_remove(&synced->link); + synced->surface->synced_len--; +} + +void *wlr_surface_synced_get_state(struct wlr_surface_synced *synced, + const struct wlr_surface_state *state) { + void **synced_states = state->synced.data; + return synced_states[synced->index]; +}