aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2023-08-14 15:53:24 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2023-08-14 15:53:24 +0400
commitc65a4f162405d1b8c780925b723feeb481c87919 (patch)
treeba18ba0bcafc2bbf2c0014179679479325441fdb
parent529a24cc14859890029ad20ba07a589f4b3bc559 (diff)
downloadniri-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.rs60
-rw-r--r--src/main.rs2
-rw-r--r--src/niri.rs6
-rw-r--r--src/tty.rs51
-rw-r--r--src/utils.rs7
-rw-r--r--src/winit.rs2
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");
diff --git a/src/tty.rs b/src/tty.rs
index 59ff104a..82e121e5 100644
--- a/src/tty.rs
+++ b/src/tty.rs
@@ -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) {