Scope redraw requests and avoid redundant animation fallback wakeups
Instrument redraw sources, per-output request coalescing, and animation fallback scheduling. Route local visual changes to their affected outputs while preserving deferred grab and gesture updates. Schedule fallback recovery using each orphaned animation output's refresh interval, suppress it while a normal frame chain exists, and retain requested final frames after scheduling failures. Add mixed-refresh, cursor footprint, frame-chain, and disconnect regressions and document measurement limits.
This commit is contained in:
1 parent
28e03a2624
commit
6f2951c6b0
27 files changed
+965
-139
No files matched your search
@@ -72,10 +72,6 @@ impl Ferese {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn animation_fallback_deadline(&self, interval: std::time::Duration) -> Instant {
|
||||
self.last_animation_tick + interval.saturating_mul(2)
|
||||
}
|
||||
|
||||
pub(crate) fn reset_animation_clock(&mut self) {
|
||||
self.last_animation_tick = Instant::now();
|
||||
#[cfg(feature = "resize-metrics")]
|
||||
|
||||
@@ -45,7 +45,7 @@ impl Ferese {
|
||||
return;
|
||||
}
|
||||
|
||||
self.relayout();
|
||||
self.relayout_window(window);
|
||||
}
|
||||
|
||||
pub fn toggle_focused_fullscreen(&mut self) {
|
||||
@@ -58,7 +58,7 @@ impl Ferese {
|
||||
return;
|
||||
}
|
||||
|
||||
self.relayout();
|
||||
self.relayout_window(window);
|
||||
}
|
||||
|
||||
pub fn toggle_focused_maximized(&mut self) {
|
||||
@@ -106,7 +106,7 @@ impl Ferese {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.relayout();
|
||||
self.relayout_window(window);
|
||||
}
|
||||
|
||||
pub(crate) fn output_has_fullscreen(&self, output: &Output) -> bool {
|
||||
@@ -124,7 +124,7 @@ impl Ferese {
|
||||
|
||||
pub fn set_window_fullscreen(&mut self, window: WindowId, enabled: bool) {
|
||||
match self.workspaces.set_fullscreen(window, enabled) {
|
||||
Ok(true) => self.relayout(),
|
||||
Ok(true) => self.relayout_window(window),
|
||||
Ok(false) => {}
|
||||
Err(error) => {
|
||||
tracing::error!(%error, ?window, enabled, "failed to set fullscreen window")
|
||||
@@ -195,6 +195,16 @@ impl Ferese {
|
||||
// Move/resize/cancel callbacks run with Smithay's pointer mutex held.
|
||||
// Cursor rendering reads current_location(), which would lock it again.
|
||||
// The event-loop epilogue coalesces this redraw after the grab returns.
|
||||
let outputs = self
|
||||
.space
|
||||
.outputs()
|
||||
.filter(|output| self.window_belongs_to_output(id, output))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
for output in outputs {
|
||||
self.defer_output_redraw(output);
|
||||
}
|
||||
|
||||
self.cursor_redraw_pending = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,8 +73,9 @@ impl Ferese {
|
||||
.as_mut()
|
||||
.and_then(|cycle| cycle.advance(reverse, &available))
|
||||
{
|
||||
let previous = self.overview.selection_state();
|
||||
self.overview.select_window(id);
|
||||
crate::backends::direct::render_all(self);
|
||||
self.redraw_overview_selection(previous);
|
||||
} else {
|
||||
self.cancel_focus_cycle();
|
||||
}
|
||||
|
||||
@@ -50,6 +50,35 @@ impl Ferese {
|
||||
}
|
||||
|
||||
pub fn relayout(&mut self) {
|
||||
self.relayout_outputs(None);
|
||||
}
|
||||
|
||||
pub(crate) fn relayout_window(&mut self, id: WindowId) {
|
||||
let outputs = self
|
||||
.space
|
||||
.outputs()
|
||||
.filter(|output| self.window_belongs_to_output(id, output))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
self.relayout_on(&outputs);
|
||||
}
|
||||
|
||||
pub(crate) fn relayout_on(&mut self, outputs: &[Output]) {
|
||||
self.relayout_outputs(Some(outputs.to_vec()));
|
||||
}
|
||||
|
||||
fn relayout_outputs(&mut self, mut redraw_outputs: Option<Vec<Output>>) {
|
||||
if let Some(outputs) = redraw_outputs.as_mut() {
|
||||
outputs.extend(
|
||||
self.space
|
||||
.outputs()
|
||||
.filter(|output| {
|
||||
self.output_has_animations(output) || self.output_has_pending_visual_changes(output)
|
||||
})
|
||||
.cloned(),
|
||||
);
|
||||
}
|
||||
|
||||
self.refresh_input_capture_zones();
|
||||
let visible_workspaces = self.visible_workspace_ids();
|
||||
self.reconcile_workspaces(&visible_workspaces);
|
||||
@@ -384,7 +413,11 @@ impl Ferese {
|
||||
self.retarget_overview();
|
||||
self.send_shell_snapshots();
|
||||
|
||||
crate::backends::direct::render_all(self);
|
||||
if let Some(outputs) = redraw_outputs {
|
||||
crate::backends::direct::render_on(self, &outputs);
|
||||
} else {
|
||||
crate::backends::direct::render_all(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn raise_window(&mut self, window: &Window, activate: bool) {
|
||||
|
||||
@@ -72,7 +72,15 @@ impl Ferese {
|
||||
swipe.progress = progress;
|
||||
}
|
||||
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
|
||||
self.cursor_redraw_pending = true;
|
||||
if let Some(output) = self
|
||||
.focus_swipe
|
||||
.as_ref()
|
||||
.and_then(|swipe| self.output_workspaces.output_for_workspace(swipe.workspace))
|
||||
.and_then(|id| self.outputs_by_id.get(&id))
|
||||
.cloned()
|
||||
{
|
||||
self.defer_output_redraw(output);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn finish_focus_swipe(&mut self, direction: Option<SwipeDirection>) -> bool {
|
||||
@@ -433,7 +441,9 @@ impl Ferese {
|
||||
.held_progress = Some(progress);
|
||||
self.refresh_workspace_slide_offsets();
|
||||
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
|
||||
self.cursor_redraw_pending = true;
|
||||
if let Some(output) = self.outputs_by_id.get(&output).cloned() {
|
||||
self.defer_output_redraw(output);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if self.swipe.preview_started() {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
use super::*;
|
||||
|
||||
impl Ferese {
|
||||
pub(crate) fn defer_output_redraw(&mut self, output: Output) {
|
||||
if !self.output_redraw_pending.contains(&output) {
|
||||
self.output_redraw_pending.push(output);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_output(&mut self, output: &Output, identity: String) {
|
||||
if self.output_ids.is_empty() {
|
||||
self.reset_animation_clock();
|
||||
@@ -51,6 +57,7 @@ impl Ferese {
|
||||
return;
|
||||
};
|
||||
|
||||
self.output_redraw_pending.retain(|pending| pending != output);
|
||||
self.outputs_by_id.remove(&output_id);
|
||||
self.output_names.remove(&output_id);
|
||||
self.workspace_slides.remove(&output_id);
|
||||
|
||||
@@ -44,8 +44,36 @@ impl Ferese {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn output_has_pending_visual_changes(&self, output: &Output) -> bool {
|
||||
let selected = if self.overview.is_active() {
|
||||
self.overview.selected()
|
||||
} else {
|
||||
self.focused_window
|
||||
};
|
||||
self.output_window_records(output, false).any(|(id, record, _)| {
|
||||
self.window_belongs_to_output(id, output)
|
||||
&& (record
|
||||
.focus
|
||||
.as_ref()
|
||||
.is_some_and(|focus| focus.needs_update(if selected == Some(id) { 1.0 } else { 0.0 }))
|
||||
|| record.dimming.as_ref().is_some_and(|dim| {
|
||||
dim.needs_update(crate::dimming::target(
|
||||
self.inactive_dim,
|
||||
self.focused_window,
|
||||
id,
|
||||
self.overview.is_presenting(),
|
||||
))
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn output_has_animations(&self, output: &Output) -> bool {
|
||||
if self.overview.is_animating(self.spring_config) || !self.dismissing_popups.is_empty() {
|
||||
if self.overview.is_animating(self.spring_config)
|
||||
|| self
|
||||
.dismissing_popups
|
||||
.iter()
|
||||
.any(|(root, _, _)| self.surface_outputs(root).contains(output))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -421,6 +449,26 @@ mod tests {
|
||||
state.animations_enabled = true;
|
||||
assert!(!state.sample_frame(&output, Duration::ZERO).animating);
|
||||
assert!(state.sample_frame(&other, Duration::ZERO).animating);
|
||||
// Target changes with animations disabled still need their final frame
|
||||
// on both displays, even though no spring/fade remains active afterward.
|
||||
state.focused_window = Some(id);
|
||||
state
|
||||
.windows
|
||||
.update(id, |record| record.focus = Some(crate::dimming::DimAnimation::new(1.0)));
|
||||
state.focused_window = Some(other_id);
|
||||
assert!(state.output_has_pending_visual_changes(&output));
|
||||
state.animations_enabled = false;
|
||||
state.advance_animations(Instant::now());
|
||||
assert!(!state.output_has_pending_visual_changes(&output));
|
||||
state.animations_enabled = true;
|
||||
state.windows.set_geometry(id, geometry);
|
||||
assert!(state.output_has_animations(&output));
|
||||
state.unregister_output(&other);
|
||||
assert!(!state.output_has_animations(&other));
|
||||
assert!(
|
||||
state.output_has_animations(&output),
|
||||
"remaining output keeps its active animation"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -660,7 +660,7 @@ impl Ferese {
|
||||
self.windows.update(focused, |record| {
|
||||
record.closing.get_or_insert_default();
|
||||
});
|
||||
crate::backends::direct::render_all(self);
|
||||
crate::backends::direct::render_window(self, focused);
|
||||
}
|
||||
|
||||
pub(crate) fn close_managed_window(&mut self, id: WindowId) -> bool {
|
||||
@@ -672,7 +672,7 @@ impl Ferese {
|
||||
self.windows.update(id, |record| {
|
||||
record.closing.get_or_insert_default();
|
||||
});
|
||||
crate::backends::direct::render_all(self);
|
||||
crate::backends::direct::render_window(self, id);
|
||||
} else {
|
||||
self.send_window_close(id);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user