use drmkit_core::Device;
use drmkit_dumb::{Buffer, Config, DumbError, MapAccess, Mapping};
use drmkit_scene::{
AcquiredBuffer, BindingModel, DamageRect, LayerBufferSource, SourceError, SourceFormat,
};
use crate::{BufferRing, Rect, Repaint};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum PaintError {
#[error("every ring slot is busy")]
WouldBlock,
#[error("allocating a ring slot: {0}")]
Allocate(#[from] DumbError),
#[error("no known bits-per-pixel for fourcc {0:#010x}")]
UnknownFormat(u32),
#[error("a dumb ring needs a non-zero {0}")]
Zero(&'static str),
}
pub struct DumbRingSource {
format: SourceFormat,
bpp: u32,
ring: BufferRing,
buffers: Vec<Option<Buffer>>,
pending: Option<(usize, Vec<DamageRect>)>,
}
impl DumbRingSource {
pub fn new(width: u32, height: u32, fourcc: u32, max_slots: usize) -> Result<Self, PaintError> {
for (name, value) in [
("width", usize::try_from(width).unwrap_or(usize::MAX)),
("height", usize::try_from(height).unwrap_or(usize::MAX)),
("slot count", max_slots),
] {
if value == 0 {
return Err(PaintError::Zero(name));
}
}
let bpp = drmkit_fmt::format_bpp(fourcc);
if bpp == 0 {
return Err(PaintError::UnknownFormat(fourcc));
}
Ok(Self {
format: SourceFormat {
fourcc,
modifier: drmkit_fmt::Modifier::LINEAR.0,
width,
height,
},
bpp,
ring: BufferRing::new(max_slots, BufferRing::DEFAULT_MAX_DAMAGE_RECTS),
buffers: Vec::new(),
pending: None,
})
}
pub fn paint<F>(&mut self, device: &Device, paint: F) -> Result<(), PaintError>
where
F: FnOnce(&mut Mapping<'_>, &Repaint) -> Vec<Rect>,
{
let lease = self.ring.acquire().ok_or(PaintError::WouldBlock)?;
let buffer = self.ensure_slot(device, lease.slot)?;
let painted = {
let mut mapping = buffer.map(MapAccess::Write);
paint(&mut mapping, &lease.repaint)
};
let damage = painted
.iter()
.map(|r| DamageRect {
x: r.x,
y: r.y,
w: r.width,
h: r.height,
})
.collect();
self.ring.present(lease.slot, &painted);
self.pending = Some((lease.slot, damage));
Ok(())
}
#[must_use]
pub fn allocated(&self) -> usize {
self.buffers.iter().filter(|b| b.is_some()).count()
}
fn ensure_slot(&mut self, device: &Device, slot: usize) -> Result<&mut Buffer, PaintError> {
if self.buffers.len() <= slot {
self.buffers.resize_with(slot + 1, || None);
}
if self.buffers[slot].is_none() {
self.buffers[slot] = Some(Self::allocate(device, &self.format, self.bpp)?);
}
Ok(self.buffers[slot]
.as_mut()
.unwrap_or_else(|| unreachable!("just allocated")))
}
fn allocate(device: &Device, format: &SourceFormat, bpp: u32) -> Result<Buffer, PaintError> {
let buffer = Buffer::create(
device,
&Config {
width: format.width,
height: format.height,
fourcc: format.fourcc,
bpp,
add_fb: true,
},
)?;
Ok(buffer)
}
}
impl LayerBufferSource for DumbRingSource {
fn acquire(&mut self) -> Result<AcquiredBuffer, SourceError> {
let (slot, damage) = self.pending.take().ok_or(SourceError::WouldBlock)?;
let fb_id = self
.buffers
.get(slot)
.and_then(Option::as_ref)
.and_then(Buffer::fb_id)
.ok_or(SourceError::WouldBlock)?;
Ok(AcquiredBuffer {
fb_id,
token: u64::try_from(slot).unwrap_or(u64::MAX),
acquire_fence: None,
damage,
})
}
fn release(&mut self, acquired: AcquiredBuffer) {
let slot = usize::try_from(acquired.token).unwrap_or(usize::MAX);
self.ring.release(slot);
}
fn binding_model(&self) -> BindingModel {
BindingModel::SceneSubmitsFbId
}
fn format(&self) -> SourceFormat {
self.format
}
fn on_session_paused(&mut self) {
for slot in &mut self.buffers {
if let Some(buffer) = slot.as_mut() {
buffer.forget();
}
*slot = None;
}
self.pending = None;
self.ring = BufferRing::new(self.ring.max_slots(), BufferRing::DEFAULT_MAX_DAMAGE_RECTS);
}
fn on_session_resumed(&mut self, _device: &Device) -> Result<(), SourceError> {
Ok(())
}
}