/* 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 . */ use crate::{ camera::Camera, download::Downloader, interfaces::profiler::TimingProfiler, scene_prepare::ScenePreparer, scene_render::ScenePipeline, state::InputState, ui::UiRenderer, }; use anyhow::{Result, anyhow}; use log::{info, warn}; use pollster::FutureExt; use std::{ mem::swap, sync::{Arc, Mutex}, thread::{sleep, spawn}, time::Duration, }; use weareshared::tree::SceneTree; use wgpu::{ AdapterInfo, Backends, CommandEncoderDescriptor, Device, DeviceDescriptor, Extent3d, Features, Instance, InstanceDescriptor, Limits, MaintainBase, PowerPreference, 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, 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, } 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, required_limits: Limits { max_push_constant_size: 64, max_vertex_buffers: 16, ..Limits::default() }, ..Default::default() }, None, ) .block_on()?; let surface_configuration = surface .get_default_config(&adapter, 256, 256) .ok_or(anyhow!("no default config"))?; surface.configure(&device, &surface_configuration); let device = Arc::new(device); let queue = Arc::new(queue); let (scene_pipeline, texture_bgl) = ScenePipeline::new(&device, surface_configuration.format); let scene_prepare = Arc::new(ScenePreparer::new( device.clone(), queue.clone(), texture_bgl, )); let ui_renderer = UiRenderer::new(device.clone(), queue.clone(), surface_configuration.format); 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: 1, dimension: TextureDimension::D2, format: TextureFormat::Depth32Float, usage: TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }); let depth = depth.create_view(&TextureViewDescriptor::default()); for _ in 0..2 { let scene_prepare = scene_prepare.clone(); let downloader = downloader.clone(); spawn(move || { loop { let ndone = scene_prepare.update(&downloader).unwrap(); if ndone == 0 { sleep(Duration::from_millis(50)); } } }); } let adapter_info = Arc::new(adapter.get_info()); Ok(Self { scene_pipeline, scene_prepare, surface, adapter_info, depth, device, queue, surface_configuration, ui_renderer, surface_needs_reconfigure: false, timing: Default::default(), timing_submit: Default::default(), }) } 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.depth = self .device .create_texture(&TextureDescriptor { label: None, size: Extent3d { height, width, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, format: TextureFormat::Depth32Float, 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.timing.begin("prepare"); if self.surface_needs_reconfigure { self.surface .configure(&self.device, &self.surface_configuration); self.surface_needs_reconfigure = false } let target = self.surface.get_current_texture()?; if target.suboptimal { warn!("suboptimal swapchain texture"); self.surface_needs_reconfigure = true; } let target_view = target .texture .create_view(&TextureViewDescriptor::default()); let mut commands = self .device .create_command_encoder(&CommandEncoderDescriptor { label: None }); let projection = camera.to_matrix(); self.timing.checkpoint("draw scene"); self.scene_pipeline.draw( &mut commands, &target_view, &self.depth, scene, &self.scene_prepare.prefabs, projection, ); self.timing.checkpoint("draw ui"); self.ui_renderer.draw( &mut commands, &target_view, &self.depth, projection, 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"); target.present(); self.timing.checkpoint(""); let mut ts = self.timing_submit.lock().unwrap(); swap(&mut *ts, &mut self.timing); Ok(()) } }