2021-04-07 22:58:39 +00:00
|
|
|
use std::convert::TryInto;
|
2021-04-06 22:54:46 +00:00
|
|
|
use std::ops::Deref;
|
2021-04-28 22:32:47 +00:00
|
|
|
use std::sync::{
|
|
|
|
atomic::{AtomicBool, Ordering},
|
|
|
|
Arc, Mutex, MutexGuard,
|
|
|
|
};
|
2021-04-06 22:54:46 +00:00
|
|
|
|
|
|
|
use crate::backend::allocator::{Allocator, Buffer, Format};
|
|
|
|
|
2021-04-11 21:01:08 +00:00
|
|
|
pub const SLOT_CAP: usize = 4;
|
2021-04-06 22:54:46 +00:00
|
|
|
|
2021-04-30 15:21:35 +00:00
|
|
|
/// 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.
|
2021-04-25 21:30:25 +00:00
|
|
|
pub struct Swapchain<A: Allocator<B>, B: Buffer + TryInto<B>, U: 'static, D: Buffer = B> {
|
2021-04-30 15:21:35 +00:00
|
|
|
/// Allocator used by the swapchain
|
2021-04-25 21:30:25 +00:00
|
|
|
pub allocator: A,
|
2021-04-07 22:58:39 +00:00
|
|
|
_original_buffer_format: std::marker::PhantomData<B>,
|
2021-04-06 22:54:46 +00:00
|
|
|
|
|
|
|
width: u32,
|
|
|
|
height: u32,
|
|
|
|
format: Format,
|
|
|
|
|
2021-04-25 21:30:25 +00:00
|
|
|
slots: [Slot<D, U>; SLOT_CAP],
|
2021-04-06 22:54:46 +00:00
|
|
|
}
|
|
|
|
|
2021-04-30 15:21:35 +00:00
|
|
|
/// 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.
|
2021-04-25 21:30:25 +00:00
|
|
|
pub struct Slot<B: Buffer, U: 'static> {
|
2021-04-06 22:54:46 +00:00
|
|
|
buffer: Arc<Option<B>>,
|
|
|
|
acquired: Arc<AtomicBool>,
|
2021-04-25 21:30:25 +00:00
|
|
|
userdata: Arc<Mutex<Option<U>>>,
|
2021-04-06 22:54:46 +00:00
|
|
|
}
|
|
|
|
|
2021-04-25 21:30:25 +00:00
|
|
|
impl<B: Buffer, U: 'static> Slot<B, U> {
|
2021-04-30 15:21:35 +00:00
|
|
|
/// Set userdata for this slot.
|
2021-04-25 21:30:25 +00:00
|
|
|
pub fn set_userdata(&self, data: U) -> Option<U> {
|
|
|
|
self.userdata.lock().unwrap().replace(data)
|
|
|
|
}
|
|
|
|
|
2021-04-30 15:21:35 +00:00
|
|
|
/// Retrieve userdata for this slot.
|
2021-04-25 21:30:25 +00:00
|
|
|
pub fn userdata(&self) -> MutexGuard<'_, Option<U>> {
|
|
|
|
self.userdata.lock().unwrap()
|
|
|
|
}
|
|
|
|
|
2021-04-30 15:21:35 +00:00
|
|
|
/// Clear userdata contained in this slot.
|
2021-04-25 21:30:25 +00:00
|
|
|
pub fn clear_userdata(&self) -> Option<U> {
|
|
|
|
self.userdata.lock().unwrap().take()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<B: Buffer, U: 'static> Default for Slot<B, U> {
|
2021-04-06 22:54:46 +00:00
|
|
|
fn default() -> Self {
|
|
|
|
Slot {
|
|
|
|
buffer: Arc::new(None),
|
|
|
|
acquired: Arc::new(AtomicBool::new(false)),
|
2021-04-25 21:30:25 +00:00
|
|
|
userdata: Arc::new(Mutex::new(None)),
|
2021-04-06 22:54:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-25 21:30:25 +00:00
|
|
|
impl<B: Buffer, U: 'static> Clone for Slot<B, U> {
|
2021-04-06 22:54:46 +00:00
|
|
|
fn clone(&self) -> Self {
|
|
|
|
Slot {
|
|
|
|
buffer: self.buffer.clone(),
|
|
|
|
acquired: self.acquired.clone(),
|
2021-04-25 21:30:25 +00:00
|
|
|
userdata: self.userdata.clone(),
|
2021-04-06 22:54:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-25 21:30:25 +00:00
|
|
|
impl<B: Buffer, U: 'static> Deref for Slot<B, U> {
|
2021-04-06 22:54:46 +00:00
|
|
|
type Target = B;
|
|
|
|
fn deref(&self) -> &B {
|
|
|
|
Option::as_ref(&*self.buffer).unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-25 21:30:25 +00:00
|
|
|
impl<B: Buffer, U: 'static> Drop for Slot<B, U> {
|
2021-04-06 22:54:46 +00:00
|
|
|
fn drop(&mut self) {
|
2021-04-25 21:30:25 +00:00
|
|
|
self.acquired.store(false, Ordering::SeqCst);
|
2021-04-06 22:54:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-30 15:21:35 +00:00
|
|
|
/// Error that can happen on acquiring a buffer
|
2021-04-07 22:58:39 +00:00
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
pub enum SwapchainError<E1, E2>
|
|
|
|
where
|
|
|
|
E1: std::error::Error + 'static,
|
|
|
|
E2: std::error::Error + 'static,
|
|
|
|
{
|
2021-04-30 15:21:35 +00:00
|
|
|
/// The allocator returned an error
|
2021-04-07 22:58:39 +00:00
|
|
|
#[error("Failed to allocate a new buffer: {0}")]
|
|
|
|
AllocationError(#[source] E1),
|
2021-04-30 15:21:35 +00:00
|
|
|
/// The buffer could not be successfully converted into the desired format
|
2021-04-07 22:58:39 +00:00
|
|
|
#[error("Failed to convert a new buffer: {0}")]
|
|
|
|
ConversionError(#[source] E2),
|
|
|
|
}
|
|
|
|
|
2021-04-25 21:30:25 +00:00
|
|
|
impl<A, B, D, U, E1, E2> Swapchain<A, B, U, D>
|
2021-04-07 22:58:39 +00:00
|
|
|
where
|
2021-04-28 22:32:47 +00:00
|
|
|
A: Allocator<B, Error = E1>,
|
|
|
|
B: Buffer + TryInto<D, Error = E2>,
|
2021-04-07 22:58:39 +00:00
|
|
|
D: Buffer,
|
|
|
|
E1: std::error::Error + 'static,
|
|
|
|
E2: std::error::Error + 'static,
|
2021-04-28 22:32:47 +00:00
|
|
|
U: 'static,
|
2021-04-07 22:58:39 +00:00
|
|
|
{
|
2021-04-30 15:21:35 +00:00
|
|
|
/// Create a new swapchain with the desired allocator and dimensions and pixel format for the created buffers.
|
2021-04-25 21:30:25 +00:00
|
|
|
pub fn new(allocator: A, width: u32, height: u32, format: Format) -> Swapchain<A, B, U, D> {
|
2021-04-06 22:54:46 +00:00
|
|
|
Swapchain {
|
|
|
|
allocator,
|
2021-04-07 22:58:39 +00:00
|
|
|
_original_buffer_format: std::marker::PhantomData,
|
2021-04-06 22:54:46 +00:00
|
|
|
width,
|
|
|
|
height,
|
|
|
|
format,
|
|
|
|
slots: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-30 15:21:35 +00:00
|
|
|
/// 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.
|
2021-04-25 21:30:25 +00:00
|
|
|
pub fn acquire(&mut self) -> Result<Option<Slot<D, U>>, SwapchainError<E1, E2>> {
|
2021-04-28 22:31:49 +00:00
|
|
|
if let Some(free_slot) = self.slots.iter_mut().find(|s| !s.acquired.load(Ordering::SeqCst)) {
|
2021-04-06 22:54:46 +00:00
|
|
|
if free_slot.buffer.is_none() {
|
2021-04-07 22:58:39 +00:00
|
|
|
free_slot.buffer = Arc::new(Some(
|
|
|
|
self.allocator
|
2021-04-28 22:32:47 +00:00
|
|
|
.create_buffer(self.width, self.height, self.format)
|
|
|
|
.map_err(SwapchainError::AllocationError)?
|
|
|
|
.try_into()
|
|
|
|
.map_err(SwapchainError::ConversionError)?,
|
2021-04-07 22:58:39 +00:00
|
|
|
));
|
2021-04-06 22:54:46 +00:00
|
|
|
}
|
2021-04-25 21:30:25 +00:00
|
|
|
assert!(free_slot.buffer.is_some());
|
2021-04-06 22:54:46 +00:00
|
|
|
|
2021-04-25 21:30:25 +00:00
|
|
|
if !free_slot.acquired.swap(true, Ordering::SeqCst) {
|
2021-04-06 22:54:46 +00:00
|
|
|
return Ok(Some(free_slot.clone()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// no free slots
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
|
2021-04-30 15:21:35 +00:00
|
|
|
/// Change the dimensions of newly returned buffers.
|
|
|
|
///
|
|
|
|
/// Already optained buffers are unaffected and will be cleaned up on drop.
|
2021-04-06 22:54:46 +00:00
|
|
|
pub fn resize(&mut self, width: u32, height: u32) {
|
|
|
|
if self.width == width && self.height == height {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.width = width;
|
|
|
|
self.height = height;
|
2021-04-07 22:42:06 +00:00
|
|
|
self.slots = Default::default();
|
2021-04-06 22:54:46 +00:00
|
|
|
}
|
2021-04-28 22:32:47 +00:00
|
|
|
}
|