diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-04-14 09:23:15 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-04-14 09:37:42 +0400 |
| commit | 3c6d8062c5c9b31afb5eeacd66a58b19b6566d9f (patch) | |
| tree | ec1000121dbe270a81152ebd189871e50b8f496c /src | |
| parent | 40374942db919b798fe5a4bd15b108a61c34aadc (diff) | |
| download | niri-3c6d8062c5c9b31afb5eeacd66a58b19b6566d9f.tar.gz niri-3c6d8062c5c9b31afb5eeacd66a58b19b6566d9f.tar.bz2 niri-3c6d8062c5c9b31afb5eeacd66a58b19b6566d9f.zip | |
Add variable-refresh-rate flag
Diffstat (limited to 'src')
| -rw-r--r-- | src/backend/tty.rs | 150 | ||||
| -rw-r--r-- | src/backend/winit.rs | 2 | ||||
| -rw-r--r-- | src/frame_clock.rs | 22 | ||||
| -rw-r--r-- | src/niri.rs | 4 |
4 files changed, 148 insertions, 30 deletions
diff --git a/src/backend/tty.rs b/src/backend/tty.rs index e00bdc4a..abeec237 100644 --- a/src/backend/tty.rs +++ b/src/backend/tty.rs @@ -171,6 +171,7 @@ struct Surface { gamma_props: Option<GammaProps>, /// Gamma change to apply upon session resume. pending_gamma_change: Option<Option<Vec<u16>>>, + vrr_enabled: bool, /// Tracy frame that goes from vblank to vblank. vblank_frame: Option<tracy_client::Frame>, /// Frame name for the VBlank frame. @@ -739,6 +740,47 @@ impl Tty { Err(err) => debug!("error setting max bpc: {err:?}"), } + // Try to enable VRR if requested. + let mut vrr_enabled = false; + if let Some(capable) = is_vrr_capable(&device.drm, connector.handle()) { + if capable { + let word = if config.variable_refresh_rate { + "enabling" + } else { + "disabling" + }; + + match set_vrr_enabled(&device.drm, crtc, config.variable_refresh_rate) { + Ok(enabled) => { + if enabled != config.variable_refresh_rate { + warn!("failed {} VRR", word); + } + + vrr_enabled = enabled; + } + Err(err) => { + warn!("error {} VRR: {err:?}", word); + } + } + } else { + if config.variable_refresh_rate { + warn!("cannot enable VRR because connector is not vrr_capable"); + } + + // Try to disable it anyway to work around a bug where resetting DRM state causes + // vrr_capable to be reset to 0, potentially leaving VRR_ENABLED at 1. + let res = set_vrr_enabled(&device.drm, crtc, config.variable_refresh_rate); + if matches!(res, Ok(true)) { + warn!("error disabling VRR"); + + // So that we can try it again later. + vrr_enabled = true; + } + } + } else if config.variable_refresh_rate { + warn!("cannot enable VRR because connector is not vrr_capable"); + } + let mut gamma_props = GammaProps::new(&device.drm, crtc) .map_err(|err| debug!("error getting gamma properties: {err:?}")) .ok(); @@ -864,6 +906,7 @@ impl Tty { compositor, dmabuf_feedback, gamma_props, + vrr_enabled, pending_gamma_change: None, vblank_frame: None, vblank_frame_name, @@ -874,7 +917,7 @@ impl Tty { let res = device.surfaces.insert(crtc, surface); assert!(res.is_none(), "crtc must not have already existed"); - niri.add_output(output.clone(), Some(refresh_interval(mode))); + niri.add_output(output.clone(), Some(refresh_interval(mode)), vrr_enabled); // Power on all monitors if necessary and queue a redraw on the new one. niri.event_loop.insert_idle(move |state| { @@ -1511,7 +1554,9 @@ impl Tty { continue; }; - if surface.compositor.pending_mode() == mode { + let change_mode = surface.compositor.pending_mode() != mode; + let change_vrr = surface.vrr_enabled != config.variable_refresh_rate; + if !change_mode && !change_vrr { continue; } @@ -1532,33 +1577,65 @@ impl Tty { continue; }; - if fallback { - let target = config.mode.unwrap(); - warn!( - "output {:?}: configured mode {}x{}{} could not be found, \ - falling back to preferred", - surface.name, - target.width, - target.height, - if let Some(refresh) = target.refresh { - format!("@{refresh}") + if change_vrr { + if is_vrr_capable(&device.drm, connector.handle()) == Some(true) { + let word = if config.variable_refresh_rate { + "enabling" } else { - String::new() - }, - ); - } + "disabling" + }; - debug!("output {:?}: picking mode: {mode:?}", surface.name); - if let Err(err) = surface.compositor.use_mode(mode) { - warn!("error changing mode: {err:?}"); - continue; + match set_vrr_enabled(&device.drm, crtc, config.variable_refresh_rate) { + Ok(enabled) => { + if enabled != config.variable_refresh_rate { + warn!("output {:?}: failed {} VRR", surface.name, word); + } + + surface.vrr_enabled = enabled; + output_state.frame_clock.set_vrr(enabled); + } + Err(err) => { + warn!("output {:?}: error {} VRR: {err:?}", surface.name, word); + } + } + } else if config.variable_refresh_rate { + warn!( + "output {:?}: cannot enable VRR because connector is not vrr_capable", + surface.name + ); + } } - let wl_mode = Mode::from(mode); - output.change_current_state(Some(wl_mode), None, None, None); - output.set_preferred(wl_mode); - output_state.frame_clock = FrameClock::new(Some(refresh_interval(mode))); - niri.output_resized(&output); + if change_mode { + if fallback { + let target = config.mode.unwrap(); + warn!( + "output {:?}: configured mode {}x{}{} could not be found, \ + falling back to preferred", + surface.name, + target.width, + target.height, + if let Some(refresh) = target.refresh { + format!("@{refresh}") + } else { + String::new() + }, + ); + } + + debug!("output {:?}: picking mode: {mode:?}", surface.name); + if let Err(err) = surface.compositor.use_mode(mode) { + warn!("error changing mode: {err:?}"); + continue; + } + + let wl_mode = Mode::from(mode); + output.change_current_state(Some(wl_mode), None, None, None); + output.set_preferred(wl_mode); + output_state.frame_clock = + FrameClock::new(Some(refresh_interval(mode)), surface.vrr_enabled); + niri.output_resized(&output); + } } for (connector, crtc) in device.drm_scanner.crtcs() { @@ -2095,6 +2172,29 @@ fn set_max_bpc(device: &DrmDevice, connector: connector::Handle, bpc: u64) -> an Err(anyhow!("couldn't find max bpc property")) } +fn is_vrr_capable(device: &DrmDevice, connector: connector::Handle) -> Option<bool> { + let (_, info, value) = find_drm_property(device, connector, "vrr_capable")?; + info.value_type().convert_value(value).as_boolean() +} + +fn set_vrr_enabled(device: &DrmDevice, crtc: crtc::Handle, enabled: bool) -> anyhow::Result<bool> { + let (prop, info, _) = + find_drm_property(device, crtc, "VRR_ENABLED").context("VRR_ENABLED property missing")?; + + let value = property::Value::UnsignedRange(if enabled { 1 } else { 0 }); + device + .set_property(crtc, prop, value.into()) + .context("error setting VRR_ENABLED property")?; + + let value = get_drm_property(device, crtc, prop) + .context("VRR_ENABLED property missing after setting")?; + match info.value_type().convert_value(value) { + property::Value::UnsignedRange(value) => Ok(value == 1), + property::Value::Boolean(value) => Ok(value), + _ => bail!("wrong VRR_ENABLED property type"), + } +} + pub fn set_gamma_for_crtc( device: &DrmDevice, crtc: crtc::Handle, diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 9e05822f..acf16481 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -133,7 +133,7 @@ impl Winit { resources::init(renderer); shaders::init(renderer); - niri.add_output(self.output.clone(), None); + niri.add_output(self.output.clone(), None, false); } pub fn seat_name(&self) -> String { diff --git a/src/frame_clock.rs b/src/frame_clock.rs index ad8cf2f7..d60867ea 100644 --- a/src/frame_clock.rs +++ b/src/frame_clock.rs @@ -7,10 +7,11 @@ use crate::utils::get_monotonic_time; pub struct FrameClock { last_presentation_time: Option<Duration>, refresh_interval_ns: Option<NonZeroU64>, + vrr: bool, } impl FrameClock { - pub fn new(refresh_interval: Option<Duration>) -> Self { + pub fn new(refresh_interval: Option<Duration>, vrr: bool) -> 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()) @@ -21,6 +22,7 @@ impl FrameClock { Self { last_presentation_time: None, refresh_interval_ns, + vrr, } } @@ -29,6 +31,15 @@ impl FrameClock { .map(|r| Duration::from_nanos(r.get())) } + pub fn set_vrr(&mut self, vrr: bool) { + if self.vrr == vrr { + return; + } + + self.vrr = vrr; + self.last_presentation_time = None; + } + pub fn presented(&mut self, presentation_time: Duration) { if presentation_time.is_zero() { // Not interested in these. @@ -71,6 +82,13 @@ impl FrameClock { 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) + + // If VRR is enabled and more than one frame passed since last presentation, assume that we + // can present immediately. + if self.vrr && to_next_ns > refresh_interval_ns { + now + } else { + last_presentation_time + Duration::from_nanos(to_next_ns) + } } } diff --git a/src/niri.rs b/src/niri.rs index f14f7dcd..145d143e 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -1543,7 +1543,7 @@ impl Niri { } } - pub fn add_output(&mut self, output: Output, refresh_interval: Option<Duration>) { + pub fn add_output(&mut self, output: Output, refresh_interval: Option<Duration>, vrr: bool) { let global = output.create_global::<State>(&self.display_handle); let name = output.name(); @@ -1583,7 +1583,7 @@ impl Niri { global, redraw_state: RedrawState::Idle, unfinished_animations_remain: false, - frame_clock: FrameClock::new(refresh_interval), + frame_clock: FrameClock::new(refresh_interval, vrr), last_drm_sequence: None, frame_callback_sequence: 0, background_buffer: SolidColorBuffer::new(size, CLEAR_COLOR), |
