From 3b31a7d433fa4ad84998754d38f73cb7c1956525 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Jul 2025 11:00:01 +0200 Subject: [PATCH 1/6] macros: add dbg macro --- src/macros.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/macros.rs b/src/macros.rs index cf7161b6..6e958a54 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -772,3 +772,22 @@ macro_rules! jay_allow_realtime_config_so { "JAY_ALLOW_REALTIME_CONFIG_SO" }; } + +#[allow(clippy::allow_attributes, unused_macros)] +macro_rules! dbg { + ($val:expr) => { + match $val { + tmp => { + log::warn!( + "[{}:{}:{}] {} = {:#?}", + file!(), + line!(), + column!(), + stringify!($val), + &tmp + ); + tmp + } + } + }; +} From b8d4eeb8e453126f254a203b12b47ec11d4cecfe Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Jul 2025 11:01:35 +0200 Subject: [PATCH 2/6] utils: add BinarySearchMap util --- src/utils.rs | 1 + src/utils/binary_search_map.rs | 215 +++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 src/utils/binary_search_map.rs diff --git a/src/utils.rs b/src/utils.rs index 92928ca2..1ba53964 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,6 +2,7 @@ pub mod activation_token; pub mod array; pub mod array_to_tuple; pub mod asyncevent; +pub mod binary_search_map; pub mod bindings; pub mod bitfield; pub mod bitflags; diff --git a/src/utils/binary_search_map.rs b/src/utils/binary_search_map.rs new file mode 100644 index 00000000..0976e02b --- /dev/null +++ b/src/utils/binary_search_map.rs @@ -0,0 +1,215 @@ +use { + crate::utils::ptr_ext::{MutPtrExt, PtrExt}, + smallvec::SmallVec, + std::{ + fmt::{Debug, Formatter}, + mem, + }, +}; + +pub struct BinarySearchMap { + m: SmallVec<[(K, V); N]>, +} + +impl Debug for BinarySearchMap { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_map() + .entries(self.m.iter().map(|e| (&e.0, &e.1))) + .finish() + } +} + +impl Default for BinarySearchMap { + fn default() -> Self { + Self { + m: Default::default(), + } + } +} + +impl BinarySearchMap { + pub fn new_with(k: K, v: V) -> Self { + let mut sv = SmallVec::new(); + sv.push((k, v)); + Self { m: sv } + } + + pub fn new() -> Self { + Self { + m: SmallVec::new_const(), + } + } + + pub fn len(&self) -> usize { + self.m.len() + } + + fn pos(&self, k: &K) -> Result + where + K: Ord + Eq, + { + self.m.binary_search_by(|(c, _)| c.cmp(k)) + } + + pub fn contains(&self, k: &K) -> bool + where + K: Ord + Eq, + { + self.pos(k).is_ok() + } + + pub fn not_contains(&self, k: &K) -> bool + where + K: Ord + Eq, + { + !self.contains(k) + } + + pub fn insert(&mut self, k: K, v: V) -> Option + where + K: Ord + Eq, + { + match self.pos(&k) { + Ok(p) => Some(mem::replace(&mut self.m[p], (k, v)).1), + Err(p) => { + self.m.insert(p, (k, v)); + None + } + } + } + + pub fn get(&self, k: &K) -> Option<&V> + where + K: Ord + Eq, + { + self.pos(k).ok().map(|p| &self.m[p].1) + } + + pub fn get_mut(&mut self, k: &K) -> Option<&mut V> + where + K: Ord + Eq, + { + self.pos(k).ok().map(|p| &mut self.m[p].1) + } + + pub fn get_or_default_mut(&mut self, k: K) -> &mut V + where + K: Ord + Eq, + V: Default, + { + self.get_or_insert_with(k, || V::default()) + } + + pub fn get_or_insert_with(&mut self, k: K, f: F) -> &mut V + where + K: Ord + Eq, + F: FnOnce() -> V, + { + let p = match self.pos(&k) { + Ok(p) => return &mut self.m[p].1, + Err(p) => p, + }; + self.m.insert(p, (k, f())); + &mut self.m[p].1 + } + + pub fn is_empty(&self) -> bool { + self.m.is_empty() + } + + pub fn remove(&mut self, k: &K) -> Option + where + K: Ord + Eq, + { + if let Ok(p) = self.pos(k) { + return Some(self.m.remove(p).1); + } + None + } + + pub fn clear(&mut self) { + let _v = mem::replace(&mut self.m, SmallVec::new()); + } + + pub fn take(&mut self) -> SmallVec<[(K, V); N]> { + mem::take(&mut self.m) + } + + pub fn iter<'a>(&'a self) -> BinarySearchMapIter<'a, K, V, N> { + BinarySearchMapIter { pos: 0, map: self } + } + + pub fn values<'a>(&'a self) -> impl Iterator + 'a { + self.iter().map(|(_, v)| v) + } + + pub fn iter_mut<'a>(&'a mut self) -> BinarySearchMapMutIterMut<'a, K, V, N> { + BinarySearchMapMutIterMut { pos: 0, map: self } + } + + pub fn remove_if bool>(&mut self, mut f: F) { + let mut i = 0; + while i < self.m.len() { + let (k, v) = &self.m[i]; + if f(k, v) { + self.m.remove(i); + } else { + i += 1; + } + } + } +} + +impl<'a, K: Copy, V, const N: usize> IntoIterator for &'a BinarySearchMap { + type Item = (&'a K, &'a V); + type IntoIter = BinarySearchMapIter<'a, K, V, N>; + + fn into_iter(self) -> Self::IntoIter { + BinarySearchMapIter { pos: 0, map: self } + } +} + +impl<'a, K: Copy, V, const N: usize> IntoIterator for &'a mut BinarySearchMap { + type Item = (&'a K, &'a mut V); + type IntoIter = BinarySearchMapMutIterMut<'a, K, V, N>; + + fn into_iter(self) -> Self::IntoIter { + BinarySearchMapMutIterMut { pos: 0, map: self } + } +} + +pub struct BinarySearchMapIter<'a, K, V, const N: usize> { + pos: usize, + map: &'a BinarySearchMap, +} + +impl<'a, K, V, const N: usize> Iterator for BinarySearchMapIter<'a, K, V, N> { + type Item = (&'a K, &'a V); + + fn next(&mut self) -> Option { + if self.pos >= self.map.m.len() { + return None; + } + let (k, v) = &self.map.m[self.pos]; + self.pos += 1; + Some((k, v)) + } +} + +pub struct BinarySearchMapMutIterMut<'a, K, V, const N: usize> { + pos: usize, + map: &'a mut BinarySearchMap, +} + +impl<'a, K, V, const N: usize> Iterator for BinarySearchMapMutIterMut<'a, K, V, N> { + type Item = (&'a K, &'a mut V); + + fn next(&mut self) -> Option { + if self.pos >= self.map.m.len() { + return None; + } + let (k, v) = &mut self.map.m[self.pos]; + self.pos += 1; + unsafe { Some(((k as *const K).deref(), (v as *mut V).deref_mut())) } + } +} From e95e764b469365db6d5c87b7164767fb747086c7 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Jul 2025 11:10:07 +0200 Subject: [PATCH 3/6] drm: add ObjectChangeValue --- src/backends/metal.rs | 2 +- src/backends/metal/present.rs | 18 +++++++++--------- src/backends/metal/video.rs | 26 +++++++++++++------------- src/video/drm.rs | 34 ++++++++++++++++++++++++++++++++-- 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 516f86be..f6954de8 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -252,7 +252,7 @@ impl Backend for MetalBackend { { crtc.active.value.set(!idle); change.change_object(crtc.id, |c| { - c.change(crtc.active.id, (!idle) as _); + c.change(crtc.active.id, !idle); }); } } diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index fcef2803..8bebeb59 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -335,7 +335,7 @@ impl MetalConnector { macro_rules! change { ($c:expr, $prop:expr, $new:expr) => {{ if $prop.value.get() != $new { - $c.change($prop.id, $new as u64); + $c.change($prop.id, $new); try_async_flip = false; $prop.pending_value.set(Some($new)); } @@ -362,7 +362,7 @@ impl MetalConnector { } }; changes.change_object(plane.id, |c| { - c.change(plane.fb_id, fb.fb.id().0 as _); + c.change(plane.fb_id, fb.fb.id()); change!(c, plane.src_w, (src_width as u32) << 16); change!(c, plane.src_h, (src_height as u32) << 16); change!(c, plane.crtc_x, crtc_x); @@ -383,7 +383,7 @@ impl MetalConnector { && let Some(fb) = &*self.active_framebuffer.borrow() { changes.change_object(plane.id, |c| { - c.change(plane.fb_id, fb.fb.id().0 as _); + c.change(plane.fb_id, fb.fb.id()); }); } } @@ -400,12 +400,12 @@ impl MetalConnector { .. } => { changes.change_object(plane.id, |c| { - c.change(plane.fb_id, fb.id().0 as _); - c.change(plane.crtc_id.id, crtc.id.0 as _); - c.change(plane.crtc_x.id, *x as _); - c.change(plane.crtc_y.id, *y as _); - c.change(plane.crtc_w.id, *width as _); - c.change(plane.crtc_h.id, *height as _); + c.change(plane.fb_id, fb.id()); + c.change(plane.crtc_id.id, crtc.id); + c.change(plane.crtc_x.id, *x); + c.change(plane.crtc_y.id, *y); + c.change(plane.crtc_w.id, *width); + c.change(plane.crtc_h.id, *height); c.change(plane.src_x.id, 0); c.change(plane.src_y.id, 0); c.change(plane.src_w.id, (*width as u64) << 16); diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index e125a463..3fa455e1 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -956,7 +956,7 @@ impl Connector for MetalConnector { }; let mut change = self.master.change(); change.change_object(crtc.id, |c| { - c.change(crtc.vrr_enabled.id, new_enabled as _); + c.change(crtc.vrr_enabled.id, new_enabled); }); if let Err(e) = change.commit(0, 0) { log::error!("Could not change vrr mode: {}", ErrorFmt(e)); @@ -2389,8 +2389,8 @@ impl MetalBackend { changes.change_object(plane.id, |c| { c.change(plane.crtc_id.id, 0); c.change(plane.fb_id, 0); - c.change(plane.in_fence_fd, -1i32 as u64); - }) + c.change(plane.in_fence_fd, -1i32); + }); } } @@ -2441,7 +2441,7 @@ impl MetalBackend { c.change(crtc.active.id, 0); c.change(crtc.mode_id.id, 0); c.change(crtc.vrr_enabled.id, 0); - }) + }); } } @@ -2832,7 +2832,7 @@ impl MetalBackend { } let vrr_requested = vrr_crtcs.contains(&crtc.id); if crtc.vrr_enabled.value.get() != vrr_requested { - c.change(crtc.vrr_enabled.id, vrr_requested as _); + c.change(crtc.vrr_enabled.id, vrr_requested); crtc.vrr_enabled.value.set(vrr_requested); } }); @@ -3077,9 +3077,9 @@ impl MetalBackend { let hdr_blob_id = hdr_blob.as_ref().map(|b| b.id()).unwrap_or_default(); let mode_blob = mode.create_blob(&connector.master)?; changes.change_object(connector.id, |c| { - c.change(dd.crtc_id.id, crtc.id.0 as _); + c.change(dd.crtc_id.id, crtc.id); if let Some(meta) = &dd.hdr_metadata { - c.change(meta.id, hdr_blob_id.0 as _); + c.change(meta.id, hdr_blob_id); } if let Some(cs) = &dd.colorspace { c.change(cs.id, dd.persistent.color_space.get().to_drm()); @@ -3087,8 +3087,8 @@ impl MetalBackend { }); changes.change_object(crtc.id, |c| { c.change(crtc.active.id, 1); - c.change(crtc.mode_id.id, mode_blob.id().0 as _); - c.change(crtc.vrr_enabled.id, dd.should_enable_vrr() as _); + c.change(crtc.mode_id.id, mode_blob.id()); + c.change(crtc.vrr_enabled.id, dd.should_enable_vrr()); }); connector.crtc.set(Some(crtc.clone())); connector.version.fetch_add(1); @@ -3212,12 +3212,12 @@ impl MetalBackend { } } changes.change_object(primary_plane.id, |c| { - c.change(primary_plane.fb_id, buffers[0].drm.id().0 as _); - c.change(primary_plane.crtc_id.id, crtc.id.0 as _); + c.change(primary_plane.fb_id, buffers[0].drm.id()); + c.change(primary_plane.crtc_id.id, crtc.id); c.change(primary_plane.crtc_x.id, 0); c.change(primary_plane.crtc_y.id, 0); - c.change(primary_plane.crtc_w.id, mode.hdisplay as _); - c.change(primary_plane.crtc_h.id, mode.vdisplay as _); + c.change(primary_plane.crtc_w.id, mode.hdisplay); + c.change(primary_plane.crtc_h.id, mode.vdisplay); c.change(primary_plane.src_x.id, 0); c.change(primary_plane.src_y.id, 0); c.change(primary_plane.src_w.id, (mode.hdisplay as u64) << 16); diff --git a/src/video/drm.rs b/src/video/drm.rs index 915118f6..97e466db 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -1052,9 +1052,9 @@ impl Change { } impl<'a> ObjectChange<'a> { - pub fn change(&mut self, property_id: DrmProperty, value: u64) { + pub fn change(&mut self, property_id: DrmProperty, value: impl ObjectChangeValue) { self.change.props.push(property_id.0); - self.change.values.push(value); + self.change.values.push(value.into_u64()); } } @@ -1069,6 +1069,36 @@ impl Drop for Change { } } +pub trait ObjectChangeValue { + fn into_u64(self) -> u64; +} + +macro_rules! num { + ($ty:ty) => { + impl ObjectChangeValue for $ty { + fn into_u64(self) -> u64 { + self as u64 + } + } + }; +} + +num!(u16); +num!(i32); +num!(u32); +num!(i64); +num!(u64); +num!(bool); + +impl ObjectChangeValue for T +where + T: DrmObject, +{ + fn into_u64(self) -> u64 { + self.id() as u64 + } +} + #[expect(non_camel_case_types)] #[derive(Copy, Clone, Debug)] pub enum ConnectorType { From f8d03c25a9d448d8ea12807496fce7a91ec89a93 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Jul 2025 11:13:53 +0200 Subject: [PATCH 4/6] metal: clear damage before adding full damage --- src/backends/metal/video.rs | 1 + src/rect/region.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 3fa455e1..5b6e4fe4 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -3332,6 +3332,7 @@ impl RenderBuffer { pub fn damage_full(&self) { let dmabuf = self.dev_bo.dmabuf(); let rect = Rect::new_sized_unchecked(0, 0, dmabuf.width, dmabuf.height); + self.damage_queue.clear_all(); self.damage_queue.damage(&[rect]); } } diff --git a/src/rect/region.rs b/src/rect/region.rs index 9679bc8a..2891f195 100644 --- a/src/rect/region.rs +++ b/src/rect/region.rs @@ -327,6 +327,13 @@ impl DamageQueue { data.clear(); } + pub fn clear_all(&self) { + let datas = unsafe { self.datas.get().deref_mut() }; + for data in datas { + data.clear(); + } + } + pub fn get(&self) -> Region { let data = unsafe { &self.datas.get().deref()[self.this] }; Region::from_rects2(data) From 7ab99bb8409947c8239e36ec62a3d60196ef343f Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 10 Jul 2025 11:17:34 +0200 Subject: [PATCH 5/6] backend: implement output transactions --- src/backend.rs | 89 +- src/backend/transaction.rs | 219 +++ src/backends/dummy.rs | 6 +- src/backends/metal.rs | 53 +- src/backends/metal/present.rs | 280 +-- src/backends/metal/transaction.rs | 1147 ++++++++++++ src/backends/metal/video.rs | 1780 +++++++------------ src/backends/x.rs | 132 +- src/compositor.rs | 42 +- src/config/handler.rs | 36 +- src/ifs/ext_session_lock_manager_v1.rs | 3 +- src/ifs/ext_session_lock_v1.rs | 16 +- src/ifs/jay_randr.rs | 55 +- src/ifs/wl_output.rs | 14 +- src/it/test_backend.rs | 99 +- src/it/tests/t0034_workspace_restoration.rs | 24 +- src/it/tests/t0036_idle.rs | 2 +- src/macros.rs | 2 +- src/state.rs | 34 +- src/tasks/connector.rs | 39 +- src/tasks/idle.rs | 31 +- src/tree/output.rs | 43 +- src/utils/binary_search_map.rs | 4 + src/video/drm.rs | 21 +- src/video/drm/sys.rs | 1 + 25 files changed, 2712 insertions(+), 1460 deletions(-) create mode 100644 src/backend/transaction.rs create mode 100644 src/backends/metal/transaction.rs diff --git a/src/backend.rs b/src/backend.rs index 6e063fbb..c93dbe8e 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,6 +1,10 @@ use { crate::{ async_engine::SpawnedFuture, + backend::transaction::{ + BackendConnectorTransaction, BackendConnectorTransactionError, + BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn, + }, cmm::cmm_primaries::Primaries, drm_feedback::DrmFeedback, fixed::Fixed, @@ -30,11 +34,14 @@ use { any::Any, error::Error, fmt::{Debug, Display, Formatter}, + hash::Hash, rc::Rc, }, uapi::{OwnedFd, c}, }; +pub mod transaction; + linear_ids!(ConnectorIds, ConnectorId); linear_ids!(InputDeviceIds, InputDeviceId); linear_ids!(DrmDeviceIds, DrmDeviceId); @@ -49,10 +56,6 @@ pub trait Backend: Any { let _ = vtnr; } - fn set_idle(&self, idle: bool) { - let _ = idle; - } - fn import_environment(&self) -> bool { false } @@ -82,21 +85,31 @@ impl Mode { } } +impl Display for Mode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}x{}@{}", + self.width, + self.height, + self.refresh_rate_millihz as f64 / 1000.0, + ) + } +} + #[derive(Clone, Debug)] pub struct MonitorInfo { pub modes: Vec, pub output_id: Rc, - pub initial_mode: Mode, pub width_mm: i32, pub height_mm: i32, pub non_desktop: bool, pub vrr_capable: bool, pub transfer_functions: Vec, - pub transfer_function: BackendTransferFunction, pub color_spaces: Vec, - pub color_space: BackendColorSpace, pub primaries: Primaries, pub luminance: Option, + pub state: BackendConnectorState, } #[derive(Copy, Clone, Debug)] @@ -111,41 +124,35 @@ impl Display for ConnectorKernelId { } } -pub trait Connector { +pub trait Connector: Any { fn id(&self) -> ConnectorId; fn kernel_id(&self) -> ConnectorKernelId; fn event(&self) -> Option; fn on_change(&self, cb: Rc); fn damage(&self); fn drm_dev(&self) -> Option; - fn enabled(&self) -> bool { - true - } - fn set_enabled(&self, enabled: bool) { - let _ = enabled; - } + fn effectively_locked(&self) -> bool; fn drm_feedback(&self) -> Option> { None } - fn set_mode(&self, mode: Mode); - fn set_non_desktop_override(&self, non_desktop: Option) { - let _ = non_desktop; - } fn drm_object_id(&self) -> Option { None } - fn set_vrr_enabled(&self, enabled: bool) { - let _ = enabled; + fn before_non_desktop_override_update(&self, overrd: Option) { + let _ = overrd; } - fn set_tearing_enabled(&self, enabled: bool) { - let _ = enabled; + fn transaction_type(&self) -> Box { + #[derive(Hash, Eq, PartialEq)] + struct UnimplementedConnectorTransactionType; + impl BackendConnectorTransactionType for UnimplementedConnectorTransactionType {} + Box::new(UnimplementedConnectorTransactionType) } - fn set_fb_format(&self, format: &'static Format) { - let _ = format; - } - fn set_colors(&self, bcs: BackendColorSpace, btf: BackendTransferFunction) { - let _ = bcs; - let _ = btf; + fn create_transaction( + &self, + ) -> Result, BackendConnectorTransactionError> { + Err(BackendConnectorTransactionError::TransactionsNotSupported( + self.kernel_id(), + )) } } @@ -155,12 +162,10 @@ pub enum ConnectorEvent { HardwareCursor(Option>), Disconnected, Removed, - ModeChanged(Mode), Unavailable, Available, - VrrChanged(bool), - FormatsChanged(Rc>, &'static Format), - ColorsChanged(BackendColorSpace, BackendTransferFunction), + State(BackendConnectorState), + FormatsChanged(Rc>), } pub trait HardwareCursorUpdate { @@ -570,3 +575,23 @@ impl BackendColorSpace { } } } + +linear_ids!( + BackendConnectorStateSerials, + BackendConnectorStateSerial, + u64 +); + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct BackendConnectorState { + pub serial: BackendConnectorStateSerial, + pub enabled: bool, + pub active: bool, + pub mode: Mode, + pub non_desktop_override: Option, + pub vrr: bool, + pub tearing: bool, + pub format: &'static Format, + pub color_space: BackendColorSpace, + pub transfer_function: BackendTransferFunction, +} diff --git a/src/backend/transaction.rs b/src/backend/transaction.rs new file mode 100644 index 00000000..e118af0c --- /dev/null +++ b/src/backend/transaction.rs @@ -0,0 +1,219 @@ +use { + crate::{ + backend::{ + BackendColorSpace, BackendConnectorState, BackendTransferFunction, Connector, + ConnectorKernelId, Mode, + }, + backends::metal::MetalError, + utils::{errorfmt::ErrorFmt, hash_map_ext::HashMapExt}, + video::drm::DrmError, + }, + ahash::AHashMap, + std::{ + any::{Any, TypeId}, + cell::{Cell, RefCell}, + collections::hash_map::Entry, + hash::{Hash, Hasher}, + rc::Rc, + }, + thiserror::Error, +}; + +pub trait BackendConnectorTransactionType: Hash + Eq + Any {} + +pub trait BackendConnectorTransactionTypeDyn: Any { + fn eq(&self, other: &dyn BackendConnectorTransactionTypeDyn) -> bool; + fn hash(&self, hasher: &mut dyn Hasher); +} + +impl BackendConnectorTransactionTypeDyn for T +where + T: BackendConnectorTransactionType, +{ + fn eq(&self, other: &dyn BackendConnectorTransactionTypeDyn) -> bool { + let Some(other) = (other as &dyn Any).downcast_ref::() else { + return false; + }; + self.eq(other) + } + + fn hash(&self, hasher: &mut dyn Hasher) { + struct BufHasher<'a> { + buf: Vec, + clear: Cell, + any: Cell, + hasher: RefCell<&'a mut dyn Hasher>, + } + impl Hasher for BufHasher<'_> { + fn finish(&self) -> u64 { + let hasher = &mut *self.hasher.borrow_mut(); + if self.any.take() { + self.clear.set(true); + hasher.write(&self.buf); + } + hasher.finish() + } + + fn write(&mut self, bytes: &[u8]) { + if self.clear.take() { + self.buf.clear(); + } + self.any.set(true); + self.buf.extend_from_slice(bytes); + } + } + let mut hasher = BufHasher { + buf: Default::default(), + clear: Cell::new(false), + any: Cell::new(false), + hasher: RefCell::new(hasher), + }; + TypeId::of::().hash(&mut hasher); + self.hash(&mut hasher) + } +} + +impl PartialEq for dyn BackendConnectorTransactionTypeDyn { + fn eq(&self, other: &Self) -> bool { + self.eq(other) + } +} + +impl Eq for dyn BackendConnectorTransactionTypeDyn {} + +impl Hash for dyn BackendConnectorTransactionTypeDyn { + fn hash(&self, state: &mut H) { + self.hash(state) + } +} + +#[derive(Debug, Error)] +pub enum BackendConnectorTransactionError { + #[error("The underlying DRM device of connector {} no longer exists", .0)] + MissingDrmDevice(ConnectorKernelId), + #[error("Connector {} does not support transactions", .0)] + TransactionsNotSupported(ConnectorKernelId), + #[error("Connector {} is not supported by this transaction", .0)] + UnsupportedConnectorType(ConnectorKernelId), + #[error("Connector {} cannot be modified because it is leased", .0)] + LeasedConnector(ConnectorKernelId), + #[error("Connector {} does not exist", .0)] + UnknownConnector(ConnectorKernelId), + #[error("Cannot initialize connector {} because no CRTC is available", .0)] + NoCrtcForConnector(ConnectorKernelId), + #[error("Cannot initialize connector {} because no primary plane is available", .0)] + NoPrimaryPlaneForConnector(ConnectorKernelId), + #[error("Connector {} does not support the requested mode {}", .0, .1)] + UnsupportedMode(ConnectorKernelId, Mode), + #[error("Connector {} does not support VRR", .0)] + NotVrrCapable(ConnectorKernelId), + #[error("Connector {} does not support tearing", .0)] + TearingNotSupported(ConnectorKernelId), + #[error("Connector {} does not support color space {:?}", .0, .1)] + ColorSpaceNotSupported(ConnectorKernelId, BackendColorSpace), + #[error("Connector {} does not support transfer function {:?}", .0, .1)] + TransferFunctionNotSupported(ConnectorKernelId, BackendTransferFunction), + #[error("Could not create an hdr metadata blob")] + CreateHdrMetadataBlob(#[source] DrmError), + #[error("Could not create a mode blob")] + CreateModeBlob(#[source] DrmError), + #[error("Could not allocate buffers for connector {}", .0)] + AllocateScanoutBuffers(ConnectorKernelId, #[source] Box), + #[error("Test commit failed")] + AtomicTestFailed(#[source] DrmError), + #[error("Commit failed")] + AtomicCommitFailed(#[source] DrmError), +} + +pub trait BackendConnectorTransaction { + fn add( + &mut self, + connector: &Rc, + change: BackendConnectorState, + ) -> Result<(), BackendConnectorTransactionError>; + + fn prepare( + self: Box, + ) -> Result, BackendConnectorTransactionError>; +} + +pub trait BackendPreparedConnectorTransaction { + fn apply( + self: Box, + ) -> Result, BackendConnectorTransactionError>; +} + +pub trait BackendAppliedConnectorTransaction { + fn commit(self: Box); + + fn rollback(self: Box) -> Result<(), BackendConnectorTransactionError>; +} + +#[derive(Default)] +pub struct ConnectorTransaction { + parts: + AHashMap, Box>, +} + +#[derive(Default)] +pub struct PreparedConnectorTransaction { + parts: Vec>, +} + +#[derive(Default)] +pub struct AppliedConnectorTransaction { + parts: Vec>, +} + +impl ConnectorTransaction { + pub fn add( + &mut self, + connector: &Rc, + change: BackendConnectorState, + ) -> Result<(), BackendConnectorTransactionError> { + let ty = connector.transaction_type(); + let tran = match self.parts.entry(ty) { + Entry::Occupied(v) => v.into_mut(), + Entry::Vacant(v) => v.insert(connector.create_transaction()?), + }; + tran.add(connector, change) + } + + pub fn prepare( + &mut self, + ) -> Result { + let mut new = vec![]; + for tran in self.parts.drain_values() { + new.push(tran.prepare()?); + } + Ok(PreparedConnectorTransaction { parts: new }) + } +} + +impl PreparedConnectorTransaction { + pub fn apply(self) -> Result { + let mut applied = AppliedConnectorTransaction::default(); + for tran in self.parts { + applied.parts.push(tran.apply()?); + } + Ok(applied) + } +} + +impl AppliedConnectorTransaction { + pub fn commit(mut self) { + for tran in self.parts.drain(..) { + tran.commit(); + } + } +} + +impl Drop for AppliedConnectorTransaction { + fn drop(&mut self) { + for tran in self.parts.drain(..).rev() { + if let Err(e) = tran.rollback() { + log::error!("Could not roll back transaction: {}", ErrorFmt(e)); + } + } + } +} diff --git a/src/backends/dummy.rs b/src/backends/dummy.rs index 4f3b40ad..5242bdc1 100644 --- a/src/backends/dummy.rs +++ b/src/backends/dummy.rs @@ -2,7 +2,7 @@ use { crate::{ async_engine::SpawnedFuture, backend::{ - Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, Mode, + Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, }, video::drm::ConnectorType, }, @@ -49,7 +49,7 @@ impl Connector for DummyOutput { None } - fn set_mode(&self, _mode: Mode) { - // nothing + fn effectively_locked(&self) -> bool { + true } } diff --git a/src/backends/metal.rs b/src/backends/metal.rs index f6954de8..ae9ce6e1 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -1,6 +1,7 @@ mod input; mod monitor; mod present; +mod transaction; mod video; use { @@ -9,7 +10,7 @@ use { backend::{ Backend, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceGroupId, InputDeviceId, InputEvent, KeyState, - TransformMatrix, + TransformMatrix, transaction::BackendConnectorTransactionError, }, backends::metal::video::{ MetalDrmDeviceData, MetalLeaseData, MetalRenderContext, PendingDrmDevice, @@ -47,10 +48,7 @@ use { smallmap::SmallMap, syncqueue::SyncQueue, }, - video::{ - drm::{DRM_MODE_ATOMIC_ALLOW_MODESET, DrmError}, - gbm::GbmError, - }, + video::{drm::DrmError, gbm::GbmError}, }, bstr::ByteSlice, std::{ @@ -87,12 +85,6 @@ pub enum MetalError { UpdateProperties(#[source] DrmError), #[error("Could not create a render context")] CreateRenderContex(#[source] GfxError), - #[error("Cannot initialize connector because no CRTC is available")] - NoCrtcForConnector, - #[error("Cannot initialize connector because no primary plane is available")] - NoPrimaryPlaneForConnector, - #[error("Cannot initialize connector because no mode is available")] - NoModeForConnector, #[error("Could not allocate scanout buffer")] ScanoutBuffer(#[source] GbmError), #[error("addfb2 failed")] @@ -104,7 +96,7 @@ pub enum MetalError { #[error("Could not import an image into the graphics API")] ImportImage(#[source] GfxError), #[error("Could not perform modeset")] - Modeset(#[source] DrmError), + Modeset(#[source] BackendConnectorTransactionError), #[error("Could not enable atomic modesetting")] AtomicModesetting(#[source] OsError), #[error("Could not inspect a plane")] @@ -137,6 +129,12 @@ pub enum MetalError { Clear(#[source] GfxError), #[error("The present configuration is out of date")] OutOfDate, + #[error("Could not add connector to transaction")] + AddToTransaction(#[source] BackendConnectorTransactionError), + #[error("Could not calculate DRM state")] + CalculateDrmState(#[source] BackendConnectorTransactionError), + #[error("Could not calculate DRM change set")] + CalculateDrmChange(#[source] BackendConnectorTransactionError), } pub struct MetalBackend { @@ -204,6 +202,7 @@ impl Backend for MetalBackend { dev.futures.clear(); for crtc in dev.dev.crtcs.values() { crtc.connector.take(); + crtc.pending_flip.take(); } dev.dev.handle_events.handle_events.take(); dev.dev.on_change.clear(); @@ -242,36 +241,6 @@ impl Backend for MetalBackend { }) } - fn set_idle(&self, idle: bool) { - let devices = self.device_holder.drm_devices.lock(); - for device in devices.values() { - let mut change = device.dev.master.change(); - for connector in device.connectors.lock().values() { - if let Some(crtc) = connector.crtc.get() - && idle == crtc.active.value.get() - { - crtc.active.value.set(!idle); - change.change_object(crtc.id, |c| { - c.change(crtc.active.id, !idle); - }); - } - } - if let Err(e) = change.commit(DRM_MODE_ATOMIC_ALLOW_MODESET, 0) { - log::error!("Could not set monitors idle/not idle: {}", ErrorFmt(e)); - return; - } - } - if idle { - self.state.set_backend_idle(true); - } else { - for device in devices.values() { - for connector in device.connectors.lock().values() { - connector.schedule_present(); - } - } - } - } - fn import_environment(&self) -> bool { true } diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index 8bebeb59..d4c42ac8 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -3,6 +3,7 @@ use { backend::Connector, backends::metal::{ MetalError, + transaction::{DrmConnectorState, DrmPlaneState}, video::{ MetalConnector, MetalCrtc, MetalHardwareCursorChange, MetalPlane, RenderBuffer, }, @@ -22,18 +23,20 @@ use { dmabuf::DmaBufId, drm::{ DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_ASYNC, DRM_MODE_PAGE_FLIP_EVENT, - DrmError, DrmFramebuffer, + DrmCrtc, DrmError, DrmFb, DrmFramebuffer, DrmObject, }, }, }, + arrayvec::ArrayVec, std::rc::{Rc, Weak}, - uapi::c, + uapi::{OwnedFd, c}, }; struct Latched { pass: GfxRenderPass, damage_count: u64, damage: Region, + locked: bool, } #[derive(Debug)] @@ -68,11 +71,18 @@ pub struct PresentFb { tex: Rc, direct_scanout_data: Option, sync_file: Option, + pub locked: bool, } -enum CursorProgramming { +#[derive(Debug)] +struct CursorProgramming { + plane: Rc, + ty: CursorProgrammingType, +} + +#[derive(Debug)] +enum CursorProgrammingType { Enable { - plane: Rc, fb: Rc, x: i32, y: i32, @@ -80,9 +90,12 @@ enum CursorProgramming { height: i32, swap: bool, }, - Disable { - plane: Rc, - }, + Disable, +} + +struct ChangedPlane { + plane: Rc, + state: DrmPlaneState, } pub const DEFAULT_PRE_COMMIT_MARGIN: u64 = 16_000_000; // 16ms @@ -101,13 +114,17 @@ impl MetalConnector { let mut max = 0; loop { self.present_trigger.triggered().await; - if !self.can_present.get() { + if !self.buffers_idle.get() || !self.crtc_idle.get() { continue; } + let Some(crtc) = self.crtc.get() else { + continue; + }; let Some(node) = self.state.root.outputs.get(&self.connector_id) else { continue; }; - let mut expected_sequence = self.sequence.get() + 1; + let version = self.version.get(); + let mut expected_sequence = crtc.sequence.get() + 1; let mut start = Time::now_unchecked(); let use_frame_scheduling = !self.try_async_flip(); if use_frame_scheduling { @@ -132,7 +149,11 @@ impl MetalConnector { }; node.before_latch(flip).await; } - if let Err(e) = self.present_once(&node).await { + if version != self.version.get() { + self.present_trigger.trigger(); + continue; + } + if let Err(e) = self.present_once(&node, &crtc).await { log::error!("Could not present: {}", ErrorFmt(e)); continue; } @@ -152,19 +173,19 @@ impl MetalConnector { } } - async fn present_once(&self, node: &Rc) -> Result<(), MetalError> { + async fn present_once( + self: &Rc, + node: &Rc, + crtc: &Rc, + ) -> Result<(), MetalError> { let version = self.version.get(); - if !self.can_present.get() { + if !self.buffers_idle.get() || !self.crtc_idle.get() { return Ok(()); } if !self.backend.check_render_context(&self.dev) { return Ok(()); } - let crtc = match self.crtc.get() { - Some(crtc) => crtc, - _ => return Ok(()), - }; - if !crtc.active.value.get() { + if !crtc.drm_state.borrow().active { return Ok(()); } let plane = match self.primary_plane.get() { @@ -175,7 +196,9 @@ impl MetalConnector { Some(b) => b, _ => return Ok(()), }; - let buffer = &buffers[self.next_buffer.get() % buffers.len()]; + let mut connector_drm_state = self.display.borrow().drm_state.clone(); + let next_buffer_idx = ((connector_drm_state.fb_idx + 1) % buffers.len() as u64) as usize; + let buffer = &buffers[next_buffer_idx]; let cd = node.global.color_description.get(); let linear_cd = node.global.linear_color_description.get(); @@ -183,8 +206,8 @@ impl MetalConnector { if self.has_damage.get() > 0 || self.cursor_damage.get() { node.schedule.commit_cursor(); } - self.latch_cursor(&node, &cd)?; - let cursor_programming = self.compute_cursor_programming(); + self.latch_cursor(&node, &connector_drm_state, &cd)?; + let cursor_programming = self.compute_cursor_programming(&connector_drm_state); let latched = self.latch(&node, buffer); node.latched(self.try_async_flip()); @@ -209,12 +232,15 @@ impl MetalConnector { ); } self.await_present_fb(present_fb.as_mut()).await; + let mut changed_planes = ArrayVec::new(); let mut res = self.program_connector( version, &crtc, &plane, cursor_programming.as_ref(), present_fb.as_ref(), + &mut changed_planes, + &mut connector_drm_state, ); if res.is_err() && let Some(dsd_id) = direct_scanout_id @@ -235,6 +261,8 @@ impl MetalConnector { &plane, cursor_programming.as_ref(), present_fb.as_ref(), + &mut changed_planes, + &mut connector_drm_state, ); if res.is_ok() { let mut cache = self.scanout_buffers.borrow_mut(); @@ -265,35 +293,30 @@ impl MetalConnector { } Err(e) } else { - macro_rules! apply_change { - ($prop:expr) => { - if let Some(v) = $prop.pending_value.take() { - $prop.value.set(v); - } - }; + crtc.pending_flip.set(Some(self.clone())); + self.crtc_idle.set(false); + self.color_description.set(cd); + self.display.borrow_mut().drm_state = connector_drm_state; + for plane in changed_planes { + *plane.plane.drm_state.borrow_mut() = plane.state; } - apply_change!(plane.src_w); - apply_change!(plane.src_h); - apply_change!(plane.crtc_x); - apply_change!(plane.crtc_y); - apply_change!(plane.crtc_w); - apply_change!(plane.crtc_h); if let Some(fb) = present_fb { self.presentation_is_zero_copy .set(fb.direct_scanout_data.is_some()); if fb.direct_scanout_data.is_none() { buffer.damage_queue.clear(); - self.next_buffer.fetch_add(1); } else { reset_damage(); } + buffer.locked.set(fb.locked); self.next_framebuffer.set(Some(fb)); } - if let Some(CursorProgramming::Enable { swap: true, .. }) = cursor_programming { + if let Some(programming) = cursor_programming + && let CursorProgrammingType::Enable { swap: true, .. } = &programming.ty + { self.cursor_swap_buffer.set(false); - self.cursor_front_buffer.fetch_add(1); } - self.can_present.set(false); + self.buffers_idle.set(false); if let Some(latched) = latched { self.has_damage.fetch_sub(latched.damage_count); } @@ -318,7 +341,7 @@ impl MetalConnector { } fn try_async_flip(&self) -> bool { - self.tearing_requested.get() && self.dev.supports_async_commit + self.display.borrow().persistent.state.borrow().tearing && self.dev.supports_async_commit } fn program_connector( @@ -328,19 +351,15 @@ impl MetalConnector { plane: &Rc, cursor: Option<&CursorProgramming>, new_fb: Option<&PresentFb>, + changed_planes: &mut ArrayVec, + connector_drm_state: &mut DrmConnectorState, ) -> Result<(), MetalError> { zone!("program_connector"); let mut changes = self.master.change(); let mut try_async_flip = self.try_async_flip(); - macro_rules! change { - ($c:expr, $prop:expr, $new:expr) => {{ - if $prop.value.get() != $new { - $c.change($prop.id, $new); - try_async_flip = false; - $prop.pending_value.set(Some($new)); - } - }}; - } + let mut drm_state = plane.drm_state.borrow().clone(); + changed_planes.clear(); + let mut connector_state = connector_drm_state.clone(); if let Some(fb) = new_fb { let (crtc_x, crtc_y, crtc_w, crtc_h, src_width, src_height) = match &fb.direct_scanout_data { @@ -363,68 +382,89 @@ impl MetalConnector { }; changes.change_object(plane.id, |c| { c.change(plane.fb_id, fb.fb.id()); - change!(c, plane.src_w, (src_width as u32) << 16); - change!(c, plane.src_h, (src_height as u32) << 16); - change!(c, plane.crtc_x, crtc_x); - change!(c, plane.crtc_y, crtc_y); - change!(c, plane.crtc_w, crtc_w); - change!(c, plane.crtc_h, crtc_h); - if !try_async_flip - && !self.dev.is_nvidia - && let Some(sf) = self.backend.signaled_sync_file.get() - { - c.change(plane.in_fence_fd, sf.0.raw() as u64); + drm_state.fb_id = fb.fb.id(); + connector_state.fb = fb.fb.id(); + connector_state.locked = fb.locked; + if fb.direct_scanout_data.is_none() { + connector_state.fb_idx += 1; } + macro_rules! change { + ($prop:ident, $new:expr) => {{ + if drm_state.$prop != $new { + c.change(plane.$prop, $new as u64); + try_async_flip = false; + drm_state.$prop = $new; + } + connector_state.$prop = drm_state.$prop; + }}; + } + change!(src_w, (src_width as u32) << 16); + change!(src_h, (src_height as u32) << 16); + change!(crtc_x, crtc_x); + change!(crtc_y, crtc_y); + change!(crtc_w, crtc_w); + change!(crtc_h, crtc_h); + }); + changed_planes.push(ChangedPlane { + plane: plane.clone(), + state: drm_state, }); - } else { - // Work around https://gitlab.freedesktop.org/drm/amd/-/issues/2186 - if self.dev.is_amd - && crtc.vrr_enabled.value.get() - && let Some(fb) = &*self.active_framebuffer.borrow() - { - changes.change_object(plane.id, |c| { - c.change(plane.fb_id, fb.fb.id()); - }); - } } if let Some(cursor) = cursor { + let plane = &cursor.plane; + let mut drm_state = plane.drm_state.borrow().clone(); try_async_flip = false; - match cursor { - CursorProgramming::Enable { - plane, - fb, - x, - y, - width, - height, - .. - } => { - changes.change_object(plane.id, |c| { - c.change(plane.fb_id, fb.id()); - c.change(plane.crtc_id.id, crtc.id); - c.change(plane.crtc_x.id, *x); - c.change(plane.crtc_y.id, *y); - c.change(plane.crtc_w.id, *width); - c.change(plane.crtc_h.id, *height); - c.change(plane.src_x.id, 0); - c.change(plane.src_y.id, 0); - c.change(plane.src_w.id, (*width as u64) << 16); - c.change(plane.src_h.id, (*height as u64) << 16); + changes.change_object(plane.id, |c| { + macro_rules! change { + ($prop:ident, $new:expr) => {{ + c.change(plane.$prop, $new); + drm_state.$prop = $new; + }}; + } + match &cursor.ty { + CursorProgrammingType::Enable { + fb, + x, + y, + width, + height, + swap, + } => { + connector_state.cursor_fb = fb.id(); + if *swap { + connector_state.cursor_fb_idx += 1; + } + connector_state.cursor_x = *x; + connector_state.cursor_y = *y; + change!(fb_id, fb.id()); + change!(crtc_id, crtc.id); + change!(crtc_x, *x); + change!(crtc_y, *y); + change!(crtc_w, *width); + change!(crtc_h, *height); + change!(src_x, 0); + change!(src_y, 0); + change!(src_w, (*width as u32) << 16); + change!(src_h, (*height as u32) << 16); if !self.dev.is_nvidia && let Some(sf) = self.backend.signaled_sync_file.get() { c.change(plane.in_fence_fd, sf.0.raw() as u64); } - }); + } + CursorProgrammingType::Disable => { + connector_state.cursor_fb = DrmFb::NONE; + change!(fb_id, DrmFb::NONE); + change!(crtc_id, DrmCrtc::NONE); + } } - CursorProgramming::Disable { plane } => { - changes.change_object(plane.id, |c| { - c.change(plane.fb_id, 0); - c.change(plane.crtc_id.id, 0); - }); - } - } + }); + changed_planes.push(ChangedPlane { + plane: plane.clone(), + state: drm_state, + }); } + let mut out_fd: c::c_int = -1; if version != self.version.get() { return Err(MetalError::OutOfDate); } @@ -439,14 +479,32 @@ impl MetalConnector { } } self.presentation_is_sync.set(true); + if !self.dev.is_nvidia { + if new_fb.is_some() + && let Some(sf) = self.backend.signaled_sync_file.get() + { + changes.change_object(plane.id, |c| { + c.change(plane.in_fence_fd, sf.0.raw() as u64); + }); + } + changes.change_object(crtc.id, |c| { + c.change(crtc.out_fence_ptr, &raw mut out_fd as u64); + }); + } res = changes.commit(FLAGS, 0); } + if res.is_ok() { + connector_state.out_fd = + (out_fd != -1).then(|| SyncFile(Rc::new(OwnedFd::new(out_fd)))); + *connector_drm_state = connector_state; + } res.map_err(MetalError::Commit) } fn latch_cursor( &self, node: &Rc, + connector_drm_state: &DrmConnectorState, cd: &Rc, ) -> Result<(), MetalError> { if !self.cursor_damage.take() { @@ -456,12 +514,13 @@ impl MetalConnector { return Ok(()); } let buffers = self.cursor_buffers.get().unwrap(); + let buffer_idx = ((connector_drm_state.cursor_fb_idx + 1) % buffers.len() as u64) as usize; let mut c = MetalHardwareCursorChange { cursor_enabled: self.cursor_enabled.get(), cursor_swap_buffer: false, cursor_x: self.cursor_x.get(), cursor_y: self.cursor_y.get(), - cursor_buffer: &buffers[(self.cursor_front_buffer.get() + 1) % buffers.len()], + cursor_buffer: &buffers[buffer_idx], sync_file: None, cursor_size: (self.dev.cursor_width as _, self.dev.cursor_height as _), }; @@ -484,22 +543,25 @@ impl MetalConnector { Ok(()) } - fn compute_cursor_programming(&self) -> Option { + fn compute_cursor_programming( + &self, + connector_drm_state: &DrmConnectorState, + ) -> Option { if !self.cursor_changed.get() { return None; } let plane = self.cursor_plane.get()?; - let programming = if self.cursor_enabled.get() { + let ty = if self.cursor_enabled.get() { let swap = self.cursor_swap_buffer.get(); - let mut front_buffer = self.cursor_front_buffer.get(); - if swap { - front_buffer = front_buffer.wrapping_add(1); - } let buffers = self.cursor_buffers.get().unwrap(); - let buffer = &buffers[front_buffer % buffers.len()]; + let mut front_buffer = connector_drm_state.cursor_fb_idx; + if swap { + front_buffer += 1; + } + let buffer_idx = (front_buffer % buffers.len() as u64) as usize; + let buffer = &buffers[buffer_idx]; let (width, height) = buffer.dev_fb.physical_size(); - CursorProgramming::Enable { - plane, + CursorProgrammingType::Enable { fb: buffer.drm.clone(), x: self.cursor_x.get(), y: self.cursor_y.get(), @@ -508,9 +570,9 @@ impl MetalConnector { swap, } } else { - CursorProgramming::Disable { plane } + CursorProgrammingType::Disable }; - Some(programming) + Some(CursorProgramming { plane, ty }) } fn latch(&self, node: &Rc, buffer: &RenderBuffer) -> Option { @@ -545,6 +607,7 @@ impl MetalConnector { pass, damage_count, damage, + locked: self.state.lock.locked.get(), }) } @@ -797,6 +860,7 @@ impl MetalConnector { tex, direct_scanout_data, sync_file, + locked: latched.locked, }) } diff --git a/src/backends/metal/transaction.rs b/src/backends/metal/transaction.rs new file mode 100644 index 00000000..40047b0a --- /dev/null +++ b/src/backends/metal/transaction.rs @@ -0,0 +1,1147 @@ +use { + crate::{ + allocator::BufferObject, + backend::{ + BackendColorSpace, BackendConnectorState, BackendTransferFunction, Connector, + ConnectorEvent, + transaction::{ + BackendAppliedConnectorTransaction, BackendConnectorTransaction, + BackendConnectorTransactionError, BackendPreparedConnectorTransaction, + }, + }, + backends::metal::video::{ + FrontState, MetalConnector, MetalCrtc, MetalDrmDeviceData, MetalPlane, PlaneType, + RenderBuffer, + }, + format::{ARGB8888, Format}, + gfx_api::{AcquireSync, ReleaseSync, SyncFile}, + utils::{ + binary_search_map::BinarySearchMap, cell_ext::CellExt, errorfmt::ErrorFmt, rc_eq::rc_eq, + }, + video::drm::{ + Change, ConnectorStatus, DRM_MODE_ATOMIC_ALLOW_MODESET, DrmBlob, DrmConnector, DrmCrtc, + DrmFb, DrmModeInfo, DrmObject, DrmPlane, PropBlob, hdr_output_metadata, + }, + }, + arrayvec::ArrayVec, + bstr::ByteSlice, + isnt::std_1::collections::IsntHashMap2Ext, + std::{any::Any, cell::Cell, mem, rc::Rc, slice}, + uapi::c, +}; + +const LEVEL: log::Level = log::Level::Debug; + +#[derive(Default, Clone, Debug)] +pub struct DrmPlaneState { + pub fb_id: DrmFb, + pub src_x: u32, + pub src_y: u32, + pub src_w: u32, + pub src_h: u32, + pub assigned_crtc: DrmCrtc, + pub crtc_id: DrmCrtc, + pub crtc_x: i32, + pub crtc_y: i32, + pub crtc_w: i32, + pub crtc_h: i32, + pub buffers: Option>, +} + +#[derive(Default, Clone)] +pub struct DrmCrtcState { + pub active: bool, + pub mode: Option, + pub mode_blob_id: DrmBlob, + pub mode_blob: Option>, + pub vrr_enabled: bool, + pub assigned_connector: DrmConnector, +} + +#[derive(Default, Clone, Debug)] +pub struct DrmConnectorState { + pub crtc_id: DrmCrtc, + pub color_space: Option, + pub hdr_metadata: Option, + pub hdr_metadata_blob_id: DrmBlob, + pub hdr_metadata_blob: Option>, + pub locked: bool, + pub fb: DrmFb, + pub fb_idx: u64, + pub cursor_fb: DrmFb, + pub cursor_fb_idx: u64, + pub cursor_x: i32, + pub cursor_y: i32, + pub out_fd: Option, + pub src_w: u32, + pub src_h: u32, + pub crtc_x: i32, + pub crtc_y: i32, + pub crtc_w: i32, + pub crtc_h: i32, +} + +struct PlaneConfig { + obj: Rc, + new: DrmPlaneState, + changed: ArrayVec>, 4>, +} + +struct CrtcConfig { + obj: Rc, + new: DrmCrtcState, + changed: ArrayVec>, 2>, +} + +struct ConnectorConfig { + obj: Rc, + new: DrmConnectorState, + state: BackendConnectorState, + requested: bool, + changed: Rc>, +} + +const SIZE: usize = 16; + +struct TransactionCommon { + dev: Rc, + planes: BinarySearchMap, + crtcs: BinarySearchMap, + connectors: BinarySearchMap, +} + +pub struct MetalDeviceTransaction { + common: TransactionCommon, + allow_direct_scanout: bool, +} + +pub struct MetalDeviceTransactionWithDrmState { + common: TransactionCommon, +} + +pub struct MetalDeviceTransactionWithChange { + common: TransactionCommon, + change: Change, +} + +pub struct MetalDeviceAppliedTransaction { + rollback: MetalDeviceTransactionWithDrmState, +} + +impl MetalConnector { + pub fn create_transaction( + &self, + ) -> Result { + let Some(dev) = self.backend.device_holder.drm_devices.get(&self.dev.devnum) else { + return Err(BackendConnectorTransactionError::MissingDrmDevice( + self.kernel_id(), + )); + }; + Ok(dev.create_transaction()) + } +} + +impl MetalDrmDeviceData { + pub fn create_transaction(self: &Rc) -> MetalDeviceTransaction { + let mut tran = MetalDeviceTransaction { + common: TransactionCommon { + dev: self.clone(), + planes: Default::default(), + crtcs: Default::default(), + connectors: Default::default(), + }, + allow_direct_scanout: true, + }; + for plane in self.dev.planes.values() { + if plane.lease.is_some() { + continue; + } + tran.common.planes.insert( + plane.id, + PlaneConfig { + obj: plane.clone(), + new: plane.drm_state.borrow().clone(), + changed: Default::default(), + }, + ); + } + for crtc in self.dev.crtcs.values() { + if crtc.lease.is_some() { + continue; + } + tran.common.crtcs.insert( + crtc.id, + CrtcConfig { + obj: crtc.clone(), + new: crtc.drm_state.borrow().clone(), + changed: Default::default(), + }, + ); + } + for connector in self.connectors.lock().values() { + if connector.lease.is_some() { + continue; + } + let dd = &*connector.display.borrow(); + tran.common.connectors.insert( + connector.id, + ConnectorConfig { + obj: connector.clone(), + new: dd.drm_state.clone(), + state: *dd.persistent.state.borrow(), + requested: false, + changed: Default::default(), + }, + ); + } + tran + } +} + +const CURSOR_FORMAT: &Format = ARGB8888; + +#[derive(Default, Debug)] +struct CrtcPlanes { + primary: DrmPlane, + cursor: DrmPlane, +} + +impl MetalDeviceTransaction { + pub fn add( + &mut self, + connector: &Rc, + state: BackendConnectorState, + ) -> Result<(), BackendConnectorTransactionError> { + let Some(config) = self.common.connectors.get_mut(&connector.id) else { + if self.common.dev.connectors.contains(&connector.id) { + return Err(BackendConnectorTransactionError::LeasedConnector( + connector.kernel_id(), + )); + } + return Err(BackendConnectorTransactionError::UnknownConnector( + connector.kernel_id(), + )); + }; + config.state = state; + config.requested = true; + Ok(()) + } + + pub fn disable_direct_scanout(&mut self) { + self.allow_direct_scanout = false; + } + + pub fn calculate_drm_state( + mut self, + ) -> Result { + let mut unused_crtcs = BinarySearchMap::<_, _, SIZE>::new(); + let mut unused_planes = BinarySearchMap::<_, _, SIZE>::new(); + let mut crtc_planes = BinarySearchMap::<_, _, SIZE>::new(); + let mut sync_files = vec![]; + let slf = &mut self.common; + for (_, crtc) in &mut slf.crtcs { + crtc_planes.insert(crtc.obj.id, CrtcPlanes::default()); + unused_crtcs.insert(crtc.obj.id, ()); + } + for (_, connector) in &slf.connectors { + unused_crtcs.remove(&connector.new.crtc_id); + if let Some(crtc) = slf.crtcs.get_mut(&connector.new.crtc_id) + && crtc.changed.is_empty() + { + crtc.changed.push(connector.changed.clone()); + } + } + for (_, plane) in &mut slf.planes { + if let Some(crtc) = slf.crtcs.get_mut(&plane.new.assigned_crtc) { + plane.changed.extend(crtc.changed.iter().cloned()); + } + if plane.new.crtc_id.is_some() { + plane.new.assigned_crtc = plane.new.crtc_id; + } + macro_rules! discard_plane { + () => { + unused_planes.insert(plane.obj.id, ()); + plane.new.crtc_id = DrmCrtc::NONE; + plane.new.assigned_crtc = DrmCrtc::NONE; + }; + } + if plane.new.assigned_crtc.is_none() { + discard_plane!(); + continue; + } + if unused_crtcs.contains(&plane.new.assigned_crtc) { + discard_plane!(); + continue; + } + let Some(crtc_planes) = crtc_planes.get_mut(&plane.new.assigned_crtc) else { + discard_plane!(); + continue; + }; + let field = match plane.obj.ty { + PlaneType::Overlay => { + discard_plane!(); + continue; + } + PlaneType::Primary => &mut crtc_planes.primary, + PlaneType::Cursor => &mut crtc_planes.cursor, + }; + if field.is_some() { + discard_plane!(); + continue; + } + *field = plane.obj.id; + } + let render_ctx = slf.dev.dev.backend.ctx.get(); + let dev_ctx = slf.dev.dev.ctx.get(); + for connector in slf.connectors.values_mut() { + let state = &connector.state; + let dd = &*connector.obj.display.borrow(); + if !state.enabled + || dd.connection != ConnectorStatus::Connected + || state.non_desktop_override.unwrap_or(dd.non_desktop) + { + if connector.new.crtc_id.is_some() { + unused_crtcs.insert(connector.new.crtc_id, ()); + if let Some(crtc) = slf.crtcs.get(&connector.new.crtc_id) { + let planes = crtc_planes.get_mut(&crtc.obj.id).unwrap(); + for plane in [&mut planes.primary, &mut planes.cursor] { + if plane.is_some() { + unused_planes.insert(*plane, ()); + let plane = slf.planes.get_mut(plane).unwrap(); + plane.new.crtc_id = DrmCrtc::NONE; + plane.new.assigned_crtc = DrmCrtc::NONE; + } + } + *planes = CrtcPlanes::default(); + } + } + connector.new = DrmConnectorState::default(); + continue; + } + if connector.new.crtc_id.is_none() { + let crtc_id = 'crtc_id: { + for (crtc, _) in &dd.crtcs { + if unused_crtcs.contains(crtc) { + break 'crtc_id crtc; + } + } + return Err(BackendConnectorTransactionError::NoCrtcForConnector( + connector.obj.kernel_id(), + )); + }; + unused_crtcs.remove(crtc_id); + connector.new.crtc_id = *crtc_id; + } + let crtc = slf.crtcs.get_mut(&connector.new.crtc_id).unwrap(); + crtc.new.active = state.active; + crtc.new.assigned_connector = connector.obj.id; + crtc.changed.push(connector.changed.clone()); + let crtc_planes = crtc_planes.get_mut(&crtc.obj.id).unwrap(); + let plane_not_supports_format = |plane: &MetalPlane| { + let format = match plane.ty { + PlaneType::Overlay => unreachable!(), + PlaneType::Primary => state.format, + PlaneType::Cursor => CURSOR_FORMAT, + }; + plane.formats.not_contains_key(&format.drm) + }; + for plane in [&mut crtc_planes.primary, &mut crtc_planes.cursor] { + macro_rules! discard_plane { + () => { + unused_planes.insert(*plane, ()); + *plane = DrmPlane::NONE; + }; + } + if plane.is_none() { + discard_plane!(); + continue; + } + let plane = slf.planes.get(plane).unwrap(); + if plane_not_supports_format(&plane.obj) { + discard_plane!(); + continue; + } + } + for (primary, plane) in [ + (true, &mut crtc_planes.primary), + (false, &mut crtc_planes.cursor), + ] { + if plane.is_some() { + continue; + } + let ty = match primary { + true => PlaneType::Primary, + false => PlaneType::Cursor, + }; + for (_, p) in &crtc.obj.possible_planes { + if p.ty != ty { + continue; + } + if unused_planes.not_contains(&p.id) { + continue; + } + if plane_not_supports_format(p) { + continue; + } + *plane = p.id; + unused_planes.remove(&p.id); + } + } + if crtc_planes.primary.is_none() { + return Err( + BackendConnectorTransactionError::NoPrimaryPlaneForConnector( + connector.obj.kernel_id(), + ), + ); + } + let mode = 'mode: { + let Some(mode) = dd.modes.iter().find(|m| m.to_backend() == state.mode) else { + return Err(BackendConnectorTransactionError::UnsupportedMode( + connector.obj.kernel_id(), + state.mode, + )); + }; + if let Some(old) = &crtc.new.mode + && modes_equal(old, mode) + { + break 'mode mode.clone(); + } + crtc.new.mode = Some(mode.clone()); + let blob = slf + .dev + .dev + .master + .create_blob(&mode.to_raw()) + .map_err(BackendConnectorTransactionError::CreateModeBlob)?; + crtc.new.mode_blob_id = blob.id(); + crtc.new.mode_blob = Some(Rc::new(blob)); + mode.clone() + }; + for plane in [crtc_planes.primary, crtc_planes.cursor] { + if plane.is_none() { + continue; + } + let plane = slf.planes.get_mut(&plane).unwrap(); + plane.new.assigned_crtc = crtc.obj.id; + plane.changed.extend(crtc.changed.iter().cloned()); + let (x, y, width, height, format, old_buffers); + match plane.obj.ty { + PlaneType::Overlay => unreachable!(), + PlaneType::Primary => { + (x, y) = (0, 0); + width = mode.hdisplay as i32; + height = mode.vdisplay as i32; + format = state.format; + old_buffers = connector.obj.buffers.get(); + } + PlaneType::Cursor => { + x = connector.new.cursor_x; + y = connector.new.cursor_y; + width = connector.obj.dev.cursor_width as i32; + height = connector.obj.dev.cursor_height as i32; + format = CURSOR_FORMAT; + old_buffers = connector.obj.cursor_buffers.get(); + } + }; + plane.new.buffers = old_buffers.clone(); + plane.new.src_x = 0; + plane.new.src_y = 0; + plane.new.src_w = (width as u32) << 16; + plane.new.src_h = (height as u32) << 16; + plane.new.crtc_x = x; + plane.new.crtc_y = y; + plane.new.crtc_w = width; + plane.new.crtc_h = height; + if let Some(b) = &plane.new.buffers { + 'discard: { + macro_rules! discard { + () => { + plane.new.buffers = None; + break 'discard; + }; + } + if b[0].width != width || b[0].height != height || b[0].format != format { + discard!(); + } + let Some(render_ctx) = &render_ctx else { + discard!(); + }; + if !rc_eq(render_ctx, &b[0].render_ctx) { + discard!(); + } + if !rc_eq(&dev_ctx, &b[0].dev_ctx) { + discard!(); + } + let modifiers = &plane.obj.formats.get(&format.drm).unwrap().modifiers; + for b in &**b { + if !modifiers.contains(&b.dev_bo.dmabuf().modifier) { + discard!(); + } + } + } + } + let mut new_buffers = None; + let current_buffers = match &plane.new.buffers { + Some(b) => b.clone(), + None => { + let modifiers = &plane.obj.formats.get(&format.drm).unwrap().modifiers; + let buffers = slf + .dev + .dev + .backend + .create_scanout_buffers( + &slf.dev.dev, + format, + modifiers, + width, + height, + &slf.dev.dev.ctx.get(), + plane.obj.ty == PlaneType::Cursor, + ) + .map_err(|e| { + BackendConnectorTransactionError::AllocateScanoutBuffers( + connector.obj.kernel_id(), + Box::new(e), + ) + })?; + let buffers = Rc::new(buffers); + plane.new.buffers = Some(buffers.clone()); + new_buffers = Some(buffers.clone()); + buffers + } + }; + let (fb_id, fb_idx) = match plane.obj.ty { + PlaneType::Overlay => unreachable!(), + PlaneType::Primary => (connector.new.fb, &mut connector.new.fb_idx), + PlaneType::Cursor => { + (connector.new.cursor_fb, &mut connector.new.cursor_fb_idx) + } + }; + plane.new.crtc_id = DrmCrtc::NONE; + plane.new.fb_id = DrmFb::NONE; + if plane.obj.ty == PlaneType::Primary || fb_id.is_some() { + plane.new.crtc_id = crtc.obj.id; + let locked = slf.dev.dev.backend.state.lock.locked.get(); + let may_show_current_fb = !crtc.new.active + || connector.new.locked + || !locked + || plane.obj.ty != PlaneType::Primary; + if plane.obj.ty == PlaneType::Primary + && connector.obj.direct_scanout_active.get() + && self.allow_direct_scanout + && may_show_current_fb + { + plane.new.fb_id = fb_id; + macro_rules! copy { + ($field:ident) => { + plane.new.$field = connector.new.$field; + }; + } + copy!(src_w); + copy!(src_h); + copy!(crtc_x); + copy!(crtc_y); + copy!(crtc_w); + copy!(crtc_h); + } else if current_buffers.iter().any(|b| b.drm.id() == fb_id) + && may_show_current_fb + { + plane.new.fb_id = fb_id; + } else if let Some(new_buffers) = &new_buffers { + let new_buffer = &new_buffers[0]; + plane.new.fb_id = new_buffer.drm.id(); + *fb_idx = 0; + let cd = connector.obj.color_description.get(); + let res = if let Some(prev) = &old_buffers + && let Some(prev) = prev.iter().find(|b| b.drm.id() == fb_id) + && rc_eq(&new_buffer.dev_ctx, &prev.dev_ctx) + && may_show_current_fb + { + let src = prev.dev_tex.as_ref().unwrap_or(&prev.render_tex); + let dst = &new_buffer.dev_fb; + dst.copy_texture( + AcquireSync::Unnecessary, + ReleaseSync::Explicit, + &cd, + src, + &cd, + None, + AcquireSync::Unnecessary, + ReleaseSync::Explicit, + 0, + 0, + ) + } else { + new_buffer.dev_fb.clear( + AcquireSync::Unnecessary, + ReleaseSync::Explicit, + &cd, + ) + }; + match res { + Ok(sf) => sync_files.extend(sf), + Err(e) => { + log::warn!("Could not copy from old buffer: {}", ErrorFmt(e)); + } + } + } else { + if may_show_current_fb { + let idx = *fb_idx % current_buffers.len() as u64; + plane.new.fb_id = current_buffers[idx as usize].drm.id(); + } else { + let idx = (*fb_idx + 1) % current_buffers.len() as u64; + *fb_idx = idx; + let buffer = ¤t_buffers[idx as usize]; + plane.new.fb_id = buffer.drm.id(); + if !buffer.locked.get() { + if !connector.obj.buffers_idle.get() + && let Some(fd) = &connector.new.out_fd + { + log::log!(LEVEL, "waiting for CRTC sync file before blanking"); + let mut pollfd = c::pollfd { + fd: fd.raw(), + events: c::POLLIN, + revents: 0, + }; + let res = uapi::poll(slice::from_mut(&mut pollfd), -1); + if let Err(e) = res { + log::warn!( + "Could not wait for CRTC sync file to become readable: {}", + ErrorFmt(e), + ); + } + } + buffer.damage_full(); + let cd = connector.obj.color_description.get(); + let res = buffer.dev_fb.clear( + AcquireSync::Unnecessary, + ReleaseSync::Explicit, + &cd, + ); + match res { + Ok(sf) => { + buffer.locked.set(true); + sync_files.extend(sf); + } + Err(e) => { + log::error!( + "Could not black out old buffer: {}", + ErrorFmt(e), + ); + } + } + } + } + } + } + if plane.obj.ty == PlaneType::Primary { + macro_rules! copy { + ($field:ident) => { + connector.new.$field = plane.new.$field; + }; + } + copy!(src_w); + copy!(src_h); + copy!(crtc_x); + copy!(crtc_y); + copy!(crtc_w); + copy!(crtc_h); + } + } + if state.vrr && !dd.vrr_capable { + return Err(BackendConnectorTransactionError::NotVrrCapable( + connector.obj.kernel_id(), + )); + } + crtc.new.vrr_enabled = state.vrr; + if state.tearing && !slf.dev.dev.supports_async_commit { + return Err(BackendConnectorTransactionError::TearingNotSupported( + connector.obj.kernel_id(), + )); + } + match state.color_space { + BackendColorSpace::Default => {} + BackendColorSpace::Bt2020 => { + if !dd.supports_bt2020 { + return Err(BackendConnectorTransactionError::ColorSpaceNotSupported( + connector.obj.kernel_id(), + state.color_space, + )); + } + } + } + match state.transfer_function { + BackendTransferFunction::Default => {} + BackendTransferFunction::Pq => { + if !dd.supports_pq { + return Err( + BackendConnectorTransactionError::TransferFunctionNotSupported( + connector.obj.kernel_id(), + state.transfer_function, + ), + ); + } + } + } + if let Some(cs) = &mut connector.new.color_space { + *cs = state.color_space.to_drm(); + } + if dd.hdr_metadata.is_some() { + let new = if state.transfer_function == BackendTransferFunction::Default { + None + } else { + Some(hdr_output_metadata::from_eotf( + state.transfer_function.to_drm(), + )) + }; + if connector.new.hdr_metadata != new { + if let Some(new) = &new { + let blob = slf + .dev + .dev + .master + .create_blob(new) + .map_err(BackendConnectorTransactionError::CreateHdrMetadataBlob)?; + connector.new.hdr_metadata_blob_id = blob.id(); + connector.new.hdr_metadata_blob = Some(Rc::new(blob)); + } else { + connector.new.hdr_metadata_blob_id = DrmBlob::NONE; + connector.new.hdr_metadata_blob = None; + } + connector.new.hdr_metadata = new; + } else if new.is_none() { + connector.new.hdr_metadata_blob_id = DrmBlob::NONE; + connector.new.hdr_metadata_blob = None; + } + } + } + for (crtc, _) in &unused_crtcs { + if let Some(crtc) = slf.crtcs.get_mut(crtc) { + crtc.new = DrmCrtcState::default(); + } + } + for (plane, _) in &unused_planes { + if let Some(plane) = slf.planes.get_mut(plane) { + plane.new = DrmPlaneState::default(); + } + } + for sf in sync_files { + let mut pollfd = c::pollfd { + fd: sf.0.raw(), + events: c::POLLIN, + revents: 0, + }; + let res = uapi::poll(slice::from_mut(&mut pollfd), -1); + if let Err(e) = res { + log::warn!( + "Could not wait for sync file to become readable: {}", + ErrorFmt(e) + ); + } + } + Ok(MetalDeviceTransactionWithDrmState { + common: self.common, + }) + } +} + +macro_rules! log_change { + ($o:expr, $n:expr, $field:ident) => { + log::log!( + LEVEL, + "changed {}: {:?} -> {:?}", + stringify!($field), + $o.$field, + $n.$field + ); + }; +} + +impl MetalDeviceTransactionWithDrmState { + pub fn calculate_change( + mut self, + test: bool, + reset_default_properties: bool, + ) -> Result { + macro_rules! reset_default_properties { + ($c:expr, $props:expr, $defaults:expr $(,)?) => {{ + if reset_default_properties { + let props = $props; + for dp in $defaults { + let old = props.get(&dp.prop).copied().unwrap_or_default(); + let new = dp.value; + if old != new { + log::log!(LEVEL, "changed {}: {old} -> {new}", dp.name); + $c.change(dp.prop, new); + } + } + } + }}; + } + + let slf = &mut self.common; + let mut c = slf.dev.dev.master.change(); + for (_, connector) in &mut slf.connectors { + let dd = &*connector.obj.display.borrow(); + let n = &mut connector.new; + let o = &dd.drm_state; + let changed = c.change_object(connector.obj.id, |c| { + if n.crtc_id != o.crtc_id { + log_change!(o, n, crtc_id); + c.change(dd.crtc_id, n.crtc_id); + } + if let Some(prop) = &dd.colorspace + && let Some(new_cs) = n.color_space + && let Some(old_cs) = o.color_space + && new_cs != old_cs + { + log_change!(o, n, color_space); + c.change(*prop, new_cs); + } + if let Some(prop) = &dd.hdr_metadata + && n.hdr_metadata_blob_id != o.hdr_metadata_blob_id + { + log_change!(o, n, hdr_metadata_blob_id); + c.change(*prop, n.hdr_metadata_blob_id); + } + reset_default_properties!(c, &dd.untyped_properties, &dd.default_properties); + }); + if changed { + connector.changed.set(true); + } + log::log!( + LEVEL, + "connector {:?} (crtc {:?}) {}changed", + connector.obj.id, + connector.new.crtc_id, + if changed { "" } else { "un" }, + ); + } + for (_, crtc) in &mut slf.crtcs { + let n = &mut crtc.new; + let o = &*crtc.obj.drm_state.borrow(); + let changed = c.change_object(crtc.obj.id, |c| { + if n.active != o.active { + log_change!(o, n, active); + c.change(crtc.obj.active, n.active); + } + if n.vrr_enabled != o.vrr_enabled { + log_change!(o, n, vrr_enabled); + c.change(crtc.obj.vrr_enabled, n.vrr_enabled); + } + if n.mode_blob_id != o.mode_blob_id { + log_change!(o, n, mode_blob_id); + c.change(crtc.obj.mode_id, n.mode_blob_id); + } + reset_default_properties!( + c, + &*crtc.obj.untyped_properties.borrow(), + &crtc.obj.default_properties, + ); + }); + if changed { + log::log!(LEVEL, "crtc {:?} changed", crtc.obj.id); + crtc.changed.iter().for_each(|c| c.set(true)); + } + } + for (_, plane) in &mut slf.planes { + let n = &mut plane.new; + let o = &*plane.obj.drm_state.borrow(); + let changed = c.change_object(plane.obj.id, |c| { + if n.fb_id != o.fb_id { + log_change!(o, n, fb_id); + c.change(plane.obj.fb_id, n.fb_id); + c.change(plane.obj.in_fence_fd, -1i32); + } + if n.crtc_id != o.crtc_id { + log_change!(o, n, crtc_id); + c.change(plane.obj.crtc_id, n.crtc_id); + } + macro_rules! change { + ($field:ident) => { + if n.$field != o.$field { + log_change!(o, n, $field); + c.change(plane.obj.$field, n.$field); + } + }; + } + change!(src_x); + change!(src_y); + change!(src_w); + change!(src_h); + change!(crtc_x); + change!(crtc_y); + change!(crtc_w); + change!(crtc_h); + reset_default_properties!( + c, + &*plane.obj.untyped_properties.borrow(), + &plane.obj.default_properties, + ); + }); + if changed { + plane.changed.iter().for_each(|c| c.set(true)); + } + log::log!( + LEVEL, + "plane {:?} (crtc {:?}) (ty {:?}) {}changed", + plane.obj.id, + plane.new.crtc_id, + plane.obj.ty, + if changed { "" } else { "un" }, + ); + } + log::log!( + LEVEL, + "device {} {}changed", + self.common.dev.dev.devnode.to_bytes().as_bstr(), + if c.is_not_empty() { "" } else { "un" }, + ); + if test { + c.test(DRM_MODE_ATOMIC_ALLOW_MODESET) + .map_err(BackendConnectorTransactionError::AtomicTestFailed)?; + } + Ok(MetalDeviceTransactionWithChange { + common: self.common, + change: c, + }) + } +} + +impl MetalDeviceTransactionWithChange { + pub fn apply( + mut self, + ) -> Result { + let c = &self.change; + if c.is_not_empty() + && let Err(e) = c.commit(0, 0) + { + log::log!( + LEVEL, + "Transaction of device {} could not be applied without modeset: {}", + self.common.dev.dev.devnode.to_bytes().as_bstr(), + ErrorFmt(e), + ); + log::log!(LEVEL, "Performing modeset"); + c.commit(DRM_MODE_ATOMIC_ALLOW_MODESET, 0) + .map_err(BackendConnectorTransactionError::AtomicCommitFailed)?; + } + let slf = &mut self.common; + let mut crtc_planes = BinarySearchMap::<_, _, SIZE>::new(); + for (_, crtc) in &mut slf.crtcs { + crtc.obj.connector.set(None); + if crtc.new.assigned_connector.is_some() { + let connector = slf + .dev + .connectors + .get(&crtc.new.assigned_connector) + .unwrap(); + crtc.obj.connector.set(Some(connector)); + crtc_planes.insert(crtc.obj.id, CrtcPlanes::default()); + } + } + for (_, plane) in &mut slf.planes { + if plane.new.assigned_crtc.is_some() { + let crtc = slf.crtcs.get(&plane.new.assigned_crtc).unwrap(); + let mode = crtc.new.mode.as_ref().unwrap(); + plane.obj.mode_w.set(mode.hdisplay as _); + plane.obj.mode_h.set(mode.vdisplay as _); + let planes = crtc_planes.get_mut(&plane.new.assigned_crtc).unwrap(); + match plane.obj.ty { + PlaneType::Overlay => unreachable!(), + PlaneType::Primary => planes.primary = plane.obj.id, + PlaneType::Cursor => planes.cursor = plane.obj.id, + } + } + } + for (_, connector) in &mut slf.connectors { + if !connector.changed.get() { + continue; + } + connector.obj.version.fetch_add(1); + if connector.new.crtc_id.is_none() { + connector.obj.crtc.set(None); + connector.obj.primary_plane.set(None); + connector.obj.cursor_plane.set(None); + connector.obj.buffers.set(None); + connector.obj.cursor_buffers.set(None); + } else { + let crtc = slf.crtcs.get(&connector.new.crtc_id).unwrap(); + crtc.obj.connector.set(Some(connector.obj.clone())); + connector.obj.crtc.set(Some(crtc.obj.clone())); + connector.obj.crtc_idle.set(crtc.obj.pending_flip.is_none()); + let planes = crtc_planes.get(&crtc.obj.id).unwrap(); + for (primary, plane) in [(true, planes.primary), (false, planes.cursor)] { + if plane.is_none() { + match primary { + true => { + connector.obj.primary_plane.set(None); + connector.obj.buffers.set(None); + connector.new.fb = DrmFb::NONE; + } + false => { + connector.obj.cursor_plane.set(None); + connector.obj.cursor_buffers.set(None); + connector.new.cursor_fb = DrmFb::NONE; + } + } + continue; + } + let plane = slf.planes.get(&plane).unwrap(); + match plane.obj.ty { + PlaneType::Overlay => unreachable!(), + PlaneType::Primary => { + connector.obj.primary_plane.set(Some(plane.obj.clone())); + connector.obj.buffers.set(plane.new.buffers.clone()); + connector.new.fb = plane.new.fb_id; + } + PlaneType::Cursor => { + connector.obj.cursor_plane.set(Some(plane.obj.clone())); + connector.obj.cursor_buffers.set(plane.new.buffers.clone()); + connector.new.cursor_fb = plane.new.fb_id; + } + } + } + } + } + for (_, crtc) in &mut slf.crtcs { + let o = &mut *crtc.obj.drm_state.borrow_mut(); + mem::swap(o, &mut crtc.new); + } + for (_, plane) in &mut slf.planes { + let o = &mut *plane.obj.drm_state.borrow_mut(); + mem::swap(o, &mut plane.new); + } + for (_, connector) in &mut slf.connectors { + let is_enabled; + let is_connected; + let is_non_desktop; + { + let dd = &mut *connector.obj.display.borrow_mut(); + mem::swap(&mut dd.drm_state, &mut connector.new); + mem::swap(&mut *dd.persistent.state.borrow_mut(), &mut connector.state); + dd.update_cached_fields(&slf.dev.dev); + is_enabled = dd.persistent.state.borrow().enabled; + is_non_desktop = dd.non_desktop_effective; + is_connected = dd.connection == ConnectorStatus::Connected; + } + if connector.obj.crtc.is_some() { + if connector.changed.get() { + if let Some(buffers) = connector.obj.buffers.get() { + buffers[0].damage_full(); + } + connector.obj.has_damage.fetch_add(1); + connector.obj.cursor_damage.set(true); + if connector.obj.buffers_idle.get() && connector.obj.crtc_idle.get() { + connector.obj.schedule_present(); + } + } + match connector.obj.frontend_state.get() { + FrontState::Removed | FrontState::Unavailable => {} + FrontState::Disconnected => connector.obj.send_connected(), + FrontState::Connected { non_desktop: false } => { + if connector.changed.get() || connector.requested { + connector.obj.send_hardware_cursor(); + connector.obj.send_formats(); + connector.obj.update_drm_feedback(); + connector.obj.send_state(); + } + } + FrontState::Connected { non_desktop: true } => { + connector.obj.send_event(ConnectorEvent::Disconnected); + connector.obj.send_connected(); + } + } + } else if is_enabled && is_connected && is_non_desktop { + match connector.obj.frontend_state.get() { + FrontState::Removed + | FrontState::Unavailable + | FrontState::Connected { non_desktop: true } => {} + FrontState::Disconnected => connector.obj.send_connected(), + FrontState::Connected { non_desktop: false } => { + connector.obj.send_event(ConnectorEvent::Disconnected); + connector.obj.send_connected(); + } + } + } else { + match connector.obj.frontend_state.get() { + FrontState::Removed | FrontState::Unavailable | FrontState::Disconnected => {} + FrontState::Connected { .. } => { + connector.obj.send_event(ConnectorEvent::Disconnected); + } + } + } + } + Ok(MetalDeviceAppliedTransaction { + rollback: MetalDeviceTransactionWithDrmState { + common: self.common, + }, + }) + } +} + +fn modes_equal(a: &DrmModeInfo, b: &DrmModeInfo) -> bool { + a.clock == b.clock + && a.hdisplay == b.hdisplay + && a.hsync_start == b.hsync_start + && a.hsync_end == b.hsync_end + && a.htotal == b.htotal + && a.hskew == b.hskew + && a.vdisplay == b.vdisplay + && a.vsync_start == b.vsync_start + && a.vsync_end == b.vsync_end + && a.vtotal == b.vtotal + && a.vscan == b.vscan + && a.vrefresh == b.vrefresh + && a.flags == b.flags +} + +impl MetalDeviceAppliedTransaction { + pub fn rollback(self) -> Result<(), BackendConnectorTransactionError> { + self.rollback.calculate_change(false, false)?.apply()?; + Ok(()) + } +} + +impl BackendConnectorTransaction for MetalDeviceTransaction { + fn add( + &mut self, + connector: &Rc, + change: BackendConnectorState, + ) -> Result<(), BackendConnectorTransactionError> { + let Ok(connector) = (connector.clone() as Rc).downcast::() else { + return Err(BackendConnectorTransactionError::UnsupportedConnectorType( + connector.kernel_id(), + )); + }; + self.add(&connector, change)?; + Ok(()) + } + + fn prepare( + self: Box, + ) -> Result, BackendConnectorTransactionError> + { + self.calculate_drm_state()? + .calculate_change(true, false) + .map(|v| Box::new(v) as _) + } +} + +impl BackendPreparedConnectorTransaction for MetalDeviceTransactionWithChange { + fn apply( + self: Box, + ) -> Result, BackendConnectorTransactionError> { + (*self).apply().map(|v| Box::new(v) as _) + } +} + +impl BackendAppliedConnectorTransaction for MetalDeviceAppliedTransaction { + fn commit(self: Box) { + // nothing + } + + fn rollback(self: Box) -> Result<(), BackendConnectorTransactionError> { + (*self).rollback() + } +} diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 5b6e4fe4..62963ab9 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -3,10 +3,14 @@ use { allocator::BufferObject, async_engine::{Phase, SpawnedFuture}, backend::{ - BackendColorSpace, BackendDrmDevice, BackendDrmLease, BackendDrmLessee, BackendEvent, - BackendLuminance, BackendTransferFunction, Connector, ConnectorEvent, ConnectorId, - ConnectorKernelId, DrmDeviceId, HardwareCursor, HardwareCursorUpdate, Mode, - MonitorInfo, + BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLease, + BackendDrmLessee, BackendEvent, BackendLuminance, BackendTransferFunction, Connector, + ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor, + HardwareCursorUpdate, Mode, MonitorInfo, + transaction::{ + BackendConnectorTransaction, BackendConnectorTransactionError, + BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn, + }, }, backends::metal::{ MetalBackend, MetalError, @@ -14,11 +18,12 @@ use { DEFAULT_POST_COMMIT_MARGIN, DEFAULT_PRE_COMMIT_MARGIN, DirectScanoutCache, POST_COMMIT_MARGIN_DELTA, PresentFb, }, + transaction::{DrmConnectorState, DrmCrtcState, DrmPlaneState, MetalDeviceTransaction}, }, cmm::{cmm_description::ColorDescription, cmm_primaries::Primaries}, drm_feedback::DrmFeedback, edid::{CtaDataBlock, Descriptor, EdidExtension}, - format::{ARGB8888, Format, XRGB8888}, + format::{Format, XRGB8888}, gfx_api::{ AcquireSync, GfxBlendBuffer, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, SyncFile, needs_render_usage, @@ -32,20 +37,20 @@ use { tree::OutputNode, udev::UdevDevice, utils::{ - asyncevent::AsyncEvent, bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell, - copyhashmap::CopyHashMap, errorfmt::ErrorFmt, geometric_decay::GeometricDecay, - numcell::NumCell, on_change::OnChange, opaque_cell::OpaqueCell, ordered_float::F64, - oserror::OsError, + asyncevent::AsyncEvent, binary_search_map::BinarySearchMap, bitflags::BitflagsExt, + cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, + geometric_decay::GeometricDecay, numcell::NumCell, on_change::OnChange, + opaque_cell::OpaqueCell, ordered_float::F64, oserror::OsError, }, video::{ INVALID_MODIFIER, Modifier, dmabuf::DmaBufId, drm::{ - Change, ConnectorStatus, ConnectorType, DRM_CLIENT_CAP_ATOMIC, - DRM_MODE_ATOMIC_ALLOW_MODESET, DrmBlob, DrmConnector, DrmCrtc, DrmEncoder, - DrmError, DrmEvent, DrmFramebuffer, DrmLease, DrmMaster, DrmModeInfo, DrmObject, - DrmPlane, DrmProperty, DrmPropertyDefinition, DrmPropertyType, DrmVersion, - PropBlob, drm_mode_modeinfo, hdr_output_metadata, + ConnectorStatus, ConnectorType, DRM_CLIENT_CAP_ATOMIC, DrmBlob, DrmConnector, + DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFb, DrmFramebuffer, DrmLease, + DrmMaster, DrmModeInfo, DrmObject, DrmPlane, DrmProperty, DrmPropertyDefinition, + DrmPropertyType, DrmVersion, HDMI_EOTF_TRADITIONAL_GAMMA_SDR, drm_mode_modeinfo, + hdr_output_metadata, }, gbm::{GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING, GBM_BO_USE_SCANOUT, GbmBo, GbmDevice}, }, @@ -57,7 +62,6 @@ use { isnt::std_1::collections::IsntHashMap2Ext, jay_config::video::GfxApi, std::{ - any::Any, cell::{Cell, RefCell}, collections::hash_map::Entry, ffi::CString, @@ -66,7 +70,6 @@ use { ops::DerefMut, rc::Rc, }, - thiserror::Error, uapi::{ OwnedFd, c::{self, dev_t}, @@ -108,7 +111,7 @@ pub struct MetalDrmDevice { pub on_change: OnChange, pub direct_scanout_enabled: Cell>, pub is_nvidia: bool, - pub is_amd: bool, + pub _is_amd: bool, pub lease_ids: MetalLeaseIds, pub leases: CopyHashMap, pub leases_to_break: CopyHashMap, @@ -236,7 +239,7 @@ impl BackendDrmDevice for MetalDrmDevice { return; }; let plane = crtc.possible_planes.values().find(|p| { - !p.assigned.get() + p.drm_state.borrow().assigned_crtc.is_none() && p.lease.is_none() && planes.not_contains_key(&p.id) && p.ty == PlaneType::Primary @@ -317,31 +320,33 @@ pub struct MetalDrmDeviceData { pub dev: Rc, pub connectors: CopyHashMap>, pub futures: CopyHashMap, - pub unprocessed_change: Cell, } #[derive(Debug)] pub struct PersistentDisplayData { - pub mode: RefCell>, - pub vrr_requested: Cell, - pub format: Cell<&'static Format>, - pub eotf: Cell, - pub color_space: Cell, + pub state: RefCell, +} + +#[derive(Debug)] +pub struct DefaultProperty { + pub name: &'static str, + pub prop: DrmProperty, + pub value: u64, } #[derive(Debug)] pub struct ConnectorDisplayData { - pub crtc_id: MutableProperty, - pub crtcs: AHashMap>, + pub crtc_id: DrmProperty, + pub crtcs: BinarySearchMap, 8>, + pub first_mode: Mode, pub modes: Vec, - pub mode: Option, pub persistent: Rc, pub refresh: u32, pub non_desktop: bool, pub non_desktop_effective: bool, pub vrr_capable: bool, pub _vrr_refresh_max_nsec: u64, - pub default_properties: Vec<(DrmProperty, u64)>, + pub default_properties: Vec, pub untyped_properties: AHashMap, pub connector_id: ConnectorKernelId, @@ -357,14 +362,38 @@ pub struct ConnectorDisplayData { pub primaries: Primaries, pub luminance: Option, - pub colorspace: Option>, - pub hdr_metadata: Option>, - pub hdr_metadata_blob: Option, + pub colorspace: Option, + pub hdr_metadata: Option, + pub drm_state: DrmConnectorState, } impl ConnectorDisplayData { - fn should_enable_vrr(&self) -> bool { - self.persistent.vrr_requested.get() && self.vrr_capable + fn update_refresh(&mut self, dev: &MetalDrmDevice) { + self.refresh = 0; + if self.drm_state.crtc_id.is_none() { + return; + } + let Some(crtc) = dev.crtcs.get(&self.drm_state.crtc_id) else { + return; + }; + let drm_state = &*crtc.drm_state.borrow(); + let Some(mode) = &drm_state.mode else { + return; + }; + let refresh_rate_mhz = mode.refresh_rate_millihz(); + if refresh_rate_mhz != 0 { + self.refresh = (1_000_000_000_000u64 / refresh_rate_mhz as u64) as u32; + } + } + + fn update_non_desktop_effective(&mut self) { + let state = &*self.persistent.state.borrow(); + self.non_desktop_effective = state.non_desktop_override.unwrap_or(self.non_desktop); + } + + pub fn update_cached_fields(&mut self, dev: &MetalDrmDevice) { + self.update_refresh(dev); + self.update_non_desktop_effective(); } } @@ -389,12 +418,21 @@ impl MetalLeaseData { self.revoked.set(res); for c in &self.connectors { c.lease.take(); + if let Err(e) = c.update_properties() { + log::error!("Could not update connector properties: {}", ErrorFmt(e)); + } } for c in &self.crtcs { c.lease.take(); + if let Err(e) = c.update_properties() { + log::error!("Could not update crtc properties: {}", ErrorFmt(e)); + } } for p in &self.planes { p.lease.take(); + if let Err(e) = p.update_properties() { + log::error!("Could not update plane properties: {}", ErrorFmt(e)); + } } } res @@ -445,6 +483,7 @@ pub enum FrontState { pub struct MetalConnector { pub id: DrmConnector, + pub kernel_id: Cell, pub master: Rc, pub state: Rc, @@ -453,16 +492,13 @@ pub struct MetalConnector { pub connector_id: ConnectorId, - pub buffer_format: Cell<&'static Format>, pub buffers: CloneCell>>, - pub next_buffer: NumCell, - - pub enabled: Cell, - pub non_desktop_override: Cell>, + pub color_description: CloneCell>, pub lease: Cell>, - pub can_present: Cell, + pub buffers_idle: Cell, + pub crtc_idle: Cell, pub has_damage: NumCell, pub cursor_changed: Cell, pub cursor_damage: Cell, @@ -485,7 +521,6 @@ pub struct MetalConnector { pub cursor_y: Cell, pub cursor_enabled: Cell, pub cursor_buffers: CloneCell>>, - pub cursor_front_buffer: NumCell, pub cursor_swap_buffer: Cell, pub cursor_sync_file: CloneCell>, @@ -495,11 +530,7 @@ pub struct MetalConnector { pub next_framebuffer: OpaqueCell>, pub direct_scanout_active: Cell, - pub tearing_requested: Cell, - pub try_switch_format: Cell, - pub version: NumCell, - pub sequence: Cell, pub expected_sequence: Cell>, pub pre_commit_margin: Cell, pub pre_commit_margin_decay: GeometricDecay, @@ -541,7 +572,7 @@ impl Debug for MetalHardwareCursor { impl HardwareCursor for MetalHardwareCursor { fn damage(&self) { self.connector.cursor_damage.set(true); - if self.connector.can_present.get() { + if self.connector.buffers_idle.get() && self.connector.crtc_idle.get() { self.connector.schedule_present(); } } @@ -585,7 +616,12 @@ impl Debug for ConnectorFutures { } impl MetalConnector { - fn send_vrr_enabled(&self) { + pub fn send_connected(self: &Rc) { + let dd = &*self.display.borrow(); + self.backend.send_connected(self, dd); + } + + pub fn send_state(&self) { match self.frontend_state.get() { FrontState::Removed | FrontState::Disconnected @@ -593,12 +629,12 @@ impl MetalConnector { | FrontState::Connected { non_desktop: true } => return, FrontState::Connected { non_desktop: false } => {} } - if let Some(crtc) = self.crtc.get() { - self.send_event(ConnectorEvent::VrrChanged(crtc.vrr_enabled.value.get())); - } + let mut state = *self.display.borrow().persistent.state.borrow(); + state.serial = self.state.backend_connector_state_serials.next(); + self.send_event(ConnectorEvent::State(state)); } - fn send_formats(&self) { + pub fn send_formats(&self) { match self.frontend_state.get() { FrontState::Removed | FrontState::Disconnected @@ -611,13 +647,10 @@ impl MetalConnector { formats = plane.formats.values().map(|f| f.format).collect(); } let formats = Rc::new(formats); - self.send_event(ConnectorEvent::FormatsChanged( - formats, - self.buffer_format.get(), - )); + self.send_event(ConnectorEvent::FormatsChanged(formats)); } - fn send_hardware_cursor(self: &Rc) { + pub fn send_hardware_cursor(self: &Rc) { match self.frontend_state.get() { FrontState::Removed | FrontState::Disconnected @@ -636,7 +669,7 @@ impl MetalConnector { fn connected(&self) -> bool { let dd = self.display.borrow_mut(); - self.enabled.get() && dd.connection == ConnectorStatus::Connected + dd.persistent.state.borrow().enabled && dd.connection == ConnectorStatus::Connected } pub fn update_drm_feedback(&self) { @@ -682,13 +715,19 @@ impl MetalConnector { } }; } + let set_state = |ns: FrontState| { + log::debug!( + "Changing state of {}: {state:?} -> {ns:?}", + self.kernel_id.get(), + ); + self.frontend_state.set(ns); + }; match &event { ConnectorEvent::Connected(ty) => match state { FrontState::Disconnected => { let non_desktop = ty.non_desktop; self.on_change.send_event(event); - self.frontend_state - .set(FrontState::Connected { non_desktop }); + set_state(FrontState::Connected { non_desktop }); } FrontState::Removed | FrontState::Connected { .. } | FrontState::Unavailable => { log::error!("Tried to send connected event in invalid state: {state:?}"); @@ -697,13 +736,13 @@ impl MetalConnector { ConnectorEvent::HardwareCursor(_) => { desktop_event!("hardware cursor"); } - ConnectorEvent::ModeChanged(_) => { - desktop_event!("mode change"); + ConnectorEvent::State(_) => { + desktop_event!("state"); } ConnectorEvent::Disconnected => match state { FrontState::Connected { .. } | FrontState::Unavailable => { self.on_change.send_event(event); - self.frontend_state.set(FrontState::Disconnected); + set_state(FrontState::Disconnected); } FrontState::Removed | FrontState::Disconnected => { log::error!("Tried to send disconnected event in invalid state: {state:?}"); @@ -712,7 +751,7 @@ impl MetalConnector { ConnectorEvent::Removed => match state { FrontState::Disconnected => { self.on_change.send_event(event); - self.frontend_state.set(FrontState::Removed); + set_state(FrontState::Removed); } FrontState::Removed | FrontState::Connected { .. } | FrontState::Unavailable => { log::error!("Tried to send removed event in invalid state: {state:?}"); @@ -721,7 +760,7 @@ impl MetalConnector { ConnectorEvent::Unavailable => match state { FrontState::Connected { non_desktop: true } => { self.on_change.send_event(event); - self.frontend_state.set(FrontState::Unavailable); + set_state(FrontState::Unavailable); } FrontState::Connected { non_desktop: false } | FrontState::Removed @@ -733,93 +772,44 @@ impl MetalConnector { ConnectorEvent::Available => match state { FrontState::Unavailable => { self.on_change.send_event(event); - self.frontend_state - .set(FrontState::Connected { non_desktop: true }); + set_state(FrontState::Connected { non_desktop: true }); } FrontState::Connected { .. } | FrontState::Removed | FrontState::Disconnected => { log::error!("Tried to send available event in invalid state: {state:?}"); } }, - ConnectorEvent::VrrChanged(_) => { - desktop_event!("vrr-changed"); - } - ConnectorEvent::FormatsChanged(_, _) => { + ConnectorEvent::FormatsChanged(_) => { desktop_event!("formats-changed"); } - ConnectorEvent::ColorsChanged(_, _) => { - desktop_event!("colors-changed"); - } } } fn queue_sequence(&self) { if let Some(crtc) = self.crtc.get() { - if crtc.needs_vblank_emulation.get() { - return; - } - if let Err(e) = self.master.queue_sequence(crtc.id) { - log::error!("Could not queue a CRTC sequence: {}", ErrorFmt(&e)); - if let DrmError::QueueSequence(OsError(c::EOPNOTSUPP)) = e - && let Some(node) = self.state.root.outputs.get(&self.connector_id) - { - log::warn!("{}: Switching to vblank emulation", self.kernel_id()); - crtc.needs_vblank_emulation.set(true); - node.global.connector.needs_vblank_emulation.set(true); - node.vblank(); - } - } else { - crtc.have_queued_sequence.set(true); - } + crtc.queue_sequence(); } } +} - fn change_property( - &self, - name: &str, - needs_change: impl FnOnce(&ConnectorDisplayData) -> bool, - supports_change: impl FnOnce(&ConnectorDisplayData) -> bool, - change: impl FnOnce(&ConnectorDisplayData), - changed: impl FnOnce(), - reset: impl FnOnce(&ConnectorDisplayData), - ) { - match self.frontend_state.get() { - FrontState::Connected { non_desktop: false } => {} - FrontState::Connected { non_desktop: true } - | FrontState::Removed - | FrontState::Disconnected - | FrontState::Unavailable => return, - } - let dd = self.display.borrow(); - if !needs_change(&dd) { +impl MetalCrtc { + fn queue_sequence(&self) { + if self.needs_vblank_emulation.get() { return; } - if !supports_change(&dd) { - return; + if let Err(e) = self.master.queue_sequence(self.id) { + log::error!("Could not queue a CRTC sequence: {}", ErrorFmt(&e)); + if let DrmError::QueueSequence(OsError(c::EOPNOTSUPP)) = e + && let Some(connector) = self.connector.get() + && let Some(node) = connector.state.root.outputs.get(&connector.connector_id) + { + log::warn!("{}: Switching to vblank emulation", connector.kernel_id()); + self.needs_vblank_emulation.set(true); + node.global.connector.needs_vblank_emulation.set(true); + node.vblank(); + } + } else { + self.have_queued_sequence.set(true); } - if dd.connection != ConnectorStatus::Connected { - log::warn!("Cannot change {name} of connector that is not connected"); - return; - } - let Some(dev) = self.backend.device_holder.drm_devices.get(&self.dev.devnum) else { - log::warn!("Cannot change {name} because underlying device does not exist?"); - return; - }; - change(&dd); - drop(dd); - let Err(e) = self.backend.handle_drm_change_(&dev, true) else { - changed(); - return; - }; - log::warn!("Could not change {name}: {}", ErrorFmt(&e)); - reset(&self.display.borrow()); - if let MetalError::Modeset(DrmError::Atomic(OsError(c::EACCES))) = e { - log::warn!("Failed due to access denied. Resetting in memory only."); - return; - } - log::warn!("Trying to re-initialize the drm device"); - if let Err(e) = self.backend.handle_drm_change_(&dev, true) { - log::warn!("Could not restore the previous {name}: {}", ErrorFmt(e)); - }; } } @@ -829,7 +819,7 @@ impl Connector for MetalConnector { } fn kernel_id(&self) -> ConnectorKernelId { - self.display.borrow().connector_id + self.kernel_id.get() } fn event(&self) -> Option { @@ -842,7 +832,7 @@ impl Connector for MetalConnector { fn damage(&self) { self.has_damage.fetch_add(1); - if self.can_present.get() { + if self.buffers_idle.get() && self.crtc_idle.get() { self.schedule_present(); } } @@ -851,212 +841,81 @@ impl Connector for MetalConnector { Some(self.dev.id) } - fn enabled(&self) -> bool { - self.enabled.get() - } - - fn set_enabled(&self, enabled: bool) { - if self.enabled.replace(enabled) != enabled - && self.display.borrow_mut().connection == ConnectorStatus::Connected - && let Some(dev) = self.backend.device_holder.drm_devices.get(&self.dev.devnum) - && let Err(e) = self.backend.handle_drm_change_(&dev, true) - { - dev.unprocessed_change.set(true); - log::error!("Could not dis/enable connector: {}", ErrorFmt(e)); + fn effectively_locked(&self) -> bool { + let dd = &*self.display.borrow(); + let state = &*dd.persistent.state.borrow(); + if !state.enabled || !state.active { + return true; } + let Some(fb) = &*self.active_framebuffer.borrow() else { + return false; + }; + fb.locked } fn drm_feedback(&self) -> Option> { self.drm_feedback.get() } - fn set_mode(&self, be_mode: Mode) { - match self.frontend_state.get() { - FrontState::Connected { non_desktop: false } => {} - FrontState::Connected { non_desktop: true } - | FrontState::Removed - | FrontState::Disconnected - | FrontState::Unavailable => return, - } - let mut dd = self.display.borrow_mut(); - let Some(mode) = dd.modes.iter().find(|m| m.to_backend() == be_mode) else { - log::warn!("Connector does not support mode {:?}", be_mode); - return; - }; - let prev = dd.mode.clone(); - if prev.as_ref() == Some(mode) { - return; - } - if dd.connection != ConnectorStatus::Connected { - log::warn!("Cannot change mode of connector that is not connected"); - return; - } - let Some(dev) = self.backend.device_holder.drm_devices.get(&self.dev.devnum) else { - log::warn!("Cannot change mode because underlying device does not exist?"); - return; - }; - log::info!("Trying to change mode from {:?} to {:?}", prev, mode); - let persistent = dd.persistent.clone(); - *persistent.mode.borrow_mut() = Some(mode.clone()); - dd.mode = Some(mode.clone()); - drop(dd); - let Err(e) = self.backend.handle_drm_change_(&dev, true) else { - self.send_event(ConnectorEvent::ModeChanged(be_mode)); - return; - }; - log::warn!("Could not change mode: {}", ErrorFmt(&e)); - *persistent.mode.borrow_mut() = prev.clone(); - self.display.borrow_mut().mode = prev; - if let MetalError::Modeset(DrmError::Atomic(OsError(c::EACCES))) = e { - log::warn!("Failed due to access denied. Resetting in memory only."); - return; - } - log::warn!("Trying to re-initialize the drm device"); - if let Err(e) = self.backend.handle_drm_change_(&dev, true) { - log::warn!("Could not restore the previous mode: {}", ErrorFmt(e)); - }; - } - - fn set_non_desktop_override(&self, non_desktop: Option) { - if self.non_desktop_override.replace(non_desktop) == non_desktop { - return; - } - let mut dd = self.display.borrow_mut(); - let non_desktop_effective = non_desktop.unwrap_or(dd.non_desktop); - if dd.non_desktop_effective == non_desktop_effective { - return; - } - dd.non_desktop_effective = non_desktop_effective; - drop(dd); - if let Some(dev) = self.backend.device_holder.drm_devices.get(&self.dev.devnum) - && let Err(e) = self.backend.handle_drm_change_(&dev, true) - { - dev.unprocessed_change.set(true); - log::error!("Could not override non-desktop setting: {}", ErrorFmt(e)); - } - } - fn drm_object_id(&self) -> Option { Some(self.id) } - fn set_vrr_enabled(&self, enabled: bool) { - if self.frontend_state.get() != (FrontState::Connected { non_desktop: false }) { - return; - } - let dd = &mut *self.display.borrow_mut(); - let old_enabled = dd.should_enable_vrr(); - dd.persistent.vrr_requested.set(enabled); - let new_enabled = dd.should_enable_vrr(); - if old_enabled == new_enabled { - return; - } - let Some(crtc) = self.crtc.get() else { - return; - }; - let mut change = self.master.change(); - change.change_object(crtc.id, |c| { - c.change(crtc.vrr_enabled.id, new_enabled); - }); - if let Err(e) = change.commit(0, 0) { - log::error!("Could not change vrr mode: {}", ErrorFmt(e)); - return; - } - crtc.vrr_enabled.value.set(new_enabled); - self.send_vrr_enabled(); - } - - fn set_tearing_enabled(&self, enabled: bool) { - if !self.dev.supports_async_commit { - return; - } - if self.tearing_requested.replace(enabled) != enabled { - let msg = match enabled { - true => "Enabling", - false => "Disabling", - }; - log::debug!("{msg} tearing on output {}", self.kernel_id()); - } - } - - fn set_fb_format(&self, format: &'static Format) { + fn before_non_desktop_override_update(&self, overrd: Option) { { - let dd = self.display.borrow().persistent.clone(); - dd.format.set(format); - if format == self.buffer_format.get() { - self.try_switch_format.set(false); + let dd = &*self.display.borrow(); + let old = dd.non_desktop_effective; + let new = overrd.unwrap_or(dd.non_desktop); + if old == new || new { return; } - self.try_switch_format.set(true); } - if let Some(dev) = self.backend.device_holder.drm_devices.get(&self.dev.devnum) - && let Err(e) = self.backend.handle_drm_change_(&dev, true) + if let Some(lease_id) = self.lease.get() + && let Some(lease) = self.dev.leases.remove(&lease_id) { - dev.unprocessed_change.set(true); - log::error!("Could not change format: {}", ErrorFmt(e)); + if lease.try_revoke() { + self.send_event(ConnectorEvent::Available); + } else { + self.dev.leases_to_break.set(lease_id, lease); + } } } - fn set_colors(&self, bcs: BackendColorSpace, btf: BackendTransferFunction) { - let prev_bcs = Cell::new(bcs); - let prev_btf = Cell::new(btf); - self.change_property( - "colors", - |dd| { - prev_bcs.set(dd.persistent.color_space.get()); - prev_btf.set(dd.persistent.eotf.get()); - prev_bcs.get() != bcs || prev_btf.get() != btf - }, - |dd| { - let cs = match bcs { - BackendColorSpace::Default => true, - BackendColorSpace::Bt2020 => dd.supports_bt2020, - }; - if !cs { - log::warn!("Display does not support color space {:?}", bcs); - } - let tf = match btf { - BackendTransferFunction::Default => true, - BackendTransferFunction::Pq => dd.supports_pq, - }; - if !tf { - log::warn!("Display does not support transfer function {:?}", btf); - } - cs && tf - }, - |dd| { - dd.persistent.color_space.set(bcs); - dd.persistent.eotf.set(btf); - }, - || { - self.send_event(ConnectorEvent::ColorsChanged(bcs, btf)); - }, - |dd| { - dd.persistent.color_space.set(prev_bcs.get()); - dd.persistent.eotf.set(prev_btf.get()); - }, - ); + fn transaction_type(&self) -> Box { + #[derive(Eq, PartialEq, Hash)] + struct TT(dev_t); + impl BackendConnectorTransactionType for TT {} + Box::new(TT(self.dev.devnum)) + } + + fn create_transaction( + &self, + ) -> Result, BackendConnectorTransactionError> { + self.create_transaction().map(|v| Box::new(v) as _) } } pub struct MetalCrtc { pub id: DrmCrtc, pub idx: usize, - pub _master: Rc, - pub default_properties: Vec<(DrmProperty, u64)>, + pub master: Rc, + pub default_properties: Vec, pub untyped_properties: RefCell>, pub lease: Cell>, - pub possible_planes: AHashMap>, + pub possible_planes: BinarySearchMap, 8>, pub connector: CloneCell>>, + pub pending_flip: CloneCell>>, - pub active: MutableProperty, - pub mode_id: MutableProperty, - pub vrr_enabled: MutableProperty, + pub active: DrmProperty, + pub mode_id: DrmProperty, + pub vrr_enabled: DrmProperty, + pub out_fence_ptr: DrmProperty, + pub drm_state: RefCell, - pub mode_blob: CloneCell>>, + pub sequence: Cell, pub have_queued_sequence: Cell, pub needs_vblank_emulation: Cell, } @@ -1088,8 +947,8 @@ pub struct PlaneFormat { pub struct MetalPlane { pub id: DrmPlane, - pub _master: Rc, - pub default_properties: Vec<(DrmProperty, u64)>, + pub master: Rc, + pub default_properties: Vec, pub untyped_properties: RefCell>, pub ty: PlaneType, @@ -1098,22 +957,23 @@ pub struct MetalPlane { pub formats: AHashMap, pub lease: Cell>, - pub assigned: Cell, pub mode_w: Cell, pub mode_h: Cell, - pub crtc_id: MutableProperty, - pub crtc_x: MutableProperty, - pub crtc_y: MutableProperty, - pub crtc_w: MutableProperty, - pub crtc_h: MutableProperty, - pub src_x: MutableProperty, - pub src_y: MutableProperty, - pub src_w: MutableProperty, - pub src_h: MutableProperty, + pub crtc_id: DrmProperty, + pub crtc_x: DrmProperty, + pub crtc_y: DrmProperty, + pub crtc_w: DrmProperty, + pub crtc_h: DrmProperty, + pub src_x: DrmProperty, + pub src_y: DrmProperty, + pub src_w: DrmProperty, + pub src_h: DrmProperty, pub in_fence_fd: DrmProperty, pub fb_id: DrmProperty, + + pub drm_state: RefCell, } impl Debug for MetalPlane { @@ -1158,8 +1018,8 @@ enum DefaultValue { fn create_default_properties( props: &CollectedProperties, - defaults: &[(&str, DefaultValue)], -) -> Vec<(DrmProperty, u64)> { + defaults: &[(&'static str, DefaultValue)], +) -> Vec { let mut res = vec![]; let mut defaults = defaults.iter(); 'outer: loop { @@ -1201,7 +1061,11 @@ fn create_default_properties( _ => continue, }, }; - res.push((definition.id, value)); + res.push(DefaultProperty { + name, + prop: definition.id, + value, + }); } } res @@ -1212,21 +1076,20 @@ fn create_connector( connector: DrmConnector, dev: &Rc, ) -> Result<(Rc, ConnectorFutures), DrmError> { - let display = create_connector_display_data(connector, dev, None)?; + let display = create_connector_display_data(connector, dev)?; let slf = Rc::new(MetalConnector { id: connector, + kernel_id: Cell::new(display.connector_id), master: dev.master.clone(), state: backend.state.clone(), dev: dev.clone(), backend: backend.clone(), connector_id: backend.state.connector_ids.next(), - buffer_format: Cell::new(XRGB8888), buffers: Default::default(), - next_buffer: Default::default(), - enabled: Cell::new(true), - non_desktop_override: Default::default(), + color_description: CloneCell::new(backend.state.color_manager.srgb_srgb().clone()), lease: Cell::new(None), - can_present: Cell::new(true), + buffers_idle: Cell::new(true), + crtc_idle: Cell::new(true), has_damage: NumCell::new(1), primary_plane: Default::default(), cursor_plane: Default::default(), @@ -1238,10 +1101,9 @@ fn create_connector( cursor_enabled: Cell::new(false), cursor_buffers: Default::default(), display: RefCell::new(display), - frontend_state: Cell::new(FrontState::Disconnected), + frontend_state: Cell::new(FrontState::Removed), cursor_changed: Cell::new(false), cursor_damage: Cell::new(false), - cursor_front_buffer: Default::default(), cursor_swap_buffer: Cell::new(false), cursor_sync_file: Default::default(), drm_feedback: Default::default(), @@ -1250,10 +1112,7 @@ fn create_connector( next_framebuffer: Default::default(), direct_scanout_active: Cell::new(false), next_vblank_nsec: Cell::new(0), - tearing_requested: Cell::new(false), - try_switch_format: Cell::new(false), version: Default::default(), - sequence: Default::default(), expected_sequence: Default::default(), pre_commit_margin_decay: GeometricDecay::new(0.5, DEFAULT_PRE_COMMIT_MARGIN), pre_commit_margin: Cell::new(DEFAULT_PRE_COMMIT_MARGIN), @@ -1277,10 +1136,9 @@ fn create_connector( fn create_connector_display_data( connector: DrmConnector, dev: &Rc, - non_desktop_override: Option, ) -> Result { let info = dev.master.get_connector_info(connector, true)?; - let mut crtcs = AHashMap::new(); + let mut crtcs = BinarySearchMap::new(); for encoder in info.encoders { if let Some(encoder) = dev.encoders.get(&encoder) { for (_, crtc) in &encoder.crtcs { @@ -1316,7 +1174,7 @@ fn create_connector_display_data( break 'fetch_edid; } }; - let blob = match dev.master.getblob_vec::(DrmBlob(edid.value.get() as _)) { + let blob = match dev.master.getblob_vec::(DrmBlob(edid.value as _)) { Ok(b) => b, Err(e) => { log::error!( @@ -1426,7 +1284,13 @@ fn create_connector_display_data( name, serial_number, )); - let desired_state = match dev.backend.persistent_display_data.get(&output_id) { + let first_mode = info + .modes + .first() + .cloned() + .map(|m| m.to_backend()) + .unwrap_or_default(); + let persistent = match dev.backend.persistent_display_data.get(&output_id) { Some(ds) => { if connection != ConnectorStatus::Disconnected { log::info!("Reusing desired state for {:?}", output_id); @@ -1435,11 +1299,18 @@ fn create_connector_display_data( } None => { let ds = Rc::new(PersistentDisplayData { - mode: RefCell::new(info.modes.first().cloned()), - vrr_requested: Default::default(), - format: Cell::new(XRGB8888), - eotf: Default::default(), - color_space: Default::default(), + state: RefCell::new(BackendConnectorState { + serial: dev.backend.state.backend_connector_state_serials.next(), + enabled: true, + active: true, + mode: first_mode, + non_desktop_override: None, + vrr: false, + tearing: false, + format: XRGB8888, + color_space: Default::default(), + transfer_function: Default::default(), + }), }); dev.backend .persistent_display_data @@ -1447,47 +1318,43 @@ fn create_connector_display_data( ds } }; - let mut mode_opt = desired_state.mode.borrow_mut(); - if let Some(mode) = &*mode_opt - && !info.modes.contains(mode) + let mut desired_state = persistent.state.borrow_mut(); + if desired_state.mode == Mode::default() { + desired_state.mode = first_mode; + } else if info + .modes + .iter() + .all(|m| m.to_backend() != desired_state.mode) { log::warn!("Discarding previously desired mode"); - *mode_opt = None; + desired_state.mode = first_mode; } - if mode_opt.is_none() { - *mode_opt = info.modes.first().cloned(); - } - let refresh = mode_opt - .as_ref() - .map(|m| 1_000_000_000_000u64 / (m.refresh_rate_millihz() as u64)) - .unwrap_or(0) as u32; - let non_desktop = props.get("non-desktop")?.value.get() != 0; + let non_desktop = props.get("non-desktop")?.value != 0; let vrr_capable = match props.get("vrr_capable") { - Ok(c) => c.value.get() == 1, + Ok(c) => c.value == 1, Err(_) => false, }; - let mode = mode_opt.clone(); - drop(mode_opt); { - let viable = match desired_state.eotf.get() { + let viable = match desired_state.transfer_function { BackendTransferFunction::Default => true, BackendTransferFunction::Pq => supports_pq, }; if !viable { log::warn!("Discarding previously desired transfer function"); - desired_state.eotf.set(BackendTransferFunction::Default); + desired_state.transfer_function = BackendTransferFunction::Default; } } { - let viable = match desired_state.color_space.get() { + let viable = match desired_state.color_space { BackendColorSpace::Default => true, BackendColorSpace::Bt2020 => supports_bt2020, }; if !viable { log::warn!("Discarding previously desired color space"); - desired_state.color_space.set(BackendColorSpace::Default); + desired_state.color_space = BackendColorSpace::Default; } } + drop(desired_state); let default_properties = create_default_properties( &props, &[ @@ -1501,15 +1368,58 @@ fn create_connector_display_data( ("max bpc", DefaultValue::RangeMax), ], ); + let hdr_metadata_prop = props + .get("HDR_OUTPUT_METADATA") + .map(|p| p.map(|v| DrmBlob(v as _))) + .ok(); + let mut hdr_metadata = None; + let mut hdr_metadata_blob_id = DrmBlob::NONE; + if let Some(p) = &hdr_metadata_prop { + hdr_metadata_blob_id = p.value; + hdr_metadata = Some(hdr_output_metadata::from_eotf( + HDMI_EOTF_TRADITIONAL_GAMMA_SDR, + )); + if p.value.is_some() { + match dev.master.getblob::(p.value) { + Ok(m) => hdr_metadata = Some(m), + _ => { + log::debug!("Could not retrieve hdr output metadata"); + } + } + } + } + let colorspace_prop = props.get("Colorspace").ok(); + let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); + let drm_state = DrmConnectorState { + crtc_id: crtc_id.value, + color_space: colorspace_prop.map(|p| p.value), + hdr_metadata, + hdr_metadata_blob_id, + hdr_metadata_blob: None, + locked: true, + fb: DrmFb::NONE, + fb_idx: 0, + cursor_fb: DrmFb::NONE, + cursor_fb_idx: 0, + cursor_x: 0, + cursor_y: 0, + out_fd: None, + src_w: 0, + src_h: 0, + crtc_x: 0, + crtc_y: 0, + crtc_w: 0, + crtc_h: 0, + }; Ok(ConnectorDisplayData { - crtc_id: props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)), + crtc_id: props.get("CRTC_ID")?.id, crtcs, + first_mode, modes: info.modes, - mode, - persistent: desired_state, - refresh, + persistent, + refresh: 0, non_desktop, - non_desktop_effective: non_desktop_override.unwrap_or(non_desktop), + non_desktop_effective: non_desktop, vrr_capable, _vrr_refresh_max_nsec: vrr_refresh_max_nsec, default_properties, @@ -1524,12 +1434,9 @@ fn create_connector_display_data( luminance, connector_id, output_id, - colorspace: props.get("Colorspace").ok(), - hdr_metadata: props - .get("HDR_OUTPUT_METADATA") - .ok() - .map(|v| v.map(|v| DrmBlob(v as _))), - hdr_metadata_blob: None, + colorspace: colorspace_prop.map(|p| p.id), + hdr_metadata: hdr_metadata_prop.map(|p| p.id), + drm_state, }) } @@ -1558,7 +1465,7 @@ fn create_crtc( planes: &AHashMap>, ) -> Result { let mask = 1 << idx; - let mut possible_planes = AHashMap::new(); + let mut possible_planes = BinarySearchMap::new(); for plane in planes.values() { if plane.possible_crtcs.contains(mask) { possible_planes.insert(plane.id, plane.clone()); @@ -1575,19 +1482,43 @@ fn create_crtc( ("OUT_FENCE_PTR", DefaultValue::Fixed(0)), ], ); + let active = props.get("ACTIVE")?.map(|v| v == 1); + let mode_id = props.get("MODE_ID")?.map(|v| DrmBlob(v as u32)); + let vrr_enabled = props.get("VRR_ENABLED")?.map(|v| v == 1); + let out_fence_ptr = props.get("OUT_FENCE_PTR")?; + let mut mode = None; + if mode_id.value.is_some() { + match master.getblob::(mode_id.value) { + Ok(m) => mode = Some(m.into()), + _ => { + log::debug!("Could not retrieve current mode of connector"); + } + } + } + let state = DrmCrtcState { + active: active.value, + mode, + mode_blob_id: mode_id.value, + mode_blob: None, + vrr_enabled: vrr_enabled.value, + assigned_connector: DrmConnector::NONE, + }; Ok(MetalCrtc { id: crtc, idx, - _master: master.clone(), + master: master.clone(), default_properties, untyped_properties: RefCell::new(props.to_untyped()), lease: Cell::new(None), possible_planes, connector: Default::default(), - active: props.get("ACTIVE")?.map(|v| v == 1), - mode_id: props.get("MODE_ID")?.map(|v| DrmBlob(v as u32)), - vrr_enabled: props.get("VRR_ENABLED")?.map(|v| v == 1), - mode_blob: Default::default(), + pending_flip: Default::default(), + drm_state: RefCell::new(state), + active: active.id, + mode_id: mode_id.id, + vrr_enabled: vrr_enabled.id, + out_fence_ptr: out_fence_ptr.id, + sequence: Cell::new(0), have_queued_sequence: Cell::new(false), needs_vblank_emulation: Cell::new(false), }) @@ -1664,26 +1595,51 @@ fn create_plane(plane: DrmPlane, master: &Rc) -> Result Result, DrmError> { + fn get(&self, name: &str) -> Result, DrmError> { match self.props.get(name.as_bytes().as_bstr()) { - Some((def, value)) => Ok(MutableProperty { + Some((def, value)) => Ok(TypedProperty { id: def.id, - value: Cell::new(*value), - pending_value: Cell::new(None), + value: *value, }), _ => Err(DrmError::MissingProperty(name.to_string().into_boxed_str())), } @@ -1739,33 +1694,24 @@ impl CollectedProperties { } } -#[derive(Debug)] -pub struct MutableProperty { +#[derive(Copy, Clone, Debug)] +pub struct TypedProperty { pub id: DrmProperty, - pub value: Cell, - pub pending_value: Cell>, + pub value: T, } -impl MutableProperty { - fn map(self, f: F) -> MutableProperty +impl TypedProperty { + fn map(self, f: F) -> TypedProperty where F: FnOnce(T) -> U, { - MutableProperty { + TypedProperty { id: self.id, - value: Cell::new(f(self.value.into_inner())), - pending_value: Cell::new(None), + value: f(self.value), } } } -#[derive(Default)] -struct Preserve { - connectors: AHashSet, - crtcs: AHashSet, - planes: AHashSet, -} - impl MetalBackend { pub fn check_render_context(&self, dev: &Rc) -> bool { let ctx = match self.ctx.get() { @@ -1820,18 +1766,13 @@ impl MetalBackend { Some(dev) => dev, _ => return None, }; - if let Err(e) = self.handle_drm_change_(&dev, true) { - dev.unprocessed_change.set(true); + if let Err(e) = self.handle_drm_change_(&dev) { log::error!("Could not handle change of drm device: {}", ErrorFmt(e)); } None } - fn handle_drm_change_( - self: &Rc, - dev: &Rc, - preserve_any: bool, - ) -> Result<(), MetalError> { + fn handle_drm_change_(self: &Rc, dev: &Rc) -> Result<(), MetalError> { if let Err(e) = self.update_device_properties(dev) { return Err(MetalError::UpdateProperties(e)); } @@ -1867,9 +1808,8 @@ impl MetalBackend { c.send_event(ConnectorEvent::Removed); } } - let mut preserve = Preserve::default(); for c in dev.connectors.lock().values() { - let dd = create_connector_display_data(c.id, &dev.dev, c.non_desktop_override.get()); + let dd = create_connector_display_data(c.id, &dev.dev); let mut dd = match dd { Ok(d) => d, Err(e) => { @@ -1880,15 +1820,16 @@ impl MetalBackend { continue; } }; + c.kernel_id.set(dd.connector_id); let mut old = c.display.borrow_mut(); mem::swap(old.deref_mut(), &mut dd); - let mut preserve_connector = false; + old.drm_state = dd.drm_state; match c.frontend_state.get() { FrontState::Removed | FrontState::Disconnected => {} FrontState::Connected { .. } | FrontState::Unavailable => { let mut disconnect = false; // Disconnect if the connector has been disabled. - disconnect |= !c.enabled.get(); + disconnect |= !old.persistent.state.borrow().enabled; // If the connector is connected and switched between being a non-desktop // and desktop device, break leases and disconnect. disconnect |= old.connection == ConnectorStatus::Connected @@ -1904,7 +1845,6 @@ impl MetalBackend { disconnect |= old.output_id != dd.output_id; } if disconnect { - c.tearing_requested.set(false); if let Some(lease_id) = c.lease.get() && let Some(lease) = dev.dev.leases.remove(&lease_id) && !lease.try_revoke() @@ -1912,17 +1852,9 @@ impl MetalBackend { dev.dev.leases_to_break.set(lease_id, lease); } c.send_event(ConnectorEvent::Disconnected); - } else if preserve_any { - preserve_connector = true; } } } - if c.try_switch_format.get() && old.persistent.format.get() != c.buffer_format.get() { - preserve_connector = false; - } - if preserve_connector { - preserve.connectors.insert(c.id); - } } for c in new_connectors { let (connector, future) = match create_connector(self, c, &dev.dev) { @@ -1935,23 +1867,20 @@ impl MetalBackend { self.state .backend_events .push(BackendEvent::NewConnector(connector.clone())); + connector.frontend_state.set(FrontState::Disconnected); dev.futures.set(c, future); dev.connectors.set(c, connector); } - self.init_drm_device(dev, &mut preserve)?; + self.init_drm_device(dev)?; for connector in dev.connectors.lock().values() { if connector.connected() { - if !preserve.connectors.contains(&connector.id) { - connector.can_present.set(true); - } self.start_connector(connector, true); } } - dev.unprocessed_change.set(false); Ok(()) } - fn send_connected(&self, connector: &Rc, dd: &ConnectorDisplayData) { + pub fn send_connected(&self, connector: &Rc, dd: &ConnectorDisplayData) { match connector.frontend_state.get() { FrontState::Removed | FrontState::Connected { .. } | FrontState::Unavailable => { return; @@ -1973,23 +1902,23 @@ impl MetalBackend { if dd.supports_bt2020 { color_spaces.push(BackendColorSpace::Bt2020); } + let mut state = *dd.persistent.state.borrow(); + state.serial = self.state.backend_connector_state_serials.next(); connector.send_event(ConnectorEvent::Connected(MonitorInfo { modes, output_id: dd.output_id.clone(), - initial_mode: dd.mode.clone().unwrap().to_backend(), width_mm: dd.mm_width as _, height_mm: dd.mm_height as _, non_desktop: dd.non_desktop_effective, vrr_capable: dd.vrr_capable, transfer_functions, - transfer_function: dd.persistent.eotf.get(), color_spaces, - color_space: dd.persistent.color_space.get(), primaries: dd.primaries, luminance: dd.luminance, + state, })); connector.send_hardware_cursor(); - connector.send_vrr_enabled(); + connector.update_drm_feedback(); connector.send_formats(); } @@ -2098,7 +2027,7 @@ impl MetalBackend { on_change: Default::default(), direct_scanout_enabled: Default::default(), is_nvidia, - is_amd, + _is_amd: is_amd, lease_ids: Default::default(), leases: Default::default(), leases_to_break: Default::default(), @@ -2112,10 +2041,9 @@ impl MetalBackend { dev: dev.clone(), connectors, futures, - unprocessed_change: Cell::new(false), }); - self.init_drm_device(&slf, &mut Preserve::default())?; + self.init_drm_device(&slf)?; self.state .backend_events @@ -2125,6 +2053,7 @@ impl MetalBackend { self.state .backend_events .push(BackendEvent::NewConnector(connector.clone())); + connector.frontend_state.set(FrontState::Disconnected); if connector.connected() { self.start_connector(connector, true); } @@ -2140,56 +2069,128 @@ impl MetalBackend { } fn update_device_properties(&self, dev: &Rc) -> Result<(), DrmError> { + for c in dev.connectors.lock().values() { + c.update_properties()?; + } + for c in dev.dev.crtcs.values() { + c.update_properties()?; + } + for c in dev.dev.planes.values() { + c.update_properties()?; + } + Ok(()) + } +} + +impl MetalConnector { + fn update_properties(&self) -> Result<(), DrmError> { let get = |p: &AHashMap, k: DrmProperty| match p.get(&k) { Some(v) => Ok(*v), _ => todo!(), }; - let master = &dev.dev.master; - for c in dev.connectors.lock().values() { - let dd = &mut *c.display.borrow_mut(); - collect_untyped_properties(master, c.id, &mut dd.untyped_properties)?; - let props = &dd.untyped_properties; - dd.crtc_id - .value - .set(DrmCrtc(get(props, dd.crtc_id.id)? as _)); - if let Some(meta) = &dd.hdr_metadata { - meta.value.set(DrmBlob(get(props, meta.id)? as _)); - } - if let Some(cs) = &dd.colorspace { - cs.value.set(get(props, cs.id)?); - } + let master = &self.dev.master; + let dd = &mut *self.display.borrow_mut(); + collect_untyped_properties(master, self.id, &mut dd.untyped_properties)?; + let props = &dd.untyped_properties; + let state = &mut dd.drm_state; + state.crtc_id = DrmCrtc(get(props, dd.crtc_id)? as _); + if let Some(cs) = dd.colorspace { + state.color_space = Some(get(props, cs)?); + } else { + state.color_space = None; } - for c in dev.dev.crtcs.values() { - let props = &mut *c.untyped_properties.borrow_mut(); - collect_untyped_properties(master, c.id, props)?; - c.active.value.set(get(&props, c.active.id)? != 0); - c.vrr_enabled.value.set(get(&props, c.vrr_enabled.id)? != 0); - c.mode_id.value.set(DrmBlob(get(props, c.mode_id.id)? as _)); - } - for c in dev.dev.planes.values() { - let props = &mut *c.untyped_properties.borrow_mut(); - collect_untyped_properties(master, c.id, props)?; - c.crtc_id.value.set(DrmCrtc(get(props, c.crtc_id.id)? as _)); + if let Some(meta) = dd.hdr_metadata { + let id = DrmBlob(get(props, meta)? as _); + let old = state.hdr_metadata_blob_id; + state.hdr_metadata_blob_id = id; + if old != id { + state.hdr_metadata = None; + state.hdr_metadata_blob = None; + if id.is_some() { + match master.getblob::(id) { + Ok(b) => { + state.hdr_metadata = Some(b); + } + Err(e) => { + log::error!("Could not fetch hdr_output_metadata: {}", ErrorFmt(e)); + } + } + } + } } Ok(()) } +} +impl MetalCrtc { + fn update_properties(&self) -> Result<(), DrmError> { + let get = |p: &AHashMap, k: DrmProperty| match p.get(&k) { + Some(v) => Ok(*v), + _ => todo!(), + }; + let master = &self.master; + let props = &mut *self.untyped_properties.borrow_mut(); + collect_untyped_properties(master, self.id, props)?; + let state = &mut *self.drm_state.borrow_mut(); + state.active = get(&props, self.active)? != 0; + state.vrr_enabled = get(&props, self.vrr_enabled)? != 0; + let id = DrmBlob(get(props, self.mode_id)? as _); + let old = state.mode_blob_id; + state.mode_blob_id = id; + if old != id { + state.mode = None; + state.mode_blob = None; + if id.is_some() { + match master.getblob::(id) { + Ok(b) => { + state.mode = Some(b.into()); + } + Err(e) => { + log::error!("Could not fetch drm_mode_modeinfo: {}", ErrorFmt(e)); + } + } + } + } + Ok(()) + } +} + +impl MetalPlane { + fn update_properties(&self) -> Result<(), DrmError> { + let get = |p: &AHashMap, k: DrmProperty| match p.get(&k) { + Some(v) => Ok(*v), + _ => todo!(), + }; + let props = &mut *self.untyped_properties.borrow_mut(); + collect_untyped_properties(&self.master, self.id, props)?; + let state = &mut *self.drm_state.borrow_mut(); + state.fb_id = DrmFb(get(props, self.fb_id)? as _); + state.src_x = get(props, self.src_x)? as _; + state.src_y = get(props, self.src_y)? as _; + state.src_w = get(props, self.src_w)? as _; + state.src_h = get(props, self.src_h)? as _; + state.crtc_id = DrmCrtc(get(props, self.crtc_id)? as _); + state.crtc_x = get(props, self.crtc_x)? as _; + state.crtc_y = get(props, self.crtc_y)? as _; + state.crtc_w = get(props, self.crtc_w)? as _; + state.crtc_h = get(props, self.crtc_h)? as _; + Ok(()) + } +} + +impl MetalBackend { pub fn resume_drm_device( self: &Rc, dev: &Rc, ) -> Result<(), MetalError> { for connector in dev.connectors.lock().values() { - connector.can_present.set(true); connector.has_damage.fetch_add(1); connector.cursor_changed.set(true); } - if dev.unprocessed_change.get() { - return self.handle_drm_change_(dev, false); - } if let Err(e) = self.update_device_properties(dev) { return Err(MetalError::UpdateProperties(e)); } - self.init_drm_device(dev, &mut Preserve::default())?; + self.init_drm_device(dev)?; for connector in dev.connectors.lock().values() { if connector.primary_plane.is_some() { connector.schedule_present(); @@ -2226,16 +2227,18 @@ impl MetalBackend { } => 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 { +impl MetalCrtc { + fn update_sequence(&self, new: u64) { + if self.sequence.replace(new) == new { return; } // nothing } - fn update_u32_sequence(&self, connector: &Rc, sequence: u32) { - let old = connector.sequence.get(); + fn update_u32_sequence(&self, sequence: u32) { + let old = self.sequence.get(); let mut new = (old & !(u32::MAX as u64)) | (sequence as u64); if new < old { new += 1 << u32::BITS; @@ -2250,9 +2253,11 @@ impl MetalBackend { return; } } - self.update_sequence(connector, new); + self.update_sequence(new); } +} +impl MetalBackend { fn handle_drm_sequence_event( self: &Rc, dev: &Rc, @@ -2269,8 +2274,8 @@ impl MetalBackend { Some(c) => c, _ => return, }; - self.update_sequence(&connector, sequence); - connector.queue_sequence(); + crtc.update_sequence(sequence); + crtc.queue_sequence(); self.state.vblank(connector.connector_id); let dd = connector.display.borrow(); connector @@ -2290,43 +2295,29 @@ impl MetalBackend { Some(c) => c, _ => return, }; - let connector = match crtc.connector.get() { - Some(c) => c, - _ => return, + crtc.update_u32_sequence(sequence); + let wants_present = |c: &MetalConnector| { + c.has_damage.is_not_zero() || c.cursor_damage.get() || c.cursor_changed.get() }; - if !crtc.have_queued_sequence.get() { - connector.queue_sequence(); - } - self.update_u32_sequence(&connector, sequence); - let time_ns = tv_sec as u64 * 1_000_000_000 + tv_usec as u64 * 1000; - if crtc.needs_vblank_emulation.get() { - self.handle_drm_sequence_event(dev, crtc_id, time_ns as _, connector.sequence.get()); - } - connector.can_present.set(true); - if let Some(fb) = connector.next_framebuffer.take() { - *connector.active_framebuffer.borrow_mut() = Some(fb); - } - let dd = connector.display.borrow(); - let global = self.state.root.outputs.get(&connector.connector_id); - if let Some(expected) = connector.expected_sequence.take() { - if connector.vblank_miss_sec.replace(tv_sec) != tv_sec { - self.update_post_commit_margin(dev, &connector, &dd, global.as_deref()); + if let Some(connector) = crtc.pending_flip.take() { + connector.buffers_idle.set(true); + if let Some(fb) = connector.next_framebuffer.take() { + *connector.active_framebuffer.borrow_mut() = Some(fb); } - let actual = connector.sequence.get(); - if expected < actual { - connector.vblank_miss_this_sec.fetch_add(1); + if wants_present(&connector) && connector.crtc_idle.get() { + connector.schedule_present(); + } + let dd = connector.display.borrow(); + let global = self.state.root.outputs.get(&connector.connector_id); + if let Some(expected) = connector.expected_sequence.take() { + if connector.vblank_miss_sec.replace(tv_sec) != tv_sec { + self.update_post_commit_margin(dev, &connector, &dd, global.as_deref()); + } + let actual = crtc.sequence.get(); + if expected < actual { + connector.vblank_miss_this_sec.fetch_add(1); + } } - } - if connector.has_damage.is_not_zero() - || connector.cursor_damage.get() - || connector.cursor_changed.get() - { - connector.schedule_present(); - } - if connector.presentation_is_sync.get() { - connector.next_vblank_nsec.set(time_ns + dd.refresh as u64); - } - { let mut flags = KIND_HW_COMPLETION; if connector.presentation_is_sync.get() { flags |= KIND_VSYNC; @@ -2339,12 +2330,30 @@ impl MetalBackend { tv_sec as _, tv_usec * 1000, dd.refresh, - connector.sequence.get(), + crtc.sequence.get(), flags, - crtc.vrr_enabled.value.get(), + dd.persistent.state.borrow().vrr, + dd.drm_state.locked, ); } } + if let Some(connector) = crtc.connector.get() { + connector.crtc_idle.set(true); + if !crtc.have_queued_sequence.get() { + connector.queue_sequence(); + } + let time_ns = tv_sec as u64 * 1_000_000_000 + tv_usec as u64 * 1000; + if crtc.needs_vblank_emulation.get() { + self.handle_drm_sequence_event(dev, crtc_id, time_ns as _, crtc.sequence.get()); + } + if wants_present(&connector) && connector.buffers_idle.get() { + connector.schedule_present(); + } + if connector.presentation_is_sync.get() { + let dd = connector.display.borrow(); + connector.next_vblank_nsec.set(time_ns + dd.refresh as u64); + } + } } fn update_post_commit_margin( @@ -2379,163 +2388,6 @@ impl MetalBackend { } } - fn reset_planes(&self, dev: &MetalDrmDeviceData, changes: &mut Change, preserve: &Preserve) { - for plane in dev.dev.planes.values() { - if preserve.planes.contains(&plane.id) { - continue; - } - plane.crtc_id.value.set(DrmCrtc::NONE); - plane.assigned.set(false); - changes.change_object(plane.id, |c| { - c.change(plane.crtc_id.id, 0); - c.change(plane.fb_id, 0); - c.change(plane.in_fence_fd, -1i32); - }); - } - } - - fn reset_connectors_and_crtcs( - &self, - dev: &MetalDrmDeviceData, - changes: &mut Change, - preserve: &Preserve, - ) { - for connector in dev.connectors.lock().values() { - if preserve.connectors.contains(&connector.id) { - continue; - } - connector.buffers.set(None); - connector.cursor_buffers.set(None); - connector.primary_plane.set(None); - connector.cursor_plane.set(None); - connector.cursor_enabled.set(false); - connector.crtc.set(None); - connector.version.fetch_add(1); - let dd = connector.display.borrow_mut(); - dd.crtc_id.value.set(DrmCrtc::NONE); - if let Some(cs) = &dd.colorspace { - cs.value.set(0); - } - if let Some(hdr) = &dd.hdr_metadata { - hdr.value.set(DrmBlob(0)); - } - changes.change_object(connector.id, |c| { - c.change(dd.crtc_id.id, 0); - if let Some(cs) = &dd.colorspace { - c.change(cs.id, 0); - } - if let Some(hdr) = &dd.hdr_metadata { - c.change(hdr.id, 0); - } - }); - } - for crtc in dev.dev.crtcs.values() { - if preserve.crtcs.contains(&crtc.id) { - continue; - } - crtc.connector.set(None); - crtc.active.value.set(false); - crtc.mode_id.value.set(DrmBlob::NONE); - crtc.vrr_enabled.value.set(false); - changes.change_object(crtc.id, |c| { - c.change(crtc.active.id, 0); - c.change(crtc.mode_id.id, 0); - c.change(crtc.vrr_enabled.id, 0); - }); - } - } - - fn validate_preserve(&self, dev: &Rc, preserve: &mut Preserve) { - let mut remove_connectors = vec![]; - macro_rules! fail { - ($c:expr) => {{ - remove_connectors.push($c); - continue; - }}; - } - for c in &preserve.connectors { - let c = match dev.connectors.get(c) { - Some(c) => c, - _ => { - log::warn!("Cannot preserve connector which no longer exists"); - fail!(*c) - } - }; - let dd = c.display.borrow_mut(); - if let Some(crtc) = c.crtc.get() { - if dd.crtc_id.value.get() != crtc.id { - log::warn!("Cannot preserve connector attached to a different crtc"); - fail!(c.id); - } - if let Some(mode) = &dd.mode { - let mode_id = crtc.mode_id.value.get(); - if mode_id.is_none() { - log::warn!("Cannot preserve connector whose crtc has no mode attached"); - fail!(c.id); - } - let current_mode = match dev.dev.master.getblob::(mode_id) { - Ok(m) => m.into(), - _ => { - log::warn!("Could not retrieve current mode of connector"); - fail!(c.id); - } - }; - if !modes_equal(mode, ¤t_mode) { - log::warn!("Cannot preserve connector whose crtc has a different mode"); - fail!(c.id); - } - } - if !crtc.active.value.get() { - log::warn!("Cannot preserve connector whose crtc is inactive"); - fail!(c.id); - } - if let Some(plane) = c.primary_plane.get() - && plane.crtc_id.value.get() != crtc.id - { - log::warn!( - "Cannot preserve connector whose primary plane is attached to a different crtc" - ); - fail!(c.id); - } - if let Some(plane) = c.cursor_plane.get() { - let crtc_id = plane.crtc_id.value.get(); - if crtc_id.is_some() && crtc_id != crtc.id { - log::warn!( - "Cannot preserve connector whose cursor plane is attached to a different crtc" - ); - fail!(c.id); - } - } - if let Some(m) = &dd.colorspace - && m.value.get() != dd.persistent.color_space.get().to_drm() - { - log::debug!("Connector has wrong colorspace"); - fail!(c.id); - } - if let Some(diff) = self.compare_hdr_metadata(&dev.dev, &dd) { - log::debug!("{}", diff); - fail!(c.id); - } - } - } - for c in remove_connectors { - preserve.connectors.remove(&c); - } - for connector in dev.connectors.lock().values() { - if preserve.connectors.contains(&connector.id) { - if let Some(pp) = connector.primary_plane.get() { - preserve.planes.insert(pp.id); - } - if let Some(pp) = connector.cursor_plane.get() { - preserve.planes.insert(pp.id); - } - if let Some(crtc) = connector.crtc.get() { - preserve.crtcs.insert(crtc.id); - } - } - } - } - fn make_render_device(&self, dev: &MetalDrmDevice, force: bool) { if !force && let Some(ctx) = self.ctx.get() @@ -2568,6 +2420,9 @@ impl MetalBackend { self.ctx.set(Some(ctx)); for dev in self.device_holder.drm_devices.lock().values() { self.re_init_drm_device(&dev); + for connector in dev.connectors.lock().values() { + connector.send_hardware_cursor(); + } } } @@ -2604,7 +2459,7 @@ impl MetalBackend { } fn re_init_drm_device(&self, dev: &Rc) { - if let Err(e) = self.init_drm_device(dev, &mut Preserve::default()) { + if let Err(e) = self.init_drm_device(dev) { log::error!("Could not initialize device: {}", ErrorFmt(e)); } for connector in dev.connectors.lock().values() { @@ -2621,238 +2476,105 @@ impl MetalBackend { .retain(|_, lease| !lease.try_revoke()); } - fn reset_default_properties(&self, dev: &Rc, changes: &mut Change) { - macro_rules! reset { - ($obj:expr, $default:expr, $untyped:expr) => {{ - let props = $untyped; - for (k, v) in $default { - if props.get(k) != Some(v) { - changes.change_object($obj.id, |c| c.change(*k, *v)); - } - } - }}; - ($obj:expr) => { - reset!( - $obj, - &$obj.default_properties, - &*$obj.untyped_properties.borrow() - ) - }; - } - for connector in dev.connectors.lock().values() { - let dd = &*connector.display.borrow(); - reset!(connector, &dd.default_properties, &dd.untyped_properties); - } - for plane in dev.dev.planes.values() { - reset!(plane); - } - for crtc in dev.dev.crtcs.values() { - reset!(crtc); - } - } - - fn init_drm_device( - &self, - dev: &Rc, - preserve: &mut Preserve, - ) -> Result<(), MetalError> { + fn init_drm_device(&self, dev: &Rc) -> Result<(), MetalError> { self.break_leases(dev); - let ctx = match self.ctx.get() { - Some(ctx) => ctx, - _ => return Ok(()), + enum Quirks { + DirectScanout, + NonDefaultFormat, + NonDefaultMode, + } + let mut has_non_default_mode = false; + let mut has_non_default_format = false; + let mut has_direct_scanout = false; + for c in dev.connectors.lock().values() { + let dd = &*c.display.borrow(); + let state = &*dd.persistent.state.borrow(); + if state.mode != dd.first_mode { + has_non_default_mode = true; + } + if state.format != XRGB8888 { + has_non_default_format = true; + } + if c.direct_scanout_active.get() { + has_direct_scanout = true; + } + } + let mut quirks = vec![]; + if has_non_default_mode { + quirks.push(Quirks::NonDefaultMode); + } + if has_non_default_format { + quirks.push(Quirks::NonDefaultFormat); + } + if has_direct_scanout { + quirks.push(Quirks::DirectScanout); + } + let apply = |tran: MetalDeviceTransaction| { + tran.calculate_drm_state() + .map_err(MetalError::CalculateDrmState)? + .calculate_change(false, true) + .map_err(MetalError::CalculateDrmChange)? + .apply() + .map_err(MetalError::Modeset) }; - self.validate_preserve(dev, preserve); - let mut flags = 0; - let mut changes = dev.dev.master.change(); - self.reset_default_properties(dev, &mut changes); - if !self.can_use_current_drm_mode(dev) { - log::warn!("Cannot use existing connector configuration. Trying to perform modeset."); - flags = DRM_MODE_ATOMIC_ALLOW_MODESET; - self.reset_connectors_and_crtcs(dev, &mut changes, preserve); - for connector in dev.connectors.lock().values() { - if !preserve.connectors.contains(&connector.id) - && let Err(e) = self.assign_connector_crtc(connector, &mut changes) - { - log::error!("Could not assign a crtc: {}", ErrorFmt(e)); + let mut disable_non_default_mode = false; + let mut disable_non_default_format = false; + let mut disable_direct_scanout = false; + loop { + let mut tran = dev.create_transaction(); + for c in dev.connectors.lock().values() { + let dd = &*c.display.borrow(); + let mut state = *dd.persistent.state.borrow(); + let mut changed_any = false; + if disable_non_default_format && state.format != XRGB8888 { + state.format = XRGB8888; + changed_any = true; + } + if disable_non_default_mode && state.mode != dd.first_mode { + state.mode = dd.first_mode; + changed_any = true; + } + if changed_any { + tran.add(c, state).map_err(MetalError::AddToTransaction)?; } } - } - self.reset_planes(dev, &mut changes, preserve); - let mut old_buffers = vec![]; - for connector in dev.connectors.lock().values() { - if !preserve.connectors.contains(&connector.id) - && let Err(e) = - self.assign_connector_planes(connector, &mut changes, &ctx, &mut old_buffers) - { - log::error!("Could not assign a plane: {}", ErrorFmt(e)); + if disable_direct_scanout { + tran.disable_direct_scanout(); } - } - let res = loop { - let res = changes.commit(flags, 0); - if let Err(e) = &res - && flags.not_contains(DRM_MODE_ATOMIC_ALLOW_MODESET) - { - log::warn!("Fast commit failed, retrying with modeset: {}", ErrorFmt(e)); - flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; - continue; + let err = match apply(tran) { + Ok(_) => break, + Err(e) => e, + }; + log::error!("Could not initialize DRM device: {}", ErrorFmt(&err)); + let Some(q) = quirks.pop() else { + return Err(err); + }; + match q { + Quirks::DirectScanout => { + log::info!("Trying to disable direct scanout"); + disable_direct_scanout = true; + } + Quirks::NonDefaultFormat => { + log::info!("Trying to disable non XRGB8888 formats"); + disable_non_default_format = true; + } + Quirks::NonDefaultMode => { + log::info!("Trying to disable non-default modes"); + disable_non_default_mode = true; + } } - break res; - }; - if let Err(e) = res { - return Err(MetalError::Modeset(e)); - } - for connector in dev.connectors.lock().values() { - if preserve.connectors.contains(&connector.id) { - continue; - } - connector.send_hardware_cursor(); - connector.send_vrr_enabled(); - connector.update_drm_feedback(); - connector.send_formats(); } Ok(()) } - fn compare_hdr_metadata( - &self, - dev: &MetalDrmDevice, - dd: &ConnectorDisplayData, - ) -> Option { - let Some(m) = &dd.hdr_metadata else { - return None; - }; - match dd.persistent.eotf.get() { - BackendTransferFunction::Default => { - if m.value.get() != DrmBlob::NONE { - return Some(HdrMetadataDiff::Undesired); - } - } - eotf => { - if m.value.get() == DrmBlob::NONE { - return Some(HdrMetadataDiff::No); - } - let current_metadata = - match dev.master.getblob::(m.value.get()) { - Ok(m) => m, - _ => { - return Some(HdrMetadataDiff::CouldNotRetrieve); - } - }; - if current_metadata != hdr_output_metadata::from_eotf(eotf.to_drm()) { - return Some(HdrMetadataDiff::Incompatible); - } - } - } - None - } - - fn can_use_current_drm_mode(&self, dev: &Rc) -> bool { - let mut used_crtcs = AHashSet::new(); - let mut vrr_crtcs = AHashSet::new(); - let mut used_planes = AHashSet::new(); - - for connector in dev.connectors.lock().values() { - let dd = connector.display.borrow_mut(); - if should_ignore(connector, &dd) { - if dd.crtc_id.value.get().is_some() { - log::debug!("Connector should be ignored but has an assigned crtc"); - return false; - } - continue; - } - let crtc_id = dd.crtc_id.value.get(); - if crtc_id.is_none() { - log::debug!("Connector is connected but has no assigned crtc"); - return false; - } - used_crtcs.insert(crtc_id); - if dd.should_enable_vrr() { - vrr_crtcs.insert(crtc_id); - } - if let Some(m) = &dd.colorspace - && m.value.get() != dd.persistent.color_space.get().to_drm() - { - log::debug!("Connector has wrong colorspace"); - return false; - } - if let Some(diff) = self.compare_hdr_metadata(&dev.dev, &dd) { - log::debug!("{}", diff); - return false; - } - let crtc = dev.dev.crtcs.get(&crtc_id).unwrap(); - connector.crtc.set(Some(crtc.clone())); - connector.version.fetch_add(1); - crtc.connector.set(Some(connector.clone())); - if !crtc.active.value.get() { - log::debug!("Crtc is not active"); - return false; - } - let mode = match &dd.mode { - Some(m) => m, - _ => { - log::debug!("Connector has no assigned mode"); - return false; - } - }; - let current_mode = match dev - .dev - .master - .getblob::(crtc.mode_id.value.get()) - { - Ok(m) => m.into(), - _ => { - log::debug!("Could not retrieve current mode of connector"); - return false; - } - }; - if !modes_equal(mode, ¤t_mode) { - log::debug!("Connector mode differs from desired mode"); - return false; - } - let mut have_primary_plane = false; - for plane in crtc.possible_planes.values() { - if plane.ty == PlaneType::Primary && used_planes.insert(plane.id) { - have_primary_plane = true; - break; - } - } - if !have_primary_plane { - log::debug!("Connector has no primary plane assigned"); - return false; - } - } - - let mut changes = dev.dev.master.change(); - let mut flags = 0; - for crtc in dev.dev.crtcs.values() { - changes.change_object(crtc.id, |c| { - if !used_crtcs.contains(&crtc.id) && crtc.active.value.take() { - flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; - c.change(crtc.active.id, 0); - } - let vrr_requested = vrr_crtcs.contains(&crtc.id); - if crtc.vrr_enabled.value.get() != vrr_requested { - c.change(crtc.vrr_enabled.id, vrr_requested); - crtc.vrr_enabled.value.set(vrr_requested); - } - }); - } - if let Err(e) = changes.commit(flags, 0) { - log::debug!("Could not deactivate crtcs: {}", ErrorFmt(e)); - return false; - } - - true - } - - fn create_scanout_buffers( + pub fn create_scanout_buffers( &self, dev: &Rc, - format: &Format, + format: &'static Format, plane_modifiers: &IndexSet, width: i32, height: i32, - ctx: &MetalRenderContext, + ctx: &Rc, cursor: bool, ) -> Result<[RenderBuffer; N], MetalError> { let mut blend_buffer = None; @@ -2891,17 +2613,17 @@ impl MetalBackend { fn create_scanout_buffer( &self, dev: &Rc, - format: &Format, + format: &'static Format, plane_modifiers: &IndexSet, width: i32, height: i32, - render_ctx: &MetalRenderContext, + render_ctx: &Rc, cursor: bool, damage_queue: DamageQueue, blend_buffer: Option>, ) -> Result { - let ctx = dev.ctx.get(); - let dev_gfx_formats = ctx.gfx.formats(); + let dev_ctx = dev.ctx.get(); + let dev_gfx_formats = dev_ctx.gfx.formats(); let dev_gfx_format = match dev_gfx_formats.get(&format.drm) { None => return Err(MetalError::MissingDevFormat(format.name)), Some(f) => f, @@ -2943,7 +2665,7 @@ impl MetalBackend { Ok(fb) => Rc::new(fb), Err(e) => return Err(MetalError::Framebuffer(e)), }; - let dev_img = match ctx.gfx.clone().dmabuf_img(dev_bo.dmabuf()) { + let dev_img = match dev_ctx.gfx.clone().dmabuf_img(dev_bo.dmabuf()) { Ok(img) => img, Err(e) => return Err(MetalError::ImportImage(e)), }; @@ -3022,7 +2744,7 @@ impl MetalBackend { }; // Import the bridge BO into the current device - let dev_img = match ctx.gfx.clone().dmabuf_img(render_bo.dmabuf()) { + let dev_img = match dev_ctx.gfx.clone().dmabuf_img(render_bo.dmabuf()) { Ok(img) => img, Err(e) => return Err(MetalError::ImportImage(e)), }; @@ -3034,6 +2756,12 @@ impl MetalBackend { (Some(dev_tex), render_tex, Some(render_fb), Some(render_bo)) }; Ok(RenderBuffer { + width, + height, + locked: Cell::new(true), + format, + dev_ctx, + render_ctx: render_ctx.clone(), drm: drm_fb, damage_queue, dev_bo, @@ -3046,214 +2774,6 @@ impl MetalBackend { }) } - fn assign_connector_crtc( - &self, - connector: &Rc, - changes: &mut Change, - ) -> Result<(), MetalError> { - let dd = &mut *connector.display.borrow_mut(); - if should_ignore(connector, dd) { - return Ok(()); - } - let crtc = 'crtc: { - for crtc in dd.crtcs.values() { - if crtc.connector.is_none() && crtc.lease.is_none() { - break 'crtc crtc.clone(); - } - } - return Err(MetalError::NoCrtcForConnector); - }; - let mode = match &dd.mode { - Some(m) => m, - _ => return Err(MetalError::NoModeForConnector), - }; - let hdr_blob = match dd.persistent.eotf.get() { - BackendTransferFunction::Default => None, - eotf => { - let m = hdr_output_metadata::from_eotf(eotf.to_drm()); - Some(connector.master.create_blob(&m)?) - } - }; - let hdr_blob_id = hdr_blob.as_ref().map(|b| b.id()).unwrap_or_default(); - let mode_blob = mode.create_blob(&connector.master)?; - changes.change_object(connector.id, |c| { - c.change(dd.crtc_id.id, crtc.id); - if let Some(meta) = &dd.hdr_metadata { - c.change(meta.id, hdr_blob_id); - } - if let Some(cs) = &dd.colorspace { - c.change(cs.id, dd.persistent.color_space.get().to_drm()); - } - }); - changes.change_object(crtc.id, |c| { - c.change(crtc.active.id, 1); - c.change(crtc.mode_id.id, mode_blob.id()); - c.change(crtc.vrr_enabled.id, dd.should_enable_vrr()); - }); - connector.crtc.set(Some(crtc.clone())); - connector.version.fetch_add(1); - dd.crtc_id.value.set(crtc.id); - dd.hdr_metadata_blob = hdr_blob; - if let Some(meta) = &dd.hdr_metadata { - meta.value.set(hdr_blob_id); - } - if let Some(cs) = &dd.colorspace { - cs.value.set(dd.persistent.color_space.get().to_drm()); - } - crtc.connector.set(Some(connector.clone())); - crtc.active.value.set(true); - crtc.mode_id.value.set(mode_blob.id()); - crtc.mode_blob.set(Some(Rc::new(mode_blob))); - crtc.vrr_enabled.value.set(dd.should_enable_vrr() as _); - Ok(()) - } - - fn assign_connector_planes( - &self, - connector: &Rc, - changes: &mut Change, - ctx: &MetalRenderContext, - old_buffers: &mut Vec>, - ) -> Result<(), MetalError> { - let dd = &mut *connector.display.borrow_mut(); - let crtc = match connector.crtc.get() { - Some(c) => c, - _ => return Ok(()), - }; - let mode = match &dd.mode { - Some(m) => m, - _ => { - log::error!("Connector has a crtc assigned but no mode"); - return Ok(()); - } - }; - let allocate_primary_plane = |format: &'static Format| { - let (primary_plane, primary_modifiers) = 'primary_plane: { - for plane in crtc.possible_planes.values() { - if plane.ty == PlaneType::Primary - && !plane.assigned.get() - && plane.lease.is_none() - && let Some(format) = plane.formats.get(&format.drm) - { - break 'primary_plane (plane.clone(), &format.modifiers); - } - } - return Err(MetalError::NoPrimaryPlaneForConnector); - }; - let buffers = Rc::new(self.create_scanout_buffers( - &connector.dev, - format, - primary_modifiers, - mode.hdisplay as _, - mode.vdisplay as _, - ctx, - false, - )?); - Ok((primary_plane, buffers)) - }; - let primary_plane; - let buffers; - let buffer_format; - 'primary_plane: { - let format = dd.persistent.format.get(); - if format != XRGB8888 { - match allocate_primary_plane(format) { - Ok(v) => { - (primary_plane, buffers) = v; - buffer_format = format; - break 'primary_plane; - } - Err(e) => { - log::error!( - "Could not allocate framebuffer with requested format {}: {}", - format.name, - ErrorFmt(e) - ); - } - } - } - (primary_plane, buffers) = allocate_primary_plane(XRGB8888)?; - buffer_format = XRGB8888; - } - let mut cursor_plane = None; - let mut cursor_modifiers = &IndexSet::new(); - for plane in crtc.possible_planes.values() { - if plane.ty == PlaneType::Cursor - && !plane.assigned.get() - && plane.lease.is_none() - && plane.formats.contains_key(&ARGB8888.drm) - && let Some(format) = plane.formats.get(&ARGB8888.drm) - { - cursor_plane = Some(plane.clone()); - cursor_modifiers = &format.modifiers; - break; - } - } - let mut cursor_buffers = None; - if cursor_plane.is_some() { - let res = self.create_scanout_buffers( - &connector.dev, - ARGB8888, - cursor_modifiers, - connector.dev.cursor_width as _, - connector.dev.cursor_height as _, - ctx, - true, - ); - match res { - Ok(r) => cursor_buffers = Some(Rc::new(r)), - Err(e) => { - log::warn!( - "Could not allocate buffers for the cursor plane: {}", - ErrorFmt(e) - ); - cursor_plane = None; - } - } - } - changes.change_object(primary_plane.id, |c| { - c.change(primary_plane.fb_id, buffers[0].drm.id()); - c.change(primary_plane.crtc_id.id, crtc.id); - c.change(primary_plane.crtc_x.id, 0); - c.change(primary_plane.crtc_y.id, 0); - c.change(primary_plane.crtc_w.id, mode.hdisplay); - c.change(primary_plane.crtc_h.id, mode.vdisplay); - c.change(primary_plane.src_x.id, 0); - c.change(primary_plane.src_y.id, 0); - c.change(primary_plane.src_w.id, (mode.hdisplay as u64) << 16); - c.change(primary_plane.src_h.id, (mode.vdisplay as u64) << 16); - }); - primary_plane.assigned.set(true); - primary_plane.mode_w.set(mode.hdisplay as _); - primary_plane.mode_h.set(mode.vdisplay as _); - primary_plane.crtc_id.value.set(crtc.id); - primary_plane.crtc_x.value.set(0); - primary_plane.crtc_y.value.set(0); - primary_plane.crtc_w.value.set(mode.hdisplay as _); - primary_plane.crtc_h.value.set(mode.vdisplay as _); - primary_plane.src_x.value.set(0); - primary_plane.src_y.value.set(0); - primary_plane.src_w.value.set((mode.hdisplay as u32) << 16); - primary_plane.src_h.value.set((mode.vdisplay as u32) << 16); - if let Some(old) = connector.buffers.set(Some(buffers)) { - old_buffers.push(old); - } - connector.next_buffer.set(1); - connector.primary_plane.set(Some(primary_plane.clone())); - if let Some(cp) = &cursor_plane { - cp.assigned.set(true); - } - if let Some(old) = connector.cursor_buffers.set(cursor_buffers) { - old_buffers.push(old); - } - connector.cursor_plane.set(cursor_plane); - connector.cursor_enabled.set(false); - connector.buffer_format.set(buffer_format); - connector.try_switch_format.set(false); - connector.version.fetch_add(1); - Ok(()) - } - fn start_connector(&self, connector: &Rc, log_mode: bool) { let dd = &*connector.display.borrow(); self.send_connected(connector, dd); @@ -3264,11 +2784,15 @@ impl MetalBackend { | FrontState::Disconnected | FrontState::Unavailable => return, } - if log_mode { + if log_mode && let Some(crtc) = connector.crtc.get() { + let state = &*crtc.drm_state.borrow(); log::info!( "Initialized connector {} with mode {:?}", dd.connector_id, - dd.mode.as_ref().unwrap(), + state + .mode + .as_ref() + .map_or(Default::default(), |m| m.to_backend()), ); } connector.has_damage.fetch_add(1); @@ -3279,6 +2803,12 @@ impl MetalBackend { #[derive(Debug)] pub struct RenderBuffer { + pub width: i32, + pub height: i32, + pub locked: Cell, + pub format: &'static Format, + pub dev_ctx: Rc, + pub render_ctx: Rc, pub drm: Rc, pub damage_queue: DamageQueue, pub dev_bo: GbmBo, @@ -3336,37 +2866,3 @@ impl RenderBuffer { self.damage_queue.damage(&[rect]); } } - -fn modes_equal(a: &DrmModeInfo, b: &DrmModeInfo) -> bool { - a.clock == b.clock - && a.hdisplay == b.hdisplay - && a.hsync_start == b.hsync_start - && a.hsync_end == b.hsync_end - && a.htotal == b.htotal - && a.hskew == b.hskew - && a.vdisplay == b.vdisplay - && a.vsync_start == b.vsync_start - && a.vsync_end == b.vsync_end - && a.vtotal == b.vtotal - && a.vscan == b.vscan - && a.vrefresh == b.vrefresh - && a.flags == b.flags -} - -fn should_ignore(connector: &MetalConnector, dd: &ConnectorDisplayData) -> bool { - !connector.enabled.get() - || dd.connection != ConnectorStatus::Connected - || dd.non_desktop_effective -} - -#[derive(Error, Debug)] -enum HdrMetadataDiff { - #[error("Connector has undesired HDR metadata")] - Undesired, - #[error("Connector has no HDR metadata")] - No, - #[error("Could not retrieve current HDR metadata of connector")] - CouldNotRetrieve, - #[error("Connector has incompatible HDR metadata")] - Incompatible, -} diff --git a/src/backends/x.rs b/src/backends/x.rs index c55c87b7..d35cacbb 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -3,15 +3,19 @@ use { allocator::BufferObject, async_engine::{Phase, SpawnedFuture}, backend::{ - AXIS_120, AxisSource, Backend, BackendColorSpace, BackendDrmDevice, BackendEvent, - BackendTransferFunction, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, - DrmDeviceId, DrmEvent, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, - InputDeviceClickMethod, InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, - ScrollAxis, TransformMatrix, + AXIS_120, AxisSource, Backend, BackendConnectorState, BackendDrmDevice, BackendEvent, + Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, DrmEvent, + InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, + InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix, + transaction::{ + BackendAppliedConnectorTransaction, BackendConnectorTransaction, + BackendConnectorTransactionError, BackendConnectorTransactionType, + BackendConnectorTransactionTypeDyn, BackendPreparedConnectorTransaction, + }, }, cmm::cmm_primaries::Primaries, fixed::Fixed, - format::XRGB8888, + format::{Format, XRGB8888}, gfx_api::{AcquireSync, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync}, ifs::wl_output::OutputId, state::State, @@ -51,8 +55,10 @@ use { }, }, }, + ahash::AHashMap, jay_config::video::GfxApi, std::{ + any::Any, borrow::Cow, cell::{Cell, RefCell}, collections::VecDeque, @@ -120,6 +126,8 @@ pub enum XBackendError { XRGB8888, } +const FORMAT: &Format = XRGB8888; + pub async fn create(state: &Rc) -> Result, XBackendError> { let c = match Xcon::connect(state).await { Ok(c) => c, @@ -376,7 +384,7 @@ impl XBackend { ) -> Result<[XImage; 2], XBackendError> { let mut images = [None, None]; let formats = self.ctx.formats(); - let format = match formats.get(&XRGB8888.drm) { + let format = match formats.get(&FORMAT.drm) { Some(f) => f, None => return Err(XBackendError::XRGB8888), }; @@ -385,7 +393,7 @@ impl XBackend { &self.state.dma_buf_ids, width, height, - XRGB8888, + FORMAT, format.write_modifiers.keys(), GBM_BO_USE_RENDERING, )?; @@ -469,6 +477,22 @@ impl XBackend { cw.wid }; let images = self.create_images(window_id, WIDTH, HEIGHT).await?; + let state = BackendConnectorState { + serial: self.state.backend_connector_state_serials.next(), + enabled: true, + active: true, + mode: Mode { + width: WIDTH, + height: HEIGHT, + refresh_rate_millihz: 60_000, // TODO + }, + non_desktop_override: None, + vrr: false, + tearing: false, + format: FORMAT, + color_space: Default::default(), + transfer_function: Default::default(), + }; let output = Rc::new(XOutput { id: self.state.connector_ids.next(), backend: self.clone(), @@ -481,6 +505,7 @@ impl XBackend { next_image: Default::default(), cb: CloneCell::new(None), images, + state: Cell::new(state), }); { let class = "jay\0jay\0"; @@ -569,21 +594,15 @@ impl XBackend { format!("X-Window-{}", output.window), output.window.to_string(), )), - initial_mode: Mode { - width: output.width.get(), - height: output.height.get(), - refresh_rate_millihz: 60_000, // TODO - }, width_mm: output.width.get(), height_mm: output.height.get(), non_desktop: false, vrr_capable: false, transfer_functions: vec![], - transfer_function: BackendTransferFunction::Default, color_spaces: vec![], - color_space: BackendColorSpace::Default, primaries: Primaries::SRGB, luminance: None, + state: output.state.get(), })); output.changed(); self.present(output).await; @@ -962,11 +981,12 @@ impl XBackend { old.tex.set(new.tex.get()); old.pixmap.set(new.pixmap.get()); } - output.events.push(ConnectorEvent::ModeChanged(Mode { - width, - height, - refresh_rate_millihz: 60, // TODO - })); + let mut state = output.state.get(); + state.serial = self.state.backend_connector_state_serials.next(); + state.mode.width = width; + state.mode.height = height; + output.state.set(state); + output.events.push(ConnectorEvent::State(state)); output.changed(); } Ok(()) @@ -1035,6 +1055,7 @@ struct XOutput { next_image: NumCell, images: [XImage; 2], cb: CloneCell>>, + state: Cell, } struct XImage { @@ -1083,8 +1104,75 @@ impl Connector for XOutput { Some(self.backend.drm_device_id) } - fn set_mode(&self, _mode: Mode) { - log::warn!("X backend doesn't support changing the connector mode"); + fn effectively_locked(&self) -> bool { + // todo + true + } + + fn transaction_type(&self) -> Box { + Box::new(XTransactionType) + } + + fn create_transaction( + &self, + ) -> Result, BackendConnectorTransactionError> { + Ok(Box::new(XTransaction::default())) + } +} + +#[derive(Hash, Eq, PartialEq)] +struct XTransactionType; +impl BackendConnectorTransactionType for XTransactionType {} + +#[derive(Default)] +struct XTransaction { + connectors: AHashMap>, +} +impl XTransaction { + fn send_state(&self) { + for con in self.connectors.values() { + let mut state = con.state.get(); + state.serial = con.backend.state.backend_connector_state_serials.next(); + con.events.push(ConnectorEvent::State(state)); + } + } +} +impl BackendConnectorTransaction for XTransaction { + fn add( + &mut self, + connector: &Rc, + _change: BackendConnectorState, + ) -> Result<(), BackendConnectorTransactionError> { + let con = (connector.clone() as Rc) + .downcast::() + .map_err(|_| { + BackendConnectorTransactionError::UnsupportedConnectorType(connector.kernel_id()) + })?; + self.connectors.insert(con.id, con.clone()); + Ok(()) + } + fn prepare( + self: Box, + ) -> Result, BackendConnectorTransactionError> + { + Ok(self) + } +} +impl BackendPreparedConnectorTransaction for XTransaction { + fn apply( + self: Box, + ) -> Result, BackendConnectorTransactionError> { + self.send_state(); + Ok(self) + } +} +impl BackendAppliedConnectorTransaction for XTransaction { + fn commit(self: Box) { + // nothing + } + fn rollback(self: Box) -> Result<(), BackendConnectorTransactionError> { + self.send_state(); + Ok(()) } } diff --git a/src/compositor.rs b/src/compositor.rs index 1d7c85c2..edc3f916 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -4,7 +4,7 @@ use { crate::{ acceptor::{Acceptor, AcceptorError}, async_engine::{AsyncEngine, Phase, SpawnedFuture}, - backend::{self, Backend, BackendColorSpace, BackendTransferFunction, Connector}, + backend::{self, Backend, BackendConnectorState, BackendConnectorStateSerial, Connector}, backends::{ dummy::{DummyBackend, DummyOutput}, metal, x, @@ -26,6 +26,7 @@ use { dbus::Dbus, ei::ei_client::EiClients, forker, + format::XRGB8888, globals::Globals, ifs::{ jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts}, @@ -337,6 +338,7 @@ fn start_compositor2( toplevel_managers: Default::default(), node_at_tree: Default::default(), position_hint_requests: Default::default(), + backend_connector_state_serials: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -396,7 +398,7 @@ async fn start_compositor3(state: Rc, test_future: Option) { } state.update_ei_acceptor(); - let _geh = start_global_event_handlers(&state, &backend); + let _geh = start_global_event_handlers(&state); state.start_xwayland(); match backend.run().await { @@ -424,10 +426,7 @@ fn load_config( } } -fn start_global_event_handlers( - state: &Rc, - backend: &Rc, -) -> Vec> { +fn start_global_event_handlers(state: &Rc) -> Vec> { let eng = &state.eng; vec![ @@ -471,11 +470,7 @@ fn start_global_event_handlers( Phase::PostLayout, float_titles(state.clone()), ), - eng.spawn2( - "idle", - Phase::PostLayout, - idle(state.clone(), backend.clone()), - ), + eng.spawn2("idle", Phase::PostLayout, idle(state.clone())), eng.spawn2( "input, popup positioning", Phase::PostLayout, @@ -610,6 +605,23 @@ fn create_dummy_output(state: &Rc) { let connector = Rc::new(DummyOutput { id: state.connector_ids.next(), }) as Rc; + let mode = backend::Mode { + width: 0, + height: 0, + refresh_rate_millihz: 40_000, + }; + let backend_state = BackendConnectorState { + serial: BackendConnectorStateSerial::from_raw(0), + enabled: true, + active: false, + mode, + non_desktop_override: None, + vrr: false, + tearing: false, + format: XRGB8888, + color_space: Default::default(), + transfer_function: Default::default(), + }; let connector_data = Rc::new(ConnectorData { connector, handler: Cell::new(None), @@ -621,6 +633,7 @@ fn create_dummy_output(state: &Rc) { damage: Default::default(), needs_vblank_emulation: Cell::new(false), damage_intersect: Default::default(), + state: Cell::new(backend_state), }); let schedule = Rc::new(OutputSchedule::new( &state.ring, @@ -635,19 +648,12 @@ fn create_dummy_output(state: &Rc) { state, &connector_data, Vec::new(), - &backend::Mode { - width: 0, - height: 0, - refresh_rate_millihz: 40_000, - }, 0, 0, &output_id, &persistent_state, Vec::new(), - BackendTransferFunction::Default, Vec::new(), - BackendColorSpace::Default, Primaries::SRGB, None, )), diff --git a/src/config/handler.rs b/src/config/handler.rs index c3febba5..514c68fb 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -4,6 +4,7 @@ use { backend::{ self, BackendColorSpace, BackendTransferFunction, ConnectorId, DrmDeviceId, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, + transaction::BackendConnectorTransactionError, }, client::{Client, ClientId}, cmm::cmm_transfer_function::TransferFunction, @@ -1141,12 +1142,16 @@ impl ConfigProxyHandler { connector: Connector, mode: WireMode, ) -> Result<(), CphError> { - let connector = self.get_output(connector)?; - connector.connector.connector.set_mode(backend::Mode { - width: mode.width, - height: mode.height, - refresh_rate_millihz: mode.refresh_millihz, - }); + let connector = self.get_connector(connector)?; + connector + .modify_state(&self.state, |s| { + s.mode = backend::Mode { + width: mode.width, + height: mode.height, + refresh_rate_millihz: mode.refresh_millihz, + }; + }) + .map_err(CphError::ModifyConnectorState)?; Ok(()) } @@ -1265,7 +1270,9 @@ impl ConfigProxyHandler { return Err(CphError::UnknownFormat(format)); }; let connector = self.get_connector(connector)?; - connector.connector.set_fb_format(format); + connector + .modify_state(&self.state, |s| s.format = format) + .map_err(CphError::ModifyConnectorState)?; Ok(()) } @@ -1286,7 +1293,12 @@ impl ConfigProxyHandler { _ => return Err(CphError::UnknownTransferFunction(transfer_function)), }; let connector = self.get_connector(connector)?; - connector.connector.set_colors(bcs, btf); + connector + .modify_state(&self.state, |s| { + s.color_space = bcs; + s.transfer_function = btf; + }) + .map_err(CphError::ModifyConnectorState)?; Ok(()) } @@ -1447,7 +1459,11 @@ impl ConfigProxyHandler { enabled: bool, ) -> Result<(), CphError> { let connector = self.get_connector(connector)?; - connector.connector.set_enabled(enabled); + connector + .modify_state(&self.state, |s| { + s.enabled = enabled; + }) + .map_err(CphError::ModifyConnectorState)?; Ok(()) } @@ -3057,6 +3073,8 @@ enum CphError { InvalidRegex(#[source] regex::Error), #[error("Window matcher {0:?} does not exist")] WindowMatcherDoesNotExist(WindowMatcher), + #[error("Could not modify the connector state")] + ModifyConnectorState(#[source] BackendConnectorTransactionError), } trait WithRequestName { diff --git a/src/ifs/ext_session_lock_manager_v1.rs b/src/ifs/ext_session_lock_manager_v1.rs index 3caaf183..834324c5 100644 --- a/src/ifs/ext_session_lock_manager_v1.rs +++ b/src/ifs/ext_session_lock_manager_v1.rs @@ -60,6 +60,7 @@ impl ExtSessionLockManagerV1RequestHandler for ExtSessionLockManagerV1 { client: self.client.clone(), tracker: Default::default(), did_lock, + awaiting_locked: Cell::new(true), finished: Cell::new(false), version: self.version, }); @@ -75,7 +76,7 @@ impl ExtSessionLockManagerV1RequestHandler for ExtSessionLockManagerV1 { state.lock.lock.set(Some(new.clone())); state.tree_changed(); state.damage(state.root.extents.get()); - new.send_locked(); + new.check_locked(); } else { new.finish(); } diff --git a/src/ifs/ext_session_lock_v1.rs b/src/ifs/ext_session_lock_v1.rs index 352fa820..584a833c 100644 --- a/src/ifs/ext_session_lock_v1.rs +++ b/src/ifs/ext_session_lock_v1.rs @@ -17,12 +17,26 @@ pub struct ExtSessionLockV1 { pub client: Rc, pub tracker: Tracker, pub did_lock: bool, + pub awaiting_locked: Cell, pub finished: Cell, pub version: Version, } impl ExtSessionLockV1 { - pub fn send_locked(&self) { + pub fn check_locked(&self) { + if !self.awaiting_locked.get() { + return; + } + for output in self.client.state.outputs.lock().values() { + if !output.connector.connector.effectively_locked() { + return; + } + } + self.send_locked(); + self.awaiting_locked.set(false); + } + + fn send_locked(&self) { self.client.event(Locked { self_id: self.id }) } diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index e6746aca..2ab860d9 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -7,9 +7,9 @@ use { leaks::Tracker, object::{Object, Version}, scale::Scale, - state::{ConnectorData, DrmDevData, OutputData}, + state::{ConnectorData, DrmDevData, OutputData, State}, tree::{OutputNode, TearingMode, VrrMode}, - utils::{gfx_api_ext::GfxApiExt, transform_ext::TransformExt}, + utils::{errorfmt::ErrorFmt, gfx_api_ext::GfxApiExt, transform_ext::TransformExt}, wire::{JayRandrId, jay_randr::*}, }, jay_config::video::{ @@ -23,6 +23,7 @@ use { pub struct JayRandr { pub id: JayRandrId, pub client: Rc, + pub state: Rc, pub tracker: Tracker, pub version: Version, } @@ -39,6 +40,7 @@ impl JayRandr { Self { id, client: client.clone(), + state: client.state.clone(), tracker: Default::default(), version, } @@ -67,6 +69,7 @@ impl JayRandr { } fn send_connector(&self, data: &ConnectorData) { + let state = data.state.get(); self.client.event(Connector { self_id: self.id, id: data.connector.id().raw() as _, @@ -75,7 +78,7 @@ impl JayRandr { .as_ref() .map(|d| d.dev.id().raw() as _) .unwrap_or_default(), - enabled: data.connector.enabled() as _, + enabled: state.enabled as _, name: &data.name, }); let Some(output) = self.client.state.outputs.get(&data.connector.id()) else { @@ -347,14 +350,19 @@ impl JayRandrRequestHandler for JayRandr { } fn set_mode(&self, req: SetMode, _slf: &Rc) -> Result<(), Self::Error> { - let Some(c) = self.get_output(req.output) else { + let Some(c) = self.get_connector(req.output) else { return Ok(()); }; - c.connector.connector.set_mode(backend::Mode { - width: req.width, - height: req.height, - refresh_rate_millihz: req.refresh_rate_millihz, + let res = c.modify_state(&self.state, |s| { + s.mode = backend::Mode { + width: req.width, + height: req.height, + refresh_rate_millihz: req.refresh_rate_millihz, + }; }); + if let Err(e) = res { + self.send_error(&format!("Could not modify connector mode: {}", ErrorFmt(e))); + } Ok(()) } @@ -378,7 +386,10 @@ impl JayRandrRequestHandler for JayRandr { let Some(c) = self.get_connector(req.output) else { return Ok(()); }; - c.connector.set_enabled(req.enabled != 0); + let res = c.modify_state(&self.state, |s| s.enabled = req.enabled != 0); + if let Err(e) = res { + self.send_error(&format!("Could not en/disable connector: {}", ErrorFmt(e))); + } Ok(()) } @@ -391,7 +402,13 @@ impl JayRandrRequestHandler for JayRandr { 1 => Some(false), _ => Some(true), }; - c.connector.set_non_desktop_override(non_desktop); + c.connector.before_non_desktop_override_update(non_desktop); + let res = c.modify_state(&self.state, |s| { + s.non_desktop_override = non_desktop; + }); + if let Err(e) = res { + self.send_error(&format!("Could not change non-desktop override: {}", e)); + } Ok(()) } @@ -439,10 +456,13 @@ impl JayRandrRequestHandler for JayRandr { let Some(&format) = named_formats().get(req.format) else { return Err(JayRandrError::UnknownFormat(req.format.to_string())); }; - let Some(c) = self.get_output_node(req.output) else { + let Some(c) = self.get_connector(req.output) else { return Ok(()); }; - c.global.connector.connector.set_fb_format(format); + let res = c.modify_state(&self.state, |s| s.format = format); + if let Err(e) = res { + self.send_error(&format!("Could not modify connector format: {}", e)); + } Ok(()) } @@ -478,7 +498,16 @@ impl JayRandrRequestHandler for JayRandr { let Some(c) = self.get_connector(req.output) else { return Ok(()); }; - c.connector.set_colors(cs, tf); + let res = c.modify_state(&self.state, |s| { + s.color_space = cs; + s.transfer_function = tf; + }); + if let Err(e) = res { + self.send_error(&format!( + "Could not modify connector colors: {}", + ErrorFmt(e), + )); + } Ok(()) } diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index 1147aea4..dc0dc28b 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -161,22 +161,20 @@ impl WlOutputGlobal { state: &Rc, connector: &Rc, modes: Vec, - mode: &backend::Mode, width_mm: i32, height_mm: i32, output_id: &Rc, persistent_state: &Rc, transfer_functions: Vec, - btf: BackendTransferFunction, color_spaces: Vec, - bcs: BackendColorSpace, primaries: Primaries, luminance: Option, ) -> Self { let (x, y) = persistent_state.pos.get(); let scale = persistent_state.scale.get(); + let connector_state = connector.state.get(); let (width, height) = calculate_logical_size( - (mode.width, mode.height), + (connector_state.mode.width, connector_state.mode.height), persistent_state.transform.get(), scale, ); @@ -186,8 +184,8 @@ impl WlOutputGlobal { connector: connector.clone(), pos: Cell::new(Rect::new_sized(x, y, width, height).unwrap()), output_id: output_id.clone(), - mode: Cell::new(*mode), - refresh_nsec: Cell::new(mode.refresh_nsec()), + mode: Cell::new(connector_state.mode), + refresh_nsec: Cell::new(connector_state.mode.refresh_nsec()), modes, formats: CloneCell::new(Rc::new(vec![])), format: Cell::new(XRGB8888), @@ -203,8 +201,8 @@ impl WlOutputGlobal { persistent: persistent_state.clone(), opt: Default::default(), damage_matrix: Default::default(), - btf: Cell::new(btf), - bcs: Cell::new(bcs), + btf: Cell::new(connector_state.transfer_function), + bcs: Cell::new(connector_state.color_space), color_description: CloneCell::new(state.color_manager.srgb_srgb().clone()), linear_color_description: CloneCell::new(state.color_manager.srgb_linear().clone()), color_description_listeners: Default::default(), diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 7fa63494..e755f151 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -3,15 +3,21 @@ use { allocator::{Allocator, AllocatorError}, async_engine::SpawnedFuture, backend::{ - AxisSource, Backend, BackendColorSpace, BackendEvent, BackendTransferFunction, - Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, InputDevice, - InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, - InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix, + AxisSource, Backend, BackendConnectorState, BackendEvent, Connector, ConnectorEvent, + ConnectorId, ConnectorKernelId, DrmDeviceId, InputDevice, InputDeviceAccelProfile, + InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, InputEvent, KeyState, + Mode, MonitorInfo, ScrollAxis, TransformMatrix, + transaction::{ + BackendAppliedConnectorTransaction, BackendConnectorTransaction, + BackendConnectorTransactionError, BackendConnectorTransactionType, + BackendConnectorTransactionTypeDyn, BackendPreparedConnectorTransaction, + }, }, cmm::cmm_primaries::Primaries, compositor::TestFuture, drm_feedback::DrmFeedback, fixed::Fixed, + format::XRGB8888, gfx_api::GfxError, gfx_apis::create_vulkan_allocator, ifs::wl_output::OutputId, @@ -29,8 +35,9 @@ use { gbm::{GbmDevice, GbmError}, }, }, + ahash::AHashMap, bstr::ByteSlice, - std::{cell::Cell, error::Error, io, os::unix::ffi::OsStrExt, pin::Pin, rc::Rc}, + std::{any::Any, cell::Cell, error::Error, io, os::unix::ffi::OsStrExt, pin::Pin, rc::Rc}, thiserror::Error, uapi::c, }; @@ -63,7 +70,6 @@ pub struct TestBackend { pub default_mouse: Rc, pub default_kb: Rc, pub render_context_installed: Cell, - pub idle: TEEH, } impl TestBackend { @@ -77,6 +83,7 @@ impl TestBackend { }, events: Default::default(), feedback: Default::default(), + idle: Default::default(), }); let default_mouse = Rc::new(TestBackendMouse { common: TestInputDeviceCommon { @@ -125,17 +132,26 @@ impl TestBackend { model: "TestConnector".to_string(), serial_number: default_connector.id.to_string(), }), - initial_mode: mode, width_mm: 80, height_mm: 60, non_desktop: false, vrr_capable: false, transfer_functions: vec![], - transfer_function: BackendTransferFunction::Default, color_spaces: vec![], - color_space: BackendColorSpace::Default, primaries: Primaries::SRGB, luminance: None, + state: BackendConnectorState { + serial: state.backend_connector_state_serials.next(), + enabled: true, + active: true, + mode, + non_desktop_override: None, + vrr: false, + tearing: false, + format: XRGB8888, + color_space: Default::default(), + transfer_function: Default::default(), + }, }; Self { state: state.clone(), @@ -145,7 +161,6 @@ impl TestBackend { default_mouse, default_kb, render_context_installed: Cell::new(false), - idle: Rc::new(Default::default()), } } @@ -291,10 +306,6 @@ impl Backend for TestBackend { let _ = vtnr; } - fn set_idle(&self, idle: bool) { - self.idle.push(idle); - } - fn supports_presentation_feedback(&self) -> bool { true } @@ -305,6 +316,7 @@ pub struct TestConnector { pub kernel_id: ConnectorKernelId, pub events: OnChange, pub feedback: CloneCell>>, + pub idle: TEEH, } impl Connector for TestConnector { @@ -332,13 +344,70 @@ impl Connector for TestConnector { None } - fn set_mode(&self, _mode: Mode) { + fn effectively_locked(&self) -> bool { // todo + true } fn drm_feedback(&self) -> Option> { self.feedback.get() } + + fn transaction_type(&self) -> Box { + Box::new(TestBackendTransactionType) + } + + fn create_transaction( + &self, + ) -> Result, BackendConnectorTransactionError> { + Ok(Box::new(TestBackendTransaction::default())) + } +} + +#[derive(Hash, Eq, PartialEq)] +struct TestBackendTransactionType; +impl BackendConnectorTransactionType for TestBackendTransactionType {} + +#[derive(Default)] +struct TestBackendTransaction { + connectors: AHashMap, BackendConnectorState)>, +} +impl BackendConnectorTransaction for TestBackendTransaction { + fn add( + &mut self, + connector: &Rc, + change: BackendConnectorState, + ) -> Result<(), BackendConnectorTransactionError> { + let c = (connector.clone() as Rc) + .downcast::() + .unwrap(); + self.connectors.insert(c.id(), (c, change)); + Ok(()) + } + fn prepare( + self: Box, + ) -> Result, BackendConnectorTransactionError> + { + Ok(self) + } +} +impl BackendPreparedConnectorTransaction for TestBackendTransaction { + fn apply( + self: Box, + ) -> Result, BackendConnectorTransactionError> { + for (c, s) in self.connectors.values() { + c.idle.push(!s.active); + } + Ok(self) + } +} +impl BackendAppliedConnectorTransaction for TestBackendTransaction { + fn commit(self: Box) { + // nothing + } + fn rollback(self: Box) -> Result<(), BackendConnectorTransactionError> { + unimplemented!() + } } pub struct TestMouseClick { diff --git a/src/it/tests/t0034_workspace_restoration.rs b/src/it/tests/t0034_workspace_restoration.rs index 2065c206..97e63050 100644 --- a/src/it/tests/t0034_workspace_restoration.rs +++ b/src/it/tests/t0034_workspace_restoration.rs @@ -1,10 +1,10 @@ use { crate::{ backend::{ - BackendColorSpace, BackendEvent, BackendTransferFunction, ConnectorEvent, - ConnectorKernelId, Mode, MonitorInfo, + BackendConnectorState, BackendEvent, ConnectorEvent, ConnectorKernelId, MonitorInfo, }, cmm::cmm_primaries::Primaries, + format::XRGB8888, ifs::wl_output::OutputId, it::{test_backend::TestConnector, test_error::TestResult, testrun::TestRun}, video::drm::ConnectorType, @@ -34,6 +34,7 @@ async fn test(run: Rc) -> TestResult { }, events: Default::default(), feedback: Default::default(), + idle: Default::default(), }); let new_monitor_info = MonitorInfo { modes: vec![], @@ -43,21 +44,26 @@ async fn test(run: Rc) -> TestResult { model: "jay second connector".to_string(), serial_number: "".to_string(), }), - initial_mode: Mode { - width: 400, - height: 400, - refresh_rate_millihz: 60000, - }, width_mm: 0, height_mm: 0, non_desktop: false, vrr_capable: false, transfer_functions: vec![], - transfer_function: BackendTransferFunction::Default, color_spaces: vec![], - color_space: BackendColorSpace::Default, primaries: Primaries::SRGB, luminance: None, + state: BackendConnectorState { + serial: run.state.backend_connector_state_serials.next(), + enabled: true, + active: true, + mode: Default::default(), + non_desktop_override: None, + vrr: false, + tearing: false, + format: XRGB8888, + color_space: Default::default(), + transfer_function: Default::default(), + }, }; run.backend .state diff --git a/src/it/tests/t0036_idle.rs b/src/it/tests/t0036_idle.rs index 89f585d2..830d83e1 100644 --- a/src/it/tests/t0036_idle.rs +++ b/src/it/tests/t0036_idle.rs @@ -14,7 +14,7 @@ async fn test(run: Rc) -> TestResult { run.cfg.set_idle(Duration::from_micros(100))?; run.cfg.set_idle_grace_period(Duration::from_secs(0))?; - let idle = run.backend.idle.expect()?; + let idle = ds.connector.idle.expect()?; tassert!(idle.next().is_err()); run.state.wheel.timeout(3).await?; diff --git a/src/macros.rs b/src/macros.rs index 6e958a54..5c442c26 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -205,7 +205,7 @@ macro_rules! linear_ids { } } - #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct $id($ty); impl $id { diff --git a/src/state.rs b/src/state.rs index cd6ddc20..b8e3dfad 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,9 +3,10 @@ use { acceptor::Acceptor, async_engine::{AsyncEngine, SpawnedFuture}, backend::{ - Backend, BackendDrmDevice, BackendEvent, Connector, ConnectorId, ConnectorIds, - DrmDeviceId, DrmDeviceIds, HardwareCursorUpdate, InputDevice, InputDeviceGroupIds, - InputDeviceId, InputDeviceIds, MonitorInfo, + Backend, BackendConnectorState, BackendConnectorStateSerials, BackendDrmDevice, + BackendEvent, Connector, ConnectorId, ConnectorIds, DrmDeviceId, DrmDeviceIds, + HardwareCursorUpdate, InputDevice, InputDeviceGroupIds, InputDeviceId, InputDeviceIds, + MonitorInfo, transaction::BackendConnectorTransactionError, }, backends::dummy::DummyBackend, cli::RunArgs, @@ -255,6 +256,7 @@ pub struct State { pub caps_thread: Option, pub node_at_tree: RefCell>, pub position_hint_requests: AsyncQueue, + pub backend_connector_state_serials: BackendConnectorStateSerials, } // impl Drop for State { @@ -376,6 +378,7 @@ pub struct ConnectorData { pub damage: RefCell>, pub needs_vblank_emulation: Cell, pub damage_intersect: Cell, + pub state: Cell, } pub struct OutputData { @@ -403,6 +406,31 @@ impl ConnectorData { self.connector.damage(); } } + + pub fn modify_state( + &self, + state: &State, + f: impl FnOnce(&mut BackendConnectorState), + ) -> Result<(), BackendConnectorTransactionError> { + let old = self.state.get(); + let mut s = old; + f(&mut s); + if old == s { + return Ok(()); + } + s.serial = state.backend_connector_state_serials.next(); + let mut tran = self.connector.create_transaction()?; + tran.add(&self.connector, s)?; + tran.prepare()?.apply()?.commit(); + if let Some(output) = state.outputs.get(&self.connector.id()) + && let Some(node) = &output.node + { + node.update_state(s); + } else { + self.state.set(s); + } + Ok(()) + } } impl DrmDevData { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index cd714ef4..9b9b752b 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -1,6 +1,10 @@ use { crate::{ - backend::{Connector, ConnectorEvent, ConnectorId, MonitorInfo}, + backend::{ + BackendConnectorState, BackendConnectorStateSerial, Connector, ConnectorEvent, + ConnectorId, MonitorInfo, + }, + format::XRGB8888, globals::GlobalName, ifs::{ jay_tray_v1::JayTrayV1Global, @@ -22,6 +26,18 @@ pub fn handle(state: &Rc, connector: &Rc) { _ => panic!("connector's drm device does not exist"), }; } + let backend_state = BackendConnectorState { + serial: BackendConnectorStateSerial::from_raw(0), + enabled: true, + active: false, + mode: Default::default(), + non_desktop_override: None, + vrr: false, + tearing: false, + format: XRGB8888, + color_space: Default::default(), + transfer_function: Default::default(), + }; let id = connector.id(); let data = Rc::new(ConnectorData { connector: connector.clone(), @@ -34,6 +50,7 @@ pub fn handle(state: &Rc, connector: &Rc) { damage: Default::default(), needs_vblank_emulation: Cell::new(false), damage_intersect: Default::default(), + state: Cell::new(backend_state), }); if let Some(dev) = drm_dev { dev.connectors.set(id, data.clone()); @@ -88,6 +105,10 @@ impl ConnectorHandler { async fn handle_connected(&self, info: MonitorInfo) { log::info!("Connector {} connected", self.data.connector.kernel_id()); self.data.connected.set(true); + let old_state = self.data.state.get(); + if old_state.serial < info.state.serial { + self.data.state.set(info.state); + } let name = self.state.globals.name(); if info.non_desktop { self.handle_non_desktop_connected(info).await; @@ -132,15 +153,12 @@ impl ConnectorHandler { &self.state, &self.data, info.modes.clone(), - &info.initial_mode, info.width_mm, info.height_mm, &output_id, &desired_state, info.transfer_functions.clone(), - info.transfer_function, info.color_spaces.clone(), - info.color_space, info.primaries, info.luminance, )); @@ -268,18 +286,11 @@ impl ConnectorHandler { on.hardware_cursor.set(hc); self.state.refresh_hardware_cursors(); } - ConnectorEvent::ModeChanged(mode) => { - on.update_mode(mode); - } - ConnectorEvent::VrrChanged(enabled) => { - on.schedule.set_vrr_enabled(enabled); - } - ConnectorEvent::FormatsChanged(formats, format) => { + ConnectorEvent::FormatsChanged(formats) => { on.global.formats.set(formats); - on.global.format.set(format); } - ConnectorEvent::ColorsChanged(bcs, btf) => { - on.update_btf_and_bcs(btf, bcs); + ConnectorEvent::State(state) => { + on.update_state(state); } ev => unreachable!("received unexpected event {:?}", ev), } diff --git a/src/tasks/idle.rs b/src/tasks/idle.rs index 6477c80a..b576b4bc 100644 --- a/src/tasks/idle.rs +++ b/src/tasks/idle.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::Backend, + backend::transaction::{BackendConnectorTransactionError, ConnectorTransaction}, state::State, utils::{ errorfmt::ErrorFmt, @@ -12,7 +12,7 @@ use { uapi::c, }; -pub async fn idle(state: Rc, backend: Rc) { +pub async fn idle(state: Rc) { let timer = match TimerFd::new(c::CLOCK_MONOTONIC) { Ok(t) => t, Err(e) => { @@ -24,7 +24,6 @@ pub async fn idle(state: Rc, backend: Rc) { state.idle.timeout_changed.set(true); let mut idle = Idle { state, - backend, timer, idle: false, dead: false, @@ -36,7 +35,6 @@ pub async fn idle(state: Rc, backend: Rc) { struct Idle { state: Rc, - backend: Rc, timer: TimerFd, idle: bool, dead: bool, @@ -71,7 +69,7 @@ impl Idle { if let Some(config) = self.state.config.get() { config.idle(); } - self.backend.set_idle(true); + self.set_idle(true); self.idle = true; } } else if since >= timeout { @@ -110,7 +108,7 @@ impl Idle { self.last_input = now(); self.set_in_grace_period(false); if self.idle { - self.backend.set_idle(false); + self.set_idle(false); self.idle = false; self.program_timer(); } @@ -127,6 +125,27 @@ impl Idle { self.dead = true; } } + + fn set_idle(&self, idle: bool) { + if let Err(e) = self.try_set_idle(idle) { + log::error!("Could not change idle status of backend: {}", ErrorFmt(e)) + } + if let Some(lock) = self.state.lock.lock.get() { + lock.check_locked(); + } + } + + fn try_set_idle(&self, idle: bool) -> Result<(), BackendConnectorTransactionError> { + let mut tran = ConnectorTransaction::default(); + for connector in self.state.connectors.lock().values() { + let mut state = connector.state.get(); + state.active = !idle; + tran.add(&connector.connector, state)?; + } + tran.prepare()?.apply()?.commit(); + self.state.set_backend_idle(idle); + Ok(()) + } } fn now() -> c::timespec { diff --git a/src/tree/output.rs b/src/tree/output.rs index eddb7b74..924cd0f5 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -1,6 +1,9 @@ use { crate::{ - backend::{BackendColorSpace, BackendTransferFunction, HardwareCursor, KeyState, Mode}, + backend::{ + BackendColorSpace, BackendConnectorState, BackendTransferFunction, HardwareCursor, + KeyState, Mode, + }, client::ClientId, cmm::cmm_description::ColorDescription, cursor::KnownCursor, @@ -207,10 +210,14 @@ impl OutputNode { seq: u64, flags: u32, vrr: bool, + locked: bool, ) { for listener in self.presentation_event.iter() { listener.presented(self, tv_sec, tv_nsec, refresh, seq, flags, vrr); } + if locked && let Some(lock) = self.state.lock.lock.get() { + lock.check_locked() + } } pub fn update_exclusive_zones(self: &Rc) { @@ -842,7 +849,23 @@ impl OutputNode { self.state.tree_changed(); } - pub fn update_btf_and_bcs(&self, btf: BackendTransferFunction, bcs: BackendColorSpace) { + pub fn update_state(self: &Rc, state: BackendConnectorState) { + let old = self.global.connector.state.get(); + if old.serial >= state.serial { + return; + } + self.global.connector.state.set(state); + self.update_btf_and_bcs(state.transfer_function, state.color_space); + if old.vrr != state.vrr { + self.schedule.set_vrr_enabled(state.vrr); + } + if old.mode != state.mode { + self.update_mode(state.mode); + } + self.global.format.set(state.format); + } + + fn update_btf_and_bcs(&self, btf: BackendTransferFunction, bcs: BackendColorSpace) { let old_btf = self.global.btf.replace(btf); let old_bcs = self.global.bcs.replace(bcs); if (old_btf, old_bcs) == (btf, bcs) { @@ -1067,7 +1090,13 @@ impl OutputNode { true } }; - self.global.connector.connector.set_vrr_enabled(enabled); + let res = self + .global + .connector + .modify_state(&self.state, |s| s.vrr = enabled); + if let Err(e) = res { + log::error!("Could not set vrr mode: {}", e); + } } fn update_tearing(&self) { @@ -1094,7 +1123,13 @@ impl OutputNode { true } }; - self.global.connector.connector.set_tearing_enabled(enabled); + let res = self + .global + .connector + .modify_state(&self.state, |s| s.tearing = enabled); + if let Err(e) = res { + log::error!("Could not set tearing mode: {}", e); + } } pub fn tile_drag_destination( diff --git a/src/utils/binary_search_map.rs b/src/utils/binary_search_map.rs index 0976e02b..e6f2865a 100644 --- a/src/utils/binary_search_map.rs +++ b/src/utils/binary_search_map.rs @@ -147,6 +147,10 @@ impl BinarySearchMap { BinarySearchMapMutIterMut { pos: 0, map: self } } + pub fn values_mut<'a>(&'a mut self) -> impl Iterator + 'a { + self.iter_mut().map(|(_, v)| v) + } + pub fn remove_if bool>(&mut self, mut f: F) { let mut i = 0; while i < self.m.len() { diff --git a/src/video/drm.rs b/src/video/drm.rs index 97e466db..b3734001 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -710,7 +710,7 @@ pub trait DrmObject { macro_rules! drm_obj { ($name:ident, $ty:expr) => { #[repr(transparent)] - #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default, Ord, PartialOrd)] pub struct $name(pub u32); impl DrmObject for $name { @@ -929,11 +929,6 @@ pub struct hdr_metadata_infoframe { } impl DrmModeInfo { - pub fn create_blob(&self, master: &Rc) -> Result { - let raw = self.to_raw(); - master.create_blob(&raw) - } - pub fn to_raw(&self) -> drm_mode_modeinfo { let mut name = [0u8; DRM_DISPLAY_MODE_LEN]; let len = name.len().min(self.name.len()); @@ -1006,7 +1001,6 @@ pub struct ObjectChange<'a> { } impl Change { - #[expect(dead_code)] pub fn test(&self, flags: u32) -> Result<(), DrmError> { mode_atomic( self.master.raw(), @@ -1031,7 +1025,15 @@ impl Change { ) } - pub fn change_object(&mut self, obj: T, f: F) + pub fn is_empty(&self) -> bool { + self.values.is_empty() + } + + pub fn is_not_empty(&self) -> bool { + !self.is_empty() + } + + pub fn change_object(&mut self, obj: T, f: F) -> bool where T: DrmObject, F: FnOnce(&mut ObjectChange), @@ -1047,6 +1049,9 @@ impl Change { self.objects.push(obj.id()); self.object_lengths.push(new); } + true + } else { + false } } } diff --git a/src/video/drm/sys.rs b/src/video/drm/sys.rs index 2f3a53b4..e19903c6 100644 --- a/src/video/drm/sys.rs +++ b/src/video/drm/sys.rs @@ -693,6 +693,7 @@ pub fn mode_getencoder(fd: c::c_int, encoder_id: u32) -> Result Date: Sat, 12 Jul 2025 12:02:27 +0200 Subject: [PATCH 6/6] metal: don't treat disabled connectors as disconnected --- src/backends/metal/transaction.rs | 4 +--- src/backends/metal/video.rs | 7 ++++--- src/cli/randr.rs | 11 +++++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/backends/metal/transaction.rs b/src/backends/metal/transaction.rs index 40047b0a..abe1321f 100644 --- a/src/backends/metal/transaction.rs +++ b/src/backends/metal/transaction.rs @@ -1013,7 +1013,6 @@ impl MetalDeviceTransactionWithChange { mem::swap(o, &mut plane.new); } for (_, connector) in &mut slf.connectors { - let is_enabled; let is_connected; let is_non_desktop; { @@ -1021,7 +1020,6 @@ impl MetalDeviceTransactionWithChange { mem::swap(&mut dd.drm_state, &mut connector.new); mem::swap(&mut *dd.persistent.state.borrow_mut(), &mut connector.state); dd.update_cached_fields(&slf.dev.dev); - is_enabled = dd.persistent.state.borrow().enabled; is_non_desktop = dd.non_desktop_effective; is_connected = dd.connection == ConnectorStatus::Connected; } @@ -1052,7 +1050,7 @@ impl MetalDeviceTransactionWithChange { connector.obj.send_connected(); } } - } else if is_enabled && is_connected && is_non_desktop { + } else if is_connected && is_non_desktop { match connector.obj.frontend_state.get() { FrontState::Removed | FrontState::Unavailable diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 62963ab9..4b1aaaed 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -388,7 +388,8 @@ impl ConnectorDisplayData { fn update_non_desktop_effective(&mut self) { let state = &*self.persistent.state.borrow(); - self.non_desktop_effective = state.non_desktop_override.unwrap_or(self.non_desktop); + self.non_desktop_effective = + !state.enabled || state.non_desktop_override.unwrap_or(self.non_desktop); } pub fn update_cached_fields(&mut self, dev: &MetalDrmDevice) { @@ -668,8 +669,8 @@ impl MetalConnector { } fn connected(&self) -> bool { - let dd = self.display.borrow_mut(); - dd.persistent.state.borrow().enabled && dd.connection == ConnectorStatus::Connected + let dd = self.display.borrow(); + dd.connection == ConnectorStatus::Connected } pub fn update_drm_feedback(&self) { diff --git a/src/cli/randr.rs b/src/cli/randr.rs index b784a2fa..09449dbd 100644 --- a/src/cli/randr.rs +++ b/src/cli/randr.rs @@ -853,10 +853,11 @@ impl Randr { fn print_connector(&self, connector: &Connector, modes: bool, formats: bool) { println!(" {}:", connector.name); + if !connector.enabled { + println!(" disabled"); + } let Some(o) = &connector.output else { - if !connector.enabled { - println!(" disabled"); - } else { + if connector.enabled { println!(" disconnected"); } return; @@ -869,7 +870,9 @@ impl Randr { o.width_mm, o.height_mm ); if o.non_desktop { - println!(" non-desktop"); + if connector.enabled { + println!(" non-desktop"); + } return; } println!(" VRR capable: {}", o.vrr_capable);