From bd5690bd77dbbc47de71cdb6b4d77f6fec0dd174 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 21 Nov 2018 09:44:48 +0100 Subject: [PATCH] Refactor drm backend - Split `DrmDevice` into `LegacyDrmDevice`, `GbmDevice` and `EglDevice` - Provide common `Device` and `RawDevice` traits - Change `DrmBackend` into `Surface` (and `RawSurface`) implementations of each `Device` (or `RawDevice`) --- Cargo.toml | 9 +- src/backend/drm/backend.rs | 537 ------------------ src/backend/drm/egl/error.rs | 19 + src/backend/drm/egl/mod.rs | 172 ++++++ src/backend/drm/egl/session.rs | 35 ++ src/backend/drm/egl/surface.rs | 110 ++++ src/backend/drm/gbm/egl.rs | 118 ++++ src/backend/drm/gbm/error.rs | 47 ++ src/backend/drm/gbm/mod.rs | 191 +++++++ src/backend/drm/gbm/session.rs | 89 +++ src/backend/drm/gbm/surface.rs | 329 ++++++++++++ src/backend/drm/{ => legacy}/error.rs | 19 +- src/backend/drm/legacy/mod.rs | 257 +++++++++ src/backend/drm/legacy/session.rs | 97 ++++ src/backend/drm/legacy/surface.rs | 264 +++++++++ src/backend/drm/mod.rs | 747 +++----------------------- 16 files changed, 1811 insertions(+), 1229 deletions(-) delete mode 100644 src/backend/drm/backend.rs create mode 100644 src/backend/drm/egl/error.rs create mode 100644 src/backend/drm/egl/mod.rs create mode 100644 src/backend/drm/egl/session.rs create mode 100644 src/backend/drm/egl/surface.rs create mode 100644 src/backend/drm/gbm/egl.rs create mode 100644 src/backend/drm/gbm/error.rs create mode 100644 src/backend/drm/gbm/mod.rs create mode 100644 src/backend/drm/gbm/session.rs create mode 100644 src/backend/drm/gbm/surface.rs rename src/backend/drm/{ => legacy}/error.rs (76%) create mode 100644 src/backend/drm/legacy/mod.rs create mode 100644 src/backend/drm/legacy/session.rs create mode 100644 src/backend/drm/legacy/surface.rs diff --git a/Cargo.toml b/Cargo.toml index ce3a8db..b65e227 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ slog-stdlog = "3.0.2" libloading = "0.4.0" wayland-client = { version = "0.21.1", features = ["egl"], optional = true } winit = { version = "0.18.0", optional = true } -drm = { version = "^0.3.1", optional = true } +drm = { version = "^0.3.4", optional = true } gbm = { version = "^0.4.0", optional = true, default-features = false, features = ["drm-support"] } glium = { version = "0.19.0", optional = true, default-features = false } input = { version = "0.4.0", optional = true } @@ -37,9 +37,12 @@ lazy_static = "1.0.0" gl_generator = { version = "0.9", optional = true } [features] -default = ["backend_winit", "backend_drm", "backend_libinput", "backend_udev", "renderer_glium", "xwayland"] +default = ["backend_winit", "backend_drm_legacy", "backend_drm_gbm", "backend_drm_egl", "backend_libinput", "backend_udev", "renderer_glium", "xwayland"] backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen", "backend_egl"] -backend_drm = ["drm", "backend_egl"] +backend_drm = ["drm"] +backend_drm_legacy = ["backend_drm"] +backend_drm_gbm = ["backend_drm", "gbm"] +backend_drm_egl = ["backend_drm", "backend_egl"] backend_egl = ["gl_generator"] backend_libinput = ["input"] backend_session = [] diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs deleted file mode 100644 index 6abd4bb..0000000 --- a/src/backend/drm/backend.rs +++ /dev/null @@ -1,537 +0,0 @@ -use super::{error::*, DevPath}; -use backend::graphics::{ - egl::{ - error::Result as EGLResult, - native::{Gbm, GbmSurfaceArguments}, - wayland::{EGLDisplay, EGLWaylandExtensions}, - EGLContext, EGLGraphicsBackend, EGLSurface, PixelFormat, SwapBuffersError, - }, - GraphicsBackend, -}; -use drm::{ - control::{connector, crtc, encoder, framebuffer, Device, Mode, ResourceInfo}, - Device as BasicDevice, -}; -use gbm::{ - BufferObject, BufferObjectFlags, Device as GbmDevice, Format as GbmFormat, Surface as GbmSurface, - SurfaceBufferHandle, -}; -use image::{ImageBuffer, Rgba}; -use nix::libc::c_void; -use std::{ - cell::Cell, - os::unix::io::{AsRawFd, RawFd}, - rc::{Rc, Weak}, -}; -use wayland_server::Display; - -/// Backend based on a `DrmDevice` and a given crtc -pub struct DrmBackend { - backend: Rc>, - surface: EGLSurface>, - mode: Mode, - connectors: Vec, -} - -pub(crate) struct DrmBackendInternal { - pub(crate) context: Rc, GbmDevice>>, - pub(crate) cursor: Cell<(BufferObject<()>, (u32, u32))>, - current_frame_buffer: Cell, - front_buffer: Cell>, - next_buffer: Cell>>, - crtc: crtc::Handle, - logger: ::slog::Logger, -} - -impl DrmBackend { - pub(crate) fn new( - context: Rc, GbmDevice>>, - crtc: crtc::Handle, - mode: Mode, - connectors: Vec, - log: ::slog::Logger, - ) -> Result { - // logger already initialized by the DrmDevice - info!(log, "Initializing DrmBackend"); - - let (w, h) = mode.size(); - - debug!(log, "Creating Surface"); - let surface = context - .create_surface(GbmSurfaceArguments { - size: (w as u32, h as u32), - format: GbmFormat::XRGB8888, - flags: BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, - }).chain_err(|| ErrorKind::GbmInitFailed)?; - - // make it active for the first `crtc::set` - // (which is needed before the first page_flip) - unsafe { surface.make_current().chain_err(|| ErrorKind::FailedToSwap)? }; - surface.swap_buffers().chain_err(|| ErrorKind::FailedToSwap)?; - - // init the first screen - // (must be done before calling page_flip for the first time) - let mut front_bo = surface - .lock_front_buffer() - .chain_err(|| ErrorKind::FailedToSwap)?; - - debug!(log, "FrontBuffer color format: {:?}", front_bo.format()); - - // we need a framebuffer for the front buffer - let fb = framebuffer::create(&*context, &*front_bo).chain_err(|| { - ErrorKind::DrmDev(format!("Error creating framebuffer on {:?}", context.dev_path())) - })?; - - debug!(log, "Initialize screen"); - crtc::set(&*context, crtc, fb.handle(), &connectors, (0, 0), Some(mode)).chain_err(|| { - ErrorKind::DrmDev(format!( - "Error setting crtc {:?} on {:?}", - crtc, - context.dev_path() - )) - })?; - front_bo.set_userdata(fb).unwrap(); - - let cursor = Cell::new(( - context - .create_buffer_object( - 1, - 1, - GbmFormat::ARGB8888, - BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, - ).chain_err(|| ErrorKind::GbmInitFailed)?, - (0, 0), - )); - - Ok(DrmBackend { - backend: Rc::new(DrmBackendInternal { - context, - cursor, - current_frame_buffer: Cell::new(fb), - front_buffer: Cell::new(front_bo), - next_buffer: Cell::new(None), - crtc, - logger: log, - }), - surface, - mode, - connectors, - }) - } - - pub(crate) fn weak(&self) -> Weak> { - Rc::downgrade(&self.backend) - } - - /// Add a connector to backend - /// - /// # Errors - /// - /// Errors if the new connector does not support the currently set `Mode` - pub fn add_connector(&mut self, connector: connector::Handle) -> Result<()> { - let info = connector::Info::load_from_device(&*self.backend.context, connector).chain_err(|| { - ErrorKind::DrmDev(format!( - "Error loading connector info on {:?}", - self.backend.context.dev_path() - )) - })?; - - // check if the connector can handle the current mode - if info.modes().contains(&self.mode) { - // check if there is a valid encoder - let encoders = info - .encoders() - .iter() - .map(|encoder| { - encoder::Info::load_from_device(&*self.backend.context, *encoder).chain_err(|| { - ErrorKind::DrmDev(format!( - "Error loading encoder info on {:?}", - self.backend.context.dev_path() - )) - }) - }).collect::>>()?; - - // and if any encoder supports the selected crtc - let resource_handles = self.backend.context.resource_handles().chain_err(|| { - ErrorKind::DrmDev(format!( - "Error loading resources on {:?}", - self.backend.context.dev_path() - )) - })?; - if !encoders - .iter() - .map(|encoder| encoder.possible_crtcs()) - .all(|crtc_list| { - resource_handles - .filter_crtcs(crtc_list) - .contains(&self.backend.crtc) - }) { - bail!(ErrorKind::NoSuitableEncoder(info, self.backend.crtc)); - } - - info!( - self.backend.logger, - "Adding new connector: {:?}", - info.connector_type() - ); - self.connectors.push(connector); - Ok(()) - } else { - bail!(ErrorKind::ModeNotSuitable(self.mode)) - } - } - - /// Returns the currently set connectors - pub fn used_connectors(&self) -> &[connector::Handle] { - &*self.connectors - } - - /// Removes a currently set connector - pub fn remove_connector(&mut self, connector: connector::Handle) { - if let Ok(info) = connector::Info::load_from_device(&*self.backend.context, connector) { - info!( - self.backend.logger, - "Removing connector: {:?}", - info.connector_type() - ); - } else { - info!(self.backend.logger, "Removing unknown connector"); - } - - self.connectors.retain(|x| *x != connector); - } - - /// Gets the currently used mode - pub fn current_mode(&self) -> Mode { - self.mode - } - - /// Changes the currently set mode - /// - /// # Errors - /// - /// This will fail if not all set connectors support the new `Mode`. - /// Several internal resources will need to be recreated to fit the new `Mode`. - /// Other errors might occur. - pub fn use_mode(&mut self, mode: Mode) -> Result<()> { - // check the connectors - for connector in &self.connectors { - if !connector::Info::load_from_device(&*self.backend.context, *connector) - .chain_err(|| { - ErrorKind::DrmDev(format!( - "Error loading connector info on {:?}", - self.backend.context.dev_path() - )) - })?.modes() - .contains(&mode) - { - bail!(ErrorKind::ModeNotSuitable(mode)); - } - } - - info!(self.backend.logger, "Setting new mode: {:?}", mode.name()); - let (w, h) = mode.size(); - - // Recreate the surface and the related resources to match the new - // resolution. - debug!( - self.backend.logger, - "Reinitializing surface for new mode: {}:{}", w, h - ); - let surface = self - .backend - .context - .create_surface(GbmSurfaceArguments { - size: (w as u32, h as u32), - format: GbmFormat::XRGB8888, - flags: BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, - }).chain_err(|| ErrorKind::GbmInitFailed)?; - - // make it active for the first `crtc::set` - // (which is needed before the first page_flip) - unsafe { surface.make_current().chain_err(|| ErrorKind::FailedToSwap)? }; - surface.swap_buffers().chain_err(|| ErrorKind::FailedToSwap)?; - - // Clean up next_buffer - { - if let Some(mut old_bo) = self.backend.next_buffer.take() { - if let Ok(Some(fb)) = old_bo.take_userdata() { - if let Err(err) = framebuffer::destroy(&*self.backend.context, fb.handle()) { - warn!( - self.backend.logger, - "Error releasing old back_buffer framebuffer: {:?}", err - ); - } - } - } - } - - // Cleanup front_buffer and init the first screen on the new front_buffer - // (must be done before calling page_flip for the first time) - let mut old_front_bo = self.backend.front_buffer.replace({ - let mut front_bo = surface - .lock_front_buffer() - .chain_err(|| ErrorKind::FailedToSwap)?; - - debug!( - self.backend.logger, - "FrontBuffer color format: {:?}", - front_bo.format() - ); - - // we also need a new framebuffer for the front buffer - let dev_path = self.backend.context.dev_path(); - let fb = framebuffer::create(&*self.backend.context, &*front_bo) - .chain_err(|| ErrorKind::DrmDev(format!("Error creating framebuffer on {:?}", dev_path)))?; - - front_bo.set_userdata(fb).unwrap(); - - debug!(self.backend.logger, "Setting screen"); - crtc::set( - &*self.backend.context, - self.backend.crtc, - fb.handle(), - &self.connectors, - (0, 0), - Some(mode), - ).chain_err(|| { - ErrorKind::DrmDev(format!( - "Error setting crtc {:?} on {:?}", - self.backend.crtc, - self.backend.context.dev_path() - )) - })?; - - front_bo - }); - if let Ok(Some(fb)) = old_front_bo.take_userdata() { - if let Err(err) = framebuffer::destroy(&*self.backend.context, fb.handle()) { - warn!( - self.backend.logger, - "Error releasing old front_buffer framebuffer: {:?}", err - ); - } - } - - // Drop the old surface after cleanup - self.surface = surface; - self.mode = mode; - Ok(()) - } - - /// Returns the crtc id used by this backend - pub fn crtc(&self) -> crtc::Handle { - self.backend.crtc - } -} - -impl DrmBackendInternal { - pub(crate) fn unlock_buffer(&self) { - // after the page swap is finished we need to release the rendered buffer. - // this is called from the PageFlipHandler - if let Some(next_buffer) = self.next_buffer.replace(None) { - trace!(self.logger, "Releasing old front buffer"); - self.front_buffer.set(next_buffer); - // drop and release the old buffer - } - } - - pub(crate) fn page_flip( - &self, - fb: Option<&framebuffer::Info>, - ) -> ::std::result::Result<(), SwapBuffersError> { - trace!(self.logger, "Queueing Page flip"); - - let fb = *fb.unwrap_or(&self.current_frame_buffer.get()); - - // and flip - crtc::page_flip( - &*self.context, - self.crtc, - fb.handle(), - &[crtc::PageFlipFlags::PageFlipEvent], - ).map_err(|_| SwapBuffersError::ContextLost)?; - - self.current_frame_buffer.set(fb); - - Ok(()) - } -} - -impl Drop for DrmBackend { - 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) - if let Ok(Some(fb)) = { - if let Some(mut next) = self.backend.next_buffer.take() { - next.take_userdata() - } else if let Ok(mut next) = self.surface.lock_front_buffer() { - next.take_userdata() - } else { - Ok(None) - } - } { - // ignore failure at this point - let _ = framebuffer::destroy(&*self.backend.context, fb.handle()); - } - } -} - -impl Drop for DrmBackendInternal { - fn drop(&mut self) { - if let Ok(Some(fb)) = self.front_buffer.get_mut().take_userdata() { - // ignore failure at this point - let _ = framebuffer::destroy(&*self.context, fb.handle()); - } - - // ignore failure at this point - let _ = crtc::clear_cursor(&*self.context, self.crtc); - } -} - -impl GraphicsBackend for DrmBackend { - type CursorFormat = ImageBuffer, Vec>; - type Error = Error; - - fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> { - trace!(self.backend.logger, "Move the cursor to {},{}", x, y); - crtc::move_cursor(&*self.backend.context, self.backend.crtc, (x as i32, y as i32)).chain_err(|| { - ErrorKind::DrmDev(format!( - "Error moving cursor on {:?}", - self.backend.context.dev_path() - )) - }) - } - - fn set_cursor_representation( - &self, - buffer: &ImageBuffer, Vec>, - hotspot: (u32, u32), - ) -> Result<()> { - let (w, h) = buffer.dimensions(); - debug!(self.backend.logger, "Importing cursor"); - - // import the cursor into a buffer we can render - let mut cursor = self - .backend - .context - .create_buffer_object( - w, - h, - GbmFormat::ARGB8888, - BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, - ).chain_err(|| ErrorKind::GbmInitFailed)?; - cursor - .write(&**buffer) - .chain_err(|| ErrorKind::GbmInitFailed)? - .chain_err(|| ErrorKind::GbmInitFailed)?; - - trace!(self.backend.logger, "Setting the new imported cursor"); - - // and set it - if crtc::set_cursor2( - &*self.backend.context, - self.backend.crtc, - &cursor, - (hotspot.0 as i32, hotspot.1 as i32), - ).is_err() - { - crtc::set_cursor(&*self.backend.context, self.backend.crtc, &cursor).chain_err(|| { - ErrorKind::DrmDev(format!( - "Failed to set cursor on {:?}", - self.backend.context.dev_path() - )) - })?; - } - - // and store it - self.backend.cursor.set((cursor, hotspot)); - Ok(()) - } -} - -impl EGLGraphicsBackend for DrmBackend { - fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { - let res = { - let nb = self.backend.next_buffer.take(); - let res = nb.is_some(); - self.backend.next_buffer.set(nb); - res - }; - if res { - // We cannot call lock_front_buffer anymore without releasing the previous buffer, which will happen when the page flip is done - warn!( - self.backend.logger, - "Tried to swap a DrmBackend with a queued flip" - ); - return Err(SwapBuffersError::AlreadySwapped); - } - - // flip normally - self.surface.swap_buffers()?; - - // 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_front_buffer() - .expect("Surface only has one front buffer. Not supported by smithay"); - - // 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(|_| SwapBuffersError::ContextLost)? - .cloned(); - let fb = if let Some(info) = maybe_fb { - info - } else { - let fb = framebuffer::create(&*self.backend.context, &*next_bo) - .map_err(|_| SwapBuffersError::ContextLost)?; - next_bo.set_userdata(fb).unwrap(); - fb - }; - self.backend.next_buffer.set(Some(next_bo)); - - self.backend.page_flip(Some(&fb)) - } - - unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { - self.backend.context.get_proc_address(symbol) - } - - fn get_framebuffer_dimensions(&self) -> (u32, u32) { - let (w, h) = self.mode.size(); - (w as u32, h as u32) - } - - fn is_current(&self) -> bool { - self.backend.context.is_current() && self.surface.is_current() - } - - unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { - self.surface.make_current() - } - - fn get_pixel_format(&self) -> PixelFormat { - self.backend.context.get_pixel_format() - } -} - -// for users convenience -impl AsRawFd for DrmBackend { - fn as_raw_fd(&self) -> RawFd { - self.backend.context.as_raw_fd() - } -} - -impl BasicDevice for DrmBackend {} -impl Device for DrmBackend {} - -impl EGLWaylandExtensions for DrmBackend { - fn bind_wl_display(&self, display: &Display) -> EGLResult { - self.backend.context.bind_wl_display(display) - } -} diff --git a/src/backend/drm/egl/error.rs b/src/backend/drm/egl/error.rs new file mode 100644 index 0000000..07f0e94 --- /dev/null +++ b/src/backend/drm/egl/error.rs @@ -0,0 +1,19 @@ +//! +//! Errors thrown by the `DrmDevice` and `DrmBackend` +//! + +use backend::egl::error as egl; + +error_chain! { + errors { + #[doc = "Underlying backend failed"] + UnderlyingBackendError { + description("The underlying backend reported an error"), + display("The underlying backend reported an error"), + } + } + + links { + EGL(egl::Error, egl::ErrorKind) #[doc = "EGL error"]; + } +} diff --git a/src/backend/drm/egl/mod.rs b/src/backend/drm/egl/mod.rs new file mode 100644 index 0000000..6f08942 --- /dev/null +++ b/src/backend/drm/egl/mod.rs @@ -0,0 +1,172 @@ +use drm::control::{crtc, Mode}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::{Rc, Weak}; +use std::os::unix::io::{AsRawFd, RawFd}; +use wayland_server::Display; + +use backend::egl::{EGLContext, EGLGraphicsBackend, EGLDisplay}; +use backend::egl::context::GlAttributes; +use backend::egl::native::{Backend, NativeDisplay, NativeSurface}; +use backend::egl::error::Result as EGLResult; +use super::{Device, Surface, DeviceHandler}; + +pub mod error; +use self::error::*; + +mod surface; +pub use self::surface::*; + +#[cfg(feature = "backend_session")] +pub mod session; + +/// Representation of an open gbm device to create rendering backends +pub struct EglDevice::Surface> + 'static, D: Device + NativeDisplay + 'static> + where ::Surface: NativeSurface +{ + dev: Rc>>, + backends: Rc>>>>, + logger: ::slog::Logger, +} + +impl::Surface> + 'static, D: Device + NativeDisplay + 'static> AsRawFd for EglDevice + where ::Surface: NativeSurface +{ + fn as_raw_fd(&self) -> RawFd { + self.dev.borrow().as_raw_fd() + } +} + +impl::Surface> + 'static, D: Device + NativeDisplay + 'static> EglDevice + where ::Surface: NativeSurface +{ + /// Create a new `EglGbmDrmDevice` 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(dev: D, logger: L) -> Result + where + L: Into>, + { + EglDevice::new_with_gl_attr( + dev, + GlAttributes { + version: None, + profile: None, + debug: cfg!(debug_assertions), + vsync: true, + }, + logger, + ) + } + + /// Create a new `EglGbmDrmDevice` from an open `RawDevice` and given `GlAttributes` + /// + /// Returns an error if the file is no valid drm node or context creation was not + /// successful. + pub fn new_with_gl_attr(mut dev: D, attributes: GlAttributes, logger: L) -> Result + where + L: Into>, + { + let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_egl")); + + dev.clear_handler(); + + debug!(log, "Creating egl context from device"); + Ok(EglDevice { + // Open the gbm device from the drm device and create a context based on that + dev: Rc::new(RefCell::new(EGLContext::new( + dev, + attributes, + Default::default(), + log.clone(), + ).map_err(Error::from)? + )), + backends: Rc::new(RefCell::new(HashMap::new())), + logger: log, + }) + } +} + +struct InternalDeviceHandler::Surface> + 'static, D: Device + NativeDisplay + 'static> + where ::Surface: NativeSurface +{ + handler: Box> + 'static>, + backends: Weak>>>>, + logger: ::slog::Logger, +} + +impl::Surface> + 'static, D: Device + NativeDisplay + 'static> DeviceHandler for InternalDeviceHandler + where + >::Arguments: From<(crtc::Handle, Mode, <::Surface as Surface>::Connectors)>, + ::Surface: NativeSurface, +{ + type Device=D; + + fn vblank(&mut self, surface: &::Surface) { + if let Some(backends) = self.backends.upgrade() { + if let Some(surface) = backends.borrow().get(&surface.crtc()) { + if let Some(surface) = surface.upgrade() { + self.handler.vblank(&*surface); + } + } else { + warn!(self.logger, "Surface ({:?}) not managed by egl, event not handled.", surface.crtc()); + } + } + } + fn error(&mut self, error: <::Surface as Surface>::Error) { + self.handler.error(ResultExt::<()>::chain_err(Err(error), || ErrorKind::UnderlyingBackendError).unwrap_err()) + } +} + +impl::Surface> + 'static, D: Device + NativeDisplay + 'static> Device for EglDevice + where + >::Arguments: From<(crtc::Handle, Mode, <::Surface as Surface>::Connectors)>, + ::Surface: NativeSurface, +{ + type Surface = EglSurface; + type Return = Rc>; + + fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { + self.dev.borrow_mut().set_handler(InternalDeviceHandler { + handler: Box::new(handler), + backends: Rc::downgrade(&self.backends), + logger: self.logger.clone(), + }); + } + + fn clear_handler(&mut self) { + self.dev.borrow_mut().clear_handler() + } + + fn create_surface( + &mut self, + crtc: crtc::Handle, + mode: Mode, + connectors: impl Into<<::Surface as Surface>::Connectors>, + ) -> Result>> { + info!(self.logger, "Initializing EglSurface"); + + let surface = self.dev.borrow_mut().create_surface((crtc, mode, connectors.into()).into())?; + + let backend = Rc::new(EglSurface { + dev: self.dev.clone(), + surface, + }); + self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend)); + Ok(backend) + } + + + fn process_events(&mut self) { + self.dev.borrow_mut().process_events() + } +} + +impl::Surface> + 'static, D: Device + NativeDisplay + 'static> EGLGraphicsBackend for EglDevice + where ::Surface: NativeSurface +{ + fn bind_wl_display(&self, display: &Display) -> EGLResult { + self.dev.borrow().bind_wl_display(display) + } +} \ No newline at end of file diff --git a/src/backend/drm/egl/session.rs b/src/backend/drm/egl/session.rs new file mode 100644 index 0000000..f440544 --- /dev/null +++ b/src/backend/drm/egl/session.rs @@ -0,0 +1,35 @@ +use std::os::unix::io::RawFd; + +use backend::egl::native::{Backend, NativeDisplay, NativeSurface}; +use backend::session::{AsSessionObserver, SessionObserver}; +use backend::drm::Device; +use super::{EglDevice}; + +/// `SessionObserver` linked to the `DrmDevice` it was created from. +pub struct EglDeviceObserver { + observer: S, +} + +impl< + S: SessionObserver + 'static, + B: Backend::Surface> + 'static, + D: Device + NativeDisplay + AsSessionObserver + 'static, +> AsSessionObserver> for EglDevice + where ::Surface: NativeSurface +{ + fn observer(&mut self) -> EglDeviceObserver { + EglDeviceObserver { + observer: (**self.dev.borrow_mut()).observer(), + } + } +} + +impl SessionObserver for EglDeviceObserver { + fn pause(&mut self, devnum: Option<(u32, u32)>) { + self.observer.pause(devnum); + } + + fn activate(&mut self, devnum: Option<(u32, u32, Option)>) { + self.observer.activate(devnum); + } +} diff --git a/src/backend/drm/egl/surface.rs b/src/backend/drm/egl/surface.rs new file mode 100644 index 0000000..3d62af6 --- /dev/null +++ b/src/backend/drm/egl/surface.rs @@ -0,0 +1,110 @@ +use drm::control::{connector, crtc, Mode}; +use nix::libc::c_void; +use std::cell::RefCell; +use std::rc::Rc; + +use backend::drm::{Device, Surface}; +use backend::egl::{EGLContext, EGLSurface}; +use backend::egl::native::{Backend, NativeDisplay, NativeSurface}; +use backend::graphics::{CursorBackend, SwapBuffersError}; +use backend::graphics::gl::{GLGraphicsBackend, PixelFormat}; +use super::error::*; + +pub struct EglSurface::Surface> + 'static, D: Device + NativeDisplay + 'static> + where ::Surface: NativeSurface +{ + pub(in super) dev: Rc>>, + pub(in super) surface: EGLSurface, +} + +impl::Surface> + 'static, D: Device + NativeDisplay + 'static> Surface for EglSurface + where ::Surface: NativeSurface +{ + type Error = Error; + type Connectors = <::Surface as Surface>::Connectors; + + fn crtc(&self) -> crtc::Handle { + (*self.surface).crtc() + } + + fn current_connectors(&self) -> Self::Connectors { + self.surface.current_connectors() + } + + fn pending_connectors(&self) -> Self::Connectors { + self.surface.pending_connectors() + } + + fn add_connector(&self, connector: connector::Handle) -> Result<()> { + self.surface.add_connector(connector).chain_err(|| ErrorKind::UnderlyingBackendError) + } + + fn remove_connector(&self, connector: connector::Handle) -> Result<()> { + self.surface.remove_connector(connector).chain_err(|| ErrorKind::UnderlyingBackendError) + } + + fn current_mode(&self) -> Mode { + self.surface.current_mode() + } + + fn pending_mode(&self) -> Mode { + self.surface.pending_mode() + } + + fn use_mode(&self, mode: Mode) -> Result<()> { + self.surface.use_mode(mode).chain_err(|| ErrorKind::UnderlyingBackendError) + } +} + +impl<'a, B: Backend::Surface> + 'static, D: Device + NativeDisplay + 'static> CursorBackend<'a> for EglSurface + where + D: CursorBackend<'a>, + ::Surface: NativeSurface +{ + type CursorFormat = >::CursorFormat; + type Error = >::Error; + + fn set_cursor_position(&self, x: u32, y: u32) -> ::std::result::Result<(), Self::Error> { + self.dev.borrow().set_cursor_position(x, y) + } + + fn set_cursor_representation<'b>( + &'b self, + buffer: Self::CursorFormat, + hotspot: (u32, u32), + ) -> ::std::result::Result<(), Self::Error> + where 'a: 'b + { + let dev = self.dev.borrow(); + dev.set_cursor_representation(buffer, hotspot) + } +} + +impl::Surface> + 'static, D: Device + NativeDisplay + 'static> GLGraphicsBackend for EglSurface + where ::Surface: NativeSurface +{ + fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { + self.surface.swap_buffers() + } + + unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { + self.dev.borrow().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.dev.borrow().is_current() && self.surface.is_current() + } + + unsafe fn make_current(&self) -> ::std::result::Result<(), SwapBuffersError> { + self.surface.make_current() + } + + fn get_pixel_format(&self) -> PixelFormat { + self.dev.borrow().get_pixel_format() + } +} \ No newline at end of file diff --git a/src/backend/drm/gbm/egl.rs b/src/backend/drm/gbm/egl.rs new file mode 100644 index 0000000..915df9b --- /dev/null +++ b/src/backend/drm/gbm/egl.rs @@ -0,0 +1,118 @@ +use backend::drm::{RawDevice, Device, RawSurface, Surface}; +use backend::egl::native::{Backend, NativeDisplay, NativeSurface}; +use backend::egl::error::{Result as EglResult}; +use backend::egl::ffi; +use backend::graphics::SwapBuffersError; + +use super::{GbmDevice, GbmSurface}; +use super::error::{Error, Result}; + +use drm::control::{crtc, Device as ControlDevice, Mode}; +use gbm::AsRaw; +use std::marker::PhantomData; +use std::rc::Rc; +use std::ptr; + +/// Gbm backend type +pub struct Gbm +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + _userdata: PhantomData, +} + +impl Backend for Gbm +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + type Surface = Rc>; + + unsafe fn get_display( + display: ffi::NativeDisplayType, + has_dp_extension: F, + log: ::slog::Logger, + ) -> ffi::egl::types::EGLDisplay + 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"); + ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_KHR, display as *mut _, ptr::null()) + } else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplayEXT::is_loaded() { + trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); + ffi::egl::GetPlatformDisplayEXT(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) + } else if has_dp_extension("EGL_MESA_platform_gbm") && ffi::egl::GetPlatformDisplay::is_loaded() { + trace!(log, "EGL Display Initialization via EGL_MESA_platform_gbm"); + ffi::egl::GetPlatformDisplay(ffi::egl::PLATFORM_GBM_MESA, display as *mut _, ptr::null()) + } else { + trace!(log, "Default EGL Display Initialization via GetDisplay"); + ffi::egl::GetDisplay(display as *mut _) + } + } +} + +/// Arguments necessary to construct a `GbmSurface` +pub struct SurfaceArguments +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + /// Crtc + pub crtc: crtc::Handle, + /// Mode + pub mode: Mode, + /// Connectors + pub connectors: as Surface>::Connectors, +} + +impl From<(crtc::Handle, Mode, as Surface>::Connectors)> for SurfaceArguments +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + fn from((crtc, mode, connectors): (crtc::Handle, Mode, as Surface>::Connectors)) -> Self { + SurfaceArguments { + crtc, + mode, + connectors, + } + } +} + +unsafe impl NativeDisplay> for GbmDevice +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + type Arguments = SurfaceArguments; + type Error = Error; + + fn is_backend(&self) -> bool { + true + } + + fn ptr(&self) -> EglResult { + Ok(self.dev.borrow().as_raw() as *const _) + } + + fn create_surface(&mut self, args: SurfaceArguments) -> Result>> { + Device::create_surface(self, args.crtc, args.mode, args.connectors) + } +} + +unsafe impl NativeSurface for Rc> +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + fn ptr(&self) -> ffi::NativeWindowType { + self.surface.borrow().as_raw() as *const _ + } + + fn swap_buffers(&self, flip: F) -> ::std::result::Result<(), SwapBuffersError> + where + F: FnOnce() -> ::std::result::Result<(), SwapBuffersError> + { + if ::std::borrow::Borrow::borrow(&self.crtc).commit_pending() { + self.recreate(flip).map_err(|_| SwapBuffersError::ContextLost) + } else { + self.page_flip(flip) + } + } +} \ No newline at end of file diff --git a/src/backend/drm/gbm/error.rs b/src/backend/drm/gbm/error.rs new file mode 100644 index 0000000..e2fba7e --- /dev/null +++ b/src/backend/drm/gbm/error.rs @@ -0,0 +1,47 @@ +//! +//! Errors thrown by the `DrmDevice` and `DrmBackend` +//! + +error_chain! { + errors { + #[doc = "Creation of gbm device failed"] + InitFailed { + description("Creation of gbm device failed"), + display("Creation of gbm device failed"), + } + + #[doc = "Creation of gbm surface failed"] + SurfaceCreationFailed { + description("Creation of gbm surface failed"), + display("Creation of gbm surface failed"), + } + + #[doc = "Creation of gbm buffer object failed"] + BufferCreationFailed { + description("Creation of gbm buffer object failed"), + display("Creation of gbm buffer object failed"), + } + + #[doc = "Writing to gbm buffer failed"] + BufferWriteFailed { + description("Writing to gbm buffer failed"), + display("Writing to gbm buffer failed"), + } + + #[doc = "Lock of gbm surface front buffer failed"] + FrontBufferLockFailed { + description("Lock of gbm surface front buffer failed"), + display("Lock of gbm surface front buffer failed"), + } + + #[doc = "Underlying backend failed"] + UnderlyingBackendError { + description("The underlying backend reported an error"), + display("The underlying backend reported an error"), + } + } + + foreign_links { + FailedToSwap(::backend::graphics::SwapBuffersError); + } +} diff --git a/src/backend/drm/gbm/mod.rs b/src/backend/drm/gbm/mod.rs new file mode 100644 index 0000000..43f501e --- /dev/null +++ b/src/backend/drm/gbm/mod.rs @@ -0,0 +1,191 @@ +use super::{Device, RawDevice, Surface, DeviceHandler}; + +use drm::control::{crtc, framebuffer, Device as ControlDevice, Mode}; +use gbm::{self, Format as GbmFormat, BufferObjectFlags}; + +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; +use std::rc::{Rc, Weak}; +use std::sync::{Once, ONCE_INIT}; +use std::os::unix::io::{AsRawFd, RawFd}; + +pub mod error; +use self::error::*; + +mod surface; +pub use self::surface::GbmSurface; + +pub mod egl; + +#[cfg(feature = "backend_session")] +pub mod session; + +static LOAD: Once = ONCE_INIT; + +/// Representation of an open gbm device to create rendering backends +pub struct GbmDevice +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + pub(in self) dev: Rc>>, + backends: Rc>>>>, + logger: ::slog::Logger, +} + +impl GbmDevice +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + /// 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 + 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 = ::slog_or_stdlog(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: Rc::new(RefCell::new(gbm::Device::new(dev).chain_err(|| ErrorKind::InitFailed)?)), + backends: Rc::new(RefCell::new(HashMap::new())), + logger: log, + }) + } +} + +struct InternalDeviceHandler +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + handler: Box> + 'static>, + backends: Weak>>>>, + logger: ::slog::Logger, +} + +impl DeviceHandler for InternalDeviceHandler +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + type Device = D; + + fn vblank(&mut self, surface: &::Surface) { + if let Some(backends) = self.backends.upgrade() { + if let Some(surface) = backends.borrow().get(&surface.crtc()) { + if let Some(surface) = surface.upgrade() { + surface.unlock_buffer(); + self.handler.vblank(&*surface); + } + } else { + warn!(self.logger, "Surface ({:?}) not managed by gbm, event not handled.", surface.crtc()); + } + } + } + fn error(&mut self, error: <::Surface as Surface>::Error) { + self.handler.error(ResultExt::<()>::chain_err(Err(error), || ErrorKind::UnderlyingBackendError).unwrap_err()) + } +} + +impl Device for GbmDevice +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + type Surface = GbmSurface; + type Return = Rc>; + + fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { + self.dev.borrow_mut().set_handler(InternalDeviceHandler { + handler: Box::new(handler), + backends: Rc::downgrade(&self.backends), + logger: self.logger.clone(), + }); + } + + fn clear_handler(&mut self) { + self.dev.borrow_mut().clear_handler(); + } + + fn create_surface( + &mut self, + crtc: crtc::Handle, + mode: Mode, + connectors: impl Into<::Connectors> + ) -> Result>> { + info!(self.logger, "Initializing GbmSurface"); + + let (w, h) = mode.size(); + let surface = self.dev.borrow().create_surface( + w as u32, + h as u32, + GbmFormat::XRGB8888, + BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, + ).chain_err(|| ErrorKind::SurfaceCreationFailed)?; + + // init the first screen + // (must be done before calling page_flip for the first time) + let mut front_bo = surface + .lock_front_buffer() + .chain_err(|| ErrorKind::FrontBufferLockFailed)?; + + debug!(self.logger, "FrontBuffer color format: {:?}", front_bo.format()); + + // we need a framebuffer for the front buffer + let fb = framebuffer::create(&*self.dev.borrow(), &*front_bo).chain_err(|| ErrorKind::UnderlyingBackendError)?; + front_bo.set_userdata(fb).unwrap(); + + let cursor = Cell::new(( + self.dev.borrow() + .create_buffer_object( + 1, + 1, + GbmFormat::ARGB8888, + BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, + ).chain_err(|| ErrorKind::BufferCreationFailed)?, + (0, 0), + )); + + let backend = Rc::new(GbmSurface { + dev: self.dev.clone(), + surface: RefCell::new(surface), + crtc: Device::create_surface(&mut **self.dev.borrow_mut(), crtc, mode, connectors) + .chain_err(|| ErrorKind::UnderlyingBackendError)?, + cursor, + current_frame_buffer: Cell::new(fb), + front_buffer: Cell::new(front_bo), + next_buffer: Cell::new(None), + logger: self.logger.new(o!("crtc" => format!("{:?}", crtc))), + }); + self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend)); + Ok(backend) + } + + fn process_events(&mut self) { + self.dev.borrow_mut().process_events() + } +} + +impl AsRawFd for GbmDevice +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + fn as_raw_fd(&self) -> RawFd { + self.dev.borrow().as_raw_fd() + } +} \ No newline at end of file diff --git a/src/backend/drm/gbm/session.rs b/src/backend/drm/gbm/session.rs new file mode 100644 index 0000000..1a84c41 --- /dev/null +++ b/src/backend/drm/gbm/session.rs @@ -0,0 +1,89 @@ +use drm::control::{crtc, Device as ControlDevice, ResourceInfo}; +use gbm::BufferObject; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::{Rc, Weak}; +use std::os::unix::io::RawFd; + +use backend::session::{AsSessionObserver, SessionObserver}; +use backend::drm::{Device, RawDevice, RawSurface}; +use super::{GbmDevice, GbmSurface}; + +/// `SessionObserver` linked to the `DrmDevice` it was created from. +pub struct GbmDeviceObserver< + S: SessionObserver + 'static, + D: RawDevice + ControlDevice + AsSessionObserver + 'static, +> +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + observer: S, + backends: Weak>>>>, + logger: ::slog::Logger, +} + +impl< + S: SessionObserver + 'static, + D: RawDevice + ControlDevice + AsSessionObserver + 'static, +> AsSessionObserver> for GbmDevice +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + fn observer(&mut self) -> GbmDeviceObserver { + GbmDeviceObserver { + observer: (**self.dev.borrow_mut()).observer(), + backends: Rc::downgrade(&self.backends), + logger: self.logger.clone(), + } + } +} + +impl< + S: SessionObserver + 'static, + D: RawDevice + ControlDevice + AsSessionObserver + 'static, +> SessionObserver for GbmDeviceObserver +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + fn pause(&mut self, devnum: Option<(u32, u32)>) { + self.observer.pause(devnum); + } + + fn activate(&mut self, devnum: Option<(u32, u32, Option)>) { + self.observer.activate(devnum); + 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() { + // restart rendering loop + if let Err(err) = + ::std::borrow::Borrow::borrow(&backend.crtc).page_flip(backend.current_frame_buffer.get().handle()) + { + warn!(self.logger, "Failed to restart rendering loop. Error: {}", err); + } + // reset cursor + { + let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) = + unsafe { &*backend.cursor.as_ptr() }; + if crtc::set_cursor2( + &*backend.dev.borrow(), + *crtc, + cursor, + ((*hotspot).0 as i32, (*hotspot).1 as i32), + ).is_err() + { + if let Err(err) = crtc::set_cursor(&*backend.dev.borrow(), *crtc, cursor) { + error!(self.logger, "Failed to reset cursor. Error: {}", err); + } + } + } + } 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 new file mode 100644 index 0000000..c279a00 --- /dev/null +++ b/src/backend/drm/gbm/surface.rs @@ -0,0 +1,329 @@ +use super::error::*; +use super::super::{Device, RawDevice, Surface, RawSurface}; + +use drm::control::{crtc, connector, framebuffer, Mode, ResourceInfo}; +use gbm::{self, SurfaceBufferHandle, Format as GbmFormat, BufferObject, BufferObjectFlags}; +use image::{ImageBuffer, Rgba}; + +use std::cell::{Cell, RefCell}; +use std::rc::Rc; +use std::os::unix::io::AsRawFd; + +use backend::drm::legacy::{LegacyDrmDevice, LegacyDrmSurface}; +use backend::graphics::CursorBackend; +use backend::graphics::SwapBuffersError; + +pub struct GbmSurface +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + pub(in super) dev: Rc>>, + pub(in super) surface: RefCell>, + pub(in super) crtc: ::Return, + pub(in super) cursor: Cell<(BufferObject<()>, (u32, u32))>, + pub(in super) current_frame_buffer: Cell, + pub(in super) front_buffer: Cell>, + pub(in super) next_buffer: Cell>>, + pub(in super) logger: ::slog::Logger, +} + +impl GbmSurface +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + pub(in super) fn unlock_buffer(&self) { + // after the page swap is finished we need to release the rendered buffer. + // this is called from the PageFlipHandler + if let Some(next_buffer) = self.next_buffer.replace(None) { + trace!(self.logger, "Releasing old front buffer"); + self.front_buffer.set(next_buffer); + // drop and release the old buffer + } + } + + pub fn page_flip(&self, flip: F) -> ::std::result::Result<(), SwapBuffersError> + where + F: FnOnce() -> ::std::result::Result<(), SwapBuffersError> + { + let res = { + let nb = self.next_buffer.take(); + let res = nb.is_some(); + self.next_buffer.set(nb); + res + }; + if res { + // 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(SwapBuffersError::AlreadySwapped); + } + + // flip normally + flip()?; + + // 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 + .borrow() + .lock_front_buffer() + .expect("Surface only has one front buffer. Not supported by smithay"); + + // 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(|_| SwapBuffersError::ContextLost)? + .cloned(); + let fb = if let Some(info) = maybe_fb { + info + } else { + let fb = framebuffer::create(::std::borrow::Borrow::borrow(&self.crtc), &*next_bo) + .map_err(|_| SwapBuffersError::ContextLost)?; + next_bo.set_userdata(fb).unwrap(); + fb + }; + self.next_buffer.set(Some(next_bo)); + + trace!(self.logger, "Queueing Page flip"); + ::std::borrow::Borrow::borrow(&self.crtc).page_flip(fb.handle())?; + + self.current_frame_buffer.set(fb); + + Ok(()) + } + + pub fn recreate(&self, flip: F) -> Result<()> + where + F: FnOnce() -> ::std::result::Result<(), SwapBuffersError> + { + let (w, h) = self.pending_mode().size(); + + // Recreate the surface and the related resources to match the new + // resolution. + debug!( + self.logger, + "Reinitializing surface for new mode: {}:{}", w, h + ); + let surface = self + .dev + .borrow_mut() + .create_surface( + w as u32, + h as u32, + GbmFormat::XRGB8888, + BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING, + ).chain_err(|| ErrorKind::SurfaceCreationFailed)?; + + flip()?; + + // Clean up next_buffer + { + if let Some(mut old_bo) = self.next_buffer.take() { + if let Ok(Some(fb)) = old_bo.take_userdata() { + if let Err(err) = framebuffer::destroy(::std::borrow::Borrow::borrow(&self.crtc), fb.handle()) { + warn!( + self.logger, + "Error releasing old back_buffer framebuffer: {:?}", err + ); + } + } + } + } + + // Cleanup front_buffer and init the first screen on the new front_buffer + // (must be done before calling page_flip for the first time) + let mut old_front_bo = self.front_buffer.replace({ + let mut front_bo = surface + .lock_front_buffer() + .chain_err(|| ErrorKind::FrontBufferLockFailed)?; + + debug!( + self.logger, + "FrontBuffer color format: {:?}", + front_bo.format() + ); + + // we also need a new framebuffer for the front buffer + let fb = framebuffer::create(::std::borrow::Borrow::borrow(&self.crtc), &*front_bo) + .chain_err(|| ErrorKind::UnderlyingBackendError)?; + + ::std::borrow::Borrow::borrow(&self.crtc).commit(fb.handle()) + .chain_err(|| ErrorKind::UnderlyingBackendError)?; + + front_bo.set_userdata(fb).unwrap(); + front_bo + }); + if let Ok(Some(fb)) = old_front_bo.take_userdata() { + if let Err(err) = framebuffer::destroy(::std::borrow::Borrow::borrow(&self.crtc), fb.handle()) { + warn!( + self.logger, + "Error releasing old front_buffer framebuffer: {:?}", err + ); + } + } + + // Drop the old surface after cleanup + *self.surface.borrow_mut() = surface; + + Ok(()) + } +} + +impl Surface for GbmSurface +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + type Connectors = <::Surface as Surface>::Connectors; + type Error = Error; + + fn crtc(&self) -> crtc::Handle { + ::std::borrow::Borrow::borrow(&self.crtc) + .crtc() + } + + fn current_connectors(&self) -> Self::Connectors { + ::std::borrow::Borrow::borrow(&self.crtc) + .current_connectors() + } + + fn pending_connectors(&self) -> Self::Connectors { + ::std::borrow::Borrow::borrow(&self.crtc) + .pending_connectors() + } + + fn add_connector(&self, connector: connector::Handle) -> Result<()> { + ::std::borrow::Borrow::borrow(&self.crtc) + .add_connector(connector) + .chain_err(|| ErrorKind::UnderlyingBackendError) + } + + fn remove_connector(&self, connector: connector::Handle) -> Result<()> { + ::std::borrow::Borrow::borrow(&self.crtc) + .remove_connector(connector) + .chain_err(|| ErrorKind::UnderlyingBackendError) + } + + fn current_mode(&self) -> Mode { + ::std::borrow::Borrow::borrow(&self.crtc) + .current_mode() + } + + fn pending_mode(&self) -> Mode { + ::std::borrow::Borrow::borrow(&self.crtc) + .pending_mode() + } + + fn use_mode(&self, mode: Mode) -> Result<()> { + ::std::borrow::Borrow::borrow(&self.crtc) + .use_mode(mode) + .chain_err(|| ErrorKind::UnderlyingBackendError) + } +} + +// FIXME: +// +// Option 1: When there is GAT support, impl `GraphicsBackend` for `LegacyDrmBackend` +// using a new generic `B: Buffer` and use this: +/* +impl<'a, D: RawDevice + 'static> CursorBackend<'a> for GbmSurface +where + ::Return: ::std::borrow::Borrow<::Surface>, + ::Surface: CursorBackend<'a>, + <::Surface as CursorBackend<'a>>::CursorFormat: Buffer, + <::Surface as CursorBackend<'a>>::Error: ::std::error::Error + Send +{ +*/ +// +// Option 2: When equality checks in where clauses are supported, we could at least do this: +/* +impl<'a, D: RawDevice + 'static> GraphicsBackend<'a> for GbmSurface +where + ::Return: ::std::borrow::Borrow<::Surface>, + ::Surface: CursorBackend<'a>, + <::Surface as CursorBackend<'a>>::CursorFormat=&'a Buffer, + <::Surface as CursorBackend<'a>>::Error: ::std::error::Error + Send +{ +*/ +// But for now got to do this: + +impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for GbmSurface> { + type CursorFormat = &'a ImageBuffer, Vec>; + type Error = Error; + + fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> { + ResultExt::chain_err( + ::std::borrow::Borrow::>>::borrow(&self.crtc) + .set_cursor_position(x, y), + || ErrorKind::UnderlyingBackendError) + } + + fn set_cursor_representation<'b>( + &'b self, + buffer: &ImageBuffer, Vec>, + hotspot: (u32, u32), + ) -> Result<()> + where 'a: 'b + { + let (w, h) = buffer.dimensions(); + debug!(self.logger, "Importing cursor"); + + // import the cursor into a buffer we can render + let mut cursor = self + .dev + .borrow_mut() + .create_buffer_object( + w, + h, + GbmFormat::ARGB8888, + BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE, + ).chain_err(|| ErrorKind::BufferCreationFailed)?; + + cursor + .write(&**buffer) + .chain_err(|| ErrorKind::BufferWriteFailed)? + .chain_err(|| ErrorKind::BufferWriteFailed)?; + + trace!(self.logger, "Setting the new imported cursor"); + + ResultExt::chain_err( + ::std::borrow::Borrow::>>::borrow(&self.crtc) + .set_cursor_representation(&cursor, hotspot), + || ErrorKind::UnderlyingBackendError)?; + + // and store it + self.cursor.set((cursor, hotspot)); + Ok(()) + } +} + +impl Drop for GbmSurface +where + ::Return: ::std::borrow::Borrow<::Surface> +{ + 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) + if let Ok(Some(fb)) = { + if let Some(mut next) = self.next_buffer.take() { + next.take_userdata() + } else if let Ok(mut next) = self.surface.borrow().lock_front_buffer() { + next.take_userdata() + } else { + Ok(None) + } + } { + // ignore failure at this point + let _ = framebuffer::destroy(::std::borrow::Borrow::borrow(&self.crtc), fb.handle()); + } + + if let Ok(Some(fb)) = self.front_buffer.get_mut().take_userdata() { + // ignore failure at this point + let _ = framebuffer::destroy(::std::borrow::Borrow::borrow(&self.crtc), fb.handle()); + } + } +} \ No newline at end of file diff --git a/src/backend/drm/error.rs b/src/backend/drm/legacy/error.rs similarity index 76% rename from src/backend/drm/error.rs rename to src/backend/drm/legacy/error.rs index 3c9b589..4e14eaf 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/legacy/error.rs @@ -1,8 +1,7 @@ //! -//! Errors thrown by the `DrmDevice` and `DrmBackend` +//! Errors thrown by the `LegacyDrmDevice` and `LegacyDrmSurface` //! -use backend::graphics::egl::error as egl; use drm::control::{connector, crtc, Mode}; error_chain! { @@ -23,18 +22,6 @@ error_chain! { description("Unable to determine device id of drm device"), } - #[doc = "Creation of gbm resource failed"] - GbmInitFailed { - description("Creation of gbm resource failed"), - display("Creation of gbm resource failed"), - } - - #[doc = "Swapping front buffers failed"] - FailedToSwap { - description("Swapping front buffers failed"), - display("Swapping front buffers failed"), - } - #[doc = "Device is currently paused"] DeviceInactive { description("Device is currently paused, operation rejected"), @@ -60,7 +47,7 @@ error_chain! { } } - links { - EGL(egl::Error, egl::ErrorKind) #[doc = "EGL error"]; + foreign_links { + FailedToSwap(::backend::graphics::SwapBuffersError) #[doc = "Swapping front buffers failed"]; } } diff --git a/src/backend/drm/legacy/mod.rs b/src/backend/drm/legacy/mod.rs new file mode 100644 index 0000000..166e664 --- /dev/null +++ b/src/backend/drm/legacy/mod.rs @@ -0,0 +1,257 @@ +use super::{Device, RawDevice, Surface, DeviceHandler, DevPath}; + +use drm::Device as BasicDevice; +use drm::control::{crtc, connector, encoder, Device as ControlDevice, Mode, ResourceInfo}; +use nix::libc::dev_t; +use nix::sys::stat::fstat; + +use std::cell::RefCell; +use std::collections::{HashMap, HashSet}; +use std::rc::{Rc, Weak}; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::sync::{Arc, RwLock}; +use std::sync::atomic::{AtomicBool, Ordering}; + +mod surface; +pub use self::surface::LegacyDrmSurface; +use self::surface::State; + +pub mod error; +use self::error::*; + +#[cfg(feature = "backend_session")] +pub mod session; + +pub struct LegacyDrmDevice { + dev: Rc>, + dev_id: dev_t, + priviledged: bool, + active: Arc, + old_state: HashMap)>, + backends: Rc>>>>, + handler: Option>>>>, + logger: ::slog::Logger, +} + +pub(in crate::backend::drm) struct Dev(A); +impl AsRawFd for Dev { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} +impl BasicDevice for Dev {} +impl ControlDevice for Dev {} + +impl LegacyDrmDevice { + /// Create a new `LegacyDrmDevice` 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(dev: A, logger: L) -> Result + where + L: Into>, + { + let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm")); + + let dev_id = fstat(dev.as_raw_fd()) + .chain_err(|| ErrorKind::UnableToGetDeviceId)? + .st_rdev; + + let mut drm = LegacyDrmDevice { + // Open the drm device and create a context based on that + dev: Rc::new(Dev(dev)), + dev_id, + priviledged: true, + active: Arc::new(AtomicBool::new(true)), + old_state: HashMap::new(), + backends: Rc::new(RefCell::new(HashMap::new())), + handler: None, + logger: log.clone(), + }; + + info!(log, "DrmDevice initializing"); + + // we want to modeset, so we better be the master, if we run via a tty session + if drm.set_master().is_err() { + warn!(log, "Unable to become drm master, assuming unpriviledged mode"); + drm.priviledged = false; + }; + + let res_handles = drm.resource_handles().chain_err(|| { + ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", drm.dev_path())) + })?; + for &con in res_handles.connectors() { + let con_info = connector::Info::load_from_device(&drm, con).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading connector info on {:?}", drm.dev_path())) + })?; + if let Some(enc) = con_info.current_encoder() { + let enc_info = encoder::Info::load_from_device(&drm, enc).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", drm.dev_path())) + })?; + if let Some(crtc) = enc_info.current_crtc() { + let info = crtc::Info::load_from_device(&drm, crtc).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading crtc info on {:?}", drm.dev_path())) + })?; + drm.old_state + .entry(crtc) + .or_insert((info, Vec::new())) + .1 + .push(con); + } + } + } + + Ok(drm) + } + + pub fn dev_id(&self) -> dev_t { + self.dev_id + } +} + +impl AsRawFd for LegacyDrmDevice { + fn as_raw_fd(&self) -> RawFd { + self.dev.0.as_raw_fd() + } +} + +impl BasicDevice for LegacyDrmDevice {} +impl ControlDevice for LegacyDrmDevice {} + +impl Device for LegacyDrmDevice { + type Surface = LegacyDrmSurface; + type Return = Rc>; + + 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: impl Into<::Connectors> + ) -> Result>> { + if self.backends.borrow().contains_key(&crtc) { + bail!(ErrorKind::CrtcAlreadyInUse(crtc)); + } + + if !self.active.load(Ordering::SeqCst) { + bail!(ErrorKind::DeviceInactive); + } + + let connectors: HashSet<_> = connectors.into(); + // check if we have an encoder for every connector and the mode mode + for connector in &connectors { + let con_info = connector::Info::load_from_device(self, *connector).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path())) + })?; + + // check the mode + if !con_info.modes().contains(&mode) { + bail!(ErrorKind::ModeNotSuitable(mode)); + } + + // check for every connector which encoders it does support + let encoders = con_info + .encoders() + .iter() + .map(|encoder| { + encoder::Info::load_from_device(self, *encoder).chain_err(|| { + ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path())) + }) + }).collect::>>()?; + + // and if any encoder supports the selected crtc + let resource_handles = self.resource_handles().chain_err(|| { + ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", self.dev_path())) + })?; + if !encoders + .iter() + .map(|encoder| encoder.possible_crtcs()) + .any(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&crtc)) + { + bail!(ErrorKind::NoSuitableEncoder(con_info, crtc)) + } + } + + // configuration is valid, the kernel will figure out the rest + let logger = self.logger.new(o!("crtc" => format!("{:?}", crtc))); + + let state = State { + mode, + connectors, + }; + + let backend = Rc::new(LegacyDrmSurface { + dev: self.dev.clone(), + crtc, + state: RwLock::new(state.clone()), + pending: RwLock::new(state), + logger, + }); + + self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend)); + Ok(backend) + } + + fn process_events(&mut self) { + match crtc::receive_events(self) { + Ok(events) => for event in events { + if let crtc::Event::PageFlip(event) = event { + if self.active.load(Ordering::SeqCst) { + if let Some(backend) = self.backends.borrow().get(&event.crtc).iter().flat_map(|x| x.upgrade()).next() { + trace!(self.logger, "Handling event for backend {:?}", event.crtc); + if let Some(handler) = self.handler.as_ref() { + handler.borrow_mut().vblank(&backend); + } + } else { + self.backends.borrow_mut().remove(&event.crtc); + } + } + } + }, + Err(err) => if let Some(handler) = self.handler.as_ref() { + handler.borrow_mut().error(ResultExt::<()>::chain_err(Err(err), || + ErrorKind::DrmDev(format!("Error processing drm events on {:?}", self.dev_path())) + ).unwrap_err()); + } + } + } +} + +impl RawDevice for LegacyDrmDevice { + type Surface = LegacyDrmSurface; +} + +impl Drop for LegacyDrmDevice { + fn drop(&mut self) { + self.backends.borrow_mut().clear(); + if Rc::strong_count(&self.dev) > 1 { + panic!("Pending DrmBackends. You need to free all backends before the DrmDevice gets destroyed"); + } + if self.active.load(Ordering::SeqCst) { + for (handle, (info, connectors)) in self.old_state.drain() { + if let Err(err) = crtc::set( + &*self.dev, + handle, + info.fb(), + &connectors, + info.position(), + info.mode(), + ) { + error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err); + } + } + if self.priviledged { + if let Err(err) = self.drop_master() { + error!(self.logger, "Failed to drop drm master state. Error: {}", err); + } + } + } + } +} diff --git a/src/backend/drm/legacy/session.rs b/src/backend/drm/legacy/session.rs new file mode 100644 index 0000000..bc80c09 --- /dev/null +++ b/src/backend/drm/legacy/session.rs @@ -0,0 +1,97 @@ +use drm::Device as BasicDevice; +use drm::control::{crtc, connector, Device as ControlDevice}; +use nix::libc::dev_t; +use nix::sys::stat; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::{Rc, Weak}; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::os::unix::io::{AsRawFd, RawFd}; + +use backend::session::{AsSessionObserver, SessionObserver}; +use super::{LegacyDrmDevice, LegacyDrmSurface, Dev}; + +/// `SessionObserver` linked to the `DrmDevice` it was created from. +pub struct LegacyDrmDeviceObserver { + dev: Weak>, + dev_id: dev_t, + priviledged: bool, + active: Arc, + old_state: HashMap)>, + backends: Weak>>>>, + logger: ::slog::Logger, +} + +impl AsSessionObserver> for LegacyDrmDevice { + fn observer(&mut self) -> LegacyDrmDeviceObserver { + LegacyDrmDeviceObserver { + dev: Rc::downgrade(&self.dev), + dev_id: self.dev_id, + old_state: self.old_state.clone(), + active: self.active.clone(), + priviledged: self.priviledged, + backends: Rc::downgrade(&self.backends), + logger: self.logger.clone(), + } + } +} + +impl SessionObserver for LegacyDrmDeviceObserver { + 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(Weak::upgrade) { + let _ = crtc::clear_cursor(&*device, surface.crtc); + } + } + for (handle, &(ref info, ref connectors)) in &self.old_state { + if let Err(err) = crtc::set( + &*device, + *handle, + info.fb(), + connectors, + info.position(), + info.mode(), + ) { + error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err); + } + } + } + self.active.store(false, Ordering::SeqCst); + if self.priviledged { + if let Some(device) = self.dev.upgrade() { + if let Err(err) = device.drop_master() { + 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.priviledged { + if let Some(device) = self.dev.upgrade() { + if let Err(err) = device.set_master() { + crit!(self.logger, "Failed to acquire drm master again. Error: {}", err); + } + } + } + } +} diff --git a/src/backend/drm/legacy/surface.rs b/src/backend/drm/legacy/surface.rs new file mode 100644 index 0000000..6f4b057 --- /dev/null +++ b/src/backend/drm/legacy/surface.rs @@ -0,0 +1,264 @@ +use drm::Device as BasicDevice; +use drm::control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, ResourceInfo}; +pub use drm::buffer::Buffer; + +use std::collections::HashSet; +use std::rc::Rc; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::sync::RwLock; + +use backend::drm::{RawSurface, Surface, DevPath}; +use backend::graphics::CursorBackend; +use backend::graphics::SwapBuffersError; + +use super::{Dev, error::*}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct State { + pub mode: Mode, + pub connectors: HashSet, +} + +pub struct LegacyDrmSurface { + pub(in super) dev: Rc>, + pub(in super) crtc: crtc::Handle, + pub(in super) state: RwLock, + pub(in super) pending: RwLock, + pub(in super) logger: ::slog::Logger, +} + +impl AsRawFd for LegacyDrmSurface { + fn as_raw_fd(&self) -> RawFd { + self.dev.as_raw_fd() + } +} + +impl BasicDevice for LegacyDrmSurface {} +impl ControlDevice for LegacyDrmSurface {} + +impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for LegacyDrmSurface { + type CursorFormat = &'a Buffer; + type Error = Error; + + fn set_cursor_position(&self, x: u32, y: u32) -> Result<()> { + trace!(self.logger, "Move the cursor to {},{}", x, y); + crtc::move_cursor(self, self.crtc, (x as i32, y as i32)).chain_err(|| { + ErrorKind::DrmDev(format!( + "Error moving cursor on {:?}", + self.dev_path() + )) + }) + } + + fn set_cursor_representation<'b>( + &'b self, + buffer: Self::CursorFormat, + hotspot: (u32, u32), + ) -> Result<()> + where 'a: 'b + { + trace!(self.logger, "Setting the new imported cursor"); + + if crtc::set_cursor2( + self, + self.crtc, + buffer, + (hotspot.0 as i32, hotspot.1 as i32), + ).is_err() + { + crtc::set_cursor(self, self.crtc, buffer).chain_err(|| { + ErrorKind::DrmDev(format!( + "Failed to set cursor on {:?}", + self.dev_path() + )) + })?; + } + + Ok(()) + } +} + +impl Surface for LegacyDrmSurface { + 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.clone() + } + + fn pending_mode(&self) -> Mode { + self.pending.read().unwrap().mode.clone() + } + + fn add_connector(&self, connector: connector::Handle) -> Result<()> { + let info = connector::Info::load_from_device(self, connector).chain_err(|| { + ErrorKind::DrmDev(format!( + "Error loading connector info on {:?}", + self.dev_path() + )) + })?; + + let mut pending = self.pending.write().unwrap(); + + // check if the connector can handle the current mode + if info.modes().contains(&pending.mode) { + // check if there is a valid encoder + let encoders = info + .encoders() + .iter() + .map(|encoder| { + encoder::Info::load_from_device(self, *encoder).chain_err(|| { + ErrorKind::DrmDev(format!( + "Error loading encoder info on {:?}", + self.dev_path() + )) + }) + }).collect::>>()?; + + // and if any encoder supports the selected crtc + let resource_handles = self.resource_handles().chain_err(|| { + ErrorKind::DrmDev(format!( + "Error loading resources on {:?}", + self.dev_path() + )) + })?; + if !encoders + .iter() + .map(|encoder| encoder.possible_crtcs()) + .all(|crtc_list| { + resource_handles + .filter_crtcs(crtc_list) + .contains(&self.crtc) + }) { + bail!(ErrorKind::NoSuitableEncoder(info, self.crtc)); + } + + pending.connectors.insert(connector); + Ok(()) + } else { + bail!(ErrorKind::ModeNotSuitable(pending.mode)); + } + } + + fn remove_connector(&self, connector: connector::Handle) -> Result<()> { + self.pending.write().unwrap().connectors.remove(&connector); + Ok(()) + } + + fn use_mode(&self, mode: Mode) -> Result<()> { + let mut pending = self.pending.write().unwrap(); + + // check the connectors + for connector in &pending.connectors { + if !connector::Info::load_from_device(self, *connector) + .chain_err(|| { + ErrorKind::DrmDev(format!( + "Error loading connector info on {:?}", + self.dev_path() + )) + })?.modes() + .contains(&mode) + { + bail!(ErrorKind::ModeNotSuitable(mode)); + } + } + + pending.mode = mode; + + Ok(()) + } +} + +impl RawSurface for LegacyDrmSurface { + fn commit_pending(&self) -> bool { + *self.pending.read().unwrap() != *self.state.read().unwrap() + } + + fn commit(&self, framebuffer: framebuffer::Handle) -> Result<()> { + 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); + + for conn in removed { + if let Ok(info) = connector::Info::load_from_device(self, *conn) { + info!( + self.logger, + "Removing connector: {:?}", + info.connector_type() + ); + } else { + info!(self.logger, "Removing unknown connector"); + } + } + + for conn in added { + if let Ok(info) = connector::Info::load_from_device(self, *conn) { + info!( + self.logger, + "Adding connector: {:?}", + info.connector_type() + ); + } else { + info!(self.logger, "Adding unknown connector"); + } + } + + if current.mode != pending.mode { + info!(self.logger, "Setting new mode: {:?}", pending.mode.name()); + } + } + + debug!(self.logger, "Setting screen"); + crtc::set( + self, + self.crtc, + framebuffer, + &pending.connectors.iter().map(|x| *x).collect::>(), + (0, 0), + Some(pending.mode), + ).chain_err(|| { + ErrorKind::DrmDev(format!( + "Error setting crtc {:?} on {:?}", + self.crtc, + self.dev_path() + )) + })?; + + *current = pending.clone(); + + Ok(()) + } + + fn page_flip(&self, framebuffer: framebuffer::Handle) -> ::std::result::Result<(), SwapBuffersError> { + trace!(self.logger, "Queueing Page flip"); + + crtc::page_flip( + self, + self.crtc, + framebuffer, + &[crtc::PageFlipFlags::PageFlipEvent], + ).map_err(|_| SwapBuffersError::ContextLost) + } +} + +impl Drop for LegacyDrmSurface { + fn drop(&mut self) { + // ignore failure at this point + let _ = crtc::clear_cursor(self, self.crtc); + } +} diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 39ec1cc..05f5e21 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -1,454 +1,77 @@ -//! Drm/Kms types and backend implementations -//! -//! This module provide a `DrmDevice` which acts as a representation for any DRM -//! device and can be used to create the second provided structure a `DrmBackend`. -//! -//! Initialization happens through the types provided by [`drm-rs`](https://docs.rs/drm/). -//! -//! Three entities are relevant for the initialization procedure. -//! -//! "Crtc"s represent scanout engines of the device pointer to one framebuffer. There 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. -//! -//! An "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. -//! -//! The last entity the "Connector" represents a port on your computer, possibly with a connected -//! monitor, TV, capture card, etc. -//! -//! The `DrmBackend` created from a `DrmDevice` represents a crtc of the device you can render to -//! and that feeds a given set of connectors, that can be manipulated at runtime. -//! -//! From these circumstances it becomes clear, that one crtc might only send it's data to a connector, -//! that is attached to any encoder that is attached to the crtc itself. It is the responsibility of the -//! user to ensure that a given set of a crtc with it's connectors is valid or an error will be thrown. -//! -//! For more details refer to the [`drm-rs` documentation](https://docs.rs/drm). -//! -//! -//! ## How to use it -//! -//! ### Initialization -//! -//! To initialize the `DrmDevice` you need either a `RawFd` or a `File` of -//! your DRM node. The `File` is recommended as it represents the save API. -//! -//! Once you got your `DrmDevice` you can then use it to create `DrmBackend`s. -//! You will need to use the `drm` crate to provide the required types to create -//! a backend. -//! -//! ```rust,no_run -//! extern crate drm; -//! extern crate smithay; -//! extern crate wayland_server; -//! -//! use drm::Device as BasicDevice; -//! use drm::control::{Device as ControlDevice, ResourceInfo}; -//! use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState}; -//! use drm::control::encoder::{Info as EncoderInfo}; -//! use std::fs::{File, OpenOptions}; -//! use std::os::unix::io::RawFd; -//! use std::os::unix::io::AsRawFd; -//! use smithay::backend::drm::{DrmDevice, DrmBackend}; -//! -//! #[derive(Debug)] -//! pub struct Card(File); -//! -//! impl AsRawFd for Card { -//! fn as_raw_fd(&self) -> RawFd { -//! self.0.as_raw_fd() -//! } -//! } -//! -//! impl BasicDevice for Card {} -//! impl ControlDevice for Card {} -//! -//! # fn main() { -//! // Open the drm device -//! let mut options = OpenOptions::new(); -//! options.read(true); -//! options.write(true); -//! let mut device = DrmDevice::new( -//! Card(options.open("/dev/dri/card0").unwrap()), // try to detect it properly -//! None /*put a logger here*/ -//! ).unwrap(); -//! -//! // Get a set of all modesetting resource handles -//! let res_handles = device.resource_handles().unwrap(); -//! -//! // Use first connected connector for this example -//! let connector_info = res_handles.connectors().iter() -//! .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap()) -//! .find(|conn| conn.connection_state() == ConnectorState::Connected) -//! .unwrap(); -//! -//! // Use the first encoder -//! let encoder_info = EncoderInfo::load_from_device(&device, connector_info.encoders()[0]).unwrap(); -//! -//! // use the connected crtc if any -//! let crtc = encoder_info.current_crtc() -//! // or use the first one that is compatible with the encoder -//! .unwrap_or_else(|| -//! *res_handles.filter_crtcs(encoder_info.possible_crtcs()) -//! .iter() -//! .next() -//! .unwrap()); -//! -//! // Use first mode (usually the highest resolution) -//! let mode = connector_info.modes()[0]; -//! -//! // Create the backend -//! let backend = device.create_backend( -//! crtc, -//! mode, -//! vec![connector_info.handle()] -//! ).unwrap(); -//! # } -//! ``` -//! -//! ### Page Flips / Tear-free video -//! Calling the usual `EglGraphicsBackend::swap_buffers` function on a -//! `DrmBackend` works the same to finish the rendering, but will return -//! `SwapBuffersError::AlreadySwapped` for any new calls until the page flip of the -//! crtc has happened. -//! -//! You can monitor the page flips by registering the `DrmDevice` as and -//! `FdEventSourceHandler` and setting a `DrmHandler` on it. You will be notified -//! whenever a page flip has happened, so you can render the next frame immediately -//! and get a tear-free representation on the display. -//! -//! You need to render at least once to successfully trigger the first event. -//! -//! ```rust,no_run -//! # extern crate drm; -//! # extern crate smithay; -//! # extern crate wayland_server; -//! # -//! # use drm::Device as BasicDevice; -//! # use drm::control::{Device as ControlDevice, ResourceInfo}; -//! # use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState}; -//! use drm::control::crtc::{Handle as CrtcHandle}; -//! use drm::result::Error as DrmError; -//! # use std::fs::{File, OpenOptions}; -//! # use std::os::unix::io::RawFd; -//! # use std::os::unix::io::AsRawFd; -//! # use std::time::Duration; -//! use smithay::backend::drm::{DrmDevice, DrmBackend, DrmHandler, drm_device_bind}; -//! use smithay::backend::graphics::egl::EGLGraphicsBackend; -//! # -//! # #[derive(Debug)] -//! # pub struct Card(File); -//! # impl AsRawFd for Card { -//! # fn as_raw_fd(&self) -> RawFd { -//! # self.0.as_raw_fd() -//! # } -//! # } -//! # impl BasicDevice for Card {} -//! # impl ControlDevice for Card {} -//! # -//! # fn main() { -//! # -//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap(); -//! # let mut display = wayland_server::Display::new(event_loop.handle()); -//! # -//! # let mut options = OpenOptions::new(); -//! # options.read(true); -//! # options.write(true); -//! # let mut device = DrmDevice::new( -//! # Card(options.open("/dev/dri/card0").unwrap()), // try to detect it properly -//! # None /*put a logger here*/ -//! # ).unwrap(); -//! # -//! # let res_handles = device.resource_handles().unwrap(); -//! # let connector_info = res_handles.connectors().iter() -//! # .map(|conn| ConnectorInfo::load_from_device(&device, *conn).unwrap()) -//! # .find(|conn| conn.connection_state() == ConnectorState::Connected) -//! # .unwrap(); -//! # let crtc = res_handles.crtcs()[0]; -//! # let mode = connector_info.modes()[0]; -//! # let backend = device.create_backend( -//! # crtc, -//! # mode, -//! # vec![connector_info.handle()] -//! # ).unwrap(); -//! -//! struct MyDrmHandler(DrmBackend); -//! -//! impl DrmHandler for MyDrmHandler { -//! fn ready( -//! &mut self, -//! _device: &mut DrmDevice, -//! _crtc: CrtcHandle, -//! _frame: u32, -//! _duration: Duration) -//! { -//! // render surfaces and swap again -//! self.0.swap_buffers().unwrap(); -//! } -//! fn error( -//! &mut self, -//! device: &mut DrmDevice, -//! error: DrmError) -//! { -//! panic!("DrmDevice errored: {}", error); -//! } -//! } -//! -//! // render something (like clear_color) -//! backend.swap_buffers().unwrap(); -//! -//! let (_source, _device_rc) = drm_device_bind( -//! &event_loop.handle(), -//! device, -//! MyDrmHandler(backend) -//! ).map_err(|(err, _)| err).unwrap(); -//! -//! /* And then run the event loop once all your setup is done */ -//! # } -//! ``` +use drm::Device as BasicDevice; +use drm::control::Device as ControlDevice; +pub use drm::control::crtc; +pub use drm::control::connector; +pub use drm::control::framebuffer; +pub use drm::control::Mode; -use backend::graphics::egl::{ - context::{EGLContext, GlAttributes}, - error::Result as EGLResult, - native::Gbm, - wayland::{EGLDisplay, EGLWaylandExtensions}, -}; -#[cfg(feature = "backend_session")] -use backend::session::{AsSessionObserver, SessionObserver}; -use drm::{ - control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, ResourceInfo}, - result::Error as DrmError, - Device as BasicDevice, -}; -use gbm::{BufferObject, Device as GbmDevice}; -use nix::{ - self, - sys::stat::{self, dev_t, fstat}, -}; -use std::{ - cell::RefCell, - collections::HashMap, - hash::{Hash, Hasher}, - io::Error as IoError, - os::unix::io::{AsRawFd, RawFd}, - path::PathBuf, - rc::{Rc, Weak}, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Once, ONCE_INIT, - }, - time::Duration, -}; +use std::borrow::Borrow; +use std::error::Error; +use std::path::PathBuf; +use std::os::unix::io::AsRawFd; -use wayland_server::{ - calloop::{ - generic::{EventedRawFd, Generic}, - mio::Ready, - LoopHandle, Source, - }, - Display, -}; +use wayland_server::calloop::generic::{EventedFd, Generic}; +use wayland_server::calloop::{LoopHandle, Source}; +use wayland_server::calloop::mio::Ready; +pub use wayland_server::calloop::InsertError; -mod backend; -pub mod error; +use super::graphics::SwapBuffersError; -pub use self::backend::DrmBackend; -use self::{backend::DrmBackendInternal, error::*}; +#[cfg(feature = "backend_drm_legacy")] +pub mod legacy; +#[cfg(feature = "backend_drm_gbm")] +pub mod gbm; +#[cfg(feature = "backend_drm_egl")] +pub mod egl; -static LOAD: Once = ONCE_INIT; - -/// Representation of an open DRM device node to create rendering backends -pub struct DrmDevice { - context: Rc, GbmDevice>>, - old_state: HashMap)>, - device_id: dev_t, - backends: Rc>>>>, - active: Arc, - privileged: bool, - logger: ::slog::Logger, +pub trait DeviceHandler { + type Device: Device + ?Sized; + fn vblank(&mut self, surface: &<::Device as Device>::Surface); + fn error(&mut self, error: <<::Device as Device>::Surface as Surface>::Error); } -impl DrmDevice { - /// Create a new `DrmDevice` 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(dev: A, logger: L) -> Result - where - L: Into>, - { - DrmDevice::new_with_gl_attr( - dev, - GlAttributes { - version: None, - profile: None, - debug: cfg!(debug_assertions), - vsync: true, - }, - logger, - ) - } +pub trait Device: AsRawFd + DevPath { + type Surface: Surface; + type Return: Borrow; - /// Create a new `DrmDevice` from an open DRM node and given `GlAttributes` - /// - /// Returns an error if the file is no valid DRM node or context creation was not - /// successful. - pub fn new_with_gl_attr(dev: A, attributes: GlAttributes, logger: L) -> Result - where - L: Into>, - { - let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm")); - - /* 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 device_id = fstat(dev.as_raw_fd()) - .chain_err(|| ErrorKind::UnableToGetDeviceId)? - .st_rdev; - - let mut drm = DrmDevice { - // Open the gbm device from the DRM device and create a context based on that - context: Rc::new( - EGLContext::new( - { - debug!(log, "Creating gbm device"); - let gbm = GbmDevice::new(dev).chain_err(|| ErrorKind::GbmInitFailed)?; - debug!(log, "Creating egl context from gbm device"); - gbm - }, - attributes, - Default::default(), - log.clone(), - ).map_err(Error::from)?, - ), - backends: Rc::new(RefCell::new(HashMap::new())), - device_id, - old_state: HashMap::new(), - active: Arc::new(AtomicBool::new(true)), - privileged: true, - logger: log.clone(), - }; - - info!(log, "DrmDevice initializing"); - - // we want to mode-set, so we better be the master, if we run via a tty session - if drm.set_master().is_err() { - warn!(log, "Unable to become drm master, assuming unprivileged mode"); - drm.privileged = false; - }; - - let res_handles = drm.resource_handles().chain_err(|| { - ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", drm.dev_path())) - })?; - for &con in res_handles.connectors() { - let con_info = connector::Info::load_from_device(&drm, con).chain_err(|| { - ErrorKind::DrmDev(format!("Error loading connector info on {:?}", drm.dev_path())) - })?; - if let Some(enc) = con_info.current_encoder() { - let enc_info = encoder::Info::load_from_device(&drm, enc).chain_err(|| { - ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", drm.dev_path())) - })?; - if let Some(crtc) = enc_info.current_crtc() { - let info = crtc::Info::load_from_device(&drm, crtc).chain_err(|| { - ErrorKind::DrmDev(format!("Error loading crtc info on {:?}", drm.dev_path())) - })?; - drm.old_state - .entry(crtc) - .or_insert((info, Vec::new())) - .1 - .push(con); - } - } - } - - Ok(drm) - } - - /// Create a new backend on a given crtc with a given `Mode` for a given amount - /// of `connectors` (mirroring). - /// - /// Errors if initialization fails or the mode is not available on all given - /// connectors. - pub fn create_backend( + fn set_handler(&mut self, handler: impl DeviceHandler + 'static); + fn clear_handler(&mut self); + fn create_surface( &mut self, - crtc: crtc::Handle, + ctrc: crtc::Handle, mode: Mode, - connectors: I, - ) -> Result> - where - I: Into>, - { - if self.backends.borrow().contains_key(&crtc) { - bail!(ErrorKind::CrtcAlreadyInUse(crtc)); - } - - if !self.active.load(Ordering::SeqCst) { - bail!(ErrorKind::DeviceInactive); - } - - // check if the given connectors and crtc match - let connectors = connectors.into(); - - // check if we have an encoder for every connector and the mode mode - for connector in &connectors { - let con_info = connector::Info::load_from_device(self, *connector).chain_err(|| { - ErrorKind::DrmDev(format!("Error loading connector info on {:?}", self.dev_path())) - })?; - - // check the mode - if !con_info.modes().contains(&mode) { - bail!(ErrorKind::ModeNotSuitable(mode)); - } - - // check for every connector which encoders it does support - let encoders = con_info - .encoders() - .iter() - .map(|encoder| { - encoder::Info::load_from_device(self, *encoder).chain_err(|| { - ErrorKind::DrmDev(format!("Error loading encoder info on {:?}", self.dev_path())) - }) - }).collect::>>()?; - - // and if any encoder supports the selected crtc - let resource_handles = self.resource_handles().chain_err(|| { - ErrorKind::DrmDev(format!("Error loading drm resources on {:?}", self.dev_path())) - })?; - if !encoders - .iter() - .map(|encoder| encoder.possible_crtcs()) - .any(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&crtc)) - { - bail!(ErrorKind::NoSuitableEncoder(con_info, crtc)) - } - } - - // configuration is valid, the kernel will figure out the rest - - let logger = self.logger.new(o!("crtc" => format!("{:?}", crtc))); - let backend = DrmBackend::new(self.context.clone(), crtc, mode, connectors, logger)?; - self.backends.borrow_mut().insert(crtc, backend.weak()); - Ok(backend) - } - - /// Returns an internal device id, that is unique per boot per system - pub fn device_id(&self) -> u64 { - self.device_id - } + connectors: impl Into<::Connectors> + ) -> Result::Error>; + fn process_events(&mut self); } +pub trait RawDevice: Device::Surface> +where + ::Return: Borrow<::Surface> +{ + type Surface: RawSurface; +} + +pub trait Surface { + type Connectors: IntoIterator; + type Error: Error + Send; + + fn crtc(&self) -> crtc::Handle; + fn current_connectors(&self) -> Self::Connectors; + fn pending_connectors(&self) -> Self::Connectors; + fn add_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>; + fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>; + fn current_mode(&self) -> Mode; + fn pending_mode(&self) -> Mode; + fn use_mode(&self, mode: Mode) -> Result<(), Self::Error>; +} + +pub trait RawSurface: Surface + ControlDevice + BasicDevice { + fn commit_pending(&self) -> bool; + fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), ::Error>; + fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError>; +} + /// Trait for types representing open devices pub trait DevPath { /// Returns the path of the open device if possible @@ -459,247 +82,25 @@ impl DevPath for A { fn dev_path(&self) -> Option { use std::fs; - fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok() + fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok() } } -impl PartialEq for DrmDevice { - fn eq(&self, other: &DrmDevice) -> bool { - self.device_id == other.device_id - } -} -impl Eq for DrmDevice {} - -impl Hash for DrmDevice { - fn hash(&self, state: &mut H) { - self.device_id.hash(state); - } -} - -// for users convenience and FdEventSource registering -impl AsRawFd for DrmDevice { - fn as_raw_fd(&self) -> RawFd { - self.context.as_raw_fd() - } -} - -impl BasicDevice for DrmDevice {} -impl ControlDevice for DrmDevice {} - -impl EGLWaylandExtensions for DrmDevice { - fn bind_wl_display(&self, display: &Display) -> EGLResult { - self.context.bind_wl_display(display) - } -} - -impl Drop for DrmDevice { - fn drop(&mut self) { - if Rc::strong_count(&self.context) > 1 { - panic!("Pending DrmBackends. You need to free all backends before the DrmDevice gets destroyed"); - } - for (handle, (info, connectors)) in self.old_state.drain() { - if let Err(err) = crtc::set( - &*self.context, - handle, - info.fb(), - &connectors, - info.position(), - info.mode(), - ) { - error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err); - } - } - if self.privileged { - if let Err(err) = self.drop_master() { - error!(self.logger, "Failed to drop drm master state. Error: {}", err); - } - } - } -} - -/// Handler for DRM node events +/// Bind a `Device` to an `EventLoop`, /// -/// See module-level documentation for its use -pub trait DrmHandler { - /// The `DrmBackend` of crtc has finished swapping buffers and new frame can now - /// (and should be immediately) be rendered. - fn ready(&mut self, device: &mut DrmDevice, crtc: crtc::Handle, frame: u32, duration: Duration); - /// The `DrmDevice` has thrown an error. - /// - /// The related backends are most likely *not* usable anymore and - /// the whole stack has to be recreated.. - fn error(&mut self, device: &mut DrmDevice, error: DrmError); -} - -/// Bind a `DrmDevice` to an `EventLoop`, -/// -/// This will cause it to recieve events and feed them into an `DrmHandler` -pub fn drm_device_bind( +/// This will cause it to recieve events and feed them into an `DeviceHandler` +pub fn device_bind( handle: &LoopHandle, - device: DrmDevice, - mut handler: H, -) -> ::std::result::Result<(Source>, Rc>>), (IoError, DrmDevice)> + device: D, +) -> ::std::result::Result>>, InsertError>>> where - A: ControlDevice + 'static, - H: DrmHandler + 'static, + D: Device, + Data: 'static, { - let fd = device.as_raw_fd(); - let device = Rc::new(RefCell::new(device)); - - let mut source = Generic::from_raw_fd(fd); + let mut source = Generic::from_fd_source(device); source.set_interest(Ready::readable()); - match handle.insert_source(source, { - let device = device.clone(); - move |_evt, _| { - let mut device = device.borrow_mut(); - process_events(&mut *device, &mut handler); - } - }) { - Ok(source) => Ok((source, device)), - Err(e) => { - let device = Rc::try_unwrap(device).unwrap_or_else(|_| unreachable!()); - Err((e.into(), device.into_inner())) - } - } -} - -fn process_events(device: &mut DrmDevice, handler: &mut H) -where - A: ControlDevice + 'static, - H: DrmHandler + 'static, -{ - match crtc::receive_events(&*device) { - Ok(events) => for event in events { - if let crtc::Event::PageFlip(event) = event { - if device.active.load(Ordering::SeqCst) { - let backends = device.backends.borrow().clone(); - if let Some(backend) = backends.get(&event.crtc).iter().flat_map(|x| x.upgrade()).next() { - // we can now unlock the buffer - backend.unlock_buffer(); - trace!(device.logger, "Handling event for backend {:?}", event.crtc); - // and then call the user to render the next frame - handler.ready(device, event.crtc, event.frame, event.duration); - } else { - device.backends.borrow_mut().remove(&event.crtc); - } - } - } - }, - Err(err) => handler.error(device, err), - } -} - -/// `SessionObserver` linked to the `DrmDevice` it was created from. -pub struct DrmDeviceObserver { - context: Weak, GbmDevice>>, - device_id: dev_t, - backends: Rc>>>>, - old_state: HashMap)>, - active: Arc, - privileged: bool, - logger: ::slog::Logger, -} - -#[cfg(feature = "backend_session")] -impl AsSessionObserver> for DrmDevice { - fn observer(&mut self) -> DrmDeviceObserver { - DrmDeviceObserver { - context: Rc::downgrade(&self.context), - device_id: self.device_id, - backends: self.backends.clone(), - old_state: self.old_state.clone(), - active: self.active.clone(), - privileged: self.privileged, - logger: self.logger.clone(), - } - } -} - -#[cfg(feature = "backend_session")] -impl SessionObserver for DrmDeviceObserver { - fn pause(&mut self, devnum: Option<(u32, u32)>) { - if let Some((major, minor)) = devnum { - if major as u64 != stat::major(self.device_id) || minor as u64 != stat::minor(self.device_id) { - return; - } - } - if let Some(device) = self.context.upgrade() { - for (handle, &(ref info, ref connectors)) in &self.old_state { - if let Err(err) = crtc::set( - &*device, - *handle, - info.fb(), - connectors, - info.position(), - info.mode(), - ) { - error!(self.logger, "Failed to reset crtc ({:?}). Error: {}", handle, err); - } - } - } - self.active.store(false, Ordering::SeqCst); - if self.privileged { - if let Some(device) = self.context.upgrade() { - if let Err(err) = device.drop_master() { - 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.device_id) || minor as u64 != stat::minor(self.device_id) { - return; - } else if let Some(fd) = fd { - info!(self.logger, "Replacing fd"); - if let Some(device) = self.context.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.context.upgrade() { - if let Err(err) = device.set_master() { - crit!(self.logger, "Failed to acquire drm master again. Error: {}", err); - } - } - } - let mut crtcs = Vec::new(); - for (crtc, backend) in self.backends.borrow().iter() { - if let Some(backend) = backend.upgrade() { - backend.unlock_buffer(); - if let Err(err) = backend.page_flip(None) { - error!( - self.logger, - "Failed to activate crtc ({:?}) again. Error: {}", crtc, err - ); - } - // reset cursor - { - let &(ref cursor, ref hotspot): &(BufferObject<()>, (u32, u32)) = - unsafe { &*backend.cursor.as_ptr() }; - if crtc::set_cursor2( - &*backend.context, - *crtc, - cursor, - ((*hotspot).0 as i32, (*hotspot).1 as i32), - ).is_err() - { - if let Err(err) = crtc::set_cursor(&*backend.context, *crtc, cursor) { - error!(self.logger, "Failed to reset cursor. Error: {}", err); - } - } - } - } else { - crtcs.push(*crtc); - } - } - for crtc in crtcs { - self.backends.borrow_mut().remove(&crtc); - } - } + handle.insert_source(source, |evt, _| { + evt.source.borrow_mut().0.process_events(); + }) }