From cf357d7058910864018c3e3702a9723194fce916 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Thu, 22 Aug 2024 14:36:47 +0300 Subject: [PATCH] Implement window resize throttling --- niri-config/src/lib.rs | 2 + niri-visual-tests/src/test_window.rs | 7 +++- src/layout/mod.rs | 23 +++++++++++ src/layout/workspace.rs | 12 +++++- src/window/mapped.rs | 62 +++++++++++++++++++++++++++- wiki/Configuration:-Debug-Options.md | 18 ++++++++ 6 files changed, 119 insertions(+), 5 deletions(-) diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index 7afb4fc..ca176af 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -1368,6 +1368,8 @@ pub struct DebugConfig { pub render_drm_device: Option, #[knuffel(child)] pub emulate_zero_presentation_time: bool, + #[knuffel(child)] + pub disable_resize_throttling: bool, } #[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)] diff --git a/niri-visual-tests/src/test_window.rs b/niri-visual-tests/src/test_window.rs index dbba519..2068cfb 100644 --- a/niri-visual-tests/src/test_window.rs +++ b/niri-visual-tests/src/test_window.rs @@ -3,7 +3,8 @@ use std::cmp::{max, min}; use std::rc::Rc; use niri::layout::{ - InteractiveResizeData, LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot, + ConfigureIntent, InteractiveResizeData, LayoutElement, LayoutElementRenderElement, + LayoutElementRenderSnapshot, }; use niri::render_helpers::renderer::NiriRenderer; use niri::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement}; @@ -215,6 +216,10 @@ impl LayoutElement for TestWindow { fn set_bounds(&self, _bounds: Size) {} + fn configure_intent(&self) -> ConfigureIntent { + ConfigureIntent::CanSend + } + fn send_pending_configure(&mut self) {} fn is_fullscreen(&self) -> bool { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 39fa283..abcf9d4 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -80,6 +80,19 @@ pub struct InteractiveResizeData { pub edges: ResizeEdge, } +#[derive(Debug, Clone, Copy)] +pub enum ConfigureIntent { + /// A configure is not needed (no changes to server pending state). + NotNeeded, + /// A configure is throttled (due to resizing too fast for example). + Throttled, + /// Can send the configure if it isn't throttled externally (only size changed). + CanSend, + /// Should send the configure regardless of external throttling (something other than size + /// changed). + ShouldSend, +} + pub trait LayoutElement { /// Type that can be used as a unique ID of this element. type Id: PartialEq + std::fmt::Debug; @@ -154,6 +167,7 @@ pub trait LayoutElement { fn set_active_in_column(&mut self, active: bool); fn set_bounds(&self, bounds: Size); + fn configure_intent(&self) -> ConfigureIntent; fn send_pending_configure(&mut self); /// Whether the element is currently fullscreen. @@ -220,6 +234,9 @@ pub struct Options { /// Initial width for new columns. pub default_width: Option, pub animations: niri_config::Animations, + + // Debug flags. + pub disable_resize_throttling: bool, } impl Default for Options { @@ -237,6 +254,7 @@ impl Default for Options { ], default_width: None, animations: Default::default(), + disable_resize_throttling: false, } } } @@ -273,6 +291,7 @@ impl Options { preset_widths, default_width, animations: config.animations.clone(), + disable_resize_throttling: config.debug.disable_resize_throttling, } } @@ -2654,6 +2673,10 @@ mod tests { fn set_bounds(&self, _bounds: Size) {} + fn configure_intent(&self) -> ConfigureIntent { + ConfigureIntent::CanSend + } + fn send_pending_configure(&mut self) {} fn set_active_in_column(&mut self, _active: bool) {} diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 85ba7b4..8f0ae6e 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -15,7 +15,7 @@ use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform}; use super::closing_window::{ClosingWindow, ClosingWindowRenderElement}; use super::tile::{Tile, TileRenderElement}; -use super::{InteractiveResizeData, LayoutElement, Options}; +use super::{ConfigureIntent, InteractiveResizeData, LayoutElement, Options}; use crate::animation::Animation; use crate::input::swipe_tracker::SwipeTracker; use crate::niri_render_elements; @@ -2759,7 +2759,15 @@ impl Workspace { ); win.set_bounds(bounds); - win.send_pending_configure(); + let intent = win.configure_intent(); + + if matches!( + intent, + ConfigureIntent::CanSend | ConfigureIntent::ShouldSend + ) { + win.send_pending_configure(); + } + win.refresh(); } } diff --git a/src/window/mapped.rs b/src/window/mapped.rs index 727473b..6a1fe04 100644 --- a/src/window/mapped.rs +++ b/src/window/mapped.rs @@ -12,14 +12,16 @@ use smithay::output::{self, Output}; use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1; use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; +use smithay::reexports::wayland_server::Resource as _; use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform}; use smithay::wayland::compositor::{remove_pre_commit_hook, with_states, HookId}; -use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface}; +use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface, XdgToplevelSurfaceData}; use super::{ResolvedWindowRules, WindowRef}; use crate::handlers::KdeDecorationsModeState; use crate::layout::{ - InteractiveResizeData, LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot, + ConfigureIntent, InteractiveResizeData, LayoutElement, LayoutElementRenderElement, + LayoutElementRenderSnapshot, }; use crate::niri::WindowOffscreenId; use crate::niri_render_elements; @@ -568,6 +570,62 @@ impl LayoutElement for Mapped { }); } + fn configure_intent(&self) -> ConfigureIntent { + let _span = + trace_span!("configure_intent", surface = ?self.toplevel().wl_surface().id()).entered(); + + with_states(self.toplevel().wl_surface(), |states| { + let attributes = states + .data_map + .get::() + .unwrap() + .lock() + .unwrap(); + + if let Some(server_pending) = &attributes.server_pending { + let current_server = attributes.current_server_state(); + if server_pending != current_server { + // Something changed. Check if the only difference is the size, and if the + // current server size matches the current committed size. + let mut current_server_same_size = current_server.clone(); + current_server_same_size.size = server_pending.size; + if current_server_same_size == *server_pending { + // Only the size changed. Check if the window committed our previous size + // request. + if attributes.current.size == current_server.size { + // The window had committed for our previous size change, so we can + // change the size again. + trace!( + "current size matches server size: {:?}", + attributes.current.size + ); + ConfigureIntent::CanSend + } else { + // The window had not committed for our previous size change yet. Since + // nothing else changed, do not send the new size request yet. This + // throttling is done because some clients do not batch size requests, + // leading to bad behavior with very fast input devices (i.e. a 1000 Hz + // mouse). This throttling also helps interactive resize transactions + // preserve visual consistency. + trace!("throttling resize"); + ConfigureIntent::Throttled + } + } else { + // Something else changed other than the size; send it. + trace!("something changed other than the size"); + ConfigureIntent::ShouldSend + } + } else { + // Nothing changed since the last configure. + ConfigureIntent::NotNeeded + } + } else { + // Nothing changed since the last configure. + ConfigureIntent::NotNeeded + } + }) + } + fn send_pending_configure(&mut self) { if let Some(serial) = self.toplevel().send_pending_configure() { if self.animate_next_configure { diff --git a/wiki/Configuration:-Debug-Options.md b/wiki/Configuration:-Debug-Options.md index a703abd..cce69f4 100644 --- a/wiki/Configuration:-Debug-Options.md +++ b/wiki/Configuration:-Debug-Options.md @@ -20,6 +20,7 @@ debug { dbus-interfaces-in-non-session-instances wait-for-frame-completion-before-queueing emulate-zero-presentation-time + disable-resize-throttling } binds { @@ -128,6 +129,23 @@ debug { } ``` +### `disable-resize-throttling` + +Since: 0.1.9 + +Disable throttling resize events sent to windows. + +By default, when resizing quickly (e.g. interactively), a window will only receive the next size once it has made a commit for the previously requested size. +This is required for resize transactions to work properly, and it also helps certain clients which don't batch incoming resizes from the compositor. + +Disabling resize throttling will send resizes to windows as fast as possible, which is potentially very fast (for example, on a 1000 Hz mouse). + +```kdl +debug { + disable-resize-throttling +} +``` + ### Key Bindings These are not debug options, but rather key bindings.