summaryrefslogtreecommitdiff
path: root/src/renderer/mod.rs
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-25 00:32:16 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-25 00:32:16 -0700
commita779011b9048cdda04887c77e20ee8936f332a7a (patch)
tree6a771ff692d1717967755cc56e4534a582dd117a /src/renderer/mod.rs
parent9cb48c504ddb3882f39e2b17048f0593316a95ce (diff)
vfx pipeline
Diffstat (limited to 'src/renderer/mod.rs')
-rw-r--r--src/renderer/mod.rs222
1 files changed, 212 insertions, 10 deletions
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index f21c794..c1b156e 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -9,21 +9,22 @@ use crate::{
Config, Diagnostics, InputManager,
camera::Camera,
config::{CELLS_IN_CHUNK, CHUNK_SIZE},
- content::materials::MaterialId,
+ content::{materials::MaterialId, vfx::VfxMaterialId},
renderer::ui::draw_egui,
sim::{
- cell_manager::manager::CellManager,
- entity::EntityId,
- rb_manager::debug_render::DebugVertex,
- sim_manager::{SimCtx, SimManager},
+ cell_manager::manager::CellManager, entity::EntityId,
+ rb_manager::debug_render::DebugVertex, sim_manager::SimManager,
},
+ vfx::VfxManager,
};
// TODO derive from shared chunk config
const CHUNK_SLOTS: usize = 11 * 200;
-const RB_ENTITY_SLOTS: usize = 64;
-const CELL_SLOTS: usize = CHUNK_SLOTS + RB_ENTITY_SLOTS;
+const ENTITY_SLOTS: usize = 64;
+const CELL_SLOTS: usize = CHUNK_SLOTS + ENTITY_SLOTS;
const PARTICLE_SLOTS: usize = 10_000;
+// TODO make this dynamic
+const VFX_LINE_SLOTS: usize = 10_000;
const INITIAL_DEBUG_VERTEX_CAPACITY: usize = 4096;
@@ -46,6 +47,25 @@ struct RendererParticle {
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 {
@@ -84,7 +104,7 @@ pub struct RendererState {
// common
camera_uniform_buffer: wgpu::Buffer,
- // world pixels
+ // instances
instance_pipeline: wgpu::RenderPipeline,
instance_buffer: wgpu::Buffer,
cell_buffer: wgpu::Buffer,
@@ -95,6 +115,11 @@ pub struct RendererState {
particle_buffer: wgpu::Buffer,
particle_bind_group: wgpu::BindGroup,
+ // vfx
+ vfx_line_pipeline: wgpu::RenderPipeline,
+ vfx_line_buffer: wgpu::Buffer,
+ vfx_line_bind_group: wgpu::BindGroup,
+
// physics debug lines
debug_pipeline: wgpu::RenderPipeline,
debug_bind_group: wgpu::BindGroup,
@@ -193,6 +218,39 @@ impl RendererState {
mapped_at_creation: false,
});
+ 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 {
@@ -298,8 +356,6 @@ impl RendererState {
mapped_at_creation: false,
});
- queue.write_buffer(&palette_buffer, 0, bytemuck::cast_slice(&palette));
-
let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Instance buffer"),
size: (CELL_SLOTS * size_of::<RendererInstance>()) as u64,
@@ -450,6 +506,120 @@ impl RendererState {
],
});
+ // --- 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 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: 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_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("VFX line buffer"),
+ size: (VFX_LINE_SLOTS * size_of::<RendererVfxLine>()) as u64,
+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+
+ 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 {
@@ -554,6 +724,10 @@ impl RendererState {
particle_buffer,
particle_bind_group,
+ vfx_line_pipeline,
+ vfx_line_buffer,
+ vfx_line_bind_group,
+
debug_pipeline,
debug_bind_group,
debug_vertex_buffer,
@@ -575,6 +749,7 @@ impl RendererState {
pub fn render(
&mut self,
sim: &mut SimManager,
+ vfx_manager: &VfxManager,
camera: &mut Camera,
config: &mut Config,
diagnostics: &Diagnostics,
@@ -784,6 +959,28 @@ impl RendererState {
.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();
+
+ self.queue
+ .write_buffer(&self.vfx_line_buffer, 0, bytemuck::cast_slice(&vfx_lines));
+ }
+
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);
@@ -854,6 +1051,11 @@ impl RendererState {
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);