mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
feature: add on-demand vrr (#586)
* feature: add on-demand vrr * Don't require connector::Info in try_to_set_vrr * Improve VRR help message * Rename connector_handle => connector * Fix tracy span name * Move on demand vrr flag set higher * wiki: Mention on-demand VRR --------- Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
+26
-4
@@ -324,11 +324,25 @@ pub struct Output {
|
|||||||
#[knuffel(child, unwrap(argument, str))]
|
#[knuffel(child, unwrap(argument, str))]
|
||||||
pub mode: Option<ConfiguredMode>,
|
pub mode: Option<ConfiguredMode>,
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub variable_refresh_rate: bool,
|
pub variable_refresh_rate: Option<Vrr>,
|
||||||
#[knuffel(child, default = DEFAULT_BACKGROUND_COLOR)]
|
#[knuffel(child, default = DEFAULT_BACKGROUND_COLOR)]
|
||||||
pub background_color: Color,
|
pub background_color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Output {
|
||||||
|
pub fn is_vrr_always_on(&self) -> bool {
|
||||||
|
self.variable_refresh_rate == Some(Vrr { on_demand: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_vrr_on_demand(&self) -> bool {
|
||||||
|
self.variable_refresh_rate == Some(Vrr { on_demand: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_vrr_always_off(&self) -> bool {
|
||||||
|
self.variable_refresh_rate.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Output {
|
impl Default for Output {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -338,7 +352,7 @@ impl Default for Output {
|
|||||||
transform: Transform::Normal,
|
transform: Transform::Normal,
|
||||||
position: None,
|
position: None,
|
||||||
mode: None,
|
mode: None,
|
||||||
variable_refresh_rate: false,
|
variable_refresh_rate: None,
|
||||||
background_color: DEFAULT_BACKGROUND_COLOR,
|
background_color: DEFAULT_BACKGROUND_COLOR,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,6 +366,12 @@ pub struct Position {
|
|||||||
pub y: i32,
|
pub y: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Default)]
|
||||||
|
pub struct Vrr {
|
||||||
|
#[knuffel(property, default = false)]
|
||||||
|
pub on_demand: bool,
|
||||||
|
}
|
||||||
|
|
||||||
// MIN and MAX generics are only used during parsing to check the value.
|
// MIN and MAX generics are only used during parsing to check the value.
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||||
pub struct FloatOrInt<const MIN: i32, const MAX: i32>(pub f64);
|
pub struct FloatOrInt<const MIN: i32, const MAX: i32>(pub f64);
|
||||||
@@ -896,6 +916,8 @@ pub struct WindowRule {
|
|||||||
pub clip_to_geometry: Option<bool>,
|
pub clip_to_geometry: Option<bool>,
|
||||||
#[knuffel(child, unwrap(argument))]
|
#[knuffel(child, unwrap(argument))]
|
||||||
pub block_out_from: Option<BlockOutFrom>,
|
pub block_out_from: Option<BlockOutFrom>,
|
||||||
|
#[knuffel(child, unwrap(argument))]
|
||||||
|
pub variable_refresh_rate: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember to update the PartialEq impl when adding fields!
|
// Remember to update the PartialEq impl when adding fields!
|
||||||
@@ -2705,7 +2727,7 @@ mod tests {
|
|||||||
transform "flipped-90"
|
transform "flipped-90"
|
||||||
position x=10 y=20
|
position x=10 y=20
|
||||||
mode "1920x1080@144"
|
mode "1920x1080@144"
|
||||||
variable-refresh-rate
|
variable-refresh-rate on-demand=true
|
||||||
background-color "rgba(25, 25, 102, 1.0)"
|
background-color "rgba(25, 25, 102, 1.0)"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2889,7 +2911,7 @@ mod tests {
|
|||||||
height: 1080,
|
height: 1080,
|
||||||
refresh: Some(144.),
|
refresh: Some(144.),
|
||||||
}),
|
}),
|
||||||
variable_refresh_rate: true,
|
variable_refresh_rate: Some(Vrr { on_demand: true }),
|
||||||
background_color: Color::from_rgba8_unpremul(25, 25, 102, 255),
|
background_color: Color::from_rgba8_unpremul(25, 25, 102, 255),
|
||||||
}]),
|
}]),
|
||||||
layout: Layout {
|
layout: Layout {
|
||||||
|
|||||||
+25
-11
@@ -352,18 +352,11 @@ pub enum OutputAction {
|
|||||||
#[cfg_attr(feature = "clap", command(subcommand))]
|
#[cfg_attr(feature = "clap", command(subcommand))]
|
||||||
position: PositionToSet,
|
position: PositionToSet,
|
||||||
},
|
},
|
||||||
/// Toggle variable refresh rate.
|
/// Set the variable refresh rate mode.
|
||||||
Vrr {
|
Vrr {
|
||||||
/// Whether to enable variable refresh rate.
|
/// Variable refresh rate mode to set.
|
||||||
#[cfg_attr(
|
#[cfg_attr(feature = "clap", command(flatten))]
|
||||||
feature = "clap",
|
vrr: VrrToSet,
|
||||||
arg(
|
|
||||||
value_name = "ON|OFF",
|
|
||||||
action = clap::ArgAction::Set,
|
|
||||||
value_parser = clap::builder::BoolishValueParser::new(),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
enable: bool,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,6 +418,27 @@ pub struct ConfiguredPosition {
|
|||||||
pub y: i32,
|
pub y: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Output VRR to set.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "clap", derive(clap::Args))]
|
||||||
|
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
||||||
|
pub struct VrrToSet {
|
||||||
|
/// Whether to enable variable refresh rate.
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "clap",
|
||||||
|
arg(
|
||||||
|
value_name = "ON|OFF",
|
||||||
|
action = clap::ArgAction::Set,
|
||||||
|
value_parser = clap::builder::BoolishValueParser::new(),
|
||||||
|
hide_possible_values = true,
|
||||||
|
),
|
||||||
|
)]
|
||||||
|
pub vrr: bool,
|
||||||
|
/// Only enable when the output shows a window matching the variable-refresh-rate window rule.
|
||||||
|
#[cfg_attr(feature = "clap", arg(long))]
|
||||||
|
pub on_demand: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Connected output.
|
/// Connected output.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
||||||
|
|||||||
@@ -153,6 +153,13 @@ impl Backend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_output_on_demand_vrr(&mut self, niri: &mut Niri, output: &Output, enable_vrr: bool) {
|
||||||
|
match self {
|
||||||
|
Backend::Tty(tty) => tty.set_output_on_demand_vrr(niri, output, enable_vrr),
|
||||||
|
Backend::Winit(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn on_output_config_changed(&mut self, niri: &mut Niri) {
|
pub fn on_output_config_changed(&mut self, niri: &mut Niri) {
|
||||||
match self {
|
match self {
|
||||||
Backend::Tty(tty) => tty.on_output_config_changed(niri),
|
Backend::Tty(tty) => tty.on_output_config_changed(niri),
|
||||||
|
|||||||
+50
-38
@@ -180,6 +180,7 @@ struct TtyOutputState {
|
|||||||
struct Surface {
|
struct Surface {
|
||||||
name: String,
|
name: String,
|
||||||
compositor: GbmDrmCompositor,
|
compositor: GbmDrmCompositor,
|
||||||
|
connector: connector::Handle,
|
||||||
dmabuf_feedback: Option<SurfaceDmabufFeedback>,
|
dmabuf_feedback: Option<SurfaceDmabufFeedback>,
|
||||||
gamma_props: Option<GammaProps>,
|
gamma_props: Option<GammaProps>,
|
||||||
/// Gamma change to apply upon session resume.
|
/// Gamma change to apply upon session resume.
|
||||||
@@ -426,18 +427,6 @@ impl Tty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Restore VRR.
|
// Restore VRR.
|
||||||
let Some(connector) =
|
|
||||||
surface.compositor.pending_connectors().into_iter().next()
|
|
||||||
else {
|
|
||||||
error!("surface pending connectors is empty");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let Some(connector) = device.drm_scanner.connectors().get(&connector)
|
|
||||||
else {
|
|
||||||
error!("missing enabled connector in drm_scanner");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let output = niri
|
let output = niri
|
||||||
.global_space
|
.global_space
|
||||||
.outputs()
|
.outputs()
|
||||||
@@ -457,7 +446,7 @@ impl Tty {
|
|||||||
|
|
||||||
try_to_change_vrr(
|
try_to_change_vrr(
|
||||||
&device.drm,
|
&device.drm,
|
||||||
connector,
|
surface.connector,
|
||||||
*crtc,
|
*crtc,
|
||||||
surface,
|
surface,
|
||||||
output_state,
|
output_state,
|
||||||
@@ -832,15 +821,13 @@ impl Tty {
|
|||||||
let mut vrr_enabled = false;
|
let mut vrr_enabled = false;
|
||||||
if let Some(capable) = is_vrr_capable(&device.drm, connector.handle()) {
|
if let Some(capable) = is_vrr_capable(&device.drm, connector.handle()) {
|
||||||
if capable {
|
if capable {
|
||||||
let word = if config.variable_refresh_rate {
|
// Even if on-demand, we still disable it until later checks.
|
||||||
"enabling"
|
let vrr = config.is_vrr_always_on();
|
||||||
} else {
|
let word = if vrr { "enabling" } else { "disabling" };
|
||||||
"disabling"
|
|
||||||
};
|
|
||||||
|
|
||||||
match set_vrr_enabled(&device.drm, crtc, config.variable_refresh_rate) {
|
match set_vrr_enabled(&device.drm, crtc, vrr) {
|
||||||
Ok(enabled) => {
|
Ok(enabled) => {
|
||||||
if enabled != config.variable_refresh_rate {
|
if enabled != vrr {
|
||||||
warn!("failed {} VRR", word);
|
warn!("failed {} VRR", word);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -851,13 +838,13 @@ impl Tty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if config.variable_refresh_rate {
|
if !config.is_vrr_always_off() {
|
||||||
warn!("cannot enable VRR because connector is not vrr_capable");
|
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
|
// 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.
|
// 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);
|
let res = set_vrr_enabled(&device.drm, crtc, false);
|
||||||
if matches!(res, Ok(true)) {
|
if matches!(res, Ok(true)) {
|
||||||
warn!("error disabling VRR");
|
warn!("error disabling VRR");
|
||||||
|
|
||||||
@@ -865,7 +852,7 @@ impl Tty {
|
|||||||
vrr_enabled = true;
|
vrr_enabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if config.variable_refresh_rate {
|
} else if !config.is_vrr_always_off() {
|
||||||
warn!("cannot enable VRR because connector is not vrr_capable");
|
warn!("cannot enable VRR because connector is not vrr_capable");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1017,6 +1004,7 @@ impl Tty {
|
|||||||
|
|
||||||
let surface = Surface {
|
let surface = Surface {
|
||||||
name: output_name.clone(),
|
name: output_name.clone(),
|
||||||
|
connector: connector.handle(),
|
||||||
compositor,
|
compositor,
|
||||||
dmabuf_feedback,
|
dmabuf_feedback,
|
||||||
gamma_props,
|
gamma_props,
|
||||||
@@ -1661,6 +1649,33 @@ impl Tty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_output_on_demand_vrr(&mut self, niri: &mut Niri, output: &Output, enable_vrr: bool) {
|
||||||
|
let _span = tracy_client::span!("Tty::set_output_on_demand_vrr");
|
||||||
|
|
||||||
|
let output_state = niri.output_state.get_mut(output).unwrap();
|
||||||
|
output_state.on_demand_vrr_enabled = enable_vrr;
|
||||||
|
if output_state.frame_clock.vrr() == enable_vrr {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (&node, device) in self.devices.iter_mut() {
|
||||||
|
for (&crtc, surface) in device.surfaces.iter_mut() {
|
||||||
|
let tty_state: &TtyOutputState = output.user_data().get().unwrap();
|
||||||
|
if tty_state.node == node && tty_state.crtc == crtc {
|
||||||
|
try_to_change_vrr(
|
||||||
|
&device.drm,
|
||||||
|
surface.connector,
|
||||||
|
crtc,
|
||||||
|
surface,
|
||||||
|
output_state,
|
||||||
|
enable_vrr,
|
||||||
|
);
|
||||||
|
self.refresh_ipc_outputs(niri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn on_output_config_changed(&mut self, niri: &mut Niri) {
|
pub fn on_output_config_changed(&mut self, niri: &mut Niri) {
|
||||||
let _span = tracy_client::span!("Tty::on_output_config_changed");
|
let _span = tracy_client::span!("Tty::on_output_config_changed");
|
||||||
|
|
||||||
@@ -1675,9 +1690,7 @@ impl Tty {
|
|||||||
let mut to_connect = vec![];
|
let mut to_connect = vec![];
|
||||||
|
|
||||||
for (&node, device) in &mut self.devices {
|
for (&node, device) in &mut self.devices {
|
||||||
for surface in device.surfaces.values_mut() {
|
for (&crtc, surface) in device.surfaces.iter_mut() {
|
||||||
let crtc = surface.compositor.crtc();
|
|
||||||
|
|
||||||
let config = self
|
let config = self
|
||||||
.config
|
.config
|
||||||
.borrow()
|
.borrow()
|
||||||
@@ -1691,12 +1704,8 @@ impl Tty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if we need to change the mode.
|
// Check if we need to change the mode.
|
||||||
let Some(connector) = surface.compositor.pending_connectors().into_iter().next()
|
let Some(connector) = device.drm_scanner.connectors().get(&surface.connector)
|
||||||
else {
|
else {
|
||||||
error!("surface pending connectors is empty");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let Some(connector) = device.drm_scanner.connectors().get(&connector) else {
|
|
||||||
error!("missing enabled connector in drm_scanner");
|
error!("missing enabled connector in drm_scanner");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@@ -1707,8 +1716,9 @@ impl Tty {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let change_mode = surface.compositor.pending_mode() != mode;
|
let change_mode = surface.compositor.pending_mode() != mode;
|
||||||
let change_vrr = surface.vrr_enabled != config.variable_refresh_rate;
|
let change_always_vrr = surface.vrr_enabled != config.is_vrr_always_on();
|
||||||
if !change_mode && !change_vrr {
|
let is_on_demand_vrr = config.is_vrr_on_demand();
|
||||||
|
if !change_mode && !change_always_vrr && !is_on_demand_vrr {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1729,14 +1739,16 @@ impl Tty {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if change_vrr {
|
if (is_on_demand_vrr && surface.vrr_enabled != output_state.on_demand_vrr_enabled)
|
||||||
|
|| (!is_on_demand_vrr && change_always_vrr)
|
||||||
|
{
|
||||||
try_to_change_vrr(
|
try_to_change_vrr(
|
||||||
&device.drm,
|
&device.drm,
|
||||||
connector,
|
connector.handle(),
|
||||||
crtc,
|
crtc,
|
||||||
surface,
|
surface,
|
||||||
output_state,
|
output_state,
|
||||||
config.variable_refresh_rate,
|
!surface.vrr_enabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2388,7 +2400,7 @@ pub fn set_gamma_for_crtc(
|
|||||||
|
|
||||||
fn try_to_change_vrr(
|
fn try_to_change_vrr(
|
||||||
device: &DrmDevice,
|
device: &DrmDevice,
|
||||||
connector: &connector::Info,
|
connector: connector::Handle,
|
||||||
crtc: crtc::Handle,
|
crtc: crtc::Handle,
|
||||||
surface: &mut Surface,
|
surface: &mut Surface,
|
||||||
output_state: &mut crate::niri::OutputState,
|
output_state: &mut crate::niri::OutputState,
|
||||||
@@ -2396,7 +2408,7 @@ fn try_to_change_vrr(
|
|||||||
) {
|
) {
|
||||||
let _span = tracy_client::span!("try_to_change_vrr");
|
let _span = tracy_client::span!("try_to_change_vrr");
|
||||||
|
|
||||||
if is_vrr_capable(device, connector.handle()) == Some(true) {
|
if is_vrr_capable(device, connector) == Some(true) {
|
||||||
let word = if enable_vrr { "enabling" } else { "disabling" };
|
let word = if enable_vrr { "enabling" } else { "disabling" };
|
||||||
|
|
||||||
match set_vrr_enabled(device, crtc, enable_vrr) {
|
match set_vrr_enabled(device, crtc, enable_vrr) {
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ impl FrameClock {
|
|||||||
self.last_presentation_time = None;
|
self.last_presentation_time = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn vrr(&self) -> bool {
|
||||||
|
self.vrr
|
||||||
|
}
|
||||||
|
|
||||||
pub fn presented(&mut self, presentation_time: Duration) {
|
pub fn presented(&mut self, presentation_time: Duration) {
|
||||||
if presentation_time.is_zero() {
|
if presentation_time.is_zero() {
|
||||||
// Not interested in these.
|
// Not interested in these.
|
||||||
|
|||||||
+46
-7
@@ -299,6 +299,7 @@ pub struct OutputState {
|
|||||||
pub global: GlobalId,
|
pub global: GlobalId,
|
||||||
pub frame_clock: FrameClock,
|
pub frame_clock: FrameClock,
|
||||||
pub redraw_state: RedrawState,
|
pub redraw_state: RedrawState,
|
||||||
|
pub on_demand_vrr_enabled: bool,
|
||||||
// After the last redraw, some ongoing animations still remain.
|
// After the last redraw, some ongoing animations still remain.
|
||||||
pub unfinished_animations_remain: bool,
|
pub unfinished_animations_remain: bool,
|
||||||
/// Last sequence received in a vblank event.
|
/// Last sequence received in a vblank event.
|
||||||
@@ -1202,8 +1203,14 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
niri_ipc::OutputAction::Vrr { enable } => {
|
niri_ipc::OutputAction::Vrr { vrr } => {
|
||||||
config.variable_refresh_rate = enable;
|
config.variable_refresh_rate = if vrr.vrr {
|
||||||
|
Some(niri_config::Vrr {
|
||||||
|
on_demand: vrr.on_demand,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2005,6 +2012,7 @@ impl Niri {
|
|||||||
let state = OutputState {
|
let state = OutputState {
|
||||||
global,
|
global,
|
||||||
redraw_state: RedrawState::Idle,
|
redraw_state: RedrawState::Idle,
|
||||||
|
on_demand_vrr_enabled: false,
|
||||||
unfinished_animations_remain: false,
|
unfinished_animations_remain: false,
|
||||||
frame_clock: FrameClock::new(refresh_interval, vrr),
|
frame_clock: FrameClock::new(refresh_interval, vrr),
|
||||||
last_drm_sequence: None,
|
last_drm_sequence: None,
|
||||||
@@ -3089,6 +3097,8 @@ impl Niri {
|
|||||||
lock_state => self.lock_state = lock_state,
|
lock_state => self.lock_state = lock_state,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.refresh_on_demand_vrr(backend, output);
|
||||||
|
|
||||||
// Send the frame callbacks.
|
// Send the frame callbacks.
|
||||||
//
|
//
|
||||||
// FIXME: The logic here could be a bit smarter. Currently, during an animation, the
|
// FIXME: The logic here could be a bit smarter. Currently, during an animation, the
|
||||||
@@ -3118,6 +3128,39 @@ impl Niri {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn refresh_on_demand_vrr(&mut self, backend: &mut Backend, output: &Output) {
|
||||||
|
let _span = tracy_client::span!("Niri::refresh_on_demand_vrr");
|
||||||
|
let Some(on_demand) = self
|
||||||
|
.config
|
||||||
|
.borrow()
|
||||||
|
.outputs
|
||||||
|
.find(&output.name())
|
||||||
|
.map(|output| output.is_vrr_on_demand())
|
||||||
|
else {
|
||||||
|
warn!("error getting output config for {}", output.name());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !on_demand {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = self.layout.windows_for_output(output).any(|mapped| {
|
||||||
|
mapped.rules().variable_refresh_rate == Some(true) && {
|
||||||
|
let mut visible = false;
|
||||||
|
mapped.window.with_surfaces(|surface, states| {
|
||||||
|
if !visible
|
||||||
|
&& surface_primary_scanout_output(surface, states).as_ref() == Some(output)
|
||||||
|
{
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
visible
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
backend.set_output_on_demand_vrr(self, output, current);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_primary_scanout_output(
|
pub fn update_primary_scanout_output(
|
||||||
&self,
|
&self,
|
||||||
output: &Output,
|
output: &Output,
|
||||||
@@ -3181,13 +3224,9 @@ impl Niri {
|
|||||||
let offscreen_id = offscreen_id.as_ref();
|
let offscreen_id = offscreen_id.as_ref();
|
||||||
|
|
||||||
win.with_surfaces(|surface, states| {
|
win.with_surfaces(|surface, states| {
|
||||||
states
|
|
||||||
.data_map
|
|
||||||
.insert_if_missing_threadsafe(Mutex::<PrimaryScanoutOutput>::default);
|
|
||||||
let surface_primary_scanout_output = states
|
let surface_primary_scanout_output = states
|
||||||
.data_map
|
.data_map
|
||||||
.get::<Mutex<PrimaryScanoutOutput>>()
|
.get_or_insert_threadsafe(Mutex::<PrimaryScanoutOutput>::default);
|
||||||
.unwrap();
|
|
||||||
surface_primary_scanout_output
|
surface_primary_scanout_output
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::collections::HashMap;
|
|||||||
use std::iter::zip;
|
use std::iter::zip;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use niri_config::FloatOrInt;
|
use niri_config::{FloatOrInt, Vrr};
|
||||||
use niri_ipc::Transform;
|
use niri_ipc::Transform;
|
||||||
use smithay::reexports::wayland_protocols_wlr::output_management::v1::server::{
|
use smithay::reexports::wayland_protocols_wlr::output_management::v1::server::{
|
||||||
zwlr_output_configuration_head_v1, zwlr_output_configuration_v1, zwlr_output_head_v1,
|
zwlr_output_configuration_head_v1, zwlr_output_configuration_v1, zwlr_output_head_v1,
|
||||||
@@ -693,9 +693,9 @@ where
|
|||||||
new_config.scale = Some(FloatOrInt(scale));
|
new_config.scale = Some(FloatOrInt(scale));
|
||||||
}
|
}
|
||||||
zwlr_output_configuration_head_v1::Request::SetAdaptiveSync { state } => {
|
zwlr_output_configuration_head_v1::Request::SetAdaptiveSync { state } => {
|
||||||
let enabled = match state {
|
let vrr = match state {
|
||||||
WEnum::Value(AdaptiveSyncState::Enabled) => true,
|
WEnum::Value(AdaptiveSyncState::Enabled) => Some(Vrr { on_demand: false }),
|
||||||
WEnum::Value(AdaptiveSyncState::Disabled) => false,
|
WEnum::Value(AdaptiveSyncState::Disabled) => None,
|
||||||
_ => {
|
_ => {
|
||||||
warn!("SetAdaptativeSync: unknown requested adaptative sync");
|
warn!("SetAdaptativeSync: unknown requested adaptative sync");
|
||||||
conf_head.post_error(
|
conf_head.post_error(
|
||||||
@@ -705,7 +705,7 @@ where
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
new_config.variable_refresh_rate = enabled;
|
new_config.variable_refresh_rate = vrr;
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ pub struct ResolvedWindowRules {
|
|||||||
|
|
||||||
/// Whether to block out this window from certain render targets.
|
/// Whether to block out this window from certain render targets.
|
||||||
pub block_out_from: Option<BlockOutFrom>,
|
pub block_out_from: Option<BlockOutFrom>,
|
||||||
|
|
||||||
|
/// Whether to enable VRR on this window's primary output if it is on-demand.
|
||||||
|
pub variable_refresh_rate: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> WindowRef<'a> {
|
impl<'a> WindowRef<'a> {
|
||||||
@@ -132,6 +135,7 @@ impl ResolvedWindowRules {
|
|||||||
geometry_corner_radius: None,
|
geometry_corner_radius: None,
|
||||||
clip_to_geometry: None,
|
clip_to_geometry: None,
|
||||||
block_out_from: None,
|
block_out_from: None,
|
||||||
|
variable_refresh_rate: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +235,9 @@ impl ResolvedWindowRules {
|
|||||||
if let Some(x) = rule.block_out_from {
|
if let Some(x) = rule.block_out_from {
|
||||||
resolved.block_out_from = Some(x);
|
resolved.block_out_from = Some(x);
|
||||||
}
|
}
|
||||||
|
if let Some(x) = rule.variable_refresh_rate {
|
||||||
|
resolved.variable_refresh_rate = Some(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolved.open_on_output = open_on_output.map(|x| x.to_owned());
|
resolved.open_on_output = open_on_output.map(|x| x.to_owned());
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ output "eDP-1" {
|
|||||||
scale 2.0
|
scale 2.0
|
||||||
transform "90"
|
transform "90"
|
||||||
position x=1280 y=0
|
position x=1280 y=0
|
||||||
variable-refresh-rate
|
variable-refresh-rate // on-demand=true
|
||||||
background-color "#003300"
|
background-color "#003300"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +147,15 @@ output "HDMI-A-1" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<sup>Since: 0.1.9</sup> You can also set the `on-demand=true` property, which will only enable VRR when this output shows a window matching the `variable-refresh-rate` window rule.
|
||||||
|
This is helpful to avoid various issues with VRR, since it can be disabled most of the time, and only enabled for specific windows, like games or video players.
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
output "HDMI-A-1" {
|
||||||
|
variable-refresh-rate on-demand=true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `background-color`
|
### `background-color`
|
||||||
|
|
||||||
<sup>Since: 0.1.8</sup>
|
<sup>Since: 0.1.8</sup>
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ window-rule {
|
|||||||
opacity 0.5
|
opacity 0.5
|
||||||
block-out-from "screencast"
|
block-out-from "screencast"
|
||||||
// block-out-from "screen-capture"
|
// block-out-from "screen-capture"
|
||||||
|
variable-refresh-rate true
|
||||||
|
|
||||||
focus-ring {
|
focus-ring {
|
||||||
// off
|
// off
|
||||||
@@ -391,6 +392,26 @@ window-rule {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `variable-refresh-rate`
|
||||||
|
|
||||||
|
<sup>Since: 0.1.9</sup>
|
||||||
|
|
||||||
|
If set to true, whenever this window displays on an output with on-demand VRR, it will enable VRR on that output.
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
// Configure some output with on-demand VRR.
|
||||||
|
output "HDMI-A-1" {
|
||||||
|
variable-refresh-rate on-demand=true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable on-demand VRR when mpv displays on the output.
|
||||||
|
window-rule {
|
||||||
|
match app-id="^mpv$"
|
||||||
|
|
||||||
|
variable-refresh-rate true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### `draw-border-with-background`
|
#### `draw-border-with-background`
|
||||||
|
|
||||||
Override whether the border and the focus ring draw with a background.
|
Override whether the border and the focus ring draw with a background.
|
||||||
|
|||||||
Reference in New Issue
Block a user