Fix DRM callback pacing and shared animation timing
This commit is contained in:
1 parent
46d33751c6
commit
83574451df
4 files changed
+270
-52
No files matched your search
@@ -28,7 +28,10 @@ use smithay::{
|
|||||||
desktop::{layer_map_for_output, utils::OutputPresentationFeedback},
|
desktop::{layer_map_for_output, utils::OutputPresentationFeedback},
|
||||||
output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel},
|
output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel},
|
||||||
reexports::{
|
reexports::{
|
||||||
calloop::EventLoop,
|
calloop::{
|
||||||
|
EventLoop, RegistrationToken,
|
||||||
|
timer::{TimeoutAction, Timer},
|
||||||
|
},
|
||||||
drm::control::{
|
drm::control::{
|
||||||
Device as ControlDevice, Mode as DrmMode, ModeTypeFlags, connector, crtc, property,
|
Device as ControlDevice, Mode as DrmMode, ModeTypeFlags, connector, crtc, property,
|
||||||
},
|
},
|
||||||
@@ -58,6 +61,8 @@ pub struct DirectBackendState {
|
|||||||
input_devices: Vec<LibinputDevice>,
|
input_devices: Vec<LibinputDevice>,
|
||||||
presentation: HashMap<(DrmNode, crtc::Handle), PresentationClock>,
|
presentation: HashMap<(DrmNode, crtc::Handle), PresentationClock>,
|
||||||
pub(crate) connected_outputs: Vec<ConnectedOutputInfo>,
|
pub(crate) connected_outputs: Vec<ConnectedOutputInfo>,
|
||||||
|
animation_timer: Option<RegistrationToken>,
|
||||||
|
animation_active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectBackendState {
|
impl DirectBackendState {
|
||||||
@@ -185,6 +190,8 @@ struct DirectOutput {
|
|||||||
render_metrics: RenderMetrics,
|
render_metrics: RenderMetrics,
|
||||||
frame_pending: bool,
|
frame_pending: bool,
|
||||||
power_off: bool,
|
power_off: bool,
|
||||||
|
callback_clock: crate::presentation::CallbackClock,
|
||||||
|
callback_timer: Option<RegistrationToken>,
|
||||||
lock_frame_pending: bool,
|
lock_frame_pending: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +238,8 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
input_devices: Vec::new(),
|
input_devices: Vec::new(),
|
||||||
presentation: HashMap::new(),
|
presentation: HashMap::new(),
|
||||||
connected_outputs: Vec::new(),
|
connected_outputs: Vec::new(),
|
||||||
|
animation_timer: None,
|
||||||
|
animation_active: false,
|
||||||
});
|
});
|
||||||
open_primary_device(event_loop, state, &primary_path)?;
|
open_primary_device(event_loop, state, &primary_path)?;
|
||||||
|
|
||||||
@@ -288,6 +297,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
state.reset_animation_clock();
|
||||||
tracing::info!("direct session activated");
|
tracing::info!("direct session activated");
|
||||||
state.lock_input_activity();
|
state.lock_input_activity();
|
||||||
render_all(state);
|
render_all(state);
|
||||||
@@ -503,12 +513,6 @@ fn open_primary_device(
|
|||||||
Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
|
Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let focused = state
|
|
||||||
.direct_backend
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|backend| backend.devices.get(&node))
|
|
||||||
.and_then(|device| device.outputs.get(&crtc))
|
|
||||||
.is_some_and(|output| state.is_focused_output(&output.output));
|
|
||||||
let lock_output = state
|
let lock_output = state
|
||||||
.direct_backend
|
.direct_backend
|
||||||
.as_mut()
|
.as_mut()
|
||||||
@@ -532,13 +536,26 @@ fn open_primary_device(
|
|||||||
{
|
{
|
||||||
output.frame_pending = false;
|
output.frame_pending = false;
|
||||||
}
|
}
|
||||||
if focused {
|
|
||||||
state.record_drm_presentation(node, crtc, metadata.time, metadata.sequence);
|
state.record_drm_presentation(node, crtc, metadata.time, metadata.sequence);
|
||||||
}
|
|
||||||
tracing::trace!(?node, ?crtc, sequence = metadata.sequence, "page flip");
|
tracing::trace!(?node, ?crtc, sequence = metadata.sequence, "page flip");
|
||||||
|
let active = state
|
||||||
|
.direct_backend
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|backend| backend.active);
|
||||||
|
if active {
|
||||||
|
let was_animating = state
|
||||||
|
.direct_backend
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|backend| backend.animation_active);
|
||||||
|
let animating = state.advance_animations(Instant::now());
|
||||||
|
if animating || was_animating {
|
||||||
|
render_outputs(state, animating);
|
||||||
|
} else {
|
||||||
render_output(state, node, crtc);
|
render_output(state, node, crtc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
DrmEvent::Error(error) => {
|
DrmEvent::Error(error) => {
|
||||||
tracing::error!(?node, %error, "DRM event error");
|
tracing::error!(?node, %error, "DRM event error");
|
||||||
}
|
}
|
||||||
@@ -604,6 +621,22 @@ pub(crate) fn wake_locked_outputs(state: &mut Ferese) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_all(state: &mut Ferese) {
|
pub fn render_all(state: &mut Ferese) {
|
||||||
|
if state
|
||||||
|
.direct_backend
|
||||||
|
.as_ref()
|
||||||
|
.is_none_or(|backend| !backend.active)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let animating = state.advance_animations(Instant::now());
|
||||||
|
render_outputs(state, animating);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_outputs(state: &mut Ferese, animating: bool) {
|
||||||
|
if let Some(backend) = state.direct_backend.as_mut() {
|
||||||
|
backend.animation_active = animating && !state.session_lock.sleeping;
|
||||||
|
}
|
||||||
|
arm_animation_timer(state);
|
||||||
let outputs = state
|
let outputs = state
|
||||||
.direct_backend
|
.direct_backend
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -694,6 +727,7 @@ fn lid_hides_panel(closed: bool, external_available: bool) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) {
|
fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) {
|
||||||
|
schedule_frame_callbacks(state, node, crtc);
|
||||||
if state.session_lock.active && state.session_lock.sleeping {
|
if state.session_lock.active && state.session_lock.sleeping {
|
||||||
sleep_locked_outputs(state);
|
sleep_locked_outputs(state);
|
||||||
let asleep = state
|
let asleep = state
|
||||||
@@ -817,23 +851,13 @@ fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) {
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
})();
|
})();
|
||||||
|
|
||||||
let queued = match rendered {
|
match rendered {
|
||||||
Ok(true) => {
|
Ok(true) => tracing::trace!(?node, ?crtc, "queued DRM frame"),
|
||||||
tracing::trace!(?node, ?crtc, "queued DRM frame");
|
Ok(false) => (),
|
||||||
true
|
Err(error) => tracing::error!(?node, ?crtc, %error, "failed to render DRM frame"),
|
||||||
}
|
}
|
||||||
Ok(false) => false,
|
|
||||||
Err(error) => {
|
|
||||||
tracing::error!(?node, ?crtc, %error, "failed to render DRM frame");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let callback_output = output.output.clone();
|
|
||||||
device.outputs.insert(crtc, output);
|
device.outputs.insert(crtc, output);
|
||||||
restore_device(state, node, device);
|
restore_device(state, node, device);
|
||||||
if queued {
|
|
||||||
send_frame_callbacks(state, &callback_output);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore_device(state: &mut Ferese, node: DrmNode, device: DirectDevice) {
|
fn restore_device(state: &mut Ferese, node: DrmNode, device: DirectDevice) {
|
||||||
@@ -1120,12 +1144,148 @@ fn refresh_duration(state: &Ferese, node: DrmNode, crtc: crtc::Handle) -> Durati
|
|||||||
.unwrap_or_else(|| Duration::from_millis(16))
|
.unwrap_or_else(|| Duration::from_millis(16))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn animation_interval(state: &Ferese) -> Duration {
|
||||||
|
state
|
||||||
|
.direct_backend
|
||||||
|
.as_ref()
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|backend| backend.devices.values())
|
||||||
|
.flat_map(|device| device.outputs.values())
|
||||||
|
.filter_map(|output| output.output.current_mode())
|
||||||
|
.filter(|mode| mode.refresh > 0)
|
||||||
|
.map(|mode| Duration::from_nanos(1_000_000_000_000 / mode.refresh as u64))
|
||||||
|
.min()
|
||||||
|
.unwrap_or(Duration::from_millis(16))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arm_animation_timer(state: &mut Ferese) {
|
||||||
|
let needed = state
|
||||||
|
.direct_backend
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|backend| backend.animation_active && backend.animation_timer.is_none());
|
||||||
|
if !needed {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let deadline = state.animation_fallback_deadline(animation_interval(state));
|
||||||
|
match state
|
||||||
|
.loop_handle
|
||||||
|
.insert_source(Timer::from_deadline(deadline), |_, _, state| {
|
||||||
|
let active = state.direct_backend.as_ref().is_some_and(|backend| {
|
||||||
|
backend.active && backend.animation_active && !state.session_lock.sleeping
|
||||||
|
});
|
||||||
|
if active {
|
||||||
|
let deadline = state.animation_fallback_deadline(animation_interval(state));
|
||||||
|
if Instant::now() < deadline {
|
||||||
|
return TimeoutAction::ToInstant(deadline);
|
||||||
|
}
|
||||||
|
render_all(state);
|
||||||
|
if state
|
||||||
|
.direct_backend
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|backend| backend.animation_active)
|
||||||
|
{
|
||||||
|
return TimeoutAction::ToInstant(
|
||||||
|
state.animation_fallback_deadline(animation_interval(state)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(backend) = state.direct_backend.as_mut() {
|
||||||
|
backend.animation_timer = None;
|
||||||
|
}
|
||||||
|
TimeoutAction::Drop
|
||||||
|
}) {
|
||||||
|
Ok(token) => state.direct_backend.as_mut().unwrap().animation_timer = Some(token),
|
||||||
|
Err(error) => tracing::warn!(%error, "could not schedule DRM animation fallback"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schedule_frame_callbacks(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) {
|
||||||
|
if state.session_lock.sleeping {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let now = Instant::now();
|
||||||
|
let interval = refresh_duration(state, node, crtc);
|
||||||
|
let Some(output) = state
|
||||||
|
.direct_backend
|
||||||
|
.as_ref()
|
||||||
|
.filter(|backend| backend.active)
|
||||||
|
.and_then(|backend| backend.devices.get(&node))
|
||||||
|
.filter(|device| device.drm.is_active())
|
||||||
|
.and_then(|device| device.outputs.get(&crtc))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if output.callback_timer.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let identity = output.output.clone();
|
||||||
|
let deadline = output.callback_clock.deadline(now, interval);
|
||||||
|
if deadline <= now {
|
||||||
|
deliver_frame_callbacks(state, node, crtc, &identity);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match state
|
||||||
|
.loop_handle
|
||||||
|
.insert_source(Timer::from_deadline(deadline), move |_, _, state| {
|
||||||
|
deliver_frame_callbacks(state, node, crtc, &identity);
|
||||||
|
TimeoutAction::Drop
|
||||||
|
}) {
|
||||||
|
Ok(token) => {
|
||||||
|
if let Some(output) = state
|
||||||
|
.direct_backend
|
||||||
|
.as_mut()
|
||||||
|
.and_then(|backend| backend.devices.get_mut(&node))
|
||||||
|
.and_then(|device| device.outputs.get_mut(&crtc))
|
||||||
|
{
|
||||||
|
output.callback_timer = Some(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => tracing::warn!(%error, "could not schedule DRM frame callbacks"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deliver_frame_callbacks(
|
||||||
|
state: &mut Ferese,
|
||||||
|
node: DrmNode,
|
||||||
|
crtc: crtc::Handle,
|
||||||
|
identity: &Output,
|
||||||
|
) {
|
||||||
|
let Some(backend) = state.direct_backend.as_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(device) = backend.devices.get_mut(&node) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(output) = device
|
||||||
|
.outputs
|
||||||
|
.get_mut(&crtc)
|
||||||
|
.filter(|output| &output.output == identity)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
output.callback_timer = None;
|
||||||
|
if !backend.active || !device.drm.is_active() || state.session_lock.sleeping {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
output.callback_clock.sent(Instant::now());
|
||||||
|
send_frame_callbacks(state, identity);
|
||||||
|
}
|
||||||
|
|
||||||
fn send_frame_callbacks(state: &mut Ferese, output: &Output) {
|
fn send_frame_callbacks(state: &mut Ferese, output: &Output) {
|
||||||
if state.session_lock.active {
|
if state.session_lock.active {
|
||||||
state.lock_frame_callbacks(output);
|
state.lock_frame_callbacks(output);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.space.elements().for_each(|window| {
|
state
|
||||||
|
.space
|
||||||
|
.elements()
|
||||||
|
.filter(|window| {
|
||||||
|
state
|
||||||
|
.window_ids
|
||||||
|
.get(*window)
|
||||||
|
.is_some_and(|id| state.window_belongs_to_output(*id, output))
|
||||||
|
})
|
||||||
|
.for_each(|window| {
|
||||||
window.send_frame(
|
window.send_frame(
|
||||||
output,
|
output,
|
||||||
state.start_time.elapsed(),
|
state.start_time.elapsed(),
|
||||||
@@ -1373,6 +1533,8 @@ fn create_direct_output(
|
|||||||
render_metrics,
|
render_metrics,
|
||||||
frame_pending: false,
|
frame_pending: false,
|
||||||
power_off: false,
|
power_off: false,
|
||||||
|
callback_clock: Default::default(),
|
||||||
|
callback_timer: None,
|
||||||
lock_frame_pending: false,
|
lock_frame_pending: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-15
@@ -107,7 +107,7 @@ impl Ferese {
|
|||||||
self,
|
self,
|
||||||
&GestureSwipeBeginEvent {
|
&GestureSwipeBeginEvent {
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
time: event.time() as u32,
|
time: event.time_msec(),
|
||||||
fingers: event.fingers(),
|
fingers: event.fingers(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -122,7 +122,7 @@ impl Ferese {
|
|||||||
.gesture_swipe_update(
|
.gesture_swipe_update(
|
||||||
self,
|
self,
|
||||||
&GestureSwipeUpdateEvent {
|
&GestureSwipeUpdateEvent {
|
||||||
time: event.time() as u32,
|
time: event.time_msec(),
|
||||||
delta: event.delta(),
|
delta: event.delta(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -158,7 +158,7 @@ impl Ferese {
|
|||||||
self,
|
self,
|
||||||
&GestureSwipeEndEvent {
|
&GestureSwipeEndEvent {
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
time: event.time() as u32,
|
time: event.time_msec(),
|
||||||
cancelled: event.cancelled(),
|
cancelled: event.cancelled(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -184,7 +184,7 @@ impl Ferese {
|
|||||||
keycode,
|
keycode,
|
||||||
state,
|
state,
|
||||||
SERIAL_COUNTER.next_serial(),
|
SERIAL_COUNTER.next_serial(),
|
||||||
Event::time(&event) as u32,
|
event.time_msec(),
|
||||||
|data, modifiers, keysym| {
|
|data, modifiers, keysym| {
|
||||||
if state == KeyState::Released {
|
if state == KeyState::Released {
|
||||||
data.portal_shortcuts.release(keycode, Event::time(&event));
|
data.portal_shortcuts.release(keycode, Event::time(&event));
|
||||||
@@ -316,7 +316,7 @@ impl Ferese {
|
|||||||
&MotionEvent {
|
&MotionEvent {
|
||||||
location: position,
|
location: position,
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
time: event.time() as u32,
|
time: event.time_msec(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
@@ -348,7 +348,7 @@ impl Ferese {
|
|||||||
delta: (delta.x / scale_x, delta.y / scale_y).into(),
|
delta: (delta.x / scale_x, delta.y / scale_y).into(),
|
||||||
delta_unaccel: (delta_unaccel.x / scale_x, delta_unaccel.y / scale_y)
|
delta_unaccel: (delta_unaccel.x / scale_x, delta_unaccel.y / scale_y)
|
||||||
.into(),
|
.into(),
|
||||||
utime: (event.time_msec() as u64).saturating_mul(1_000),
|
utime: event.time(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let requested = self.clamp_pointer_position(position + delta);
|
let requested = self.clamp_pointer_position(position + delta);
|
||||||
@@ -360,7 +360,7 @@ impl Ferese {
|
|||||||
&MotionEvent {
|
&MotionEvent {
|
||||||
location,
|
location,
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
time: event.time() as u32,
|
time: event.time_msec(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
@@ -397,7 +397,7 @@ impl Ferese {
|
|||||||
popup,
|
popup,
|
||||||
crate::dimming::DimAnimation::new(f64::from(opacity)),
|
crate::dimming::DimAnimation::new(f64::from(opacity)),
|
||||||
));
|
));
|
||||||
pointer.unset_grab(self, serial, event.time() as u32);
|
pointer.unset_grab(self, serial, event.time_msec());
|
||||||
self.focus_window_at(pointer.current_location(), serial, true);
|
self.focus_window_at(pointer.current_location(), serial, true);
|
||||||
crate::backends::direct::render_all(self);
|
crate::backends::direct::render_all(self);
|
||||||
}
|
}
|
||||||
@@ -411,7 +411,7 @@ impl Ferese {
|
|||||||
button: event.button_code(),
|
button: event.button_code(),
|
||||||
state: event.state(),
|
state: event.state(),
|
||||||
serial,
|
serial,
|
||||||
time: event.time() as u32,
|
time: event.time_msec(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
@@ -443,7 +443,7 @@ impl Ferese {
|
|||||||
button: event.button_code(),
|
button: event.button_code(),
|
||||||
state: event.state(),
|
state: event.state(),
|
||||||
serial,
|
serial,
|
||||||
time: event.time() as u32,
|
time: event.time_msec(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
@@ -460,7 +460,7 @@ impl Ferese {
|
|||||||
button: event.button_code(),
|
button: event.button_code(),
|
||||||
state: event.state(),
|
state: event.state(),
|
||||||
serial,
|
serial,
|
||||||
time: event.time() as u32,
|
time: event.time_msec(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
@@ -490,7 +490,7 @@ impl Ferese {
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut frame = AxisFrame::new(event.time() as u32).source(source);
|
let mut frame = AxisFrame::new(event.time_msec()).source(source);
|
||||||
if horizontal != 0.0 {
|
if horizontal != 0.0 {
|
||||||
frame = frame.value(Axis::Horizontal, horizontal);
|
frame = frame.value(Axis::Horizontal, horizontal);
|
||||||
if let Some(value) = event.amount_v120(Axis::Horizontal) {
|
if let Some(value) = event.amount_v120(Axis::Horizontal) {
|
||||||
@@ -534,7 +534,7 @@ impl Ferese {
|
|||||||
slot: event.slot(),
|
slot: event.slot(),
|
||||||
location,
|
location,
|
||||||
serial,
|
serial,
|
||||||
time: event.time() as u32,
|
time: event.time_msec(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -550,7 +550,7 @@ impl Ferese {
|
|||||||
&TouchMotionEvent {
|
&TouchMotionEvent {
|
||||||
slot: event.slot(),
|
slot: event.slot(),
|
||||||
location,
|
location,
|
||||||
time: event.time() as u32,
|
time: event.time_msec(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -561,7 +561,7 @@ impl Ferese {
|
|||||||
&UpEvent {
|
&UpEvent {
|
||||||
slot: event.slot(),
|
slot: event.slot(),
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
time: event.time() as u32,
|
time: event.time_msec(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,22 @@ use smithay::{
|
|||||||
pub(crate) const HANDOFF: Duration = Duration::from_millis(80);
|
pub(crate) const HANDOFF: Duration = Duration::from_millis(80);
|
||||||
pub(crate) const SNAPSHOT_BUDGET: usize = 64 * 1024 * 1024;
|
pub(crate) const SNAPSHOT_BUDGET: usize = 64 * 1024 * 1024;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct CallbackClock {
|
||||||
|
last_sent: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallbackClock {
|
||||||
|
pub(crate) fn deadline(&self, now: Instant, interval: Duration) -> Instant {
|
||||||
|
self.last_sent
|
||||||
|
.map_or(now, |last| (last + interval).max(now))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sent(&mut self, now: Instant) {
|
||||||
|
self.last_sent = Some(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn frame_delta(last: &mut Instant, now: Instant) -> Duration {
|
pub(crate) fn frame_delta(last: &mut Instant, now: Instant) -> Duration {
|
||||||
let delta = now.saturating_duration_since(*last);
|
let delta = now.saturating_duration_since(*last);
|
||||||
*last = (*last).max(now);
|
*last = (*last).max(now);
|
||||||
@@ -277,6 +293,43 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_damage_requests_are_paced_without_buffer_submission() {
|
||||||
|
let start = Instant::now();
|
||||||
|
let refresh = Duration::from_millis(16);
|
||||||
|
let mut clock = CallbackClock::default();
|
||||||
|
assert_eq!(clock.deadline(start, refresh), start);
|
||||||
|
clock.sent(start);
|
||||||
|
for millisecond in 1..16 {
|
||||||
|
assert_eq!(
|
||||||
|
clock.deadline(start + Duration::from_millis(millisecond), refresh),
|
||||||
|
start + refresh
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let late = start + Duration::from_millis(50);
|
||||||
|
assert_eq!(clock.deadline(late, refresh), late);
|
||||||
|
clock.sent(late);
|
||||||
|
assert_eq!(clock.deadline(late, refresh), late + refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn independent_output_callback_clocks_do_not_sum_their_rates() {
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut slow = CallbackClock::default();
|
||||||
|
let mut fast = CallbackClock::default();
|
||||||
|
slow.sent(start);
|
||||||
|
fast.sent(start);
|
||||||
|
fast.sent(start + Duration::from_millis(8));
|
||||||
|
assert_eq!(
|
||||||
|
slow.deadline(start + Duration::from_millis(8), Duration::from_millis(16)),
|
||||||
|
start + Duration::from_millis(16)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fast.deadline(start + Duration::from_millis(8), Duration::from_millis(8)),
|
||||||
|
start + Duration::from_millis(16)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mixed_refresh_outputs_do_not_double_the_animation_clock() {
|
fn mixed_refresh_outputs_do_not_double_the_animation_clock() {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|||||||
+16
-13
@@ -788,10 +788,6 @@ impl Ferese {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_focused_output(&self, output: &Output) -> bool {
|
|
||||||
self.output_ids.get(output).copied() == self.output_workspaces.focused_output()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn activate_output_workspace(&mut self, output: OutputId, workspace: ferese_core::WorkspaceId) {
|
fn activate_output_workspace(&mut self, output: OutputId, workspace: ferese_core::WorkspaceId) {
|
||||||
if let Err(error) = self.output_workspaces.focus_output(output) {
|
if let Err(error) = self.output_workspaces.focus_output(output) {
|
||||||
tracing::error!(%error, "failed to focus output");
|
tracing::error!(%error, "failed to focus output");
|
||||||
@@ -1023,6 +1019,12 @@ impl Ferese {
|
|||||||
let CursorImageStatus::Surface(surface) = &self.cursor_status else {
|
let CursorImageStatus::Surface(surface) = &self.cursor_status else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let Some(pointer) = self.seat.get_pointer() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if self.space.output_under(pointer.current_location()).next() != Some(output) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
send_frames_surface_tree(
|
send_frames_surface_tree(
|
||||||
surface,
|
surface,
|
||||||
@@ -1949,18 +1951,19 @@ impl Ferese {
|
|||||||
time: DrmEventTime,
|
time: DrmEventTime,
|
||||||
sequence: u32,
|
sequence: u32,
|
||||||
) {
|
) {
|
||||||
let delta = self
|
if let Some(backend) = self.direct_backend.as_mut() {
|
||||||
.direct_backend
|
backend.record_presentation(node, crtc, time, sequence);
|
||||||
.as_mut()
|
|
||||||
.and_then(|backend| backend.record_presentation(node, crtc, time, sequence));
|
|
||||||
if delta.is_some() {
|
|
||||||
// All outputs animate the same scene. Summing each CRTC's frame
|
|
||||||
// interval makes motion run faster with two monitors (and uneven
|
|
||||||
// with mixed 60/120 Hz). Advance once by actual elapsed time.
|
|
||||||
self.advance_animations(Instant::now());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
fn advance_animations_by(&mut self, delta: std::time::Duration) -> bool {
|
fn advance_animations_by(&mut self, delta: std::time::Duration) -> bool {
|
||||||
let delta = delta.mul_f64(self.animation_speed);
|
let delta = delta.mul_f64(self.animation_speed);
|
||||||
let visible_workspaces = self
|
let visible_workspaces = self
|
||||||
|
|||||||
Reference in new issue
Block a user