From 80965a61b9b6b6b70233f43725347ea5c6d03b6f Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 18 Nov 2024 19:47:59 +0100 Subject: [PATCH] kms: Adaptive VRR support --- src/backend/kms/drm_helpers.rs | 34 --------- src/backend/kms/mod.rs | 48 ++++++++----- src/backend/kms/surface/mod.rs | 69 ++++++++++++++++--- src/config/mod.rs | 23 +++++-- src/utils/prelude.rs | 37 ++++++---- src/wayland/handlers/output_configuration.rs | 8 ++- .../protocols/output_configuration/mod.rs | 14 ++-- 7 files changed, 146 insertions(+), 87 deletions(-) diff --git a/src/backend/kms/drm_helpers.rs b/src/backend/kms/drm_helpers.rs index 2724a8bd..0b7cdc3b 100644 --- a/src/backend/kms/drm_helpers.rs +++ b/src/backend/kms/drm_helpers.rs @@ -349,40 +349,6 @@ pub fn calculate_refresh_rate(mode: Mode) -> u32 { refresh as u32 } -pub fn supports_vrr(dev: &impl ControlDevice, conn: connector::Handle) -> Result { - get_property_val(dev, conn, "vrr_capable").map(|(val_type, val)| { - match val_type.convert_value(val) { - property::Value::UnsignedRange(res) => res == 1, - property::Value::Boolean(res) => res, - _ => false, - } - }) -} - -pub fn set_vrr( - dev: &impl ControlDevice, - crtc: crtc::Handle, - conn: connector::Handle, - vrr: bool, -) -> Result { - if supports_vrr(dev, conn)? { - dev.set_property( - conn, - get_prop(dev, crtc, "VRR_ENABLED")?, - property::Value::UnsignedRange(if vrr { 1 } else { 0 }).into(), - ) - .map_err(Into::::into) - .and_then(|_| get_property_val(dev, crtc, "VRR_ENABLED")) - .map(|(val_type, val)| match val_type.convert_value(val) { - property::Value::UnsignedRange(vrr) => vrr == 1, - property::Value::Boolean(vrr) => vrr, - _ => false, - }) - } else { - Ok(false) - } -} - pub fn get_max_bpc( dev: &impl ControlDevice, conn: connector::Handle, diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index e01f5065..8edbd71d 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -1,6 +1,11 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{config::OutputState, shell::Shell, state::BackendData, utils::prelude::*}; +use crate::{ + config::{AdaptiveSync, OutputState}, + shell::Shell, + state::BackendData, + utils::prelude::*, +}; use anyhow::{Context, Result}; use calloop::LoopSignal; @@ -663,10 +668,6 @@ impl KmsState { let gbm = device.gbm.clone(); let cursor_size = drm.cursor_size(); - let vrr = drm_helpers::set_vrr(drm, *crtc, conn, output_config.vrr) - .unwrap_or(false); - surface.output.set_adaptive_sync(vrr); - if let Some(bpc) = output_config.max_bpc { if let Err(err) = drm_helpers::set_max_bpc(drm, conn, bpc) { warn!( @@ -678,20 +679,35 @@ impl KmsState { } } + let vrr = output_config.vrr; std::mem::drop(output_config); - surface - .resume(drm_surface, gbm, cursor_size, vrr) - .context("Failed to create surface")?; - } else { - if output_config.vrr != surface.output.adaptive_sync() { - surface.output.set_adaptive_sync(drm_helpers::set_vrr( - drm, - surface.crtc, - surface.connector, - output_config.vrr, - )?); + + match surface.resume(drm_surface, gbm, cursor_size) { + Ok(_) => { + if surface.use_adaptive_sync(vrr)? { + surface.output.set_adaptive_sync(vrr); + } else { + surface.output.config_mut().vrr = AdaptiveSync::Disabled; + surface.output.set_adaptive_sync(AdaptiveSync::Disabled); + } + } + Err(err) => { + surface.output.config_mut().enabled = OutputState::Disabled; + return Err(err).context("Failed to create surface"); + } } + } else { + let vrr = output_config.vrr; std::mem::drop(output_config); + if vrr != surface.output.adaptive_sync() { + if surface.use_adaptive_sync(vrr)? { + surface.output.set_adaptive_sync(vrr); + } else if vrr != AdaptiveSync::Disabled { + anyhow::bail!("Requested VRR mode unsupported"); + } else { + surface.output.set_adaptive_sync(AdaptiveSync::Disabled); + } + } surface .set_mode(*mode) .context("Failed to apply new mode")?; diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index 8ec9dad0..ce7a449b 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -5,6 +5,7 @@ use crate::{ element::{CosmicElement, DamageElement}, init_shaders, workspace_elements, CursorMode, ElementFilter, GlMultiRenderer, CLEAR_COLOR, }, + config::AdaptiveSync, shell::Shell, state::SurfaceDmabufFeedback, utils::{prelude::*, quirks::workspace_overview_is_open}, @@ -27,7 +28,7 @@ use smithay::{ }, drm::{ compositor::{BlitFrameResultError, DrmCompositor, FrameError, PrimaryPlaneElement}, - DrmDeviceFd, DrmEventMetadata, DrmEventTime, DrmNode, DrmSurface, + DrmDeviceFd, DrmEventMetadata, DrmEventTime, DrmNode, DrmSurface, VrrSupport, }, egl::EGLContext, renderer::{ @@ -104,7 +105,7 @@ static NVIDIA_LOGO: &'static [u8] = include_bytes!("../../../../resources/icons/ #[derive(Debug)] pub struct Surface { pub(crate) connector: connector::Handle, - pub(super) crtc: crtc::Handle, + pub(super) _crtc: crtc::Handle, pub(crate) output: Output, known_nodes: HashSet, @@ -125,6 +126,7 @@ pub struct SurfaceThreadState { primary_node: DrmNode, target_node: DrmNode, active: Arc, + vrr_mode: AdaptiveSync, compositor: Option, state: QueueState, @@ -225,7 +227,6 @@ pub enum ThreadCommand { surface: DrmSurface, gbm: GbmDevice, cursor_size: Size, - vrr: bool, result: SyncSender>, }, NodeAdded { @@ -240,6 +241,8 @@ pub enum ThreadCommand { VBlank(Option), ScheduleRender, SetMode(Mode, SyncSender>), + AdaptiveSyncAvailable(SyncSender>), + UseAdaptiveSync(AdaptiveSync), End, DpmsOff, } @@ -345,7 +348,7 @@ impl Surface { Ok(Surface { connector, - crtc, + _crtc: crtc, output: output.clone(), known_nodes: HashSet::new(), active, @@ -402,6 +405,25 @@ impl Surface { rx.recv().context("Surface thread died")? } + pub fn use_adaptive_sync(&mut self, vrr: AdaptiveSync) -> Result { + if vrr != AdaptiveSync::Disabled { + let (tx, rx) = std::sync::mpsc::sync_channel(1); + let _ = self + .thread_command + .send(ThreadCommand::AdaptiveSyncAvailable(tx)); + match rx.recv().context("Surface thread died")?? { + VrrSupport::RequiresModeset if vrr == AdaptiveSync::Enabled => return Ok(false), + VrrSupport::NotSupported => return Ok(false), + _ => {} + }; + } + + let _ = self + .thread_command + .send(ThreadCommand::UseAdaptiveSync(vrr)); + Ok(true) + } + pub fn suspend(&mut self) { let _ = self.thread_command.send(ThreadCommand::Suspend); } @@ -411,7 +433,6 @@ impl Surface { surface: DrmSurface, gbm: GbmDevice, cursor_size: Size, - vrr: bool, ) -> Result<()> { let (tx, rx) = std::sync::mpsc::sync_channel(1); self.plane_formats = surface @@ -432,7 +453,6 @@ impl Surface { surface, gbm, cursor_size, - vrr, result: tx, }); @@ -503,6 +523,7 @@ fn surface_thread( target_node, active, compositor: None, + vrr_mode: AdaptiveSync::Disabled, state: QueueState::Idle, timings: Timings::new(None, false), @@ -529,10 +550,9 @@ fn surface_thread( surface, gbm, cursor_size, - vrr, result, }) => { - let _ = result.send(state.resume(surface, gbm, cursor_size, vrr)); + let _ = result.send(state.resume(surface, gbm, cursor_size)); } Event::Msg(ThreadCommand::NodeAdded { node, gbm, egl }) => { if let Err(err) = state.node_added(node, gbm, egl) { @@ -562,6 +582,22 @@ fn surface_thread( let _ = result.send(Err(anyhow::anyhow!("Set mode with inactive surface"))); } } + Event::Msg(ThreadCommand::AdaptiveSyncAvailable(result)) => { + if let Some(compositor) = state.compositor.as_mut() { + let _ = result.send( + compositor + .vrr_supported( + compositor.pending_connectors().into_iter().next().unwrap(), + ) + .map_err(Into::into), + ); + } else { + let _ = result.send(Err(anyhow::anyhow!("Set vrr with inactive surface"))); + } + } + Event::Msg(ThreadCommand::UseAdaptiveSync(vrr)) => { + state.vrr_mode = vrr; + } Event::Msg(ThreadCommand::DpmsOff) => { if let Some(compositor) = state.compositor.as_mut() { if let Err(err) = compositor.clear() { @@ -623,7 +659,6 @@ impl SurfaceThreadState { surface: DrmSurface, gbm: GbmDevice, cursor_size: Size, - vrr: bool, ) -> Result<()> { let driver = surface.get_driver().ok(); let mut planes = surface.planes().clone(); @@ -649,7 +684,6 @@ impl SurfaceThreadState { .set_refresh_interval(Some(Duration::from_secs_f64( 1_000.0 / drm_helpers::calculate_refresh_rate(surface.pending_mode()) as f64, ))); - self.timings.set_vrr(vrr); match DrmCompositor::new( &self.output, @@ -920,6 +954,11 @@ impl SurfaceThreadState { self.timings.start_render(&self.clock); + let mut vrr = match self.vrr_mode { + AdaptiveSync::Force => true, + _ => false, + }; + let mut elements = { let shell = self.shell.read().unwrap(); let output = self.mirroring.as_ref().unwrap_or(&self.output); @@ -929,6 +968,9 @@ impl SurfaceThreadState { let previous_workspace = previous_workspace .zip(previous_idx) .map(|((w, start), idx)| (w.handle, idx, start)); + if self.vrr_mode == AdaptiveSync::Enabled { + vrr = workspace.get_fullscreen().is_some(); + } let workspace = (workspace.handle, idx); std::mem::drop(shell); @@ -958,6 +1000,7 @@ impl SurfaceThreadState { anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) })? }; + self.timings.set_vrr(vrr); self.timings.elements_done(&self.clock); // we can't use the elements after `compositor.render_frame`, @@ -1113,8 +1156,14 @@ impl SurfaceThreadState { .collect::>(); renderer = self.api.single_renderer(&self.target_node).unwrap(); + if let Err(err) = compositor.use_vrr(false) { + warn!("Unable to set adaptive VRR state: {}", err); + } compositor.render_frame(&mut renderer, &elements, [0.0, 0.0, 0.0, 1.0]) } else { + if let Err(err) = compositor.use_vrr(vrr) { + warn!("Unable to set adaptive VRR state: {}", err); + } compositor.render_frame( &mut renderer, &elements, diff --git a/src/config/mod.rs b/src/config/mod.rs index b944a6ed..f8bd9a2f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -98,19 +98,34 @@ pub enum OutputState { Mirroring(String), } -fn default_enabled() -> OutputState { +fn default_state() -> OutputState { OutputState::Enabled } +#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum AdaptiveSync { + #[serde(rename = "true")] + Enabled, + #[serde(rename = "false")] + Disabled, + Force, +} + +fn default_sync() -> AdaptiveSync { + AdaptiveSync::Enabled +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] pub struct OutputConfig { pub mode: ((i32, i32), Option), - pub vrr: bool, + #[serde(default = "default_sync")] + pub vrr: AdaptiveSync, pub scale: f64, #[serde(with = "TransformDef")] pub transform: Transform, pub position: (u32, u32), - #[serde(default = "default_enabled")] + #[serde(default = "default_state")] pub enabled: OutputState, #[serde(default, skip_serializing_if = "Option::is_none")] pub max_bpc: Option, @@ -120,7 +135,7 @@ impl Default for OutputConfig { fn default() -> OutputConfig { OutputConfig { mode: ((0, 0), None), - vrr: false, + vrr: AdaptiveSync::Enabled, scale: 1.0, transform: Transform::Normal, position: (0, 0), diff --git a/src/utils/prelude.rs b/src/utils/prelude.rs index 39d71a7e..1984033f 100644 --- a/src/utils/prelude.rs +++ b/src/utils/prelude.rs @@ -4,7 +4,7 @@ use smithay::{ }; pub use super::geometry::*; -use crate::config::{OutputConfig, OutputState}; +use crate::config::{AdaptiveSync, OutputConfig, OutputState}; pub use crate::shell::{SeatExt, Shell, Workspace}; pub use crate::state::{Common, State}; pub use crate::wayland::handlers::xdg_shell::popup::update_reactive_popups; @@ -12,15 +12,15 @@ pub use crate::wayland::handlers::xdg_shell::popup::update_reactive_popups; use std::{ cell::{Ref, RefCell, RefMut}, sync::{ - atomic::{AtomicBool, Ordering}, + atomic::{AtomicU8, Ordering}, Mutex, }, }; pub trait OutputExt { fn geometry(&self) -> Rectangle; - fn adaptive_sync(&self) -> bool; - fn set_adaptive_sync(&self, vrr: bool); + fn adaptive_sync(&self) -> AdaptiveSync; + fn set_adaptive_sync(&self, vrr: AdaptiveSync); fn mirroring(&self) -> Option; fn set_mirroring(&self, output: Option); @@ -29,7 +29,7 @@ pub trait OutputExt { fn config_mut(&self) -> RefMut<'_, OutputConfig>; } -struct Vrr(AtomicBool); +struct Vrr(AtomicU8); struct Mirroring(Mutex>); @@ -49,20 +49,27 @@ impl OutputExt for Output { .as_global() } - fn adaptive_sync(&self) -> bool { + fn adaptive_sync(&self) -> AdaptiveSync { self.user_data() .get::() - .map(|vrr| vrr.0.load(Ordering::SeqCst)) - .unwrap_or(false) + .map(|vrr| match vrr.0.load(Ordering::SeqCst) { + 2 => AdaptiveSync::Force, + 1 => AdaptiveSync::Enabled, + _ => AdaptiveSync::Disabled, + }) + .unwrap_or(AdaptiveSync::Disabled) } - fn set_adaptive_sync(&self, vrr: bool) { + fn set_adaptive_sync(&self, vrr: AdaptiveSync) { let user_data = self.user_data(); - user_data.insert_if_missing_threadsafe(|| Vrr(AtomicBool::new(false))); - user_data - .get::() - .unwrap() - .0 - .store(vrr, Ordering::SeqCst); + user_data.insert_if_missing_threadsafe(|| Vrr(AtomicU8::new(0))); + user_data.get::().unwrap().0.store( + match vrr { + AdaptiveSync::Disabled => 0, + AdaptiveSync::Enabled => 1, + AdaptiveSync::Force => 2, + }, + Ordering::SeqCst, + ); } fn mirroring(&self) -> Option { diff --git a/src/wayland/handlers/output_configuration.rs b/src/wayland/handlers/output_configuration.rs index eb900ddc..6e8b23ce 100644 --- a/src/wayland/handlers/output_configuration.rs +++ b/src/wayland/handlers/output_configuration.rs @@ -4,7 +4,7 @@ use smithay::{output::Output, utils::Point}; use tracing::{error, warn}; use crate::{ - config::{OutputConfig, OutputState}, + config::{AdaptiveSync, OutputConfig, OutputState}, state::State, wayland::protocols::output_configuration::{ delegate_output_configuration, ModeConfiguration, OutputConfiguration, @@ -120,7 +120,11 @@ impl State { current_config.position = (position.x as u32, position.y as u32); } if let Some(vrr) = adaptive_sync { - current_config.vrr = *vrr; + current_config.vrr = if *vrr { + AdaptiveSync::Force + } else { + AdaptiveSync::Disabled + }; } if let Some(mirror) = mirroring { current_config.enabled = OutputState::Mirroring(mirror.name()); diff --git a/src/wayland/protocols/output_configuration/mod.rs b/src/wayland/protocols/output_configuration/mod.rs index b34995c3..8f32407e 100644 --- a/src/wayland/protocols/output_configuration/mod.rs +++ b/src/wayland/protocols/output_configuration/mod.rs @@ -441,11 +441,13 @@ where } if instance.obj.version() >= zwlr_output_head_v1::EVT_ADAPTIVE_SYNC_SINCE { - instance.obj.adaptive_sync(if output.adaptive_sync() { - zwlr_output_head_v1::AdaptiveSyncState::Enabled - } else { - zwlr_output_head_v1::AdaptiveSyncState::Disabled - }); + instance + .obj + .adaptive_sync(if output.adaptive_sync() == AdaptiveSync::Disabled { + zwlr_output_head_v1::AdaptiveSyncState::Disabled + } else { + zwlr_output_head_v1::AdaptiveSyncState::Enabled + }); } } @@ -498,4 +500,4 @@ macro_rules! delegate_output_configuration { } pub(crate) use delegate_output_configuration; -use crate::utils::prelude::OutputExt; +use crate::{config::AdaptiveSync, utils::prelude::OutputExt};