diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 12d9d147..7a6f7e9d 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -15,7 +15,7 @@ use { MetalBackend, MetalError, }, drm_feedback::DrmFeedback, - edid::Descriptor, + edid::{CtaDataBlock, Descriptor, EdidExtension}, format::{Format, ARGB8888, XRGB8888}, gfx_api::{ needs_render_usage, AcquireSync, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, @@ -23,7 +23,7 @@ use { }, ifs::{ wl_output::OutputId, - wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC, KIND_ZERO_COPY}, + wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VRR, KIND_VSYNC, KIND_ZERO_COPY}, }, state::State, tree::OutputNode, @@ -331,6 +331,7 @@ pub struct ConnectorDisplayData { pub non_desktop: bool, pub non_desktop_effective: bool, pub vrr_capable: bool, + pub vrr_refresh_max_nsec: u64, pub connector_id: ConnectorKernelId, pub output_id: Rc, @@ -1117,6 +1118,7 @@ fn create_connector_display_data( let mut name = String::new(); let mut manufacturer = String::new(); let mut serial_number = String::new(); + let mut vrr_refresh_max_nsec = u64::MAX; let connector_id = ConnectorKernelId { ty: ConnectorType::from_drm(info.connector_type), idx: info.connector_type_id, @@ -1182,6 +1184,28 @@ fn create_connector_display_data( ); serial_number = edid.base_block.id_serial_number.to_string(); } + let min_vrr_hz = 'fetch_min_hz: { + for ext in &edid.extension_blocks { + if let EdidExtension::CtaV3(cta) = ext { + for data_block in &cta.data_blocks { + if let CtaDataBlock::VendorAmd(amd) = data_block { + break 'fetch_min_hz amd.minimum_refresh_hz as u64; + } + } + } + } + for desc in &edid.base_block.descriptors { + if let Some(desc) = desc { + if let Descriptor::DisplayRangeLimitsAndAdditionalTiming(timings) = desc { + break 'fetch_min_hz timings.vertical_field_rate_min as u64; + } + } + } + 0 + }; + if min_vrr_hz > 0 { + vrr_refresh_max_nsec = 1_000_000_000 / min_vrr_hz; + } } let output_id = Rc::new(OutputId::new( connector_id.to_string(), @@ -1237,6 +1261,7 @@ fn create_connector_display_data( non_desktop, non_desktop_effective: non_desktop_override.unwrap_or(non_desktop), vrr_capable, + vrr_refresh_max_nsec, connection, mm_width: info.mm_width, mm_height: info.mm_height, @@ -1987,17 +2012,17 @@ impl MetalBackend { if connector.presentation_is_zero_copy.get() { flags |= KIND_ZERO_COPY; } - let refresh = match crtc.vrr_enabled.value.get() { - true => 0, - false => dd.refresh, - }; + if crtc.vrr_enabled.value.get() { + flags |= KIND_VRR; + } if let Some(g) = &global { g.presented( tv_sec as _, tv_usec * 1000, - refresh, + dd.refresh, connector.sequence.get(), flags, + dd.vrr_refresh_max_nsec, ); } } diff --git a/src/edid.rs b/src/edid.rs index 783888eb..9bcdc579 100644 --- a/src/edid.rs +++ b/src/edid.rs @@ -1161,32 +1161,28 @@ pub struct EdidBaseBlock { #[derive(Debug)] pub enum EdidExtension { Unknown, - #[expect(dead_code)] CtaV3(CtaExtensionV3), } #[derive(Debug)] pub struct CtaExtensionV3 { - #[expect(dead_code)] pub data_blocks: Vec, } #[derive(Debug)] pub enum CtaDataBlock { Unknown, - #[expect(dead_code)] VendorAmd(CtaAmdVendorDataBlock), } #[derive(Debug)] -#[expect(dead_code)] pub struct CtaAmdVendorDataBlock { pub minimum_refresh_hz: u8, + #[expect(dead_code)] pub maximum_refresh_hz: u8, } #[derive(Debug)] -#[expect(dead_code)] pub struct EdidFile { pub base_block: EdidBaseBlock, pub extension_blocks: Vec, diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 61df4dd0..9660dad6 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -55,7 +55,7 @@ use { zwlr_layer_surface_v1::{PendingLayerSurfaceData, ZwlrLayerSurfaceV1Error}, }, wp_content_type_v1::ContentType, - wp_presentation_feedback::WpPresentationFeedback, + wp_presentation_feedback::{WpPresentationFeedback, KIND_VRR, VRR_BOUNDS_SINCE}, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, }, leaks::Tracker, @@ -68,10 +68,10 @@ use { VblankListener, }, utils::{ - cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, - double_buffered::DoubleBuffered, errorfmt::ErrorFmt, event_listener::EventListener, - linkedlist::LinkedList, numcell::NumCell, smallmap::SmallMap, - transform_ext::TransformExt, + bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell, + copyhashmap::CopyHashMap, double_buffered::DoubleBuffered, errorfmt::ErrorFmt, + event_listener::EventListener, linkedlist::LinkedList, numcell::NumCell, + smallmap::SmallMap, transform_ext::TransformExt, }, video::{ dmabuf::DMA_BUF_SYNC_READ, @@ -2096,15 +2096,27 @@ impl PresentationListener for WlSurface { refresh: u32, seq: u64, flags: u32, + vrr_refresh_max_nsec: u64, ) { let bindings = output.global.bindings.borrow(); let bindings = bindings.get(&self.client.id); + let vrr_refresh_min_nsec = refresh as u64; for pf in self.latched_presentation_feedback.borrow_mut().drain(..) { if let Some(bindings) = bindings { for binding in bindings.values() { pf.send_sync_output(binding); } } + let mut flags = flags; + let mut refresh = refresh; + if flags.contains(KIND_VRR) { + if pf.version >= VRR_BOUNDS_SINCE { + pf.send_variable_refresh_bounds(vrr_refresh_min_nsec, vrr_refresh_max_nsec); + } else { + flags &= !KIND_VRR; + refresh = 0; + } + } pf.send_presented(tv_sec, tv_nsec, refresh, seq, flags); let _ = pf.client.remove_obj(&*pf); } diff --git a/src/ifs/wp_presentation.rs b/src/ifs/wp_presentation.rs index cdaa1f18..558f671a 100644 --- a/src/ifs/wp_presentation.rs +++ b/src/ifs/wp_presentation.rs @@ -48,7 +48,7 @@ impl Global for WpPresentationGlobal { } fn version(&self) -> u32 { - 1 + 2 } } diff --git a/src/ifs/wp_presentation_feedback.rs b/src/ifs/wp_presentation_feedback.rs index aa9de5cc..8713aeef 100644 --- a/src/ifs/wp_presentation_feedback.rs +++ b/src/ifs/wp_presentation_feedback.rs @@ -23,6 +23,9 @@ pub const KIND_VSYNC: u32 = 0x1; pub const KIND_HW_CLOCK: u32 = 0x2; pub const KIND_HW_COMPLETION: u32 = 0x4; pub const KIND_ZERO_COPY: u32 = 0x8; +pub const KIND_VRR: u32 = 0x10; + +pub const VRR_BOUNDS_SINCE: Version = Version(2); impl WpPresentationFeedback { pub fn send_sync_output(&self, output: &WlOutput) { @@ -32,6 +35,16 @@ impl WpPresentationFeedback { }); } + pub fn send_variable_refresh_bounds(&self, min_nsec: u64, max_nsec: u64) { + self.client.event(VariableRefreshBounds { + self_id: self.id, + refresh_min_nsec_hi: (min_nsec >> 32) as u32, + refresh_min_nsec_lo: min_nsec as u32, + refresh_max_nsec_hi: (max_nsec >> 32) as u32, + refresh_max_nsec_lo: max_nsec as u32, + }); + } + pub fn send_presented(&self, tv_sec: u64, tv_nsec: u32, refresh: u32, seq: u64, flags: u32) { self.client.event(Presented { self_id: self.id, diff --git a/src/tree/output.rs b/src/tree/output.rs index 93b43aef..a806e173 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -103,6 +103,7 @@ pub trait PresentationListener { refresh: u32, seq: u64, flags: u32, + vrr_refresh_max_nsec: u64, ); } @@ -138,9 +139,25 @@ impl OutputNode { } } - pub fn presented(&self, tv_sec: u64, tv_nsec: u32, refresh: u32, seq: u64, flags: u32) { + pub fn presented( + &self, + tv_sec: u64, + tv_nsec: u32, + refresh: u32, + seq: u64, + flags: u32, + vrr_refresh_max_nsec: u64, + ) { for listener in self.presentation_event.iter() { - listener.presented(self, tv_sec, tv_nsec, refresh, seq, flags); + listener.presented( + self, + tv_sec, + tv_nsec, + refresh, + seq, + flags, + vrr_refresh_max_nsec, + ); } } diff --git a/wire/wp_presentation_feedback.txt b/wire/wp_presentation_feedback.txt index b6635dee..5be0bbd3 100644 --- a/wire/wp_presentation_feedback.txt +++ b/wire/wp_presentation_feedback.txt @@ -17,3 +17,10 @@ event presented { event discarded { } + +event variable_refresh_bounds (since = 2) { + refresh_min_nsec_hi: u32, + refresh_min_nsec_lo: u32, + refresh_max_nsec_hi: u32, + refresh_max_nsec_lo: u32, +}