diff --git a/.cargo/config.toml b/.cargo/config.toml index ebaf696b8..a9c0375a9 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,7 +2,7 @@ # these and list no features. Needs cargo-nextest. [alias] # The features CI adds to the default build; the one place they are listed. -ci-nextest = "nextest run --workspace --features jitv2,rex-gpu,rex-jit" +ci-nextest = "nextest run --workspace --features jitv2,rex-jit,gputrace" ci-test = "ci-nextest --profile ci" # The default features alone: the only run of the real-cache path that tcache # (implied by jitv2) compiles out. diff --git a/src/wgpu/device.rs b/src/wgpu/device.rs index 40e95d956..70e21a7c1 100644 --- a/src/wgpu/device.rs +++ b/src/wgpu/device.rs @@ -506,11 +506,12 @@ impl Gpu { /// Block until `submission` has finished (or everything, with None). pub fn wait(&self, submission: Option) -> Result<(), String> { - self.device - .poll(wgpu::PollType::Wait { submission_index: submission, timeout: Some(Duration::from_secs(30)) }) - .map(|_| ()) - .map_err(|e| format!("wait for GPU: {e}"))?; - self.check() + let waited = self.device.poll(wgpu::PollType::Wait { submission_index: submission, timeout: Some(Duration::from_secs(30)) }); + // wgpu cannot wait for a submission it refused (a pipeline that + // failed to build, a lost device); the error that refused it is + // the one to report. + self.check()?; + waited.map(|_| ()).map_err(|e| format!("wait for GPU: {e}")) } /// Let callbacks run without blocking. diff --git a/src/wgpu/tests/mod.rs b/src/wgpu/tests/mod.rs index 9d6568fd5..0adc36028 100644 --- a/src/wgpu/tests/mod.rs +++ b/src/wgpu/tests/mod.rs @@ -378,3 +378,32 @@ fn empty_flush_is_a_no_op() { assert!(board.batch.flush(&board.pipelines, &[&board.vram_group]).is_none()); assert!(board.plane().iter().all(|&w| w == 0)); } + +/// A pipeline that fails to build makes the submission that uses it fail, +/// and wgpu cannot wait for a failed submission. The wait reports the build +/// error, not the missing submission. +#[test] +fn a_refused_submission_reports_why() { + let Some(gpu) = gpu() else { return }; + let module = gpu.device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("refused"), + source: wgpu::ShaderSource::Wgsl("@compute @workgroup_size(1) fn main() { let x: u32 = 1.5; }".into()), + }); + let pipeline = gpu.device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("refused"), + layout: None, + module: &module, + entry_point: Some("main"), + compilation_options: Default::default(), + cache: None, + }); + let mut encoder = gpu.encoder("refused"); + { + let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor::default()); + pass.set_pipeline(&pipeline); + pass.dispatch_workgroups(1, 1, 1); + } + let index = gpu.submit(encoder); + let err = gpu.wait(Some(index)).unwrap_err(); + assert!(err.starts_with("wgpu error"), "{err}"); +}