1
0
Fork 0
forked from wry/wry

metal: commit 1.5ms before the next page flip

This commit is contained in:
Julian Orth 2024-09-10 14:28:06 +02:00
parent a37ce1acda
commit 87d60d267e
4 changed files with 105 additions and 9 deletions

View file

@ -12,6 +12,7 @@ use {
SyncFile, SyncFile,
}, },
theme::Color, theme::Color,
time::Time,
tree::OutputNode, tree::OutputNode,
utils::{errorfmt::ErrorFmt, oserror::OsError, transform_ext::TransformExt}, utils::{errorfmt::ErrorFmt, oserror::OsError, transform_ext::TransformExt},
video::{ video::{
@ -22,7 +23,11 @@ use {
}, },
}, },
}, },
std::rc::{Rc, Weak}, std::{
env,
rc::{Rc, Weak},
sync::LazyLock,
},
uapi::c, uapi::c,
}; };
@ -79,19 +84,63 @@ enum CursorProgramming {
}, },
} }
pub const DEFAULT_PRE_COMMIT_MARGIN: u64 = 16_000_000; // 16ms
pub const MIN_POST_COMMIT_MARGIN: u64 = 1_500_000; // 1.5ms
pub const MAX_POST_COMMIT_MARGIN: u64 = 16_000_000; // 16ms
pub const DEFAULT_POST_COMMIT_MARGIN: u64 = MIN_POST_COMMIT_MARGIN;
pub const POST_COMMIT_MARGIN_DELTA: u64 = 500_000; // 500us
static NO_FRAME_SCHEDULING: LazyLock<bool> = LazyLock::new(|| {
let res = env::var("JAY_NO_FRAME_SCHEDULING").ok().as_deref() == Some("1");
if res {
log::warn!("Frame scheduling is disabled.");
}
res
});
impl MetalConnector { impl MetalConnector {
pub fn schedule_present(&self) { pub fn schedule_present(&self) {
self.present_trigger.trigger(); self.present_trigger.trigger();
} }
pub async fn present_loop(self: Rc<Self>) { pub async fn present_loop(self: Rc<Self>) {
let mut cur_sec = 0;
let mut max = 0;
loop { loop {
self.present_trigger.triggered().await; self.present_trigger.triggered().await;
if !self.can_present.get() {
continue;
}
let mut expected_sequence = self.sequence.get() + 1;
let mut start = Time::now_unchecked();
let use_frame_scheduling = !self.try_async_flip() && !*NO_FRAME_SCHEDULING;
if use_frame_scheduling {
let margin = self.pre_commit_margin.get() + self.post_commit_margin.get();
let next_present = self.next_flip_nsec.get().saturating_sub(margin);
if start.nsec() < next_present {
self.state.ring.timeout(next_present).await.unwrap();
start = Time::now_unchecked();
} else {
expected_sequence += 1;
}
}
if let Err(e) = self.present_once().await { if let Err(e) = self.present_once().await {
log::error!("Could not present: {}", ErrorFmt(e)); log::error!("Could not present: {}", ErrorFmt(e));
continue; continue;
} }
if use_frame_scheduling {
self.expected_sequence.set(Some(expected_sequence));
}
self.state.set_backend_idle(false); self.state.set_backend_idle(false);
let duration = start.elapsed();
max = max.max(duration.as_nanos() as _);
if start.0.tv_sec != cur_sec {
cur_sec = start.0.tv_sec;
self.pre_commit_margin_decay.add(max);
self.pre_commit_margin
.set(self.pre_commit_margin_decay.get());
max = 0;
}
} }
} }
@ -244,6 +293,10 @@ impl MetalConnector {
} }
} }
fn try_async_flip(&self) -> bool {
self.tearing_requested.get() && self.dev.supports_async_commit
}
fn program_connector( fn program_connector(
&self, &self,
version: u64, version: u64,
@ -253,7 +306,7 @@ impl MetalConnector {
new_fb: Option<&PresentFb>, new_fb: Option<&PresentFb>,
) -> Result<(), MetalError> { ) -> Result<(), MetalError> {
let mut changes = self.master.change(); let mut changes = self.master.change();
let mut try_async_flip = self.tearing_requested.get() && self.dev.supports_async_commit; let mut try_async_flip = self.try_async_flip();
macro_rules! change { macro_rules! change {
($c:expr, $prop:expr, $new:expr) => {{ ($c:expr, $prop:expr, $new:expr) => {{
if $prop.value.get() != $new { if $prop.value.get() != $new {

View file

@ -8,7 +8,11 @@ use {
HardwareCursorUpdate, Mode, MonitorInfo, HardwareCursorUpdate, Mode, MonitorInfo,
}, },
backends::metal::{ backends::metal::{
present::{DirectScanoutCache, PresentFb}, present::{
DirectScanoutCache, PresentFb, DEFAULT_POST_COMMIT_MARGIN,
DEFAULT_PRE_COMMIT_MARGIN, MAX_POST_COMMIT_MARGIN, MIN_POST_COMMIT_MARGIN,
POST_COMMIT_MARGIN_DELTA,
},
MetalBackend, MetalError, MetalBackend, MetalError,
}, },
drm_feedback::DrmFeedback, drm_feedback::DrmFeedback,
@ -27,8 +31,8 @@ use {
udev::UdevDevice, udev::UdevDevice,
utils::{ utils::{
asyncevent::AsyncEvent, bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell, asyncevent::AsyncEvent, bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell,
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, on_change::OnChange, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, geometric_decay::GeometricDecay,
opaque_cell::OpaqueCell, oserror::OsError, numcell::NumCell, on_change::OnChange, opaque_cell::OpaqueCell, oserror::OsError,
}, },
video::{ video::{
dmabuf::DmaBufId, dmabuf::DmaBufId,
@ -464,6 +468,13 @@ pub struct MetalConnector {
pub version: NumCell<u64>, pub version: NumCell<u64>,
pub sequence: Cell<u64>, pub sequence: Cell<u64>,
pub expected_sequence: Cell<Option<u64>>,
pub pre_commit_margin: Cell<u64>,
pub pre_commit_margin_decay: GeometricDecay,
pub post_commit_margin: Cell<u64>,
pub post_commit_margin_decay: GeometricDecay,
pub vblank_miss_sec: Cell<u32>,
pub vblank_miss_this_sec: NumCell<u32>,
} }
impl Debug for MetalConnector { impl Debug for MetalConnector {
@ -1055,6 +1066,13 @@ fn create_connector(
try_switch_format: Cell::new(false), try_switch_format: Cell::new(false),
version: Default::default(), version: Default::default(),
sequence: Default::default(), sequence: Default::default(),
expected_sequence: Default::default(),
pre_commit_margin_decay: GeometricDecay::new(0.5, DEFAULT_PRE_COMMIT_MARGIN),
pre_commit_margin: Cell::new(DEFAULT_PRE_COMMIT_MARGIN),
post_commit_margin_decay: GeometricDecay::new(0.1, DEFAULT_POST_COMMIT_MARGIN),
post_commit_margin: Cell::new(DEFAULT_POST_COMMIT_MARGIN),
vblank_miss_sec: Cell::new(0),
vblank_miss_this_sec: Default::default(),
}); });
let futures = ConnectorFutures { let futures = ConnectorFutures {
_present: backend _present: backend
@ -1924,6 +1942,30 @@ impl MetalBackend {
if let Some(fb) = connector.next_framebuffer.take() { if let Some(fb) = connector.next_framebuffer.take() {
*connector.active_framebuffer.borrow_mut() = Some(fb); *connector.active_framebuffer.borrow_mut() = Some(fb);
} }
if let Some(expected) = connector.expected_sequence.take() {
if connector.vblank_miss_sec.replace(tv_sec) != tv_sec {
let n_missed = connector.vblank_miss_this_sec.replace(0);
if n_missed > 0 {
log::debug!("{}: Missed {n_missed} page flips", connector.kernel_id());
let new_margin = (connector.post_commit_margin.get()
+ POST_COMMIT_MARGIN_DELTA)
.min(MAX_POST_COMMIT_MARGIN);
connector.post_commit_margin_decay.reset(new_margin);
connector.post_commit_margin.set(new_margin);
} else {
connector
.post_commit_margin_decay
.add(MIN_POST_COMMIT_MARGIN);
connector
.post_commit_margin
.set(connector.post_commit_margin_decay.get());
}
}
let actual = connector.sequence.get();
if expected < actual {
connector.vblank_miss_this_sec.fetch_add(1);
}
}
if connector.has_damage.is_not_zero() if connector.has_damage.is_not_zero()
|| connector.cursor_damage.get() || connector.cursor_damage.get()
|| connector.cursor_changed.get() || connector.cursor_changed.get()

View file

@ -58,6 +58,11 @@ impl Time {
let nsec = self.0.tv_nsec as u64 / 1_000_000; let nsec = self.0.tv_nsec as u64 / 1_000_000;
sec + nsec sec + nsec
} }
pub fn elapsed(self) -> Duration {
let now = Self::now_unchecked();
now - self
}
} }
impl Eq for Time {} impl Eq for Time {}

View file

@ -7,7 +7,6 @@ pub struct GeometricDecay {
} }
impl GeometricDecay { impl GeometricDecay {
#[expect(dead_code)]
pub fn new(mut p1: f64, reset: u64) -> Self { pub fn new(mut p1: f64, reset: u64) -> Self {
if p1.is_nan() || p1 < 0.01 { if p1.is_nan() || p1 < 0.01 {
p1 = 0.01; p1 = 0.01;
@ -23,17 +22,14 @@ impl GeometricDecay {
} }
} }
#[expect(dead_code)]
pub fn reset(&self, v: u64) { pub fn reset(&self, v: u64) {
self.v.set(v as f64 / self.p1); self.v.set(v as f64 / self.p1);
} }
#[expect(dead_code)]
pub fn get(&self) -> u64 { pub fn get(&self) -> u64 {
(self.p1 * self.v.get()) as u64 (self.p1 * self.v.get()) as u64
} }
#[expect(dead_code)]
pub fn add(&self, n: u64) { pub fn add(&self, n: u64) {
let v = n as f64 + self.p2 * self.v.get(); let v = n as f64 + self.p2 * self.v.get();
self.v.set(v); self.v.set(v);