Skip to content

Commit 85fcf94

Browse files
committed
Merge branch 'main' into piet
2 parents 8ef9b55 + dd524b5 commit 85fcf94

File tree

8 files changed

+170
-146
lines changed

8 files changed

+170
-146
lines changed

compute-shader-toy/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = "2018"
88

99
[dependencies]
1010
wgpu = { version = "0.8.1", features = ["cross"] }
11-
winit = "0.24"
11+
winit = "0.25"
1212
pollster = "0.2"
1313
async-executor = "1.0"
14+
bytemuck = "1.5.1"

compute-shader-toy/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Compute shader toy based on wgpu
2+
3+
This is a starting point for running compute shaders and drawing the image output into a window, based on [wgpu]. You should be able to change the shader (paint.wgsl) and run using simply `cargo run`.
4+
5+
If there are syntax errors in the shader, the error message can be pretty cryptic. It might be useful to run [naga] from the commandline to validate the shader code.
6+
7+
The shading language for this example is [WGSL], translated by naga, but it is possible to run wgpu in native mode with SPIR-V shaders as well. At the time of this writing, compute shaders are blocked on [naga#875], but when the fix for that lands, the experience should be better, and that will also open up features like atomics (and possibly subgroup operations) that are not presently supported by the wgpu stack.
8+
9+
[naga]: https://github.com/gfx-rs/naga
10+
[wgpu]: https://wgpu.rs/
11+
[WGSL]: https://gpuweb.github.io/gpuweb/wgsl/
12+
[naga#975]: https://github.com/gfx-rs/naga/issues/875

compute-shader-toy/foo.comp

Lines changed: 0 additions & 15 deletions
This file was deleted.

compute-shader-toy/src/copy.wgsl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// Also licensed under MIT license, at your choice.
16+
17+
// A simple vert/frag shader to copy an image to the swapchain.
18+
119
struct VertexOutput {
220
[[location(0)]] tex_coord: vec2<f32>;
321
[[builtin(position)]] position: vec4<f32>;

compute-shader-toy/src/main.rs

Lines changed: 122 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
//! A simple compute shader example that draws into a window, based on wgpu.
1818
19-
use wgpu::Extent3d;
19+
use wgpu::util::DeviceExt;
20+
use wgpu::{BufferUsage, Extent3d};
21+
2022
use winit::{
2123
event::{Event, WindowEvent},
2224
event_loop::{ControlFlow, EventLoop},
@@ -27,12 +29,10 @@ async fn run(event_loop: EventLoop<()>, window: Window) {
2729
let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
2830
let surface = unsafe { instance.create_surface(&window) };
2931
let adapter = instance
30-
.request_adapter(
31-
&wgpu::RequestAdapterOptions {
32-
power_preference: Default::default(),
33-
compatible_surface: Some(&surface),
34-
},
35-
)
32+
.request_adapter(&wgpu::RequestAdapterOptions {
33+
power_preference: Default::default(),
34+
compatible_surface: Some(&surface),
35+
})
3636
.await
3737
.expect("error finding adapter");
3838

@@ -60,31 +60,32 @@ async fn run(event_loop: EventLoop<()>, window: Window) {
6060
source: wgpu::ShaderSource::Wgsl(include_str!("copy.wgsl").into()),
6161
flags: shader_flags,
6262
});
63-
let copy_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
64-
label: None,
65-
entries: &[
66-
wgpu::BindGroupLayoutEntry {
67-
binding: 0,
68-
visibility: wgpu::ShaderStage::FRAGMENT,
69-
ty: wgpu::BindingType::Texture {
70-
multisampled: false,
71-
// Should filterable be false if we want nearest-neighbor?
72-
sample_type: wgpu::TextureSampleType::Float { filterable: true },
73-
view_dimension: wgpu::TextureViewDimension::D2,
63+
let copy_bind_group_layout =
64+
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
65+
label: None,
66+
entries: &[
67+
wgpu::BindGroupLayoutEntry {
68+
binding: 0,
69+
visibility: wgpu::ShaderStage::FRAGMENT,
70+
ty: wgpu::BindingType::Texture {
71+
multisampled: false,
72+
// Should filterable be false if we want nearest-neighbor?
73+
sample_type: wgpu::TextureSampleType::Float { filterable: true },
74+
view_dimension: wgpu::TextureViewDimension::D2,
75+
},
76+
count: None,
7477
},
75-
count: None,
76-
},
77-
wgpu::BindGroupLayoutEntry {
78-
binding: 1,
79-
visibility: wgpu::ShaderStage::FRAGMENT,
80-
ty: wgpu::BindingType::Sampler {
81-
filtering: false,
82-
comparison: false,
78+
wgpu::BindGroupLayoutEntry {
79+
binding: 1,
80+
visibility: wgpu::ShaderStage::FRAGMENT,
81+
ty: wgpu::BindingType::Sampler {
82+
filtering: false,
83+
comparison: false,
84+
},
85+
count: None,
8386
},
84-
count: None,
85-
},
86-
]
87-
});
87+
],
88+
});
8889
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
8990
label: None,
9091
bind_group_layouts: &[&copy_bind_group_layout],
@@ -107,77 +108,108 @@ async fn run(event_loop: EventLoop<()>, window: Window) {
107108
depth_stencil: None,
108109
multisample: wgpu::MultisampleState::default(),
109110
});
111+
112+
let img = device.create_texture(&wgpu::TextureDescriptor {
113+
label: None,
114+
size: Extent3d {
115+
width: size.width,
116+
height: size.height,
117+
depth_or_array_layers: 1,
118+
},
119+
mip_level_count: 1,
120+
sample_count: 1,
121+
dimension: wgpu::TextureDimension::D2,
122+
format: wgpu::TextureFormat::Rgba8Unorm,
123+
usage: wgpu::TextureUsage::STORAGE | wgpu::TextureUsage::SAMPLED,
124+
});
125+
let img_view = img.create_view(&Default::default());
126+
127+
const CONFIG_SIZE: u64 = 12;
128+
129+
let config_dev = device.create_buffer(&wgpu::BufferDescriptor {
130+
label: None,
131+
size: CONFIG_SIZE,
132+
usage: BufferUsage::COPY_DST | BufferUsage::STORAGE,
133+
mapped_at_creation: false,
134+
});
135+
let config_resource = config_dev.as_entire_binding();
136+
137+
let cs_module = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
138+
label: None,
139+
source: wgpu::ShaderSource::Wgsl(include_str!("paint.wgsl").into()),
140+
flags: shader_flags,
141+
});
142+
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
143+
label: None,
144+
layout: None,
145+
module: &cs_module,
146+
entry_point: "main",
147+
});
148+
let bind_group_layout = pipeline.get_bind_group_layout(0);
149+
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
150+
label: None,
151+
layout: &bind_group_layout,
152+
entries: &[
153+
wgpu::BindGroupEntry {
154+
binding: 0,
155+
resource: config_resource,
156+
},
157+
wgpu::BindGroupEntry {
158+
binding: 1,
159+
resource: wgpu::BindingResource::TextureView(&img_view),
160+
},
161+
],
162+
});
163+
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
164+
address_mode_u: wgpu::AddressMode::ClampToEdge,
165+
address_mode_v: wgpu::AddressMode::ClampToEdge,
166+
address_mode_w: wgpu::AddressMode::ClampToEdge,
167+
mag_filter: wgpu::FilterMode::Nearest,
168+
min_filter: wgpu::FilterMode::Nearest,
169+
mipmap_filter: wgpu::FilterMode::Nearest,
170+
..Default::default()
171+
});
172+
let copy_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
173+
label: None,
174+
layout: &copy_bind_group_layout,
175+
entries: &[
176+
wgpu::BindGroupEntry {
177+
binding: 0,
178+
resource: wgpu::BindingResource::TextureView(&img_view),
179+
},
180+
wgpu::BindGroupEntry {
181+
binding: 1,
182+
resource: wgpu::BindingResource::Sampler(&sampler),
183+
},
184+
],
185+
});
186+
let start_time = std::time::Instant::now();
187+
110188
event_loop.run(move |event, _, control_flow| {
111-
//println!("event {:?}", event);
112-
*control_flow = ControlFlow::Wait;
189+
// TODO: this may be excessive polling. It really should be synchronized with
190+
// swapchain presentation, but that's currently underbaked in wgpu.
191+
*control_flow = ControlFlow::Poll;
113192
match event {
114193
Event::RedrawRequested(_) => {
115194
let frame = swap_chain
116195
.get_current_frame()
117196
.expect("error getting frame from swap chain")
118197
.output;
119-
let img = device.create_texture(&wgpu::TextureDescriptor {
120-
label: None,
121-
size: Extent3d {
122-
width: size.width,
123-
height: size.height,
124-
depth_or_array_layers: 1,
125-
},
126-
mip_level_count: 1,
127-
sample_count: 1,
128-
dimension: wgpu::TextureDimension::D2,
129-
format: wgpu::TextureFormat::Rgba8Unorm,
130-
usage: wgpu::TextureUsage::STORAGE | wgpu::TextureUsage::SAMPLED,
131-
});
132-
let img_view = img.create_view(&Default::default());
133-
let cs_module = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
134-
label: None,
135-
source: wgpu::ShaderSource::SpirV(bytes_to_u32(include_bytes!("paint.spv")).into()),
136-
//source: wgpu::ShaderSource::Wgsl(include_str!("paint.wgsl").into()),
137-
flags: shader_flags,
138-
});
139-
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
140-
label: None,
141-
layout: None,
142-
module: &cs_module,
143-
entry_point: "main",
144-
});
145-
let bind_group_layout = pipeline.get_bind_group_layout(0);
146-
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
147-
label: None,
148-
layout: &bind_group_layout,
149-
entries: &[wgpu::BindGroupEntry {
150-
binding: 0,
151-
resource: wgpu::BindingResource::TextureView(&img_view),
152-
}],
153-
});
154-
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
155-
address_mode_u: wgpu::AddressMode::ClampToEdge,
156-
address_mode_v: wgpu::AddressMode::ClampToEdge,
157-
address_mode_w: wgpu::AddressMode::ClampToEdge,
158-
mag_filter: wgpu::FilterMode::Nearest,
159-
min_filter: wgpu::FilterMode::Nearest,
160-
mipmap_filter: wgpu::FilterMode::Nearest,
161-
..Default::default()
162-
});
163-
let copy_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
198+
199+
let i_time: f32 = 0.5 + start_time.elapsed().as_micros() as f32 * 1e-6;
200+
let config_data = [size.width, size.height, i_time.to_bits()];
201+
let config_host = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
164202
label: None,
165-
layout: &copy_bind_group_layout,
166-
entries: &[wgpu::BindGroupEntry {
167-
binding: 0,
168-
resource: wgpu::BindingResource::TextureView(&img_view),
169-
},
170-
wgpu::BindGroupEntry {
171-
binding: 1,
172-
resource: wgpu::BindingResource::Sampler(&sampler),
173-
}],
203+
contents: bytemuck::bytes_of(&config_data),
204+
usage: BufferUsage::COPY_SRC,
174205
});
175206
let mut encoder = device.create_command_encoder(&Default::default());
207+
encoder.copy_buffer_to_buffer(&config_host, 0, &config_dev, 0, CONFIG_SIZE);
176208
{
177209
let mut cpass = encoder.begin_compute_pass(&Default::default());
178210
cpass.set_pipeline(&pipeline);
179211
cpass.set_bind_group(0, &bind_group, &[]);
180-
cpass.dispatch(1, 1, 1);
212+
cpass.dispatch(size.width / 16, size.height / 16, 1);
181213
}
182214
{
183215
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
@@ -198,6 +230,9 @@ async fn run(event_loop: EventLoop<()>, window: Window) {
198230
}
199231
queue.submit(Some(encoder.finish()));
200232
}
233+
Event::MainEventsCleared => {
234+
window.request_redraw();
235+
}
201236
Event::WindowEvent {
202237
event: WindowEvent::CloseRequested,
203238
..
@@ -212,16 +247,3 @@ fn main() {
212247
let window = Window::new(&event_loop).unwrap();
213248
pollster::block_on(run(event_loop, window));
214249
}
215-
216-
// This will be used if we have spv shaders.
217-
#[allow(unused)]
218-
fn bytes_to_u32(bytes: &[u8]) -> Vec<u32> {
219-
bytes
220-
.chunks_exact(4)
221-
.map(|b| {
222-
let mut bytes = [0; 4];
223-
bytes.copy_from_slice(b);
224-
u32::from_le_bytes(bytes)
225-
})
226-
.collect()
227-
}

compute-shader-toy/src/paint.comp

Lines changed: 0 additions & 26 deletions
This file was deleted.

compute-shader-toy/src/paint.spv

-740 Bytes
Binary file not shown.

compute-shader-toy/src/paint.wgsl

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,23 @@
1414
//
1515
// Also licensed under MIT license, at your choice.
1616

17-
[[group(0), binding(0)]] var outputTex: [[access(write)]] texture_storage_2d<rgba8unorm>;
17+
[[block]]
18+
struct Params {
19+
width: u32;
20+
height: u32;
21+
iTime: f32;
22+
};
23+
24+
[[group(0), binding(0)]] var params: [[access(read)]] Params;
25+
[[group(0), binding(1)]] var outputTex: [[access(write)]] texture_storage_2d<rgba8unorm>;
1826

1927
[[stage(compute), workgroup_size(16, 16)]]
2028
fn main([[builtin(global_invocation_id)]] global_ix: vec3<u32>) {
21-
let rgba: vec4<f32> = vec4<f32>(1.0, 0.0, 0.0, 1.0);
22-
let write_ix = vec2<i32>(i32(global_ix.x), i32(global_ix.y));
23-
textureStore(outputTex, write_ix, rgba);
29+
let fragCoord: vec2<f32> = vec2<f32>(global_ix.xy) / vec2<f32>(f32(params.width), f32(params.height))
30+
- vec2<f32>(0.5, 0.5);
31+
32+
// Shadertoy-like code can go here.
33+
let fragColor: vec4<f32> = vec4<f32>(fragCoord.x + 0.5, fragCoord.y + 0.5, sin(params.iTime), 1.0);
34+
35+
textureStore(outputTex, vec2<i32>(global_ix.xy), fragColor);
2436
}

0 commit comments

Comments
 (0)