From c388a502c4fca304f41fefab8a7fb86553be7619 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 7 Apr 2021 00:54:46 +0200 Subject: [PATCH] Add allocator module - Add module that deals with different kinds of buffers (memory and external), their allocation and usage for rendering. Also try to properly support modifiers this time. - Describe gbm functionality as an allocator (instead of a rendering device/surface). - Also create a quick-and-dirty dumb buffer allocator for tesing / simpler tasks. - Add a (current untested) wrapper for dmabufs and some code for converting from gbm for now. - (also untested) Swapchain helper to manage front/backbuffers for surfaceless rendering. --- src/backend/allocator/dmabuf.rs | 119 +++++++++++++++++++++++++++++ src/backend/allocator/dumb.rs | 51 +++++++++++++ src/backend/allocator/gbm.rs | 83 ++++++++++++++++++++ src/backend/allocator/mod.rs | 23 ++++++ src/backend/allocator/swapchain.rs | 94 +++++++++++++++++++++++ src/wayland/dmabuf/mod.rs | 12 +-- 6 files changed, 377 insertions(+), 5 deletions(-) create mode 100644 src/backend/allocator/dmabuf.rs create mode 100644 src/backend/allocator/dumb.rs create mode 100644 src/backend/allocator/gbm.rs create mode 100644 src/backend/allocator/mod.rs create mode 100644 src/backend/allocator/swapchain.rs diff --git a/src/backend/allocator/dmabuf.rs b/src/backend/allocator/dmabuf.rs new file mode 100644 index 0000000..5a5005a --- /dev/null +++ b/src/backend/allocator/dmabuf.rs @@ -0,0 +1,119 @@ +use super::{Buffer, Format, Modifier}; +use std::sync::{Arc, Weak}; +use std::os::unix::io::RawFd; + +const MAX_PLANES: usize = 4; + +struct DmabufInternal { + src: Box, + + num_planes: usize, + offsets: [u32; MAX_PLANES], + strides: [u32; MAX_PLANES], + fds: [RawFd; MAX_PLANES], +} + +#[derive(Clone)] +pub struct Dmabuf(Arc); + +#[derive(Clone)] +pub struct WeakDmabuf(Weak); + +impl PartialEq for Dmabuf { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} +impl Eq for Dmabuf {} + +impl PartialEq for Dmabuf { + fn eq(&self, other: &WeakDmabuf) -> bool { + if let Some(dmabuf) = other.upgrade() { + return Arc::ptr_eq(&self.0, &dmabuf.0); + } + false + } +} + +impl PartialEq for WeakDmabuf { + fn eq(&self, other: &Self) -> bool { + if let Some(dmabuf) = self.upgrade() { + return &dmabuf == other; + } + false + } +} + +impl Buffer for Dmabuf { + fn width(&self) -> u32 { + self.0.src.width() + } + + fn height(&self) -> u32 { + self.0.src.height() + } + + fn format(&self) -> Format { + self.0.src.format() + } +} + +impl Dmabuf { + pub fn new( + src: impl Buffer + 'static, + + planes: usize, + offsets: &[u32], + strides: &[u32], + fds: &[RawFd], + ) -> Option { + if offsets.len() < planes + || strides.len() < planes + || fds.len() < planes + || planes == 0 || planes > MAX_PLANES { + return None; + } + + let end = [0u32, 0, 0]; + let end_fds = [0i32, 0, 0]; + let mut offsets = offsets.iter().take(planes).chain(end.iter()); + let mut strides = strides.iter().take(planes).chain(end.iter()); + let mut fds = fds.iter().take(planes).chain(end_fds.iter()); + + Some(Dmabuf(Arc::new(DmabufInternal { + src: Box::new(src), + + num_planes: planes, + offsets: [*offsets.next().unwrap(), *offsets.next().unwrap(), *offsets.next().unwrap(), *offsets.next().unwrap()], + strides: [*strides.next().unwrap(), *strides.next().unwrap(), *strides.next().unwrap(), *strides.next().unwrap()], + fds: [*fds.next().unwrap(), *fds.next().unwrap(), *fds.next().unwrap(), *fds.next().unwrap()], + }))) + } + + pub fn handles(&self) -> &[RawFd] { + self.0.fds.split_at(self.0.num_planes).0 + } + + pub fn offsets(&self) -> &[u32] { + self.0.offsets.split_at(self.0.num_planes).0 + } + + pub fn strides(&self) -> &[u32] { + self.0.strides.split_at(self.0.num_planes).0 + } + + pub fn has_modifier(&self) -> bool { + self.0.src.format().modifier != Modifier::Invalid && + self.0.src.format().modifier != Modifier::Linear + } + + pub fn weak(&self) -> WeakDmabuf { + WeakDmabuf(Arc::downgrade(&self.0)) + } +} + +impl WeakDmabuf { + pub fn upgrade(&self) -> Option { + self.0.upgrade().map(|internal| Dmabuf(internal)) + } +} \ No newline at end of file diff --git a/src/backend/allocator/dumb.rs b/src/backend/allocator/dumb.rs new file mode 100644 index 0000000..e274227 --- /dev/null +++ b/src/backend/allocator/dumb.rs @@ -0,0 +1,51 @@ +use std::os::unix::io::AsRawFd; +use std::sync::Arc; + +use drm::buffer::Buffer as DrmBuffer; +use drm::control::{Device as ControlDevice, dumbbuffer::DumbBuffer as Handle}; + +use super::{Allocator, Buffer, Format}; +use crate::backend::drm::device::{DrmDevice, DrmDeviceInternal, FdWrapper}; + +pub struct DumbBuffer { + fd: Arc>, + handle: Handle, + format: Format, +} + +impl Allocator> for DrmDevice { + type Error = drm::SystemError; + + fn create_buffer(&mut self, width: u32, height: u32, format: Format) -> Result, Self::Error> { + let handle = self.create_dumb_buffer((width, height), format.code, 32/* TODO */)?; + + Ok(DumbBuffer { + fd: match &*self.internal { + DrmDeviceInternal::Atomic(dev) => dev.fd.clone(), + DrmDeviceInternal::Legacy(dev) => dev.fd.clone(), + }, + handle, + format, + }) + } +} + +impl Buffer for DumbBuffer { + fn width(&self) -> u32 { + self.handle.size().0 + } + + fn height(&self) -> u32 { + self.handle.size().1 + } + + fn format(&self) -> Format { + self.format + } +} + +impl Drop for DumbBuffer { + fn drop(&mut self) { + let _ = self.fd.destroy_dumb_buffer(self.handle); + } +} \ No newline at end of file diff --git a/src/backend/allocator/gbm.rs b/src/backend/allocator/gbm.rs new file mode 100644 index 0000000..2e0ae69 --- /dev/null +++ b/src/backend/allocator/gbm.rs @@ -0,0 +1,83 @@ +use std::os::unix::io::AsRawFd; +use gbm::{BufferObject as GbmBuffer, Device as GbmDevice, BufferObjectFlags}; +use super::{Allocator, Buffer, Format, Fourcc, Modifier, dmabuf::Dmabuf}; + +impl Allocator> for GbmDevice { + type Error = std::io::Error; + + fn create_buffer(&mut self, width: u32, height: u32, format: Format) -> std::io::Result> { + if format.modifier == Modifier::Invalid || format.modifier == Modifier::Linear { + let mut usage = BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING; + if format.modifier == Modifier::Linear { + usage |= BufferObjectFlags::LINEAR; + } + self.create_buffer_object(width, height, format.code, usage) + } else { + self.create_buffer_object_with_modifiers(width, height, format.code, Some(format.modifier).into_iter()) + } + } +} + +impl Buffer for GbmBuffer { + fn width(&self) -> u32 { + self.width().unwrap_or(0) + } + + fn height(&self) -> u32 { + self.height().unwrap_or(0) + } + + fn format(&self) -> Format { + Format { + code: self.format().unwrap_or(Fourcc::Argb8888), // we got to return something, but this should never happen anyway + modifier: self.modifier().unwrap_or(Modifier::Invalid), + } + } +} + +#[derive(thiserror::Error, Debug)] +pub enum GbmConvertError { + #[error("The gbm device was destroyed")] + DeviceDestroyed(#[from] gbm::DeviceDestroyedError), + #[error("Buffer consists out of multiple file descriptors, which is currently unsupported")] + UnsupportedBuffer, + #[error("Buffer returned invalid file descriptor")] + InvalidFD, +} + +impl std::convert::TryFrom> for Dmabuf { + type Error = GbmConvertError; + + fn try_from(buf: GbmBuffer) -> Result { + let planes = buf.plane_count()? as i32; + + //TODO switch to gbm_bo_get_plane_fd when it lands + let mut iter = (0i32..planes).map(|i| buf.handle_for_plane(i)); + let first = iter.next().expect("Encountered a buffer with zero planes"); + if iter.try_fold(first, |first, next| { + if let (Ok(next), Ok(first)) = (next, first) { + if unsafe { next.u64_ == first.u64_ } { + return Some(Ok(first)); + } + } + None + }).is_none() { + // GBM is lacking a function to get a FD for a given plane. Instead, + // check all planes have the same handle. We can't use + // drmPrimeHandleToFD because that messes up handle ref'counting in + // the user-space driver. + return Err(GbmConvertError::UnsupportedBuffer); //TODO + } + + let fds = [buf.fd()?, 0, 0, 0]; + //if fds.iter().any(|fd| fd == 0) { + if fds[0] < 0 { + return Err(GbmConvertError::InvalidFD); + } + + let offsets = (0i32..planes).map(|i| buf.offset(i)).collect::, gbm::DeviceDestroyedError>>()?; + let strides = (0i32..planes).map(|i| buf.stride_for_plane(i)).collect::, gbm::DeviceDestroyedError>>()?; + + Ok(Dmabuf::new(buf, planes as usize, &offsets, &strides, &fds).unwrap()) + } +} \ No newline at end of file diff --git a/src/backend/allocator/mod.rs b/src/backend/allocator/mod.rs new file mode 100644 index 0000000..0b553d8 --- /dev/null +++ b/src/backend/allocator/mod.rs @@ -0,0 +1,23 @@ +#[cfg(feature = "backend_gbm")] +pub mod gbm; +#[cfg(feature = "backend_drm")] +pub mod dumb; +pub mod dmabuf; + +mod swapchain; +pub use swapchain::{Slot, Swapchain}; + +pub use drm_fourcc::{DrmFormat as Format, DrmFourcc as Fourcc, DrmModifier as Modifier, DrmVendor as Vendor, UnrecognizedFourcc, UnrecognizedVendor}; + +pub trait Buffer { + fn width(&self) -> u32; + fn height(&self) -> u32; + fn size(&self) -> (u32, u32) { (self.width(), self.height()) } + fn format(&self) -> Format; +} + +pub trait Allocator { + type Error: std::error::Error; + + fn create_buffer(&mut self, width: u32, height: u32, format: Format) -> Result; +} \ No newline at end of file diff --git a/src/backend/allocator/swapchain.rs b/src/backend/allocator/swapchain.rs new file mode 100644 index 0000000..9fce3d9 --- /dev/null +++ b/src/backend/allocator/swapchain.rs @@ -0,0 +1,94 @@ +use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; +use std::ops::Deref; + +use crate::backend::allocator::{Allocator, Buffer, Format}; + +pub const SLOT_CAP: usize = 3; + +pub struct Swapchain, B: Buffer> { + allocator: A, + + width: u32, + height: u32, + format: Format, + + slots: [Slot; SLOT_CAP], +} + +pub struct Slot { + buffer: Arc>, + acquired: Arc, +} + +impl Default for Slot { + fn default() -> Self { + Slot { + buffer: Arc::new(None), + acquired: Arc::new(AtomicBool::new(false)), + } + } +} + +impl Clone for Slot { + fn clone(&self) -> Self { + Slot { + buffer: self.buffer.clone(), + acquired: self.acquired.clone(), + } + } +} + +impl Deref for Slot { + type Target = B; + fn deref(&self) -> &B { + Option::as_ref(&*self.buffer).unwrap() + } +} + +impl Drop for Slot { + fn drop(&mut self) { + self.acquired.store(false, Ordering::AcqRel); + } +} + +impl, B: Buffer> Swapchain { + pub fn new(allocator: A, width: u32, height: u32, format: Format) -> Swapchain { + Swapchain { + allocator, + width, + height, + format, + slots: Default::default(), + } + } + + pub fn acquire(&mut self) -> Result>, A::Error> { + if let Some(free_slot) = self.slots.iter_mut().filter(|s| !s.acquired.load(Ordering::SeqCst)).next() { + if free_slot.buffer.is_none() { + free_slot.buffer = Arc::new(Some(self.allocator.create_buffer(self.width, self.height, self.format)?)); + } + assert!(!free_slot.buffer.is_some()); + + if !free_slot.acquired.swap(true, Ordering::AcqRel) { + return Ok(Some(free_slot.clone())); + } + + } + + // no free slots + Ok(None) + } + + pub fn resize(&mut self, width: u32, height: u32) { + if self.width == width && self.height == height { + return; + } + + self.width = width; + self.height = height; + + for mut slot in &mut self.slots { + let _ = std::mem::replace(&mut slot, &mut Slot::default()); + } + } +} \ No newline at end of file diff --git a/src/wayland/dmabuf/mod.rs b/src/wayland/dmabuf/mod.rs index 5a152f1..63e4798 100644 --- a/src/wayland/dmabuf/mod.rs +++ b/src/wayland/dmabuf/mod.rs @@ -75,16 +75,18 @@ use wayland_protocols::unstable::linux_dmabuf::v1::server::{ }; use wayland_server::{protocol::wl_buffer, Display, Filter, Global, Main}; +use crate::backend::allocator::{Fourcc, Modifier}; + /// Representation of a Dmabuf format, as advertized to the client #[derive(Debug)] pub struct Format { /// The format identifier. - pub format: ::drm::buffer::format::PixelFormat, + pub format: Fourcc, /// The supported dmabuf layout modifier. /// /// This is an opaque token. Drivers use this token to express tiling, compression, etc. driver-specific /// modifications to the base format defined by the DRM fourcc code. - pub modifier: u64, + pub modifier: Modifier, /// Number of planes used by this format pub plane_count: u32, } @@ -251,9 +253,9 @@ where // send the supported formats for f in &*formats { - dmabuf.format(f.format.as_raw()); + dmabuf.format(f.format as u32); if version >= 3 { - dmabuf.modifier(f.format.as_raw(), (f.modifier >> 32) as u32, f.modifier as u32); + dmabuf.modifier(f.format as u32, (Into::::into(f.modifier) >> 32) as u32, Into::::into(f.modifier) as u32); } } }, @@ -424,7 +426,7 @@ fn buffer_basic_checks( ) -> bool { // protocol_checks: // This must be a known format - let format = match formats.iter().find(|f| f.format.as_raw() == format) { + let format = match formats.iter().find(|f| f.format as u32 == format) { Some(f) => f, None => { params.as_ref().post_error(