From 4a9692013d14550d56d381ff44ff2edc184ded7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 20 Mar 2026 11:26:16 +0000 Subject: [PATCH 1/3] linux_drm_syncobj_v1: add _state_add_release_from_implicit_sync() This can help to gradually convert existing components away from `wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer()` --- include/render/drm_syncobj_merger.h | 20 +++++ include/wlr/types/wlr_linux_drm_syncobj_v1.h | 17 +++++ render/drm_syncobj_merger.c | 77 ++++++++++++++++++-- types/wlr_linux_drm_syncobj_v1.c | 10 +++ 4 files changed, 116 insertions(+), 8 deletions(-) diff --git a/include/render/drm_syncobj_merger.h b/include/render/drm_syncobj_merger.h index c55c87314..397c5ce80 100644 --- a/include/render/drm_syncobj_merger.h +++ b/include/render/drm_syncobj_merger.h @@ -3,6 +3,8 @@ #include +struct wlr_buffer; + /** * Accumulate timeline points, to have a destination timeline point be * signalled when all inputs are @@ -41,4 +43,22 @@ bool wlr_drm_syncobj_merger_add(struct wlr_drm_syncobj_merger *merger, struct wlr_drm_syncobj_timeline *dst_timeline, uint64_t dst_point, struct wl_event_loop *loop); +/** + * Add a new sync file to wait for. + * + * Ownership of fd is transferred to the merger. + */ +bool wlr_drm_syncobj_merger_add_sync_file(struct wlr_drm_syncobj_merger *merger, + int fd); + +/** + * Add a new DMA-BUF release to wait for. + * + * Waits for write access. + * If the platform does not support DMA-BUF<->sync file interop, the supplied + * event_loop is used to schedule a wait. + */ +bool wlr_drm_syncobj_merger_add_dmabuf(struct wlr_drm_syncobj_merger *merger, + struct wlr_buffer *buffer, struct wl_event_loop *event_loop); + #endif diff --git a/include/wlr/types/wlr_linux_drm_syncobj_v1.h b/include/wlr/types/wlr_linux_drm_syncobj_v1.h index 7fd55ec2e..dfcbc4d43 100644 --- a/include/wlr/types/wlr_linux_drm_syncobj_v1.h +++ b/include/wlr/types/wlr_linux_drm_syncobj_v1.h @@ -74,4 +74,21 @@ bool wlr_linux_drm_syncobj_v1_state_add_release_point( struct wlr_drm_syncobj_timeline *release_timeline, uint64_t release_point, struct wl_event_loop *event_loop); +/** + * Register the DMA-BUF release of a buffer for buffer usage. + * Non-dmabuf buffers are considered to be immediately available (no wait). + * + * This function may be called multiple times for the same commit. The client's + * release point will be signalled when all registered points are signalled, and + * a new buffer has been committed. + * + * Because the platform may not support DMA-BUF fence merges, a wl_event_loop + * must be supplied to schedule a wait internally, if needed + * + * Waits for write access + */ +bool wlr_linux_drm_syncobj_v1_state_add_release_from_implicit_sync( + struct wlr_linux_drm_syncobj_surface_v1_state *state, + struct wlr_buffer *buffer, struct wl_event_loop *event_loop); + #endif diff --git a/render/drm_syncobj_merger.c b/render/drm_syncobj_merger.c index d50d28c28..0cfdfcc96 100644 --- a/render/drm_syncobj_merger.c +++ b/render/drm_syncobj_merger.c @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include "render/dmabuf.h" #include "render/drm_syncobj_merger.h" #include "config.h" @@ -79,14 +81,7 @@ void wlr_drm_syncobj_merger_unref(struct wlr_drm_syncobj_merger *merger) { static bool merger_add_exportable(struct wlr_drm_syncobj_merger *merger, struct wlr_drm_syncobj_timeline *src_timeline, uint64_t src_point) { int new_sync = wlr_drm_syncobj_timeline_export_sync_file(src_timeline, src_point); - if (merger->sync_fd != -1) { - int fd2 = new_sync; - new_sync = sync_file_merge(merger->sync_fd, fd2); - close(fd2); - close(merger->sync_fd); - } - merger->sync_fd = new_sync; - return true; + return wlr_drm_syncobj_merger_add_sync_file(merger, new_sync); } struct export_waiter { @@ -131,3 +126,69 @@ bool wlr_drm_syncobj_merger_add(struct wlr_drm_syncobj_merger *merger, merger->n_ref++; return true; } + +bool wlr_drm_syncobj_merger_add_sync_file(struct wlr_drm_syncobj_merger *merger, + int fd) { + int new_sync = fd; + if (merger->sync_fd != -1) { + new_sync = sync_file_merge(merger->sync_fd, fd); + close(fd); + close(merger->sync_fd); + } + merger->sync_fd = new_sync; + return merger->sync_fd != -1; +} + +struct poll_waiter { + struct wl_event_source *event_source; + struct wlr_drm_syncobj_merger *merger; +}; + +static int poll_waiter_handle_done(int fd, uint32_t mask, void *data) { + struct poll_waiter *waiter = data; + wlr_drm_syncobj_merger_unref(waiter->merger); + wl_event_source_remove(waiter->event_source); + free(waiter); + return 0; +} + +bool wlr_drm_syncobj_merger_add_dmabuf(struct wlr_drm_syncobj_merger *merger, + struct wlr_buffer *buffer, struct wl_event_loop *event_loop) { + struct wlr_dmabuf_attributes dmabuf_attributes; + if (!wlr_buffer_get_dmabuf(buffer, &dmabuf_attributes)) { + return true; + } + + bool res = true; + for (int i = 0; i < dmabuf_attributes.n_planes; ++i) { + int sync_fd = dmabuf_export_sync_file(dmabuf_attributes.fd[i], DMA_BUF_SYNC_WRITE); + if (sync_fd == -1) { + res = false; + break; + } + if (!wlr_drm_syncobj_merger_add_sync_file(merger, sync_fd)) { + return false; + } + } + + if (res) { + return true; + } + + uint32_t mask = WL_EVENT_ERROR | WL_EVENT_HANGUP | WL_EVENT_WRITABLE; + for (int i = 0; i < dmabuf_attributes.n_planes; ++i) { + struct poll_waiter *waiter = calloc(1, sizeof(*waiter)); + if (waiter == NULL) { + return false; + } + waiter->merger = wlr_drm_syncobj_merger_ref(merger); + waiter->event_source = wl_event_loop_add_fd(event_loop, + dmabuf_attributes.fd[i], mask, poll_waiter_handle_done, waiter); + if (waiter->event_source == NULL) { + wlr_drm_syncobj_merger_unref(waiter->merger); + free(waiter); + return false; + } + } + return true; +} diff --git a/types/wlr_linux_drm_syncobj_v1.c b/types/wlr_linux_drm_syncobj_v1.c index 53fc2fd43..b9865dcce 100644 --- a/types/wlr_linux_drm_syncobj_v1.c +++ b/types/wlr_linux_drm_syncobj_v1.c @@ -12,6 +12,7 @@ #include #include "config.h" #include "linux-drm-syncobj-v1-protocol.h" +#include "render/dmabuf.h" #include "render/drm_syncobj_merger.h" #define LINUX_DRM_SYNCOBJ_V1_VERSION 1 @@ -540,3 +541,12 @@ bool wlr_linux_drm_syncobj_v1_state_add_release_point( return wlr_drm_syncobj_merger_add(state->release_merger, release_timeline, release_point, event_loop); } + +bool wlr_linux_drm_syncobj_v1_state_add_release_from_implicit_sync( + struct wlr_linux_drm_syncobj_surface_v1_state *state, + struct wlr_buffer *buffer, struct wl_event_loop *event_loop) { + if (state->release_merger != NULL) { + return true; + } + return wlr_drm_syncobj_merger_add_dmabuf(state->release_merger, buffer, event_loop); +} From 1433c1ee9ee280af0de46c6deb85cb694d57e856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 27 Mar 2026 20:06:57 +0000 Subject: [PATCH 2/3] ext_image_capture_source_v1/output: wait for explicit sync Synchronize from the scene render to the copy to capture client. Buffer return is still covered by implicit sync. We know this is the scene's render buffer, direct scanout is disallowed for the output capture source. --- .../wlr/types/wlr_ext_image_copy_capture_v1.h | 4 +++- types/ext_image_capture_source_v1/output.c | 14 +++++++++++-- types/ext_image_capture_source_v1/scene.c | 2 +- types/wlr_ext_image_copy_capture_v1.c | 20 ++++++++++++------- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/include/wlr/types/wlr_ext_image_copy_capture_v1.h b/include/wlr/types/wlr_ext_image_copy_capture_v1.h index 0b02b9808..7edd9270f 100644 --- a/include/wlr/types/wlr_ext_image_copy_capture_v1.h +++ b/include/wlr/types/wlr_ext_image_copy_capture_v1.h @@ -15,6 +15,7 @@ #include struct wlr_renderer; +struct wlr_drm_syncobj_timeline; struct wlr_ext_image_copy_capture_manager_v1 { struct wl_global *global; @@ -82,6 +83,7 @@ void wlr_ext_image_copy_capture_frame_v1_fail(struct wlr_ext_image_copy_capture_ * Copy a struct wlr_buffer into the client-provided buffer for the frame. */ bool wlr_ext_image_copy_capture_frame_v1_copy_buffer(struct wlr_ext_image_copy_capture_frame_v1 *frame, - struct wlr_buffer *src, struct wlr_renderer *renderer); + struct wlr_buffer *src, struct wlr_renderer *renderer, + struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point); #endif diff --git a/types/ext_image_capture_source_v1/output.c b/types/ext_image_capture_source_v1/output.c index 0e3a57823..53589faa3 100644 --- a/types/ext_image_capture_source_v1/output.c +++ b/types/ext_image_capture_source_v1/output.c @@ -41,6 +41,8 @@ struct wlr_ext_output_image_capture_source_v1_frame_event { struct wlr_ext_image_capture_source_v1_frame_event base; struct wlr_buffer *buffer; struct timespec when; + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; }; static void output_source_start(struct wlr_ext_image_capture_source_v1 *base, @@ -86,7 +88,8 @@ static void output_source_copy_frame(struct wlr_ext_image_capture_source_v1 *bas wl_container_of(base_event, event, base); if (wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame, - event->buffer, source->output->renderer)) { + event->buffer, source->output->renderer, + event->wait_timeline, event->wait_point)) { wlr_ext_image_copy_capture_frame_v1_ready(frame, source->output->transform, &event->when); } @@ -152,6 +155,10 @@ static void source_handle_output_commit(struct wl_listener *listener, .buffer = buffer, .when = event->when, // TODO: predict next presentation time instead }; + if (event->state->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) { + frame_event.wait_timeline = event->state->wait_timeline; + frame_event.wait_point = event->state->wait_point; + } wl_signal_emit_mutable(&source->base.events.frame, &frame_event); pixman_region32_fini(&full_damage); @@ -279,6 +286,8 @@ static void output_cursor_source_copy_frame(struct wlr_ext_image_capture_source_ struct wlr_ext_image_copy_capture_frame_v1 *frame, struct wlr_ext_image_capture_source_v1_frame_event *base_event) { struct output_cursor_source *cursor_source = wl_container_of(base, cursor_source, base); + struct wlr_ext_output_image_capture_source_v1_frame_event *event = + wl_container_of(base_event, event, base); struct wlr_buffer *src_buffer = cursor_source->output->cursor_front_buffer; if (src_buffer == NULL) { @@ -287,7 +296,8 @@ static void output_cursor_source_copy_frame(struct wlr_ext_image_capture_source_ } if (!wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame, - src_buffer, cursor_source->output->renderer)) { + src_buffer, cursor_source->output->renderer, + event->wait_timeline, event->wait_point)) { return; } diff --git a/types/ext_image_capture_source_v1/scene.c b/types/ext_image_capture_source_v1/scene.c index 7d4b8928a..07a1f52e7 100644 --- a/types/ext_image_capture_source_v1/scene.c +++ b/types/ext_image_capture_source_v1/scene.c @@ -149,7 +149,7 @@ static void source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, struct scene_node_source_frame_event *event = wl_container_of(base_event, event, base); if (wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame, - event->buffer, source->output.renderer)) { + event->buffer, source->output.renderer, NULL, 0)) { wlr_ext_image_copy_capture_frame_v1_ready(frame, source->output.transform, &event->when); } diff --git a/types/wlr_ext_image_copy_capture_v1.c b/types/wlr_ext_image_copy_capture_v1.c index 2e969b4a9..9b0f4856d 100644 --- a/types/wlr_ext_image_copy_capture_v1.c +++ b/types/wlr_ext_image_copy_capture_v1.c @@ -105,9 +105,9 @@ void wlr_ext_image_copy_capture_frame_v1_ready(struct wlr_ext_image_copy_capture frame_destroy(frame); } -static bool copy_dmabuf(struct wlr_buffer *dst, - struct wlr_buffer *src, struct wlr_renderer *renderer, - const pixman_region32_t *clip) { +static bool copy_dmabuf(struct wlr_buffer *dst, struct wlr_buffer *src, + struct wlr_renderer *renderer, const pixman_region32_t *clip, + struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point) { struct wlr_texture *texture = wlr_texture_from_buffer(renderer, src); if (texture == NULL) { return false; @@ -123,6 +123,8 @@ static bool copy_dmabuf(struct wlr_buffer *dst, .texture = texture, .clip = clip, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, + .wait_timeline = wait_timeline, + .wait_point = wait_point, }); ok = wlr_render_pass_submit(pass); @@ -133,7 +135,8 @@ out: } static bool copy_shm(void *data, uint32_t format, size_t stride, - struct wlr_buffer *src, struct wlr_renderer *renderer) { + struct wlr_buffer *src, struct wlr_renderer *renderer, + struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point) { // TODO: bypass renderer if source buffer supports data ptr access struct wlr_texture *texture = wlr_texture_from_buffer(renderer, src); if (!texture) { @@ -145,6 +148,8 @@ static bool copy_shm(void *data, uint32_t format, size_t stride, .data = data, .format = format, .stride = stride, + .wait_timeline = wait_timeline, + .wait_point = wait_point, }); wlr_texture_destroy(texture); @@ -153,7 +158,8 @@ static bool copy_shm(void *data, uint32_t format, size_t stride, } bool wlr_ext_image_copy_capture_frame_v1_copy_buffer(struct wlr_ext_image_copy_capture_frame_v1 *frame, - struct wlr_buffer *src, struct wlr_renderer *renderer) { + struct wlr_buffer *src, struct wlr_renderer *renderer, + struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point) { struct wlr_buffer *dst = frame->buffer; if (src->width != dst->width || src->height != dst->height) { @@ -174,7 +180,7 @@ bool wlr_ext_image_copy_capture_frame_v1_copy_buffer(struct wlr_ext_image_copy_c ok = false; failure_reason = EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS; } else { - ok = copy_dmabuf(dst, src, renderer, &frame->buffer_damage); + ok = copy_dmabuf(dst, src, renderer, &frame->buffer_damage, wait_timeline, wait_point); } } else if (wlr_buffer_begin_data_ptr_access(dst, WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) { @@ -182,7 +188,7 @@ bool wlr_ext_image_copy_capture_frame_v1_copy_buffer(struct wlr_ext_image_copy_c ok = false; failure_reason = EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS; } else { - ok = copy_shm(data, format, stride, src, renderer); + ok = copy_shm(data, format, stride, src, renderer, wait_timeline, wait_point); } wlr_buffer_end_data_ptr_access(dst); } From 09efe10a62a406dc7d7dcbdfc7947ed3ca605dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sat, 28 Mar 2026 09:58:12 +0000 Subject: [PATCH 3/3] ext_image_capture_source_v1/scene: use explicit sync Allow and handle timelines on the virtual backend. --- types/ext_image_capture_source_v1/scene.c | 33 +++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/types/ext_image_capture_source_v1/scene.c b/types/ext_image_capture_source_v1/scene.c index 07a1f52e7..989fc4f31 100644 --- a/types/ext_image_capture_source_v1/scene.c +++ b/types/ext_image_capture_source_v1/scene.c @@ -7,6 +7,8 @@ #include #include +#include "render/dmabuf.h" +#include "render/drm_syncobj_merger.h" #include "types/wlr_output.h" #include "types/wlr_scene.h" @@ -30,6 +32,9 @@ struct scene_node_source_frame_event { struct wlr_ext_image_capture_source_v1_frame_event base; struct wlr_buffer *buffer; struct timespec when; + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; + struct wlr_drm_syncobj_merger *release_merger; }; static size_t last_output_num = 0; @@ -149,9 +154,14 @@ static void source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, struct scene_node_source_frame_event *event = wl_container_of(base_event, event, base); if (wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame, - event->buffer, source->output.renderer, NULL, 0)) { + event->buffer, source->output.renderer, + event->wait_timeline, event->wait_point)) { wlr_ext_image_copy_capture_frame_v1_ready(frame, source->output.transform, &event->when); + if (event->release_merger) { + wlr_drm_syncobj_merger_add_dmabuf(event->release_merger, frame->buffer, + source->output.event_loop); + } } } @@ -162,7 +172,14 @@ static const struct wlr_ext_image_capture_source_v1_interface source_impl = { .copy_frame = source_copy_frame, }; -static const struct wlr_backend_impl backend_impl = {0}; +static int source_backend_get_drm_fd(struct wlr_backend *backend) { + struct scene_node_source *source = wl_container_of(backend, source, backend); + return wlr_renderer_get_drm_fd(source->output.renderer); +} + +static const struct wlr_backend_impl backend_impl = { + .get_drm_fd = source_backend_get_drm_fd +}; static void source_update_buffer_constraints(struct scene_node_source *source, const struct wlr_output_state *state) { @@ -180,6 +197,8 @@ static bool output_test(struct wlr_output *output, const struct wlr_output_state uint32_t supported = WLR_OUTPUT_STATE_BACKEND_OPTIONAL | WLR_OUTPUT_STATE_BUFFER | + WLR_OUTPUT_STATE_WAIT_TIMELINE | + WLR_OUTPUT_STATE_SIGNAL_TIMELINE | WLR_OUTPUT_STATE_ENABLED | WLR_OUTPUT_STATE_MODE; if ((state->committed & ~supported) != 0) { @@ -242,7 +261,16 @@ static bool output_commit(struct wlr_output *output, const struct wlr_output_sta .buffer = buffer, .when = now, }; + if (state->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) { + frame_event.wait_timeline = state->wait_timeline; + frame_event.wait_point = state->wait_point; + } + if (state->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) { + frame_event.release_merger = wlr_drm_syncobj_merger_create( + state->signal_timeline, state->signal_point); + } wl_signal_emit_mutable(&source->base.events.frame, &frame_event.base); + wlr_drm_syncobj_merger_unref(frame_event.release_merger); pixman_region32_fini(&full_damage); @@ -311,6 +339,7 @@ struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_w wlr_backend_init(&source->backend, &backend_impl); source->backend.buffer_caps = WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_SHM; + source->backend.features.timeline = true; wlr_output_init(&source->output, &source->backend, &output_impl, event_loop, NULL);