Merge pull request #419 from Smithay/feature/swapchain_age
This commit is contained in:
commit
c0216d15bf
|
@ -25,6 +25,8 @@
|
||||||
- `render_texture` was removed from `Frame`, use `render_texture_at` or `render_texture_from_to` instead or use `Gles2Renderer::render_texture` as a direct replacement.
|
- `render_texture` was removed from `Frame`, use `render_texture_at` or `render_texture_from_to` instead or use `Gles2Renderer::render_texture` as a direct replacement.
|
||||||
- Remove `InputBackend::dispatch_new_events`, turning `InputBackend` into a definition of backend event types. Future input backends should be a `calloop::EventSource`.
|
- Remove `InputBackend::dispatch_new_events`, turning `InputBackend` into a definition of backend event types. Future input backends should be a `calloop::EventSource`.
|
||||||
- Remove `InputBackend::EventError` associated type as it is unneeded since `dispatch_new_events` was removed.
|
- Remove `InputBackend::EventError` associated type as it is unneeded since `dispatch_new_events` was removed.
|
||||||
|
- `Swapchain` does not have a generic Userdata-parameter anymore, but utilizes `UserDataMap` instead
|
||||||
|
- `GbmBufferedSurface::next_buffer` now additionally returns the age of the buffer
|
||||||
|
|
||||||
### Additions
|
### Additions
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ libc = "0.2.103"
|
||||||
libseat= { version = "0.1.1", optional = true }
|
libseat= { version = "0.1.1", optional = true }
|
||||||
libloading = { version="0.7.0", optional = true }
|
libloading = { version="0.7.0", optional = true }
|
||||||
nix = "0.22"
|
nix = "0.22"
|
||||||
|
once_cell = "1.8.0"
|
||||||
rand = "0.8.4"
|
rand = "0.8.4"
|
||||||
slog = "2"
|
slog = "2"
|
||||||
slog-stdlog = { version = "4", optional = true }
|
slog-stdlog = { version = "4", optional = true }
|
||||||
|
|
|
@ -738,7 +738,7 @@ fn render_surface(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let dmabuf = surface.surface.next_buffer()?;
|
let (dmabuf, _age) = surface.surface.next_buffer()?;
|
||||||
renderer.bind(dmabuf)?;
|
renderer.bind(dmabuf)?;
|
||||||
|
|
||||||
// and draw to our buffer
|
// and draw to our buffer
|
||||||
|
@ -867,7 +867,7 @@ fn schedule_initial_render<Data: 'static>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initial_render(surface: &mut RenderSurface, renderer: &mut Gles2Renderer) -> Result<(), SwapBuffersError> {
|
fn initial_render(surface: &mut RenderSurface, renderer: &mut Gles2Renderer) -> Result<(), SwapBuffersError> {
|
||||||
let dmabuf = surface.next_buffer()?;
|
let (dmabuf, _age) = surface.next_buffer()?;
|
||||||
renderer.bind(dmabuf)?;
|
renderer.bind(dmabuf)?;
|
||||||
// Does not matter if we render an empty frame
|
// Does not matter if we render an empty frame
|
||||||
renderer
|
renderer
|
||||||
|
|
|
@ -105,9 +105,9 @@ fn main() {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let mut swapchain = Swapchain::new(allocator, w.into(), h.into(), Fourcc::Argb8888, mods);
|
let mut swapchain = Swapchain::new(allocator, w.into(), h.into(), Fourcc::Argb8888, mods);
|
||||||
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.userdata() = Some(framebuffer);
|
first_buffer.userdata().insert_if_missing(|| framebuffer);
|
||||||
|
|
||||||
// Get the device as an allocator into the
|
// Get the device as an allocator into the
|
||||||
let mut vblank_handler = VBlankHandler {
|
let mut vblank_handler = VBlankHandler {
|
||||||
|
@ -137,8 +137,8 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VBlankHandler {
|
pub struct VBlankHandler {
|
||||||
swapchain: Swapchain<DrmDevice<FdWrapper>, DumbBuffer<FdWrapper>, framebuffer::Handle>,
|
swapchain: Swapchain<DrmDevice<FdWrapper>, DumbBuffer<FdWrapper>>,
|
||||||
current: Slot<DumbBuffer<FdWrapper>, framebuffer::Handle>,
|
current: Slot<DumbBuffer<FdWrapper>>,
|
||||||
surface: Rc<DrmSurface<FdWrapper>>,
|
surface: Rc<DrmSurface<FdWrapper>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,9 +147,9 @@ impl VBlankHandler {
|
||||||
{
|
{
|
||||||
// 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().get::<framebuffer::Handle>().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.userdata() = Some(fb);
|
next.userdata().insert_if_missing(|| fb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we could render to the mapping via software rendering.
|
// now we could render to the mapping via software rendering.
|
||||||
|
@ -165,7 +165,7 @@ impl VBlankHandler {
|
||||||
self.current = next;
|
self.current = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fb = self.current.userdata().unwrap();
|
let fb = *self.current.userdata().get::<framebuffer::Handle>().unwrap();
|
||||||
self.surface
|
self.surface
|
||||||
.page_flip([(fb, self.surface.plane())].iter(), true)
|
.page_flip([(fb, self.surface.plane())].iter(), true)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, AtomicU8, Ordering},
|
||||||
Arc, Mutex, MutexGuard,
|
Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::backend::allocator::{Allocator, Buffer, Fourcc, Modifier};
|
use crate::backend::allocator::{Allocator, Buffer, Fourcc, Modifier};
|
||||||
|
use crate::utils::user_data::UserDataMap;
|
||||||
|
|
||||||
pub const SLOT_CAP: usize = 4;
|
pub const SLOT_CAP: usize = 4;
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ pub const SLOT_CAP: usize = 4;
|
||||||
/// you can store then in the `Slot`s userdata field. If a buffer is re-used, its userdata is preserved for the next time
|
/// you can store then in the `Slot`s userdata field. If a buffer is re-used, its userdata is preserved for the next time
|
||||||
/// it is returned by `acquire()`.
|
/// it is returned by `acquire()`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Swapchain<A: Allocator<B>, B: Buffer, U: 'static> {
|
pub struct Swapchain<A: Allocator<B>, B: Buffer> {
|
||||||
/// Allocator used by the swapchain
|
/// Allocator used by the swapchain
|
||||||
pub allocator: A,
|
pub allocator: A,
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ pub struct Swapchain<A: Allocator<B>, B: Buffer, U: 'static> {
|
||||||
fourcc: Fourcc,
|
fourcc: Fourcc,
|
||||||
modifiers: Vec<Modifier>,
|
modifiers: Vec<Modifier>,
|
||||||
|
|
||||||
slots: [Arc<InternalSlot<B, U>>; SLOT_CAP],
|
slots: [Arc<InternalSlot<B>>; SLOT_CAP],
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Slot of a swapchain containing an allocated buffer and its userdata.
|
/// Slot of a swapchain containing an allocated buffer and its userdata.
|
||||||
|
@ -53,50 +54,56 @@ pub struct Swapchain<A: Allocator<B>, B: Buffer, U: 'static> {
|
||||||
/// The buffer is marked for re-use once all copies are dropped.
|
/// The buffer is marked for re-use once all copies are dropped.
|
||||||
/// Holding on to this struct will block the buffer in the swapchain.
|
/// Holding on to this struct will block the buffer in the swapchain.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Slot<B: Buffer, U: 'static>(Arc<InternalSlot<B, U>>);
|
pub struct Slot<B: Buffer>(Arc<InternalSlot<B>>);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct InternalSlot<B: Buffer, U: 'static> {
|
struct InternalSlot<B: Buffer> {
|
||||||
buffer: Option<B>,
|
buffer: Option<B>,
|
||||||
acquired: AtomicBool,
|
acquired: AtomicBool,
|
||||||
userdata: Mutex<Option<U>>,
|
age: AtomicU8,
|
||||||
|
userdata: UserDataMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Buffer, U: 'static> Slot<B, U> {
|
impl<B: Buffer> Slot<B> {
|
||||||
/// Retrieve userdata for this slot.
|
/// Retrieve userdata for this slot.
|
||||||
pub fn userdata(&self) -> MutexGuard<'_, Option<U>> {
|
pub fn userdata(&self) -> &UserDataMap {
|
||||||
self.0.userdata.lock().unwrap()
|
&self.0.userdata
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the age of the buffer
|
||||||
|
pub fn age(&self) -> u8 {
|
||||||
|
self.0.age.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Buffer, U: 'static> Default for InternalSlot<B, U> {
|
impl<B: Buffer> Default for InternalSlot<B> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
InternalSlot {
|
InternalSlot {
|
||||||
buffer: None,
|
buffer: None,
|
||||||
acquired: AtomicBool::new(false),
|
acquired: AtomicBool::new(false),
|
||||||
userdata: Mutex::new(None),
|
age: AtomicU8::new(0),
|
||||||
|
userdata: UserDataMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Buffer, U: 'static> Deref for Slot<B, U> {
|
impl<B: Buffer> Deref for Slot<B> {
|
||||||
type Target = B;
|
type Target = B;
|
||||||
fn deref(&self) -> &B {
|
fn deref(&self) -> &B {
|
||||||
Option::as_ref(&self.0.buffer).unwrap()
|
Option::as_ref(&self.0.buffer).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Buffer, U: 'static> Drop for Slot<B, U> {
|
impl<B: Buffer> Drop for Slot<B> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.0.acquired.store(false, Ordering::SeqCst);
|
self.0.acquired.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, B, U> Swapchain<A, B, U>
|
impl<A, B> Swapchain<A, B>
|
||||||
where
|
where
|
||||||
A: Allocator<B>,
|
A: Allocator<B>,
|
||||||
B: Buffer,
|
B: Buffer,
|
||||||
U: 'static,
|
|
||||||
{
|
{
|
||||||
/// Create a new swapchain with the desired allocator, dimensions and pixel format for the created buffers.
|
/// Create a new swapchain with the desired allocator, dimensions and pixel format for the created buffers.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
@ -105,7 +112,7 @@ where
|
||||||
height: u32,
|
height: u32,
|
||||||
fourcc: Fourcc,
|
fourcc: Fourcc,
|
||||||
modifiers: Vec<Modifier>,
|
modifiers: Vec<Modifier>,
|
||||||
) -> Swapchain<A, B, U> {
|
) -> Swapchain<A, B> {
|
||||||
Swapchain {
|
Swapchain {
|
||||||
allocator,
|
allocator,
|
||||||
width,
|
width,
|
||||||
|
@ -120,7 +127,7 @@ where
|
||||||
///
|
///
|
||||||
/// The swapchain has an internal maximum of four re-usable buffers.
|
/// The swapchain has an internal maximum of four re-usable buffers.
|
||||||
/// This function returns the first free one.
|
/// This function returns the first free one.
|
||||||
pub fn acquire(&mut self) -> Result<Option<Slot<B, U>>, A::Error> {
|
pub fn acquire(&mut self) -> Result<Option<Slot<B>>, A::Error> {
|
||||||
if let Some(free_slot) = self
|
if let Some(free_slot) = self
|
||||||
.slots
|
.slots
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
@ -144,6 +151,27 @@ where
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mark a given buffer as submitted.
|
||||||
|
///
|
||||||
|
/// This might effect internal data (e.g. buffer age) and may only be called,
|
||||||
|
/// if the buffer was actually used for display.
|
||||||
|
/// Buffers can always just be safely discarded by dropping them, but not
|
||||||
|
/// calling this function may affect performance characteristics
|
||||||
|
/// (e.g. by not tracking the buffer age).
|
||||||
|
pub fn submitted(&self, slot: Slot<B>) {
|
||||||
|
// don't mess up the state, if the user submitted and old buffer, after e.g. a resize
|
||||||
|
if !self.slots.iter().any(|other| Arc::ptr_eq(&slot.0, other)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
slot.0.age.store(1, Ordering::SeqCst);
|
||||||
|
for other_slot in &self.slots {
|
||||||
|
if !Arc::ptr_eq(other_slot, &slot.0) {
|
||||||
|
other_slot.age.fetch_add(1, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Change the dimensions of newly returned buffers.
|
/// Change the dimensions of newly returned buffers.
|
||||||
///
|
///
|
||||||
/// Already obtained buffers are unaffected and will be cleaned up on drop.
|
/// Already obtained buffers are unaffected and will be cleaned up on drop.
|
||||||
|
@ -156,4 +184,9 @@ where
|
||||||
self.height = height;
|
self.height = height;
|
||||||
self.slots = Default::default();
|
self.slots = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove all internally cached buffers to e.g. reset age values
|
||||||
|
pub fn reset_buffers(&mut self) {
|
||||||
|
self.slots = Default::default();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,22 +17,16 @@ use crate::backend::SwapBuffersError;
|
||||||
use slog::{debug, error, o, trace, warn};
|
use slog::{debug, error, o, trace, warn};
|
||||||
|
|
||||||
/// Simplified abstraction of a swapchain for gbm-buffers displayed on a [`DrmSurface`].
|
/// Simplified abstraction of a swapchain for gbm-buffers displayed on a [`DrmSurface`].
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct GbmBufferedSurface<D: AsRawFd + 'static> {
|
pub struct GbmBufferedSurface<D: AsRawFd + 'static> {
|
||||||
buffers: Buffers<D>,
|
current_fb: Slot<BufferObject<()>>,
|
||||||
swapchain: Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)>,
|
pending_fb: Option<Slot<BufferObject<()>>>,
|
||||||
|
queued_fb: Option<Slot<BufferObject<()>>>,
|
||||||
|
next_fb: Option<Slot<BufferObject<()>>>,
|
||||||
|
swapchain: Swapchain<GbmDevice<D>, BufferObject<()>>,
|
||||||
drm: Arc<DrmSurface<D>>,
|
drm: Arc<DrmSurface<D>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Replace with #[derive(Debug)] once gbm::BufferObject implements debug
|
|
||||||
impl<D: std::fmt::Debug + AsRawFd + 'static> std::fmt::Debug for GbmBufferedSurface<D> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("GbmBufferedSurface")
|
|
||||||
.field("buffers", &self.buffers)
|
|
||||||
.field("drm", &self.drm)
|
|
||||||
.finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D> GbmBufferedSurface<D>
|
impl<D> GbmBufferedSurface<D>
|
||||||
where
|
where
|
||||||
D: AsRawFd + 'static,
|
D: AsRawFd + 'static,
|
||||||
|
@ -127,7 +121,7 @@ where
|
||||||
|
|
||||||
let mode = drm.pending_mode();
|
let mode = drm.pending_mode();
|
||||||
|
|
||||||
let mut swapchain: Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)> = Swapchain::new(
|
let mut swapchain: Swapchain<GbmDevice<D>, BufferObject<()>> = Swapchain::new(
|
||||||
allocator,
|
allocator,
|
||||||
mode.size().0 as u32,
|
mode.size().0 as u32,
|
||||||
mode.size().1 as u32,
|
mode.size().1 as u32,
|
||||||
|
@ -148,14 +142,17 @@ where
|
||||||
let fb = attach_framebuffer(&drm, &*buffer)?;
|
let fb = attach_framebuffer(&drm, &*buffer)?;
|
||||||
let dmabuf = buffer.export()?;
|
let dmabuf = buffer.export()?;
|
||||||
let handle = fb.fb;
|
let handle = fb.fb;
|
||||||
*buffer.userdata() = Some((dmabuf, fb));
|
buffer.userdata().insert_if_missing(|| dmabuf);
|
||||||
|
buffer.userdata().insert_if_missing(|| fb);
|
||||||
|
|
||||||
match drm.test_buffer(handle, &mode, true) {
|
match drm.test_buffer(handle, &mode, true) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!(logger, "Choosen format: {:?}", format);
|
debug!(logger, "Choosen format: {:?}", format);
|
||||||
let buffers = Buffers::new(drm.clone(), buffer);
|
|
||||||
Ok(GbmBufferedSurface {
|
Ok(GbmBufferedSurface {
|
||||||
buffers,
|
current_fb: buffer,
|
||||||
|
pending_fb: None,
|
||||||
|
queued_fb: None,
|
||||||
|
next_fb: None,
|
||||||
swapchain,
|
swapchain,
|
||||||
drm,
|
drm,
|
||||||
})
|
})
|
||||||
|
@ -170,12 +167,29 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the next buffer to be rendered into.
|
/// Retrieves the next buffer to be rendered into and it's age.
|
||||||
///
|
///
|
||||||
/// *Note*: This function can be called multiple times and
|
/// *Note*: This function can be called multiple times and
|
||||||
/// will return the same buffer until it is queued (see [`GbmBufferedSurface::queue_buffer`]).
|
/// will return the same buffer until it is queued (see [`GbmBufferedSurface::queue_buffer`]).
|
||||||
pub fn next_buffer(&mut self) -> Result<Dmabuf, Error> {
|
pub fn next_buffer(&mut self) -> Result<(Dmabuf, u8), Error> {
|
||||||
self.buffers.next(&mut self.swapchain)
|
if self.next_fb.is_none() {
|
||||||
|
let slot = self.swapchain.acquire()?.ok_or(Error::NoFreeSlotsError)?;
|
||||||
|
|
||||||
|
let maybe_buffer = slot.userdata().get::<Dmabuf>().cloned();
|
||||||
|
if maybe_buffer.is_none() {
|
||||||
|
let dmabuf = slot.export().map_err(Error::AsDmabufError)?;
|
||||||
|
let fb_handle = attach_framebuffer(&self.drm, &*slot)?;
|
||||||
|
|
||||||
|
let userdata = slot.userdata();
|
||||||
|
userdata.insert_if_missing(|| dmabuf);
|
||||||
|
userdata.insert_if_missing(|| fb_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.next_fb = Some(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
let slot = self.next_fb.as_ref().unwrap();
|
||||||
|
Ok((slot.userdata().get::<Dmabuf>().unwrap().clone(), slot.age()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queues the current buffer for rendering.
|
/// Queues the current buffer for rendering.
|
||||||
|
@ -184,7 +198,11 @@ where
|
||||||
/// when a vblank event is received, that denotes successful scanout of the buffer.
|
/// when a vblank event is received, that denotes successful scanout of the buffer.
|
||||||
/// Otherwise the underlying swapchain will eventually run out of buffers.
|
/// Otherwise the underlying swapchain will eventually run out of buffers.
|
||||||
pub fn queue_buffer(&mut self) -> Result<(), Error> {
|
pub fn queue_buffer(&mut self) -> Result<(), Error> {
|
||||||
self.buffers.queue()
|
self.queued_fb = self.next_fb.take();
|
||||||
|
if self.pending_fb.is_none() && self.queued_fb.is_some() {
|
||||||
|
self.submit()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks the current frame as submitted.
|
/// Marks the current frame as submitted.
|
||||||
|
@ -193,7 +211,31 @@ where
|
||||||
/// was received after calling [`GbmBufferedSurface::queue_buffer`] on this surface.
|
/// was received after calling [`GbmBufferedSurface::queue_buffer`] on this surface.
|
||||||
/// Otherwise the underlying swapchain will run out of buffers eventually.
|
/// Otherwise the underlying swapchain will run out of buffers eventually.
|
||||||
pub fn frame_submitted(&mut self) -> Result<(), Error> {
|
pub fn frame_submitted(&mut self) -> Result<(), Error> {
|
||||||
self.buffers.submitted()
|
if let Some(mut pending) = self.pending_fb.take() {
|
||||||
|
std::mem::swap(&mut pending, &mut self.current_fb);
|
||||||
|
self.swapchain.submitted(pending);
|
||||||
|
if self.queued_fb.is_some() {
|
||||||
|
self.submit()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit(&mut self) -> Result<(), Error> {
|
||||||
|
// yes it does not look like it, but both of these lines should be safe in all cases.
|
||||||
|
let slot = self.queued_fb.take().unwrap();
|
||||||
|
let fb = slot.userdata().get::<FbHandle<D>>().unwrap().fb;
|
||||||
|
|
||||||
|
let flip = if self.drm.commit_pending() {
|
||||||
|
self.drm.commit([(fb, self.drm.plane())].iter(), true)
|
||||||
|
} else {
|
||||||
|
self.drm.page_flip([(fb, self.drm.plane())].iter(), true)
|
||||||
|
};
|
||||||
|
if flip.is_ok() {
|
||||||
|
self.pending_fb = Some(slot);
|
||||||
|
}
|
||||||
|
flip.map_err(Error::DrmError)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the underlying [`crtc`](drm::control::crtc) of this surface
|
/// Returns the underlying [`crtc`](drm::control::crtc) of this surface
|
||||||
|
@ -284,103 +326,6 @@ impl<A: AsRawFd + 'static> Drop for FbHandle<A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type DmabufSlot<D> = Slot<BufferObject<()>, (Dmabuf, FbHandle<D>)>;
|
|
||||||
|
|
||||||
struct Buffers<D: AsRawFd + 'static> {
|
|
||||||
drm: Arc<DrmSurface<D>>,
|
|
||||||
_current_fb: DmabufSlot<D>,
|
|
||||||
pending_fb: Option<DmabufSlot<D>>,
|
|
||||||
queued_fb: Option<DmabufSlot<D>>,
|
|
||||||
next_fb: Option<DmabufSlot<D>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Replace with #[derive(Debug)] once gbm::BufferObject implements debug
|
|
||||||
impl<D: std::fmt::Debug + AsRawFd + 'static> std::fmt::Debug for Buffers<D> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("Buffers")
|
|
||||||
.field("drm", &self.drm)
|
|
||||||
.finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D> Buffers<D>
|
|
||||||
where
|
|
||||||
D: AsRawFd + 'static,
|
|
||||||
{
|
|
||||||
pub fn new(drm: Arc<DrmSurface<D>>, slot: DmabufSlot<D>) -> Buffers<D> {
|
|
||||||
Buffers {
|
|
||||||
drm,
|
|
||||||
_current_fb: slot,
|
|
||||||
pending_fb: None,
|
|
||||||
queued_fb: None,
|
|
||||||
next_fb: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(
|
|
||||||
&mut self,
|
|
||||||
swapchain: &mut Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)>,
|
|
||||||
) -> Result<Dmabuf, Error> {
|
|
||||||
if let Some(slot) = self.next_fb.as_ref() {
|
|
||||||
return Ok(slot.userdata().as_ref().unwrap().0.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let slot = swapchain.acquire()?.ok_or(Error::NoFreeSlotsError)?;
|
|
||||||
|
|
||||||
let maybe_buffer = slot.userdata().as_ref().map(|(buf, _)| buf.clone());
|
|
||||||
let dmabuf = match maybe_buffer {
|
|
||||||
Some(buf) => buf,
|
|
||||||
None => {
|
|
||||||
let dmabuf = slot.export()?;
|
|
||||||
let fb_handle = attach_framebuffer(&self.drm, &*slot)?;
|
|
||||||
*slot.userdata() = Some((dmabuf.clone(), fb_handle));
|
|
||||||
dmabuf
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.next_fb = Some(slot);
|
|
||||||
|
|
||||||
Ok(dmabuf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queue(&mut self) -> Result<(), Error> {
|
|
||||||
self.queued_fb = self.next_fb.take();
|
|
||||||
if self.pending_fb.is_none() && self.queued_fb.is_some() {
|
|
||||||
self.submit()
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn submitted(&mut self) -> Result<(), Error> {
|
|
||||||
if self.pending_fb.is_none() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
self._current_fb = self.pending_fb.take().unwrap();
|
|
||||||
if self.queued_fb.is_some() {
|
|
||||||
self.submit()
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit(&mut self) -> Result<(), Error> {
|
|
||||||
// yes it does not look like it, but both of these lines should be safe in all cases.
|
|
||||||
let slot = self.queued_fb.take().unwrap();
|
|
||||||
let fb = slot.userdata().as_ref().unwrap().1.fb;
|
|
||||||
|
|
||||||
let flip = if self.drm.commit_pending() {
|
|
||||||
self.drm.commit([(fb, self.drm.plane())].iter(), true)
|
|
||||||
} else {
|
|
||||||
self.drm.page_flip([(fb, self.drm.plane())].iter(), true)
|
|
||||||
};
|
|
||||||
if flip.is_ok() {
|
|
||||||
self.pending_fb = Some(slot);
|
|
||||||
}
|
|
||||||
flip.map_err(Error::DrmError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn attach_framebuffer<A>(drm: &Arc<DrmSurface<A>>, bo: &BufferObject<()>) -> Result<FbHandle<A>, Error>
|
fn attach_framebuffer<A>(drm: &Arc<DrmSurface<A>>, bo: &BufferObject<()>) -> Result<FbHandle<A>, Error>
|
||||||
where
|
where
|
||||||
A: AsRawFd + 'static,
|
A: AsRawFd + 'static,
|
||||||
|
|
|
@ -6,6 +6,8 @@ pub mod signaling;
|
||||||
#[cfg(feature = "x11rb_event_source")]
|
#[cfg(feature = "x11rb_event_source")]
|
||||||
pub mod x11rb;
|
pub mod x11rb;
|
||||||
|
|
||||||
|
pub mod user_data;
|
||||||
|
|
||||||
pub use self::geometry::{Buffer, Logical, Physical, Point, Raw, Rectangle, Size};
|
pub use self::geometry::{Buffer, Logical, Physical, Point, Raw, Rectangle, Size};
|
||||||
|
|
||||||
/// This resource is not managed by Smithay
|
/// This resource is not managed by Smithay
|
||||||
|
|
|
@ -0,0 +1,324 @@
|
||||||
|
//! Various utilities used for user data implementations
|
||||||
|
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
|
use std::any::Any;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::thread::{self, ThreadId};
|
||||||
|
|
||||||
|
use self::list::AppendList;
|
||||||
|
|
||||||
|
/// A wrapper for user data, able to store any type, and correctly
|
||||||
|
/// handling access from a wrong thread
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UserData {
|
||||||
|
inner: OnceCell<UserDataInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum UserDataInner {
|
||||||
|
ThreadSafe(Box<dyn Any + Send + Sync + 'static>),
|
||||||
|
NonThreadSafe(Box<ManuallyDrop<dyn Any + 'static>>, ThreadId),
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserData itself is always threadsafe, as it only gives access to its
|
||||||
|
// content if it is send+sync or we are on the right thread
|
||||||
|
unsafe impl Send for UserData {}
|
||||||
|
unsafe impl Sync for UserData {}
|
||||||
|
|
||||||
|
impl UserData {
|
||||||
|
/// Create a new UserData instance
|
||||||
|
pub const fn new() -> UserData {
|
||||||
|
UserData {
|
||||||
|
inner: OnceCell::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the UserData to a given value
|
||||||
|
///
|
||||||
|
/// The provided closure is called to init the UserData,
|
||||||
|
/// does nothing is the UserData had already been set.
|
||||||
|
pub fn set<T: Any + 'static, F: FnOnce() -> T>(&self, f: F) {
|
||||||
|
self.inner.get_or_init(|| {
|
||||||
|
UserDataInner::NonThreadSafe(Box::new(ManuallyDrop::new(f())), thread::current().id())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the UserData to a given threadsafe value
|
||||||
|
///
|
||||||
|
/// The provided closure is called to init the UserData,
|
||||||
|
/// does nothing is the UserData had already been set.
|
||||||
|
pub fn set_threadsafe<T: Any + Send + Sync + 'static, F: FnOnce() -> T>(&self, f: F) {
|
||||||
|
self.inner
|
||||||
|
.get_or_init(|| UserDataInner::ThreadSafe(Box::new(f())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to access the wrapped user data
|
||||||
|
///
|
||||||
|
/// Will return `None` if either:
|
||||||
|
///
|
||||||
|
/// - The requested type `T` does not match the type used for construction
|
||||||
|
/// - This `UserData` has been created using the non-threadsafe variant and access
|
||||||
|
/// is attempted from an other thread than the one it was created on
|
||||||
|
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||||
|
match self.inner.get() {
|
||||||
|
Some(&UserDataInner::ThreadSafe(ref val)) => <dyn Any>::downcast_ref::<T>(&**val),
|
||||||
|
Some(&UserDataInner::NonThreadSafe(ref val, threadid)) => {
|
||||||
|
// only give access if we are on the right thread
|
||||||
|
if threadid == thread::current().id() {
|
||||||
|
<dyn Any>::downcast_ref::<T>(&***val)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for UserData {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// only drop non-Send user data if we are on the right thread, leak it otherwise
|
||||||
|
if let Some(&mut UserDataInner::NonThreadSafe(ref mut val, threadid)) = self.inner.get_mut() {
|
||||||
|
if threadid == thread::current().id() {
|
||||||
|
unsafe {
|
||||||
|
ManuallyDrop::drop(&mut **val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A storage able to store several values of `UserData`
|
||||||
|
/// of different types. It behaves similarly to a `TypeMap`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UserDataMap {
|
||||||
|
list: AppendList<UserData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserDataMap {
|
||||||
|
/// Create a new map
|
||||||
|
pub fn new() -> UserDataMap {
|
||||||
|
UserDataMap {
|
||||||
|
list: AppendList::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to access the wrapped user data of a given type
|
||||||
|
///
|
||||||
|
/// Will return `None` if no value of type `T` is stored in this `UserDataMap`
|
||||||
|
/// and accessible from this thread
|
||||||
|
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||||
|
for user_data in &self.list {
|
||||||
|
if let Some(val) = user_data.get::<T>() {
|
||||||
|
return Some(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a value in the map if it is not already there
|
||||||
|
///
|
||||||
|
/// This is the non-threadsafe variant, the type you insert don't have to be
|
||||||
|
/// threadsafe, but they will not be visible from other threads (even if they are
|
||||||
|
/// actually threadsafe).
|
||||||
|
///
|
||||||
|
/// If the value does not already exists, the closure is called to create it and
|
||||||
|
/// this function returns `true`. If the value already exists, the closure is not
|
||||||
|
/// called, and this function returns `false`.
|
||||||
|
pub fn insert_if_missing<T: 'static, F: FnOnce() -> T>(&self, init: F) -> bool {
|
||||||
|
if self.get::<T>().is_some() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let data = UserData::new();
|
||||||
|
data.set(init);
|
||||||
|
self.list.append(data);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a value in the map if it is not already there
|
||||||
|
///
|
||||||
|
/// This is the threadsafe variant, the type you insert must be threadsafe and will
|
||||||
|
/// be visible from all threads.
|
||||||
|
///
|
||||||
|
/// If the value does not already exists, the closure is called to create it and
|
||||||
|
/// this function returns `true`. If the value already exists, the closure is not
|
||||||
|
/// called, and this function returns `false`.
|
||||||
|
pub fn insert_if_missing_threadsafe<T: Send + Sync + 'static, F: FnOnce() -> T>(&self, init: F) -> bool {
|
||||||
|
if self.get::<T>().is_some() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let data = UserData::new();
|
||||||
|
data.set_threadsafe(init);
|
||||||
|
self.list.append(data);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for UserDataMap {
|
||||||
|
fn default() -> UserDataMap {
|
||||||
|
UserDataMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod list {
|
||||||
|
/*
|
||||||
|
* This is a lock-free append-only list, it is used as an implementation
|
||||||
|
* detail of the UserDataMap.
|
||||||
|
*
|
||||||
|
* It was extracted from https://github.com/Diggsey/lockless under MIT license
|
||||||
|
* Copyright © Diggory Blake <diggsey@googlemail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::sync::atomic::{AtomicPtr, Ordering};
|
||||||
|
use std::{mem, ptr};
|
||||||
|
|
||||||
|
type NodePtr<T> = Option<Box<Node<T>>>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Node<T> {
|
||||||
|
value: T,
|
||||||
|
next: AppendList<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AppendList<T>(AtomicPtr<Node<T>>);
|
||||||
|
|
||||||
|
impl<T> AppendList<T> {
|
||||||
|
fn node_into_raw(ptr: NodePtr<T>) -> *mut Node<T> {
|
||||||
|
match ptr {
|
||||||
|
Some(b) => Box::into_raw(b),
|
||||||
|
None => ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe fn node_from_raw(ptr: *mut Node<T>) -> NodePtr<T> {
|
||||||
|
if ptr.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Box::from_raw(ptr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_internal(ptr: NodePtr<T>) -> Self {
|
||||||
|
AppendList(AtomicPtr::new(Self::node_into_raw(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::new_internal(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append(&self, value: T) {
|
||||||
|
self.append_list(AppendList::new_internal(Some(Box::new(Node {
|
||||||
|
value,
|
||||||
|
next: AppendList::new(),
|
||||||
|
}))));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn append_ptr(&self, p: *mut Node<T>) {
|
||||||
|
loop {
|
||||||
|
match self
|
||||||
|
.0
|
||||||
|
.compare_exchange_weak(ptr::null_mut(), p, Ordering::AcqRel, Ordering::Acquire)
|
||||||
|
{
|
||||||
|
Ok(_) => return,
|
||||||
|
Err(head) => {
|
||||||
|
if !head.is_null() {
|
||||||
|
return (*head).next.append_ptr(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_list(&self, other: AppendList<T>) {
|
||||||
|
let p = other.0.load(Ordering::Acquire);
|
||||||
|
mem::forget(other);
|
||||||
|
unsafe { self.append_ptr(p) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> AppendListIterator<'_, T> {
|
||||||
|
AppendListIterator(&self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_mut(&mut self) -> AppendListMutIterator<'_, T> {
|
||||||
|
AppendListMutIterator(&mut self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> IntoIterator for &'a AppendList<T> {
|
||||||
|
type Item = &'a T;
|
||||||
|
type IntoIter = AppendListIterator<'a, T>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> AppendListIterator<'a, T> {
|
||||||
|
self.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> IntoIterator for &'a mut AppendList<T> {
|
||||||
|
type Item = &'a mut T;
|
||||||
|
type IntoIter = AppendListMutIterator<'a, T>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> AppendListMutIterator<'a, T> {
|
||||||
|
self.iter_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Drop for AppendList<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { Self::node_from_raw(mem::replace(self.0.get_mut(), ptr::null_mut())) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AppendListIterator<'a, T>(&'a AtomicPtr<Node<T>>);
|
||||||
|
|
||||||
|
impl<'a, T: 'a> Iterator for AppendListIterator<'a, T> {
|
||||||
|
type Item = &'a T;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<&'a T> {
|
||||||
|
let p = self.0.load(Ordering::Acquire);
|
||||||
|
if p.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
self.0 = &(*p).next.0;
|
||||||
|
Some(&(*p).value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AppendListMutIterator<'a, T>(&'a mut AtomicPtr<Node<T>>);
|
||||||
|
|
||||||
|
impl<'a, T: 'a> Iterator for AppendListMutIterator<'a, T> {
|
||||||
|
type Item = &'a mut T;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<&'a mut T> {
|
||||||
|
let p = self.0.load(Ordering::Acquire);
|
||||||
|
if p.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
self.0 = &mut (*p).next.0;
|
||||||
|
Some(&mut (*p).value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::UserDataMap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_twice() {
|
||||||
|
let map = UserDataMap::new();
|
||||||
|
|
||||||
|
assert_eq!(map.get::<usize>(), None);
|
||||||
|
assert!(map.insert_if_missing(|| 42usize));
|
||||||
|
assert!(!map.insert_if_missing(|| 43usize));
|
||||||
|
assert_eq!(map.get::<usize>(), Some(&42));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue