From de47705a32ef3e2586a9651a429731b6c6561a07 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 25 Sep 2024 15:41:50 +0200 Subject: [PATCH 1/2] edid: parse cta amd vendor block --- src/edid.rs | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 2 deletions(-) diff --git a/src/edid.rs b/src/edid.rs index 61108d6f..783888eb 100644 --- a/src/edid.rs +++ b/src/edid.rs @@ -431,6 +431,16 @@ impl<'a> EdidParser<'a> { } } + fn nest(&self, data: &'a [u8]) -> Self { + Self { + data, + pos: 0, + context: self.context.clone(), + saved_ctx: vec![], + errors: vec![], + } + } + fn store_error(&mut self, error: EdidError) { self.errors.push((error, self.saved_ctx.clone())); } @@ -449,6 +459,16 @@ impl<'a> EdidParser<'a> { Ok(v) } + fn read_var_n(&mut self, n: usize) -> Result<&'a [u8], EdidError> { + let _ctx = self.push_ctx(EdidParseContext::ReadingBytes(n)); + if self.data.len() - self.pos < n { + bail!(self, EdidError::UnexpectedEof); + } + let v = &self.data[self.pos..self.pos + n]; + self.pos += n; + Ok(v) + } + fn read_u8(&mut self) -> Result { let &[a] = self.read_n()?; Ok(a) @@ -1000,10 +1020,71 @@ impl<'a> EdidParser<'a> { }) } + fn parse_cta_amd_vendor_data_block(&mut self) -> Result { + let _ = self.read_n::<2>()?; + Ok(CtaDataBlock::VendorAmd(CtaAmdVendorDataBlock { + minimum_refresh_hz: self.read_u8()?, + maximum_refresh_hz: self.read_u8()?, + })) + } + + fn parse_cta_vendor_data_block(&mut self) -> Result { + match self.read_n::<3>()? { + [0x1A, 0x00, 0x00] => self.parse_cta_amd_vendor_data_block(), + _ => Ok(CtaDataBlock::Unknown), + } + } + + fn parse_cta_data_block(&mut self, tag: u8) -> Result { + match tag { + 0x3 => self.parse_cta_vendor_data_block(), + _ => Ok(CtaDataBlock::Unknown), + } + } + + fn parse_cta_extension_v3(&mut self) -> Result { + let detailed_timing_descriptors_offset = self.read_u8()? as usize; + let _ = self.read_u8()?; + let mut data_blocks = vec![]; + while self.pos < detailed_timing_descriptors_offset { + let b1 = self.read_u8()?; + let data = self.read_var_n(b1 as usize & 0x1f)?; + let mut parser = self.nest(data); + match parser.parse_cta_data_block(b1 >> 5) { + Ok(d) => data_blocks.push(d), + Err(e) => { + self.saved_ctx = parser.saved_ctx; + self.store_error(e); + } + } + } + Ok(EdidExtension::CtaV3(CtaExtensionV3 { data_blocks })) + } + + fn parse_cta_extension(&mut self) -> Result { + // https://web.archive.org/web/20171201033424/https://standards.cta.tech/kwspub/published_docs/CTA-861-G_FINAL_revised_2017.pdf + match self.read_u8()? { + 0x3 => self.parse_cta_extension_v3(), + _ => Ok(EdidExtension::Unknown), + } + } + + fn parse_extension_impl(&mut self) -> Result { + match self.read_u8()? { + 0x2 => self.parse_cta_extension(), + _ => Ok(EdidExtension::Unknown), + } + } + fn parse_extension(&mut self) -> Result { let _ctx = self.push_ctx(EdidParseContext::Extension); - self.read_n::<128>()?; - Ok(EdidExtension::Unknown) + let data = self.read_n::<128>()?; + let mut parser = self.nest(data); + let res = parser.parse_extension_impl(); + if res.is_err() { + self.saved_ctx = parser.saved_ctx; + } + res } fn parse(&mut self) -> Result { @@ -1080,6 +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, + pub maximum_refresh_hz: u8, } #[derive(Debug)] From 03dce4af061c13c7379769b8d7bb1d4d6e1322a7 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 25 Sep 2024 16:25:20 +0200 Subject: [PATCH 2/2] wp-presentation: implement version 2 --- docs/features.md | 2 +- src/backends/metal/video.rs | 34 +++++++++++++++---- src/edid.rs | 6 +--- .../ext_image_copy_capture_session_v1.rs | 1 + src/ifs/wl_surface.rs | 7 +++- src/ifs/wp_presentation.rs | 2 +- src/ifs/wp_presentation_feedback.rs | 2 ++ src/tree/output.rs | 13 +++++-- 8 files changed, 51 insertions(+), 16 deletions(-) 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); } }