1
0
Fork 0
forked from wry/wry

Merge pull request #201 from mahkoh/jorth/page-flip-delay

metal: delay rendering until shortly before page flip
This commit is contained in:
mahkoh 2024-05-20 15:38:15 +02:00 committed by GitHub
commit 98f5e14ed0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 113 additions and 22 deletions

View file

@ -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)

View file

@ -18,6 +18,7 @@ use {
renderer::RenderResult,
state::State,
theme::Color,
time::now_nsec,
tree::OutputNode,
udev::UdevDevice,
utils::{
@ -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},
@ -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>,
@ -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) => {
@ -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
@ -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();

View file

@ -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::{
@ -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(),
@ -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>>,

View file

@ -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>;

View file

@ -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()

View file

@ -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 _)

View file

@ -1,10 +1,8 @@
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,
};
@ -12,27 +10,35 @@ use {
#[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(())
}
}
@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
@ -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()
}