ext_image_copy_capture: explicit sync for frame copy

Keep a drm timeline in `wlr_ext_image_copy_capture_session_v1`, allowing
asynchronous operation for
`wlr_ext_image_copy_capture_frame_v1_copy_buffer()`
This commit is contained in:
Félix Poisot 2026-05-20 19:57:02 +00:00
parent 55ab582563
commit ee980b79a3
4 changed files with 135 additions and 16 deletions

View file

@ -12,6 +12,7 @@
#include <pixman.h>
#include <wayland-server-protocol.h>
#include <wayland-protocols/ext-image-copy-capture-v1-enum.h>
#include <wlr/render/drm_syncobj.h>
#include <time.h>
struct wlr_renderer;
@ -44,6 +45,8 @@ struct wlr_ext_image_copy_capture_session_v1 {
struct wl_listener source_frame;
pixman_region32_t damage;
struct wlr_drm_syncobj_timeline *copy_timeline;
uint64_t copy_point;
} WLR_PRIVATE;
};
@ -59,6 +62,10 @@ struct wlr_ext_image_copy_capture_frame_v1 {
struct {
struct wlr_ext_image_copy_capture_session_v1 *session;
enum wl_output_transform pending_transform;
struct timespec pending_presentation_time;
bool copy_waiter_initialized;
struct wlr_drm_syncobj_timeline_waiter copy_waiter;
} WLR_PRIVATE;
};
@ -72,6 +79,17 @@ struct wlr_ext_image_copy_capture_manager_v1 *wlr_ext_image_copy_capture_manager
*/
void wlr_ext_image_copy_capture_frame_v1_ready(struct wlr_ext_image_copy_capture_frame_v1 *frame,
enum wl_output_transform transform, const struct timespec *presentation_time);
/**
* Notify the client that the frame is ready, when timeline point is signalled.
*
* This function causes the frame destruction, and may destroy it synchronously.
*/
bool wlr_ext_image_copy_capture_frame_v1_ready_deferred(
struct wlr_ext_image_copy_capture_frame_v1 *frame,
enum wl_output_transform transform, const struct timespec *presentation_time,
struct wlr_drm_syncobj_timeline *timeline, uint64_t point);
/**
* Notify the client that the frame has failed.
*
@ -81,9 +99,14 @@ void wlr_ext_image_copy_capture_frame_v1_fail(struct wlr_ext_image_copy_capture_
enum ext_image_copy_capture_frame_v1_failure_reason reason);
/**
* Copy a struct wlr_buffer into the client-provided buffer for the frame.
*
* If the caller obtains a timeline point through `out_copy_timeline` and
* `out_copy_timeline`, it must wait for it to signal before sending the
* "frame ready" event to the capture client
*/
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_drm_syncobj_timeline *wait_timeline, uint64_t wait_point);
struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point,
struct wlr_drm_syncobj_timeline **out_copy_timeline, uint64_t *out_copy_point);
#endif

View file

@ -87,11 +87,19 @@ static void output_source_copy_frame(struct wlr_ext_image_capture_source_v1 *bas
struct wlr_ext_output_image_capture_source_v1_frame_event *event =
wl_container_of(base_event, event, base);
struct wlr_drm_syncobj_timeline *copy_timeline;
uint64_t copy_point;
if (wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame,
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);
event->wait_timeline, event->wait_point, &copy_timeline, &copy_point)) {
if (copy_timeline != NULL) {
wlr_ext_image_copy_capture_frame_v1_ready_deferred(frame,
source->output->transform, &event->when, copy_timeline, copy_point);
wlr_drm_syncobj_timeline_unref(copy_timeline);
} else {
wlr_ext_image_copy_capture_frame_v1_ready(frame,
source->output->transform, &event->when);
}
}
}
@ -295,17 +303,25 @@ static void output_cursor_source_copy_frame(struct wlr_ext_image_capture_source_
return;
}
struct wlr_drm_syncobj_timeline *copy_timeline;
uint64_t copy_point;
if (!wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame,
src_buffer, cursor_source->output->renderer,
event->wait_timeline, event->wait_point)) {
event->wait_timeline, event->wait_point, &copy_timeline, &copy_point)) {
return;
}
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
wlr_ext_image_copy_capture_frame_v1_ready(frame,
if (copy_timeline != NULL) {
wlr_ext_image_copy_capture_frame_v1_ready_deferred(frame,
cursor_source->output->transform, &now, copy_timeline, copy_point);
wlr_drm_syncobj_timeline_unref(copy_timeline);
} else {
wlr_ext_image_copy_capture_frame_v1_ready(frame,
cursor_source->output->transform, &now);
}
}
static const struct wlr_ext_image_capture_source_v1_interface output_cursor_source_impl = {

View file

@ -153,14 +153,22 @@ static void source_copy_frame(struct wlr_ext_image_capture_source_v1 *base,
struct scene_node_source *source = wl_container_of(base, source, base);
struct scene_node_source_frame_event *event = wl_container_of(base_event, event, base);
struct wlr_drm_syncobj_timeline *copy_timeline;
uint64_t copy_point;
if (wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame,
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, event->buffer,
source->output.event_loop);
event->wait_timeline, event->wait_point, &copy_timeline, &copy_point)) {
if (copy_timeline != NULL) {
if (event->release_merger != NULL) {
wlr_drm_syncobj_merger_add(event->release_merger, copy_timeline, copy_point,
source->output.event_loop);
}
wlr_ext_image_copy_capture_frame_v1_ready_deferred(frame,
source->output.transform, &event->when, copy_timeline, copy_point);
wlr_drm_syncobj_timeline_unref(copy_timeline);
} else {
wlr_ext_image_copy_capture_frame_v1_ready(frame,
source->output.transform, &event->when);
}
}
}

View file

@ -74,6 +74,9 @@ static void frame_destroy(struct wlr_ext_image_copy_capture_frame_v1 *frame) {
if (frame->session->frame == frame) {
frame->session->frame = NULL;
}
if (frame->copy_waiter_initialized) {
wlr_drm_syncobj_timeline_waiter_finish(&frame->copy_waiter);
}
free(frame);
}
@ -107,14 +110,19 @@ void wlr_ext_image_copy_capture_frame_v1_ready(struct wlr_ext_image_copy_capture
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_drm_syncobj_timeline *wait_timeline, uint64_t wait_point,
struct wlr_drm_syncobj_timeline *signal_timeline, uint64_t signal_point) {
struct wlr_texture *texture = wlr_texture_from_buffer(renderer, src);
if (texture == NULL) {
return false;
}
bool ok = false;
struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, dst, NULL);
struct wlr_buffer_pass_options options = {
.signal_timeline = signal_timeline,
.signal_point = signal_point,
};
struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, dst, &options);
if (!pass) {
goto out;
}
@ -159,8 +167,15 @@ 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_drm_syncobj_timeline *wait_timeline, uint64_t wait_point) {
struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point,
struct wlr_drm_syncobj_timeline **out_copy_timeline, uint64_t *out_copy_point) {
struct wlr_buffer *dst = frame->buffer;
if (out_copy_timeline) {
*out_copy_timeline = NULL;
}
if (out_copy_point) {
*out_copy_point = 0;
}
if (src->width != dst->width || src->height != dst->height) {
wlr_ext_image_copy_capture_frame_v1_fail(frame,
@ -168,6 +183,20 @@ bool wlr_ext_image_copy_capture_frame_v1_copy_buffer(struct wlr_ext_image_copy_c
return false;
}
struct wlr_drm_syncobj_timeline *copy_timeline = frame->session->copy_timeline;
if (copy_timeline == NULL && renderer->features.timeline) {
int drm_fd = wlr_renderer_get_drm_fd(renderer);
copy_timeline = wlr_drm_syncobj_timeline_create(drm_fd);
if (copy_timeline == NULL) {
wlr_ext_image_copy_capture_frame_v1_fail(frame,
EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN);
return false;
}
frame->session->copy_timeline = copy_timeline;
}
frame->session->copy_point++;
uint64_t copy_point = frame->session->copy_point;
bool ok = false;
enum ext_image_copy_capture_frame_v1_failure_reason failure_reason =
EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN;
@ -180,7 +209,18 @@ 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, wait_timeline, wait_point);
if (out_copy_timeline == NULL) {
copy_timeline = NULL;
copy_point = 0;
}
ok = copy_dmabuf(dst, src, renderer, &frame->buffer_damage,
wait_timeline, wait_point, copy_timeline, copy_point);
if (ok && copy_timeline != NULL) {
*out_copy_timeline = wlr_drm_syncobj_timeline_ref(frame->session->copy_timeline);
}
if (ok && out_copy_point != NULL) {
*out_copy_point = copy_point;
}
}
} else if (wlr_buffer_begin_data_ptr_access(dst,
WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) {
@ -206,6 +246,37 @@ void wlr_ext_image_copy_capture_frame_v1_fail(struct wlr_ext_image_copy_capture_
frame_destroy(frame);
}
static void frame_handle_copy_done(struct wlr_drm_syncobj_timeline_waiter *waiter) {
struct wlr_ext_image_copy_capture_frame_v1 *frame = wl_container_of(waiter, frame, copy_waiter);
wlr_ext_image_copy_capture_frame_v1_ready(frame, frame->pending_transform,
&frame->pending_presentation_time);
}
bool wlr_ext_image_copy_capture_frame_v1_ready_deferred(
struct wlr_ext_image_copy_capture_frame_v1 *frame,
enum wl_output_transform transform, const struct timespec *presentation_time,
struct wlr_drm_syncobj_timeline *timeline, uint64_t point) {
assert(!frame->copy_waiter_initialized);
frame->pending_transform = transform;
frame->pending_presentation_time = *presentation_time;
struct wl_display *display = wl_client_get_display(frame->resource->client);
struct wl_event_loop *event_loop = wl_display_get_event_loop(display);
bool ok = wlr_drm_syncobj_timeline_waiter_init(&frame->copy_waiter, timeline, point,
0, event_loop, frame_handle_copy_done);
frame->copy_waiter_initialized = ok;
if (ok) {
if (frame->session->frame == frame) {
frame->session->frame = NULL;
}
} else {
wlr_ext_image_copy_capture_frame_v1_fail(frame,
EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN);
}
return ok;
}
static void frame_handle_destroy(struct wl_client *client,
struct wl_resource *frame_resource) {
wl_resource_destroy(frame_resource);
@ -403,6 +474,7 @@ static void session_destroy(struct wlr_ext_image_copy_capture_session_v1 *sessio
wl_resource_set_user_data(session->resource, NULL);
pixman_region32_fini(&session->damage);
wlr_drm_syncobj_timeline_unref(session->copy_timeline);
wl_list_remove(&session->source_destroy.link);
wl_list_remove(&session->source_constraints_update.link);
wl_list_remove(&session->source_frame.link);