use drm::control::Device as ControlDevice;
use drmkit_core::Device;
use drmkit_dumb::{Buffer, Config, MapAccess, Mapping};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[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,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SurfaceError {
#[error("{field} must be non-zero")]
Invalid {
field: &'static str,
},
#[error("allocating a decoration surface: {0}")]
Allocate(#[from] drmkit_dumb::DumbError),
#[error("this surface holds no buffer")]
Empty,
#[error("exporting a decoration surface: {0}")]
Export(rustix::io::Errno),
}
pub const SURFACE_FOURCC: u32 = drmkit_fmt::fourcc::ARGB8888;
#[derive(Debug, Default)]
pub struct Surface {
buffer: Option<Buffer>,
generation: u64,
}
impl Surface {
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" });
}
let buffer = Buffer::create(
device,
&Config {
width: config.width,
height: config.height,
fourcc: SURFACE_FOURCC,
..Config::default()
},
)?;
Ok(Self {
buffer: Some(buffer),
generation: 0,
})
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.buffer.is_none()
}
#[must_use]
pub fn fb_id(&self) -> u32 {
self.buffer.as_ref().and_then(Buffer::fb_id).unwrap_or(0)
}
#[must_use]
pub fn width(&self) -> u32 {
self.buffer.as_ref().map_or(0, Buffer::width)
}
#[must_use]
pub fn height(&self) -> u32 {
self.buffer.as_ref().map_or(0, Buffer::height)
}
#[must_use]
pub fn stride(&self) -> u32 {
self.buffer.as_ref().map_or(0, Buffer::stride)
}
#[must_use]
pub const fn format(&self) -> u32 {
SURFACE_FOURCC
}
#[must_use]
pub const fn generation(&self) -> u64 {
self.generation
}
pub fn paint(&mut self, access: MapAccess) -> Result<Mapping<'_>, SurfaceError> {
let buffer = self.buffer.as_mut().ok_or(SurfaceError::Empty)?;
if !matches!(access, MapAccess::Read) {
self.generation = self.generation.wrapping_add(1);
}
Ok(buffer.map(access))
}
pub fn export(&self, device: &Device) -> Result<std::os::fd::OwnedFd, SurfaceError> {
let buffer = self.buffer.as_ref().ok_or(SurfaceError::Empty)?;
let handle = buffer
.gem_handle()
.and_then(drm::control::from_u32)
.ok_or(SurfaceError::Empty)?;
device
.buffer_to_prime_fd(handle, rustix::fs::OFlags::CLOEXEC.bits())
.map_err(|error| {
SurfaceError::Export(
rustix::io::Errno::from_io_error(&error).unwrap_or(rustix::io::Errno::IO),
)
})
}
pub fn forget(&mut self) {
if let Some(mut buffer) = self.buffer.take() {
buffer.forget();
}
}
}