use std::sync::Arc;
use wgpu::Buffer;
pub(crate) struct GpuArena {
chunks: Vec<Arc<Buffer>>,
chunk_caps: Vec<u64>,
cur_chunk: usize,
cur_used: u64,
chunk_size: u64,
alignment: u64,
usage: wgpu::BufferUsages,
label: &'static str,
}
impl GpuArena {
pub fn new(
device: &wgpu::Device,
chunk_size: u64,
alignment: u64,
usage: wgpu::BufferUsages,
label: &'static str,
) -> Self {
debug_assert!(
alignment.is_power_of_two(),
"GpuArena alignment must be a power of two"
);
let buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size: chunk_size,
usage: usage | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Self {
chunks: vec![Arc::new(buf)],
chunk_caps: vec![chunk_size],
cur_chunk: 0,
cur_used: 0,
chunk_size,
alignment,
usage,
label,
}
}
pub fn begin_frame(&mut self) {
self.cur_chunk = 0;
self.cur_used = 0;
}
pub fn alloc(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
data: &[u8],
) -> (Arc<Buffer>, u64, u64) {
let size = data.len() as u64;
let aligned = round_up(size, self.alignment);
if self.cur_used + aligned > self.chunk_caps[self.cur_chunk] {
self.cur_chunk += 1;
self.cur_used = 0;
let needed = aligned.max(self.chunk_size);
if self.cur_chunk >= self.chunks.len() {
let buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(self.label),
size: needed,
usage: self.usage | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.chunks.push(Arc::new(buf));
self.chunk_caps.push(needed);
} else if self.chunk_caps[self.cur_chunk] < needed {
let buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(self.label),
size: needed,
usage: self.usage | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.chunks[self.cur_chunk] = Arc::new(buf);
self.chunk_caps[self.cur_chunk] = needed;
}
}
let offset = self.cur_used;
self.cur_used += aligned;
let buf = Arc::clone(&self.chunks[self.cur_chunk]);
queue.write_buffer(&buf, offset, data);
(buf, offset, size)
}
}
pub(crate) struct FrameArenas {
pub vertex: GpuArena,
pub index: GpuArena,
pub uniform: GpuArena,
}
impl FrameArenas {
pub fn new(device: &wgpu::Device) -> Self {
let uniform_align = device.limits().min_uniform_buffer_offset_alignment as u64;
let chunk = 256 * 1024;
Self {
vertex: GpuArena::new(
device,
chunk,
wgpu::COPY_BUFFER_ALIGNMENT,
wgpu::BufferUsages::VERTEX,
"frame-vertex-arena",
),
index: GpuArena::new(
device,
chunk,
wgpu::COPY_BUFFER_ALIGNMENT,
wgpu::BufferUsages::INDEX,
"frame-index-arena",
),
uniform: GpuArena::new(
device,
chunk,
uniform_align,
wgpu::BufferUsages::UNIFORM,
"frame-uniform-arena",
),
}
}
pub fn begin_frame(&mut self) {
self.vertex.begin_frame();
self.index.begin_frame();
self.uniform.begin_frame();
}
}
#[inline]
fn round_up(n: u64, align: u64) -> u64 {
(n + align - 1) & !(align - 1)
}
#[cfg(test)]
mod tests {
use super::round_up;
#[test]
fn round_up_basics() {
assert_eq!(round_up(0, 4), 0);
assert_eq!(round_up(1, 4), 4);
assert_eq!(round_up(4, 4), 4);
assert_eq!(round_up(5, 4), 8);
assert_eq!(round_up(255, 256), 256);
assert_eq!(round_up(256, 256), 256);
assert_eq!(round_up(257, 256), 512);
}
}