Overhaul resize presentation and wallpaper rendering
Coordinate native-pixel resize handoffs, fractional geometry and shared animation timing. Delegate wallpaper rendering to the compositor, advertise DMA-BUF device feedback, and improve callback pacing and render metrics. Handle monitor-local shell surfaces, lid-driven output evacuation and workspace restoration.
This commit is contained in:
1 parent
48ac963ffe
commit
0709484efb
20 files changed
+2093
-319
No files matched your search
@@ -380,8 +380,9 @@ impl WindowGeometry {
|
||||
self.presentation_size_request(now)
|
||||
}
|
||||
|
||||
/// Configure the destination once. Intermediate sizes make clients reflow
|
||||
/// and replace buffers on every frame, disrupting an otherwise smooth zoom.
|
||||
/// Request the destination once, independently of the presentation clock.
|
||||
/// The compositor coordinates commits; delaying this until settlement
|
||||
/// forces presentation to use an obsolete buffer for the entire resize.
|
||||
pub fn presentation_size_request(&mut self, now: Duration) -> Option<ClientSize> {
|
||||
let size = ClientSize::from_rect(self.logical);
|
||||
self.client.request_size(size, now).then_some(size)
|
||||
@@ -710,7 +711,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zoom_requests_destination_once_even_with_a_responsive_client() {
|
||||
fn zoom_configures_destination_once_before_animating() {
|
||||
let start = Rect::new(0.0, 0.0, 600.0, 800.0);
|
||||
let end = Rect::new(0.0, 0.0, 1920.0, 1080.0);
|
||||
let mut geometry = WindowGeometry::new(start, Some(ClientSize::from_rect(start)));
|
||||
@@ -718,20 +719,72 @@ mod tests {
|
||||
geometry.set_presentation_target(end, true, Duration::ZERO),
|
||||
Some(ClientSize::from_rect(end))
|
||||
);
|
||||
geometry.client.commit(ClientSize::from_rect(end));
|
||||
for frame in 0..240 {
|
||||
let now = Duration::from_millis(frame * 8);
|
||||
geometry.advance(Duration::from_millis(8), SpringConfig::default(), true);
|
||||
assert_eq!(
|
||||
geometry.presentation_size_request(Duration::from_millis(frame * 8)),
|
||||
None
|
||||
);
|
||||
assert_eq!(geometry.presentation_size_request(now), None);
|
||||
}
|
||||
assert_eq!(geometry.visual.current, end);
|
||||
assert_eq!(
|
||||
geometry.set_presentation_target(start, false, Duration::from_secs(2)),
|
||||
Some(ClientSize::from_rect(start))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn animated_resize_configures_each_new_target_without_framewise_requests() {
|
||||
let start = Rect::new(10.0, 10.0, 600.0, 800.0);
|
||||
let middle = Rect::new(10.0, 10.0, 900.0, 800.0);
|
||||
let end = Rect::new(10.0, 10.0, 450.0, 800.0);
|
||||
let mut geometry = WindowGeometry::new(start, Some(ClientSize::from_rect(start)));
|
||||
assert_eq!(
|
||||
geometry.set_presentation_mode(middle, PresentationMode::Normal, Duration::ZERO),
|
||||
Some(ClientSize::from_rect(middle))
|
||||
);
|
||||
geometry.advance(Duration::from_millis(64), SpringConfig::default(), true);
|
||||
assert_eq!(
|
||||
geometry.presentation_size_request(Duration::from_millis(64)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
geometry.set_presentation_mode(
|
||||
end,
|
||||
PresentationMode::Normal,
|
||||
Duration::from_millis(64)
|
||||
),
|
||||
Some(ClientSize::from_rect(end))
|
||||
);
|
||||
let mut requests = Vec::new();
|
||||
for frame in 1..240 {
|
||||
geometry.advance(Duration::from_millis(8), SpringConfig::default(), true);
|
||||
if let Some(size) =
|
||||
geometry.presentation_size_request(Duration::from_millis(64 + frame * 8))
|
||||
{
|
||||
assert_eq!(geometry.visual.current.width, end.width);
|
||||
requests.push(size);
|
||||
}
|
||||
}
|
||||
assert!(requests.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pointer_resize_stays_live_and_reduced_motion_configures_immediately() {
|
||||
let start = Rect::new(0.0, 0.0, 600.0, 800.0);
|
||||
let end = Rect::new(0.0, 0.0, 900.0, 800.0);
|
||||
let mut pointer = WindowGeometry::new(start, Some(ClientSize::from_rect(start)));
|
||||
assert_eq!(
|
||||
pointer.follow_pointer(end, Duration::ZERO),
|
||||
Some(ClientSize::from_rect(end))
|
||||
);
|
||||
let mut reduced = WindowGeometry::new(start, Some(ClientSize::from_rect(start)));
|
||||
assert_eq!(
|
||||
reduced.set_presentation_target(end, true, Duration::ZERO),
|
||||
Some(ClientSize::from_rect(end))
|
||||
);
|
||||
reduced.advance(Duration::ZERO, SpringConfig::default(), false);
|
||||
assert_eq!(reduced.presentation_size_request(Duration::ZERO), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_and_right_zoom_edges_do_not_reverse_direction() {
|
||||
let fullscreen = Rect::new(0.0, 0.0, 1920.0, 1080.0);
|
||||
|
||||
@@ -271,7 +271,7 @@ impl OutputWorkspaceMap {
|
||||
return Ok(WorkspaceSwitch::FocusedExisting(owner));
|
||||
}
|
||||
|
||||
self.assignments.insert(workspace, output);
|
||||
let reassigned = self.assignments.insert(workspace, output) != Some(output);
|
||||
let state = self
|
||||
.outputs
|
||||
.get_mut(&output)
|
||||
@@ -279,7 +279,9 @@ impl OutputWorkspaceMap {
|
||||
state.workspaces.insert(workspace);
|
||||
state.active = workspace;
|
||||
self.focused = Some(output);
|
||||
self.bump_revision(workspace);
|
||||
if reassigned {
|
||||
self.bump_revision(workspace);
|
||||
}
|
||||
|
||||
debug_assert!(self.validate());
|
||||
Ok(WorkspaceSwitch::Activated(output))
|
||||
@@ -482,7 +484,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_workspace_selection_prevents_automatic_return() {
|
||||
fn using_an_evacuated_workspace_does_not_prevent_automatic_return() {
|
||||
let mut outputs = OutputWorkspaceMap::default();
|
||||
outputs
|
||||
.connect(OutputId(1), geometry(0), WorkspaceId(1))
|
||||
@@ -498,14 +500,63 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
outputs.connect(OutputId(2), geometry(1_920), WorkspaceId(3)),
|
||||
Ok(WorkspaceId(3))
|
||||
Ok(WorkspaceId(2))
|
||||
);
|
||||
assert_eq!(
|
||||
outputs.output_for_workspace(WorkspaceId(2)),
|
||||
Some(OutputId(1))
|
||||
Some(OutputId(2))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeated_lid_and_monitor_handoffs_restore_both_workspace_owners() {
|
||||
let mut outputs = OutputWorkspaceMap::default();
|
||||
outputs
|
||||
.connect(OutputId(1), geometry(0), WorkspaceId(1))
|
||||
.unwrap();
|
||||
outputs
|
||||
.connect(OutputId(2), geometry(1920), WorkspaceId(2))
|
||||
.unwrap();
|
||||
for removed in [OutputId(1), OutputId(2), OutputId(1), OutputId(2)] {
|
||||
let remaining = if removed == OutputId(1) {
|
||||
OutputId(2)
|
||||
} else {
|
||||
OutputId(1)
|
||||
};
|
||||
outputs.focus_output(removed).unwrap();
|
||||
outputs.disconnect(removed).unwrap();
|
||||
assert_eq!(
|
||||
outputs.output_for_workspace(WorkspaceId(1)),
|
||||
Some(remaining)
|
||||
);
|
||||
assert_eq!(
|
||||
outputs.output_for_workspace(WorkspaceId(2)),
|
||||
Some(remaining)
|
||||
);
|
||||
// Switching between handed-off workspaces is normal use, not an
|
||||
// explicit change of their home output.
|
||||
outputs.switch_workspace(remaining, WorkspaceId(1)).unwrap();
|
||||
outputs.switch_workspace(remaining, WorkspaceId(2)).unwrap();
|
||||
outputs
|
||||
.connect(
|
||||
removed,
|
||||
geometry(if removed == OutputId(1) { 0 } else { 1920 }),
|
||||
WorkspaceId(3),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
outputs.output_for_workspace(WorkspaceId(1)),
|
||||
Some(OutputId(1))
|
||||
);
|
||||
assert_eq!(
|
||||
outputs.output_for_workspace(WorkspaceId(2)),
|
||||
Some(OutputId(2))
|
||||
);
|
||||
assert_eq!(outputs.active_workspace(OutputId(1)), Some(WorkspaceId(1)));
|
||||
assert_eq!(outputs.active_workspace(OutputId(2)), Some(WorkspaceId(2)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selecting_a_workspace_owned_by_another_output_focuses_that_output() {
|
||||
let mut outputs = OutputWorkspaceMap::default();
|
||||
|
||||
Reference in new issue
Block a user