use wgpu::util::DeviceExt;
use super::pipeline::{create_particle_pipelines, ParticlePipelines};
use super::types::*;
pub struct GpuParticleSystem {
pub max_particles: u32,
pub particles_buffer: wgpu::Buffer,
pub params_buffer: wgpu::Buffer,
pub pipelines: ParticlePipelines,
pub quad_vertex_buffer: wgpu::Buffer,
pub active_particles: u32,
pub ring_head: std::sync::atomic::AtomicU32,
}
impl GpuParticleSystem {
pub fn new(
device: &wgpu::Device,
max_particles: u32,
global_bind_group_layout: &wgpu::BindGroupLayout,
output_format: wgpu::TextureFormat,
) -> Self {
let mut initial_particles = Vec::with_capacity(max_particles as usize);
for _ in 0..max_particles {
initial_particles.push(GpuParticle {
position: [0.0, 0.0, 0.0],
life: 999.0, velocity: [0.0, 0.0, 0.0],
max_life: 0.1,
color: [0.0, 0.0, 0.0, 0.0],
size_start: 0.0,
size_end: 0.0,
_padding: [0.0; 2],
});
}
let particles_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("GPU Particles Buffer"),
contents: bytemuck::cast_slice(&initial_particles),
usage: wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::VERTEX
| wgpu::BufferUsages::COPY_DST,
});
let params = ParticleSimParams {
dt: 0.0,
global_gravity: 0.0,
global_drag: 0.0,
_padding: 0.0,
};
let params_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("GPU Particle Params Buffer"),
contents: bytemuck::cast_slice(&[params]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let quad_vertices: [[f32; 2]; 4] = [[-0.5, -0.5], [0.5, -0.5], [-0.5, 0.5], [0.5, 0.5]];
let quad_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Particle Quad Vertex Buffer"),
contents: bytemuck::cast_slice(&quad_vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let pipelines = create_particle_pipelines(
device,
global_bind_group_layout,
output_format,
¶ms_buffer,
&particles_buffer,
);
Self {
max_particles,
particles_buffer,
params_buffer,
pipelines,
quad_vertex_buffer,
active_particles: max_particles,
ring_head: std::sync::atomic::AtomicU32::new(0),
}
}
pub fn update_params(&self, queue: &wgpu::Queue, dt: f32) {
let params = ParticleSimParams {
dt,
global_gravity: 9.81,
global_drag: 0.8,
_padding: 0.0,
};
queue.write_buffer(&self.params_buffer, 0, bytemuck::cast_slice(&[params]));
}
pub fn spawn_particles(&self, queue: &wgpu::Queue, new_particles: &[GpuParticle]) {
if new_particles.is_empty() {
return;
}
let count = new_particles.len() as u32;
let mut head = self
.ring_head
.fetch_add(count, std::sync::atomic::Ordering::Relaxed)
% self.max_particles;
let mut remaining = count;
let mut offset = 0;
while remaining > 0 {
let to_write = remaining.min(self.max_particles - head);
let slice = &new_particles[offset as usize..(offset + to_write) as usize];
queue.write_buffer(
&self.particles_buffer,
(head as usize * std::mem::size_of::<GpuParticle>()) as wgpu::BufferAddress,
bytemuck::cast_slice(slice),
);
head = (head + to_write) % self.max_particles;
offset += to_write;
remaining -= to_write;
}
}
pub fn compute_pass(&self, encoder: &mut wgpu::CommandEncoder, active_particles: u32) {
if active_particles == 0 {
return;
}
let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Particle Compute Pass"),
timestamp_writes: None,
});
cpass.set_pipeline(&self.pipelines.compute_pipeline);
cpass.set_bind_group(0, &self.pipelines.compute_bind_group, &[]);
let workgroups = active_particles.div_ceil(64);
cpass.dispatch_workgroups(workgroups, 1, 1);
}
pub fn render_pass<'a>(
&'a self,
rpass: &mut wgpu::RenderPass<'a>,
global_bind_group: &'a wgpu::BindGroup,
active_particles: u32,
) {
if active_particles == 0 {
return;
}
rpass.set_pipeline(&self.pipelines.render_pipeline);
rpass.set_bind_group(0, global_bind_group, &[]);
rpass.set_vertex_buffer(0, self.quad_vertex_buffer.slice(..));
rpass.set_vertex_buffer(1, self.particles_buffer.slice(..));
rpass.draw(0..4, 0..active_particles);
}
pub fn spawn_explosion(
&self,
queue: &wgpu::Queue,
center: [f32; 3],
count: u32,
base_color: [f32; 4],
force: f32,
) {
let mut new_particles = Vec::with_capacity(count as usize);
for _ in 0..count {
let u: f32 = rand::random();
let v: f32 = rand::random();
let theta = u * 2.0 * std::f32::consts::PI;
let phi = (2.0 * v - 1.0).acos();
let r = force * (0.5 + 0.5 * rand::random::<f32>());
let vx = r * phi.sin() * theta.cos();
let vy = r * phi.sin() * theta.sin() + force * 0.5; let vz = r * phi.cos();
let life = 0.5 + rand::random::<f32>() * 1.5;
new_particles.push(GpuParticle {
position: center,
life,
velocity: [vx, vy, vz],
max_life: life,
color: base_color,
size_start: 0.1 + rand::random::<f32>() * 0.2,
size_end: 0.0,
_padding: [0.0; 2],
});
}
self.spawn_particles(queue, &new_particles);
}
}