compositor: add wlr_surface_synced

A lot of protocols extend the wl_surface state. Such protocols need
to synchronize their extended state with wl_surface.commit and
cached states. Add a new utility for this purpose.
This commit is contained in:
Simon Ser 2023-12-04 22:43:13 +01:00
parent 34d5af4172
commit d847516765
2 changed files with 277 additions and 13 deletions

View file

@ -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.
*/

View file

@ -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;
}
surface_state_init(cached);
surface_state_move(cached, &surface->pending);
if (!surface_state_init(cached, surface)) {
goto error_cached;
}
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];
}