metal: delay rendering until shortly before page flip
This commit is contained in:
parent
33d5c61c37
commit
c2d31cb639
9 changed files with 113 additions and 22 deletions
|
|
@ -1,6 +1,10 @@
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
- Add remaining layer-shell features.
|
- 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)
|
# 1.2.0 (2024-05-05)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ use {
|
||||||
renderer::RenderResult,
|
renderer::RenderResult,
|
||||||
state::State,
|
state::State,
|
||||||
theme::Color,
|
theme::Color,
|
||||||
|
time::now_nsec,
|
||||||
tree::OutputNode,
|
tree::OutputNode,
|
||||||
udev::UdevDevice,
|
udev::UdevDevice,
|
||||||
utils::{
|
utils::{
|
||||||
|
|
@ -45,6 +46,7 @@ use {
|
||||||
indexmap::{indexset, IndexSet},
|
indexmap::{indexset, IndexSet},
|
||||||
isnt::std_1::collections::IsntHashMap2Ext,
|
isnt::std_1::collections::IsntHashMap2Ext,
|
||||||
jay_config::video::GfxApi,
|
jay_config::video::GfxApi,
|
||||||
|
once_cell::sync::Lazy,
|
||||||
std::{
|
std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
|
@ -416,6 +418,7 @@ pub struct MetalConnector {
|
||||||
pub can_present: Cell<bool>,
|
pub can_present: Cell<bool>,
|
||||||
pub has_damage: Cell<bool>,
|
pub has_damage: Cell<bool>,
|
||||||
pub cursor_changed: Cell<bool>,
|
pub cursor_changed: Cell<bool>,
|
||||||
|
pub next_flip_nsec: Cell<u64>,
|
||||||
|
|
||||||
pub display: RefCell<ConnectorDisplayData>,
|
pub display: RefCell<ConnectorDisplayData>,
|
||||||
|
|
||||||
|
|
@ -578,6 +581,20 @@ impl MetalConnector {
|
||||||
async fn present_loop(self: Rc<Self>) {
|
async fn present_loop(self: Rc<Self>) {
|
||||||
loop {
|
loop {
|
||||||
self.present_trigger.triggered().await;
|
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) {
|
match self.present(true) {
|
||||||
Ok(_) => self.state.set_backend_idle(false),
|
Ok(_) => self.state.set_backend_idle(false),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -1397,6 +1414,7 @@ fn create_connector(
|
||||||
active_framebuffer: Default::default(),
|
active_framebuffer: Default::default(),
|
||||||
next_framebuffer: Default::default(),
|
next_framebuffer: Default::default(),
|
||||||
direct_scanout_active: Cell::new(false),
|
direct_scanout_active: Cell::new(false),
|
||||||
|
next_flip_nsec: Cell::new(0),
|
||||||
});
|
});
|
||||||
let futures = ConnectorFutures {
|
let futures = ConnectorFutures {
|
||||||
present: backend
|
present: backend
|
||||||
|
|
@ -2161,6 +2179,9 @@ impl MetalBackend {
|
||||||
connector.schedule_present();
|
connector.schedule_present();
|
||||||
}
|
}
|
||||||
let dd = connector.display.borrow_mut();
|
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 global = self.state.root.outputs.get(&connector.connector_id);
|
||||||
let mut rr = connector.render_result.borrow_mut();
|
let mut rr = connector.render_result.borrow_mut();
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use {
|
||||||
ops::{
|
ops::{
|
||||||
accept::AcceptTask, async_cancel::AsyncCancelTask, connect::ConnectTask,
|
accept::AcceptTask, async_cancel::AsyncCancelTask, connect::ConnectTask,
|
||||||
poll::PollTask, read_write::ReadWriteTask, recvmsg::RecvmsgTask,
|
poll::PollTask, read_write::ReadWriteTask, recvmsg::RecvmsgTask,
|
||||||
sendmsg::SendmsgTask, timeout::TimeoutTask,
|
sendmsg::SendmsgTask, timeout::TimeoutTask, timeout_link::TimeoutLinkTask,
|
||||||
},
|
},
|
||||||
pending_result::PendingResults,
|
pending_result::PendingResults,
|
||||||
sys::{
|
sys::{
|
||||||
|
|
@ -211,6 +211,7 @@ impl IoUring {
|
||||||
cached_sendmsg: Default::default(),
|
cached_sendmsg: Default::default(),
|
||||||
cached_recvmsg: Default::default(),
|
cached_recvmsg: Default::default(),
|
||||||
cached_timeouts: Default::default(),
|
cached_timeouts: Default::default(),
|
||||||
|
cached_timeout_links: Default::default(),
|
||||||
cached_cmsg_bufs: Default::default(),
|
cached_cmsg_bufs: Default::default(),
|
||||||
cached_connects: Default::default(),
|
cached_connects: Default::default(),
|
||||||
cached_accepts: Default::default(),
|
cached_accepts: Default::default(),
|
||||||
|
|
@ -266,6 +267,7 @@ struct IoUringData {
|
||||||
cached_sendmsg: Stack<Box<SendmsgTask>>,
|
cached_sendmsg: Stack<Box<SendmsgTask>>,
|
||||||
cached_recvmsg: Stack<Box<RecvmsgTask>>,
|
cached_recvmsg: Stack<Box<RecvmsgTask>>,
|
||||||
cached_timeouts: Stack<Box<TimeoutTask>>,
|
cached_timeouts: Stack<Box<TimeoutTask>>,
|
||||||
|
cached_timeout_links: Stack<Box<TimeoutLinkTask>>,
|
||||||
cached_cmsg_bufs: Stack<Buf>,
|
cached_cmsg_bufs: Stack<Buf>,
|
||||||
cached_connects: Stack<Box<ConnectTask>>,
|
cached_connects: Stack<Box<ConnectTask>>,
|
||||||
cached_accepts: Stack<Box<AcceptTask>>,
|
cached_accepts: Stack<Box<AcceptTask>>,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ pub mod read_write;
|
||||||
pub mod recvmsg;
|
pub mod recvmsg;
|
||||||
pub mod sendmsg;
|
pub mod sendmsg;
|
||||||
pub mod timeout;
|
pub mod timeout;
|
||||||
|
pub mod timeout_link;
|
||||||
|
|
||||||
pub type TaskResult<T> = Result<Result<T, OsError>, IoUringError>;
|
pub type TaskResult<T> = Result<Result<T, OsError>, IoUringError>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ impl IoUring {
|
||||||
});
|
});
|
||||||
self.ring.schedule(pw);
|
self.ring.schedule(pw);
|
||||||
if let Some(time) = timeout {
|
if let Some(time) = timeout {
|
||||||
self.schedule_timeout(time);
|
self.schedule_timeout_link(time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(pr.await.map(|v| v as usize)).merge()
|
Ok(pr.await.map(|v| v as usize)).merge()
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ impl IoUring {
|
||||||
st.has_timeout = timeout.is_some();
|
st.has_timeout = timeout.is_some();
|
||||||
self.ring.schedule(st);
|
self.ring.schedule(st);
|
||||||
if let Some(timeout) = timeout {
|
if let Some(timeout) = timeout {
|
||||||
self.schedule_timeout(timeout);
|
self.schedule_timeout_link(timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(pr.await? as _)
|
Ok(pr.await? as _)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::io_uring::{
|
||||||
io_uring::{
|
pending_result::PendingResult,
|
||||||
sys::{io_uring_sqe, IORING_OP_LINK_TIMEOUT, IORING_TIMEOUT_ABS},
|
sys::{io_uring_sqe, IORING_OP_TIMEOUT, IORING_TIMEOUT_ABS},
|
||||||
IoUring, IoUringData, Task,
|
IoUring, IoUringData, IoUringError, Task,
|
||||||
},
|
|
||||||
time::Time,
|
|
||||||
},
|
},
|
||||||
uapi::c,
|
uapi::c,
|
||||||
};
|
};
|
||||||
|
|
@ -12,27 +10,35 @@ use {
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct timespec64 {
|
pub(super) struct timespec64 {
|
||||||
tv_sec: i64,
|
pub tv_sec: i64,
|
||||||
tv_nsec: c::c_long,
|
pub tv_nsec: c::c_long,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct TimeoutTask {
|
pub struct TimeoutTask {
|
||||||
id: u64,
|
id: u64,
|
||||||
timespec: timespec64,
|
timespec: timespec64,
|
||||||
|
pr: Option<PendingResult>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IoUring {
|
impl IoUring {
|
||||||
pub(super) fn schedule_timeout(&self, timeout: Time) {
|
pub async fn timeout(&self, timeout_nsec: u64) -> Result<(), IoUringError> {
|
||||||
let id = self.ring.id_raw();
|
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();
|
let mut pw = self.ring.cached_timeouts.pop().unwrap_or_default();
|
||||||
to.id = id;
|
pw.id = id.id;
|
||||||
to.timespec.tv_sec = timeout.0.tv_sec as _;
|
pw.timespec = timespec64 {
|
||||||
to.timespec.tv_nsec = timeout.0.tv_nsec as _;
|
tv_sec: (timeout_nsec / 1_000_000_000) as _,
|
||||||
self.ring.schedule(to);
|
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
|
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);
|
ring.cached_timeouts.push(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(&self, sqe: &mut io_uring_sqe) {
|
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.u2.addr = &self.timespec as *const _ as _;
|
||||||
sqe.len = 1;
|
sqe.len = 1;
|
||||||
sqe.u3.timeout_flags = IORING_TIMEOUT_ABS;
|
sqe.u3.timeout_flags = IORING_TIMEOUT_ABS;
|
||||||
|
sqe.u1.off = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
44
src/io_uring/ops/timeout_link.rs
Normal file
44
src/io_uring/ops/timeout_link.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/time.rs
11
src/time.rs
|
|
@ -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 {
|
pub fn usec(self) -> u64 {
|
||||||
let sec = self.0.tv_sec as u64 * 1_000_000;
|
let sec = self.0.tv_sec as u64 * 1_000_000;
|
||||||
let nsec = self.0.tv_nsec as u64 / 1_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 {
|
pub fn now_usec() -> u64 {
|
||||||
Time::now_unchecked().usec()
|
Time::now_unchecked().usec()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue