From e9a6b3b85dfb3f4c43cc901b6936a7c0e27ea0ee Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 26 Oct 2024 19:19:41 +0200 Subject: [PATCH] backend/wayland: add support for explicit sync wl_buffer.release event delivery becomes undefined when using the linux-drm-syncobj-v1 protocol, so we need to wait for buffer release via a timeline point instead. The protocol requires both wait and signal timelines to be set, so we need to create one when the compositor only supplies a wait timeline. --- backend/wayland/backend.c | 15 ++++ backend/wayland/meson.build | 1 + backend/wayland/output.c | 165 ++++++++++++++++++++++++++++++++++-- include/backend/wayland.h | 19 +++++ 4 files changed, 195 insertions(+), 5 deletions(-) diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c index 9a74e6919..475d44222 100644 --- a/backend/wayland/backend.c +++ b/backend/wayland/backend.c @@ -21,6 +21,7 @@ #include "drm-client-protocol.h" #include "linux-dmabuf-v1-client-protocol.h" +#include "linux-drm-syncobj-v1-client-protocol.h" #include "pointer-gestures-unstable-v1-client-protocol.h" #include "presentation-time-client-protocol.h" #include "xdg-activation-v1-client-protocol.h" @@ -411,6 +412,9 @@ static void registry_global(void *data, struct wl_registry *registry, } else if (strcmp(iface, wp_viewporter_interface.name) == 0) { wl->viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1); + } else if (strcmp(iface, wp_linux_drm_syncobj_manager_v1_interface.name) == 0) { + wl->drm_syncobj_manager_v1 = wl_registry_bind(registry, name, + &wp_linux_drm_syncobj_manager_v1_interface, 1); } } @@ -484,6 +488,11 @@ static void backend_destroy(struct wlr_backend *backend) { destroy_wl_buffer(buffer); } + struct wlr_wl_drm_syncobj_timeline *timeline, *tmp_timeline; + wl_list_for_each_safe(timeline, tmp_timeline, &wl->drm_syncobj_timelines, link) { + destroy_wl_drm_syncobj_timeline(timeline); + } + wlr_backend_finish(backend); wl_list_remove(&wl->event_loop_destroy.link); @@ -518,6 +527,9 @@ static void backend_destroy(struct wlr_backend *backend) { if (wl->zwp_linux_dmabuf_v1) { zwp_linux_dmabuf_v1_destroy(wl->zwp_linux_dmabuf_v1); } + if (wl->drm_syncobj_manager_v1) { + wp_linux_drm_syncobj_manager_v1_destroy(wl->drm_syncobj_manager_v1); + } if (wl->legacy_drm != NULL) { wl_drm_destroy(wl->legacy_drm); } @@ -592,6 +604,7 @@ struct wlr_backend *wlr_wl_backend_create(struct wl_event_loop *loop, wl_list_init(&wl->outputs); wl_list_init(&wl->seats); wl_list_init(&wl->buffers); + wl_list_init(&wl->drm_syncobj_timelines); if (remote_display != NULL) { wl->remote_display = remote_display; @@ -624,6 +637,8 @@ struct wlr_backend *wlr_wl_backend_create(struct wl_event_loop *loop, goto error_registry; } + wl->backend.features.timeline = wl->drm_syncobj_manager_v1 != NULL; + wl_display_roundtrip(wl->remote_display); // process initial event bursts struct zwp_linux_dmabuf_feedback_v1 *linux_dmabuf_feedback_v1 = NULL; diff --git a/backend/wayland/meson.build b/backend/wayland/meson.build index fcf9fdd8f..9235b7fe3 100644 --- a/backend/wayland/meson.build +++ b/backend/wayland/meson.build @@ -15,6 +15,7 @@ wlr_files += files( client_protos = [ 'drm', 'linux-dmabuf-v1', + 'linux-drm-syncobj-v1', 'pointer-gestures-unstable-v1', 'presentation-time', 'relative-pointer-unstable-v1', diff --git a/backend/wayland/output.c b/backend/wayland/output.c index c823ad5d4..5bc05f947 100644 --- a/backend/wayland/output.c +++ b/backend/wayland/output.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include "types/wlr_output.h" #include "linux-dmabuf-v1-client-protocol.h" +#include "linux-drm-syncobj-v1-client-protocol.h" #include "presentation-time-client-protocol.h" #include "viewporter-client-protocol.h" #include "xdg-activation-v1-client-protocol.h" @@ -31,7 +33,9 @@ static const uint32_t SUPPORTED_OUTPUT_STATE = WLR_OUTPUT_STATE_BUFFER | WLR_OUTPUT_STATE_ENABLED | WLR_OUTPUT_STATE_MODE | - WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED; + WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED | + WLR_OUTPUT_STATE_WAIT_TIMELINE | + WLR_OUTPUT_STATE_SIGNAL_TIMELINE; static size_t last_output_num = 0; @@ -140,19 +144,40 @@ void destroy_wl_buffer(struct wlr_wl_buffer *buffer) { if (!buffer->released) { wlr_buffer_unlock(buffer->buffer); } + wlr_drm_syncobj_timeline_unref(buffer->fallback_signal_timeline); free(buffer); } +static void buffer_release(struct wlr_wl_buffer *buffer) { + if (buffer->released) { + return; + } + buffer->released = true; + wlr_buffer_unlock(buffer->buffer); // might free buffer +} + static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer) { struct wlr_wl_buffer *buffer = data; - buffer->released = true; - wlr_buffer_unlock(buffer->buffer); // might free buffer + if (buffer->has_drm_syncobj_waiter) { + return; + } + buffer_release(buffer); } static const struct wl_buffer_listener buffer_listener = { .release = buffer_handle_release, }; +static void buffer_handle_drm_syncobj_ready(struct wl_listener *listener, void *data) { + struct wlr_wl_buffer *buffer = wl_container_of(listener, buffer, drm_syncobj_ready); + + wl_list_remove(&buffer->drm_syncobj_ready.link); + wlr_drm_syncobj_timeline_waiter_finish(&buffer->drm_syncobj_waiter); + buffer->has_drm_syncobj_waiter = false; + + buffer_release(buffer); +} + static void buffer_handle_buffer_destroy(struct wl_listener *listener, void *data) { struct wlr_wl_buffer *buffer = @@ -293,6 +318,58 @@ static struct wlr_wl_buffer *get_or_create_wl_buffer(struct wlr_wl_backend *wl, return create_wl_buffer(wl, wlr_buffer); } +void destroy_wl_drm_syncobj_timeline(struct wlr_wl_drm_syncobj_timeline *timeline) { + wp_linux_drm_syncobj_timeline_v1_destroy(timeline->wl); + wlr_addon_finish(&timeline->addon); + wl_list_remove(&timeline->link); + free(timeline); +} + +static void drm_syncobj_timeline_addon_destroy(struct wlr_addon *addon) { + struct wlr_wl_drm_syncobj_timeline *timeline = wl_container_of(addon, timeline, addon); + destroy_wl_drm_syncobj_timeline(timeline); +} + +static const struct wlr_addon_interface drm_syncobj_timeline_addon_impl = { + .name = "wlr_wl_drm_syncobj_timeline", + .destroy = drm_syncobj_timeline_addon_destroy, +}; + +static struct wlr_wl_drm_syncobj_timeline *get_or_create_drm_syncobj_timeline( + struct wlr_wl_backend *wl, struct wlr_drm_syncobj_timeline *wlr_timeline) { + struct wlr_addon *addon = + wlr_addon_find(&wlr_timeline->addons, wl, &drm_syncobj_timeline_addon_impl); + if (addon != NULL) { + struct wlr_wl_drm_syncobj_timeline *timeline = wl_container_of(addon, timeline, addon); + return timeline; + } + + struct wlr_wl_drm_syncobj_timeline *timeline = calloc(1, sizeof(*timeline)); + if (timeline == NULL) { + return NULL; + } + + timeline->base = wlr_timeline; + + int fd = wlr_drm_syncobj_timeline_export(wlr_timeline); + if (fd < 0) { + free(timeline); + return NULL; + } + + timeline->wl = wp_linux_drm_syncobj_manager_v1_import_timeline(wl->drm_syncobj_manager_v1, fd); + close(fd); + if (timeline->wl == NULL) { + free(timeline); + return NULL; + } + + wlr_addon_init(&timeline->addon, &wlr_timeline->addons, wl, &drm_syncobj_timeline_addon_impl); + wl_list_insert(&wl->drm_syncobj_timelines, &timeline->link); + + return timeline; +} + static bool update_title(struct wlr_wl_output *output, const char *title) { struct wlr_output *wlr_output = &output->wlr_output; @@ -387,6 +464,21 @@ static bool output_test(struct wlr_output *wlr_output, return false; } + if ((state->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) && + !(state->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE)) { + wlr_log(WLR_DEBUG, "Signal timeline requires a wait timeline"); + return false; + } + + if ((state->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) || + (state->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE)) { + struct wlr_dmabuf_attributes dmabuf; + if (!wlr_buffer_get_dmabuf(state->buffer, &dmabuf)) { + wlr_log(WLR_DEBUG, "Wait/signal timelines require DMA-BUFs"); + return false; + } + } + if (state->committed & WLR_OUTPUT_STATE_LAYERS) { // If we can't use a sub-surface for a layer, then we can't use a // sub-surface for any layer underneath @@ -660,6 +752,7 @@ static bool output_commit(struct wlr_output *wlr_output, } } + struct wlr_wl_buffer *buffer = NULL; if (state->committed & WLR_OUTPUT_STATE_BUFFER) { const pixman_region32_t *damage = NULL; if (state->committed & WLR_OUTPUT_STATE_DAMAGE) { @@ -667,8 +760,7 @@ static bool output_commit(struct wlr_output *wlr_output, } struct wlr_buffer *wlr_buffer = state->buffer; - struct wlr_wl_buffer *buffer = - get_or_create_wl_buffer(output->backend, wlr_buffer); + buffer = get_or_create_wl_buffer(output->backend, wlr_buffer); if (buffer == NULL) { return false; } @@ -677,6 +769,66 @@ static bool output_commit(struct wlr_output *wlr_output, damage_surface(output->surface, damage); } + if (state->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) { + struct wlr_wl_drm_syncobj_timeline *wait_timeline = + get_or_create_drm_syncobj_timeline(output->backend, state->wait_timeline); + + struct wlr_wl_drm_syncobj_timeline *signal_timeline; + uint64_t signal_point; + if (state->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) { + signal_timeline = get_or_create_drm_syncobj_timeline(output->backend, state->signal_timeline); + signal_point = state->signal_point; + } else { + if (buffer->fallback_signal_timeline == NULL) { + buffer->fallback_signal_timeline = + wlr_drm_syncobj_timeline_create(output->backend->drm_fd); + if (buffer->fallback_signal_timeline == NULL) { + return false; + } + } + signal_timeline = + get_or_create_drm_syncobj_timeline(output->backend, buffer->fallback_signal_timeline); + signal_point = ++buffer->fallback_signal_point; + } + + if (wait_timeline == NULL || signal_timeline == NULL) { + return false; + } + + if (output->drm_syncobj_surface_v1 == NULL) { + output->drm_syncobj_surface_v1 = wp_linux_drm_syncobj_manager_v1_get_surface( + output->backend->drm_syncobj_manager_v1, output->surface); + if (output->drm_syncobj_surface_v1 == NULL) { + return false; + } + } + + uint32_t wait_point_hi = state->wait_point >> 32; + uint32_t wait_point_lo = state->wait_point & UINT32_MAX; + uint32_t signal_point_hi = signal_point >> 32; + uint32_t signal_point_lo = signal_point & UINT32_MAX; + + wp_linux_drm_syncobj_surface_v1_set_acquire_point(output->drm_syncobj_surface_v1, + wait_timeline->wl, wait_point_hi, wait_point_lo); + wp_linux_drm_syncobj_surface_v1_set_release_point(output->drm_syncobj_surface_v1, + signal_timeline->wl, signal_point_hi, signal_point_lo); + + if (!wlr_drm_syncobj_timeline_waiter_init(&buffer->drm_syncobj_waiter, + signal_timeline->base, signal_point, 0, output->backend->event_loop)) { + return false; + } + buffer->has_drm_syncobj_waiter = true; + + buffer->drm_syncobj_ready.notify = buffer_handle_drm_syncobj_ready; + wl_signal_add(&buffer->drm_syncobj_waiter.events.ready, + &buffer->drm_syncobj_ready); + } else { + if (output->drm_syncobj_surface_v1 != NULL) { + wp_linux_drm_syncobj_surface_v1_destroy(output->drm_syncobj_surface_v1); + output->drm_syncobj_surface_v1 = NULL; + } + } + if ((state->committed & WLR_OUTPUT_STATE_LAYERS) && !commit_layers(output, state->layers, state->layers_len)) { return false; @@ -801,6 +953,9 @@ static void output_destroy(struct wlr_output *wlr_output) { wl_callback_destroy(output->unmap_callback); } + if (output->drm_syncobj_surface_v1) { + wp_linux_drm_syncobj_surface_v1_destroy(output->drm_syncobj_surface_v1); + } if (output->zxdg_toplevel_decoration_v1) { zxdg_toplevel_decoration_v1_destroy(output->zxdg_toplevel_decoration_v1); } diff --git a/include/backend/wayland.h b/include/backend/wayland.h index aa266eef8..46e8df95c 100644 --- a/include/backend/wayland.h +++ b/include/backend/wayland.h @@ -14,6 +14,7 @@ #include #include #include +#include struct wlr_wl_backend { struct wlr_backend backend; @@ -40,6 +41,8 @@ struct wlr_wl_backend { struct wp_presentation *presentation; struct wl_shm *shm; struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1; + struct wp_linux_drm_syncobj_manager_v1 *drm_syncobj_manager_v1; + struct wl_list drm_syncobj_timelines; // wlr_wl_drm_syncobj_timeline.link struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1; struct wl_list seats; // wlr_wl_seat.link struct zwp_tablet_manager_v2 *tablet_manager; @@ -58,6 +61,20 @@ struct wlr_wl_buffer { bool released; struct wl_list link; // wlr_wl_backend.buffers struct wl_listener buffer_destroy; + + bool has_drm_syncobj_waiter; + struct wlr_drm_syncobj_timeline_waiter drm_syncobj_waiter; + struct wl_listener drm_syncobj_ready; + + struct wlr_drm_syncobj_timeline *fallback_signal_timeline; + uint64_t fallback_signal_point; +}; + +struct wlr_wl_drm_syncobj_timeline { + struct wlr_drm_syncobj_timeline *base; + struct wlr_addon addon; + struct wl_list link; // wlr_wl_backend.drm_syncobj_timelines + struct wp_linux_drm_syncobj_timeline_v1 *wl; }; struct wlr_wl_presentation_feedback { @@ -88,6 +105,7 @@ struct wlr_wl_output { struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1; + struct wp_linux_drm_syncobj_surface_v1 *drm_syncobj_surface_v1; struct wl_list presentation_feedbacks; char *title; @@ -190,6 +208,7 @@ bool create_wl_seat(struct wl_seat *wl_seat, struct wlr_wl_backend *wl, uint32_t global_name); void destroy_wl_seat(struct wlr_wl_seat *seat); void destroy_wl_buffer(struct wlr_wl_buffer *buffer); +void destroy_wl_drm_syncobj_timeline(struct wlr_wl_drm_syncobj_timeline *timeline); extern const struct wlr_pointer_impl wl_pointer_impl; extern const struct wlr_tablet_pad_impl wl_tablet_pad_impl;