bevyengine/bevy · error

Tried to read past the end of the buffer (start: {start}, si

Error message

Tried to read past the end of the buffer (start: {start}, size: {size}, buffer size: {full_size}).

What it means

The GPU readback system (driven by entities spawned with `GpuReadbackBundles::buffer_range(handle, offset, size)`) panics during preparation when the requested `start + size` exceeds the actual byte size of the backing `ShaderBuffer` SSBO. It is a host-side bounds check run before the GPU copy is queued.

Source

Thrown at crates/bevy_render/src/gpu_readback.rs:341

                            size: gpu_image.texture_descriptor.size,
                        },
                        buffer,
                        rx,
                        tx,
                    });
                }
            }
            Readback::Buffer {
                buffer,
                start_offset_and_size,
            } => {
                if let Some(ssbo) = ssbos.get(buffer) {
                    let full_size = ssbo.buffer.size();
                    let size = start_offset_and_size
                        .map(|(start, size)| {
                            let end = start + size;
                            if end > full_size {
                                panic!(
                                    "Tried to read past the end of the buffer (start: {start}, \
                                    size: {size}, buffer size: {full_size})."
                                );
                            }
                            size
                        })
                        .unwrap_or(full_size);
                    let buffer = buffer_pool.get(&render_device, size);
                    let (tx, rx) = async_channel::bounded(1);
                    readbacks.requested.push(GpuReadback {
                        entity: entity.id(),
                        src: ReadbackSource::Buffer {
                            start_offset_and_size: *start_offset_and_size,
                            buffer: ssbo.buffer.clone(),
                        },
                        buffer,
                        rx,
                        tx,

View on GitHub (pinned to 4805ca792c)

Solutions

  1. Use `GpuReadbackBundles::buffer(handle)` when you want the whole buffer
  2. Compute the subrange from live data at spawn time (e.g. `len * size_of::<T>()`) instead of hardcoding
  3. Clamp size to `buffer_size - start` before spawning the readback entity

Example fix

// before: buffer only holds 1024 bytes
commands.spawn(GpuReadbackBundles::buffer_range(handle, 0, 4096));

// after
commands.spawn(GpuReadbackBundles::buffer_range(handle, 0, 1024));
// or read the whole buffer:
commands.spawn(GpuReadbackBundles::buffer(handle));
Defensive patterns

Strategy: validation

Validate before calling

// before spawning a readback, clamp the range to the buffer's real size
if let Some(buffer) = shader_buffers.get(&handle) {
    let full = buffer.buffer().size();
    let size = size.min(full.saturating_sub(start));
    commands.spawn(GpuReadbackBundles::buffer_range(handle, start, size));
}

Prevention

When it happens

Trigger: Spawning a readback with `buffer_range(handle, start, size)` where `start + size > buffer.size()`: a hardcoded size larger than the buffer, a stale offset/size after the buffer shrank, or offsets computed in the wrong units.

Common situations: Periodic readbacks of a typed SSBO with a fixed byte size while the buffer contents grow/shrink; mixing element counts with byte offsets.

Related errors


AI-assisted analysis of bevyengine/bevy@4805ca792c (2026-08-20). Data as JSON: /api/errors/62ef5e1ebc807110. Report an issue: GitHub.