diff options
| -rw-r--r-- | src/backend/tty.rs | 30 | ||||
| -rw-r--r-- | src/backend/winit.rs | 4 | ||||
| -rw-r--r-- | src/niri.rs | 39 |
3 files changed, 43 insertions, 30 deletions
diff --git a/src/backend/tty.rs b/src/backend/tty.rs index 63e81322..77b88cc8 100644 --- a/src/backend/tty.rs +++ b/src/backend/tty.rs @@ -983,15 +983,15 @@ impl Tty { } } - if let Some(last_sequence) = output_state.current_estimated_sequence { + if let Some(last_sequence) = output_state.last_drm_sequence { let delta = meta.sequence as f64 - last_sequence as f64; tracy_client::Client::running() .unwrap() .plot(surface.sequence_delta_plot_name, delta); } + output_state.last_drm_sequence = Some(meta.sequence); output_state.frame_clock.presented(presentation_time); - output_state.current_estimated_sequence = Some(meta.sequence); let redraw_needed = match mem::replace(&mut output_state.redraw_state, RedrawState::Idle) { RedrawState::Idle => unreachable!(), @@ -1024,6 +1024,9 @@ impl Tty { return; }; + // We waited for the timer, now we can send frame callbacks again. + output_state.frame_callback_sequence = output_state.frame_callback_sequence.wrapping_add(1); + match mem::replace(&mut output_state.redraw_state, RedrawState::Idle) { RedrawState::Idle => unreachable!(), RedrawState::Queued(_) => unreachable!(), @@ -1036,14 +1039,10 @@ impl Tty { } } - if let Some(sequence) = output_state.current_estimated_sequence.as_mut() { - *sequence = sequence.wrapping_add(1); - - if output_state.unfinished_animations_remain { - niri.queue_redraw(output); - } else { - niri.send_frame_callbacks(&output); - } + if output_state.unfinished_animations_remain { + niri.queue_redraw(output); + } else { + niri.send_frame_callbacks(&output); } } @@ -1147,12 +1146,11 @@ impl Tty { } }; - // We queued this frame successfully, so now we'll be sending frame - // callbacks for the next sequence. - if let Some(sequence) = output_state.current_estimated_sequence.as_mut() - { - *sequence = sequence.wrapping_add(1); - } + // We queued this frame successfully, so the current client buffers were + // latched. We can send frame callbacks now, since a new client commit + // will no longer overwrite this frame and will wait for a VBlank. + output_state.frame_callback_sequence = + output_state.frame_callback_sequence.wrapping_add(1); return RenderResult::Submitted; } diff --git a/src/backend/winit.rs b/src/backend/winit.rs index e9413698..580802bc 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -199,6 +199,10 @@ impl Winit { RedrawState::WaitingForEstimatedVBlankAndQueued(_) => unreachable!(), } + output_state.frame_callback_sequence = output_state.frame_callback_sequence.wrapping_add(1); + + // FIXME: this should wait until a frame callback from the host compositor, but it redraws + // right away instead. if output_state.unfinished_animations_remain { self.backend.window().request_redraw(); } diff --git a/src/niri.rs b/src/niri.rs index 28aba19e..2d1c80ff 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -219,15 +219,28 @@ pub struct OutputState { pub redraw_state: RedrawState, // After the last redraw, some ongoing animations still remain. pub unfinished_animations_remain: bool, - /// Estimated sequence currently displayed on this output. + /// Last sequence received in a vblank event. + pub last_drm_sequence: Option<u32>, + /// Sequence for frame callback throttling. /// - /// When a frame is presented on this output, this becomes the real sequence from the VBlank - /// callback. Then, as long as there are no KMS submissions, but we keep getting commits, this - /// sequence increases by 1 at estimated VBlank times. + /// We want to send frame callbacks for each surface at most once per monitor refresh cycle. /// - /// If there are no commits, then we won't have a timer running, so the estimated sequence will - /// not increase. - pub current_estimated_sequence: Option<u32>, + /// Even if a surface commit resulted in empty damage to the monitor, we want to delay the next + /// frame callback until roughly when a VBlank would occur, had the monitor been damaged. This + /// is necessary to prevent clients busy-looping with frame callbacks that result in empty + /// damage. + /// + /// This counter wrapping-increments by 1 every time we move into the next refresh cycle, as + /// far as frame callback throttling is concerned. Specifically, it happens: + /// + /// 1. Upon a successful DRM frame submission. Notably, we don't wait for the VBlank here, + /// because the client buffers are already "latched" at the point of submission. Even if a + /// client submits a new buffer right away, we will wait for a VBlank to draw it, which + /// means that busy looping is avoided. + /// 2. If a frame resulted in empty damage, a timer is queued to fire roughly when a VBlank + /// would occur, based on the last presentation time and output refresh interval. Sequence + /// is incremented in that timer, before attempting a redraw or sending frame callbacks. + pub frame_callback_sequence: u32, /// Solid color buffer for the background that we use instead of clearing to avoid damage /// tracking issues and make screenshots easier. pub background_buffer: SolidColorBuffer, @@ -1246,7 +1259,8 @@ impl Niri { redraw_state: RedrawState::Idle, unfinished_animations_remain: false, frame_clock: FrameClock::new(refresh_interval), - current_estimated_sequence: None, + last_drm_sequence: None, + frame_callback_sequence: 0, background_buffer: SolidColorBuffer::new(size, CLEAR_COLOR), lock_render_state, lock_surface: None, @@ -2368,7 +2382,7 @@ impl Niri { let _span = tracy_client::span!("Niri::send_frame_callbacks"); let state = self.output_state.get(output).unwrap(); - let sequence = state.current_estimated_sequence; + let sequence = state.frame_callback_sequence; let should_send = |surface: &WlSurface, states: &SurfaceData| { // Do the standard primary scanout output check. For pointer surfaces it deduplicates @@ -2390,16 +2404,13 @@ impl Niri { // If we already sent a frame callback to this surface this output refresh // cycle, don't send one again to prevent empty-damage commit busy loops. if let Some((last_output, last_sequence)) = &*last_sent_at { - if last_output == output && Some(*last_sequence) == sequence { + if last_output == output && *last_sequence == sequence { send = false; } } if send { - if let Some(sequence) = sequence { - *last_sent_at = Some((output.clone(), sequence)); - } - + *last_sent_at = Some((output.clone(), sequence)); Some(output.clone()) } else { None |
