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..ba236716a 100644 --- a/include/wlr/types/wlr_ext_image_copy_capture_v1.h +++ b/include/wlr/types/wlr_ext_image_copy_capture_v1.h @@ -10,11 +10,14 @@ #define WLR_TYPES_WLR_EXT_IMAGE_COPY_CAPTURE_V1_H #include +#include #include #include +#include #include 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; }; diff --git a/types/wlr_ext_image_copy_capture_v1.c b/types/wlr_ext_image_copy_capture_v1.c index 2e969b4a9..2e310e7f9 100644 --- a/types/wlr_ext_image_copy_capture_v1.c +++ b/types/wlr_ext_image_copy_capture_v1.c @@ -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);