diff --git a/examples/raw_nvidia.rs b/examples/raw_nvidia.rs deleted file mode 100644 index 0f4576a..0000000 --- a/examples/raw_nvidia.rs +++ /dev/null @@ -1,169 +0,0 @@ -#![warn(rust_2018_idioms)] - -#[macro_use] -extern crate slog; - -use glium::Surface as GliumSurface; -use slog::Drain; -use smithay::{ - backend::{ - drm::{ - atomic::{AtomicDrmDevice, AtomicDrmSurface}, - common::fallback::{EitherError, FallbackDevice, FallbackSurface}, - common::Error as DrmError, - device_bind, - egl::{EglDevice, EglSurface, Error as EglError}, - eglstream::{ - egl::EglStreamDeviceBackend, EglStreamDevice, EglStreamSurface, Error as EglStreamError, - }, - legacy::{LegacyDrmDevice, LegacyDrmSurface}, - Device, DeviceHandler, - }, - graphics::glium::GliumGraphicsBackend, - }, - reexports::{ - calloop::EventLoop, - drm::control::{connector::State as ConnectorState, crtc}, - }, -}; -use std::{ - fs::{File, OpenOptions}, - io::Error as IoError, - os::unix::io::{AsRawFd, RawFd}, - rc::Rc, - sync::Mutex, -}; - -pub struct ClonableFile { - file: File, -} - -impl AsRawFd for ClonableFile { - fn as_raw_fd(&self) -> RawFd { - self.file.as_raw_fd() - } -} - -impl Clone for ClonableFile { - fn clone(&self) -> Self { - ClonableFile { - file: self.file.try_clone().expect("Unable to clone file"), - } - } -} - -fn main() { - let log = slog::Logger::root(Mutex::new(slog_term::term_full().fuse()).fuse(), o!()); - - /* - * Initialize the drm backend - */ - - // "Find" a suitable drm device - let mut options = OpenOptions::new(); - options.read(true); - options.write(true); - let file = ClonableFile { - file: options.open("/dev/dri/card1").expect("Failed to open card1"), - }; - let mut device = EglDevice::new( - EglStreamDevice::new( - FallbackDevice::, LegacyDrmDevice<_>>::new(file, true, log.clone()) - .expect("Failed to initialize drm device"), - log.clone(), - ) - .expect("Failed to initialize egl stream device"), - log.clone(), - ) - .expect("Failed to initialize egl device"); - - // Get a set of all modesetting resource handles (excluding planes): - let res_handles = Device::resource_handles(&device).unwrap(); - - // Use first connected connector - let connector_info = res_handles - .connectors() - .iter() - .map(|conn| Device::get_connector_info(&device, *conn).unwrap()) - .find(|conn| conn.state() == ConnectorState::Connected) - .unwrap(); - println!("Conn: {:?}", connector_info.interface()); - - // Use the first encoder - let encoder_info = - Device::get_encoder_info(&device, connector_info.encoders()[0].expect("expected encoder")).unwrap(); - - // use the connected crtc if any - let crtc = encoder_info - .crtc() - // or use the first one that is compatible with the encoder - .unwrap_or_else(|| res_handles.filter_crtcs(encoder_info.possible_crtcs())[0]); - println!("Crtc {:?}", crtc); - - // Assuming we found a good connector and loaded the info into `connector_info` - let mode = connector_info.modes()[0]; // Use first mode (usually highest resolution, but in reality you should filter and sort and check and match with other connectors, if you use more then one.) - println!("Mode: {:?}", mode); - - // Initialize the hardware backend - let surface = device - .create_surface(crtc, mode, &[connector_info.handle()]) - .expect("Failed to create surface"); - - let backend: Rc> = Rc::new(surface.into()); - device.set_handler(DrmHandlerImpl { - surface: backend.clone(), - }); - - /* - * Register the DrmDevice on the EventLoop - */ - let mut event_loop = EventLoop::<()>::new().unwrap(); - let _source = device_bind(&event_loop.handle(), device) - .map_err(|err| -> IoError { err.into() }) - .unwrap(); - - // Start rendering - { - if let Err(smithay::backend::graphics::SwapBuffersError::ContextLost(err)) = backend.draw().finish() { - println!("{}", err); - return; - }; - } - - // Run - event_loop.run(None, &mut (), |_| {}).unwrap(); -} - -pub struct DrmHandlerImpl { - surface: Rc< - GliumGraphicsBackend< - EglSurface< - EglStreamSurface< - FallbackSurface, LegacyDrmSurface>, - >, - >, - >, - >, -} - -impl DeviceHandler for DrmHandlerImpl { - type Device = EglDevice< - EglStreamDeviceBackend, LegacyDrmDevice>>, - EglStreamDevice, LegacyDrmDevice>>, - >; - - fn vblank(&mut self, _crtc: crtc::Handle) { - { - println!("Vblank"); - let mut frame = self.surface.draw(); - frame.clear(None, Some((0.8, 0.8, 0.9, 1.0)), false, Some(1.0), None); - if let Err(smithay::backend::graphics::SwapBuffersError::ContextLost(err)) = frame.finish() { - panic!("Failed to swap: {}", err); - } - } - } - - fn error(&mut self, error: EglError>>) { - panic!("{:?}", error); - } -} diff --git a/src/backend/drm/atomic/mod.rs b/src/backend/drm/atomic/mod.rs deleted file mode 100644 index 38e2e71..0000000 --- a/src/backend/drm/atomic/mod.rs +++ /dev/null @@ -1,528 +0,0 @@ -//! -//! [`RawDevice`](RawDevice) and [`RawSurface`](RawSurface) -//! implementations using the atomic mode-setting infrastructure. -//! -//! Atomic mode-setting (previously referred to a nuclear page-flip) is a new api of the Direct Rendering -//! Manager subsystem of the linux kernel. Adaptations of this api can be found in BSD kernels. -//! -//! This api is objectively better than the outdated legacy-api, but not supported by every driver. -//! Initialization will fail, if the api is unsupported. The legacy-api is also wrapped by smithay -//! and may be used instead in these cases. Currently there are no features in smithay that are -//! exclusive to the atomic api. -//! -//! Usually this implementation will wrapped into a [`GbmDevice`](::backend::drm::gbm::GbmDevice). -//! Take a look at `anvil`s source code for an example of this. -//! -//! For an example how to use this standalone, take a look at the raw_atomic_drm example. -//! -//! For detailed overview of these abstractions take a look at the module documentation of backend::drm. -//! - -use std::cell::RefCell; -use std::collections::HashMap; -use std::fmt; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::rc::Rc; -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Weak, -}; - -use drm::control::{atomic::AtomicModeReq, AtomicCommitFlags, Device as ControlDevice, Event}; -use drm::control::{ - connector, crtc, encoder, framebuffer, plane, property, Mode, PropertyValueSet, ResourceHandle, - ResourceHandles, -}; -use drm::SystemError as DrmError; -use drm::{ClientCapability, Device as BasicDevice}; -use failure::{Fail, ResultExt}; -use nix::libc::dev_t; -use nix::sys::stat::fstat; - -use super::{common::Error, DevPath, Device, DeviceHandler, RawDevice}; - -mod surface; -pub use self::surface::AtomicDrmSurface; -use self::surface::AtomicDrmSurfaceInternal; - -#[cfg(feature = "backend_session")] -pub mod session; - -/// Open raw drm device utilizing atomic mode-setting -pub struct AtomicDrmDevice { - dev: Arc>, - dev_id: dev_t, - active: Arc, - backends: Rc>>>>, - handler: Option>>>>, - #[cfg(feature = "backend_session")] - links: Vec, - logger: ::slog::Logger, -} - -// DeviceHandler does not implement Debug, so we have to impl Debug manually -impl fmt::Debug for AtomicDrmDevice { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut debug = f.debug_struct("AtomicDrmDevice"); - - debug - .field("dev", &self.dev) - .field("dev_id", &self.dev_id) - .field("active", &self.active) - .field("backends", &self.backends) - .field("handler", &"..."); - - #[cfg(feature = "backend_session")] - debug.field("links", &self.links); - - debug.field("logger", &self.logger).finish() - } -} - -type OldState = ( - Vec<(connector::Handle, PropertyValueSet)>, - Vec<(crtc::Handle, PropertyValueSet)>, - Vec<(framebuffer::Handle, PropertyValueSet)>, - Vec<(plane::Handle, PropertyValueSet)>, -); - -type Mapping = ( - HashMap>, - HashMap>, - HashMap>, - HashMap>, -); - -#[derive(Debug)] -pub(in crate::backend::drm) struct Dev { - fd: A, - privileged: bool, - active: Arc, - old_state: OldState, - prop_mapping: Mapping, - logger: ::slog::Logger, -} - -impl AsRawFd for Dev { - fn as_raw_fd(&self) -> RawFd { - self.fd.as_raw_fd() - } -} -impl BasicDevice for Dev {} -impl ControlDevice for Dev {} -impl Drop for Dev { - fn drop(&mut self) { - info!(self.logger, "Dropping device: {:?}", self.dev_path()); - if self.active.load(Ordering::SeqCst) { - // Here we restore the card/tty's to it's previous state. - // In case e.g. getty was running on the tty sets the correct framebuffer again, - // so that getty will be visible. - // We do exit correctly if this fails, but the user will be presented with - // a black screen if no display handler takes control again. - - // create an atomic mode request consisting of all properties we captured on creation. - let mut req = AtomicModeReq::new(); - fn add_multiple_props( - req: &mut AtomicModeReq, - old_state: &[(T, PropertyValueSet)], - ) { - for (handle, set) in old_state { - let (prop_handles, values) = set.as_props_and_values(); - for (&prop_handle, &val) in prop_handles.iter().zip(values.iter()) { - req.add_raw_property((*handle).into(), prop_handle, val); - } - } - }; - - add_multiple_props(&mut req, &self.old_state.0); - add_multiple_props(&mut req, &self.old_state.1); - add_multiple_props(&mut req, &self.old_state.2); - add_multiple_props(&mut req, &self.old_state.3); - - if let Err(err) = self.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) { - error!(self.logger, "Failed to restore previous state. Error: {}", err); - } - } - if self.privileged { - if let Err(err) = self.release_master_lock() { - error!(self.logger, "Failed to drop drm master state. Error: {}", err); - } - } - } -} - -impl Dev { - // Add all properties of given handles to a given drm resource type to state. - // You may use this to snapshot the current state of the drm device (fully or partially). - fn add_props(&self, handles: &[T], state: &mut Vec<(T, PropertyValueSet)>) -> Result<(), Error> - where - A: AsRawFd + 'static, - T: ResourceHandle, - { - let iter = handles.iter().map(|x| (x, self.get_properties(*x))); - if let Some(len) = iter.size_hint().1 { - state.reserve_exact(len) - } - - iter.map(|(x, y)| (*x, y)) - .try_for_each(|(x, y)| match y { - Ok(y) => { - state.push((x, y)); - Ok(()) - } - Err(err) => Err(err), - }) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error reading properties", - dev: self.dev_path(), - source, - }) - } - - /// Create a mapping of property names and handles for given handles of a given drm resource type. - /// You may use this to easily lookup properties by name instead of going through this procedure manually. - fn map_props( - &self, - handles: &[T], - mapping: &mut HashMap>, - ) -> Result<(), Error> - where - A: AsRawFd + 'static, - T: ResourceHandle + Eq + std::hash::Hash, - { - handles - .iter() - .map(|x| (x, self.get_properties(*x))) - .try_for_each(|(handle, props)| { - let mut map = HashMap::new(); - match props { - Ok(props) => { - let (prop_handles, _) = props.as_props_and_values(); - for prop in prop_handles { - if let Ok(info) = self.get_property(*prop) { - let name = info.name().to_string_lossy().into_owned(); - map.insert(name, *prop); - } - } - mapping.insert(*handle, map); - Ok(()) - } - Err(err) => Err(err), - } - }) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error reading properties on {:?}", - dev: self.dev_path(), - source, - }) - } -} - -impl AtomicDrmDevice { - /// Create a new [`AtomicDrmDevice`] from an open drm node - /// - /// # Arguments - /// - /// - `fd` - Open drm node - /// - `disable_connectors` - Setting this to true will initialize all connectors \ - /// as disabled on device creation. smithay enables connectors, when attached \ - /// to a surface, and disables them, when detached. Setting this to `false` \ - /// requires usage of `drm-rs` to disable unused connectors to prevent them \ - /// showing garbage, but will also prevent flickering of already turned on \ - /// connectors (assuming you won't change the resolution). - /// - `logger` - Optional [`slog::Logger`] to be used by this device. - /// - /// # Return - /// - /// Returns an error if the file is no valid drm node or the device is not accessible. - pub fn new(fd: A, disable_connectors: bool, logger: L) -> Result - where - L: Into>, - { - let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm")); - info!(log, "AtomicDrmDevice initializing"); - - let dev_id = fstat(fd.as_raw_fd()).map_err(Error::UnableToGetDeviceId)?.st_rdev; - - // we wrap some of the internal states in another struct to share with - // the surfaces and event loop handlers. - let active = Arc::new(AtomicBool::new(true)); - let mut dev = Dev { - fd, - privileged: true, - active: active.clone(), - old_state: (Vec::new(), Vec::new(), Vec::new(), Vec::new()), - prop_mapping: (HashMap::new(), HashMap::new(), HashMap::new(), HashMap::new()), - logger: log.clone(), - }; - - // we need to be the master to do modesetting if we run via a tty session. - // This is only needed on older kernels. Newer kernels grant this permission, - // if no other process is already the *master*. So we skip over this error. - if dev.acquire_master_lock().is_err() { - warn!(log, "Unable to become drm master, assuming unprivileged mode"); - dev.privileged = false; - }; - - // Enable the features we need. - // We technically could use the atomic api without universal plane support. - // But the two are almost exclusively implemented together, as plane synchronization - // is one of the killer-features of the atomic api. - // - // We could bould more abstractions in smithay for devices with partial support, - // but for now we role with the oldest possible api (legacy) and the newest feature set - // we can use (atomic + universal planes), although we barely use planes yet. - dev.set_client_capability(ClientCapability::UniversalPlanes, true) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error enabling UniversalPlanes", - dev: dev.dev_path(), - source, - })?; - dev.set_client_capability(ClientCapability::Atomic, true) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error enabling AtomicModesetting", - dev: dev.dev_path(), - source, - })?; - - // Enumerate (and save) the current device state. - let res_handles = ControlDevice::resource_handles(&dev) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error loading drm resources", - dev: dev.dev_path(), - source, - })?; - - let plane_handles = dev.plane_handles().compat().map_err(|source| Error::Access { - errmsg: "Error loading planes", - dev: dev.dev_path(), - source, - })?; - let planes = plane_handles.planes(); - - let mut old_state = dev.old_state.clone(); - let mut mapping = dev.prop_mapping.clone(); - - // This helper function takes a snapshot of the current device properties. - // (everything in the atomic api is set via properties.) - dev.add_props(res_handles.connectors(), &mut old_state.0)?; - dev.add_props(res_handles.crtcs(), &mut old_state.1)?; - dev.add_props(res_handles.framebuffers(), &mut old_state.2)?; - dev.add_props(planes, &mut old_state.3)?; - - // And because the mapping is not consistent across devices, - // we also need to lookup the handle for a property name. - // And we do this a fair bit, so lets cache that mapping. - dev.map_props(res_handles.connectors(), &mut mapping.0)?; - dev.map_props(res_handles.crtcs(), &mut mapping.1)?; - dev.map_props(res_handles.framebuffers(), &mut mapping.2)?; - dev.map_props(planes, &mut mapping.3)?; - - dev.old_state = old_state; - dev.prop_mapping = mapping; - trace!(log, "Mapping: {:#?}", dev.prop_mapping); - - // If the user does not explicitly requests us to skip this, - // we clear out the complete connector<->crtc mapping on device creation. - // - // The reason is, that certain operations may be racy otherwise. Surfaces can - // exist on different threads: as a result, we cannot really enumerate the current state - // (it might be changed on another thread during the enumeration). And commits can fail, - // if e.g. a connector is already bound to another surface, which is difficult to analyse at runtime. - // - // An easy workaround is to set a known state on device creation, so we can only - // run into these errors on our own and not because previous compositors left the device - // in a funny state. - if disable_connectors { - // Disable all connectors as initial state - let mut req = AtomicModeReq::new(); - for conn in res_handles.connectors() { - let prop = dev - .prop_mapping - .0 - .get(&conn) - .expect("Unknown handle") - .get("CRTC_ID") - .expect("Unknown property CRTC_ID"); - req.add_property(*conn, *prop, property::Value::CRTC(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() { - let active_prop = dev - .prop_mapping - .1 - .get(&crtc) - .expect("Unknown handle") - .get("ACTIVE") - .expect("Unknown property ACTIVE"); - let mode_prop = dev - .prop_mapping - .1 - .get(&crtc) - .expect("Unknown handle") - .get("MODE_ID") - .expect("Unknown property MODE_ID"); - req.add_property(*crtc, *mode_prop, property::Value::Unknown(0)); - req.add_property(*crtc, *active_prop, property::Value::Boolean(false)); - } - dev.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) - .compat() - .map_err(|source| Error::Access { - errmsg: "Failed to disable connectors", - dev: dev.dev_path(), - source, - })?; - } - - Ok(AtomicDrmDevice { - dev: Arc::new(dev), - dev_id, - active, - backends: Rc::new(RefCell::new(HashMap::new())), - handler: None, - #[cfg(feature = "backend_session")] - links: Vec::new(), - logger: log.clone(), - }) - } -} - -impl AsRawFd for AtomicDrmDevice { - fn as_raw_fd(&self) -> RawFd { - self.dev.as_raw_fd() - } -} - -impl BasicDevice for AtomicDrmDevice {} -impl ControlDevice for AtomicDrmDevice {} - -impl Device for AtomicDrmDevice { - type Surface = AtomicDrmSurface; - - fn device_id(&self) -> dev_t { - self.dev_id - } - - fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { - self.handler = Some(RefCell::new(Box::new(handler))); - } - - fn clear_handler(&mut self) { - let _ = self.handler.take(); - } - - fn create_surface( - &mut self, - crtc: crtc::Handle, - mode: Mode, - connectors: &[connector::Handle], - ) -> Result, Error> { - if self.backends.borrow().contains_key(&crtc) { - return Err(Error::CrtcAlreadyInUse(crtc)); - } - - if !self.active.load(Ordering::SeqCst) { - return Err(Error::DeviceInactive); - } - - if connectors.is_empty() { - return Err(Error::SurfaceWithoutConnectors(crtc)); - } - - let backend = Arc::new(AtomicDrmSurfaceInternal::new( - self.dev.clone(), - crtc, - mode, - connectors, - self.logger.new(o!("crtc" => format!("{:?}", crtc))), - )?); - - self.backends.borrow_mut().insert(crtc, Arc::downgrade(&backend)); - Ok(AtomicDrmSurface(backend)) - } - - fn process_events(&mut self) { - match self.receive_events() { - Ok(events) => { - for event in events { - if let Event::PageFlip(event) = event { - trace!(self.logger, "Got a page-flip event for crtc ({:?})", event.crtc); - if self - .backends - .borrow() - .get(&event.crtc) - .iter() - .flat_map(|x| x.upgrade()) - .next() - .is_some() - { - trace!(self.logger, "Handling event for backend {:?}", event.crtc); - if let Some(handler) = self.handler.as_ref() { - handler.borrow_mut().vblank(event.crtc); - } - } else { - self.backends.borrow_mut().remove(&event.crtc); - } - } else { - trace!( - self.logger, - "Got a non-page-flip event of device '{:?}'.", - self.dev_path() - ); - } - } - } - Err(source) => { - if let Some(handler) = self.handler.as_ref() { - handler.borrow_mut().error(Error::Access { - errmsg: "Error processing drm events", - dev: self.dev_path(), - source: source.compat(), - }); - } - } - } - } - - fn resource_handles(&self) -> Result { - ControlDevice::resource_handles(self) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error loading resource info", - dev: self.dev_path(), - source, - }) - } - - fn get_connector_info(&self, conn: connector::Handle) -> Result { - self.get_connector(conn) - } - fn get_crtc_info(&self, crtc: crtc::Handle) -> Result { - self.get_crtc(crtc) - } - fn get_encoder_info(&self, enc: encoder::Handle) -> Result { - self.get_encoder(enc) - } - fn get_framebuffer_info(&self, fb: framebuffer::Handle) -> Result { - self.get_framebuffer(fb) - } - fn get_plane_info(&self, plane: plane::Handle) -> Result { - self.get_plane(plane) - } -} - -impl RawDevice for AtomicDrmDevice { - type Surface = AtomicDrmSurface; -} - -impl Drop for AtomicDrmDevice { - fn drop(&mut self) { - self.clear_handler(); - } -} diff --git a/src/backend/drm/atomic/session.rs b/src/backend/drm/atomic/session.rs deleted file mode 100644 index d58e9ba..0000000 --- a/src/backend/drm/atomic/session.rs +++ /dev/null @@ -1,216 +0,0 @@ -//! -//! Support to register an open [`AtomicDrmDevice`](AtomicDrmDevice) -//! to an open [`Session`](::backend::session::Session). -//! - -use drm::control::{atomic::AtomicModeReq, crtc, property, AtomicCommitFlags, Device as ControlDevice}; -use drm::Device as BasicDevice; -use failure::ResultExt; -use nix::libc::dev_t; -use nix::sys::stat; -use std::cell::RefCell; -use std::collections::HashMap; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::rc::{Rc, Weak}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Weak as WeakArc}; - -use super::{surface::CursorState, AtomicDrmDevice, AtomicDrmSurfaceInternal, Dev}; -use crate::backend::drm::{common::Error, DevPath, Surface}; -use crate::{ - backend::session::Signal as SessionSignal, - signaling::{Linkable, Signaler}, -}; - -/// [`SessionObserver`](SessionObserver) -/// linked to the [`AtomicDrmDevice`](AtomicDrmDevice) -/// it was created from. -#[derive(Debug)] -pub struct AtomicDrmDeviceObserver { - dev: WeakArc>, - dev_id: dev_t, - privileged: bool, - active: Arc, - backends: Weak>>>>, - logger: ::slog::Logger, -} - -impl Linkable for AtomicDrmDevice { - fn link(&mut self, signaler: Signaler) { - let mut observer = AtomicDrmDeviceObserver { - dev: Arc::downgrade(&self.dev), - dev_id: self.dev_id, - active: self.active.clone(), - privileged: self.dev.privileged, - backends: Rc::downgrade(&self.backends), - logger: self.logger.clone(), - }; - - let token = signaler.register(move |signal| observer.signal(*signal)); - self.links.push(token); - } -} - -impl AtomicDrmDeviceObserver { - fn signal(&mut self, signal: SessionSignal) { - match signal { - SessionSignal::PauseSession => self.pause(None), - SessionSignal::PauseDevice { major, minor } => self.pause(Some((major, minor))), - SessionSignal::ActivateSession => self.activate(None), - SessionSignal::ActivateDevice { major, minor, new_fd } => { - self.activate(Some((major, minor, new_fd))) - } - } - } - - fn pause(&mut self, devnum: Option<(u32, u32)>) { - if let Some((major, minor)) = devnum { - if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) { - return; - } - } - - // TODO: Clear overlay planes (if we ever use them) - - if let Some(backends) = self.backends.upgrade() { - for surface in backends.borrow().values().filter_map(WeakArc::upgrade) { - // other ttys that use no cursor, might not clear it themselves. - // This makes sure our cursor won't stay visible. - // - // This usually happens with getty, and a cursor on top of a kernel console looks very weird. - if let Err(err) = surface.clear_plane(surface.planes.cursor) { - warn!( - self.logger, - "Failed to clear cursor on {:?}: {}", surface.planes.cursor, err - ); - } - } - } - - self.active.store(false, Ordering::SeqCst); - if self.privileged { - if let Some(device) = self.dev.upgrade() { - if let Err(err) = device.release_master_lock() { - error!(self.logger, "Failed to drop drm master state Error: {}", err); - } - } - } - } - - fn activate(&mut self, devnum: Option<(u32, u32, Option)>) { - if let Some((major, minor, fd)) = devnum { - if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) { - return; - } else if let Some(fd) = fd { - info!(self.logger, "Replacing fd"); - if let Some(device) = self.dev.upgrade() { - ::nix::unistd::dup2(device.as_raw_fd(), fd) - .expect("Failed to replace file descriptor of drm device"); - } - } - } - if self.privileged { - if let Some(device) = self.dev.upgrade() { - if let Err(err) = device.acquire_master_lock() { - crit!(self.logger, "Failed to acquire drm master again. Error: {}", err); - } - } - } - self.active.store(true, Ordering::SeqCst); - // okay, the previous session/whatever might left the drm devices in any state... - // lets fix that - if let Err(err) = self.reset_state() { - warn!(self.logger, "Unable to reset state after tty switch: {}", err); - // TODO call drm-handler::error - } - } - - fn reset_state(&mut self) -> Result<(), Error> { - // reset state sets the connectors into a known state (all disabled), - // for the same reasons we do this on device creation. - // - // We might end up with conflicting commit requirements, if we want to restore our state, - // on top of the state the previous compositor left the device in. - // This is because we do commits per surface and not per device, so we do a global - // commit here, to fix any conflicts. - if let Some(dev) = self.dev.upgrade() { - let res_handles = ControlDevice::resource_handles(&*dev) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error loading drm resources", - dev: dev.dev_path(), - source, - })?; - - // Disable all connectors (otherwise we might run into conflicting commits when restarting the rendering loop) - let mut req = AtomicModeReq::new(); - for conn in res_handles.connectors() { - let prop = dev - .prop_mapping - .0 - .get(&conn) - .expect("Unknown handle") - .get("CRTC_ID") - .expect("Unknown property CRTC_ID"); - req.add_property(*conn, *prop, property::Value::CRTC(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() { - let mode_prop = dev - .prop_mapping - .1 - .get(&crtc) - .expect("Unknown handle") - .get("MODE_ID") - .expect("Unknown property MODE_ID"); - let active_prop = dev - .prop_mapping - .1 - .get(&crtc) - .expect("Unknown handle") - .get("ACTIVE") - .expect("Unknown property ACTIVE"); - req.add_property(*crtc, *active_prop, property::Value::Boolean(false)); - req.add_property(*crtc, *mode_prop, property::Value::Unknown(0)); - } - dev.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) - .compat() - .map_err(|source| Error::Access { - errmsg: "Failed to disable connectors", - dev: dev.dev_path(), - source, - })?; - - // because we change the state and disabled everything, - // we want to force a commit (instead of a page-flip) on all used surfaces - // for the next rendering step to re-activate the connectors. - // - // Lets do that, by creating a garbage/non-matching current-state. - if let Some(backends) = self.backends.upgrade() { - for surface in backends.borrow().values().filter_map(WeakArc::upgrade) { - let mut current = surface.state.write().unwrap(); - - // lets force a non matching state - current.connectors.clear(); - current.mode = unsafe { std::mem::zeroed() }; - - // recreate property blob - let mode = { - let pending = surface.pending.read().unwrap(); - pending.mode - }; - surface.use_mode(mode)?; - - // drop cursor state to force setting the cursor again. - *surface.cursor.lock().unwrap() = CursorState { - position: None, - hotspot: (0, 0), - framebuffer: None, - }; - } - } - } - Ok(()) - } -} diff --git a/src/backend/drm/common/fallback.rs b/src/backend/drm/common/fallback.rs deleted file mode 100644 index b853001..0000000 --- a/src/backend/drm/common/fallback.rs +++ /dev/null @@ -1,584 +0,0 @@ -//! -//! Types to make fallback device initialization easier -//! - -// The macros in this module rely on these constructs -#![allow(clippy::needless_arbitrary_self_type)] - -#[cfg(feature = "backend_drm_egl")] -use crate::backend::drm::egl::{Arguments as EglDeviceArguments, EglDevice, Error as EglDeviceError}; -#[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))] -use crate::backend::drm::{atomic::AtomicDrmDevice, legacy::LegacyDrmDevice}; -use crate::backend::drm::{common::Error, Device, DeviceHandler, RawDevice, RawSurface, Surface}; -#[cfg(all(feature = "backend_drm_gbm", feature = "backend_drm_eglstream"))] -use crate::backend::drm::{ - eglstream::{EglStreamDevice, Error as EglStreamError}, - gbm::{Error as GbmError, GbmDevice}, -}; -#[cfg(feature = "backend_drm_egl")] -use crate::backend::egl::context::{GlAttributes, PixelFormatRequirements}; -#[cfg(feature = "backend_drm_egl")] -use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface}; -use crate::backend::egl::Error as EGLError; -#[cfg(feature = "use_system_lib")] -use crate::backend::egl::{display::EGLBufferReader, EGLGraphicsBackend}; -#[cfg(feature = "renderer_gl")] -use crate::backend::graphics::gl::GLGraphicsBackend; -#[cfg(feature = "renderer_gl")] -use crate::backend::graphics::PixelFormat; -use crate::backend::graphics::{CursorBackend, SwapBuffersError}; - -use drm::{ - control::{connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Mode, ResourceHandles}, - Device as BasicDevice, SystemError as DrmError, -}; -#[cfg(feature = "renderer_gl")] -use nix::libc::c_void; -use nix::libc::dev_t; -use std::env; -use std::fmt; -use std::os::unix::io::{AsRawFd, RawFd}; -#[cfg(feature = "use_system_lib")] -use wayland_server::Display; - -/// [`Device`](::backend::drm::Device) Wrapper to assist fallback -/// in case initialization of the preferred device type fails. -#[derive(Debug)] -pub enum FallbackDevice { - /// Variant for successful initialization of the preferred device - Preference(D1), - /// Variant for the fallback device - Fallback(D2), -} - -struct FallbackDeviceHandlerD1( - Box> + 'static>, -) -where - E1: std::error::Error + Send + 'static, - E2: std::error::Error + Send + 'static, - C: IntoIterator + 'static, - S1: Surface + 'static, - S2: Surface + 'static, - D1: Device + 'static, - D2: Device + 'static; - -impl DeviceHandler for FallbackDeviceHandlerD1 -where - E1: std::error::Error + Send + 'static, - E2: std::error::Error + Send + 'static, - C: IntoIterator + 'static, - S1: Surface + 'static, - S2: Surface + 'static, - D1: Device + 'static, - D2: Device + 'static, -{ - type Device = D1; - - fn vblank(&mut self, crtc: crtc::Handle) { - self.0.vblank(crtc) - } - fn error(&mut self, error: E1) { - self.0.error(EitherError::Either(error)); - } -} - -struct FallbackDeviceHandlerD2( - Box> + 'static>, -) -where - E1: std::error::Error + Send + 'static, - E2: std::error::Error + Send + 'static, - C: IntoIterator + 'static, - S1: Surface + 'static, - S2: Surface + 'static, - D1: Device + 'static, - D2: Device + 'static; - -impl DeviceHandler for FallbackDeviceHandlerD2 -where - E1: std::error::Error + Send + 'static, - E2: std::error::Error + Send + 'static, - C: IntoIterator + 'static, - S1: Surface + 'static, - S2: Surface + 'static, - D1: Device + 'static, - D2: Device + 'static, -{ - type Device = D2; - - fn vblank(&mut self, crtc: crtc::Handle) { - self.0.vblank(crtc) - } - fn error(&mut self, error: E2) { - self.0.error(EitherError::Or(error)); - } -} - -#[cfg(feature = "backend_session")] -impl crate::signaling::Linkable for FallbackDevice -where - D1: Device + crate::signaling::Linkable + 'static, - D2: Device + crate::signaling::Linkable + 'static, -{ - fn link(&mut self, signal: crate::signaling::Signaler) { - match self { - FallbackDevice::Preference(d) => d.link(signal), - FallbackDevice::Fallback(d) => d.link(signal), - } - } -} - -/// [`Surface`](::backend::drm::Surface) Wrapper to assist fallback -/// in case initialization of the preferred device type fails. -#[derive(Debug)] -pub enum FallbackSurface { - /// Variant for successful initialization of the preferred device - Preference(S1), - /// Variant for the fallback device - Fallback(S2), -} - -/// Enum uniting two kinds of possible errors. -#[derive(Debug, thiserror::Error)] -pub enum EitherError { - /// Either this error - #[error("{0}")] - Either(#[source] E1), - /// Or this error - #[error("{0}")] - Or(#[source] E2), -} - -impl Into for EitherError -where - E1: std::error::Error + Into + 'static, - E2: std::error::Error + Into + 'static, -{ - fn into(self) -> SwapBuffersError { - match self { - EitherError::Either(err) => err.into(), - EitherError::Or(err) => err.into(), - } - } -} - -#[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))] -impl FallbackDevice, LegacyDrmDevice> { - /// Try to initialize an [`AtomicDrmDevice`](::backend::drm::atomic::AtomicDrmDevice) - /// and fall back to a [`LegacyDrmDevice`] if atomic-modesetting is not supported. - /// - /// # Arguments - /// - /// - `fd` - Open drm node (needs to be clonable to be passed to multiple initializers) - /// - `disable_connectors` - Setting this to true will initialize all connectors \ - /// as disabled on device creation. smithay enables connectors, when attached \ - /// to a surface, and disables them, when detached. Setting this to `false` \ - /// requires usage of `drm-rs` to disable unused connectors to prevent them \ - /// showing garbage, but will also prevent flickering of already turned on \ - /// connectors (assuming you won't change the resolution). - /// - `logger` - Optional [`slog::Logger`] to be used by the resulting device. - /// - /// # Return - /// - /// Returns an error, if both devices fail to initialize due to `fd` being no valid - /// drm node or the device being not accessible. - pub fn new(fd: A, disable_connectors: bool, logger: L) -> Result - where - L: Into>, - { - let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm_fallback")); - info!(log, "Trying to initialize AtomicDrmDevice"); - - let force_legacy = env::var("SMITHAY_USE_LEGACY") - .map(|x| { - x == "1" || x.to_lowercase() == "true" || x.to_lowercase() == "yes" || x.to_lowercase() == "y" - }) - .unwrap_or(false); - if force_legacy { - info!(log, "SMITHAY_USE_LEGACY is set. Forcing LegacyDrmDevice."); - return Ok(FallbackDevice::Fallback(LegacyDrmDevice::new( - fd, - disable_connectors, - log, - )?)); - } - - match AtomicDrmDevice::new(fd.clone(), disable_connectors, log.clone()) { - Ok(dev) => Ok(FallbackDevice::Preference(dev)), - Err(err) => { - warn!(log, "Failed to initialize preferred AtomicDrmDevice: {}", err); - info!(log, "Falling back to fallback LegacyDrmDevice"); - Ok(FallbackDevice::Fallback(LegacyDrmDevice::new( - fd, - disable_connectors, - log, - )?)) - } - } - } -} - -#[cfg(all( - feature = "backend_drm_gbm", - feature = "backend_drm_eglstream", - feature = "backend_udev" -))] -type GbmOrEglStreamError = EitherError< - GbmError<<::Surface as Surface>::Error>, - EglStreamError<<::Surface as Surface>::Error>, ->; -#[cfg(all( - feature = "backend_drm_gbm", - feature = "backend_drm_eglstream", - feature = "backend_udev" -))] -impl FallbackDevice, EglStreamDevice> -where - D: RawDevice + ControlDevice + 'static, -{ - /// Try to initialize a [`GbmDevice`](::backend::drm::gbm::GbmDevice) - /// or a [`EglStreamDevice`](::backend::drm::eglstream::EglStreamDevice) depending on the used driver. - /// - /// # Arguments - /// - /// - `dev` - Open drm device (needs implement [`RawDevice`](::backend::drm::RawDevice)) - /// - `logger` - Optional [`slog::Logger`] to be used by the resulting device. - /// - /// # Return - /// - /// Returns an error, if the choosen device fails to initialize. - pub fn new(dev: D, logger: L) -> Result> - where - L: Into>, - { - let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm_fallback")); - - let driver = crate::backend::udev::driver(dev.device_id()).expect("Failed to query device"); - info!(log, "Drm device driver: {:?}", driver); - if driver.as_ref().and_then(|x| x.to_str()) == Some("nvidia") { - Ok(FallbackDevice::Fallback( - EglStreamDevice::new(dev, log).map_err(EitherError::Or)?, - )) - } else { - Ok(FallbackDevice::Preference( - GbmDevice::new(dev, log.clone()).map_err(EitherError::Either)?, - )) - } - } -} - -#[cfg(feature = "backend_drm_egl")] -type EglUnderlying = EitherError< - EglDeviceError<<::Surface as Surface>::Error>, - EglDeviceError<<::Surface as Surface>::Error>, ->; - -#[cfg(feature = "backend_drm_egl")] -type FallbackEglDevice = FallbackDevice, EglDevice>; - -#[cfg(feature = "backend_drm_egl")] -impl FallbackDevice -where - D1: Device + 'static, - ::Surface: NativeSurface::Surface as Surface>::Error>, - D2: Device + 'static, - ::Surface: NativeSurface::Surface as Surface>::Error>, -{ - /// Try to create a new [`EglDevice`] from a [`FallbackDevice`] containing two compatible device types. - /// - /// This helper function is necessary as implementing [`NativeDevice`](::backend::egl::native::NativeDevice) for [`FallbackDevice`] is impossible - /// as selecting the appropriate [`Backend`](::backend::egl::native::Backend) would be impossible without knowing - /// the underlying device type, that was selected by [`FallbackDevice`]. - /// - /// Returns an error if the context creation was not successful. - pub fn new_egl( - dev: FallbackDevice, - logger: L, - ) -> Result, EglUnderlying> - where - B1: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D1: NativeDisplay, - B2: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D2: NativeDisplay, - L: Into>, - { - let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm_fallback")); - match dev { - FallbackDevice::Preference(gbm) => match EglDevice::new(gbm, log) { - Ok(dev) => Ok(FallbackDevice::Preference(dev)), - Err(err) => Err(EglUnderlying::::Either(err)), - }, - FallbackDevice::Fallback(eglstream) => match EglDevice::new(eglstream, log) { - Ok(dev) => Ok(FallbackDevice::Fallback(dev)), - Err(err) => Err(EglUnderlying::::Or(err)), - }, - } - } - - /// Try to create a new [`EglDevice`] from a [`FallbackDevice`] containing two compatible device types with - /// the given attributes and requirements as defaults for new surfaces. - /// - /// This helper function is necessary as implementing [`NativeDevice`](::backend::egl::native::NativeDevice) for [`FallbackDevice`] is impossible - /// as selecting the appropriate [`Backend`](::backend::egl::native::Backend) would be impossible without knowing - /// the underlying device type, that was selected by [`FallbackDevice`]. - /// - /// Returns an error if the context creation was not successful. - pub fn new_egl_with_defaults( - dev: FallbackDevice, - default_attributes: GlAttributes, - default_requirements: PixelFormatRequirements, - logger: L, - ) -> Result, EglUnderlying> - where - B1: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D1: NativeDisplay, - B2: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D2: NativeDisplay, - L: Into>, - { - let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm_fallback")); - match dev { - FallbackDevice::Preference(gbm) => { - match EglDevice::new_with_defaults(gbm, default_attributes, default_requirements, log) { - Ok(dev) => Ok(FallbackDevice::Preference(dev)), - Err(err) => Err(EglUnderlying::::Either(err)), - } - } - FallbackDevice::Fallback(eglstream) => { - match EglDevice::new_with_defaults(eglstream, default_attributes, default_requirements, log) { - Ok(dev) => Ok(FallbackDevice::Fallback(dev)), - Err(err) => Err(EglUnderlying::::Or(err)), - } - } - } - } -} - -macro_rules! fallback_device_impl { - ($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => { - fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return { - match self { - FallbackDevice::Preference(dev) => dev.$func_name($($arg_name),*), - FallbackDevice::Fallback(dev) => dev.$func_name($($arg_name),*), - } - } - }; - ($func_name:ident, $self:ty, $return:ty) => { - fallback_device_impl!($func_name, $self, $return,); - }; - ($func_name:ident, $self:ty) => { - fallback_device_impl!($func_name, $self, ()); - }; -} -macro_rules! fallback_device_err_impl { - ($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => { - fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return { - match self { - FallbackDevice::Preference(dev) => dev.$func_name($($arg_name),*).map_err(EitherError::Either), - FallbackDevice::Fallback(dev) => dev.$func_name($($arg_name),*).map_err(EitherError::Or), - } - } - }; - ($func_name:ident, $self:ty, $return:ty) => { - fallback_device_err_impl!($func_name, $self, $return,); - }; -} - -macro_rules! fallback_surface_impl { - ($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => { - fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return { - match self { - FallbackSurface::Preference(dev) => dev.$func_name($($arg_name),*), - FallbackSurface::Fallback(dev) => dev.$func_name($($arg_name),*), - } - } - }; - ($func_name:ident, $self:ty, $return:ty) => { - fallback_surface_impl!($func_name, $self, $return,); - }; - ($func_name:ident, $self:ty) => { - fallback_surface_impl!($func_name, $self, ()); - }; -} -macro_rules! fallback_surface_err_impl { - ($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => { - fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return { - match self { - FallbackSurface::Preference(dev) => dev.$func_name($($arg_name),*).map_err(EitherError::Either), - FallbackSurface::Fallback(dev) => dev.$func_name($($arg_name),*).map_err(EitherError::Or), - } - } - }; -} - -impl AsRawFd for FallbackDevice { - fallback_device_impl!(as_raw_fd, &Self, RawFd); -} -impl BasicDevice for FallbackDevice {} -impl ControlDevice for FallbackDevice {} - -impl Device for FallbackDevice -where - // Connectors need to match for both Surfaces - E1: std::error::Error + Send + 'static, - E2: std::error::Error + Send + 'static, - C: IntoIterator + 'static, - S1: Surface + 'static, - S2: Surface + 'static, - D1: Device + 'static, - D2: Device + 'static, -{ - type Surface = FallbackSurface; - - fallback_device_impl!(device_id, &Self, dev_t); - fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { - match self { - FallbackDevice::Preference(dev) => dev.set_handler(FallbackDeviceHandlerD1(Box::new(handler))), - FallbackDevice::Fallback(dev) => dev.set_handler(FallbackDeviceHandlerD2(Box::new(handler))), - } - } - fallback_device_impl!(clear_handler, &mut Self); - fn create_surface( - &mut self, - crtc: crtc::Handle, - mode: Mode, - connectors: &[connector::Handle], - ) -> Result> { - match self { - FallbackDevice::Preference(dev) => Ok(FallbackSurface::Preference( - dev.create_surface(crtc, mode, connectors) - .map_err(EitherError::Either)?, - )), - FallbackDevice::Fallback(dev) => Ok(FallbackSurface::Fallback( - dev.create_surface(crtc, mode, connectors) - .map_err(EitherError::Or)?, - )), - } - } - fallback_device_impl!(process_events, &mut Self); - fallback_device_err_impl!(resource_handles, &Self, Result>); - fallback_device_impl!(get_connector_info, &Self, Result, conn: connector::Handle); - fallback_device_impl!(get_crtc_info, &Self, Result, crtc: crtc::Handle); - fallback_device_impl!(get_encoder_info, &Self, Result, enc: encoder::Handle); - fallback_device_impl!(get_framebuffer_info, &Self, Result, fb: framebuffer::Handle); - fallback_device_impl!(get_plane_info, &Self, Result, plane : plane::Handle); -} - -// Impl RawDevice where underlying types implement RawDevice -impl RawDevice for FallbackDevice -where - // Connectors need to match for both Surfaces - E1: std::error::Error + Send + 'static, - E2: std::error::Error + Send + 'static, - C: IntoIterator + 'static, - S1: RawSurface + Surface + 'static, - S2: RawSurface + Surface + 'static, - D1: RawDevice + 'static, - D2: RawDevice + 'static, -{ - type Surface = FallbackSurface; -} - -#[cfg(feature = "use_system_lib")] -impl EGLGraphicsBackend - for FallbackDevice -{ - fallback_device_impl!(bind_wl_display, &Self, Result, display : &Display); -} - -impl Surface for FallbackSurface -where - // Connectors need to match for both Surfaces - E1: std::error::Error + Send + 'static, - E2: std::error::Error + Send + 'static, - C: IntoIterator + 'static, - S1: Surface + 'static, - S2: Surface + 'static, -{ - type Error = EitherError; - type Connectors = C; - - fallback_surface_impl!(crtc, &Self, crtc::Handle); - fallback_surface_impl!(current_connectors, &Self, C); - fallback_surface_impl!(pending_connectors, &Self, C); - fallback_surface_err_impl!(add_connector, &Self, Result<(), EitherError>, conn: connector::Handle); - fallback_surface_err_impl!(remove_connector, &Self, Result<(), EitherError>, conn: connector::Handle); - fallback_surface_err_impl!(set_connectors, &Self, Result<(), EitherError>, conns: &[connector::Handle]); - fallback_surface_impl!(current_mode, &Self, Mode); - fallback_surface_impl!(pending_mode, &Self, Mode); - fallback_surface_err_impl!(use_mode, &Self, Result<(), EitherError>, mode: Mode); -} - -impl RawSurface for FallbackSurface -where - E1: std::error::Error + Send + 'static, - E2: std::error::Error + Send + 'static, - C: IntoIterator + 'static, - S1: RawSurface + Surface + 'static, - S2: RawSurface + Surface + 'static, -{ - fallback_surface_impl!(commit_pending, &Self, bool); - fallback_surface_err_impl!(commit, &Self, Result<(), EitherError>, fb: framebuffer::Handle); - fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), EitherError> { - match self { - FallbackSurface::Preference(dev) => { - RawSurface::page_flip(dev, framebuffer).map_err(EitherError::Either) - } - FallbackSurface::Fallback(dev) => { - RawSurface::page_flip(dev, framebuffer).map_err(EitherError::Or) - } - } - } -} - -impl AsRawFd for FallbackSurface { - fallback_surface_impl!(as_raw_fd, &Self, RawFd); -} -impl BasicDevice for FallbackSurface {} -impl ControlDevice for FallbackSurface {} - -impl CursorBackend for FallbackSurface -where - E1: std::error::Error + Send + 'static, - E2: std::error::Error + Send + 'static, - E3: std::error::Error + 'static, - E4: std::error::Error + 'static, - CF: ?Sized, - C: IntoIterator + 'static, - S1: Surface + CursorBackend + 'static, - S2: Surface + CursorBackend + 'static, -{ - type CursorFormat = CF; - type Error = EitherError; - - fallback_surface_err_impl!(set_cursor_position, &Self, Result<(), EitherError>, x: u32, y: u32); - fallback_surface_err_impl!(set_cursor_representation, &Self, Result<(), EitherError>, buffer: &Self::CursorFormat, hotspot: (u32, u32)); - fallback_surface_err_impl!(clear_cursor_representation, &Self, Result<(), EitherError>,); -} - -#[cfg(feature = "renderer_gl")] -impl GLGraphicsBackend for FallbackSurface -where - E1: std::error::Error + Send + 'static, - E2: std::error::Error + Send + 'static, - C: IntoIterator + 'static, - S1: Surface + GLGraphicsBackend + 'static, - S2: Surface + GLGraphicsBackend + 'static, -{ - fallback_surface_impl!(swap_buffers, &Self, Result<(), SwapBuffersError>); - fallback_surface_impl!(get_proc_address, &Self, *const c_void, symbol: &str); - fallback_surface_impl!(get_framebuffer_dimensions, &Self, (u32, u32)); - fallback_surface_impl!(is_current, &Self, bool); - unsafe fn make_current(&self) -> Result<(), SwapBuffersError> { - match self { - FallbackSurface::Preference(dev) => dev.make_current(), - FallbackSurface::Fallback(dev) => dev.make_current(), - } - } - fallback_surface_impl!(get_pixel_format, &Self, PixelFormat); -} diff --git a/src/backend/drm/device/atomic.rs b/src/backend/drm/device/atomic.rs new file mode 100644 index 0000000..1ed7b1a --- /dev/null +++ b/src/backend/drm/device/atomic.rs @@ -0,0 +1,254 @@ +use std::collections::HashMap; +use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; +use std::os::unix::io::AsRawFd; + +use drm::control::atomic::AtomicModeReq; +use drm::control::{Device as ControlDevice, AtomicCommitFlags, PropertyValueSet, ResourceHandle, crtc, connector, framebuffer, plane, property}; + +use super::{DevPath, FdWrapper}; +use crate::backend::drm::error::Error; + +type OldState = ( + Vec<(connector::Handle, PropertyValueSet)>, + Vec<(crtc::Handle, PropertyValueSet)>, + Vec<(framebuffer::Handle, PropertyValueSet)>, + Vec<(plane::Handle, PropertyValueSet)>, +); + +pub type Mapping = ( + HashMap>, + HashMap>, + HashMap>, + HashMap>, +); + +pub struct AtomicDrmDevice { + pub(crate) fd: Arc>, + pub(crate) active: Arc, + old_state: OldState, + pub(crate) prop_mapping: Mapping, + logger: ::slog::Logger, +} + +impl AtomicDrmDevice { + pub fn new(fd: Arc>, active: Arc, disable_connectors: bool, logger: ::slog::Logger) -> Result + { + let mut dev = AtomicDrmDevice { + fd, + active, + old_state: (Vec::new(), Vec::new(), Vec::new(), Vec::new()), + prop_mapping: (HashMap::new(), HashMap::new(), HashMap::new(), HashMap::new()), + logger: logger.new(o!("smithay_module" => "backend_drm_atomic", "drm_module" => "device")), + }; + + // Enumerate (and save) the current device state. + let res_handles = dev.fd.resource_handles() + .map_err(|source| Error::Access { + errmsg: "Error loading drm resources", + dev: dev.fd.dev_path(), + source, + })?; + + let plane_handles = dev.fd.plane_handles().map_err(|source| Error::Access { + errmsg: "Error loading planes", + dev: dev.fd.dev_path(), + source, + })?; + let planes = plane_handles.planes(); + + let mut old_state = dev.old_state.clone(); + let mut mapping = dev.prop_mapping.clone(); + + // This helper function takes a snapshot of the current device properties. + // (everything in the atomic api is set via properties.) + dev.add_props(res_handles.connectors(), &mut old_state.0)?; + dev.add_props(res_handles.crtcs(), &mut old_state.1)?; + dev.add_props(res_handles.framebuffers(), &mut old_state.2)?; + dev.add_props(planes, &mut old_state.3)?; + + // And because the mapping is not consistent across devices, + // we also need to lookup the handle for a property name. + // And we do this a fair bit, so lets cache that mapping. + dev.map_props(res_handles.connectors(), &mut mapping.0)?; + dev.map_props(res_handles.crtcs(), &mut mapping.1)?; + dev.map_props(res_handles.framebuffers(), &mut mapping.2)?; + dev.map_props(planes, &mut mapping.3)?; + + dev.old_state = old_state; + dev.prop_mapping = mapping; + trace!(dev.logger, "Mapping: {:#?}", dev.prop_mapping); + + // If the user does not explicitly requests us to skip this, + // we clear out the complete connector<->crtc mapping on device creation. + // + // The reason is, that certain operations may be racy otherwise. Surfaces can + // exist on different threads: as a result, we cannot really enumerate the current state + // (it might be changed on another thread during the enumeration). And commits can fail, + // if e.g. a connector is already bound to another surface, which is difficult to analyse at runtime. + // + // An easy workaround is to set a known state on device creation, so we can only + // run into these errors on our own and not because previous compositors left the device + // in a funny state. + if disable_connectors { + dev.reset_state()?; + } + + Ok(dev) + } + + // Add all properties of given handles to a given drm resource type to state. + // You may use this to snapshot the current state of the drm device (fully or partially). + fn add_props(&self, handles: &[T], state: &mut Vec<(T, PropertyValueSet)>) -> Result<(), Error> + where + T: ResourceHandle, + { + let iter = handles.iter().map(|x| (x, self.fd.get_properties(*x))); + if let Some(len) = iter.size_hint().1 { + state.reserve_exact(len) + } + + iter.map(|(x, y)| (*x, y)) + .try_for_each(|(x, y)| match y { + Ok(y) => { + state.push((x, y)); + Ok(()) + } + Err(err) => Err(err), + }) + .map_err(|source| Error::Access { + errmsg: "Error reading properties", + dev: self.fd.dev_path(), + source, + }) + } + + /// Create a mapping of property names and handles for given handles of a given drm resource type. + /// You may use this to easily lookup properties by name instead of going through this procedure manually. + fn map_props( + &self, + handles: &[T], + mapping: &mut HashMap>, + ) -> Result<(), Error> + where + T: ResourceHandle + Eq + std::hash::Hash, + { + handles + .iter() + .map(|x| (x, self.fd.get_properties(*x))) + .try_for_each(|(handle, props)| { + let mut map = HashMap::new(); + match props { + Ok(props) => { + let (prop_handles, _) = props.as_props_and_values(); + for prop in prop_handles { + if let Ok(info) = self.fd.get_property(*prop) { + let name = info.name().to_string_lossy().into_owned(); + map.insert(name, *prop); + } + } + mapping.insert(*handle, map); + Ok(()) + } + Err(err) => Err(err), + } + }) + .map_err(|source| Error::Access { + errmsg: "Error reading properties on {:?}", + dev: self.fd.dev_path(), + source, + }) + } + + pub(super) fn reset_state(&self) -> Result<(), Error> { + // reset state sets the connectors into a known state (all disabled), + // for the same reasons we do this on device creation. + // + // We might end up with conflicting commit requirements, if we want to restore our state, + // on top of the state the previous compositor left the device in. + // This is because we do commits per surface and not per device, so we do a global + // commit here, to fix any conflicts. + let res_handles = self.fd.resource_handles() + .map_err(|source| Error::Access { + errmsg: "Error loading drm 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(); + for conn in res_handles.connectors() { + let prop = self + .prop_mapping + .0 + .get(&conn) + .expect("Unknown handle") + .get("CRTC_ID") + .expect("Unknown property CRTC_ID"); + req.add_property(*conn, *prop, property::Value::CRTC(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() { + let mode_prop = self + .prop_mapping + .1 + .get(&crtc) + .expect("Unknown handle") + .get("MODE_ID") + .expect("Unknown property MODE_ID"); + let active_prop = self + .prop_mapping + .1 + .get(&crtc) + .expect("Unknown handle") + .get("ACTIVE") + .expect("Unknown property ACTIVE"); + req.add_property(*crtc, *active_prop, property::Value::Boolean(false)); + req.add_property(*crtc, *mode_prop, property::Value::Unknown(0)); + } + self.fd.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) + .map_err(|source| Error::Access { + errmsg: "Failed to disable connectors", + dev: self.fd.dev_path(), + source, + })?; + + Ok(()) + } +} + +impl Drop for AtomicDrmDevice { + fn drop(&mut self) { + if self.active.load(Ordering::SeqCst) { + // Here we restore the card/tty's to it's previous state. + // In case e.g. getty was running on the tty sets the correct framebuffer again, + // so that getty will be visible. + // We do exit correctly if this fails, but the user will be presented with + // a black screen if no display handler takes control again. + + // create an atomic mode request consisting of all properties we captured on creation. + // TODO, check current connector status and remove deactivated connectors from this req. + let mut req = AtomicModeReq::new(); + fn add_multiple_props( + req: &mut AtomicModeReq, + old_state: &[(T, PropertyValueSet)], + ) { + for (handle, set) in old_state { + let (prop_handles, values) = set.as_props_and_values(); + for (&prop_handle, &val) in prop_handles.iter().zip(values.iter()) { + req.add_raw_property((*handle).into(), prop_handle, val); + } + } + }; + + add_multiple_props(&mut req, &self.old_state.0); + add_multiple_props(&mut req, &self.old_state.1); + add_multiple_props(&mut req, &self.old_state.2); + add_multiple_props(&mut req, &self.old_state.3); + + if let Err(err) = self.fd.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) { + error!(self.logger, "Failed to restore previous state. Error: {}", err); + } + } + } +} diff --git a/src/backend/drm/device/legacy.rs b/src/backend/drm/device/legacy.rs new file mode 100644 index 0000000..d78f955 --- /dev/null +++ b/src/backend/drm/device/legacy.rs @@ -0,0 +1,184 @@ +use std::collections::HashMap; +use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; +use std::os::unix::io::AsRawFd; + +use drm::control::{Device as ControlDevice, crtc, connector}; + +use super::{DevPath, FdWrapper}; +use crate::backend::drm::error::Error; + +pub struct LegacyDrmDevice { + pub(crate) fd: Arc>, + pub(crate) active: Arc, + old_state: HashMap)>, + logger: ::slog::Logger, +} + +impl LegacyDrmDevice { + pub fn new(fd: Arc>, active: Arc, disable_connectors: bool, logger: slog::Logger) -> Result + { + let mut dev = LegacyDrmDevice { + fd, + active, + old_state: HashMap::new(), + logger: logger.new(o!("smithay_module" => "backend_drm_legacy", "drm_module" => "device")), + }; + + // Enumerate (and save) the current device state. + // We need to keep the previous device configuration to restore the state later, + // so we query everything, that we can set. + let res_handles = dev.fd.resource_handles() + .map_err(|source| Error::Access { + errmsg: "Error loading drm resources", + dev: dev.fd.dev_path(), + source, + })?; + for &con in res_handles.connectors() { + let con_info = dev.fd.get_connector(con).map_err(|source| Error::Access { + errmsg: "Error loading connector info", + dev: dev.fd.dev_path(), + source, + })?; + if let Some(enc) = con_info.current_encoder() { + let enc_info = dev.fd.get_encoder(enc).map_err(|source| Error::Access { + errmsg: "Error loading encoder info", + dev: dev.fd.dev_path(), + source, + })?; + if let Some(crtc) = enc_info.crtc() { + let info = dev.fd.get_crtc(crtc).map_err(|source| Error::Access { + errmsg: "Error loading crtc info", + dev: dev.fd.dev_path(), + source, + })?; + dev.old_state + .entry(crtc) + .or_insert((info, Vec::new())) + .1 + .push(con); + } + } + } + + + // If the user does not explicitly requests us to skip this, + // we clear out the complete connector<->crtc mapping on device creation. + // + // The reason is, that certain operations may be racy otherwise, as surfaces can + // exist on different threads. As a result, we cannot enumerate the current state + // on surface creation (it might be changed on another thread during the enumeration). + // An easy workaround is to set a known state on device creation. + if disable_connectors { + dev.reset_state()?; + } + + Ok(dev) + } + + pub(super) fn reset_state(&self) -> Result<(), Error> { + let res_handles = self.fd.resource_handles().map_err(|source| Error::Access { + errmsg: "Failed to query resource handles", + dev: self.fd.dev_path(), + source, + })?; + set_connector_state(&*self.fd, res_handles.connectors().iter().copied(), false)?; + + for crtc in res_handles.crtcs() { + // null commit (necessary to trigger removal on the kernel side with the legacy api.) + self.fd.set_crtc(*crtc, None, (0, 0), &[], None) + .map_err(|source| Error::Access { + errmsg: "Error setting crtc", + dev: self.fd.dev_path(), + source, + })?; + } + + Ok(()) + } +} + +impl Drop for LegacyDrmDevice { + fn drop(&mut self) { + info!(self.logger, "Dropping device: {:?}", self.fd.dev_path()); + if self.active.load(Ordering::SeqCst) { + // Here we restore the tty to it's previous state. + // In case e.g. getty was running on the tty sets the correct framebuffer again, + // so that getty will be visible. + // We do exit correctly, if this fails, but the user will be presented with + // a black screen, if no display handler takes control again. + for (handle, (info, connectors)) in self.old_state.drain() { + if let Err(err) = self.fd.set_crtc( + handle, + info.framebuffer(), + info.position(), + &connectors, + info.mode(), + ) { + error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err); + } + } + } + } +} + +pub fn set_connector_state( + dev: &D, + connectors: impl Iterator, + enabled: bool, +) -> Result<(), Error> +where + D: ControlDevice +{ + // for every connector... + for conn in connectors { + let info = dev + .get_connector(conn) + .map_err(|source| Error::Access { + errmsg: "Failed to get connector infos", + dev: dev.dev_path(), + source, + })?; + // that is currently connected ... + if info.state() == connector::State::Connected { + // get a list of it's properties. + let props = dev + .get_properties(conn) + .map_err(|source| Error::Access { + errmsg: "Failed to get properties for connector", + dev: dev.dev_path(), + source, + })?; + let (handles, _) = props.as_props_and_values(); + // for every handle ... + for handle in handles { + // get information of that property + let info = dev + .get_property(*handle) + .map_err(|source| Error::Access { + errmsg: "Failed to get property of connector", + dev: dev.dev_path(), + source, + })?; + // to find out, if we got the handle of the "DPMS" property ... + if info.name().to_str().map(|x| x == "DPMS").unwrap_or(false) { + // so we can use that to turn on / off the connector + dev.set_property( + conn, + *handle, + if enabled { + 0 /*DRM_MODE_DPMS_ON*/ + } else { + 3 /*DRM_MODE_DPMS_OFF*/ + }, + ) + .map_err(|source| Error::Access { + errmsg: "Failed to set property of connector", + dev: dev.dev_path(), + source, + })?; + } + } + } + } + Ok(()) +} \ No newline at end of file diff --git a/src/backend/drm/device/mod.rs b/src/backend/drm/device/mod.rs new file mode 100644 index 0000000..b22bee9 --- /dev/null +++ b/src/backend/drm/device/mod.rs @@ -0,0 +1,365 @@ +use std::rc::Rc; +use std::cell::RefCell; +use std::sync::{Arc, atomic::AtomicBool}; +use std::path::PathBuf; +use std::os::unix::io::{AsRawFd, RawFd}; + +use calloop::{generic::Generic, InsertError, LoopHandle, Source}; +use drm::{Device as BasicDevice, ClientCapability}; +use drm::control::{ResourceHandles, PlaneResourceHandles, Device as ControlDevice, Event, Mode, PlaneType, crtc, plane, connector}; +use nix::libc::dev_t; +use nix::sys::stat::fstat; + +pub(super) mod atomic; +pub(super) mod legacy; +use atomic::AtomicDrmDevice; +use legacy::LegacyDrmDevice; +use super::surface::{DrmSurface, DrmSurfaceInternal, atomic::AtomicDrmSurface, legacy::LegacyDrmSurface}; +use super::error::Error; + +pub struct DrmDevice { + pub(super) dev_id: dev_t, + pub(crate) internal: Arc>, + handler: Rc>>>, + #[cfg(feature = "backend_session")] + pub(super) links: RefCell>, + has_universal_planes: bool, + resources: ResourceHandles, + planes: PlaneResourceHandles, + pub(super) logger: ::slog::Logger, +} + +impl AsRawFd for DrmDevice { + fn as_raw_fd(&self) -> RawFd { + match &*self.internal { + DrmDeviceInternal::Atomic(dev) => dev.fd.as_raw_fd(), + DrmDeviceInternal::Legacy(dev) => dev.fd.as_raw_fd(), + } + } +} +impl BasicDevice for DrmDevice {} +impl ControlDevice for DrmDevice {} + +pub struct FdWrapper { + fd: A, + pub(super) privileged: bool, + logger: ::slog::Logger, +} + +impl AsRawFd for FdWrapper { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} +impl BasicDevice for FdWrapper {} +impl ControlDevice for FdWrapper {} + +impl Drop for FdWrapper { + fn drop(&mut self) { + info!(self.logger, "Dropping device: {:?}", self.dev_path()); + if self.privileged { + if let Err(err) = self.release_master_lock() { + error!(self.logger, "Failed to drop drm master state. Error: {}", err); + } + } + } +} + +pub enum DrmDeviceInternal { + Atomic(AtomicDrmDevice), + Legacy(LegacyDrmDevice), +} +impl AsRawFd for DrmDeviceInternal { + fn as_raw_fd(&self) -> RawFd { + match self { + DrmDeviceInternal::Atomic(dev) => dev.fd.as_raw_fd(), + DrmDeviceInternal::Legacy(dev) => dev.fd.as_raw_fd(), + } + } +} +impl BasicDevice for DrmDeviceInternal {} +impl ControlDevice for DrmDeviceInternal {} + +impl DrmDevice { + pub fn new(fd: A, disable_connectors: bool, logger: L) -> Result + where + A: AsRawFd + Clone + 'static, + L: Into>, + { + let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm")); + info!(log, "DrmDevice initializing"); + + let dev_id = fstat(fd.as_raw_fd()) + .map_err(Error::UnableToGetDeviceId)? + .st_rdev; + let active = Arc::new(AtomicBool::new(true)); + let dev = Arc::new({ + let mut dev = FdWrapper { + fd: fd.clone(), + privileged: false, + logger: log.clone(), + }; + + // We want to modeset, so we better be the master, if we run via a tty session. + // This is only needed on older kernels. Newer kernels grant this permission, + // if no other process is already the *master*. So we skip over this error. + if dev.acquire_master_lock().is_err() { + warn!(log, "Unable to become drm master, assuming unprivileged mode"); + } else { + dev.privileged = true; + } + + dev + }); + + let has_universal_planes = dev.set_client_capability(ClientCapability::UniversalPlanes, true).is_ok(); + let resources = dev.resource_handles().map_err(|source| Error::Access { + errmsg: "Error loading resource handles", + 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, disable_connectors, log.clone())?); + + Ok(DrmDevice { + dev_id, + internal, + handler: Rc::new(RefCell::new(None)), + #[cfg(feature = "backend_session")] + links: RefCell::new(Vec::new()), + has_universal_planes, + resources, + planes, + logger: log, + }) + } + + fn create_internal(dev: Arc>, active: Arc, disable_connectors: bool, log: ::slog::Logger) -> Result, Error> { + let force_legacy = std::env::var("SMITHAY_USE_LEGACY") + .map(|x| { + x == "1" || x.to_lowercase() == "true" || x.to_lowercase() == "yes" || x.to_lowercase() == "y" + }) + .unwrap_or(false); + + if force_legacy { + info!(log, "SMITHAY_USE_LEGACY is set. Forcing LegacyDrmDevice."); + }; + + Ok(if dev.set_client_capability(ClientCapability::Atomic, true).is_ok() && !force_legacy { + DrmDeviceInternal::Atomic(AtomicDrmDevice::new(dev, active, disable_connectors, log)?) + } else { + info!(log, "Falling back to LegacyDrmDevice"); + DrmDeviceInternal::Legacy(LegacyDrmDevice::new(dev, active, disable_connectors, log)?) + }) + } + + pub fn process_events(&mut self) { + match self.receive_events() { + Ok(events) => { + for event in events { + if let Event::PageFlip(event) = event { + trace!(self.logger, "Got a page-flip event for crtc ({:?})", event.crtc); + if let Some(handler) = self.handler.borrow_mut().as_mut() { + handler.vblank(event.crtc); + } + } else { + trace!( + self.logger, + "Got a non-page-flip event of device '{:?}'.", + self.dev_path() + ); + } + } + } + Err(source) => { + if let Some(handler) = self.handler.borrow_mut().as_mut() { + handler.error(Error::Access { + errmsg: "Error processing drm events", + dev: self.dev_path(), + source, + }); + } + } + } + } + + pub fn is_atomic(&self) -> bool { + match *self.internal { + DrmDeviceInternal::Atomic(_) => true, + DrmDeviceInternal::Legacy(_) => false, + } + } + + pub fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { + let handler = Some(Box::new(handler) as Box); + *self.handler.borrow_mut() = handler; + } + + pub fn clear_handler(&mut self) { + self.handler.borrow_mut().take(); + } + + pub fn crtcs(&self) -> &[crtc::Handle] { + self.resources.crtcs() + } + + 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!() + } + + pub fn create_surface(&self, crtc: crtc::Handle, plane: plane::Handle, mode: Mode, connectors: &[connector::Handle]) -> Result, Error> { + if connectors.is_empty() { + return Err(Error::SurfaceWithoutConnectors(crtc)); + } + + let info = self.get_plane(plane).map_err(|source| Error::Access { + errmsg: "Failed to get plane info", + dev: self.dev_path(), + source + })?; + let filter = info.possible_crtcs(); + if !self.resources.filter_crtcs(filter).contains(&crtc) { + return Err(Error::PlaneNotCompatible(crtc, plane)); + } + + let active = match &*self.internal { + DrmDeviceInternal::Atomic(dev) => dev.active.clone(), + DrmDeviceInternal::Legacy(dev) => dev.active.clone(), + }; + + let internal = Arc::new(if self.is_atomic() { + let mapping = match &*self.internal { + DrmDeviceInternal::Atomic(dev) => dev.prop_mapping.clone(), + _ => unreachable!(), + }; + + DrmSurfaceInternal::Atomic(AtomicDrmSurface::new(self.internal.clone(), active, crtc, plane, mapping, mode, connectors, self.logger.clone())?) + } else { + if self.plane_type(plane)? != PlaneType::Primary { + return Err(Error::NonPrimaryPlane(plane)); + } + + DrmSurfaceInternal::Legacy(LegacyDrmSurface::new(self.internal.clone(), active, crtc, mode, connectors, self.logger.clone())?) + }); + + Ok(DrmSurface { + crtc, + plane, + internal, + }) + } +} + +pub struct Planes { + pub primary: plane::Handle, + pub cursor: Option, + pub overlay: Option>, +} + +impl DrmDeviceInternal { + pub(super) fn reset_state(&self) -> Result<(), Error> { + match self { + DrmDeviceInternal::Atomic(dev) => dev.reset_state(), + DrmDeviceInternal::Legacy(dev) => dev.reset_state(), + } + } +} + +/// Trait to receive events of a bound [`DrmDevice`] +/// +/// See [`device_bind`] +pub trait DeviceHandler { + /// A vblank blank event on the provided crtc has happend + fn vblank(&mut self, crtc: crtc::Handle); + /// An error happend while processing events + fn error(&mut self, error: Error); +} + +/// Trait representing open devices that *may* return a `Path` +pub trait DevPath { + /// Returns the path of the open device if possible + fn dev_path(&self) -> Option; +} + +impl DevPath for A { + fn dev_path(&self) -> Option { + use std::fs; + + fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok() + } +} + +/// calloop source associated with a Device +pub type DrmSource = Generic>; + +/// Bind a `Device` to an [`EventLoop`](calloop::EventLoop), +/// +/// This will cause it to recieve events and feed them into a previously +/// set [`DeviceHandler`](DeviceHandler). +pub fn device_bind( + handle: &LoopHandle, + device: DrmDevice, +) -> ::std::result::Result>, InsertError>> +where + A: AsRawFd + 'static, + Data: 'static, +{ + let source = Generic::new(device, calloop::Interest::Readable, calloop::Mode::Level); + + handle.insert_source(source, |_, source, _| { + source.process_events(); + Ok(()) + }) +} diff --git a/src/backend/drm/egl/mod.rs b/src/backend/drm/egl/mod.rs deleted file mode 100644 index 36633ac..0000000 --- a/src/backend/drm/egl/mod.rs +++ /dev/null @@ -1,301 +0,0 @@ -//! -//! [`Device`](Device) and [`Surface`](Surface) -//! implementations using egl contexts and surfaces for efficient rendering. -//! -//! Usually this implementation's [`EglSurface`](::backend::drm::egl::EglSurface)s implementation -//! of [`GLGraphicsBackend`](::backend::graphics::gl::GLGraphicsBackend) will be used -//! to let your compositor render. -//! Take a look at `anvil`s source code for an example of this. -//! -//! For detailed overview of these abstractions take a look at the module documentation of backend::drm. -//! - -use drm::control::{connector, crtc, encoder, framebuffer, plane, Mode, ResourceHandles}; -use drm::SystemError as DrmError; -use nix::libc::dev_t; -use std::cell::RefCell; -use std::collections::HashMap; -use std::fmt; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::rc::Rc; -use std::sync::{Arc, Weak as WeakArc}; -#[cfg(feature = "use_system_lib")] -use wayland_server::Display; - -use super::{Device, DeviceHandler, Surface}; -use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface}; -#[cfg(feature = "use_system_lib")] -use crate::backend::egl::{display::EGLBufferReader, EGLGraphicsBackend}; -use crate::backend::egl::{EGLError as RawEGLError, Error as EGLError, SurfaceCreationError}; - -mod surface; -pub use self::surface::*; -use crate::backend::egl::context::{GlAttributes, PixelFormatRequirements}; -use crate::backend::egl::display::EGLDisplay; - -#[cfg(feature = "backend_session")] -pub mod session; - -/// Errors for the DRM/EGL module -#[derive(thiserror::Error, Debug)] -pub enum Error { - /// EGL Error - #[error("EGL error: {0:}")] - EGL(#[source] EGLError), - /// EGL Error - #[error("EGL error: {0:}")] - RawEGL(#[source] RawEGLError), - /// Underlying backend error - #[error("Underlying backend error: {0:?}")] - Underlying(#[source] U), -} - -pub(crate) type Arguments = (crtc::Handle, Mode, Vec); -type BackendRef = WeakArc::Surface>>; - -/// Representation of an egl device to create egl rendering surfaces -pub struct EglDevice -where - B: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D: Device + NativeDisplay + 'static, - ::Surface: NativeSurface::Surface as Surface>::Error>, -{ - dev: EGLDisplay, - logger: ::slog::Logger, - default_attributes: GlAttributes, - default_requirements: PixelFormatRequirements, - backends: Rc>>>, - #[cfg(feature = "backend_session")] - links: Vec, -} - -// BackendRef does not implement debug, so we have to impl Debug manually -impl fmt::Debug for EglDevice -where - B: Backend::Surface, Error = <::Surface as Surface>::Error> - + fmt::Debug - + 'static, - D: Device + NativeDisplay + fmt::Debug + 'static, - ::Surface: NativeSurface::Surface as Surface>::Error>, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut debug = f.debug_struct("EglDevice"); - - debug - .field("dev", &self.dev) - .field("logger", &self.logger) - .field("default_attributes", &self.default_attributes) - .field("default_requirements", &self.default_requirements) - .field("backends", &"..."); - - #[cfg(feature = "backend_session")] - debug.field("links", &self.links); - - debug.finish() - } -} - -impl AsRawFd for EglDevice -where - B: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D: Device + NativeDisplay + 'static, - ::Surface: NativeSurface::Surface as Surface>::Error>, -{ - fn as_raw_fd(&self) -> RawFd { - self.dev.borrow().as_raw_fd() - } -} - -impl EglDevice -where - B: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D: Device + NativeDisplay + 'static, - ::Surface: NativeSurface::Surface as Surface>::Error>, -{ - /// Try to create a new [`EglDevice`] from an open device. - /// - /// Returns an error if the file is no valid device or context - /// creation was not successful. - pub fn new(dev: D, logger: L) -> Result::Surface as Surface>::Error>> - where - L: Into>, - { - EglDevice::new_with_defaults( - dev, - GlAttributes { - version: None, - profile: None, - debug: cfg!(debug_assertions), - vsync: true, - }, - Default::default(), - logger, - ) - } - - /// Try to create a new [`EglDevice`] from an open device with the given attributes and - /// requirements as defaults for new surfaces. - /// - /// Returns an error if the file is no valid device or context - /// creation was not successful. - pub fn new_with_defaults( - mut dev: D, - default_attributes: GlAttributes, - default_requirements: PixelFormatRequirements, - logger: L, - ) -> Result::Surface as Surface>::Error>> - where - L: Into>, - { - let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_egl")); - - dev.clear_handler(); - - debug!(log, "Creating egl context from device"); - Ok(EglDevice { - dev: EGLDisplay::new(dev, log.clone()).map_err(Error::EGL)?, - default_attributes, - default_requirements, - backends: Rc::new(RefCell::new(HashMap::new())), - logger: log, - #[cfg(feature = "backend_session")] - links: Vec::new(), - }) - } -} - -struct InternalDeviceHandler -where - B: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D: Device + NativeDisplay + 'static, - ::Surface: NativeSurface::Surface as Surface>::Error>, -{ - handler: Box> + 'static>, -} - -impl DeviceHandler for InternalDeviceHandler -where - B: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D: Device + NativeDisplay + 'static, - ::Surface: NativeSurface::Surface as Surface>::Error>, -{ - type Device = D; - - fn vblank(&mut self, crtc: crtc::Handle) { - self.handler.vblank(crtc) - } - fn error(&mut self, error: <::Surface as Surface>::Error) { - self.handler.error(Error::Underlying(error)); - } -} - -impl Device for EglDevice -where - B: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D: Device + NativeDisplay + 'static, - ::Surface: NativeSurface::Surface as Surface>::Error>, -{ - type Surface = EglSurface<::Surface>; - - fn device_id(&self) -> dev_t { - self.dev.borrow().device_id() - } - - fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { - self.dev.borrow_mut().set_handler(InternalDeviceHandler { - handler: Box::new(handler), - }); - } - - fn clear_handler(&mut self) { - self.dev.borrow_mut().clear_handler() - } - - fn create_surface( - &mut self, - crtc: crtc::Handle, - mode: Mode, - connectors: &[connector::Handle], - ) -> Result::Error> { - info!(self.logger, "Initializing EglSurface"); - - let context = self - .dev - .create_context(self.default_attributes, self.default_requirements) - .map_err(Error::EGL)?; - let surface = self - .dev - .create_surface( - context.get_pixel_format(), - self.default_requirements.double_buffer, - context.get_config_id(), - (crtc, mode, Vec::from(connectors)), - ) - .map_err(|err| match err { - SurfaceCreationError::EGLSurfaceCreationFailed(err) => Error::RawEGL(err), - SurfaceCreationError::NativeSurfaceCreationFailed(err) => Error::Underlying(err), - })?; - - let backend = Arc::new(EglSurfaceInternal { context, surface }); - self.backends.borrow_mut().insert(crtc, Arc::downgrade(&backend)); - Ok(EglSurface(backend)) - } - - fn process_events(&mut self) { - self.dev.borrow_mut().process_events() - } - - fn resource_handles(&self) -> Result::Error> { - self.dev.borrow().resource_handles().map_err(Error::Underlying) - } - - fn get_connector_info(&self, conn: connector::Handle) -> std::result::Result { - self.dev.borrow().get_connector_info(conn) - } - fn get_crtc_info(&self, crtc: crtc::Handle) -> std::result::Result { - self.dev.borrow().get_crtc_info(crtc) - } - fn get_encoder_info(&self, enc: encoder::Handle) -> std::result::Result { - self.dev.borrow().get_encoder_info(enc) - } - fn get_framebuffer_info( - &self, - fb: framebuffer::Handle, - ) -> std::result::Result { - self.dev.borrow().get_framebuffer_info(fb) - } - fn get_plane_info(&self, plane: plane::Handle) -> std::result::Result { - self.dev.borrow().get_plane_info(plane) - } -} - -#[cfg(feature = "use_system_lib")] -impl EGLGraphicsBackend for EglDevice -where - B: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D: Device + NativeDisplay + 'static, - ::Surface: NativeSurface::Surface as Surface>::Error>, -{ - fn bind_wl_display(&self, display: &Display) -> Result { - self.dev.bind_wl_display(display) - } -} - -impl Drop for EglDevice -where - B: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D: Device + NativeDisplay + 'static, - ::Surface: NativeSurface::Surface as Surface>::Error>, -{ - fn drop(&mut self) { - self.clear_handler(); - } -} diff --git a/src/backend/drm/egl/session.rs b/src/backend/drm/egl/session.rs deleted file mode 100644 index 2465beb..0000000 --- a/src/backend/drm/egl/session.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! -//! Support to register an [`EglDevice`](EglDevice) -//! to an open [`Session`](::backend::session::Session). -//! - -use drm::control::{connector, crtc, Mode}; -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::{Rc, Weak}; -use std::sync::{atomic::Ordering, Weak as WeakArc}; - -use super::{EglDevice, EglSurfaceInternal}; -use crate::backend::drm::{Device, Surface}; -use crate::backend::egl::{ - ffi, - native::{Backend, NativeDisplay, NativeSurface}, -}; -use crate::{ - backend::session::Signal as SessionSignal, - signaling::{Linkable, Signaler}, -}; - -/// [`SessionObserver`](SessionObserver) -/// linked to the [`EglDevice`](EglDevice) it was -/// created from. -#[derive(Debug)] -pub struct EglDeviceObserver { - backends: Weak>>>>, -} - -impl Linkable for EglDevice -where - B: Backend::Surface, Error = <::Surface as Surface>::Error> - + 'static, - D: Device - + NativeDisplay)> - + Linkable - + 'static, - ::Surface: NativeSurface::Surface as Surface>::Error>, -{ - fn link(&mut self, signaler: Signaler) { - let lower_signal = Signaler::new(); - self.dev.borrow_mut().link(lower_signal.clone()); - let mut observer = EglDeviceObserver { - backends: Rc::downgrade(&self.backends), - }; - - let token = signaler.register(move |&signal| match signal { - SessionSignal::ActivateSession | SessionSignal::ActivateDevice { .. } => { - // Activate lower *before* we process the event - lower_signal.signal(signal); - observer.activate() - } - _ => { - lower_signal.signal(signal); - } - }); - - self.links.push(token); - } -} - -impl EglDeviceObserver { - fn activate(&mut self) { - if let Some(backends) = self.backends.upgrade() { - for (_crtc, backend) in backends.borrow().iter() { - if let Some(backend) = backend.upgrade() { - let old_surface = backend - .surface - .surface - .swap(std::ptr::null_mut(), Ordering::SeqCst); - if !old_surface.is_null() { - unsafe { - ffi::egl::DestroySurface(**backend.surface.display, old_surface as *const _); - } - } - } - } - } - } -} diff --git a/src/backend/drm/egl/surface.rs b/src/backend/drm/egl/surface.rs deleted file mode 100644 index 4967300..0000000 --- a/src/backend/drm/egl/surface.rs +++ /dev/null @@ -1,158 +0,0 @@ -use drm::control::{connector, crtc, Mode}; -use nix::libc::c_void; -use std::convert::TryInto; -use std::sync::Arc; - -use super::Error; -use crate::backend::drm::Surface; -use crate::backend::egl::native::NativeSurface; -use crate::backend::egl::{get_proc_address, native, EGLContext, EGLSurface}; -#[cfg(feature = "renderer_gl")] -use crate::backend::graphics::gl::GLGraphicsBackend; -#[cfg(feature = "renderer_gl")] -use crate::backend::graphics::PixelFormat; -use crate::backend::graphics::{CursorBackend, SwapBuffersError}; - -/// Egl surface for rendering -#[derive(Debug)] -pub struct EglSurface(pub(super) Arc>); - -#[derive(Debug)] -pub(super) struct EglSurfaceInternal -where - N: native::NativeSurface + Surface, -{ - pub(super) context: EGLContext, - pub(super) surface: EGLSurface, -} - -impl Surface for EglSurface -where - N: native::NativeSurface + Surface, -{ - type Connectors = ::Connectors; - type Error = Error<::Error>; - - fn crtc(&self) -> crtc::Handle { - (*self.0.surface).crtc() - } - - fn current_connectors(&self) -> Self::Connectors { - self.0.surface.current_connectors() - } - - fn pending_connectors(&self) -> Self::Connectors { - self.0.surface.pending_connectors() - } - - fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { - self.0.surface.add_connector(connector).map_err(Error::Underlying) - } - - fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { - self.0 - .surface - .remove_connector(connector) - .map_err(Error::Underlying) - } - - fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { - self.0 - .surface - .set_connectors(connectors) - .map_err(Error::Underlying) - } - - fn current_mode(&self) -> Mode { - self.0.surface.current_mode() - } - - fn pending_mode(&self) -> Mode { - self.0.surface.pending_mode() - } - - fn use_mode(&self, mode: Mode) -> Result<(), Self::Error> { - self.0.surface.use_mode(mode).map_err(Error::Underlying) - } -} - -impl CursorBackend for EglSurface -where - N: NativeSurface + Surface + CursorBackend, -{ - type CursorFormat = ::CursorFormat; - type Error = ::Error; - - fn set_cursor_position(&self, x: u32, y: u32) -> ::std::result::Result<(), Self::Error> { - self.0.surface.set_cursor_position(x, y) - } - - fn set_cursor_representation( - &self, - buffer: &Self::CursorFormat, - hotspot: (u32, u32), - ) -> ::std::result::Result<(), Self::Error> { - self.0.surface.set_cursor_representation(buffer, hotspot) - } - - fn clear_cursor_representation(&self) -> Result<(), Self::Error> { - self.0.surface.clear_cursor_representation() - } -} - -#[cfg(feature = "renderer_gl")] -impl GLGraphicsBackend for EglSurface -where - N: native::NativeSurface + Surface, - ::Error: Into + 'static, -{ - fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { - if let Err(err) = self.0.surface.swap_buffers() { - Err(match err.try_into() { - Ok(x) => x, - Err(x) => x.into(), - }) - } else { - Ok(()) - } - } - - fn get_proc_address(&self, symbol: &str) -> *const c_void { - get_proc_address(symbol) - } - - fn get_framebuffer_dimensions(&self) -> (u32, u32) { - let (w, h) = self.pending_mode().size(); - (w as u32, h as u32) - } - - fn is_current(&self) -> bool { - self.0.context.is_current() && self.0.surface.is_current() - } - - unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { - self.0 - .context - .make_current_with_surface(&self.0.surface) - .map_err(Into::into) - } - - fn get_pixel_format(&self) -> PixelFormat { - self.0.surface.get_pixel_format() - } -} - -#[cfg(test)] -mod test { - use super::EglSurface; - use crate::backend::drm::gbm::GbmSurface; - use crate::backend::drm::legacy::LegacyDrmSurface; - use std::fs::File; - - fn is_send() {} - - #[test] - fn surface_is_send() { - is_send::>>>(); - } -} diff --git a/src/backend/drm/eglstream/egl.rs b/src/backend/drm/eglstream/egl.rs deleted file mode 100644 index 5e836a0..0000000 --- a/src/backend/drm/eglstream/egl.rs +++ /dev/null @@ -1,235 +0,0 @@ -//! -//! Egl [`NativeDisplay`](::backend::egl::native::NativeDisplay) and -//! [`NativeSurface`](::backend::egl::native::NativeSurface) support for -//! [`EglStreamDevice`](EglStreamDevice) and [`EglStreamSurface`](EglStreamSurface). -//! - -#[cfg(feature = "backend_drm_atomic")] -use crate::backend::drm::atomic::AtomicDrmSurface; -#[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))] -use crate::backend::drm::common::fallback::{EitherError, FallbackSurface}; -#[cfg(any(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))] -use crate::backend::drm::common::Error as DrmError; -#[cfg(feature = "backend_drm_legacy")] -use crate::backend::drm::legacy::LegacyDrmSurface; -use crate::backend::drm::{Device, RawDevice, RawSurface, Surface}; -use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface}; -use crate::backend::egl::{ - display::EGLDisplayHandle, ffi, wrap_egl_call, EGLError, Error as EglBackendError, SurfaceCreationError, - SwapBuffersError, -}; - -use super::Error; -use super::{EglStreamDevice, EglStreamSurface}; - -use drm::control::{connector, crtc, Device as ControlDevice, Mode}; -use nix::libc::{c_int, c_void}; -use std::marker::PhantomData; -use std::os::unix::io::AsRawFd; -use std::sync::Arc; - -/// Egl Device backend type -/// -/// See [`Backend`](::backend::egl::native::Backend). -#[derive(Debug)] -pub struct EglStreamDeviceBackend { - _userdata: PhantomData, -} - -impl Backend for EglStreamDeviceBackend -where - EglStreamSurface<::Surface>: - NativeSurface::Surface as Surface>::Error>>, -{ - type Surface = EglStreamSurface<::Surface>; - type Error = Error<<::Surface as Surface>::Error>; - - // create an EGLDisplay for the EGLstream platform - unsafe fn get_display( - display: ffi::NativeDisplayType, - attribs: &[ffi::EGLint], - has_dp_extension: F, - log: ::slog::Logger, - ) -> Result - where - F: Fn(&str) -> bool, - { - if has_dp_extension("EGL_EXT_platform_device") && ffi::egl::GetPlatformDisplayEXT::is_loaded() { - debug!( - log, - "EGL Display Initialization via EGL_EXT_platform_device with {:?}", display - ); - wrap_egl_call(|| { - ffi::egl::GetPlatformDisplayEXT( - ffi::egl::PLATFORM_DEVICE_EXT, - display as *mut _, - attribs.as_ptr() as *const _, - ) - }) - } else { - Ok(ffi::egl::NO_DISPLAY) - } - } -} - -unsafe impl NativeDisplay> - for EglStreamDevice -where - EglStreamSurface<::Surface>: - NativeSurface::Surface as Surface>::Error>>, -{ - type Arguments = (crtc::Handle, Mode, Vec); - - fn is_backend(&self) -> bool { - true - } - - fn ptr(&self) -> Result { - Ok(self.dev as *const _) - } - - fn attributes(&self) -> Vec { - vec![ - ffi::egl::DRM_MASTER_FD_EXT as ffi::EGLint, - self.raw.as_raw_fd(), - ffi::egl::NONE as i32, - ] - } - - fn surface_type(&self) -> ffi::EGLint { - ffi::egl::STREAM_BIT_KHR as ffi::EGLint - } - - fn create_surface( - &mut self, - args: Self::Arguments, - ) -> Result::Surface>, Error<<::Surface as Surface>::Error>> - { - Device::create_surface(self, args.0, args.1, &args.2) - } -} - -// we need either a `crtc` or a `plane` for EGLStream initializations, -// which totally breaks our abstraction.. (This is normally an implementation details of `RawSurface`-implementations). -// -// as a result, we need three implemenations for atomic, legacy and fallback... - -#[cfg(feature = "backend_drm_atomic")] -unsafe impl NativeSurface for EglStreamSurface> { - type Error = Error; - - unsafe fn create( - &self, - display: &Arc, - config_id: ffi::egl::types::EGLConfig, - surface_attribs: &[c_int], - ) -> Result<*const c_void, SurfaceCreationError> { - let output_attributes = { - let mut out: Vec = Vec::with_capacity(3); - out.push(ffi::egl::DRM_PLANE_EXT as isize); - out.push(Into::::into(self.0.crtc.0.planes.primary) as isize); - out.push(ffi::egl::NONE as isize); - out - }; - - self.create_surface(display, config_id, surface_attribs, &output_attributes) - .map_err(SurfaceCreationError::NativeSurfaceCreationFailed) - } - - fn needs_recreation(&self) -> bool { - self.0.crtc.commit_pending() - } - - fn swap_buffers( - &self, - display: &Arc, - surface: ffi::egl::types::EGLSurface, - ) -> Result<(), SwapBuffersError>> { - self.flip(self.0.crtc.0.crtc, display, surface) - } -} - -#[cfg(feature = "backend_drm_legacy")] -unsafe impl NativeSurface for EglStreamSurface> { - type Error = Error; - - unsafe fn create( - &self, - display: &Arc, - config_id: ffi::egl::types::EGLConfig, - surface_attribs: &[c_int], - ) -> Result<*const c_void, SurfaceCreationError> { - let output_attributes = { - let mut out: Vec = Vec::with_capacity(3); - out.push(ffi::egl::DRM_CRTC_EXT as isize); - out.push(Into::::into(self.0.crtc.0.crtc) as isize); - out.push(ffi::egl::NONE as isize); - out - }; - - self.create_surface(display, config_id, surface_attribs, &output_attributes) - .map_err(SurfaceCreationError::NativeSurfaceCreationFailed) - } - - fn needs_recreation(&self) -> bool { - self.0.crtc.commit_pending() - } - - fn swap_buffers( - &self, - display: &Arc, - surface: ffi::egl::types::EGLSurface, - ) -> Result<(), SwapBuffersError>> { - self.flip(self.0.crtc.0.crtc, display, surface) - } -} - -#[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))] -unsafe impl NativeSurface - for EglStreamSurface, LegacyDrmSurface>> -{ - type Error = Error>; - - unsafe fn create( - &self, - display: &Arc, - config_id: ffi::egl::types::EGLConfig, - surface_attribs: &[c_int], - ) -> Result<*const c_void, SurfaceCreationError> { - let output_attributes = { - let mut out: Vec = Vec::with_capacity(3); - match &self.0.crtc { - FallbackSurface::Preference(dev) => { - out.push(ffi::egl::DRM_PLANE_EXT as isize); - out.push(Into::::into(dev.0.planes.primary) as isize); - } //AtomicDrmSurface - FallbackSurface::Fallback(dev) => { - out.push(ffi::egl::DRM_CRTC_EXT as isize); - out.push(Into::::into(dev.0.crtc) as isize); - } // LegacyDrmSurface - } - out.push(ffi::egl::NONE as isize); - out - }; - - self.create_surface(display, config_id, surface_attribs, &output_attributes) - .map_err(SurfaceCreationError::NativeSurfaceCreationFailed) - } - - fn needs_recreation(&self) -> bool { - self.0.crtc.commit_pending() - } - - fn swap_buffers( - &self, - display: &Arc, - surface: ffi::egl::types::EGLSurface, - ) -> Result<(), SwapBuffersError> { - let crtc = match &self.0.crtc { - FallbackSurface::Preference(dev) => dev.0.crtc, - FallbackSurface::Fallback(dev) => dev.0.crtc, - }; - - self.flip(crtc, display, surface) - } -} diff --git a/src/backend/drm/eglstream/mod.rs b/src/backend/drm/eglstream/mod.rs deleted file mode 100644 index 869127d..0000000 --- a/src/backend/drm/eglstream/mod.rs +++ /dev/null @@ -1,403 +0,0 @@ -//! -//! [`Device`](Device) and [`Surface`](Surface) implementations using -//! the proposed EGLStream api for efficient rendering. -//! Currently this api is only implemented by the proprietary `nvidia` driver. -//! -//! Usually this implementation will be wrapped into a [`EglDevice`](::backend::drm::egl::EglDevice). -//! -//! To use these types standalone, you will need to render via egl yourself as page flips -//! are driven via `eglSwapBuffers`. -//! -//! To use this api in place of GBM for nvidia cards take a look at -//! [`FallbackDevice`](::backend::drm::common::fallback::FallbackDevice). -//! Take a look at `anvil`s source code for an example of this. -//! -//! For detailed overview of these abstractions take a look at the module documentation of backend::drm. -//! - -use super::{Device, DeviceHandler, RawDevice, Surface}; - -use drm::buffer::format::PixelFormat; -use drm::control::{ - connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Mode, ResourceHandles, -}; -use drm::SystemError as DrmError; -use failure::ResultExt; -use nix::libc::dev_t; - -use std::cell::RefCell; -use std::collections::HashMap; -use std::ffi::CStr; -use std::fmt; -use std::os::unix::fs::MetadataExt; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::rc::{Rc, Weak}; -use std::sync::{Arc, Mutex, Weak as WeakArc}; -use std::{fs, ptr}; - -mod surface; -pub use self::surface::EglStreamSurface; -use self::surface::EglStreamSurfaceInternal; - -pub mod egl; -#[cfg(feature = "backend_session")] -pub mod session; - -use crate::backend::egl::ffi::{self, egl::types::EGLDeviceEXT}; -use crate::backend::egl::{wrap_egl_call, EGLError as RawEGLError, Error as EglError}; -use crate::backend::graphics::SwapBuffersError; - -/// Errors thrown by the [`EglStreamDevice`](::backend::drm::eglstream::EglStreamDevice) -/// and [`EglStreamSurface`](::backend::drm::eglstream::EglStreamSurface). -#[derive(thiserror::Error, Debug)] -pub enum Error { - /// `eglInitialize` returned an error - #[error("Failed to initialize EGL: {0:}")] - InitFailed(#[source] RawEGLError), - /// Failed to enumerate EGL_EXT_drm_device - #[error("Failed to enumerate EGL_EXT_drm_device: {0:}")] - FailedToEnumerateDevices(#[source] RawEGLError), - /// Device is not compatible with EGL_EXT_drm_device extension - #[error("Device is not compatible with EGL_EXT_drm_device extension")] - DeviceIsNoEGLStreamDevice, - /// Device has not suitable output layer - #[error("Device has no suitable output layer")] - DeviceNoOutputLayer, - /// Device was unable to create an EGLStream - #[error("EGLStream creation failed")] - DeviceStreamCreationFailed, - /// Underlying backend error - #[error("Underlying error: {0}")] - Underlying(#[source] U), - /// Buffer creation failed - #[error("Buffer creation failed")] - BufferCreationFailed(#[source] failure::Compat), - /// Buffer write failed - #[error("Buffer write failed")] - BufferWriteFailed(#[source] failure::Compat), - /// Stream flip failed - #[error("Stream flip failed ({0})")] - StreamFlipFailed(#[source] RawEGLError), -} - -type SurfaceInternalRef = WeakArc::Surface>>; -/// Representation of an open egl stream device to create egl rendering surfaces -pub struct EglStreamDevice { - pub(self) dev: EGLDeviceEXT, - raw: D, - backends: Rc>>>, - logger: ::slog::Logger, - #[cfg(feature = "backend_session")] - links: Vec, -} - -// SurfaceInternalRef does not implement debug, so we have to impl Debug manually -impl fmt::Debug for EglStreamDevice { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut debug = f.debug_struct("EglStreamDevice"); - debug - .field("dev", &self.dev) - .field("raw", &self.raw) - .field("backends", &"...") - .field("logger", &self.logger); - - #[cfg(feature = "backend_session")] - debug.field("links", &self.links); - debug.finish() - } -} - -impl EglStreamDevice { - /// Try to create a new [`EglStreamDevice`] from an open device. - /// - /// Returns an error if the underlying device would not support the required EGL extensions. - pub fn new(mut raw: D, logger: L) -> Result::Surface as Surface>::Error>> - where - L: Into>, - { - let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_eglstream")); - - raw.clear_handler(); - - debug!(log, "Creating egl stream device"); - - ffi::make_sure_egl_is_loaded(); - - fn has_extensions(exts: &[String], check: &'static [&'static str]) -> Result<(), EglError> { - check - .iter() - .map(|ext| { - if exts.iter().any(|s| *s == *ext) { - Ok(()) - } else { - Err(EglError::EglExtensionNotSupported(check)) - } - }) - .collect() - } - - let device = unsafe { - // the first step is to query the list of extensions without any display, if supported - let dp_extensions = { - let p = wrap_egl_call(|| { - ffi::egl::QueryString(ffi::egl::NO_DISPLAY, ffi::egl::EXTENSIONS as i32) - }) - .map_err(Error::InitFailed)?; - - // this possibility is available only with EGL 1.5 or EGL_EXT_platform_base, otherwise - // `eglQueryString` returns an error - if p.is_null() { - vec![] - } else { - let p = CStr::from_ptr(p); - let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); - list.split(' ').map(|e| e.to_string()).collect::>() - } - }; - debug!(log, "EGL No-Display Extensions: {:?}", dp_extensions); - - // we need either EGL_EXT_device_base or EGL_EXT_device_enumeration &_query - if let Err(_err) = has_extensions(&dp_extensions, &["EGL_EXT_device_base"]) { - has_extensions( - &dp_extensions, - &["EGL_EXT_device_enumeration", "EGL_EXT_device_query"], - ) - .map_err(|_| Error::DeviceIsNoEGLStreamDevice)?; - } - - // we can now query the amount of devices implementing the required extension - let mut num_devices = 0; - wrap_egl_call(|| ffi::egl::QueryDevicesEXT(0, ptr::null_mut(), &mut num_devices)) - .map_err(Error::FailedToEnumerateDevices)?; - if num_devices == 0 { - return Err(Error::DeviceIsNoEGLStreamDevice); - } - - // afterwards we can allocate a buffer large enough and query the actual device (this is a common pattern in egl). - let mut devices = Vec::with_capacity(num_devices as usize); - wrap_egl_call(|| ffi::egl::QueryDevicesEXT(num_devices, devices.as_mut_ptr(), &mut num_devices)) - .map_err(Error::FailedToEnumerateDevices)?; - devices.set_len(num_devices as usize); - debug!(log, "Devices: {:#?}, Count: {}", devices, num_devices); - - devices - .into_iter() - .find(|device| { - // we may get devices, that are - well - NO_DEVICE... - *device != ffi::egl::NO_DEVICE_EXT - && { - // the device then also needs EGL_EXT_device_drm - let device_extensions = { - let p = ffi::egl::QueryDeviceStringEXT(*device, ffi::egl::EXTENSIONS as i32); - if p.is_null() { - vec![] - } else { - let p = CStr::from_ptr(p); - let list = String::from_utf8(p.to_bytes().to_vec()) - .unwrap_or_else(|_| String::new()); - list.split(' ').map(|e| e.to_string()).collect::>() - } - }; - - device_extensions.iter().any(|s| *s == "EGL_EXT_device_drm") - } - && { - // and we want to get the file descriptor to check, that we found - // the device the user wants to initialize. - // - // notice how this is kinda the other way around. - // EGL_EXT_device_query expects use to find all devices using this extension... - // But there is no way, we are going to replace our udev-interface with this, so we list devices - // just to find the id of the one, that we actually want, because we cannot - // request it directly afaik... - let path = { - let p = ffi::egl::QueryDeviceStringEXT( - *device, - ffi::egl::DRM_DEVICE_FILE_EXT as i32, - ); - if p.is_null() { - String::new() - } else { - let p = CStr::from_ptr(p); - String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()) - } - }; - - match fs::metadata(&path) { - Ok(metadata) => metadata.rdev() == raw.device_id(), - Err(_) => false, - } - } - }) - .ok_or(Error::DeviceIsNoEGLStreamDevice)? - }; - - // okay the device is compatible and found, ready to go. - Ok(EglStreamDevice { - dev: device, - raw, - backends: Rc::new(RefCell::new(HashMap::new())), - logger: log, - #[cfg(feature = "backend_session")] - links: Vec::new(), - }) - } -} - -struct InternalDeviceHandler { - handler: Box> + 'static>, - backends: Weak>>>, - logger: ::slog::Logger, -} - -impl DeviceHandler for InternalDeviceHandler { - type Device = D; - - fn vblank(&mut self, crtc: crtc::Handle) { - if let Some(backends) = self.backends.upgrade() { - if let Some(surface) = backends.borrow().get(&crtc) { - if surface.upgrade().is_some() { - self.handler.vblank(crtc); - } - } else { - warn!( - self.logger, - "Surface ({:?}) not managed by egl stream, event not handled.", crtc - ); - } - } - } - fn error(&mut self, error: <::Surface as Surface>::Error) { - self.handler.error(Error::Underlying(error)) - } -} - -impl Device for EglStreamDevice { - type Surface = EglStreamSurface<::Surface>; - - fn device_id(&self) -> dev_t { - self.raw.device_id() - } - - fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { - self.raw.set_handler(InternalDeviceHandler { - handler: Box::new(handler), - backends: Rc::downgrade(&self.backends), - logger: self.logger.clone(), - }); - } - - fn clear_handler(&mut self) { - self.raw.clear_handler(); - } - - fn create_surface( - &mut self, - crtc: crtc::Handle, - mode: Mode, - connectors: &[connector::Handle], - ) -> Result::Surface>, Error<<::Surface as Surface>::Error>> - { - info!(self.logger, "Initializing EglStreamSurface"); - - let drm_surface = - Device::create_surface(&mut self.raw, crtc, mode, connectors).map_err(Error::Underlying)?; - - // initialize a buffer for the cursor image - let cursor = Some(( - self.raw - .create_dumb_buffer((1, 1), PixelFormat::ARGB8888) - .compat() - .map_err(Error::BufferCreationFailed)?, - (0, 0), - )); - - let backend = Arc::new(EglStreamSurfaceInternal { - crtc: drm_surface, - cursor: Mutex::new(cursor), - stream: Mutex::new(None), - commit_buffer: Mutex::new(None), - logger: self.logger.new(o!("crtc" => format!("{:?}", crtc))), - locked: std::sync::atomic::AtomicBool::new(false), - }); - self.backends.borrow_mut().insert(crtc, Arc::downgrade(&backend)); - Ok(EglStreamSurface(backend)) - } - - fn process_events(&mut self) { - self.raw.process_events() - } - - fn resource_handles(&self) -> Result::Surface as Surface>::Error>> { - Device::resource_handles(&self.raw).map_err(Error::Underlying) - } - - fn get_connector_info(&self, conn: connector::Handle) -> Result { - self.raw.get_connector_info(conn) - } - fn get_crtc_info(&self, crtc: crtc::Handle) -> Result { - self.raw.get_crtc_info(crtc) - } - fn get_encoder_info(&self, enc: encoder::Handle) -> Result { - self.raw.get_encoder_info(enc) - } - fn get_framebuffer_info(&self, fb: framebuffer::Handle) -> Result { - self.raw.get_framebuffer_info(fb) - } - fn get_plane_info(&self, plane: plane::Handle) -> Result { - self.raw.get_plane_info(plane) - } -} - -impl AsRawFd for EglStreamDevice { - fn as_raw_fd(&self) -> RawFd { - self.raw.as_raw_fd() - } -} - -impl Drop for EglStreamDevice { - fn drop(&mut self) { - self.clear_handler(); - } -} - -impl Into for Error -where - E: std::error::Error + Into + 'static, -{ - fn into(self) -> SwapBuffersError { - match self { - Error::BufferCreationFailed(x) - if match x.get_ref() { - drm::SystemError::Unknown { - errno: nix::errno::Errno::EBUSY, - } => true, - drm::SystemError::Unknown { - errno: nix::errno::Errno::EINTR, - } => true, - _ => false, - } => - { - SwapBuffersError::TemporaryFailure(Box::new(Error::::BufferCreationFailed(x))) - } - Error::BufferWriteFailed(x) - if match x.get_ref() { - drm::SystemError::Unknown { - errno: nix::errno::Errno::EBUSY, - } => true, - drm::SystemError::Unknown { - errno: nix::errno::Errno::EINTR, - } => true, - _ => false, - } => - { - SwapBuffersError::TemporaryFailure(Box::new(Error::::BufferCreationFailed(x))) - } - Error::StreamFlipFailed(x @ RawEGLError::ResourceBusy) => { - SwapBuffersError::TemporaryFailure(Box::new(Error::::StreamFlipFailed(x))) - } - Error::Underlying(x) => x.into(), - x => SwapBuffersError::ContextLost(Box::new(x)), - } - } -} diff --git a/src/backend/drm/eglstream/session.rs b/src/backend/drm/eglstream/session.rs deleted file mode 100644 index ca845cc..0000000 --- a/src/backend/drm/eglstream/session.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! -//! Support to register a [`EglStreamDevice`](EglStreamDevice) -//! to an open [`Session`](::backend::session::Session). -//! - -use super::{EglStreamDevice, EglStreamSurfaceInternal}; -use crate::backend::drm::{Device, RawDevice, RawSurface}; -use crate::backend::egl::ffi; -use crate::backend::session::Signal as SessionSignal; -use crate::signaling::{Linkable, Signaler}; - -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::{Rc, Weak}; -use std::sync::Weak as WeakArc; - -use drm::control::{crtc, Device as ControlDevice}; - -/// [`SessionObserver`](SessionObserver) -/// linked to the [`EglStreamDevice`](EglStreamDevice) it was -/// created from. -#[derive(Debug)] -pub struct EglStreamDeviceObserver { - backends: Weak>>>>, - logger: ::slog::Logger, -} - -impl Linkable for EglStreamDevice -where - D: RawDevice + ControlDevice + Linkable + 'static, -{ - fn link(&mut self, signaler: Signaler) { - let lower_signal = Signaler::new(); - self.raw.link(lower_signal.clone()); - let mut observer = EglStreamDeviceObserver::<::Surface> { - backends: Rc::downgrade(&self.backends), - logger: self.logger.clone(), - }; - - let token = signaler.register(move |&signal| match signal { - SessionSignal::ActivateSession | SessionSignal::ActivateDevice { .. } => { - // activate lower device *before* we process the signal - lower_signal.signal(signal); - observer.activate(); - } - SessionSignal::PauseSession | SessionSignal::PauseDevice { .. } => { - // pause lower device *after* we process the signal - observer.pause(); - lower_signal.signal(signal); - } - }); - - self.links.push(token); - } -} - -impl EglStreamDeviceObserver { - fn pause(&mut self) { - if let Some(backends) = self.backends.upgrade() { - for (_, backend) in backends.borrow().iter() { - if let Some(backend) = backend.upgrade() { - // destroy/disable the streams so it will not submit any pending frames - if let Some((display, stream)) = backend.stream.lock().unwrap().take() { - unsafe { - ffi::egl::DestroyStreamKHR(display.handle, stream.0); - } - } - // framebuffers will be likely not valid anymore, lets just recreate those after activation. - if let Some((buffer, fb)) = backend.commit_buffer.lock().unwrap().take() { - let _ = backend.crtc.destroy_framebuffer(fb); - let _ = backend.crtc.destroy_dumb_buffer(buffer); - } - } - } - } - } - - fn activate(&mut self) { - if let Some(backends) = self.backends.upgrade() { - for (_, backend) in backends.borrow().iter() { - if let Some(backend) = backend.upgrade() { - let cursor = backend.cursor.lock().unwrap(); - if let Some((ref cursor, ref hotspot)) = &*cursor { - if backend - .crtc - .set_cursor2( - backend.crtc.crtc(), - Some(cursor), - (hotspot.0 as i32, hotspot.1 as i32), - ) - .is_err() - { - if let Err(err) = backend.crtc.set_cursor(backend.crtc.crtc(), Some(cursor)) { - warn!(self.logger, "Failed to reset cursor: {}", err) - } - } - } - } - } - } - } -} diff --git a/src/backend/drm/eglstream/surface.rs b/src/backend/drm/eglstream/surface.rs deleted file mode 100644 index a7d86ea..0000000 --- a/src/backend/drm/eglstream/surface.rs +++ /dev/null @@ -1,601 +0,0 @@ -use super::super::{RawSurface, Surface}; -use super::Error; - -use drm::buffer::format::PixelFormat; -use drm::control::{connector, crtc, dumbbuffer::DumbBuffer, framebuffer, Mode}; -#[cfg(feature = "backend_drm")] -use failure::ResultExt; -#[cfg(feature = "backend_drm")] -use image::{ImageBuffer, Rgba}; -use nix::libc::{c_int, c_void}; - -use std::ffi::CStr; -use std::ptr; -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, -}; - -#[cfg(feature = "backend_drm")] -use crate::backend::drm::common::Error as DrmError; -#[cfg(feature = "backend_drm")] -use crate::backend::drm::DevPath; -use crate::backend::egl::ffi::{ - self, - egl::{self, types::EGLStreamKHR}, -}; -use crate::backend::egl::{display::EGLDisplayHandle, wrap_egl_call, EGLError, SwapBuffersError}; -#[cfg(feature = "backend_drm")] -use crate::backend::graphics::CursorBackend; - -// We do not want to mark the whole surface as `Send` in case we screw up somewhere else -// and because S needs to be `Send` as well for this to work. -#[derive(Debug)] -pub(super) struct StreamHandle(pub(super) EGLStreamKHR); -// EGLStreamKHR can be moved between threads -unsafe impl Send for StreamHandle {} - -#[derive(Debug)] -pub(in crate::backend::drm) struct EglStreamSurfaceInternal { - pub(in crate::backend::drm) crtc: S, - pub(in crate::backend::drm) cursor: Mutex>, - pub(super) stream: Mutex, StreamHandle)>>, - pub(in crate::backend::drm) commit_buffer: Mutex>, - pub(in crate::backend::drm) locked: AtomicBool, - pub(in crate::backend::drm) logger: ::slog::Logger, -} - -impl Surface for EglStreamSurfaceInternal { - type Connectors = ::Connectors; - type Error = Error<::Error>; - - fn crtc(&self) -> crtc::Handle { - self.crtc.crtc() - } - - fn current_connectors(&self) -> Self::Connectors { - self.crtc.current_connectors() - } - - fn pending_connectors(&self) -> Self::Connectors { - self.crtc.pending_connectors() - } - - fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { - self.crtc.add_connector(connector).map_err(Error::Underlying) - } - - fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { - self.crtc.remove_connector(connector).map_err(Error::Underlying) - } - - fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { - self.crtc.set_connectors(connectors).map_err(Error::Underlying) - } - - fn current_mode(&self) -> Mode { - self.crtc.current_mode() - } - - fn pending_mode(&self) -> Mode { - self.crtc.pending_mode() - } - - fn use_mode(&self, mode: Mode) -> Result<(), Error<::Error>> { - self.crtc.use_mode(mode).map_err(Error::Underlying) - } -} - -impl Drop for EglStreamSurfaceInternal { - fn drop(&mut self) { - if let Some((buffer, _)) = self.cursor.lock().unwrap().take() { - let _ = self.crtc.destroy_dumb_buffer(buffer); - } - if let Some((buffer, fb)) = self.commit_buffer.lock().unwrap().take() { - let _ = self.crtc.destroy_framebuffer(fb); - let _ = self.crtc.destroy_dumb_buffer(buffer); - } - if let Some((display, stream)) = self.stream.lock().unwrap().take() { - unsafe { - egl::DestroyStreamKHR(display.handle, stream.0); - } - } - } -} - -// Conceptionally EglStream is a weird api. -// It does modesetting on its own, bypassing our `RawSurface::commit` function. -// As a result, we cannot easily sync any other planes to the commit without more -// experimental egl extensions to do this via the EglStream-API. -// -// So instead we leverage the fact, that all drm-drivers still support the legacy -// `drmModeSetCursor` and `drmModeMoveCursor` functions, that (mostly) implicitly sync to the primary plane. -// That way we can use hardware cursors at least on all drm-backends (including atomic), although -// this is a little hacky. Overlay planes however are completely out of question for now. -// -// Note that this might still appear a little choppy, we should just use software cursors -// on eglstream devices by default and only use this, if the user really wants it. -#[cfg(feature = "backend_drm")] -impl CursorBackend for EglStreamSurfaceInternal { - type CursorFormat = ImageBuffer, Vec>; - type Error = Error; - - fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error> { - trace!(self.logger, "Move the cursor to {},{}", x, y); - self.crtc - .move_cursor(self.crtc.crtc(), (x as i32, y as i32)) - .compat() - .map_err(|source| DrmError::Access { - errmsg: "Error moving cursor", - dev: self.crtc.dev_path(), - source, - }) - .map_err(Error::Underlying) - } - - fn set_cursor_representation( - &self, - buffer: &ImageBuffer, Vec>, - hotspot: (u32, u32), - ) -> Result<(), Self::Error> { - let (w, h) = buffer.dimensions(); - debug!(self.logger, "Importing cursor"); - - // import the cursor into a buffer we can render - let mut cursor = self - .crtc - .create_dumb_buffer((w, h), PixelFormat::ARGB8888) - .compat() - .map_err(Error::BufferCreationFailed)?; - - { - let mut mapping = self - .crtc - .map_dumb_buffer(&mut cursor) - .compat() - .map_err(Error::BufferWriteFailed)?; - mapping.as_mut().copy_from_slice(buffer); - } - - trace!(self.logger, "Setting the new imported cursor"); - - trace!(self.logger, "Setting the new imported cursor"); - - // call the drm-functions directly to bypass ::commit/::page_flip and eglstream... - - if self - .crtc - .set_cursor2( - self.crtc.crtc(), - Some(&cursor), - (hotspot.0 as i32, hotspot.1 as i32), - ) - .is_err() - { - self.crtc - .set_cursor(self.crtc.crtc(), Some(&cursor)) - .compat() - .map_err(|source| DrmError::Access { - errmsg: "Failed to set cursor", - dev: self.crtc.dev_path(), - source, - }) - .map_err(Error::Underlying)?; - } - - // and store it - if let Some((old, _)) = self.cursor.lock().unwrap().replace((cursor, hotspot)) { - if self.crtc.destroy_dumb_buffer(old).is_err() { - warn!(self.logger, "Failed to free old cursor"); - } - } - - Ok(()) - } - - fn clear_cursor_representation(&self) -> Result<(), Self::Error> { - self.crtc - .set_cursor(self.crtc.crtc(), Option::<&DumbBuffer>::None) - .compat() - .map_err(|source| DrmError::Access { - errmsg: "Failed to clear cursor", - dev: self.crtc.dev_path(), - source, - }) - .map_err(Error::Underlying) - } -} - -/// egl stream surface for rendering -#[derive(Debug)] -pub struct EglStreamSurface( - pub(in crate::backend::drm) Arc>, -); - -impl EglStreamSurface { - /// Check if underlying gbm resources need to be recreated. - pub fn needs_recreation(&self) -> bool { - self.0.crtc.commit_pending() || self.0.stream.lock().unwrap().is_none() - } - - // An EGLStream is basically the pump that requests and consumes images to display them. - // The heart of this weird api. Any changes to its configuration, require a re-creation. - pub(super) fn create_stream( - &self, - display: &Arc, - output_attribs: &[isize], - ) -> Result::Error>> { - let mut stream = self.0.stream.lock().unwrap(); - // drop old steam, if it already exists - if let Some((display, stream)) = stream.take() { - // ignore result - unsafe { - ffi::egl::DestroyStreamKHR(display.handle, stream.0); - } - } - - // because we are re-creating there might be a new mode. if there is? -> commit it - if self.0.crtc.commit_pending() { - let (w, h) = self.pending_mode().size(); - // but we need a buffer to commit... - // well lets create one and clean it up once the stream is running - if let Ok(buffer) = self - .0 - .crtc - .create_dumb_buffer((w as u32, h as u32), PixelFormat::ARGB8888) - { - if let Ok(fb) = self.0.crtc.add_framebuffer(&buffer) { - if let Some((buffer, fb)) = self.0.commit_buffer.lock().unwrap().replace((buffer, fb)) { - let _ = self.0.crtc.destroy_framebuffer(fb); - let _ = self.0.crtc.destroy_dumb_buffer(buffer); - } - self.0.crtc.commit(fb).map_err(Error::Underlying)?; - } - } - } - - // again enumerate extensions - let extensions = { - let p = - unsafe { CStr::from_ptr(ffi::egl::QueryString(display.handle, ffi::egl::EXTENSIONS as i32)) }; - let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); - list.split(' ').map(|e| e.to_string()).collect::>() - }; - - // we need quite a bunch to implement a full-blown renderer. - if !extensions.iter().any(|s| *s == "EGL_EXT_output_base") - || !extensions.iter().any(|s| *s == "EGL_EXT_output_drm") - || !extensions.iter().any(|s| *s == "EGL_KHR_stream") - || !extensions - .iter() - .any(|s| *s == "EGL_EXT_stream_consumer_egloutput") - || !extensions - .iter() - .any(|s| *s == "EGL_KHR_stream_producer_eglsurface") - { - error!(self.0.logger, "Extension for EGLStream surface creation missing"); - return Err(Error::DeviceIsNoEGLStreamDevice); - } - - if cfg!(debug_assertions) { - // TEST START - let mut num_layers = 0; - if unsafe { - ffi::egl::GetOutputLayersEXT( - display.handle, - ptr::null(), - ptr::null_mut(), - 10, - &mut num_layers, - ) - } == 0 - { - error!(self.0.logger, "Failed to get any! output layer"); - } - if num_layers == 0 { - error!(self.0.logger, "Failed to find any! output layer"); - } - let mut layers = Vec::with_capacity(num_layers as usize); - if unsafe { - ffi::egl::GetOutputLayersEXT( - display.handle, - ptr::null(), - layers.as_mut_ptr(), - num_layers, - &mut num_layers, - ) - } == 0 - { - error!(self.0.logger, "Failed to receive Output Layers"); - } - unsafe { - layers.set_len(num_layers as usize); - } - for layer in layers { - debug!(self.0.logger, "Found layer: {:?}", layer); - let mut val = 0; - if unsafe { - ffi::egl::QueryOutputLayerAttribEXT( - display.handle, - layer, - ffi::egl::DRM_CRTC_EXT as i32, - &mut val, - ) - } != 0 - { - info!(self.0.logger, "Possible crtc output layer: {}", val); - } - val = 0; - if unsafe { - ffi::egl::QueryOutputLayerAttribEXT( - display.handle, - layer, - ffi::egl::DRM_PLANE_EXT as i32, - &mut val, - ) - } != 0 - { - info!(self.0.logger, "Possible plane output layer: {}", val); - } - } - // TEST END - } - - // alright, if the surface appears to be supported, we need an "output layer". - // this is basically just a fancy name for a `crtc` or a `plane`. - // those are exactly whats inside the `output_attribs` depending on underlying device. - let mut num_layers = 0; - if unsafe { - ffi::egl::GetOutputLayersEXT( - display.handle, - output_attribs.as_ptr(), - ptr::null_mut(), - 1, - &mut num_layers, - ) - } == 0 - { - error!( - self.0.logger, - "Failed to acquire Output Layer. Attributes {:?}", output_attribs - ); - return Err(Error::DeviceNoOutputLayer); - } - if num_layers == 0 { - error!(self.0.logger, "Failed to find Output Layer"); - return Err(Error::DeviceNoOutputLayer); - } - let mut layers = Vec::with_capacity(num_layers as usize); - if unsafe { - ffi::egl::GetOutputLayersEXT( - display.handle, - output_attribs.as_ptr(), - layers.as_mut_ptr(), - num_layers, - &mut num_layers, - ) - } == 0 - { - error!(self.0.logger, "Failed to get Output Layer"); - return Err(Error::DeviceNoOutputLayer); - } - unsafe { - layers.set_len(num_layers as usize); - } - - // lets just use the first layer and try to set the swap interval. - // this is needed to make sure `eglSwapBuffers` does not block. - let layer = layers[0]; - unsafe { - ffi::egl::OutputLayerAttribEXT(display.handle, layer, ffi::egl::SWAP_INTERVAL_EXT as i32, 0); - } - - // The stream needs to know, it needs to request frames - // as soon as another one is rendered (we do not want to build a buffer and delay frames), - // which is handled by STREAM_FIFO_LENGTH_KHR = 0. - // We also want to "acquire" the frames manually. Like this we can request page-flip events - // to drive our event loop. Otherwise we would have no way to know rendering is finished. - let stream_attributes = { - let mut out: Vec = Vec::with_capacity(7); - out.push(ffi::egl::STREAM_FIFO_LENGTH_KHR as i32); - out.push(0); - out.push(ffi::egl::CONSUMER_AUTO_ACQUIRE_EXT as i32); - out.push(ffi::egl::FALSE as i32); - out.push(ffi::egl::CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR as i32); - out.push(0); - out.push(ffi::egl::NONE as i32); - out - }; - - // okay, we have a config, lets create the stream. - let raw_stream = unsafe { ffi::egl::CreateStreamKHR(display.handle, stream_attributes.as_ptr()) }; - if raw_stream == ffi::egl::NO_STREAM_KHR { - error!(self.0.logger, "Failed to create egl stream"); - return Err(Error::DeviceStreamCreationFailed); - } - - // we have a stream, lets connect it to our output layer - if unsafe { ffi::egl::StreamConsumerOutputEXT(display.handle, raw_stream, layer) } == 0 { - error!(self.0.logger, "Failed to link Output Layer as Stream Consumer"); - return Err(Error::DeviceStreamCreationFailed); - } - - *stream = Some((display.clone(), StreamHandle(raw_stream))); - - Ok(raw_stream) - } - - pub(super) fn create_surface( - &self, - display: &Arc, - config_id: ffi::egl::types::EGLConfig, - _surface_attribs: &[c_int], - output_attribs: &[isize], - ) -> Result<*const c_void, Error<::Error>> { - // our surface needs a stream - let stream = self.create_stream(display, output_attribs)?; - - let (w, h) = self.current_mode().size(); - info!(self.0.logger, "Creating stream surface with size: ({}:{})", w, h); - let surface_attributes = { - let mut out: Vec = Vec::with_capacity(5); - out.push(ffi::egl::WIDTH as i32); - out.push(w as i32); - out.push(ffi::egl::HEIGHT as i32); - out.push(h as i32); - out.push(ffi::egl::NONE as i32); - out - }; - - // the stream is already connected to the consumer (output layer) during creation. - // we now connect the producer (out egl surface, that we render to). - let surface = unsafe { - ffi::egl::CreateStreamProducerSurfaceKHR( - display.handle, - config_id, - stream, - surface_attributes.as_ptr(), - ) - }; - if surface == ffi::egl::NO_SURFACE { - error!(self.0.logger, "Failed to create surface: 0x{:X}", unsafe { - ffi::egl::GetError() - }); - } - Ok(surface) - } - - pub(super) fn flip( - &self, - crtc: crtc::Handle, - display: &Arc, - surface: ffi::egl::types::EGLSurface, - ) -> Result<(), SwapBuffersError::Error>>> { - // if we have already swapped the buffer successfully, we need to free it again. - // - // we need to do this here, because the call may fail (compare this with gbm's unlock_buffer...). - // if it fails we do not want to swap, because that would block and as a result deadlock us. - if self.0.locked.load(Ordering::SeqCst) { - // which means in eglstream terms: "acquire it". - // here we set the user data of the page_flip event - // (which is also only triggered if we manually acquire frames). - // This is the crtc id like always to get the matching surface for the device handler later. - let acquire_attributes = [ - ffi::egl::DRM_FLIP_EVENT_DATA_NV as isize, - Into::::into(crtc) as isize, - ffi::egl::NONE as isize, - ]; - - if let Ok(stream) = self.0.stream.try_lock() { - // lets try to acquire the frame. - // this may fail, if the buffer is still in use by the gpu, - // e.g. the flip was not done yet. In this case this call fails as `BUSY`. - let res = if let Some(&(ref display, ref stream)) = stream.as_ref() { - wrap_egl_call(|| unsafe { - ffi::egl::StreamConsumerAcquireAttribNV( - display.handle, - stream.0, - acquire_attributes.as_ptr(), - ); - }) - .map_err(Error::StreamFlipFailed) - } else { - Err(Error::StreamFlipFailed(EGLError::NotInitialized)) - }; - - // so we need to unlock on success and return on failure. - if res.is_ok() { - self.0.locked.store(false, Ordering::SeqCst); - } else { - return res.map_err(SwapBuffersError::Underlying); - } - } - } - - // so if we are not locked any more we can send the next frame by calling swap buffers. - if !self.0.locked.load(Ordering::SeqCst) { - wrap_egl_call(|| unsafe { ffi::egl::SwapBuffers(***display, surface as *const _) }) - .map_err(SwapBuffersError::EGLSwapBuffers)?; - self.0.locked.store(true, Ordering::SeqCst); - } - - Ok(()) - } -} - -impl Surface for EglStreamSurface { - type Connectors = ::Connectors; - type Error = Error<::Error>; - - fn crtc(&self) -> crtc::Handle { - self.0.crtc() - } - - fn current_connectors(&self) -> Self::Connectors { - self.0.current_connectors() - } - - fn pending_connectors(&self) -> Self::Connectors { - self.0.pending_connectors() - } - - fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { - self.0.add_connector(connector) - } - - fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { - self.0.remove_connector(connector) - } - - fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { - self.0.set_connectors(connectors) - } - - fn current_mode(&self) -> Mode { - self.0.current_mode() - } - - fn pending_mode(&self) -> Mode { - self.0.pending_mode() - } - - fn use_mode(&self, mode: Mode) -> Result<(), Self::Error> { - self.0.use_mode(mode) - } -} - -#[cfg(feature = "backend_drm_legacy")] -impl CursorBackend for EglStreamSurface { - type CursorFormat = ImageBuffer, Vec>; - type Error = Error; - - fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error> { - self.0.set_cursor_position(x, y) - } - - fn set_cursor_representation( - &self, - buffer: &ImageBuffer, Vec>, - hotspot: (u32, u32), - ) -> Result<(), Self::Error> { - self.0.set_cursor_representation(buffer, hotspot) - } - - fn clear_cursor_representation(&self) -> Result<(), Self::Error> { - self.0.clear_cursor_representation() - } -} - -#[cfg(test)] -mod test { - use super::EglStreamSurface; - use crate::backend::drm::legacy::LegacyDrmSurface; - use std::fs::File; - - fn is_send() {} - - #[test] - fn surface_is_send() { - is_send::>>(); - } -} diff --git a/src/backend/drm/common/mod.rs b/src/backend/drm/error.rs similarity index 74% rename from src/backend/drm/common/mod.rs rename to src/backend/drm/error.rs index 3ee2723..38eff09 100644 --- a/src/backend/drm/common/mod.rs +++ b/src/backend/drm/error.rs @@ -1,18 +1,9 @@ -//! -//! Module for common/shared types of the various [`Device`](::backend::drm::Device) -//! and [`Surface`](::backend::drm::Surface) implementations of the `backend::drm` module. -//! - -use crate::backend::graphics::SwapBuffersError; -use drm::control::{connector, crtc, Mode, RawResourceHandle}; +//use crate::backend::graphics::SwapBuffersError; +use drm::control::{connector, crtc, plane, Mode, RawResourceHandle}; use std::path::PathBuf; -pub mod fallback; - -/// Errors thrown by the [`LegacyDrmDevice`](::backend::drm::legacy::LegacyDrmDevice), -/// [`AtomicDrmDevice`](::backend::drm::atomic::AtomicDrmDevice) -/// and their surfaces: [`LegacyDrmSurface`](::backend::drm::legacy::LegacyDrmSurface) -/// and [`AtomicDrmSurface`](::backend::drm::atomic::AtomicDrmSurface). +/// Errors thrown by the [`DrmDevice`](::backend::drm::DrmDevice) +/// and the [`DrmSurface`](::backend::drm::DrmSurface). #[derive(thiserror::Error, Debug)] pub enum Error { /// Unable to acquire DRM master @@ -26,7 +17,7 @@ pub enum Error { /// Device on which the error was generated dev: Option, /// Underlying device error - source: failure::Compat, + source: drm::SystemError, }, /// Unable to determine device id of drm device #[error("Unable to determine device id of drm device")] @@ -43,6 +34,10 @@ pub enum Error { /// This operation would result in a surface without connectors. #[error("Surface of crtc `{0:?}` would have no connectors, which is not accepted")] SurfaceWithoutConnectors(crtc::Handle), + #[error("Plane `{1:?}` is not compatible for use with crtc `{0:?}`")] + PlaneNotCompatible(crtc::Handle, plane::Handle), + #[error("Non-Primary Planes (provided was `{0:?}`) are not available for use with legacy devices")] + NonPrimaryPlane(plane::Handle), /// No encoder was found for a given connector on the set crtc #[error("No encoder found for the given connector '{connector:?}' on crtc `{crtc:?}`")] NoSuitableEncoder { @@ -51,14 +46,6 @@ pub enum Error { /// CRTC crtc: crtc::Handle, }, - /// No matching primary and cursor plane could be found for the given crtc - #[error("No matching primary and cursor plane could be found for crtc {crtc:?} on {dev:?}")] - NoSuitablePlanes { - /// CRTC - crtc: crtc::Handle, - /// Device on which the error was generated - dev: Option, - }, /// The DrmDevice is missing a required property #[error("The DrmDevice is missing a required property '{name}' for handle ({handle:?})")] UnknownProperty { @@ -72,6 +59,7 @@ pub enum Error { TestFailed(crtc::Handle), } +/* impl Into for Error { fn into(self) -> SwapBuffersError { match self { @@ -95,3 +83,4 @@ impl Into for Error { } } } +*/ \ No newline at end of file diff --git a/src/backend/drm/gbm/egl.rs b/src/backend/drm/gbm/egl.rs deleted file mode 100644 index 2e89beb..0000000 --- a/src/backend/drm/gbm/egl.rs +++ /dev/null @@ -1,126 +0,0 @@ -//! -//! Egl [`NativeDisplay`](::backend::egl::native::NativeDisplay) and -//! [`NativeSurface`](::backend::egl::native::NativeSurface) support for -//! [`GbmDevice`](GbmDevice) and [`GbmSurface`](GbmSurface). -//! - -use crate::backend::drm::{Device, RawDevice, RawSurface, Surface}; -use crate::backend::egl::native::{Backend, NativeDisplay, NativeSurface}; -use crate::backend::egl::{display::EGLDisplayHandle, ffi}; -use crate::backend::egl::{ - wrap_egl_call, EGLError, Error as EglBackendError, SurfaceCreationError, SwapBuffersError, -}; - -use super::{Error, GbmDevice, GbmSurface}; - -use drm::control::{connector, crtc, Device as ControlDevice, Mode}; -use gbm::AsRaw; -use nix::libc::{c_int, c_void}; -use std::marker::PhantomData; -use std::sync::Arc; - -/// Egl Gbm backend type -/// -/// See [`Backend`](::backend::egl::native::Backend). -pub struct Gbm { - _userdata: PhantomData, -} - -impl Backend for Gbm { - type Surface = GbmSurface<::Surface>; - type Error = Error<<::Surface as Surface>::Error>; - - // this creates an EGLDisplay for the gbm platform. - unsafe fn get_display( - display: ffi::NativeDisplayType, - attribs: &[ffi::EGLint], - has_dp_extension: F, - log: ::slog::Logger, - ) -> Result - where - F: Fn(&str) -> bool, - { - if has_dp_extension("EGL_KHR_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() { - trace!(log, "EGL Display Initialization via EGL_KHR_platform_gbm"); - let attribs = attribs.iter().map(|x| *x as isize).collect::>(); - wrap_egl_call(|| { - ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, attribs.as_ptr()) - }) - } else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplayEXT::is_loaded() { - trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); - wrap_egl_call(|| { - ffi::egl::GetPlatformDisplayEXT( - ffi::egl::PLATFORM_GBM_MESA, - display as *mut _, - attribs.as_ptr(), - ) - }) - } else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() { - trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); - let attribs = attribs.iter().map(|x| *x as isize).collect::>(); - wrap_egl_call(|| { - ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, attribs.as_ptr()) - }) - } else { - Ok(ffi::egl::NO_DISPLAY) - } - } -} - -unsafe impl NativeDisplay> for GbmDevice { - type Arguments = (crtc::Handle, Mode, Vec); - - fn is_backend(&self) -> bool { - true - } - - fn ptr(&self) -> Result { - Ok(self.dev.lock().unwrap().as_raw() as *const _) - } - - fn create_surface( - &mut self, - args: Self::Arguments, - ) -> Result::Surface>, Error<<::Surface as Surface>::Error>> { - Device::create_surface(self, args.0, args.1, &args.2) - } -} - -unsafe impl NativeSurface for GbmSurface { - type Error = Error<::Error>; - - unsafe fn create( - &self, - display: &Arc, - config_id: ffi::egl::types::EGLConfig, - surface_attributes: &[c_int], - ) -> Result<*const c_void, SurfaceCreationError> { - GbmSurface::recreate(self).map_err(SurfaceCreationError::NativeSurfaceCreationFailed)?; - - wrap_egl_call(|| { - ffi::egl::CreateWindowSurface( - display.handle, - config_id, - self.0.surface.lock().unwrap().as_raw() as *const _, - surface_attributes.as_ptr(), - ) - }) - .map_err(SurfaceCreationError::EGLSurfaceCreationFailed) - } - - fn needs_recreation(&self) -> bool { - self.needs_recreation() - } - - fn swap_buffers( - &self, - display: &Arc, - surface: ffi::egl::types::EGLSurface, - ) -> Result<(), SwapBuffersError> { - wrap_egl_call(|| unsafe { ffi::egl::SwapBuffers(***display, surface as *const _) }) - .map_err(SwapBuffersError::EGLSwapBuffers)?; - // this is safe since `eglSwapBuffers` will have been called exactly once - // if this is used by our egl module, which is why this trait is unsafe. - unsafe { self.page_flip() }.map_err(SwapBuffersError::Underlying) - } -} diff --git a/src/backend/drm/gbm/mod.rs b/src/backend/drm/gbm/mod.rs deleted file mode 100644 index b09f139..0000000 --- a/src/backend/drm/gbm/mod.rs +++ /dev/null @@ -1,264 +0,0 @@ -//! -//! [`Device`](Device) and [`Surface`](Surface) -//! implementations using gbm buffers for efficient rendering. -//! -//! Usually this implementation will be wrapped into a [`EglDevice`](::backend::drm::egl::EglDevice). -//! Take a look at `anvil`s source code for an example of this. -//! -//! To use these types standalone, you will need to consider the special requirements -//! of [`GbmSurface::page_flip`](::backend::drm::gbm::GbmSurface::page_flip). -//! -//! For detailed overview of these abstractions take a look at the module documentation of backend::drm. -//! - -use super::{Device, DeviceHandler, RawDevice, ResourceHandles, Surface}; -use crate::backend::graphics::SwapBuffersError; - -use drm::control::{connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Mode}; -use drm::SystemError as DrmError; -use nix::libc::dev_t; - -use std::cell::RefCell; -use std::collections::HashMap; -use std::io; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::rc::{Rc, Weak}; -use std::sync::{Arc, Mutex, Once, Weak as WeakArc}; - -/// Errors thrown by the [`GbmDevice`](::backend::drm::gbm::GbmDevice) -/// and [`GbmSurface`](::backend::drm::gbm::GbmSurface). -#[derive(thiserror::Error, Debug)] -pub enum Error { - /// Creation of GBM device failed - #[error("Creation of GBM device failed")] - InitFailed(#[source] io::Error), - /// Creation of GBM surface failed - #[error("Creation of GBM surface failed")] - SurfaceCreationFailed(#[source] io::Error), - /// Creation of GBM buffer object failed - #[error("Creation of GBM buffer object failed")] - BufferCreationFailed(#[source] io::Error), - /// Writing to GBM buffer failed - #[error("Writing to GBM buffer failed")] - BufferWriteFailed(#[source] io::Error), - /// Creation of drm framebuffer failed - #[error("Creation of drm framebuffer failed")] - FramebufferCreationFailed(#[source] failure::Compat), - /// Lock of GBM surface front buffer failed - #[error("Lock of GBM surface font buffer failed")] - FrontBufferLockFailed, - /// No additional buffers are available - #[error("No additional buffers are available. Did you swap twice?")] - FrontBuffersExhausted, - /// Internal state was modified - #[error("Internal state was modified. Did you change gbm userdata?")] - InvalidInternalState, - /// The GBM device was destroyed - #[error("The GBM device was destroyed")] - DeviceDestroyed, - /// Underlying backend error - #[error("Underlying error: {0}")] - Underlying(#[source] U), -} - -mod surface; -pub use self::surface::GbmSurface; -use self::surface::GbmSurfaceInternal; - -#[cfg(feature = "backend_egl")] -pub mod egl; - -#[cfg(feature = "backend_session")] -pub mod session; - -static LOAD: Once = Once::new(); - -type SurfaceInternalRef = WeakArc::Surface>>; -/// Representation of an open gbm device to create rendering surfaces -pub struct GbmDevice { - pub(self) dev: Arc>>, - pub(self) raw: D, - backends: Rc>>>, - #[cfg(feature = "backend_session")] - links: Vec, - logger: ::slog::Logger, -} - -impl GbmDevice { - /// Create a new [`GbmDevice`] from an open drm node - /// - /// Returns an error if the file is no valid drm node or context creation was not - /// successful. - pub fn new(mut dev: D, logger: L) -> Result::Surface as Surface>::Error>> - where - L: Into>, - { - /* GBM will load a dri driver, but even though they need symbols from - * libglapi, in some version of Mesa they are not linked to it. Since - * only the gl-renderer module links to it, these symbols won't be - * globally available, and loading the DRI driver fails. - * Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL. - */ - LOAD.call_once(|| unsafe { - nix::libc::dlopen( - "libglapi.so.0".as_ptr() as *const _, - nix::libc::RTLD_LAZY | nix::libc::RTLD_GLOBAL, - ); - }); - - let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_gbm")); - - dev.clear_handler(); - - debug!(log, "Creating gbm device"); - Ok(GbmDevice { - // Open the gbm device from the drm device - dev: Arc::new(Mutex::new(unsafe { - gbm::Device::new_from_fd(dev.as_raw_fd()).map_err(Error::InitFailed)? - })), - raw: dev, - backends: Rc::new(RefCell::new(HashMap::new())), - #[cfg(feature = "backend_session")] - links: Vec::new(), - logger: log, - }) - } -} - -struct InternalDeviceHandler { - handler: Box> + 'static>, - backends: Weak>>>, - logger: ::slog::Logger, -} - -impl DeviceHandler for InternalDeviceHandler { - type Device = D; - - fn vblank(&mut self, crtc: crtc::Handle) { - if let Some(backends) = self.backends.upgrade() { - if let Some(surface) = backends.borrow().get(&crtc) { - if let Some(surface) = surface.upgrade() { - // here we unlock the buffer again, that was locked during rendering, - // to make sure it is always unlocked after a successful page_flip. - surface.unlock_buffer(); - self.handler.vblank(crtc); - } - } else { - warn!( - self.logger, - "Surface ({:?}) not managed by gbm, event not handled.", crtc - ); - } - } - } - fn error(&mut self, error: <::Surface as Surface>::Error) { - self.handler.error(Error::Underlying(error)) - } -} - -impl Device for GbmDevice { - type Surface = GbmSurface<::Surface>; - - fn device_id(&self) -> dev_t { - self.raw.device_id() - } - - fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { - self.raw.set_handler(InternalDeviceHandler { - handler: Box::new(handler), - backends: Rc::downgrade(&self.backends), - logger: self.logger.clone(), - }); - } - - fn clear_handler(&mut self) { - self.raw.clear_handler(); - } - - fn create_surface( - &mut self, - crtc: crtc::Handle, - mode: Mode, - connectors: &[connector::Handle], - ) -> Result::Surface>, Error<<::Surface as Surface>::Error>> { - info!(self.logger, "Initializing GbmSurface"); - - let drm_surface = self - .raw - .create_surface(crtc, mode, connectors) - .map_err(Error::Underlying)?; - - let backend = Arc::new(GbmSurfaceInternal::new( - self.dev.clone(), - drm_surface, - self.logger.new(o!("crtc" => format!("{:?}", crtc))), - )?); - self.backends.borrow_mut().insert(crtc, Arc::downgrade(&backend)); - Ok(GbmSurface(backend)) - } - - fn process_events(&mut self) { - self.raw.process_events() - } - - fn resource_handles(&self) -> Result::Surface as Surface>::Error>> { - Device::resource_handles(&self.raw).map_err(Error::Underlying) - } - - fn get_connector_info(&self, conn: connector::Handle) -> std::result::Result { - self.raw.get_connector_info(conn) - } - fn get_crtc_info(&self, crtc: crtc::Handle) -> std::result::Result { - self.raw.get_crtc_info(crtc) - } - fn get_encoder_info(&self, enc: encoder::Handle) -> std::result::Result { - self.raw.get_encoder_info(enc) - } - fn get_framebuffer_info( - &self, - fb: framebuffer::Handle, - ) -> std::result::Result { - self.raw.get_framebuffer_info(fb) - } - fn get_plane_info(&self, plane: plane::Handle) -> std::result::Result { - self.raw.get_plane_info(plane) - } -} - -impl AsRawFd for GbmDevice { - fn as_raw_fd(&self) -> RawFd { - self.raw.as_raw_fd() - } -} - -impl Drop for GbmDevice { - fn drop(&mut self) { - self.clear_handler(); - } -} - -impl Into for Error -where - E: std::error::Error + Into + 'static, -{ - fn into(self) -> SwapBuffersError { - match self { - Error::FrontBuffersExhausted => SwapBuffersError::AlreadySwapped, - Error::FramebufferCreationFailed(x) - if match x.get_ref() { - drm::SystemError::Unknown { - errno: nix::errno::Errno::EBUSY, - } => true, - drm::SystemError::Unknown { - errno: nix::errno::Errno::EINTR, - } => true, - _ => false, - } => - { - SwapBuffersError::TemporaryFailure(Box::new(Error::::FramebufferCreationFailed(x))) - } - Error::Underlying(x) => x.into(), - x => SwapBuffersError::ContextLost(Box::new(x)), - } - } -} diff --git a/src/backend/drm/gbm/session.rs b/src/backend/drm/gbm/session.rs deleted file mode 100644 index cd68393..0000000 --- a/src/backend/drm/gbm/session.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! -//! Support to register a [`GbmDevice`](GbmDevice) -//! to an open [`Session`](::backend::session::Session). -//! - -use drm::control::crtc; -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::{Rc, Weak}; - -use super::{GbmDevice, SurfaceInternalRef}; -use crate::backend::drm::{Device, RawDevice}; -use crate::backend::graphics::CursorBackend; -use crate::{ - backend::session::Signal as SessionSignal, - signaling::{Linkable, Signaler}, -}; - -/// [`SessionObserver`](SessionObserver) -/// linked to the [`GbmDevice`](GbmDevice) it was -/// created from. -pub(crate) struct GbmDeviceObserver { - backends: Weak>>>, - _logger: ::slog::Logger, -} - -impl Linkable for GbmDevice -where - D: RawDevice + drm::control::Device + Linkable + 'static, - ::Surface: CursorBackend, -{ - fn link(&mut self, signaler: Signaler) { - let lower_signal = Signaler::new(); - self.raw.link(lower_signal.clone()); - let mut observer = GbmDeviceObserver:: { - backends: Rc::downgrade(&self.backends), - _logger: self.logger.clone(), - }; - - let token = signaler.register(move |&signal| match signal { - SessionSignal::ActivateSession | SessionSignal::ActivateDevice { .. } => { - // Activate lower *before* we process the event - lower_signal.signal(signal); - observer.activate() - } - _ => { - lower_signal.signal(signal); - } - }); - - self.links.push(token); - } -} - -impl GbmDeviceObserver -where - ::Surface: CursorBackend, -{ - fn activate(&mut self) { - let mut crtcs = Vec::new(); - if let Some(backends) = self.backends.upgrade() { - for (crtc, backend) in backends.borrow().iter() { - if let Some(backend) = backend.upgrade() { - backend.clear_framebuffers(); - - // reset cursor - { - let cursor = backend.cursor.lock().unwrap(); - let _ = backend.crtc.set_cursor_representation(&cursor.0, cursor.1); - } - } else { - crtcs.push(*crtc); - } - } - for crtc in crtcs { - backends.borrow_mut().remove(&crtc); - } - } - } -} diff --git a/src/backend/drm/gbm/surface.rs b/src/backend/drm/gbm/surface.rs deleted file mode 100644 index 56e500d..0000000 --- a/src/backend/drm/gbm/surface.rs +++ /dev/null @@ -1,453 +0,0 @@ -use super::super::{RawSurface, Surface}; -use super::Error; - -use drm::control::{connector, crtc, framebuffer, Mode}; -use failure::ResultExt; -use gbm::{self, BufferObject, BufferObjectFlags, Format as GbmFormat}; -use image::{ImageBuffer, Rgba}; - -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, -}; - -use crate::backend::graphics::CursorBackend; - -pub struct Buffers { - pub(super) current_frame_buffer: Option, - pub(super) front_buffer: Option>, - pub(super) next_buffer: Option>, -} - -pub(super) struct GbmSurfaceInternal { - pub(super) dev: Arc>>, - pub(super) surface: Mutex>, - pub(super) crtc: S, - pub(super) cursor: Mutex<(BufferObject<()>, (u32, u32))>, - pub(super) buffers: Mutex, - pub(super) recreated: AtomicBool, - pub(super) logger: ::slog::Logger, -} - -impl GbmSurfaceInternal { - pub(super) fn new( - dev: Arc>>, - drm_surface: S, - logger: ::slog::Logger, - ) -> Result, Error<::Error>> { - // initialize the surface - let (w, h) = drm_surface.pending_mode().size(); - let surface = dev - .lock() - .unwrap() - .create_surface( - w as u32, - h as u32, - GbmFormat::XRGB8888, - BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, - ) - .map_err(Error::SurfaceCreationFailed)?; - - // initialize a buffer for the cursor image - let cursor = ( - dev.lock() - .unwrap() - .create_buffer_object( - 1, - 1, - GbmFormat::ARGB8888, - BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, - ) - .map_err(Error::BufferCreationFailed)?, - (0, 0), - ); - - Ok(GbmSurfaceInternal { - dev, - surface: Mutex::new(surface), - crtc: drm_surface, - cursor: Mutex::new(cursor), - buffers: Mutex::new(Buffers { - current_frame_buffer: None, - front_buffer: None, - next_buffer: None, - }), - recreated: AtomicBool::new(true), - logger, - }) - } - - pub(super) fn unlock_buffer(&self) { - // after the page swap is finished we need to release the rendered buffer. - // this is called from the PageFlipHandler - trace!(self.logger, "Releasing old front buffer"); - let mut buffers = self.buffers.lock().unwrap(); - buffers.front_buffer = buffers.next_buffer.take(); - // drop and release the old buffer - } - - pub unsafe fn page_flip(&self) -> Result<(), Error<::Error>> { - let (result, fb) = { - let mut buffers = self.buffers.lock().unwrap(); - if buffers.next_buffer.is_some() { - // We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done - warn!(self.logger, "Tried to swap with an already queued flip"); - return Err(Error::FrontBuffersExhausted); - } - - // supporting only one buffer would cause a lot of inconvinience and - // would most likely result in a lot of flickering. - // neither weston, wlc or wlroots bother with that as well. - // so we just assume we got at least two buffers to do flipping. - let mut next_bo = self - .surface - .lock() - .unwrap() - .lock_front_buffer() - .map_err(|_| Error::FrontBufferLockFailed)?; - - // create a framebuffer if the front buffer does not have one already - // (they are reused by gbm) - let maybe_fb = next_bo - .userdata() - .map_err(|_| Error::InvalidInternalState)? - .cloned(); - let fb = if let Some(info) = maybe_fb { - info - } else { - let fb = self - .crtc - .add_planar_framebuffer(&next_bo, &[0; 4], 0) - .compat() - .map_err(Error::FramebufferCreationFailed)?; - next_bo.set_userdata(fb).unwrap(); - fb - }; - buffers.next_buffer = Some(next_bo); - - if cfg!(debug_assertions) { - if let Err(err) = self.crtc.get_framebuffer(fb) { - error!(self.logger, "Cached framebuffer invalid: {:?}: {}", fb, err); - } - } - - // if we re-created the surface, we need to commit the new changes, as we might trigger a modeset - ( - if self.recreated.load(Ordering::SeqCst) { - debug!(self.logger, "Commiting new state"); - self.crtc.commit(fb).map_err(Error::Underlying) - } else { - trace!(self.logger, "Queueing Page flip"); - RawSurface::page_flip(&self.crtc, fb).map_err(Error::Underlying) - }, - fb, - ) - }; - - // if it was successful, we can clear the re-created state - match result { - Ok(_) => { - self.recreated.store(false, Ordering::SeqCst); - let mut buffers = self.buffers.lock().unwrap(); - buffers.current_frame_buffer = Some(fb); - Ok(()) - } - Err(err) => { - // if there was an error we need to free the buffer again, - // otherwise we may never lock again. - self.unlock_buffer(); - Err(err) - } - } - } - - // this function is called, if we e.g. need to create the surface to match a new mode. - pub fn recreate(&self) -> Result<(), Error<::Error>> { - let (w, h) = self.pending_mode().size(); - - // Recreate the surface and the related resources to match the new - // resolution. - debug!(self.logger, "(Re-)Initializing surface (with mode: {}:{})", w, h); - let surface = self - .dev - .lock() - .unwrap() - .create_surface( - w as u32, - h as u32, - GbmFormat::XRGB8888, - BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, - ) - .map_err(Error::SurfaceCreationFailed)?; - - // Clean up buffers - self.clear_framebuffers(); - - // Drop the old surface after cleanup - *self.surface.lock().unwrap() = surface; - - self.recreated.store(true, Ordering::SeqCst); - - Ok(()) - } - - // if the underlying drm-device is closed and re-opened framebuffers may get invalided. - // here we clear them just to be sure, they get recreated on the next page_flip. - pub fn clear_framebuffers(&self) { - let mut buffers = self.buffers.lock().unwrap(); - if let Some(Ok(Some(fb))) = buffers.next_buffer.take().map(|mut bo| bo.take_userdata()) { - if let Err(err) = self.crtc.destroy_framebuffer(fb) { - warn!( - self.logger, - "Error releasing old back_buffer framebuffer: {:?}", err - ); - } - } - - if let Some(Ok(Some(fb))) = buffers.front_buffer.take().map(|mut bo| bo.take_userdata()) { - if let Err(err) = self.crtc.destroy_framebuffer(fb) { - warn!( - self.logger, - "Error releasing old front_buffer framebuffer: {:?}", err - ); - } - } - } -} - -impl Surface for GbmSurfaceInternal { - type Connectors = ::Connectors; - type Error = Error<::Error>; - - fn crtc(&self) -> crtc::Handle { - self.crtc.crtc() - } - - fn current_connectors(&self) -> Self::Connectors { - self.crtc.current_connectors() - } - - fn pending_connectors(&self) -> Self::Connectors { - self.crtc.pending_connectors() - } - - fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { - self.crtc.add_connector(connector).map_err(Error::Underlying) - } - - fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { - self.crtc.remove_connector(connector).map_err(Error::Underlying) - } - - fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { - self.crtc.set_connectors(connectors).map_err(Error::Underlying) - } - - fn current_mode(&self) -> Mode { - self.crtc.current_mode() - } - - fn pending_mode(&self) -> Mode { - self.crtc.pending_mode() - } - - fn use_mode(&self, mode: Mode) -> Result<(), Self::Error> { - self.crtc.use_mode(mode).map_err(Error::Underlying) - } -} - -#[cfg(feature = "backend_drm")] -impl CursorBackend for GbmSurfaceInternal -where - S: CursorBackend, - ::Error: ::std::error::Error + Send, -{ - type CursorFormat = ImageBuffer, Vec>; - type Error = Error<::Error>; - - fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error> { - self.crtc.set_cursor_position(x, y).map_err(Error::Underlying) - } - - fn set_cursor_representation( - &self, - buffer: &ImageBuffer, Vec>, - hotspot: (u32, u32), - ) -> Result<(), Self::Error> { - let (w, h) = buffer.dimensions(); - debug!(self.logger, "Importing cursor"); - - // import the cursor into a buffer we can render - let mut cursor = self - .dev - .lock() - .unwrap() - .create_buffer_object( - w, - h, - GbmFormat::ARGB8888, - BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, - ) - .map_err(Error::BufferCreationFailed)?; - - cursor - .write(&**buffer) - .map_err(|_| Error::DeviceDestroyed)? - .map_err(Error::BufferWriteFailed)?; - - trace!(self.logger, "Setting the new imported cursor"); - - self.crtc - .set_cursor_representation(&cursor, hotspot) - .map_err(Error::Underlying)?; - - // and store it - *self.cursor.lock().unwrap() = (cursor, hotspot); - Ok(()) - } - - fn clear_cursor_representation(&self) -> Result<(), Self::Error> { - *self.cursor.lock().unwrap() = ( - self.dev - .lock() - .unwrap() - .create_buffer_object( - 1, - 1, - GbmFormat::ARGB8888, - BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, - ) - .map_err(Error::BufferCreationFailed)?, - (0, 0), - ); - self.crtc.clear_cursor_representation().map_err(Error::Underlying) - } -} - -impl Drop for GbmSurfaceInternal { - fn drop(&mut self) { - // Drop framebuffers attached to the userdata of the gbm surface buffers. - // (They don't implement drop, as they need the device) - self.clear_framebuffers(); - } -} - -/// Gbm surface for rendering -pub struct GbmSurface(pub(super) Arc>); - -impl GbmSurface { - /// Flips the underlying buffers. - /// - /// The surface will report being already flipped until the matching event - /// was processed either by calling [`Device::process_events`] manually after the flip - /// (bad idea performance-wise) or by binding the device to an event-loop by using - /// [`device_bind`](::backend::drm::device_bind). - /// - /// *Note*: This might trigger a full modeset on the underlying device, - /// potentially causing some flickering. In that case this operation is - /// blocking until the crtc is in the desired state. - /// - /// # Safety - /// - /// When used in conjunction with an EGL context, this must be called exactly once - /// after page-flipping the associated context. - pub unsafe fn page_flip(&self) -> ::std::result::Result<(), ::Error> { - self.0.page_flip() - } - - /// Recreate underlying gbm resources. - /// - /// This recreates the gbm surfaces resources, which might be needed after e.g. - /// calling [`Surface::use_mode`](Surface::use_mode). - /// You may check if your [`GbmSurface`] needs recreation through - /// [`needs_recreation`](GbmSurface::needs_recreation). - pub fn recreate(&self) -> Result<(), ::Error> { - self.0.recreate() - } - - /// Check if underlying gbm resources need to be recreated. - pub fn needs_recreation(&self) -> bool { - self.0.crtc.commit_pending() - } -} - -impl Surface for GbmSurface { - type Connectors = ::Connectors; - type Error = Error<::Error>; - - fn crtc(&self) -> crtc::Handle { - self.0.crtc() - } - - fn current_connectors(&self) -> Self::Connectors { - self.0.current_connectors() - } - - fn pending_connectors(&self) -> Self::Connectors { - self.0.pending_connectors() - } - - fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { - self.0.add_connector(connector) - } - - fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error> { - self.0.remove_connector(connector) - } - - fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { - self.0.set_connectors(connectors) - } - - fn current_mode(&self) -> Mode { - self.0.current_mode() - } - - fn pending_mode(&self) -> Mode { - self.0.pending_mode() - } - - fn use_mode(&self, mode: Mode) -> Result<(), Self::Error> { - self.0.use_mode(mode) - } -} - -#[cfg(feature = "backend_drm")] -impl CursorBackend for GbmSurface -where - S: CursorBackend, - ::Error: ::std::error::Error + Send, -{ - type CursorFormat = ImageBuffer, Vec>; - type Error = Error<::Error>; - - fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error> { - self.0.set_cursor_position(x, y) - } - - fn set_cursor_representation( - &self, - buffer: &ImageBuffer, Vec>, - hotspot: (u32, u32), - ) -> Result<(), Self::Error> { - self.0.set_cursor_representation(buffer, hotspot) - } - - fn clear_cursor_representation(&self) -> Result<(), Self::Error> { - self.0.clear_cursor_representation() - } -} - -#[cfg(test)] -mod test { - use super::GbmSurface; - use crate::backend::drm::legacy::LegacyDrmSurface; - use std::fs::File; - - fn is_send() {} - - #[test] - fn surface_is_send() { - is_send::>>(); - } -} diff --git a/src/backend/drm/legacy/mod.rs b/src/backend/drm/legacy/mod.rs deleted file mode 100644 index 5302601..0000000 --- a/src/backend/drm/legacy/mod.rs +++ /dev/null @@ -1,412 +0,0 @@ -//! -//! [`RawDevice`](RawDevice) and [`RawSurface`](RawSurface) -//! implementations using the legacy mode-setting infrastructure. -//! -//! Legacy mode-setting refers to the now outdated, but still supported direct manager api -//! of the linux kernel. Adaptations of this api can be found in BSD kernels. -//! -//! The newer and objectively better api is known as atomic-modesetting (or nuclear-page-flip), -//! however this api is not supported by every driver, so this is provided for backwards compatibility. -//! Currenly there are no features in smithay, that are exclusive to the atomic api. -//! -//! Usually this implementation will be wrapped into a [`GbmDevice`](::backend::drm::gbm::GbmDevice). -//! Take a look at `anvil`s source code for an example of this. -//! -//! For an example how to use this standalone, take a look at the `raw_legacy_drm` example. -//! -//! For detailed overview of these abstractions take a look at the module documentation of backend::drm. -//! - -use super::{common::Error, DevPath, Device, DeviceHandler, RawDevice}; - -use drm::control::{ - connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Event, Mode, ResourceHandles, -}; -use drm::{Device as BasicDevice, SystemError as DrmError}; -use nix::libc::dev_t; -use nix::sys::stat::fstat; - -use std::cell::RefCell; -use std::collections::HashMap; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::rc::Rc; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Weak}; - -use failure::{Fail, ResultExt}; - -mod surface; -pub use self::surface::LegacyDrmSurface; -use self::surface::LegacyDrmSurfaceInternal; - -#[cfg(feature = "backend_session")] -pub mod session; - -/// Open raw drm device utilizing legacy mode-setting -pub struct LegacyDrmDevice { - dev: Arc>, - dev_id: dev_t, - active: Arc, - backends: Rc>>>>, - handler: Option>>>>, - #[cfg(feature = "backend_session")] - links: Vec, - logger: ::slog::Logger, -} - -pub(in crate::backend::drm) struct Dev { - fd: A, - privileged: bool, - active: Arc, - old_state: HashMap)>, - logger: ::slog::Logger, -} -impl AsRawFd for Dev { - fn as_raw_fd(&self) -> RawFd { - self.fd.as_raw_fd() - } -} -impl BasicDevice for Dev {} -impl ControlDevice for Dev {} -impl Drop for Dev { - fn drop(&mut self) { - info!(self.logger, "Dropping device: {:?}", self.dev_path()); - if self.active.load(Ordering::SeqCst) { - // Here we restore the tty to it's previous state. - // In case e.g. getty was running on the tty sets the correct framebuffer again, - // so that getty will be visible. - // We do exit correctly, if this fails, but the user will be presented with - // a black screen, if no display handler takes control again. - let old_state = self.old_state.clone(); - for (handle, (info, connectors)) in old_state { - if let Err(err) = self.set_crtc( - handle, - info.framebuffer(), - info.position(), - &connectors, - info.mode(), - ) { - error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err); - } - } - } - if self.privileged { - if let Err(err) = self.release_master_lock() { - error!(self.logger, "Failed to drop drm master state. Error: {}", err); - } - } - } -} - -impl LegacyDrmDevice { - /// Create a new [`LegacyDrmDevice`] from an open drm node. - /// - /// # Arguments - /// - /// - `fd` - Open drm node - /// - `disable_connectors` - Setting this to true will initialize all connectors \ - /// as disabled on device creation. smithay enables connectors, when attached \ - /// to a surface, and disables them, when detached. Setting this to `false` \ - /// requires usage of `drm-rs` to disable unused connectors to prevent them \ - /// showing garbage, but will also prevent flickering of already turned on \ - /// connectors (assuming you won't change the resolution). - /// - `logger` - Optional [`slog::Logger`] to be used by this device. - /// - /// # Return - /// - /// Returns an error if the file is no valid drm node or the device is not accessible. - pub fn new(dev: A, disable_connectors: bool, logger: L) -> Result - where - L: Into>, - { - let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm")); - info!(log, "LegacyDrmDevice initializing"); - - let dev_id = fstat(dev.as_raw_fd()) - .map_err(Error::UnableToGetDeviceId)? - .st_rdev; - - // we wrap some of the internal state in another struct to share with - // the surfaces and event loop handlers. - let active = Arc::new(AtomicBool::new(true)); - let mut dev = Dev { - fd: dev, - privileged: true, - old_state: HashMap::new(), - active: active.clone(), - logger: log.clone(), - }; - - // We want to modeset, so we better be the master, if we run via a tty session. - // This is only needed on older kernels. Newer kernels grant this permission, - // if no other process is already the *master*. So we skip over this error. - if dev.acquire_master_lock().is_err() { - warn!(log, "Unable to become drm master, assuming unprivileged mode"); - dev.privileged = false; - }; - - // Enumerate (and save) the current device state. - // We need to keep the previous device configuration to restore the state later, - // so we query everything, that we can set. - let res_handles = ControlDevice::resource_handles(&dev) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error loading drm resources", - dev: dev.dev_path(), - source, - })?; - for &con in res_handles.connectors() { - let con_info = dev.get_connector(con).compat().map_err(|source| Error::Access { - errmsg: "Error loading connector info", - dev: dev.dev_path(), - source, - })?; - if let Some(enc) = con_info.current_encoder() { - let enc_info = dev.get_encoder(enc).compat().map_err(|source| Error::Access { - errmsg: "Error loading encoder info", - dev: dev.dev_path(), - source, - })?; - if let Some(crtc) = enc_info.crtc() { - let info = dev.get_crtc(crtc).compat().map_err(|source| Error::Access { - errmsg: "Error loading crtc info", - dev: dev.dev_path(), - source, - })?; - dev.old_state - .entry(crtc) - .or_insert((info, Vec::new())) - .1 - .push(con); - } - } - } - - // If the user does not explicitly requests us to skip this, - // we clear out the complete connector<->crtc mapping on device creation. - // - // The reason is, that certain operations may be racy otherwise, as surfaces can - // exist on different threads. As a result, we cannot enumerate the current state - // on surface creation (it might be changed on another thread during the enumeration). - // An easy workaround is to set a known state on device creation. - if disable_connectors { - dev.set_connector_state(res_handles.connectors().iter().copied(), false)?; - - for crtc in res_handles.crtcs() { - // null commit (necessary to trigger removal on the kernel side with the legacy api.) - dev.set_crtc(*crtc, None, (0, 0), &[], None) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error setting crtc", - dev: dev.dev_path(), - source, - })?; - } - } - - Ok(LegacyDrmDevice { - dev: Arc::new(dev), - dev_id, - active, - backends: Rc::new(RefCell::new(HashMap::new())), - handler: None, - #[cfg(feature = "backend_session")] - links: Vec::new(), - logger: log.clone(), - }) - } -} - -impl Dev { - pub(in crate::backend::drm::legacy) fn set_connector_state( - &self, - connectors: impl Iterator, - enabled: bool, - ) -> Result<(), Error> { - // for every connector... - for conn in connectors { - let info = self - .get_connector(conn) - .compat() - .map_err(|source| Error::Access { - errmsg: "Failed to get connector infos", - dev: self.dev_path(), - source, - })?; - // that is currently connected ... - if info.state() == connector::State::Connected { - // get a list of it's properties. - let props = self - .get_properties(conn) - .compat() - .map_err(|source| Error::Access { - errmsg: "Failed to get properties for connector", - dev: self.dev_path(), - source, - })?; - let (handles, _) = props.as_props_and_values(); - // for every handle ... - for handle in handles { - // get information of that property - let info = self - .get_property(*handle) - .compat() - .map_err(|source| Error::Access { - errmsg: "Failed to get property of connector", - dev: self.dev_path(), - source, - })?; - // to find out, if we got the handle of the "DPMS" property ... - if info.name().to_str().map(|x| x == "DPMS").unwrap_or(false) { - // so we can use that to turn on / off the connector - self.set_property( - conn, - *handle, - if enabled { - 0 /*DRM_MODE_DPMS_ON*/ - } else { - 3 /*DRM_MODE_DPMS_OFF*/ - }, - ) - .compat() - .map_err(|source| Error::Access { - errmsg: "Failed to set property of connector", - dev: self.dev_path(), - source, - })?; - } - } - } - } - Ok(()) - } -} - -impl AsRawFd for LegacyDrmDevice { - fn as_raw_fd(&self) -> RawFd { - self.dev.as_raw_fd() - } -} - -impl BasicDevice for LegacyDrmDevice {} -impl ControlDevice for LegacyDrmDevice {} - -impl Device for LegacyDrmDevice { - type Surface = LegacyDrmSurface; - - fn device_id(&self) -> dev_t { - self.dev_id - } - - fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { - self.handler = Some(RefCell::new(Box::new(handler))); - } - - fn clear_handler(&mut self) { - let _ = self.handler.take(); - } - - fn create_surface( - &mut self, - crtc: crtc::Handle, - mode: Mode, - connectors: &[connector::Handle], - ) -> Result, Error> { - if self.backends.borrow().contains_key(&crtc) { - return Err(Error::CrtcAlreadyInUse(crtc)); - } - - if !self.active.load(Ordering::SeqCst) { - return Err(Error::DeviceInactive); - } - - if connectors.is_empty() { - return Err(Error::SurfaceWithoutConnectors(crtc)); - } - - let backend = Arc::new(LegacyDrmSurfaceInternal::new( - self.dev.clone(), - crtc, - mode, - connectors, - self.logger.new(o!("crtc" => format!("{:?}", crtc))), - )?); - - self.backends.borrow_mut().insert(crtc, Arc::downgrade(&backend)); - Ok(LegacyDrmSurface(backend)) - } - - fn process_events(&mut self) { - match self.receive_events() { - Ok(events) => { - for event in events { - if let Event::PageFlip(event) = event { - if self - .backends - .borrow() - .get(&event.crtc) - .iter() - .flat_map(|x| x.upgrade()) - .next() - .is_some() - { - trace!(self.logger, "Handling event for backend {:?}", event.crtc); - if let Some(handler) = self.handler.as_ref() { - handler.borrow_mut().vblank(event.crtc); - } - } else { - self.backends.borrow_mut().remove(&event.crtc); - } - } else { - trace!(self.logger, "Unrelated event"); - } - } - } - Err(source) => { - if let Some(handler) = self.handler.as_ref() { - handler.borrow_mut().error(Error::Access { - errmsg: "Error processing drm events", - dev: self.dev_path(), - source: source.compat(), - }); - } - } - } - } - - fn resource_handles(&self) -> Result { - ControlDevice::resource_handles(self) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error loading resource info", - dev: self.dev_path(), - source, - }) - } - - fn get_connector_info(&self, conn: connector::Handle) -> Result { - self.get_connector(conn) - } - fn get_crtc_info(&self, crtc: crtc::Handle) -> Result { - self.get_crtc(crtc) - } - fn get_encoder_info(&self, enc: encoder::Handle) -> Result { - self.get_encoder(enc) - } - fn get_framebuffer_info(&self, fb: framebuffer::Handle) -> Result { - self.get_framebuffer(fb) - } - fn get_plane_info(&self, plane: plane::Handle) -> Result { - self.get_plane(plane) - } -} - -impl RawDevice for LegacyDrmDevice { - type Surface = LegacyDrmSurface; -} - -impl Drop for LegacyDrmDevice { - fn drop(&mut self) { - self.clear_handler(); - } -} diff --git a/src/backend/drm/legacy/session.rs b/src/backend/drm/legacy/session.rs deleted file mode 100644 index 0571a50..0000000 --- a/src/backend/drm/legacy/session.rs +++ /dev/null @@ -1,198 +0,0 @@ -//! -//! Support to register an open [`LegacyDrmDevice`](LegacyDrmDevice) -//! to an open [`Session`](::backend::session::Session). -//! - -use drm::control::{crtc, Device as ControlDevice}; -use drm::Device as BasicDevice; -use failure::ResultExt; -use nix::libc::dev_t; -use nix::sys::stat; -use std::cell::RefCell; -use std::collections::{HashMap, HashSet}; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::rc::{Rc, Weak}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Weak as WeakArc}; - -use super::{Dev, DevPath, Error, LegacyDrmDevice, LegacyDrmSurfaceInternal}; -use crate::{ - backend::session::Signal as SessionSignal, - signaling::{Linkable, Signaler}, -}; - -/// [`SessionObserver`](SessionObserver) -/// linked to the [`LegacyDrmDevice`](LegacyDrmDevice) -/// it was created from. -pub(crate) struct LegacyDrmDeviceObserver { - dev: WeakArc>, - dev_id: dev_t, - privileged: bool, - active: Arc, - backends: Weak>>>>, - logger: ::slog::Logger, -} - -impl Linkable for LegacyDrmDevice { - fn link(&mut self, signaler: Signaler) { - let mut observer = LegacyDrmDeviceObserver { - dev: Arc::downgrade(&self.dev), - dev_id: self.dev_id, - active: self.active.clone(), - privileged: self.dev.privileged, - backends: Rc::downgrade(&self.backends), - logger: self.logger.clone(), - }; - - let token = signaler.register(move |signal| observer.signal(*signal)); - self.links.push(token); - } -} - -impl LegacyDrmDeviceObserver { - fn signal(&mut self, signal: SessionSignal) { - match signal { - SessionSignal::PauseSession => self.pause(None), - SessionSignal::PauseDevice { major, minor } => self.pause(Some((major, minor))), - SessionSignal::ActivateSession => self.activate(None), - SessionSignal::ActivateDevice { major, minor, new_fd } => { - self.activate(Some((major, minor, new_fd))) - } - } - } - - fn pause(&mut self, devnum: Option<(u32, u32)>) { - if let Some((major, minor)) = devnum { - if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) { - return; - } - } - if let Some(device) = self.dev.upgrade() { - if let Some(backends) = self.backends.upgrade() { - for surface in backends.borrow().values().filter_map(WeakArc::upgrade) { - // other ttys that use no cursor, might not clear it themselves. - // This makes sure our cursor won't stay visible. - // - // This usually happens with getty, and a cursor on top of a kernel console looks very weird. - let _ = (*device).set_cursor( - surface.crtc, - Option::<&drm::control::dumbbuffer::DumbBuffer>::None, - ); - } - } - } - self.active.store(false, Ordering::SeqCst); - if self.privileged { - if let Some(device) = self.dev.upgrade() { - if let Err(err) = device.release_master_lock() { - error!(self.logger, "Failed to drop drm master state. Error: {}", err); - } - } - } - } - - fn activate(&mut self, devnum: Option<(u32, u32, Option)>) { - if let Some((major, minor, fd)) = devnum { - if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) { - return; - } else if let Some(fd) = fd { - info!(self.logger, "Replacing fd"); - if let Some(device) = self.dev.upgrade() { - ::nix::unistd::dup2(device.as_raw_fd(), fd) - .expect("Failed to replace file descriptor of drm device"); - } - } - } - self.active.store(true, Ordering::SeqCst); - if self.privileged { - if let Some(device) = self.dev.upgrade() { - if let Err(err) = device.acquire_master_lock() { - crit!(self.logger, "Failed to acquire drm master again. Error: {}", err); - } - } - } - // okay, the previous session/whatever might left the drm devices in any state... - // lets fix that - if let Err(err) = self.reset_state() { - warn!(self.logger, "Unable to reset state after tty switch: {}", err); - // TODO call drm-handler::error - } - } -} - -impl LegacyDrmDeviceObserver { - // reset state enumerates the actual current state of the drm device - // and applies that to the `current_state` as saved in the surfaces. - // - // This re-sync is necessary after a tty swap, as the pipeline might - // be left in a different state. - fn reset_state(&mut self) -> Result<(), Error> { - // lets enumerate it the current state - if let Some(dev) = self.dev.upgrade() { - let res_handles = ControlDevice::resource_handles(&*dev) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error loading drm resources", - dev: dev.dev_path(), - source, - })?; - - let mut used_connectors = HashSet::new(); - if let Some(backends) = self.backends.upgrade() { - for surface in backends.borrow().values().filter_map(WeakArc::upgrade) { - let mut current = surface.state.write().unwrap(); - let pending = surface.pending.read().unwrap(); - - // store (soon to be) used connectors - used_connectors.extend(pending.connectors.clone()); - - // set current connectors - current.connectors.clear(); - for conn in res_handles.connectors() { - let conn_info = - dev.get_connector(*conn) - .compat() - .map_err(|source| Error::Access { - errmsg: "Could not load connector info", - dev: dev.dev_path(), - source, - })?; - if let Some(enc) = conn_info.current_encoder() { - let enc_info = dev.get_encoder(enc).compat().map_err(|source| Error::Access { - errmsg: "Could not load encoder info", - dev: dev.dev_path(), - source, - })?; - if enc_info.crtc().map(|crtc| crtc == surface.crtc).unwrap_or(false) { - current.connectors.insert(*conn); - } - } - } - - // set current mode - let crtc_info = dev - .get_crtc(surface.crtc) - .compat() - .map_err(|source| Error::Access { - errmsg: "Could not load crtc info", - dev: dev.dev_path(), - source, - })?; - - // If we have no current mode, we create a fake one, which will not match (and thus gets overriden on the commit below). - // A better fix would probably be making mode an `Option`, but that would mean - // we need to be sure, we require a mode to always be set without relying on the compiler. - // So we cheat, because it works and is easier to handle later. - current.mode = crtc_info.mode().unwrap_or_else(|| unsafe { std::mem::zeroed() }); - } - } - - // Disable unused connectors - let all_set = res_handles.connectors().iter().copied().collect::>(); - let unused = used_connectors.difference(&all_set); - dev.set_connector_state(unused.copied(), false)?; - } - - Ok(()) - } -} diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index f5835cc..370295f 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -1,391 +1,9 @@ -//! -//! This module provides Traits reprensentating open devices -//! and their surfaces to render contents. -//! -//! # Abstractions -//! -//! This is a model of what the user typically perceives as a `Device` and a `Surface`. -//! The meaning of these words is very overloaded in the general computer graphics context, -//! so let me give you a quick run down, what these mean in the context of smithay's public api: -//! -//! ## Device -//! -//! A device is some sort of rendering device. It exposes certain properties, which are directly derived -//! from the *device* as perceived by the direct rendering manager api (drm). These resources consists -//! out of connectors, encoders, framebuffers, planes and crtcs. -//! -//! [`crtc`](drm::control::crtc)s represent scanout engines of the device pointer to one framebuffer. -//! Their responsibility is to read the data of the framebuffer and export it into an "Encoder". -//! The number of crtc's represent the number of independent output devices the hardware may handle. -//! -//! On modern graphic cards it is better to think about the `crtc` as some sort of rendering engine. -//! You can only have so many different pictures, you may display, as you have `crtc`s, but a single image -//! may be put onto multiple displays. -//! -//! An [`encoder`](drm::control::encoder) encodes the data of connected crtcs into a video signal for a fixed set of connectors. -//! E.g. you might have an analog encoder based on a DAG for VGA ports, but another one for digital ones. -//! Also not every encoder might be connected to every crtc. -//! -//! A [`connector`](drm::control::connector) represents a port on your computer, possibly with a connected monitor, TV, capture card, etc. -//! -//! A [`framebuffer`](drm::control::framebuffer) represents a buffer you may be rendering to, see `Surface` below. -//! -//! Planes are another layer on top of the crtcs, which allow us to layer multiple images on top of each other more efficiently -//! then by combining the rendered images in the rendering phase, e.g. via OpenGL. They are internally used by smithay to provide -//! hardware-accelerated cursors. More features regarding planes may be added in the future, but they are mostly optional and -//! mainly provide possibilies for optimizations. -//! -//! The main functionality of a `Device` in smithay is to give access to all these properties for the user to -//! choose an appropriate rendering configuration. What that means is defined by the requirements and constraints documented -//! in the specific device implementations. The second functionality is the creation of a `Surface`. -//! Surface creation requires a `crtc` (which cannot be the same as another existing `Surface`'s crtc), -//! as well as a `Mode` and a set of `connectors`. -//! -//! smithay does not make sure that `connectors` are not already in use by another `Surface`. Overlapping `connector`-Sets may -//! be an error or result in undefined rendering behavior depending on the `Surface` implementation. -//! -//! ## Surface -//! -//! A surface is a part of a `Device` that may output a picture to a number of connectors. It pumps pictures of buffers to outputs. -//! -//! On surface creation a matching encoder for your `encoder`-`connector` is automatically selected, -//! if it exists, which means you still need to check your configuration. -//! -//! A surface consists of one `crtc` that is rendered to by the user. This is fixed for the `Surface`s lifetime and cannot be changed. -//! A surface also always needs at least one connector to output the resulting image to as well as a `Mode` that is valid for the given connector. -//! -//! The state of a `Surface` is double-buffered, meaning all operations that chance the set of `connector`s or their `Mode` are stored and -//! only applied on the next commit. `Surface`s do their best to validate these changes, if possible. -//! -//! A commit/page_flip may be triggered by any other method the `Surface` has, but needs to be documented as such. -//! The most low-level `Surface`s also implement `RawSurface`, which let you trigger a commit explicitly. -//! -//! ## RawDevice -//! -//! A low-level device that produces not only `Surface`s, but `RawSurface`s. -//! -//! ## RawSurface -//! -//! RawSurfaces provide two additional functions related to rendering: `commit` and `page_flip`. -//! Both attach the contents of a framebuffer, resulting in it's contents being displayed on the set connectors. -//! -//! `commit` also applies any pending changes to the current `Mode` and `connector`-Sets. -//! You can easily check, if there are pending changes and avoid the costly commit by using `commit_pending`, -//! which compares the current and pending state internally. -//! -//! # Rendering -//! -//! To perform actual rendering operations you need to render your image to a framebuffer. -//! Several types of framebuffers exists. -//! -//! The simplest one - the `Dumbbuffer` is a simple bitmap, usually very slow and not hardware-accelerated. -//! It should be avoided at any cost, but works on all devices and can be used directly with a `RawSurface`. -//! Rendering to it can be done manually by coping bitmaps around (cpu rendering). -//! -//! Accelerated buffers are usually hardware manufacturer dependent. A problem, which is mostly solved by -//! the General Buffer Manager (gbm-)API. `gbm::Buffer`s can only be rendered to by egl. Appropriate hardware-accelerated -//! buffers are picked automatically. gbm even may fall back to `Dumbbuffers` on unsupported devices and -//! egl-support is then provided by Mesa's software renderer `llvmpipe`. -//! -//! An alternative api to `gbm` is the so called `eglstream`-api, proposed (and implemented) by nvidia. -//! It is currenly only supported by nvidias proprietary driver, which is also not accelerated by gbm, although other -//! manufacturers could in theory implement the standard, just like nvidia could implement gbm-support in their driver. -//! Ongoing discussions about these api's may result in another (but hopefully unified) api in the future. -//! -//! # Implementations -//! -//! Smithay provided these different functionalities as wrappers around many `Device` and `Surface` implementations. -//! -//! At the low-level we have: -//! -//! - [`AtomicDrmDevice`](atomic::AtomicDrmDevice) (and [`AtomicDrmSurface`](atomic::AtomicDrmSurface)), which implement the traits directly using the modern atomic drm-api. -//! - [`LegacyDrmDevice`](legacy::LegacyDrmDevice) (and [`LegacyDrmSurface`](legacy::LegacyDrmSurface)), which implement the traits directly using the outdated drm-api. -//! -//! Both of these also implement `RawDevice` and `RawSurface`. -//! -//! On top of that the following wrappers add buffer allocation apis: -//! -//! - [`GbmDevice`](gbm::GbmDevice) (and [`GbmSurface`](gbm::Surface)), which replace the direct rendering capabilities of any underlying `RawSurface` with an egl-specific `page_flip` function. -//! - [`EglStreamDevice`](eglstream::EglStreamDevice) (and [`EglStreamSurface`](eglstream::EglStreamSurface)), which replace the direct rendering capabilities of any underlying `RawSurface` with functionality to create EGLStreams. -//! -//! On top of that the [`EglDevice`](egl::EglDevice) (and [`EglSurface`](egl::EglSurface)) replace the manual buffer management -//! with an implementation of smithays [`GLGraphicsBackend`](::backend::graphics::gl::GLGraphicsBackend) to initialize the OpenGL-API for rendering. -//! -//! Additionally the [`FallbackDevice`](fallback::FallbackDevice) provides an easy wrapper to automatically pick an appropriate device. -//! E.g. it can initialize an `AtomicDrmDevice` or fallback to a `LegacyDrmDevice` with the atomic api is not supported by the graphics card. -//! It can also check for a loaded nvidia-driver for a given device and choose between an `EglStreamDevice` or a `GbmDevice`. -//! -//! Any layer of this chain can be implemented and therefore extended by the user. All building blocks are exposed to add addtional -//! buffer management solutions or rendering apis out of tree. Additionally all of these implementations can be excluded from a build -//! by disabling their corresponding feature. -//! -//! The most versatile type currently provided by smithay, that should be able to initialize almost every -//! common graphics card is -//! ```rust,ignore -//! type RenderDevice = FallbackDevice< -//! EglDevice< -//! EglGbmBackend, LegacyDrmDevice>>, -//! GbmDevice, LegacyDrmDevice>>, -//! >, -//! EglDevice< -//! EglStreamDeviceBackend, LegacyDrmDevice>>, -//! EglStreamDevice, LegacyDrmDevice>>, -//! > -//! >; -//! ``` -//! -//! # Event handling -//! -//! Devices can be attached to an eventloop using `device_bind`. -//! Events triggered by a drm-device correspond to finished page-flips. -//! After submitting a page_flip (possibly through a high-level api, such as a `EglSurface`) -//! the GPU may take some time until the new framebuffer is displayed. Rendering the next -//! frame should be delayed until the flip is done. -//! -//! Naive implementations should thus be driven by the drm device events to synchronize rendering -//! with the device. More advanced implementations may take into account that, if no change has occurred, -//! a page_flip may waste resources and therefore delay rendering until the buffer change from a client -//! is received or input is registered, etc. Because a flip may fail (which will result in no event), -//! a robust rescheduling implementation for missed frames or intentionally skipped flips should be -//! in place. -//! +pub(crate) mod device; +pub(self) mod surface; +pub(self) mod error; +pub(self) mod session; -use drm::{ - control::{connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Mode, ResourceHandles}, - Device as BasicDevice, SystemError as DrmError, -}; -use nix::libc::dev_t; - -use std::error::Error; -use std::iter::IntoIterator; -use std::os::unix::io::AsRawFd; -use std::path::PathBuf; - -use calloop::{generic::Generic, InsertError, LoopHandle, Source}; - -#[cfg(feature = "backend_drm_atomic")] -pub mod atomic; -#[cfg(feature = "backend_drm")] -pub mod common; -#[cfg(feature = "backend_drm_egl")] -pub mod egl; -#[cfg(feature = "backend_drm_eglstream")] -pub mod eglstream; -#[cfg(feature = "backend_drm_gbm")] -pub mod gbm; -#[cfg(feature = "backend_drm_legacy")] -pub mod legacy; - -/// Trait to receive events of a bound [`Device`] -/// -/// See [`device_bind`] -pub trait DeviceHandler { - /// The [`Device`] type this handler can handle - type Device: Device + ?Sized; - - /// A vblank blank event on the provided crtc has happend - fn vblank(&mut self, crtc: crtc::Handle); - /// An error happend while processing events - fn error(&mut self, error: <<::Device as Device>::Surface as Surface>::Error); -} - -/// An open drm device -pub trait Device: AsRawFd + DevPath { - /// Associated [`Surface`] of this [`Device`] type - type Surface: Surface; - - /// Returns the id of this device node. - fn device_id(&self) -> dev_t; - - /// Assigns a [`DeviceHandler`] called during event processing. - /// - /// See [`device_bind`] and [`DeviceHandler`] - fn set_handler(&mut self, handler: impl DeviceHandler + 'static); - /// Clear a set [`DeviceHandler`](trait.DeviceHandler.html), if any - fn clear_handler(&mut self); - - /// Creates a new rendering surface. - /// - /// # Arguments - /// - /// Initialization of surfaces happens through the types provided by - /// [`drm-rs`](drm). - /// - /// - [`crtc`](drm::control::crtc)s represent scanout engines of the device pointing to one framebuffer. \ - /// Their responsibility is to read the data of the framebuffer and export it into an "Encoder". \ - /// The number of crtc's represent the number of independant output devices the hardware may handle. - /// - [`mode`](drm::control::Mode) describes the resolution and rate of images produced by the crtc and \ - /// has to be compatible with the provided `connectors`. - /// - [`connectors`] - List of connectors driven by the crtc. At least one(!) connector needs to be \ - /// attached to a crtc in smithay. - fn create_surface( - &mut self, - crtc: crtc::Handle, - mode: Mode, - connectors: &[connector::Handle], - ) -> Result::Error>; - - /// Processes any open events of the underlying file descriptor. - /// - /// You should not call this function manually, but rather use - /// [`device_bind`] to register the device - /// to an [`EventLoop`](calloop::EventLoop) - /// to synchronize your rendering to the vblank events of the open crtc's - fn process_events(&mut self); - - /// Attempts to acquire a copy of the [`Device`]'s - /// [`ResourceHandle`](drm::control::ResourceHandle) - fn resource_handles(&self) -> Result::Error>; - - /// Retrieve the information for a connector - fn get_connector_info(&self, conn: connector::Handle) -> Result; - - /// Retrieve the information for a crtc - fn get_crtc_info(&self, crtc: crtc::Handle) -> Result; - - /// Retrieve the information for an encoder - fn get_encoder_info(&self, enc: encoder::Handle) -> Result; - - /// Retrieve the information for a framebuffer - fn get_framebuffer_info(&self, fb: framebuffer::Handle) -> Result; - - /// Retrieve the information for a plane - fn get_plane_info(&self, plane: plane::Handle) -> Result; -} - -/// Marker trait for [`Device`]s able to provide [`RawSurface`]s -pub trait RawDevice: Device::Surface> { - /// Associated [`RawSurface`] of this [`RawDevice`] type - type Surface: RawSurface; -} - -/// An open crtc that can be used for rendering -pub trait Surface { - /// Type repesenting a collection of - /// [`connector`](drm::control::connector)s - /// returned by [`current_connectors`](Surface::current_connectors) and - /// [`pending_connectors`](Surface::pending_connectors) - type Connectors: IntoIterator; - /// Error type returned by methods of this trait - type Error: Error + Send + 'static; - - /// Returns the underlying [`crtc`](drm::control::crtc) of this surface - fn crtc(&self) -> crtc::Handle; - /// Currently used [`connector`](drm::control::connector)s of this `Surface` - fn current_connectors(&self) -> Self::Connectors; - /// Returns the pending [`connector`](drm::control::connector)s - /// used after the next [`commit`](RawSurface::commit) of this [`Surface`] - /// - /// *Note*: Only on a [`RawSurface`] you may directly trigger - /// a [`commit`](RawSurface::commit). Other `Surface`s provide their - /// own methods that *may* trigger a commit, you will need to read their docs. - fn pending_connectors(&self) -> Self::Connectors; - /// Tries to add a new [`connector`](drm::control::connector) - /// to be used after the next commit. - /// - /// **Warning**: You need to make sure, that the connector is not used with another surface - /// or was properly removed via `remove_connector` + `commit` before adding it to another surface. - /// Behavior if failing to do so is undefined, but might result in rendering errors or the connector - /// getting removed from the other surface without updating it's internal state. - /// - /// Fails if the `connector` is not compatible with the underlying [`crtc`](drm::control::crtc) - /// (e.g. no suitable [`encoder`](drm::control::encoder) may be found) - /// or is not compatible with the currently pending - /// [`Mode`](drm::control::Mode). - fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>; - /// Tries to mark a [`connector`](drm::control::connector) - /// for removal on the next commit. - fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>; - /// Tries to replace the current connector set with the newly provided one on the next commit. - /// - /// Fails if one new `connector` is not compatible with the underlying [`crtc`](drm::control::crtc) - /// (e.g. no suitable [`encoder`](drm::control::encoder) may be found) - /// or is not compatible with the currently pending - /// [`Mode`](drm::control::Mode). - fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error>; - /// Returns the currently active [`Mode`](drm::control::Mode) - /// of the underlying [`crtc`](drm::control::crtc) - fn current_mode(&self) -> Mode; - /// Returns the currently pending [`Mode`](drm::control::Mode) - /// to be used after the next commit. - fn pending_mode(&self) -> Mode; - /// Tries to set a new [`Mode`](drm::control::Mode) - /// to be used after the next commit. - /// - /// Fails if the mode is not compatible with the underlying - /// [`crtc`](drm::control::crtc) or any of the - /// pending [`connector`](drm::control::connector)s. - /// - /// *Note*: Only on a [`RawSurface`] you may directly trigger - /// a [`commit`](RawSurface::commit). Other [`Surface`]s provide their - /// own methods that *may* trigger a commit, you will need to read their docs. - fn use_mode(&self, mode: Mode) -> Result<(), Self::Error>; -} - -/// An open bare crtc without any rendering abstractions -pub trait RawSurface: Surface + ControlDevice + BasicDevice { - /// Returns true whenever any state changes are pending to be commited - /// - /// The following functions may trigger a pending commit: - /// - [`add_connector`](Surface::add_connector) - /// - [`remove_connector`](Surface::remove_connector) - /// - [`use_mode`](Surface::use_mode) - fn commit_pending(&self) -> bool; - /// Commit the pending state rendering a given framebuffer. - /// - /// *Note*: This will trigger a full modeset on the underlying device, - /// potentially causing some flickering. Check before performing this - /// operation if a commit really is necessary using [`commit_pending`](RawSurface::commit_pending). - /// - /// This operation is not necessarily blocking until the crtc is in the desired state, - /// 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. - fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), ::Error>; - /// Page-flip the underlying [`crtc`](drm::control::crtc) - /// to a new given [`framebuffer`]. - /// - /// This will not cause the crtc to modeset. - /// - /// 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. - fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), ::Error>; -} - -/// Trait representing open devices that *may* return a `Path` -pub trait DevPath { - /// Returns the path of the open device if possible - fn dev_path(&self) -> Option; -} - -impl DevPath for A { - fn dev_path(&self) -> Option { - use std::fs; - - fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok() - } -} - -/// calloop source associated with a Device -pub type DrmSource = Generic; - -/// Bind a `Device` to an [`EventLoop`](calloop::EventLoop), -/// -/// This will cause it to recieve events and feed them into a previously -/// set [`DeviceHandler`](DeviceHandler). -pub fn device_bind( - handle: &LoopHandle, - device: D, -) -> ::std::result::Result>, InsertError>> -where - D: Device, - Data: 'static, -{ - let source = Generic::new(device, calloop::Interest::Readable, calloop::Mode::Level); - - handle.insert_source(source, |_, source, _| { - source.process_events(); - Ok(()) - }) -} +pub use device::{DrmDevice, DrmSource, device_bind}; +pub use surface::DrmSurface; +pub use error::Error as DrmError; +pub use session::DrmDeviceObserver; \ No newline at end of file diff --git a/src/backend/drm/render.rs b/src/backend/drm/render.rs new file mode 100644 index 0000000..9a77d40 --- /dev/null +++ b/src/backend/drm/render.rs @@ -0,0 +1,27 @@ +pub struct DrmRenderSurface< + D: AsRawFd + 'static, + A: Allocator, + S: Buffer, + D: Buffer + TryFrom, + E: Error, + T, F, + R: Renderer, +> { + drm: DrmSurface, + allocator: A, + renderer: R, + swapchain: Swapchain, +} + +impl DrmRenderSurface +where + D: AsRawFd + 'static, + A: Allocator, + S: Buffer, + D: Buffer + TryFrom, + E: Error, + T, F, + R: Renderer, +{ + +} \ No newline at end of file diff --git a/src/backend/drm/session.rs b/src/backend/drm/session.rs new file mode 100644 index 0000000..a03e844 --- /dev/null +++ b/src/backend/drm/session.rs @@ -0,0 +1,113 @@ +use std::sync::{Arc, Weak, atomic::{AtomicBool, Ordering}}; +use std::os::unix::io::{AsRawFd, RawFd}; + +use drm::Device as BasicDevice; +use nix::libc::dev_t; +use nix::sys::stat; + +use super::device::{DrmDevice, DrmDeviceInternal}; +use super::error::Error; +use crate::{ + backend::session::Signal as SessionSignal, + signaling::{Linkable, Signaler}, +}; + +/// [`SessionObserver`](SessionObserver) +/// linked to the [`DrmDevice`](DrmDevice) +/// it was created from. +pub struct DrmDeviceObserver { + dev_id: dev_t, + dev: Weak>, + privileged: bool, + active: Arc, + logger: ::slog::Logger, +} + +impl Linkable for DrmDevice { + fn link(&mut self, signaler: Signaler) { + let mut observer = DrmDeviceObserver { + dev: Arc::downgrade(&self.internal), + dev_id: self.dev_id, + active: match &*self.internal { + DrmDeviceInternal::Atomic(dev) => dev.active.clone(), + DrmDeviceInternal::Legacy(dev) => dev.active.clone(), + }, + privileged: match &*self.internal { + DrmDeviceInternal::Atomic(dev) => dev.fd.privileged, + DrmDeviceInternal::Legacy(dev) => dev.fd.privileged, + }, + logger: self.logger.new(o!("drm_module" => "observer")), + }; + + let token = signaler.register(move |signal| observer.signal(*signal)); + self.links.borrow_mut().push(token); + } +} + +impl DrmDeviceObserver { + fn signal(&mut self, signal: SessionSignal) { + match signal { + SessionSignal::PauseSession => self.pause(None), + SessionSignal::PauseDevice { major, minor } => self.pause(Some((major, minor))), + SessionSignal::ActivateSession => self.activate(None), + SessionSignal::ActivateDevice { major, minor, new_fd } => { + self.activate(Some((major, minor, new_fd))) + } + } + } + + fn pause(&mut self, devnum: Option<(u32, u32)>) { + if let Some((major, minor)) = devnum { + if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) { + return; + } + } + + self.active.store(false, Ordering::SeqCst); + if self.privileged { + if let Some(device) = self.dev.upgrade() { + if let Err(err) = device.release_master_lock() { + error!(self.logger, "Failed to drop drm master state Error: {}", err); + } + } + } + } + + fn activate(&mut self, devnum: Option<(u32, u32, Option)>) { + if let Some((major, minor, fd)) = devnum { + if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) { + return; + } else if let Some(fd) = fd { + info!(self.logger, "Replacing fd"); + if let Some(device) = self.dev.upgrade() { + ::nix::unistd::dup2(device.as_raw_fd(), fd) + .expect("Failed to replace file descriptor of drm device"); + } + } + } + if self.privileged { + if let Some(device) = self.dev.upgrade() { + if let Err(err) = device.acquire_master_lock() { + crit!(self.logger, "Failed to acquire drm master again. Error: {}", err); + } + } + } + + // okay, the previous session/whatever might left the drm devices in any state... + // lets fix that + if let Err(err) = self.reset_state() { + warn!(self.logger, "Unable to reset state after tty switch: {}", err); + // TODO call drm-handler::error + } + + self.active.store(true, Ordering::SeqCst); + } + + fn reset_state(&mut self) -> Result<(), Error> { + if let Some(dev) = self.dev.upgrade() { + dev.reset_state() + } else { + Ok(()) + } + } +} \ No newline at end of file diff --git a/src/backend/drm/atomic/surface.rs b/src/backend/drm/surface/atomic.rs similarity index 52% rename from src/backend/drm/atomic/surface.rs rename to src/backend/drm/surface/atomic.rs index 9946858..c77059c 100644 --- a/src/backend/drm/atomic/surface.rs +++ b/src/backend/drm/surface/atomic.rs @@ -1,27 +1,18 @@ -use drm::buffer::Buffer; use drm::control::atomic::AtomicModeReq; use drm::control::Device as ControlDevice; use drm::control::{ - connector, crtc, dumbbuffer::DumbBuffer, framebuffer, plane, property, AtomicCommitFlags, Mode, PlaneType, + connector, crtc, dumbbuffer::DumbBuffer, framebuffer, plane, property, AtomicCommitFlags, Mode, }; -use drm::Device as BasicDevice; use std::collections::HashSet; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::sync::{atomic::Ordering, Arc, Mutex, RwLock}; +use std::os::unix::io::AsRawFd; +use std::sync::{atomic::{AtomicBool, Ordering}, Arc, Mutex, RwLock}; -use failure::ResultExt as FailureResultExt; - -use super::Dev; -use crate::backend::drm::{common::Error, DevPath, RawSurface, Surface}; -use crate::backend::graphics::CursorBackend; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CursorState { - pub position: Option<(u32, u32)>, - pub hotspot: (u32, u32), - pub framebuffer: Option, -} +use crate::backend::drm::{ + device::{DevPath, DrmDeviceInternal}, + device::atomic::Mapping, + error::Error, +}; #[derive(Debug, PartialEq, Eq, Clone)] pub struct State { @@ -30,49 +21,38 @@ pub struct State { pub connectors: HashSet, } -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Planes { - pub primary: plane::Handle, - pub cursor: plane::Handle, +pub struct AtomicDrmSurface { + pub(super) fd: Arc>, + active: Arc, + crtc: crtc::Handle, + plane: plane::Handle, + prop_mapping: Mapping, + state: RwLock, + pending: RwLock, + test_buffer: Mutex>, + logger: ::slog::Logger, } -#[derive(Debug)] -pub(in crate::backend::drm) struct AtomicDrmSurfaceInternal { - pub(super) dev: Arc>, - pub(in crate::backend::drm) crtc: crtc::Handle, - pub(super) cursor: Mutex, - pub(in crate::backend::drm) planes: Planes, - pub(super) state: RwLock, - pub(super) pending: RwLock, - pub(super) logger: ::slog::Logger, - pub(super) test_buffer: Mutex>, -} - -impl AsRawFd for AtomicDrmSurfaceInternal { - fn as_raw_fd(&self) -> RawFd { - self.dev.as_raw_fd() - } -} - -impl BasicDevice for AtomicDrmSurfaceInternal {} -impl ControlDevice for AtomicDrmSurfaceInternal {} - -impl AtomicDrmSurfaceInternal { - pub(crate) fn new( - dev: Arc>, +impl AtomicDrmSurface { + pub fn new( + fd: Arc>, + active: Arc, crtc: crtc::Handle, + plane: plane::Handle, + prop_mapping: Mapping, mode: Mode, connectors: &[connector::Handle], logger: ::slog::Logger, ) -> Result { + let logger = logger.new(o!("smithay_module" => "backend_drm_atomic", "drm_module" => "surface")); info!( logger, - "Initializing drm surface with mode {:?} and connectors {:?}", mode, connectors + "Initializing drm surface ({:?}:{:?}) with mode {:?} and connectors {:?}", crtc, plane, mode, connectors ); - let crtc_info = dev.get_crtc(crtc).compat().map_err(|source| Error::Access { + let crtc_info = fd.get_crtc(crtc).map_err(|source| Error::Access { errmsg: "Error loading crtc info", - dev: dev.dev_path(), + dev: fd.dev_path(), source, })?; @@ -82,31 +62,28 @@ impl AtomicDrmSurfaceInternal { // So we cheat, because it works and is easier to handle later. let current_mode = crtc_info.mode().unwrap_or_else(|| unsafe { std::mem::zeroed() }); let current_blob = match crtc_info.mode() { - Some(mode) => dev + Some(mode) => fd .create_property_blob(mode) - .compat() .map_err(|source| Error::Access { errmsg: "Failed to create Property Blob for mode", - dev: dev.dev_path(), + dev: fd.dev_path(), source, })?, None => property::Value::Unknown(0), }; - let blob = dev + let blob = fd .create_property_blob(mode) - .compat() .map_err(|source| Error::Access { errmsg: "Failed to create Property Blob for mode", - dev: dev.dev_path(), + dev: fd.dev_path(), source, })?; - let res_handles = ControlDevice::resource_handles(&*dev) - .compat() + let res_handles = fd.resource_handles() .map_err(|source| Error::Access { errmsg: "Error loading drm resources", - dev: dev.dev_path(), + dev: fd.dev_path(), source, })?; @@ -116,8 +93,8 @@ impl AtomicDrmSurfaceInternal { // If they don't match, `commit_pending` will return true and they will be changed on the next `commit`. let mut current_connectors = HashSet::new(); for conn in res_handles.connectors() { - let crtc_prop = dev - .prop_mapping + let crtc_prop = + prop_mapping .0 .get(&conn) .expect("Unknown handle") @@ -127,7 +104,7 @@ impl AtomicDrmSurfaceInternal { name: "CRTC_ID", }) .map(|x| *x)?; - if let (Ok(crtc_prop_info), Ok(props)) = (dev.get_property(crtc_prop), dev.get_properties(*conn)) + if let (Ok(crtc_prop_info), Ok(props)) = (fd.get_property(crtc_prop), fd.get_properties(*conn)) { let (ids, vals) = props.as_props_and_values(); for (&id, &val) in ids.iter().zip(vals.iter()) { @@ -155,27 +132,16 @@ impl AtomicDrmSurfaceInternal { connectors: connectors.iter().copied().collect(), }; - // we need to find planes for this crtc. - // (cursor and primary planes are usually available once for every crtc, - // so this is a very naive algorithm.) - let (primary, cursor) = - AtomicDrmSurfaceInternal::find_planes(&dev, crtc).ok_or(Error::NoSuitablePlanes { - crtc, - dev: dev.dev_path(), - })?; - let surface = AtomicDrmSurfaceInternal { - dev, + let surface = AtomicDrmSurface { + fd, + active, crtc, - cursor: Mutex::new(CursorState { - position: None, - framebuffer: None, - hotspot: (0, 0), - }), - planes: Planes { primary, cursor }, + plane, + prop_mapping, state: RwLock::new(state), pending: RwLock::new(pending), - logger, test_buffer: Mutex::new(None), + logger, }; Ok(surface) @@ -185,132 +151,57 @@ impl AtomicDrmSurfaceInternal { // here we create a dumbbuffer for that purpose. fn create_test_buffer(&self, mode: &Mode) -> Result { let (w, h) = mode.size(); - let db = self - .create_dumb_buffer((w as u32, h as u32), drm::buffer::format::PixelFormat::ARGB8888) - .compat() + let db = self.fd + .create_dumb_buffer((w as u32, h as u32), crate::backend::allocator::Fourcc::Argb8888, 32) .map_err(|source| Error::Access { errmsg: "Failed to create dumb buffer", - dev: self.dev_path(), + dev: self.fd.dev_path(), source, })?; - let fb = self - .add_framebuffer(&db) - .compat() + let fb = self.fd + .add_framebuffer(&db, 32, 32) .map_err(|source| Error::Access { errmsg: "Failed to create framebuffer", - dev: self.dev_path(), + dev: self.fd.dev_path(), source, })?; let mut test_buffer = self.test_buffer.lock().unwrap(); if let Some((old_db, old_fb)) = test_buffer.take() { - let _ = self.destroy_framebuffer(old_fb); - let _ = self.destroy_dumb_buffer(old_db); + let _ = self.fd.destroy_framebuffer(old_fb); + let _ = self.fd.destroy_dumb_buffer(old_db); }; *test_buffer = Some((db, fb)); Ok(fb) } -} -impl Drop for AtomicDrmSurfaceInternal { - fn drop(&mut self) { - if let Some((db, fb)) = self.test_buffer.lock().unwrap().take() { - let _ = self.destroy_framebuffer(fb); - let _ = self.destroy_dumb_buffer(db); - } - - if !self.dev.active.load(Ordering::SeqCst) { - // the device is gone or we are on another tty - // old state has been restored, we shouldn't touch it. - // if we are on another tty the connectors will get disabled - // by the device, when switching back - return; - } - - // 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(self.planes.cursor) { - warn!( - self.logger, - "Failed to clear cursor on {:?}: {}", self.planes.cursor, err - ); - } - - // disable connectors again - let current = self.state.read().unwrap(); - let mut req = AtomicModeReq::new(); - for conn in current.connectors.iter() { - let prop = self - .dev - .prop_mapping - .0 - .get(&conn) - .expect("Unknown Handle") - .get("CRTC_ID") - .expect("Unknown property CRTC_ID"); - req.add_property(*conn, *prop, property::Value::CRTC(None)); - } - let active_prop = self - .dev - .prop_mapping - .1 - .get(&self.crtc) - .expect("Unknown Handle") - .get("ACTIVE") - .expect("Unknown property ACTIVE"); - let mode_prop = self - .dev - .prop_mapping - .1 - .get(&self.crtc) - .expect("Unknown Handle") - .get("MODE_ID") - .expect("Unknown property MODE_ID"); - - req.add_property(self.crtc, *active_prop, property::Value::Boolean(false)); - req.add_property(self.crtc, *mode_prop, property::Value::Unknown(0)); - if let Err(err) = self.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) { - warn!(self.logger, "Unable to disable connectors: {}", err); - } - } -} - -impl Surface for AtomicDrmSurfaceInternal { - type Error = Error; - type Connectors = HashSet; - - fn crtc(&self) -> crtc::Handle { - self.crtc - } - - fn current_connectors(&self) -> Self::Connectors { + pub fn current_connectors(&self) -> HashSet { self.state.read().unwrap().connectors.clone() } - fn pending_connectors(&self) -> Self::Connectors { + pub fn pending_connectors(&self) -> HashSet { self.pending.read().unwrap().connectors.clone() } - fn current_mode(&self) -> Mode { + pub fn current_mode(&self) -> Mode { self.state.read().unwrap().mode } - fn pending_mode(&self) -> Mode { + pub fn pending_mode(&self) -> Mode { self.pending.read().unwrap().mode } - fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> { - if !self.dev.active.load(Ordering::SeqCst) { + pub fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> { + if !self.active.load(Ordering::SeqCst) { return Err(Error::DeviceInactive); } - let info = self + let info = self.fd .get_connector(conn) - .compat() .map_err(|source| Error::Access { errmsg: "Error loading connector info", - dev: self.dev_path(), + dev: self.fd.dev_path(), source, })?; @@ -322,16 +213,15 @@ impl Surface for AtomicDrmSurfaceInternal { let req = self.build_request( &mut [conn].iter(), &mut [].iter(), - &self.planes, + self.plane, Some(self.create_test_buffer(&pending.mode)?), Some(pending.mode), Some(pending.blob), )?; - self.atomic_commit( + self.fd.atomic_commit( &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], req, ) - .compat() .map_err(|_| Error::TestFailed(self.crtc))?; // seems to be, lets add the connector @@ -343,8 +233,8 @@ impl Surface for AtomicDrmSurfaceInternal { } } - fn remove_connector(&self, conn: connector::Handle) -> Result<(), Error> { - if !self.dev.active.load(Ordering::SeqCst) { + pub fn remove_connector(&self, conn: connector::Handle) -> Result<(), Error> { + if !self.active.load(Ordering::SeqCst) { return Err(Error::DeviceInactive); } @@ -359,16 +249,15 @@ impl Surface for AtomicDrmSurfaceInternal { let req = self.build_request( &mut [].iter(), &mut [conn].iter(), - &self.planes, + self.plane, Some(self.create_test_buffer(&pending.mode)?), Some(pending.mode), Some(pending.blob), )?; - self.atomic_commit( + self.fd.atomic_commit( &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], req, ) - .compat() .map_err(|_| Error::TestFailed(self.crtc))?; // seems to be, lets remove the connector @@ -377,13 +266,13 @@ impl Surface for AtomicDrmSurfaceInternal { Ok(()) } - fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> { + pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> { // the test would also prevent this, but the error message is far less helpful if connectors.is_empty() { return Err(Error::SurfaceWithoutConnectors(self.crtc)); } - if !self.dev.active.load(Ordering::SeqCst) { + if !self.active.load(Ordering::SeqCst) { return Err(Error::DeviceInactive); } @@ -397,13 +286,13 @@ impl Surface for AtomicDrmSurfaceInternal { let req = self.build_request( &mut added, &mut removed, - &self.planes, + self.plane, Some(self.create_test_buffer(&pending.mode)?), Some(pending.mode), Some(pending.blob), )?; - self.atomic_commit( + self.fd.atomic_commit( &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], req, ) @@ -414,20 +303,19 @@ impl Surface for AtomicDrmSurfaceInternal { Ok(()) } - fn use_mode(&self, mode: Mode) -> Result<(), Error> { - if !self.dev.active.load(Ordering::SeqCst) { + pub fn use_mode(&self, mode: Mode) -> Result<(), Error> { + if !self.active.load(Ordering::SeqCst) { return Err(Error::DeviceInactive); } let mut pending = self.pending.write().unwrap(); // check if new config is supported - let new_blob = self + let new_blob = self.fd .create_property_blob(mode) - .compat() .map_err(|source| Error::Access { errmsg: "Failed to create Property Blob for mode", - dev: self.dev_path(), + dev: self.fd.dev_path(), source, })?; @@ -435,20 +323,19 @@ impl Surface for AtomicDrmSurfaceInternal { let req = self.build_request( &mut pending.connectors.iter(), &mut [].iter(), - &self.planes, + self.plane, test_fb, Some(mode), Some(new_blob), )?; - if let Err(err) = self + if let Err(err) = self.fd .atomic_commit( &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], req, ) - .compat() .map_err(|_| Error::TestFailed(self.crtc)) { - let _ = self.dev.destroy_property_blob(new_blob.into()); + let _ = self.fd.destroy_property_blob(new_blob.into()); return Err(err); } @@ -458,15 +345,13 @@ impl Surface for AtomicDrmSurfaceInternal { Ok(()) } -} - -impl RawSurface for AtomicDrmSurfaceInternal { - fn commit_pending(&self) -> bool { + + pub fn commit_pending(&self) -> bool { *self.pending.read().unwrap() != *self.state.read().unwrap() } - fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { - if !self.dev.active.load(Ordering::SeqCst) { + pub fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { + if !self.active.load(Ordering::SeqCst) { return Err(Error::DeviceInactive); } @@ -485,7 +370,7 @@ impl RawSurface for AtomicDrmSurfaceInternal { let mut added = pending_conns.difference(¤t_conns); for conn in removed.clone() { - if let Ok(info) = self.get_connector(*conn) { + if let Ok(info) = self.fd.get_connector(*conn) { info!(self.logger, "Removing connector: {:?}", info.interface()); } else { info!(self.logger, "Removing unknown connector"); @@ -493,7 +378,7 @@ impl RawSurface for AtomicDrmSurfaceInternal { } for conn in added.clone() { - if let Ok(info) = self.get_connector(*conn) { + if let Ok(info) = self.fd.get_connector(*conn) { info!(self.logger, "Adding connector: {:?}", info.interface()); } else { info!(self.logger, "Adding unknown connector"); @@ -511,18 +396,17 @@ impl RawSurface for AtomicDrmSurfaceInternal { let req = self.build_request( &mut added, &mut removed, - &self.planes, + self.plane, Some(framebuffer), Some(pending.mode), Some(pending.blob), )?; - if let Err(err) = self + if let Err(err) = self.fd .atomic_commit( &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], req.clone(), ) - .compat() .map_err(|_| Error::TestFailed(self.crtc)) { warn!( @@ -533,7 +417,7 @@ impl RawSurface for AtomicDrmSurfaceInternal { return Err(err); } else { if current.mode != pending.mode { - if let Err(err) = self.dev.destroy_property_blob(current.blob.into()) { + if let Err(err) = self.fd.destroy_property_blob(current.blob.into()) { warn!(self.logger, "Failed to destory old mode property blob: {}", err); } } @@ -544,7 +428,7 @@ impl RawSurface for AtomicDrmSurfaceInternal { }; debug!(self.logger, "Setting screen: {:?}", req); - let result = self + let result = self.fd .atomic_commit( &[ // on the atomic api we can modeset and trigger a page_flip event on the same call! @@ -556,10 +440,9 @@ impl RawSurface for AtomicDrmSurfaceInternal { ], req, ) - .compat() .map_err(|source| Error::Access { errmsg: "Error setting crtc", - dev: self.dev_path(), + dev: self.fd.dev_path(), source, }); @@ -570,8 +453,8 @@ impl RawSurface for AtomicDrmSurfaceInternal { result } - fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { - if !self.dev.active.load(Ordering::SeqCst) { + pub fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { + if !self.active.load(Ordering::SeqCst) { return Err(Error::DeviceInactive); } @@ -579,7 +462,7 @@ impl RawSurface for AtomicDrmSurfaceInternal { let req = self.build_request( &mut [].iter(), &mut [].iter(), - &self.planes, + self.plane, Some(framebuffer), None, None, @@ -589,82 +472,25 @@ impl RawSurface for AtomicDrmSurfaceInternal { // If we would set anything here, that would require a modeset, this would fail, // indicating a problem in our assumptions. trace!(self.logger, "Queueing page flip: {:?}", req); - self.atomic_commit( + self.fd.atomic_commit( &[AtomicCommitFlags::PageFlipEvent, AtomicCommitFlags::Nonblock], req, ) - .compat() .map_err(|source| Error::Access { errmsg: "Page flip commit failed", - dev: self.dev_path(), + dev: self.fd.dev_path(), source, })?; Ok(()) } -} -// this whole implementation just queues the cursor state for the next commit. -impl CursorBackend for AtomicDrmSurfaceInternal { - type CursorFormat = dyn Buffer; - type Error = Error; - - fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Error> { - if !self.dev.active.load(Ordering::SeqCst) { - return Err(Error::DeviceInactive); - } - - trace!(self.logger, "New cursor position ({},{}) pending", x, y); - self.cursor.lock().unwrap().position = Some((x, y)); - Ok(()) - } - - fn set_cursor_representation( - &self, - buffer: &Self::CursorFormat, - hotspot: (u32, u32), - ) -> Result<(), Error> { - if !self.dev.active.load(Ordering::SeqCst) { - return Err(Error::DeviceInactive); - } - - trace!(self.logger, "Setting the new imported cursor"); - - let mut cursor = self.cursor.lock().unwrap(); - - if let Some(fb) = cursor.framebuffer.take() { - let _ = self.destroy_framebuffer(fb); - } - - cursor.framebuffer = Some(self.add_planar_framebuffer(buffer, &[0; 4], 0).compat().map_err( - |source| Error::Access { - errmsg: "Failed to import cursor", - dev: self.dev_path(), - source, - }, - )?); - cursor.hotspot = hotspot; - - Ok(()) - } - - fn clear_cursor_representation(&self) -> Result<(), Self::Error> { - let mut cursor = self.cursor.lock().unwrap(); - if let Some(fb) = cursor.framebuffer.take() { - let _ = self.destroy_framebuffer(fb); - } - - self.clear_plane(self.planes.cursor) - } -} - -impl AtomicDrmSurfaceInternal { fn conn_prop_handle( &self, handle: connector::Handle, name: &'static str, ) -> Result { - (*self.dev) + self .prop_mapping .0 .get(&handle) @@ -678,7 +504,7 @@ impl AtomicDrmSurfaceInternal { } fn crtc_prop_handle(&self, handle: crtc::Handle, name: &'static str) -> Result { - (*self.dev) + self .prop_mapping .1 .get(&handle) @@ -697,7 +523,7 @@ impl AtomicDrmSurfaceInternal { handle: framebuffer::Handle, name: &'static str, ) -> Result { - (*self.dev) + self .prop_mapping .2 .get(&handle) @@ -715,7 +541,7 @@ impl AtomicDrmSurfaceInternal { handle: plane::Handle, name: &'static str, ) -> Result { - (*self.dev) + self .prop_mapping .3 .get(&handle) @@ -729,11 +555,11 @@ impl AtomicDrmSurfaceInternal { } // If a mode is set a matching blob needs to be set (the inverse is not true) - fn build_request( + pub fn build_request( &self, new_connectors: &mut dyn Iterator, removed_connectors: &mut dyn Iterator, - planes: &Planes, + plane: plane::Handle, framebuffer: Option, mode: Option, blob: Option>, @@ -780,317 +606,156 @@ impl AtomicDrmSurfaceInternal { // and we need to set the framebuffer for our primary plane if let Some(fb) = framebuffer { req.add_property( - planes.primary, - self.plane_prop_handle(planes.primary, "FB_ID")?, + plane, + self.plane_prop_handle(plane, "FB_ID")?, property::Value::Framebuffer(Some(fb)), ); } + + // we also need to connect the plane + req.add_property( + plane, + self.plane_prop_handle(plane, "CRTC_ID")?, + property::Value::CRTC(Some(self.crtc)), + ); - // if there is a new mode, we shoudl also make sure the plane is connected + // if there is a new mode, we should also make sure the plane is sized correctly if let Some(mode) = mode { req.add_property( - planes.primary, - self.plane_prop_handle(planes.primary, "CRTC_ID")?, - property::Value::CRTC(Some(self.crtc)), - ); - // we can take different parts of a plane ... - req.add_property( - planes.primary, - self.plane_prop_handle(planes.primary, "SRC_X")?, + plane, + self.plane_prop_handle(plane, "SRC_X")?, property::Value::UnsignedRange(0), ); req.add_property( - planes.primary, - self.plane_prop_handle(planes.primary, "SRC_Y")?, + plane, + self.plane_prop_handle(plane, "SRC_Y")?, property::Value::UnsignedRange(0), ); req.add_property( - planes.primary, - self.plane_prop_handle(planes.primary, "SRC_W")?, + plane, + self.plane_prop_handle(plane, "SRC_W")?, // these are 16.16. fixed point property::Value::UnsignedRange((mode.size().0 as u64) << 16), ); req.add_property( - planes.primary, - self.plane_prop_handle(planes.primary, "SRC_H")?, + plane, + self.plane_prop_handle(plane, "SRC_H")?, property::Value::UnsignedRange((mode.size().1 as u64) << 16), ); - // .. onto different coordinated on the crtc, but we just use a 1:1 mapping. + // we can map parts of the plane onto different coordinated on the crtc, but we just use a 1:1 mapping. req.add_property( - planes.primary, - self.plane_prop_handle(planes.primary, "CRTC_X")?, + plane, + self.plane_prop_handle(plane, "CRTC_X")?, property::Value::SignedRange(0), ); req.add_property( - planes.primary, - self.plane_prop_handle(planes.primary, "CRTC_Y")?, + plane, + self.plane_prop_handle(plane, "CRTC_Y")?, property::Value::SignedRange(0), ); req.add_property( - planes.primary, - self.plane_prop_handle(planes.primary, "CRTC_W")?, + plane, + self.plane_prop_handle(plane, "CRTC_W")?, property::Value::UnsignedRange(mode.size().0 as u64), ); req.add_property( - planes.primary, - self.plane_prop_handle(planes.primary, "CRTC_H")?, + plane, + self.plane_prop_handle(plane, "CRTC_H")?, property::Value::UnsignedRange(mode.size().1 as u64), ); } - // if there is a cursor, we add the cursor plane to the request as well. - // this synchronizes cursor movement with rendering, which reduces flickering. - let mut cursor = self.cursor.lock().unwrap(); - if let (Some(pos), Some(fb)) = (cursor.position, cursor.framebuffer) { - match self.get_framebuffer(fb).compat().map_err(|source| Error::Access { - errmsg: "Error getting cursor fb", - dev: self.dev_path(), - source, - }) { - Ok(cursor_info) => { - let hotspot = cursor.hotspot; - - // again like the primary plane we need to set crtc and framebuffer. - req.add_property( - planes.cursor, - self.plane_prop_handle(planes.cursor, "CRTC_ID")?, - property::Value::CRTC(Some(self.crtc)), - ); - // copy the whole plane - req.add_property( - planes.cursor, - self.plane_prop_handle(planes.cursor, "SRC_X")?, - property::Value::UnsignedRange(0), - ); - req.add_property( - planes.cursor, - self.plane_prop_handle(planes.cursor, "SRC_Y")?, - property::Value::UnsignedRange(0), - ); - req.add_property( - planes.cursor, - self.plane_prop_handle(planes.cursor, "SRC_W")?, - property::Value::UnsignedRange((cursor_info.size().0 as u64) << 16), - ); - req.add_property( - planes.cursor, - self.plane_prop_handle(planes.cursor, "SRC_H")?, - property::Value::UnsignedRange((cursor_info.size().1 as u64) << 16), - ); - // but this time add this at some very specific coordinates of the crtc - req.add_property( - planes.cursor, - self.plane_prop_handle(planes.cursor, "CRTC_X")?, - property::Value::SignedRange(pos.0 as i64 - (hotspot.0 as i64)), - ); - req.add_property( - planes.cursor, - self.plane_prop_handle(planes.cursor, "CRTC_Y")?, - property::Value::SignedRange(pos.1 as i64 - (hotspot.1 as i64)), - ); - req.add_property( - planes.cursor, - self.plane_prop_handle(planes.cursor, "CRTC_W")?, - property::Value::UnsignedRange(cursor_info.size().0 as u64), - ); - req.add_property( - planes.cursor, - self.plane_prop_handle(planes.cursor, "CRTC_H")?, - property::Value::UnsignedRange(cursor_info.size().1 as u64), - ); - req.add_property( - planes.cursor, - self.plane_prop_handle(planes.cursor, "FB_ID")?, - property::Value::Framebuffer(Some(fb)), - ); - } - Err(err) => { - warn!(self.logger, "Cursor FB invalid: {}. Skipping.", err); - cursor.framebuffer = None; - } - } - } - Ok(req) } - // primary and cursor planes are almost always unique to a crtc. - // otherwise we would be in trouble and would need to figure this out - // on the device level to find the best plane combination. - fn find_planes(card: &Dev, crtc: crtc::Handle) -> Option<(plane::Handle, plane::Handle)> { - let res = card.resource_handles().expect("Could not list resources"); - let planes = card.plane_handles().expect("Could not list planes"); - let vec: Vec<(PlaneType, plane::Handle)> = planes - .planes() - .iter() - .copied() - .filter(|plane| { - card.get_plane(*plane) - .map(|plane_info| { - let compatible_crtcs = res.filter_crtcs(plane_info.possible_crtcs()); - compatible_crtcs.contains(&crtc) - }) - .unwrap_or(false) - }) - .filter_map(|plane| { - if let Ok(props) = card.get_properties(plane) { - let (ids, vals) = props.as_props_and_values(); - for (&id, &val) in ids.iter().zip(vals.iter()) { - if let Ok(info) = card.get_property(id) { - if info.name().to_str().map(|x| x == "type").unwrap_or(false) { - if val == (PlaneType::Primary as u32).into() { - return Some((PlaneType::Primary, plane)); - } - if val == (PlaneType::Cursor as u32).into() { - return Some((PlaneType::Cursor, plane)); - } - } - } - } - } - None - }) - .collect(); - - Some(( - vec.iter().find_map(|(plane_type, plane)| { - if *plane_type == PlaneType::Primary { - Some(*plane) - } else { - None - } - })?, - vec.iter().find_map(|(plane_type, plane)| { - if *plane_type == PlaneType::Cursor { - Some(*plane) - } else { - None - } - })?, - )) - } - - // this helper function clears the contents of a single plane. - // this is mostly used to remove the cursor, e.g. on tty switch, + // this helper function disconnects the plane. + // 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 cursor as a relict of a better time on the screen. - pub(super) fn clear_plane(&self, plane: plane::Handle) -> Result<(), Error> { + // leaving our e.g. cursor or overlays as a relict of a better time on the screen. + pub fn clear_plane(&self) -> Result<(), Error> { let mut req = AtomicModeReq::new(); req.add_property( - plane, - self.plane_prop_handle(plane, "CRTC_ID")?, + self.plane, + self.plane_prop_handle(self.plane, "CRTC_ID")?, property::Value::CRTC(None), ); req.add_property( - plane, - self.plane_prop_handle(plane, "FB_ID")?, + self.plane, + self.plane_prop_handle(self.plane, "FB_ID")?, property::Value::Framebuffer(None), ); - self.atomic_commit(&[AtomicCommitFlags::TestOnly], req.clone()) - .compat() + self.fd.atomic_commit(&[AtomicCommitFlags::TestOnly], req.clone()) .map_err(|_| Error::TestFailed(self.crtc))?; - self.atomic_commit(&[AtomicCommitFlags::Nonblock], req) - .compat() + self.fd.atomic_commit(&[AtomicCommitFlags::Nonblock], req) .map_err(|source| Error::Access { errmsg: "Failed to commit on clear_plane", - dev: self.dev_path(), + dev: self.fd.dev_path(), source, }) } } -/// Open raw crtc utilizing atomic mode-setting -#[derive(Debug)] -pub struct AtomicDrmSurface( - pub(in crate::backend::drm) Arc>, -); +impl Drop for AtomicDrmSurface { + fn drop(&mut self) { + if let Some((db, fb)) = self.test_buffer.lock().unwrap().take() { + let _ = self.fd.destroy_framebuffer(fb); + let _ = self.fd.destroy_dumb_buffer(db); + } -impl AsRawFd for AtomicDrmSurface { - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } -} + if !self.active.load(Ordering::SeqCst) { + // the device is gone or we are on another tty + // old state has been restored, we shouldn't touch it. + // if we are on another tty the connectors will get disabled + // by the device, when switching back + return; + } -impl BasicDevice for AtomicDrmSurface {} -impl ControlDevice 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 + ); + } -impl CursorBackend for AtomicDrmSurface { - type CursorFormat = dyn Buffer; - type Error = Error; + // disable connectors again + let current = self.state.read().unwrap(); + let mut req = AtomicModeReq::new(); + for conn in current.connectors.iter() { + let prop = self + .prop_mapping + .0 + .get(&conn) + .expect("Unknown Handle") + .get("CRTC_ID") + .expect("Unknown property CRTC_ID"); + req.add_property(*conn, *prop, property::Value::CRTC(None)); + } + let active_prop = self + .prop_mapping + .1 + .get(&self.crtc) + .expect("Unknown Handle") + .get("ACTIVE") + .expect("Unknown property ACTIVE"); + let mode_prop = self + .prop_mapping + .1 + .get(&self.crtc) + .expect("Unknown Handle") + .get("MODE_ID") + .expect("Unknown property MODE_ID"); - fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Error> { - self.0.set_cursor_position(x, y) - } - - fn set_cursor_representation( - &self, - buffer: &Self::CursorFormat, - hotspot: (u32, u32), - ) -> Result<(), Error> { - self.0.set_cursor_representation(buffer, hotspot) - } - - fn clear_cursor_representation(&self) -> Result<(), Self::Error> { - self.0.clear_cursor_representation() - } -} - -impl Surface for AtomicDrmSurface { - type Error = Error; - type Connectors = HashSet; - - fn crtc(&self) -> crtc::Handle { - self.0.crtc() - } - - fn current_connectors(&self) -> Self::Connectors { - self.0.current_connectors() - } - - fn pending_connectors(&self) -> Self::Connectors { - self.0.pending_connectors() - } - - fn current_mode(&self) -> Mode { - self.0.current_mode() - } - - fn pending_mode(&self) -> Mode { - self.0.pending_mode() - } - - fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> { - self.0.add_connector(connector) - } - - fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> { - self.0.remove_connector(connector) - } - - fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> { - self.0.set_connectors(connectors) - } - - fn use_mode(&self, mode: Mode) -> Result<(), Error> { - self.0.use_mode(mode) - } -} - -impl RawSurface for AtomicDrmSurface { - fn commit_pending(&self) -> bool { - self.0.commit_pending() - } - - fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { - self.0.commit(framebuffer) - } - - fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { - RawSurface::page_flip(&*self.0, framebuffer) + req.add_property(self.crtc, *active_prop, property::Value::Boolean(false)); + req.add_property(self.crtc, *mode_prop, property::Value::Unknown(0)); + if let Err(err) = self.fd.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) { + warn!(self.logger, "Unable to disable connectors: {}", err); + } } } diff --git a/src/backend/drm/legacy/surface.rs b/src/backend/drm/surface/legacy.rs similarity index 52% rename from src/backend/drm/legacy/surface.rs rename to src/backend/drm/surface/legacy.rs index 04cbe49..7aaa59b 100644 --- a/src/backend/drm/legacy/surface.rs +++ b/src/backend/drm/surface/legacy.rs @@ -1,20 +1,17 @@ -use drm::buffer::Buffer; use drm::control::{ - connector, crtc, dumbbuffer::DumbBuffer, encoder, framebuffer, Device as ControlDevice, Mode, + connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, PageFlipFlags, }; -use drm::Device as BasicDevice; use std::collections::HashSet; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::sync::{atomic::Ordering, Arc, RwLock}; +use std::os::unix::io::AsRawFd; +use std::sync::{atomic::{AtomicBool, Ordering}, Arc, RwLock}; -use crate::backend::drm::{common::Error, DevPath, RawSurface, Surface}; -use crate::backend::graphics::CursorBackend; - -use super::Dev; - -use failure::{Fail, ResultExt}; +use crate::backend::drm::{ + device::{DevPath, DrmDeviceInternal}, + device::legacy::set_connector_state, + error::Error, +}; #[derive(Debug, PartialEq, Eq, Clone)] pub struct State { @@ -22,313 +19,25 @@ pub struct State { pub connectors: HashSet, } -pub(in crate::backend::drm) struct LegacyDrmSurfaceInternal { - pub(super) dev: Arc>, - pub(in crate::backend::drm) crtc: crtc::Handle, - pub(super) state: RwLock, - pub(super) pending: RwLock, - pub(super) logger: ::slog::Logger, +pub struct LegacyDrmSurface { + pub(super) fd: Arc>, + active: Arc, + crtc: crtc::Handle, + state: RwLock, + pending: RwLock, + logger: ::slog::Logger, } -impl AsRawFd for LegacyDrmSurfaceInternal { - fn as_raw_fd(&self) -> RawFd { - self.dev.as_raw_fd() - } -} - -impl BasicDevice for LegacyDrmSurfaceInternal {} -impl ControlDevice for LegacyDrmSurfaceInternal {} - -impl CursorBackend for LegacyDrmSurfaceInternal { - type CursorFormat = dyn Buffer; - type Error = Error; - - fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Error> { - if !self.dev.active.load(Ordering::SeqCst) { - return Err(Error::DeviceInactive); - } - - trace!(self.logger, "Move the cursor to {},{}", x, y); - self.move_cursor(self.crtc, (x as i32, y as i32)) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error moving cursor", - dev: self.dev_path(), - source, - }) - } - - fn set_cursor_representation( - &self, - buffer: &Self::CursorFormat, - hotspot: (u32, u32), - ) -> Result<(), Error> { - if !self.dev.active.load(Ordering::SeqCst) { - return Err(Error::DeviceInactive); - } - - trace!(self.logger, "Setting the new imported cursor"); - - // set_cursor2 allows us to set the hotspot, but is not supported by every implementation. - if self - .set_cursor2(self.crtc, Some(buffer), (hotspot.0 as i32, hotspot.1 as i32)) - .is_err() - { - // the cursor will be slightly misplaced, when using the function for hotspots other then (0, 0), - // but that is still better then no cursor. - self.set_cursor(self.crtc, Some(buffer)) - .compat() - .map_err(|source| Error::Access { - errmsg: "Failed to set cursor", - dev: self.dev_path(), - source, - })?; - } - - Ok(()) - } - - fn clear_cursor_representation(&self) -> Result<(), Error> { - self.set_cursor(self.crtc, Option::<&DumbBuffer>::None) - .compat() - .map_err(|source| Error::Access { - errmsg: "Failed to set cursor", - dev: self.dev_path(), - source, - }) - } -} - -impl Surface for LegacyDrmSurfaceInternal { - type Error = Error; - type Connectors = HashSet; - - fn crtc(&self) -> crtc::Handle { - self.crtc - } - - fn current_connectors(&self) -> Self::Connectors { - self.state.read().unwrap().connectors.clone() - } - - fn pending_connectors(&self) -> Self::Connectors { - self.pending.read().unwrap().connectors.clone() - } - - fn current_mode(&self) -> Mode { - self.state.read().unwrap().mode - } - - fn pending_mode(&self) -> Mode { - self.pending.read().unwrap().mode - } - - fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> { - if !self.dev.active.load(Ordering::SeqCst) { - return Err(Error::DeviceInactive); - } - - let mut pending = self.pending.write().unwrap(); - - if self.check_connector(conn, &pending.mode)? { - pending.connectors.insert(conn); - } - - Ok(()) - } - - fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> { - let mut pending = self.pending.write().unwrap(); - - if pending.connectors.contains(&connector) && pending.connectors.len() == 1 { - return Err(Error::SurfaceWithoutConnectors(self.crtc)); - } - - pending.connectors.remove(&connector); - Ok(()) - } - - fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { - if connectors.is_empty() { - return Err(Error::SurfaceWithoutConnectors(self.crtc)); - } - - if !self.dev.active.load(Ordering::SeqCst) { - return Err(Error::DeviceInactive); - } - - let mut pending = self.pending.write().unwrap(); - - if connectors - .iter() - .map(|conn| self.check_connector(*conn, &pending.mode)) - .collect::, _>>()? - .iter() - .all(|v| *v) - { - pending.connectors = connectors.iter().cloned().collect(); - } - - Ok(()) - } - - fn use_mode(&self, mode: Mode) -> Result<(), Error> { - if !self.dev.active.load(Ordering::SeqCst) { - return Err(Error::DeviceInactive); - } - - let mut pending = self.pending.write().unwrap(); - - // check the connectors to see if this mode is supported - for connector in &pending.connectors { - if !self - .get_connector(*connector) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error loading connector info", - dev: self.dev_path(), - source, - })? - .modes() - .contains(&mode) - { - return Err(Error::ModeNotSuitable(mode)); - } - } - - pending.mode = mode; - - Ok(()) - } -} - -impl RawSurface for LegacyDrmSurfaceInternal { - fn commit_pending(&self) -> bool { - *self.pending.read().unwrap() != *self.state.read().unwrap() - } - - fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { - if !self.dev.active.load(Ordering::SeqCst) { - return Err(Error::DeviceInactive); - } - - let mut current = self.state.write().unwrap(); - let pending = self.pending.read().unwrap(); - - { - let removed = current.connectors.difference(&pending.connectors); - let added = pending.connectors.difference(¤t.connectors); - - let mut conn_removed = false; - for conn in removed.clone() { - if let Ok(info) = self.get_connector(*conn) { - info!(self.logger, "Removing connector: {:?}", info.interface()); - } else { - info!(self.logger, "Removing unknown connector"); - } - // if the connector was mapped to our crtc, we need to ack the disconnect. - // the graphics pipeline will not be freed otherwise - conn_removed = true; - } - self.dev.set_connector_state(removed.copied(), false)?; - - if conn_removed { - // null commit (necessary to trigger removal on the kernel side with the legacy api.) - self.set_crtc(self.crtc, None, (0, 0), &[], None) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error setting crtc", - dev: self.dev_path(), - source, - })?; - } - - for conn in added.clone() { - if let Ok(info) = self.get_connector(*conn) { - info!(self.logger, "Adding connector: {:?}", info.interface()); - } else { - info!(self.logger, "Adding unknown connector"); - } - } - self.dev.set_connector_state(added.copied(), true)?; - - if current.mode != pending.mode { - info!(self.logger, "Setting new mode: {:?}", pending.mode.name()); - } - } - - debug!(self.logger, "Setting screen"); - // do a modeset and attach the given framebuffer - self.set_crtc( - self.crtc, - Some(framebuffer), - (0, 0), - &pending - .connectors - .iter() - .copied() - .collect::>(), - Some(pending.mode), - ) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error setting crtc", - dev: self.dev_path(), - source, - })?; - - *current = pending.clone(); - - // set crtc does not trigger page_flip events, so we immediately queue a flip - // with the same framebuffer. - // this will result in wasting a frame, because this flip will need to wait - // for `set_crtc`, but is necessary to drive the event loop and thus provide - // a more consistent api. - ControlDevice::page_flip( - self, - self.crtc, - framebuffer, - &[PageFlipFlags::PageFlipEvent], - None, - ) - .map_err(|source| Error::Access { - errmsg: "Failed to queue page flip", - dev: self.dev_path(), - source: source.compat(), - }) - } - - fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { - trace!(self.logger, "Queueing Page flip"); - - if !self.dev.active.load(Ordering::SeqCst) { - return Err(Error::DeviceInactive); - } - - ControlDevice::page_flip( - self, - self.crtc, - framebuffer, - &[PageFlipFlags::PageFlipEvent], - None, - ) - .compat() - .map_err(|source| Error::Access { - errmsg: "Failed to page flip", - dev: self.dev_path(), - source, - }) - } -} - -impl LegacyDrmSurfaceInternal { - pub(crate) fn new( - dev: Arc>, +impl LegacyDrmSurface { + pub fn new( + fd: Arc>, + active: Arc, crtc: crtc::Handle, mode: Mode, connectors: &[connector::Handle], logger: ::slog::Logger, - ) -> Result, Error> { + ) -> Result { + let logger = logger.new(o!("smithay_module" => "backend_drm_legacy", "drm_module" => "surface")); info!( logger, "Initializing drm surface with mode {:?} and connectors {:?}", mode, connectors @@ -336,32 +45,31 @@ impl LegacyDrmSurfaceInternal { // Try to enumarate the current state to set the initial state variable correctly. // We need an accurate state to handle `commit_pending`. - let crtc_info = dev.get_crtc(crtc).compat().map_err(|source| Error::Access { + let crtc_info = fd.get_crtc(crtc).map_err(|source| Error::Access { errmsg: "Error loading crtc info", - dev: dev.dev_path(), + dev: fd.dev_path(), source, })?; let current_mode = crtc_info.mode(); let mut current_connectors = HashSet::new(); - let res_handles = ControlDevice::resource_handles(&*dev) - .compat() + let res_handles = fd.resource_handles() .map_err(|source| Error::Access { errmsg: "Error loading drm resources", - dev: dev.dev_path(), + dev: fd.dev_path(), source, })?; for &con in res_handles.connectors() { - let con_info = dev.get_connector(con).compat().map_err(|source| Error::Access { + let con_info = fd.get_connector(con).map_err(|source| Error::Access { errmsg: "Error loading connector info", - dev: dev.dev_path(), + dev: fd.dev_path(), source, })?; if let Some(enc) = con_info.current_encoder() { - let enc_info = dev.get_encoder(enc).compat().map_err(|source| Error::Access { + let enc_info = fd.get_encoder(enc).map_err(|source| Error::Access { errmsg: "Error loading encoder info", - dev: dev.dev_path(), + dev: fd.dev_path(), source, })?; if let Some(current_crtc) = enc_info.crtc() { @@ -385,8 +93,9 @@ impl LegacyDrmSurfaceInternal { connectors: connectors.iter().copied().collect(), }; - let surface = LegacyDrmSurfaceInternal { - dev, + let surface = LegacyDrmSurface { + fd, + active, crtc, state: RwLock::new(state), pending: RwLock::new(pending), @@ -396,6 +105,213 @@ impl LegacyDrmSurfaceInternal { Ok(surface) } + pub fn current_connectors(&self) -> HashSet { + self.state.read().unwrap().connectors.clone() + } + + pub fn pending_connectors(&self) -> HashSet { + self.pending.read().unwrap().connectors.clone() + } + + pub fn current_mode(&self) -> Mode { + self.state.read().unwrap().mode + } + + pub fn pending_mode(&self) -> Mode { + self.pending.read().unwrap().mode + } + + pub fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> { + if !self.active.load(Ordering::SeqCst) { + return Err(Error::DeviceInactive); + } + + let mut pending = self.pending.write().unwrap(); + + if self.check_connector(conn, &pending.mode)? { + pending.connectors.insert(conn); + } + + Ok(()) + } + + pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> { + let mut pending = self.pending.write().unwrap(); + + if pending.connectors.contains(&connector) && pending.connectors.len() == 1 { + return Err(Error::SurfaceWithoutConnectors(self.crtc)); + } + + pending.connectors.remove(&connector); + Ok(()) + } + + pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> { + if connectors.is_empty() { + return Err(Error::SurfaceWithoutConnectors(self.crtc)); + } + + if !self.active.load(Ordering::SeqCst) { + return Err(Error::DeviceInactive); + } + + let mut pending = self.pending.write().unwrap(); + + if connectors + .iter() + .map(|conn| self.check_connector(*conn, &pending.mode)) + .collect::, _>>()? + .iter() + .all(|v| *v) + { + pending.connectors = connectors.iter().cloned().collect(); + } + + Ok(()) + } + + pub fn use_mode(&self, mode: Mode) -> Result<(), Error> { + if !self.active.load(Ordering::SeqCst) { + return Err(Error::DeviceInactive); + } + + let mut pending = self.pending.write().unwrap(); + + // check the connectors to see if this mode is supported + for connector in &pending.connectors { + if !self.fd + .get_connector(*connector) + .map_err(|source| Error::Access { + errmsg: "Error loading connector info", + dev: self.fd.dev_path(), + source, + })? + .modes() + .contains(&mode) + { + return Err(Error::ModeNotSuitable(mode)); + } + } + + pending.mode = mode; + + Ok(()) + } + + pub fn commit_pending(&self) -> bool { + *self.pending.read().unwrap() != *self.state.read().unwrap() + } + + pub fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { + if !self.active.load(Ordering::SeqCst) { + return Err(Error::DeviceInactive); + } + + let mut current = self.state.write().unwrap(); + let pending = self.pending.read().unwrap(); + + { + let removed = current.connectors.difference(&pending.connectors); + let added = pending.connectors.difference(¤t.connectors); + + let mut conn_removed = false; + for conn in removed.clone() { + if let Ok(info) = self.fd.get_connector(*conn) { + info!(self.logger, "Removing connector: {:?}", info.interface()); + } else { + info!(self.logger, "Removing unknown connector"); + } + // if the connector was mapped to our crtc, we need to ack the disconnect. + // the graphics pipeline will not be freed otherwise + conn_removed = true; + } + set_connector_state(&*self.fd, removed.copied(), false)?; + + if conn_removed { + // null commit (necessary to trigger removal on the kernel side with the legacy api.) + self.fd.set_crtc(self.crtc, None, (0, 0), &[], None) + .map_err(|source| Error::Access { + errmsg: "Error setting crtc", + dev: self.fd.dev_path(), + source, + })?; + } + + for conn in added.clone() { + if let Ok(info) = self.fd.get_connector(*conn) { + info!(self.logger, "Adding connector: {:?}", info.interface()); + } else { + info!(self.logger, "Adding unknown connector"); + } + } + set_connector_state(&*self.fd, added.copied(), true)?; + + if current.mode != pending.mode { + info!(self.logger, "Setting new mode: {:?}", pending.mode.name()); + } + } + + debug!(self.logger, "Setting screen"); + // do a modeset and attach the given framebuffer + self.fd.set_crtc( + self.crtc, + Some(framebuffer), + (0, 0), + &pending + .connectors + .iter() + .copied() + .collect::>(), + Some(pending.mode), + ) + .map_err(|source| Error::Access { + errmsg: "Error setting crtc", + dev: self.fd.dev_path(), + source, + })?; + + *current = pending.clone(); + + // set crtc does not trigger page_flip events, so we immediately queue a flip + // with the same framebuffer. + // this will result in wasting a frame, because this flip will need to wait + // for `set_crtc`, but is necessary to drive the event loop and thus provide + // a more consistent api. + ControlDevice::page_flip( + &*self.fd, + self.crtc, + framebuffer, + &[PageFlipFlags::PageFlipEvent], + None, + ) + .map_err(|source| Error::Access { + errmsg: "Failed to queue page flip", + dev: self.fd.dev_path(), + source, + }) + } + + pub fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { + trace!(self.logger, "Queueing Page flip"); + + if !self.active.load(Ordering::SeqCst) { + return Err(Error::DeviceInactive); + } + + ControlDevice::page_flip( + &*self.fd, + self.crtc, + framebuffer, + &[PageFlipFlags::PageFlipEvent], + None, + ) + .map_err(|source| Error::Access { + errmsg: "Failed to page flip", + dev: self.fd.dev_path(), + source, + }) + } + // we use this function to verify, if a certain connector/mode combination // is valid on our crtc. We do this with the most basic information we have: // - is there a matching encoder @@ -404,12 +320,11 @@ impl LegacyDrmSurfaceInternal { // Better would be some kind of test commit to ask the driver, // but that only exists for the atomic api. fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result { - let info = self + let info = self.fd .get_connector(conn) - .compat() .map_err(|source| Error::Access { errmsg: "Error loading connector info", - dev: self.dev_path(), + dev: self.fd.dev_path(), source, })?; @@ -422,20 +337,19 @@ impl LegacyDrmSurfaceInternal { .filter(|enc| enc.is_some()) .map(|enc| enc.unwrap()) .map(|encoder| { - self.get_encoder(encoder) - .compat() + self.fd.get_encoder(encoder) .map_err(|source| Error::Access { errmsg: "Error loading encoder info", - dev: self.dev_path(), + dev: self.fd.dev_path(), source, }) }) .collect::, _>>()?; // and if any encoder supports the selected crtc - let resource_handles = self.resource_handles().compat().map_err(|source| Error::Access { + let resource_handles = self.fd.resource_handles().map_err(|source| Error::Access { errmsg: "Error loading resources", - dev: self.dev_path(), + dev: self.fd.dev_path(), source, })?; if !encoders @@ -453,11 +367,11 @@ impl LegacyDrmSurfaceInternal { } } -impl Drop for LegacyDrmSurfaceInternal { +impl Drop for LegacyDrmSurface { fn drop(&mut self) { // ignore failure at this point - if !self.dev.active.load(Ordering::SeqCst) { + if !self.active.load(Ordering::SeqCst) { // the device is gone or we are on another tty // old state has been restored, we shouldn't touch it. // if we are on another tty the connectors will get disabled @@ -465,110 +379,17 @@ impl Drop for LegacyDrmSurfaceInternal { return; } - let _ = self.set_cursor(self.crtc, Option::<&DumbBuffer>::None); // disable connectors again let current = self.state.read().unwrap(); - if self - .dev - .set_connector_state(current.connectors.iter().copied(), false) - .is_ok() + if set_connector_state(&*self.fd, current.connectors.iter().copied(), false) + .is_ok() { // null commit - let _ = self.set_crtc(self.crtc, None, (0, 0), &[], None); + let _ = self.fd.set_crtc(self.crtc, None, (0, 0), &[], None); } } } -/// Open raw crtc utilizing legacy mode-setting -pub struct LegacyDrmSurface( - pub(in crate::backend::drm) Arc>, -); - -impl AsRawFd for LegacyDrmSurface { - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } -} - -impl BasicDevice for LegacyDrmSurface {} -impl ControlDevice for LegacyDrmSurface {} - -impl CursorBackend for LegacyDrmSurface { - type CursorFormat = dyn Buffer; - type Error = Error; - - fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Error> { - self.0.set_cursor_position(x, y) - } - - fn set_cursor_representation( - &self, - buffer: &Self::CursorFormat, - hotspot: (u32, u32), - ) -> Result<(), Error> { - self.0.set_cursor_representation(buffer, hotspot) - } - - fn clear_cursor_representation(&self) -> Result<(), Self::Error> { - self.0.clear_cursor_representation() - } -} - -impl Surface for LegacyDrmSurface { - type Error = Error; - type Connectors = HashSet; - - fn crtc(&self) -> crtc::Handle { - self.0.crtc() - } - - fn current_connectors(&self) -> Self::Connectors { - self.0.current_connectors() - } - - fn pending_connectors(&self) -> Self::Connectors { - self.0.pending_connectors() - } - - fn current_mode(&self) -> Mode { - self.0.current_mode() - } - - fn pending_mode(&self) -> Mode { - self.0.pending_mode() - } - - fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> { - self.0.add_connector(connector) - } - - fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> { - self.0.remove_connector(connector) - } - - fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { - self.0.set_connectors(connectors) - } - - fn use_mode(&self, mode: Mode) -> Result<(), Error> { - self.0.use_mode(mode) - } -} - -impl RawSurface for LegacyDrmSurface { - fn commit_pending(&self) -> bool { - self.0.commit_pending() - } - - fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { - self.0.commit(framebuffer) - } - - fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { - RawSurface::page_flip(&*self.0, framebuffer) - } -} - #[cfg(test)] mod test { use super::LegacyDrmSurface; diff --git a/src/backend/drm/surface/mod.rs b/src/backend/drm/surface/mod.rs new file mode 100644 index 0000000..36ad49b --- /dev/null +++ b/src/backend/drm/surface/mod.rs @@ -0,0 +1,180 @@ +use std::os::unix::io::{AsRawFd, RawFd}; +use std::sync::Arc; + +use drm::Device as BasicDevice; +use drm::control::{Device as ControlDevice, Mode, crtc, connector, framebuffer, plane}; + +pub(super) mod atomic; +pub(super) mod legacy; +use super::error::Error; +use atomic::AtomicDrmSurface; +use legacy::LegacyDrmSurface; + +pub struct DrmSurface +{ + pub(super) crtc: crtc::Handle, + pub(super) plane: plane::Handle, + pub(super) internal: Arc>, +} + +pub enum DrmSurfaceInternal { + Atomic(AtomicDrmSurface), + Legacy(LegacyDrmSurface), +} + +impl AsRawFd for DrmSurface { + fn as_raw_fd(&self) -> RawFd { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.fd.as_raw_fd(), + DrmSurfaceInternal::Legacy(surf) => surf.fd.as_raw_fd(), + } + } +} +impl BasicDevice for DrmSurface {} +impl ControlDevice for DrmSurface {} + +impl DrmSurface { + /// Returns the underlying [`crtc`](drm::control::crtc) of this surface + pub fn crtc(&self) -> crtc::Handle { + self.crtc + } + + /// Returns the underlying [`plane`](drm::control::plane) of this surface + pub fn plane(&self) -> plane::Handle { + self.plane + } + + /// Currently used [`connector`](drm::control::connector)s of this `Surface` + pub fn current_connectors(&self) -> impl IntoIterator { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.current_connectors(), + DrmSurfaceInternal::Legacy(surf) => surf.current_connectors(), + } + } + + /// Returns the pending [`connector`](drm::control::connector)s + /// used after the next [`commit`](Surface::commit) of this [`Surface`] + pub fn pending_connectors(&self) -> impl IntoIterator { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.pending_connectors(), + DrmSurfaceInternal::Legacy(surf) => surf.pending_connectors(), + } + } + + /// Tries to add a new [`connector`](drm::control::connector) + /// to be used after the next commit. + /// + /// **Warning**: You need to make sure, that the connector is not used with another surface + /// or was properly removed via `remove_connector` + `commit` before adding it to another surface. + /// Behavior if failing to do so is undefined, but might result in rendering errors or the connector + /// getting removed from the other surface without updating it's internal state. + /// + /// Fails if the `connector` is not compatible with the underlying [`crtc`](drm::control::crtc) + /// (e.g. no suitable [`encoder`](drm::control::encoder) may be found) + /// or is not compatible with the currently pending + /// [`Mode`](drm::control::Mode). + pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.add_connector(connector), + DrmSurfaceInternal::Legacy(surf) => surf.add_connector(connector), + } + } + + /// Tries to mark a [`connector`](drm::control::connector) + /// for removal on the next commit. + pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.remove_connector(connector), + DrmSurfaceInternal::Legacy(surf) => surf.remove_connector(connector), + } + } + + /// Tries to replace the current connector set with the newly provided one on the next commit. + /// + /// Fails if one new `connector` is not compatible with the underlying [`crtc`](drm::control::crtc) + /// (e.g. no suitable [`encoder`](drm::control::encoder) may be found) + /// or is not compatible with the currently pending + /// [`Mode`](drm::control::Mode). + pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.set_connectors(connectors), + DrmSurfaceInternal::Legacy(surf) => surf.set_connectors(connectors), + } + } + + /// Returns the currently active [`Mode`](drm::control::Mode) + /// of the underlying [`crtc`](drm::control::crtc) + pub fn current_mode(&self) -> Mode { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.current_mode(), + DrmSurfaceInternal::Legacy(surf) => surf.current_mode(), + } + } + + /// Returns the currently pending [`Mode`](drm::control::Mode) + /// to be used after the next commit. + pub fn pending_mode(&self) -> Mode { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.pending_mode(), + DrmSurfaceInternal::Legacy(surf) => surf.pending_mode(), + } + } + + /// Tries to set a new [`Mode`](drm::control::Mode) + /// to be used after the next commit. + /// + /// Fails if the mode is not compatible with the underlying + /// [`crtc`](drm::control::crtc) or any of the + /// pending [`connector`](drm::control::connector)s. + pub fn use_mode(&self, mode: Mode) -> Result<(), Error> { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.use_mode(mode), + DrmSurfaceInternal::Legacy(surf) => surf.use_mode(mode), + } + } + + /// Returns true whenever any state changes are pending to be commited + /// + /// The following functions may trigger a pending commit: + /// - [`add_connector`](Surface::add_connector) + /// - [`remove_connector`](Surface::remove_connector) + /// - [`use_mode`](Surface::use_mode) + pub fn commit_pending(&self) -> bool { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.commit_pending(), + DrmSurfaceInternal::Legacy(surf) => surf.commit_pending(), + } + } + + /// Commit the pending state rendering a given framebuffer. + /// + /// *Note*: This will trigger a full modeset on the underlying device, + /// potentially causing some flickering. Check before performing this + /// operation if a commit really is necessary using [`commit_pending`](RawSurface::commit_pending). + /// + /// This operation is not necessarily blocking until the crtc is in the desired state, + /// 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) -> Result<(), Error> { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.commit(framebuffer), + DrmSurfaceInternal::Legacy(surf) => surf.commit(framebuffer), + } + } + + /// Page-flip the underlying [`crtc`](drm::control::crtc) + /// to a new given [`framebuffer`]. + /// + /// This will not cause the crtc to modeset. + /// + /// 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) -> Result<(), Error> { + match &*self.internal { + DrmSurfaceInternal::Atomic(surf) => surf.page_flip(framebuffer), + DrmSurfaceInternal::Legacy(surf) => surf.page_flip(framebuffer) + } + } +} \ No newline at end of file