use std::cell::RefCell;
use std::ffi::c_void;
use crate::types::{compute_length_bytes, compute_row_bytes, mip_buffer_size_bytes, BufferObj, ImageBuffer};
const ALLOC_GUARD_BYTES: usize = 64;
const MAX_CPU_BUFFER_ENTRIES: usize = 12;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
struct Key {
width: u32,
height: u32,
bytes_per_pixel: u32,
tag: u32,
mip_levels: u32,
}
struct OrderedLru {
entries: Vec<(Key, Vec<u8>)>,
capacity: usize,
}
impl OrderedLru {
fn new(capacity: usize) -> Self {
Self {
entries: Vec::with_capacity(capacity),
capacity,
}
}
fn promote(&mut self, key: &Key) -> bool {
if let Some(idx) = self.entries.iter().position(|(k, _)| k == key) {
let entry = self.entries.remove(idx);
self.entries.push(entry);
true
} else {
false
}
}
fn last_data_ptr(&mut self) -> *mut c_void {
self.entries.last_mut().unwrap().1.as_mut_ptr() as *mut c_void
}
fn insert(&mut self, key: Key, value: Vec<u8>) -> Option<(Key, usize)> {
let evicted = if self.entries.len() >= self.capacity {
let (k, v) = self.entries.remove(0);
Some((k, v.len()))
} else {
None
};
self.entries.push((key, value));
evicted
}
fn clear(&mut self) {
self.entries.clear();
}
}
thread_local! {
static CPU_CACHE: RefCell<OrderedLru> = RefCell::new(OrderedLru::new(MAX_CPU_BUFFER_ENTRIES));
}
pub fn get_or_create(width: u32, height: u32, bytes_per_pixel: u32, tag: u32) -> ImageBuffer {
get_or_create_with_mips(width, height, bytes_per_pixel, 1, tag)
}
pub fn get_or_create_with_mips(width: u32, height: u32, bytes_per_pixel: u32, mip_levels: u32, tag: u32) -> ImageBuffer {
let key = Key {
width,
height,
bytes_per_pixel,
tag,
mip_levels: mip_levels.max(1),
};
CPU_CACHE.with(|cache| {
let mut guard = cache.borrow_mut();
if guard.promote(&key) {
let raw = guard.last_data_ptr();
return ImageBuffer {
buf: BufferObj { raw },
width,
height,
bytes_per_pixel,
row_bytes: compute_row_bytes(width, bytes_per_pixel),
pitch_px: width,
};
}
let len = if mip_levels <= 1 {
compute_length_bytes(width, height, bytes_per_pixel) as usize
} else {
mip_buffer_size_bytes(width, height, bytes_per_pixel, mip_levels) as usize
};
let data = vec![0u8; len + ALLOC_GUARD_BYTES];
guard.insert(key, data);
let raw = guard.last_data_ptr();
ImageBuffer {
buf: BufferObj { raw },
width,
height,
bytes_per_pixel,
row_bytes: compute_row_bytes(width, bytes_per_pixel),
pitch_px: width,
}
})
}
pub fn cleanup() {
CPU_CACHE.with(|cache| {
cache.borrow_mut().clear();
});
}