use std::collections::HashMap;
use drm::control::{Device as ControlDevice, FbCmd2Flags, framebuffer};
use drmkit_core::Device;
use drmkit_scene::{AcquiredBuffer, BindingModel, LayerBufferSource, SourceError, SourceFormat};
use drmkit_sync::SyncFence;
#[derive(Debug, Clone, Copy)]
#[allow(
clippy::exhaustive_structs,
reason = "built by callers with `..Default::default()`, which a new field does not break"
)]
pub struct SurfaceConfig {
pub width: u32,
pub height: u32,
pub fourcc: u32,
pub modifier: Option<u64>,
pub require_bind: bool,
}
impl Default for SurfaceConfig {
fn default() -> Self {
Self {
width: 0,
height: 0,
fourcc: drmkit_fmt::fourcc::XRGB8888,
modifier: None,
require_bind: false,
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SurfaceError {
#[error("{field} must be non-zero")]
Invalid {
field: &'static str,
},
#[error("format {fourcc:#x} is not a GBM format")]
UnsupportedFormat {
fourcc: u32,
},
#[error("opening a GBM device: {0}")]
Device(#[from] drmkit_gbm::GbmError),
#[error("creating the GBM surface: {0}")]
Create(String),
#[error("registering a framebuffer: {0}")]
Framebuffer(rustix::io::Errno),
}
pub struct GbmSurfaceSource {
surface: drmkit_gbm::GbmSurface,
format: SourceFormat,
config: SurfaceConfig,
framebuffers: HashMap<u32, framebuffer::Handle>,
locked: HashMap<u64, drmkit_gbm::GbmBuffer>,
next_token: u64,
pending_fence: Option<SyncFence>,
device_fd: std::os::fd::OwnedFd,
paused: bool,
bound: bool,
gbm: drmkit_gbm::GbmDevice,
}
impl std::fmt::Debug for GbmSurfaceSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GbmSurfaceSource")
.field("format", &self.format)
.field("registered", &self.framebuffers.len())
.field("locked", &self.locked.len())
.finish_non_exhaustive()
}
}
impl GbmSurfaceSource {
pub fn create(device: &Device, config: &SurfaceConfig) -> Result<Self, SurfaceError> {
if config.width == 0 {
return Err(SurfaceError::Invalid { field: "width" });
}
if config.height == 0 {
return Err(SurfaceError::Invalid { field: "height" });
}
if config.fourcc == 0 {
return Err(SurfaceError::Invalid { field: "fourcc" });
}
if drm::buffer::DrmFourcc::try_from(config.fourcc).is_err() {
return Err(SurfaceError::UnsupportedFormat {
fourcc: config.fourcc,
});
}
let gbm = drmkit_gbm::GbmDevice::new(device)?;
let surface = Self::make_surface(&gbm, config)?;
Ok(Self {
format: SourceFormat {
fourcc: config.fourcc,
modifier: config.modifier.unwrap_or(0),
width: config.width,
height: config.height,
},
surface,
config: *config,
framebuffers: HashMap::new(),
locked: HashMap::new(),
next_token: 1,
pending_fence: None,
device_fd: duplicate(device).map_err(drmkit_gbm::GbmError::Io)?,
paused: false,
bound: false,
gbm,
})
}
fn make_surface(
gbm: &drmkit_gbm::GbmDevice,
config: &SurfaceConfig,
) -> Result<drmkit_gbm::GbmSurface, SurfaceError> {
let modifiers: Vec<u64> = config.modifier.into_iter().collect();
gbm.create_surface(config.width, config.height, config.fourcc, &modifiers)
.map_err(|error| SurfaceError::Create(error.to_string()))
}
#[must_use]
pub const fn native_surface(&self) -> &drmkit_gbm::GbmSurface {
&self.surface
}
#[must_use]
pub const fn native_device(&self) -> &drmkit_gbm::GbmDevice {
&self.gbm
}
pub fn set_acquire_fence(&mut self, fence: SyncFence) {
self.pending_fence = Some(fence);
}
fn take_acquire_fence(&mut self) -> Option<SyncFence> {
self.pending_fence.take()
}
pub const fn mark_bound(&mut self) {
self.bound = true;
}
#[must_use]
pub fn has_free_buffers(&self) -> bool {
self.surface.has_free_buffers()
}
fn framebuffer_for(
&mut self,
device: &RawDevice<'_>,
buffer: &drmkit_gbm::GbmBuffer,
) -> Result<framebuffer::Handle, SurfaceError> {
let handle = buffer.handle();
if let Some(existing) = self.framebuffers.get(&handle) {
return Ok(*existing);
}
let fourcc = drm::buffer::DrmFourcc::try_from(self.format.fourcc).map_err(|_| {
SurfaceError::UnsupportedFormat {
fourcc: self.format.fourcc,
}
})?;
let modifier = buffer.modifier();
let declared = drmkit_core::framebuffer_modifier(device, modifier);
let layout = Layout {
width: self.format.width,
height: self.format.height,
fourcc,
modifier: declared,
handle,
pitch: buffer.stride(),
};
let flags = if declared.is_some() {
FbCmd2Flags::MODIFIERS
} else {
FbCmd2Flags::empty()
};
let fb = device
.add_planar_framebuffer(&layout, flags)
.map_err(|error| {
SurfaceError::Framebuffer(
rustix::io::Errno::from_io_error(&error).unwrap_or(rustix::io::Errno::INVAL),
)
})?;
self.framebuffers.insert(handle, fb);
Ok(fb)
}
}
fn duplicate(device: &Device) -> Result<std::os::fd::OwnedFd, rustix::io::Errno> {
std::os::fd::AsFd::as_fd(device)
.try_clone_to_owned()
.map_err(|error| {
rustix::io::Errno::from_io_error(&error).unwrap_or(rustix::io::Errno::MFILE)
})
}
fn acquire_error(error: &SurfaceError) -> SourceError {
match error {
SurfaceError::Framebuffer(errno) => SourceError::Failed(*errno),
_ => SourceError::Failed(rustix::io::Errno::INVAL),
}
}
struct Layout {
width: u32,
height: u32,
fourcc: drm::buffer::DrmFourcc,
modifier: Option<u64>,
handle: u32,
pitch: u32,
}
impl drm::buffer::PlanarBuffer for Layout {
fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
fn format(&self) -> drm::buffer::DrmFourcc {
self.fourcc
}
fn modifier(&self) -> Option<drm::buffer::DrmModifier> {
self.modifier.map(drm::buffer::DrmModifier::from)
}
fn pitches(&self) -> [u32; 4] {
[self.pitch, 0, 0, 0]
}
fn handles(&self) -> [Option<drm::buffer::Handle>; 4] {
[
std::num::NonZeroU32::new(self.handle).map(drm::buffer::Handle::from),
None,
None,
None,
]
}
fn offsets(&self) -> [u32; 4] {
[0; 4]
}
}
struct RawDevice<'a> {
fd: std::os::fd::BorrowedFd<'a>,
}
impl std::os::fd::AsFd for RawDevice<'_> {
fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
self.fd
}
}
impl drm::Device for RawDevice<'_> {}
impl ControlDevice for RawDevice<'_> {}
impl LayerBufferSource for GbmSurfaceSource {
fn acquire(&mut self) -> Result<AcquiredBuffer, SourceError> {
if self.paused || (self.config.require_bind && !self.bound) {
return Err(SourceError::WouldBlock);
}
let buffer = unsafe { self.surface.lock_front_buffer() }.ok_or(SourceError::WouldBlock)?;
let borrowed = unsafe {
std::os::fd::BorrowedFd::borrow_raw(std::os::fd::AsRawFd::as_raw_fd(&self.device_fd))
};
let device = RawDevice { fd: borrowed };
let fb = self
.framebuffer_for(&device, &buffer)
.map_err(|error| acquire_error(&error))?;
self.format.modifier = buffer.modifier();
let token = self.next_token;
self.next_token = self.next_token.wrapping_add(1).max(1);
self.locked.insert(token, buffer);
Ok(AcquiredBuffer {
fb_id: u32::from(fb),
token,
acquire_fence: self.take_acquire_fence(),
damage: Vec::new(),
})
}
fn release(&mut self, acquired: AcquiredBuffer) {
self.locked.remove(&acquired.token);
}
fn binding_model(&self) -> BindingModel {
BindingModel::SceneSubmitsFbId
}
fn format(&self) -> SourceFormat {
self.format
}
fn on_session_paused(&mut self) {
self.paused = true;
self.framebuffers.clear();
self.locked.clear();
}
fn on_session_resumed(&mut self, device: &Device) -> Result<(), SourceError> {
let gbm = drmkit_gbm::GbmDevice::new(device).map_err(|_| SourceError::Unsupported)?;
let surface =
Self::make_surface(&gbm, &self.config).map_err(|_| SourceError::Unsupported)?;
self.locked.clear();
self.surface = surface;
self.gbm = gbm;
self.framebuffers.clear();
self.device_fd = duplicate(device).map_err(SourceError::Failed)?;
self.paused = false;
self.bound = false;
Ok(())
}
}
impl Drop for GbmSurfaceSource {
fn drop(&mut self) {
if self.paused || self.framebuffers.is_empty() {
return;
}
let device = RawDevice {
fd: std::os::fd::AsFd::as_fd(&self.device_fd),
};
for fb in self.framebuffers.values() {
let _ = device.destroy_framebuffer(*fb);
}
}
}
#[cfg(test)]
mod tests;