Add frame clock

Tracks the presentation time and allows querying the next presentation
time.
This commit is contained in:
Ivan Molodetskikh
2023-08-14 15:53:24 +04:00
parent 529a24cc14
commit c65a4f1624
6 changed files with 118 additions and 10 deletions
+60
View File
@@ -0,0 +1,60 @@
use std::num::NonZeroU64;
use std::time::Duration;
use crate::utils::get_monotonic_time;
#[derive(Debug)]
pub struct FrameClock {
last_presentation_time: Option<Duration>,
refresh_interval_ns: Option<NonZeroU64>,
}
impl FrameClock {
pub fn new(refresh_interval: Option<Duration>) -> Self {
let refresh_interval_ns = if let Some(interval) = &refresh_interval {
assert_eq!(interval.as_secs(), 0);
Some(NonZeroU64::new(interval.subsec_nanos().into()).unwrap())
} else {
None
};
Self {
last_presentation_time: None,
refresh_interval_ns,
}
}
pub fn presented(&mut self, presentation_time: Duration) {
if presentation_time.is_zero() {
// Not interested in these.
return;
}
self.last_presentation_time = Some(presentation_time);
}
pub fn next_presentation_time(&self) -> Duration {
let mut now = get_monotonic_time();
let Some(refresh_interval_ns) = self.refresh_interval_ns else {
return now;
};
let Some(last_presentation_time) = self.last_presentation_time else {
return now;
};
let refresh_interval_ns = refresh_interval_ns.get();
if now <= last_presentation_time {
// Got an early VBlank.
now += Duration::from_nanos(refresh_interval_ns);
// Assume two-frame early VBlanks don't happen. Overflow checks will catch them.
}
let since_last = now - last_presentation_time;
let since_last_ns =
since_last.as_secs() * 1_000_000_000 + u64::from(since_last.subsec_nanos());
let to_next_ns = (since_last_ns / refresh_interval_ns + 1) * refresh_interval_ns;
last_presentation_time + Duration::from_nanos(to_next_ns)
}
}
+2
View File
@@ -4,11 +4,13 @@ extern crate tracing;
mod handlers; mod handlers;
mod backend; mod backend;
mod frame_clock;
mod grabs; mod grabs;
mod input; mod input;
mod layout; mod layout;
mod niri; mod niri;
mod tty; mod tty;
mod utils;
mod winit; mod winit;
use std::env; use std::env;
+5 -1
View File
@@ -1,6 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use smithay::backend::renderer::element::render_elements; use smithay::backend::renderer::element::render_elements;
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
@@ -27,6 +28,7 @@ use smithay::wayland::shm::ShmState;
use smithay::wayland::socket::ListeningSocketSource; use smithay::wayland::socket::ListeningSocketSource;
use crate::backend::Backend; use crate::backend::Backend;
use crate::frame_clock::FrameClock;
use crate::layout::MonitorSet; use crate::layout::MonitorSet;
use crate::LoopData; use crate::LoopData;
@@ -71,6 +73,7 @@ pub struct OutputState {
// Set to `true` when the output was redrawn and is waiting for a VBlank. Upon VBlank a redraw // Set to `true` when the output was redrawn and is waiting for a VBlank. Upon VBlank a redraw
// will always be queued, so you cannot queue a redraw while waiting for a VBlank. // will always be queued, so you cannot queue a redraw while waiting for a VBlank.
pub waiting_for_vblank: bool, pub waiting_for_vblank: bool,
pub frame_clock: FrameClock,
} }
impl Niri { impl Niri {
@@ -164,7 +167,7 @@ impl Niri {
} }
} }
pub fn add_output(&mut self, output: Output) { pub fn add_output(&mut self, output: Output, refresh_interval: Option<Duration>) {
let x = self let x = self
.global_space .global_space
.outputs() .outputs()
@@ -180,6 +183,7 @@ impl Niri {
global: output.create_global::<Niri>(&self.display_handle), global: output.create_global::<Niri>(&self.display_handle),
queued_redraw: None, queued_redraw: None,
waiting_for_vblank: false, waiting_for_vblank: false,
frame_clock: FrameClock::new(refresh_interval),
}; };
let rv = self.output_state.insert(output, state); let rv = self.output_state.insert(output, state);
assert!(rv.is_none(), "output was already tracked"); assert!(rv.is_none(), "output was already tracked");
+43 -8
View File
@@ -1,13 +1,14 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::os::fd::FromRawFd; use std::os::fd::FromRawFd;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Duration;
use anyhow::anyhow; use anyhow::anyhow;
use smithay::backend::allocator::dmabuf::Dmabuf; use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}; use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};
use smithay::backend::allocator::{Format as DrmFormat, Fourcc}; use smithay::backend::allocator::{Format as DrmFormat, Fourcc};
use smithay::backend::drm::compositor::DrmCompositor; use smithay::backend::drm::compositor::DrmCompositor;
use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent}; use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime};
use smithay::backend::egl::{EGLContext, EGLDisplay}; use smithay::backend::egl::{EGLContext, EGLDisplay};
use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface}; use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface};
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture}; use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
@@ -17,7 +18,9 @@ use smithay::backend::session::{Event as SessionEvent, Session};
use smithay::backend::udev::{self, UdevBackend, UdevEvent}; use smithay::backend::udev::{self, UdevBackend, UdevEvent};
use smithay::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpixel}; use smithay::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpixel};
use smithay::reexports::calloop::{LoopHandle, RegistrationToken}; use smithay::reexports::calloop::{LoopHandle, RegistrationToken};
use smithay::reexports::drm::control::{connector, crtc, ModeTypeFlags}; use smithay::reexports::drm::control::{
connector, crtc, Mode as DrmMode, ModeFlags, ModeTypeFlags,
};
use smithay::reexports::input::Libinput; use smithay::reexports::input::Libinput;
use smithay::reexports::nix::fcntl::OFlag; use smithay::reexports::nix::fcntl::OFlag;
use smithay::reexports::nix::libc::dev_t; use smithay::reexports::nix::libc::dev_t;
@@ -248,6 +251,16 @@ impl Tty {
error!("error marking frame as submitted: {err}"); error!("error marking frame as submitted: {err}");
} }
let presentation_time = match metadata.as_mut().unwrap().time {
DrmEventTime::Monotonic(time) => time,
DrmEventTime::Realtime(_) => {
// Not supported.
// This value will be ignored in the frame clock code.
Duration::ZERO
}
};
// Send presentation time feedback. // Send presentation time feedback.
// catacomb // catacomb
// .windows // .windows
@@ -263,11 +276,9 @@ impl Tty {
}) })
.unwrap() .unwrap()
.clone(); .clone();
data.niri let output_state = data.niri.output_state.get_mut(&output).unwrap();
.output_state output_state.waiting_for_vblank = false;
.get_mut(&output) output_state.frame_clock.presented(presentation_time);
.unwrap()
.waiting_for_vblank = false;
data.niri.queue_redraw(output); data.niri.queue_redraw(output);
} }
DrmEvent::Error(error) => error!("DRM error: {error}"), DrmEvent::Error(error) => error!("DRM error: {error}"),
@@ -425,7 +436,7 @@ impl Tty {
let res = device.surfaces.insert(crtc, compositor); let res = device.surfaces.insert(crtc, compositor);
assert!(res.is_none(), "crtc must not have already existed"); assert!(res.is_none(), "crtc must not have already existed");
niri.add_output(output.clone()); niri.add_output(output.clone(), Some(refresh_interval(*mode)));
niri.queue_redraw(output); niri.queue_redraw(output);
Ok(()) Ok(())
@@ -464,3 +475,27 @@ impl Tty {
} }
} }
} }
fn refresh_interval(mode: DrmMode) -> Duration {
let clock = mode.clock() as u64;
let htotal = mode.hsync().2 as u64;
let vtotal = mode.vsync().2 as u64;
let mut numerator = htotal * vtotal * 1_000_000;
let mut denominator = clock;
if mode.flags().contains(ModeFlags::INTERLACE) {
denominator *= 2;
}
if mode.flags().contains(ModeFlags::DBLSCAN) {
numerator *= 2;
}
if mode.vscan() > 1 {
numerator *= mode.vscan() as u64;
}
let refresh_interval = (numerator + denominator / 2) / denominator;
Duration::from_nanos(refresh_interval)
}
+7
View File
@@ -0,0 +1,7 @@
use std::time::Duration;
use smithay::reexports::nix::time::{clock_gettime, ClockId};
pub fn get_monotonic_time() -> Duration {
Duration::from(clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap())
}
+1 -1
View File
@@ -110,7 +110,7 @@ impl Winit {
{ {
warn!("error binding renderer wl_display: {err}"); warn!("error binding renderer wl_display: {err}");
} }
niri.add_output(self.output.clone()); niri.add_output(self.output.clone(), None);
} }
fn dispatch(&mut self, niri: &mut Niri) { fn dispatch(&mut self, niri: &mut Niri) {