Commit f887aaaeea0d
tarball1 file changed+86-2
@@ -21,6 +21,7 @@ use wayland_client::{Connection, Dispatch, QueueHandle, delegate_noop};
2121
use wayland_protocols::wp::linux_dmabuf::zv1::client::{2222
zwp_linux_buffer_params_v1, zwp_linux_dmabuf_v1,2323
};24
+use wayland_protocols::wp::presentation_time::client::{wp_presentation, wp_presentation_feedback};2425
use wayland_protocols::xdg::decoration::zv1::client::{2526
zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1,2627
};@@ -28,6 +29,7 @@ use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_ba
2829
2930
const CLEAR: [f32; 4] = [0.07, 0.075, 0.09, 1.0];3031
const DEFAULT_SIZE: (i32, i32) = (1280, 800);32
+const CLOCK_MONOTONIC: u32 = 1;3133
3234
pub struct Host {3335
pub size: Size<i32, Logical>,@@ -46,6 +48,24 @@ pub struct Host {
4648
configured: bool,4749
frame_pending: bool,4850
pending_size: Option<Size<i32, Logical>>,51
+ presentation: Option<wp_presentation::WpPresentation>,52
+ /// The host clock is CLOCK_MONOTONIC, so its timestamps share our timeline.53
+ monotonic: bool,54
+ last_present_ns: Option<u64>,55
+ refresh_ns: u64,56
+}57
+58
+impl Host {59
+ /// When a frame committed now will reach the screen, the next host60
+ /// vblank after `now`.61
+ pub fn predict(&self, now: u64) -> u64 {62
+ match self.last_present_ns.filter(|_| self.monotonic) {63
+ Some(last) if self.refresh_ns > 0 && last <= now => {64
+ last + ((now - last) / self.refresh_ns + 1) * self.refresh_ns65
+ }66
+ _ => now,67
+ }68
+ }4969
}5070
5171
/// Destroys the host-side buffer when its swapchain slot goes away.@@ -67,6 +87,9 @@ pub fn init(
6787
let compositor: wl_compositor::WlCompositor = globals.bind(&qh, 4..=6, ())?;6888
let wm_base: xdg_wm_base::XdgWmBase = globals.bind(&qh, 1..=6, ())?;6989
let dmabuf: zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1 = globals.bind(&qh, 3..=3, ())?;90
+ let presentation = globals91
+ .bind::<wp_presentation::WpPresentation, _, _>(&qh, 1..=2, ())92
+ .ok();7093
// Dropping a proxy handle does not destroy it, so seat events keep reaching input.rs.7194
let _ = globals.bind::<wayland_client::protocol::wl_seat::WlSeat, _, _>(&qh, 1..=7, ());7295
@@ -131,6 +154,10 @@ pub fn init(
131154
configured: false,132155
frame_pending: false,133156
pending_size: None,157
+ presentation,158
+ monotonic: false,159
+ last_present_ns: None,160
+ refresh_ns: 16_666_667,134161
});135162
queue.roundtrip(state)?;136163
queue.roundtrip(state)?;@@ -159,7 +186,7 @@ pub fn render(state: &mut State) {
159186
host.size = size;160187
let mode = Mode {161188
size: (size.w, size.h).into(),162
- refresh: 60_000,189
+ refresh: (1_000_000_000_000 / host.refresh_ns) as i32,163190
};164191
host.output165192
.change_current_state(Some(mode), None, None, None);@@ -168,6 +195,7 @@ pub fn render(state: &mut State) {
168195
sc.resize(size.w as u32, size.h as u32);169196
}170197
}198
+ let now = host.predict(crate::state::now_ns());171199
if host.swapchain.is_none() {172200
host.swapchain = make_swapchain(renderer, &host.formats, host.size);173201
}@@ -182,7 +210,6 @@ pub fn render(state: &mut State) {
182210
return;183211
}184212
};185
- let now = crate::state::now_ns();186213
let mut dmabuf = (*slot).clone();187214
let damage = {188215
let mut fb = match renderer.bind(&mut dmabuf) {@@ -240,6 +267,9 @@ pub fn render(state: &mut State) {
240267
.damage_buffer(r.loc.x, r.loc.y, r.size.w, r.size.h);241268
}242269
host.surface.frame(&host.qh, ());270
+ if let Some(p) = &host.presentation {271
+ p.feedback(&host.surface, &host.qh, ());272
+ }243273
host.surface.commit();244274
swapchain.submitted(&slot);245275
host.in_flight.push((buffer, slot));@@ -441,3 +471,57 @@ delegate_noop!(State: ignore wl_surface::WlSurface);
441471
delegate_noop!(State: ignore zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1);442472
delegate_noop!(State: zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);443473
delegate_noop!(State: ignore zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1);474
+475
+impl Dispatch<wp_presentation::WpPresentation, ()> for State {476
+ fn event(477
+ state: &mut Self,478
+ _: &wp_presentation::WpPresentation,479
+ event: wp_presentation::Event,480
+ _: &(),481
+ _: &Connection,482
+ _: &QueueHandle<Self>,483
+ ) {484
+ if let wp_presentation::Event::ClockId { clk_id } = event485
+ && let Some(host) = state.host.as_mut()486
+ {487
+ host.monotonic = clk_id == CLOCK_MONOTONIC;488
+ }489
+ }490
+}491
+492
+impl Dispatch<wp_presentation_feedback::WpPresentationFeedback, ()> for State {493
+ fn event(494
+ state: &mut Self,495
+ _: &wp_presentation_feedback::WpPresentationFeedback,496
+ event: wp_presentation_feedback::Event,497
+ _: &(),498
+ _: &Connection,499
+ _: &QueueHandle<Self>,500
+ ) {501
+ let wp_presentation_feedback::Event::Presented {502
+ tv_sec_hi,503
+ tv_sec_lo,504
+ tv_nsec,505
+ refresh,506
+ ..507
+ } = event508
+ else {509
+ return;510
+ };511
+ let Some(host) = state.host.as_mut() else {512
+ return;513
+ };514
+ let secs = (u64::from(tv_sec_hi) << 32) | u64::from(tv_sec_lo);515
+ host.last_present_ns = Some(secs * 1_000_000_000 + u64::from(tv_nsec));516
+ if refresh > 0 && u64::from(refresh) != host.refresh_ns {517
+ host.refresh_ns = u64::from(refresh);518
+ let mode = Mode {519
+ size: (host.size.w, host.size.h).into(),520
+ refresh: (1_000_000_000_000 / u64::from(refresh)) as i32,521
+ };522
+ host.output523
+ .change_current_state(Some(mode), None, None, None);524
+ host.output.set_preferred(mode);525
+ }526
+ }527
+}