Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.