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,
},
theme::Color,
time::Time,
tree::OutputNode,
utils::{errorfmt::ErrorFmt, oserror::OsError, transform_ext::TransformExt},
video::{
@ -22,7 +23,11 @@ use {
},
},
},
std::rc::{Rc, Weak},
std::{
env,
rc::{Rc, Weak},
sync::LazyLock,
},
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 {
pub fn schedule_present(&self) {
self.present_trigger.trigger();
}
pub async fn present_loop(self: Rc<Self>) {
let mut cur_sec = 0;
let mut max = 0;
loop {
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 {
log::error!("Could not present: {}", ErrorFmt(e));
continue;
}
if use_frame_scheduling {
self.expected_sequence.set(Some(expected_sequence));
}
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(
&self,
version: u64,
@ -253,7 +306,7 @@ impl MetalConnector {
new_fb: Option<&PresentFb>,
) -> Result<(), MetalError> {
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 {
($c:expr, $prop:expr, $new:expr) => {{
if $prop.value.get() != $new {

View file

@ -8,7 +8,11 @@ use {
HardwareCursorUpdate, Mode, MonitorInfo,
},
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,
},
drm_feedback::DrmFeedback,
@ -27,8 +31,8 @@ use {
udev::UdevDevice,
utils::{
asyncevent::AsyncEvent, bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell,
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, on_change::OnChange,
opaque_cell::OpaqueCell, oserror::OsError,
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, geometric_decay::GeometricDecay,
numcell::NumCell, on_change::OnChange, opaque_cell::OpaqueCell, oserror::OsError,
},
video::{
dmabuf::DmaBufId,
@ -464,6 +468,13 @@ pub struct MetalConnector {
pub version: NumCell<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 {
@ -1055,6 +1066,13 @@ fn create_connector(
try_switch_format: Cell::new(false),
version: 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 {
_present: backend
@ -1924,6 +1942,30 @@ impl MetalBackend {
if let Some(fb) = connector.next_framebuffer.take() {
*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()
|| connector.cursor_damage.get()
|| connector.cursor_changed.get()

View file

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

View file

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