use std::time::Instant;
const ARGB8888: u32 = drmkit_fmt::fourcc::ARGB8888;
use drmkit_core::Device;
use drmkit_cursor::Cursor;
use drmkit_dumb::{Buffer, Config, DumbError, MapAccess, Mapping};
use drmkit_scene::{
AcquiredBuffer, BindingModel, DamageRect, LayerBufferSource, SourceError, SourceFormat,
};
#[derive(Debug)]
pub struct CursorSource {
cursor: Cursor,
buffer: Buffer,
format: SourceFormat,
hotspot: (u32, u32),
started: Option<Instant>,
painted: Option<usize>,
pending_damage: Vec<DamageRect>,
}
impl CursorSource {
pub fn new(device: &Device, cursor: Cursor) -> Result<Self, DumbError> {
let width = cursor.frames().iter().map(|frame| frame.width).max();
let height = cursor.frames().iter().map(|frame| frame.height).max();
let (width, height) = (width.unwrap_or(1).max(1), height.unwrap_or(1).max(1));
let buffer = Buffer::create(
device,
&Config {
width,
height,
fourcc: ARGB8888,
..Config::default()
},
)?;
let mut source = Self {
cursor,
buffer,
format: SourceFormat {
fourcc: ARGB8888,
modifier: 0,
width,
height,
},
hotspot: (0, 0),
started: None,
painted: None,
pending_damage: Vec::new(),
};
source.paint(0);
Ok(source)
}
pub fn from_argb(
device: &Device,
pixels: &[u32],
width: u32,
height: u32,
xhot: u32,
yhot: u32,
) -> Result<Self, SourceError> {
let cursor = Cursor::from_argb(pixels, width, height, xhot, yhot)
.map_err(|_| SourceError::Failed(rustix::io::Errno::INVAL))?;
Self::new(device, cursor).map_err(|error| SourceError::Failed(errno(&error)))
}
#[must_use]
pub const fn hotspot(&self) -> (u32, u32) {
self.hotspot
}
#[must_use]
pub const fn size(&self) -> (u32, u32) {
(self.format.width, self.format.height)
}
#[must_use]
pub const fn cursor(&self) -> &Cursor {
&self.cursor
}
fn paint(&mut self, index: usize) {
let frame = self
.cursor
.frames()
.get(index)
.unwrap_or_else(|| self.cursor.first());
let (xhot, yhot) = (frame.xhot, frame.yhot);
let pixels = frame.pixels.clone();
let (width, height) = (frame.width, frame.height);
blit(&mut self.buffer, &pixels, width, height, self.format.width);
self.hotspot = (xhot, yhot);
self.painted = Some(index);
}
fn due(&mut self) -> Option<usize> {
if !self.cursor.is_animated() {
return None;
}
let started = *self.started.get_or_insert_with(Instant::now);
let index = self.cursor.frame_index_at(started.elapsed());
(self.painted != Some(index)).then_some(index)
}
}
fn blit(buffer: &mut Buffer, pixels: &[u32], width: u32, height: u32, buffer_width: u32) {
let mut mapping = buffer.map(MapAccess::Write);
for row in 0..mapping.height() {
let Some(bytes) = mapping.row_mut(row) else {
break;
};
bytes.fill(0);
if row >= height {
continue;
}
let columns = width.min(buffer_width) as usize;
let source = &pixels[row as usize * width as usize..][..columns];
for (column, pixel) in source.iter().enumerate() {
let at = column * 4;
bytes[at..at + 4].copy_from_slice(&pixel.to_le_bytes());
}
}
}
pub(crate) fn errno(error: &DumbError) -> rustix::io::Errno {
match error {
DumbError::Io(errno) => *errno,
DumbError::InvalidConfig { .. } | DumbError::UnsupportedFormat { .. } => {
rustix::io::Errno::INVAL
}
DumbError::TooLarge { .. } => rustix::io::Errno::OVERFLOW,
_ => rustix::io::Errno::INVAL,
}
}
impl LayerBufferSource for CursorSource {
fn acquire(&mut self) -> Result<AcquiredBuffer, SourceError> {
if let Some(index) = self.due() {
self.paint(index);
self.pending_damage.clear();
}
let fb_id = self
.buffer
.fb_id()
.ok_or(SourceError::Failed(rustix::io::Errno::INVAL))?;
Ok(AcquiredBuffer {
fb_id,
token: 0,
acquire_fence: None,
damage: std::mem::take(&mut self.pending_damage),
})
}
fn release(&mut self, _acquired: AcquiredBuffer) {
}
fn binding_model(&self) -> BindingModel {
BindingModel::SceneSubmitsFbId
}
fn format(&self) -> SourceFormat {
self.format
}
fn map(&mut self, access: MapAccess) -> Result<Mapping<'_>, SourceError> {
Ok(self.buffer.map(access))
}
fn on_session_paused(&mut self) {
}
fn on_session_resumed(&mut self, device: &Device) -> Result<(), SourceError> {
let (width, height) = (self.format.width, self.format.height);
self.buffer.forget();
self.buffer = Buffer::create(
device,
&Config {
width,
height,
fourcc: self.format.fourcc,
..Config::default()
},
)
.map_err(|error| SourceError::Failed(errno(&error)))?;
let index = self.painted.unwrap_or(0);
self.painted = None;
self.paint(index);
Ok(())
}
}