diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index a4353a7..1b9288f 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -330,15 +330,6 @@ impl UdevHandlerImpl { .collect::>(); 'outer: for encoder_info in encoder_infos { for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) { - // TODO cursor - let primary = match device.planes(&crtc) { - Ok(planes) => planes.primary, - Err(err) => { - warn!(logger, "Failed to enumerate planes: {}", err); - continue; - } - }; - if let Entry::Vacant(entry) = backends.entry(crtc) { info!( logger, @@ -363,7 +354,6 @@ impl UdevHandlerImpl { }; let mut surface = match device.create_surface( crtc, - primary, connector_info.modes()[0], &[connector_info.handle()], ) { diff --git a/examples/raw_drm.rs b/examples/raw_drm.rs index 8847bfa..e47ff1f 100644 --- a/examples/raw_drm.rs +++ b/examples/raw_drm.rs @@ -79,13 +79,10 @@ fn main() { // Assuming we found a good connector and loaded the info into `connector_info` let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.) - // We just use one plane, the primary one - let plane = device.planes(&crtc).unwrap().primary; - // Initialize the hardware backend let surface = Rc::new( device - .create_surface(crtc, plane, mode, &[connector_info.handle()]) + .create_surface(crtc, mode, &[connector_info.handle()]) .unwrap(), ); @@ -125,7 +122,7 @@ fn main() { .unwrap(); // Start rendering - surface.commit(framebuffer, true).unwrap(); + surface.commit([(framebuffer, surface.plane())].iter(), true).unwrap(); // Run event_loop.run(None, &mut (), |_| {}).unwrap(); @@ -161,7 +158,7 @@ impl DeviceHandler for DrmHandlerImpl { } let fb = self.current.userdata().unwrap(); - self.surface.page_flip(fb, true).unwrap(); + self.surface.page_flip([(fb, self.surface.plane())].iter(), true).unwrap(); } fn error(&mut self, error: DrmError) { diff --git a/src/backend/drm/device/atomic.rs b/src/backend/drm/device/atomic.rs index 9dca352..fbaa044 100644 --- a/src/backend/drm/device/atomic.rs +++ b/src/backend/drm/device/atomic.rs @@ -181,6 +181,11 @@ impl AtomicDrmDevice { dev: self.fd.dev_path(), source, })?; + let plane_handles = self.fd.plane_handles().map_err(|source| Error::Access { + errmsg: "Error loading drm plane resources", + dev: self.fd.dev_path(), + source, + })?; // Disable all connectors (otherwise we might run into conflicting commits when restarting the rendering loop) let mut req = AtomicModeReq::new(); @@ -194,6 +199,26 @@ impl AtomicDrmDevice { .expect("Unknown property CRTC_ID"); req.add_property(*conn, *prop, property::Value::CRTC(None)); } + // Disable all planes + for plane in plane_handles.planes() { + let prop = self + .prop_mapping + .3 + .get(&plane) + .expect("Unknown handle") + .get("CRTC_ID") + .expect("Unknown property CRTC_ID"); + req.add_property(*plane, *prop, property::Value::CRTC(None)); + + let prop = self + .prop_mapping + .3 + .get(&plane) + .expect("Unknown handle") + .get("FB_ID") + .expect("Unknown property FB_ID"); + req.add_property(*plane, *prop, property::Value::Framebuffer(None)); + } // A crtc without a connector has no mode, we also need to reset that. // Otherwise the commit will not be accepted. for crtc in res_handles.crtcs() { diff --git a/src/backend/drm/device/legacy.rs b/src/backend/drm/device/legacy.rs index edf9143..df05346 100644 --- a/src/backend/drm/device/legacy.rs +++ b/src/backend/drm/device/legacy.rs @@ -89,6 +89,8 @@ impl LegacyDrmDevice { set_connector_state(&*self.fd, res_handles.connectors().iter().copied(), false)?; for crtc in res_handles.crtcs() { + #[allow(deprecated)] + let _ = self.fd.set_cursor(*crtc, Option::<&drm::control::dumbbuffer::DumbBuffer>::None); // null commit (necessary to trigger removal on the kernel side with the legacy api.) self.fd .set_crtc(*crtc, None, (0, 0), &[], None) diff --git a/src/backend/drm/device/mod.rs b/src/backend/drm/device/mod.rs index 79c17b5..a45c13c 100644 --- a/src/backend/drm/device/mod.rs +++ b/src/backend/drm/device/mod.rs @@ -1,6 +1,4 @@ use std::cell::RefCell; -use std::collections::HashSet; -use std::convert::TryFrom; use std::os::unix::io::{AsRawFd, RawFd}; use std::path::PathBuf; use std::rc::Rc; @@ -8,18 +6,17 @@ use std::sync::{atomic::AtomicBool, Arc}; use calloop::{generic::Generic, InsertError, LoopHandle, Source}; use drm::control::{ - connector, crtc, plane, property, Device as ControlDevice, Event, Mode, PlaneResourceHandles, PlaneType, + connector, crtc, Device as ControlDevice, Event, Mode, ResourceHandles, }; -use drm::{ClientCapability, Device as BasicDevice, DriverCapability}; +use drm::{ClientCapability, Device as BasicDevice}; use nix::libc::dev_t; use nix::sys::stat::fstat; pub(super) mod atomic; pub(super) mod legacy; -use super::error::Error; +use super::{error::Error, Planes, planes}; use super::surface::{atomic::AtomicDrmSurface, legacy::LegacyDrmSurface, DrmSurface, DrmSurfaceInternal}; -use crate::backend::allocator::{Format, Fourcc, Modifier}; use atomic::AtomicDrmDevice; use legacy::LegacyDrmDevice; @@ -32,7 +29,6 @@ pub struct DrmDevice { pub(super) links: RefCell>, has_universal_planes: bool, resources: ResourceHandles, - planes: PlaneResourceHandles, pub(super) logger: ::slog::Logger, } @@ -142,11 +138,6 @@ impl DrmDevice { dev: dev.dev_path(), source, })?; - let planes = dev.plane_handles().map_err(|source| Error::Access { - errmsg: "Error loading plane handles", - dev: dev.dev_path(), - source, - })?; let internal = Arc::new(DrmDevice::create_internal( dev, active, @@ -162,7 +153,6 @@ impl DrmDevice { links: RefCell::new(Vec::new()), has_universal_planes, resources, - planes, logger: log, }) } @@ -258,65 +248,7 @@ impl DrmDevice { /// Returns a set of available planes for a given crtc pub fn planes(&self, crtc: &crtc::Handle) -> Result { - let mut primary = None; - let mut cursor = None; - let mut overlay = Vec::new(); - - for plane in self.planes.planes() { - let info = self.get_plane(*plane).map_err(|source| Error::Access { - errmsg: "Failed to get plane information", - dev: self.dev_path(), - source, - })?; - let filter = info.possible_crtcs(); - if self.resources.filter_crtcs(filter).contains(crtc) { - match self.plane_type(*plane)? { - PlaneType::Primary => { - primary = Some(*plane); - } - PlaneType::Cursor => { - cursor = Some(*plane); - } - PlaneType::Overlay => { - overlay.push(*plane); - } - }; - } - } - - Ok(Planes { - primary: primary.expect("Crtc has no primary plane"), - cursor, - overlay: if self.has_universal_planes { - Some(overlay) - } else { - None - }, - }) - } - - fn plane_type(&self, plane: plane::Handle) -> Result { - let props = self.get_properties(plane).map_err(|source| Error::Access { - errmsg: "Failed to get properties of plane", - dev: self.dev_path(), - source, - })?; - let (ids, vals) = props.as_props_and_values(); - for (&id, &val) in ids.iter().zip(vals.iter()) { - let info = self.get_property(id).map_err(|source| Error::Access { - errmsg: "Failed to get property info", - dev: self.dev_path(), - source, - })?; - if info.name().to_str().map(|x| x == "type").unwrap_or(false) { - return Ok(match val { - x if x == (PlaneType::Primary as u64) => PlaneType::Primary, - x if x == (PlaneType::Cursor as u64) => PlaneType::Cursor, - _ => PlaneType::Overlay, - }); - } - } - unreachable!() + planes(self, crtc, self.has_universal_planes) } /// Creates a new rendering surface. @@ -338,7 +270,6 @@ impl DrmDevice { pub fn create_surface( &self, crtc: crtc::Handle, - plane: plane::Handle, mode: Mode, connectors: &[connector::Handle], ) -> Result, Error> { @@ -346,6 +277,7 @@ impl DrmDevice { return Err(Error::SurfaceWithoutConnectors(crtc)); } + let plane = planes(self, &crtc, self.has_universal_planes)?.primary; let info = self.get_plane(plane).map_err(|source| Error::Access { errmsg: "Failed to get plane info", dev: self.dev_path(), @@ -378,10 +310,6 @@ impl DrmDevice { self.logger.clone(), )?) } else { - if self.plane_type(plane)? != PlaneType::Primary { - return Err(Error::NonPrimaryPlane(plane)); - } - DrmSurfaceInternal::Legacy(LegacyDrmSurface::new( self.internal.clone(), active, @@ -392,131 +320,12 @@ impl DrmDevice { )?) }; - // get plane formats - let plane_info = self.get_plane(plane).map_err(|source| Error::Access { - errmsg: "Error loading plane info", - dev: self.dev_path(), - source, - })?; - let mut formats = HashSet::new(); - for code in plane_info - .formats() - .iter() - .flat_map(|x| Fourcc::try_from(*x).ok()) - { - formats.insert(Format { - code, - modifier: Modifier::Invalid, - }); - } - - if let Ok(1) = self.get_driver_capability(DriverCapability::AddFB2Modifiers) { - let set = self.get_properties(plane).map_err(|source| Error::Access { - errmsg: "Failed to query properties", - dev: self.dev_path(), - source, - })?; - let (handles, _) = set.as_props_and_values(); - // for every handle ... - let prop = handles - .iter() - .find(|handle| { - // get information of that property - if let Some(info) = self.get_property(**handle).ok() { - // to find out, if we got the handle of the "IN_FORMATS" property ... - if info.name().to_str().map(|x| x == "IN_FORMATS").unwrap_or(false) { - // so we can use that to get formats - return true; - } - } - false - }) - .copied(); - if let Some(prop) = prop { - let prop_info = self.get_property(prop).map_err(|source| Error::Access { - errmsg: "Failed to query property", - dev: self.dev_path(), - source, - })?; - let (handles, raw_values) = set.as_props_and_values(); - let raw_value = raw_values[handles - .iter() - .enumerate() - .find_map(|(i, handle)| if *handle == prop { Some(i) } else { None }) - .unwrap()]; - if let property::Value::Blob(blob) = prop_info.value_type().convert_value(raw_value) { - let data = self.get_property_blob(blob).map_err(|source| Error::Access { - errmsg: "Failed to query property blob data", - dev: self.dev_path(), - source, - })?; - // be careful here, we have no idea about the alignment inside the blob, so always copy using `read_unaligned`, - // although slice::from_raw_parts would be so much nicer to iterate and to read. - unsafe { - let fmt_mod_blob_ptr = data.as_ptr() as *const drm_ffi::drm_format_modifier_blob; - let fmt_mod_blob = &*fmt_mod_blob_ptr; - - let formats_ptr: *const u32 = fmt_mod_blob_ptr - .cast::() - .offset(fmt_mod_blob.formats_offset as isize) - as *const _; - let modifiers_ptr: *const drm_ffi::drm_format_modifier = fmt_mod_blob_ptr - .cast::() - .offset(fmt_mod_blob.modifiers_offset as isize) - as *const _; - let formats_ptr = formats_ptr as *const u32; - let modifiers_ptr = modifiers_ptr as *const drm_ffi::drm_format_modifier; - - for i in 0..fmt_mod_blob.count_modifiers { - let mod_info = modifiers_ptr.offset(i as isize).read_unaligned(); - for j in 0..64 { - if mod_info.formats & (1u64 << j) != 0 { - let code = Fourcc::try_from( - formats_ptr - .offset((j + mod_info.offset) as isize) - .read_unaligned(), - ) - .ok(); - let modifier = Modifier::from(mod_info.modifier); - if let Some(code) = code { - formats.insert(Format { code, modifier }); - } - } - } - } - } - } - } - } else if self.plane_type(plane)? == PlaneType::Cursor { - // Force a LINEAR layout for the cursor if the driver doesn't support modifiers - for format in formats.clone() { - formats.insert(Format { - code: format.code, - modifier: Modifier::Linear, - }); - } - } - - if formats.is_empty() { - formats.insert(Format { - code: Fourcc::Argb8888, - modifier: Modifier::Invalid, - }); - } - - trace!( - self.logger, - "Supported scan-out formats for plane ({:?}): {:?}", - plane, - formats - ); - Ok(DrmSurface { dev_id: self.dev_id, crtc, - plane, + primary: plane, internal: Arc::new(internal), - formats, + has_universal_planes: self.has_universal_planes, #[cfg(feature = "backend_session")] links: RefCell::new(Vec::new()), }) @@ -528,15 +337,7 @@ impl DrmDevice { } } -/// A set of planes as supported by a crtc -pub struct Planes { - /// The primary plane of the crtc - pub primary: plane::Handle, - /// The cursor plane of the crtc, if available - pub cursor: Option, - /// Overlay planes supported by the crtc, if available - pub overlay: Option>, -} + /// Trait to receive events of a bound [`DrmDevice`] /// diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 6273360..3527b0f 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -68,10 +68,100 @@ mod render; pub(self) mod session; pub(self) mod surface; -pub use device::{device_bind, DevPath, DeviceHandler, DrmDevice, DrmSource, Planes}; +pub use device::{device_bind, DevPath, DeviceHandler, DrmDevice, DrmSource}; pub use error::Error as DrmError; #[cfg(feature = "backend_gbm")] pub use render::{DrmRenderSurface, Error as DrmRenderError}; #[cfg(feature = "backend_session")] pub use session::{DrmDeviceObserver, DrmSurfaceObserver}; pub use surface::DrmSurface; + +use drm::control::{plane, crtc, Device as ControlDevice, PlaneType}; + +/// A set of planes as supported by a crtc +pub struct Planes { + /// The primary plane of the crtc (automatically selected for [DrmDevice::create_surface]) + pub primary: plane::Handle, + /// The cursor plane of the crtc, if available + pub cursor: Option, + /// Overlay planes supported by the crtc, if available + pub overlay: Option>, +} + +fn planes(dev: &impl ControlDevice, crtc: &crtc::Handle, has_universal_planes: bool) -> Result { + let mut primary = None; + let mut cursor = None; + let mut overlay = Vec::new(); + + let planes = dev.plane_handles().map_err(|source| DrmError::Access { + errmsg: "Error loading plane handles", + dev: dev.dev_path(), + source, + })?; + + let resources = dev.resource_handles().map_err(|source| DrmError::Access { + errmsg: "Error loading resource handles", + dev: dev.dev_path(), + source, + })?; + + for plane in planes.planes() { + let info = dev.get_plane(*plane).map_err(|source| DrmError::Access { + errmsg: "Failed to get plane information", + dev: dev.dev_path(), + source, + })?; + let filter = info.possible_crtcs(); + if resources.filter_crtcs(filter).contains(crtc) { + match plane_type(dev, *plane)? { + PlaneType::Primary => { + primary = Some(*plane); + } + PlaneType::Cursor => { + cursor = Some(*plane); + } + PlaneType::Overlay => { + overlay.push(*plane); + } + }; + } + } + + Ok(Planes { + primary: primary.expect("Crtc has no primary plane"), + cursor: if has_universal_planes { + cursor + } else { + None + }, + overlay: if has_universal_planes && !overlay.is_empty() { + Some(overlay) + } else { + None + }, + }) +} + +fn plane_type(dev: &impl ControlDevice, plane: plane::Handle) -> Result { + let props = dev.get_properties(plane).map_err(|source| DrmError::Access { + errmsg: "Failed to get properties of plane", + dev: dev.dev_path(), + source, + })?; + let (ids, vals) = props.as_props_and_values(); + for (&id, &val) in ids.iter().zip(vals.iter()) { + let info = dev.get_property(id).map_err(|source| DrmError::Access { + errmsg: "Failed to get property info", + dev: dev.dev_path(), + source, + })?; + if info.name().to_str().map(|x| x == "type").unwrap_or(false) { + return Ok(match val { + x if x == (PlaneType::Primary as u64) => PlaneType::Primary, + x if x == (PlaneType::Cursor as u64) => PlaneType::Cursor, + _ => PlaneType::Overlay, + }); + } + } + unreachable!() +} \ No newline at end of file diff --git a/src/backend/drm/render.rs b/src/backend/drm/render.rs index 944fa55..81ca82b 100644 --- a/src/backend/drm/render.rs +++ b/src/backend/drm/render.rs @@ -79,7 +79,7 @@ where // select a format let plane_formats = drm - .supported_formats() + .supported_formats(drm.plane())? .iter() .filter(|fmt| fmt.code == code) .cloned() @@ -517,9 +517,9 @@ where .fb; let flip = if self.drm.commit_pending() { - self.drm.commit(fb, true) + self.drm.commit([(fb, self.drm.plane())].iter(), true) } else { - self.drm.page_flip(fb, true) + self.drm.page_flip([(fb, self.drm.plane())].iter(), true) }; if flip.is_ok() { self.pending_fb = Some(slot); diff --git a/src/backend/drm/surface/atomic.rs b/src/backend/drm/surface/atomic.rs index fc07a74..2bb4dc8 100644 --- a/src/backend/drm/surface/atomic.rs +++ b/src/backend/drm/surface/atomic.rs @@ -96,11 +96,21 @@ impl State { } } +#[derive(Debug, Clone)] +pub struct PlaneInfo { + handle: plane::Handle, + x: i32, + y: i32, + w: u32, + h: u32, +} + pub struct AtomicDrmSurface { pub(super) fd: Arc>, pub(super) active: Arc, crtc: crtc::Handle, plane: plane::Handle, + additional_planes: Mutex>, prop_mapping: Mapping, state: RwLock, pending: RwLock, @@ -147,6 +157,7 @@ impl AtomicDrmSurface { active, crtc, plane, + additional_planes: Mutex::new(Vec::new()), prop_mapping, state: RwLock::new(state), pending: RwLock::new(pending), @@ -159,8 +170,8 @@ impl AtomicDrmSurface { // we need a framebuffer to do test commits, which we use to verify our pending state. // here we create a dumbbuffer for that purpose. - fn create_test_buffer(&self, mode: &Mode) -> Result { - let (w, h) = mode.size(); + fn create_test_buffer(&self, size: (u16, u16)) -> Result { + let (w, h) = size; let db = self .fd .create_dumb_buffer( @@ -228,7 +239,8 @@ impl AtomicDrmSurface { &mut [conn].iter(), &mut [].iter(), self.plane, - Some(self.create_test_buffer(&pending.mode)?), + &[], + Some([(self.create_test_buffer(pending.mode.size())?, self.plane)].iter()), Some(pending.mode), Some(pending.blob), )?; @@ -265,7 +277,8 @@ impl AtomicDrmSurface { &mut [].iter(), &mut [conn].iter(), self.plane, - Some(self.create_test_buffer(&pending.mode)?), + &[], + Some([(self.create_test_buffer(pending.mode.size())?, self.plane)].iter()), Some(pending.mode), Some(pending.blob), )?; @@ -303,7 +316,8 @@ impl AtomicDrmSurface { &mut added, &mut removed, self.plane, - Some(self.create_test_buffer(&pending.mode)?), + &[], + Some([(self.create_test_buffer(pending.mode.size())?, self.plane)].iter()), Some(pending.mode), Some(pending.blob), )?; @@ -337,12 +351,13 @@ impl AtomicDrmSurface { source, })?; - let test_fb = Some(self.create_test_buffer(&pending.mode)?); + let test_fb = self.create_test_buffer(pending.mode.size())?; let req = self.build_request( &mut pending.connectors.iter(), &mut [].iter(), self.plane, - test_fb, + &[], + Some([(test_fb, self.plane)].iter()), Some(mode), Some(new_blob), )?; @@ -364,12 +379,56 @@ impl AtomicDrmSurface { Ok(()) } + + pub fn use_plane(&self, plane: plane::Handle, position: (i32, i32), size: (u32, u32)) -> Result<(), Error> { + let info = PlaneInfo { + handle: plane, + x: position.0, + y: position.1, + w: size.0, + h: size.1, + }; + + let mut planes = self.additional_planes.lock().unwrap(); + let mut new_planes = planes.clone(); + new_planes.push(info); + + let pending = self.pending.write().unwrap(); + let req = self.build_request( + &mut pending.connectors.iter(), + &mut [].iter(), + self.plane, + &new_planes, + Some([(self.create_test_buffer(pending.mode.size())?, self.plane)] + .iter() + .chain(new_planes.iter().map(|info| { + match self.create_test_buffer((info.w as u16, info.h as u16)) { + Ok(test_buff) => Ok((test_buff, info.handle)), + Err(err) => Err(err), + } + }).collect::, _>>()?.iter()) + ), + Some(pending.mode), + Some(pending.blob), + )?; + self + .fd + .atomic_commit( + &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + req, + ) + .map_err(|_| Error::TestFailed(self.crtc))?; + + *planes = new_planes; + + Ok(()) + } pub fn commit_pending(&self) -> bool { *self.pending.read().unwrap() != *self.state.read().unwrap() } - pub fn commit(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> { + pub fn commit<'a>(&self, framebuffers: impl Iterator, event: bool) -> Result<(), Error> { if !self.active.load(Ordering::SeqCst) { return Err(Error::DeviceInactive); } @@ -416,7 +475,8 @@ impl AtomicDrmSurface { &mut added, &mut removed, self.plane, - Some(framebuffer), + &*self.additional_planes.lock().unwrap(), + Some(framebuffers), Some(pending.mode), Some(pending.blob), )?; @@ -478,7 +538,7 @@ impl AtomicDrmSurface { result } - pub fn page_flip(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> { + pub fn page_flip<'a>(&self, framebuffers: impl Iterator, event: bool) -> Result<(), Error> { if !self.active.load(Ordering::SeqCst) { return Err(Error::DeviceInactive); } @@ -488,7 +548,8 @@ impl AtomicDrmSurface { &mut [].iter(), &mut [].iter(), self.plane, - Some(framebuffer), + &*self.additional_planes.lock().unwrap(), + Some(framebuffers), None, None, )?; @@ -541,7 +602,8 @@ impl AtomicDrmSurface { &mut added, &mut removed, self.plane, - Some(fb), + &[], + Some([(fb, self.plane)].iter()), Some(*mode), Some(blob), )?; @@ -556,6 +618,38 @@ impl AtomicDrmSurface { Ok(result) } + pub fn test_plane_buffer(&self, fb: framebuffer::Handle, plane: plane::Handle, position: (i32, i32), size: (u32, u32)) -> Result { + if !self.active.load(Ordering::SeqCst) { + return Err(Error::DeviceInactive); + } + + let pending = self.pending.read().unwrap(); + let req = self.build_request( + &mut pending.connectors.iter(), + &mut [].iter(), + self.plane, + &[PlaneInfo { + handle: plane, + x: position.0, + y: position.1, + w: size.0, + h: size.1, + }], + Some([(fb, self.plane), (fb, plane)].iter()), + Some(pending.mode), + Some(pending.blob), + )?; + + let result = self + .fd + .atomic_commit( + &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + req, + ) + .is_ok(); + Ok(result) + } + pub(crate) fn conn_prop_handle( &self, handle: connector::Handle, @@ -626,12 +720,13 @@ impl AtomicDrmSurface { } // If a mode is set a matching blob needs to be set (the inverse is not true) - pub fn build_request( + pub fn build_request<'a>( &self, new_connectors: &mut dyn Iterator, removed_connectors: &mut dyn Iterator, - plane: plane::Handle, - framebuffer: Option, + primary: plane::Handle, + planes: &[PlaneInfo], + framebuffers: Option>, mode: Option, blob: Option>, ) -> Result { @@ -674,67 +769,115 @@ impl AtomicDrmSurface { property::Value::Boolean(true), ); - // and we need to set the framebuffer for our primary plane - if let Some(fb) = framebuffer { - req.add_property( - plane, - self.plane_prop_handle(plane, "FB_ID")?, - property::Value::Framebuffer(Some(fb)), - ); + // and we need to set the framebuffers for our planes + if let Some(fbs) = framebuffers { + for (fb, plane) in fbs { + req.add_property( + *plane, + self.plane_prop_handle(*plane, "FB_ID")?, + property::Value::Framebuffer(Some(*fb)), + ); + } } - // we also need to connect the plane + // we also need to connect the primary plane req.add_property( - plane, - self.plane_prop_handle(plane, "CRTC_ID")?, + primary, + self.plane_prop_handle(primary, "CRTC_ID")?, property::Value::CRTC(Some(self.crtc)), ); - // if there is a new mode, we should also make sure the plane is sized correctly + // if there is a new mode, we should also make sure the primary plane is sized correctly if let Some(mode) = mode { req.add_property( - plane, - self.plane_prop_handle(plane, "SRC_X")?, + primary, + self.plane_prop_handle(primary, "SRC_X")?, property::Value::UnsignedRange(0), ); req.add_property( - plane, - self.plane_prop_handle(plane, "SRC_Y")?, + primary, + self.plane_prop_handle(primary, "SRC_Y")?, property::Value::UnsignedRange(0), ); req.add_property( - plane, - self.plane_prop_handle(plane, "SRC_W")?, + primary, + self.plane_prop_handle(primary, "SRC_W")?, // these are 16.16. fixed point property::Value::UnsignedRange((mode.size().0 as u64) << 16), ); req.add_property( - plane, - self.plane_prop_handle(plane, "SRC_H")?, + primary, + self.plane_prop_handle(primary, "SRC_H")?, property::Value::UnsignedRange((mode.size().1 as u64) << 16), ); // we can map parts of the plane onto different coordinated on the crtc, but we just use a 1:1 mapping. req.add_property( - plane, - self.plane_prop_handle(plane, "CRTC_X")?, + primary, + self.plane_prop_handle(primary, "CRTC_X")?, property::Value::SignedRange(0), ); req.add_property( - plane, - self.plane_prop_handle(plane, "CRTC_Y")?, + primary, + self.plane_prop_handle(primary, "CRTC_Y")?, property::Value::SignedRange(0), ); req.add_property( - plane, - self.plane_prop_handle(plane, "CRTC_W")?, + primary, + self.plane_prop_handle(primary, "CRTC_W")?, property::Value::UnsignedRange(mode.size().0 as u64), ); req.add_property( - plane, - self.plane_prop_handle(plane, "CRTC_H")?, + primary, + self.plane_prop_handle(primary, "CRTC_H")?, property::Value::UnsignedRange(mode.size().1 as u64), ); } + + // and finally the others + for plane_info in planes { + req.add_property( + plane_info.handle, + self.plane_prop_handle(plane_info.handle, "SRC_X")?, + property::Value::UnsignedRange(0), + ); + req.add_property( + plane_info.handle, + self.plane_prop_handle(plane_info.handle, "SRC_Y")?, + property::Value::UnsignedRange(0), + ); + req.add_property( + plane_info.handle, + self.plane_prop_handle(plane_info.handle, "SRC_W")?, + // these are 16.16. fixed point + property::Value::UnsignedRange((plane_info.w as u64) << 16), + ); + req.add_property( + plane_info.handle, + self.plane_prop_handle(plane_info.handle, "SRC_H")?, + property::Value::UnsignedRange((plane_info.h as u64) << 16), + ); + // we can map parts of the plane onto different coordinated on the crtc, but we just use a 1:1 mapping. + req.add_property( + plane_info.handle, + self.plane_prop_handle(plane_info.handle, "CRTC_X")?, + property::Value::SignedRange(plane_info.x as i64), + ); + req.add_property( + plane_info.handle, + self.plane_prop_handle(plane_info.handle, "CRTC_Y")?, + property::Value::SignedRange(plane_info.y as i64), + ); + req.add_property( + plane_info.handle, + self.plane_prop_handle(plane_info.handle, "CRTC_W")?, + property::Value::UnsignedRange(plane_info.w as u64), + ); + req.add_property( + plane_info.handle, + self.plane_prop_handle(plane_info.handle, "CRTC_H")?, + property::Value::UnsignedRange(plane_info.h as u64), + ); + } Ok(req) } @@ -743,7 +886,7 @@ impl AtomicDrmSurface { // this is mostly used to remove the contents quickly, e.g. on tty switch, // as other compositors might not make use of other planes, // leaving our e.g. cursor or overlays as a relict of a better time on the screen. - pub fn clear_plane(&self) -> Result<(), Error> { + pub fn clear_plane(&self, plane: plane::Handle) -> Result<(), Error> { let mut req = AtomicModeReq::new(); req.add_property( @@ -758,17 +901,19 @@ impl AtomicDrmSurface { property::Value::Framebuffer(None), ); - self.fd - .atomic_commit(&[AtomicCommitFlags::TestOnly], req.clone()) - .map_err(|_| Error::TestFailed(self.crtc))?; - - self.fd + let result = self.fd .atomic_commit(&[AtomicCommitFlags::Nonblock], req) .map_err(|source| Error::Access { errmsg: "Failed to commit on clear_plane", dev: self.fd.dev_path(), source, - }) + }); + + if result.is_ok() { + self.additional_planes.lock().unwrap().retain(|info| info.handle != plane); + } + + result } pub(crate) fn reset_state( @@ -801,8 +946,13 @@ impl Drop for AtomicDrmSurface { // other ttys that use no cursor, might not clear it themselves. // This makes sure our cursor won't stay visible. - if let Err(err) = self.clear_plane() { - warn!(self.logger, "Failed to clear plane on {:?}: {}", self.crtc, err); + if let Err(err) = self.clear_plane(self.plane) { + warn!(self.logger, "Failed to clear plane {:?} on {:?}: {}", self.plane, self.crtc, err); + } + for plane_info in self.additional_planes.lock().unwrap().iter() { + if let Err(err) = self.clear_plane(plane_info.handle) { + warn!(self.logger, "Failed to clear plane {:?} on {:?}: {}", plane_info.handle, self.crtc, err); + } } // disable connectors again diff --git a/src/backend/drm/surface/mod.rs b/src/backend/drm/surface/mod.rs index d4191ab..5bf03f8 100644 --- a/src/backend/drm/surface/mod.rs +++ b/src/backend/drm/surface/mod.rs @@ -1,17 +1,23 @@ use std::cell::RefCell; use std::collections::HashSet; +use std::convert::TryFrom; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::Arc; -use drm::control::{connector, crtc, framebuffer, plane, Device as ControlDevice, Mode}; -use drm::Device as BasicDevice; +use drm::control::{connector, crtc, framebuffer, plane, property, Device as ControlDevice, Mode}; +use drm::{Device as BasicDevice, DriverCapability}; use nix::libc::dev_t; pub(super) mod atomic; pub(super) mod legacy; -use super::error::Error; -use crate::backend::allocator::Format; +use super::{ + error::Error, + Planes, PlaneType, + plane_type, planes, + device::DevPath, +}; +use crate::backend::allocator::{Format, Fourcc, Modifier}; use atomic::AtomicDrmSurface; use legacy::LegacyDrmSurface; @@ -19,9 +25,9 @@ use legacy::LegacyDrmSurface; pub struct DrmSurface { pub(super) dev_id: dev_t, pub(super) crtc: crtc::Handle, - pub(super) plane: plane::Handle, + pub(super) primary: plane::Handle, pub(super) internal: Arc>, - pub(super) formats: HashSet, + pub(super) has_universal_planes: bool, #[cfg(feature = "backend_session")] pub(super) links: RefCell>, } @@ -48,9 +54,9 @@ impl DrmSurface { self.crtc } - /// Returns the underlying [`plane`](drm::control::plane) of this surface + /// Returns the underlying primary [`plane`](drm::control::plane) of this surface pub fn plane(&self) -> plane::Handle { - self.plane + self.primary } /// Currently used [`connector`](drm::control::connector)s of this `Surface` @@ -142,6 +148,35 @@ impl DrmSurface { } } + /// Tries to setup a cursor or overlay [`Plane`](drm::control::plane) + /// to be set at the next commit/page_flip with the given position and size. + /// + /// Planes can have arbitrary hardware constraints, that cannot be expressed in the api, + /// like supporting only positions at even or odd values, allowing only certain sizes or disallowing overlapping planes. + /// Using planes should therefor be done in a best-efford manner. Failures on `page_flip` or `commit` + /// should be expected and alternative code paths without the usage of planes prepared. + /// + /// Fails if tests for the given plane fail, if the underlying + /// implementation does not support the use of planes or if the plane + /// is not supported by this crtc. + pub fn use_plane(&self, plane: plane::Handle, position: (i32, i32), size: (u32, u32)) -> Result<(), Error> { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.use_plane(plane, position, size), + DrmSurfaceInternal::Legacy(_) => Err(Error::NonPrimaryPlane(plane)), + } + } + + /// Disables the given plane. + /// + /// Errors if the plane is not supported by this crtc or if the underlying + /// implementation does not support the use of planes. + pub fn clear_plane(&self, plane: plane::Handle) -> Result<(), Error> { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.clear_plane(plane), + DrmSurfaceInternal::Legacy(_) => Err(Error::NonPrimaryPlane(plane)), + } + } + /// Returns true whenever any state changes are pending to be commited /// /// The following functions may trigger a pending commit: @@ -165,10 +200,17 @@ impl DrmSurface { /// but will trigger a `vblank` event once done. /// Make sure to [set a `DeviceHandler`](Device::set_handler) and /// [register the belonging `Device`](device_bind) before to receive the event in time. - pub fn commit(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> { + pub fn commit<'a>(&self, mut framebuffers: impl Iterator, event: bool) -> Result<(), Error> { match &*self.internal { - DrmSurfaceInternal::Atomic(surf) => surf.commit(framebuffer, event), - DrmSurfaceInternal::Legacy(surf) => surf.commit(framebuffer, event), + DrmSurfaceInternal::Atomic(surf) => surf.commit(framebuffers, event), + DrmSurfaceInternal::Legacy(surf) => if let Some((fb, plane)) = framebuffers.next() { + if plane_type(self, *plane)? != PlaneType::Primary { + return Err(Error::NonPrimaryPlane(*plane)); + } + surf.commit(*fb, event) + } else { + Ok(()) + }, } } @@ -180,16 +222,151 @@ impl DrmSurface { /// This operation is not blocking and will produce a `vblank` event once swapping is done. /// Make sure to [set a `DeviceHandler`](Device::set_handler) and /// [register the belonging `Device`](device_bind) before to receive the event in time. - pub fn page_flip(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> { + pub fn page_flip<'a>(&self, mut framebuffers: impl Iterator, event: bool) -> Result<(), Error> { match &*self.internal { - DrmSurfaceInternal::Atomic(surf) => surf.page_flip(framebuffer, event), - DrmSurfaceInternal::Legacy(surf) => surf.page_flip(framebuffer, event), + DrmSurfaceInternal::Atomic(surf) => surf.page_flip(framebuffers, event), + DrmSurfaceInternal::Legacy(surf) => if let Some((fb, plane)) = framebuffers.next() { + if plane_type(self, *plane)? != PlaneType::Primary { + return Err(Error::NonPrimaryPlane(*plane)); + } + surf.page_flip(*fb, event) + } else { + Ok(()) + }, } } /// Returns a set of supported pixel formats for attached buffers - pub fn supported_formats(&self) -> &HashSet { - &self.formats + pub fn supported_formats(&self, plane: plane::Handle) -> Result, Error> { + // get plane formats + let plane_info = self.get_plane(plane).map_err(|source| Error::Access { + errmsg: "Error loading plane info", + dev: self.dev_path(), + source, + })?; + let mut formats = HashSet::new(); + for code in plane_info + .formats() + .iter() + .flat_map(|x| Fourcc::try_from(*x).ok()) + { + formats.insert(Format { + code, + modifier: Modifier::Invalid, + }); + } + + if let Ok(1) = self.get_driver_capability(DriverCapability::AddFB2Modifiers) { + let set = self.get_properties(plane).map_err(|source| Error::Access { + errmsg: "Failed to query properties", + dev: self.dev_path(), + source, + })?; + let (handles, _) = set.as_props_and_values(); + // for every handle ... + let prop = handles + .iter() + .find(|handle| { + // get information of that property + if let Some(info) = self.get_property(**handle).ok() { + // to find out, if we got the handle of the "IN_FORMATS" property ... + if info.name().to_str().map(|x| x == "IN_FORMATS").unwrap_or(false) { + // so we can use that to get formats + return true; + } + } + false + }) + .copied(); + if let Some(prop) = prop { + let prop_info = self.get_property(prop).map_err(|source| Error::Access { + errmsg: "Failed to query property", + dev: self.dev_path(), + source, + })?; + let (handles, raw_values) = set.as_props_and_values(); + let raw_value = raw_values[handles + .iter() + .enumerate() + .find_map(|(i, handle)| if *handle == prop { Some(i) } else { None }) + .unwrap()]; + if let property::Value::Blob(blob) = prop_info.value_type().convert_value(raw_value) { + let data = self.get_property_blob(blob).map_err(|source| Error::Access { + errmsg: "Failed to query property blob data", + dev: self.dev_path(), + source, + })?; + // be careful here, we have no idea about the alignment inside the blob, so always copy using `read_unaligned`, + // although slice::from_raw_parts would be so much nicer to iterate and to read. + unsafe { + let fmt_mod_blob_ptr = data.as_ptr() as *const drm_ffi::drm_format_modifier_blob; + let fmt_mod_blob = &*fmt_mod_blob_ptr; + + let formats_ptr: *const u32 = fmt_mod_blob_ptr + .cast::() + .offset(fmt_mod_blob.formats_offset as isize) + as *const _; + let modifiers_ptr: *const drm_ffi::drm_format_modifier = fmt_mod_blob_ptr + .cast::() + .offset(fmt_mod_blob.modifiers_offset as isize) + as *const _; + let formats_ptr = formats_ptr as *const u32; + let modifiers_ptr = modifiers_ptr as *const drm_ffi::drm_format_modifier; + + for i in 0..fmt_mod_blob.count_modifiers { + let mod_info = modifiers_ptr.offset(i as isize).read_unaligned(); + for j in 0..64 { + if mod_info.formats & (1u64 << j) != 0 { + let code = Fourcc::try_from( + formats_ptr + .offset((j + mod_info.offset) as isize) + .read_unaligned(), + ) + .ok(); + let modifier = Modifier::from(mod_info.modifier); + if let Some(code) = code { + formats.insert(Format { code, modifier }); + } + } + } + } + } + } + } + } else if plane_type(self, plane)? == PlaneType::Cursor { + // Force a LINEAR layout for the cursor if the driver doesn't support modifiers + for format in formats.clone() { + formats.insert(Format { + code: format.code, + modifier: Modifier::Linear, + }); + } + } + + if formats.is_empty() { + formats.insert(Format { + code: Fourcc::Argb8888, + modifier: Modifier::Invalid, + }); + } + + let logger = match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => &surf.logger, + DrmSurfaceInternal::Legacy(surf) => &surf.logger, + }; + trace!( + logger, + "Supported scan-out formats for plane ({:?}): {:?}", + plane, + formats + ); + + Ok(formats) + } + + /// Returns a set of available planes for this surface + pub fn planes(&self) -> Result { + planes(self, &self.crtc, self.has_universal_planes) } /// Tests is a framebuffer can be used with this surface. @@ -217,6 +394,32 @@ impl DrmSurface { } // There is no test-commiting with the legacy interface } } + + /// Tests is a framebuffer can be used with this surface and a given plane. + /// + /// # Arguments + /// + /// - `fb` - Framebuffer handle that has an attached buffer, that shall be tested + /// - `plane` - The plane that should be used to display the buffer + /// (only works for *cursor* and *overlay* planes - for primary planes use `test_buffer`) + /// - `position` - The position of the plane + /// - `size` - The size of the plane + /// + /// If the test cannot be performed, this function returns false. + /// This is always the case for non-atomic surfaces. + pub fn test_plane_buffer( + &self, + fb: framebuffer::Handle, + plane: plane::Handle, + position: (i32, i32), + size: (u32, u32), + ) -> Result { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.test_plane_buffer(fb, plane, position, size), + DrmSurfaceInternal::Legacy(surf) => { Ok(false) } + // There is no test-commiting with the legacy interface + } + } /// Re-evaluates the current state of the crtc. ///