use edgefirst_tensor::{PixelFormat, Tensor, TensorTrait};
#[derive(Debug, PartialEq)]
pub(super) enum CacheKind {
Src,
Dst,
}
pub(super) struct CachedImport<I> {
pub(super) import: I,
pub(super) guard: std::sync::Weak<()>,
pub(super) renderbuffer: Option<u32>,
pub(super) last_used: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) struct BufferImportKey {
pub(super) luma_id: u64,
pub(super) chroma_id: Option<u64>,
pub(super) plane_offset: usize,
pub(super) width: usize,
pub(super) height: usize,
pub(super) row_stride: usize,
pub(super) format: PixelFormat,
}
impl BufferImportKey {
pub(super) fn from_tensor<T>(img: &Tensor<T>, format: PixelFormat, for_dst: bool) -> Self
where
T: num_traits::Num + Clone + std::fmt::Debug + Send + Sync,
{
let view_origin = if for_dst { img.view_origin() } else { None };
let (width, height, row_stride, plane_offset) = match view_origin {
Some(vo) => (vo.parent_width, vo.parent_height, vo.parent_row_stride, 0),
None => (
img.width().unwrap_or(0),
img.height().unwrap_or(0),
img.effective_row_stride().unwrap_or(0),
img.plane_offset().unwrap_or(0),
),
};
Self {
luma_id: img.buffer_identity().id(),
chroma_id: img.chroma().map(|t| t.buffer_identity().id()),
plane_offset,
width,
height,
row_stride,
format,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CacheStats {
pub hits: u64,
pub misses: u64,
pub entries: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GlCacheStats {
pub src: CacheStats,
pub dst: CacheStats,
pub nv_r8: CacheStats,
}
impl GlCacheStats {
pub fn total_misses(&self) -> u64 {
self.src.misses + self.dst.misses + self.nv_r8.misses
}
}
pub(super) struct ImportCache<I> {
pub(super) entries: std::collections::HashMap<BufferImportKey, CachedImport<I>>,
pub(super) capacity: usize,
pub(super) hits: u64,
pub(super) misses: u64,
pub(super) access_counter: u64,
}
impl<I> ImportCache<I> {
pub(super) fn new(capacity: usize) -> Self {
Self {
entries: std::collections::HashMap::with_capacity(capacity),
capacity,
hits: 0,
misses: 0,
access_counter: 0,
}
}
pub(super) fn stats(&self) -> CacheStats {
CacheStats {
hits: self.hits,
misses: self.misses,
entries: self.entries.len(),
}
}
pub(super) fn next_timestamp(&mut self) -> u64 {
self.access_counter += 1;
self.access_counter
}
pub(super) fn evict_lru(&mut self) -> bool {
if let Some((&evict_id, _)) = self.entries.iter().min_by_key(|(_, entry)| entry.last_used) {
let evicted = self.entries.remove(&evict_id).expect("key just found");
if let Some(rbo) = evicted.renderbuffer {
unsafe { edgefirst_gl::gl::DeleteRenderbuffers(1, &rbo) };
}
return true;
}
false
}
pub(super) fn sweep(&mut self) -> bool {
let before = self.entries.len();
self.entries.retain(|_id, entry| {
let alive = entry.guard.upgrade().is_some();
if !alive {
if let Some(rbo) = entry.renderbuffer {
unsafe { edgefirst_gl::gl::DeleteRenderbuffers(1, &rbo) };
}
}
alive
});
let swept = before - self.entries.len();
if swept > 0 {
log::debug!("ImportCache: swept {swept} dead entries");
}
swept > 0
}
}
impl<I> Drop for ImportCache<I> {
fn drop(&mut self) {
for entry in self.entries.values() {
if let Some(rbo) = entry.renderbuffer {
unsafe { edgefirst_gl::gl::DeleteRenderbuffers(1, &rbo) };
}
}
log::debug!(
"ImportCache stats: {} hits, {} misses, {} entries remaining",
self.hits,
self.misses,
self.entries.len()
);
}
}
#[cfg(test)]
mod tests {
use super::BufferImportKey;
use edgefirst_tensor::PixelFormat;
use std::collections::HashMap;
fn key(
luma_id: u64,
plane_offset: usize,
width: usize,
height: usize,
row_stride: usize,
format: PixelFormat,
) -> BufferImportKey {
BufferImportKey {
luma_id,
chroma_id: None,
plane_offset,
width,
height,
row_stride,
format,
}
}
#[test]
fn cache_key_distinguishes_foreign_plane_offset() {
let mut map: HashMap<BufferImportKey, u32> = HashMap::new();
let base = key(0xABCD, 0, 64, 64, 64, PixelFormat::Grey);
let at_offset = key(0xABCD, 4096, 64, 64, 64, PixelFormat::Grey);
map.insert(base, 1);
map.insert(at_offset, 2);
assert_eq!(
map.len(),
2,
"offset-distinct foreign imports must not collide"
);
assert_eq!(map.get(&base), Some(&1));
assert_eq!(map.get(&at_offset), Some(&2));
map.insert(base, 3);
assert_eq!(map.len(), 2);
assert_eq!(map.get(&base), Some(&3));
}
#[test]
fn cache_key_collapses_sibling_views() {
use edgefirst_tensor::{Region, Tensor, TensorMemory};
let parent =
Tensor::<u8>::image(64, 64, PixelFormat::Rgba, Some(TensorMemory::Mem)).unwrap();
let a = parent.view(Region::new(0, 0, 32, 32)).unwrap();
let b = parent.view(Region::new(0, 32, 32, 32)).unwrap();
let ka = BufferImportKey::from_tensor(&a, PixelFormat::Rgba, true);
let kb = BufferImportKey::from_tensor(&b, PixelFormat::Rgba, true);
let kp = BufferImportKey::from_tensor(&parent, PixelFormat::Rgba, true);
assert_eq!(
ka, kb,
"sibling dst views collapse to one parent-keyed import"
);
assert_eq!(ka, kp, "a dst view keys identically to its whole parent");
assert_eq!(
ka.plane_offset, 0,
"a dst view contributes no offset to the key"
);
assert_eq!((ka.width, ka.height), (64, 64), "keyed on parent geometry");
let sa = BufferImportKey::from_tensor(&a, PixelFormat::Rgba, false);
let sb = BufferImportKey::from_tensor(&b, PixelFormat::Rgba, false);
assert_ne!(sa, sb, "source views key on their own region (no collapse)");
assert_eq!(
(sa.width, sa.height),
(32, 32),
"a source view keys on its own dimensions"
);
}
#[test]
fn cache_key_distinguishes_geometry() {
let mut map: HashMap<BufferImportKey, u32> = HashMap::new();
let g0 = key(0xBEEF, 0, 128, 96, 128, PixelFormat::Grey);
let g1 = key(0xBEEF, 0, 96, 128, 96, PixelFormat::Grey); let g2 = key(0xBEEF, 0, 128, 96, 128, PixelFormat::Nv12); map.insert(g0, 1);
map.insert(g1, 2);
map.insert(g2, 3);
assert_eq!(
map.len(),
3,
"geometry/format-distinct reuses must not collide"
);
let g0_again = key(0xBEEF, 0, 128, 96, 128, PixelFormat::Grey);
map.insert(g0_again, 4);
assert_eq!(map.len(), 3);
assert_eq!(map.get(&g0), Some(&4));
}
#[test]
fn cache_key_distinguishes_stride() {
let mut map: HashMap<BufferImportKey, u32> = HashMap::new();
let tight = key(0xBEEF, 0, 128, 96, 128, PixelFormat::Grey);
let padded = key(0xBEEF, 0, 128, 96, 256, PixelFormat::Grey); map.insert(tight, 1);
map.insert(padded, 2);
assert_eq!(map.len(), 2, "stride-distinct imports must not collide");
assert_eq!(map.get(&tight), Some(&1));
assert_eq!(map.get(&padded), Some(&2));
}
}