diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 79aa6470..627c497a 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -463,6 +463,7 @@ pub struct MetalConnector { pub try_switch_format: Cell, pub version: NumCell, + pub sequence: Cell, } impl Debug for MetalConnector { @@ -712,6 +713,16 @@ impl MetalConnector { }, } } + + fn queue_sequence(&self) { + if let Some(crtc) = self.crtc.get() { + if let Err(e) = self.master.queue_sequence(crtc.id) { + log::error!("Could not queue a CRTC sequence: {}", ErrorFmt(e)); + } else { + crtc.have_queued_sequence.set(true); + } + } + } } impl Connector for MetalConnector { @@ -908,6 +919,7 @@ pub struct MetalCrtc { pub vrr_enabled: MutableProperty, pub mode_blob: CloneCell>>, + pub have_queued_sequence: Cell, } impl Debug for MetalCrtc { @@ -1042,6 +1054,7 @@ fn create_connector( tearing_requested: Cell::new(false), try_switch_format: Cell::new(false), version: Default::default(), + sequence: Default::default(), }); let futures = ConnectorFutures { _present: backend @@ -1244,6 +1257,7 @@ fn create_crtc( out_fence_ptr: props.get("OUT_FENCE_PTR")?.id, vrr_enabled: props.get("VRR_ENABLED")?.map(|v| v == 1), mode_blob: Default::default(), + have_queued_sequence: Cell::new(false), }) } @@ -1828,9 +1842,64 @@ impl MetalBackend { sequence, crtc_id, } => self.handle_drm_flip_event(dev, crtc_id, tv_sec, tv_usec, sequence), + DrmEvent::Sequence { + time_ns, + sequence, + crtc_id, + } => self.handle_drm_sequence_event(dev, crtc_id, time_ns, sequence), } } + fn update_sequence(&self, connector: &Rc, new: u64) { + if connector.sequence.replace(new) == new { + return; + } + // nothing + } + + fn update_u32_sequence(&self, connector: &Rc, sequence: u32) { + let old = connector.sequence.get(); + let mut new = (old & !(u32::MAX as u64)) | (sequence as u64); + if new < old { + new += 1 << u32::BITS; + if new < old { + log::warn!("Ignoring nonsensical sequence {sequence} (old = {old})"); + return; + } + } + if new > old + (1 << (u32::BITS - 1)) { + new = new.saturating_sub(1 << u32::BITS); + if new < old { + return; + } + } + self.update_sequence(connector, new); + } + + fn handle_drm_sequence_event( + self: &Rc, + dev: &Rc, + crtc_id: DrmCrtc, + time_ns: i64, + sequence: u64, + ) { + let crtc = match dev.dev.crtcs.get(&crtc_id) { + Some(c) => c, + _ => return, + }; + crtc.have_queued_sequence.set(false); + let connector = match crtc.connector.get() { + Some(c) => c, + _ => return, + }; + self.update_sequence(&connector, sequence); + connector.queue_sequence(); + let dd = connector.display.borrow(); + connector + .next_flip_nsec + .set(time_ns as u64 + dd.refresh as u64); + } + fn handle_drm_flip_event( self: &Rc, dev: &Rc, @@ -1847,6 +1916,10 @@ impl MetalBackend { Some(c) => c, _ => return, }; + if !crtc.have_queued_sequence.get() { + connector.queue_sequence(); + } + self.update_u32_sequence(&connector, sequence); connector.can_present.set(true); if let Some(fb) = connector.next_framebuffer.take() { *connector.active_framebuffer.borrow_mut() = Some(fb); @@ -1877,7 +1950,7 @@ impl MetalBackend { tv_sec as _, tv_usec * 1000, refresh, - sequence as _, + connector.sequence.get(), KIND_VSYNC | KIND_HW_COMPLETION, ); let _ = fb.client.remove_obj(&*fb); diff --git a/src/video/drm.rs b/src/video/drm.rs index ceca8c77..be0a05e2 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -40,9 +40,9 @@ use crate::{ video::{ dmabuf::DmaBuf, drm::sys::{ - auth_magic, drm_format_modifier, drm_format_modifier_blob, drop_master, get_version, - revoke_lease, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, DRM_CAP_CURSOR_HEIGHT, - DRM_CAP_CURSOR_WIDTH, FORMAT_BLOB_CURRENT, + auth_magic, drm_event_crtc_sequence, drm_format_modifier, drm_format_modifier_blob, + drop_master, get_version, queue_sequence, revoke_lease, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, + DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH, FORMAT_BLOB_CURRENT, }, Modifier, INVALID_MODIFIER, }, @@ -142,6 +142,8 @@ pub enum DrmError { CreateLease(#[source] OsError), #[error("Could not drop DRM master")] DropMaster(#[source] OsError), + #[error("Could not queue a CRTC sequence")] + QueueSequence(#[source] OsError), } fn render_node_name(fd: c::c_int) -> Result { @@ -223,6 +225,10 @@ impl Drm { pub fn is_master(&self) -> bool { auth_magic(self.fd.raw(), 0) != Err(OsError(c::EACCES)) } + + pub fn queue_sequence(&self, crtc: DrmCrtc) -> Result<(), DrmError> { + queue_sequence(self.fd.raw(), crtc).map_err(DrmError::QueueSequence) + } } pub struct InFormat { @@ -554,6 +560,17 @@ impl DrmMaster { crtc_id: DrmCrtc(event.crtc_id), }); } + sys::DRM_EVENT_CRTC_SEQUENCE => { + let event: drm_event_crtc_sequence = match uapi::pod_read_init(buf) { + Ok(e) => e, + _ => return Err(DrmError::InvalidRead), + }; + self.events.push(DrmEvent::Sequence { + time_ns: event.time_ns, + sequence: event.sequence, + crtc_id: DrmCrtc(event.user_data as _), + }); + } _ => {} } buf = &buf[len..]; @@ -582,6 +599,11 @@ pub enum DrmEvent { sequence: u32, crtc_id: DrmCrtc, }, + Sequence { + time_ns: i64, + sequence: u64, + crtc_id: DrmCrtc, + }, } pub struct DrmFramebuffer { diff --git a/src/video/drm/sys.rs b/src/video/drm/sys.rs index 225d0693..65bb050e 100644 --- a/src/video/drm/sys.rs +++ b/src/video/drm/sys.rs @@ -1048,7 +1048,6 @@ pub fn gem_close(fd: c::c_int, handle: u32) -> Result<(), OsError> { #[expect(dead_code)] pub const DRM_EVENT_VBLANK: u32 = 0x01; pub const DRM_EVENT_FLIP_COMPLETE: u32 = 0x02; -#[expect(dead_code)] pub const DRM_EVENT_CRTC_SEQUENCE: u32 = 0x03; #[repr(C)] @@ -1071,6 +1070,16 @@ pub struct drm_event_vblank { unsafe impl Pod for drm_event_vblank {} +#[repr(C)] +pub struct drm_event_crtc_sequence { + pub base: drm_event, + pub user_data: u64, + pub time_ns: i64, + pub sequence: u64, +} + +unsafe impl Pod for drm_event_crtc_sequence {} + #[repr(C)] struct drm_mode_get_blob { blob_id: u32, @@ -1399,3 +1408,29 @@ pub fn auth_magic(fd: c::c_int, magic: c::c_uint) -> Result<(), OsError> { } Ok(()) } + +const DRM_CRTC_SEQUENCE_RELATIVE: u32 = 0x00000001; +// const DRM_CRTC_SEQUENCE_NEXT_ON_MISS: u32 = 0x00000002; + +#[repr(C)] +struct drm_crtc_queue_sequence { + crtc_id: u32, + flags: u32, + sequence: u64, + user_data: u64, +} + +const DRM_IOCTL_CRTC_QUEUE_SEQUENCE: u64 = drm_iowr::(0x3c); + +pub fn queue_sequence(fd: c::c_int, crtc: DrmCrtc) -> Result<(), OsError> { + let mut res = drm_crtc_queue_sequence { + crtc_id: crtc.0, + flags: DRM_CRTC_SEQUENCE_RELATIVE, + sequence: 1, + user_data: crtc.0 as _, + }; + unsafe { + ioctl(fd, DRM_IOCTL_CRTC_QUEUE_SEQUENCE, &mut res)?; + } + Ok(()) +}