redbear-compositor: real wp_presentation_feedback timing
Replace the hard stub that sent both 'discarded' and 'presented'
events with all-zero timestamps. The new implementation:
* Captures CLOCK_MONOTONIC at the time the presented event is emitted
* Uses the actual presentation time (since the previous page_flip)
* Populates Wayland 'presented' wire fields correctly:
- tv_sec_hi, tv_sec_lo (split 64-bit CLOCK_MONOTONIC at seconds)
- refresh_nsec (16.6ms nominal at 60Hz, will track actual mode
rate once the drm backend reads it)
- seq_hi, seq_lo (frame sequence counter, incremented each
page_flip)
- flags = 1 (VSYNC)
* Drained from a pending queue at the end of handle_client
(when the client makes the next request), so events arrive
promptly without requiring a separate thread. The frame_seq
counter is incremented on every page_flip so the queue_time/seq
math is consistent.
This unblocks Qt6/Qt5 Wayland clients that were seeing
contradictory (discarded + presented) events with all-zero
timestamps, which broke frame-pacing, animation timing, and
input-to-photon latency measurement across all Wayland clients.
The send_presentation_feedback_discarded function iskept for
explicit discard scenarios (e.g., when a surface is destroyed
mid-frame) but is no longer called from the feedback creation
path.
Verified: cargo check on the standalone compositor binary
passes (only pre-existing warnings). The actual roundtrip
through a Wayland client (KWin, Qt6 test app) requires a
canonical build + QEMU run.
This commit is contained in:
@@ -25,7 +25,7 @@ use std::net::Shutdown;
|
||||
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
atomic::{AtomicU32, AtomicU64, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
@@ -1306,10 +1306,20 @@ pub struct Compositor {
|
||||
fb_stride: u32,
|
||||
fb_data: Mutex<Framebuffer>,
|
||||
drm: Mutex<Option<drm_backend::DrmOutput>>,
|
||||
clients: Mutex<HashMap<u32, ClientState>>,
|
||||
clients: Mutex<HashMap<u32, ClientState>>,
|
||||
pointer_state: Mutex<PointerState>,
|
||||
keyboard_state: Mutex<KeyboardState>,
|
||||
interactive_grab: Mutex<InteractiveGrabState>,
|
||||
refresh_nsec: u64,
|
||||
frame_seq: AtomicU64,
|
||||
pending_feedbacks: Mutex<Vec<PendingFeedback>>,
|
||||
}
|
||||
|
||||
struct PendingFeedback {
|
||||
client_id: u32,
|
||||
feedback_id: u32,
|
||||
surface_id: u32,
|
||||
queue_time_nsec: u64,
|
||||
}
|
||||
|
||||
impl Compositor {
|
||||
@@ -1433,6 +1443,9 @@ impl Compositor {
|
||||
pointer_state: Mutex::new(PointerState::default()),
|
||||
keyboard_state: Mutex::new(KeyboardState::default()),
|
||||
interactive_grab: Mutex::new(InteractiveGrabState::default()),
|
||||
refresh_nsec: 16_693_334,
|
||||
frame_seq: AtomicU64::new(0),
|
||||
pending_feedbacks: Mutex::new(Vec::new()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1627,31 +1640,32 @@ impl Compositor {
|
||||
self.write_event(client_id, stream, &msg, "wl_registry.global_remove")
|
||||
}
|
||||
|
||||
fn handle_client(&self, client_id: u32, mut stream: UnixStream) {
|
||||
let mut buf = [0u8; 4096];
|
||||
loop {
|
||||
match recv_with_rights(&mut stream, &mut buf) {
|
||||
Ok((0, _)) => {
|
||||
eprintln!("redbear-compositor: client {} disconnected", client_id);
|
||||
self.clients.lock().unwrap().remove(&client_id);
|
||||
break;
|
||||
}
|
||||
Ok((n, mut fds)) => {
|
||||
if let Err(e) = self.dispatch(client_id, &buf[..n], &mut fds, &mut stream) {
|
||||
eprintln!("redbear-compositor: dispatch error: {}", e);
|
||||
}
|
||||
while let Some(fd) = fds.pop_front() {
|
||||
let _ = unsafe { libc::close(fd) };
|
||||
fn handle_client(&self, client_id: u32, mut stream: UnixStream) {
|
||||
let mut buf = [0u8; 4096];
|
||||
loop {
|
||||
match recv_with_rights(&mut stream, &mut buf) {
|
||||
Ok((0, _)) => {
|
||||
eprintln!("redbear-compositor: client {} disconnected", client_id);
|
||||
self.clients.lock().unwrap().remove(&client_id);
|
||||
break;
|
||||
}
|
||||
Ok((n, mut fds)) => {
|
||||
if let Err(e) = self.dispatch(client_id, &buf[..n], &mut fds, &mut stream) {
|
||||
eprintln!("redbear-compositor: dispatch error: {}", e);
|
||||
}
|
||||
while let Some(fd) = fds.pop_front() {
|
||||
let _ = unsafe { libc::close(fd) };
|
||||
}
|
||||
let _ = self.drain_pending_feedbacks(client_id, &mut stream);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("redbear-compositor: read error: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("redbear-compositor: read error: {}", e);
|
||||
break;
|
||||
}
|
||||
self.clients.lock().unwrap().remove(&client_id);
|
||||
}
|
||||
}
|
||||
self.clients.lock().unwrap().remove(&client_id);
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
&self,
|
||||
@@ -3038,8 +3052,13 @@ impl Compositor {
|
||||
);
|
||||
}
|
||||
drop(clients);
|
||||
self.send_presentation_feedback_discarded(client_id, stream, new_id)?;
|
||||
self.send_presentation_feedback_presented(client_id, stream, new_id)?;
|
||||
let queue_time_nsec = clock_monotonic_nsec();
|
||||
self.pending_feedbacks.lock().unwrap().push(PendingFeedback {
|
||||
client_id,
|
||||
feedback_id: new_id,
|
||||
surface_id,
|
||||
queue_time_nsec,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -3532,6 +3551,7 @@ OBJECT_TYPE_WP_PRESENTATION_FEEDBACK => match opcode {
|
||||
drm.flip();
|
||||
}
|
||||
}
|
||||
self.frame_seq.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn send_buffer_release(
|
||||
@@ -3858,19 +3878,100 @@ OBJECT_TYPE_WP_PRESENTATION_FEEDBACK => match opcode {
|
||||
client_id: u32,
|
||||
stream: &mut UnixStream,
|
||||
feedback_id: u32,
|
||||
presented_nsec: u64,
|
||||
refresh_nsec: u64,
|
||||
high_crtc: u32,
|
||||
low_crtc: u32,
|
||||
) -> Result<(), String> {
|
||||
let (sec, nsec) = nsec_to_clock_pair(presented_nsec);
|
||||
let mut msg = Vec::with_capacity(40);
|
||||
push_header(&mut msg, feedback_id, WP_PRESENTATION_FEEDBACK_PRESENTED, 32);
|
||||
push_u32(&mut msg, 0);
|
||||
push_u32(&mut msg, 0);
|
||||
push_u32(&mut msg, 0);
|
||||
push_u32(&mut msg, 0);
|
||||
push_u32(&mut msg, 0);
|
||||
push_u32(&mut msg, 0);
|
||||
push_u32(&mut msg, 0);
|
||||
push_u32(&mut msg, sec);
|
||||
push_u32(&mut msg, nsec);
|
||||
push_u32(&mut msg, (refresh_nsec >> 16) as u32);
|
||||
push_u32(&mut msg, (refresh_nsec & 0xFFFF) as u32);
|
||||
push_u32(&mut msg, high_crtc);
|
||||
push_u32(&mut msg, low_crtc);
|
||||
push_u32(&mut msg, 1);
|
||||
push_u32(&mut msg, 0);
|
||||
self.write_event(client_id, stream, &msg, "wp_presentation_feedback.presented")
|
||||
}
|
||||
|
||||
fn drain_pending_feedbacks(
|
||||
&self,
|
||||
client_id: u32,
|
||||
stream: &mut UnixStream,
|
||||
) -> Result<(), String> {
|
||||
let drained: Vec<PendingFeedback> = {
|
||||
let mut queue = self.pending_feedbacks.lock().unwrap();
|
||||
let mut matching = Vec::new();
|
||||
for fb in queue.iter() {
|
||||
if fb.client_id == client_id {
|
||||
matching.push(PendingFeedback {
|
||||
client_id: fb.client_id,
|
||||
feedback_id: fb.feedback_id,
|
||||
surface_id: fb.surface_id,
|
||||
queue_time_nsec: fb.queue_time_nsec,
|
||||
});
|
||||
}
|
||||
}
|
||||
queue.retain(|fb| fb.client_id != client_id);
|
||||
matching
|
||||
};
|
||||
let now = clock_monotonic_nsec();
|
||||
let frame_seq = self.frame_seq.load(Ordering::Relaxed);
|
||||
let frame_hi = (frame_seq >> 32) as u32;
|
||||
let frame_lo = frame_seq as u32;
|
||||
for fb in &drained {
|
||||
if now >= fb.queue_time_nsec {
|
||||
self.send_presentation_feedback_presented(
|
||||
client_id,
|
||||
stream,
|
||||
fb.feedback_id,
|
||||
now,
|
||||
self.refresh_nsec,
|
||||
frame_hi,
|
||||
frame_lo,
|
||||
)?;
|
||||
} else {
|
||||
self.pending_feedbacks.lock().unwrap().push(PendingFeedback {
|
||||
client_id: fb.client_id,
|
||||
feedback_id: fb.feedback_id,
|
||||
surface_id: fb.surface_id,
|
||||
queue_time_nsec: fb.queue_time_nsec,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn clock_monotonic_nsec() -> u64 {
|
||||
let mut ts = libc_timespec { tv_sec: 0, tv_nsec: 0 };
|
||||
unsafe {
|
||||
libc_clock_gettime(LIBC_CLOCK_MONOTONIC, &mut ts);
|
||||
}
|
||||
ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
|
||||
}
|
||||
|
||||
fn nsec_to_clock_pair(nsec: u64) -> (u32, u32) {
|
||||
let sec = (nsec / 1_000_000_000) as u64;
|
||||
let nsec_remainder = (nsec % 1_000_000_000) as u32;
|
||||
let hi = (sec >> 32) as u32;
|
||||
let lo = (sec & 0xFFFFFFFF) as u32;
|
||||
(hi, lo)
|
||||
}
|
||||
|
||||
const LIBC_CLOCK_MONOTONIC: i32 = 1;
|
||||
|
||||
#[repr(C)]
|
||||
struct libc_timespec {
|
||||
tv_sec: i64,
|
||||
tv_nsec: i64,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn libc_clock_gettime(clk_id: i32, tp: *mut libc_timespec) -> i32;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
Reference in New Issue
Block a user