diff --git a/include/wlr/types/wlr_buffer.h b/include/wlr/types/wlr_buffer.h index 9a655dd2b..91543b5ea 100644 --- a/include/wlr/types/wlr_buffer.h +++ b/include/wlr/types/wlr_buffer.h @@ -56,6 +56,7 @@ struct wlr_buffer { struct { struct wl_signal destroy; struct wl_signal release; + struct wl_signal prerelease; } events; struct wlr_addon_set addons; diff --git a/include/wlr/types/wlr_raster.h b/include/wlr/types/wlr_raster.h new file mode 100644 index 000000000..6e654bb21 --- /dev/null +++ b/include/wlr/types/wlr_raster.h @@ -0,0 +1,95 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_RASTER_H +#define WLR_TYPES_WLR_RASTER_H + +#include +#include +#include + +struct wlr_buffer; +struct wlr_texture; +struct wlr_renderer; +struct wlr_drm_syncobj_timeline; +struct wlr_surface; + +struct wlr_raster { + // May be NULL + struct wlr_buffer *buffer; + + uint32_t width, height; + bool opaque; + + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; + + struct { + struct wl_signal destroy; + } events; + + // private state + + size_t n_locks; + + struct wl_listener buffer_release; + + struct wlr_texture *texture; + struct wl_listener renderer_destroy; +}; + +struct wlr_raster_create_options { + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; +}; + +/** + * Creates a new wlr_raster being backed by the given buffer. The raster will + * not lock the given buffer meaning that once it's released, the raster will + * NULL its buffer reference and potentially become invalid. + * The creation function is referenced: once the creator is done with the raster, + * wlr_raster_unlock must be called as the reference count will start at 1 + * from creation. + * + * Options can be NULL. + */ +struct wlr_raster *wlr_raster_create(struct wlr_buffer *buffer, + const struct wlr_raster_create_options *options); + +/** + * Lock the raster for use. As long as the raster has at least one lock, it + * will not be destroyed. + */ +struct wlr_raster *wlr_raster_lock(struct wlr_raster *raster); + +/** + * Unlock the raster. This must be called after wlr_raster_lock once the raster + * has been finished being used or after creation from wlr_raster_create. + */ +void wlr_raster_unlock(struct wlr_raster *raster); + +/** + * Returns the texture allocated for this renderer. If there is none, + * a new texture will be created and attached to this wlr_raster. Users do not + * own the texture returned by this function and can only be used for read-only + * purposes. + * + * Will return NULL if the creation was unsuccessful. + */ +struct wlr_texture *wlr_raster_obtain_texture(struct wlr_raster *raster, + struct wlr_renderer *renderer); + +/** + * Creates a wlr_raster from a surface. This will automatically deduplicate + * rasters if multiple are consumed from the same surface so that redundant + * uploads are not performed. The raster returned will automatically be locked. + * Users are required to call wlr_raster_unlock() after invoking this function. + */ +struct wlr_raster *wlr_raster_from_surface(struct wlr_surface *surface); + +#endif diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index bcfbc72f9..c3571e6f0 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -35,6 +35,7 @@ struct wlr_xdg_surface; struct wlr_layer_surface_v1; struct wlr_drag_icon; struct wlr_surface; +struct wlr_raster; struct wlr_scene_node; struct wlr_scene_buffer; @@ -158,6 +159,7 @@ struct wlr_scene_buffer { // May be NULL struct wlr_buffer *buffer; + struct wlr_raster *raster; struct { struct wl_signal outputs_update; // struct wlr_scene_outputs_update_event @@ -188,15 +190,9 @@ struct wlr_scene_buffer { // private state uint64_t active_outputs; - struct wlr_texture *texture; struct wlr_linux_dmabuf_feedback_v1_init_options prev_feedback_options; bool own_buffer; - int buffer_width, buffer_height; - bool buffer_is_opaque; - - struct wlr_drm_syncobj_timeline *wait_timeline; - uint64_t wait_point; struct wl_listener buffer_release; struct wl_listener renderer_destroy; @@ -427,7 +423,7 @@ struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, struct wlr_buffer *buffer); /** - * Sets the buffer's backing buffer. + * Sets the buffer's backing raster. * * If the buffer is NULL, the buffer node will not be displayed. */ @@ -435,7 +431,7 @@ void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, struct wlr_buffer *buffer); /** - * Sets the buffer's backing buffer with a custom damage region. + * Sets the buffer's backing raster with a custom damage region. * * The damage region is in buffer-local coordinates. If the region is NULL, * the whole buffer node will be damaged. @@ -465,6 +461,15 @@ struct wlr_scene_buffer_set_buffer_options { void wlr_scene_buffer_set_buffer_with_options(struct wlr_scene_buffer *scene_buffer, struct wlr_buffer *buffer, const struct wlr_scene_buffer_set_buffer_options *options); +/* + * Sets the buffer's backing raster with a custom damage region. + * + * The damage region is in buffer-local coordinates. If the region is NULL, + * the whole buffer node will be damaged. + */ +void wlr_scene_buffer_set_raster_with_damage(struct wlr_scene_buffer *scene_buffer, + struct wlr_raster *raster, const pixman_region32_t *damage); + /** * Sets the buffer's opaque region. This is an optimization hint used to * determine if buffers which reside under this one need to be rendered or not. diff --git a/tinywl/tinywl.c b/tinywl/tinywl.c index 6cf08b119..dbae39bc0 100644 --- a/tinywl/tinywl.c +++ b/tinywl/tinywl.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,7 @@ struct tinywl_server { struct wl_listener cursor_frame; struct wlr_seat *seat; + struct wl_listener new_surface; struct wl_listener new_input; struct wl_listener request_cursor; struct wl_listener request_set_selection; @@ -109,6 +111,12 @@ struct tinywl_keyboard { struct wl_listener destroy; }; +struct tinywl_surface { + struct wlr_surface *surface; + struct wl_listener commit; + struct wl_listener destroy; +}; + static void focus_toplevel(struct tinywl_toplevel *toplevel, struct wlr_surface *surface) { /* Note: this function only deals with keyboard focus. */ if (toplevel == NULL) { @@ -870,6 +878,48 @@ static void server_new_xdg_popup(struct wl_listener *listener, void *data) { wl_signal_add(&xdg_popup->events.destroy, &popup->destroy); } +static void surface_handle_commit(struct wl_listener *listener, void *data) { + struct tinywl_surface *surface = wl_container_of(listener, surface, commit); + + /* + * wlr_raster_from_surface will not automatically latch onto a surface and + * update itself as the surface commits new buffers. We have to handle that + * ourselves. Every time a surface is committed, we have to make sure to + * read from the surface buffer before it is unlocked at the end of the + * commit. Since wlr_raster_from_surface will de-duplicate rasters created + * from the same surface, wlr_scene will consume rasters that are created + * here. + */ + struct wlr_raster *raster = wlr_raster_from_surface(surface->surface); + + // unlock the raster immediately as we're only prepping the surface + wlr_raster_unlock(raster); +} + +static void surface_handle_destroy(struct wl_listener *listener, void *data) { + struct tinywl_surface *surface = wl_container_of(listener, surface, destroy); + wl_list_remove(&surface->commit.link); + wl_list_remove(&surface->destroy.link); + free(surface); +} + +static void handle_new_surface(struct wl_listener *listener, void *data) { + struct wlr_surface *wlr_surface = data; + + struct tinywl_surface *surface = calloc(1, sizeof(*surface)); + if (!surface) { + return; + } + + surface->surface = wlr_surface; + + surface->commit.notify = surface_handle_commit; + wl_signal_add(&wlr_surface->events.commit, &surface->commit); + + surface->destroy.notify = surface_handle_destroy; + wl_signal_add(&wlr_surface->events.destroy, &surface->destroy); +} + int main(int argc, char *argv[]) { wlr_log_init(WLR_DEBUG, NULL); char *startup_cmd = NULL; @@ -934,7 +984,18 @@ int main(int argc, char *argv[]) { * to dig your fingers in and play with their behavior if you want. Note that * the clients cannot set the selection directly without compositor approval, * see the handling of the request_set_selection event below.*/ - wlr_compositor_create(server.wl_display, 5, server.renderer); + struct wlr_compositor *compositor = wlr_compositor_create(server.wl_display, 5, NULL); + + /* + * Surfaces act as a container for state that come from a wayland surface + * of a client. Surfaces provide buffers that act as the pixel data that the + * client wants to show. However, buffers aren't immetiately useful for us. + * We need to upload them to the GPU and for this, we'll use wlr_raster to + * help us do that. + */ + server.new_surface.notify = handle_new_surface; + wl_signal_add(&compositor->events.new_surface, &server.new_surface); + wlr_subcompositor_create(server.wl_display); wlr_data_device_manager_create(server.wl_display); diff --git a/types/buffer/buffer.c b/types/buffer/buffer.c index 953207a2c..ef744f1b5 100644 --- a/types/buffer/buffer.c +++ b/types/buffer/buffer.c @@ -19,6 +19,7 @@ void wlr_buffer_init(struct wlr_buffer *buffer, }; wl_signal_init(&buffer->events.destroy); wl_signal_init(&buffer->events.release); + wl_signal_init(&buffer->events.prerelease); wlr_addon_set_init(&buffer->addons); } @@ -58,6 +59,10 @@ void wlr_buffer_unlock(struct wlr_buffer *buffer) { assert(buffer->n_locks > 0); buffer->n_locks--; + if (buffer->n_locks == 0) { + wl_signal_emit_mutable(&buffer->events.prerelease, NULL); + } + if (buffer->n_locks == 0) { wl_signal_emit_mutable(&buffer->events.release, NULL); } diff --git a/types/meson.build b/types/meson.build index ec70d4b7c..032143db6 100644 --- a/types/meson.build +++ b/types/meson.build @@ -68,6 +68,7 @@ wlr_files += files( 'wlr_presentation_time.c', 'wlr_primary_selection_v1.c', 'wlr_primary_selection.c', + 'wlr_raster.c', 'wlr_region.c', 'wlr_relative_pointer_v1.c', 'wlr_screencopy_v1.c', diff --git a/types/scene/surface.c b/types/scene/surface.c index 2aff5af37..297c9f632 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "types/wlr_scene.h" @@ -76,28 +77,6 @@ static void scene_surface_handle_surface_destroy( wlr_scene_node_destroy(&surface->buffer->node); } -// This is used for wlr_scene where it unconditionally locks buffers preventing -// reuse of the existing texture for shm clients. With the usage pattern of -// wlr_scene surface handling, we can mark its locked buffer as safe -// for mutation. -static void client_buffer_mark_next_can_damage(struct wlr_client_buffer *buffer) { - buffer->n_ignore_locks++; -} - -static void scene_buffer_unmark_client_buffer(struct wlr_scene_buffer *scene_buffer) { - if (!scene_buffer->buffer) { - return; - } - - struct wlr_client_buffer *buffer = wlr_client_buffer_get(scene_buffer->buffer); - if (!buffer) { - return; - } - - assert(buffer->n_ignore_locks > 0); - buffer->n_ignore_locks--; -} - static int min(int a, int b) { return a < b ? a : b; } @@ -160,38 +139,23 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { wlr_scene_buffer_set_transform(scene_buffer, state->transform); wlr_scene_buffer_set_opacity(scene_buffer, opacity); - scene_buffer_unmark_client_buffer(scene_buffer); - - if (surface->buffer) { - client_buffer_mark_next_can_damage(surface->buffer); + struct wlr_raster *raster = wlr_raster_from_surface(surface); + if (raster) { + wlr_scene_buffer_set_raster_with_damage(scene_buffer, + raster, &surface->buffer_damage); struct wlr_linux_drm_syncobj_surface_v1_state *syncobj_surface_state = wlr_linux_drm_syncobj_v1_get_surface_state(surface); - - struct wlr_drm_syncobj_timeline *wait_timeline = NULL; - uint64_t wait_point = 0; - if (syncobj_surface_state != NULL) { - wait_timeline = syncobj_surface_state->acquire_timeline; - wait_point = syncobj_surface_state->acquire_point; - } - - struct wlr_scene_buffer_set_buffer_options options = { - .damage = &surface->buffer_damage, - .wait_timeline = wait_timeline, - .wait_point = wait_point, - }; - wlr_scene_buffer_set_buffer_with_options(scene_buffer, - &surface->buffer->base, &options); - - if (syncobj_surface_state != NULL && + if (syncobj_surface_state != NULL && raster->buffer && (surface->current.committed & WLR_SURFACE_STATE_BUFFER)) { wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer(syncobj_surface_state, - &surface->buffer->base); + raster->buffer); } } else { wlr_scene_buffer_set_buffer(scene_buffer, NULL); } + wlr_raster_unlock(raster); pixman_region32_fini(&opaque); } @@ -231,8 +195,6 @@ static bool scene_buffer_point_accepts_input(struct wlr_scene_buffer *scene_buff static void surface_addon_destroy(struct wlr_addon *addon) { struct wlr_scene_surface *surface = wl_container_of(addon, surface, addon); - scene_buffer_unmark_client_buffer(surface->buffer); - wlr_addon_finish(&surface->addon); wl_list_remove(&surface->outputs_update.link); diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 8cbb0b98e..2357e09b4 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -91,8 +92,6 @@ struct highlight_region { static void scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, struct wlr_buffer *buffer); -static void scene_buffer_set_texture(struct wlr_scene_buffer *scene_buffer, - struct wlr_texture *texture); void wlr_scene_node_destroy(struct wlr_scene_node *node) { if (node == NULL) { @@ -123,9 +122,8 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { } scene_buffer_set_buffer(scene_buffer, NULL); - scene_buffer_set_texture(scene_buffer, NULL); + wlr_raster_unlock(scene_buffer->raster); pixman_region32_fini(&scene_buffer->opaque_region); - wlr_drm_syncobj_timeline_unref(scene_buffer->wait_timeline); } else if (node->type == WLR_SCENE_NODE_TREE) { struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); @@ -259,7 +257,7 @@ static void scene_node_opaque_region(struct wlr_scene_node *node, int x, int y, } else if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); - if (!scene_buffer->buffer) { + if (!scene_buffer->raster) { return; } @@ -267,7 +265,7 @@ static void scene_node_opaque_region(struct wlr_scene_node *node, int x, int y, return; } - if (!scene_buffer->buffer_is_opaque) { + if (!scene_buffer->raster->opaque) { pixman_region32_copy(opaque, &scene_buffer->opaque_region); pixman_region32_intersect_rect(opaque, opaque, 0, 0, width, height); pixman_region32_translate(opaque, x, y); @@ -760,57 +758,17 @@ static void scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, if (scene_buffer->own_buffer) { wlr_buffer_unlock(scene_buffer->buffer); } - scene_buffer->buffer = NULL; + scene_buffer->buffer = buffer; scene_buffer->own_buffer = false; - scene_buffer->buffer_width = scene_buffer->buffer_height = 0; - scene_buffer->buffer_is_opaque = false; if (!buffer) { return; } - scene_buffer->own_buffer = true; - scene_buffer->buffer = wlr_buffer_lock(buffer); - scene_buffer->buffer_width = buffer->width; - scene_buffer->buffer_height = buffer->height; - scene_buffer->buffer_is_opaque = buffer_is_opaque(buffer); - scene_buffer->buffer_release.notify = scene_buffer_handle_buffer_release; wl_signal_add(&buffer->events.release, &scene_buffer->buffer_release); } -static void scene_buffer_handle_renderer_destroy(struct wl_listener *listener, - void *data) { - struct wlr_scene_buffer *scene_buffer = wl_container_of(listener, scene_buffer, renderer_destroy); - scene_buffer_set_texture(scene_buffer, NULL); -} - -static void scene_buffer_set_texture(struct wlr_scene_buffer *scene_buffer, - struct wlr_texture *texture) { - wl_list_remove(&scene_buffer->renderer_destroy.link); - wlr_texture_destroy(scene_buffer->texture); - scene_buffer->texture = texture; - - if (texture != NULL) { - scene_buffer->renderer_destroy.notify = scene_buffer_handle_renderer_destroy; - wl_signal_add(&texture->renderer->events.destroy, &scene_buffer->renderer_destroy); - } else { - wl_list_init(&scene_buffer->renderer_destroy.link); - } -} - -static void scene_buffer_set_wait_timeline(struct wlr_scene_buffer *scene_buffer, - struct wlr_drm_syncobj_timeline *timeline, uint64_t point) { - wlr_drm_syncobj_timeline_unref(scene_buffer->wait_timeline); - if (timeline != NULL) { - scene_buffer->wait_timeline = wlr_drm_syncobj_timeline_ref(timeline); - scene_buffer->wait_point = point; - } else { - scene_buffer->wait_timeline = NULL; - scene_buffer->wait_point = 0; - } -} - struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, struct wlr_buffer *buffer) { struct wlr_scene_buffer *scene_buffer = calloc(1, sizeof(*scene_buffer)); @@ -829,8 +787,14 @@ struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, wl_list_init(&scene_buffer->buffer_release.link); wl_list_init(&scene_buffer->renderer_destroy.link); scene_buffer->opacity = 1; - scene_buffer_set_buffer(scene_buffer, buffer); + + if (buffer) { + scene_buffer->raster = wlr_raster_create(buffer, NULL); + wlr_buffer_lock(buffer); + scene_buffer->own_buffer = true; + } + scene_node_update(&scene_buffer->node, NULL); return scene_buffer; @@ -843,32 +807,47 @@ void wlr_scene_buffer_set_buffer_with_options(struct wlr_scene_buffer *scene_buf options = &default_options; } - // specifying a region for a NULL buffer doesn't make sense. We need to know - // about the buffer to scale the buffer local coordinates down to scene + struct wlr_raster *raster = NULL; + if (buffer) { + raster = wlr_raster_create(buffer, &(struct wlr_raster_create_options) { + .wait_timeline = options->wait_timeline, + .wait_point = options->wait_point, + }); + } + + wlr_scene_buffer_set_raster_with_damage(scene_buffer, raster, options->damage); + + if (raster) { + wlr_buffer_lock(buffer); + scene_buffer->own_buffer = true; + } + + wlr_raster_unlock(raster); +} + +void wlr_scene_buffer_set_raster_with_damage(struct wlr_scene_buffer *scene_buffer, + struct wlr_raster *raster, const pixman_region32_t *damage) { + // specifying a region for a NULL raster doesn't make sense. We need to know + // about the raster to scale the raster local coordinates down to scene // coordinates. - assert(buffer || !options->damage); + assert(raster || !damage); - bool mapped = buffer != NULL; - bool prev_mapped = scene_buffer->buffer != NULL || scene_buffer->texture != NULL; - - if (!mapped && !prev_mapped) { - // unmapping already unmapped buffer - noop + if (raster == scene_buffer->raster) { return; } // if this node used to not be mapped or its previous displayed // buffer region will be different from what the new buffer would // produce we need to update the node. - bool update = mapped != prev_mapped; - if (buffer != NULL && scene_buffer->dst_width == 0 && scene_buffer->dst_height == 0) { - update = update || scene_buffer->buffer_width != buffer->width || - scene_buffer->buffer_height != buffer->height; + bool update = !raster != !scene_buffer->raster; + if (raster != NULL && scene_buffer->dst_width == 0 && scene_buffer->dst_height == 0) { + update = update || scene_buffer->raster->width != raster->width || + scene_buffer->raster->height != raster->height; } - scene_buffer_set_buffer(scene_buffer, buffer); - scene_buffer_set_texture(scene_buffer, NULL); - scene_buffer_set_wait_timeline(scene_buffer, - options->wait_timeline, options->wait_point); + wlr_raster_unlock(scene_buffer->raster); + scene_buffer_set_buffer(scene_buffer, raster ? raster->buffer : NULL); + scene_buffer->raster = raster ? wlr_raster_lock(raster) : NULL; if (update) { scene_node_update(&scene_buffer->node, NULL); @@ -883,8 +862,7 @@ void wlr_scene_buffer_set_buffer_with_options(struct wlr_scene_buffer *scene_buf } pixman_region32_t fallback_damage; - pixman_region32_init_rect(&fallback_damage, 0, 0, buffer->width, buffer->height); - const pixman_region32_t *damage = options->damage; + pixman_region32_init_rect(&fallback_damage, 0, 0, raster->width, raster->height); if (!damage) { damage = &fallback_damage; } @@ -893,26 +871,26 @@ void wlr_scene_buffer_set_buffer_with_options(struct wlr_scene_buffer *scene_buf if (wlr_fbox_empty(&box)) { box.x = 0; box.y = 0; - box.width = buffer->width; - box.height = buffer->height; + box.width = raster->width; + box.height = raster->height; } wlr_fbox_transform(&box, &box, scene_buffer->transform, - buffer->width, buffer->height); + raster->width, raster->height); float scale_x, scale_y; if (scene_buffer->dst_width || scene_buffer->dst_height) { scale_x = scene_buffer->dst_width / box.width; scale_y = scene_buffer->dst_height / box.height; } else { - scale_x = buffer->width / box.width; - scale_y = buffer->height / box.height; + scale_x = raster->width / box.width; + scale_y = raster->height / box.height; } pixman_region32_t trans_damage; pixman_region32_init(&trans_damage); wlr_region_transform(&trans_damage, damage, - scene_buffer->transform, buffer->width, buffer->height); + scene_buffer->transform, raster->width, raster->height); pixman_region32_intersect_rect(&trans_damage, &trans_damage, box.x, box.y, box.width, box.height); pixman_region32_translate(&trans_damage, -box.x, -box.y); @@ -1063,28 +1041,6 @@ void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer, scene_node_update(&scene_buffer->node, NULL); } -static struct wlr_texture *scene_buffer_get_texture( - struct wlr_scene_buffer *scene_buffer, struct wlr_renderer *renderer) { - if (scene_buffer->buffer == NULL || scene_buffer->texture != NULL) { - return scene_buffer->texture; - } - - struct wlr_client_buffer *client_buffer = - wlr_client_buffer_get(scene_buffer->buffer); - if (client_buffer != NULL) { - return client_buffer->texture; - } - - struct wlr_texture *texture = - wlr_texture_from_buffer(renderer, scene_buffer->buffer); - if (texture != NULL && scene_buffer->own_buffer) { - scene_buffer->own_buffer = false; - wlr_buffer_unlock(scene_buffer->buffer); - } - scene_buffer_set_texture(scene_buffer, texture); - return texture; -} - static void scene_node_get_size(struct wlr_scene_node *node, int *width, int *height) { *width = 0; @@ -1103,9 +1059,9 @@ static void scene_node_get_size(struct wlr_scene_node *node, if (scene_buffer->dst_width > 0 && scene_buffer->dst_height > 0) { *width = scene_buffer->dst_width; *height = scene_buffer->dst_height; - } else { - *width = scene_buffer->buffer_width; - *height = scene_buffer->buffer_height; + } else if (scene_buffer->raster) { + *width = scene_buffer->raster->width; + *height = scene_buffer->raster->height; wlr_output_transform_coords(scene_buffer->transform, width, height); } break; @@ -1374,8 +1330,12 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren case WLR_SCENE_NODE_BUFFER:; struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); - struct wlr_texture *texture = scene_buffer_get_texture(scene_buffer, - data->output->output->renderer); + struct wlr_texture *texture = NULL; + if (scene_buffer->raster) { + texture = wlr_raster_obtain_texture(scene_buffer->raster, + data->output->output->renderer); + } + if (texture == NULL) { scene_output_damage(data->output, &render_region); break; @@ -1396,8 +1356,8 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren .blend_mode = !data->output->scene->calculate_visibility || pixman_region32_not_empty(&opaque) ? WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, - .wait_timeline = scene_buffer->wait_timeline, - .wait_point = scene_buffer->wait_point, + .wait_timeline = scene_buffer->raster->wait_timeline, + .wait_point = scene_buffer->raster->wait_point, }); struct wlr_scene_output_sample_event sample_event = { @@ -1708,7 +1668,7 @@ static bool scene_node_invisible(struct wlr_scene_node *node) { } else if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); - return buffer->buffer == NULL && buffer->texture == NULL; + return buffer->raster == NULL; } return false; @@ -1831,8 +1791,8 @@ static bool scene_entry_try_direct_scanout(struct render_list_entry *entry, return false; } - int default_width = buffer->buffer->width; - int default_height = buffer->buffer->height; + int default_width = buffer->raster->width; + int default_height = buffer->raster->height; wlr_output_transform_coords(buffer->transform, &default_width, &default_height); struct wlr_fbox default_box = { .width = default_width, @@ -1872,8 +1832,9 @@ static bool scene_entry_try_direct_scanout(struct render_list_entry *entry, } wlr_output_state_set_buffer(&pending, buffer->buffer); - if (buffer->wait_timeline != NULL) { - wlr_output_state_set_wait_timeline(&pending, buffer->wait_timeline, buffer->wait_point); + if (buffer->raster->wait_timeline != NULL) { + wlr_output_state_set_wait_timeline(&pending, + buffer->raster->wait_timeline, buffer->raster->wait_point); } if (!wlr_output_test_state(scene_output->output, &pending)) { diff --git a/types/wlr_cursor.c b/types/wlr_cursor.c index a2489fbd1..9b00e386e 100644 --- a/types/wlr_cursor.c +++ b/types/wlr_cursor.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -539,7 +540,12 @@ static void cursor_output_cursor_update(struct wlr_cursor_output_cursor *output_ } else if (cur->state->surface != NULL) { struct wlr_surface *surface = cur->state->surface; - struct wlr_texture *texture = wlr_surface_get_texture(surface); + struct wlr_texture *texture = NULL; + struct wlr_raster *raster = wlr_raster_from_surface(surface); + if (raster) { + texture = wlr_raster_obtain_texture(raster, output_cursor->output_cursor->output->renderer); + } + int32_t hotspot_x = cur->state->surface_hotspot.x; int32_t hotspot_y = cur->state->surface_hotspot.y; @@ -561,12 +567,14 @@ static void cursor_output_cursor_update(struct wlr_cursor_output_cursor *output_ &src_box, dst_width, dst_height, surface->current.transform, hotspot_x, hotspot_y, wait_timeline, wait_point); - if (syncobj_surface_state != NULL && surface->buffer != NULL && + if (syncobj_surface_state != NULL && raster && raster->buffer != NULL && (surface->current.committed & WLR_SURFACE_STATE_BUFFER)) { wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer(syncobj_surface_state, - &surface->buffer->base); + raster->buffer); } + wlr_raster_unlock(raster); + if (output_cursor->output_cursor->visible) { wlr_surface_send_enter(surface, output); } else { diff --git a/types/wlr_raster.c b/types/wlr_raster.c new file mode 100644 index 000000000..9248ca471 --- /dev/null +++ b/types/wlr_raster.c @@ -0,0 +1,392 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_buffer.h" + +static void raster_handle_buffer_release(struct wl_listener *listener, void *data) { + struct wlr_raster *raster = wl_container_of(listener, raster, buffer_release); + raster->buffer = NULL; + wl_list_remove(&raster->buffer_release.link); + wl_list_init(&raster->buffer_release.link); +} + +struct wlr_raster *wlr_raster_create(struct wlr_buffer *buffer, + const struct wlr_raster_create_options *options) { + struct wlr_raster *raster = calloc(1, sizeof(*raster)); + if (!raster) { + return NULL; + } + + wl_signal_init(&raster->events.destroy); + + assert(buffer); + raster->opaque = buffer_is_opaque(buffer); + raster->width = buffer->width; + raster->height = buffer->height; + raster->buffer = buffer; + + raster->n_locks = 1; + + raster->buffer_release.notify = raster_handle_buffer_release; + wl_signal_add(&raster->buffer->events.release, &raster->buffer_release); + + if (options && options->wait_timeline) { + raster->wait_timeline = wlr_drm_syncobj_timeline_ref(options->wait_timeline); + raster->wait_point = options->wait_point; + } + + return raster; +} + +static void raster_consider_destroy(struct wlr_raster *raster) { + if (raster->n_locks > 0) { + return; + } + + wl_signal_emit_mutable(&raster->events.destroy, NULL); + + if (raster->texture) { + wl_list_remove(&raster->renderer_destroy.link); + wlr_texture_destroy(raster->texture); + } + + wl_list_remove(&raster->buffer_release.link); + wlr_drm_syncobj_timeline_unref(raster->wait_timeline); + free(raster); +} + +struct wlr_raster *wlr_raster_lock(struct wlr_raster *raster) { + raster->n_locks++; + return raster; +} + +void wlr_raster_unlock(struct wlr_raster *raster) { + if (!raster) { + return; + } + + assert(raster->n_locks > 0); + + raster->n_locks--; + raster_consider_destroy(raster); +} + +static void raster_detach(struct wlr_raster *raster, struct wlr_texture *texture) { + assert(texture); + assert(raster->texture == texture); + + wl_list_remove(&raster->renderer_destroy.link); + raster->texture = NULL; +} + +static void handle_renderer_destroy(struct wl_listener *listener, void *data) { + struct wlr_raster *raster = wl_container_of(listener, raster, renderer_destroy); + raster_detach(raster, raster->texture); +} + +static void raster_attach(struct wlr_raster *raster, struct wlr_texture *texture) { + assert(texture->width == raster->width && texture->height == raster->height); + assert(!raster->texture); + + raster->renderer_destroy.notify = handle_renderer_destroy; + wl_signal_add(&texture->renderer->events.destroy, &raster->renderer_destroy); + + raster->texture = texture; +} + +struct wlr_texture *wlr_raster_obtain_texture(struct wlr_raster *raster, + struct wlr_renderer *renderer) { + if (raster->texture) { + assert(raster->texture->renderer == renderer); + return raster->texture; + } + + assert(raster->buffer); + + struct wlr_client_buffer *client_buffer = + wlr_client_buffer_get(raster->buffer); + if (client_buffer != NULL) { + return client_buffer->texture; + } + + struct wlr_texture *texture = wlr_texture_from_buffer(renderer, raster->buffer); + if (texture) { + raster_attach(raster, texture); + } + + return texture; +} + +struct raster_update_state { + struct wlr_buffer *buffer; + pixman_region32_t damage; + + struct wlr_raster *new_raster; + struct wlr_raster *old_raster; + + struct wl_listener old_raster_destroy; + struct wl_listener new_raster_destroy; + struct wl_listener buffer_release; +}; + +static void destroy_raster_update_state(struct raster_update_state *state) { + wl_list_remove(&state->old_raster_destroy.link); + wl_list_remove(&state->new_raster_destroy.link); + wl_list_remove(&state->buffer_release.link); + pixman_region32_fini(&state->damage); + free(state); +} + +static void raster_update_handle_new_raster_destroy(struct wl_listener *listener, void *data) { + struct raster_update_state *state = wl_container_of(listener, state, new_raster_destroy); + destroy_raster_update_state(state); +} + +static void raster_update_handle_old_raster_destroy(struct wl_listener *listener, void *data) { + struct raster_update_state *state = wl_container_of(listener, state, old_raster_destroy); + + // if the new raster already has a texture, there's nothing we can do to help. + if (state->new_raster->texture) { + assert(state->new_raster->texture->renderer == state->old_raster->texture->renderer); + destroy_raster_update_state(state); + return; + } + + struct wlr_texture *texture = state->old_raster->texture; + if (!texture) { + destroy_raster_update_state(state); + return; + } + + if (wlr_texture_update_from_buffer(texture, state->buffer, &state->damage)) { + raster_detach(state->old_raster, texture); + raster_attach(state->new_raster, texture); + } + + destroy_raster_update_state(state); +} + +static void raster_update_handle_buffer_release(struct wl_listener *listener, void *data) { + struct raster_update_state *state = wl_container_of(listener, state, buffer_release); + destroy_raster_update_state(state); +} + +static struct wlr_raster *raster_update(struct wlr_raster *raster, + struct wlr_buffer *buffer, const pixman_region32_t *damage, + const struct wlr_raster_create_options *options) { + struct raster_update_state *state = calloc(1, sizeof(*state)); + if (!state) { + return NULL; + } + + struct wlr_raster *new_raster = wlr_raster_create(buffer, options); + if (!new_raster) { + free(state); + return NULL; + } + + state->old_raster_destroy.notify = raster_update_handle_old_raster_destroy; + wl_signal_add(&raster->events.destroy, &state->old_raster_destroy); + state->new_raster_destroy.notify = raster_update_handle_new_raster_destroy; + wl_signal_add(&new_raster->events.destroy, &state->new_raster_destroy); + state->buffer_release.notify = raster_update_handle_buffer_release; + wl_signal_add(&buffer->events.release, &state->buffer_release); + + state->new_raster = new_raster; + state->old_raster = raster; + state->buffer = buffer; + + pixman_region32_init(&state->damage); + pixman_region32_copy(&state->damage, damage); + + return new_raster; +} + +struct surface_raster { + struct wlr_raster *raster; + struct wlr_surface *surface; + + struct wlr_addon addon; + + struct wl_listener buffer_prerelease; + + bool locking_buffer; +}; + +static void surface_raster_drop_raster(struct surface_raster *surface_raster) { + if (surface_raster->locking_buffer) { + wlr_buffer_unlock(surface_raster->raster->buffer); + surface_raster->locking_buffer = false; + } + + wlr_raster_unlock(surface_raster->raster); + surface_raster->raster = NULL; +} + +static void surface_raster_destroy(struct surface_raster *surface_raster) { + surface_raster_drop_raster(surface_raster); + + wl_list_remove(&surface_raster->buffer_prerelease.link); + wlr_addon_finish(&surface_raster->addon); + free(surface_raster); +} + +static void surface_raster_handle_addon_destroy(struct wlr_addon *addon) { + struct surface_raster *surface_raster = wl_container_of(addon, surface_raster, addon); + surface_raster_destroy(surface_raster); +} + +static void surface_raster_handle_buffer_prerelease(struct wl_listener *listener, void *data) { + struct surface_raster *surface_raster = + wl_container_of(listener, surface_raster, buffer_prerelease); + struct wlr_raster *raster = surface_raster->raster; + + struct wlr_surface_output *output; + wl_list_for_each(output, &surface_raster->surface->current_outputs, link) { + wlr_raster_obtain_texture(raster, output->output->renderer); + } + + // if there was a failed texture upload, keep on locking the buffer + if (!raster->texture) { + wlr_buffer_lock(raster->buffer); + surface_raster->locking_buffer = true; + } + + wl_list_remove(&surface_raster->buffer_prerelease.link); + wl_list_init(&surface_raster->buffer_prerelease.link); +} + +const struct wlr_addon_interface surface_raster_addon_impl = { + .name = "wlr_raster_surface", + .destroy = surface_raster_handle_addon_destroy, +}; + +static struct surface_raster *get_surface_raster(struct wlr_surface *surface) { + struct wlr_addon *addon = wlr_addon_find(&surface->addons, NULL, + &surface_raster_addon_impl); + if (!addon) { + return NULL; + } + + struct surface_raster *surface_raster = wl_container_of(addon, surface_raster, addon); + return surface_raster; +} + +// Because wlr_raster doesn't lock the buffer itself, we need something extra +// to keep client buffer locked when operating in legacy mode. +struct client_buffer_compat { + struct wlr_client_buffer *buffer; + struct wl_listener destroy; +}; + +static void client_buffer_compat_raster_destroy(struct wl_listener *listener, void *data) { + struct client_buffer_compat *compat = wl_container_of(listener, compat, destroy); + + wlr_buffer_unlock(&compat->buffer->base); + wl_list_remove(&compat->destroy.link); + free(compat); +} + +struct wlr_raster *wlr_raster_from_surface(struct wlr_surface *surface) { + struct wlr_linux_drm_syncobj_surface_v1_state *syncobj_surface_state = + wlr_linux_drm_syncobj_v1_get_surface_state(surface); + + struct wlr_raster_create_options options = {0}; + if (syncobj_surface_state) { + options.wait_timeline = syncobj_surface_state->acquire_timeline; + options.wait_point = syncobj_surface_state->acquire_point; + } + + if (surface->compositor->renderer) { + // use legacy wlr_client_buffer + if (!surface->buffer) { + return NULL; + } + + struct client_buffer_compat *compat = calloc(1, sizeof(*compat)); + if (!compat) { + return NULL; + } + + struct wlr_raster *raster = wlr_raster_create(&surface->buffer->base, &options); + if (!raster) { + free(compat); + return NULL; + } + + compat->destroy.notify = client_buffer_compat_raster_destroy; + wl_signal_add(&raster->events.destroy, &compat->destroy); + + compat->buffer = surface->buffer; + wlr_buffer_lock(&surface->buffer->base); + + return raster; + } + + struct surface_raster *surface_raster = get_surface_raster(surface); + if (!surface_raster) { + surface_raster = calloc(1, sizeof(*surface_raster)); + if (!surface_raster) { + return NULL; + } + + surface_raster->surface = surface; + + wlr_addon_init(&surface_raster->addon, &surface->addons, NULL, + &surface_raster_addon_impl); + + surface_raster->buffer_prerelease.notify = surface_raster_handle_buffer_prerelease; + wl_list_init(&surface_raster->buffer_prerelease.link); + } + + if (!surface->current.buffer) { + // surface is mapped but it hasn't committed a new buffer. We need to keep + // using the old one + if (wlr_surface_has_buffer(surface)) { + if (surface_raster->raster) { + return wlr_raster_lock(surface_raster->raster); + } else { + return NULL; + } + } + + wl_list_remove(&surface_raster->buffer_prerelease.link); + wl_list_init(&surface_raster->buffer_prerelease.link); + + surface_raster_drop_raster(surface_raster); + + return NULL; + } + + struct wlr_raster *raster; + if (surface_raster->raster) { + // make sure we haven't already seen this buffer + if (surface_raster->raster->buffer == surface->current.buffer) { + return wlr_raster_lock(surface_raster->raster); + } + + raster = raster_update(surface_raster->raster, + surface->current.buffer, &surface->buffer_damage, &options); + } else { + raster = wlr_raster_create(surface->current.buffer, &options); + } + + if (!raster) { + return NULL; + } + + surface_raster_drop_raster(surface_raster); + surface_raster->raster = wlr_raster_lock(raster); + + wl_list_remove(&surface_raster->buffer_prerelease.link); + wl_signal_add(&surface->current.buffer->events.prerelease, &surface_raster->buffer_prerelease); + + return raster; +}