use std::collections::HashSet;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
use drm::buffer::PlanarBuffer;
use drm::control::{connector, crtc, framebuffer, plane, Device, Mode};
use gbm::{BufferObject, Device as GbmDevice};
use crate::backend::allocator::{
dmabuf::{AsDmabuf, Dmabuf},
gbm::GbmConvertError,
Format, Fourcc, Modifier, Slot, Swapchain,
};
use crate::backend::drm::{device::DevPath, surface::DrmSurfaceInternal, DrmError, DrmSurface};
use crate::backend::SwapBuffersError;
use slog::{debug, error, o, trace, warn};
pub struct GbmBufferedSurface<D: AsRawFd + 'static> {
buffers: Buffers<D>,
swapchain: Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)>,
drm: Arc<DrmSurface<D>>,
}
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>
where
D: AsRawFd + 'static,
{
#[allow(clippy::type_complexity)]
pub fn new<L>(
drm: DrmSurface<D>,
allocator: GbmDevice<D>,
mut renderer_formats: HashSet<Format>,
log: L,
) -> Result<GbmBufferedSurface<D>, Error>
where
L: Into<Option<::slog::Logger>>,
{
let code = Fourcc::Argb8888;
let logger = crate::slog_or_fallback(log).new(o!("backend" => "drm_render"));
let plane_formats = drm
.supported_formats(drm.plane())?
.iter()
.filter(|fmt| fmt.code == code)
.cloned()
.collect::<HashSet<_>>();
renderer_formats.retain(|fmt| fmt.code == code);
trace!(logger, "Plane formats: {:?}", plane_formats);
trace!(logger, "Renderer formats: {:?}", renderer_formats);
debug!(
logger,
"Remaining intersected formats: {:?}",
plane_formats
.intersection(&renderer_formats)
.collect::<HashSet<_>>()
);
if plane_formats.is_empty() {
return Err(Error::NoSupportedPlaneFormat);
} else if renderer_formats.is_empty() {
return Err(Error::NoSupportedRendererFormat);
}
let formats = {
if (plane_formats.len() == 1
&& plane_formats.iter().next().unwrap().modifier == Modifier::Invalid
&& renderer_formats.iter().all(|x| x.modifier != Modifier::Invalid)
&& renderer_formats.iter().any(|x| x.modifier == Modifier::Linear))
|| (renderer_formats.len() == 1
&& renderer_formats.iter().next().unwrap().modifier == Modifier::Invalid
&& plane_formats.iter().all(|x| x.modifier != Modifier::Invalid)
&& plane_formats.iter().any(|x| x.modifier == Modifier::Linear))
{
vec![Format {
code,
modifier: Modifier::Invalid,
}]
} else {
plane_formats
.intersection(&renderer_formats)
.cloned()
.collect::<Vec<_>>()
}
};
debug!(logger, "Testing Formats: {:?}", formats);
let drm = Arc::new(drm);
let modifiers = formats.iter().map(|x| x.modifier).collect::<Vec<_>>();
let mode = drm.pending_mode();
let mut swapchain: Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)> = Swapchain::new(
allocator,
mode.size().0 as u32,
mode.size().1 as u32,
code,
modifiers,
);
let buffer = swapchain.acquire()?.unwrap();
let format = Format {
code,
modifier: buffer.modifier().unwrap(), };
let fb = attach_framebuffer(&drm, &*buffer)?;
let dmabuf = buffer.export()?;
let handle = fb.fb;
*buffer.userdata() = Some((dmabuf, fb));
match drm.test_buffer(handle, &mode, true) {
Ok(_) => {
debug!(logger, "Choosen format: {:?}", format);
let buffers = Buffers::new(drm.clone(), buffer);
Ok(GbmBufferedSurface {
buffers,
swapchain,
drm,
})
}
Err(err) => {
warn!(
logger,
"Mode-setting failed with automatically selected buffer format {:?}: {}", format, err
);
Err(err).map_err(Into::into)
}
}
}
pub fn next_buffer(&mut self) -> Result<Dmabuf, Error> {
self.buffers.next(&mut self.swapchain)
}
pub fn queue_buffer(&mut self) -> Result<(), Error> {
self.buffers.queue()
}
pub fn frame_submitted(&mut self) -> Result<(), Error> {
self.buffers.submitted()
}
pub fn crtc(&self) -> crtc::Handle {
self.drm.crtc()
}
pub fn plane(&self) -> plane::Handle {
self.drm.plane()
}
pub fn current_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
self.drm.current_connectors()
}
pub fn pending_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
self.drm.pending_connectors()
}
pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> {
self.drm.add_connector(connector).map_err(Error::DrmError)
}
pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> {
self.drm.remove_connector(connector).map_err(Error::DrmError)
}
pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> {
self.drm.set_connectors(connectors).map_err(Error::DrmError)
}
pub fn current_mode(&self) -> Mode {
self.drm.current_mode()
}
pub fn pending_mode(&self) -> Mode {
self.drm.pending_mode()
}
pub fn use_mode(&self, mode: Mode) -> Result<(), Error> {
self.drm.use_mode(mode).map_err(Error::DrmError)
}
}
#[derive(Debug)]
struct FbHandle<D: AsRawFd + 'static> {
drm: Arc<DrmSurface<D>>,
fb: framebuffer::Handle,
}
impl<A: AsRawFd + 'static> Drop for FbHandle<A> {
fn drop(&mut self) {
let _ = self.drm.destroy_framebuffer(self.fb);
}
}
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>>,
}
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> {
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>
where
A: AsRawFd + 'static,
{
let modifier = match bo.modifier().unwrap() {
Modifier::Invalid => None,
x => Some(x),
};
let logger = match &*(*drm).internal {
DrmSurfaceInternal::Atomic(surf) => surf.logger.clone(),
DrmSurfaceInternal::Legacy(surf) => surf.logger.clone(),
};
let fb = match if modifier.is_some() {
let num = bo.plane_count().unwrap();
let modifiers = [
modifier,
if num > 1 { modifier } else { None },
if num > 2 { modifier } else { None },
if num > 3 { modifier } else { None },
];
drm.add_planar_framebuffer(bo, &modifiers, drm_ffi::DRM_MODE_FB_MODIFIERS)
} else {
drm.add_planar_framebuffer(bo, &[None, None, None, None], 0)
} {
Ok(fb) => fb,
Err(source) => {
if drm::buffer::Buffer::format(bo) != Fourcc::Argb8888 || bo.handles()[1].is_some() {
return Err(Error::DrmError(DrmError::Access {
errmsg: "Failed to add framebuffer",
dev: drm.dev_path(),
source,
}));
}
debug!(logger, "Failed to add framebuffer, trying legacy method");
drm.add_framebuffer(bo, 32, 32)
.map_err(|source| DrmError::Access {
errmsg: "Failed to add framebuffer",
dev: drm.dev_path(),
source,
})?
}
};
Ok(FbHandle { drm: drm.clone(), fb })
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("No supported plane buffer format found")]
NoSupportedPlaneFormat,
#[error("No supported renderer buffer format found")]
NoSupportedRendererFormat,
#[error("Supported plane and renderer buffer formats are incompatible")]
FormatsNotCompatible,
#[error("Failed to allocate a new buffer")]
NoFreeSlotsError,
#[error("Failed to render test frame")]
InitialRenderingError,
#[error("The underlying drm surface encounted an error: {0}")]
DrmError(#[from] DrmError),
#[error("The underlying gbm device encounted an error: {0}")]
GbmError(#[from] std::io::Error),
#[error("The allocated buffer could not be exported as a dmabuf: {0}")]
AsDmabufError(#[from] GbmConvertError),
}
impl From<Error> for SwapBuffersError {
fn from(err: Error) -> SwapBuffersError {
match err {
x @ Error::NoSupportedPlaneFormat
| x @ Error::NoSupportedRendererFormat
| x @ Error::FormatsNotCompatible
| x @ Error::InitialRenderingError => SwapBuffersError::ContextLost(Box::new(x)),
x @ Error::NoFreeSlotsError => SwapBuffersError::TemporaryFailure(Box::new(x)),
Error::DrmError(err) => err.into(),
Error::GbmError(err) => SwapBuffersError::ContextLost(Box::new(err)),
Error::AsDmabufError(err) => SwapBuffersError::ContextLost(Box::new(err)),
}
}
}