use std::time::Duration;
use drmkit_core::Device;
use drmkit_scene::{
AcquiredBuffer, DamageRect, DmaBufDesc, LayerBufferSource, SourceError, SourceFormat,
};
use drmkit_sync::SyncFence;
use crate::external::{ExternalError, ExternalPlane, ImportedFramebuffer};
use crate::presenter::{Present, RingPresenter};
pub type OnRelease = Box<dyn FnMut(usize, Option<SyncFence>) + Send>;
pub struct ExternalDmaBufRing {
slots: Vec<ImportedFramebuffer>,
presenter: RingPresenter,
on_release: Option<OnRelease>,
format: SourceFormat,
presented: Option<usize>,
}
impl std::fmt::Debug for ExternalDmaBufRing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExternalDmaBufRing")
.field("slots", &self.slots.len())
.field("format", &self.format)
.field("scanning", &self.presenter.scanning_key())
.finish_non_exhaustive()
}
}
impl ExternalDmaBufRing {
pub fn create(
device: &Device,
format: SourceFormat,
slots: &[&[ExternalPlane<'_>]],
fence_deadline: Option<Duration>,
) -> Result<Self, ExternalError> {
if slots.is_empty() {
return Err(ExternalError::Invalid {
reason: "a ring needs at least one slot",
});
}
let mut imported = Vec::with_capacity(slots.len());
for planes in slots {
imported.push(ImportedFramebuffer::import(device, format, planes)?);
}
Ok(Self {
slots: imported,
presented: None,
presenter: RingPresenter::new(fence_deadline),
on_release: None,
format,
})
}
pub fn set_on_release(&mut self, on_release: OnRelease) {
self.on_release = Some(on_release);
}
pub fn submit(&self, slot: usize, fence: Option<SyncFence>, damage: &[DamageRect]) {
if slot >= self.slots.len() {
return;
}
self.presenter.submit(slot as u64, fence, damage);
}
#[must_use]
pub fn slot_count(&self) -> usize {
self.slots.len()
}
#[must_use]
pub fn has_fresh_frame(&self) -> bool {
self.presenter.has_fresh_frame()
}
#[must_use]
pub fn scanning_slot(&self) -> Option<usize> {
self.presenter
.scanning_key()
.and_then(|key| usize::try_from(key).ok())
}
fn fire_release(&mut self, slot: usize, fence: Option<SyncFence>) {
if let Some(callback) = self.on_release.as_mut() {
callback(slot, fence);
}
}
fn fb_of(&self, key: u64) -> Option<u32> {
self.slots.get(usize::try_from(key).ok()?)?.fb_id()
}
}
impl LayerBufferSource for ExternalDmaBufRing {
fn export_dma_buf(&mut self) -> Result<DmaBufDesc<'_>, SourceError> {
let slot = self
.presented
.and_then(|index| self.slots.get(index))
.ok_or(SourceError::Unsupported)?;
let (fds, offsets, pitches) = slot.dma_buf_planes();
if fds.is_empty() {
return Err(SourceError::Unsupported);
}
Ok(DmaBufDesc {
fds: fds.iter().map(std::os::fd::AsFd::as_fd).collect(),
offsets: offsets.to_vec(),
pitches: pitches.to_vec(),
format: self.format,
})
}
fn acquire(&mut self) -> Result<AcquiredBuffer, SourceError> {
let decision = self.presenter.acquire();
match decision.kind {
Present::Fresh => {
let fb_id = self
.fb_of(decision.key)
.ok_or(SourceError::Failed(rustix::io::Errno::INVAL))?;
self.presented = usize::try_from(decision.key).ok();
Ok(AcquiredBuffer {
fb_id,
token: decision.token,
acquire_fence: decision.fence,
damage: decision.damage,
})
}
Present::Hold => {
let fb_id = self.fb_of(decision.key).ok_or(SourceError::WouldBlock)?;
self.presented = usize::try_from(decision.key).ok();
Ok(AcquiredBuffer {
fb_id,
token: decision.token,
..AcquiredBuffer::default()
})
}
Present::None => Err(SourceError::WouldBlock),
}
}
fn release(&mut self, acquired: AcquiredBuffer) {
self.release_with_fence(acquired, None);
}
fn release_with_fence(&mut self, acquired: AcquiredBuffer, release_fence: Option<SyncFence>) {
if let Some(key) = self.presenter.release(acquired.token)
&& let Ok(slot) = usize::try_from(key)
{
self.fire_release(slot, release_fence);
}
}
fn wants_release_fence(&self) -> bool {
self.on_release.is_some()
}
fn has_fresh_content(&self) -> bool {
self.presenter.has_fresh_frame()
}
fn on_retired(&mut self) {
self.presenter.retire_scanning();
}
fn format(&self) -> SourceFormat {
self.format
}
fn on_session_paused(&mut self) {
for slot in &mut self.slots {
slot.forget();
}
self.presented = None;
self.presenter.reset();
}
fn on_session_resumed(&mut self, device: &Device) -> Result<(), SourceError> {
for index in 0..self.slots.len() {
if let Err(error) = self.slots[index].reimport(device, self.format) {
for slot in &mut self.slots[..index] {
slot.release();
}
return Err(crate::external::source_error(&error));
}
}
Ok(())
}
}