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/debug.rs | 148 ++++++++ src/renderer/instances.rs | 325 +++++++++++++++++ src/renderer/mod.rs | 871 +++------------------------------------------- src/renderer/particles.rs | 183 ++++++++++ src/renderer/ui.rs | 2 - src/renderer/vfx.rs | 232 ++++++++++++ 6 files changed, 931 insertions(+), 830 deletions(-) create mode 100644 src/renderer/debug.rs create mode 100644 src/renderer/instances.rs create mode 100644 src/renderer/particles.rs create mode 100644 src/renderer/vfx.rs (limited to 'src/renderer') diff --git a/src/renderer/debug.rs b/src/renderer/debug.rs new file mode 100644 index 0000000..c86d35e --- /dev/null +++ b/src/renderer/debug.rs @@ -0,0 +1,148 @@ +use rapier2d::pipeline::DebugRenderMode; + +use crate::sim::{rb_manager::debug_render::DebugVertex, sim_manager::SimManager}; + +const DEBUG_VERTEX_ATTRIBUTES: [wgpu::VertexAttribute; 2] = + wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x4]; + +fn create_debug_vertex_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer { + device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Debug vertex buffer"), + size: (capacity * size_of::()) as u64, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }) +} + +pub struct PhysicsDebugRendererState { + debug_pipeline: wgpu::RenderPipeline, + debug_bind_group: wgpu::BindGroup, + debug_vertex_buffer: wgpu::Buffer, + debug_vertex_capacity: usize, + + debug_vertex_count: usize, +} + +impl PhysicsDebugRendererState { + pub fn update_buffers( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + sim: &mut SimManager, + debug_render_mode: DebugRenderMode, + ) { + puffin::profile_function!(); + let vertices = sim.rb_manager.debug_render(debug_render_mode); + + if vertices.len() > self.debug_vertex_capacity { + self.debug_vertex_capacity = vertices.len().next_power_of_two(); + self.debug_vertex_buffer = + create_debug_vertex_buffer(&device, self.debug_vertex_capacity); + } + + queue.write_buffer(&self.debug_vertex_buffer, 0, bytemuck::cast_slice(vertices)); + + self.debug_vertex_count = vertices.len(); + } + + pub fn render(&self, render_pass: &mut wgpu::RenderPass) { + render_pass.set_pipeline(&self.debug_pipeline); + render_pass.set_bind_group(0, &self.debug_bind_group, &[]); + render_pass.set_vertex_buffer(0, self.debug_vertex_buffer.slice(..)); + render_pass.draw(0..self.debug_vertex_count as u32, 0..1); + } + + pub fn new( + device: &wgpu::Device, + surface_config: &wgpu::SurfaceConfiguration, + camera_uniform_buffer: &wgpu::Buffer, + ) -> Self { + let debug_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Debug bind group layout"), + 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, + }, + ], + }); + + let debug_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("Debug bind group"), + layout: &debug_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: camera_uniform_buffer.as_entire_binding(), + }], + }); + + let debug_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Debug shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("../shader/debug.wgsl").into()), + }); + + let debug_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Debug pipeline layout"), + immediate_size: 0, + bind_group_layouts: &[Some(&debug_bind_group_layout)], + }); + + let debug_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Debug pipeline"), + layout: Some(&debug_pipeline_layout), + vertex: wgpu::VertexState { + module: &debug_shader, + entry_point: Some("vs_main"), + buffers: &[wgpu::VertexBufferLayout { + array_stride: size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &DEBUG_VERTEX_ATTRIBUTES, + }], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &debug_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::LineList, + 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 debug_vertex_capacity = 4096; + let debug_vertex_buffer = create_debug_vertex_buffer(&device, debug_vertex_capacity); + + PhysicsDebugRendererState { + debug_pipeline, + debug_bind_group, + debug_vertex_buffer, + debug_vertex_capacity, + debug_vertex_count: 0, + } + } +} diff --git a/src/renderer/instances.rs b/src/renderer/instances.rs new file mode 100644 index 0000000..1292b99 --- /dev/null +++ b/src/renderer/instances.rs @@ -0,0 +1,325 @@ +use fxhash::FxHashMap; + +use crate::{ + camera::Camera, + config::{CELLS_IN_CHUNK, CHUNK_SIZE}, + sim::{cell_manager::manager::CellManager, entity::EntityId, sim_manager::SimManager}, +}; + +// TODO derive from shared chunk config +const CHUNK_SLOTS: usize = 11 * 200; + +fn create_instance_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer { + device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Instance buffer"), + size: (capacity * size_of::()) as u64, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }) +} + +fn create_cell_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer { + device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Cell buffer"), + size: (capacity * CELLS_IN_CHUNK) as u64, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }) +} + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct RendererInstance { + centre: [f32; 2], + cos_sin: [f32; 2], + half_size: [f32; 2], + dims: [u32; 2], + cell_offset: u32, + _padding: u32, +} + +pub struct InstancesRendererState { + instance_pipeline: wgpu::RenderPipeline, + instance_buffer: wgpu::Buffer, + cell_buffer: wgpu::Buffer, + instance_bind_group: wgpu::BindGroup, + entity_capacity: usize, + + renderer_entities: FxHashMap, + instances_count: usize, +} + +impl InstancesRendererState { + pub fn update_buffers( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + sim: &mut SimManager, + camera: &Camera, + ) { + puffin::profile_function!(); + let mut cell_buffer: [u8; CELLS_IN_CHUNK] = [0; CELLS_IN_CHUNK]; + let mut instances: Vec = Vec::new(); + + for &idx in sim.cell_manager.chunk_position_to_chunk_idx.values() { + let chunk = &mut sim.cell_manager.chunks[idx]; + if !chunk.needs_texture_update { + continue; + } + chunk.needs_texture_update = false; + + for (byte, cell) in cell_buffer.iter_mut().zip(chunk.cells.iter()) { + *byte = cell.material as u8; + } + + queue.write_buffer( + &self.cell_buffer, + (idx * CELLS_IN_CHUNK) as u64, + &cell_buffer, + ); + } + + // TODO optimize, this is very inefficient, just build it per frame with positions and skip the lookup? + self.renderer_entities.clear(); + for entity in sim.entities.values() { + // TODO add "needs texture update"? + if let Some(cells) = &entity.data.cells { + for i in 0..cells.cells.len() { + cell_buffer[i] = cells.cells[i].material as u8; + } + + let next_slot = CHUNK_SLOTS + self.renderer_entities.len(); + let slot = *self + .renderer_entities + .entry(entity.data.id) + .or_insert(next_slot); + + queue.write_buffer( + &self.cell_buffer, + (slot * CHUNK_SIZE as usize * CHUNK_SIZE as usize) as u64, + &cell_buffer, + ); + } + } + + let (lower, upper) = camera.viewport_bounds_world(); + let ((cxl, cyl), _) = + CellManager::split_game_position(lower.x.floor() as i32, lower.y.floor() as i32); + let ((cxu, cyu), _) = + CellManager::split_game_position(upper.x.floor() as i32, upper.y.floor() as i32); + + let half_size = [CHUNK_SIZE as f32 / 2.0, CHUNK_SIZE as f32 / 2.0]; + let dims = [CHUNK_SIZE as u32, CHUNK_SIZE as u32]; + + for cx in cxl..=cxu { + for cy in cyl..=cyu { + if let Some(idx) = sim.cell_manager.chunk_position_to_chunk_idx.get(&(cx, cy)) { + instances.push(RendererInstance { + centre: [ + (cx * CHUNK_SIZE) as f32 + half_size[0], + (cy * CHUNK_SIZE) as f32 + half_size[1], + ], + cos_sin: [1.0, 0.0], + half_size, + dims, + cell_offset: (idx * CELLS_IN_CHUNK) as u32, + _padding: 0, + }); + } + } + } + + let mut entities = 0; + for (id, slot) in &self.renderer_entities { + if let Some(entity) = sim.entities.get(id) + && let Some(cells) = &entity.data.cells + && let Some((pos, (cos, sin))) = entity.data._transform(&sim.rb_manager) + { + // TODO access this better + // let sleeping = sim + // .rb_manager + // .physics_manager + // .world + // .bodies + // .get(entity.data.rb_h.unwrap()) + // .unwrap() + // .is_sleeping(); + let sleeping = false; + + entities += 1; + instances.push(RendererInstance { + centre: if sleeping { + pos.round().to_array() + } else { + pos.to_array() + }, + cos_sin: [cos, sin], + half_size: (cells.size / 2).as_vec2().to_array(), + dims: cells.size.as_uvec2().to_array(), + cell_offset: (slot * CHUNK_SIZE as usize * CHUNK_SIZE as usize) as u32, + _padding: 0, + }); + } + } + + if entities > self.entity_capacity { + self.entity_capacity = entities.next_power_of_two(); + self.instance_buffer = + create_instance_buffer(device, self.entity_capacity + CHUNK_SLOTS); + self.cell_buffer = create_cell_buffer(device, CHUNK_SLOTS + self.entity_capacity); + } + + queue.write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&instances)); + + self.instances_count = instances.len(); + } + + pub fn render(&self, render_pass: &mut wgpu::RenderPass) { + render_pass.set_pipeline(&self.instance_pipeline); + render_pass.set_bind_group(0, &self.instance_bind_group, &[]); + render_pass.draw(0..4, 0..self.instances_count as u32); + } + + pub fn new( + device: &wgpu::Device, + surface_config: &wgpu::SurfaceConfiguration, + camera_uniform_buffer: &wgpu::Buffer, + palette_buffer: &wgpu::Buffer, + ) -> Self { + let instance_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Instance shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("../shader/instance.wgsl").into()), + }); + + let instance_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, + }, + // cells + wgpu::BindGroupLayoutEntry { + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + binding: 3, + count: None, + visibility: wgpu::ShaderStages::FRAGMENT, + }, + ], + }); + + let instance_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + immediate_size: 0, + bind_group_layouts: &[Some(&instance_bind_group_layout)], + }); + + let instance_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&instance_pipeline_layout), + vertex: wgpu::VertexState { + module: &instance_shader, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &instance_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 entity_capacity = 2048; + let instance_buffer = create_instance_buffer(&device, CHUNK_SLOTS + entity_capacity); + + let cell_buffer = create_cell_buffer(&device, entity_capacity + CHUNK_SLOTS); + + let instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &instance_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: instance_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: cell_buffer.as_entire_binding(), + }, + ], + }); + + InstancesRendererState { + instance_pipeline, + instance_buffer, + cell_buffer, + instance_bind_group, + entity_capacity, + + renderer_entities: FxHashMap::default(), + instances_count: 0, + } + } +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 2deb278..3cf340d 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,65 +1,26 @@ +mod debug; +mod instances; +mod particles; mod ui; +mod vfx; use std::sync::Arc; -use fxhash::FxHashMap; use winit::window::Window; use crate::{ Config, Diagnostics, InputManager, camera::Camera, - config::{CELLS_IN_CHUNK, CHUNK_SIZE}, - content::{materials::MaterialId, vfx::VfxMaterialId}, + content::materials::MaterialId, proc_gen::TilesetWorldGenerator, - renderer::ui::draw_egui, - sim::{ - cell_manager::manager::CellManager, entity::EntityId, - rb_manager::debug_render::DebugVertex, sim_manager::SimManager, + renderer::{ + debug::PhysicsDebugRendererState, instances::InstancesRendererState, + particles::ParticlesRendererState, ui::draw_egui, vfx::VfxRendererState, }, + sim::sim_manager::SimManager, vfx::VfxManager, }; -// TODO derive from shared chunk config -const CHUNK_SLOTS: usize = 11 * 200; - -#[repr(C)] -#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -struct RendererInstance { - centre: [f32; 2], - cos_sin: [f32; 2], - half_size: [f32; 2], - dims: [u32; 2], - cell_offset: u32, - _padding: u32, -} - -#[repr(C)] -#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -struct RendererParticle { - position: [f32; 2], - data: u32, - life: f32, -} - -#[repr(C)] -#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -struct RendererVfxMaterial { - color: [f32; 4], - length: f32, - _padding: [u32; 3], -} - -#[repr(C)] -#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -struct RendererVfxLine { - a: [f32; 2], - b: [f32; 2], - width: f32, - material: u32, - alive_for: f32, - lifetime: f32, -} - fn srgb_to_linear(channel: u8) -> f32 { let channel = channel as f32 / 255.0; if channel <= 0.04045 { @@ -69,54 +30,6 @@ fn srgb_to_linear(channel: u8) -> f32 { } } -const DEBUG_VERTEX_ATTRIBUTES: [wgpu::VertexAttribute; 2] = - wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x4]; - -fn create_debug_vertex_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer { - device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Debug vertex buffer"), - size: (capacity * size_of::()) as u64, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }) -} - -fn create_instance_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer { - device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Instance buffer"), - size: (capacity * size_of::()) as u64, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }) -} - -fn create_cell_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer { - device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Cell buffer"), - size: (capacity * CELLS_IN_CHUNK) as u64, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }) -} - -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, - }) -} - -fn create_vfx_line_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer { - device.create_buffer(&wgpu::BufferDescriptor { - label: Some("VFX line buffer"), - size: (capacity * size_of::()) as u64, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }) -} - pub struct RendererState { window: Arc, surface: wgpu::Surface<'static>, @@ -134,36 +47,13 @@ pub struct RendererState { // common camera_uniform_buffer: wgpu::Buffer, - // instances - instance_pipeline: wgpu::RenderPipeline, - instance_buffer: wgpu::Buffer, - cell_buffer: wgpu::Buffer, - instance_bind_group: wgpu::BindGroup, - entity_capacity: usize, - - // particles - particle_pipeline: wgpu::RenderPipeline, - particle_buffer: wgpu::Buffer, - particle_bind_group: wgpu::BindGroup, - particle_capacity: usize, - - // vfx - vfx_line_pipeline: wgpu::RenderPipeline, - vfx_line_buffer: wgpu::Buffer, - vfx_line_bind_group: wgpu::BindGroup, - vfx_line_capacity: usize, - - // physics debug lines - debug_pipeline: wgpu::RenderPipeline, - debug_bind_group: wgpu::BindGroup, - debug_vertex_buffer: wgpu::Buffer, - debug_vertex_capacity: usize, - - renderer_rb_entities: FxHashMap, + instances_renderer_state: InstancesRendererState, + particles_renderer_state: ParticlesRendererState, + vfx_renderer_state: VfxRendererState, + physics_debug_renderer_state: PhysicsDebugRendererState, } impl RendererState { - // https://sotrh.github.io/learn-wgpu/beginner/tutorial1-window pub async fn new(window: Arc) -> Self { let size = window.inner_size(); println!("Got window! ({}x{})", size.width, size.height); @@ -253,135 +143,6 @@ impl RendererState { queue.write_buffer(&palette_buffer, 0, bytemuck::cast_slice(&palette)); - // --- VFX materials --- - let mut vfx_material_palette = [RendererVfxMaterial { - color: [0.0f32; 4], - length: 0.0, - _padding: [0; 3], - }; VfxMaterialId::ALL.len()]; - for material in VfxMaterialId::ALL { - let def = material.def(); - let i = material as usize; - vfx_material_palette[i].color = [ - srgb_to_linear(def.color.0), - srgb_to_linear(def.color.1), - srgb_to_linear(def.color.2), - def.color.3 as f32 / 255.0, - ]; - vfx_material_palette[i].length = def.length; - } - - let vfx_material_palette_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("VFX Material Palette buffer"), - size: size_of_val(&vfx_material_palette) as u64, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - queue.write_buffer( - &vfx_material_palette_buffer, - 0, - bytemuck::cast_slice(&vfx_material_palette), - ); - - // --- INSTANCES --- - let instance_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, - }, - // cells - wgpu::BindGroupLayoutEntry { - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - binding: 3, - count: None, - visibility: wgpu::ShaderStages::FRAGMENT, - }, - ], - }); - - let instance_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Instance shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("../shader/instance.wgsl").into()), - }); - - let instance_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - immediate_size: 0, - bind_group_layouts: &[Some(&instance_bind_group_layout)], - }); - - let instance_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: None, - layout: Some(&instance_pipeline_layout), - vertex: wgpu::VertexState { - module: &instance_shader, - entry_point: Some("vs_main"), - buffers: &[], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &instance_shader, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - format: 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 camera_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("Camera uniform buffer"), size: 16, @@ -389,333 +150,17 @@ impl RendererState { mapped_at_creation: false, }); - let entity_capacity = 2048; - let instance_buffer = create_instance_buffer(&device, CHUNK_SLOTS + entity_capacity); - - let cell_buffer = create_cell_buffer(&device, entity_capacity + CHUNK_SLOTS); - - let instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: None, - layout: &instance_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: instance_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 3, - resource: cell_buffer.as_entire_binding(), - }, - ], - }); - - // --- PARTICLES --- - 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: 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(), - }, - ], - }); - - // --- VFX_LINES --- - let vfx_line_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, - }, - // material 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, - // don't need vertex here - visibility: wgpu::ShaderStages::VERTEX | 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 vfx_line_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("VFX Line shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("../shader/vfx_line.wgsl").into()), - }); + let instances_renderer_state = + InstancesRendererState::new(&device, &config, &camera_uniform_buffer, &palette_buffer); - let vfx_line_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - immediate_size: 0, - bind_group_layouts: &[Some(&vfx_line_bind_group_layout)], - }); + let particles_renderer_state = + ParticlesRendererState::new(&device, &config, &camera_uniform_buffer, &palette_buffer); - let vfx_line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: None, - layout: Some(&vfx_line_pipeline_layout), - vertex: wgpu::VertexState { - module: &vfx_line_shader, - entry_point: Some("vs_main"), - buffers: &[], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &vfx_line_shader, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - format: 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 vfx_renderer_state = + VfxRendererState::new(&device, &queue, &config, &camera_uniform_buffer); - let vfx_line_capacity = 512; - let vfx_line_buffer = create_vfx_line_buffer(&device, vfx_line_capacity); - - let vfx_line_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: None, - layout: &vfx_line_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: camera_uniform_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: vfx_material_palette_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: vfx_line_buffer.as_entire_binding(), - }, - ], - }); - - // --- DEBUG --- - let debug_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Debug bind group layout"), - 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, - }, - ], - }); - - let debug_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("Debug bind group"), - layout: &debug_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: camera_uniform_buffer.as_entire_binding(), - }], - }); - - let debug_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Debug shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("../shader/debug.wgsl").into()), - }); - - let debug_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Debug pipeline layout"), - immediate_size: 0, - bind_group_layouts: &[Some(&debug_bind_group_layout)], - }); - - let debug_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Debug pipeline"), - layout: Some(&debug_pipeline_layout), - vertex: wgpu::VertexState { - module: &debug_shader, - entry_point: Some("vs_main"), - buffers: &[wgpu::VertexBufferLayout { - array_stride: size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &DEBUG_VERTEX_ATTRIBUTES, - }], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &debug_shader, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::LineList, - 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 debug_vertex_capacity = 4096; - let debug_vertex_buffer = create_debug_vertex_buffer(&device, debug_vertex_capacity); + let physics_debug_renderer_state = + PhysicsDebugRendererState::new(&device, &config, &camera_uniform_buffer); RendererState { window, @@ -731,28 +176,10 @@ impl RendererState { camera_uniform_buffer, - instance_pipeline, - instance_buffer, - cell_buffer, - instance_bind_group, - entity_capacity, - - particle_pipeline, - particle_buffer, - particle_bind_group, - particle_capacity, - - vfx_line_pipeline, - vfx_line_buffer, - vfx_line_bind_group, - vfx_line_capacity, - - debug_pipeline, - debug_bind_group, - debug_vertex_buffer, - debug_vertex_capacity, - - renderer_rb_entities: FxHashMap::default(), + instances_renderer_state, + particles_renderer_state, + vfx_renderer_state, + physics_debug_renderer_state, } } @@ -868,207 +295,21 @@ impl RendererState { bytemuck::bytes_of(&camera.to_uniform()), ); - let mut cell_buffer: [u8; CELLS_IN_CHUNK] = [0; CELLS_IN_CHUNK]; - let mut instances: Vec = Vec::new(); - - { - puffin::profile_scope!("Upload chunk cells"); - for &idx in sim.cell_manager.chunk_position_to_chunk_idx.values() { - let chunk = &mut sim.cell_manager.chunks[idx]; - if !chunk.needs_texture_update { - continue; - } - chunk.needs_texture_update = false; - - for (byte, cell) in cell_buffer.iter_mut().zip(chunk.cells.iter()) { - *byte = cell.material as u8; - } - - self.queue.write_buffer( - &self.cell_buffer, - (idx * CELLS_IN_CHUNK) as u64, - &cell_buffer, - ); - } - } - - { - puffin::profile_scope!("Upload entity cells"); - // TODO optimize, this is very inefficient, just build it per frame with positions and skip the lookup? - self.renderer_rb_entities.clear(); - for entity in sim.entities.values() { - // TODO add "needs texture update"? - if let Some(cells) = &entity.data.cells { - for i in 0..cells.cells.len() { - cell_buffer[i] = cells.cells[i].material as u8; - } - - let next_slot = CHUNK_SLOTS + self.renderer_rb_entities.len(); - let slot = *self - .renderer_rb_entities - .entry(entity.data.id) - .or_insert(next_slot); - - self.queue.write_buffer( - &self.cell_buffer, - (slot * CHUNK_SIZE as usize * CHUNK_SIZE as usize) as u64, - &cell_buffer, - ); - } - } - } - - { - puffin::profile_scope!("Build instances"); - let (lower, upper) = camera.viewport_bounds_world(); - let ((cxl, cyl), _) = - CellManager::split_game_position(lower.x.floor() as i32, lower.y.floor() as i32); - let ((cxu, cyu), _) = - CellManager::split_game_position(upper.x.floor() as i32, upper.y.floor() as i32); - - let half_size = [CHUNK_SIZE as f32 / 2.0, CHUNK_SIZE as f32 / 2.0]; - let dims = [CHUNK_SIZE as u32, CHUNK_SIZE as u32]; - - for cx in cxl..=cxu { - for cy in cyl..=cyu { - if let Some(idx) = sim.cell_manager.chunk_position_to_chunk_idx.get(&(cx, cy)) { - instances.push(RendererInstance { - centre: [ - (cx * CHUNK_SIZE) as f32 + half_size[0], - (cy * CHUNK_SIZE) as f32 + half_size[1], - ], - cos_sin: [1.0, 0.0], - half_size, - dims, - cell_offset: (idx * CELLS_IN_CHUNK) as u32, - _padding: 0, - }); - } - } - } - - let mut entities = 0; - for (id, slot) in &self.renderer_rb_entities { - if let Some(entity) = sim.entities.get(id) - && let Some(cells) = &entity.data.cells - && let Some((pos, (cos, sin))) = entity.data._transform(&sim.rb_manager) - { - // TODO access this better - // let sleeping = sim - // .rb_manager - // .physics_manager - // .world - // .bodies - // .get(entity.data.rb_h.unwrap()) - // .unwrap() - // .is_sleeping(); - let sleeping = false; - - entities += 1; - instances.push(RendererInstance { - centre: if sleeping { - pos.round().to_array() - } else { - pos.to_array() - }, - cos_sin: [cos, sin], - half_size: (cells.size / 2).as_vec2().to_array(), - dims: cells.size.as_uvec2().to_array(), - cell_offset: (slot * CHUNK_SIZE as usize * CHUNK_SIZE as usize) as u32, - _padding: 0, - }); - } - } - - if entities > self.entity_capacity { - self.entity_capacity = entities.next_power_of_two(); - self.instance_buffer = - create_instance_buffer(&self.device, self.entity_capacity + CHUNK_SLOTS); - self.cell_buffer = - create_cell_buffer(&self.device, CHUNK_SLOTS + self.entity_capacity); - } - - self.queue - .write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&instances)); - } - - let mut particles: Vec = Vec::new(); - - { - puffin::profile_scope!("Build particles"); - // TODO could cull this to visible, maybe it's slower? - particles = 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(&self.device, self.particle_capacity); - } - - self.queue - .write_buffer(&self.particle_buffer, 0, bytemuck::cast_slice(&particles)); - } - - let mut vfx_lines: Vec = Vec::new(); - - { - puffin::profile_scope!("Build VFX lines"); - // TODO could cull this to visible, maybe it's slower? - vfx_lines = vfx_manager - .lines - .iter() - .map(|p| RendererVfxLine { - a: p.a.to_array(), - b: p.b.to_array(), - material: p.material as u32, - width: p.width, - alive_for: p.alive_for, - lifetime: p.lifetime, - }) - .collect(); - - if vfx_lines.len() > self.vfx_line_capacity { - self.vfx_line_capacity = vfx_lines.len().next_power_of_two(); - self.vfx_line_buffer = create_vfx_line_buffer(&self.device, self.vfx_line_capacity); - } - - self.queue - .write_buffer(&self.vfx_line_buffer, 0, bytemuck::cast_slice(&vfx_lines)); + self.instances_renderer_state + .update_buffers(&self.device, &self.queue, sim, camera); + self.particles_renderer_state + .update_buffers(&self.device, &self.queue, sim); + self.vfx_renderer_state + .update_buffers(&self.device, &self.queue, vfx_manager); + if config.debug_render { + self.physics_debug_renderer_state.update_buffers( + &self.device, + &self.queue, + sim, + config.debug_render_mode, + ); } - let debug_vertex_count = if config.debug_render { - puffin::profile_scope!("Build physics debug lines"); - let vertices = sim.rb_manager.debug_render(config.debug_render_mode); - - if vertices.is_empty() { - 0 - } else { - if vertices.len() > self.debug_vertex_capacity { - self.debug_vertex_capacity = vertices.len().next_power_of_two(); - self.debug_vertex_buffer = - create_debug_vertex_buffer(&self.device, self.debug_vertex_capacity); - } - - self.queue.write_buffer( - &self.debug_vertex_buffer, - 0, - bytemuck::cast_slice(vertices), - ); - - vertices.len() as u32 - } - } else { - 0 - }; - { puffin::profile_scope!("Main render pass"); let mut render_pass: wgpu::RenderPass<'_> = @@ -1094,37 +335,11 @@ impl RendererState { multiview_mask: None, }); - // this is kind of a hack - let instances_start = u32::min( - if config.cells_render { - 0 - } else { - CHUNK_SLOTS as u32 - }, - instances.len() as u32 - 1, - ); - - // instances - render_pass.set_pipeline(&self.instance_pipeline); - render_pass.set_bind_group(0, &self.instance_bind_group, &[]); - render_pass.draw(0..4, instances_start..instances.len() as u32); - - // particles - render_pass.set_pipeline(&self.particle_pipeline); - render_pass.set_bind_group(0, &self.particle_bind_group, &[]); - render_pass.draw(0..4, 0..particles.len() as u32); - - // vfx - render_pass.set_pipeline(&self.vfx_line_pipeline); - render_pass.set_bind_group(0, &self.vfx_line_bind_group, &[]); - render_pass.draw(0..4, 0..vfx_lines.len() as u32); - - // debug - if debug_vertex_count > 0 { - render_pass.set_pipeline(&self.debug_pipeline); - render_pass.set_bind_group(0, &self.debug_bind_group, &[]); - render_pass.set_vertex_buffer(0, self.debug_vertex_buffer.slice(..)); - render_pass.draw(0..debug_vertex_count, 0..1); + self.instances_renderer_state.render(&mut render_pass); + self.particles_renderer_state.render(&mut render_pass); + self.vfx_renderer_state.render(&mut render_pass); + if config.debug_render { + self.physics_debug_renderer_state.render(&mut render_pass); } } 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, + } + } +} diff --git a/src/renderer/ui.rs b/src/renderer/ui.rs index 1ef1bd1..9bad522 100644 --- a/src/renderer/ui.rs +++ b/src/renderer/ui.rs @@ -89,8 +89,6 @@ pub fn draw_egui<'a>( ui.checkbox(&mut config.use_threading, "Use multithreading"); - ui.checkbox(&mut config.cells_render, "Draw cells"); - ui.checkbox(&mut config.debug_render, "Draw physics debug lines"); if config.debug_render { diff --git a/src/renderer/vfx.rs b/src/renderer/vfx.rs new file mode 100644 index 0000000..e861ad8 --- /dev/null +++ b/src/renderer/vfx.rs @@ -0,0 +1,232 @@ +use crate::{content::vfx::VfxMaterialId, renderer::srgb_to_linear, vfx::VfxManager}; + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct RendererVfxMaterial { + color: [f32; 4], + length: f32, + _padding: [u32; 3], +} + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct RendererVfxLine { + a: [f32; 2], + b: [f32; 2], + width: f32, + material: u32, + alive_for: f32, + lifetime: f32, +} + +fn create_vfx_line_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer { + device.create_buffer(&wgpu::BufferDescriptor { + label: Some("VFX line buffer"), + size: (capacity * size_of::()) as u64, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }) +} + +pub struct VfxRendererState { + vfx_line_pipeline: wgpu::RenderPipeline, + vfx_line_buffer: wgpu::Buffer, + vfx_line_bind_group: wgpu::BindGroup, + vfx_line_capacity: usize, + + vfx_lines_count: usize, +} + +impl VfxRendererState { + pub fn update_buffers( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + vfx_manager: &VfxManager, + ) { + puffin::profile_function!(); + // TODO could cull this to visible, maybe it's slower? + let vfx_lines: Vec = vfx_manager + .lines + .iter() + .map(|p| RendererVfxLine { + a: p.a.to_array(), + b: p.b.to_array(), + material: p.material as u32, + width: p.width, + alive_for: p.alive_for, + lifetime: p.lifetime, + }) + .collect(); + + if vfx_lines.len() > self.vfx_line_capacity { + self.vfx_line_capacity = vfx_lines.len().next_power_of_two(); + self.vfx_line_buffer = create_vfx_line_buffer(&device, self.vfx_line_capacity); + } + + queue.write_buffer(&self.vfx_line_buffer, 0, bytemuck::cast_slice(&vfx_lines)); + + self.vfx_line_capacity = vfx_lines.len(); + } + + pub fn render(&self, render_pass: &mut wgpu::RenderPass) { + render_pass.set_pipeline(&self.vfx_line_pipeline); + render_pass.set_bind_group(0, &self.vfx_line_bind_group, &[]); + render_pass.draw(0..4, 0..self.vfx_lines_count as u32); + } + + pub fn new( + device: &wgpu::Device, + queue: &wgpu::Queue, + surface_config: &wgpu::SurfaceConfiguration, + camera_uniform_buffer: &wgpu::Buffer, + ) -> Self { + let mut vfx_material_palette = [RendererVfxMaterial { + color: [0.0f32; 4], + length: 0.0, + _padding: [0; 3], + }; VfxMaterialId::ALL.len()]; + for material in VfxMaterialId::ALL { + let def = material.def(); + let i = material as usize; + vfx_material_palette[i].color = [ + srgb_to_linear(def.color.0), + srgb_to_linear(def.color.1), + srgb_to_linear(def.color.2), + def.color.3 as f32 / 255.0, + ]; + vfx_material_palette[i].length = def.length; + } + + let vfx_material_palette_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("VFX Material Palette buffer"), + size: size_of_val(&vfx_material_palette) as u64, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + queue.write_buffer( + &vfx_material_palette_buffer, + 0, + bytemuck::cast_slice(&vfx_material_palette), + ); + + let vfx_line_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, + }, + // material 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, + // don't need vertex here + visibility: wgpu::ShaderStages::VERTEX | 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 vfx_line_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("VFX Line shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("../shader/vfx_line.wgsl").into()), + }); + + let vfx_line_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + immediate_size: 0, + bind_group_layouts: &[Some(&vfx_line_bind_group_layout)], + }); + + let vfx_line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&vfx_line_pipeline_layout), + vertex: wgpu::VertexState { + module: &vfx_line_shader, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &vfx_line_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 vfx_line_capacity = 512; + let vfx_line_buffer = create_vfx_line_buffer(&device, vfx_line_capacity); + + let vfx_line_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &vfx_line_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: camera_uniform_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: vfx_material_palette_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: vfx_line_buffer.as_entire_binding(), + }, + ], + }); + + VfxRendererState { + vfx_line_pipeline, + vfx_line_buffer, + vfx_line_bind_group, + vfx_line_capacity, + + vfx_lines_count: 0, + } + } +} -- cgit v1.3.1