First pass of adding documentation

This commit is contained in:
Victor Brekenfeld 2021-04-30 17:21:35 +02:00
parent 52c01535d0
commit 7e47d648d4
19 changed files with 474 additions and 17 deletions

View File

@ -106,7 +106,7 @@ fn main() {
}, },
); );
let first_buffer: Slot<DumbBuffer<FdWrapper>, _> = swapchain.acquire().unwrap().unwrap(); let first_buffer: Slot<DumbBuffer<FdWrapper>, _> = swapchain.acquire().unwrap().unwrap();
let framebuffer = surface.add_framebuffer(&first_buffer.handle, 32, 32).unwrap(); let framebuffer = surface.add_framebuffer(first_buffer.handle(), 32, 32).unwrap();
first_buffer.set_userdata(framebuffer); first_buffer.set_userdata(framebuffer);
// Get the device as an allocator into the // Get the device as an allocator into the
@ -144,17 +144,19 @@ impl DeviceHandler for DrmHandlerImpl {
// Next buffer // Next buffer
let next = self.swapchain.acquire().unwrap().unwrap(); let next = self.swapchain.acquire().unwrap().unwrap();
if next.userdata().is_none() { if next.userdata().is_none() {
let fb = self.surface.add_framebuffer(&next.handle, 32, 32).unwrap(); let fb = self.surface.add_framebuffer(next.handle(), 32, 32).unwrap();
next.set_userdata(fb); next.set_userdata(fb);
} }
// now we could render to the mapping via software rendering. // now we could render to the mapping via software rendering.
// this example just sets some grey color // this example just sets some grey color
let mut db = next.handle; {
let mut mapping = self.surface.map_dumb_buffer(&mut db).unwrap(); let mut db = *next.handle();
for x in mapping.as_mut() { let mut mapping = self.surface.map_dumb_buffer(&mut db).unwrap();
*x = 128; for x in mapping.as_mut() {
*x = 128;
}
} }
self.current = next; self.current = next;
} }

View File

@ -1,3 +1,5 @@
//! Module for [dmabuf](https://01.org/linuxgraphics/gfx-docs/drm/driver-api/dma-buf.html) buffers.
use super::{Buffer, Format, Modifier}; use super::{Buffer, Format, Modifier};
use std::os::unix::io::RawFd; use std::os::unix::io::RawFd;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
@ -15,9 +17,11 @@ pub(crate) struct DmabufInternal {
} }
#[derive(Clone)] #[derive(Clone)]
/// Strong reference to a dmabuf handle
pub struct Dmabuf(pub(crate) Arc<DmabufInternal>); pub struct Dmabuf(pub(crate) Arc<DmabufInternal>);
#[derive(Clone)] #[derive(Clone)]
/// Weak reference to a dmabuf handle
pub struct WeakDmabuf(pub(crate) Weak<DmabufInternal>); pub struct WeakDmabuf(pub(crate) Weak<DmabufInternal>);
impl PartialEq for Dmabuf { impl PartialEq for Dmabuf {
@ -109,28 +113,36 @@ impl Dmabuf {
}))) })))
} }
/// Return raw handles of the planes of this buffer
pub fn handles(&self) -> &[RawFd] { pub fn handles(&self) -> &[RawFd] {
self.0.fds.split_at(self.0.num_planes).0 self.0.fds.split_at(self.0.num_planes).0
} }
/// Return offsets for the planes of this buffer
pub fn offsets(&self) -> &[u32] { pub fn offsets(&self) -> &[u32] {
self.0.offsets.split_at(self.0.num_planes).0 self.0.offsets.split_at(self.0.num_planes).0
} }
/// Return strides for the planes of this buffer
pub fn strides(&self) -> &[u32] { pub fn strides(&self) -> &[u32] {
self.0.strides.split_at(self.0.num_planes).0 self.0.strides.split_at(self.0.num_planes).0
} }
/// Check if this buffer format has any vendor-specific modifiers set or is implicit/linear
pub fn has_modifier(&self) -> bool { pub fn has_modifier(&self) -> bool {
self.0.format.modifier != Modifier::Invalid && self.0.format.modifier != Modifier::Linear self.0.format.modifier != Modifier::Invalid && self.0.format.modifier != Modifier::Linear
} }
/// Create a weak reference to this dmabuf
pub fn weak(&self) -> WeakDmabuf { pub fn weak(&self) -> WeakDmabuf {
WeakDmabuf(Arc::downgrade(&self.0)) WeakDmabuf(Arc::downgrade(&self.0))
} }
} }
impl WeakDmabuf { impl WeakDmabuf {
/// Try to upgrade to a strong reference of this buffer.
///
/// Fails if no strong references exist anymore and the handle was already closed.
pub fn upgrade(&self) -> Option<Dmabuf> { pub fn upgrade(&self) -> Option<Dmabuf> {
self.0.upgrade().map(Dmabuf) self.0.upgrade().map(Dmabuf)
} }

View File

@ -1,3 +1,5 @@
//! Module for [DumbBuffer](https://01.org/linuxgraphics/gfx-docs/drm/gpu/drm-kms.html#dumb-buffer-objects) buffers
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::sync::Arc; use std::sync::Arc;
@ -7,9 +9,10 @@ use drm::control::{dumbbuffer::DumbBuffer as Handle, Device as ControlDevice};
use super::{Allocator, Buffer, Format}; use super::{Allocator, Buffer, Format};
use crate::backend::drm::device::{DrmDevice, DrmDeviceInternal, FdWrapper}; use crate::backend::drm::device::{DrmDevice, DrmDeviceInternal, FdWrapper};
/// Wrapper around raw DumbBuffer handles.
pub struct DumbBuffer<A: AsRawFd + 'static> { pub struct DumbBuffer<A: AsRawFd + 'static> {
fd: Arc<FdWrapper<A>>, fd: Arc<FdWrapper<A>>,
pub handle: Handle, handle: Handle,
format: Format, format: Format,
} }
@ -49,6 +52,16 @@ impl<A: AsRawFd + 'static> Buffer for DumbBuffer<A> {
} }
} }
impl<A: AsRawFd + 'static> DumbBuffer<A> {
/// Raw handle to the underlying buffer.
///
/// Note: This handle will become invalid, once the `DumbBuffer` wrapper is dropped
/// or the device used to create is closed. Do not copy this handle and assume it keeps being valid.
pub fn handle(&self) -> &Handle {
&self.handle
}
}
impl<A: AsRawFd + 'static> Drop for DumbBuffer<A> { impl<A: AsRawFd + 'static> Drop for DumbBuffer<A> {
fn drop(&mut self) { fn drop(&mut self) {
let _ = self.fd.destroy_dumb_buffer(self.handle); let _ = self.fd.destroy_dumb_buffer(self.handle);

View File

@ -1,3 +1,5 @@
//! Module for Buffers created using [libgbm](reexports::gbm)
use super::{dmabuf::Dmabuf, Allocator, Buffer, Format, Fourcc, Modifier}; use super::{dmabuf::Dmabuf, Allocator, Buffer, Format, Fourcc, Modifier};
use gbm::{BufferObject as GbmBuffer, BufferObjectFlags, Device as GbmDevice}; use gbm::{BufferObject as GbmBuffer, BufferObjectFlags, Device as GbmDevice};
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
@ -40,12 +42,16 @@ impl<T> Buffer for GbmBuffer<T> {
} }
} }
/// Errors during conversion to a dmabuf handle from a gbm buffer object
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum GbmConvertError { pub enum GbmConvertError {
/// The gbm device was destroyed
#[error("The gbm device was destroyed")] #[error("The gbm device was destroyed")]
DeviceDestroyed(#[from] gbm::DeviceDestroyedError), DeviceDestroyed(#[from] gbm::DeviceDestroyedError),
/// The buffer consists out of multiple file descriptions, which is currently unsupported
#[error("Buffer consists out of multiple file descriptors, which is currently unsupported")] #[error("Buffer consists out of multiple file descriptors, which is currently unsupported")]
UnsupportedBuffer, UnsupportedBuffer,
/// The conversion returned an invalid file descriptor
#[error("Buffer returned invalid file descriptor")] #[error("Buffer returned invalid file descriptor")]
InvalidFD, InvalidFD,
} }
@ -94,6 +100,7 @@ impl<T> std::convert::TryFrom<GbmBuffer<T>> for Dmabuf {
} }
impl Dmabuf { impl Dmabuf {
/// Import a Dmabuf using libgbm, creating a gbm Buffer Object to the same underlying data.
pub fn import<A: AsRawFd + 'static, T>( pub fn import<A: AsRawFd + 'static, T>(
&self, &self,
gbm: &GbmDevice<A>, gbm: &GbmDevice<A>,

View File

@ -1,3 +1,20 @@
//! Buffer allocation and management.
//!
//! Collection of common traits and implementations around
//! buffer creation and handling from various sources.
//!
//! Allocators provided:
//! - Dumb Buffers through [`backend::drm::DrmDevice`]
//! - Gbm Buffers through [`reexports::gbm::Device`]
//!
//! Buffer types supported:
//! - [DumbBuffers](dumb::DumbBuffer)
//! - [gbm BufferObjects](reexports::gbm::BufferObject)
//! - [DmaBufs](dmabuf::Dmabuf)
//!
//! Helpers:
//! - [`Swapchain`] to help with buffer management for framebuffers
pub mod dmabuf; pub mod dmabuf;
#[cfg(feature = "backend_drm")] #[cfg(feature = "backend_drm")]
pub mod dumb; pub mod dumb;
@ -12,17 +29,25 @@ pub use drm_fourcc::{
UnrecognizedFourcc, UnrecognizedVendor, UnrecognizedFourcc, UnrecognizedVendor,
}; };
/// Common trait describing common properties of most types of buffers.
pub trait Buffer { pub trait Buffer {
/// Width of the two-dimensional buffer
fn width(&self) -> u32; fn width(&self) -> u32;
/// Height of the two-dimensional buffer
fn height(&self) -> u32; fn height(&self) -> u32;
/// Size (w x h) of the two-dimensional buffer
fn size(&self) -> (u32, u32) { fn size(&self) -> (u32, u32) {
(self.width(), self.height()) (self.width(), self.height())
} }
/// Pixel format of the buffer
fn format(&self) -> Format; fn format(&self) -> Format;
} }
/// Interface to create Buffers
pub trait Allocator<B: Buffer> { pub trait Allocator<B: Buffer> {
/// Error type thrown if allocations fail
type Error: std::error::Error; type Error: std::error::Error;
/// Try to create a buffer with the given dimensions and pixel format
fn create_buffer(&mut self, width: u32, height: u32, format: Format) -> Result<B, Self::Error>; fn create_buffer(&mut self, width: u32, height: u32, format: Format) -> Result<B, Self::Error>;
} }

View File

@ -9,7 +9,36 @@ use crate::backend::allocator::{Allocator, Buffer, Format};
pub const SLOT_CAP: usize = 4; pub const SLOT_CAP: usize = 4;
/// Swapchain handling a fixed set of re-usable buffers e.g. for scan-out.
///
/// ## How am I supposed to use this?
///
/// To do proper buffer management, most compositors do so called double-buffering.
/// Which means you use two buffers, one that is currently presented (the front buffer)
/// and one that is currently rendered to (the back buffer). After each rendering operation
/// you swap the buffers around, the old front buffer becomes the new back buffer, while
/// the new front buffer is displayed to the user. This avoids showing the user rendering
/// artifacts doing rendering.
///
/// There are also reasons to do triple-buffering, e.g. if you swap operation takes a
/// unspecified amount of time. In that case you have one buffer, that is currently
/// displayed, one that is done drawing and about to be swapped in and another one,
/// which you can use to render currently.
///
/// Re-using and managing these buffers becomes increasingly complex the more buffers you
/// introduce, which is where `Swapchain` comes into play.
///
/// `Swapchain` allocates buffers for you and transparently re-created them, e.g. when resizing.
/// All you tell the swapchain is: *"Give me the next free buffer"* (by calling [`acquire`](Swapchain::acquire)).
/// You then hold on to the returned buffer during rendering and swapping and free it once it is displayed.
/// Efficient re-use of the buffers is done by the swapchain.
///
/// If you have associated resources for each buffer, that can also be re-used (e.g. framebuffer Handles for a `DrmDevice`),
/// you can store then in the buffer slots userdata, where it gets freed, if the buffer gets allocated, but
/// is still valid, if the buffer was just re-used. So instead of creating a framebuffer handle for each new
/// buffer, you can skip creation, if the userdata already contains a framebuffer handle.
pub struct Swapchain<A: Allocator<B>, B: Buffer + TryInto<B>, U: 'static, D: Buffer = B> { pub struct Swapchain<A: Allocator<B>, B: Buffer + TryInto<B>, U: 'static, D: Buffer = B> {
/// Allocator used by the swapchain
pub allocator: A, pub allocator: A,
_original_buffer_format: std::marker::PhantomData<B>, _original_buffer_format: std::marker::PhantomData<B>,
@ -20,6 +49,11 @@ pub struct Swapchain<A: Allocator<B>, B: Buffer + TryInto<B>, U: 'static, D: Buf
slots: [Slot<D, U>; SLOT_CAP], slots: [Slot<D, U>; SLOT_CAP],
} }
/// Slot of a swapchain containing an allocated buffer and its userdata.
///
/// Can be cloned and passed around freely, the buffer is marked for re-use
/// once all copies are dropped. Holding on to this struct will block the
/// buffer in the swapchain.
pub struct Slot<B: Buffer, U: 'static> { pub struct Slot<B: Buffer, U: 'static> {
buffer: Arc<Option<B>>, buffer: Arc<Option<B>>,
acquired: Arc<AtomicBool>, acquired: Arc<AtomicBool>,
@ -27,14 +61,17 @@ pub struct Slot<B: Buffer, U: 'static> {
} }
impl<B: Buffer, U: 'static> Slot<B, U> { impl<B: Buffer, U: 'static> Slot<B, U> {
/// Set userdata for this slot.
pub fn set_userdata(&self, data: U) -> Option<U> { pub fn set_userdata(&self, data: U) -> Option<U> {
self.userdata.lock().unwrap().replace(data) self.userdata.lock().unwrap().replace(data)
} }
/// Retrieve userdata for this slot.
pub fn userdata(&self) -> MutexGuard<'_, Option<U>> { pub fn userdata(&self) -> MutexGuard<'_, Option<U>> {
self.userdata.lock().unwrap() self.userdata.lock().unwrap()
} }
/// Clear userdata contained in this slot.
pub fn clear_userdata(&self) -> Option<U> { pub fn clear_userdata(&self) -> Option<U> {
self.userdata.lock().unwrap().take() self.userdata.lock().unwrap().take()
} }
@ -73,14 +110,17 @@ impl<B: Buffer, U: 'static> Drop for Slot<B, U> {
} }
} }
/// Error that can happen on acquiring a buffer
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum SwapchainError<E1, E2> pub enum SwapchainError<E1, E2>
where where
E1: std::error::Error + 'static, E1: std::error::Error + 'static,
E2: std::error::Error + 'static, E2: std::error::Error + 'static,
{ {
/// The allocator returned an error
#[error("Failed to allocate a new buffer: {0}")] #[error("Failed to allocate a new buffer: {0}")]
AllocationError(#[source] E1), AllocationError(#[source] E1),
/// The buffer could not be successfully converted into the desired format
#[error("Failed to convert a new buffer: {0}")] #[error("Failed to convert a new buffer: {0}")]
ConversionError(#[source] E2), ConversionError(#[source] E2),
} }
@ -94,6 +134,7 @@ where
E2: std::error::Error + 'static, E2: std::error::Error + 'static,
U: 'static, U: 'static,
{ {
/// Create a new swapchain with the desired allocator and dimensions and pixel format for the created buffers.
pub fn new(allocator: A, width: u32, height: u32, format: Format) -> Swapchain<A, B, U, D> { pub fn new(allocator: A, width: u32, height: u32, format: Format) -> Swapchain<A, B, U, D> {
Swapchain { Swapchain {
allocator, allocator,
@ -105,6 +146,10 @@ where
} }
} }
/// Acquire a new slot from the swapchain, if one is still free.
///
/// The swapchain has an internal maximum of four re-usable buffers.
/// This function returns the first free one.
pub fn acquire(&mut self) -> Result<Option<Slot<D, U>>, SwapchainError<E1, E2>> { pub fn acquire(&mut self) -> Result<Option<Slot<D, U>>, SwapchainError<E1, E2>> {
if let Some(free_slot) = self.slots.iter_mut().find(|s| !s.acquired.load(Ordering::SeqCst)) { if let Some(free_slot) = self.slots.iter_mut().find(|s| !s.acquired.load(Ordering::SeqCst)) {
if free_slot.buffer.is_none() { if free_slot.buffer.is_none() {
@ -127,6 +172,9 @@ where
Ok(None) Ok(None)
} }
/// Change the dimensions of newly returned buffers.
///
/// Already optained buffers are unaffected and will be cleaned up on drop.
pub fn resize(&mut self, width: u32, height: u32) { pub fn resize(&mut self, width: u32, height: u32) {
if self.width == width && self.height == height { if self.width == width && self.height == height {
return; return;

View File

@ -23,6 +23,7 @@ use crate::backend::allocator::{Format, Fourcc, Modifier};
use atomic::AtomicDrmDevice; use atomic::AtomicDrmDevice;
use legacy::LegacyDrmDevice; use legacy::LegacyDrmDevice;
/// An open drm device
pub struct DrmDevice<A: AsRawFd + 'static> { pub struct DrmDevice<A: AsRawFd + 'static> {
pub(super) dev_id: dev_t, pub(super) dev_id: dev_t,
pub(crate) internal: Arc<DrmDeviceInternal<A>>, pub(crate) internal: Arc<DrmDeviceInternal<A>>,
@ -87,6 +88,23 @@ impl<A: AsRawFd + 'static> BasicDevice for DrmDeviceInternal<A> {}
impl<A: AsRawFd + 'static> ControlDevice for DrmDeviceInternal<A> {} impl<A: AsRawFd + 'static> ControlDevice for DrmDeviceInternal<A> {}
impl<A: AsRawFd + 'static> DrmDevice<A> { impl<A: AsRawFd + 'static> DrmDevice<A> {
/// Create a new [`DrmDevice`] from an open drm node
///
/// # Arguments
///
/// - `fd` - Open drm node
/// - `disable_connectors` - Setting this to true will initialize all connectors \
/// as disabled on device creation. smithay enables connectors, when attached \
/// to a surface, and disables them, when detached. Setting this to `false` \
/// requires usage of `drm-rs` to disable unused connectors to prevent them \
/// showing garbage, but will also prevent flickering of already turned on \
/// connectors (assuming you won't change the resolution).
/// - `logger` - Optional [`slog::Logger`] to be used by this device.
///
/// # Return
///
/// Returns an error if the file is no valid drm node or the device is not accessible.
pub fn new<L>(fd: A, disable_connectors: bool, logger: L) -> Result<Self, Error> pub fn new<L>(fd: A, disable_connectors: bool, logger: L) -> Result<Self, Error>
where where
A: AsRawFd + 'static, A: AsRawFd + 'static,
@ -175,6 +193,13 @@ impl<A: AsRawFd + 'static> DrmDevice<A> {
) )
} }
/// Processes any open events of the underlying file descriptor.
///
/// You should not call this function manually, but rather use
/// [`device_bind`] to register the device
/// to an [`EventLoop`](calloop::EventLoop)
/// and call this function when the device becomes readable
/// to synchronize your rendering to the vblank events of the open crtc's
pub fn process_events(&mut self) { pub fn process_events(&mut self) {
match self.receive_events() { match self.receive_events() {
Ok(events) => { Ok(events) => {
@ -205,6 +230,7 @@ impl<A: AsRawFd + 'static> DrmDevice<A> {
} }
} }
/// Returns if the underlying implementation uses atomic-modesetting or not.
pub fn is_atomic(&self) -> bool { pub fn is_atomic(&self) -> bool {
match *self.internal { match *self.internal {
DrmDeviceInternal::Atomic(_) => true, DrmDeviceInternal::Atomic(_) => true,
@ -212,19 +238,25 @@ impl<A: AsRawFd + 'static> DrmDevice<A> {
} }
} }
/// Assigns a [`DeviceHandler`] called during event processing.
///
/// See [`device_bind`] and [`DeviceHandler`]
pub fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { pub fn set_handler(&mut self, handler: impl DeviceHandler + 'static) {
let handler = Some(Box::new(handler) as Box<dyn DeviceHandler + 'static>); let handler = Some(Box::new(handler) as Box<dyn DeviceHandler + 'static>);
*self.handler.borrow_mut() = handler; *self.handler.borrow_mut() = handler;
} }
/// Clear a set [`DeviceHandler`](trait.DeviceHandler.html), if any
pub fn clear_handler(&mut self) { pub fn clear_handler(&mut self) {
self.handler.borrow_mut().take(); self.handler.borrow_mut().take();
} }
/// Returns a list of crtcs for this device
pub fn crtcs(&self) -> &[crtc::Handle] { pub fn crtcs(&self) -> &[crtc::Handle] {
self.resources.crtcs() self.resources.crtcs()
} }
/// Returns a set of available planes for a given crtc
pub fn planes(&self, crtc: &crtc::Handle) -> Result<Planes, Error> { pub fn planes(&self, crtc: &crtc::Handle) -> Result<Planes, Error> {
let mut primary = None; let mut primary = None;
let mut cursor = None; let mut cursor = None;
@ -287,6 +319,22 @@ impl<A: AsRawFd + 'static> DrmDevice<A> {
unreachable!() unreachable!()
} }
/// Creates a new rendering surface.
///
/// # Arguments
///
/// Initialization of surfaces happens through the types provided by
/// [`drm-rs`](drm).
///
/// - [`crtc`](drm::control::crtc)s represent scanout engines of the device pointing to one framebuffer. \
/// Their responsibility is to read the data of the framebuffer and export it into an "Encoder". \
/// The number of crtc's represent the number of independant output devices the hardware may handle.
/// - [`plane`](drm::control::plane)s represent a single plane on a crtc, which is composite together with
/// other planes on the same crtc to present the final image.
/// - [`mode`](drm::control::Mode) describes the resolution and rate of images produced by the crtc and \
/// has to be compatible with the provided `connectors`.
/// - [`connectors`] - List of connectors driven by the crtc. At least one(!) connector needs to be \
/// attached to a crtc in smithay.
pub fn create_surface( pub fn create_surface(
&self, &self,
crtc: crtc::Handle, crtc: crtc::Handle,
@ -456,14 +504,19 @@ impl<A: AsRawFd + 'static> DrmDevice<A> {
}) })
} }
/// Returns the device_id of the underlying drm node
pub fn device_id(&self) -> dev_t { pub fn device_id(&self) -> dev_t {
self.dev_id self.dev_id
} }
} }
/// A set of planes as supported by a crtc
pub struct Planes { pub struct Planes {
/// The primary plane of the crtc
pub primary: plane::Handle, pub primary: plane::Handle,
/// The cursor plane of the crtc, if available
pub cursor: Option<plane::Handle>, pub cursor: Option<plane::Handle>,
/// Overlay planes supported by the crtc, if available
pub overlay: Option<Vec<plane::Handle>>, pub overlay: Option<Vec<plane::Handle>>,
} }

View File

@ -35,8 +35,10 @@ pub enum Error {
/// This operation would result in a surface without connectors. /// This operation would result in a surface without connectors.
#[error("Surface of crtc `{0:?}` would have no connectors, which is not accepted")] #[error("Surface of crtc `{0:?}` would have no connectors, which is not accepted")]
SurfaceWithoutConnectors(crtc::Handle), SurfaceWithoutConnectors(crtc::Handle),
/// The given plane cannot be used with the given crtc
#[error("Plane `{1:?}` is not compatible for use with crtc `{0:?}`")] #[error("Plane `{1:?}` is not compatible for use with crtc `{0:?}`")]
PlaneNotCompatible(crtc::Handle, plane::Handle), PlaneNotCompatible(crtc::Handle, plane::Handle),
/// The given plane is not a primary plane and therefor not supported by the underlying implementation
#[error("Non-Primary Planes (provided was `{0:?}`) are not available for use with legacy devices")] #[error("Non-Primary Planes (provided was `{0:?}`) are not available for use with legacy devices")]
NonPrimaryPlane(plane::Handle), NonPrimaryPlane(plane::Handle),
/// No encoder was found for a given connector on the set crtc /// No encoder was found for a given connector on the set crtc

View File

@ -1,3 +1,65 @@
//! This module represents abstraction on top the linux direct rendering manager api (drm).
//!
//! ## DrmDevice
//!
//! A device exposes certain properties, which are directly derived
//! from the *device* as perceived by the direct rendering manager api (drm). These resources consists
//! out of connectors, encoders, framebuffers, planes and crtcs.
//!
//! [`crtc`](drm::control::crtc)s represent scanout engines of the device pointer to one framebuffer.
//! Their responsibility is to read the data of the framebuffer and export it into an "Encoder".
//! The number of crtc's represent the number of independent output devices the hardware may handle.
//!
//! On modern graphic cards it is better to think about the `crtc` as some sort of rendering engine.
//! You can only have so many different pictures, you may display, as you have `crtc`s, but a single image
//! may be put onto multiple displays.
//!
//! An [`encoder`](drm::control::encoder) encodes the data of connected crtcs into a video signal for a fixed set of connectors.
//! E.g. you might have an analog encoder based on a DAG for VGA ports, but another one for digital ones.
//! Also not every encoder might be connected to every crtc.
//!
//! A [`connector`](drm::control::connector) represents a port on your computer, possibly with a connected monitor, TV, capture card, etc.
//!
//! A [`framebuffer`](drm::control::framebuffer) represents a buffer you may be rendering to, see `Surface` below.
//!
//! A [`plane`](drm::controll::plane) adds another layer on top of the crtcs, which allow us to layer multiple images on top of each other more efficiently
//! then by combining the rendered images in the rendering phase, e.g. via OpenGL. Planes can be explicitly used by the user.
//! Every device has at least one primary plane used to display an image to the whole crtc. Additionally cursor and overlay planes may be present.
//! Cursor planes are usually very restricted in size and meant to be used for hardware cursors, while overlay planes may
//! be used for performance reasons to display any overlay on top of the image, e.g. top-most windows.
//!
//! The main functionality of a `Device` in smithay is to give access to all these properties for the user to
//! choose an appropriate rendering configuration. What that means is defined by the requirements and constraints documented
//! in the specific device implementations. The second functionality is the creation of a `Surface`.
//! Surface creation requires a `crtc` (which cannot be the same as another existing `Surface`'s crtc), a plane,
//! as well as a `Mode` and a set of `connectors`.
//!
//! smithay does not make sure that `connectors` are not already in use by another `Surface`. Overlapping `connector`-Sets may
//! be an error or result in undefined rendering behavior depending on the `Surface` implementation.
//!
//! ## DrmSurface
//!
//! A surface is a part of a `Device` that may output a picture to a number of connectors. It pumps pictures of buffers to outputs.
//!
//! On surface creation a matching encoder for your `encoder`-`connector` is automatically selected,
//! if it exists, which means you still need to check your configuration.
//!
//! A surface consists of one `crtc` that is rendered to by the user. This is fixed for the `Surface`s lifetime and cannot be changed.
//! A surface also always needs at least one connector to output the resulting image to as well as a `Mode` that is valid for the given connector.
//!
//! The state of a `Surface` is double-buffered, meaning all operations that chance the set of `connector`s or their `Mode` are stored and
//! only applied on the next commit. `Surface`s do their best to validate these changes, if possible.
//!
//! A commit/page_flip may be triggered to apply the pending state.
//!
//! ## Rendering
//!
//! The drm infrastructure makes no assumptions about the used renderer and does not interface with them directly.
//! It just provides a way to create framebuffers from various buffer types (mainly `DumbBuffer`s and hardware-backed gbm `BufferObject`s).
//!
//! Buffer management and details about the various types can be found in the [`allocator`-Module](backend::allocator) and
//! renderering abstractions, which can target these buffers can be found in the [`renderer`-Module](backend::renderer).
pub(crate) mod device; pub(crate) mod device;
pub(self) mod error; pub(self) mod error;
mod render; mod render;

View File

@ -18,6 +18,13 @@ use crate::backend::egl::EGLBuffer;
use crate::backend::renderer::{Bind, Renderer, Texture, Transform}; use crate::backend::renderer::{Bind, Renderer, Texture, Transform};
use crate::backend::SwapBuffersError; use crate::backend::SwapBuffersError;
/// Simplified by limited abstraction to link single [`DrmSurface`]s to renderers.
///
/// # Use-case
///
/// In some scenarios it might be enough to use of a drm-surface as the one and only target
/// of a single renderer. In these cases `DrmRenderSurface` provides a way to quickly
/// get up and running without manually handling and binding buffers.
pub struct DrmRenderSurface< pub struct DrmRenderSurface<
D: AsRawFd + 'static, D: AsRawFd + 'static,
A: Allocator<B>, A: Allocator<B>,
@ -42,6 +49,15 @@ where
E2: std::error::Error + 'static, E2: std::error::Error + 'static,
E3: std::error::Error + 'static, E3: std::error::Error + 'static,
{ {
/// Create a new `DrmRendererSurface` from a given compatible combination
/// of a surface, an allocator and a renderer.
///
/// To sucessfully call this function, you need to have a renderer,
/// which can render into a Dmabuf, and an allocator, which can create
/// a buffer type, which can be converted into a Dmabuf.
///
/// The function will futhermore check for compatibility by enumerating
/// supported pixel formats and choosing an appropriate one.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn new<L: Into<Option<::slog::Logger>>>( pub fn new<L: Into<Option<::slog::Logger>>>(
drm: DrmSurface<D>, drm: DrmSurface<D>,
@ -201,12 +217,17 @@ where
} }
} }
/// Shortcut to [`Renderer::begin`] with the pending mode as dimensions.
pub fn queue_frame(&mut self) -> Result<(), Error<E1, E2, E3>> { pub fn queue_frame(&mut self) -> Result<(), Error<E1, E2, E3>> {
let mode = self.drm.pending_mode(); let mode = self.drm.pending_mode();
let (width, height) = (mode.size().0 as u32, mode.size().1 as u32); let (width, height) = (mode.size().0 as u32, mode.size().1 as u32);
self.begin(width, height, Transform::Flipped180 /* TODO */) self.begin(width, height, Transform::Normal)
} }
/// Shortcut to abort the current frame.
///
/// Allows [`DrmRenderSurface::queue_frame`] or [`Renderer::begin`] to be called again
/// without displaying the current rendering context to the user.
pub fn drop_frame(&mut self) -> Result<(), SwapBuffersError> { pub fn drop_frame(&mut self) -> Result<(), SwapBuffersError> {
if self.current_buffer.is_none() { if self.current_buffer.is_none() {
return Ok(()); return Ok(());
@ -219,46 +240,86 @@ where
result result
} }
/// Marks the current frame as submitted.
///
/// Needs to be called, after the vblank event of the matching [`DrmDevice`](super::DrmDevice)
/// was received after calling [`Renderer::finish`] on this surface. Otherwise the rendering
/// will run out of buffers eventually.
pub fn frame_submitted(&mut self) -> Result<(), Error<E1, E2, E3>> { pub fn frame_submitted(&mut self) -> Result<(), Error<E1, E2, E3>> {
self.buffers.submitted() self.buffers.submitted()
} }
/// Returns the underlying [`crtc`](drm::control::crtc) of this surface
pub fn crtc(&self) -> crtc::Handle { pub fn crtc(&self) -> crtc::Handle {
self.drm.crtc() self.drm.crtc()
} }
/// Returns the underlying [`plane`](drm::control::plane) of this surface
pub fn plane(&self) -> plane::Handle { pub fn plane(&self) -> plane::Handle {
self.drm.plane() self.drm.plane()
} }
/// Currently used [`connector`](drm::control::connector)s of this `Surface`
pub fn current_connectors(&self) -> impl IntoIterator<Item = connector::Handle> { pub fn current_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
self.drm.current_connectors() self.drm.current_connectors()
} }
/// Returns the pending [`connector`](drm::control::connector)s
/// used after the next [`commit`](Surface::commit) of this [`Surface`]
pub fn pending_connectors(&self) -> impl IntoIterator<Item = connector::Handle> { pub fn pending_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
self.drm.pending_connectors() self.drm.pending_connectors()
} }
/// Tries to add a new [`connector`](drm::control::connector)
/// to be used after the next commit.
///
/// **Warning**: You need to make sure, that the connector is not used with another surface
/// or was properly removed via `remove_connector` + `commit` before adding it to another surface.
/// Behavior if failing to do so is undefined, but might result in rendering errors or the connector
/// getting removed from the other surface without updating it's internal state.
///
/// Fails if the `connector` is not compatible with the underlying [`crtc`](drm::control::crtc)
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
/// or is not compatible with the currently pending
/// [`Mode`](drm::control::Mode).
pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error<E1, E2, E3>> { pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error<E1, E2, E3>> {
self.drm.add_connector(connector).map_err(Error::DrmError) self.drm.add_connector(connector).map_err(Error::DrmError)
} }
/// Tries to mark a [`connector`](drm::control::connector)
/// for removal on the next commit.
pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error<E1, E2, E3>> { pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error<E1, E2, E3>> {
self.drm.remove_connector(connector).map_err(Error::DrmError) self.drm.remove_connector(connector).map_err(Error::DrmError)
} }
/// Tries to replace the current connector set with the newly provided one on the next commit.
///
/// Fails if one new `connector` is not compatible with the underlying [`crtc`](drm::control::crtc)
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
/// or is not compatible with the currently pending
/// [`Mode`](drm::control::Mode).
pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error<E1, E2, E3>> { pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error<E1, E2, E3>> {
self.drm.set_connectors(connectors).map_err(Error::DrmError) self.drm.set_connectors(connectors).map_err(Error::DrmError)
} }
/// Returns the currently active [`Mode`](drm::control::Mode)
/// of the underlying [`crtc`](drm::control::crtc)
pub fn current_mode(&self) -> Mode { pub fn current_mode(&self) -> Mode {
self.drm.current_mode() self.drm.current_mode()
} }
/// Returns the currently pending [`Mode`](drm::control::Mode)
/// to be used after the next commit.
pub fn pending_mode(&self) -> Mode { pub fn pending_mode(&self) -> Mode {
self.drm.pending_mode() self.drm.pending_mode()
} }
/// Tries to set a new [`Mode`](drm::control::Mode)
/// to be used after the next commit.
///
/// Fails if the mode is not compatible with the underlying
/// [`crtc`](drm::control::crtc) or any of the
/// pending [`connector`](drm::control::connector)s.
pub fn use_mode(&self, mode: Mode) -> Result<(), Error<E1, E2, E3>> { pub fn use_mode(&self, mode: Mode) -> Result<(), Error<E1, E2, E3>> {
self.drm.use_mode(mode).map_err(Error::DrmError) self.drm.use_mode(mode).map_err(Error::DrmError)
} }
@ -305,7 +366,7 @@ where
self.renderer.destroy_texture(texture).map_err(Error::RenderError) self.renderer.destroy_texture(texture).map_err(Error::RenderError)
} }
fn begin(&mut self, width: u32, height: u32, transform: Transform) -> Result<(), Error<E1, E2, E3>> { fn begin(&mut self, width: u32, height: u32, _transform: Transform) -> Result<(), Error<E1, E2, E3>> {
if self.current_buffer.is_some() { if self.current_buffer.is_some() {
return Ok(()); return Ok(());
} }
@ -314,7 +375,7 @@ where
self.renderer.bind((*slot).clone()).map_err(Error::RenderError)?; self.renderer.bind((*slot).clone()).map_err(Error::RenderError)?;
self.current_buffer = Some(slot); self.current_buffer = Some(slot);
self.renderer self.renderer
.begin(width, height, transform) .begin(width, height, Transform::Flipped180 /* TODO: add Add<Transform> implementation to add and correct _transform here */)
.map_err(Error::RenderError) .map_err(Error::RenderError)
} }
@ -512,6 +573,7 @@ where
Ok(bo) Ok(bo)
} }
/// Errors thrown by a [`DrmRenderSurface`]
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error<E1, E2, E3> pub enum Error<E1, E2, E3>
where where
@ -519,22 +581,31 @@ where
E2: std::error::Error + 'static, E2: std::error::Error + 'static,
E3: std::error::Error + 'static, E3: std::error::Error + 'static,
{ {
/// No supported pixel format for the given plane could be determined
#[error("No supported plane buffer format found")] #[error("No supported plane buffer format found")]
NoSupportedPlaneFormat, NoSupportedPlaneFormat,
/// No supported pixel format for the given renderer could be determined
#[error("No supported renderer buffer format found")] #[error("No supported renderer buffer format found")]
NoSupportedRendererFormat, NoSupportedRendererFormat,
/// The supported pixel formats of the renderer and plane are incompatible
#[error("Supported plane and renderer buffer formats are incompatible")] #[error("Supported plane and renderer buffer formats are incompatible")]
FormatsNotCompatible, FormatsNotCompatible,
/// The swapchain is exhausted, you need to call `frame_submitted`
#[error("Failed to allocate a new buffer")] #[error("Failed to allocate a new buffer")]
NoFreeSlotsError, NoFreeSlotsError,
/// Failed to renderer using the given renderer
#[error("Failed to render test frame")] #[error("Failed to render test frame")]
InitialRenderingError, InitialRenderingError,
/// Error accessing the drm device
#[error("The underlying drm surface encounted an error: {0}")] #[error("The underlying drm surface encounted an error: {0}")]
DrmError(#[from] DrmError), DrmError(#[from] DrmError),
/// Error importing the rendered buffer to libgbm for scan-out
#[error("The underlying gbm device encounted an error: {0}")] #[error("The underlying gbm device encounted an error: {0}")]
GbmError(#[from] std::io::Error), GbmError(#[from] std::io::Error),
/// Error allocating or converting newly created buffers
#[error("The swapchain encounted an error: {0}")] #[error("The swapchain encounted an error: {0}")]
SwapchainError(#[from] SwapchainError<E1, E2>), SwapchainError(#[from] SwapchainError<E1, E2>),
/// Error during rendering
#[error("The renderer encounted an error: {0}")] #[error("The renderer encounted an error: {0}")]
RenderError(#[source] E3), RenderError(#[source] E3),
} }

View File

@ -12,6 +12,7 @@ use crate::backend::allocator::Format;
use atomic::AtomicDrmSurface; use atomic::AtomicDrmSurface;
use legacy::LegacyDrmSurface; use legacy::LegacyDrmSurface;
/// An open crtc + plane combination that can be used for scan-out
pub struct DrmSurface<A: AsRawFd + 'static> { pub struct DrmSurface<A: AsRawFd + 'static> {
pub(super) crtc: crtc::Handle, pub(super) crtc: crtc::Handle,
pub(super) plane: plane::Handle, pub(super) plane: plane::Handle,
@ -180,10 +181,19 @@ impl<A: AsRawFd + 'static> DrmSurface<A> {
} }
} }
/// Returns a set of supported pixel formats for attached buffers
pub fn supported_formats(&self) -> &HashSet<Format> { pub fn supported_formats(&self) -> &HashSet<Format> {
&self.formats &self.formats
} }
/// Tests is a framebuffer can be used with this surface.
///
/// # Arguments
///
/// - `fb` - Framebuffer handle that has an attached buffer, that shall be tested
/// - `mode` - The mode that should be used to display the buffer
/// - `allow_screen_change` - If an actual screen change is permitted to carry out this test.
/// If the test cannot be performed otherwise, this function returns false.
pub fn test_buffer( pub fn test_buffer(
&self, &self,
fb: framebuffer::Handle, fb: framebuffer::Handle,

View File

@ -19,6 +19,7 @@ unsafe impl Send for EGLContext {}
unsafe impl Sync for EGLContext {} unsafe impl Sync for EGLContext {}
impl EGLContext { impl EGLContext {
/// Creates a new configless `EGLContext` from a given `EGLDisplay`
pub fn new<L>(display: &EGLDisplay, log: L) -> Result<EGLContext, Error> pub fn new<L>(display: &EGLDisplay, log: L) -> Result<EGLContext, Error>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
@ -26,7 +27,7 @@ impl EGLContext {
Self::new_internal(display, None, None, log) Self::new_internal(display, None, None, log)
} }
/// Create a new [`EGLContext`] from a given [`NativeDisplay`](native::NativeDisplay) /// Create a new [`EGLContext`] from a given `EGLDisplay` and configuration requirements
pub fn new_with_config<L>( pub fn new_with_config<L>(
display: &EGLDisplay, display: &EGLDisplay,
attributes: GlAttributes, attributes: GlAttributes,
@ -39,6 +40,7 @@ impl EGLContext {
Self::new_internal(display, None, Some((attributes, reqs)), log) Self::new_internal(display, None, Some((attributes, reqs)), log)
} }
/// Create a new configless `EGLContext` from a given `EGLDisplay` sharing resources with another context
pub fn new_shared<L>(display: &EGLDisplay, share: &EGLContext, log: L) -> Result<EGLContext, Error> pub fn new_shared<L>(display: &EGLDisplay, share: &EGLContext, log: L) -> Result<EGLContext, Error>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
@ -46,6 +48,7 @@ impl EGLContext {
Self::new_internal(display, Some(share), None, log) Self::new_internal(display, Some(share), None, log)
} }
/// Create a new `EGLContext` from a given `EGLDisplay` and configuration requirements sharing resources with another context
pub fn new_shared_with_config<L>( pub fn new_shared_with_config<L>(
display: &EGLDisplay, display: &EGLDisplay,
share: &EGLContext, share: &EGLContext,

View File

@ -275,6 +275,7 @@ impl EGLBuffer {
self.format.num_planes() self.format.num_planes()
} }
/// Returns the `EGLImage` handle for a given plane
pub fn image(&self, plane: usize) -> Option<EGLImage> { pub fn image(&self, plane: usize) -> Option<EGLImage> {
if plane > self.format.num_planes() { if plane > self.format.num_planes() {
None None

View File

@ -16,8 +16,11 @@ use winit::window::Window as WinitWindow;
#[cfg(feature = "backend_gbm")] #[cfg(feature = "backend_gbm")]
use gbm::{AsRaw, Device as GbmDevice}; use gbm::{AsRaw, Device as GbmDevice};
/// Trait describing platform specific functionality to create a valid `EGLDisplay` using the `EGL_EXT_platform_base`(https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) extension.
pub trait EGLNativeDisplay: Send { pub trait EGLNativeDisplay: Send {
/// Required extensions to use this platform
fn required_extensions(&self) -> &'static [&'static str]; fn required_extensions(&self) -> &'static [&'static str];
/// Type, Raw handle and attributes used to call [`eglGetPlatformDisplayEXT`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt)
fn platform_display(&self) -> (ffi::egl::types::EGLenum, *mut c_void, Vec<ffi::EGLint>); fn platform_display(&self) -> (ffi::egl::types::EGLenum, *mut c_void, Vec<ffi::EGLint>);
/// Type of surfaces created /// Type of surfaces created
fn surface_type(&self) -> ffi::EGLint { fn surface_type(&self) -> ffi::EGLint {

View File

@ -29,6 +29,14 @@ pub struct EGLSurface {
unsafe impl Send for EGLSurface {} unsafe impl Send for EGLSurface {}
impl EGLSurface { impl EGLSurface {
/// Create a new `EGLSurface`.
///
/// Requires:
/// - A EGLDisplay supported by the corresponding plattform matching the surface type
/// - A pixel format
/// - An (optional) preference for double_buffering
/// - A valid `EGLConfig` (see `EGLContext::config_id()`)
/// - An (optional) Logger
pub fn new<N, L>( pub fn new<N, L>(
display: &EGLDisplay, display: &EGLDisplay,
pixel_format: PixelFormat, pixel_format: PixelFormat,

View File

@ -14,10 +14,6 @@
//! - winit //! - winit
//! - libinput //! - libinput
// TODO TEMPORARY
#![allow(missing_docs)]
//pub mod graphics;
pub mod allocator; pub mod allocator;
pub mod input; pub mod input;
pub mod renderer; pub mod renderer;

View File

@ -1,3 +1,5 @@
//! Implementation of the rendering traits using OpenGL ES 2
use std::collections::HashSet; use std::collections::HashSet;
use std::ffi::CStr; use std::ffi::CStr;
use std::ptr; use std::ptr;
@ -35,7 +37,7 @@ struct Gles2Program {
attrib_tex_coords: ffi::types::GLint, attrib_tex_coords: ffi::types::GLint,
} }
// TODO: drop? /// A handle to a GLES2 texture
pub struct Gles2Texture { pub struct Gles2Texture {
texture: ffi::types::GLuint, texture: ffi::types::GLuint,
texture_kind: usize, texture_kind: usize,
@ -67,6 +69,7 @@ struct Gles2Buffer {
_dmabuf: Dmabuf, _dmabuf: Dmabuf,
} }
/// A renderer utilizing OpenGL ES 2
pub struct Gles2Renderer { pub struct Gles2Renderer {
buffers: Vec<WeakGles2Buffer>, buffers: Vec<WeakGles2Buffer>,
target_buffer: Option<Gles2Buffer>, target_buffer: Option<Gles2Buffer>,
@ -80,29 +83,39 @@ pub struct Gles2Renderer {
_not_send: *mut (), _not_send: *mut (),
} }
/// Error returned during rendering using GL ES
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Gles2Error { pub enum Gles2Error {
/// A shader could not be compiled
#[error("Failed to compile Shader: {0}")] #[error("Failed to compile Shader: {0}")]
ShaderCompileError(&'static str), ShaderCompileError(&'static str),
/// A program could not be linked
#[error("Failed to link Program")] #[error("Failed to link Program")]
ProgramLinkError, ProgramLinkError,
/// A framebuffer could not be bound
#[error("Failed to bind Framebuffer")] #[error("Failed to bind Framebuffer")]
FramebufferBindingError, FramebufferBindingError,
/// Required GL functions could not be loaded
#[error("Failed to load GL functions from EGL")] #[error("Failed to load GL functions from EGL")]
GLFunctionLoaderError, GLFunctionLoaderError,
/// The required GL extension is not supported by the underlying implementation /// Required GL extension are not supported by the underlying implementation
#[error("None of the following GL extensions is supported by the underlying GL implementation, at least one is required: {0:?}")] #[error("None of the following GL extensions is supported by the underlying GL implementation, at least one is required: {0:?}")]
GLExtensionNotSupported(&'static [&'static str]), GLExtensionNotSupported(&'static [&'static str]),
/// The underlying egl context could not be activated
#[error("Failed to active egl context")] #[error("Failed to active egl context")]
ContextActivationError(#[from] crate::backend::egl::MakeCurrentError), ContextActivationError(#[from] crate::backend::egl::MakeCurrentError),
///The given dmabuf could not be converted to an EGLImage for framebuffer use
#[error("Failed to convert dmabuf to EGLImage")] #[error("Failed to convert dmabuf to EGLImage")]
BindBufferEGLError(#[source] crate::backend::egl::Error), BindBufferEGLError(#[source] crate::backend::egl::Error),
/// The given buffer has an unsupported pixel format
#[error("Unsupported pixel format: {0:?}")] #[error("Unsupported pixel format: {0:?}")]
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
UnsupportedPixelFormat(wl_shm::Format), UnsupportedPixelFormat(wl_shm::Format),
/// The given buffer was not accessible
#[error("Error accessing the buffer ({0:?})")] #[error("Error accessing the buffer ({0:?})")]
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
BufferAccessError(crate::wayland::shm::BufferAccessError), BufferAccessError(crate::wayland::shm::BufferAccessError),
/// This rendering operation was called without a previous `begin`-call
#[error("Call begin before doing any rendering operations")] #[error("Call begin before doing any rendering operations")]
UnconstraintRenderingOperation, UnconstraintRenderingOperation,
} }
@ -220,6 +233,19 @@ unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result<Gles2Pr
} }
impl Gles2Renderer { impl Gles2Renderer {
/// Creates a new OpenGL ES 2 renderer from a given [`EGLContext`](backend::egl::EGLBuffer).
///
/// # Safety
///
/// This operation will cause undefined behavior if the given EGLContext is active in another thread.
///
/// # Implementation details
///
/// - Texture handles created by the resulting renderer are valid for every rendered created with an
/// `EGLContext` shared with the given one (see `EGLContext::new_shared`) and can be used and destroyed on
/// any of these renderers.
/// - This renderer has no default framebuffer, use `Bind::bind` before rendering.
/// - Shm buffers can be released after a successful import, without the texture handle becoming invalid.
pub unsafe fn new<L>(context: EGLContext, logger: L) -> Result<Gles2Renderer, Gles2Error> pub unsafe fn new<L>(context: EGLContext, logger: L) -> Result<Gles2Renderer, Gles2Error>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,

View File

@ -1,3 +1,12 @@
//! Rendering functionality and abstractions
//!
//! Collection of common traits and implementations
//! to facilitate (possible hardware-accelerated) rendering.
//!
//! Supported rendering apis:
//!
//! - Raw OpenGL ES 2
use std::collections::HashSet; use std::collections::HashSet;
use std::error::Error; use std::error::Error;
@ -12,18 +21,28 @@ pub mod gles2;
use crate::backend::egl::EGLBuffer; use crate::backend::egl::EGLBuffer;
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
/// Possible transformations to two-dimensional planes
pub enum Transform { pub enum Transform {
/// Identity transformation (plane is unaltered when applied)
Normal, Normal,
/// Plane is rotated by 90 degrees
_90, _90,
/// Plane is rotated by 180 degrees
_180, _180,
/// Plane is rotated by 270 degrees
_270, _270,
/// Plane is flipped vertically
Flipped, Flipped,
/// Plane is flipped vertically and rotated by 90 degrees
Flipped90, Flipped90,
/// Plane is flipped vertically and rotated by 180 degrees
Flipped180, Flipped180,
/// Plane is flipped vertically and rotated by 270 degrees
Flipped270, Flipped270,
} }
impl Transform { impl Transform {
/// A projection matrix to apply this transformation
pub fn matrix(&self) -> Matrix3<f32> { pub fn matrix(&self) -> Matrix3<f32> {
match self { match self {
Transform::Normal => Matrix3::new(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0), Transform::Normal => Matrix3::new(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0),
@ -37,6 +56,9 @@ impl Transform {
} }
} }
/// Inverts any 90-degree transformation into 270-degree transformations and vise versa.
///
/// Flipping is preserved and 180/Normal transformation are uneffected.
pub fn invert(&self) -> Transform { pub fn invert(&self) -> Transform {
match self { match self {
Transform::Normal => Transform::Normal, Transform::Normal => Transform::Normal,
@ -50,6 +72,7 @@ impl Transform {
} }
} }
/// Transformed size after applying this transformation.
pub fn transform_size(&self, width: u32, height: u32) -> (u32, u32) { pub fn transform_size(&self, width: u32, height: u32) -> (u32, u32) {
if *self == Transform::_90 if *self == Transform::_90
|| *self == Transform::_270 || *self == Transform::_270
@ -80,61 +103,142 @@ impl From<wayland_server::protocol::wl_output::Transform> for Transform {
} }
} }
/// Abstraction for Renderers, that can render into different targets
pub trait Bind<Target>: Unbind { pub trait Bind<Target>: Unbind {
/// Bind a given rendering target, which will contain the rendering results until `unbind` is called.
fn bind(&mut self, target: Target) -> Result<(), <Self as Renderer>::Error>; fn bind(&mut self, target: Target) -> Result<(), <Self as Renderer>::Error>;
/// Supported pixel formats for given targets, if applicable.
fn supported_formats(&self) -> Option<HashSet<crate::backend::allocator::Format>> { fn supported_formats(&self) -> Option<HashSet<crate::backend::allocator::Format>> {
None None
} }
} }
/// Functionality to unbind the current rendering target
pub trait Unbind: Renderer { pub trait Unbind: Renderer {
/// Unbind the current rendering target.
///
/// May fall back to a default target, if defined by the implementation.
fn unbind(&mut self) -> Result<(), <Self as Renderer>::Error>; fn unbind(&mut self) -> Result<(), <Self as Renderer>::Error>;
} }
/// A two dimensional texture
pub trait Texture { pub trait Texture {
/// Size of the texture plane (w x h)
fn size(&self) -> (u32, u32) { fn size(&self) -> (u32, u32) {
(self.width(), self.height()) (self.width(), self.height())
} }
/// Width of the texture plane
fn width(&self) -> u32; fn width(&self) -> u32;
/// Height of the texture plane
fn height(&self) -> u32; fn height(&self) -> u32;
} }
/// Abstraction of commonly used rendering operations for compositors.
pub trait Renderer { pub trait Renderer {
/// Error type returned by the rendering operations of this renderer.
type Error: Error; type Error: Error;
/// Texture Handle type used by this renderer.
type TextureId: Texture; type TextureId: Texture;
/// Import a given bitmap into the renderer.
///
/// Returns a texture_id, which can be used with `render_texture(_at)` or implementation-specific functions.
///
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it,
/// and needs to be freed by calling `destroy_texture` on this renderer to avoid a resource leak.
///
/// This operation needs no bound or default rendering target.
#[cfg(feature = "image")] #[cfg(feature = "image")]
fn import_bitmap<C: std::ops::Deref<Target = [u8]>>( fn import_bitmap<C: std::ops::Deref<Target = [u8]>>(
&mut self, &mut self,
image: &image::ImageBuffer<image::Rgba<u8>, C>, image: &image::ImageBuffer<image::Rgba<u8>, C>,
) -> Result<Self::TextureId, Self::Error>; ) -> Result<Self::TextureId, Self::Error>;
/// Returns supported formats for shared memory buffers.
///
/// Will always contain At least `Argb8888` and `Xrgb8888`.
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
fn shm_formats(&self) -> &[wl_shm::Format] { fn shm_formats(&self) -> &[wl_shm::Format] {
// Mandatory // Mandatory
&[wl_shm::Format::Argb8888, wl_shm::Format::Xrgb8888] &[wl_shm::Format::Argb8888, wl_shm::Format::Xrgb8888]
} }
/// Import a given shared memory buffer into the renderer.
///
/// Returns a texture_id, which can be used with `render_texture(_at)` or implementation-specific functions.
///
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it,
/// and needs to be freed by calling `destroy_texture` on this renderer to avoid a resource leak.
///
/// This operation needs no bound or default rendering target.
///
/// The implementation defines, if the id keeps being valid, if the buffer is released,
/// to avoid relying on implementation details, keep the buffer alive, until you destroyed this texture again.
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
fn import_shm(&mut self, buffer: &wl_buffer::WlBuffer) -> Result<Self::TextureId, Self::Error>; fn import_shm(&mut self, buffer: &wl_buffer::WlBuffer) -> Result<Self::TextureId, Self::Error>;
/// Import a given egl-backed memory buffer into the renderer.
///
/// Returns a texture_id, which can be used with `render_texture(_at)` or implementation-specific functions.
///
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it,
/// and needs to be freed by calling `destroy_texture` on this renderer to avoid a resource leak.
///
/// This operation needs no bound or default rendering target.
///
/// The implementation defines, if the id keeps being valid, if the buffer is released,
/// to avoid relying on implementation details, keep the buffer alive, until you destroyed this texture again.
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
fn import_egl(&mut self, buffer: &EGLBuffer) -> Result<Self::TextureId, Self::Error>; fn import_egl(&mut self, buffer: &EGLBuffer) -> Result<Self::TextureId, Self::Error>;
/// Deallocate the given texture.
///
/// In case the texture type of this renderer is cloneable or copyable, those handles will also become invalid
/// and destroy calls with one of these handles might error out as the texture is already freed.
fn destroy_texture(&mut self, texture: Self::TextureId) -> Result<(), Self::Error>; fn destroy_texture(&mut self, texture: Self::TextureId) -> Result<(), Self::Error>;
/// Initialize a rendering context on the current rendering target with given dimensions and transformation.
///
/// This function *may* error, if:
/// - The given dimensions are unsuppored (too large) for this renderer
/// - The given Transformation is not supported by the renderer (`Transform::Normal` is always supported).
/// - There was a previous `begin`-call, which was not terminated by `finish`.
/// - This renderer implements `Bind`, no target was bound *and* has no default target.
/// - (Renderers not implementing `Bind` always have a default target.)
fn begin( fn begin(
&mut self, &mut self,
width: u32, width: u32,
height: u32, height: u32,
transform: Transform, transform: Transform,
) -> Result<(), <Self as Renderer>::Error>; ) -> Result<(), <Self as Renderer>::Error>;
/// Finish a renderering context, previously started by `begin`.
///
/// After this operation is finished the current rendering target contains a sucessfully rendered image.
/// If the image is immediently shown to the user depends on the target.
fn finish(&mut self) -> Result<(), SwapBuffersError>; fn finish(&mut self) -> Result<(), SwapBuffersError>;
/// Clear the complete current target with a single given color.
///
/// This operation is only valid in between a `begin` and `finish`-call.
/// If called outside this operation may error-out, do nothing or modify future rendering results in any way.
fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error>; fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error>;
/// Render a texture to the current target using given projection matrix and alpha.
///
/// This operation is only valid in between a `begin` and `finish`-call.
/// If called outside this operation may error-out, do nothing or modify future rendering results in any way.
fn render_texture( fn render_texture(
&mut self, &mut self,
texture: &Self::TextureId, texture: &Self::TextureId,
matrix: Matrix3<f32>, matrix: Matrix3<f32>,
alpha: f32, alpha: f32,
) -> Result<(), Self::Error>; ) -> Result<(), Self::Error>;
/// Render a texture to the current target as a flat 2d-plane at a given
/// position, applying the given transformation with the given alpha value.
///
/// This operation is only valid in between a `begin` and `finish`-call.
/// If called outside this operation may error-out, do nothing or modify future rendering results in any way.
fn render_texture_at( fn render_texture_at(
&mut self, &mut self,
texture: &Self::TextureId, texture: &Self::TextureId,

View File

@ -52,9 +52,12 @@ pub enum Error {
RendererCreationError(#[from] Gles2Error), RendererCreationError(#[from] Gles2Error),
} }
/// Size properties of a winit window
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct WindowSize { pub struct WindowSize {
/// Pixel side of the window
pub physical_size: PhysicalSize<u32>, pub physical_size: PhysicalSize<u32>,
/// Scaling factor of the window
pub scale_factor: f64, pub scale_factor: f64,
} }
@ -239,18 +242,26 @@ pub enum WinitEvent {
#[cfg(feature = "use_system_lib")] #[cfg(feature = "use_system_lib")]
impl WinitGraphicsBackend { impl WinitGraphicsBackend {
/// Bind a `wl_display` to allow hardware-accelerated clients using `wl_drm`.
///
/// Returns an `EGLBufferReader` used to access the contents of these buffers.
///
/// *Note*: Only on implementation of `wl_drm` can be bound by a single wayland display.
pub fn bind_wl_display(&self, wl_display: &Display) -> Result<EGLBufferReader, EGLError> { pub fn bind_wl_display(&self, wl_display: &Display) -> Result<EGLBufferReader, EGLError> {
self.display.bind_wl_display(wl_display) self.display.bind_wl_display(wl_display)
} }
/// Window size of the underlying window
pub fn window_size(&self) -> WindowSize { pub fn window_size(&self) -> WindowSize {
self.size.borrow().clone() self.size.borrow().clone()
} }
/// Reference to the underlying window
pub fn window(&self) -> &WinitWindow { pub fn window(&self) -> &WinitWindow {
&*self.window &*self.window
} }
/// Shortcut to `Renderer::begin` with the current window dimensions.
pub fn begin(&mut self) -> Result<(), Gles2Error> { pub fn begin(&mut self) -> Result<(), Gles2Error> {
let (width, height) = { let (width, height) = {
let size = self.size.borrow(); let size = self.size.borrow();