mod debug; mod instances; mod lighting; mod particles; mod ui; mod vfx; use std::sync::Arc; use winit::window::Window; use crate::{ Config, Diagnostics, InputManager, camera::Camera, content::materials::MaterialId, proc_gen::TilesetWorldGenerator, renderer::{ debug::PhysicsDebugRendererState, instances::InstancesRendererState, lighting::LightingRendererState, particles::ParticlesRendererState, ui::draw_egui, vfx::VfxRendererState, }, sim::sim_manager::SimManager, vfx::VfxManager, }; 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) } } 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_renderer_state: InstancesRendererState, particles_renderer_state: ParticlesRendererState, vfx_renderer_state: VfxRendererState, physics_debug_renderer_state: PhysicsDebugRendererState, lighting_renderer_state: LightingRendererState, } impl RendererState { 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)); 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 lighting_renderer_state = LightingRendererState::new(&device); let instances_renderer_state = InstancesRendererState::new( &device, &config, &camera_uniform_buffer, &palette_buffer, &lighting_renderer_state.lighting_view(), lighting_renderer_state.uniform_buffer(), ); let particles_renderer_state = ParticlesRendererState::new(&device, &config, &camera_uniform_buffer, &palette_buffer); let vfx_renderer_state = VfxRendererState::new(&device, &queue, &config, &camera_uniform_buffer); let physics_debug_renderer_state = PhysicsDebugRendererState::new(&device, &config, &camera_uniform_buffer); RendererState { window, surface, device, queue, config, is_surface_configured: false, egui_context, egui_state, egui_renderer, camera_uniform_buffer, instances_renderer_state, particles_renderer_state, vfx_renderer_state, physics_debug_renderer_state, lighting_renderer_state, } } 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: &TilesetWorldGenerator, ) { 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 lighting_recreated = self .lighting_renderer_state .update_scene_texture(&self.device, &self.queue, sim, camera); if lighting_recreated { self.instances_renderer_state.rebuild_bind_group( &self.device, &self.lighting_renderer_state.lighting_view(), self.lighting_renderer_state.uniform_buffer(), ); } 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, ); } { puffin::profile_scope!("Lighting compute pass"); let mut compute_pass: wgpu::ComputePass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: Some("Lighting compute pass"), timestamp_writes: None, }); self.lighting_renderer_state.compute(&mut compute_pass); } { 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, }); 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); } } { 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(); } } }