use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::sync::Arc;
use std::sync::Mutex;
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};
use crate::ring::OnRelease;
struct PoolSlot {
imported: Arc<ImportedFramebuffer>,
last_used: u64,
retiring: bool,
stale: bool,
}
pub struct ExternalDmaBufPool {
slots: Mutex<HashMap<u64, PoolSlot>>,
presenter: RingPresenter,
format: Mutex<SourceFormat>,
on_release: Option<OnRelease>,
acquired: Option<Arc<ImportedFramebuffer>>,
max_pool: usize,
tick: std::sync::atomic::AtomicU64,
}
const DEFAULT_MAX_POOL: usize = 32;
impl std::fmt::Debug for ExternalDmaBufPool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExternalDmaBufPool")
.field("cached", &self.cached_count())
.field("scanning", &self.presenter.scanning_key())
.finish_non_exhaustive()
}
}
impl ExternalDmaBufPool {
#[must_use]
pub fn new(format: SourceFormat, fence_deadline: Option<Duration>) -> Self {
Self {
slots: Mutex::new(HashMap::new()),
presenter: RingPresenter::new(fence_deadline),
format: Mutex::new(format),
on_release: None,
acquired: None,
max_pool: DEFAULT_MAX_POOL,
tick: std::sync::atomic::AtomicU64::new(0),
}
}
pub const fn set_max_pool(&mut self, max_pool: usize) {
self.max_pool = if max_pool == 0 { 1 } else { max_pool };
}
pub fn set_on_release(&mut self, on_release: OnRelease) {
self.on_release = Some(on_release);
}
pub fn submit(
&self,
device: &Device,
key: u64,
planes: &[ExternalPlane<'_>],
fence: Option<SyncFence>,
damage: &[DamageRect],
) -> Result<(), ExternalError> {
let format = *self.lock_format();
{
let mut slots = self.lock_slots();
match slots.entry(key) {
Entry::Occupied(entry) if entry.get().stale => {
return Err(ExternalError::Retired);
}
Entry::Occupied(_) => {}
Entry::Vacant(entry) => {
let imported = ImportedFramebuffer::import(device, format, planes)?;
entry.insert(PoolSlot {
imported: Arc::new(imported),
last_used: 0,
retiring: false,
stale: false,
});
}
}
let stamp = self
.tick
.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
.wrapping_add(1);
if let Some(slot) = slots.get_mut(&key) {
slot.last_used = stamp;
slot.retiring = false;
}
self.enforce_cap(&mut slots);
}
self.presenter.submit(key, fence, damage);
Ok(())
}
pub fn retire(&self, key: u64) {
if let Some(slot) = self.lock_slots().get_mut(&key) {
slot.retiring = true;
slot.stale = true;
}
}
#[must_use]
pub fn cached_count(&self) -> usize {
self.lock_slots().len()
}
pub fn reset_generation(&self, format: SourceFormat) {
*self.lock_format() = format;
for slot in self.lock_slots().values_mut() {
slot.retiring = true;
}
}
fn enforce_cap(&self, slots: &mut HashMap<u64, PoolSlot>) {
if slots.len() <= self.max_pool {
return;
}
let mut by_age: Vec<(u64, u64)> = slots
.iter()
.map(|(key, slot)| (*key, slot.last_used))
.collect();
by_age.sort_unstable_by_key(|(_, last_used)| *last_used);
let mut over = slots.len() - self.max_pool;
for (key, _) in by_age {
if over == 0 {
break;
}
if self.presenter.is_referenced(key) {
if let Some(slot) = slots.get_mut(&key) {
slot.retiring = true;
}
} else {
slots.remove(&key);
}
over -= 1;
}
}
fn sweep(&mut self) {
let Self {
slots, presenter, ..
} = self;
let slots = slots
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner);
slots.retain(|key, slot| !slot.retiring || presenter.is_referenced(*key));
}
fn fb_of(&self, key: u64) -> Option<u32> {
self.lock_slots().get(&key)?.imported.fb_id()
}
fn lock_slots(&self) -> std::sync::MutexGuard<'_, HashMap<u64, PoolSlot>> {
self.slots
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn lock_format(&self) -> std::sync::MutexGuard<'_, SourceFormat> {
self.format
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
}
impl LayerBufferSource for ExternalDmaBufPool {
fn export_dma_buf(&mut self) -> Result<DmaBufDesc<'_>, SourceError> {
let format = *self.lock_format();
let imported = self.acquired.as_ref().ok_or(SourceError::Unsupported)?;
let (fds, offsets, pitches) = imported.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,
})
}
fn acquire(&mut self) -> Result<AcquiredBuffer, SourceError> {
let decision = self.presenter.acquire();
let result = match decision.kind {
Present::Fresh => self.fb_of(decision.key).map_or(
Err(SourceError::Failed(rustix::io::Errno::INVAL)),
|fb_id| {
Ok(AcquiredBuffer {
fb_id,
token: decision.token,
acquire_fence: decision.fence,
damage: decision.damage,
})
},
),
Present::Hold => {
self.fb_of(decision.key)
.map_or(Err(SourceError::WouldBlock), |fb_id| {
Ok(AcquiredBuffer {
fb_id,
token: decision.token,
..AcquiredBuffer::default()
})
})
}
Present::None => Err(SourceError::WouldBlock),
};
if result.is_ok() {
let handed_out = self
.lock_slots()
.get(&decision.key)
.map(|slot| Arc::clone(&slot.imported));
self.acquired = handed_out;
}
self.sweep();
result
}
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) {
if let Some(callback) = self.on_release.as_mut() {
callback(usize::try_from(key).unwrap_or(usize::MAX), release_fence);
}
self.sweep();
}
}
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.lock_format()
}
fn on_session_paused(&mut self) {
self.acquired = None;
for slot in self.lock_slots().values_mut() {
if let Some(imported) = Arc::get_mut(&mut slot.imported) {
imported.forget();
}
}
self.lock_slots().clear();
self.presenter.reset();
}
}