Skip to content

Commit 62307dc

Browse files
committed
example-runner-wpgu: emulate push constants on wasm/WebGPU.
1 parent 97357eb commit 62307dc

File tree

2 files changed

+111
-13
lines changed

2 files changed

+111
-13
lines changed

examples/runners/wgpu/src/graphics.rs

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::{CompiledShaderModules, Options, maybe_watch};
22
use wgpu::ShaderModuleDescriptorPassthrough;
33

44
use shared::ShaderConstants;
5+
use std::slice;
56
use winit::{
67
event::{ElementState, Event, MouseButton, WindowEvent},
78
event_loop::{ControlFlow, EventLoop},
@@ -174,15 +175,59 @@ async fn run(
174175
let mut surface_with_config = initial_surface
175176
.map(|surface| auto_configure_surface(&adapter, &device, surface, window.inner_size()));
176177

177-
// Load the shaders from disk
178+
// Describe the pipeline layout and build the initial pipeline.
179+
let push_constants_or_rossbo_emulation = {
180+
const PUSH_CONSTANTS_SIZE: usize = std::mem::size_of::<ShaderConstants>();
181+
let stages = wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT;
178182

183+
if !options.emulate_push_constants_with_storage_buffer {
184+
Ok(wgpu::PushConstantRange {
185+
stages,
186+
range: 0..PUSH_CONSTANTS_SIZE as u32,
187+
})
188+
} else {
189+
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
190+
label: None,
191+
size: PUSH_CONSTANTS_SIZE as u64,
192+
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
193+
mapped_at_creation: false,
194+
});
195+
let binding0 = wgpu::BindGroupLayoutEntry {
196+
binding: 0,
197+
visibility: stages,
198+
ty: wgpu::BindingType::Buffer {
199+
ty: wgpu::BufferBindingType::Storage { read_only: true },
200+
has_dynamic_offset: false,
201+
min_binding_size: Some((PUSH_CONSTANTS_SIZE as u64).try_into().unwrap()),
202+
},
203+
count: None,
204+
};
205+
let bind_group_layout =
206+
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
207+
label: None,
208+
entries: &[binding0],
209+
});
210+
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
211+
label: None,
212+
layout: &bind_group_layout,
213+
entries: &[wgpu::BindGroupEntry {
214+
binding: 0,
215+
resource: buffer.as_entire_binding(),
216+
}],
217+
});
218+
Err((buffer, bind_group_layout, bind_group))
219+
}
220+
};
179221
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
180222
label: None,
181-
bind_group_layouts: &[],
182-
push_constant_ranges: &[wgpu::PushConstantRange {
183-
stages: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
184-
range: 0..std::mem::size_of::<ShaderConstants>() as u32,
185-
}],
223+
bind_group_layouts: push_constants_or_rossbo_emulation
224+
.as_ref()
225+
.err()
226+
.map(|(_, layout, _)| layout)
227+
.as_slice(),
228+
push_constant_ranges: push_constants_or_rossbo_emulation
229+
.as_ref()
230+
.map_or(&[], slice::from_ref),
186231
});
187232

188233
let mut render_pipeline = create_pipeline(
@@ -339,11 +384,23 @@ async fn run(
339384
};
340385

341386
rpass.set_pipeline(render_pipeline);
342-
rpass.set_push_constants(
343-
wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
344-
0,
345-
bytemuck::bytes_of(&push_constants),
346-
);
387+
let (push_constant_offset, push_constant_bytes) =
388+
(0, bytemuck::bytes_of(&push_constants));
389+
match &push_constants_or_rossbo_emulation {
390+
Ok(_) => rpass.set_push_constants(
391+
wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
392+
push_constant_offset as u32,
393+
push_constant_bytes,
394+
),
395+
Err((buffer, _, bind_group)) => {
396+
queue.write_buffer(
397+
buffer,
398+
push_constant_offset,
399+
push_constant_bytes,
400+
);
401+
rpass.set_bind_group(0, bind_group, &[]);
402+
}
403+
}
347404
rpass.draw(0..3, 0..1);
348405
}
349406

@@ -421,8 +478,39 @@ fn create_pipeline(
421478
device: &wgpu::Device,
422479
pipeline_layout: &wgpu::PipelineLayout,
423480
surface_format: wgpu::TextureFormat,
424-
compiled_shader_modules: CompiledShaderModules,
481+
mut compiled_shader_modules: CompiledShaderModules,
425482
) -> wgpu::RenderPipeline {
483+
if options.emulate_push_constants_with_storage_buffer {
484+
let (ds, b) = (0, 0);
485+
486+
for (_, shader_module_descr) in &mut compiled_shader_modules.named_spv_modules {
487+
let w = shader_module_descr.source.to_mut();
488+
assert_eq!((w[0], w[4]), (0x07230203, 0));
489+
let mut last_op_decorate_start = None;
490+
let mut i = 5;
491+
while i < w.len() {
492+
let (op, len) = (w[i] & 0xffff, w[i] >> 16);
493+
match op {
494+
71 => last_op_decorate_start = Some(i),
495+
32 if w[i + 2] == 9 => w[i + 2] = 12,
496+
59 if w[i + 3] == 9 => {
497+
w[i + 3] = 12;
498+
let id = w[i + 2];
499+
let j = last_op_decorate_start.expect("no OpDecorate?");
500+
w.splice(
501+
j..j,
502+
[0x4_0047, id, 34, ds, 0x4_0047, id, 33, b, 0x3_0047, id, 24],
503+
);
504+
i += 11;
505+
}
506+
54 => break,
507+
_ => {}
508+
}
509+
i += len as usize;
510+
}
511+
}
512+
}
513+
426514
// FIXME(eddyb) automate this decision by default.
427515
let create_module = |module| {
428516
if options.force_spirv_passthru {

examples/runners/wgpu/src/lib.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ pub struct Options {
221221

222222
#[arg(long)]
223223
force_spirv_passthru: bool,
224+
225+
#[structopt(long)]
226+
emulate_push_constants_with_storage_buffer: bool,
224227
}
225228

226229
#[cfg_attr(target_os = "android", unsafe(export_name = "android_main"))]
@@ -238,13 +241,20 @@ pub fn main(
238241
std::env::set_var("PROFILE", env!("PROFILE"));
239242
}
240243

241-
let options = Options::parse();
244+
let mut options = Options::parse();
242245

243246
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
244247
if options.shader == RustGPUShader::Compute {
245248
return compute::start(&options);
246249
}
247250

251+
// HACK(eddyb) force push constant emulation using (read-only) SSBOs, on
252+
// wasm->WebGPU, as push constants are currently not supported.
253+
// FIXME(eddyb) could push constant support be automatically detected at runtime?
254+
if cfg!(target_arch = "wasm32") {
255+
options.emulate_push_constants_with_storage_buffer = true;
256+
}
257+
248258
graphics::start(
249259
#[cfg(target_os = "android")]
250260
android_app,

0 commit comments

Comments
 (0)