From b7944e4bbfa64a6bb1a1d9e1f172827dde53aed2 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Tue, 13 Oct 2026 17:12:10 +0300 Subject: Read back the last presented CPU frame wsap_term writes PNG screenshots for its debug server and keeps its own copy of the frame because YASL had no readback. yasl_surface_read_pixels() copies the frame from the last successful present, or reports its extent when the buffer is too small. X11 and Cocoa keep it in their single CPU target until the next draw starts; X11 now also forgets it when a resize replaces the target, and Cocoa records the presented extent because a resize can change the size before the next draw. Wayland remembers which buffer it committed and forgets it when a draw reuses that buffer. The backend test checks the copy on Wayland and X11, and that draws that never present do not show through. The Cocoa path is not built or run here. Co-Authored-By: Claude Opus 5.5 --- src/common/backend.c | 9 +++++++++ src/common/backend.h | 16 ++++++++++++++++ 2 files changed, 25 insertions(+) (limited to 'src/common') diff --git a/src/common/backend.c b/src/common/backend.c index b15233f..bb5ae33 100644 --- a/src/common/backend.c +++ b/src/common/backend.c @@ -322,6 +322,15 @@ bool yasl_surface_poll_event(struct yasl_surface *s, struct yasl_event *ev) return input_queue_pop(&s->input, ev); } +bool yasl_surface_read_pixels(struct yasl_surface *s, uint32_t *out, size_t cap, + struct yasl_vector2i *size) +{ + if (!size) return false; + *size = (struct yasl_vector2i){0}; + if (!s || (!out && cap) || !s->display->ops->surface_read_pixels) return false; + return s->display->ops->surface_read_pixels(s, out, cap, size); +} + bool yasl_surface_set_pointer_mode(struct yasl_surface *s, enum yasl_pointer_mode mode) { if (!s || (mode != YASL_POINTER_NORMAL && mode != YASL_POINTER_RELATIVE)) diff --git a/src/common/backend.h b/src/common/backend.h index 96ce9e7..13ce9b5 100644 --- a/src/common/backend.h +++ b/src/common/backend.h @@ -7,6 +7,8 @@ #include "common/input.h" #include "common/timing.h" +#include + struct yasl_backend_ops; struct yasl_display { @@ -43,6 +45,17 @@ void yasl_surface_clear_timers(struct yasl_surface *s); struct yasl_surface *yasl_create_toplevel(struct yasl_display *d, const struct yasl_window_spec *spec, void *graphics_device); +/* Report a presented CPU frame's extent, and copy it when out can hold it. */ +static inline bool yasl_copy_frame(const uint32_t *pixels, struct yasl_vector2i extent, + uint32_t *out, size_t cap, struct yasl_vector2i *size) +{ + *size = extent; + size_t count = (size_t)extent.x * (size_t)extent.y; + if (!count || count > cap) return false; + memcpy(out, pixels, count * sizeof(*pixels)); + return true; +} + static inline uint64_t yasl_frame_begin(struct yasl_surface *s) { if (++s->frame_generation == 0) ++s->frame_generation; @@ -95,6 +108,9 @@ struct yasl_backend_ops { bool (*surface_place_under_pointer)(struct yasl_surface *s, struct yasl_vector2i at); /* Optional; NULL means the bell is unsupported. */ bool (*surface_bell)(struct yasl_surface *s); + /* Optional; NULL means no readback. Fills size only for an available frame. */ + bool (*surface_read_pixels)(struct yasl_surface *s, uint32_t *out, size_t cap, + struct yasl_vector2i *size); void *(*display_get_native_handle)(struct yasl_display *d); void *(*surface_get_native_handle)(struct yasl_surface *s); bool (*surface_get_native_frame)(struct yasl_surface *s, -- cgit v1.2.3