Expand description
A pool that packs many vertex streams into one buffer per layout.
Uploading a mesh’s vertices per buffer is wasteful: every wgpu::Buffer
carries driver overhead, and a draw can only bind a handful of them. A
VertexStreamPool instead owns one large buffer per vertex layout and
hands out element ranges inside them, so every mesh that shares a layout
shares a buffer.
§Why one allocator for every layout
Every vertex stream a draw binds reads the same element index: a draw’s
firstVertex, or its baseVertex plus an index, picks one element out of
each stream at once. So a mesh whose vertices live in a pool has to land at
the same element index in all of them, and one allocation has to cover all
of its streams.
That is why the pool hands out elements, not bytes, and keeps a single
allocator for every layout: one VertexAllocation is one range of
element indices, and each layout’s buffer is that many elements times its
stride. Giving each layout its own allocator would not do, because two
allocators asked for the same count in different units — bytes, scaled by
different strides — drift apart: the allocator’s size bins are a
logarithmic approximation, so n * 8 and n * 12 can round into different
bins and two streams of the same mesh end up at different indices, which
silently reads the wrong vertices.
A byte offset inside a layout’s buffer is its element index times that
layout’s stride. Since a stride is always a multiple of
[wgpu::VERTEX_ALIGNMENT], every offset is a multiple of it too, which is
what [wgpu::Queue::write_buffer] asks for.
§Example
use unlit_wgpu::specialize::{VertexAttributes, VertexBufferLayoutDesc};
use unlit_wgpu::vertex_pool::VertexStreamPool;
let (device, queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
let layout = VertexBufferLayoutDesc {
array_stride: 8,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: VertexAttributes::new(),
};
let mut pool = VertexStreamPool::new(
&device,
"example::vertices",
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1024,
);
let vertices = pool
.allocate(&device, &queue, &[layout.clone()], 3)
.expect("the pool has room");
let buffer = pool.buffer(&layout).expect("the layout has a buffer");
let offset = VertexStreamPool::byte_offset(&layout, vertices.allocation.offset);
queue.write_buffer(buffer, offset, &[0u8; 24]);
pool.release(vertices.allocation);Re-exports§
use core::num::NonZeroU32;🔒use hashbrown::HashMap;🔒use crate::offset_allocator::Allocation;🔒use crate::offset_allocator::Allocator;🔒use crate::offset_allocator::min_allocator_size;🔒use crate::specialize::VertexBufferLayoutDesc;🔒
Structs§
- Stream 🔒
- One layout’s buffer and how much of it is in use.
- Vertex
Allocation - One mesh’s vertices in a
VertexStreamPool: one range of element indices, shared by every stream the mesh binds. - Vertex
Stream Pool - One large buffer per vertex layout, sub-allocated into element ranges.
Constants§
- DEFAULT_
MAX_ 🔒ALLOCATIONS - How many allocations a pool tracks unless the caller says otherwise.
- ELEMENT_
ALIGNMENT 🔒 - The alignment the allocator hands out element indices at.
- MIN_
ELEMENTS 🔒 - The step an element count grows by, and the floor for the first one.
Functions§
- copy_
into_ 🔒larger - Replaces
bufferwith a larger one, copyingold_sizebytes across. - create_
buffer 🔒 - Creates a buffer of
sizebytes with the usages the pool needs. - growth_
step 🔒 - The element count to grow
currentto so it coverscount.