diff options
Diffstat (limited to 'client')
-rw-r--r-- | client/src/interfaces/graphicsconfig.rs | 49 | ||||
-rw-r--r-- | client/src/interfaces/mod.rs | 15 | ||||
-rw-r--r-- | client/src/render/mod.rs | 39 | ||||
-rw-r--r-- | client/src/render/scene/demand_map.rs | 18 | ||||
-rw-r--r-- | client/src/render/scene/mod.rs | 38 | ||||
-rw-r--r-- | client/src/render/scene/pipelines.rs | 9 | ||||
-rw-r--r-- | client/src/render/scene/textures.rs | 4 | ||||
-rw-r--r-- | client/src/render/ui.rs | 66 | ||||
-rw-r--r-- | client/src/state.rs | 5 |
9 files changed, 199 insertions, 44 deletions
diff --git a/client/src/interfaces/graphicsconfig.rs b/client/src/interfaces/graphicsconfig.rs new file mode 100644 index 0000000..8832e8d --- /dev/null +++ b/client/src/interfaces/graphicsconfig.rs @@ -0,0 +1,49 @@ +/* + 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 <https://www.gnu.org/licenses/>. +*/ +use super::InterfaceData; +use egui::{Grid, Slider, Widget}; +use std::sync::Arc; + +pub struct GraphicsConfigInterface { + pub idata: Arc<InterfaceData>, +} + +impl Widget for &mut GraphicsConfigInterface { + fn ui(self, ui: &mut egui::Ui) -> egui::Response { + let mut conf = self.idata.graphics_config.lock().unwrap(); + Grid::new("gconf").show(ui, |ui| { + ui.label("Primitive Multisampling AA"); + ui.horizontal(|ui| { + ui.radio_value(&mut conf.1.sample_count, 1, "No MSAA"); + ui.radio_value(&mut conf.1.sample_count, 2, "MSAAx2"); + ui.radio_value(&mut conf.1.sample_count, 4, "MSAAx4"); + ui.radio_value(&mut conf.1.sample_count, 8, "MSAAx8"); + }); + ui.end_row(); + ui.label("Mipmap Levels"); + ui.add(Slider::new(&mut conf.1.max_mip_count, 1..=32).show_value(true)); + ui.end_row(); + ui.label("Maximum Anisotropy"); + ui.add(Slider::new(&mut conf.1.max_anisotropy, 1..=32).show_value(true)); + ui.end_row(); + }); + if ui.button("Apply").clicked() { + conf.0 = true; + } + ui.response() + } +} diff --git a/client/src/interfaces/mod.rs b/client/src/interfaces/mod.rs index 5fb9e6e..9cbfbe3 100644 --- a/client/src/interfaces/mod.rs +++ b/client/src/interfaces/mod.rs @@ -14,15 +14,17 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ +pub mod graphicsconfig; pub mod prefabindex; pub mod profiler; use crate::{ download::Downloader, network::Network, - render::{scene::ScenePreparer, ui::UI_POSITION_OFFSET}, + render::{GraphicsConfig, scene::ScenePreparer, ui::UI_POSITION_OFFSET}, }; use egui::{Pos2, Widget}; +use graphicsconfig::GraphicsConfigInterface; use prefabindex::PrefabIndexInterface; use profiler::{Profiler, TimingProfiler}; use std::sync::{Arc, Mutex}; @@ -33,6 +35,7 @@ enum Interface { Selector, Profiler(Profiler), PrefabIndex(PrefabIndexInterface), + GraphicsConfig(GraphicsConfigInterface), } pub struct InterfaceData { @@ -42,6 +45,7 @@ pub struct InterfaceData { pub prefab_index: Arc<PrefabIndex>, pub render_timing: Arc<Mutex<TimingProfiler>>, pub adapter_info: Arc<AdapterInfo>, + pub graphics_config: Arc<Mutex<(bool, GraphicsConfig)>>, } pub fn ui_selector(idata: Arc<InterfaceData>) -> impl Fn(&egui::Context) -> bool { @@ -53,6 +57,7 @@ pub fn ui_selector(idata: Arc<InterfaceData>) -> impl Fn(&egui::Context) -> bool Interface::Selector => "Select Interface", Interface::Profiler(_) => "Profiler", Interface::PrefabIndex(_) => "Prefab Index", + Interface::GraphicsConfig(_) => "Graphics Configuration", }) .open(&mut open) .default_pos(Pos2::new(UI_POSITION_OFFSET, UI_POSITION_OFFSET)) @@ -68,10 +73,18 @@ pub fn ui_selector(idata: Arc<InterfaceData>) -> impl Fn(&egui::Context) -> bool idata: idata.clone(), }) } + if ui.button("Graphics Config").clicked() { + *state = Interface::GraphicsConfig(GraphicsConfigInterface { + idata: idata.clone(), + }) + } ui.response() } Interface::Profiler(profiler) => profiler.ui(ui), Interface::PrefabIndex(prefab_index_interface) => prefab_index_interface.ui(ui), + Interface::GraphicsConfig(graphics_config_interface) => { + graphics_config_interface.ui(ui) + } }); open } diff --git a/client/src/render/mod.rs b/client/src/render/mod.rs index db961f4..19f717a 100644 --- a/client/src/render/mod.rs +++ b/client/src/render/mod.rs @@ -57,13 +57,14 @@ pub struct Renderer<'a> { color_msaa: TextureView, config: GraphicsConfig, + pub config_update: Arc<Mutex<(bool, GraphicsConfig)>>, } #[derive(Debug, Clone)] pub struct GraphicsConfig { - max_anisotropy: u16, - max_mip_count: u32, - sample_count: u32, + pub max_anisotropy: u16, + pub max_mip_count: u32, + pub sample_count: u32, } impl<'a> Renderer<'a> { @@ -87,7 +88,8 @@ impl<'a> Renderer<'a> { let (device, queue) = adapter .request_device( &DeviceDescriptor { - required_features: Features::PUSH_CONSTANTS, + required_features: Features::PUSH_CONSTANTS + | Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, required_limits: Limits { max_push_constant_size: 128, max_vertex_buffers: 16, @@ -188,23 +190,42 @@ impl<'a> Renderer<'a> { 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(), - color_msaa, }) } + pub fn reconfigure(&mut self, config: GraphicsConfig) { + info!("graphics configuration changed"); + self.scene_prepare.reconfigure(&config); + self.ui_renderer.reconfigure(&config); + 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 { - height, - width, + width: self.surface_configuration.width, + height: self.surface_configuration.height, depth_or_array_layers: 1, }; self.depth = self @@ -242,6 +263,8 @@ impl<'a> Renderer<'a> { camera: &Camera, input_state: &mut InputState, ) -> Result<()> { + self.check_reconfigure(); + self.timing.begin("prepare"); if self.surface_needs_reconfigure { self.surface diff --git a/client/src/render/scene/demand_map.rs b/client/src/render/scene/demand_map.rs index 16fa181..c27eaac 100644 --- a/client/src/render/scene/demand_map.rs +++ b/client/src/render/scene/demand_map.rs @@ -25,7 +25,7 @@ pub struct DemandMap<K, V> { inner: RwLock<DemandMapState<K, V>>, } struct DemandMapState<K, V> { - values: HashMap<K, V>, + values: HashMap<K, (V, usize)>, needed: HashSet<K>, size_metric: usize, } @@ -46,18 +46,30 @@ impl<K: Hash + Eq + Clone, V: Clone> DemandMap<K, V> { pub fn insert(&self, key: K, value: V, size: usize) { let mut s = self.inner.write().unwrap(); s.needed.remove(&key); - s.values.insert(key, value); + if let Some((_, old_size)) = s.values.insert(key, (value, size)) { + s.size_metric -= old_size; + } s.size_metric += size; } pub fn try_get(&self, key: K) -> Option<V> { let mut s = self.inner.write().unwrap(); - if let Some(k) = s.values.get(&key) { + if let Some((k, _)) = s.values.get(&key) { Some(k.to_owned()) } else { s.needed.insert(key); None } } + pub fn regenerate_all(&self) { + let mut s = self.inner.write().unwrap(); + let keys = s.values.keys().cloned().collect::<Vec<_>>(); + s.needed.extend(keys); + } + pub fn clear(&self) { + let mut s = self.inner.write().unwrap(); + s.values.clear(); + s.size_metric = 0; + } } impl<K, V> Widget for &DemandMap<K, V> { diff --git a/client/src/render/scene/mod.rs b/client/src/render/scene/mod.rs index ad7e0ce..16eecfc 100644 --- a/client/src/render/scene/mod.rs +++ b/client/src/render/scene/mod.rs @@ -21,16 +21,21 @@ pub mod pipelines; pub mod textures; pub mod vertex_buffers; -use super::{shaders::SceneShaders, GraphicsConfig}; +use super::{GraphicsConfig, shaders::SceneShaders}; use crate::{armature::RArmature, download::Downloader}; use anyhow::Result; use bytemuck::{Pod, Zeroable}; use demand_map::DemandMap; use egui::{Grid, Widget}; use glam::{UVec3, UVec4, Vec2, Vec3, Vec3A, uvec3, uvec4}; -use log::{debug, trace}; +use log::{debug, info, trace}; use pipelines::SceneBgLayouts; -use std::{hash::Hash, marker::PhantomData, sync::Arc, time::Instant}; +use std::{ + hash::Hash, + marker::PhantomData, + sync::{Arc, RwLock}, + time::Instant, +}; use textures::MipGenerationPipeline; use weareshared::{ Affine3A, @@ -49,7 +54,7 @@ pub struct ScenePreparer { layouts: SceneBgLayouts, shaders: SceneShaders, render_format: TextureFormat, - config: GraphicsConfig, + config: RwLock<GraphicsConfig>, downloader: Arc<Downloader>, textures: DemandMap<TextureSpec, (Arc<Texture>, Arc<BindGroup>)>, @@ -143,7 +148,7 @@ impl ScenePreparer { ) -> Self { Self { render_format, - config, + config: config.into(), layouts: SceneBgLayouts::load(&device), shaders: SceneShaders::load(&device), device, @@ -163,6 +168,22 @@ impl ScenePreparer { mip_generation_pipelines: DemandMap::new(), } } + pub fn reconfigure(&self, config: &GraphicsConfig) { + let mut cc = self.config.write().unwrap(); + if cc.max_anisotropy != config.max_anisotropy || cc.max_mip_count != config.max_mip_count { + info!("clear all scene textures"); + self.textures.clear(); + self.mesh_parts.clear(); + self.prefabs.clear(); + } + if cc.sample_count != config.sample_count { + info!("clear all scene pipelines"); + self.pipelines.clear(); + self.mesh_parts.clear(); + self.prefabs.clear(); + } + *cc = config.clone(); + } pub fn update(&self) -> Result<usize> { let mut num_done = 0; @@ -204,7 +225,12 @@ impl ScenePreparer { for spec in self.pipelines.needed() { self.pipelines.insert( spec.clone(), - Arc::new(spec.create(&self.device, &self.layouts, &self.shaders, &self.config)), + Arc::new(spec.create( + &self.device, + &self.layouts, + &self.shaders, + &self.config.read().unwrap().clone(), + )), 0, ); } diff --git a/client/src/render/scene/pipelines.rs b/client/src/render/scene/pipelines.rs index 53064c9..834d86d 100644 --- a/client/src/render/scene/pipelines.rs +++ b/client/src/render/scene/pipelines.rs @@ -16,6 +16,7 @@ */ use super::{GraphicsConfig, PipelineSpec}; use crate::render::shaders::SceneShaders; +use log::info; use wgpu::{ BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendState, BufferBindingType, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, @@ -95,8 +96,12 @@ impl PipelineSpec { shaders: &SceneShaders, config: &GraphicsConfig, ) -> RenderPipeline { + info!( + "creating scene pipeline (format={:?}, skin={}, culling={})", + self.format, self.backface_culling, self.skin + ); let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { - label: None, + label: Some("scene pipeline layout"), bind_group_layouts: &[&layouts.texture, &layouts.texture, &layouts.material], push_constant_ranges: &[PushConstantRange { // 4x4 model * view * project @@ -106,7 +111,7 @@ impl PipelineSpec { }], }); device.create_render_pipeline(&RenderPipelineDescriptor { - label: None, + label: Some("scene pipeline"), layout: Some(&pipeline_layout), fragment: Some(FragmentState { module: &shaders.fragment_pbr, diff --git a/client/src/render/scene/textures.rs b/client/src/render/scene/textures.rs index f85f21f..c385be2 100644 --- a/client/src/render/scene/textures.rs +++ b/client/src/render/scene/textures.rs @@ -94,7 +94,7 @@ impl ScenePreparer { TextureFormat::Rgba8UnormSrgb }, None, - &self.config, + &self.config.read().unwrap().clone(), ); self.placeholder_textures.insert(kind, tex_bg, 4); *num_done += 1; @@ -122,7 +122,7 @@ impl ScenePreparer { dims.1, format, Some(&mipgen), - &self.config, + &self.config.read().unwrap().clone(), ); self.textures.insert(spec, tex_bg, image.len()); debug!( diff --git a/client/src/render/ui.rs b/client/src/render/ui.rs index e27b9cb..4de2633 100644 --- a/client/src/render/ui.rs +++ b/client/src/render/ui.rs @@ -34,8 +34,8 @@ use wgpu::{ Buffer, BufferDescriptor, BufferUsages, ColorTargetState, ColorWrites, CommandEncoder, CompareFunction, DepthStencilState, Device, Extent3d, FilterMode, FragmentState, FrontFace, ImageCopyTexture, ImageDataLayout, IndexFormat, LoadOp, MultisampleState, Operations, Origin3d, - PipelineCompilationOptions, PipelineLayoutDescriptor, PolygonMode, PrimitiveState, - PrimitiveTopology, PushConstantRange, Queue, RenderPassColorAttachment, + PipelineCompilationOptions, PipelineLayout, PipelineLayoutDescriptor, PolygonMode, + PrimitiveState, PrimitiveTopology, PushConstantRange, Queue, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, SamplerBindingType, SamplerDescriptor, ShaderStages, StoreOp, SurfaceConfiguration, Texture, TextureAspect, TextureDescriptor, TextureDimension, @@ -51,9 +51,11 @@ pub const UI_POSITION_OFFSET: f32 = 1000.; pub struct UiRenderer { device: Arc<Device>, queue: Arc<Queue>, - _config: GraphicsConfig, + config: GraphicsConfig, ctx: Context, pipeline: RenderPipeline, + format: TextureFormat, + pipeline_layout: PipelineLayout, bind_group_layout: BindGroupLayout, textures: RwLock<HashMap<TextureId, (BindGroup, Texture, [u32; 2])>>, surfaces: RwLock<HashMap<ViewportId, UiSurface>>, @@ -82,9 +84,6 @@ impl UiRenderer { format: TextureFormat, config: GraphicsConfig, ) -> Self { - let frag_shader = device.create_shader_module(include_wgsl!("shaders/fragment_ui.wgsl")); - let vert_shader = device.create_shader_module(include_wgsl!("shaders/vertex_ui.wgsl")); - let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ BindGroupLayoutEntry { @@ -114,8 +113,34 @@ impl UiRenderer { stages: ShaderStages::VERTEX, }], }); - let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { - label: None, + let pipeline = + Self::create_pipeline(&device, &pipeline_layout, format, config.sample_count); + + Self { + ctx: Context::default(), + pipeline, + device, + queue, + pipeline_layout, + bind_group_layout, + config, + format, + last_pointer: Vec2::ZERO, + textures: HashMap::new().into(), + surfaces: HashMap::new().into(), + } + } + + fn create_pipeline( + device: &Device, + pipeline_layout: &PipelineLayout, + format: TextureFormat, + sample_count: u32, + ) -> RenderPipeline { + let frag_shader = device.create_shader_module(include_wgsl!("shaders/fragment_ui.wgsl")); + let vert_shader = device.create_shader_module(include_wgsl!("shaders/vertex_ui.wgsl")); + device.create_render_pipeline(&RenderPipelineDescriptor { + label: Some("ui pipeline"), layout: Some(&pipeline_layout), fragment: Some(FragmentState { module: &frag_shader, @@ -152,22 +177,23 @@ impl UiRenderer { bias: Default::default(), }), multisample: MultisampleState { - count: config.sample_count, + count: sample_count, ..Default::default() }, multiview: None, cache: None, - }); - Self { - ctx: Context::default(), - pipeline, - device, - queue, - bind_group_layout, - _config: config, - last_pointer: Vec2::ZERO, - textures: HashMap::new().into(), - surfaces: HashMap::new().into(), + }) + } + + pub fn reconfigure(&mut self, config: &GraphicsConfig) { + if self.config.sample_count != config.sample_count { + self.pipeline = Self::create_pipeline( + &self.device, + &self.pipeline_layout, + self.format, + config.sample_count, + ); + self.config.sample_count = config.sample_count; } } diff --git a/client/src/state.rs b/client/src/state.rs index ae29bae..b260c9e 100644 --- a/client/src/state.rs +++ b/client/src/state.rs @@ -18,9 +18,9 @@ use crate::{ audio::Audio, camera::Camera, download::Downloader, - interfaces::{ui_selector, InterfaceData}, + interfaces::{InterfaceData, ui_selector}, network::Network, - render::{ui::UiEvent, Renderer}, + render::{Renderer, ui::UiEvent}, }; use anyhow::{Context, Result}; use glam::{Vec2, Vec3}; @@ -109,6 +109,7 @@ impl<'a> State<'a> { prefab_index: self.prefab_index.clone(), render_timing: self.renderer.timing_submit.clone(), adapter_info: self.renderer.adapter_info.clone(), + graphics_config: self.renderer.config_update.clone(), }); self.renderer .ui_renderer |