summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--client/src/interfaces/graphicsconfig.rs49
-rw-r--r--client/src/interfaces/mod.rs15
-rw-r--r--client/src/render/mod.rs39
-rw-r--r--client/src/render/scene/demand_map.rs18
-rw-r--r--client/src/render/scene/mod.rs38
-rw-r--r--client/src/render/scene/pipelines.rs9
-rw-r--r--client/src/render/scene/textures.rs4
-rw-r--r--client/src/render/ui.rs66
-rw-r--r--client/src/state.rs5
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