From 39e918edc89306661b4f96393b557e25523adc40 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 13 Mar 2026 17:43:52 +0100 Subject: [PATCH] scene: avoid redundant wl_surface.enter/leave events Currently we send wl_surface.enter/leave when a surface is hidden and shown again on the same output. In practice, this happens very often since compositors like river and sway enable and disable the scene nodes of surfaces as part of their atomic transaction strategy involving rendering saved buffers while waiting for clients to submit new buffers of the desired size. The new strategy documented in the new comments avoids sending redundant events in this case. --- include/wlr/types/wlr_scene.h | 2 -- types/scene/surface.c | 58 +++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 46635f4bf..962b01bcc 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -131,8 +131,6 @@ struct wlr_scene_surface { struct wlr_addon addon; struct wl_listener outputs_update; - struct wl_listener output_enter; - struct wl_listener output_leave; struct wl_listener output_sample; struct wl_listener frame_done; struct wl_listener surface_destroy; diff --git a/types/scene/surface.c b/types/scene/surface.c index 7b4d7fd36..c7a53a1b3 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -94,14 +94,44 @@ static void handle_scene_buffer_outputs_update( struct wl_listener *listener, void *data) { struct wlr_scene_surface *surface = wl_container_of(listener, surface, outputs_update); + struct wlr_scene_outputs_update_event *event = data; struct wlr_scene *scene = scene_node_get_root(&surface->buffer->node); // If the surface is no longer visible on any output, keep the last sent // preferred configuration to avoid unnecessary redraws - if (wl_list_empty(&surface->surface->current_outputs)) { + if (event->size == 0) { return; } + // To avoid sending redundant leave/enter events when a surface is hidden and then shown + // without moving to a different output the following policy is implemented: + // + // 1. When a surface transitions from being visible on >0 outputs to being visible on 0 outputs + // don't send any leave events. + // + // 2. When a surface transitions from being visible on 0 outputs to being visible on >0 outputs + // send leave events for all entered outputs on which the surface is no longer visible as + // well as enter events for any outputs not already entered. + struct wlr_surface_output *entered_output; + wl_list_for_each(entered_output, &surface->surface->current_outputs, link) { + bool active = false; + for (size_t i = 0; i < event->size; i++) { + if (entered_output->output == event->active[i]->output) { + active = true; + break; + } + } + if (!active) { + wlr_surface_send_leave(surface->surface, entered_output->output); + } + } + + for (size_t i = 0; i < event->size; i++) { + // This function internally checks if an enter event was already sent for the output + // to avoid sending redundant events. + wlr_surface_send_enter(surface->surface, event->active[i]->output); + } + double scale = get_surface_preferred_buffer_scale(surface->surface); wlr_fractional_scale_v1_notify_scale(surface->surface, scale); wlr_surface_set_preferred_buffer_scale(surface->surface, ceil(scale)); @@ -114,24 +144,6 @@ static void handle_scene_buffer_outputs_update( } } -static void handle_scene_buffer_output_enter( - struct wl_listener *listener, void *data) { - struct wlr_scene_surface *surface = - wl_container_of(listener, surface, output_enter); - struct wlr_scene_output *output = data; - - wlr_surface_send_enter(surface->surface, output->output); -} - -static void handle_scene_buffer_output_leave( - struct wl_listener *listener, void *data) { - struct wlr_scene_surface *surface = - wl_container_of(listener, surface, output_leave); - struct wlr_scene_output *output = data; - - wlr_surface_send_leave(surface->surface, output->output); -} - static void handle_scene_buffer_output_sample( struct wl_listener *listener, void *data) { struct wlr_scene_surface *surface = @@ -380,8 +392,6 @@ static void surface_addon_destroy(struct wlr_addon *addon) { wlr_addon_finish(&surface->addon); wl_list_remove(&surface->outputs_update.link); - wl_list_remove(&surface->output_enter.link); - wl_list_remove(&surface->output_leave.link); wl_list_remove(&surface->output_sample.link); wl_list_remove(&surface->frame_done.link); wl_list_remove(&surface->surface_destroy.link); @@ -427,12 +437,6 @@ struct wlr_scene_surface *wlr_scene_surface_create(struct wlr_scene_tree *parent surface->outputs_update.notify = handle_scene_buffer_outputs_update; wl_signal_add(&scene_buffer->events.outputs_update, &surface->outputs_update); - surface->output_enter.notify = handle_scene_buffer_output_enter; - wl_signal_add(&scene_buffer->events.output_enter, &surface->output_enter); - - surface->output_leave.notify = handle_scene_buffer_output_leave; - wl_signal_add(&scene_buffer->events.output_leave, &surface->output_leave); - surface->output_sample.notify = handle_scene_buffer_output_sample; wl_signal_add(&scene_buffer->events.output_sample, &surface->output_sample);