Skip to main content

gizmo_renderer/gpu_particles/
system.rs

1use wgpu::util::DeviceExt;
2
3use super::pipeline::{create_particle_pipelines, ParticlePipelines};
4use super::types::*;
5
6pub struct GpuParticleSystem {
7    pub max_particles: u32,
8    pub particles_buffer: wgpu::Buffer,
9    pub params_buffer: wgpu::Buffer,
10    pub pipelines: ParticlePipelines,
11    pub quad_vertex_buffer: wgpu::Buffer,
12    pub active_particles: u32,
13    pub ring_head: std::sync::atomic::AtomicU32,
14}
15
16impl GpuParticleSystem {
17    pub fn new(
18        device: &wgpu::Device,
19        max_particles: u32,
20        global_bind_group_layout: &wgpu::BindGroupLayout,
21        output_format: wgpu::TextureFormat,
22    ) -> Self {
23        let mut initial_particles = Vec::with_capacity(max_particles as usize);
24        for _ in 0..max_particles {
25            initial_particles.push(GpuParticle {
26                position: [0.0, 0.0, 0.0],
27                life: 999.0, // Başlangıçta hepsi ÖLÜ
28                velocity: [0.0, 0.0, 0.0],
29                max_life: 0.1,
30                color: [0.0, 0.0, 0.0, 0.0],
31                size_start: 0.0,
32                size_end: 0.0,
33                _padding: [0.0; 2],
34            });
35        }
36
37        let particles_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
38            label: Some("GPU Particles Buffer"),
39            contents: bytemuck::cast_slice(&initial_particles),
40            usage: wgpu::BufferUsages::STORAGE
41                | wgpu::BufferUsages::VERTEX
42                | wgpu::BufferUsages::COPY_DST,
43        });
44
45        let params = ParticleSimParams {
46            dt: 0.0,
47            global_gravity: 0.0,
48            global_drag: 0.0,
49            _padding: 0.0,
50        };
51        let params_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
52            label: Some("GPU Particle Params Buffer"),
53            contents: bytemuck::cast_slice(&[params]),
54            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
55        });
56
57        let quad_vertices: [[f32; 2]; 4] = [[-0.5, -0.5], [0.5, -0.5], [-0.5, 0.5], [0.5, 0.5]];
58
59        let quad_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
60            label: Some("Particle Quad Vertex Buffer"),
61            contents: bytemuck::cast_slice(&quad_vertices),
62            usage: wgpu::BufferUsages::VERTEX,
63        });
64
65        let pipelines = create_particle_pipelines(
66            device,
67            global_bind_group_layout,
68            output_format,
69            &params_buffer,
70            &particles_buffer,
71        );
72
73        Self {
74            max_particles,
75            particles_buffer,
76            params_buffer,
77            pipelines,
78            quad_vertex_buffer,
79            active_particles: max_particles,
80            ring_head: std::sync::atomic::AtomicU32::new(0),
81        }
82    }
83
84    pub fn update_params(&self, queue: &wgpu::Queue, dt: f32) {
85        let params = ParticleSimParams {
86            dt,
87            global_gravity: 9.81,
88            global_drag: 0.8,
89            _padding: 0.0,
90        };
91        queue.write_buffer(&self.params_buffer, 0, bytemuck::cast_slice(&[params]));
92    }
93
94    pub fn spawn_particles(&self, queue: &wgpu::Queue, new_particles: &[GpuParticle]) {
95        if new_particles.is_empty() {
96            return;
97        }
98
99        let count = new_particles.len() as u32;
100        let mut head = self
101            .ring_head
102            .fetch_add(count, std::sync::atomic::Ordering::Relaxed)
103            % self.max_particles;
104
105        let mut remaining = count;
106        let mut offset = 0;
107
108        while remaining > 0 {
109            let to_write = remaining.min(self.max_particles - head);
110            let slice = &new_particles[offset as usize..(offset + to_write) as usize];
111
112            queue.write_buffer(
113                &self.particles_buffer,
114                (head as usize * std::mem::size_of::<GpuParticle>()) as wgpu::BufferAddress,
115                bytemuck::cast_slice(slice),
116            );
117
118            head = (head + to_write) % self.max_particles;
119            offset += to_write;
120            remaining -= to_write;
121        }
122    }
123
124    pub fn compute_pass(&self, encoder: &mut wgpu::CommandEncoder, active_particles: u32) {
125        if active_particles == 0 {
126            return;
127        }
128        let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
129            label: Some("Particle Compute Pass"),
130            timestamp_writes: None,
131        });
132        cpass.set_pipeline(&self.pipelines.compute_pipeline);
133        cpass.set_bind_group(0, &self.pipelines.compute_bind_group, &[]);
134        let workgroups = active_particles.div_ceil(64);
135        cpass.dispatch_workgroups(workgroups, 1, 1);
136    }
137
138    pub fn render_pass<'a>(
139        &'a self,
140        rpass: &mut wgpu::RenderPass<'a>,
141        global_bind_group: &'a wgpu::BindGroup,
142        active_particles: u32,
143    ) {
144        if active_particles == 0 {
145            return;
146        }
147        rpass.set_pipeline(&self.pipelines.render_pipeline);
148        rpass.set_bind_group(0, global_bind_group, &[]);
149        rpass.set_vertex_buffer(0, self.quad_vertex_buffer.slice(..));
150        rpass.set_vertex_buffer(1, self.particles_buffer.slice(..));
151        rpass.draw(0..4, 0..active_particles);
152    }
153
154    /// Helper method to spawn an explosion of particles (e.g. dust or debris from a fracture).
155    pub fn spawn_explosion(
156        &self,
157        queue: &wgpu::Queue,
158        center: [f32; 3],
159        count: u32,
160        base_color: [f32; 4],
161        force: f32,
162    ) {
163        let mut new_particles = Vec::with_capacity(count as usize);
164        for _ in 0..count {
165            let u: f32 = rand::random();
166            let v: f32 = rand::random();
167            let theta = u * 2.0 * std::f32::consts::PI;
168            let phi = (2.0 * v - 1.0).acos();
169            let r = force * (0.5 + 0.5 * rand::random::<f32>());
170
171            let vx = r * phi.sin() * theta.cos();
172            let vy = r * phi.sin() * theta.sin() + force * 0.5; // Upward bias
173            let vz = r * phi.cos();
174
175            let life = 0.5 + rand::random::<f32>() * 1.5;
176
177            new_particles.push(GpuParticle {
178                position: center,
179                life,
180                velocity: [vx, vy, vz],
181                max_life: life,
182                color: base_color,
183                size_start: 0.1 + rand::random::<f32>() * 0.2,
184                size_end: 0.0,
185                _padding: [0.0; 2],
186            });
187        }
188        self.spawn_particles(queue, &new_particles);
189    }
190}