From 7e47d648d4b50c1d2e1c017e1a9aae2e33f18c59 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Fri, 30 Apr 2021 17:21:35 +0200 Subject: [PATCH] First pass of adding documentation --- examples/raw_drm.rs | 14 ++-- src/backend/allocator/dmabuf.rs | 12 ++++ src/backend/allocator/dumb.rs | 15 ++++- src/backend/allocator/gbm.rs | 7 ++ src/backend/allocator/mod.rs | 25 +++++++ src/backend/allocator/swapchain.rs | 48 +++++++++++++ src/backend/drm/device/mod.rs | 53 +++++++++++++++ src/backend/drm/error.rs | 2 + src/backend/drm/mod.rs | 62 +++++++++++++++++ src/backend/drm/render.rs | 77 ++++++++++++++++++++- src/backend/drm/surface/mod.rs | 10 +++ src/backend/egl/context.rs | 5 +- src/backend/egl/mod.rs | 1 + src/backend/egl/native.rs | 3 + src/backend/egl/surface.rs | 8 +++ src/backend/mod.rs | 4 -- src/backend/renderer/gles2/mod.rs | 30 ++++++++- src/backend/renderer/mod.rs | 104 +++++++++++++++++++++++++++++ src/backend/winit.rs | 11 +++ 19 files changed, 474 insertions(+), 17 deletions(-) diff --git a/examples/raw_drm.rs b/examples/raw_drm.rs index 415944e..261b7b8 100644 --- a/examples/raw_drm.rs +++ b/examples/raw_drm.rs @@ -106,7 +106,7 @@ fn main() { }, ); let first_buffer: Slot, _> = 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); // Get the device as an allocator into the @@ -144,17 +144,19 @@ impl DeviceHandler for DrmHandlerImpl { // Next buffer let next = self.swapchain.acquire().unwrap().unwrap(); 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); } // now we could render to the mapping via software rendering. // this example just sets some grey color - let mut db = next.handle; - let mut mapping = self.surface.map_dumb_buffer(&mut db).unwrap(); - for x in mapping.as_mut() { - *x = 128; + { + let mut db = *next.handle(); + let mut mapping = self.surface.map_dumb_buffer(&mut db).unwrap(); + for x in mapping.as_mut() { + *x = 128; + } } self.current = next; } diff --git a/src/backend/allocator/dmabuf.rs b/src/backend/allocator/dmabuf.rs index d5fdd49..b025af4 100644 --- a/src/backend/allocator/dmabuf.rs +++ b/src/backend/allocator/dmabuf.rs @@ -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 std::os::unix::io::RawFd; use std::sync::{Arc, Weak}; @@ -15,9 +17,11 @@ pub(crate) struct DmabufInternal { } #[derive(Clone)] +/// Strong reference to a dmabuf handle pub struct Dmabuf(pub(crate) Arc); #[derive(Clone)] +/// Weak reference to a dmabuf handle pub struct WeakDmabuf(pub(crate) Weak); impl PartialEq for Dmabuf { @@ -109,28 +113,36 @@ impl Dmabuf { }))) } + /// Return raw handles of the planes of this buffer pub fn handles(&self) -> &[RawFd] { self.0.fds.split_at(self.0.num_planes).0 } + /// Return offsets for the planes of this buffer pub fn offsets(&self) -> &[u32] { self.0.offsets.split_at(self.0.num_planes).0 } + /// Return strides for the planes of this buffer pub fn strides(&self) -> &[u32] { 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 { self.0.format.modifier != Modifier::Invalid && self.0.format.modifier != Modifier::Linear } + /// Create a weak reference to this dmabuf pub fn weak(&self) -> WeakDmabuf { WeakDmabuf(Arc::downgrade(&self.0)) } } 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 { self.0.upgrade().map(Dmabuf) } diff --git a/src/backend/allocator/dumb.rs b/src/backend/allocator/dumb.rs index f166662..42b858c 100644 --- a/src/backend/allocator/dumb.rs +++ b/src/backend/allocator/dumb.rs @@ -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::sync::Arc; @@ -7,9 +9,10 @@ use drm::control::{dumbbuffer::DumbBuffer as Handle, Device as ControlDevice}; use super::{Allocator, Buffer, Format}; use crate::backend::drm::device::{DrmDevice, DrmDeviceInternal, FdWrapper}; +/// Wrapper around raw DumbBuffer handles. pub struct DumbBuffer { fd: Arc>, - pub handle: Handle, + handle: Handle, format: Format, } @@ -49,6 +52,16 @@ impl Buffer for DumbBuffer { } } +impl DumbBuffer { + /// 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 Drop for DumbBuffer { fn drop(&mut self) { let _ = self.fd.destroy_dumb_buffer(self.handle); diff --git a/src/backend/allocator/gbm.rs b/src/backend/allocator/gbm.rs index 70c6d87..e5ef0ea 100644 --- a/src/backend/allocator/gbm.rs +++ b/src/backend/allocator/gbm.rs @@ -1,3 +1,5 @@ +//! Module for Buffers created using [libgbm](reexports::gbm) + use super::{dmabuf::Dmabuf, Allocator, Buffer, Format, Fourcc, Modifier}; use gbm::{BufferObject as GbmBuffer, BufferObjectFlags, Device as GbmDevice}; use std::os::unix::io::AsRawFd; @@ -40,12 +42,16 @@ impl Buffer for GbmBuffer { } } +/// Errors during conversion to a dmabuf handle from a gbm buffer object #[derive(thiserror::Error, Debug)] pub enum GbmConvertError { + /// The gbm device was destroyed #[error("The gbm device was destroyed")] 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")] UnsupportedBuffer, + /// The conversion returned an invalid file descriptor #[error("Buffer returned invalid file descriptor")] InvalidFD, } @@ -94,6 +100,7 @@ impl std::convert::TryFrom> for Dmabuf { } impl Dmabuf { + /// Import a Dmabuf using libgbm, creating a gbm Buffer Object to the same underlying data. pub fn import( &self, gbm: &GbmDevice, diff --git a/src/backend/allocator/mod.rs b/src/backend/allocator/mod.rs index c543e42..7cbe4da 100644 --- a/src/backend/allocator/mod.rs +++ b/src/backend/allocator/mod.rs @@ -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; #[cfg(feature = "backend_drm")] pub mod dumb; @@ -12,17 +29,25 @@ pub use drm_fourcc::{ UnrecognizedFourcc, UnrecognizedVendor, }; +/// Common trait describing common properties of most types of buffers. pub trait Buffer { + /// Width of the two-dimensional buffer fn width(&self) -> u32; + /// Height of the two-dimensional buffer fn height(&self) -> u32; + /// Size (w x h) of the two-dimensional buffer fn size(&self) -> (u32, u32) { (self.width(), self.height()) } + /// Pixel format of the buffer fn format(&self) -> Format; } +/// Interface to create Buffers pub trait Allocator { + /// Error type thrown if allocations fail 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; } diff --git a/src/backend/allocator/swapchain.rs b/src/backend/allocator/swapchain.rs index cca4bb1..64a8821 100644 --- a/src/backend/allocator/swapchain.rs +++ b/src/backend/allocator/swapchain.rs @@ -9,7 +9,36 @@ use crate::backend::allocator::{Allocator, Buffer, Format}; 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, B: Buffer + TryInto, U: 'static, D: Buffer = B> { + /// Allocator used by the swapchain pub allocator: A, _original_buffer_format: std::marker::PhantomData, @@ -20,6 +49,11 @@ pub struct Swapchain, B: Buffer + TryInto, U: 'static, D: Buf slots: [Slot; 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 { buffer: Arc>, acquired: Arc, @@ -27,14 +61,17 @@ pub struct Slot { } impl Slot { + /// Set userdata for this slot. pub fn set_userdata(&self, data: U) -> Option { self.userdata.lock().unwrap().replace(data) } + /// Retrieve userdata for this slot. pub fn userdata(&self) -> MutexGuard<'_, Option> { self.userdata.lock().unwrap() } + /// Clear userdata contained in this slot. pub fn clear_userdata(&self) -> Option { self.userdata.lock().unwrap().take() } @@ -73,14 +110,17 @@ impl Drop for Slot { } } +/// Error that can happen on acquiring a buffer #[derive(Debug, thiserror::Error)] pub enum SwapchainError where E1: std::error::Error + 'static, E2: std::error::Error + 'static, { + /// The allocator returned an error #[error("Failed to allocate a new buffer: {0}")] AllocationError(#[source] E1), + /// The buffer could not be successfully converted into the desired format #[error("Failed to convert a new buffer: {0}")] ConversionError(#[source] E2), } @@ -94,6 +134,7 @@ where E2: std::error::Error + '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 { Swapchain { 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>, SwapchainError> { if let Some(free_slot) = self.slots.iter_mut().find(|s| !s.acquired.load(Ordering::SeqCst)) { if free_slot.buffer.is_none() { @@ -127,6 +172,9 @@ where 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) { if self.width == width && self.height == height { return; diff --git a/src/backend/drm/device/mod.rs b/src/backend/drm/device/mod.rs index 59de70d..20ad6cf 100644 --- a/src/backend/drm/device/mod.rs +++ b/src/backend/drm/device/mod.rs @@ -23,6 +23,7 @@ use crate::backend::allocator::{Format, Fourcc, Modifier}; use atomic::AtomicDrmDevice; use legacy::LegacyDrmDevice; +/// An open drm device pub struct DrmDevice { pub(super) dev_id: dev_t, pub(crate) internal: Arc>, @@ -87,6 +88,23 @@ impl BasicDevice for DrmDeviceInternal {} impl ControlDevice for DrmDeviceInternal {} impl DrmDevice { + /// 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(fd: A, disable_connectors: bool, logger: L) -> Result where A: AsRawFd + 'static, @@ -175,6 +193,13 @@ impl DrmDevice { ) } + /// 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) { match self.receive_events() { Ok(events) => { @@ -205,6 +230,7 @@ impl DrmDevice { } } + /// Returns if the underlying implementation uses atomic-modesetting or not. pub fn is_atomic(&self) -> bool { match *self.internal { DrmDeviceInternal::Atomic(_) => true, @@ -212,19 +238,25 @@ impl DrmDevice { } } + /// Assigns a [`DeviceHandler`] called during event processing. + /// + /// See [`device_bind`] and [`DeviceHandler`] pub fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { let handler = Some(Box::new(handler) as Box); *self.handler.borrow_mut() = handler; } + /// Clear a set [`DeviceHandler`](trait.DeviceHandler.html), if any pub fn clear_handler(&mut self) { self.handler.borrow_mut().take(); } + /// Returns a list of crtcs for this device pub fn crtcs(&self) -> &[crtc::Handle] { self.resources.crtcs() } + /// Returns a set of available planes for a given crtc pub fn planes(&self, crtc: &crtc::Handle) -> Result { let mut primary = None; let mut cursor = None; @@ -287,6 +319,22 @@ impl DrmDevice { 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( &self, crtc: crtc::Handle, @@ -456,14 +504,19 @@ impl DrmDevice { }) } + /// Returns the device_id of the underlying drm node pub fn device_id(&self) -> dev_t { self.dev_id } } +/// A set of planes as supported by a crtc pub struct Planes { + /// The primary plane of the crtc pub primary: plane::Handle, + /// The cursor plane of the crtc, if available pub cursor: Option, + /// Overlay planes supported by the crtc, if available pub overlay: Option>, } diff --git a/src/backend/drm/error.rs b/src/backend/drm/error.rs index 1a2587e..446477a 100644 --- a/src/backend/drm/error.rs +++ b/src/backend/drm/error.rs @@ -35,8 +35,10 @@ pub enum Error { /// This operation would result in a surface without connectors. #[error("Surface of crtc `{0:?}` would have no connectors, which is not accepted")] SurfaceWithoutConnectors(crtc::Handle), + /// The given plane cannot be used with the given crtc #[error("Plane `{1:?}` is not compatible for use with crtc `{0:?}`")] 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")] NonPrimaryPlane(plane::Handle), /// No encoder was found for a given connector on the set crtc diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 26e0957..148c77f 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -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(self) mod error; mod render; diff --git a/src/backend/drm/render.rs b/src/backend/drm/render.rs index 4ace014..1c3e3cd 100644 --- a/src/backend/drm/render.rs +++ b/src/backend/drm/render.rs @@ -18,6 +18,13 @@ use crate::backend::egl::EGLBuffer; use crate::backend::renderer::{Bind, Renderer, Texture, Transform}; 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< D: AsRawFd + 'static, A: Allocator, @@ -42,6 +49,15 @@ where E2: 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)] pub fn new>>( drm: DrmSurface, @@ -201,12 +217,17 @@ where } } + /// Shortcut to [`Renderer::begin`] with the pending mode as dimensions. pub fn queue_frame(&mut self) -> Result<(), Error> { let mode = self.drm.pending_mode(); 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> { if self.current_buffer.is_none() { return Ok(()); @@ -219,46 +240,86 @@ where 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> { self.buffers.submitted() } + /// Returns the underlying [`crtc`](drm::control::crtc) of this surface pub fn crtc(&self) -> crtc::Handle { self.drm.crtc() } + /// Returns the underlying [`plane`](drm::control::plane) of this surface pub fn plane(&self) -> plane::Handle { self.drm.plane() } + /// Currently used [`connector`](drm::control::connector)s of this `Surface` pub fn current_connectors(&self) -> impl IntoIterator { 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 { 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> { 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> { 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> { 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 { 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 { 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> { self.drm.use_mode(mode).map_err(Error::DrmError) } @@ -305,7 +366,7 @@ where self.renderer.destroy_texture(texture).map_err(Error::RenderError) } - fn begin(&mut self, width: u32, height: u32, transform: Transform) -> Result<(), Error> { + fn begin(&mut self, width: u32, height: u32, _transform: Transform) -> Result<(), Error> { if self.current_buffer.is_some() { return Ok(()); } @@ -314,7 +375,7 @@ where self.renderer.bind((*slot).clone()).map_err(Error::RenderError)?; self.current_buffer = Some(slot); self.renderer - .begin(width, height, transform) + .begin(width, height, Transform::Flipped180 /* TODO: add Add implementation to add and correct _transform here */) .map_err(Error::RenderError) } @@ -512,6 +573,7 @@ where Ok(bo) } +/// Errors thrown by a [`DrmRenderSurface`] #[derive(Debug, thiserror::Error)] pub enum Error where @@ -519,22 +581,31 @@ where E2: 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")] NoSupportedPlaneFormat, + /// No supported pixel format for the given renderer could be determined #[error("No supported renderer buffer format found")] NoSupportedRendererFormat, + /// The supported pixel formats of the renderer and plane are incompatible #[error("Supported plane and renderer buffer formats are incompatible")] FormatsNotCompatible, + /// The swapchain is exhausted, you need to call `frame_submitted` #[error("Failed to allocate a new buffer")] NoFreeSlotsError, + /// Failed to renderer using the given renderer #[error("Failed to render test frame")] InitialRenderingError, + /// Error accessing the drm device #[error("The underlying drm surface encounted an error: {0}")] DrmError(#[from] DrmError), + /// Error importing the rendered buffer to libgbm for scan-out #[error("The underlying gbm device encounted an error: {0}")] GbmError(#[from] std::io::Error), + /// Error allocating or converting newly created buffers #[error("The swapchain encounted an error: {0}")] SwapchainError(#[from] SwapchainError), + /// Error during rendering #[error("The renderer encounted an error: {0}")] RenderError(#[source] E3), } diff --git a/src/backend/drm/surface/mod.rs b/src/backend/drm/surface/mod.rs index 5550a40..06359a4 100644 --- a/src/backend/drm/surface/mod.rs +++ b/src/backend/drm/surface/mod.rs @@ -12,6 +12,7 @@ use crate::backend::allocator::Format; use atomic::AtomicDrmSurface; use legacy::LegacyDrmSurface; +/// An open crtc + plane combination that can be used for scan-out pub struct DrmSurface { pub(super) crtc: crtc::Handle, pub(super) plane: plane::Handle, @@ -180,10 +181,19 @@ impl DrmSurface { } } + /// Returns a set of supported pixel formats for attached buffers pub fn supported_formats(&self) -> &HashSet { &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( &self, fb: framebuffer::Handle, diff --git a/src/backend/egl/context.rs b/src/backend/egl/context.rs index 518e415..2151e69 100644 --- a/src/backend/egl/context.rs +++ b/src/backend/egl/context.rs @@ -19,6 +19,7 @@ unsafe impl Send for EGLContext {} unsafe impl Sync for EGLContext {} impl EGLContext { + /// Creates a new configless `EGLContext` from a given `EGLDisplay` pub fn new(display: &EGLDisplay, log: L) -> Result where L: Into>, @@ -26,7 +27,7 @@ impl EGLContext { 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( display: &EGLDisplay, attributes: GlAttributes, @@ -39,6 +40,7 @@ impl EGLContext { 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(display: &EGLDisplay, share: &EGLContext, log: L) -> Result where L: Into>, @@ -46,6 +48,7 @@ impl EGLContext { 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( display: &EGLDisplay, share: &EGLContext, diff --git a/src/backend/egl/mod.rs b/src/backend/egl/mod.rs index caf44ee..1f02640 100644 --- a/src/backend/egl/mod.rs +++ b/src/backend/egl/mod.rs @@ -275,6 +275,7 @@ impl EGLBuffer { self.format.num_planes() } + /// Returns the `EGLImage` handle for a given plane pub fn image(&self, plane: usize) -> Option { if plane > self.format.num_planes() { None diff --git a/src/backend/egl/native.rs b/src/backend/egl/native.rs index 01b59ca..37c58f2 100644 --- a/src/backend/egl/native.rs +++ b/src/backend/egl/native.rs @@ -16,8 +16,11 @@ use winit::window::Window as WinitWindow; #[cfg(feature = "backend_gbm")] 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 { + /// Required extensions to use this platform 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); /// Type of surfaces created fn surface_type(&self) -> ffi::EGLint { diff --git a/src/backend/egl/surface.rs b/src/backend/egl/surface.rs index ef610c0..6008749 100644 --- a/src/backend/egl/surface.rs +++ b/src/backend/egl/surface.rs @@ -29,6 +29,14 @@ pub struct EGLSurface { unsafe impl Send for 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( display: &EGLDisplay, pixel_format: PixelFormat, diff --git a/src/backend/mod.rs b/src/backend/mod.rs index ae565b8..c98f0d5 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -14,10 +14,6 @@ //! - winit //! - libinput -// TODO TEMPORARY -#![allow(missing_docs)] - -//pub mod graphics; pub mod allocator; pub mod input; pub mod renderer; diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index 306ea9a..9e93140 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -1,3 +1,5 @@ +//! Implementation of the rendering traits using OpenGL ES 2 + use std::collections::HashSet; use std::ffi::CStr; use std::ptr; @@ -35,7 +37,7 @@ struct Gles2Program { attrib_tex_coords: ffi::types::GLint, } -// TODO: drop? +/// A handle to a GLES2 texture pub struct Gles2Texture { texture: ffi::types::GLuint, texture_kind: usize, @@ -67,6 +69,7 @@ struct Gles2Buffer { _dmabuf: Dmabuf, } +/// A renderer utilizing OpenGL ES 2 pub struct Gles2Renderer { buffers: Vec, target_buffer: Option, @@ -80,29 +83,39 @@ pub struct Gles2Renderer { _not_send: *mut (), } +/// Error returned during rendering using GL ES #[derive(thiserror::Error, Debug)] pub enum Gles2Error { + /// A shader could not be compiled #[error("Failed to compile Shader: {0}")] ShaderCompileError(&'static str), + /// A program could not be linked #[error("Failed to link Program")] ProgramLinkError, + /// A framebuffer could not be bound #[error("Failed to bind Framebuffer")] FramebufferBindingError, + /// Required GL functions could not be loaded #[error("Failed to load GL functions from EGL")] 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:?}")] GLExtensionNotSupported(&'static [&'static str]), + /// The underlying egl context could not be activated #[error("Failed to active egl context")] 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")] BindBufferEGLError(#[source] crate::backend::egl::Error), + /// The given buffer has an unsupported pixel format #[error("Unsupported pixel format: {0:?}")] #[cfg(feature = "wayland_frontend")] UnsupportedPixelFormat(wl_shm::Format), + /// The given buffer was not accessible #[error("Error accessing the buffer ({0:?})")] #[cfg(feature = "wayland_frontend")] BufferAccessError(crate::wayland::shm::BufferAccessError), + /// This rendering operation was called without a previous `begin`-call #[error("Call begin before doing any rendering operations")] UnconstraintRenderingOperation, } @@ -220,6 +233,19 @@ unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result(context: EGLContext, logger: L) -> Result where L: Into>, diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index 4b1a541..6e4087d 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -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::error::Error; @@ -12,18 +21,28 @@ pub mod gles2; use crate::backend::egl::EGLBuffer; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +/// Possible transformations to two-dimensional planes pub enum Transform { + /// Identity transformation (plane is unaltered when applied) Normal, + /// Plane is rotated by 90 degrees _90, + /// Plane is rotated by 180 degrees _180, + /// Plane is rotated by 270 degrees _270, + /// Plane is flipped vertically Flipped, + /// Plane is flipped vertically and rotated by 90 degrees Flipped90, + /// Plane is flipped vertically and rotated by 180 degrees Flipped180, + /// Plane is flipped vertically and rotated by 270 degrees Flipped270, } impl Transform { + /// A projection matrix to apply this transformation pub fn matrix(&self) -> Matrix3 { match self { 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 { match self { 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) { if *self == Transform::_90 || *self == Transform::_270 @@ -80,61 +103,142 @@ impl From for Transform { } } +/// Abstraction for Renderers, that can render into different targets pub trait Bind: Unbind { + /// Bind a given rendering target, which will contain the rendering results until `unbind` is called. fn bind(&mut self, target: Target) -> Result<(), ::Error>; + /// Supported pixel formats for given targets, if applicable. fn supported_formats(&self) -> Option> { None } } +/// Functionality to unbind the current rendering target 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<(), ::Error>; } +/// A two dimensional texture pub trait Texture { + /// Size of the texture plane (w x h) fn size(&self) -> (u32, u32) { (self.width(), self.height()) } + /// Width of the texture plane fn width(&self) -> u32; + /// Height of the texture plane fn height(&self) -> u32; } +/// Abstraction of commonly used rendering operations for compositors. pub trait Renderer { + /// Error type returned by the rendering operations of this renderer. type Error: Error; + /// Texture Handle type used by this renderer. 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")] fn import_bitmap>( &mut self, image: &image::ImageBuffer, C>, ) -> Result; + + /// Returns supported formats for shared memory buffers. + /// + /// Will always contain At least `Argb8888` and `Xrgb8888`. #[cfg(feature = "wayland_frontend")] fn shm_formats(&self) -> &[wl_shm::Format] { // Mandatory &[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")] fn import_shm(&mut self, buffer: &wl_buffer::WlBuffer) -> Result; + + /// 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")] fn import_egl(&mut self, buffer: &EGLBuffer) -> Result; + + /// 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>; + /// 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( &mut self, width: u32, height: u32, transform: Transform, ) -> Result<(), ::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>; + /// 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>; + /// 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( &mut self, texture: &Self::TextureId, matrix: Matrix3, alpha: f32, ) -> 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( &mut self, texture: &Self::TextureId, diff --git a/src/backend/winit.rs b/src/backend/winit.rs index fdebd8d..de4f7ec 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -52,9 +52,12 @@ pub enum Error { RendererCreationError(#[from] Gles2Error), } +/// Size properties of a winit window #[derive(Debug, Clone)] pub struct WindowSize { + /// Pixel side of the window pub physical_size: PhysicalSize, + /// Scaling factor of the window pub scale_factor: f64, } @@ -239,18 +242,26 @@ pub enum WinitEvent { #[cfg(feature = "use_system_lib")] 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 { self.display.bind_wl_display(wl_display) } + /// Window size of the underlying window pub fn window_size(&self) -> WindowSize { self.size.borrow().clone() } + /// Reference to the underlying window pub fn window(&self) -> &WinitWindow { &*self.window } + /// Shortcut to `Renderer::begin` with the current window dimensions. pub fn begin(&mut self) -> Result<(), Gles2Error> { let (width, height) = { let size = self.size.borrow();