/* wearechat - generic multiplayer game with voip Copyright (C) 2025 metamuffin This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License only. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ pub mod scene; pub mod shaders; pub mod ui; use crate::{ camera::Camera, download::Downloader, interfaces::profiler::TimingProfiler, state::InputState, }; use anyhow::{Result, anyhow}; use log::{info, warn}; use pollster::FutureExt; use scene::{ScenePreparer, draw::ScenePipeline}; use std::{ mem::swap, sync::{Arc, Mutex}, thread::{sleep, spawn}, time::Duration, }; use ui::UiRenderer; use weareshared::tree::SceneTree; use wgpu::{ AdapterInfo, Backends, CommandEncoderDescriptor, Device, DeviceDescriptor, Extent3d, Features, Instance, InstanceDescriptor, Limits, MaintainBase, PowerPreference, PresentMode, Queue, RequestAdapterOptions, Surface, SurfaceConfiguration, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView, TextureViewDescriptor, }; use winit::window::Window; pub struct Renderer<'a> { surface: Surface<'a>, queue: Arc, device: Arc, pub surface_configuration: SurfaceConfiguration, scene_pipeline: ScenePipeline, pub ui_renderer: UiRenderer, pub scene_prepare: Arc, surface_needs_reconfigure: bool, depth: TextureView, pub timing: TimingProfiler, pub timing_submit: Arc>, pub adapter_info: Arc, color_msaa: TextureView, config: GraphicsConfig, pub config_update: Arc>, } #[derive(Debug, Clone)] pub struct GraphicsConfig { pub max_anisotropy: u16, pub max_mip_count: u32, pub sample_count: u32, pub present_mode: PresentMode, } impl<'a> Renderer<'a> { pub fn new(window: &'a Window, downloader: Arc) -> Result { info!("wgpu init"); let instance = Instance::new(&InstanceDescriptor { backends: Backends::all(), ..Default::default() }); let surface = instance.create_surface(window)?; let adapter = instance .request_adapter(&RequestAdapterOptions { compatible_surface: Some(&surface), power_preference: PowerPreference::HighPerformance, ..Default::default() }) .block_on() .ok_or(anyhow!("no adapter found"))?; let (device, queue) = adapter .request_device( &DeviceDescriptor { required_features: Features::PUSH_CONSTANTS | Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, required_limits: Limits { max_push_constant_size: 128, max_vertex_buffers: 16, ..Limits::default() }, ..Default::default() }, None, ) .block_on()?; let config = GraphicsConfig { max_anisotropy: 16, max_mip_count: 16, sample_count: 4, present_mode: PresentMode::AutoVsync, }; let mut surface_configuration = surface .get_default_config(&adapter, 256, 256) .ok_or(anyhow!("no default config"))?; surface_configuration.present_mode = config.present_mode; surface.configure(&device, &surface_configuration); let device = Arc::new(device); let queue = Arc::new(queue); let depth = device .create_texture(&TextureDescriptor { label: None, size: Extent3d { height: 256, width: 256, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: config.sample_count, dimension: TextureDimension::D2, format: TextureFormat::Depth32Float, usage: TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }) .create_view(&TextureViewDescriptor::default()); let color_msaa = device .create_texture(&TextureDescriptor { label: None, size: Extent3d { height: 256, width: 256, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: config.sample_count, dimension: TextureDimension::D2, format: surface_configuration.format, usage: TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }) .create_view(&TextureViewDescriptor::default()); let scene_prepare = Arc::new(ScenePreparer::new( device.clone(), queue.clone(), surface_configuration.format, downloader, config.clone(), )); let ui_renderer = UiRenderer::new( device.clone(), queue.clone(), surface_configuration.format, config.clone(), ); // TODO multithreading introduces double-loading some resources. fix that before increasing thread count for _ in 0..1 { let scene_prepare = scene_prepare.clone(); spawn(move || { loop { let ndone = scene_prepare.update().unwrap(); if ndone == 0 { sleep(Duration::from_millis(50)); } } }); } let adapter_info = Arc::new(adapter.get_info()); Ok(Self { scene_pipeline: ScenePipeline, scene_prepare, surface, adapter_info, depth, device, queue, surface_configuration, ui_renderer, color_msaa, config_update: Arc::new(Mutex::new((false, config.clone()))), config, surface_needs_reconfigure: false, timing: Default::default(), timing_submit: Default::default(), }) } pub fn reconfigure(&mut self, config: GraphicsConfig) { info!("graphics configuration changed"); self.scene_prepare.reconfigure(&config); self.ui_renderer.reconfigure(&config); if config.present_mode != self.surface_configuration.present_mode { self.surface_configuration.present_mode = config.present_mode; self.surface .configure(&self.device, &self.surface_configuration); } self.config = config; self.recreate_framebuffers(); } pub fn check_reconfigure(&mut self) { let m = self.config_update.clone(); let mut lock = m.lock().unwrap(); if lock.0 { self.reconfigure(lock.1.clone()); lock.0 = false; } } pub fn resize(&mut self, width: u32, height: u32) { self.surface_configuration.width = width; self.surface_configuration.height = height; self.surface .configure(&self.device, &self.surface_configuration); self.recreate_framebuffers(); } pub fn recreate_framebuffers(&mut self) { let size = Extent3d { width: self.surface_configuration.width, height: self.surface_configuration.height, depth_or_array_layers: 1, }; self.depth = self .device .create_texture(&TextureDescriptor { label: None, size, mip_level_count: 1, sample_count: self.config.sample_count, dimension: TextureDimension::D2, format: TextureFormat::Depth32Float, usage: TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }) .create_view(&TextureViewDescriptor::default()); self.color_msaa = self .device .create_texture(&TextureDescriptor { label: None, size, mip_level_count: 1, sample_count: self.config.sample_count, dimension: TextureDimension::D2, format: self.surface_configuration.format, usage: TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }) .create_view(&TextureViewDescriptor::default()); } pub fn draw( &mut self, scene: &SceneTree, camera: &Camera, input_state: &mut InputState, ) -> Result<()> { self.check_reconfigure(); self.timing.begin("prepare"); if self.surface_needs_reconfigure { self.surface .configure(&self.device, &self.surface_configuration); self.surface_needs_reconfigure = false } let surface = self.surface.get_current_texture()?; if surface.suboptimal { warn!("suboptimal swapchain texture"); self.surface_needs_reconfigure = true; } let target_view = surface .texture .create_view(&TextureViewDescriptor::default()); let mut commands = self .device .create_command_encoder(&CommandEncoderDescriptor { label: None }); let view = camera.view_matrix(); let project = camera.project_matrix(); let (target, resolve_target) = if self.config.sample_count == 1 { (&target_view, None) } else { (&self.color_msaa, Some(&target_view)) }; self.timing.checkpoint("draw scene"); self.scene_pipeline.draw( &mut commands, target, resolve_target, &self.depth, scene, &self.scene_prepare.prefabs, view, project, ); self.timing.checkpoint("draw ui"); self.ui_renderer.draw( &mut commands, target, resolve_target, &self.depth, project * view, input_state, &self.surface_configuration, ); self.timing.checkpoint("submit"); let i = self.queue.submit(Some(commands.finish())); self.timing.checkpoint("poll"); self.device.poll(MaintainBase::WaitForSubmissionIndex(i)); self.timing.checkpoint("present"); surface.present(); self.timing.checkpoint(""); let mut ts = self.timing_submit.lock().unwrap(); swap(&mut *ts, &mut self.timing); Ok(()) } }