use denise::{Frame, PixelFormat, Rect, Size, Surface, SurfaceError};
use drm::Device as _;
use drm::DriverCapability;
use drm::buffer::Buffer as _;
use drm::control::{
Device as ControlDevice, Event, Mode, PageFlipFlags, connector, crtc, dumbbuffer::DumbBuffer,
framebuffer,
};
use drm_fourcc::DrmFourcc;
use crate::device::Card;
use crate::error::DrmError;
use crate::mode::{self, ModePreference, OutputPreference};
use crate::swapchain::Swapchain;
const BPP: u32 = 32;
const DEPTH: u32 = 24;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PresentMode {
#[default]
Immediate,
Vsync,
}
#[derive(Clone, Copy, Debug)]
pub struct SurfaceConfig {
pub output: OutputPreference,
pub mode: ModePreference,
pub buffers: usize,
pub present_mode: PresentMode,
}
impl Default for SurfaceConfig {
fn default() -> Self {
Self {
output: OutputPreference::Auto,
mode: ModePreference::Preferred,
buffers: 2,
present_mode: PresentMode::Vsync,
}
}
}
#[derive(Debug)]
struct Scanout {
dumb: DumbBuffer,
fb: framebuffer::Handle,
ptr: *mut u32,
words: usize,
bytes: usize,
}
impl Scanout {
fn new(card: &Card, size: Size) -> Result<Self, DrmError> {
let mut dumb = card
.create_dumb_buffer((size.width, size.height), DrmFourcc::Xrgb8888, BPP)
.map_err(|source| DrmError::Allocate {
width: size.width,
height: size.height,
source,
})?;
let fb = card
.add_framebuffer(&dumb, DEPTH, BPP)
.map_err(DrmError::AddFramebuffer)?;
let (ptr, bytes) = {
let mut mapping = card.map_dumb_buffer(&mut dumb).map_err(DrmError::Map)?;
let slice: &mut [u8] = &mut mapping;
let ptr = slice.as_mut_ptr();
let bytes = slice.len();
core::mem::forget(mapping);
(ptr, bytes)
};
let mut scanout = Self {
dumb,
fb,
ptr: ptr.cast::<u32>(),
words: bytes / 4,
bytes,
};
scanout.pixels_mut().fill(0);
Ok(scanout)
}
fn pixels_mut(&mut self) -> &mut [u32] {
unsafe { core::slice::from_raw_parts_mut(self.ptr, self.words) }
}
}
#[derive(Debug)]
pub struct DrmSurface {
pub(crate) card: Card,
pub(crate) crtc: crtc::Handle,
connector: connector::Handle,
buffers: Vec<Scanout>,
swapchain: Swapchain,
size: Size,
stride: u32,
flip_pending: bool,
present_mode: PresentMode,
flip_flags: PageFlipFlags,
saved_crtc: Option<crtc::Info>,
mode_name: String,
pub(crate) cursor: Option<crate::cursor::CursorBuffer>,
}
impl DrmSurface {
pub fn new(card: Card, config: SurfaceConfig) -> Result<Self, DrmError> {
card.become_master()?;
let (handles, infos) = card.connectors()?;
let selection = mode::select(&infos, config.output, config.mode)?;
let connector = handles[selection.connector];
let crtc = card.crtc_for(connector)?;
let info = card
.get_connector(connector, false)
.map_err(DrmError::Resources)?;
let mode: Mode = info.modes()[selection.mode];
let (width, height) = mode.size();
let size = Size::new(u32::from(width), u32::from(height));
let saved_crtc = card.get_crtc(crtc).ok();
let mut buffers = Vec::with_capacity(config.buffers);
for _ in 0..Swapchain::new(config.buffers).count() {
buffers.push(Scanout::new(&card, size)?);
}
let pitch = buffers[0].dumb.pitch();
if !pitch.is_multiple_of(4) {
return Err(DrmError::UnalignedPitch { pitch });
}
let async_capable = card
.get_driver_capability(DriverCapability::ASyncPageFlip)
.is_ok_and(|supported| supported != 0);
let present_mode = match config.present_mode {
PresentMode::Immediate if async_capable => PresentMode::Immediate,
_ => PresentMode::Vsync,
};
let flip_flags = match present_mode {
PresentMode::Vsync => PageFlipFlags::EVENT,
PresentMode::Immediate => PageFlipFlags::EVENT | PageFlipFlags::ASYNC,
};
card.set_crtc(crtc, Some(buffers[0].fb), (0, 0), &[connector], Some(mode))
.map_err(|source| DrmError::SetMode {
mode: format!("{width}x{height}"),
crtc: u32::from(crtc),
source,
})?;
let mut swapchain = Swapchain::new(config.buffers);
swapchain.presented();
Ok(Self {
card,
crtc,
connector,
buffers,
swapchain,
size,
stride: pitch / 4,
flip_pending: false,
present_mode,
flip_flags,
saved_crtc,
mode_name: format!("{width}x{height}@{}", mode.vrefresh()),
cursor: None,
})
}
pub fn open(config: SurfaceConfig) -> Result<Self, DrmError> {
Self::new(Card::open_first()?, config)
}
pub fn mode_name(&self) -> &str {
&self.mode_name
}
pub fn stride(&self) -> u32 {
self.stride
}
pub fn buffer_count(&self) -> usize {
self.buffers.len()
}
pub fn present_mode(&self) -> PresentMode {
self.present_mode
}
fn wait_for_flip(&mut self) -> Result<(), DrmError> {
while self.flip_pending {
let events = self.card.receive_events().map_err(DrmError::WaitVblank)?;
for event in events {
if matches!(event, Event::PageFlip(_)) {
self.flip_pending = false;
}
}
}
Ok(())
}
}
impl Surface for DrmSurface {
fn size(&self) -> Size {
self.size
}
fn scale_factor(&self) -> f32 {
1.0
}
fn format(&self) -> PixelFormat {
PixelFormat::Xrgb8888
}
fn acquire(&mut self) -> Result<Frame<'_>, SurfaceError> {
self.wait_for_flip()?;
let index = self.swapchain.current();
let age = self.swapchain.age();
let size = self.size;
let stride = self.stride;
Frame::new(
self.buffers[index].pixels_mut(),
size,
stride,
PixelFormat::Xrgb8888,
age,
)
}
fn present(&mut self, _damage: &[Rect]) -> Result<(), SurfaceError> {
let index = self.swapchain.current();
let fb = self.buffers[index].fb;
self.card
.page_flip(self.crtc, fb, self.flip_flags, None)
.map_err(DrmError::PageFlip)?;
self.flip_pending = true;
self.swapchain.presented();
Ok(())
}
}
impl Drop for DrmSurface {
fn drop(&mut self) {
let _ = self.wait_for_flip();
if let Some(saved) = self.saved_crtc.as_ref() {
let _ = self.card.set_crtc(
self.crtc,
saved.framebuffer(),
saved.position(),
&[self.connector],
saved.mode(),
);
}
if let Some(cursor) = self.cursor.take() {
#[allow(deprecated)]
let _ = self
.card
.set_cursor(self.crtc, None::<&drm::control::dumbbuffer::DumbBuffer>);
cursor.release(&self.card);
}
for buffer in self.buffers.drain(..) {
unsafe {
let _ = rustix::mm::munmap(buffer.ptr.cast::<core::ffi::c_void>(), buffer.bytes);
}
let _ = self.card.destroy_framebuffer(buffer.fb);
let _ = self.card.destroy_dumb_buffer(buffer.dumb);
}
self.card.release_master();
}
}