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.
This commit is contained in:
Victor Brekenfeld 2021-04-07 00:54:46 +02:00
parent 8fc23c767b
commit c388a502c4
6 changed files with 377 additions and 5 deletions

View File

@ -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<dyn Buffer + 'static>,
num_planes: usize,
offsets: [u32; MAX_PLANES],
strides: [u32; MAX_PLANES],
fds: [RawFd; MAX_PLANES],
}
#[derive(Clone)]
pub struct Dmabuf(Arc<DmabufInternal>);
#[derive(Clone)]
pub struct WeakDmabuf(Weak<DmabufInternal>);
impl PartialEq for Dmabuf {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for Dmabuf {}
impl PartialEq<WeakDmabuf> 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<Dmabuf> {
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<Dmabuf> {
self.0.upgrade().map(|internal| Dmabuf(internal))
}
}

View File

@ -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<A: AsRawFd + 'static> {
fd: Arc<FdWrapper<A>>,
handle: Handle,
format: Format,
}
impl<A: AsRawFd + 'static> Allocator<DumbBuffer<A>> for DrmDevice<A> {
type Error = drm::SystemError;
fn create_buffer(&mut self, width: u32, height: u32, format: Format) -> Result<DumbBuffer<A>, 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<A: AsRawFd + 'static> Buffer for DumbBuffer<A> {
fn width(&self) -> u32 {
self.handle.size().0
}
fn height(&self) -> u32 {
self.handle.size().1
}
fn format(&self) -> Format {
self.format
}
}
impl<A: AsRawFd + 'static> Drop for DumbBuffer<A> {
fn drop(&mut self) {
let _ = self.fd.destroy_dumb_buffer(self.handle);
}
}

View File

@ -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<A: AsRawFd + 'static, T> Allocator<GbmBuffer<T>> for GbmDevice<A> {
type Error = std::io::Error;
fn create_buffer(&mut self, width: u32, height: u32, format: Format) -> std::io::Result<GbmBuffer<T>> {
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<T> Buffer for GbmBuffer<T> {
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<T> std::convert::TryFrom<GbmBuffer<T>> for Dmabuf {
type Error = GbmConvertError;
fn try_from(buf: GbmBuffer<T>) -> Result<Self, GbmConvertError> {
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::<Result<Vec<u32>, gbm::DeviceDestroyedError>>()?;
let strides = (0i32..planes).map(|i| buf.stride_for_plane(i)).collect::<Result<Vec<u32>, gbm::DeviceDestroyedError>>()?;
Ok(Dmabuf::new(buf, planes as usize, &offsets, &strides, &fds).unwrap())
}
}

View File

@ -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<B: Buffer> {
type Error: std::error::Error;
fn create_buffer(&mut self, width: u32, height: u32, format: Format) -> Result<B, Self::Error>;
}

View File

@ -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<A: Allocator<B>, B: Buffer> {
allocator: A,
width: u32,
height: u32,
format: Format,
slots: [Slot<B>; SLOT_CAP],
}
pub struct Slot<B: Buffer> {
buffer: Arc<Option<B>>,
acquired: Arc<AtomicBool>,
}
impl<B: Buffer> Default for Slot<B> {
fn default() -> Self {
Slot {
buffer: Arc::new(None),
acquired: Arc::new(AtomicBool::new(false)),
}
}
}
impl<B: Buffer> Clone for Slot<B> {
fn clone(&self) -> Self {
Slot {
buffer: self.buffer.clone(),
acquired: self.acquired.clone(),
}
}
}
impl<B: Buffer> Deref for Slot<B> {
type Target = B;
fn deref(&self) -> &B {
Option::as_ref(&*self.buffer).unwrap()
}
}
impl<B: Buffer> Drop for Slot<B> {
fn drop(&mut self) {
self.acquired.store(false, Ordering::AcqRel);
}
}
impl<A: Allocator<B>, B: Buffer> Swapchain<A, B> {
pub fn new(allocator: A, width: u32, height: u32, format: Format) -> Swapchain<A, B> {
Swapchain {
allocator,
width,
height,
format,
slots: Default::default(),
}
}
pub fn acquire(&mut self) -> Result<Option<Slot<B>>, 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());
}
}
}

View File

@ -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::<u64>::into(f.modifier) >> 32) as u32, Into::<u64>::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(