Buffers
Every buffer type here is opaque — there’s no way to reach a raw wgpu::Buffer from outside the crate. Binding one into a bind group goes through BindGroupBuilder directly; writing to one is a method call, not a queue.write_buffer(...) you have to thread a queue reference to.
A plain, uniform, or storage buffer
BufferBuilder — empty (write into it later) or pre-populated. Takes &WGPUBackend, not just a device, since the resulting Buffer caches its own queue access:
// Empty, written into later via `.write()`.
let camera_buffer = BufferBuilder::empty(64).label("camera").uniform().build(&backend);
// Pre-populated.
let vertex_buffer = BufferBuilder::with_data(bytemuck::cast_slice(&vertices))
.label("mesh vertices")
.usage(BufferUsages::VERTEX)
.build(&backend);
// Later, any time:
camera_buffer.write(&new_matrix_bytes); // whole buffer, offset 0
camera_buffer.write_at(offset, &partial_bytes); // starting at a byte offset
Two constructors — empty/with_data — rather than one new() plus a .size()/.data() setter pair, so there’s no way to call both and have whichever ran last silently win.
.uniform()/.storage() are shorthand for the usual UNIFORM | COPY_DST/STORAGE | COPY_DST flag pairs; use .usage(...) directly for anything else (vertex/index buffers, a MAP_READ staging buffer, an INDIRECT buffer — see Indirect Draws).
A dynamically-offset buffer (many elements, one buffer)
DynamicBufferBuilder — sized and aligned for count elements of element_size bytes, selected later via set_bind_group’s dynamic offset. Returns a DynamicBuffer bundling the buffer with its own stride and element size, so neither can drift out of sync with what it was actually built with:
let dynamic = DynamicBufferBuilder::uniform(element_size, count).build(&backend);
// ... later, per element:
dynamic.write_element(index, &element_bytes);
// ... at draw time:
pass.set_bind_group(0, &bind_group, &[index as u32 * dynamic.stride() as u32]);
Pair with BindingKind::dynamic_uniform_buffer for the layout and BindGroupBuilder::dynamic_buffer for the bind group — a large pool of per-object data (transforms, materials) selected by offset instead of one bind group per object.
Reading a buffer back to the CPU
Buffer::read()/read_as::<T>() copy the buffer’s current contents back, resolving asynchronously — the same async pattern used for any other background result:
fn start_readback(events: AsyncEventWriter<ReadbackDone>, buffer: Res<SomeGpuBuffer>) {
let future = buffer.0.read(); // buffer.0: pebble::wgpu::buffer::Buffer
events.spawn(async move { ReadbackDone(future.await) });
}
Works identically on native and web — see Async Systems and Background Tasks.