metal: commit 1.5ms before the next page flip
This commit is contained in:
parent
a37ce1acda
commit
87d60d267e
4 changed files with 105 additions and 9 deletions
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue