From b417d0ed57d49ac2eebba0270237318bdcf47903 Mon Sep 17 00:00:00 2001 From: Mars-Wave <57585293+Mars-Wave@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:56:15 +0100 Subject: [PATCH 1/4] Reuse screencopy buffers between frames --- src/outputs.c | 93 ++++++++++++++++++++++++++++++++----------------- src/wdisplays.h | 3 ++ 2 files changed, 65 insertions(+), 31 deletions(-) diff --git a/src/outputs.c b/src/outputs.c index 024a686..3b8b142 100644 --- a/src/outputs.c +++ b/src/outputs.c @@ -141,15 +141,24 @@ void wd_apply_state(struct wd_state *state, struct wl_list *new_outputs, wl_display_roundtrip(display); } -static void wd_frame_destroy(struct wd_frame *frame) { - if (frame->pixels != NULL) - munmap(frame->pixels, frame->height * frame->stride); +static void release_buffer(struct wd_frame *frame) { + if (frame->data != NULL) + munmap(frame->data, frame->height * frame->stride); if (frame->buffer != NULL) wl_buffer_destroy(frame->buffer); if (frame->pool != NULL) wl_shm_pool_destroy(frame->pool); if (frame->capture_fd != -1) close(frame->capture_fd); + frame->data = NULL; + frame->pixels = NULL; + frame->buffer = NULL; + frame->pool = NULL; + frame->capture_fd = -1; +} + +static void wd_frame_destroy(struct wd_frame *frame) { + release_buffer(frame); if (frame->wlr_frame != NULL) zwlr_screencopy_frame_v1_destroy(frame->wlr_frame); @@ -199,20 +208,35 @@ static void capture_buffer(void *data, goto err; } - size_t size = stride * height; - frame->capture_fd = create_shm_file(size, "/wd-%s", frame->output->name); - if (frame->capture_fd == -1) { - goto err; - } + if (frame->buffer == NULL || frame->format != format + || frame->width != width || frame->height != height + || frame->stride != stride) { + release_buffer(frame); - frame->pool = wl_shm_create_pool(frame->output->state->shm, - frame->capture_fd, size); - frame->buffer = wl_shm_pool_create_buffer(frame->pool, 0, - width, height, stride, format); + size_t size = stride * height; + frame->capture_fd = create_shm_file(size, "/wd-%s", frame->output->name); + if (frame->capture_fd == -1) { + goto err; + } + + frame->data = mmap(NULL, size, PROT_READ, MAP_SHARED, + frame->capture_fd, 0); + if (frame->data == MAP_FAILED) { + frame->data = NULL; + fprintf(stderr, "mmap: %d: %s\n", frame->capture_fd, strerror(errno)); + goto err; + } + + frame->pool = wl_shm_create_pool(frame->output->state->shm, + frame->capture_fd, size); + frame->buffer = wl_shm_pool_create_buffer(frame->pool, 0, + width, height, stride, format); + frame->format = format; + frame->stride = stride; + frame->width = width; + frame->height = height; + } zwlr_screencopy_frame_v1_copy(copy_frame, frame->buffer); - frame->stride = stride; - frame->width = width; - frame->height = height; frame->swap_rgb = format == WL_SHM_FORMAT_ABGR8888 || format == WL_SHM_FORMAT_XBGR8888; @@ -232,26 +256,25 @@ static void capture_ready(void *data, struct zwlr_screencopy_frame_v1 *wlr_frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { struct wd_frame *frame = data; + struct wd_output *output = frame->output; - frame->pixels = mmap(NULL, frame->stride * frame->height, - PROT_READ, MAP_SHARED, frame->capture_fd, 0); - if (frame->pixels == MAP_FAILED) { - frame->pixels = NULL; - fprintf(stderr, "mmap: %d: %s\n", frame->capture_fd, strerror(errno)); - wd_frame_destroy(frame); - return; - } else { - uint64_t tv_sec = (uint64_t) tv_sec_hi << 32 | tv_sec_lo; - frame->tick = (tv_sec * 1000000) + (tv_nsec / 1000); - } + frame->pixels = frame->data; + uint64_t tv_sec = (uint64_t) tv_sec_hi << 32 | tv_sec_lo; + frame->tick = (tv_sec * 1000000) + (tv_nsec / 1000); zwlr_screencopy_frame_v1_destroy(frame->wlr_frame); frame->wlr_frame = NULL; struct wd_frame *frame_iter, *frame_tmp; - wl_list_for_each_safe(frame_iter, frame_tmp, &frame->output->frames, link) { + wl_list_for_each_safe(frame_iter, frame_tmp, &output->frames, link) { if (frame != frame_iter) { - wd_frame_destroy(frame_iter); + if (output->spare == NULL) { + wl_list_remove(&frame_iter->link); + wl_list_init(&frame_iter->link); + output->spare = frame_iter; + } else { + wd_frame_destroy(frame_iter); + } } } } @@ -290,9 +313,14 @@ void wd_capture_frame(struct wd_state *state) { struct wd_output *output; wl_list_for_each(output, &state->outputs, link) { - struct wd_frame *frame = calloc(1, sizeof(*frame)); - frame->output = output; - frame->capture_fd = -1; + struct wd_frame *frame = output->spare; + output->spare = NULL; + if (frame == NULL) { + frame = calloc(1, sizeof(*frame)); + frame->output = output; + frame->capture_fd = -1; + } + frame->pixels = NULL; frame->wlr_frame = zwlr_screencopy_manager_v1_capture_output(state->copy_manager, 1, output->wl_output); @@ -307,6 +335,9 @@ static void wd_output_destroy(struct wd_output *output) { wl_list_for_each_safe(frame, frame_tmp, &output->frames, link) { wd_frame_destroy(frame); } + if (output->spare != NULL) { + wd_frame_destroy(output->spare); + } if (output->state->layer_shell != NULL) { wd_destroy_overlay(output); } diff --git a/src/wdisplays.h b/src/wdisplays.h index 4824017..dbf1900 100644 --- a/src/wdisplays.h +++ b/src/wdisplays.h @@ -49,6 +49,7 @@ struct wd_output { char *name; struct wl_list frames; + struct wd_frame *spare; GtkWidget *overlay_window; struct zwlr_layer_surface_v1 *overlay_layer_surface; }; @@ -62,8 +63,10 @@ struct wd_frame { unsigned stride; unsigned width; unsigned height; + uint32_t format; struct wl_shm_pool *pool; struct wl_buffer *buffer; + uint8_t *data; uint8_t *pixels; uint64_t tick; bool y_invert; From ef4c74791aa7165cda135bed4af5c9fc4b7b0979 Mon Sep 17 00:00:00 2001 From: Mars-Wave <57585293+Mars-Wave@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:42:34 +0100 Subject: [PATCH 2/4] Leave the cursor out of screen previews --- src/outputs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/outputs.c b/src/outputs.c index 3b8b142..4a61837 100644 --- a/src/outputs.c +++ b/src/outputs.c @@ -322,7 +322,7 @@ void wd_capture_frame(struct wd_state *state) { } frame->pixels = NULL; frame->wlr_frame = - zwlr_screencopy_manager_v1_capture_output(state->copy_manager, 1, + zwlr_screencopy_manager_v1_capture_output(state->copy_manager, 0, output->wl_output); zwlr_screencopy_frame_v1_add_listener(frame->wlr_frame, &capture_listener, frame); From 63479fc178c4d22cb665ba7053e9ebd325cb86c3 Mon Sep 17 00:00:00 2001 From: Mars-Wave <57585293+Mars-Wave@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:04:38 +0100 Subject: [PATCH 3/4] Capture screen previews a few times per second --- src/main.c | 31 ++++++++++++++++++++++++++----- src/wdisplays.h | 2 ++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/main.c b/src/main.c index 2484a63..6174902 100644 --- a/src/main.c +++ b/src/main.c @@ -20,6 +20,7 @@ __attribute__((noreturn)) void wd_fatal_error(int status, const char *message) { #define MIN_ZOOM (1./1000.) #define MAX_ZOOM 1000. #define CANVAS_MARGIN 40 +#define CAPTURE_INTERVAL_MS 200 static const char *APP_PREFIX = "app"; @@ -152,6 +153,13 @@ static void cache_scroll(struct wd_state *state) { static gboolean redraw_canvas(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer data); +static gboolean capture_tick(gpointer data) { + struct wd_state *state = data; + state->capture_due = true; + gtk_gl_area_queue_render(GTK_GL_AREA(state->canvas)); + return G_SOURCE_CONTINUE; +} + static void update_tick_callback(struct wd_state *state) { bool any_animate = FALSE; struct wd_render_head_data *render; @@ -162,7 +170,7 @@ static void update_tick_callback(struct wd_state *state) { break; } } - if (!any_animate && !state->capture) { + if (!any_animate) { if (state->canvas_tick != -1) { gtk_widget_remove_tick_callback(state->canvas, state->canvas_tick); state->canvas_tick = -1; @@ -171,6 +179,16 @@ static void update_tick_callback(struct wd_state *state) { state->canvas_tick = gtk_widget_add_tick_callback(state->canvas, redraw_canvas, state, NULL); } + if (!state->capture) { + if (state->capture_timeout != -1) { + g_source_remove(state->capture_timeout); + state->capture_timeout = -1; + } + } else if (state->capture_timeout == -1) { + state->capture_due = true; + state->capture_timeout = + g_timeout_add(CAPTURE_INTERVAL_MS, capture_tick, state); + } gtk_gl_area_queue_render(GTK_GL_AREA(state->canvas)); gtk_gl_area_set_auto_render(GTK_GL_AREA(state->canvas), state->capture); } @@ -415,6 +433,8 @@ static void cleanup(GtkWidget *window, gpointer data) { g_source_remove(state->reset_idle); if (state->apply_idle != -1) g_source_remove(state->apply_idle); + if (state->capture_timeout != -1) + g_source_remove(state->capture_timeout); g_object_unref(state->grab_cursor); g_object_unref(state->grabbing_cursor); g_object_unref(state->move_cursor); @@ -523,7 +543,10 @@ static void canvas_render(GtkGLArea *area, GdkGLContext *context, gpointer data) GdkFrameClock *clock = gtk_widget_get_frame_clock(state->canvas); uint64_t tick = gdk_frame_clock_get_frame_time(clock); - wd_capture_frame(state); + if (state->capture_due) { + state->capture_due = false; + wd_capture_frame(state); + } struct wd_head *head; wl_list_for_each(head, &state->heads, link) { @@ -864,9 +887,6 @@ static void auto_apply_selected(GSimpleAction *action, GVariant *param, gpointer static gboolean redraw_canvas(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer data) { struct wd_state *state = data; - if (state->capture) { - wd_capture_frame(state); - } update_tick_callback(state); queue_canvas_draw(state); return G_SOURCE_CONTINUE; @@ -922,6 +942,7 @@ static void activate(GtkApplication* app, gpointer user_data) { struct wd_state *state = wd_state_create(); state->zoom = DEFAULT_ZOOM; state->canvas_tick = -1; + state->capture_timeout = -1; state->apply_idle = -1; state->reset_idle = -1; diff --git a/src/wdisplays.h b/src/wdisplays.h index dbf1900..2159d93 100644 --- a/src/wdisplays.h +++ b/src/wdisplays.h @@ -223,6 +223,8 @@ struct wd_state { GdkCursor *move_cursor; unsigned int canvas_tick; + unsigned int capture_timeout; + bool capture_due; struct wd_gl_data *gl_data; struct wd_render_data render; }; From 294642363f1717927b9ecc13d21900b7bf77e871 Mon Sep 17 00:00:00 2001 From: Mars-Wave <57585293+Mars-Wave@users.noreply.github.com> Date: Sun, 23 Nov 2025 22:05:32 +0100 Subject: [PATCH 4/4] Recycle failed capture buffers, release when off --- src/main.c | 1 + src/outputs.c | 39 +++++++++++++++++++++++++++++++-------- src/wdisplays.h | 5 +++++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/main.c b/src/main.c index 6174902..b01895d 100644 --- a/src/main.c +++ b/src/main.c @@ -183,6 +183,7 @@ static void update_tick_callback(struct wd_state *state) { if (state->capture_timeout != -1) { g_source_remove(state->capture_timeout); state->capture_timeout = -1; + wd_capture_release(state); } } else if (state->capture_timeout == -1) { state->capture_due = true; diff --git a/src/outputs.c b/src/outputs.c index 4a61837..94a19db 100644 --- a/src/outputs.c +++ b/src/outputs.c @@ -252,6 +252,21 @@ static void capture_flags(void *data, frame->y_invert = !!(flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT); } +static void keep_as_spare(struct wd_frame *frame) { + struct wd_output *output = frame->output; + if (output->spare != NULL) { + wd_frame_destroy(frame); + return; + } + if (frame->wlr_frame != NULL) { + zwlr_screencopy_frame_v1_destroy(frame->wlr_frame); + frame->wlr_frame = NULL; + } + wl_list_remove(&frame->link); + wl_list_init(&frame->link); + output->spare = frame; +} + static void capture_ready(void *data, struct zwlr_screencopy_frame_v1 *wlr_frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { @@ -268,13 +283,7 @@ static void capture_ready(void *data, struct wd_frame *frame_iter, *frame_tmp; wl_list_for_each_safe(frame_iter, frame_tmp, &output->frames, link) { if (frame != frame_iter) { - if (output->spare == NULL) { - wl_list_remove(&frame_iter->link); - wl_list_init(&frame_iter->link); - output->spare = frame_iter; - } else { - wd_frame_destroy(frame_iter); - } + keep_as_spare(frame_iter); } } } @@ -282,7 +291,7 @@ static void capture_ready(void *data, static void capture_failed(void *data, struct zwlr_screencopy_frame_v1 *wlr_frame) { struct wd_frame *frame = data; - wd_frame_destroy(frame); + keep_as_spare(frame); } struct zwlr_screencopy_frame_v1_listener capture_listener = { @@ -330,6 +339,20 @@ void wd_capture_frame(struct wd_state *state) { } } +void wd_capture_release(struct wd_state *state) { + struct wd_output *output; + wl_list_for_each(output, &state->outputs, link) { + struct wd_frame *frame, *frame_tmp; + wl_list_for_each_safe(frame, frame_tmp, &output->frames, link) { + wd_frame_destroy(frame); + } + if (output->spare != NULL) { + wd_frame_destroy(output->spare); + output->spare = NULL; + } + } +} + static void wd_output_destroy(struct wd_output *output) { struct wd_frame *frame, *frame_tmp; wl_list_for_each_safe(frame, frame_tmp, &output->frames, link) { diff --git a/src/wdisplays.h b/src/wdisplays.h index 2159d93..e5cbd0d 100644 --- a/src/wdisplays.h +++ b/src/wdisplays.h @@ -285,6 +285,11 @@ void wd_capture_frame(struct wd_state *state); */ void wd_capture_wait(struct wd_state *state, struct wl_display *display); +/* + * Frees all captured frames and their buffers. + */ +void wd_capture_release(struct wd_state *state); + /* * Updates the UI stack of all heads. Does not update individual head forms. * Useful for when a display is plugged/unplugged and we want to add/remove