drm: Documentation

This commit is contained in:
Drakulix 2017-09-14 14:00:11 +02:00
parent 0698775153
commit f2bff6172b
6 changed files with 240 additions and 15 deletions

View File

@ -18,6 +18,7 @@ use nix::c_void;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::rc::Rc; use std::rc::Rc;
/// Backend based on a `DrmDevice` and a given crtc
pub struct DrmBackend(Rc<RefCell<DrmBackendInternal>>); pub struct DrmBackend(Rc<RefCell<DrmBackendInternal>>);
impl DrmBackend { impl DrmBackend {
@ -76,6 +77,9 @@ rental! {
} }
use self::graphics::{Graphics, Surface}; 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Id(usize); pub struct Id(usize);
@ -195,6 +199,11 @@ impl DrmBackendInternal {
} }
impl DrmBackend { 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> { pub fn add_connector(&mut self, connector: connector::Handle) -> Result<(), ModeError> {
let info = let info =
connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), connector) 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<connector::Handle> { pub fn used_connectors(&self) -> Vec<connector::Handle> {
self.0.borrow().connectors.clone() self.0.borrow().connectors.clone()
} }
/// Removes a currently set connector
pub fn remove_connector(&mut self, connector: connector::Handle) { pub fn remove_connector(&mut self, connector: connector::Handle) {
self.0.borrow_mut().connectors.retain(|x| *x != connector); 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> { pub fn use_mode(&mut self, mode: Mode) -> Result<(), DrmError> {
for connector in self.0.borrow().connectors.iter() { for connector in self.0.borrow().connectors.iter() {
if !connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), *connector)? if !connector::Info::load_from_device(self.0.borrow().graphics.head().head().head(), *connector)?
@ -278,6 +296,10 @@ impl DrmBackend {
Ok(()) 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 { pub fn is(&self, id: Id) -> bool {
self.0.borrow().own_id == id self.0.borrow().own_id == id
} }

View File

@ -1,5 +1,3 @@
use backend::graphics::egl::{CreationError, SwapBuffersError}; use backend::graphics::egl::{CreationError, SwapBuffersError};
use drm::result::Error as DrmError; use drm::result::Error as DrmError;
use gbm::FrontBufferError; use gbm::FrontBufferError;
@ -9,13 +7,21 @@ use std::error::{self, Error as ErrorTrait};
use std::fmt; use std::fmt;
use std::io::Error as IoError; 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)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// The `DrmDevice` has encountered an error on an ioctl
Drm(DrmError), Drm(DrmError),
/// The `EGLContext` could not be created
EGLCreation(CreationError), EGLCreation(CreationError),
/// Swapping Buffers via EGL was not possible
EGLSwap(SwapBuffersError), EGLSwap(SwapBuffersError),
/// Locking the front buffer of the underlying `GbmSurface` failed
Gbm(FrontBufferError), Gbm(FrontBufferError),
/// A generic IO-Error happened accessing the underlying devices
Io(IoError), Io(IoError),
/// Selected an invalid Mode
Mode(ModeError), Mode(ModeError),
} }
@ -98,9 +104,12 @@ impl<H> From<TryNewError<Error, H>> for Error {
} }
} }
/// Error when trying to select an invalid mode
#[derive(Debug)] #[derive(Debug)]
pub enum ModeError { pub enum ModeError {
/// `Mode` is not compatible with all given connectors
ModeNotSuitable, ModeNotSuitable,
/// Failed to load `Mode` information
FailedToLoad(DrmError), FailedToLoad(DrmError),
} }

View File

@ -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::<DrmDevice<MyDrmHandler>>(fd, drm_device_id, READ);
//!
//! event_loop.run().unwrap();
//! # }
//! ```
use backend::graphics::egl::{EGLContext, GlAttributes, PixelFormatRequirements}; use backend::graphics::egl::{EGLContext, GlAttributes, PixelFormatRequirements};
use drm::Device as BasicDevice; use drm::Device as BasicDevice;
@ -26,6 +166,7 @@ pub use self::backend::{DrmBackend, Id};
use self::backend::DrmBackendInternal; use self::backend::DrmBackendInternal;
pub use self::error::{Error as DrmError, ModeError}; pub use self::error::{Error as DrmError, ModeError};
/// Internal struct as required by the drm crate
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct DrmDev(File); pub(crate) struct DrmDev(File);
@ -72,8 +213,7 @@ rental! {
} }
use self::devices::{Context, Devices}; use self::devices::{Context, Devices};
/// Representation of an open drm device node to create rendering backends
// odd naming, but makes sense for the user
pub struct DrmDevice<H: DrmHandler + 'static> { pub struct DrmDevice<H: DrmHandler + 'static> {
context: Rc<Context>, context: Rc<Context>,
backends: Vec<Weak<RefCell<DrmBackendInternal>>>, backends: Vec<Weak<RefCell<DrmBackendInternal>>>,
@ -82,6 +222,14 @@ pub struct DrmDevice<H: DrmHandler + 'static> {
} }
impl<H: DrmHandler + 'static> DrmDevice<H> { impl<H: DrmHandler + 'static> DrmDevice<H> {
/// 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<L>(fd: RawFd, logger: L) -> Result<Self, DrmError> pub unsafe fn new_from_fd<L>(fd: RawFd, logger: L) -> Result<Self, DrmError>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
@ -98,6 +246,14 @@ impl<H: DrmHandler + 'static> DrmDevice<H> {
) )
} }
/// 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<L>(fd: RawFd, attributes: GlAttributes, logger: L) pub unsafe fn new_from_fd_with_gl_attr<L>(fd: RawFd, attributes: GlAttributes, logger: L)
-> Result<Self, DrmError> -> Result<Self, DrmError>
where where
@ -106,6 +262,10 @@ impl<H: DrmHandler + 'static> DrmDevice<H> {
DrmDevice::new(DrmDev::new_from_fd(fd), attributes, logger) 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<L>(file: File, logger: L) -> Result<Self, DrmError> pub fn new_from_file<L>(file: File, logger: L) -> Result<Self, DrmError>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
@ -122,6 +282,10 @@ impl<H: DrmHandler + 'static> DrmDevice<H> {
) )
} }
/// 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<L>(file: File, attributes: GlAttributes, logger: L) pub fn new_from_file_with_gl_attr<L>(file: File, attributes: GlAttributes, logger: L)
-> Result<Self, DrmError> -> Result<Self, DrmError>
where where
@ -149,6 +313,7 @@ impl<H: DrmHandler + 'static> DrmDevice<H> {
); );
} }
// Open the gbm device from the drm device and create a context based on that
Ok(DrmDevice { Ok(DrmDevice {
context: Rc::new(Context::try_new( context: Rc::new(Context::try_new(
Box::new(Devices::try_new(Box::new(drm), |drm| { Box::new(Devices::try_new(Box::new(drm), |drm| {
@ -174,6 +339,11 @@ impl<H: DrmHandler + 'static> DrmDevice<H> {
}) })
} }
/// 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<I>(&mut self, crtc: crtc::Handle, mode: Mode, connectors: I) pub fn create_backend<I>(&mut self, crtc: crtc::Handle, mode: Mode, connectors: I)
-> Result<DrmBackend, DrmError> -> Result<DrmBackend, DrmError>
where where
@ -183,6 +353,8 @@ impl<H: DrmHandler + 'static> DrmDevice<H> {
.new(o!("drm" => "backend", "crtc" => format!("{:?}", crtc))); .new(o!("drm" => "backend", "crtc" => format!("{:?}", crtc)));
let own_id = self.backends.len(); 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( let backend = Rc::new(RefCell::new(DrmBackendInternal::new(
self.context.clone(), self.context.clone(),
crtc, crtc,
@ -197,18 +369,20 @@ impl<H: DrmHandler + 'static> DrmDevice<H> {
Ok(DrmBackend::new(backend)) Ok(DrmBackend::new(backend))
} }
/// Set a handler for handling finished rendering
pub fn set_handler(&mut self, handler: H) -> Option<H> { pub fn set_handler(&mut self, handler: H) -> Option<H> {
let res = self.handler.take(); let res = self.handler.take();
self.handler = Some(handler); self.handler = Some(handler);
res res
} }
/// Clear the currently set handler
pub fn clear_handler(&mut self) -> Option<H> { pub fn clear_handler(&mut self) -> Option<H> {
self.handler.take() self.handler.take()
} }
} }
// for users convinience // for users convinience and FdEventSource registering
impl<H: DrmHandler + 'static> AsRawFd for DrmDevice<H> { impl<H: DrmHandler + 'static> AsRawFd for DrmDevice<H> {
fn as_raw_fd(&self) -> RawFd { fn as_raw_fd(&self) -> RawFd {
self.context.head().head().as_raw_fd() self.context.head().head().as_raw_fd()
@ -217,8 +391,20 @@ impl<H: DrmHandler + 'static> AsRawFd for DrmDevice<H> {
impl<H: DrmHandler + 'static> BasicDevice for DrmDevice<H> {} impl<H: DrmHandler + 'static> BasicDevice for DrmDevice<H> {}
impl<H: DrmHandler + 'static> ControlDevice for DrmDevice<H> {} impl<H: DrmHandler + 'static> ControlDevice for DrmDevice<H> {}
/// Handler for drm node events
///
/// See module-level documentation for its use
pub trait DrmHandler { 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); 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); fn error(&mut self, evlh: &mut EventLoopHandle, error: IoError);
} }
@ -245,8 +431,10 @@ impl<H: DrmHandler + 'static> FdEventSourceHandler for DrmDevice<H> {
userdata: Box<Any>) { userdata: Box<Any>) {
let id: Id = *userdata.downcast().unwrap(); let id: Id = *userdata.downcast().unwrap();
if let Some(backend) = self.0.backends[id.raw()].upgrade() { if let Some(backend) = self.0.backends[id.raw()].upgrade() {
// we can now unlock the buffer
backend.borrow().unlock_buffer(); backend.borrow().unlock_buffer();
if let Some(handler) = self.0.handler.as_mut() { 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); handler.ready(self.1, id, frame, duration);
} }
} }

View File

@ -5,15 +5,12 @@
/// ///
/// It therefore falls under glutin's Apache 2.0 license /// It therefore falls under glutin's Apache 2.0 license
/// (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE) /// (see https://github.com/tomaka/glutin/tree/044e651edf67a2029eecc650dd42546af1501414/LICENSE)
use super::GraphicsBackend; use super::GraphicsBackend;
#[cfg(feature = "backend_drm")] #[cfg(feature = "backend_drm")]
use gbm::{AsRaw, Device as GbmDevice, Surface as GbmSurface}; use gbm::{AsRaw, Device as GbmDevice, Surface as GbmSurface};
use libloading::Library; use libloading::Library;
use nix::{c_int, c_void}; use nix::{c_int, c_void};
use slog; use slog;
use std::error::{self, Error}; use std::error::{self, Error};
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::fmt; use std::fmt;
@ -22,7 +19,6 @@ use std::marker::PhantomData;
use std::mem; use std::mem;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::ptr; use std::ptr;
#[cfg(feature = "backend_winit")] #[cfg(feature = "backend_winit")]
use wayland_client::egl as wegl; use wayland_client::egl as wegl;
#[cfg(feature = "backend_winit")] #[cfg(feature = "backend_winit")]
@ -77,10 +73,14 @@ pub enum NativeSurfacePtr {
Gbm(ffi::NativeWindowType), Gbm(ffi::NativeWindowType),
} }
/// Enumerates all supported backends
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub enum NativeType { pub enum NativeType {
/// X11 window & surface
X11, X11,
/// Wayland surface
Wayland, Wayland,
/// Gbm surface
Gbm, 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 { pub unsafe trait NativeSurface {
/// Type to keep the surface valid, if needed
type Keep: 'static; type Keep: 'static;
/// Return a surface for the given type if possible
fn surface(&self, backend: NativeType) -> Result<(NativeSurfacePtr, Self::Keep), CreationError>; fn surface(&self, backend: NativeType) -> Result<(NativeSurfacePtr, Self::Keep), CreationError>;
} }
@ -214,6 +221,7 @@ pub struct EGLContext<'a, T: NativeSurface> {
} }
impl<'a> EGLContext<'a, ()> { impl<'a> EGLContext<'a, ()> {
/// Create a new context from a given `winit`-`Window`
#[cfg(feature = "backend_winit")] #[cfg(feature = "backend_winit")]
pub fn new_from_winit<L>(window: &'a WinitWindow, attributes: GlAttributes, pub fn new_from_winit<L>(window: &'a WinitWindow, attributes: GlAttributes,
reqs: PixelFormatRequirements, logger: L) 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")] #[cfg(feature = "backend_drm")]
pub fn new_from_gbm<L, U: 'static>(gbm: &'a GbmDevice<'a>, attributes: GlAttributes, pub fn new_from_gbm<L, U: 'static>(gbm: &'a GbmDevice<'a>, attributes: GlAttributes,
reqs: PixelFormatRequirements, logger: L) 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 /// 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<EGLSurface<'a, 'b, T>, CreationError> { pub fn create_surface<'b>(&'a self, native: &'b T) -> Result<EGLSurface<'a, 'b, T>, CreationError> {
trace!(self.logger, "Creating EGL window surface..."); trace!(self.logger, "Creating EGL window surface...");

View File

@ -8,6 +8,7 @@ pub trait GraphicsBackend {
/// Format representing the image drawn for the cursor. /// Format representing the image drawn for the cursor.
type CursorFormat; type CursorFormat;
/// Error the underlying backend throws if operations fail
type Error; type Error;
/// Sets the cursor position and therefor updates the drawn cursors position. /// Sets the cursor position and therefor updates the drawn cursors position.

View File

@ -7,6 +7,7 @@
//! Supported graphics backends: //! Supported graphics backends:
//! //!
//! - winit //! - winit
//! - drm
//! //!
//! Supported input backends: //! Supported input backends:
//! //!