use std::{
ops::{Deref, DerefMut},
sync::Arc,
};
use offset_allocator::{Allocation, Allocator};
use tokio::sync::{Mutex, RwLock};
use vulkano::{
Validated,
buffer::{
AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer,
},
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter},
};
use crate::{Context, resources::buffer::StarlightSubBuffer};
#[derive(Debug)]
pub struct ResourceManager<T: BufferContents + Send + Copy> {
buffer_size: u64,
buffers: RwLock<Vec<Subbuffer<[T]>>>,
allocator: Mutex<Allocator>,
}
pub struct ResourceHandle<T: BufferContents + Send + Copy> {
buffer: StarlightSubBuffer<T>,
manager: Arc<ResourceManager<T>>,
alloc: Allocation,
}
impl<T: BufferContents + Send + std::fmt::Debug + Copy> std::fmt::Debug for ResourceHandle<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ResourceHandle").finish()
}
}
impl<T: BufferContents + Send + Copy> Deref for ResourceHandle<T> {
type Target = StarlightSubBuffer<T>;
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl<T: BufferContents + Send + Copy> DerefMut for ResourceHandle<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer
}
}
impl<T: BufferContents + Send + Copy> Drop for ResourceHandle<T> {
fn drop(&mut self) {
let alloc = self.alloc;
let manager = self.manager.clone();
tokio::spawn(async move {
manager.allocator.lock().await.free(alloc);
});
}
}
impl<T: BufferContents + Send + Copy> ResourceHandle<T> {
pub fn alloc_index(&self) -> u32 {
self.alloc.offset
}
}
impl<T: BufferContents + Send + Copy> ResourceManager<T> {
pub fn new(buffer_size: u64) -> Arc<Self> {
Self {
buffer_size,
buffers: Vec::new().into(),
allocator: Allocator::new(u32::MAX - 8).into(),
}
.into()
}
pub fn new_with_max(buffer_size: u64, max_items: u32) -> Arc<Self> {
Self {
buffer_size,
buffers: Vec::new().into(),
allocator: Allocator::new(max_items.min(u32::MAX - 8)).into(),
}
.into()
}
pub async fn alloc(
self: &Arc<Self>,
ctx: &Context,
data: T,
) -> Result<ResourceHandle<T>, Validated<AllocateBufferError>> {
let alloc = self.allocator.lock().await.allocate(1).unwrap();
let buffer_index = alloc.offset as u64 / self.buffer_size;
let buffer_offset = alloc.offset as u64 % self.buffer_size;
let mut buffers_read = self.buffers.read().await;
let new_buffers = (buffer_index + 1).saturating_sub(buffers_read.len() as u64);
if new_buffers > 0 {
drop(buffers_read);
let mut buffers_write = self.buffers.write().await;
for _ in 0..new_buffers {
let buf = match Buffer::new_slice(
ctx.alloc.clone(),
BufferCreateInfo {
usage: BufferUsage::SHADER_DEVICE_ADDRESS | BufferUsage::TRANSFER_DST,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
self.buffer_size,
) {
Ok(v) => v,
Err(err) => {
self.allocator.lock().await.free(alloc);
tracing::error!("failed to alloc gtransform buffer: {err}");
return Err(err);
}
};
buffers_write.push(buf);
}
drop(buffers_write);
buffers_read = self.buffers.read().await;
}
let buffer = buffers_read[buffer_index as usize]
.clone()
.index(buffer_offset);
let buffer = StarlightSubBuffer::from_subbuffer(ctx, buffer);
buffer
.new_wite_op()
.write(move |mut v| *v = data)
.await
.unwrap();
Ok(ResourceHandle {
buffer,
manager: self.clone(),
alloc,
})
}
}