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.
This commit is contained in:
Simon Ser 2024-10-26 19:19:41 +02:00 committed by Simon Zeni
parent ca29f43a54
commit e9a6b3b85d
4 changed files with 195 additions and 5 deletions

View File

@ -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;

View File

@ -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',

View File

@ -11,6 +11,7 @@
#include <wayland-client-protocol.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/render/drm_syncobj.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output_layer.h>
#include <wlr/util/log.h>
@ -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);
}

View File

@ -14,6 +14,7 @@
#include <wlr/types/wlr_tablet_tool.h>
#include <wlr/types/wlr_touch.h>
#include <wlr/render/drm_format_set.h>
#include <wlr/render/drm_syncobj.h>
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;