/*
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: 112,
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, material_bgl) =
ScenePipeline::new(&device, surface_configuration.format);
let scene_prepare = Arc::new(ScenePreparer::new(
device.clone(),
queue.clone(),
texture_bgl,
material_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());
// TODO multithreading introduces double-loading some resources. fix that before increasing thread count
for _ in 0..1 {
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(())
}
}