atagen

monolith

public
Star0
Watch0

Commit 1a9eee4b3604

tarball

atagen <boss@atagen.co>2026-10-19 07:51 UTC

1 file changed+18-4
drm: let fresh content cut short the vrr cursor cap

An armed cursor wait or one-refresh retry in `Surface::scheduled` made `schedule` bail on `scheduled.is_some()`, so a fullscreen VRR game was held to the cursor cadence while the mouse moved. `render` now cancels such a timer when real content arrives, and cursor-only frames keep the cap.
Modified file: crates/monolith/src/drm.rs+18 additions−4 deletions
@@ -78,6 +78,9 @@ struct Surface {
7878 refresh_ns: u64,
7979 /// The pending render, timed to start just in time for `target_ns`.
8080 scheduled: Option<RegistrationToken>,
81+ /// `scheduled` only waits out a cap or a retry, so fresh content may
82+ /// cancel it and draw at once.
83+ preemptible: bool,
8184 /// The vblank the frame being drawn or queued is meant for.
8285 target_ns: Option<u64>,
8386 budget: crate::pacing::Budget,
@@ -392,6 +395,7 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
392395 redraw: true,
393396 last_vblank_ns: None,
394397 scheduled: None,
398+ preemptible: false,
395399 target_ns: None,
396400 budget: crate::pacing::Budget::new(),
397401 presented: 0,
@@ -619,7 +623,7 @@ fn schedule(state: &mut State, crtc: crtc::Handle) {
619623 return;
620624 }
621625 let wait = Duration::from_nanos(CURSOR_FRAME_NS - since_flip);
622- arm(&drm.handle, surface, crtc, wait, move |state| {
626+ arm(&drm.handle, surface, crtc, wait, true, move |state| {
623627 draw(state, crtc, now_ns());
624628 });
625629 return;
@@ -630,7 +634,7 @@ fn schedule(state: &mut State, crtc: crtc::Handle) {
630634 };
631635 surface.target_ns = Some(vblank);
632636 let wait = Duration::from_nanos(start.saturating_sub(now));
633- arm(&drm.handle, surface, crtc, wait, move |state| {
637+ arm(&drm.handle, surface, crtc, wait, false, move |state| {
634638 let target = state
635639 .backend
636640 .drm()
@@ -647,6 +651,7 @@ fn arm(
647651 surface: &mut Surface,
648652 crtc: crtc::Handle,
649653 after: Duration,
654+ preemptible: bool,
650655 mut fire: impl FnMut(&mut State) + 'static,
651656 ) {
652657 let token = handle.insert_source(Timer::from_duration(after), move |_, _, state| {
@@ -661,7 +666,10 @@ fn arm(
661666 TimeoutAction::Drop
662667 });
663668 match token {
664- Ok(token) => surface.scheduled = Some(token),
669+ Ok(token) => {
670+ surface.scheduled = Some(token);
671+ surface.preemptible = preemptible;
672+ }
665673 Err(e) => tracing::warn!(
666674 output = surface.output.name(),
667675 "could not arm a render timer: {e}"
@@ -687,7 +695,7 @@ fn draw(state: &mut State, crtc: crtc::Handle, target: u64) {
687695 return;
688696 }
689697 let wait = Duration::from_nanos(surface.refresh_ns.max(1_000_000));
690- arm(&drm.handle, surface, crtc, wait, move |state| {
698+ arm(&drm.handle, surface, crtc, wait, true, move |state| {
691699 schedule(state, crtc);
692700 });
693701 }
@@ -702,6 +710,12 @@ pub fn render(state: &mut State) {
702710 for s in drm.surfaces.values_mut() {
703711 s.redraw |= all;
704712 s.cursor_redraw |= cursor;
713+ if all
714+ && s.preemptible
715+ && let Some(token) = s.scheduled.take()
716+ {
717+ drm.handle.remove(token);
718+ }
705719 }
706720 let ready: Vec<crtc::Handle> = drm
707721 .surfaces