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

CommandEncoder::finish sometimes panics unexpectedly when ComputePass is not explicitly dropped beforehand #6145

Closed
melvdlin opened this issue Aug 23, 2024 · 8 comments · Fixed by #6619
Labels
area: api Issues related to API surface area: correctness We're behaving incorrectly

Comments

@melvdlin
Copy link

Description
Borrow shortening can cause CommandEncoder::finish to be callable before a derived ComputePass is automatically dropped. Since the Drop impl on ComputePassInner is what unlocks the CommandEncoder, this leads to an unexpected validation error, which can be avoided by explicitly dropping of the ComputePass before calling CommandEncoder::finish.

The same likely applies to RenderPass.

Repro steps

fn main() {
    let instance = wgpu::Instance::new(Default::default());
    let adapter =
        pollster::block_on(instance.request_adapter(&Default::default())).unwrap();
    let (device, queue) =
        pollster::block_on(adapter.request_device(&Default::default(), None)).unwrap();

    let mut encoder = device.create_command_encoder(&Default::default());
    let compute_pass = encoder.begin_compute_pass(&Default::default());
    // set up the compute pass...

    let cmd_buffer = encoder.finish();
    assert!(device
        .poll(wgpu::Maintain::wait_for(queue.submit([cmd_buffer])))
        .is_queue_empty());
}
[dependencies]
pollster = "0.3.0"
wgpu = "22.1.0"

Expected vs observed behavior
A panic occurs at line 12:

wgpu error: Validation Error

Caused by:
  In a CommandEncoder
    Command encoder is locked by a previously created render/compute pass. Before recording any new commands, the pass must be ended.

Since ComputePass's lifetime is bounded by a mutable borrow of the CommandEncoder, one would expect the CommandEncoder to be unlocked and a call to finish to be valid as soon as said borrow is invalidated. This is, however, not the case, since the Drop impl of ComputePassInner is only run at the end of scope, not as soon as the borrow is invalidated.

The documentation of CommandEncoder::begin_compute_pass does contain the following line: As long as the returned ComputePass has not ended, any mutating operation on this command encoder causes an error and invalidates it.. It does not specify that "a compute pass ending" specifically means "the ComputePass being dropped".

Platform
wgpu = "22.1.0"

@ErichDonGubler ErichDonGubler added area: api Issues related to API surface area: correctness We're behaving incorrectly labels Aug 23, 2024
@kpreid
Copy link
Contributor

kpreid commented Aug 23, 2024

If the cause is exactly as described, perhaps a simple fix for this would be to add:

impl Drop for ComputePass { fn drop(&mut self) {} }
impl Drop for RenderPass { fn drop(&mut self) {} }

This should require the lifetime to be valid when the pass is dropped.

@ErichDonGubler
Copy link
Member

I can confirm the crash on my Win11 machine. The workaround typically shown in examples right now is to scope the compute_pass so that it's dropped before calling encoder.finish():

--- a/src/main.rs
+++ b/src/main.rs
@@ -5,8 +5,10 @@
         pollster::block_on(adapter.request_device(&Default::default(), None)).unwrap();
 
     let mut encoder = device.create_command_encoder(&Default::default());
-    let compute_pass = encoder.begin_compute_pass(&Default::default());
-    // set up the compute pass...
+    {
+        let compute_pass = encoder.begin_compute_pass(&Default::default());
+        // set up the compute pass...
+    }
 
     let cmd_buffer = encoder.finish();
     assert!(device

…but, of course, this is still a paper cut we should strive to eliminate in our Rust-y interpretation of the WebGPU API.

@ErichDonGubler
Copy link
Member

I interpret @kpreid's suggestion to mean a diff. like this against current trunk:

diff --git a/wgpu-core/src/command/compute.rs b/wgpu-core/src/command/compute.rs
index eb929740c8..f216d1b40f 100644
--- a/wgpu-core/src/command/compute.rs
+++ b/wgpu-core/src/command/compute.rs
@@ -95,6 +95,10 @@
     }
 }
 
+impl Drop for ComputePass {
+    fn drop(&mut self) {}
+}
+
 #[derive(Clone, Debug, Default)]
 pub struct ComputePassDescriptor<'a> {
     pub label: Label<'a>,
diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs
index e4d93b042e..efc80d7475 100644
--- a/wgpu-core/src/command/render.rs
+++ b/wgpu-core/src/command/render.rs
@@ -321,6 +321,10 @@
     }
 }
 
+impl Drop for RenderPass {
+    fn drop(&mut self) {}
+}
+
 #[derive(Debug, PartialEq)]
 enum OptionalState {
     Unused,

…but this doesn't change any behavior in the MRE posted in the OP.

@teoxoy
Copy link
Member

teoxoy commented Aug 27, 2024

Render/compute passes no longer have lifetimes (#1453 was recently closed by @Wumpf's PRs).

We might want to expose the end/finish methods and require usage of those instead of implicitly doing it on drop.

@cwfitzgerald
Copy link
Member

They do still have lifetimes to their encoder.

No, the RenderPass drop needs to be added in wgpu itself.

@cwfitzgerald
Copy link
Member

@teoxoy
Copy link
Member

teoxoy commented Aug 27, 2024

Ah, I see, I should have checked before writing that :)

@cwfitzgerald
Copy link
Member

Going to tackle this as part of #6619. I need to write a test to make sure I didn't break anything anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: api Issues related to API surface area: correctness We're behaving incorrectly
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants