From 73c473e2d6b0c458e324a7f3e563300c62f86d6b Mon Sep 17 00:00:00 2001 From: Ottatop Date: Fri, 8 Sep 2023 19:56:40 -0500 Subject: [PATCH] Pause rendering until windows are idle --- src/backend/winit.rs | 10 +++++ src/handlers.rs | 2 +- src/handlers/xdg_shell.rs | 54 ++-------------------- src/handlers/xwayland.rs | 54 +--------------------- src/layout.rs | 92 ++++++++++++++++++++++++++++++++++---- src/state.rs | 25 +++-------- src/window/window_state.rs | 18 ++++++++ 7 files changed, 121 insertions(+), 134 deletions(-) diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 6a472c7..6218a35 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -232,6 +232,16 @@ pub fn run_winit() -> anyhow::Result<()> { pointer_element.set_status(state.cursor_status.clone()); + if state.pause_rendering { + state.space.refresh(); + state.popup_manager.cleanup(); + display + .flush_clients() + .expect("failed to flush client buffers"); + + return TimeoutAction::ToDuration(Duration::from_millis(1)); + } + let Backend::Winit(backend) = &mut state.backend else { unreachable!() }; let full_redraw = &mut backend.full_redraw; *full_redraw = full_redraw.saturating_sub(1); diff --git a/src/handlers.rs b/src/handlers.rs index df370fb..9725706 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -95,7 +95,7 @@ impl CompositorHandler for State { } fn commit(&mut self, surface: &WlSurface) { - // tracing::debug!("commit"); + // tracing::debug!("commit on surface {:?}", surface); X11Wm::commit_hook::(surface); diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index ebbde52..85cc4bf 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -15,7 +15,7 @@ use smithay::{ }, utils::{Serial, SERIAL_COUNTER}, wayland::{ - compositor::{self, CompositorHandler}, + compositor::{self}, shell::xdg::{ Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, @@ -26,7 +26,7 @@ use smithay::{ use crate::{ focus::FocusTarget, state::{State, WithState}, - window::{window_state::LocationRequestState, WindowElement, BLOCKER_COUNTER}, + window::{window_state::LocationRequestState, WindowElement}, }; impl XdgShellHandler for State { @@ -65,26 +65,6 @@ impl XdgShellHandler for State { tracing::debug!("new window, tags are {:?}", state.tags); }); - let windows_on_output = self - .windows - .iter() - .filter(|win| { - win.with_state(|state| { - self.focus_state - .focused_output - .as_ref() - .expect("no focused output") - .with_state(|op_state| { - op_state - .tags - .iter() - .any(|tag| state.tags.iter().any(|tg| tg == tag)) - }) - }) - }) - .cloned() - .collect::>(); - // note to self: don't reorder this // TODO: fix it so that reordering this doesn't break stuff self.windows.push(window.clone()); @@ -116,34 +96,6 @@ impl XdgShellHandler for State { if let Some(focused_output) = data.state.focus_state.focused_output.clone() { data.state.update_windows(&focused_output); - BLOCKER_COUNTER.store(1, std::sync::atomic::Ordering::SeqCst); - tracing::debug!( - "blocker {}", - BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst) - ); - for win in windows_on_output.iter() { - if let Some(surf) = win.wl_surface() { - compositor::add_blocker(&surf, crate::window::WindowBlocker); - } - } - let clone = window.clone(); - data.state.loop_handle.insert_idle(|data| { - crate::state::schedule_on_commit(data, vec![clone], move |data| { - BLOCKER_COUNTER.store(0, std::sync::atomic::Ordering::SeqCst); - tracing::debug!( - "blocker {}", - BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst) - ); - for client in windows_on_output - .iter() - .filter_map(|win| win.wl_surface()?.client()) - { - data.state - .client_compositor_state(&client) - .blocker_cleared(&mut data.state, &data.display.handle()) - } - }) - }); } data.state.loop_handle.insert_idle(move |data| { data.state @@ -283,7 +235,7 @@ impl XdgShellHandler for State { match &configure { Configure::Toplevel(configure) => { if configure.serial >= serial { - // tracing::debug!("acked configure, new loc is {:?}", new_loc); + tracing::debug!("acked configure, new loc is {:?}", new_loc); state.loc_request_state = LocationRequestState::Acknowledged(new_loc); } diff --git a/src/handlers/xwayland.rs b/src/handlers/xwayland.rs index 1bd23d0..b13b8e4 100644 --- a/src/handlers/xwayland.rs +++ b/src/handlers/xwayland.rs @@ -1,10 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later use smithay::{ - reexports::wayland_server::Resource, utils::{Logical, Point, Rectangle, SERIAL_COUNTER}, wayland::{ - compositor::{self, CompositorHandler}, data_device::{ clear_data_device_selection, current_data_device_selection_userdata, request_data_device_client_selection, set_data_device_selection, @@ -23,7 +21,7 @@ use smithay::{ use crate::{ focus::FocusTarget, state::{CalloopData, WithState}, - window::{window_state::FloatingOrTiled, WindowBlocker, WindowElement, BLOCKER_COUNTER}, + window::{window_state::FloatingOrTiled, WindowElement}, }; impl XwmHandler for CalloopData { @@ -127,28 +125,6 @@ impl XwmHandler for CalloopData { }); } - let windows_on_output = self - .state - .windows - .iter() - .filter(|win| { - win.with_state(|state| { - self.state - .focus_state - .focused_output - .as_ref() - .expect("no focused output") - .with_state(|op_state| { - op_state - .tags - .iter() - .any(|tag| state.tags.iter().any(|tg| tg == tag)) - }) - }) - }) - .cloned() - .collect::>(); - self.state.windows.push(window.clone()); self.state.focus_state.set_focus(window.clone()); @@ -157,34 +133,6 @@ impl XwmHandler for CalloopData { if let Some(focused_output) = self.state.focus_state.focused_output.clone() { self.state.update_windows(&focused_output); - BLOCKER_COUNTER.store(1, std::sync::atomic::Ordering::SeqCst); - tracing::debug!( - "blocker {}", - BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst) - ); - for win in windows_on_output.iter() { - if let Some(surf) = win.wl_surface() { - compositor::add_blocker(&surf, WindowBlocker); - } - } - let clone = window.clone(); - self.state.loop_handle.insert_idle(move |data| { - crate::state::schedule_on_commit(data, vec![clone.clone()], move |data| { - BLOCKER_COUNTER.store(0, std::sync::atomic::Ordering::SeqCst); - tracing::debug!( - "blocker {}", - BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst) - ); - for client in windows_on_output - .iter() - .filter_map(|win| win.wl_surface()?.client()) - { - data.state - .client_compositor_state(&client) - .blocker_cleared(&mut data.state, &data.display.handle()) - } - }); - }); } self.state.loop_handle.insert_idle(move |data| { data.state diff --git a/src/layout.rs b/src/layout.rs index 1477b97..3518994 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -4,14 +4,16 @@ use itertools::{Either, Itertools}; use smithay::{ desktop::layer_map_for_output, output::Output, + reexports::wayland_server::Resource, utils::{Logical, Point, Rectangle, Size}, + wayland::compositor::{self, CompositorHandler}, }; use crate::{ state::{State, WithState}, window::{ window_state::{FloatingOrTiled, FullscreenOrMaximized, LocationRequestState}, - WindowElement, + WindowElement, BLOCKER_COUNTER, }, }; @@ -110,6 +112,9 @@ impl State { } } + let mut pending_wins = Vec::<(Point<_, _>, WindowElement)>::new(); + let mut non_pending_wins = Vec::<(Point<_, _>, WindowElement)>::new(); + for window in windows_on_foc_tags.iter() { window.with_state(|state| { if let LocationRequestState::Sent(loc) = state.loc_request_state { @@ -119,34 +124,103 @@ impl State { // map the window. if !win.toplevel().has_pending_changes() { state.loc_request_state = LocationRequestState::Idle; - self.space.map_element(window.clone(), loc, false); + non_pending_wins.push((loc, window.clone())); + // TODO: wait for windows with pending state to ack and commit + // self.space.map_element(window.clone(), loc, false); } else { let serial = win.toplevel().send_configure(); state.loc_request_state = LocationRequestState::Requested(serial, loc); + pending_wins.push((loc, window.clone())); } } WindowElement::X11(surface) => { // already configured, just need to map // maybe wait for all wayland windows to commit before mapping - self.space.map_element(window.clone(), loc, false); + // self.space.map_element(window.clone(), loc, false); surface .set_mapped(true) .expect("failed to set x11 win to mapped"); state.loc_request_state = LocationRequestState::Idle; + non_pending_wins.push((loc, window.clone())); } } } }); } - self.loop_handle.insert_idle(|data| { - crate::state::schedule_on_commit(data, windows_on_foc_tags, |dt| { - for win in windows_not_on_foc_tags { - dt.state.space.unmap_elem(&win); + BLOCKER_COUNTER.store(1, std::sync::atomic::Ordering::SeqCst); + tracing::debug!( + "blocker {}", + BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst) + ); + + let start_time = self.clock.now(); + + // Pause rendering. Here we'll wait until all windows have ack'ed and committed, + // then resume rendering. This prevents flickering because some windows will commit before + // others. + // + // This *will* cause everything to freeze for a few frames, but it should'nt impact + // anything meaningfully. + self.pause_rendering = true; + + for (_loc, win) in pending_wins.iter() { + if let Some(surf) = win.wl_surface() { + tracing::debug!("adding blocker"); + compositor::add_blocker(&surf, crate::window::WindowBlocker); + } + } + + let pending_wins_clone = pending_wins.clone(); + + self.schedule( + move |_data| { + pending_wins_clone.iter().all(|(_, win)| { + win.with_state(|state| state.loc_request_state.is_acknowledged()) + }) + }, + move |data| { + // remove and trigger blockers + BLOCKER_COUNTER.store(0, std::sync::atomic::Ordering::SeqCst); + tracing::debug!( + "blocker {}", + BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst) + ); + for client in pending_wins + .iter() + .filter_map(|(_, win)| win.wl_surface()?.client()) + { + data.state + .client_compositor_state(&client) + .blocker_cleared(&mut data.state, &data.display.handle()) } - }) - }); + + // schedule on all idle + data.state.schedule( + move |_dt| { + pending_wins.iter().all(|(_, win)| { + win.with_state(|state| state.loc_request_state.is_idle()) + }) + }, + move |dt| { + for (loc, win) in non_pending_wins { + dt.state.space.map_element(win, loc, false); + } + for win in windows_not_on_foc_tags { + dt.state.space.unmap_elem(&win); + } + dt.state.pause_rendering = false; + let finish_time = + smithay::utils::Time::elapsed(&start_time, dt.state.clock.now()); + tracing::debug!( + "spent {} microseconds not rendering", + finish_time.as_micros() + ); + }, + ); + }, + ); } } diff --git a/src/state.rs b/src/state.rs index 5c5f752..262972f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -24,7 +24,7 @@ use crate::{ grab::resize_grab::ResizeSurfaceState, metaconfig::Metaconfig, tag::TagId, - window::{window_state::LocationRequestState, WindowElement}, + window::WindowElement, }; use anyhow::Context; use calloop::futures::Scheduler; @@ -50,7 +50,7 @@ use smithay::{ Display, DisplayHandle, }, }, - utils::{Clock, IsAlive, Logical, Monotonic, Point, Size}, + utils::{Clock, Logical, Monotonic, Point, Size}, wayland::{ compositor::{self, CompositorClientState, CompositorState}, data_device::DataDeviceState, @@ -138,25 +138,8 @@ pub struct State { pub xwayland: XWayland, pub xwm: Option, pub xdisplay: Option, -} -/// Schedule something to be done when windows have finished committing and have become -/// idle. -pub fn schedule_on_commit(data: &mut CalloopData, windows: Vec, on_commit: F) -where - F: FnOnce(&mut CalloopData) + 'static, -{ - for window in windows.iter().filter(|win| win.alive()) { - if window.with_state(|state| !matches!(state.loc_request_state, LocationRequestState::Idle)) - { - data.state.loop_handle.insert_idle(|data| { - schedule_on_commit(data, windows, on_commit); - }); - return; - } - } - - on_commit(data); + pub pause_rendering: bool, } impl State { @@ -376,6 +359,8 @@ impl State { xwayland, xwm: None, xdisplay: None, + + pause_rendering: false, }) } diff --git a/src/window/window_state.rs b/src/window/window_state.rs index 9cf99d2..0b346fc 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -114,6 +114,24 @@ pub enum LocationRequestState { Acknowledged(Point), } +impl LocationRequestState { + /// Returns `true` if the location request state is [`Idle`]. + /// + /// [`Idle`]: LocationRequestState::Idle + #[must_use] + pub fn is_idle(&self) -> bool { + matches!(self, Self::Idle) + } + + /// Returns `true` if the location request state is [`Acknowledged`]. + /// + /// [`Acknowledged`]: LocationRequestState::Acknowledged + #[must_use] + pub fn is_acknowledged(&self) -> bool { + matches!(self, Self::Acknowledged(..)) + } +} + impl WindowElement { /// This method uses a [`RefCell`]. pub fn toggle_floating(&self) {