use { crate::{ allocator::BufferObject, backend::{ BackendColorSpace, BackendConnectorState, BackendEotfs, BackendGammaLut, Connector, ConnectorEvent, transaction::{ BackendAppliedConnectorTransaction, BackendConnectorTransaction, BackendConnectorTransactionError, BackendPreparedConnectorTransaction, }, }, backends::metal::{ allocator::RenderBuffer, video::{ FrontState, MetalConnector, MetalCrtc, MetalDrmDeviceData, MetalPlane, PlaneType, }, }, format::{ARGB8888, Format}, gfx_api::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::IsntHashMapExt, 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, pub gamma_lut: Option>, pub gamma_lut_blob_id: DrmBlob, pub gamma_lut_blob: Option>, } #[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().clone(), 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 syncs = 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 dev_ctx = slf.dev.dev.ctx.get(); let render_ctx = slf.dev.dev.backend.ctx.get(); let render_ctx = render_ctx.as_ref().unwrap_or(&dev_ctx); 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); break; } } 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() }; if crtc.new.gamma_lut != state.gamma_lut { if let Some(gamma_lut) = &state.gamma_lut { let blob = slf .dev .dev .master .create_blob(&gamma_lut.gamma_lut as &[_]) .map_err(BackendConnectorTransactionError::CreateGammaLutBlob)?; crtc.new.gamma_lut_blob_id = blob.id(); crtc.new.gamma_lut_blob = Some(Rc::new(blob)); } else { crtc.new.gamma_lut_blob_id = DrmBlob::NONE; crtc.new.gamma_lut_blob = None; } crtc.new.gamma_lut = state.gamma_lut.clone(); } for plane_id in [&mut crtc_planes.primary, &mut crtc_planes.cursor] { if plane_id.is_none() { continue; } let plane = slf.planes.get_mut(plane_id).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!(); } 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; connector.changed.set(true); let is_cursor = plane.obj.ty == PlaneType::Cursor; let res = slf .dev .dev .backend .create_scanout_buffers( &slf.dev.dev, format, modifiers, width, height, render_ctx, is_cursor, ) .map_err(|e| { BackendConnectorTransactionError::AllocateScanoutBuffers( connector.obj.kernel_id(), Box::new(e), ) }); if let Err(e) = &res && is_cursor { log::error!( "Could not allocate buffers for cursor plane of {}: {}", connector.obj.kernel_id(), ErrorFmt(e), ); plane.new = DrmPlaneState::default(); unused_planes.insert(*plane_id, ()); *plane_id = DrmPlane::NONE; continue; } let res = res?; if let Some(method) = res[0].prime.method() { let plane = match plane.obj.ty { PlaneType::Overlay => "overlay", PlaneType::Primary => "primary", PlaneType::Cursor => "cursor", }; let dev = slf.dev.dev.devnode.as_bytes().as_bstr(); let connector = connector.obj.kernel_id(); log::info!( "Using prime method {method} for {dev} {connector} ({plane})", ); } let buffers = Rc::new(res); 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) && may_show_current_fb { match prev.copy_to_new(new_buffer, &cd) { Ok(sf) => Ok(sf), Err(e) => { log::warn!("Could not copy from old buffer: {}", ErrorFmt(e)); new_buffer.clear(&cd) } } } else { new_buffer.clear(&cd) }; match res { Ok(sf) => syncs.extend(sf), Err(e) => { log::warn!("Could not clear new 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.clear(&cd); match res { Ok(sf) => { buffer.locked.set(true); syncs.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.eotf { BackendEotfs::Default => {} BackendEotfs::Pq => { if !dd.supports_pq { return Err(BackendConnectorTransactionError::EotfNotSupported( connector.obj.kernel_id(), state.eotf, )); } } } 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.eotf == BackendEotfs::Default { None } else { Some(hdr_output_metadata::from_eotf(state.eotf.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 sync in syncs { sync.signaled_blocking("transaction"); } 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); } if let Some(gamma_lut) = crtc.obj.gamma_lut && n.gamma_lut_blob_id != o.gamma_lut_blob_id { log_change!(o, n, gamma_lut_blob_id); c.change(gamma_lut, n.gamma_lut_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_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_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_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() } }