use std::cell::RefCell;
use ash::vk;
use super::allocator::{DeviceAllocator, PooledBuffer};
use crate::gfx::fullscreen::align_up;
pub(in crate::vulkan) const UPLOAD_ALIGN: u64 = 256;
const UPLOAD_MIN_CAPACITY: u64 = 64 * 1024;
fn grow_capacity(capacity: u64, needed: u64) -> u64 {
let mut cap = capacity.max(UPLOAD_MIN_CAPACITY);
while cap < needed {
cap *= 2;
}
cap
}
struct Slot {
buffer: PooledBuffer,
capacity: u64,
cursor: u64,
}
impl Slot {
fn empty() -> Self {
Slot {
buffer: PooledBuffer::null(),
capacity: 0,
cursor: 0,
}
}
}
pub(in crate::vulkan) struct UploadRing {
slots: Vec<RefCell<Slot>>,
}
impl UploadRing {
pub(in crate::vulkan) fn new(frames: usize) -> Self {
UploadRing {
slots: (0..frames.max(1))
.map(|_| RefCell::new(Slot::empty()))
.collect(),
}
}
pub(in crate::vulkan) fn reserve(
&self,
alloc: &DeviceAllocator,
frame: usize,
needed: u64,
) -> Result<(), String> {
let mut slot = self.slots[frame % self.slots.len()].borrow_mut();
slot.cursor = 0;
if needed <= slot.capacity {
return Ok(());
}
let new_cap = grow_capacity(slot.capacity, needed);
let buffer = alloc.create_buffer(
new_cap,
vk::BufferUsageFlags::VERTEX_BUFFER | vk::BufferUsageFlags::INDEX_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
if buffer.mapped_ptr().is_null() {
return Err("text upload buffer is not host-mapped".to_string());
}
slot.buffer = buffer;
slot.capacity = new_cap;
Ok(())
}
pub(in crate::vulkan) fn push(
&self,
frame: usize,
bytes: &[u8],
) -> Result<(vk::Buffer, vk::DeviceSize), String> {
let mut slot = self.slots[frame % self.slots.len()].borrow_mut();
let offset = align_up(slot.cursor, UPLOAD_ALIGN);
let end = offset + bytes.len() as u64;
if end > slot.capacity {
return Err(format!(
"text upload ring overflow: need {end} bytes, reserved {}",
slot.capacity
));
}
slot.buffer.write_bytes(offset as usize, bytes);
slot.cursor = end;
Ok((slot.buffer.buffer(), offset))
}
pub(in crate::vulkan) fn destroy(&self) {
for slot in &self.slots {
*slot.borrow_mut() = Slot::empty();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grow_capacity_starts_at_minimum() {
assert_eq!(grow_capacity(0, 1), UPLOAD_MIN_CAPACITY);
assert_eq!(grow_capacity(0, 0), UPLOAD_MIN_CAPACITY);
}
#[test]
fn grow_capacity_doubles_until_it_fits() {
let need = UPLOAD_MIN_CAPACITY * 3 + 1;
let cap = grow_capacity(0, need);
assert!(cap >= need);
assert_eq!(cap, UPLOAD_MIN_CAPACITY * 4);
}
#[test]
fn grow_capacity_never_shrinks_below_existing() {
let cap = grow_capacity(UPLOAD_MIN_CAPACITY * 8, 10);
assert_eq!(cap, UPLOAD_MIN_CAPACITY * 8);
}
#[test]
fn reserved_bytes_bound_the_ring_cursor() {
let blocks: [u64; 5] = [128, 12, 4096, 1, 255];
let total: u64 = blocks.iter().map(|&n| align_up(n, UPLOAD_ALIGN)).sum();
let mut cursor = 0u64;
for &n in &blocks {
cursor = align_up(cursor, UPLOAD_ALIGN) + n;
assert!(cursor <= total, "cursor {cursor} exceeded reserved {total}");
}
}
}