🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
use std::os::unix::io::AsFd;
use std::sync::Arc;
use drm::control::{connector, crtc, plane, Mode};
use drm::{Device, DriverCapability};
use indexmap::IndexSet;
use crate::backend::allocator::dmabuf::{AsDmabuf, Dmabuf};
use crate::backend::allocator::format::get_opaque;
use crate::backend::allocator::gbm::{GbmBuffer, GbmConvertError};
use crate::backend::allocator::{Allocator, Format, Fourcc, Modifier, Slot, Swapchain};
use crate::backend::drm::error::AccessError;
use crate::backend::drm::gbm::{framebuffer_from_bo, GbmFramebuffer};
use crate::backend::drm::{plane_has_property, DrmError, DrmSurface};
use crate::backend::renderer::sync::SyncPoint;
use crate::backend::SwapBuffersError;
use crate::utils::{DevPath, Physical, Rectangle, Transform};
use tracing::{debug, error, info_span, instrument, trace, warn};
use super::{PlaneConfig, PlaneDamageClips, PlaneState, VrrSupport};
#[derive(Debug)]
struct QueuedFb<U> {
slot: Slot<GbmBuffer>,
sync: Option<SyncPoint>,
damage: Option<Vec<Rectangle<i32, Physical>>>,
user_data: U,
}
/// Simplified abstraction of a swapchain for gbm-buffers displayed on a [`DrmSurface`].
#[derive(Debug)]
pub struct GbmBufferedSurface<A: Allocator<Buffer = GbmBuffer> + 'static, U> {
current_fb: Slot<GbmBuffer>,
pending_fb: Option<(Slot<GbmBuffer>, U)>,
queued_fb: Option<QueuedFb<U>>,
next_fb: Option<Slot<GbmBuffer>>,
swapchain: Swapchain<A>,
drm: Arc<DrmSurface>,
is_opaque: bool,
supports_fencing: bool,
span: tracing::Span,
}
impl<A, U> GbmBufferedSurface<A, U>
where
A: Allocator<Buffer = GbmBuffer>,
A::Error: std::error::Error + Send + Sync,
{
/// Create a new `GbmBufferedSurface` from a given compatible combination
/// of a surface, an allocator and renderer formats.
///
/// The provided color_formats are tested in order until a working configuration is found.
///
/// To successfully call this function, you need to have a renderer,
/// which can render into a Dmabuf, and a gbm allocator that can produce
/// buffers of a supported format for rendering.
pub fn new(
drm: DrmSurface,
mut allocator: A,
color_formats: &[Fourcc],
renderer_formats: impl IntoIterator<Item = Format>,
) -> Result<GbmBufferedSurface<A, U>, Error<A::Error>> {
let span = info_span!(parent: drm.span(), "drm_gbm");
let _guard = span.enter();
let mut error = None;
let drm = Arc::new(drm);
let renderer_formats = renderer_formats.into_iter().collect::<Vec<_>>();
for format in color_formats {
debug!("Testing color format: {}", format);
match Self::new_internal(drm.clone(), allocator, renderer_formats.clone(), *format) {
Ok((current_fb, swapchain, is_opaque)) => {
drop(_guard);
let supports_fencing = !drm.is_legacy()
&& drm
.get_driver_capability(DriverCapability::SyncObj)
.map(|val| val != 0)
.map_err(|err| {
Error::DrmError(DrmError::Access(AccessError {
errmsg: "Failed to query driver capability",
dev: drm.dev_path(),
source: err,
}))
})?
&& plane_has_property(&*drm, drm.plane(), "IN_FENCE_FD")?;
return Ok(GbmBufferedSurface {
current_fb,
pending_fb: None,
queued_fb: None,
next_fb: None,
swapchain,
drm,
is_opaque,
supports_fencing,
span,
});
}
Err((alloc, err)) => {
warn!("Preferred format {} not available: {:?}", format, err);
allocator = alloc;
error = Some(err);
}
}
}
Err(error.unwrap())
}
#[allow(clippy::type_complexity)]
fn new_internal(
drm: Arc<DrmSurface>,
allocator: A,
mut renderer_formats: Vec<Format>,
code: Fourcc,
) -> Result<(Slot<GbmBuffer>, Swapchain<A>, bool), (A, Error<A::Error>)> {
// select a format
let mut plane_formats = drm.plane_info().formats.iter().copied().collect::<IndexSet<_>>();
let opaque_code = get_opaque(code).unwrap_or(code);
if !plane_formats
.iter()
.any(|fmt| fmt.code == code || fmt.code == opaque_code)
{
return Err((allocator, Error::NoSupportedPlaneFormat));
}
plane_formats.retain(|fmt| fmt.code == code || fmt.code == opaque_code);
renderer_formats.retain(|fmt| fmt.code == code);
let plane_modifiers = plane_formats
.iter()
.map(|fmt| fmt.modifier)
.collect::<IndexSet<_>>();
let renderer_modifiers = renderer_formats
.iter()
.map(|fmt| fmt.modifier)
.collect::<IndexSet<_>>();
trace!("Plane formats: {:?}", plane_formats);
trace!("Renderer formats: {:?}", renderer_formats);
debug!(
"Remaining intersected modifiers: {:?}",
plane_modifiers
.intersection(&renderer_modifiers)
.collect::<IndexSet<_>>()
);
if plane_formats.is_empty() {
return Err((allocator, Error::NoSupportedPlaneFormat));
} else if renderer_formats.is_empty() {
return Err((allocator, Error::NoSupportedRendererFormat));
}
let formats = {
// Special case: if a format supports explicit LINEAR (but no implicit Modifiers)
// and the other doesn't support any modifier, force Implicit.
// This should at least result in a working pipeline possibly with a linear buffer,
// but we cannot be sure.
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.first().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_modifiers
.intersection(&renderer_modifiers)
.cloned()
.map(|modifier| Format { code, modifier })
.collect::<Vec<_>>()
}
};
debug!("Testing Formats: {:?}", formats);
let modifiers = formats.iter().map(|x| x.modifier).collect::<Vec<_>>();
let mode = drm.pending_mode();
let mut swapchain: Swapchain<A> = Swapchain::new(
allocator,
mode.size().0 as u32,
mode.size().1 as u32,
code,
modifiers,
);
// Test format
let buffer = match swapchain.acquire() {
Ok(buffer) => buffer.unwrap(),
Err(err) => return Err((swapchain.allocator, Error::GbmError(err))),
};
let format = Format {
code,
modifier: buffer.modifier(), // no guarantee
// that this is stable across allocations, but
// we want to print that here for debugging proposes.
// It has no further use.
};
let use_opaque = !plane_formats.iter().any(|f| f.code == code);
let fb = match framebuffer_from_bo(drm.device_fd(), &buffer, use_opaque) {
Ok(fb) => fb,
Err(err) => return Err((swapchain.allocator, Error::DrmError(err.into()))),
};
match buffer.export() {
Ok(dmabuf) => dmabuf,
Err(err) => return Err((swapchain.allocator, err.into())),
};
buffer.userdata().insert_if_missing(|| fb);
let handle = buffer.userdata().get::<GbmFramebuffer>().unwrap();
let plane_state = PlaneState {
handle: drm.plane(),
config: Some(PlaneConfig {
src: Rectangle::from_size((mode.size().0 as i32, mode.size().1 as i32).into()).to_f64(),
dst: Rectangle::from_size((mode.size().0 as i32, mode.size().1 as i32).into()),
alpha: 1.0,
transform: Transform::Normal,
damage_clips: None,
fb: *handle.as_ref(),
fence: None,
}),
};
match drm.test_state([plane_state], true) {
Ok(_) => {
debug!("Choosen format: {:?}", format);
Ok((buffer, swapchain, use_opaque))
}
Err(err) => {
warn!(
"Mode-setting failed with automatically selected buffer format {:?}: {}",
format, err
);
Err((swapchain.allocator, err.into()))
}
}
}
/// Retrieves the next buffer to be rendered into and it's age.
///
/// *Note*: This function can be called multiple times and
/// will return the same buffer until it is queued (see [`GbmBufferedSurface::queue_buffer`]).
#[instrument(level = "trace", skip_all, parent = &self.span, err)]
#[profiling::function]
pub fn next_buffer(&mut self) -> Result<(Dmabuf, u8), Error<A::Error>> {
if !self.drm.is_active() {
return Err(Error::<A::Error>::DrmError(DrmError::DeviceInactive));
}
if self.next_fb.is_none() {
let slot = self
.swapchain
.acquire()
.map_err(Error::GbmError)?
.ok_or(Error::NoFreeSlotsError)?;
let maybe_buffer = slot.userdata().get::<GbmFramebuffer>();
if maybe_buffer.is_none() {
let fb = framebuffer_from_bo(self.drm.device_fd(), &slot, self.is_opaque)
.map_err(|err| Error::DrmError(err.into()))?;
slot.userdata().insert_if_missing(|| fb);
}
self.next_fb = Some(slot);
}
let slot = self.next_fb.as_ref().unwrap();
Ok((slot.export()?, slot.age()))
}
/// Queues the current buffer for rendering.
///
/// Returns [`Error::NoBuffer`] in case [`GbmBufferedSurface::next_buffer`] has not been called
/// prior to this function.
///
/// *Note*: This function needs to be followed up with [`GbmBufferedSurface::frame_submitted`]
/// when a vblank event is received, that denotes successful scanout of the buffer.
/// Otherwise the underlying swapchain will eventually run out of buffers.
///
/// `user_data` can be used to attach some data to a specific buffer and later retrieved with [`GbmBufferedSurface::frame_submitted`]
#[profiling::function]
pub fn queue_buffer(
&mut self,
sync: Option<SyncPoint>,
damage: Option<Vec<Rectangle<i32, Physical>>>,
user_data: U,
) -> Result<(), Error<A::Error>> {
if !self.drm.is_active() {
return Err(Error::<A::Error>::DrmError(DrmError::DeviceInactive));
}
let next_fb = self.next_fb.take().ok_or(Error::<A::Error>::NoBuffer)?;
self.swapchain.submitted(&next_fb);
self.queued_fb = Some(QueuedFb {
slot: next_fb,
sync,
damage,
user_data,
});
if self.pending_fb.is_none() {
self.submit()?;
}
Ok(())
}
/// Marks the current frame as submitted.
///
/// *Note*: Needs to be called, after the vblank event of the matching [`DrmDevice`](super::super::DrmDevice)
/// was received after calling [`GbmBufferedSurface::queue_buffer`] on this surface.
/// Otherwise the underlying swapchain will run out of buffers eventually.
///
/// Returns the user data that was stored with [`GbmBufferedSurface::queue_buffer`] if a buffer was pending, otherwise
/// `None` is returned.
#[profiling::function]
pub fn frame_submitted(&mut self) -> Result<Option<U>, Error<A::Error>> {
if let Some((mut pending, user_data)) = self.pending_fb.take() {
std::mem::swap(&mut pending, &mut self.current_fb);
if self.queued_fb.is_some() {
self.submit()?;
}
Ok(Some(user_data))
} else {
Ok(None)
}
}
#[profiling::function]
fn submit(&mut self) -> Result<(), Error<A::Error>> {
// yes it does not look like it, but both of these lines should be safe in all cases.
let QueuedFb {
slot,
sync,
damage,
user_data,
} = self.queued_fb.take().unwrap();
let handle = slot.userdata().get::<GbmFramebuffer>().unwrap();
let mode = self.drm.pending_mode();
let src = Rectangle::from_size((mode.size().0 as i32, mode.size().1 as i32).into()).to_f64();
let dst = Rectangle::from_size((mode.size().0 as i32, mode.size().1 as i32).into());
let damage_clips = damage.and_then(|damage| {
PlaneDamageClips::from_damage(self.drm.device_fd(), src, dst, damage)
.ok()
.flatten()
});
// Try to extract a native fence out of the supplied sync point if any
// If the sync point has no native fence or the surface does not support
// fencing force a wait
let fence = if let Some(sync) = sync {
if !self.supports_fencing {
// we probably don't want to fail to submit in this case, lets hope on implicit sync
let _ = sync.wait();
None
} else {
let fence = sync.export();
if fence.is_none() {
let _ = sync.wait();
}
fence
}
} else {
None
};
let plane_state = PlaneState {
handle: self.plane(),
config: Some(PlaneConfig {
src,
dst,
transform: Transform::Normal,
alpha: 1.0,
damage_clips: damage_clips.as_ref().map(|d| d.blob()),
fb: *handle.as_ref(),
fence: fence.as_ref().map(|fence| fence.as_fd()),
}),
};
let flip = if self.drm.commit_pending() {
self.drm.commit([plane_state], true)
} else {
self.drm.page_flip([plane_state], true)
};
if flip.is_ok() {
self.pending_fb = Some((slot, user_data));
}
flip.map_err(Error::DrmError)
}
/// Reset the underlying buffers
pub fn reset_buffers(&mut self) {
self.swapchain.reset_buffers()
}
/// Reset the age for all buffers.
///
/// This can be used to efficiently clear the damage history without having to
/// modify the damage for each surface.
pub fn reset_buffer_ages(&mut self) {
self.swapchain.reset_buffer_ages();
}
/// Returns the underlying [`crtc`](drm::control::crtc) of this surface
pub fn crtc(&self) -> crtc::Handle {
self.drm.crtc()
}
/// Returns the underlying [`plane`](drm::control::plane) of this surface
pub fn plane(&self) -> plane::Handle {
self.drm.plane()
}
/// Currently used [`connector`](drm::control::connector)s of this `Surface`
pub fn current_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
self.drm.current_connectors()
}
/// Returns the pending [`connector`](drm::control::connector)s
/// used for the next frame queued via [`queue_buffer`](GbmBufferedSurface::queue_buffer).
pub fn pending_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
self.drm.pending_connectors()
}
/// Tries to add a new [`connector`](drm::control::connector)
/// to be used after the next commit.
///
/// **Warning**: You need to make sure, that the connector is not used with another surface
/// or was properly removed via `remove_connector` + `commit` before adding it to another surface.
/// Behavior if failing to do so is undefined, but might result in rendering errors or the connector
/// getting removed from the other surface without updating it's internal state.
///
/// Fails if the `connector` is not compatible with the underlying [`crtc`](drm::control::crtc)
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
/// or is not compatible with the currently pending
/// [`Mode`](drm::control::Mode).
pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error<A::Error>> {
self.drm.add_connector(connector).map_err(Error::DrmError)
}
/// Tries to mark a [`connector`](drm::control::connector)
/// for removal on the next commit.
pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error<A::Error>> {
self.drm.remove_connector(connector).map_err(Error::DrmError)
}
/// Tries to replace the current connector set with the newly provided one on the next commit.
///
/// Fails if one new `connector` is not compatible with the underlying [`crtc`](drm::control::crtc)
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
/// or is not compatible with the currently pending
/// [`Mode`](drm::control::Mode).
pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error<A::Error>> {
self.drm.set_connectors(connectors).map_err(Error::DrmError)
}
/// Returns the currently active [`Mode`](drm::control::Mode)
/// of the underlying [`crtc`](drm::control::crtc)
pub fn current_mode(&self) -> Mode {
self.drm.current_mode()
}
/// Returns the currently pending [`Mode`](drm::control::Mode)
/// to be used after the next commit.
pub fn pending_mode(&self) -> Mode {
self.drm.pending_mode()
}
/// Tries to set a new [`Mode`](drm::control::Mode)
/// to be used after the next commit.
///
/// Fails if the mode is not compatible with the underlying
/// [`crtc`](drm::control::crtc) or any of the
/// pending [`connector`](drm::control::connector)s.
pub fn use_mode(&mut self, mode: Mode) -> Result<(), Error<A::Error>> {
self.drm.use_mode(mode).map_err(Error::DrmError)?;
let (w, h) = mode.size();
self.swapchain.resize(w as _, h as _);
Ok(())
}
/// Returns if Variable Refresh Rate is advertised as supported by the given connector.
///
/// See [`DrmSurface::vrr_supported`] for more details.
pub fn vrr_supported(&self, conn: connector::Handle) -> Result<VrrSupport, Error<A::Error>> {
self.drm.vrr_supported(conn).map_err(Error::DrmError)
}
/// Returns whether the next frame state would set Variable Refresh Rate as enabled.
///
/// See [`DrmSurface::vrr_enabled`] for more details.
pub fn vrr_enabled(&self) -> bool {
self.drm.vrr_enabled()
}
/// Tries to set Variable Refresh Rate (VRR) for the next frame.
//
/// Doing so might cause the next frame to trigger a modeset.
/// Check [`GbmBufferedSurface::vrr_supported`], which indicates if VRR can be
/// used without a modeset on the attached connectors./
pub fn use_vrr(&self, vrr: bool) -> Result<(), Error<A::Error>> {
self.drm.use_vrr(vrr).map_err(Error::DrmError)
}
/// Returns a reference to the underlying drm surface
pub fn surface(&self) -> &DrmSurface {
&self.drm
}
/// Get the format of the underlying swapchain
pub fn format(&self) -> Fourcc {
self.swapchain.format()
}
}
/// Errors thrown by a [`GbmBufferedSurface`]
#[derive(Debug, thiserror::Error)]
pub enum Error<E: std::error::Error + Send + Sync + 'static> {
/// No supported pixel format for the given plane could be determined
#[error("No supported plane buffer format found")]
NoSupportedPlaneFormat,
/// No supported pixel format for the given renderer could be determined
#[error("No supported renderer buffer format found")]
NoSupportedRendererFormat,
/// The supported pixel formats of the renderer and plane are incompatible
#[error("Supported plane and renderer buffer formats are incompatible")]
FormatsNotCompatible,
/// The swapchain is exhausted, you need to call `frame_submitted`
#[error("Failed to allocate a new buffer")]
NoFreeSlotsError,
/// Failed to renderer using the given renderer
#[error("Failed to render test frame")]
InitialRenderingError,
/// Error accessing the drm device
#[error("The underlying drm surface encounted an error: {0}")]
DrmError(#[from] DrmError),
/// Error importing the rendered buffer to libgbm for scan-out
#[error("The underlying gbm device encounted an error: {0}")]
GbmError(#[source] E),
/// Error exporting as Dmabuf
#[error("The allocated buffer could not be exported as a dmabuf: {0}")]
AsDmabufError(#[from] GbmConvertError),
/// No buffer to queue
#[error("No buffer has been acquired to get queued")]
NoBuffer,
}
impl<E: std::error::Error + Send + Sync + 'static> From<Error<E>> for SwapBuffersError {
#[inline]
fn from(err: Error<E>) -> SwapBuffersError {
match err {
x @ Error::NoSupportedPlaneFormat
| x @ Error::NoSupportedRendererFormat
| x @ Error::FormatsNotCompatible
| x @ Error::InitialRenderingError => SwapBuffersError::ContextLost(Box::new(x)),
x @ Error::NoFreeSlotsError | x @ Error::NoBuffer => {
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)),
}
}
}