From 0ee1423cb267363d0b315ae7572eb2e6f98bfa4a Mon Sep 17 00:00:00 2001 From: Kai Stevenson Date: Sat, 5 Sep 2026 17:23:46 -0700 Subject: refactor renderer to separate passes --- src/renderer/particles.rs | 183 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 src/renderer/particles.rs (limited to 'src/renderer/particles.rs') diff --git a/src/renderer/particles.rs b/src/renderer/particles.rs new file mode 100644 index 0000000..79ffc59 --- /dev/null +++ b/src/renderer/particles.rs @@ -0,0 +1,183 @@ +use crate::sim::sim_manager::SimManager; + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct RendererParticle { + position: [f32; 2], + data: u32, + life: f32, +} + +fn create_particle_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer { + device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Particle buffer"), + size: (capacity * size_of::()) as u64, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }) +} + +pub struct ParticlesRendererState { + pub particle_pipeline: wgpu::RenderPipeline, + pub particle_buffer: wgpu::Buffer, + pub particle_bind_group: wgpu::BindGroup, + pub particle_capacity: usize, + + particles_count: usize, +} + +impl ParticlesRendererState { + pub fn update_buffers(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, sim: &SimManager) { + puffin::profile_function!(); + // TODO could cull this to visible, maybe it's slower? + let particles: Vec = sim + .particle_manager + .particles + .iter() + .map(|p| RendererParticle { + position: [p.position.x, p.position.y], + data: p.material as u32, + life: p.life, + }) + .collect(); + + if particles.len() > self.particle_capacity { + self.particle_capacity = particles.len().next_power_of_two(); + self.particle_buffer = create_particle_buffer(&device, self.particle_capacity); + } + + queue.write_buffer(&self.particle_buffer, 0, bytemuck::cast_slice(&particles)); + + self.particles_count = particles.len(); + } + + pub fn render(&self, render_pass: &mut wgpu::RenderPass) { + render_pass.set_pipeline(&self.particle_pipeline); + render_pass.set_bind_group(0, &self.particle_bind_group, &[]); + render_pass.draw(0..4, 0..self.particles_count as u32); + } + + pub fn new( + device: &wgpu::Device, + surface_config: &wgpu::SurfaceConfiguration, + camera_uniform_buffer: &wgpu::Buffer, + palette_buffer: &wgpu::Buffer, + ) -> Self { + let particle_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + // camera uniform + wgpu::BindGroupLayoutEntry { + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + binding: 0, + count: None, + visibility: wgpu::ShaderStages::VERTEX, + }, + // palette + wgpu::BindGroupLayoutEntry { + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + binding: 1, + count: None, + visibility: wgpu::ShaderStages::FRAGMENT, + }, + // instance buffer + wgpu::BindGroupLayoutEntry { + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + binding: 2, + count: None, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + }, + ], + }); + + let particle_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Particle shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("../shader/particle.wgsl").into()), + }); + + let particle_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + immediate_size: 0, + bind_group_layouts: &[Some(&particle_bind_group_layout)], + }); + + let particle_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&particle_pipeline_layout), + vertex: wgpu::VertexState { + module: &particle_shader, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &particle_shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: surface_config.format, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview_mask: None, + cache: None, + }); + + let particle_capacity = 2048; + let particle_buffer = create_particle_buffer(&device, particle_capacity); + + let particle_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &particle_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: camera_uniform_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: palette_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: particle_buffer.as_entire_binding(), + }, + ], + }); + + ParticlesRendererState { + particle_pipeline, + particle_buffer, + particle_bind_group, + particle_capacity, + + particles_count: 0, + } + } +} -- cgit v1.3.1