linux/x11: Reduce input latency and ensure rerender priority (#13355)

Thorsten Ball and Antonio created

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>

Change summary

crates/gpui/src/platform/linux/x11/client.rs | 94 +++++++--------------
crates/gpui/src/platform/linux/x11/window.rs | 45 ++++++++++
2 files changed, 79 insertions(+), 60 deletions(-)

Detailed changes

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -13,7 +13,6 @@ use util::ResultExt;
 use x11rb::connection::{Connection, RequestConnection};
 use x11rb::cursor;
 use x11rb::errors::ConnectionError;
-use x11rb::protocol::randr::ConnectionExt as _;
 use x11rb::protocol::xinput::ConnectionExt;
 use x11rb::protocol::xkb::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(crate) struct WindowRef {
-    window: X11WindowStatePtr,
+    pub window: X11WindowStatePtr,
     refresh_event_token: RegistrationToken,
 }
 
@@ -292,13 +291,39 @@ impl X11Client {
             .insert_source(
                 Generic::new_with_error::<EventHandlerError>(
                     fd,
-                    calloop::Interest::READ,
+                    calloop::Interest::BOTH,
                     calloop::Mode::Level,
                 ),
                 {
                     let xcb_connection = xcb_connection.clone();
                     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()? {
+                            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();
                             if state.ximc.is_none() || state.xim_handler.is_none() {
                                 drop(state);
@@ -955,60 +980,18 @@ impl LinuxClient for X11Client {
             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
             .loop_handle
             .insert_source(calloop::timer::Timer::immediate(), {
-                let refresh_duration = mode_refresh_rate(mode);
-                move |mut instant, (), client| {
-                    let state = client.0.borrow_mut();
-                    state
-                        .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();
+                let window = window.0.clone();
+                let refresh_rate = window.state.borrow().refresh_rate;
+                move |mut instant, (), _| {
+                    window.refresh();
+
                     // Take into account that some frames have been skipped
                     let now = Instant::now();
                     while instant < now {
-                        instant += refresh_duration;
+                        instant += refresh_rate;
                     }
                     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 {
     value.integral as f32 + value.frac as f32 / u32::MAX as f32
 }

crates/gpui/src/platform/linux/x11/window.rs 🔗

@@ -14,6 +14,7 @@ use util::{maybe, ResultExt};
 use x11rb::{
     connection::Connection,
     protocol::{
+        randr::{self, ConnectionExt as _},
         xinput::{self, ConnectionExt as _},
         xproto::{
             self, ClientMessageEvent, ConnectionExt as _, EventMask, TranslateCoordinatesReply,
@@ -31,6 +32,7 @@ use std::{
     ptr::NonNull,
     rc::Rc,
     sync::{self, Arc},
+    time::{Duration, Instant},
 };
 
 use super::{X11Display, XINPUT_MASTER_DEVICE};
@@ -159,6 +161,8 @@ pub struct Callbacks {
 
 pub struct X11WindowState {
     pub destroyed: bool,
+    pub last_render_at: Option<Instant>,
+    pub refresh_rate: Duration,
     client: X11ClientStatePtr,
     executor: ForegroundExecutor,
     atoms: XcbAtoms,
@@ -389,6 +393,31 @@ impl X11WindowState {
         };
         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 {
             client,
             executor,
@@ -405,6 +434,8 @@ impl X11WindowState {
             appearance,
             handle,
             destroyed: false,
+            last_render_at: None,
+            refresh_rate,
         })
     }
 
@@ -574,6 +605,11 @@ impl X11WindowStatePtr {
         let mut cb = self.callbacks.borrow_mut();
         if let Some(ref mut fun) = cb.request_frame {
             fun();
+
+            self.state
+                .borrow_mut()
+                .last_render_at
+                .replace(Instant::now());
         }
     }
 
@@ -1020,3 +1056,12 @@ impl PlatformWindow for X11Window {
         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)
+}