Gate display frame callbacks on shared presentation visibility

This commit is contained in:
Samuel Onoja committed 2026-10-02 07:59:41 +01:00
1 parent 6b2974d0fa
commit 2f63cae54c
5 files changed
+45 -18

No files matched your search

+4 -4
View File
@@ -1552,14 +1552,14 @@ fn send_frame_callbacks(state: &mut Ferese, output: &Output) {
.is_some_and(|id| state.window_belongs_to_output(*id, output))
})
.for_each(|window| {
window.send_frame(output, state.start_time.elapsed(), Some(Duration::ZERO), |_, _| {
Some(output.clone())
window.send_frame(output, state.start_time.elapsed(), None, |surface, _| {
state.display_presentation.callback_output(&surface.into())
});
});
let layers = layer_map_for_output(output).layers().cloned().collect::<Vec<_>>();
layers.iter().for_each(|layer| {
layer.send_frame(output, state.start_time.elapsed(), Some(Duration::ZERO), |_, _| {
Some(output.clone())
layer.send_frame(output, state.start_time.elapsed(), None, |surface, _| {
state.display_presentation.callback_output(&surface.into())
});
});
state.send_cursor_frame(output);
+27
View File
@@ -44,6 +44,17 @@ impl DisplayPresentation {
self.outputs.values().any(|record| record.displayed.contains(surface))
}
// Membership describes every output; callback ownership is a separate choice.
// A stable tie-break avoids alternating owners when refresh rates match.
pub fn callback_output(&self, surface: &Id) -> Option<Output> {
self.outputs
.iter()
.filter(|(_, record)| record.displayed.contains(surface))
.map(|(output, _)| output)
.max_by_key(|output| (output.current_mode().map_or(0, |mode| mode.refresh), output.name()))
.cloned()
}
pub fn remove_surface(&mut self, surface: &Id) {
for record in self.outputs.values_mut() {
record.displayed.remove(surface);
@@ -139,4 +150,20 @@ mod tests {
states.states.get_mut(&id).unwrap().presentation_state = RenderElementPresentationState::Skipped;
assert!(visible(&states).is_empty());
}
#[test]
fn callback_ownership_is_stable_and_moves_when_an_output_disappears() {
let mut record = DisplayPresentation::default();
let a = output("a");
let b = output("b");
let id = Id::new();
for output in [&a, &b] {
record.queued(output, &scene(&id));
record.presented(output);
}
assert_eq!(record.callback_output(&id), Some(b.clone()));
record.remove_output(&b);
assert_eq!(record.callback_output(&id), Some(a.clone()));
record.remove_surface(&id);
assert_eq!(record.callback_output(&id), None);
}
}
+2 -2
View File
@@ -218,8 +218,8 @@ impl Ferese {
surface.wl_surface(),
output,
self.start_time.elapsed(),
Some(std::time::Duration::ZERO),
|_, _| Some(output.clone()),
None,
|surface, _| self.display_presentation.callback_output(&surface.into()),
);
}
}
+3 -7
View File
@@ -119,13 +119,9 @@ impl Ferese {
return;
}
send_frames_surface_tree(
surface,
output,
self.start_time.elapsed(),
Some(Duration::ZERO),
|_, _| Some(output.clone()),
);
send_frames_surface_tree(surface, output, self.start_time.elapsed(), None, |surface, _| {
self.display_presentation.callback_output(&surface.into())
});
}
pub fn window_under_visual(&self, position: Point<f64, Logical>) -> Option<Window> {
+9 -5
View File
@@ -163,7 +163,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
// A callback means permission to draw the next client
// frame, not proof of a new compositor presentation.
// No-damage frames must still unblock layer clients.
send_nested_frame_callbacks(state, &output, refresh.get());
send_nested_frame_callbacks(state, &output);
state.space.refresh();
state.popups.cleanup();
layer_map_for_output(&output).cleanup();
@@ -209,7 +209,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
sequence,
PresentationKind::Vsync,
);
send_nested_frame_callbacks(state, &output, refresh.get());
send_nested_frame_callbacks(state, &output);
state.space.refresh();
state.popups.cleanup();
layer_map_for_output(&output).cleanup();
@@ -224,7 +224,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
Ok(())
}
fn send_nested_frame_callbacks(state: &mut Ferese, output: &Output, refresh: Duration) {
fn send_nested_frame_callbacks(state: &mut Ferese, output: &Output) {
if state.session_lock.active {
state.lock_frame_callbacks(output);
return;
@@ -237,10 +237,14 @@ fn send_nested_frame_callbacks(state: &mut Ferese, output: &Output, refresh: Dur
.get(*window)
.is_some_and(|id| state.window_belongs_to_output(*id, output))
}) {
window.send_frame(output, time, Some(refresh), |_, _| Some(output.clone()));
window.send_frame(output, time, None, |surface, _| {
state.display_presentation.callback_output(&surface.into())
});
}
for layer in layer_surfaces(output) {
layer.send_frame(output, time, Some(refresh), |_, _| Some(output.clone()));
layer.send_frame(output, time, None, |surface, _| {
state.display_presentation.callback_output(&surface.into())
});
}
state.send_cursor_frame(output);
}