diff --git a/docs/features.md b/docs/features.md index 4737e2eb..42392a6d 100644 --- a/docs/features.md +++ b/docs/features.md @@ -161,7 +161,7 @@ Jay supports the following wayland protocols: | wp_drm_lease_device_v1 | 1 | | | wp_fractional_scale_manager_v1 | 1 | | | wp_linux_drm_syncobj_manager_v1 | 1 | | -| wp_presentation | 1 | | +| wp_presentation | 2 | | | wp_security_context_manager_v1 | 1 | | | wp_single_pixel_buffer_manager_v1 | 1 | | | wp_tearing_control_manager_v1 | 1 | | diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index eb17e8ea..8419917d 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, @@ -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, @@ -1129,6 +1130,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, @@ -1194,6 +1196,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(), @@ -1249,6 +1273,7 @@ fn create_connector_display_data( non_desktop, non_desktop_effective: non_desktop_override.unwrap_or(non_desktop), vrr_capable, + _vrr_refresh_max_nsec: vrr_refresh_max_nsec, connection, mm_width: info.mm_width, mm_height: info.mm_height, @@ -2002,17 +2027,14 @@ 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 let Some(g) = &global { g.presented( tv_sec as _, tv_usec * 1000, - refresh, + dd.refresh, connector.sequence.get(), flags, + crtc.vrr_enabled.value.get(), ); } } 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/ext_image_copy/ext_image_copy_capture_session_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs index e0377f42..f5e644de 100644 --- a/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs @@ -301,6 +301,7 @@ impl PresentationListener for ExtImageCopyCaptureSessionV1 { _refresh: u32, _seq: u64, _flags: u32, + _vrr: bool, ) { self.presentation_listener.detach(); let Some(frame) = self.frame.get() else { diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 34583dd9..ad4d12c7 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, VRR_REFRESH_SINCE}, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, }, leaks::Tracker, @@ -2135,6 +2135,7 @@ impl PresentationListener for WlSurface { refresh: u32, seq: u64, flags: u32, + vrr: bool, ) { let bindings = output.global.bindings.borrow(); let bindings = bindings.get(&self.client.id); @@ -2144,6 +2145,10 @@ impl PresentationListener for WlSurface { pf.send_sync_output(binding); } } + let mut refresh = refresh; + if vrr && pf.version < VRR_REFRESH_SINCE { + 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..5230f50d 100644 --- a/src/ifs/wp_presentation_feedback.rs +++ b/src/ifs/wp_presentation_feedback.rs @@ -24,6 +24,8 @@ pub const KIND_HW_CLOCK: u32 = 0x2; pub const KIND_HW_COMPLETION: u32 = 0x4; pub const KIND_ZERO_COPY: u32 = 0x8; +pub const VRR_REFRESH_SINCE: Version = Version(2); + impl WpPresentationFeedback { pub fn send_sync_output(&self, output: &WlOutput) { self.client.event(SyncOutput { diff --git a/src/tree/output.rs b/src/tree/output.rs index fbdb6f95..dab1a744 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -111,6 +111,7 @@ pub trait PresentationListener { refresh: u32, seq: u64, flags: u32, + vrr: bool, ); } @@ -158,9 +159,17 @@ 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: bool, + ) { 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); } }