/*
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::{
download::Downloader,
meshops::{generate_normals, generate_tangents, generate_texcoords},
};
use anyhow::Result;
use egui::{Grid, Widget};
use glam::{Vec2, Vec3};
use humansize::DECIMAL;
use image::ImageReader;
use log::{debug, trace};
use std::{
collections::{HashMap, HashSet},
hash::Hash,
io::Cursor,
marker::PhantomData,
sync::{Arc, RwLock},
time::Instant,
};
use weareshared::{
Affine3A,
packets::Resource,
resources::{Image, MeshPart, Prefab},
};
use wgpu::{
AddressMode, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindingResource,
Buffer, BufferUsages, Device, Extent3d, FilterMode, Queue, SamplerDescriptor, Texture,
TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureViewDescriptor,
util::{BufferInitDescriptor, DeviceExt, TextureDataOrder},
};
pub struct DemandMap {
inner: RwLock>,
}
struct DemandMapState {
values: HashMap,
needed: HashSet,
size_metric: usize,
}
impl DemandMap {
pub fn new() -> Self {
Self {
inner: DemandMapState {
needed: HashSet::new(),
values: HashMap::new(),
size_metric: 0,
}
.into(),
}
}
pub fn needed(&self) -> Vec {
self.inner.read().unwrap().needed.iter().cloned().collect()
}
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);
s.size_metric += size;
}
pub fn try_get(&self, key: K) -> Option {
let mut s = self.inner.write().unwrap();
if let Some(k) = s.values.get(&key) {
Some(k.to_owned())
} else {
s.needed.insert(key);
None
}
}
}
pub struct ScenePreparer {
device: Arc,
queue: Arc,
texture_bgl: BindGroupLayout,
textures: DemandMap>, (Arc, 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, Arc>,
pub prefabs: DemandMap, Arc>,
}
pub struct RPrefab(pub Vec<(Affine3A, Arc)>);
pub struct RMeshPart {
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 double_sided: bool,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct TangentBufferSpec {
index: Resource>,
position: Resource>,
texcoord: Resource>,
}
#[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,
}
impl ScenePreparer {
pub fn new(device: Arc, queue: Arc, texture_bgl: BindGroupLayout) -> Self {
Self {
texture_bgl,
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(),
device,
queue,
}
}
pub fn update(&self, dls: &Downloader) -> Result {
let mut num_done = 0;
for pres in self.prefabs.needed() {
if let Some(prefab) = dls.try_get(pres.clone())? {
let mut rprefab = RPrefab(Vec::new());
for (aff, partres) in &prefab.mesh {
if let Some(part) = self.mesh_parts.try_get(partres.clone()) {
rprefab.0.push((*aff, part.clone()));
}
}
if rprefab.0.len() == prefab.mesh.len() {
self.prefabs.insert(pres.clone(), Arc::new(rprefab), 0);
debug!("prefab created ({pres})");
num_done += 1;
}
}
}
for pres in self.index_buffers.needed() {
let start = Instant::now();
if let Some(buf) = dls.try_get(pres.clone())? {
let buffer = self.device.create_buffer_init(&BufferInitDescriptor {
contents: bytemuck::cast_slice(buf.as_slice()),
label: None,
usage: BufferUsages::INDEX | BufferUsages::COPY_DST,
});
self.index_buffers.insert(
pres.clone(),
(Arc::new(buffer), (buf.len() * 3) as u32),
buf.len(),
);
debug!(
"index buffer created (len={}, took {:?}) {pres}",
buf.len() / size_of::(),
start.elapsed(),
);
num_done += 1;
}
}
for pres in self.vertex_buffers.needed() {
let start = Instant::now();
if let Some(buf) = dls.try_get(pres.clone())? {
let buffer = self.device.create_buffer_init(&BufferInitDescriptor {
contents: bytemuck::cast_slice(buf.as_slice()),
label: None,
usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
});
self.vertex_buffers
.insert(pres.clone(), Arc::new(buffer), buf.len());
debug!(
"vertex attribute buffer created (len={}, took {:?}) {pres}",
buf.len() / size_of::(),
start.elapsed()
);
num_done += 1;
}
}
for pres in self.textures.needed() {
let start = Instant::now();
if let Some(buf) = dls.try_get(pres.clone())? {
let image = ImageReader::new(Cursor::new(buf.0)).with_guessed_format()?;
let image = image.decode()?;
let dims = (image.width(), image.height());
let image = image.into_rgba8();
let image = image.into_vec();
let tex_bg = create_texture(
&self.device,
&self.queue,
&self.texture_bgl,
&image,
dims.0,
dims.1,
);
self.textures.insert(pres.clone(), tex_bg, image.len());
debug!(
"texture created (res={}x{}, took {:?})",
dims.0,
dims.1,
start.elapsed()
);
num_done += 1;
}
}
for kind in self.placeholder_textures.needed() {
let color = match kind {
TextureIdentityKind::Normal => [128, 128, 255, 255],
TextureIdentityKind::Multiply => [255, 255, 255, 255],
};
let tex_bg = create_texture(&self.device, &self.queue, &self.texture_bgl, &color, 1, 1);
self.placeholder_textures.insert(kind, tex_bg, 4);
num_done += 1;
}
for spec in self.generated_tangent_buffers.needed() {
if let (Some(index), Some(position), Some(texcoord)) = (
dls.try_get(spec.index.clone())?,
dls.try_get(spec.position.clone())?,
dls.try_get(spec.texcoord.clone())?,
) {
let tangents = generate_tangents(&index, &position, &texcoord);
let buffer = self.device.create_buffer_init(&BufferInitDescriptor {
label: None,
usage: BufferUsages::COPY_DST | BufferUsages::VERTEX,
contents: bytemuck::cast_slice(tangents.as_slice()),
});
self.generated_tangent_buffers.insert(
spec,
Arc::new(buffer),
size_of::() * tangents.len() * 3,
);
}
}
for spec in self.generated_normal_buffers.needed() {
if let (Some(index), Some(position)) = (
dls.try_get(spec.index.clone())?,
dls.try_get(spec.position.clone())?,
) {
let normals = generate_normals(&index, &position);
let buffer = self.device.create_buffer_init(&BufferInitDescriptor {
label: None,
usage: BufferUsages::COPY_DST | BufferUsages::VERTEX,
contents: bytemuck::cast_slice(normals.as_slice()),
});
self.generated_normal_buffers.insert(
spec,
Arc::new(buffer),
size_of::() * normals.len() * 3,
);
}
}
for spec in self.generated_texcoord_buffers.needed() {
if let (Some(index), Some(position)) = (
dls.try_get(spec.index.clone())?,
dls.try_get(spec.position.clone())?,
) {
let texcoords = generate_texcoords(&index, &position);
let buffer = self.device.create_buffer_init(&BufferInitDescriptor {
label: None,
usage: BufferUsages::COPY_DST | BufferUsages::VERTEX,
contents: bytemuck::cast_slice(texcoords.as_slice()),
});
self.generated_texcoord_buffers.insert(
spec,
Arc::new(buffer),
size_of::() * texcoords.len() * 3,
);
}
}
for pres in self.mesh_parts.needed() {
let start = Instant::now();
if let Some(part) = dls.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.expect("TODO"),
})
};
let mut tex_albedo = None;
if let Some(albedores) = part.tex_albedo {
if let Some((_tex, bg)) = self.textures.try_get(albedores) {
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(albedores) = part.tex_normal {
if let Some((_tex, bg)) = self.textures.try_get(albedores) {
tex_normal = Some(bg)
}
} else {
if let Some((_tex, bg)) = self
.placeholder_textures
.try_get(TextureIdentityKind::Normal)
{
tex_normal = Some(bg)
}
}
if let (
Some((index, index_count)),
Some(va_normal),
Some(va_tangent),
Some(va_texcoord),
Some(va_position),
Some(tex_normal),
Some(tex_albedo),
) = (
index, normal, tangent, texcoord, position, tex_normal, tex_albedo,
) {
debug!("part created (took {:?}) {pres}", start.elapsed());
self.mesh_parts.insert(
pres,
Arc::new(RMeshPart {
index_count,
index,
va_normal,
va_tangent,
va_position,
va_texcoord,
tex_albedo,
tex_normal,
double_sided: part.g_double_sided.is_some(),
}),
0,
);
num_done += 1;
}
}
}
}
self.print_missing();
Ok(num_done)
}
}
fn create_texture(
device: &Device,
queue: &Queue,
bgl: &BindGroupLayout,
data: &[u8],
width: u32,
height: u32,
) -> (Arc, Arc) {
let texture = device.create_texture_with_data(
&queue,
&TextureDescriptor {
label: None,
size: Extent3d {
depth_or_array_layers: 1,
width,
height,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8UnormSrgb,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
view_formats: &[],
},
TextureDataOrder::LayerMajor,
data,
);
let textureview = texture.create_view(&TextureViewDescriptor::default());
let sampler = device.create_sampler(&SamplerDescriptor {
address_mode_u: AddressMode::Repeat,
address_mode_v: AddressMode::Repeat,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..Default::default()
});
let bindgroup = device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &bgl,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&textureview),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&sampler),
},
],
});
(Arc::new(texture), Arc::new(bindgroup))
}
impl Widget for &DemandMap {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
let state = self.inner.read().unwrap();
ui.label(state.needed.len().to_string());
ui.label(state.values.len().to_string());
ui.label(humansize::format_size(state.size_metric, DECIMAL));
ui.end_row();
ui.response()
}
}
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);
}
}
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);
})
.response
}
}