summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-09-05 17:23:46 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-09-05 17:23:46 -0700
commit0ee1423cb267363d0b315ae7572eb2e6f98bfa4a (patch)
tree8aa4686bab49adaae8bd03faecc120fbfa309b5e
parent67aee9ec7f3282cab20f6439f755588135a32af5 (diff)
refactor renderer to separate passes
-rw-r--r--src/main.rs2
-rw-r--r--src/renderer/debug.rs148
-rw-r--r--src/renderer/instances.rs325
-rw-r--r--src/renderer/mod.rs871
-rw-r--r--src/renderer/particles.rs183
-rw-r--r--src/renderer/ui.rs2
-rw-r--r--src/renderer/vfx.rs232
7 files changed, 931 insertions, 832 deletions
diff --git a/src/main.rs b/src/main.rs
index d82389f..1c58426 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -48,7 +48,6 @@ struct Config {
brush_material: MaterialId,
dropper_material: MaterialId,
use_threading: bool,
- cells_render: bool,
debug_render: bool,
debug_render_mode: DebugRenderMode,
// proc gen
@@ -161,7 +160,6 @@ impl Default for App {
brush_radius: 10.0,
brush_material: MaterialId::Sand,
dropper_material: MaterialId::Wood,
- cells_render: true,
debug_render: false,
debug_render_mode: DebugRenderMode::default(),
};
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::<DebugVertex>()) 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::<DebugVertex>() 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::<RendererInstance>()) 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<EntityId, usize>,
+ 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<RendererInstance> = 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::<DebugVertex>()) 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::<RendererInstance>()) 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::<RendererParticle>()) 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::<RendererVfxLine>()) as u64,
- usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- })
-}
-
pub struct RendererState {
window: Arc<Window>,
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<EntityId, usize>,
+ 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<Window>) -> 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::<DebugVertex>() 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<RendererInstance> = 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<RendererParticle> = 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<RendererVfxLine> = 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::<RendererParticle>()) 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<RendererParticle> = 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::<RendererVfxLine>()) 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<RendererVfxLine> = 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,
+ }
+ }
+}