mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Rework PW screencast frame timing
- Remove the 0.5 ms hack. - Add redraw scheduling to fix stuck frame if the last redrawn frame happened too soon.
This commit is contained in:
+3
-3
@@ -3664,7 +3664,7 @@ impl Niri {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cast.should_skip_frame(target_presentation_time) {
|
if cast.check_time_and_schedule(&self.event_loop, output, target_presentation_time) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3726,7 +3726,7 @@ impl Niri {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cast.should_skip_frame(target_presentation_time) {
|
if cast.check_time_and_schedule(&self.event_loop, output, target_presentation_time) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3805,7 +3805,7 @@ impl Niri {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cast.should_skip_frame(target_presentation_time) {
|
if cast.check_time_and_schedule(&self.event_loop, output, target_presentation_time) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+74
-11
@@ -8,6 +8,8 @@ use std::rc::Rc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
|
use calloop::timer::{TimeoutAction, Timer};
|
||||||
|
use calloop::RegistrationToken;
|
||||||
use pipewire::context::Context;
|
use pipewire::context::Context;
|
||||||
use pipewire::core::Core;
|
use pipewire::core::Core;
|
||||||
use pipewire::main_loop::MainLoop;
|
use pipewire::main_loop::MainLoop;
|
||||||
@@ -34,7 +36,7 @@ use smithay::backend::drm::DrmDeviceFd;
|
|||||||
use smithay::backend::renderer::damage::OutputDamageTracker;
|
use smithay::backend::renderer::damage::OutputDamageTracker;
|
||||||
use smithay::backend::renderer::element::RenderElement;
|
use smithay::backend::renderer::element::RenderElement;
|
||||||
use smithay::backend::renderer::gles::GlesRenderer;
|
use smithay::backend::renderer::gles::GlesRenderer;
|
||||||
use smithay::output::{OutputModeSource, WeakOutput};
|
use smithay::output::{Output, OutputModeSource, WeakOutput};
|
||||||
use smithay::reexports::calloop::generic::Generic;
|
use smithay::reexports::calloop::generic::Generic;
|
||||||
use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction};
|
use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction};
|
||||||
use smithay::reexports::gbm::Modifier;
|
use smithay::reexports::gbm::Modifier;
|
||||||
@@ -44,6 +46,10 @@ use zbus::SignalContext;
|
|||||||
use crate::dbus::mutter_screen_cast::{self, CursorMode};
|
use crate::dbus::mutter_screen_cast::{self, CursorMode};
|
||||||
use crate::niri::State;
|
use crate::niri::State;
|
||||||
use crate::render_helpers::render_to_dmabuf;
|
use crate::render_helpers::render_to_dmabuf;
|
||||||
|
use crate::utils::get_monotonic_time;
|
||||||
|
|
||||||
|
// Give a 0.1 ms allowance for presentation time errors.
|
||||||
|
const CAST_DELAY_ALLOWANCE: Duration = Duration::from_micros(100);
|
||||||
|
|
||||||
pub struct PipeWire {
|
pub struct PipeWire {
|
||||||
_context: Context,
|
_context: Context,
|
||||||
@@ -70,6 +76,7 @@ pub struct Cast {
|
|||||||
pub last_frame_time: Duration,
|
pub last_frame_time: Duration,
|
||||||
min_time_between_frames: Rc<Cell<Duration>>,
|
min_time_between_frames: Rc<Cell<Duration>>,
|
||||||
dmabufs: Rc<RefCell<HashMap<i64, Dmabuf>>>,
|
dmabufs: Rc<RefCell<HashMap<i64, Dmabuf>>>,
|
||||||
|
scheduled_redraw: Option<RegistrationToken>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -315,10 +322,9 @@ impl PipeWire {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let max_frame_rate = format.max_framerate();
|
let max_frame_rate = format.max_framerate();
|
||||||
// Subtract 0.5 ms to improve edge cases when equal to refresh rate.
|
let min_frame_time = Duration::from_micros(
|
||||||
let min_frame_time = Duration::from_secs_f64(
|
1_000_000 * u64::from(max_frame_rate.denom) / u64::from(max_frame_rate.num),
|
||||||
max_frame_rate.denom as f64 / max_frame_rate.num as f64,
|
);
|
||||||
) - Duration::from_micros(500);
|
|
||||||
min_time_between_frames.set(min_frame_time);
|
min_time_between_frames.set(min_frame_time);
|
||||||
|
|
||||||
let object = pod.as_object().unwrap();
|
let object = pod.as_object().unwrap();
|
||||||
@@ -651,6 +657,7 @@ impl PipeWire {
|
|||||||
last_frame_time: Duration::ZERO,
|
last_frame_time: Duration::ZERO,
|
||||||
min_time_between_frames,
|
min_time_between_frames,
|
||||||
dmabufs,
|
dmabufs,
|
||||||
|
scheduled_redraw: None,
|
||||||
};
|
};
|
||||||
Ok(cast)
|
Ok(cast)
|
||||||
}
|
}
|
||||||
@@ -711,13 +718,13 @@ impl Cast {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn should_skip_frame(&self, target_frame_time: Duration) -> bool {
|
fn compute_extra_delay(&self, target_frame_time: Duration) -> Duration {
|
||||||
let last = self.last_frame_time;
|
let last = self.last_frame_time;
|
||||||
let min = self.min_time_between_frames.get();
|
let min = self.min_time_between_frames.get();
|
||||||
|
|
||||||
if last.is_zero() {
|
if last.is_zero() {
|
||||||
trace!(?target_frame_time, ?last, "last is zero, recording");
|
trace!(?target_frame_time, ?last, "last is zero, recording");
|
||||||
return false;
|
return Duration::ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
if target_frame_time < last {
|
if target_frame_time < last {
|
||||||
@@ -727,20 +734,76 @@ impl Cast {
|
|||||||
?last,
|
?last,
|
||||||
"target frame time is below last, did it overflow or did we mispredict?"
|
"target frame time is below last, did it overflow or did we mispredict?"
|
||||||
);
|
);
|
||||||
return false;
|
return Duration::ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
let diff = target_frame_time - last;
|
let diff = target_frame_time - last;
|
||||||
if diff < min {
|
if diff < min {
|
||||||
|
let delay = min - diff;
|
||||||
trace!(
|
trace!(
|
||||||
?target_frame_time,
|
?target_frame_time,
|
||||||
?last,
|
?last,
|
||||||
"skipping frame because it is too soon: diff={diff:?} < min={min:?}",
|
"frame is too soon: min={min:?}, delay={:?}",
|
||||||
|
delay
|
||||||
);
|
);
|
||||||
return true;
|
return delay;
|
||||||
|
} else {
|
||||||
|
trace!("overshoot={:?}", diff - min);
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
Duration::ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schedule_redraw(
|
||||||
|
&mut self,
|
||||||
|
event_loop: &LoopHandle<'static, State>,
|
||||||
|
output: Output,
|
||||||
|
target_time: Duration,
|
||||||
|
) {
|
||||||
|
if self.scheduled_redraw.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = get_monotonic_time();
|
||||||
|
let duration = target_time.saturating_sub(now);
|
||||||
|
let timer = Timer::from_duration(duration);
|
||||||
|
let token = event_loop
|
||||||
|
.insert_source(timer, move |_, _, state| {
|
||||||
|
state.niri.queue_redraw(&output);
|
||||||
|
TimeoutAction::Drop
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
self.scheduled_redraw = Some(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_scheduled_redraw(&mut self, event_loop: &LoopHandle<'static, State>) {
|
||||||
|
if let Some(token) = self.scheduled_redraw.take() {
|
||||||
|
event_loop.remove(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether this frame should be skipped because it's too soon.
|
||||||
|
///
|
||||||
|
/// If the frame should be skipped, schedules a redraw and returns `true`. Otherwise, removes a
|
||||||
|
/// scheduled redraw, if any, and returns `false`.
|
||||||
|
///
|
||||||
|
/// When this method returns `false`, the calling code is assumed to follow up with
|
||||||
|
/// [`Cast::dequeue_buffer_and_render()`].
|
||||||
|
pub fn check_time_and_schedule(
|
||||||
|
&mut self,
|
||||||
|
event_loop: &LoopHandle<'static, State>,
|
||||||
|
output: &Output,
|
||||||
|
target_frame_time: Duration,
|
||||||
|
) -> bool {
|
||||||
|
let delay = self.compute_extra_delay(target_frame_time);
|
||||||
|
if delay >= CAST_DELAY_ALLOWANCE {
|
||||||
|
trace!("delay >= allowance, scheduling redraw");
|
||||||
|
self.schedule_redraw(event_loop, output.clone(), target_frame_time + delay);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
self.remove_scheduled_redraw(event_loop);
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dequeue_buffer_and_render(
|
pub fn dequeue_buffer_and_render(
|
||||||
|
|||||||
Reference in New Issue
Block a user