Implement window resize throttling

This commit is contained in:
Ivan Molodetskikh 2024-08-22 14:36:47 +03:00
parent 618fa08aa5
commit cf357d7058
6 changed files with 119 additions and 5 deletions

View File

@ -1368,6 +1368,8 @@ pub struct DebugConfig {
pub render_drm_device: Option<PathBuf>,
#[knuffel(child)]
pub emulate_zero_presentation_time: bool,
#[knuffel(child)]
pub disable_resize_throttling: bool,
}
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]

View File

@ -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<i32, Logical>) {}
fn configure_intent(&self) -> ConfigureIntent {
ConfigureIntent::CanSend
}
fn send_pending_configure(&mut self) {}
fn is_fullscreen(&self) -> bool {

View File

@ -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<i32, Logical>);
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<ColumnWidth>,
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<i32, Logical>) {}
fn configure_intent(&self) -> ConfigureIntent {
ConfigureIntent::CanSend
}
fn send_pending_configure(&mut self) {}
fn set_active_in_column(&mut self, _active: bool) {}

View File

@ -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<W: LayoutElement> Workspace<W> {
);
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();
}
}

View File

@ -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::<XdgToplevelSurfaceData>()
.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 {

View File

@ -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`
<sup>Since: 0.1.9</sup>
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.