Skip to content

Commit

Permalink
metal: delay rendering until shortly before page flip
Browse files Browse the repository at this point in the history
  • Loading branch information
mahkoh committed May 20, 2024
1 parent 33d5c61 commit c2d31cb
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 22 deletions.
4 changes: 4 additions & 0 deletions release-notes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Unreleased

- Add remaining layer-shell features.
- Add JAY_MAX_RENDER_TIME_NSEC environment variable.
This can be used to delay rendering until shortly before a page flip, reducing input
delay.
This is an unstable feature that might change in the future.

# 1.2.0 (2024-05-05)

Expand Down
21 changes: 21 additions & 0 deletions src/backends/metal/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use {
renderer::RenderResult,
state::State,
theme::Color,
time::now_nsec,
tree::OutputNode,
udev::UdevDevice,
utils::{
Expand Down Expand Up @@ -45,6 +46,7 @@ use {
indexmap::{indexset, IndexSet},
isnt::std_1::collections::IsntHashMap2Ext,
jay_config::video::GfxApi,
once_cell::sync::Lazy,
std::{
any::Any,
cell::{Cell, RefCell},
Expand Down Expand Up @@ -416,6 +418,7 @@ pub struct MetalConnector {
pub can_present: Cell<bool>,
pub has_damage: Cell<bool>,
pub cursor_changed: Cell<bool>,
pub next_flip_nsec: Cell<u64>,

pub display: RefCell<ConnectorDisplayData>,

Expand Down Expand Up @@ -578,6 +581,20 @@ impl MetalConnector {
async fn present_loop(self: Rc<Self>) {
loop {
self.present_trigger.triggered().await;
static DELTA: Lazy<Option<u64>> = Lazy::new(|| {
if let Ok(max_render_time) = std::env::var("JAY_MAX_RENDER_TIME_NSEC") {
if let Ok(max_render_time) = max_render_time.parse() {
return Some(max_render_time);
}
}
None
});
if let Some(delta) = *DELTA {
let next_present = self.next_flip_nsec.get().saturating_sub(delta);
if now_nsec() < next_present {
self.state.ring.timeout(next_present).await.unwrap();
}
}
match self.present(true) {
Ok(_) => self.state.set_backend_idle(false),
Err(e) => {
Expand Down Expand Up @@ -1397,6 +1414,7 @@ fn create_connector(
active_framebuffer: Default::default(),
next_framebuffer: Default::default(),
direct_scanout_active: Cell::new(false),
next_flip_nsec: Cell::new(0),
});
let futures = ConnectorFutures {
present: backend
Expand Down Expand Up @@ -2161,6 +2179,9 @@ impl MetalBackend {
connector.schedule_present();
}
let dd = connector.display.borrow_mut();
connector
.next_flip_nsec
.set(tv_sec as u64 * 1_000_000_000 + tv_usec as u64 * 1000 + dd.refresh as u64);
{
let global = self.state.root.outputs.get(&connector.connector_id);
let mut rr = connector.render_result.borrow_mut();
Expand Down
4 changes: 3 additions & 1 deletion src/io_uring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use {
ops::{
accept::AcceptTask, async_cancel::AsyncCancelTask, connect::ConnectTask,
poll::PollTask, read_write::ReadWriteTask, recvmsg::RecvmsgTask,
sendmsg::SendmsgTask, timeout::TimeoutTask,
sendmsg::SendmsgTask, timeout::TimeoutTask, timeout_link::TimeoutLinkTask,
},
pending_result::PendingResults,
sys::{
Expand Down Expand Up @@ -211,6 +211,7 @@ impl IoUring {
cached_sendmsg: Default::default(),
cached_recvmsg: Default::default(),
cached_timeouts: Default::default(),
cached_timeout_links: Default::default(),
cached_cmsg_bufs: Default::default(),
cached_connects: Default::default(),
cached_accepts: Default::default(),
Expand Down Expand Up @@ -266,6 +267,7 @@ struct IoUringData {
cached_sendmsg: Stack<Box<SendmsgTask>>,
cached_recvmsg: Stack<Box<RecvmsgTask>>,
cached_timeouts: Stack<Box<TimeoutTask>>,
cached_timeout_links: Stack<Box<TimeoutLinkTask>>,
cached_cmsg_bufs: Stack<Buf>,
cached_connects: Stack<Box<ConnectTask>>,
cached_accepts: Stack<Box<AcceptTask>>,
Expand Down
1 change: 1 addition & 0 deletions src/io_uring/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod read_write;
pub mod recvmsg;
pub mod sendmsg;
pub mod timeout;
pub mod timeout_link;

pub type TaskResult<T> = Result<Result<T, OsError>, IoUringError>;

Expand Down
2 changes: 1 addition & 1 deletion src/io_uring/ops/read_write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl IoUring {
});
self.ring.schedule(pw);
if let Some(time) = timeout {
self.schedule_timeout(time);
self.schedule_timeout_link(time);
}
}
Ok(pr.await.map(|v| v as usize)).merge()
Expand Down
2 changes: 1 addition & 1 deletion src/io_uring/ops/sendmsg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl IoUring {
st.has_timeout = timeout.is_some();
self.ring.schedule(st);
if let Some(timeout) = timeout {
self.schedule_timeout(timeout);
self.schedule_timeout_link(timeout);
}
}
Ok(pr.await? as _)
Expand Down
46 changes: 28 additions & 18 deletions src/io_uring/ops/timeout.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
use {
crate::{
io_uring::{
sys::{io_uring_sqe, IORING_OP_LINK_TIMEOUT, IORING_TIMEOUT_ABS},
IoUring, IoUringData, Task,
},
time::Time,
crate::io_uring::{
pending_result::PendingResult,
sys::{io_uring_sqe, IORING_OP_TIMEOUT, IORING_TIMEOUT_ABS},
IoUring, IoUringData, IoUringError, Task,
},
uapi::c,
};

#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Default)]
struct timespec64 {
tv_sec: i64,
tv_nsec: c::c_long,
pub(super) struct timespec64 {
pub tv_sec: i64,
pub tv_nsec: c::c_long,
}

#[derive(Default)]
pub struct TimeoutTask {
id: u64,
timespec: timespec64,
pr: Option<PendingResult>,
}

impl IoUring {
pub(super) fn schedule_timeout(&self, timeout: Time) {
let id = self.ring.id_raw();
pub async fn timeout(&self, timeout_nsec: u64) -> Result<(), IoUringError> {
self.ring.check_destroyed()?;
let id = self.ring.id();
let pr = self.ring.pending_results.acquire();
{
let mut to = self.ring.cached_timeouts.pop().unwrap_or_default();
to.id = id;
to.timespec.tv_sec = timeout.0.tv_sec as _;
to.timespec.tv_nsec = timeout.0.tv_nsec as _;
self.ring.schedule(to);
let mut pw = self.ring.cached_timeouts.pop().unwrap_or_default();
pw.id = id.id;
pw.timespec = timespec64 {
tv_sec: (timeout_nsec / 1_000_000_000) as _,
tv_nsec: (timeout_nsec % 1_000_000_000) as _,
};
pw.pr = Some(pr.clone());
self.ring.schedule(pw);
}
let _ = pr.await;
Ok(())
}
}

Expand All @@ -41,14 +47,18 @@ unsafe impl Task for TimeoutTask {
self.id
}

fn complete(self: Box<Self>, ring: &IoUringData, _res: i32) {
fn complete(mut self: Box<Self>, ring: &IoUringData, res: i32) {
if let Some(pr) = self.pr.take() {
pr.complete(res);
}
ring.cached_timeouts.push(self);
}

fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = IORING_OP_LINK_TIMEOUT;
sqe.opcode = IORING_OP_TIMEOUT;
sqe.u2.addr = &self.timespec as *const _ as _;
sqe.len = 1;
sqe.u3.timeout_flags = IORING_TIMEOUT_ABS;
sqe.u1.off = 0;
}
}
44 changes: 44 additions & 0 deletions src/io_uring/ops/timeout_link.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use crate::{
io_uring::{
ops::timeout::timespec64,
sys::{io_uring_sqe, IORING_OP_LINK_TIMEOUT, IORING_TIMEOUT_ABS},
IoUring, IoUringData, Task,
},
time::Time,
};

#[derive(Default)]
pub struct TimeoutLinkTask {
id: u64,
timespec: timespec64,
}

impl IoUring {
pub(super) fn schedule_timeout_link(&self, timeout: Time) {
let id = self.ring.id_raw();
{
let mut to = self.ring.cached_timeout_links.pop().unwrap_or_default();
to.id = id;
to.timespec.tv_sec = timeout.0.tv_sec as _;
to.timespec.tv_nsec = timeout.0.tv_nsec as _;
self.ring.schedule(to);
}
}
}

unsafe impl Task for TimeoutLinkTask {
fn id(&self) -> u64 {
self.id
}

fn complete(self: Box<Self>, ring: &IoUringData, _res: i32) {
ring.cached_timeout_links.push(self);
}

fn encode(&self, sqe: &mut io_uring_sqe) {
sqe.opcode = IORING_OP_LINK_TIMEOUT;
sqe.u2.addr = &self.timespec as *const _ as _;
sqe.len = 1;
sqe.u3.timeout_flags = IORING_TIMEOUT_ABS;
}
}
11 changes: 10 additions & 1 deletion src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ impl Time {
}
}

#[allow(dead_code)]
pub fn nsec(self) -> u64 {
let sec = self.0.tv_sec as u64 * 1_000_000_000;
let nsec = self.0.tv_nsec as u64;
sec + nsec
}

pub fn usec(self) -> u64 {
let sec = self.0.tv_sec as u64 * 1_000_000;
let nsec = self.0.tv_nsec as u64 / 1_000;
Expand Down Expand Up @@ -119,6 +124,10 @@ impl Add<Duration> for Time {
}
}

pub fn now_nsec() -> u64 {
Time::now_unchecked().nsec()
}

pub fn now_usec() -> u64 {
Time::now_unchecked().usec()
}
Expand Down

0 comments on commit c2d31cb

Please sign in to comment.