aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-04-14 09:23:15 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-04-14 09:37:42 +0400
commit3c6d8062c5c9b31afb5eeacd66a58b19b6566d9f (patch)
treeec1000121dbe270a81152ebd189871e50b8f496c /src
parent40374942db919b798fe5a4bd15b108a61c34aadc (diff)
downloadniri-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.rs150
-rw-r--r--src/backend/winit.rs2
-rw-r--r--src/frame_clock.rs22
-rw-r--r--src/niri.rs4
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),