First pass of adding documentation
This commit is contained in:
parent
52c01535d0
commit
7e47d648d4
|
@ -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,18 +144,20 @@ 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 db = *next.handle();
|
||||||
let mut mapping = self.surface.map_dumb_buffer(&mut db).unwrap();
|
let mut mapping = self.surface.map_dumb_buffer(&mut db).unwrap();
|
||||||
for x in mapping.as_mut() {
|
for x in mapping.as_mut() {
|
||||||
*x = 128;
|
*x = 128;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self.current = next;
|
self.current = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>>,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue