Commit 0f99c9f9166b
tarball1 file changed+82-7
@@ -73,6 +73,11 @@ struct Surface {
7373
drm: Out,7474
/// A frame is on its way to the screen and its vblank has not landed.7575
queued: bool,76
+ queued_at: u64,77
+ /// Fires when a queued frame's vblank never arrives.78
+ watchdog: Option<RegistrationToken>,79
+ /// Consecutive frames that failed to render or queue.80
+ failures: u32,7681
redraw: bool,7782
last_vblank_ns: Option<u64>,7883
refresh_ns: u64,@@ -105,6 +110,12 @@ impl Surface {
105110
fn next_deadline(&self, now: u64) -> Option<(u64, u64)> {106111
crate::pacing::deadline(self.last_vblank_ns?, self.refresh_ns, self.budget.ns(), now)107112
}113
+114
+ /// How long a queued frame may go without its vblank. Under VRR a flip115
+ /// waits for the panel's longest frame, so the floor stays well above it.116
+ fn flip_timeout(&self) -> u64 {117
+ (self.refresh_ns * 4).max(100_000_000)118
+ }108119
}109120
110121
/// The GPU monolith drives on bare metal, the seat's primary or else its@@ -392,6 +403,9 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
392403
id,393404
drm: out,394405
queued: false,406
+ queued_at: 0,407
+ watchdog: None,408
+ failures: 0,395409
redraw: true,396410
last_vblank_ns: None,397411
scheduled: None,@@ -516,7 +530,7 @@ fn disconnect(state: &mut State, crtc: crtc::Handle) {
516530
let Some(surface) = drm.surfaces.remove(&crtc) else {517531
return;518532
};519
- if let Some(token) = surface.scheduled {533
+ for token in [surface.scheduled, surface.watchdog].into_iter().flatten() {520534
drm.handle.remove(token);521535
}522536
tracing::info!(name = surface.output.name(), "output disconnected");@@ -677,6 +691,54 @@ fn arm(
677691
}678692
}679693
694
+/// Arms the flip watchdog unless it already runs.695
+fn watch(handle: &LoopHandle<'static, State>, surface: &mut Surface, crtc: crtc::Handle) {696
+ if surface.watchdog.is_some() {697
+ return;698
+ }699
+ let after = Duration::from_nanos(surface.flip_timeout());700
+ let token = handle.insert_source(Timer::from_duration(after), move |_, _, state| {701
+ flip_watchdog(state, crtc)702
+ });703
+ match token {704
+ Ok(token) => surface.watchdog = Some(token),705
+ Err(e) => tracing::warn!(706
+ output = surface.output.name(),707
+ "could not arm the flip watchdog: {e}"708
+ ),709
+ }710
+}711
+712
+/// Recovers an output whose page flip event never came, as after a713
+/// `DrmEvent::Error`, so `queued` cannot hold it dark forever.714
+fn flip_watchdog(state: &mut State, crtc: crtc::Handle) -> TimeoutAction {715
+ let Some(drm) = state.backend.drm_mut() else {716
+ return TimeoutAction::Drop;717
+ };718
+ let active = drm.session.is_active();719
+ let Some(surface) = drm.surfaces.get_mut(&crtc) else {720
+ return TimeoutAction::Drop;721
+ };722
+ let age = now_ns().saturating_sub(surface.queued_at);723
+ let limit = surface.flip_timeout();724
+ if active && surface.queued && age < limit {725
+ return TimeoutAction::ToDuration(Duration::from_nanos(limit - age));726
+ }727
+ surface.watchdog = None;728
+ if !active || !surface.queued {729
+ return TimeoutAction::Drop;730
+ }731
+ tracing::warn!(output = surface.output.name(), "page flip never landed");732
+ if let Err(e) = surface.drm.frame_submitted() {733
+ tracing::warn!("frame was not submitted: {e}");734
+ }735
+ surface.queued = false;736
+ surface.target_ns = None;737
+ surface.redraw = true;738
+ schedule(state, crtc);739
+ TimeoutAction::Drop740
+}741
+680742
/// An animation whose frame had nothing to show, like one hidden behind a681743
/// fullscreen window, has no flip to wait for. Under VRR scheduling again682744
/// would draw at once and recurse until the stack overflowed, so the next@@ -694,8 +756,11 @@ fn draw(state: &mut State, crtc: crtc::Handle, target: u64) {
694756
if surface.scheduled.is_some() {695757
return;696758
}697
- let wait = Duration::from_nanos(surface.refresh_ns.max(1_000_000));698
- arm(&drm.handle, surface, crtc, wait, true, move |state| {759
+ let backoff = surface.failures.saturating_sub(1).min(6);760
+ let wait = (surface.refresh_ns.max(1_000_000) << backoff).min(1_000_000_000);761
+ let wait = Duration::from_nanos(wait);762
+ let preemptible = surface.failures == 0;763
+ arm(&drm.handle, surface, crtc, wait, preemptible, move |state| {699764
schedule(state, crtc);700765
});701766
}@@ -804,6 +869,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
804869
let render_began = std::time::Instant::now();805870
let cpu_began = thread_cpu_ns();806871
let mut queue_ns = 0;872
+ let mut failed = false;807873
match surface808874
.drm809875
.render_frame(renderer, &elements, CLEAR, drm.frame_flags)@@ -828,15 +894,23 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
828894
match surface.drm.queue_frame(()) {829895
Ok(()) => {830896
surface.queued = true;897
+ surface.queued_at = now_ns();898
+ watch(&drm.handle, surface, crtc);831899
lock.shown(&surface.output);832900
captures.output_damaged(&surface.output);833901
}834
- Err(e) => tracing::warn!("queueing a frame failed: {e}"),902
+ Err(e) => {903
+ failed = true;904
+ tracing::warn!("queueing a frame failed: {e}");905
+ }835906
}836907
queue_ns = queue_began.elapsed().as_nanos() as u64;837908
}838909
Ok(_) => lock.shown(&surface.output),839
- Err(e) => tracing::warn!("rendering failed: {e}"),910
+ Err(e) => {911
+ failed = true;912
+ tracing::warn!("rendering failed: {e}");913
+ }840914
}841915
let render_cpu_ns = thread_cpu_ns() - cpu_began;842916
let render_ns = render_began.elapsed().as_nanos() as u64 - queue_ns;@@ -857,10 +931,11 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
857931
if !surface.queued {858932
surface.target_ns = None;859933
}860
- surface.redraw = animating;934
+ surface.failures = if failed { surface.failures + 1 } else { 0 };935
+ surface.redraw = animating || failed;861936
let elapsed = core.start.elapsed();862937
crate::frame::send_frame_callbacks(wm, lock, surface.id, &surface.output, now, elapsed);863
- animating && !surface.queued938
+ (animating || failed) && !surface.queued864939
}865940
866941
/// CPU time this thread has used, to tell a render that works from one that