mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Add frame clock
Tracks the presentation time and allows querying the next presentation time.
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user