summaryrefslogtreecommitdiff
path: root/src/renderer/vfx.rs
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 /src/renderer/vfx.rs
parent67aee9ec7f3282cab20f6439f755588135a32af5 (diff)
refactor renderer to separate passes
Diffstat (limited to 'src/renderer/vfx.rs')
-rw-r--r--src/renderer/vfx.rs232
1 files changed, 232 insertions, 0 deletions
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,
+ }
+ }
+}