Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ By @beholdnec in [#8505](https://github.com/gfx-rs/wgpu/pull/8505).

#### General

- Add `StagingBelt::finish_and_recall_on_submit`, a convenience that combines `finish` and `recall` by deferring the buffer re-map via `CommandEncoder::map_buffer_on_submit`, so no explicit `recall()` call is needed after submission. By @ruihe774.
- Implement `i16`/`u16` 16-bit integer support in WGSL shaders, gated behind `Features::SHADER_I16` and `enable wgpu_int16;`. Supported on Vulkan, Metal, and DX12 (SM 6.2+). By @JMS55 in [#9412](https://github.com/gfx-rs/wgpu/pull/9412).
- BLAS support for procedural AABB geometry (`BlasGeometrySizeDescriptors::AABBs`, `BlasAabbGeometry`, and related descriptors). By @dylanblokhuis in [#9290](https://github.com/gfx-rs/wgpu/pull/9290)
- Added "limit bucketing" functionality which can adjust adapter limits and features to match one of several pre-defined buckets. This is controlled by the new `apply_limit_buckets` member in `RequestAdapterOptions`, which is `false` by default. By @andyleiserson in [#9119](https://github.com/gfx-rs/wgpu/pull/9119).
Expand Down
25 changes: 20 additions & 5 deletions tests/tests/wgpu-validation/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
use nanorand::Rng;

/// Generate (deterministic) random staging belt operations to exercise its logic.
#[test]
fn staging_belt_random_test() {
fn staging_belt_random_test(use_recall_on_submit: bool) {
let (device, queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
let mut rng = nanorand::WyRand::new_seed(0xDEAD_BEEF);
let buffer_size = 1024;
Expand Down Expand Up @@ -35,12 +34,28 @@ fn staging_belt_random_test() {
slice.slice(..1).copy_from_slice(&[1]);
}

belt.finish();
queue.submit([encoder.finish()]);
belt.recall();
if use_recall_on_submit {
belt.finish_and_recall_on_submit(&encoder);
queue.submit([encoder.finish()]);
// No explicit recall() needed.
} else {
belt.finish();
queue.submit([encoder.finish()]);
belt.recall();
}
}
}

#[test]
fn staging_belt_manual_recall() {
staging_belt_random_test(false);
}

#[test]
fn staging_belt_finish_and_recall_on_submit() {
staging_belt_random_test(true);
}

#[test]
fn staging_belt_panics_with_invalid_buffer_usages() {
#[track_caller]
Expand Down
37 changes: 37 additions & 0 deletions wgpu/src/util/belt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ use crate::COPY_BUFFER_ALIGNMENT;
/// 3. Submit all command encoders that were used in step 1.
/// 4. Call [`StagingBelt::recall()`].
///
/// Alternatively, steps 2 and 4 can be combined into a single call to
/// [`StagingBelt::finish_and_recall_on_submit()`], which schedules the re-map
/// automatically when the encoder is submitted, so no explicit `recall()` is needed.
///
/// [`Queue::write_buffer_with()`]: crate::Queue::write_buffer_with
pub struct StagingBelt {
device: Device,
Expand Down Expand Up @@ -268,6 +272,39 @@ impl StagingBelt {
}
}

/// Convenience for [`StagingBelt::finish()`] followed by a deferred
/// [`StagingBelt::recall()`] that runs automatically when `encoder`'s command
/// buffer is submitted.
///
/// After calling this method, the staging belt's internal buffers will be
/// re-mapped for write once the submission completes, without requiring an
/// explicit call to [`StagingBelt::recall()`].
///
/// Like [`StagingBelt::recall()`], this method does not block.
///
/// # Important
///
/// `encoder` must be finished (via [`CommandEncoder::finish()`]) and the
/// resulting [`CommandBuffer`] must be submitted to the [`Queue`] **before**
/// the next call that needs free staging-belt chunks. If the encoder is
/// never submitted, the belt's closed chunks will not be returned and the
/// belt will allocate new buffers indefinitely.
Comment on lines +289 to +291
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems sketchy to me no? Is calling this and then not submitting the encoder just a guaranteed memory leak?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just does not provide more guarantee than finish; the semantic is similar. If finish is called without recall, the memory is leaked as well. Actually, finish_and_recall_on_submit is relatively safer as it's rarer to forget to submit a CommandEncoder than forgetting to call recall after submission.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is called without recall, surely you can still recall the memory later right? Is this any different? I may have misread your comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. And for finish_and_recall_on_submit, if it is called without Queue::submit, surely you can still recall the memory by submitting the CommandEncoder. The guarantee is just similar: for finish, reclaim is on recall; for this, reclaim is on submission. An edge case may be you encode commands but never submit them and deliberately drop them; but it's rare and in that case you can just use finish

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ruihe774 So then if you call recall on a command encoder that doesn't get submitted, you can never reclaim that memory without destroying the StagingBelt? Thats probably fine I guess

Copy link
Copy Markdown
Contributor Author

@ruihe774 ruihe774 May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be:

if you call finish_and_recall_on_submit on a command encoder that doesn't get submitted, you can never reclaim that memory without destroying the StagingBelt

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the same would happen if you drop the command encoder before submitting it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Dropped CommandEncoder never gets submitted. The name of this method explicitly states that: recall on submit

///
/// [`CommandBuffer`]: crate::CommandBuffer
/// [`Queue`]: crate::Queue
pub fn finish_and_recall_on_submit(&mut self, encoder: &CommandEncoder) {
self.finish();
self.receive_chunks();

for chunk in self.closed_chunks.drain(..) {
let sender = self.sender.get_mut().clone();
let buffer = chunk.buffer.clone();
encoder.map_buffer_on_submit(&buffer, MapMode::Write, .., move |_| {
let _ = sender.send(chunk);
});
}
}

/// Move all chunks that the GPU is done with (and are now mapped again)
/// from `self.receiver` to `self.free_chunks`.
fn receive_chunks(&mut self) {
Expand Down
Loading