drm: Documentation
This commit is contained in:
parent
0698775153
commit
f2bff6172b
|
@ -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<RefCell<DrmBackendInternal>>);
|
||||
|
||||
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<connector::Handle> {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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<H> From<TryNewError<Error, H>> 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),
|
||||
}
|
||||
|
||||
|
|
|
@ -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 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<H: DrmHandler + 'static> {
|
||||
context: Rc<Context>,
|
||||
backends: Vec<Weak<RefCell<DrmBackendInternal>>>,
|
||||
|
@ -82,6 +222,14 @@ pub struct DrmDevice<H: DrmHandler + 'static> {
|
|||
}
|
||||
|
||||
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>
|
||||
where
|
||||
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)
|
||||
-> Result<Self, DrmError>
|
||||
where
|
||||
|
@ -106,6 +262,10 @@ impl<H: DrmHandler + 'static> DrmDevice<H> {
|
|||
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>
|
||||
where
|
||||
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)
|
||||
-> Result<Self, DrmError>
|
||||
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 {
|
||||
context: Rc::new(Context::try_new(
|
||||
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)
|
||||
-> Result<DrmBackend, DrmError>
|
||||
where
|
||||
|
@ -183,6 +353,8 @@ impl<H: DrmHandler + 'static> DrmDevice<H> {
|
|||
.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<H: DrmHandler + 'static> DrmDevice<H> {
|
|||
Ok(DrmBackend::new(backend))
|
||||
}
|
||||
|
||||
/// Set a handler for handling finished rendering
|
||||
pub fn set_handler(&mut self, handler: H) -> Option<H> {
|
||||
let res = self.handler.take();
|
||||
self.handler = Some(handler);
|
||||
res
|
||||
}
|
||||
|
||||
/// Clear the currently set handler
|
||||
pub fn clear_handler(&mut self) -> Option<H> {
|
||||
self.handler.take()
|
||||
}
|
||||
}
|
||||
|
||||
// for users convinience
|
||||
// for users convinience and FdEventSource registering
|
||||
impl<H: DrmHandler + 'static> AsRawFd for DrmDevice<H> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
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> ControlDevice for DrmDevice<H> {}
|
||||
|
||||
/// 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<H: DrmHandler + 'static> FdEventSourceHandler for DrmDevice<H> {
|
|||
userdata: Box<Any>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<L>(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<L, U: 'static>(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<EGLSurface<'a, 'b, T>, CreationError> {
|
||||
trace!(self.logger, "Creating EGL window surface...");
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
//! Supported graphics backends:
|
||||
//!
|
||||
//! - winit
|
||||
//! - drm
|
||||
//!
|
||||
//! Supported input backends:
|
||||
//!
|
||||
|
|
Loading…
Reference in New Issue