linux/x11: Reduce input latency and ensure rerender priority (#13355)
This change ensures that we always render a window according to its refresh rate, even if there are a lot of X11 events. We're working around some limitations of `calloop`. In the future, we think we should revisit how the event loop is implemented on X11, so that we can ensure proper prioritization of input events vs. rendering. Release Notes: - N/A Co-authored-by: Antonio <me@as-cii.com>
This commit is contained in:
parent
04a79780d8
commit
f69c8ca74e
2 changed files with 79 additions and 60 deletions
|
@ -13,7 +13,6 @@ use util::ResultExt;
|
||||||
use x11rb::connection::{Connection, RequestConnection};
|
use x11rb::connection::{Connection, RequestConnection};
|
||||||
use x11rb::cursor;
|
use x11rb::cursor;
|
||||||
use x11rb::errors::ConnectionError;
|
use x11rb::errors::ConnectionError;
|
||||||
use x11rb::protocol::randr::ConnectionExt as _;
|
|
||||||
use x11rb::protocol::xinput::ConnectionExt;
|
use x11rb::protocol::xinput::ConnectionExt;
|
||||||
use x11rb::protocol::xkb::ConnectionExt as _;
|
use x11rb::protocol::xkb::ConnectionExt as _;
|
||||||
use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _};
|
use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _};
|
||||||
|
@ -46,7 +45,7 @@ use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSour
|
||||||
pub(super) const XINPUT_MASTER_DEVICE: u16 = 1;
|
pub(super) const XINPUT_MASTER_DEVICE: u16 = 1;
|
||||||
|
|
||||||
pub(crate) struct WindowRef {
|
pub(crate) struct WindowRef {
|
||||||
window: X11WindowStatePtr,
|
pub window: X11WindowStatePtr,
|
||||||
refresh_event_token: RegistrationToken,
|
refresh_event_token: RegistrationToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,13 +291,39 @@ impl X11Client {
|
||||||
.insert_source(
|
.insert_source(
|
||||||
Generic::new_with_error::<EventHandlerError>(
|
Generic::new_with_error::<EventHandlerError>(
|
||||||
fd,
|
fd,
|
||||||
calloop::Interest::READ,
|
calloop::Interest::BOTH,
|
||||||
calloop::Mode::Level,
|
calloop::Mode::Level,
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
let xcb_connection = xcb_connection.clone();
|
let xcb_connection = xcb_connection.clone();
|
||||||
move |_readiness, _, client| {
|
move |_readiness, _, client| {
|
||||||
|
let windows = client
|
||||||
|
.0
|
||||||
|
.borrow()
|
||||||
|
.windows
|
||||||
|
.values()
|
||||||
|
.map(|window_ref| window_ref.window.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
while let Some(event) = xcb_connection.poll_for_event()? {
|
while let Some(event) = xcb_connection.poll_for_event()? {
|
||||||
|
for window in &windows {
|
||||||
|
let last_render_at;
|
||||||
|
let refresh_rate;
|
||||||
|
{
|
||||||
|
let window_state = window.state.borrow();
|
||||||
|
last_render_at = window_state.last_render_at;
|
||||||
|
refresh_rate = window_state.refresh_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(last_render_at) = last_render_at {
|
||||||
|
if last_render_at.elapsed() >= refresh_rate {
|
||||||
|
window.refresh();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut state = client.0.borrow_mut();
|
let mut state = client.0.borrow_mut();
|
||||||
if state.ximc.is_none() || state.xim_handler.is_none() {
|
if state.ximc.is_none() || state.xim_handler.is_none() {
|
||||||
drop(state);
|
drop(state);
|
||||||
|
@ -955,60 +980,18 @@ impl LinuxClient for X11Client {
|
||||||
state.common.appearance,
|
state.common.appearance,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let screen_resources = state
|
|
||||||
.xcb_connection
|
|
||||||
.randr_get_screen_resources(x_window)
|
|
||||||
.unwrap()
|
|
||||||
.reply()
|
|
||||||
.expect("Could not find available screens");
|
|
||||||
|
|
||||||
let mode = screen_resources
|
|
||||||
.crtcs
|
|
||||||
.iter()
|
|
||||||
.find_map(|crtc| {
|
|
||||||
let crtc_info = state
|
|
||||||
.xcb_connection
|
|
||||||
.randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
|
|
||||||
.ok()?
|
|
||||||
.reply()
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
screen_resources
|
|
||||||
.modes
|
|
||||||
.iter()
|
|
||||||
.find(|m| m.id == crtc_info.mode)
|
|
||||||
})
|
|
||||||
.expect("Unable to find screen refresh rate");
|
|
||||||
|
|
||||||
let refresh_event_token = state
|
let refresh_event_token = state
|
||||||
.loop_handle
|
.loop_handle
|
||||||
.insert_source(calloop::timer::Timer::immediate(), {
|
.insert_source(calloop::timer::Timer::immediate(), {
|
||||||
let refresh_duration = mode_refresh_rate(mode);
|
let window = window.0.clone();
|
||||||
move |mut instant, (), client| {
|
let refresh_rate = window.state.borrow().refresh_rate;
|
||||||
let state = client.0.borrow_mut();
|
move |mut instant, (), _| {
|
||||||
state
|
window.refresh();
|
||||||
.xcb_connection
|
|
||||||
.send_event(
|
|
||||||
false,
|
|
||||||
x_window,
|
|
||||||
xproto::EventMask::EXPOSURE,
|
|
||||||
xproto::ExposeEvent {
|
|
||||||
response_type: xproto::EXPOSE_EVENT,
|
|
||||||
sequence: 0,
|
|
||||||
window: x_window,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
count: 1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let _ = state.xcb_connection.flush().unwrap();
|
|
||||||
// Take into account that some frames have been skipped
|
// Take into account that some frames have been skipped
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
while instant < now {
|
while instant < now {
|
||||||
instant += refresh_duration;
|
instant += refresh_rate;
|
||||||
}
|
}
|
||||||
calloop::timer::TimeoutAction::ToInstant(instant)
|
calloop::timer::TimeoutAction::ToInstant(instant)
|
||||||
}
|
}
|
||||||
|
@ -1146,15 +1129,6 @@ impl LinuxClient for X11Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adatpted from:
|
|
||||||
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
|
|
||||||
pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
|
|
||||||
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
|
|
||||||
let micros = 1_000_000_000 / millihertz;
|
|
||||||
log::info!("Refreshing at {} micros", micros);
|
|
||||||
Duration::from_micros(micros)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fp3232_to_f32(value: xinput::Fp3232) -> f32 {
|
fn fp3232_to_f32(value: xinput::Fp3232) -> f32 {
|
||||||
value.integral as f32 + value.frac as f32 / u32::MAX as f32
|
value.integral as f32 + value.frac as f32 / u32::MAX as f32
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ use util::{maybe, ResultExt};
|
||||||
use x11rb::{
|
use x11rb::{
|
||||||
connection::Connection,
|
connection::Connection,
|
||||||
protocol::{
|
protocol::{
|
||||||
|
randr::{self, ConnectionExt as _},
|
||||||
xinput::{self, ConnectionExt as _},
|
xinput::{self, ConnectionExt as _},
|
||||||
xproto::{
|
xproto::{
|
||||||
self, ClientMessageEvent, ConnectionExt as _, EventMask, TranslateCoordinatesReply,
|
self, ClientMessageEvent, ConnectionExt as _, EventMask, TranslateCoordinatesReply,
|
||||||
|
@ -31,6 +32,7 @@ use std::{
|
||||||
ptr::NonNull,
|
ptr::NonNull,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::{self, Arc},
|
sync::{self, Arc},
|
||||||
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{X11Display, XINPUT_MASTER_DEVICE};
|
use super::{X11Display, XINPUT_MASTER_DEVICE};
|
||||||
|
@ -159,6 +161,8 @@ pub struct Callbacks {
|
||||||
|
|
||||||
pub struct X11WindowState {
|
pub struct X11WindowState {
|
||||||
pub destroyed: bool,
|
pub destroyed: bool,
|
||||||
|
pub last_render_at: Option<Instant>,
|
||||||
|
pub refresh_rate: Duration,
|
||||||
client: X11ClientStatePtr,
|
client: X11ClientStatePtr,
|
||||||
executor: ForegroundExecutor,
|
executor: ForegroundExecutor,
|
||||||
atoms: XcbAtoms,
|
atoms: XcbAtoms,
|
||||||
|
@ -389,6 +393,31 @@ impl X11WindowState {
|
||||||
};
|
};
|
||||||
xcb_connection.map_window(x_window).unwrap();
|
xcb_connection.map_window(x_window).unwrap();
|
||||||
|
|
||||||
|
let screen_resources = xcb_connection
|
||||||
|
.randr_get_screen_resources(x_window)
|
||||||
|
.unwrap()
|
||||||
|
.reply()
|
||||||
|
.expect("Could not find available screens");
|
||||||
|
|
||||||
|
let mode = screen_resources
|
||||||
|
.crtcs
|
||||||
|
.iter()
|
||||||
|
.find_map(|crtc| {
|
||||||
|
let crtc_info = xcb_connection
|
||||||
|
.randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
|
||||||
|
.ok()?
|
||||||
|
.reply()
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
screen_resources
|
||||||
|
.modes
|
||||||
|
.iter()
|
||||||
|
.find(|m| m.id == crtc_info.mode)
|
||||||
|
})
|
||||||
|
.expect("Unable to find screen refresh rate");
|
||||||
|
|
||||||
|
let refresh_rate = mode_refresh_rate(mode);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
client,
|
client,
|
||||||
executor,
|
executor,
|
||||||
|
@ -405,6 +434,8 @@ impl X11WindowState {
|
||||||
appearance,
|
appearance,
|
||||||
handle,
|
handle,
|
||||||
destroyed: false,
|
destroyed: false,
|
||||||
|
last_render_at: None,
|
||||||
|
refresh_rate,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -574,6 +605,11 @@ impl X11WindowStatePtr {
|
||||||
let mut cb = self.callbacks.borrow_mut();
|
let mut cb = self.callbacks.borrow_mut();
|
||||||
if let Some(ref mut fun) = cb.request_frame {
|
if let Some(ref mut fun) = cb.request_frame {
|
||||||
fun();
|
fun();
|
||||||
|
|
||||||
|
self.state
|
||||||
|
.borrow_mut()
|
||||||
|
.last_render_at
|
||||||
|
.replace(Instant::now());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1020,3 +1056,12 @@ impl PlatformWindow for X11Window {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adatpted from:
|
||||||
|
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
|
||||||
|
pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
|
||||||
|
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
|
||||||
|
let micros = 1_000_000_000 / millihertz;
|
||||||
|
log::info!("Refreshing at {} micros", micros);
|
||||||
|
Duration::from_micros(micros)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue