Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly Deal with Timeouts #7030

Merged
merged 11 commits into from
Feb 14, 2025
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
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
Loading