bevyengine/bevy · critical

`Range` provided to `render_range()` is out of bounds

Error message

`Range` provided to `render_range()` is out of bounds

What it means

RenderPhase::render_range (mod.rs:1991) panics when the supplied range cannot index self.items: items.get_range(range) returned None because the range is out of bounds for the current item count. It is a hard precondition - the caller must pass a range that fits within 0..items.len() for the phase as it exists right now.

Source

Thrown at crates/bevy_render/src/render_phase/mod.rs:1990

        render_pass: &mut TrackedRenderPass<'w>,
        world: &'w World,
        view: Entity,
    ) -> Result<(), DrawError> {
        self.render_range(render_pass, world, view, ..)
    }

    /// Renders all [`PhaseItem`]s in the provided `range` (based on their index in `self.items`) using their corresponding draw functions.
    pub fn render_range<'w>(
        &self,
        render_pass: &mut TrackedRenderPass<'w>,
        world: &'w World,
        view: Entity,
        range: impl RangeBounds<usize>,
    ) -> Result<(), DrawError> {
        let items = self
            .items
            .get_range(range)
            .expect("`Range` provided to `render_range()` is out of bounds");

        let draw_functions = world.resource::<DrawFunctions<I>>();
        let mut draw_functions = draw_functions.write();
        draw_functions.prepare(world);

        let mut index = 0;
        while index < items.len() {
            let item = &items[index];
            let batch_range = item.batch_range();
            if batch_range.is_empty() {
                index += 1;
            } else {
                let draw_function = draw_functions.get_mut(item.draw_function()).unwrap();
                draw_function.draw(world, render_pass, view, item)?;
                index += batch_range.len();
            }
        }
        Ok(())

View on GitHub (pinned to 221e52ae32)

Solutions

  1. Clamp the range to the current length before calling: 0..n.min(phase.items.len()).
  2. Recompute any cached item counts after sorting/filtering/clearing the phase.
  3. Use RenderPhase::render() instead when you do not need sub-ranges.

Example fix

// before
let end = cached_item_count; // may be stale after the phase was rebuilt
render_phase.render_range(&mut pass, &world, view, 0..end)?;

// after
let end = cached_item_count.min(render_phase.items.len());
render_phase.render_range(&mut pass, &world, view, 0..end)?;
Defensive patterns

Strategy: validation

Validate before calling

// Clamp before calling render_range:
let end = requested_end.min(render_phase.items.len());
let start = requested_start.min(end);
render_phase.render_range(&mut pass, &world, view, start..end)?;

Prevention

When it happens

Trigger: Calling render_range(pass, world, view, 0..n) with n greater than phase.items.len() - typically a count cached from before the phase was sorted, compacted, or cleared, or chunk-splitting arithmetic that overshoots.

Common situations: Custom render nodes splitting a phase into chunks (stereo/XR per-eye slices); lengths captured across frames while the phase was rebuilt; off-by-one errors after filtering items out of the phase.

Related errors


AI-assisted analysis of bevyengine/bevy@221e52ae32 (2026-08-20). Data as JSON: /api/errors/bfa6e87f06837708. Report an issue: GitHub.