ext-image-copy-capture: emit capture_duration_us event

The dmabuf path defers ready emission via wlr_drm_syncobj_timeline_waiter
so the wlr_render_timer query is available without blocking, emitting
HW_TIMER. When the renderer lacks timeline support, the dmabuf path
falls back to a CPU bracket around submit emitted as SUBMISSION_ONLY.
The SHM path brackets the synchronous copy and emits SW_TIMER.

IMAGE_COPY_CAPTURE_MANAGER_V1_VERSION bumped to 2.

Signed-off-by: Greg Lamberson <greg@lamco.io>
This commit is contained in:
Greg Lamberson 2026-05-06 19:01:30 -05:00
parent 57441ded02
commit 70f04717ba
2 changed files with 168 additions and 16 deletions

View file

@ -10,11 +10,14 @@
#define WLR_TYPES_WLR_EXT_IMAGE_COPY_CAPTURE_V1_H
#include <pixman.h>
#include <stdint.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;
struct wlr_render_timer;
struct wlr_ext_image_copy_capture_manager_v1 {
struct wl_global *global;
@ -58,6 +61,18 @@ struct wlr_ext_image_copy_capture_frame_v1 {
struct {
struct wlr_ext_image_copy_capture_session_v1 *session;
struct wlr_render_timer *timer;
struct wlr_drm_syncobj_timeline *signal_timeline;
uint64_t signal_point;
struct wlr_drm_syncobj_timeline_waiter waiter;
bool waiter_active;
int64_t cpu_duration_ns;
uint32_t cpu_duration_flags;
enum wl_output_transform deferred_transform;
struct timespec deferred_presentation_time;
} WLR_PRIVATE;
};

View file

@ -9,7 +9,7 @@
#include "ext-image-copy-capture-v1-protocol.h"
#include "render/pixel_format.h"
#define IMAGE_COPY_CAPTURE_MANAGER_V1_VERSION 1
#define IMAGE_COPY_CAPTURE_MANAGER_V1_VERSION 2
struct wlr_ext_image_copy_capture_cursor_session_v1 {
struct wl_resource *resource;
@ -71,6 +71,16 @@ static void frame_destroy(struct wlr_ext_image_copy_capture_frame_v1 *frame) {
wl_resource_set_user_data(frame->resource, NULL);
wlr_buffer_unlock(frame->buffer);
pixman_region32_fini(&frame->buffer_damage);
if (frame->waiter_active) {
wlr_drm_syncobj_timeline_waiter_finish(&frame->waiter);
frame->waiter_active = false;
}
if (frame->signal_timeline != NULL) {
wlr_drm_syncobj_timeline_unref(frame->signal_timeline);
}
if (frame->timer != NULL) {
wlr_render_timer_destroy(frame->timer);
}
if (frame->session->frame == frame) {
frame->session->frame = NULL;
}
@ -81,11 +91,7 @@ static void frame_handle_resource_destroy(struct wl_resource *resource) {
frame_destroy(wl_resource_get_user_data(resource));
}
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) {
assert(frame->capturing);
static void frame_emit_ready(struct wlr_ext_image_copy_capture_frame_v1 *frame) {
int rects_len = 0;
const pixman_box32_t *rects =
pixman_region32_rectangles(&frame->session->damage, &rects_len);
@ -97,24 +103,112 @@ void wlr_ext_image_copy_capture_frame_v1_ready(struct wlr_ext_image_copy_capture
pixman_region32_clear(&frame->session->damage);
uint64_t pres_time_sec = (uint64_t)presentation_time->tv_sec;
ext_image_copy_capture_frame_v1_send_transform(frame->resource, transform);
uint64_t pres_time_sec = (uint64_t)frame->deferred_presentation_time.tv_sec;
ext_image_copy_capture_frame_v1_send_transform(frame->resource,
frame->deferred_transform);
ext_image_copy_capture_frame_v1_send_presentation_time(frame->resource,
pres_time_sec >> 32, (uint32_t)pres_time_sec, presentation_time->tv_nsec);
pres_time_sec >> 32, (uint32_t)pres_time_sec,
frame->deferred_presentation_time.tv_nsec);
if (wl_resource_get_version(frame->resource) >=
EXT_IMAGE_COPY_CAPTURE_FRAME_V1_CAPTURE_DURATION_US_SINCE_VERSION) {
if (frame->timer != NULL) {
int duration_ns = wlr_render_timer_get_duration_ns(frame->timer);
if (duration_ns >= 0) {
uint32_t duration_us = (uint32_t)(duration_ns / 1000);
ext_image_copy_capture_frame_v1_send_capture_duration_us(frame->resource,
duration_us,
EXT_IMAGE_COPY_CAPTURE_FRAME_V1_CAPTURE_DURATION_FLAGS_HW_TIMER);
}
} else if (frame->cpu_duration_ns >= 0 && frame->cpu_duration_flags != 0) {
uint32_t duration_us = (uint32_t)(frame->cpu_duration_ns / 1000);
ext_image_copy_capture_frame_v1_send_capture_duration_us(frame->resource,
duration_us, frame->cpu_duration_flags);
}
}
ext_image_copy_capture_frame_v1_send_ready(frame->resource);
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 void frame_handle_capture_ready(struct wlr_drm_syncobj_timeline_waiter *waiter) {
struct wlr_ext_image_copy_capture_frame_v1 *frame =
wl_container_of(waiter, frame, waiter);
wlr_drm_syncobj_timeline_waiter_finish(&frame->waiter);
frame->waiter_active = false;
frame_emit_ready(frame);
}
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) {
assert(frame->capturing);
frame->deferred_transform = transform;
frame->deferred_presentation_time = *presentation_time;
if (frame->signal_timeline != NULL) {
struct wl_client *client = wl_resource_get_client(frame->resource);
struct wl_display *display = wl_client_get_display(client);
struct wl_event_loop *loop = wl_display_get_event_loop(display);
if (wlr_drm_syncobj_timeline_waiter_init(&frame->waiter,
frame->signal_timeline, frame->signal_point,
0, loop, frame_handle_capture_ready)) {
frame->waiter_active = true;
return;
}
// Waiter init failed. The GPU work was submitted with signal_timeline
// but we can't async-wait. Drop the timer to avoid a per-frame error
// log on Vulkan or a synchronous stall on GLES2; emit without duration.
if (frame->timer != NULL) {
wlr_render_timer_destroy(frame->timer);
frame->timer = NULL;
}
}
frame_emit_ready(frame);
}
static bool copy_dmabuf(struct wlr_ext_image_copy_capture_frame_v1 *frame,
struct wlr_buffer *dst, struct wlr_buffer *src,
struct wlr_renderer *renderer, const pixman_region32_t *clip) {
struct wlr_texture *texture = wlr_texture_from_buffer(renderer, src);
if (texture == NULL) {
return false;
}
struct wlr_render_timer *timer = NULL;
struct wlr_drm_syncobj_timeline *signal_timeline = NULL;
struct timespec cpu_start;
bool measure_cpu = false;
if (renderer->features.timeline) {
int drm_fd = wlr_renderer_get_drm_fd(renderer);
if (drm_fd >= 0) {
signal_timeline = wlr_drm_syncobj_timeline_create(drm_fd);
}
if (signal_timeline != NULL) {
timer = wlr_render_timer_create(renderer);
}
}
if (signal_timeline == NULL) {
// Bracket submit with a CPU monotonic clock and emit later with
// SUBMISSION_ONLY. Avoids the Vulkan VK_NOT_READY error log and the
// GLES2 stall in glGetQueryObjectui64vEXT(GL_QUERY_RESULT_EXT).
measure_cpu = true;
clock_gettime(CLOCK_MONOTONIC, &cpu_start);
}
bool ok = false;
struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, dst, NULL);
struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, dst,
&(struct wlr_buffer_pass_options){
.timer = timer,
.signal_timeline = signal_timeline,
.signal_point = 1,
});
if (!pass) {
goto out;
}
@ -127,13 +221,43 @@ static bool copy_dmabuf(struct wlr_buffer *dst,
ok = wlr_render_pass_submit(pass);
if (ok && measure_cpu) {
struct timespec cpu_end;
clock_gettime(CLOCK_MONOTONIC, &cpu_end);
int64_t ns = (int64_t)(cpu_end.tv_sec - cpu_start.tv_sec) * 1000000000LL +
((int64_t)cpu_end.tv_nsec - (int64_t)cpu_start.tv_nsec);
if (ns >= 0) {
frame->cpu_duration_ns = ns;
frame->cpu_duration_flags =
EXT_IMAGE_COPY_CAPTURE_FRAME_V1_CAPTURE_DURATION_FLAGS_SUBMISSION_ONLY;
}
}
out:
wlr_texture_destroy(texture);
if (ok) {
frame->timer = timer;
if (signal_timeline != NULL) {
frame->signal_timeline = signal_timeline;
frame->signal_point = 1;
}
} else {
if (timer != NULL) {
wlr_render_timer_destroy(timer);
}
if (signal_timeline != NULL) {
wlr_drm_syncobj_timeline_unref(signal_timeline);
}
}
return ok;
}
static bool copy_shm(void *data, uint32_t format, size_t stride,
static bool copy_shm(struct wlr_ext_image_copy_capture_frame_v1 *frame,
void *data, uint32_t format, size_t stride,
struct wlr_buffer *src, struct wlr_renderer *renderer) {
struct timespec cpu_start;
clock_gettime(CLOCK_MONOTONIC, &cpu_start);
// TODO: bypass renderer if source buffer supports data ptr access
struct wlr_texture *texture = wlr_texture_from_buffer(renderer, src);
if (!texture) {
@ -149,6 +273,18 @@ static bool copy_shm(void *data, uint32_t format, size_t stride,
wlr_texture_destroy(texture);
if (ok) {
struct timespec cpu_end;
clock_gettime(CLOCK_MONOTONIC, &cpu_end);
int64_t ns = (int64_t)(cpu_end.tv_sec - cpu_start.tv_sec) * 1000000000LL +
((int64_t)cpu_end.tv_nsec - (int64_t)cpu_start.tv_nsec);
if (ns >= 0) {
frame->cpu_duration_ns = ns;
frame->cpu_duration_flags =
EXT_IMAGE_COPY_CAPTURE_FRAME_V1_CAPTURE_DURATION_FLAGS_SW_TIMER;
}
}
return ok;
}
@ -174,7 +310,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(frame, dst, src, renderer, &frame->buffer_damage);
}
} else if (wlr_buffer_begin_data_ptr_access(dst,
WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) {
@ -182,7 +318,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(frame, data, format, stride, src, renderer);
}
wlr_buffer_end_data_ptr_access(dst);
}
@ -332,6 +468,7 @@ static void session_handle_create_frame(struct wl_client *client,
frame->resource = frame_resource;
frame->session = session;
frame->cpu_duration_ns = -1;
pixman_region32_init(&frame->buffer_damage);
wl_signal_init(&frame->events.destroy);