use crate::caps::TierCaps;
use crate::context::DeviceHandle;
pub const ARENA_USAGE: wgpu::BufferUsages = wgpu::BufferUsages::UNIFORM
.union(wgpu::BufferUsages::VERTEX)
.union(wgpu::BufferUsages::COPY_DST);
pub const MIN_ARENA_CAPACITY: u64 = 64 * 1024;
const DEFAULT_LABEL: &str = "frust-gpu host arena";
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct BufferSlice {
pub offset: u64,
pub size: u64,
}
pub trait BufferUploader {
type Buffer;
fn allocate_buffer(
&self,
label: Option<&str>,
size: u64,
usage: wgpu::BufferUsages,
) -> Self::Buffer;
fn write_buffer(&self, buffer: &Self::Buffer, offset: u64, bytes: &[u8]);
}
impl BufferUploader for DeviceHandle {
type Buffer = wgpu::Buffer;
fn allocate_buffer(
&self,
label: Option<&str>,
size: u64,
usage: wgpu::BufferUsages,
) -> wgpu::Buffer {
self.device.create_buffer(&wgpu::BufferDescriptor {
label,
size,
usage,
mapped_at_creation: false,
})
}
fn write_buffer(&self, buffer: &wgpu::Buffer, offset: u64, bytes: &[u8]) {
self.queue.write_buffer(buffer, offset, bytes);
}
}
#[derive(Debug)]
pub struct HostBuffer<B = wgpu::Buffer> {
staging: Vec<u8>,
buffer: Option<B>,
capacity: u64,
high_water_mark: u64,
min_alignment: u32,
label: String,
buffer_allocations: u64,
flushes: u64,
}
impl<B> HostBuffer<B> {
pub fn new(caps: &TierCaps) -> Self {
Self::with_label(caps, DEFAULT_LABEL)
}
pub fn with_label(caps: &TierCaps, label: &str) -> Self {
Self {
staging: Vec::new(),
buffer: None,
capacity: 0,
high_water_mark: 0,
min_alignment: caps.min_uniform_buffer_offset_alignment.max(1),
label: label.to_string(),
buffer_allocations: 0,
flushes: 0,
}
}
pub fn alloc(&mut self, bytes: &[u8], align: u32) -> BufferSlice {
let align = align.max(self.min_alignment).max(1) as usize;
let offset = self.staging.len().next_multiple_of(align);
self.staging.resize(offset, 0);
self.staging.extend_from_slice(bytes);
self.high_water_mark = self.high_water_mark.max(self.staging.len() as u64);
BufferSlice {
offset: offset as u64,
size: bytes.len() as u64,
}
}
pub fn reset(&mut self) {
self.staging.clear();
}
pub fn flush<U>(&mut self, uploader: &U) -> Option<&B>
where
U: BufferUploader<Buffer = B>,
{
let len = self
.staging
.len()
.next_multiple_of(wgpu::COPY_BUFFER_ALIGNMENT as usize);
self.staging.resize(len, 0);
if len == 0 {
return self.buffer.as_ref();
}
if self.buffer.is_none() || self.capacity < len as u64 {
let capacity = grown_capacity(self.capacity, len as u64);
self.buffer = Some(uploader.allocate_buffer(Some(&self.label), capacity, ARENA_USAGE));
self.capacity = capacity;
self.buffer_allocations += 1;
}
self.flushes += 1;
let buffer = self
.buffer
.as_ref()
.expect("the arena buffer exists once a frame has bytes");
uploader.write_buffer(buffer, 0, &self.staging);
Some(buffer)
}
pub fn buffer(&self) -> Option<&B> {
self.buffer.as_ref()
}
pub fn used(&self) -> u64 {
self.staging.len() as u64
}
pub fn capacity(&self) -> u64 {
self.capacity
}
pub fn high_water_mark(&self) -> u64 {
self.high_water_mark
}
pub fn buffer_allocations(&self) -> u64 {
self.buffer_allocations
}
pub fn flush_count(&self) -> u64 {
self.flushes
}
pub fn min_alignment(&self) -> u32 {
self.min_alignment
}
}
fn grown_capacity(current: u64, required: u64) -> u64 {
current.max(
required
.checked_next_power_of_two()
.unwrap_or(required)
.max(MIN_ARENA_CAPACITY),
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::caps::DownlevelProfile;
#[derive(Debug, Default)]
struct FakeUploader {
created: std::cell::Cell<u32>,
writes: std::cell::Cell<u32>,
last_write_len: std::cell::Cell<usize>,
}
impl BufferUploader for FakeUploader {
type Buffer = u32;
fn allocate_buffer(&self, _label: Option<&str>, _size: u64, _u: wgpu::BufferUsages) -> u32 {
self.created.set(self.created.get() + 1);
self.created.get()
}
fn write_buffer(&self, _buffer: &u32, _offset: u64, bytes: &[u8]) {
self.writes.set(self.writes.get() + 1);
self.last_write_len.set(bytes.len());
}
}
fn arena() -> HostBuffer<u32> {
HostBuffer::new(&TierCaps::fake(DownlevelProfile::WebGl2))
}
#[test]
fn alloc_aligns_to_the_adapter_minimum() {
let mut arena = arena();
assert_eq!(arena.min_alignment(), 256);
let first = arena.alloc(&[1u8; 4], 1);
let second = arena.alloc(&[2u8; 300], 1);
let third = arena.alloc(&[3u8; 1], 1);
assert_eq!(first, BufferSlice { offset: 0, size: 4 });
assert_eq!(
second,
BufferSlice {
offset: 256,
size: 300
}
);
assert_eq!(
third,
BufferSlice {
offset: 768,
size: 1
}
);
}
#[test]
fn alloc_honors_a_larger_caller_alignment() {
let mut arena = arena();
arena.alloc(&[0u8; 1], 1);
let wide = arena.alloc(&[0u8; 8], 1024);
assert_eq!(wide.offset, 1024);
}
#[test]
fn reset_rewinds_and_keeps_the_high_water_mark() {
let mut arena = arena();
arena.alloc(&[0u8; 512], 1);
assert_eq!(arena.used(), 512);
assert_eq!(arena.high_water_mark(), 512);
arena.reset();
assert_eq!(arena.used(), 0);
assert_eq!(arena.high_water_mark(), 512);
arena.alloc(&[0u8; 16], 1);
assert_eq!(arena.alloc(&[0u8; 16], 1).offset, 256);
assert_eq!(arena.high_water_mark(), 512);
}
#[test]
fn flush_with_nothing_allocated_creates_no_buffer() {
let mut arena = arena();
let uploader = FakeUploader::default();
assert_eq!(arena.flush(&uploader), None);
assert_eq!(uploader.created.get(), 0);
assert_eq!(uploader.writes.get(), 0);
}
#[test]
fn flush_pads_the_upload_to_the_copy_alignment() {
let mut arena = arena();
let uploader = FakeUploader::default();
arena.alloc(&[7u8; 5], 1);
arena.flush(&uploader);
assert_eq!(uploader.last_write_len.get(), 8);
}
#[test]
fn growth_is_geometric_and_never_shrinks() {
assert_eq!(grown_capacity(0, 1), MIN_ARENA_CAPACITY);
assert_eq!(grown_capacity(0, MIN_ARENA_CAPACITY), MIN_ARENA_CAPACITY);
assert_eq!(
grown_capacity(MIN_ARENA_CAPACITY, MIN_ARENA_CAPACITY + 1),
2 * MIN_ARENA_CAPACITY
);
assert_eq!(
grown_capacity(4 * MIN_ARENA_CAPACITY, 8),
4 * MIN_ARENA_CAPACITY
);
}
}