dmabuf: Unify types of `wayland::dmabuf` and `allocator::dmabuf`
This commit is contained in:
parent
b6822becf6
commit
a38592bc92
|
@ -10,21 +10,67 @@
|
||||||
//! This can be especially useful in resources where other parts of the stack should decide upon
|
//! This can be especially useful in resources where other parts of the stack should decide upon
|
||||||
//! the lifetime of the buffer. E.g. when you are only caching associated resources for a dmabuf.
|
//! the lifetime of the buffer. E.g. when you are only caching associated resources for a dmabuf.
|
||||||
|
|
||||||
use super::{Buffer, Format, Modifier};
|
use super::{Buffer, Format, Fourcc, Modifier};
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::{IntoRawFd, RawFd};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
const MAX_PLANES: usize = 4;
|
/// Maximum amount of planes this implementation supports
|
||||||
|
pub const MAX_PLANES: usize = 4;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct DmabufInternal {
|
pub(crate) struct DmabufInternal {
|
||||||
pub num_planes: usize,
|
/// The submitted planes
|
||||||
pub offsets: [u32; MAX_PLANES],
|
pub planes: Vec<Plane>,
|
||||||
pub strides: [u32; MAX_PLANES],
|
/// The width of this buffer
|
||||||
pub fds: [RawFd; MAX_PLANES],
|
pub width: i32,
|
||||||
pub width: u32,
|
/// The height of this buffer
|
||||||
pub height: u32,
|
pub height: i32,
|
||||||
pub format: Format,
|
/// The format in use
|
||||||
|
pub format: Fourcc,
|
||||||
|
/// The flags applied to it
|
||||||
|
///
|
||||||
|
/// This is a bitflag, to be compared with the `Flags` enum re-exported by this module.
|
||||||
|
pub flags: DmabufFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Plane {
|
||||||
|
pub fd: Option<RawFd>,
|
||||||
|
/// The plane index
|
||||||
|
pub plane_idx: u32,
|
||||||
|
/// Offset from the start of the Fd
|
||||||
|
pub offset: u32,
|
||||||
|
/// Stride for this plane
|
||||||
|
pub stride: u32,
|
||||||
|
/// Modifier for this plane
|
||||||
|
pub modifier: Modifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoRawFd for Plane {
|
||||||
|
fn into_raw_fd(mut self) -> RawFd {
|
||||||
|
self.fd.take().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Plane {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(fd) = self.fd.take() {
|
||||||
|
let _ = nix::unistd::close(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
/// Possible flags for a DMA buffer
|
||||||
|
pub struct DmabufFlags: u32 {
|
||||||
|
/// The buffer content is Y-inverted
|
||||||
|
const Y_INVERT = 1;
|
||||||
|
/// The buffer content is interlaced
|
||||||
|
const INTERLACED = 2;
|
||||||
|
/// The buffer content if interlaced is bottom-field first
|
||||||
|
const BOTTOM_FIRST = 4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -42,109 +88,138 @@ impl PartialEq for Dmabuf {
|
||||||
}
|
}
|
||||||
impl Eq for Dmabuf {}
|
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 {
|
impl PartialEq for WeakDmabuf {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
if let Some(dmabuf) = self.upgrade() {
|
Weak::ptr_eq(&self.0, &other.0)
|
||||||
return &dmabuf == other;
|
}
|
||||||
}
|
}
|
||||||
false
|
impl Eq for WeakDmabuf {}
|
||||||
|
|
||||||
|
impl Hash for Dmabuf {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
Arc::as_ptr(&self.0).hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Hash for WeakDmabuf {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.0.as_ptr().hash(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buffer for Dmabuf {
|
impl Buffer for Dmabuf {
|
||||||
fn width(&self) -> u32 {
|
fn width(&self) -> u32 {
|
||||||
self.0.width
|
self.0.width as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn height(&self) -> u32 {
|
fn height(&self) -> u32 {
|
||||||
self.0.height
|
self.0.height as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format(&self) -> Format {
|
fn format(&self) -> Format {
|
||||||
self.0.format
|
Format {
|
||||||
|
code: self.0.format,
|
||||||
|
modifier: self.0.planes[0].modifier,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builder for Dmabufs
|
||||||
|
pub struct DmabufBuilder {
|
||||||
|
internal: DmabufInternal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DmabufBuilder {
|
||||||
|
/// Add a plane to the construted Dmabuf
|
||||||
|
///
|
||||||
|
/// *Note*: Each Dmabuf needs atleast one plane.
|
||||||
|
/// MAX_PLANES notes the maximum amount of planes any format may use with this implementation.
|
||||||
|
pub fn add_plane(&mut self, fd: RawFd, idx: u32, offset: u32, stride: u32, modifier: Modifier) -> bool {
|
||||||
|
if self.internal.planes.len() == MAX_PLANES {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.internal.planes.push(Plane {
|
||||||
|
fd: Some(fd),
|
||||||
|
plane_idx: idx,
|
||||||
|
offset,
|
||||||
|
stride,
|
||||||
|
modifier,
|
||||||
|
});
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a `Dmabuf` out of the provided parameters and planes
|
||||||
|
///
|
||||||
|
/// Returns `None` if the builder has no planes attached.
|
||||||
|
pub fn build(mut self) -> Option<Dmabuf> {
|
||||||
|
if self.internal.planes.len() == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.internal.planes.sort_by_key(|plane| plane.plane_idx);
|
||||||
|
Some(Dmabuf(Arc::new(self.internal)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dmabuf {
|
impl Dmabuf {
|
||||||
|
/// Create a new Dmabuf by intializing with values from an existing buffer
|
||||||
|
///
|
||||||
// Note: the `src` Buffer is only used a reference for size and format.
|
// Note: the `src` Buffer is only used a reference for size and format.
|
||||||
// The contents are determined by the provided file descriptors, which
|
// The contents are determined by the provided file descriptors, which
|
||||||
// do not need to refer to the same buffer `src` does.
|
// do not need to refer to the same buffer `src` does.
|
||||||
pub(crate) fn new(
|
pub fn new_from_buffer(src: &impl Buffer, flags: DmabufFlags) -> DmabufBuilder {
|
||||||
src: &impl Buffer,
|
DmabufBuilder {
|
||||||
planes: usize,
|
internal: DmabufInternal {
|
||||||
offsets: &[u32],
|
planes: Vec::with_capacity(MAX_PLANES),
|
||||||
strides: &[u32],
|
width: src.width() as i32,
|
||||||
fds: &[RawFd],
|
height: src.height() as i32,
|
||||||
) -> Option<Dmabuf> {
|
format: src.format().code,
|
||||||
if offsets.len() < planes
|
flags,
|
||||||
|| 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 {
|
|
||||||
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(),
|
|
||||||
],
|
|
||||||
|
|
||||||
width: src.width(),
|
|
||||||
height: src.height(),
|
|
||||||
format: src.format(),
|
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return raw handles of the planes of this buffer
|
/// Create a new Dmabuf
|
||||||
pub fn handles(&self) -> &[RawFd] {
|
pub fn new(width: u32, height: u32, format: Fourcc, flags: DmabufFlags) -> DmabufBuilder {
|
||||||
self.0.fds.split_at(self.0.num_planes).0
|
DmabufBuilder {
|
||||||
|
internal: DmabufInternal {
|
||||||
|
planes: Vec::with_capacity(MAX_PLANES),
|
||||||
|
width: width as i32,
|
||||||
|
height: height as i32,
|
||||||
|
format,
|
||||||
|
flags,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return offsets for the planes of this buffer
|
/// The amount of planes this Dmabuf has
|
||||||
pub fn offsets(&self) -> &[u32] {
|
pub fn num_planes(&self) -> usize {
|
||||||
self.0.offsets.split_at(self.0.num_planes).0
|
self.0.planes.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return strides for the planes of this buffer
|
/// Returns raw handles of the planes of this buffer
|
||||||
pub fn strides(&self) -> &[u32] {
|
pub fn handles<'a>(&'a self) -> impl Iterator<Item = RawFd> + 'a {
|
||||||
self.0.strides.split_at(self.0.num_planes).0
|
self.0.planes.iter().map(|p| *p.fd.as_ref().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if this buffer format has any vendor-specific modifiers set or is implicit/linear
|
/// Returns offsets for the planes of this buffer
|
||||||
|
pub fn offsets<'a>(&'a self) -> impl Iterator<Item = u32> + 'a {
|
||||||
|
self.0.planes.iter().map(|p| p.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns strides for the planes of this buffer
|
||||||
|
pub fn strides<'a>(&'a self) -> impl Iterator<Item = u32> + 'a {
|
||||||
|
self.0.planes.iter().map(|p| p.stride)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns 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.planes[0].modifier != Modifier::Invalid && self.0.planes[0].modifier != Modifier::Linear
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns if the buffer is stored inverted on the y-axis
|
||||||
|
pub fn y_inverted(&self) -> bool {
|
||||||
|
self.0.flags.contains(DmabufFlags::Y_INVERT)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a weak reference to this dmabuf
|
/// Create a weak reference to this dmabuf
|
||||||
|
@ -162,16 +237,6 @@ impl WeakDmabuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for DmabufInternal {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
for fd in self.fds.iter() {
|
|
||||||
if *fd != 0 {
|
|
||||||
let _ = nix::unistd::close(*fd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Buffer that can be exported as Dmabufs
|
/// Buffer that can be exported as Dmabufs
|
||||||
pub trait AsDmabuf {
|
pub trait AsDmabuf {
|
||||||
/// Error type returned, if exporting fails
|
/// Error type returned, if exporting fails
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
//! conversions to and from [dmabufs](super::dmabuf).
|
//! conversions to and from [dmabufs](super::dmabuf).
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
dmabuf::{AsDmabuf, Dmabuf},
|
dmabuf::{AsDmabuf, Dmabuf, DmabufFlags, MAX_PLANES},
|
||||||
Allocator, Buffer, Format, Fourcc, Modifier,
|
Allocator, Buffer, Format, Fourcc, Modifier,
|
||||||
};
|
};
|
||||||
pub use gbm::{BufferObject as GbmBuffer, BufferObjectFlags as GbmBufferFlags, Device as GbmDevice};
|
pub use gbm::{BufferObject as GbmBuffer, BufferObjectFlags as GbmBufferFlags, Device as GbmDevice};
|
||||||
|
@ -95,20 +95,21 @@ impl<T> AsDmabuf for GbmBuffer<T> {
|
||||||
return Err(GbmConvertError::UnsupportedBuffer); //TODO
|
return Err(GbmConvertError::UnsupportedBuffer); //TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
let fds = [self.fd()?, 0, 0, 0];
|
if self.fd()? == 0 {
|
||||||
//if fds.iter().any(|fd| fd == 0) {
|
|
||||||
if fds[0] < 0 {
|
|
||||||
return Err(GbmConvertError::InvalidFD);
|
return Err(GbmConvertError::InvalidFD);
|
||||||
}
|
}
|
||||||
|
|
||||||
let offsets = (0i32..planes)
|
let mut builder = Dmabuf::new_from_buffer(self, DmabufFlags::empty());
|
||||||
.map(|i| self.offset(i))
|
for idx in 0..planes {
|
||||||
.collect::<Result<Vec<u32>, gbm::DeviceDestroyedError>>()?;
|
builder.add_plane(
|
||||||
let strides = (0i32..planes)
|
self.fd()?,
|
||||||
.map(|i| self.stride_for_plane(i))
|
idx as u32,
|
||||||
.collect::<Result<Vec<u32>, gbm::DeviceDestroyedError>>()?;
|
self.offset(idx)?,
|
||||||
|
self.stride_for_plane(idx)?,
|
||||||
Ok(Dmabuf::new(self, planes as usize, &offsets, &strides, &fds).unwrap())
|
self.modifier()?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(builder.build().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,27 +120,39 @@ impl Dmabuf {
|
||||||
gbm: &GbmDevice<A>,
|
gbm: &GbmDevice<A>,
|
||||||
usage: GbmBufferFlags,
|
usage: GbmBufferFlags,
|
||||||
) -> std::io::Result<GbmBuffer<T>> {
|
) -> std::io::Result<GbmBuffer<T>> {
|
||||||
let buf = &*self.0;
|
let mut handles = [0; MAX_PLANES];
|
||||||
if self.has_modifier() || buf.num_planes > 1 || buf.offsets[0] != 0 {
|
for (i, handle) in self.handles().take(MAX_PLANES).enumerate() {
|
||||||
|
handles[i] = handle;
|
||||||
|
}
|
||||||
|
let mut strides = [0i32; MAX_PLANES];
|
||||||
|
for (i, stride) in self.strides().take(MAX_PLANES).enumerate() {
|
||||||
|
strides[i] = stride as i32;
|
||||||
|
}
|
||||||
|
let mut offsets = [0i32; MAX_PLANES];
|
||||||
|
for (i, offset) in self.offsets().take(MAX_PLANES).enumerate() {
|
||||||
|
offsets[i] = offset as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.has_modifier() || self.num_planes() > 1 || self.offsets().next().unwrap() != 0 {
|
||||||
gbm.import_buffer_object_from_dma_buf_with_modifiers(
|
gbm.import_buffer_object_from_dma_buf_with_modifiers(
|
||||||
buf.num_planes as u32,
|
self.num_planes() as u32,
|
||||||
buf.fds,
|
handles,
|
||||||
buf.width,
|
self.width(),
|
||||||
buf.height,
|
self.height(),
|
||||||
buf.format.code,
|
self.format().code,
|
||||||
usage,
|
usage,
|
||||||
unsafe { std::mem::transmute::<[u32; 4], [i32; 4]>(buf.strides) },
|
strides,
|
||||||
unsafe { std::mem::transmute::<[u32; 4], [i32; 4]>(buf.offsets) },
|
offsets,
|
||||||
buf.format.modifier,
|
self.format().modifier,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
gbm.import_buffer_object_from_dma_buf(
|
gbm.import_buffer_object_from_dma_buf(
|
||||||
buf.fds[0],
|
handles[0],
|
||||||
buf.width,
|
self.width(),
|
||||||
buf.height,
|
self.height(),
|
||||||
buf.strides[0],
|
strides[0] as u32,
|
||||||
buf.format.code,
|
self.format().code,
|
||||||
if buf.format.modifier == Modifier::Linear {
|
if self.format().modifier == Modifier::Linear {
|
||||||
usage | GbmBufferFlags::LINEAR
|
usage | GbmBufferFlags::LINEAR
|
||||||
} else {
|
} else {
|
||||||
usage
|
usage
|
||||||
|
|
|
@ -19,10 +19,10 @@ use crate::backend::egl::{
|
||||||
ffi,
|
ffi,
|
||||||
ffi::egl::types::EGLImage,
|
ffi::egl::types::EGLImage,
|
||||||
native::EGLNativeDisplay,
|
native::EGLNativeDisplay,
|
||||||
wrap_egl_call, EGLError, Error, Format,
|
wrap_egl_call, EGLError, Error,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "wayland_frontend")]
|
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
|
||||||
use crate::backend::egl::{BufferAccessError, EGLBuffer};
|
use crate::backend::egl::{BufferAccessError, EGLBuffer, Format};
|
||||||
|
|
||||||
/// Wrapper around [`ffi::EGLDisplay`](ffi::egl::types::EGLDisplay) to ensure display is only destroyed
|
/// Wrapper around [`ffi::EGLDisplay`](ffi::egl::types::EGLDisplay) to ensure display is only destroyed
|
||||||
/// once all resources bound to it have been dropped.
|
/// once all resources bound to it have been dropped.
|
||||||
|
@ -473,18 +473,17 @@ impl EGLDisplay {
|
||||||
|
|
||||||
for (i, ((fd, offset), stride)) in dmabuf
|
for (i, ((fd, offset), stride)) in dmabuf
|
||||||
.handles()
|
.handles()
|
||||||
.iter()
|
|
||||||
.zip(dmabuf.offsets())
|
.zip(dmabuf.offsets())
|
||||||
.zip(dmabuf.strides())
|
.zip(dmabuf.strides())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
out.extend(&[
|
out.extend(&[
|
||||||
names[i][0] as i32,
|
names[i][0] as i32,
|
||||||
*fd,
|
fd,
|
||||||
names[i][1] as i32,
|
names[i][1] as i32,
|
||||||
*offset as i32,
|
offset as i32,
|
||||||
names[i][2] as i32,
|
names[i][2] as i32,
|
||||||
*stride as i32,
|
stride as i32,
|
||||||
]);
|
]);
|
||||||
if dmabuf.has_modifier() {
|
if dmabuf.has_modifier() {
|
||||||
out.extend(&[
|
out.extend(&[
|
||||||
|
@ -510,7 +509,6 @@ impl EGLDisplay {
|
||||||
if image == ffi::egl::NO_IMAGE_KHR {
|
if image == ffi::egl::NO_IMAGE_KHR {
|
||||||
Err(Error::EGLImageCreationFailed)
|
Err(Error::EGLImageCreationFailed)
|
||||||
} else {
|
} else {
|
||||||
// TODO check for external
|
|
||||||
Ok(image)
|
Ok(image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,11 @@ mod version;
|
||||||
use super::{Bind, Frame, Renderer, Texture, Transform, Unbind};
|
use super::{Bind, Frame, Renderer, Texture, Transform, Unbind};
|
||||||
use crate::backend::allocator::{
|
use crate::backend::allocator::{
|
||||||
dmabuf::{Dmabuf, WeakDmabuf},
|
dmabuf::{Dmabuf, WeakDmabuf},
|
||||||
Format,
|
Buffer, Format,
|
||||||
};
|
};
|
||||||
use crate::backend::egl::{
|
use crate::backend::egl::{
|
||||||
ffi::egl::{self as ffi_egl, types::EGLImage},
|
ffi::egl::{self as ffi_egl, types::EGLImage},
|
||||||
EGLContext, EGLSurface, Format as EGLFormat, MakeCurrentError,
|
EGLContext, EGLSurface, MakeCurrentError,
|
||||||
};
|
};
|
||||||
use crate::backend::SwapBuffersError;
|
use crate::backend::SwapBuffersError;
|
||||||
|
|
||||||
|
@ -34,11 +34,11 @@ use crate::{
|
||||||
wayland::compositor::SurfaceAttributes,
|
wayland::compositor::SurfaceAttributes,
|
||||||
};
|
};
|
||||||
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
|
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
|
||||||
use crate::backend::egl::display::EGLBufferReader;
|
use crate::backend::egl::{display::EGLBufferReader, Format as EGLFormat};
|
||||||
#[cfg(feature = "wayland_frontend")]
|
#[cfg(feature = "wayland_frontend")]
|
||||||
use wayland_server::protocol::{wl_buffer, wl_shm};
|
use wayland_server::protocol::{wl_buffer, wl_shm};
|
||||||
#[cfg(feature = "wayland_frontend")]
|
#[cfg(feature = "wayland_frontend")]
|
||||||
use super::ImportShm;
|
use super::{ImportShm, ImportDma};
|
||||||
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
|
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
|
||||||
use super::ImportEgl;
|
use super::ImportEgl;
|
||||||
|
|
||||||
|
@ -76,8 +76,6 @@ struct Gles2TextureInternal {
|
||||||
y_inverted: bool,
|
y_inverted: bool,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
#[cfg(feature = "wayland_frontend")]
|
|
||||||
buffer: Option<wl_buffer::WlBuffer>,
|
|
||||||
egl_images: Option<Vec<EGLImage>>,
|
egl_images: Option<Vec<EGLImage>>,
|
||||||
destruction_callback_sender: Sender<CleanupResource>,
|
destruction_callback_sender: Sender<CleanupResource>,
|
||||||
}
|
}
|
||||||
|
@ -155,7 +153,7 @@ pub struct Gles2Renderer {
|
||||||
extensions: Vec<String>,
|
extensions: Vec<String>,
|
||||||
programs: [Gles2Program; shaders::FRAGMENT_COUNT],
|
programs: [Gles2Program; shaders::FRAGMENT_COUNT],
|
||||||
#[cfg(feature = "wayland_frontend")]
|
#[cfg(feature = "wayland_frontend")]
|
||||||
dmabuf_cache: HashMap<BufferEntry, Gles2Texture>,
|
dmabuf_cache: HashMap<WeakDmabuf, Gles2Texture>,
|
||||||
egl: EGLContext,
|
egl: EGLContext,
|
||||||
gl: ffi::Gles2,
|
gl: ffi::Gles2,
|
||||||
destruction_callback: Receiver<CleanupResource>,
|
destruction_callback: Receiver<CleanupResource>,
|
||||||
|
@ -483,7 +481,7 @@ impl Gles2Renderer {
|
||||||
self.make_current()?;
|
self.make_current()?;
|
||||||
#[cfg(feature = "wayland_frontend")]
|
#[cfg(feature = "wayland_frontend")]
|
||||||
self.dmabuf_cache
|
self.dmabuf_cache
|
||||||
.retain(|entry, _tex| entry.buffer.as_ref().is_alive());
|
.retain(|entry, _tex| entry.upgrade().is_some());
|
||||||
for resource in self.destruction_callback.try_iter() {
|
for resource in self.destruction_callback.try_iter() {
|
||||||
match resource {
|
match resource {
|
||||||
CleanupResource::Texture(texture) => unsafe {
|
CleanupResource::Texture(texture) => unsafe {
|
||||||
|
@ -550,7 +548,6 @@ impl ImportShm for Gles2Renderer {
|
||||||
y_inverted: false,
|
y_inverted: false,
|
||||||
width: width as u32,
|
width: width as u32,
|
||||||
height: height as u32,
|
height: height as u32,
|
||||||
buffer: Some(buffer.clone()),
|
|
||||||
egl_images: None,
|
egl_images: None,
|
||||||
destruction_callback_sender: self.destruction_callback_sender.clone(),
|
destruction_callback_sender: self.destruction_callback_sender.clone(),
|
||||||
});
|
});
|
||||||
|
@ -657,7 +654,6 @@ impl ImportEgl for Gles2Renderer {
|
||||||
y_inverted: egl.y_inverted,
|
y_inverted: egl.y_inverted,
|
||||||
width: egl.width,
|
width: egl.width,
|
||||||
height: egl.height,
|
height: egl.height,
|
||||||
buffer: Some(buffer.clone()),
|
|
||||||
egl_images: Some(egl.into_images()),
|
egl_images: Some(egl.into_images()),
|
||||||
destruction_callback_sender: self.destruction_callback_sender.clone(),
|
destruction_callback_sender: self.destruction_callback_sender.clone(),
|
||||||
}));
|
}));
|
||||||
|
@ -666,16 +662,56 @@ impl ImportEgl for Gles2Renderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wayland_frontend")]
|
||||||
|
impl ImportDma for Gles2Renderer {
|
||||||
|
fn import_dmabuf(
|
||||||
|
&mut self,
|
||||||
|
buffer: &Dmabuf,
|
||||||
|
) -> Result<Gles2Texture, Gles2Error> {
|
||||||
|
if !self.extensions.iter().any(|ext| ext == "GL_OES_EGL_image") {
|
||||||
|
return Err(Gles2Error::GLExtensionNotSupported(&["GL_OES_EGL_image"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.existing_dmabuf_texture(&buffer)?.map(Ok).unwrap_or_else(|| {
|
||||||
|
let is_external = !self.egl.dmabuf_render_formats().contains(&buffer.format());
|
||||||
|
|
||||||
|
self.make_current()?;
|
||||||
|
let image = self.egl.display.create_image_from_dmabuf(&buffer)
|
||||||
|
.map_err(Gles2Error::BindBufferEGLError)?;
|
||||||
|
|
||||||
|
let tex = self.import_egl_image(image, is_external, None)?;
|
||||||
|
let texture = Gles2Texture(Rc::new(Gles2TextureInternal {
|
||||||
|
texture: tex,
|
||||||
|
texture_kind: if is_external { 2 } else { 0 },
|
||||||
|
is_external,
|
||||||
|
y_inverted: buffer.y_inverted(),
|
||||||
|
width: buffer.width(),
|
||||||
|
height: buffer.height(),
|
||||||
|
egl_images: Some(vec![image]),
|
||||||
|
destruction_callback_sender: self.destruction_callback_sender.clone(),
|
||||||
|
}));
|
||||||
|
self.egl.unbind()?;
|
||||||
|
self.dmabuf_cache.insert(buffer.weak(), texture.clone());
|
||||||
|
Ok(texture)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wayland_frontend")]
|
||||||
|
fn dmabuf_formats<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Format> + 'a> {
|
||||||
|
Box::new(self.egl.dmabuf_texture_formats().iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland_frontend")]
|
#[cfg(feature = "wayland_frontend")]
|
||||||
impl Gles2Renderer {
|
impl Gles2Renderer {
|
||||||
fn existing_dmabuf_texture(
|
fn existing_dmabuf_texture(
|
||||||
&self,
|
&self,
|
||||||
buffer: &wl_buffer::WlBuffer,
|
buffer: &Dmabuf,
|
||||||
) -> Result<Option<Gles2Texture>, Gles2Error> {
|
) -> Result<Option<Gles2Texture>, Gles2Error> {
|
||||||
let existing_texture = self
|
let existing_texture = self
|
||||||
.dmabuf_cache
|
.dmabuf_cache
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(old_buffer, _)| &old_buffer.buffer == buffer)
|
.find(|(weak, _)| weak.upgrade().map(|entry| &entry == buffer).unwrap_or(false))
|
||||||
.map(|(_, tex)| tex.clone());
|
.map(|(_, tex)| tex.clone());
|
||||||
|
|
||||||
if let Some(texture) = existing_texture {
|
if let Some(texture) = existing_texture {
|
||||||
|
@ -928,7 +964,6 @@ impl Renderer for Gles2Renderer {
|
||||||
y_inverted: false,
|
y_inverted: false,
|
||||||
width: image.width(),
|
width: image.width(),
|
||||||
height: image.height(),
|
height: image.height(),
|
||||||
buffer: None,
|
|
||||||
egl_images: None,
|
egl_images: None,
|
||||||
destruction_callback_sender: self.destruction_callback_sender.clone(),
|
destruction_callback_sender: self.destruction_callback_sender.clone(),
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -18,6 +18,8 @@ use wayland_server::protocol::{wl_buffer, wl_shm};
|
||||||
|
|
||||||
#[cfg(feature = "renderer_gl")]
|
#[cfg(feature = "renderer_gl")]
|
||||||
pub mod gles2;
|
pub mod gles2;
|
||||||
|
#[cfg(feature = "wayland_frontend")]
|
||||||
|
use crate::backend::allocator::{dmabuf::Dmabuf, Format};
|
||||||
#[cfg(all(feature = "wayland_frontend", feature = "backend_egl", feature = "use_system_lib"))]
|
#[cfg(all(feature = "wayland_frontend", feature = "backend_egl", feature = "use_system_lib"))]
|
||||||
use crate::backend::egl::display::EGLBufferReader;
|
use crate::backend::egl::display::EGLBufferReader;
|
||||||
|
|
||||||
|
@ -289,6 +291,54 @@ pub trait ImportEgl: Renderer {
|
||||||
) -> Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>;
|
) -> Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wayland_frontend")]
|
||||||
|
/// Trait for Renderers supporting importing dmabuf-based buffers.
|
||||||
|
pub trait ImportDma: Renderer {
|
||||||
|
/// Returns supported formats for dmabufs.
|
||||||
|
fn dmabuf_formats<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Format> + 'a> {
|
||||||
|
Box::new([].iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import a given dmabuf-based buffer into the renderer (see [`buffer_type`]).
|
||||||
|
///
|
||||||
|
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::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.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
fn import_dma_buffer(
|
||||||
|
&mut self,
|
||||||
|
buffer: &wl_buffer::WlBuffer,
|
||||||
|
) -> Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error> {
|
||||||
|
let dmabuf = buffer
|
||||||
|
.as_ref()
|
||||||
|
.user_data()
|
||||||
|
.get::<Dmabuf>()
|
||||||
|
.expect("import_dma_buffer without checking buffer type?");
|
||||||
|
self.import_dmabuf(dmabuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import a given raw dmabuf into the renderer.
|
||||||
|
///
|
||||||
|
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::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.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
fn import_dmabuf(
|
||||||
|
&mut self,
|
||||||
|
dmabuf: &Dmabuf,
|
||||||
|
) -> Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Replace this with a trait_alias, once that is stabilized.
|
// TODO: Replace this with a trait_alias, once that is stabilized.
|
||||||
// pub type ImportAll = Renderer + ImportShm + ImportEgl;
|
// pub type ImportAll = Renderer + ImportShm + ImportEgl;
|
||||||
|
|
||||||
|
@ -352,7 +402,14 @@ pub fn buffer_type(
|
||||||
buffer: &wl_buffer::WlBuffer,
|
buffer: &wl_buffer::WlBuffer,
|
||||||
egl_buffer_reader: Option<&EGLBufferReader>,
|
egl_buffer_reader: Option<&EGLBufferReader>,
|
||||||
) -> Option<BufferType> {
|
) -> Option<BufferType> {
|
||||||
if egl_buffer_reader
|
if buffer
|
||||||
|
.as_ref()
|
||||||
|
.user_data()
|
||||||
|
.get::<Dmabuf>()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
Some(BufferType::Dma)
|
||||||
|
} else if egl_buffer_reader
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|x| x.egl_buffer_dimensions(&buffer))
|
.and_then(|x| x.egl_buffer_dimensions(&buffer))
|
||||||
.is_some()
|
.is_some()
|
||||||
|
@ -371,9 +428,14 @@ pub fn buffer_type(
|
||||||
/// Returns `None` if the type is not recognized by smithay or otherwise not supported.
|
/// Returns `None` if the type is not recognized by smithay or otherwise not supported.
|
||||||
#[cfg(all(feature = "wayland_frontend", not(all(feature = "backend_egl", feature = "use_system_lib"))))]
|
#[cfg(all(feature = "wayland_frontend", not(all(feature = "backend_egl", feature = "use_system_lib"))))]
|
||||||
pub fn buffer_type(buffer: &wl_buffer::WlBuffer) -> Option<BufferType> {
|
pub fn buffer_type(buffer: &wl_buffer::WlBuffer) -> Option<BufferType> {
|
||||||
use crate::backend::allocator::Buffer;
|
if buffer
|
||||||
|
.as_ref()
|
||||||
if crate::wayland::shm::with_buffer_contents(&buffer, |_, _| ()).is_ok()
|
.user_data()
|
||||||
|
.get::<Dmabuf>()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
Some(BufferType::Dma)
|
||||||
|
} else if crate::wayland::shm::with_buffer_contents(&buffer, |_, _| ()).is_ok()
|
||||||
{
|
{
|
||||||
Some(BufferType::Shm)
|
Some(BufferType::Shm)
|
||||||
} else {
|
} else {
|
||||||
|
@ -389,7 +451,11 @@ pub fn buffer_dimensions(
|
||||||
buffer: &wl_buffer::WlBuffer,
|
buffer: &wl_buffer::WlBuffer,
|
||||||
egl_buffer_reader: Option<&EGLBufferReader>,
|
egl_buffer_reader: Option<&EGLBufferReader>,
|
||||||
) -> Option<(i32, i32)> {
|
) -> Option<(i32, i32)> {
|
||||||
if let Some((w, h)) = egl_buffer_reader
|
use crate::backend::allocator::Buffer;
|
||||||
|
|
||||||
|
if let Some(buf) = buffer.as_ref().user_data().get::<Dmabuf>() {
|
||||||
|
Some((buf.width() as i32, buf.height() as i32))
|
||||||
|
} else if let Some((w, h)) = egl_buffer_reader
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|x| x.egl_buffer_dimensions(&buffer))
|
.and_then(|x| x.egl_buffer_dimensions(&buffer))
|
||||||
{
|
{
|
||||||
|
@ -410,7 +476,9 @@ pub fn buffer_dimensions(
|
||||||
pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option<(i32, i32)> {
|
pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option<(i32, i32)> {
|
||||||
use crate::backend::allocator::Buffer;
|
use crate::backend::allocator::Buffer;
|
||||||
|
|
||||||
if let Ok((w, h)) =
|
if let Some(buf) = buffer.as_ref().user_data().get::<Dmabuf>() {
|
||||||
|
Some((buf.width() as i32, buf.height() as i32))
|
||||||
|
} else if let Ok((w, h)) =
|
||||||
crate::wayland::shm::with_buffer_contents(&buffer, |_, data| (data.width, data.height))
|
crate::wayland::shm::with_buffer_contents(&buffer, |_, data| (data.width, data.height))
|
||||||
{
|
{
|
||||||
Some((w, h))
|
Some((w, h))
|
||||||
|
|
|
@ -16,7 +16,6 @@ use crate::backend::{
|
||||||
};
|
};
|
||||||
use std::{cell::RefCell, rc::Rc, time::Instant};
|
use std::{cell::RefCell, rc::Rc, time::Instant};
|
||||||
use wayland_egl as wegl;
|
use wayland_egl as wegl;
|
||||||
use wayland_server::Display;
|
|
||||||
use winit::{
|
use winit::{
|
||||||
dpi::{LogicalPosition, LogicalSize, PhysicalSize},
|
dpi::{LogicalPosition, LogicalSize, PhysicalSize},
|
||||||
event::{
|
event::{
|
||||||
|
@ -29,6 +28,8 @@ use winit::{
|
||||||
window::{Window as WinitWindow, WindowBuilder},
|
window::{Window as WinitWindow, WindowBuilder},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "use_system_lib")]
|
||||||
|
use wayland_server::Display;
|
||||||
#[cfg(feature = "use_system_lib")]
|
#[cfg(feature = "use_system_lib")]
|
||||||
use crate::backend::egl::display::EGLBufferReader;
|
use crate::backend::egl::display::EGLBufferReader;
|
||||||
|
|
||||||
|
|
|
@ -4,52 +4,25 @@
|
||||||
//! contents as dmabuf file descriptors. These handlers automate the aggregation of the metadata associated
|
//! contents as dmabuf file descriptors. These handlers automate the aggregation of the metadata associated
|
||||||
//! with a dma buffer, and do some basic checking of the sanity of what the client sends.
|
//! with a dma buffer, and do some basic checking of the sanity of what the client sends.
|
||||||
//!
|
//!
|
||||||
//! This module is only available if the `backend_drm` cargo feature is enabled.
|
|
||||||
//!
|
|
||||||
//! ## How to use
|
//! ## How to use
|
||||||
//!
|
//!
|
||||||
//! To setup the dmabuf global, you will need to provide 2 things:
|
//! To setup the dmabuf global, you will need to provide 2 things:
|
||||||
//!
|
//!
|
||||||
//! - a list of the dmabuf formats you wish to support
|
//! - a list of the dmabuf formats you wish to support
|
||||||
//! - an implementation of the `DmabufHandler` trait
|
//! - a closure to test if a dmabuf buffer can be imported by your renderer
|
||||||
//!
|
//!
|
||||||
//! The list of supported format is just a `Vec<Format>`, where you will enter all the (format, modifier)
|
//! The list of supported format is just a `Vec<Format>`, where you will enter all the (code, modifier)
|
||||||
//! couples you support.
|
//! couples you support. You can typically receive a list of supported formats for one renderer by calling
|
||||||
//!
|
//! [`crate::backend::renderer::Renderer::dmabuf_formats`].
|
||||||
//! The implementation of the `DmabufHandler` trait will be called whenever a client has finished setting up
|
|
||||||
//! a dma buffer. You will be handled the full details of the client's submission as a `BufferInfo` struct,
|
|
||||||
//! and you need to validate it and maybe import it into your renderer. The `BufferData` associated type
|
|
||||||
//! allows you to store any metadata or handle to the resource you need into the created `wl_buffer`,
|
|
||||||
//! user data, to then retrieve it when it is attached to a surface to re-identify the dmabuf.
|
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # extern crate wayland_server;
|
//! # extern crate wayland_server;
|
||||||
//! # extern crate smithay;
|
//! # extern crate smithay;
|
||||||
//! use smithay::wayland::dmabuf::{DmabufHandler, BufferInfo, init_dmabuf_global};
|
//! use smithay::{
|
||||||
//!
|
//! backend::allocator::dmabuf::Dmabuf,
|
||||||
//! struct MyDmabufHandler;
|
//! reexports::{wayland_server::protocol::wl_buffer::WlBuffer},
|
||||||
//!
|
//! wayland::dmabuf::init_dmabuf_global,
|
||||||
//! struct MyBufferData {
|
//! };
|
||||||
//! /* ... */
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl Drop for MyBufferData {
|
|
||||||
//! fn drop(&mut self) {
|
|
||||||
//! // This is called when all handles to this buffer have been dropped,
|
|
||||||
//! // both client-side and server side.
|
|
||||||
//! // You can now free the associated resources in your renderer.
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl DmabufHandler for MyDmabufHandler {
|
|
||||||
//! type BufferData = MyBufferData;
|
|
||||||
//! fn validate_dmabuf(&mut self, info: BufferInfo) -> Result<Self::BufferData, ()> {
|
|
||||||
//! /* validate the dmabuf and import it into your renderer state */
|
|
||||||
//! Ok(MyBufferData { /* ... */ })
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! // Once this is defined, you can in your setup initialize the dmabuf global:
|
|
||||||
//!
|
//!
|
||||||
//! # let mut display = wayland_server::Display::new();
|
//! # let mut display = wayland_server::Display::new();
|
||||||
//! // define your supported formats
|
//! // define your supported formats
|
||||||
|
@ -59,12 +32,21 @@
|
||||||
//! let dmabuf_global = init_dmabuf_global(
|
//! let dmabuf_global = init_dmabuf_global(
|
||||||
//! &mut display,
|
//! &mut display,
|
||||||
//! formats,
|
//! formats,
|
||||||
//! MyDmabufHandler,
|
//! |buffer, _| {
|
||||||
|
//! /* validate the dmabuf and import it into your renderer state */
|
||||||
|
//! let dmabuf = buffer.as_ref().user_data().get::<Dmabuf>().expect("dmabuf global sets this for us");
|
||||||
|
//! true
|
||||||
|
//! },
|
||||||
//! None // we don't provide a logger in this example
|
//! None // we don't provide a logger in this example
|
||||||
//! );
|
//! );
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::{cell::RefCell, os::unix::io::RawFd, rc::Rc};
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
convert::TryFrom,
|
||||||
|
os::unix::io::{IntoRawFd, RawFd},
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
pub use wayland_protocols::unstable::linux_dmabuf::v1::server::zwp_linux_buffer_params_v1::Flags;
|
pub use wayland_protocols::unstable::linux_dmabuf::v1::server::zwp_linux_buffer_params_v1::Flags;
|
||||||
use wayland_protocols::unstable::linux_dmabuf::v1::server::{
|
use wayland_protocols::unstable::linux_dmabuf::v1::server::{
|
||||||
|
@ -73,124 +55,34 @@ use wayland_protocols::unstable::linux_dmabuf::v1::server::{
|
||||||
},
|
},
|
||||||
zwp_linux_dmabuf_v1,
|
zwp_linux_dmabuf_v1,
|
||||||
};
|
};
|
||||||
use wayland_server::{protocol::wl_buffer, Display, Filter, Global, Main};
|
use wayland_server::{protocol::wl_buffer, DispatchData, Display, Filter, Global, Main};
|
||||||
|
|
||||||
use crate::backend::allocator::{Fourcc, Modifier};
|
use crate::backend::allocator::{
|
||||||
|
dmabuf::{Dmabuf, DmabufFlags, Plane},
|
||||||
/// Representation of a Dmabuf format, as advertized to the client
|
Format, Fourcc, Modifier,
|
||||||
#[derive(Debug)]
|
};
|
||||||
pub struct Format {
|
|
||||||
/// The format identifier.
|
|
||||||
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: Modifier,
|
|
||||||
/// Number of planes used by this format
|
|
||||||
pub plane_count: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A plane send by the client
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Plane {
|
|
||||||
/// The file descriptor
|
|
||||||
pub fd: RawFd,
|
|
||||||
/// The plane index
|
|
||||||
pub plane_idx: u32,
|
|
||||||
/// Offset from the start of the Fd
|
|
||||||
pub offset: u32,
|
|
||||||
/// Stride for this plane
|
|
||||||
pub stride: u32,
|
|
||||||
/// Modifier for this plane
|
|
||||||
pub modifier: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
/// Possible flags for a DMA buffer
|
|
||||||
pub struct BufferFlags: u32 {
|
|
||||||
/// The buffer content is Y-inverted
|
|
||||||
const Y_INVERT = 1;
|
|
||||||
/// The buffer content is interlaced
|
|
||||||
const INTERLACED = 2;
|
|
||||||
/// The buffer content if interlaced is bottom-field first
|
|
||||||
const BOTTOM_FIRST = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The complete information provided by the client to create a dmabuf buffer
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BufferInfo {
|
|
||||||
/// The submitted planes
|
|
||||||
pub planes: Vec<Plane>,
|
|
||||||
/// The width of this buffer
|
|
||||||
pub width: i32,
|
|
||||||
/// The height of this buffer
|
|
||||||
pub height: i32,
|
|
||||||
/// The format in use
|
|
||||||
pub format: u32,
|
|
||||||
/// The flags applied to it
|
|
||||||
///
|
|
||||||
/// This is a bitflag, to be compared with the `Flags` enum reexported by this module.
|
|
||||||
pub flags: BufferFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler trait for dmabuf validation
|
/// Handler trait for dmabuf validation
|
||||||
///
|
///
|
||||||
/// You need to provide an implementation of this trait that will validate the parameters provided by the
|
/// You need to provide an implementation of this trait
|
||||||
/// client and import it as a dmabuf.
|
|
||||||
pub trait DmabufHandler {
|
|
||||||
/// The data of a successfully imported dmabuf.
|
|
||||||
///
|
|
||||||
/// This will be stored as the `user_data` of the `WlBuffer` associated with this dmabuf. If it has a
|
|
||||||
/// destructor, it will be run when the client has destroyed the buffer and your compositor has dropped
|
|
||||||
/// all of its `WlBuffer` handles to it.
|
|
||||||
type BufferData: 'static;
|
|
||||||
/// Validate a dmabuf
|
|
||||||
///
|
|
||||||
/// From the information provided by the client, you need to validate and/or import the buffer.
|
|
||||||
///
|
|
||||||
/// You can then store any information your compositor will need to handle it later, when the client has
|
|
||||||
/// submitted the buffer by returning `Ok(BufferData)` where `BufferData` is the associated type of this,
|
|
||||||
/// trait, a type of your choosing.
|
|
||||||
///
|
|
||||||
/// If the buffer could not be imported, whatever the reason, return `Err(())`.
|
|
||||||
fn validate_dmabuf(&mut self, info: BufferInfo) -> Result<Self::BufferData, ()>;
|
|
||||||
/// Create a buffer from validated buffer data.
|
|
||||||
///
|
|
||||||
/// This method is pre-implemented for you by storing the provided `BufferData` as the `user_data` of the
|
|
||||||
/// provided `WlBuffer`. By default it assumes that your `BufferData` is not threadsafe.
|
|
||||||
///
|
|
||||||
/// You can override it if you need your `BufferData` to be threadsafe, or which to register a destructor
|
|
||||||
/// for the `WlBuffer` for example.
|
|
||||||
fn create_buffer(
|
|
||||||
&mut self,
|
|
||||||
data: Self::BufferData,
|
|
||||||
buffer: Main<wl_buffer::WlBuffer>,
|
|
||||||
) -> wl_buffer::WlBuffer {
|
|
||||||
buffer.quick_assign(|_, _, _| {});
|
|
||||||
buffer.as_ref().user_data().set(|| data);
|
|
||||||
(*buffer).clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initialize a dmabuf global.
|
/// Initialize a dmabuf global.
|
||||||
///
|
///
|
||||||
/// You need to provide a vector of the supported formats, as well as an implementation fo the `DmabufHandler`
|
/// You need to provide a vector of the supported formats, as well as a closure,
|
||||||
/// trait, which will receive the buffer creation requests from the clients.
|
/// that will validate the parameters provided by the client and tests the import as a dmabuf.
|
||||||
pub fn init_dmabuf_global<H, L>(
|
pub fn init_dmabuf_global<F, L>(
|
||||||
display: &mut Display,
|
display: &mut Display,
|
||||||
formats: Vec<Format>,
|
formats: Vec<Format>,
|
||||||
handler: H,
|
handler: F,
|
||||||
logger: L,
|
logger: L,
|
||||||
) -> Global<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>
|
) -> Global<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>
|
||||||
where
|
where
|
||||||
L: Into<Option<::slog::Logger>>,
|
L: Into<Option<::slog::Logger>>,
|
||||||
H: DmabufHandler + 'static,
|
F: for<'a> FnMut(&Dmabuf, DispatchData<'a>) -> bool + 'static,
|
||||||
{
|
{
|
||||||
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "dmabuf_handler"));
|
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "dmabuf_handler"));
|
||||||
|
|
||||||
let max_planes = formats.iter().map(|f| f.plane_count).max().unwrap_or(0);
|
|
||||||
let formats = Rc::<[Format]>::from(formats);
|
let formats = Rc::<[Format]>::from(formats);
|
||||||
let handler = Rc::new(RefCell::new(handler));
|
let handler = Rc::new(RefCell::new(handler));
|
||||||
|
|
||||||
|
@ -211,13 +103,13 @@ where
|
||||||
if let zwp_linux_dmabuf_v1::Request::CreateParams { params_id } = req {
|
if let zwp_linux_dmabuf_v1::Request::CreateParams { params_id } = req {
|
||||||
let mut handler = ParamsHandler {
|
let mut handler = ParamsHandler {
|
||||||
pending_planes: Vec::new(),
|
pending_planes: Vec::new(),
|
||||||
max_planes,
|
max_planes: 4,
|
||||||
used: false,
|
used: false,
|
||||||
formats: dma_formats.clone(),
|
formats: dma_formats.clone(),
|
||||||
handler: dma_handler.clone(),
|
handler: dma_handler.clone(),
|
||||||
log: dma_log.clone(),
|
log: dma_log.clone(),
|
||||||
};
|
};
|
||||||
params_id.quick_assign(move |params, req, _| match req {
|
params_id.quick_assign(move |params, req, ddata| match req {
|
||||||
ParamsRequest::Add {
|
ParamsRequest::Add {
|
||||||
fd,
|
fd,
|
||||||
plane_idx,
|
plane_idx,
|
||||||
|
@ -238,14 +130,14 @@ where
|
||||||
height,
|
height,
|
||||||
format,
|
format,
|
||||||
flags,
|
flags,
|
||||||
} => handler.create(&*params, width, height, format, flags),
|
} => handler.create(&*params, width, height, format, flags, ddata),
|
||||||
ParamsRequest::CreateImmed {
|
ParamsRequest::CreateImmed {
|
||||||
buffer_id,
|
buffer_id,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
format,
|
format,
|
||||||
flags,
|
flags,
|
||||||
} => handler.create_immed(&*params, buffer_id, width, height, format, flags),
|
} => handler.create_immed(&*params, buffer_id, width, height, format, flags, ddata),
|
||||||
_ => {}
|
_ => {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -253,10 +145,10 @@ where
|
||||||
|
|
||||||
// send the supported formats
|
// send the supported formats
|
||||||
for f in &*formats {
|
for f in &*formats {
|
||||||
dmabuf.format(f.format as u32);
|
dmabuf.format(f.code as u32);
|
||||||
if version >= 3 {
|
if version >= 3 {
|
||||||
dmabuf.modifier(
|
dmabuf.modifier(
|
||||||
f.format as u32,
|
f.code as u32,
|
||||||
(Into::<u64>::into(f.modifier) >> 32) as u32,
|
(Into::<u64>::into(f.modifier) >> 32) as u32,
|
||||||
Into::<u64>::into(f.modifier) as u32,
|
Into::<u64>::into(f.modifier) as u32,
|
||||||
);
|
);
|
||||||
|
@ -267,7 +159,7 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ParamsHandler<H: DmabufHandler> {
|
struct ParamsHandler<H: for<'a> FnMut(&Dmabuf, DispatchData<'a>) -> bool + 'static> {
|
||||||
pending_planes: Vec<Plane>,
|
pending_planes: Vec<Plane>,
|
||||||
max_planes: u32,
|
max_planes: u32,
|
||||||
used: bool,
|
used: bool,
|
||||||
|
@ -276,7 +168,10 @@ struct ParamsHandler<H: DmabufHandler> {
|
||||||
log: ::slog::Logger,
|
log: ::slog::Logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H: DmabufHandler> ParamsHandler<H> {
|
impl<H> ParamsHandler<H>
|
||||||
|
where
|
||||||
|
H: for<'a> FnMut(&Dmabuf, DispatchData<'a>) -> bool + 'static
|
||||||
|
{
|
||||||
fn add(
|
fn add(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: &BufferParams,
|
params: &BufferParams,
|
||||||
|
@ -314,15 +209,15 @@ impl<H: DmabufHandler> ParamsHandler<H> {
|
||||||
}
|
}
|
||||||
// all checks passed, store the plane
|
// all checks passed, store the plane
|
||||||
self.pending_planes.push(Plane {
|
self.pending_planes.push(Plane {
|
||||||
fd,
|
fd: Some(fd),
|
||||||
plane_idx,
|
plane_idx,
|
||||||
offset,
|
offset,
|
||||||
stride,
|
stride,
|
||||||
modifier,
|
modifier: Modifier::from(modifier),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(&mut self, params: &BufferParams, width: i32, height: i32, format: u32, flags: u32) {
|
fn create<'a>(&mut self, params: &BufferParams, width: i32, height: i32, format: u32, flags: u32, ddata: DispatchData<'a>) {
|
||||||
// Cannot reuse a params:
|
// Cannot reuse a params:
|
||||||
if self.used {
|
if self.used {
|
||||||
params.as_ref().post_error(
|
params.as_ref().post_error(
|
||||||
|
@ -332,6 +227,18 @@ impl<H: DmabufHandler> ParamsHandler<H> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.used = true;
|
self.used = true;
|
||||||
|
|
||||||
|
let format = match Fourcc::try_from(format) {
|
||||||
|
Ok(format) => format,
|
||||||
|
Err(_) => {
|
||||||
|
params.as_ref().post_error(
|
||||||
|
ParamError::InvalidFormat as u32,
|
||||||
|
format!("Format {:x} is not supported", format),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if !buffer_basic_checks(
|
if !buffer_basic_checks(
|
||||||
&self.formats,
|
&self.formats,
|
||||||
&self.pending_planes,
|
&self.pending_planes,
|
||||||
|
@ -343,23 +250,46 @@ impl<H: DmabufHandler> ParamsHandler<H> {
|
||||||
trace!(self.log, "Killing client providing bogus dmabuf buffer params.");
|
trace!(self.log, "Killing client providing bogus dmabuf buffer params.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let info = BufferInfo {
|
|
||||||
planes: ::std::mem::replace(&mut self.pending_planes, Vec::new()),
|
let mut buf = Dmabuf::new(
|
||||||
width,
|
width as u32,
|
||||||
height,
|
height as u32,
|
||||||
format,
|
format,
|
||||||
flags: BufferFlags::from_bits_truncate(flags),
|
DmabufFlags::from_bits_truncate(flags),
|
||||||
|
);
|
||||||
|
let planes = ::std::mem::replace(&mut self.pending_planes, Vec::new());
|
||||||
|
for (i, plane) in planes.into_iter().enumerate() {
|
||||||
|
let offset = plane.offset;
|
||||||
|
let stride = plane.stride;
|
||||||
|
let modi = plane.modifier;
|
||||||
|
buf.add_plane(plane.into_raw_fd(), i as u32, offset, stride, modi);
|
||||||
|
}
|
||||||
|
let dmabuf = match buf.build() {
|
||||||
|
Some(buf) => buf,
|
||||||
|
None => {
|
||||||
|
params.as_ref().post_error(
|
||||||
|
ParamError::Incomplete as u32,
|
||||||
|
format!("Provided buffer is incomplete, it has zero planes"),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut handler = self.handler.borrow_mut();
|
let mut handler = self.handler.borrow_mut();
|
||||||
if let Ok(data) = handler.validate_dmabuf(info) {
|
if handler(&dmabuf, ddata) {
|
||||||
if let Some(buffer) = params
|
if let Some(buffer) = params
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.client()
|
.client()
|
||||||
.and_then(|c| c.create_resource::<wl_buffer::WlBuffer>(1))
|
.and_then(|c| c.create_resource::<wl_buffer::WlBuffer>(1))
|
||||||
{
|
{
|
||||||
let buffer = handler.create_buffer(data, buffer);
|
buffer.as_ref().user_data().set_threadsafe(|| dmabuf);
|
||||||
trace!(self.log, "Creating a new validated dma wl_buffer.");
|
buffer.quick_assign(|_, _, _| {});
|
||||||
params.created(&buffer);
|
|
||||||
|
trace!(self.log, "Created a new validated dma wl_buffer.");
|
||||||
|
params.created(&buffer);
|
||||||
|
} else {
|
||||||
|
trace!(self.log, "Failed to create a wl_buffer");
|
||||||
|
params.failed();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
trace!(self.log, "Refusing creation of an invalid dma wl_buffer.");
|
trace!(self.log, "Refusing creation of an invalid dma wl_buffer.");
|
||||||
|
@ -367,14 +297,15 @@ impl<H: DmabufHandler> ParamsHandler<H> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_immed(
|
fn create_immed<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: &BufferParams,
|
params: &BufferParams,
|
||||||
buffer_id: Main<wl_buffer::WlBuffer>,
|
buffer: Main<wl_buffer::WlBuffer>,
|
||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
format: u32,
|
format: u32,
|
||||||
flags: u32,
|
flags: u32,
|
||||||
|
ddata: DispatchData<'a>,
|
||||||
) {
|
) {
|
||||||
// Cannot reuse a params:
|
// Cannot reuse a params:
|
||||||
if self.used {
|
if self.used {
|
||||||
|
@ -385,6 +316,18 @@ impl<H: DmabufHandler> ParamsHandler<H> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.used = true;
|
self.used = true;
|
||||||
|
|
||||||
|
let format = match Fourcc::try_from(format) {
|
||||||
|
Ok(format) => format,
|
||||||
|
Err(_) => {
|
||||||
|
params.as_ref().post_error(
|
||||||
|
ParamError::InvalidFormat as u32,
|
||||||
|
format!("Format {:x} is not supported", format),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if !buffer_basic_checks(
|
if !buffer_basic_checks(
|
||||||
&self.formats,
|
&self.formats,
|
||||||
&self.pending_planes,
|
&self.pending_planes,
|
||||||
|
@ -396,17 +339,36 @@ impl<H: DmabufHandler> ParamsHandler<H> {
|
||||||
trace!(self.log, "Killing client providing bogus dmabuf buffer params.");
|
trace!(self.log, "Killing client providing bogus dmabuf buffer params.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let info = BufferInfo {
|
|
||||||
planes: ::std::mem::replace(&mut self.pending_planes, Vec::new()),
|
let mut buf = Dmabuf::new(
|
||||||
width,
|
width as u32,
|
||||||
height,
|
height as u32,
|
||||||
format,
|
format,
|
||||||
flags: BufferFlags::from_bits_truncate(flags),
|
DmabufFlags::from_bits_truncate(flags),
|
||||||
|
);
|
||||||
|
let planes = ::std::mem::replace(&mut self.pending_planes, Vec::new());
|
||||||
|
for (i, plane) in planes.into_iter().enumerate() {
|
||||||
|
let offset = plane.offset;
|
||||||
|
let stride = plane.stride;
|
||||||
|
let modi = plane.modifier;
|
||||||
|
buf.add_plane(plane.into_raw_fd(), i as u32, offset, stride, modi);
|
||||||
|
}
|
||||||
|
let dmabuf = match buf.build() {
|
||||||
|
Some(buf) => buf,
|
||||||
|
None => {
|
||||||
|
params.as_ref().post_error(
|
||||||
|
ParamError::Incomplete as u32,
|
||||||
|
format!("Provided buffer is incomplete, it has zero planes"),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut handler = self.handler.borrow_mut();
|
let mut handler = self.handler.borrow_mut();
|
||||||
if let Ok(data) = handler.validate_dmabuf(info) {
|
if handler(&dmabuf, ddata) {
|
||||||
trace!(self.log, "Creating a new validated immediate dma wl_buffer.");
|
buffer.as_ref().user_data().set_threadsafe(|| dmabuf);
|
||||||
handler.create_buffer(data, buffer_id);
|
buffer.quick_assign(|_, _, _| {});
|
||||||
|
trace!(self.log, "Created a new validated dma wl_buffer.");
|
||||||
} else {
|
} else {
|
||||||
trace!(
|
trace!(
|
||||||
self.log,
|
self.log,
|
||||||
|
@ -417,6 +379,8 @@ impl<H: DmabufHandler> ParamsHandler<H> {
|
||||||
"create_immed resulted in an invalid buffer.".into(),
|
"create_immed resulted in an invalid buffer.".into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,34 +388,22 @@ fn buffer_basic_checks(
|
||||||
formats: &[Format],
|
formats: &[Format],
|
||||||
pending_planes: &[Plane],
|
pending_planes: &[Plane],
|
||||||
params: &BufferParams,
|
params: &BufferParams,
|
||||||
format: u32,
|
format: Fourcc,
|
||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// protocol_checks:
|
// protocol_checks:
|
||||||
// This must be a known format
|
// This must be a known format
|
||||||
let format = match formats.iter().find(|f| f.format as u32 == format) {
|
let _format = match formats.iter().find(|f| f.code == format) {
|
||||||
Some(f) => f,
|
Some(f) => f,
|
||||||
None => {
|
None => {
|
||||||
params.as_ref().post_error(
|
params.as_ref().post_error(
|
||||||
ParamError::InvalidFormat as u32,
|
ParamError::InvalidFormat as u32,
|
||||||
format!("Format {:x} is not supported.", format),
|
format!("Format {:?}/{:x} is not supported.", format, format as u32),
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// The number of planes set must match what the format expects
|
|
||||||
let max_plane_set = pending_planes.iter().map(|d| d.plane_idx + 1).max().unwrap_or(0);
|
|
||||||
if max_plane_set != format.plane_count || pending_planes.len() < format.plane_count as usize {
|
|
||||||
params.as_ref().post_error(
|
|
||||||
ParamError::Incomplete as u32,
|
|
||||||
format!(
|
|
||||||
"Format {:?} requires {} planes but got {}.",
|
|
||||||
format.format, format.plane_count, max_plane_set
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Width and height must be positivie
|
// Width and height must be positivie
|
||||||
if width < 1 || height < 1 {
|
if width < 1 || height < 1 {
|
||||||
params.as_ref().post_error(
|
params.as_ref().post_error(
|
||||||
|
@ -477,9 +429,9 @@ fn buffer_basic_checks(
|
||||||
}
|
}
|
||||||
Some(e) => e,
|
Some(e) => e,
|
||||||
};
|
};
|
||||||
if let Ok(size) = ::nix::unistd::lseek(plane.fd, 0, ::nix::unistd::Whence::SeekEnd) {
|
if let Ok(size) = ::nix::unistd::lseek(plane.fd.unwrap(), 0, ::nix::unistd::Whence::SeekEnd) {
|
||||||
// reset the seek point
|
// reset the seek point
|
||||||
let _ = ::nix::unistd::lseek(plane.fd, 0, ::nix::unistd::Whence::SeekSet);
|
let _ = ::nix::unistd::lseek(plane.fd.unwrap(), 0, ::nix::unistd::Whence::SeekSet);
|
||||||
if plane.offset as i64 > size {
|
if plane.offset as i64 > size {
|
||||||
params.as_ref().post_error(
|
params.as_ref().post_error(
|
||||||
ParamError::OutOfBounds as u32,
|
ParamError::OutOfBounds as u32,
|
||||||
|
|
|
@ -16,7 +16,6 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
pub mod compositor;
|
pub mod compositor;
|
||||||
pub mod data_device;
|
pub mod data_device;
|
||||||
#[cfg(feature = "backend_drm")]
|
|
||||||
pub mod dmabuf;
|
pub mod dmabuf;
|
||||||
pub mod explicit_synchronization;
|
pub mod explicit_synchronization;
|
||||||
pub mod output;
|
pub mod output;
|
||||||
|
|
Loading…
Reference in New Issue