mod ui; 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}, proc_gen::WorldGenerator, renderer::ui::draw_egui, sim::{ 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; #[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 { channel / 12.92 } else { ((channel + 0.055) / 1.055).powf(2.4) } } 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>, device: wgpu::Device, queue: wgpu::Queue, config: wgpu::SurfaceConfiguration, is_surface_configured: bool, // egui egui_context: egui::Context, // TODO remove pub pub egui_state: egui_winit::State, egui_renderer: egui_wgpu::Renderer, // 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, } 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); let instance = wgpu::Instance::default(); let surface = instance.create_surface(window.clone()).unwrap(); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { compatible_surface: Some(&surface), ..wgpu::RequestAdapterOptions::default() }) .await .unwrap(); println!( "Initialized adapter, using GPU '{}'", adapter.get_info().name ); let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { required_features: wgpu::Features::default() | wgpu::Features::IMMEDIATES, required_limits: wgpu::Limits { max_immediate_size: 16, ..wgpu::Limits::defaults() }, ..wgpu::DeviceDescriptor::default() }) .await .unwrap(); device.on_uncaptured_error(Arc::new(|err| panic!("{err}"))); let surface_caps = surface.get_capabilities(&adapter); let surface_format = surface_caps .formats .iter() .find(|f| f.is_srgb()) .copied() .unwrap_or(surface_caps.formats[0]); let config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface_format, width: size.width, height: size.height, present_mode: wgpu::PresentMode::AutoVsync, alpha_mode: surface_caps.alpha_modes[0], view_formats: vec![], desired_maximum_frame_latency: 2, }; let egui_context = egui::Context::default(); let egui_state = egui_winit::State::new( egui_context.clone(), egui_context.viewport_id(), &window, None, None, None, ); let egui_renderer = egui_wgpu::Renderer::new(&device, surface_format, { egui_wgpu::RendererOptions { msaa_samples: 1, ..egui_wgpu::RendererOptions::default() } }); // --- PALETTE --- let mut palette = [0.0f32; MaterialId::ALL.len() * 4]; for material in MaterialId::ALL { let def = material.def(); let i = material as usize * 4; palette[i] = srgb_to_linear(def.color.0); palette[i + 1] = srgb_to_linear(def.color.1); palette[i + 2] = srgb_to_linear(def.color.2); palette[i + 3] = def.color.3 as f32 / 255.0; } let palette_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("Palette buffer"), size: size_of_val(&palette) as u64, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, 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 { 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, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, 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 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_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); RendererState { window, surface, device, queue, config, is_surface_configured: false, egui_context, egui_state, egui_renderer, 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(), } } pub fn resize(&mut self, width: u32, height: u32) { if width > 0 && height > 0 { self.config.width = width; self.config.height = height; self.surface.configure(&self.device, &self.config); self.is_surface_configured = true; } } pub fn render( &mut self, sim: &mut SimManager, vfx_manager: &VfxManager, camera: &mut Camera, config: &mut Config, diagnostics: &Diagnostics, input_manager: &InputManager, world_generator: &mut WorldGenerator, ) { puffin::profile_function!(); self.window.request_redraw(); if !self.is_surface_configured { return; } let output = { puffin::profile_scope!("Get current surface texture"); match self.surface.get_current_texture() { wgpu::CurrentSurfaceTexture::Success(surface_texture) => surface_texture, wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => surface_texture, wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded | wgpu::CurrentSurfaceTexture::Validation => { // Skip this frame return; } wgpu::CurrentSurfaceTexture::Outdated => { self.surface.configure(&self.device, &self.config); return; } wgpu::CurrentSurfaceTexture::Lost => { panic!("Lost device?"); } } }; let view = output .texture .create_view(&wgpu::TextureViewDescriptor::default()); let mut encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder"), }); let raw_input = self.egui_state.take_egui_input(&self.window); let full_output = self.egui_context.run_ui(raw_input, |ui| { let right_panel = egui::Panel::right(egui::Id::new("right_panel")); right_panel .resizable(false) // TODO collapse button .show_collapsible(ui, &mut true, |panel_ui| { draw_egui( panel_ui, config, camera, diagnostics, input_manager, sim, world_generator, ) }); }); self.egui_state .handle_platform_output(&self.window, full_output.platform_output); let clipped_primitives = self .egui_context .tessellate(full_output.shapes, full_output.pixels_per_point); let pixels_per_point = full_output.pixels_per_point; let size = self.window.inner_size(); let screen_descriptor = egui_wgpu::ScreenDescriptor { size_in_pixels: [size.width, size.height], pixels_per_point, }; for (id, delta) in &full_output.textures_delta.set { self.egui_renderer .update_texture(&self.device, &self.queue, *id, delta); } self.egui_renderer.update_buffers( &self.device, &self.queue, &mut encoder, &clipped_primitives, &screen_descriptor, ); // write the camera buffer self.queue.write_buffer( &self.camera_uniform_buffer, 0, 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 (xl, xu, yl, yu) = camera.viewport_bounds_world(); let ((cxl, cyl), _) = CellManager::split_game_position(xl.floor() as i32, yl.floor() as i32); let ((cxu, cyu), _) = CellManager::split_game_position(xu.floor() as i32, yu.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) as usize); } 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)); } 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<'_> = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, depth_slice: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0, }), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, occlusion_query_set: None, timestamp_writes: None, 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); } } { puffin::profile_scope!("Egui render pass"); let mut egui_pass = encoder .begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("egui pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, depth_slice: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }) .forget_lifetime(); self.egui_renderer .render(&mut egui_pass, &clipped_primitives, &screen_descriptor); } { puffin::profile_scope!("Submit queue and present"); self.queue.submit(std::iter::once(encoder.finish())); output.present(); } } }