diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2023-08-14 15:53:24 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2023-08-14 15:53:24 +0400 |
| commit | c65a4f162405d1b8c780925b723feeb481c87919 (patch) | |
| tree | ba18ba0bcafc2bbf2c0014179679479325441fdb | |
| parent | 529a24cc14859890029ad20ba07a589f4b3bc559 (diff) | |
| download | niri-c65a4f162405d1b8c780925b723feeb481c87919.tar.gz niri-c65a4f162405d1b8c780925b723feeb481c87919.tar.bz2 niri-c65a4f162405d1b8c780925b723feeb481c87919.zip | |
Add frame clock
Tracks the presentation time and allows querying the next presentation
time.
| -rw-r--r-- | src/frame_clock.rs | 60 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/niri.rs | 6 | ||||
| -rw-r--r-- | src/tty.rs | 51 | ||||
| -rw-r--r-- | src/utils.rs | 7 | ||||
| -rw-r--r-- | src/winit.rs | 2 |
6 files changed, 118 insertions, 10 deletions
diff --git a/src/frame_clock.rs b/src/frame_clock.rs new file mode 100644 index 00000000..5d540f8d --- /dev/null +++ b/src/frame_clock.rs @@ -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) + } +} diff --git a/src/main.rs b/src/main.rs index 5da2cf5a..49a8598d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,11 +4,13 @@ extern crate tracing; mod handlers; mod backend; +mod frame_clock; mod grabs; mod input; mod layout; mod niri; mod tty; +mod utils; mod winit; use std::env; diff --git a/src/niri.rs b/src/niri.rs index a55811f6..905f0738 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::os::unix::io::AsRawFd; use std::sync::Arc; +use std::time::Duration; use smithay::backend::renderer::element::render_elements; use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; @@ -27,6 +28,7 @@ use smithay::wayland::shm::ShmState; use smithay::wayland::socket::ListeningSocketSource; use crate::backend::Backend; +use crate::frame_clock::FrameClock; use crate::layout::MonitorSet; 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 // will always be queued, so you cannot queue a redraw while waiting for a VBlank. pub waiting_for_vblank: bool, + pub frame_clock: FrameClock, } 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 .global_space .outputs() @@ -180,6 +183,7 @@ impl Niri { global: output.create_global::<Niri>(&self.display_handle), queued_redraw: None, waiting_for_vblank: false, + frame_clock: FrameClock::new(refresh_interval), }; let rv = self.output_state.insert(output, state); assert!(rv.is_none(), "output was already tracked"); @@ -1,13 +1,14 @@ use std::collections::{HashMap, HashSet}; use std::os::fd::FromRawFd; use std::path::{Path, PathBuf}; +use std::time::Duration; use anyhow::anyhow; use smithay::backend::allocator::dmabuf::Dmabuf; use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}; use smithay::backend::allocator::{Format as DrmFormat, Fourcc}; 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::libinput::{LibinputInputBackend, LibinputSessionInterface}; 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::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpixel}; 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::nix::fcntl::OFlag; use smithay::reexports::nix::libc::dev_t; @@ -248,6 +251,16 @@ impl Tty { 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. // catacomb // .windows @@ -263,11 +276,9 @@ impl Tty { }) .unwrap() .clone(); - data.niri - .output_state - .get_mut(&output) - .unwrap() - .waiting_for_vblank = false; + let output_state = data.niri.output_state.get_mut(&output).unwrap(); + output_state.waiting_for_vblank = false; + output_state.frame_clock.presented(presentation_time); data.niri.queue_redraw(output); } DrmEvent::Error(error) => error!("DRM error: {error}"), @@ -425,7 +436,7 @@ impl Tty { let res = device.surfaces.insert(crtc, compositor); 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); 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) +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..9a7d8092 --- /dev/null +++ b/src/utils.rs @@ -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()) +} diff --git a/src/winit.rs b/src/winit.rs index 2dda23b3..ef02f5b4 100644 --- a/src/winit.rs +++ b/src/winit.rs @@ -110,7 +110,7 @@ impl Winit { { 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) { |
