use super::{
sys::{Buffer, BufferCreateInfo, RawBuffer},
BufferAccess, BufferAccessObject, BufferContents, BufferError, BufferInner, BufferUsage,
TypedBufferAccess,
};
use crate::{
buffer::sys::BufferMemory,
device::{Device, DeviceOwned},
memory::{
allocator::{
suballocator::align_up, AllocationCreateInfo, AllocationCreationError, AllocationType,
MemoryAllocatePreference, MemoryAllocator, MemoryUsage, StandardMemoryAllocator,
},
DedicatedAllocation,
},
DeviceSize, VulkanError,
};
use std::{
hash::{Hash, Hasher},
marker::PhantomData,
mem::{align_of, size_of},
ptr,
sync::{
atomic::{AtomicU64, Ordering},
Arc, Mutex, MutexGuard,
},
};
pub struct CpuBufferPool<T, A = StandardMemoryAllocator>
where
[T]: BufferContents,
A: MemoryAllocator + ?Sized,
{
allocator: Arc<A>,
current_buffer: Mutex<Option<Arc<ActualBuffer>>>,
buffer_usage: BufferUsage,
memory_usage: MemoryUsage,
marker: PhantomData<Box<T>>,
}
#[derive(Debug)]
struct ActualBuffer {
inner: Arc<Buffer>,
chunks_in_use: Mutex<Vec<ActualBufferChunk>>,
next_index: AtomicU64,
capacity: DeviceSize,
}
#[derive(Debug)]
struct ActualBufferChunk {
index: DeviceSize,
len: DeviceSize,
num_cpu_accesses: usize,
}
pub struct CpuBufferPoolChunk<T>
where
[T]: BufferContents,
{
buffer: Arc<ActualBuffer>,
index: DeviceSize,
align_offset: DeviceSize,
requested_len: DeviceSize,
marker: PhantomData<Box<T>>,
}
pub struct CpuBufferPoolSubbuffer<T>
where
[T]: BufferContents,
{
chunk: CpuBufferPoolChunk<T>,
}
impl<T, A> CpuBufferPool<T, A>
where
[T]: BufferContents,
A: MemoryAllocator + ?Sized,
{
pub fn new(
allocator: Arc<A>,
buffer_usage: BufferUsage,
memory_usage: MemoryUsage,
) -> CpuBufferPool<T, A> {
assert!(size_of::<T>() > 0);
assert!(memory_usage != MemoryUsage::GpuOnly);
CpuBufferPool {
allocator,
current_buffer: Mutex::new(None),
buffer_usage,
memory_usage,
marker: PhantomData,
}
}
pub fn upload(allocator: Arc<A>) -> CpuBufferPool<T, A> {
CpuBufferPool::new(
allocator,
BufferUsage {
transfer_src: true,
..BufferUsage::empty()
},
MemoryUsage::Upload,
)
}
pub fn download(allocator: Arc<A>) -> CpuBufferPool<T, A> {
CpuBufferPool::new(
allocator,
BufferUsage {
transfer_dst: true,
..BufferUsage::empty()
},
MemoryUsage::Download,
)
}
pub fn uniform_buffer(allocator: Arc<A>) -> CpuBufferPool<T, A> {
CpuBufferPool::new(
allocator,
BufferUsage {
uniform_buffer: true,
..BufferUsage::empty()
},
MemoryUsage::Upload,
)
}
pub fn vertex_buffer(allocator: Arc<A>) -> CpuBufferPool<T, A> {
CpuBufferPool::new(
allocator,
BufferUsage {
vertex_buffer: true,
..BufferUsage::empty()
},
MemoryUsage::Upload,
)
}
pub fn indirect_buffer(allocator: Arc<A>) -> CpuBufferPool<T, A> {
CpuBufferPool::new(
allocator,
BufferUsage {
indirect_buffer: true,
..BufferUsage::empty()
},
MemoryUsage::Upload,
)
}
}
impl<T, A> CpuBufferPool<T, A>
where
[T]: BufferContents,
A: MemoryAllocator + ?Sized,
{
pub fn capacity(&self) -> DeviceSize {
match *self.current_buffer.lock().unwrap() {
None => 0,
Some(ref buf) => buf.capacity,
}
}
pub fn reserve(&self, capacity: DeviceSize) -> Result<(), AllocationCreationError> {
if capacity == 0 {
return Ok(());
}
let mut cur_buf = self.current_buffer.lock().unwrap();
match *cur_buf {
Some(ref buf) if buf.capacity >= capacity => {
return Ok(());
}
_ => (),
};
self.reset_buf(&mut cur_buf, capacity)
}
pub fn from_data(
&self,
data: T,
) -> Result<Arc<CpuBufferPoolSubbuffer<T>>, AllocationCreationError> {
Ok(Arc::new(CpuBufferPoolSubbuffer {
chunk: self.chunk_impl([data].into_iter())?,
}))
}
pub fn from_iter<I>(
&self,
iter: I,
) -> Result<Arc<CpuBufferPoolChunk<T>>, AllocationCreationError>
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
{
self.chunk_impl(iter.into_iter()).map(Arc::new)
}
fn chunk_impl(
&self,
data: impl ExactSizeIterator<Item = T>,
) -> Result<CpuBufferPoolChunk<T>, AllocationCreationError> {
let mut mutex = self.current_buffer.lock().unwrap();
let data = match self.try_next_impl(&mut mutex, data) {
Ok(n) => return Ok(n),
Err(d) => d,
};
let next_capacity = match *mutex {
Some(ref b) if (data.len() as DeviceSize) < b.capacity => 2 * b.capacity,
_ => 2 * data.len().max(1) as DeviceSize,
};
self.reset_buf(&mut mutex, next_capacity)?;
match self.try_next_impl(&mut mutex, data) {
Ok(n) => Ok(n),
Err(_) => unreachable!(),
}
}
pub fn try_next(&self, data: T) -> Option<Arc<CpuBufferPoolSubbuffer<T>>> {
let mut mutex = self.current_buffer.lock().unwrap();
self.try_next_impl(&mut mutex, [data])
.map(|c| Arc::new(CpuBufferPoolSubbuffer { chunk: c }))
.ok()
}
fn reset_buf(
&self,
cur_buf_mutex: &mut MutexGuard<'_, Option<Arc<ActualBuffer>>>,
capacity: DeviceSize,
) -> Result<(), AllocationCreationError> {
let size = match (size_of::<T>() as DeviceSize).checked_mul(capacity) {
Some(s) => s,
None => {
return Err(AllocationCreationError::VulkanError(
VulkanError::OutOfDeviceMemory,
))
}
};
let raw_buffer = RawBuffer::new(
self.device().clone(),
BufferCreateInfo {
size,
usage: self.buffer_usage,
..Default::default()
},
)
.map_err(|err| match err {
BufferError::AllocError(err) => err,
_ => unreachable!(),
})?;
let requirements = *raw_buffer.memory_requirements();
let create_info = AllocationCreateInfo {
requirements,
allocation_type: AllocationType::Linear,
usage: self.memory_usage,
allocate_preference: MemoryAllocatePreference::Unknown,
dedicated_allocation: Some(DedicatedAllocation::Buffer(&raw_buffer)),
..Default::default()
};
match unsafe { self.allocator.allocate_unchecked(create_info) } {
Ok(mut alloc) => {
debug_assert!(alloc.offset() % requirements.alignment == 0);
debug_assert!(alloc.size() == requirements.size);
alloc.shrink(size);
let inner = unsafe {
Arc::new(
raw_buffer
.bind_memory_unchecked(alloc)
.map_err(|(err, _, _)| err)?,
)
};
**cur_buf_mutex = Some(Arc::new(ActualBuffer {
inner,
chunks_in_use: Mutex::new(vec![]),
next_index: AtomicU64::new(0),
capacity,
}));
Ok(())
}
Err(err) => Err(err),
}
}
fn try_next_impl<I>(
&self,
cur_buf_mutex: &mut MutexGuard<'_, Option<Arc<ActualBuffer>>>,
data: I,
) -> Result<CpuBufferPoolChunk<T>, I::IntoIter>
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
{
let mut data = data.into_iter();
let current_buffer = match cur_buf_mutex.clone() {
Some(b) => b,
None => return Err(data),
};
let mut chunks_in_use = current_buffer.chunks_in_use.lock().unwrap();
debug_assert!(!chunks_in_use.iter().any(|c| c.len == 0));
let requested_len = data.len() as DeviceSize;
if requested_len == 0 {
assert!(
data.next().is_none(),
"Expected iterator passed to CpuBufferPool::chunk to be empty"
);
return Ok(CpuBufferPoolChunk {
buffer: current_buffer.clone(),
index: 0,
align_offset: 0,
requested_len: 0,
marker: PhantomData,
});
}
let allocation = match current_buffer.inner.memory() {
BufferMemory::Normal(a) => a,
BufferMemory::Sparse => unreachable!(),
};
let (index, occupied_len, align_offset) = {
let (tentative_index, tentative_len, tentative_align_offset) = {
let idx = current_buffer.next_index.load(Ordering::SeqCst);
let align_uniform = if self.buffer_usage.uniform_buffer {
self.device()
.physical_device()
.properties()
.min_uniform_buffer_offset_alignment
} else {
1
};
let align_storage = if self.buffer_usage.storage_buffer {
self.device()
.physical_device()
.properties()
.min_storage_buffer_offset_alignment
} else {
1
};
let mut align_bytes = align_uniform
.max(align_storage)
.max(align_of::<T>() as DeviceSize);
if let Some(atom_size) = allocation.atom_size() {
align_bytes = DeviceSize::max(align_bytes, atom_size.get());
}
let tentative_align_offset = (align_bytes
- ((idx * size_of::<T>() as DeviceSize) % align_bytes))
% align_bytes;
let additional_len = if tentative_align_offset == 0 {
0
} else {
1 + (tentative_align_offset - 1) / size_of::<T>() as DeviceSize
};
(idx, requested_len + additional_len, tentative_align_offset)
};
if tentative_index + tentative_len <= current_buffer.capacity
&& !chunks_in_use.iter().any(|c| {
(c.index >= tentative_index && c.index < tentative_index + tentative_len)
|| (c.index <= tentative_index && c.index + c.len > tentative_index)
})
{
(tentative_index, tentative_len, tentative_align_offset)
} else {
if requested_len <= current_buffer.capacity
&& !chunks_in_use.iter().any(|c| c.index < requested_len)
{
(0, requested_len, 0)
} else {
return Err(data);
}
}
};
unsafe {
let mut range = (index * size_of::<T>() as DeviceSize + align_offset)
..((index + requested_len) * size_of::<T>() as DeviceSize + align_offset);
let bytes = allocation.write(range.clone()).unwrap();
let mapping = <[T]>::from_bytes_mut(bytes).unwrap();
let mut written = 0;
for (o, i) in mapping.iter_mut().zip(data) {
ptr::write(o, i);
written += 1;
}
if let Some(atom_size) = allocation.atom_size() {
range.end =
DeviceSize::min(align_up(range.end, atom_size.get()), allocation.size());
allocation.flush_range(range).unwrap();
}
assert_eq!(
written, requested_len,
"Iterator passed to CpuBufferPool::chunk has a mismatch between reported \
length and actual number of elements"
);
}
current_buffer
.next_index
.store(index + occupied_len, Ordering::SeqCst);
chunks_in_use.push(ActualBufferChunk {
index,
len: occupied_len,
num_cpu_accesses: 1,
});
Ok(CpuBufferPoolChunk {
buffer: current_buffer.clone(),
index,
align_offset,
requested_len,
marker: PhantomData,
})
}
}
impl<T, A> Clone for CpuBufferPool<T, A>
where
[T]: BufferContents,
A: MemoryAllocator + ?Sized,
{
fn clone(&self) -> Self {
let buf = self.current_buffer.lock().unwrap();
CpuBufferPool {
allocator: self.allocator.clone(),
current_buffer: Mutex::new(buf.clone()),
buffer_usage: self.buffer_usage,
memory_usage: self.memory_usage,
marker: PhantomData,
}
}
}
unsafe impl<T, A> DeviceOwned for CpuBufferPool<T, A>
where
[T]: BufferContents,
A: MemoryAllocator + ?Sized,
{
fn device(&self) -> &Arc<Device> {
self.allocator.device()
}
}
impl<T> Clone for CpuBufferPoolChunk<T>
where
[T]: BufferContents,
{
fn clone(&self) -> CpuBufferPoolChunk<T> {
let mut chunks_in_use_lock = self.buffer.chunks_in_use.lock().unwrap();
let chunk = chunks_in_use_lock
.iter_mut()
.find(|c| c.index == self.index)
.unwrap();
debug_assert!(chunk.num_cpu_accesses >= 1);
chunk.num_cpu_accesses = chunk
.num_cpu_accesses
.checked_add(1)
.expect("Overflow in CPU accesses");
CpuBufferPoolChunk {
buffer: self.buffer.clone(),
index: self.index,
align_offset: self.align_offset,
requested_len: self.requested_len,
marker: PhantomData,
}
}
}
unsafe impl<T> BufferAccess for CpuBufferPoolChunk<T>
where
T: Send + Sync,
[T]: BufferContents,
{
fn inner(&self) -> BufferInner<'_> {
BufferInner {
buffer: &self.buffer.inner,
offset: self.index * size_of::<T>() as DeviceSize + self.align_offset,
}
}
fn size(&self) -> DeviceSize {
self.requested_len * size_of::<T>() as DeviceSize
}
}
impl<T> BufferAccessObject for Arc<CpuBufferPoolChunk<T>>
where
T: Send + Sync,
[T]: BufferContents,
{
fn as_buffer_access_object(&self) -> Arc<dyn BufferAccess> {
self.clone()
}
}
impl<T> Drop for CpuBufferPoolChunk<T>
where
[T]: BufferContents,
{
fn drop(&mut self) {
if self.requested_len == 0 {
return;
}
let mut chunks_in_use_lock = self.buffer.chunks_in_use.lock().unwrap();
let chunk_num = chunks_in_use_lock
.iter_mut()
.position(|c| c.index == self.index)
.unwrap();
if chunks_in_use_lock[chunk_num].num_cpu_accesses >= 2 {
chunks_in_use_lock[chunk_num].num_cpu_accesses -= 1;
} else {
chunks_in_use_lock.remove(chunk_num);
}
}
}
unsafe impl<T> TypedBufferAccess for CpuBufferPoolChunk<T>
where
T: Send + Sync,
[T]: BufferContents,
{
type Content = [T];
}
unsafe impl<T> DeviceOwned for CpuBufferPoolChunk<T>
where
[T]: BufferContents,
{
fn device(&self) -> &Arc<Device> {
self.buffer.inner.device()
}
}
impl<T> PartialEq for CpuBufferPoolChunk<T>
where
T: Send + Sync,
[T]: BufferContents,
{
fn eq(&self, other: &Self) -> bool {
self.inner() == other.inner() && self.size() == other.size()
}
}
impl<T> Eq for CpuBufferPoolChunk<T>
where
T: Send + Sync,
[T]: BufferContents,
{
}
impl<T> Hash for CpuBufferPoolChunk<T>
where
T: Send + Sync,
[T]: BufferContents,
{
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner().hash(state);
self.size().hash(state);
}
}
impl<T> Clone for CpuBufferPoolSubbuffer<T>
where
[T]: BufferContents,
{
fn clone(&self) -> CpuBufferPoolSubbuffer<T> {
CpuBufferPoolSubbuffer {
chunk: self.chunk.clone(),
}
}
}
unsafe impl<T> BufferAccess for CpuBufferPoolSubbuffer<T>
where
T: Send + Sync,
[T]: BufferContents,
{
fn inner(&self) -> BufferInner<'_> {
self.chunk.inner()
}
fn size(&self) -> DeviceSize {
self.chunk.size()
}
}
impl<T> BufferAccessObject for Arc<CpuBufferPoolSubbuffer<T>>
where
T: Send + Sync,
[T]: BufferContents,
{
fn as_buffer_access_object(&self) -> Arc<dyn BufferAccess> {
self.clone()
}
}
unsafe impl<T> TypedBufferAccess for CpuBufferPoolSubbuffer<T>
where
T: BufferContents,
[T]: BufferContents,
{
type Content = T;
}
unsafe impl<T> DeviceOwned for CpuBufferPoolSubbuffer<T>
where
[T]: BufferContents,
{
fn device(&self) -> &Arc<Device> {
self.chunk.buffer.inner.device()
}
}
impl<T> PartialEq for CpuBufferPoolSubbuffer<T>
where
T: Send + Sync,
[T]: BufferContents,
{
fn eq(&self, other: &Self) -> bool {
self.inner() == other.inner() && self.size() == other.size()
}
}
impl<T> Eq for CpuBufferPoolSubbuffer<T>
where
T: Send + Sync,
[T]: BufferContents,
{
}
impl<T> Hash for CpuBufferPoolSubbuffer<T>
where
T: Send + Sync,
[T]: BufferContents,
{
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner().hash(state);
self.size().hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem;
#[test]
fn basic_create() {
let (device, _) = gfx_dev_and_queue!();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let _ = CpuBufferPool::<u8>::upload(memory_allocator);
}
#[test]
fn reserve() {
let (device, _) = gfx_dev_and_queue!();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let pool = CpuBufferPool::<u8>::upload(memory_allocator);
assert_eq!(pool.capacity(), 0);
pool.reserve(83).unwrap();
assert_eq!(pool.capacity(), 83);
}
#[test]
fn capacity_increase() {
let (device, _) = gfx_dev_and_queue!();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let pool = CpuBufferPool::upload(memory_allocator);
assert_eq!(pool.capacity(), 0);
pool.from_data(12).unwrap();
let first_cap = pool.capacity();
assert!(first_cap >= 1);
for _ in 0..first_cap + 5 {
mem::forget(pool.from_data(12).unwrap());
}
assert!(pool.capacity() > first_cap);
}
#[test]
fn reuse_subbuffers() {
let (device, _) = gfx_dev_and_queue!();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let pool = CpuBufferPool::upload(memory_allocator);
assert_eq!(pool.capacity(), 0);
let mut capacity = None;
for _ in 0..64 {
pool.from_data(12).unwrap();
let new_cap = pool.capacity();
assert!(new_cap >= 1);
match capacity {
None => capacity = Some(new_cap),
Some(c) => assert_eq!(c, new_cap),
}
}
}
#[test]
fn chunk_loopback() {
let (device, _) = gfx_dev_and_queue!();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let pool = CpuBufferPool::<u8>::upload(memory_allocator);
pool.reserve(5).unwrap();
let a = pool.from_iter(vec![0, 0]).unwrap();
let b = pool.from_iter(vec![0, 0]).unwrap();
assert_eq!(b.index, 2);
drop(a);
let c = pool.from_iter(vec![0, 0]).unwrap();
assert_eq!(c.index, 0);
assert_eq!(pool.capacity(), 5);
}
#[test]
fn chunk_0_elems_doesnt_pollute() {
let (device, _) = gfx_dev_and_queue!();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let pool = CpuBufferPool::<u8>::upload(memory_allocator);
let _ = pool.from_iter(vec![]).unwrap();
let _ = pool.from_iter(vec![0, 0]).unwrap();
}
}