use std::os::unix::io::AsFd;
use thiserror::Error;
use drm::{
buffer::PlanarBuffer,
control::{framebuffer, Device, FbCmd2Flags},
};
use drm_fourcc::DrmModifier;
use tracing::{trace, warn};
#[cfg(feature = "wayland_frontend")]
use wayland_server::protocol::wl_buffer::WlBuffer;
#[cfg(feature = "wayland_frontend")]
use crate::backend::allocator::Buffer;
use crate::backend::{
allocator::{
dmabuf::Dmabuf,
format::{get_bpp, get_depth, get_opaque},
gbm::GbmBuffer,
Fourcc,
},
drm::DrmDeviceFd,
};
use crate::utils::DevPath;
use super::{error::AccessError, warn_legacy_fb_export, Framebuffer};
#[derive(Debug)]
pub struct GbmFramebuffer {
fb: framebuffer::Handle,
format: drm_fourcc::DrmFormat,
drm: DrmDeviceFd,
}
impl Drop for GbmFramebuffer {
#[inline]
fn drop(&mut self) {
trace!(fb = ?self.fb, "destroying framebuffer");
if let Err(err) = self.drm.destroy_framebuffer(self.fb) {
warn!(fb = ?self.fb, ?err, "failed to destroy framebuffer");
}
}
}
impl AsRef<framebuffer::Handle> for GbmFramebuffer {
#[inline]
fn as_ref(&self) -> &framebuffer::Handle {
&self.fb
}
}
impl Framebuffer for GbmFramebuffer {
#[inline]
fn format(&self) -> drm_fourcc::DrmFormat {
self.format
}
}
#[cfg(feature = "wayland_frontend")]
#[profiling::function]
pub fn framebuffer_from_wayland_buffer<A: AsFd + 'static>(
drm: &DrmDeviceFd,
gbm: &gbm::Device<A>,
buffer: &WlBuffer,
use_opaque: bool,
) -> Result<Option<GbmFramebuffer>, Error> {
if let Ok(dmabuf) = crate::wayland::dmabuf::get_dmabuf(buffer) {
if dmabuf.format().modifier != DrmModifier::Invalid {
return Ok(Some(framebuffer_from_dmabuf(
drm, gbm, dmabuf, use_opaque, false,
)?));
}
}
#[cfg(all(feature = "backend_egl", feature = "use_system_lib"))]
if matches!(
crate::backend::renderer::buffer_type(buffer),
Some(crate::backend::renderer::BufferType::Egl)
) {
let bo = gbm
.import_buffer_object_from_wayland::<()>(buffer, gbm::BufferObjectFlags::SCANOUT)
.map(|bo| GbmBuffer::from_bo(bo, true))
.map_err(Error::Import)?;
let (fb, format) = framebuffer_from_bo_internal(
drm,
BufferObjectInternal {
bo: &bo,
offsets: None,
pitches: None,
},
use_opaque,
true,
)
.map_err(Error::Drm)?;
return Ok(Some(GbmFramebuffer {
fb,
format,
drm: drm.clone(),
}));
}
Ok(None)
}
#[derive(Error, Debug)]
pub enum Error {
#[error("failed to import the dmabuf to gbm")]
Import(std::io::Error),
#[error("failed to add a framebuffer for the bo")]
Drm(AccessError),
}
#[profiling::function]
pub fn framebuffer_from_dmabuf<A: AsFd + 'static>(
drm: &DrmDeviceFd,
gbm: &gbm::Device<A>,
dmabuf: &Dmabuf,
use_opaque: bool,
allow_legacy: bool,
) -> Result<GbmFramebuffer, Error> {
let bo: GbmBuffer = dmabuf
.import_to(gbm, gbm::BufferObjectFlags::SCANOUT)
.map_err(Error::Import)?;
let mut offsets: [u32; 4] = [0; 4];
let mut pitches: [u32; 4] = [0; 4];
for (index, offset) in dmabuf.offsets().enumerate() {
offsets[index] = offset;
}
for (index, stride) in dmabuf.strides().enumerate() {
pitches[index] = stride;
}
framebuffer_from_bo_internal(
drm,
BufferObjectInternal {
bo: &bo,
offsets: Some(offsets),
pitches: Some(pitches),
},
use_opaque,
allow_legacy,
)
.map_err(Error::Drm)
.map(|(fb, format)| GbmFramebuffer {
fb,
format,
drm: drm.clone(),
})
}
#[profiling::function]
pub fn framebuffer_from_bo(
drm: &DrmDeviceFd,
bo: &GbmBuffer,
use_opaque: bool,
) -> Result<GbmFramebuffer, AccessError> {
framebuffer_from_bo_internal(
drm,
BufferObjectInternal {
bo,
offsets: None,
pitches: None,
},
use_opaque,
true,
)
.map(|(fb, format)| GbmFramebuffer {
fb,
format,
drm: drm.clone(),
})
}
struct BufferObjectInternal<'a> {
bo: &'a GbmBuffer,
pitches: Option<[u32; 4]>,
offsets: Option<[u32; 4]>,
}
impl std::ops::Deref for BufferObjectInternal<'_> {
type Target = GbmBuffer;
#[inline]
fn deref(&self) -> &Self::Target {
self.bo
}
}
impl PlanarBuffer for BufferObjectInternal<'_> {
#[inline]
fn size(&self) -> (u32, u32) {
PlanarBuffer::size(self.bo)
}
#[inline]
fn format(&self) -> drm_fourcc::DrmFourcc {
PlanarBuffer::format(self.bo)
}
#[inline]
fn modifier(&self) -> Option<DrmModifier> {
PlanarBuffer::modifier(self.bo)
}
#[inline]
fn pitches(&self) -> [u32; 4] {
self.pitches.unwrap_or_else(|| PlanarBuffer::pitches(self.bo))
}
#[inline]
fn handles(&self) -> [Option<drm::buffer::Handle>; 4] {
PlanarBuffer::handles(self.bo)
}
#[inline]
fn offsets(&self) -> [u32; 4] {
self.offsets.unwrap_or_else(|| PlanarBuffer::offsets(self.bo))
}
}
struct OpaqueBufferWrapper<'a, B>(&'a B);
impl<B> PlanarBuffer for OpaqueBufferWrapper<'_, B>
where
B: PlanarBuffer,
{
#[inline]
fn size(&self) -> (u32, u32) {
self.0.size()
}
#[inline]
fn format(&self) -> Fourcc {
let fmt = self.0.format();
get_opaque(fmt).unwrap_or(fmt)
}
#[inline]
fn modifier(&self) -> Option<DrmModifier> {
self.0.modifier()
}
#[inline]
fn pitches(&self) -> [u32; 4] {
self.0.pitches()
}
#[inline]
fn handles(&self) -> [Option<drm::buffer::Handle>; 4] {
self.0.handles()
}
#[inline]
fn offsets(&self) -> [u32; 4] {
self.0.offsets()
}
}
#[profiling::function]
#[inline]
fn framebuffer_from_bo_internal<D>(
drm: &D,
bo: BufferObjectInternal<'_>,
use_opaque: bool,
allow_legacy: bool,
) -> Result<(framebuffer::Handle, drm_fourcc::DrmFormat), AccessError>
where
D: drm::control::Device + DevPath,
{
let modifier = bo.modifier();
let flags = if bo.modifier().is_some() {
FbCmd2Flags::MODIFIERS
} else {
FbCmd2Flags::empty()
};
let ret = if use_opaque {
let opaque_wrapper = OpaqueBufferWrapper(&bo);
drm.add_planar_framebuffer(&opaque_wrapper, flags).map(|fb| {
(
fb,
drm_fourcc::DrmFormat {
code: opaque_wrapper.format(),
modifier: modifier.unwrap_or(DrmModifier::Invalid),
},
)
})
} else {
drm.add_planar_framebuffer(&bo, flags).map(|fb| {
(
fb,
drm_fourcc::DrmFormat {
code: bo.format(),
modifier: modifier.unwrap_or(DrmModifier::Invalid),
},
)
})
};
let (fb, format) = match ret {
Ok(fb) => fb,
Err(source) => {
if !allow_legacy {
return Err(AccessError {
errmsg: "Failed to add framebuffer",
dev: drm.dev_path(),
source,
});
}
warn_legacy_fb_export();
if bo.plane_count() > 1 {
return Err(AccessError {
errmsg: "Failed to add framebuffer",
dev: drm.dev_path(),
source,
});
}
let fourcc = bo.format();
let (depth, bpp) = get_depth(fourcc)
.and_then(|d| get_bpp(fourcc).map(|b| (d, b)))
.ok_or_else(|| AccessError {
errmsg: "Unknown format for legacy framebuffer",
dev: drm.dev_path(),
source,
})?;
let fb = drm
.add_framebuffer(&*bo, depth as u32, bpp as u32)
.map_err(|source| AccessError {
errmsg: "Failed to add framebuffer",
dev: drm.dev_path(),
source,
})?;
(
fb,
drm_fourcc::DrmFormat {
code: fourcc,
modifier: drm_fourcc::DrmModifier::Invalid,
},
)
}
};
Ok((fb, format))
}