Skip to content

Commit 77a83fb

Browse files
authored
Remove lifetime constraints from wgpu::ComputePass methods (#5570)
* basic test setup * remove lifetime and drop resources on test - test fails now just as expected * compute pass recording is now hub dependent (needs gfx_select) * compute pass recording now bumps reference count of uses resources directly on recording TODO: * bind groups don't work because the Binder gets an id only * wgpu level error handling is missing * simplify compute pass state flush, compute pass execution no longer needs to lock bind_group storage * wgpu sided error handling * make ComputePass hal dependent, removing command cast hack. Introduce DynComputePass on wgpu side * remove stray repr(C) * changelog entry * fix deno issues -> move DynComputePass into wgc * split out resources setup from test
1 parent 00456cf commit 77a83fb

File tree

11 files changed

+613
-190
lines changed

11 files changed

+613
-190
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ Bottom level categories:
4141

4242
### Major Changes
4343

44+
#### Remove lifetime bounds on `wgpu::ComputePass`
45+
46+
TODO(wumpf): This is still work in progress. Should write a bit more about it. Also will very likely extend to `wgpu::RenderPass` before release.
47+
48+
`wgpu::ComputePass` recording methods (e.g. `wgpu::ComputePass:set_render_pipeline`) no longer impose a lifetime constraint passed in resources.
49+
50+
By @wumpf in [#5569](https://github.com/gfx-rs/wgpu/pull/5569).
51+
4452
#### Querying shader compilation errors
4553

4654
Wgpu now supports querying [shader compilation info](https://www.w3.org/TR/webgpu/#dom-gpushadermodule-getcompilationinfo).

deno_webgpu/command_encoder.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,14 @@ pub fn op_webgpu_command_encoder_begin_compute_pass(
254254
None
255255
};
256256

257+
let instance = state.borrow::<super::Instance>();
258+
let command_encoder = &command_encoder_resource.1;
257259
let descriptor = wgpu_core::command::ComputePassDescriptor {
258260
label: Some(label),
259261
timestamp_writes: timestamp_writes.as_ref(),
260262
};
261263

262-
let compute_pass =
263-
wgpu_core::command::ComputePass::new(command_encoder_resource.1, &descriptor);
264+
let compute_pass = gfx_select!(command_encoder => instance.command_encoder_create_compute_pass_dyn(*command_encoder, &descriptor));
264265

265266
let rid = state
266267
.resource_table

deno_webgpu/compute_pass.rs

Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ use std::cell::RefCell;
1010

1111
use super::error::WebGpuResult;
1212

13-
pub(crate) struct WebGpuComputePass(pub(crate) RefCell<wgpu_core::command::ComputePass>);
13+
pub(crate) struct WebGpuComputePass(
14+
pub(crate) RefCell<Box<dyn wgpu_core::command::DynComputePass>>,
15+
);
1416
impl Resource for WebGpuComputePass {
1517
fn name(&self) -> Cow<str> {
1618
"webGPUComputePass".into()
@@ -31,10 +33,10 @@ pub fn op_webgpu_compute_pass_set_pipeline(
3133
.resource_table
3234
.get::<WebGpuComputePass>(compute_pass_rid)?;
3335

34-
wgpu_core::command::compute_commands::wgpu_compute_pass_set_pipeline(
35-
&mut compute_pass_resource.0.borrow_mut(),
36-
compute_pipeline_resource.1,
37-
);
36+
compute_pass_resource
37+
.0
38+
.borrow_mut()
39+
.set_pipeline(state.borrow(), compute_pipeline_resource.1)?;
3840

3941
Ok(WebGpuResult::empty())
4042
}
@@ -52,12 +54,10 @@ pub fn op_webgpu_compute_pass_dispatch_workgroups(
5254
.resource_table
5355
.get::<WebGpuComputePass>(compute_pass_rid)?;
5456

55-
wgpu_core::command::compute_commands::wgpu_compute_pass_dispatch_workgroups(
56-
&mut compute_pass_resource.0.borrow_mut(),
57-
x,
58-
y,
59-
z,
60-
);
57+
compute_pass_resource
58+
.0
59+
.borrow_mut()
60+
.dispatch_workgroups(state.borrow(), x, y, z);
6161

6262
Ok(WebGpuResult::empty())
6363
}
@@ -77,11 +77,10 @@ pub fn op_webgpu_compute_pass_dispatch_workgroups_indirect(
7777
.resource_table
7878
.get::<WebGpuComputePass>(compute_pass_rid)?;
7979

80-
wgpu_core::command::compute_commands::wgpu_compute_pass_dispatch_workgroups_indirect(
81-
&mut compute_pass_resource.0.borrow_mut(),
82-
buffer_resource.1,
83-
indirect_offset,
84-
);
80+
compute_pass_resource
81+
.0
82+
.borrow_mut()
83+
.dispatch_workgroups_indirect(state.borrow(), buffer_resource.1, indirect_offset)?;
8584

8685
Ok(WebGpuResult::empty())
8786
}
@@ -90,24 +89,15 @@ pub fn op_webgpu_compute_pass_dispatch_workgroups_indirect(
9089
#[serde]
9190
pub fn op_webgpu_compute_pass_end(
9291
state: &mut OpState,
93-
#[smi] command_encoder_rid: ResourceId,
9492
#[smi] compute_pass_rid: ResourceId,
9593
) -> Result<WebGpuResult, AnyError> {
96-
let command_encoder_resource =
97-
state
98-
.resource_table
99-
.get::<super::command_encoder::WebGpuCommandEncoder>(command_encoder_rid)?;
100-
let command_encoder = command_encoder_resource.1;
10194
let compute_pass_resource = state
10295
.resource_table
10396
.take::<WebGpuComputePass>(compute_pass_rid)?;
104-
let compute_pass = &compute_pass_resource.0.borrow();
105-
let instance = state.borrow::<super::Instance>();
10697

107-
gfx_ok!(command_encoder => instance.command_encoder_run_compute_pass(
108-
command_encoder,
109-
compute_pass
110-
))
98+
compute_pass_resource.0.borrow_mut().run(state.borrow())?;
99+
100+
Ok(WebGpuResult::empty())
111101
}
112102

113103
#[op2]
@@ -137,12 +127,12 @@ pub fn op_webgpu_compute_pass_set_bind_group(
137127

138128
let dynamic_offsets_data: &[u32] = &dynamic_offsets_data[start..start + len];
139129

140-
wgpu_core::command::compute_commands::wgpu_compute_pass_set_bind_group(
141-
&mut compute_pass_resource.0.borrow_mut(),
130+
compute_pass_resource.0.borrow_mut().set_bind_group(
131+
state.borrow(),
142132
index,
143133
bind_group_resource.1,
144134
dynamic_offsets_data,
145-
);
135+
)?;
146136

147137
Ok(WebGpuResult::empty())
148138
}
@@ -158,8 +148,8 @@ pub fn op_webgpu_compute_pass_push_debug_group(
158148
.resource_table
159149
.get::<WebGpuComputePass>(compute_pass_rid)?;
160150

161-
wgpu_core::command::compute_commands::wgpu_compute_pass_push_debug_group(
162-
&mut compute_pass_resource.0.borrow_mut(),
151+
compute_pass_resource.0.borrow_mut().push_debug_group(
152+
state.borrow(),
163153
group_label,
164154
0, // wgpu#975
165155
);
@@ -177,9 +167,10 @@ pub fn op_webgpu_compute_pass_pop_debug_group(
177167
.resource_table
178168
.get::<WebGpuComputePass>(compute_pass_rid)?;
179169

180-
wgpu_core::command::compute_commands::wgpu_compute_pass_pop_debug_group(
181-
&mut compute_pass_resource.0.borrow_mut(),
182-
);
170+
compute_pass_resource
171+
.0
172+
.borrow_mut()
173+
.pop_debug_group(state.borrow());
183174

184175
Ok(WebGpuResult::empty())
185176
}
@@ -195,8 +186,8 @@ pub fn op_webgpu_compute_pass_insert_debug_marker(
195186
.resource_table
196187
.get::<WebGpuComputePass>(compute_pass_rid)?;
197188

198-
wgpu_core::command::compute_commands::wgpu_compute_pass_insert_debug_marker(
199-
&mut compute_pass_resource.0.borrow_mut(),
189+
compute_pass_resource.0.borrow_mut().insert_debug_marker(
190+
state.borrow(),
200191
marker_label,
201192
0, // wgpu#975
202193
);
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
//! Tests that compute passes take ownership of resources that are associated with.
2+
//! I.e. once a resource is passed in to a compute pass, it can be dropped.
3+
//!
4+
//! TODO: Test doesn't check on timestamp writes & pipeline statistics queries yet.
5+
//! (Not important as long as they are lifetime constrained to the command encoder,
6+
//! but once we lift this constraint, we should add tests for this as well!)
7+
//! TODO: Also should test resource ownership for:
8+
//! * write_timestamp
9+
//! * begin_pipeline_statistics_query
10+
11+
use std::num::NonZeroU64;
12+
13+
use wgpu::util::DeviceExt as _;
14+
use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext};
15+
16+
const SHADER_SRC: &str = "
17+
@group(0) @binding(0)
18+
var<storage, read_write> buffer: array<vec4f>;
19+
20+
@compute @workgroup_size(1, 1, 1) fn main() {
21+
buffer[0] *= 2.0;
22+
}
23+
";
24+
25+
#[gpu_test]
26+
static COMPUTE_PASS_RESOURCE_OWNERSHIP: GpuTestConfiguration = GpuTestConfiguration::new()
27+
.parameters(TestParameters::default().test_features_limits())
28+
.run_async(compute_pass_resource_ownership);
29+
30+
async fn compute_pass_resource_ownership(ctx: TestingContext) {
31+
let ResourceSetup {
32+
gpu_buffer,
33+
cpu_buffer,
34+
buffer_size,
35+
indirect_buffer,
36+
bind_group,
37+
pipeline,
38+
} = resource_setup(&ctx);
39+
40+
let mut encoder = ctx
41+
.device
42+
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
43+
label: Some("encoder"),
44+
});
45+
46+
{
47+
let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
48+
label: Some("compute_pass"),
49+
timestamp_writes: None, // TODO: See description above, we should test this as well once we lift the lifetime bound.
50+
});
51+
cpass.set_pipeline(&pipeline);
52+
cpass.set_bind_group(0, &bind_group, &[]);
53+
cpass.dispatch_workgroups_indirect(&indirect_buffer, 0);
54+
55+
// Now drop all resources we set. Then do a device poll to make sure the resources are really not dropped too early, no matter what.
56+
drop(pipeline);
57+
drop(bind_group);
58+
drop(indirect_buffer);
59+
ctx.async_poll(wgpu::Maintain::wait())
60+
.await
61+
.panic_on_timeout();
62+
}
63+
64+
// Ensure that the compute pass still executed normally.
65+
encoder.copy_buffer_to_buffer(&gpu_buffer, 0, &cpu_buffer, 0, buffer_size);
66+
ctx.queue.submit([encoder.finish()]);
67+
cpu_buffer.slice(..).map_async(wgpu::MapMode::Read, |_| ());
68+
ctx.async_poll(wgpu::Maintain::wait())
69+
.await
70+
.panic_on_timeout();
71+
72+
let data = cpu_buffer.slice(..).get_mapped_range();
73+
74+
let floats: &[f32] = bytemuck::cast_slice(&data);
75+
assert_eq!(floats, [2.0, 4.0, 6.0, 8.0]);
76+
}
77+
78+
// Setup ------------------------------------------------------------
79+
80+
struct ResourceSetup {
81+
gpu_buffer: wgpu::Buffer,
82+
cpu_buffer: wgpu::Buffer,
83+
buffer_size: u64,
84+
85+
indirect_buffer: wgpu::Buffer,
86+
bind_group: wgpu::BindGroup,
87+
pipeline: wgpu::ComputePipeline,
88+
}
89+
90+
fn resource_setup(ctx: &TestingContext) -> ResourceSetup {
91+
let sm = ctx
92+
.device
93+
.create_shader_module(wgpu::ShaderModuleDescriptor {
94+
label: Some("shader"),
95+
source: wgpu::ShaderSource::Wgsl(SHADER_SRC.into()),
96+
});
97+
98+
let buffer_size = 4 * std::mem::size_of::<f32>() as u64;
99+
100+
let bgl = ctx
101+
.device
102+
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
103+
label: Some("bind_group_layout"),
104+
entries: &[wgpu::BindGroupLayoutEntry {
105+
binding: 0,
106+
visibility: wgpu::ShaderStages::COMPUTE,
107+
ty: wgpu::BindingType::Buffer {
108+
ty: wgpu::BufferBindingType::Storage { read_only: false },
109+
has_dynamic_offset: false,
110+
min_binding_size: NonZeroU64::new(buffer_size),
111+
},
112+
count: None,
113+
}],
114+
});
115+
116+
let gpu_buffer = ctx
117+
.device
118+
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
119+
label: Some("gpu_buffer"),
120+
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
121+
contents: bytemuck::bytes_of(&[1.0_f32, 2.0, 3.0, 4.0]),
122+
});
123+
124+
let cpu_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
125+
label: Some("cpu_buffer"),
126+
size: buffer_size,
127+
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
128+
mapped_at_creation: false,
129+
});
130+
131+
let indirect_buffer = ctx
132+
.device
133+
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
134+
label: Some("gpu_buffer"),
135+
usage: wgpu::BufferUsages::INDIRECT,
136+
contents: wgpu::util::DispatchIndirectArgs { x: 1, y: 1, z: 1 }.as_bytes(),
137+
});
138+
139+
let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
140+
label: Some("bind_group"),
141+
layout: &bgl,
142+
entries: &[wgpu::BindGroupEntry {
143+
binding: 0,
144+
resource: gpu_buffer.as_entire_binding(),
145+
}],
146+
});
147+
148+
let pipeline_layout = ctx
149+
.device
150+
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
151+
label: Some("pipeline_layout"),
152+
bind_group_layouts: &[&bgl],
153+
push_constant_ranges: &[],
154+
});
155+
156+
let pipeline = ctx
157+
.device
158+
.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
159+
label: Some("pipeline"),
160+
layout: Some(&pipeline_layout),
161+
module: &sm,
162+
entry_point: "main",
163+
compilation_options: Default::default(),
164+
});
165+
166+
ResourceSetup {
167+
gpu_buffer,
168+
cpu_buffer,
169+
buffer_size,
170+
indirect_buffer,
171+
bind_group,
172+
pipeline,
173+
}
174+
}

tests/tests/root.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod buffer;
1111
mod buffer_copy;
1212
mod buffer_usages;
1313
mod clear_texture;
14+
mod compute_pass_resource_ownership;
1415
mod create_surface_error;
1516
mod device;
1617
mod encoder;

wgpu-core/src/command/bind.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use crate::{
44
binding_model::{BindGroup, LateMinBufferBindingSizeMismatch, PipelineLayout},
55
device::SHADER_STAGE_COUNT,
66
hal_api::HalApi,
7-
id::BindGroupId,
87
pipeline::LateSizedBufferGroup,
98
resource::Resource,
109
};
@@ -359,11 +358,11 @@ impl<A: HalApi> Binder<A> {
359358
&self.payloads[bind_range]
360359
}
361360

362-
pub(super) fn list_active(&self) -> impl Iterator<Item = BindGroupId> + '_ {
361+
pub(super) fn list_active<'a>(&'a self) -> impl Iterator<Item = &'a Arc<BindGroup<A>>> + '_ {
363362
let payloads = &self.payloads;
364363
self.manager
365364
.list_active()
366-
.map(move |index| payloads[index].group.as_ref().unwrap().as_info().id())
365+
.map(move |index| payloads[index].group.as_ref().unwrap())
367366
}
368367

369368
pub(super) fn invalid_mask(&self) -> BindGroupMask {

0 commit comments

Comments
 (0)