/*
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 demand_map;
pub mod draw;
pub mod meshops;
pub mod pipelines;
pub mod textures;
pub mod vertex_buffers;
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, info, trace};
use pipelines::SceneBgLayouts;
use std::{
hash::Hash,
marker::PhantomData,
sync::{Arc, RwLock},
time::Instant,
};
use textures::MipGenerationPipeline;
use weareshared::{
Affine3A,
packets::Resource,
resources::{Image, MeshPart, Prefab},
};
use wgpu::{
BindGroup, BindGroupDescriptor, BindGroupEntry, Buffer, BufferUsages, Device, Queue,
RenderPipeline, Texture, TextureFormat,
util::{BufferInitDescriptor, DeviceExt},
};
pub struct ScenePreparer {
device: Arc,
queue: Arc,
layouts: SceneBgLayouts,
shaders: SceneShaders,
render_format: TextureFormat,
config: RwLock,
downloader: Arc,
textures: DemandMap, Arc)>,
placeholder_textures: DemandMap, Arc)>,
index_buffers: DemandMap>, (Arc, u32)>,
vertex_buffers: DemandMap>, Arc>,
generated_tangent_buffers: DemandMap>,
generated_normal_buffers: DemandMap>,
generated_texcoord_buffers: DemandMap>,
mesh_parts: DemandMap, Result, String>>,
materials: DemandMap>,
pipelines: DemandMap>,
mip_generation_pipelines: DemandMap>,
pub prefabs: DemandMap, Arc>,
}
pub struct RPrefab(pub Vec<(Affine3A, Arc)>);
pub struct RMeshPart {
pub pipeline: Arc,
pub index_count: u32,
pub index: Arc,
pub va_position: Arc,
pub va_normal: Arc,
pub va_tangent: Arc,
pub va_texcoord: Arc,
pub tex_albedo: Arc,
pub tex_normal: Arc,
pub material: Arc,
pub double_sided: bool,
pub va_joint_index: Option>,
pub va_joint_weight: Option>,
pub joint_uniform: Option>,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct PipelineSpec {
format: TextureFormat,
skin: bool,
backface_culling: bool,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct TextureSpec {
data: Resource>,
linear: bool,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct TangentBufferSpec {
index: Resource>,
position: Resource>,
texcoord: Option>>,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct NormalBufferSpec {
index: Resource>,
position: Resource>,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct TexcoordBufferSpec {
index: Resource>,
position: Resource>,
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
enum TextureIdentityKind {
Normal,
Multiply,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable, Hash, PartialEq, Eq)]
#[repr(C)]
struct Material {
roughness: u32,
metallic: u32,
_pad1: [u32; 2],
albedo_alpha: UVec4,
emission: UVec3,
_pad2: u32,
}
impl ScenePreparer {
pub fn new(
device: Arc,
queue: Arc,
render_format: TextureFormat,
downloader: Arc,
config: GraphicsConfig,
) -> Self {
Self {
render_format,
config: config.into(),
layouts: SceneBgLayouts::load(&device),
shaders: SceneShaders::load(&device),
device,
queue,
downloader,
index_buffers: DemandMap::new(),
vertex_buffers: DemandMap::new(),
mesh_parts: DemandMap::new(),
prefabs: DemandMap::new(),
textures: DemandMap::new(),
placeholder_textures: DemandMap::new(),
generated_tangent_buffers: DemandMap::new(),
generated_normal_buffers: DemandMap::new(),
generated_texcoord_buffers: DemandMap::new(),
materials: DemandMap::new(),
pipelines: DemandMap::new(),
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 {
let mut num_done = 0;
self.update_textures(&mut num_done)?;
self.update_vertex_buffers(&mut num_done)?;
for pres in self.prefabs.needed() {
if let Some(prefab) = self.downloader.try_get(pres.clone())? {
let mut rprefab = RPrefab(Vec::new());
let mut done_or_failed = 0;
for (aff, partres) in &prefab.mesh {
if let Some(part) = self.mesh_parts.try_get(partres.clone()) {
if let Ok(part) = part.clone() {
rprefab.0.push((*aff, part));
}
done_or_failed += 1;
}
}
if done_or_failed == prefab.mesh.len() {
self.prefabs.insert(pres.clone(), Arc::new(rprefab), 0);
debug!("prefab created ({pres})");
num_done += 1;
}
}
}
for spec in self.materials.needed() {
let buffer = self.device.create_buffer_init(&BufferInitDescriptor {
label: Some("material props"),
usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
contents: bytemuck::cast_slice(&[spec]),
});
let bind_group = self.device.create_bind_group(&BindGroupDescriptor {
label: Some("material"),
layout: &self.layouts.material,
entries: &[BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}],
});
self.materials.insert(spec, Arc::new(bind_group), 0);
}
for spec in self.pipelines.needed() {
self.pipelines.insert(
spec.clone(),
Arc::new(spec.create(
&self.device,
&self.layouts,
&self.shaders,
&self.config.read().unwrap().clone(),
)),
0,
);
}
for pres in self.mesh_parts.needed() {
let start = Instant::now();
if let Some(part) = self.downloader.try_get(pres.clone())? {
if let (Some(indexres), Some(positionres)) = (part.index, part.va_position) {
let index = self.index_buffers.try_get(indexres.clone());
let position = self
.vertex_buffers
.try_get(Resource(positionres.0, PhantomData));
let normal = if let Some(res) = part.va_normal.clone() {
self.vertex_buffers.try_get(Resource(res.0, PhantomData))
} else {
self.generated_normal_buffers.try_get(NormalBufferSpec {
index: indexres.clone(),
position: Resource(positionres.0, PhantomData),
})
};
let texcoord = if let Some(res) = part.va_texcoord.clone() {
self.vertex_buffers.try_get(Resource(res.0, PhantomData))
} else {
self.generated_texcoord_buffers.try_get(TexcoordBufferSpec {
index: indexres.clone(),
position: Resource(positionres.0, PhantomData),
})
};
let tangent = if let Some(res) = part.va_tangent.clone() {
self.vertex_buffers.try_get(Resource(res.0, PhantomData))
} else {
self.generated_tangent_buffers.try_get(TangentBufferSpec {
index: indexres,
position: Resource(positionres.0, PhantomData),
texcoord: part.va_texcoord,
})
};
let joint_weight = if let Some(res) = part.va_joint_weight.clone() {
self.vertex_buffers
.try_get(Resource(res.0, PhantomData))
.map(Some)
} else {
Some(None)
};
let joint_index = if let Some(res) = part.va_joint_index.clone() {
self.vertex_buffers
.try_get(Resource(res.0, PhantomData))
.map(Some)
} else {
Some(None)
};
let mut tex_albedo = None;
if let Some(albedores) = part.tex_albedo {
if let Some((_tex, bg)) = self.textures.try_get(TextureSpec {
data: albedores,
linear: false,
}) {
tex_albedo = Some(bg)
}
} else if let Some((_tex, bg)) = self
.placeholder_textures
.try_get(TextureIdentityKind::Multiply)
{
tex_albedo = Some(bg)
}
let mut tex_normal = None;
if let Some(normalres) = part.tex_normal {
if let Some((_tex, bg)) = self.textures.try_get(TextureSpec {
data: normalres,
linear: true,
}) {
tex_normal = Some(bg)
}
} else if let Some((_tex, bg)) = self
.placeholder_textures
.try_get(TextureIdentityKind::Normal)
{
tex_normal = Some(bg)
}
let material = self.materials.try_get({
let albedo = part.g_albedo.unwrap_or(Vec3A::ONE);
let emission = part.g_emission.unwrap_or(Vec3A::ONE);
Material {
roughness: part.g_roughness.unwrap_or(1.).to_bits(),
metallic: part.g_metallic.unwrap_or(0.).to_bits(),
_pad1: [0, 0],
albedo_alpha: uvec4(
albedo.x.to_bits(),
albedo.y.to_bits(),
albedo.z.to_bits(),
part.g_alpha.unwrap_or(1.).to_bits(),
),
emission: uvec3(
emission.x.to_bits(),
emission.y.to_bits(),
emission.z.to_bits(),
),
_pad2: 0,
}
});
let armature = Some(None); // TODO
let pipeline = self.pipelines.try_get(PipelineSpec {
format: self.render_format,
skin: false,
backface_culling: part.g_double_sided.is_none(),
});
if let (
Some(pipeline),
Some((index, index_count)),
Some(va_normal),
Some(va_tangent),
Some(va_texcoord),
Some(va_position),
Some(va_joint_index),
Some(va_joint_weight),
Some(armature),
Some(tex_normal),
Some(tex_albedo),
Some(material),
) = (
pipeline,
index,
normal,
tangent,
texcoord,
position,
joint_index,
joint_weight,
armature,
tex_normal,
tex_albedo,
material,
) {
let double_sided = part.g_double_sided.is_some();
let joint_uniform = if let Some(a) = armature {
let ra = RArmature::new(&self.device, a);
Some(ra.joint_mat_uniform_buffer.clone())
} else {
None
};
debug!("part created (took {:?}) {pres}", start.elapsed());
self.mesh_parts.insert(
pres,
Ok(Arc::new(RMeshPart {
pipeline,
index_count,
index,
va_normal,
va_tangent,
va_position,
va_texcoord,
va_joint_index,
va_joint_weight,
tex_albedo,
tex_normal,
material,
double_sided,
joint_uniform,
})),
0,
);
num_done += 1;
}
} else {
self.mesh_parts
.insert(pres, Err("index or positions missing".to_string()), 0);
}
}
}
self.print_missing();
Ok(num_done)
}
}
impl ScenePreparer {
pub fn print_missing(&self) {
fn visit(name: &str, m: &DemandMap)
where
K: Clone,
K: Hash,
K: std::cmp::Eq,
V: Clone,
{
let nl = m.needed().len();
if nl > 0 {
trace!("{name}: need {nl}")
}
}
visit("prefabs", &self.prefabs);
visit("mesh_parts", &self.mesh_parts);
visit("vertex_buffers", &self.vertex_buffers);
visit("index_buffers", &self.index_buffers);
visit("placeholder_textures", &self.placeholder_textures);
visit("generated_tangent_buffers", &self.generated_tangent_buffers);
visit("generated_normal_buffers", &self.generated_normal_buffers);
visit(
"generated_texcoord_buffers",
&self.generated_texcoord_buffers,
);
visit("textures", &self.textures);
visit("materials", &self.materials);
visit("pipelines", &self.pipelines);
visit("mip_generation_pipelines", &self.mip_generation_pipelines);
}
}
impl Widget for &ScenePreparer {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
Grid::new("sp")
.num_columns(4)
.show(ui, |ui| {
ui.label("prefabs");
self.prefabs.ui(ui);
ui.label("mesh_parts");
self.mesh_parts.ui(ui);
ui.label("vertex_buffers");
self.vertex_buffers.ui(ui);
ui.label("index_buffers");
self.index_buffers.ui(ui);
ui.label("placeholder_textures");
self.placeholder_textures.ui(ui);
ui.label("generated_tangent_buffers");
self.generated_tangent_buffers.ui(ui);
ui.label("generated_normal_buffers");
self.generated_normal_buffers.ui(ui);
ui.label("generated_texcoord_buffers");
self.generated_texcoord_buffers.ui(ui);
ui.label("textures");
self.textures.ui(ui);
ui.label("materials");
self.materials.ui(ui);
ui.label("pipelines");
self.pipelines.ui(ui);
ui.label("mip_generation_pipelines");
self.mip_generation_pipelines.ui(ui);
})
.response
}
}