use denise::{CursorPlane, Point, Size, SurfaceError, cursor::pad_into};
use drm::control::{Device as ControlDevice, dumbbuffer::DumbBuffer};
use drm_fourcc::DrmFourcc;
use crate::device::Card;
use crate::error::DrmError;
use crate::surface::DrmSurface;
const BPP: u32 = 32;
const FALLBACK: Size = Size::new(64, 64);
#[derive(Debug)]
pub(crate) struct CursorBuffer {
dumb: DumbBuffer,
ptr: *mut u32,
words: usize,
bytes: usize,
limit: Size,
hotspot: Point,
}
impl CursorBuffer {
pub(crate) fn new(card: &Card) -> Result<Self, DrmError> {
let limit = limit_of(card);
let mut dumb = card
.create_dumb_buffer((limit.width, limit.height), DrmFourcc::Argb8888, BPP)
.map_err(|source| DrmError::Allocate {
width: limit.width,
height: limit.height,
source,
})?;
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 buffer = Self {
dumb,
ptr: ptr.cast::<u32>(),
words: bytes / 4,
bytes,
limit,
hotspot: Point::ZERO,
};
buffer.pixels_mut().fill(0);
Ok(buffer)
}
fn pixels_mut(&mut self) -> &mut [u32] {
unsafe { core::slice::from_raw_parts_mut(self.ptr, self.words) }
}
fn upload(&mut self, pixels: &[u32], size: Size, hotspot: Point) -> Result<(), SurfaceError> {
let limit = self.limit;
pad_into(pixels, size, self.pixels_mut(), limit)?;
self.hotspot = hotspot;
Ok(())
}
pub(crate) fn release(self, card: &Card) {
unsafe {
let _ = rustix::mm::munmap(self.ptr.cast::<core::ffi::c_void>(), self.bytes);
}
let _ = card.destroy_dumb_buffer(self.dumb);
}
}
fn limit_of(card: &Card) -> Size {
use drm::Device as _;
let width = card
.get_driver_capability(drm::DriverCapability::CursorWidth)
.ok()
.filter(|w| *w > 0)
.unwrap_or(FALLBACK.width as u64);
let height = card
.get_driver_capability(drm::DriverCapability::CursorHeight)
.ok()
.filter(|h| *h > 0)
.unwrap_or(FALLBACK.height as u64);
Size::new(width as u32, height as u32)
}
impl CursorPlane for DrmSurface {
fn cursor_limit(&self) -> Size {
match self.cursor.as_ref() {
Some(cursor) => cursor.limit,
None => limit_of(&self.card),
}
}
fn set_cursor(
&mut self,
pixels: &[u32],
size: Size,
hotspot: Point,
) -> Result<(), SurfaceError> {
if self.cursor.is_none() {
self.cursor = Some(CursorBuffer::new(&self.card).map_err(SurfaceError::backend)?);
}
let cursor = self.cursor.as_mut().expect("just created");
cursor.upload(pixels, size, hotspot)?;
#[allow(deprecated)]
self.card
.set_cursor(self.crtc, Some(&cursor.dumb))
.map_err(SurfaceError::backend)
}
fn move_cursor(&mut self, position: Point) -> Result<(), SurfaceError> {
let Some(cursor) = self.cursor.as_ref() else {
return Ok(());
};
let at = (position.x - cursor.hotspot.x, position.y - cursor.hotspot.y);
#[allow(deprecated)]
self.card
.move_cursor(self.crtc, at)
.map_err(SurfaceError::backend)
}
fn hide_cursor(&mut self) -> Result<(), SurfaceError> {
if self.cursor.is_none() {
return Ok(());
}
#[allow(deprecated)]
self.card
.set_cursor(self.crtc, None::<&DumbBuffer>)
.map_err(SurfaceError::backend)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_fallback_is_the_size_every_driver_supports() {
assert_eq!(FALLBACK, Size::new(64, 64));
assert!(!FALLBACK.is_empty());
}
#[test]
fn the_cursor_format_carries_alpha() {
assert_eq!(BPP, 32);
assert_eq!(DrmFourcc::Argb8888 as u32, DrmFourcc::Argb8888 as u32);
assert_ne!(DrmFourcc::Argb8888, DrmFourcc::Xrgb8888);
}
}