From f2bff6172b08920f57cf8c6fbb7636fcdcdb4cfb Mon Sep 17 00:00:00 2001 From: Drakulix Date: Thu, 14 Sep 2017 14:00:11 +0200 Subject: [PATCH] drm: Documentation --- src/backend/drm/backend.rs | 22 ++++ src/backend/drm/error.rs | 13 ++- src/backend/drm/mod.rs | 196 +++++++++++++++++++++++++++++++++++- src/backend/graphics/egl.rs | 22 ++-- src/backend/graphics/mod.rs | 1 + src/backend/mod.rs | 1 + 6 files changed, 240 insertions(+), 15 deletions(-) diff --git a/src/backend/drm/backend.rs b/src/backend/drm/backend.rs index b821a70..ef16315 100644 --- a/src/backend/drm/backend.rs +++ b/src/backend/drm/backend.rs @@ -18,6 +18,7 @@ use nix::c_void; use std::cell::{Cell, RefCell}; use std::rc::Rc; +/// Backend based on a `DrmDevice` and a given crtc pub struct DrmBackend(Rc>); impl DrmBackend { @@ -76,6 +77,9 @@ rental! { } use self::graphics::{Graphics, Surface}; +/// Id of a `DrmBackend` related to its `DrmDevice`. +/// +/// Used to track which `DrmBackend` finished page-flipping #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Id(usize); @@ -195,6 +199,11 @@ impl DrmBackendInternal { } impl DrmBackend { + /// 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<(), ModeError> { let info = connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), connector) @@ -208,14 +217,23 @@ impl DrmBackend { } } + /// Returns a copy of the currently set connectors pub fn used_connectors(&self) -> Vec { self.0.borrow().connectors.clone() } + /// Removes a currently set connector pub fn remove_connector(&mut self, connector: connector::Handle) { self.0.borrow_mut().connectors.retain(|x| *x != connector); } + /// 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<(), DrmError> { for connector in self.0.borrow().connectors.iter() { if !connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), *connector)? @@ -278,6 +296,10 @@ impl DrmBackend { Ok(()) } + /// Checks of the `DrmBackend` is of the given `Id` + /// + /// Only produces valid results, if the `Id` is from the `DrmDevice`, + /// that created this backend. pub fn is(&self, id: Id) -> bool { self.0.borrow().own_id == id } diff --git a/src/backend/drm/error.rs b/src/backend/drm/error.rs index 9110671..c689ee2 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/error.rs @@ -1,5 +1,3 @@ - - use backend::graphics::egl::{CreationError, SwapBuffersError}; use drm::result::Error as DrmError; use gbm::FrontBufferError; @@ -9,13 +7,21 @@ use std::error::{self, Error as ErrorTrait}; use std::fmt; use std::io::Error as IoError; +/// Error summing up error types related to all underlying libraries +/// involved in creating the a `DrmDevice`/`DrmBackend` #[derive(Debug)] pub enum Error { + /// The `DrmDevice` has encountered an error on an ioctl Drm(DrmError), + /// The `EGLContext` could not be created EGLCreation(CreationError), + /// Swapping Buffers via EGL was not possible EGLSwap(SwapBuffersError), + /// Locking the front buffer of the underlying `GbmSurface` failed Gbm(FrontBufferError), + /// A generic IO-Error happened accessing the underlying devices Io(IoError), + /// Selected an invalid Mode Mode(ModeError), } @@ -98,9 +104,12 @@ impl From> for Error { } } +/// Error when trying to select an invalid mode #[derive(Debug)] pub enum ModeError { + /// `Mode` is not compatible with all given connectors ModeNotSuitable, + /// Failed to load `Mode` information FailedToLoad(DrmError), } diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 68de54b..e11bc65 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -1,4 +1,144 @@ - +//! Drm/Kms types and backend implementations +//! +//! This module provide a `DrmDevice` which acts as a reprensentation for any drm +//! device and can be used to create the second provided structure a `DrmBackend`. +//! +//! The latter represents a crtc of the graphics card you can render to. +//! +//! +//! ## 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,ignore +//! extern crate drm; +//! extern crate smithay; +//! # extern crate wayland_server; +//! +//! use drm::control::{Device as ControlDevice, ResourceInfo}; +//! use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState}; +//! use std::fs::OpenOptions; +//! use smithay::backend::drm::DrmDevice; +//! +//! # fn main() { +//! // Open the drm device +//! let mut options = OpenOptions::new(); +//! options.read(true); +//! options.write(true); +//! let mut device = DrmDevice::new_from_file( +//! 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 first crtc (should be successful in most cases) +//! let crtc = res_handles.crtcs()[0]; +//! +//! // 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 happend, so you can render the next frame immediately +//! and get a tear-free reprensentation 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::control::{Device as ControlDevice, ResourceInfo}; +//! # use drm::control::connector::{Info as ConnectorInfo, State as ConnectorState}; +//! use std::io::Error as IoError; +//! use std::os::unix::io::AsRawFd; +//! # use std::fs::OpenOptions; +//! # use std::time::Duration; +//! use smithay::backend::drm::{DrmDevice, DrmBackend, DrmHandler, Id}; +//! use smithay::backend::graphics::egl::EGLGraphicsBackend; +//! use wayland_server::sources::READ; +//! # use wayland_server::EventLoopHandle; +//! # +//! # fn main() { +//! # +//! # let (_display, mut event_loop) = wayland_server::create_display(); +//! # +//! # let mut options = OpenOptions::new(); +//! # options.read(true); +//! # options.write(true); +//! # let mut device = DrmDevice::new_from_file( +//! # 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, _: &mut EventLoopHandle, id: Id, _frame: u32, _duration: Duration) { +//! if self.0.is(id) { // check id in case you got multiple backends +//! // ... render surfaces ... +//! self.0.swap_buffers().unwrap(); // trigger the swap +//! } +//! } +//! fn error(&mut self, _: &mut EventLoopHandle, error: IoError) { +//! panic!("DrmDevice errored: {}", error); +//! } +//! } +//! +//! // render something (like clear_color) +//! backend.swap_buffers().unwrap(); +//! +//! device.set_handler(MyDrmHandler(backend)); +//! let fd = device.as_raw_fd(); +//! let drm_device_id = event_loop.add_handler(device); +//! let _drm_event_source = event_loop.add_fd_event_source::>(fd, drm_device_id, READ); +//! +//! event_loop.run().unwrap(); +//! # } +//! ``` use backend::graphics::egl::{EGLContext, GlAttributes, PixelFormatRequirements}; use drm::Device as BasicDevice; @@ -26,6 +166,7 @@ pub use self::backend::{DrmBackend, Id}; use self::backend::DrmBackendInternal; pub use self::error::{Error as DrmError, ModeError}; +/// Internal struct as required by the drm crate #[derive(Debug)] pub(crate) struct DrmDev(File); @@ -72,8 +213,7 @@ rental! { } use self::devices::{Context, Devices}; - -// odd naming, but makes sense for the user +/// Representation of an open drm device node to create rendering backends pub struct DrmDevice { context: Rc, backends: Vec>>, @@ -82,6 +222,14 @@ pub struct DrmDevice { } impl DrmDevice { + /// Create a new `DrmDevice` from a raw file descriptor + /// + /// Returns an error of opening the device failed or context creation was not + /// successful. + /// + /// # Safety + /// The file descriptor might not be valid and needs to be owned by smithay, + /// make sure not to share it. Otherwise undefined behavior might occur. pub unsafe fn new_from_fd(fd: RawFd, logger: L) -> Result where L: Into>, @@ -98,6 +246,14 @@ impl DrmDevice { ) } + /// Create a new `DrmDevice` from a raw file descriptor and given `GlAttributes` + /// + /// Returns an error of opening the device failed or context creation was not + /// successful. + /// + /// # Safety + /// The file descriptor might not be valid and needs to be owned by smithay, + /// make sure not to share it. Otherwise undefined behavior might occur. pub unsafe fn new_from_fd_with_gl_attr(fd: RawFd, attributes: GlAttributes, logger: L) -> Result where @@ -106,6 +262,10 @@ impl DrmDevice { DrmDevice::new(DrmDev::new_from_fd(fd), attributes, logger) } + /// Create a new `DrmDevice` from a `File` of an open drm node + /// + /// Returns an error if the file is no valid drm node or context creation was not + /// successful. pub fn new_from_file(file: File, logger: L) -> Result where L: Into>, @@ -122,6 +282,10 @@ impl DrmDevice { ) } + /// Create a new `DrmDevice` from a `File` of 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_from_file_with_gl_attr(file: File, attributes: GlAttributes, logger: L) -> Result where @@ -149,6 +313,7 @@ impl DrmDevice { ); } + // Open the gbm device from the drm device and create a context based on that Ok(DrmDevice { context: Rc::new(Context::try_new( Box::new(Devices::try_new(Box::new(drm), |drm| { @@ -174,6 +339,11 @@ impl DrmDevice { }) } + /// 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(&mut self, crtc: crtc::Handle, mode: Mode, connectors: I) -> Result where @@ -183,6 +353,8 @@ impl DrmDevice { .new(o!("drm" => "backend", "crtc" => format!("{:?}", crtc))); let own_id = self.backends.len(); + // TODO: Make sure we do not initialize the same crtc multiple times + // (check weak pointers and return an error otherwise) let backend = Rc::new(RefCell::new(DrmBackendInternal::new( self.context.clone(), crtc, @@ -197,18 +369,20 @@ impl DrmDevice { Ok(DrmBackend::new(backend)) } + /// Set a handler for handling finished rendering pub fn set_handler(&mut self, handler: H) -> Option { let res = self.handler.take(); self.handler = Some(handler); res } + /// Clear the currently set handler pub fn clear_handler(&mut self) -> Option { self.handler.take() } } -// for users convinience +// for users convinience and FdEventSource registering impl AsRawFd for DrmDevice { fn as_raw_fd(&self) -> RawFd { self.context.head().head().as_raw_fd() @@ -217,8 +391,20 @@ impl AsRawFd for DrmDevice { impl BasicDevice for DrmDevice {} impl ControlDevice for DrmDevice {} +/// Handler for drm node events +/// +/// See module-level documentation for its use pub trait DrmHandler { + /// A `DrmBackend` has finished swapping buffers and new frame can now + /// (and should be immediately) be rendered. + /// + /// The `id` argument is the `Id` of the `DrmBackend` that finished rendering, + /// check using `DrmBackend::is`. fn ready(&mut self, evlh: &mut EventLoopHandle, id: Id, 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, evlh: &mut EventLoopHandle, error: IoError); } @@ -245,8 +431,10 @@ impl FdEventSourceHandler for DrmDevice { userdata: Box) { let id: Id = *userdata.downcast().unwrap(); if let Some(backend) = self.0.backends[id.raw()].upgrade() { + // we can now unlock the buffer backend.borrow().unlock_buffer(); if let Some(handler) = self.0.handler.as_mut() { + // and then call the user to render the next frame handler.ready(self.1, id, frame, duration); } } diff --git a/src/backend/graphics/egl.rs b/src/backend/graphics/egl.rs index fc91fe9..d08c8e4 100644 --- a/src/backend/graphics/egl.rs +++ b/src/backend/graphics/egl.rs @@ -5,15 +5,12 @@ /// /// It therefore falls under glutin's Apache 2.0 license /// (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE) - use super::GraphicsBackend; #[cfg(feature = "backend_drm")] use gbm::{AsRaw, Device as GbmDevice, Surface as GbmSurface}; - use libloading::Library; use nix::{c_int, c_void}; use slog; - use std::error::{self, Error}; use std::ffi::{CStr, CString}; use std::fmt; @@ -22,7 +19,6 @@ use std::marker::PhantomData; use std::mem; use std::ops::{Deref, DerefMut}; use std::ptr; - #[cfg(feature = "backend_winit")] use wayland_client::egl as wegl; #[cfg(feature = "backend_winit")] @@ -77,10 +73,14 @@ pub enum NativeSurfacePtr { Gbm(ffi::NativeWindowType), } +/// Enumerates all supported backends #[derive(Clone, Copy, PartialEq)] pub enum NativeType { + /// X11 window & surface X11, + /// Wayland surface Wayland, + /// Gbm surface Gbm, } @@ -145,9 +145,16 @@ impl error::Error for CreationError { } } +/// Trait for supported types returning valid surface pointers for initializing egl +/// +/// # Safety +/// The returned `NativeSurfacePtr` must be valid for egl +/// and there is no way to test that. pub unsafe trait NativeSurface { + /// Type to keep the surface valid, if needed type Keep: 'static; + /// Return a surface for the given type if possible fn surface(&self, backend: NativeType) -> Result<(NativeSurfacePtr, Self::Keep), CreationError>; } @@ -214,6 +221,7 @@ pub struct EGLContext<'a, T: NativeSurface> { } impl<'a> EGLContext<'a, ()> { + /// Create a new context from a given `winit`-`Window` #[cfg(feature = "backend_winit")] pub fn new_from_winit(window: &'a WinitWindow, attributes: GlAttributes, reqs: PixelFormatRequirements, logger: L) @@ -243,6 +251,7 @@ impl<'a> EGLContext<'a, ()> { } } + /// Create a new context from a given `gbm::Device` #[cfg(feature = "backend_drm")] pub fn new_from_gbm(gbm: &'a GbmDevice<'a>, attributes: GlAttributes, reqs: PixelFormatRequirements, logger: L) @@ -700,11 +709,6 @@ impl<'a, T: NativeSurface> EGLContext<'a, T> { } /// Creates a surface bound to the given egl context for rendering - /// - /// # Unsafety - /// - /// This method is marked unsafe, because the contents of `NativeSurface` cannot be verified and may - /// contain dangling pointers or similar unsafe content pub fn create_surface<'b>(&'a self, native: &'b T) -> Result, CreationError> { trace!(self.logger, "Creating EGL window surface..."); diff --git a/src/backend/graphics/mod.rs b/src/backend/graphics/mod.rs index 5aa3238..0a082d7 100644 --- a/src/backend/graphics/mod.rs +++ b/src/backend/graphics/mod.rs @@ -8,6 +8,7 @@ pub trait GraphicsBackend { /// Format representing the image drawn for the cursor. type CursorFormat; + /// Error the underlying backend throws if operations fail type Error; /// Sets the cursor position and therefor updates the drawn cursors position. diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 6835c02..09bb18f 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -7,6 +7,7 @@ //! Supported graphics backends: //! //! - winit +//! - drm //! //! Supported input backends: //!