From 06d3d48aa225e7e116d33ce3692ea28789388db8 Mon Sep 17 00:00:00 2001 From: Gabriel Ford Date: Sun, 12 Oct 2025 14:47:23 -0400 Subject: [PATCH] wlr_scene: fix fullscreen app stuttering when screensharing Problem: The wlroots implementation of wlr_screencopy applys a lock for a brief duration every time a frame is captured. If an output is eligible for direct scanout then this will result in direct scanout being rapidly toggled on and off since the lock count constantly flips between 0 and 1. On some hardware this causes stuttering every time direct scanout is enabled then disabled. Solution: To mitigate this we wait for there to be 0 locks for 120 frames so that we can be relatively confident that screen recording has stopped before we re-enable direct scanout. --- include/wlr/types/wlr_output.h | 2 ++ types/output/output.c | 1 + types/scene/wlr_scene.c | 18 +++++++++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 6a0dd0455..8cbce5047 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -248,6 +248,8 @@ struct wlr_output { int attach_render_locks; // number of locks forcing rendering + int frames_since_locked; + struct wl_list cursors; // wlr_output_cursor.link struct wlr_output_cursor *hardware_cursor; struct wlr_swapchain *cursor_swapchain; diff --git a/types/output/output.c b/types/output/output.c index 1fb0347a0..5e22f4d01 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -341,6 +341,7 @@ void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, .transform = WL_OUTPUT_TRANSFORM_NORMAL, .scale = 1, .commit_seq = 0, + .frames_since_locked = 0, }; wl_list_init(&output->modes); diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index a06b641e3..a3807e412 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -30,6 +30,15 @@ #define DMABUF_FEEDBACK_DEBOUNCE_FRAMES 30 #define HIGHLIGHT_DAMAGE_FADEOUT_TIME 250 +// wlroot's implementation of wlr_screencopy applys a lock for a brief duration +// every time a frame is captured. If an output is eligible for direct scanout +// then this will result in direct scanout being rapidly toggled on and off +// since the lock count constantly flips between 0 and 1. On some hardware this +// causes stuttering every time direct scanout is enabled then disabled. +// +// To mitigate this we wait for there to be 0 locks for 120 frames so that we +// can be relatively confident that screen recording has stopped. +#define SCANOUT_UNLOCK_FRAMES 120 struct wlr_scene_tree *wlr_scene_tree_from_node(struct wlr_scene_node *node) { assert(node->type == WLR_SCENE_NODE_TREE); @@ -2240,12 +2249,19 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, wlr_output_state_set_damage(state, &scene_output->pending_commit_damage); + if (output->attach_render_locks > 0) { + output->frames_since_locked = 0; + } else if (output->attach_render_locks <= SCANOUT_UNLOCK_FRAMES) { + output->frames_since_locked++; + } // We only want to try direct scanout if: + // - We are confident that the screen isn't being captured // - There is only one entry in the render list // - There are no color transforms that need to be applied // - Damage highlight debugging is not enabled enum scene_direct_scanout_result scanout_result = SCANOUT_INELIGIBLE; - if (options->color_transform == NULL && list_len == 1 + if (output->frames_since_locked > SCANOUT_UNLOCK_FRAMES + && options->color_transform == NULL && list_len == 1 && debug_damage != WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) { scanout_result = scene_entry_try_direct_scanout(&list_data[0], state, &render_data); }