1use nanorand::{Rng, WyRand};
5use wgpu::util::DeviceExt;
6
7const NUM_PARTICLES: u32 = 1500;
10
11const PARTICLES_PER_GROUP: u32 = 64;
14
15struct Example {
17 particle_bind_groups: Vec<wgpu::BindGroup>,
18 particle_buffers: Vec<wgpu::Buffer>,
19 vertices_buffer: wgpu::Buffer,
20 compute_pipeline: wgpu::ComputePipeline,
21 render_pipeline: wgpu::RenderPipeline,
22 work_group_count: u32,
23 frame_num: usize,
24}
25
26impl crate::framework::Example for Example {
27 fn required_limits() -> wgpu::Limits {
28 wgpu::Limits::downlevel_defaults()
29 }
30
31 fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities {
32 wgpu::DownlevelCapabilities {
33 flags: wgpu::DownlevelFlags::COMPUTE_SHADERS,
34 ..Default::default()
35 }
36 }
37
38 fn init(
40 config: &wgpu::SurfaceConfiguration,
41 _adapter: &wgpu::Adapter,
42 device: &wgpu::Device,
43 _queue: &wgpu::Queue,
44 ) -> Self {
45 let compute_shader = device.create_shader_module(wgpu::include_wgsl!("compute.wgsl"));
46 let draw_shader = device.create_shader_module(wgpu::include_wgsl!("draw.wgsl"));
47
48 let sim_param_data = [
51 0.04f32, 0.1, 0.025, 0.025, 0.02, 0.05, 0.005, ]
59 .to_vec();
60 let sim_param_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
61 label: Some("Simulation Parameter Buffer"),
62 contents: bytemuck::cast_slice(&sim_param_data),
63 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
64 });
65
66 let compute_bind_group_layout =
69 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
70 entries: &[
71 wgpu::BindGroupLayoutEntry {
72 binding: 0,
73 visibility: wgpu::ShaderStages::COMPUTE,
74 ty: wgpu::BindingType::Buffer {
75 ty: wgpu::BufferBindingType::Uniform,
76 has_dynamic_offset: false,
77 min_binding_size: wgpu::BufferSize::new(
78 (sim_param_data.len() * size_of::<f32>()) as _,
79 ),
80 },
81 count: None,
82 },
83 wgpu::BindGroupLayoutEntry {
84 binding: 1,
85 visibility: wgpu::ShaderStages::COMPUTE,
86 ty: wgpu::BindingType::Buffer {
87 ty: wgpu::BufferBindingType::Storage { read_only: true },
88 has_dynamic_offset: false,
89 min_binding_size: wgpu::BufferSize::new((NUM_PARTICLES * 16) as _),
90 },
91 count: None,
92 },
93 wgpu::BindGroupLayoutEntry {
94 binding: 2,
95 visibility: wgpu::ShaderStages::COMPUTE,
96 ty: wgpu::BindingType::Buffer {
97 ty: wgpu::BufferBindingType::Storage { read_only: false },
98 has_dynamic_offset: false,
99 min_binding_size: wgpu::BufferSize::new((NUM_PARTICLES * 16) as _),
100 },
101 count: None,
102 },
103 ],
104 label: None,
105 });
106 let compute_pipeline_layout =
107 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
108 label: Some("compute"),
109 bind_group_layouts: &[&compute_bind_group_layout],
110 push_constant_ranges: &[],
111 });
112
113 let render_pipeline_layout =
116 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
117 label: Some("render"),
118 bind_group_layouts: &[],
119 push_constant_ranges: &[],
120 });
121
122 let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
123 label: None,
124 layout: Some(&render_pipeline_layout),
125 vertex: wgpu::VertexState {
126 module: &draw_shader,
127 entry_point: Some("main_vs"),
128 compilation_options: Default::default(),
129 buffers: &[
130 wgpu::VertexBufferLayout {
131 array_stride: 4 * 4,
132 step_mode: wgpu::VertexStepMode::Instance,
133 attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2],
134 },
135 wgpu::VertexBufferLayout {
136 array_stride: 2 * 4,
137 step_mode: wgpu::VertexStepMode::Vertex,
138 attributes: &wgpu::vertex_attr_array![2 => Float32x2],
139 },
140 ],
141 },
142 fragment: Some(wgpu::FragmentState {
143 module: &draw_shader,
144 entry_point: Some("main_fs"),
145 compilation_options: Default::default(),
146 targets: &[Some(config.view_formats[0].into())],
147 }),
148 primitive: wgpu::PrimitiveState::default(),
149 depth_stencil: None,
150 multisample: wgpu::MultisampleState::default(),
151 multiview: None,
152 cache: None,
153 });
154
155 let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
158 label: Some("Compute pipeline"),
159 layout: Some(&compute_pipeline_layout),
160 module: &compute_shader,
161 entry_point: Some("main"),
162 compilation_options: Default::default(),
163 cache: None,
164 });
165
166 let vertex_buffer_data = [-0.01f32, -0.02, 0.01, -0.02, 0.00, 0.02];
169 let vertices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
170 label: Some("Vertex Buffer"),
171 contents: bytemuck::bytes_of(&vertex_buffer_data),
172 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
173 });
174
175 let mut initial_particle_data = vec![0.0f32; (4 * NUM_PARTICLES) as usize];
178 let mut rng = WyRand::new_seed(42);
179 let mut unif = || rng.generate::<f32>() * 2f32 - 1f32; for particle_instance_chunk in initial_particle_data.chunks_mut(4) {
181 particle_instance_chunk[0] = unif(); particle_instance_chunk[1] = unif(); particle_instance_chunk[2] = unif() * 0.1; particle_instance_chunk[3] = unif() * 0.1; }
186
187 let mut particle_buffers = Vec::<wgpu::Buffer>::new();
191 let mut particle_bind_groups = Vec::<wgpu::BindGroup>::new();
192 for i in 0..2 {
193 particle_buffers.push(
194 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
195 label: Some(&format!("Particle Buffer {i}")),
196 contents: bytemuck::cast_slice(&initial_particle_data),
197 usage: wgpu::BufferUsages::VERTEX
198 | wgpu::BufferUsages::STORAGE
199 | wgpu::BufferUsages::COPY_DST,
200 }),
201 );
202 }
203
204 for i in 0..2 {
208 particle_bind_groups.push(device.create_bind_group(&wgpu::BindGroupDescriptor {
209 layout: &compute_bind_group_layout,
210 entries: &[
211 wgpu::BindGroupEntry {
212 binding: 0,
213 resource: sim_param_buffer.as_entire_binding(),
214 },
215 wgpu::BindGroupEntry {
216 binding: 1,
217 resource: particle_buffers[i].as_entire_binding(),
218 },
219 wgpu::BindGroupEntry {
220 binding: 2,
221 resource: particle_buffers[(i + 1) % 2].as_entire_binding(), },
223 ],
224 label: None,
225 }));
226 }
227
228 let work_group_count =
230 ((NUM_PARTICLES as f32) / (PARTICLES_PER_GROUP as f32)).ceil() as u32;
231
232 Example {
235 particle_bind_groups,
236 particle_buffers,
237 vertices_buffer,
238 compute_pipeline,
239 render_pipeline,
240 work_group_count,
241 frame_num: 0,
242 }
243 }
244
245 fn update(&mut self, _event: winit::event::WindowEvent) {
247 }
249
250 fn resize(
252 &mut self,
253 _sc_desc: &wgpu::SurfaceConfiguration,
254 _device: &wgpu::Device,
255 _queue: &wgpu::Queue,
256 ) {
257 }
259
260 fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
263 let color_attachments = [Some(wgpu::RenderPassColorAttachment {
265 view,
266 depth_slice: None,
267 resolve_target: None,
268 ops: wgpu::Operations {
269 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
270 store: wgpu::StoreOp::Store,
271 },
272 })];
273 let render_pass_descriptor = wgpu::RenderPassDescriptor {
274 label: None,
275 color_attachments: &color_attachments,
276 depth_stencil_attachment: None,
277 timestamp_writes: None,
278 occlusion_query_set: None,
279 };
280
281 let mut command_encoder =
283 device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
284
285 command_encoder.push_debug_group("compute boid movement");
286 {
287 let mut cpass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
289 label: None,
290 timestamp_writes: None,
291 });
292 cpass.set_pipeline(&self.compute_pipeline);
293 cpass.set_bind_group(0, &self.particle_bind_groups[self.frame_num % 2], &[]);
294 cpass.dispatch_workgroups(self.work_group_count, 1, 1);
295 }
296 command_encoder.pop_debug_group();
297
298 command_encoder.push_debug_group("render boids");
299 {
300 let mut rpass = command_encoder.begin_render_pass(&render_pass_descriptor);
302 rpass.set_pipeline(&self.render_pipeline);
303 rpass.set_vertex_buffer(0, self.particle_buffers[(self.frame_num + 1) % 2].slice(..));
305 rpass.set_vertex_buffer(1, self.vertices_buffer.slice(..));
307 rpass.draw(0..3, 0..NUM_PARTICLES);
308 }
309 command_encoder.pop_debug_group();
310
311 self.frame_num += 1;
313
314 queue.submit(Some(command_encoder.finish()));
316 }
317}
318
319pub fn main() {
321 crate::framework::run::<Example>("boids");
322}
323
324#[cfg(test)]
325#[wgpu_test::gpu_test]
326pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
327 name: "boids",
328 image_path: "/examples/features/src/boids/screenshot.png",
330 width: 1024,
331 height: 768,
332 optional_features: wgpu::Features::default(),
333 base_test_parameters: wgpu_test::TestParameters::default()
334 .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS)
335 .limits(wgpu::Limits::downlevel_defaults())
336 .expect_fail(wgpu_test::FailureCase::molten_vk()),
338 comparisons: &[wgpu_test::ComparisonType::Mean(0.005)],
339 _phantom: std::marker::PhantomData::<Example>,
340};