Skip to content

Commit

Permalink
Properly Deal with Timeouts (#7030)
Browse files Browse the repository at this point in the history
  • Loading branch information
cwfitzgerald authored Feb 14, 2025
1 parent f90f19c commit 7e11996
Show file tree
Hide file tree
Showing 74 changed files with 470 additions and 353 deletions.
62 changes: 61 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Bottom level categories:

## Unreleased

### Major Changes
### Major Features

#### Hashmaps Removed from APIs

Expand All @@ -51,6 +51,66 @@ also allows more easily creating these structures inline.

By @cwfitzgerald in [#7133](https://github.com/gfx-rs/wgpu/pull/7133)

#### `device.poll` Api Reworked

This release reworked the poll api significantly to allow polling to return errors when polling hits internal timeout limits.

`Maintain` was renamed `PollType`. Additionally, `poll` now returns a result containing information about what happened during the poll.

```diff
-pub fn wgpu::Device::poll(&self, maintain: wgpu::Maintain) -> wgpu::MaintainResult
+pub fn wgpu::Device::poll(&self, poll_type: wgpu::PollType) -> Result<wgpu::PollStatus, wgpu::PollError>

-device.poll(wgpu::Maintain::Poll);
+device.poll(wgpu::PollType::Poll).unwrap();
```

```rust
pub enum PollType<T> {
/// On wgpu-core based backends, block until the given submission has
/// completed execution, and any callbacks have been invoked.
///
/// On WebGPU, this has no effect. Callbacks are invoked from the
/// window event loop.
WaitForSubmissionIndex(T),
/// Same as WaitForSubmissionIndex but waits for the most recent submission.
Wait,
/// Check the device for a single time without blocking.
Poll,
}

pub enum PollStatus {
/// There are no active submissions in flight as of the beginning of the poll call.
/// Other submissions may have been queued on other threads during the call.
///
/// This implies that the given Wait was satisfied before the timeout.
QueueEmpty,

/// The requested Wait was satisfied before the timeout.
WaitSucceeded,

/// This was a poll.
Poll,
}

pub enum PollError {
/// The requested Wait timed out before the submission was completed.
Timeout,
}
```

> [!WARNING]
> As part of this change, WebGL's default behavior has changed. Previously `device.poll(Wait)` appeared as though it functioned correctly. This was a quirk caused by the bug that these PRs fixed. Now it will always return `Timeout` if the submission has not already completed. As many people rely on this behavior on WebGL, there is a new options in `BackendOptions`. If you want the old behavior, set the following on instance creation:
>
> ```rust
> instance_desc.backend_options.gl.fence_behavior = wgpu::GlFenceBehavior::AutoFinish;
> ```
>
> You will lose the ability to know exactly when a submission has completed, but `device.poll(Wait)` will behave the same as it does on native.
By @cwfitzgerald in [#6942](https://github.com/gfx-rs/wgpu/pull/6942).
By @cwfitzgerald in [#7030](https://github.com/gfx-rs/wgpu/pull/7030).
### New Features
#### General
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion benches/benches/bind_groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,11 @@ fn run_bench(ctx: &mut Criterion) {
duration += start.elapsed();

drop(bind_group);
state.device_state.device.poll(wgpu::Maintain::Wait);
state
.device_state
.device
.poll(wgpu::PollType::Wait)
.unwrap();
}

duration
Expand Down
18 changes: 15 additions & 3 deletions benches/benches/computepass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,11 @@ fn run_bench(ctx: &mut Criterion) {
duration += start.elapsed();
}

state.device_state.device.poll(wgpu::Maintain::Wait);
state
.device_state
.device
.poll(wgpu::PollType::Wait)
.unwrap();
}

duration
Expand Down Expand Up @@ -531,7 +535,11 @@ fn run_bench(ctx: &mut Criterion) {
duration += start.elapsed();

state.device_state.queue.submit(buffers);
state.device_state.device.poll(wgpu::Maintain::Wait);
state
.device_state
.device
.poll(wgpu::PollType::Wait)
.unwrap();
}

duration
Expand Down Expand Up @@ -573,7 +581,11 @@ fn run_bench(ctx: &mut Criterion) {
duration += start.elapsed();

state.device_state.queue.submit([buffer]);
state.device_state.device.poll(wgpu::Maintain::Wait);
state
.device_state
.device
.poll(wgpu::PollType::Wait)
.unwrap();
}

duration
Expand Down
18 changes: 15 additions & 3 deletions benches/benches/renderpass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,11 @@ fn run_bench(ctx: &mut Criterion) {
duration += start.elapsed();
}

state.device_state.device.poll(wgpu::Maintain::Wait);
state
.device_state
.device
.poll(wgpu::PollType::Wait)
.unwrap();
}

duration
Expand Down Expand Up @@ -535,7 +539,11 @@ fn run_bench(ctx: &mut Criterion) {
duration += start.elapsed();

state.device_state.queue.submit(buffers);
state.device_state.device.poll(wgpu::Maintain::Wait);
state
.device_state
.device
.poll(wgpu::PollType::Wait)
.unwrap();
}

duration
Expand Down Expand Up @@ -571,7 +579,11 @@ fn run_bench(ctx: &mut Criterion) {
duration += start.elapsed();

state.device_state.queue.submit([buffer]);
state.device_state.device.poll(wgpu::Maintain::Wait);
state
.device_state
.device
.poll(wgpu::PollType::Wait)
.unwrap();
}

duration
Expand Down
2 changes: 1 addition & 1 deletion benches/benches/resource_creation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ fn run_bench(ctx: &mut Criterion) {
drop(buffers);

state.queue.submit([]);
state.device.poll(wgpu::Maintain::Wait);
state.device.poll(wgpu::PollType::Wait).unwrap();
}

duration
Expand Down
2 changes: 1 addition & 1 deletion deno_webgpu/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl GPUBuffer {
while !*done.borrow() {
{
self.instance
.device_poll(self.device, wgpu_types::Maintain::wait())
.device_poll(self.device, wgpu_types::PollType::wait())
.unwrap();
}
tokio::time::sleep(Duration::from_millis(10)).await;
Expand Down
2 changes: 1 addition & 1 deletion deno_webgpu/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ impl GPUDevice {
#[fast]
fn stop_capture(&self) {
self.instance
.device_poll(self.id, wgpu_types::Maintain::wait())
.device_poll(self.id, wgpu_types::PollType::wait())
.unwrap();
self.instance.device_stop_capture(self.id);
}
Expand Down
4 changes: 1 addition & 3 deletions examples/features/src/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,9 +592,7 @@ impl<E: Example + wgpu::WasmNotSendSync> From<ExampleTestParams<E>>

let dst_buffer_slice = dst_buffer.slice(..);
dst_buffer_slice.map_async(wgpu::MapMode::Read, |_| ());
ctx.async_poll(wgpu::Maintain::wait())
.await
.panic_on_timeout();
ctx.async_poll(wgpu::PollType::wait()).await.unwrap();
let bytes = dst_buffer_slice.get_mapped_range().to_vec();

wgpu_test::image::compare_image_output(
Expand Down
2 changes: 1 addition & 1 deletion examples/features/src/hello_synchronization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ async fn get_data<T: bytemuck::Pod>(
let buffer_slice = staging_buffer.slice(..);
let (sender, receiver) = flume::bounded(1);
buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap());
device.poll(wgpu::Maintain::wait()).panic_on_timeout();
device.poll(wgpu::PollType::wait()).unwrap();
receiver.recv_async().await.unwrap().unwrap();
output.copy_from_slice(bytemuck::cast_slice(&buffer_slice.get_mapped_range()[..]));
staging_buffer.unmap();
Expand Down
2 changes: 1 addition & 1 deletion examples/features/src/hello_workgroups/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ async fn get_data<T: bytemuck::Pod>(
let buffer_slice = staging_buffer.slice(..);
let (sender, receiver) = flume::bounded(1);
buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap());
device.poll(wgpu::Maintain::wait()).panic_on_timeout();
device.poll(wgpu::PollType::wait()).unwrap();
receiver.recv_async().await.unwrap().unwrap();
output.copy_from_slice(bytemuck::cast_slice(&buffer_slice.get_mapped_range()[..]));
staging_buffer.unmap();
Expand Down
2 changes: 1 addition & 1 deletion examples/features/src/mipmap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ impl crate::framework::Example for Example {
.slice(..)
.map_async(wgpu::MapMode::Read, |_| ());
// Wait for device to be done rendering mipmaps
device.poll(wgpu::Maintain::wait()).panic_on_timeout();
device.poll(wgpu::PollType::wait()).unwrap();
// This is guaranteed to be ready.
let timestamp_view = query_sets
.mapping_buffer
Expand Down
2 changes: 1 addition & 1 deletion examples/features/src/ray_shadows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ impl crate::framework::Example for Example {
rpass.draw_indexed(0..12, 0, 0..1);
}
queue.submit(Some(encoder.finish()));
device.poll(wgpu::Maintain::Wait);
device.poll(wgpu::PollType::Wait).unwrap();
}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/features/src/render_to_texture/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ async fn run(_path: Option<String>) {
let buffer_slice = output_staging_buffer.slice(..);
let (sender, receiver) = flume::bounded(1);
buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap());
device.poll(wgpu::Maintain::wait()).panic_on_timeout();
device.poll(wgpu::PollType::wait()).unwrap();
receiver.recv_async().await.unwrap().unwrap();
log::info!("Output buffer mapped.");
{
Expand Down
7 changes: 2 additions & 5 deletions examples/features/src/repeated_compute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,8 @@ async fn compute(local_buffer: &mut [u32], context: &WgpuContext) {
// In order for the mapping to be completed, one of three things must happen.
// One of those can be calling `Device::poll`. This isn't necessary on the web as devices
// are polled automatically but natively, we need to make sure this happens manually.
// `Maintain::Wait` will cause the thread to wait on native but not on WebGpu.
context
.device
.poll(wgpu::Maintain::wait())
.panic_on_timeout();
// `PollType::Wait` will cause the thread to wait on native but not on WebGpu.
context.device.poll(wgpu::PollType::wait()).unwrap();
log::info!("Device polled.");
// Now we await the receiving and panic if anything went wrong because we're lazy.
receiver.recv_async().await.unwrap().unwrap();
Expand Down
2 changes: 1 addition & 1 deletion examples/features/src/storage_texture/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ async fn run(_path: Option<String>) {
let buffer_slice = output_staging_buffer.slice(..);
let (sender, receiver) = flume::bounded(1);
buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap());
device.poll(wgpu::Maintain::wait()).panic_on_timeout();
device.poll(wgpu::PollType::wait()).unwrap();
receiver.recv_async().await.unwrap().unwrap();
log::info!("Output buffer mapped");
{
Expand Down
2 changes: 1 addition & 1 deletion examples/features/src/timestamp_queries/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl Queries {
self.destination_buffer
.slice(..)
.map_async(wgpu::MapMode::Read, |_| ());
device.poll(wgpu::Maintain::wait()).panic_on_timeout();
device.poll(wgpu::PollType::wait()).unwrap();

let timestamps = {
let timestamp_view = self
Expand Down
2 changes: 1 addition & 1 deletion examples/standalone/01_hello_compute/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ fn main() {

// Wait for the GPU to finish working on the submitted work. This doesn't work on WebGPU, so we would need
// to rely on the callback to know when the buffer is mapped.
device.poll(wgpu::Maintain::Wait);
device.poll(wgpu::PollType::Wait).unwrap();

// We can now read the data from the buffer.
let data = buffer_slice.get_mapped_range();
Expand Down
4 changes: 2 additions & 2 deletions player/src/bin/play.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ fn main() {
}

global.device_stop_capture(device);
global.device_poll(device, wgt::Maintain::wait()).unwrap();
global.device_poll(device, wgt::PollType::wait()).unwrap();
}
#[cfg(feature = "winit")]
{
Expand Down Expand Up @@ -203,7 +203,7 @@ fn main() {
},
Event::LoopExiting => {
log::info!("Closing");
global.device_poll(device, wgt::Maintain::wait()).unwrap();
global.device_poll(device, wgt::PollType::wait()).unwrap();
}
_ => {}
}
Expand Down
2 changes: 1 addition & 1 deletion player/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl Test<'_> {

println!("\t\t\tWaiting...");
global
.device_poll(device_id, wgt::Maintain::wait())
.device_poll(device_id, wgt::PollType::wait())
.unwrap();

for expect in self.expectations {
Expand Down
2 changes: 1 addition & 1 deletion tests/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ impl ReadbackBuffers {
) -> Vec<u8> {
let buffer_slice = buffer.slice(..);
buffer_slice.map_async(MapMode::Read, |_| ());
ctx.async_poll(Maintain::wait()).await.panic_on_timeout();
ctx.async_poll(PollType::wait()).await.unwrap();
let (block_width, block_height) = self.texture_format.block_dimensions();
let expected_bytes_per_row = (self.texture_width / block_width)
* self.texture_format.block_copy_size(aspect).unwrap_or(4);
Expand Down
15 changes: 14 additions & 1 deletion tests/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,20 @@ pub fn initialize_instance(backends: wgpu::Backends, force_fxc: bool) -> Instanc
dx12: wgpu::Dx12BackendOptions {
shader_compiler: dx12_shader_compiler,
},
gl: wgpu::GlBackendOptions::from_env_or_default(),
gl: wgpu::GlBackendOptions {
fence_behavior: if cfg!(target_family = "wasm") {
// On WebGL, you cannot call Poll(Wait) with any timeout. This is because the
// browser does not things to block. However all of our tests are written to
// expect this behavior. This is the workaround to allow this to work.
//
// However on native you can wait, so we want to ensure that behavior as well.
wgpu::GlFenceBehavior::AutoFinish
} else {
wgpu::GlFenceBehavior::Normal
},
..Default::default()
}
.with_env(),
// TODO(https://github.com/gfx-rs/wgpu/issues/7119): Enable noop backend?
noop: wgpu::NoopBackendOptions::default(),
},
Expand Down
7 changes: 5 additions & 2 deletions tests/src/poll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use crate::TestingContext;

impl TestingContext {
/// Utility to allow future asynchronous polling.
pub async fn async_poll(&self, maintain: wgpu::Maintain) -> wgpu::MaintainResult {
self.device.poll(maintain)
pub async fn async_poll(
&self,
poll_type: wgpu::PollType,
) -> Result<wgpu::PollStatus, wgpu::PollError> {
self.device.poll(poll_type)
}
}
4 changes: 1 addition & 3 deletions tests/tests/bgra8unorm_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,7 @@ static BGRA8_UNORM_STORAGE: GpuTestConfiguration = GpuTestConfiguration::new()

let buffer_slice = readback_buffer.slice(..);
buffer_slice.map_async(wgpu::MapMode::Read, Result::unwrap);
ctx.async_poll(wgpu::Maintain::wait())
.await
.panic_on_timeout();
ctx.async_poll(wgpu::PollType::wait()).await.unwrap();

{
let texels = buffer_slice.get_mapped_range();
Expand Down
2 changes: 1 addition & 1 deletion tests/tests/binding_array/buffers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ async fn binding_array_buffers(
let slice = readback_buffer.slice(..);
slice.map_async(MapMode::Read, |_| {});

ctx.device.poll(Maintain::Wait);
ctx.device.poll(PollType::Wait).unwrap();

let data = slice.get_mapped_range();

Expand Down
2 changes: 1 addition & 1 deletion tests/tests/binding_array/samplers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ async fn binding_array_samplers(ctx: TestingContext, partially_bound: bool) {
ctx.queue.submit(Some(encoder.finish()));

readback_buffer.slice(..).map_async(MapMode::Read, |_| {});
ctx.device.poll(Maintain::Wait);
ctx.device.poll(PollType::Wait).unwrap();

let readback_buffer_slice = readback_buffer.slice(..).get_mapped_range();

Expand Down
Loading

0 comments on commit 7e11996

Please sign in to comment.