use nanorand::{Rng, WyRand};
use std::mem::size_of;
use wgpu::util::DeviceExt;
const NUM_PARTICLES: u32 = 1500;
const PARTICLES_PER_GROUP: u32 = 64;
struct Example {
particle_bind_groups: Vec<wgpu::BindGroup>,
particle_buffers: Vec<wgpu::Buffer>,
vertices_buffer: wgpu::Buffer,
compute_pipeline: wgpu::ComputePipeline,
render_pipeline: wgpu::RenderPipeline,
work_group_count: u32,
frame_num: usize,
}
impl crate::framework::Example for Example {
fn required_limits() -> wgpu::Limits {
wgpu::Limits::downlevel_defaults()
}
fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities {
wgpu::DownlevelCapabilities {
flags: wgpu::DownlevelFlags::COMPUTE_SHADERS,
..Default::default()
}
}
fn init(
config: &wgpu::SurfaceConfiguration,
_adapter: &wgpu::Adapter,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> Self {
let compute_shader = device.create_shader_module(wgpu::include_wgsl!("compute.wgsl"));
let draw_shader = device.create_shader_module(wgpu::include_wgsl!("draw.wgsl"));
let sim_param_data = [
0.04f32, 0.1, 0.025, 0.025, 0.02, 0.05, 0.005, ]
.to_vec();
let sim_param_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Simulation Parameter Buffer"),
contents: bytemuck::cast_slice(&sim_param_data),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let compute_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new(
(sim_param_data.len() * size_of::<f32>()) as _,
),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new((NUM_PARTICLES * 16) as _),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new((NUM_PARTICLES * 16) as _),
},
count: None,
},
],
label: None,
});
let compute_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("compute"),
bind_group_layouts: &[&compute_bind_group_layout],
push_constant_ranges: &[],
});
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("render"),
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &draw_shader,
entry_point: Some("main_vs"),
compilation_options: Default::default(),
buffers: &[
wgpu::VertexBufferLayout {
array_stride: 4 * 4,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2],
},
wgpu::VertexBufferLayout {
array_stride: 2 * 4,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![2 => Float32x2],
},
],
},
fragment: Some(wgpu::FragmentState {
module: &draw_shader,
entry_point: Some("main_fs"),
compilation_options: Default::default(),
targets: &[Some(config.view_formats[0].into())],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Compute pipeline"),
layout: Some(&compute_pipeline_layout),
module: &compute_shader,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
});
let vertex_buffer_data = [-0.01f32, -0.02, 0.01, -0.02, 0.00, 0.02];
let vertices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::bytes_of(&vertex_buffer_data),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
});
let mut initial_particle_data = vec![0.0f32; (4 * NUM_PARTICLES) as usize];
let mut rng = WyRand::new_seed(42);
let mut unif = || rng.generate::<f32>() * 2f32 - 1f32; for particle_instance_chunk in initial_particle_data.chunks_mut(4) {
particle_instance_chunk[0] = unif(); particle_instance_chunk[1] = unif(); particle_instance_chunk[2] = unif() * 0.1; particle_instance_chunk[3] = unif() * 0.1; }
let mut particle_buffers = Vec::<wgpu::Buffer>::new();
let mut particle_bind_groups = Vec::<wgpu::BindGroup>::new();
for i in 0..2 {
particle_buffers.push(
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("Particle Buffer {i}")),
contents: bytemuck::cast_slice(&initial_particle_data),
usage: wgpu::BufferUsages::VERTEX
| wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_DST,
}),
);
}
for i in 0..2 {
particle_bind_groups.push(device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &compute_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: sim_param_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: particle_buffers[i].as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: particle_buffers[(i + 1) % 2].as_entire_binding(), },
],
label: None,
}));
}
let work_group_count =
((NUM_PARTICLES as f32) / (PARTICLES_PER_GROUP as f32)).ceil() as u32;
Example {
particle_bind_groups,
particle_buffers,
vertices_buffer,
compute_pipeline,
render_pipeline,
work_group_count,
frame_num: 0,
}
}
fn update(&mut self, _event: winit::event::WindowEvent) {
}
fn resize(
&mut self,
_sc_desc: &wgpu::SurfaceConfiguration,
_device: &wgpu::Device,
_queue: &wgpu::Queue,
) {
}
fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
let color_attachments = [Some(wgpu::RenderPassColorAttachment {
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})];
let render_pass_descriptor = wgpu::RenderPassDescriptor {
label: None,
color_attachments: &color_attachments,
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
};
let mut command_encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
command_encoder.push_debug_group("compute boid movement");
{
let mut cpass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: None,
timestamp_writes: None,
});
cpass.set_pipeline(&self.compute_pipeline);
cpass.set_bind_group(0, &self.particle_bind_groups[self.frame_num % 2], &[]);
cpass.dispatch_workgroups(self.work_group_count, 1, 1);
}
command_encoder.pop_debug_group();
command_encoder.push_debug_group("render boids");
{
let mut rpass = command_encoder.begin_render_pass(&render_pass_descriptor);
rpass.set_pipeline(&self.render_pipeline);
rpass.set_vertex_buffer(0, self.particle_buffers[(self.frame_num + 1) % 2].slice(..));
rpass.set_vertex_buffer(1, self.vertices_buffer.slice(..));
rpass.draw(0..3, 0..NUM_PARTICLES);
}
command_encoder.pop_debug_group();
self.frame_num += 1;
queue.submit(Some(command_encoder.finish()));
}
}
pub fn main() {
crate::framework::run::<Example>("boids");
}
#[cfg(test)]
#[wgpu_test::gpu_test]
static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
name: "boids",
image_path: "/examples/src/boids/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::default(),
base_test_parameters: wgpu_test::TestParameters::default()
.downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS)
.limits(wgpu::Limits::downlevel_defaults())
.expect_fail(wgpu_test::FailureCase::molten_vk()),
comparisons: &[wgpu_test::ComparisonType::Mean(0.005)],
_phantom: std::marker::PhantomData::<Example>,
};