gfx-rs/wgpu · critical

Failed to get mapped range for staging belt buffer

Error message

Failed to get mapped range for staging belt buffer

What it means

StagingBelt::write_buffer returns a slice by mapping the belt's internal staging buffer with get_mapped_range_mut(); this expect fires if the WebGPU implementation refuses the map. Since the belt only ever maps buffers it created as MAP_READ|MAP_WRITE, this indicates the device or buffer is no longer in a mappable state (device lost/destroyed or a driver/map failure), rather than a normal user error.

Source

Thrown at wgpu/src/util/belt.rs:164

        assert!(
            offset.is_multiple_of(COPY_BUFFER_ALIGNMENT),
            "StagingBelt::write_buffer() offset {offset} must be a multiple of `COPY_BUFFER_ALIGNMENT`"
        );

        let slice_of_belt = self.allocate(
            size,
            const { BufferSize::new(crate::COPY_BUFFER_ALIGNMENT).unwrap() },
        );
        encoder.copy_buffer_to_buffer(
            slice_of_belt.buffer(),
            slice_of_belt.offset(),
            target,
            offset,
            size.get(),
        );
        slice_of_belt
            .get_mapped_range_mut()
            .expect("Failed to get mapped range for staging belt buffer")
    }

    /// Allocate a staging belt slice with the given `size` and `alignment` and return it.
    ///
    /// `size` must be a multiple of [`COPY_BUFFER_ALIGNMENT`]
    /// (as is required by the underlying buffer operations).
    ///
    /// To use this slice, call [`BufferSlice::get_mapped_range_mut()`] and write your data into
    /// that [`BufferViewMut`].
    /// (The view must be dropped before [`StagingBelt::finish()`] is called.)
    ///
    /// You can then record your own GPU commands to perform with the slice,
    /// such as copying it to a texture (whereas
    /// [`StagingBelt::write_buffer()`] can only write to other buffers).
    /// All commands involving this slice must be submitted after
    /// [`StagingBelt::finish()`] is called and before [`StagingBelt::recall()`] is called.
    ///
    /// If the `size` is greater than the space available in any free internal buffer, a new buffer

View on GitHub (pinned to 3e11ff59bf)

Solutions

  1. Recreate the Device (and a fresh StagingBelt) if the device was lost or destroyed, then retry the upload
  2. Guard upload paths: stop using the belt after receiving an uncaptured-error / device-lost callback
  3. Verify the StagingBelt is only used with buffers/encoders from the same live Device that created it
  4. Check teardown order so the device outlives all belt writes; drop the belt before the device
  5. If it reproduces on a specific platform/driver, update drivers and wgpu version; report a bug since the belt's own buffer should always be mappable

Example fix

// before
let bytes = belt.write_buffer(
    &encoder, &dest, offset, &size, &device,
);
bytes.copy_from_slice(data); // panics if device was lost
// after
if device.is_lost() {
    (device, belt) = recreate_device_and_belt(adapter);
}
let bytes = belt.write_buffer(
    &encoder, &dest, offset, &size, &device,
);
bytes.copy_from_slice(data);
Defensive patterns

Strategy: validation

Validate before calling

// before each frame's uploads:
if device.is_lost() {
    // recreate device + belt instead of writing to the belt
    (device, belt) = recreate();
}
assert!(!device.is_lost(), "device lost; cannot map staging belt");

Type guard

fn belt_usable(device: &Device) -> bool {
    !device.is_lost()
}

Try / catch

// map failures here surface as a Rust panic (expect), not a Result.
// In wasm/browser setups you can observe the underlying cause via the
// uncaptured error handler installed at device creation:
device.on_uncaptured_error(Box::new(|e| {
    log::error!("device error: {e:?}");
    device_lost.store(true, Ordering::SeqCst);
}));
// then check device_lost before calling belt.write_buffer.

Prevention

When it happens

Trigger: Calling belt.write_buffer (or via belt.recall/Encoder use) after the Device was lost or destroyed; the belt's chunk buffer was somehow unmapped or the device was dropped while the belt was still in use; an underlying WebGPU/driver map failure on the internal chunk buffer.

Common situations: Continuing to upload data through a StagingBelt after handling a device-loss event (uncaptured error callback, device.poll returning lost); holding a belt across a device re-creation on adapter reset; teardown-order bugs where the device is destroyed while frame upload code still runs.

Related errors


AI-assisted analysis of gfx-rs/wgpu@3e11ff59bf (2026-09-03). Data as JSON: /api/errors/2a46fafde386c828. Report an issue: GitHub.