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, } } }