use std::collections::{BTreeSet, HashMap};
use std::sync::Arc;
use crate::assets::{Assets, Textures, Unresolved};
use crate::gpu::buffer;
use crate::mesh::{Animation, Built, Geometry, Meshes};
pub(crate) const DEFAULT_MEMORY: usize = 256 * 1024 * 1024;
pub(crate) struct MeshCatalog<M: Meshes> {
assets: Arc<Assets>,
ids: HashMap<M, MeshId>,
entries: Vec<Entry>,
kept: BTreeSet<(u64, MeshId)>,
held: usize,
memory: usize,
frame: u64,
unhanded: Vec<(MeshId, Arc<Geometry>)>,
dropped: Vec<MeshId>,
}
impl<M: Meshes> MeshCatalog<M> {
pub(crate) fn new(assets: Arc<Assets>, memory: usize) -> Self {
Self {
assets,
ids: HashMap::new(),
entries: Vec::new(),
kept: BTreeSet::new(),
held: 0,
memory,
frame: 0,
unhanded: Vec::new(),
dropped: Vec::new(),
}
}
pub(crate) fn id_of(&mut self, mesh: &M) -> MeshId {
let (id, unresolved) = self.built(mesh);
unresolved.logged();
id
}
pub(crate) fn prepare(&mut self, mesh: M) {
self.id_of(&mesh);
}
pub(crate) fn clips<T>(&mut self, mesh: T) -> &[Animation]
where
M: From<T>,
{
let id = self.id_of(&M::from(mesh));
self.geometry(id).map_or(&[], Geometry::clips)
}
pub(crate) fn geometry(&self, id: MeshId) -> Option<&Geometry> {
self.entries[id.index()].geometry.as_deref()
}
pub(crate) fn build_catalog(&mut self) -> Unresolved {
let mut unresolved = Unresolved::default();
for mesh in M::catalog() {
unresolved.record(self.built(&mesh).1);
}
unresolved
}
fn built(&mut self, mesh: &M) -> (MeshId, Unresolved) {
let id = match self.ids.get(mesh) {
Some(&id) => id,
None => {
let id = self.insert();
self.ids.insert(mesh.clone(), id);
id
}
};
if self.entries[id.index()].geometry.is_some() {
self.touch(id);
return (id, Unresolved::default());
}
let Built {
geometry,
unresolved,
} = mesh.build(&self.assets);
self.fill(id, Arc::new(geometry));
(id, unresolved)
}
pub(crate) fn end_frame(&mut self) -> HandedMeshes {
self.frame += 1;
HandedMeshes {
geometry: core::mem::take(&mut self.unhanded),
dropped: core::mem::take(&mut self.dropped),
}
}
fn insert(&mut self) -> MeshId {
let id = MeshId(self.entries.len() as u32);
self.entries.push(Entry {
geometry: None,
used: self.frame,
});
id
}
fn fill(&mut self, id: MeshId, geometry: Arc<Geometry>) {
let frame = self.frame;
let held = geometry.bytes();
let entry = &mut self.entries[id.index()];
entry.geometry = Some(Arc::clone(&geometry));
entry.used = frame;
self.dropped.retain(|&dropped| dropped != id);
self.unhanded.push((id, geometry));
self.held += held;
self.kept.insert((frame, id));
self.evict();
}
fn touch(&mut self, id: MeshId) {
let frame = self.frame;
let entry = &mut self.entries[id.index()];
let previous = core::mem::replace(&mut entry.used, frame);
if previous != frame {
self.kept.remove(&(previous, id));
self.kept.insert((frame, id));
}
}
fn evict(&mut self) {
while self.held > self.memory {
let Some(&(used, id)) = self.kept.first() else {
return;
};
if used == self.frame {
return;
}
self.kept.pop_first();
self.held -= self.entries[id.index()].release();
self.unhanded.retain(|&(unhanded, _)| unhanded != id);
self.dropped.push(id);
}
}
}
pub(crate) struct HandedMeshes {
geometry: Vec<(MeshId, Arc<Geometry>)>,
dropped: Vec<MeshId>,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) struct MeshId(pub(crate) u32);
impl MeshId {
fn index(self) -> usize {
self.0 as usize
}
}
struct Entry {
geometry: Option<Arc<Geometry>>,
used: u64,
}
impl Entry {
fn release(&mut self) -> usize {
self.geometry.take().map_or(0, |geometry| geometry.bytes())
}
}
pub(crate) struct GpuMeshes {
received: Vec<Option<Received>>,
}
impl GpuMeshes {
pub(crate) fn new() -> Self {
Self {
received: Vec::new(),
}
}
pub(crate) fn take(&mut self, handed: HandedMeshes) {
for id in handed.dropped {
if let Some(received) = self.received.get_mut(id.index()) {
*received = None;
}
}
for (id, geometry) in handed.geometry {
if id.index() >= self.received.len() {
self.received.resize_with(id.index() + 1, || None);
}
self.received[id.index()] = Some(Received {
geometry,
upload: None,
});
}
}
pub(crate) fn geometry(&self, id: MeshId) -> Option<&Geometry> {
Some(&self.received.get(id.index())?.as_ref()?.geometry)
}
pub(crate) fn uploaded(&self, id: MeshId) -> Option<&GpuMesh> {
self.received.get(id.index())?.as_ref()?.upload.as_ref()
}
pub(crate) fn upload(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
textures: &Textures,
id: MeshId,
) {
let Some(Some(received)) = self.received.get_mut(id.index()) else {
return;
};
if received.geometry.indices().is_empty() {
return;
}
received
.upload
.get_or_insert_with(|| GpuMesh::new(device, queue, textures, &received.geometry));
}
}
struct Received {
geometry: Arc<Geometry>,
upload: Option<GpuMesh>,
}
pub(crate) struct GpuMesh {
vertices: wgpu::Buffer,
indices: wgpu::Buffer,
skin: Option<wgpu::Buffer>,
slots: Vec<Option<wgpu::BindGroup>>,
}
impl GpuMesh {
pub(crate) fn vertices(&self) -> &wgpu::Buffer {
&self.vertices
}
pub(crate) fn indices(&self) -> &wgpu::Buffer {
&self.indices
}
pub(crate) fn skin(&self) -> Option<&wgpu::Buffer> {
self.skin.as_ref()
}
pub(crate) fn part_texture(&self, part: u32) -> Option<&wgpu::BindGroup> {
self.slots.get(part as usize)?.as_ref()
}
fn new(
device: &wgpu::Device,
queue: &wgpu::Queue,
textures: &Textures,
geometry: &Geometry,
) -> Self {
Self {
vertices: Self::written(
device,
queue,
"mirage-engine mesh vertices",
bytemuck::cast_slice(geometry.vertices()),
wgpu::BufferUsages::VERTEX,
),
indices: Self::written(
device,
queue,
"mirage-engine mesh indices",
bytemuck::cast_slice(geometry.indices()),
wgpu::BufferUsages::INDEX,
),
skin: geometry.rig().skins().then(|| {
Self::written(
device,
queue,
"mirage-engine mesh skin",
bytemuck::cast_slice(geometry.rig().weights()),
wgpu::BufferUsages::VERTEX,
)
}),
slots: (0..geometry.part_count())
.map(|part| {
textures.bind(
device,
queue,
geometry.part_texture(part),
geometry.part_relief(part),
geometry.part_shading(part),
geometry.part_emissive(part),
)
})
.collect(),
}
}
fn written(
device: &wgpu::Device,
queue: &wgpu::Queue,
label: &str,
contents: &[u8],
usage: wgpu::BufferUsages,
) -> wgpu::Buffer {
let written = buffer(device, label, contents.len() as wgpu::BufferAddress, usage);
queue.write_buffer(&written, 0, contents);
written
}
}
#[cfg(test)]
mod tests {
use core::cell::Cell;
use std::rc::Rc;
use super::*;
use crate::Catalog;
use crate::math::{Vec2, Vec3};
use crate::mesh::{Cube, Mesh, MeshData, Part, Slot, Vertex};
use crate::{Material, meshes};
const CORNERS: usize = 3;
const SLAB: usize = CORNERS * size_of::<Vertex>() + CORNERS * size_of::<u32>();
#[derive(Clone, Eq, Hash, PartialEq)]
enum Ship {
Hull,
Thruster,
}
impl Catalog for Ship {
fn catalog() -> Vec<Self> {
vec![Self::Hull, Self::Thruster]
}
}
impl Mesh for Ship {
fn build(&self, assets: &Assets) -> MeshData {
match self {
Self::Hull => assets.mesh("hull"),
Self::Thruster => assets.mesh("thruster"),
}
}
}
meshes! { enum Ships { Ship } }
#[test]
fn the_catalog_run_names_every_asset_it_could_not_find() {
let assets = Arc::new(Assets::default());
let unresolved =
MeshCatalog::<Ships>::new(Arc::clone(&assets), DEFAULT_MEMORY).build_catalog();
let error = unresolved
.error()
.expect("nothing was loaded, so both pulls miss");
assert_eq!(
error.to_string(),
"the game's assets did not resolve: no asset is named `hull`; \
no asset is named `thruster`"
);
}
#[derive(Clone, Eq, Hash, PartialEq)]
struct Beacon;
impl Catalog for Beacon {
fn catalog() -> Vec<Self> {
vec![Self]
}
}
impl Mesh<Panels> for Beacon {
fn build(&self, assets: &Assets) -> MeshData<Panels> {
assets.mesh("beacon")
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct Panels;
impl Part for Panels {
fn from_name(name: &str) -> Option<Self> {
(name == "Beacon Panels").then_some(Self)
}
fn all() -> Vec<Self> {
vec![Self]
}
fn index(&self) -> u32 {
0
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
enum Renamed {
Panels,
Wing,
}
impl Part for Renamed {
fn from_name(name: &str) -> Option<Self> {
match name {
"Beacon Panels" => Some(Self::Panels),
"Wing" => Some(Self::Wing),
_ => None,
}
}
fn all() -> Vec<Self> {
vec![Self::Panels, Self::Wing]
}
fn index(&self) -> u32 {
match self {
Self::Panels => 0,
Self::Wing => 1,
}
}
}
#[derive(Clone, Eq, Hash, PartialEq)]
struct StaleBeacon;
impl Catalog for StaleBeacon {
fn catalog() -> Vec<Self> {
vec![Self]
}
}
impl Mesh<Renamed> for StaleBeacon {
fn build(&self, assets: &Assets) -> MeshData<Renamed> {
assets.mesh("beacon")
}
}
#[derive(Clone, Eq, Hash, PartialEq)]
struct Short;
impl Catalog for Short {
fn catalog() -> Vec<Self> {
vec![Self]
}
}
impl Mesh<Panels> for Short {
fn build(&self, assets: &Assets) -> MeshData<Panels> {
let cube = Cube.build(assets);
MeshData::in_parts(cube.vertices().to_vec(), cube.indices().to_vec(), |_| {
Slot::new(3, Material::default())
})
}
}
meshes! { enum Beacons { Beacon, StaleBeacon, Short } }
fn with_model() -> Arc<Assets> {
Arc::new(
Assets::load([crate::assets::file("hello.glb", crate::assets::BEACON)])
.expect("the example's model decodes"),
)
}
#[test]
fn the_catalog_run_leaves_a_material_the_vocabulary_never_names_alone() {
let assets = with_model();
let mut catalog = MeshCatalog::<Beacons>::new(assets, DEFAULT_MEMORY);
assert!(
catalog.built(&Beacons::Beacon(Beacon)).1.is_empty(),
"the caps are simply nothing this game addresses"
);
}
#[test]
fn the_catalog_run_reports_a_part_the_mesh_turns_out_not_to_have() {
let assets = with_model();
let mut catalog = MeshCatalog::<Beacons>::new(assets, DEFAULT_MEMORY);
let unresolved = catalog.built(&Beacons::StaleBeacon(StaleBeacon)).1;
let error = unresolved
.error()
.expect("nothing the mesh holds is a wing");
assert_eq!(
error.to_string(),
"the game's assets did not resolve: the asset `beacon` has no material that \
resolves to the part Wing"
);
}
#[test]
fn the_catalog_run_reports_a_mesh_whose_slots_do_not_cover_it_under_its_own_name() {
let assets = with_model();
let mut catalog = MeshCatalog::<Beacons>::new(assets, DEFAULT_MEMORY);
let (short, unresolved) = catalog.built(&Beacons::Short(Short));
let error = unresolved.error().expect("the slots stop short");
assert_eq!(
error.to_string(),
"the game's assets did not resolve: the mesh `Short` has slots covering 3 \
indices where it holds 36"
);
assert_eq!(
catalog.geometry(short).expect("held").part_count(),
0,
"and nothing of it is drawn"
);
}
meshes! { enum Cubes { Cube } }
#[test]
fn the_catalog_run_fills_the_system_tier_once_and_hands_it_once() {
let mut catalog = MeshCatalog::<Cubes>::new(Arc::new(Assets::default()), DEFAULT_MEMORY);
catalog.build_catalog();
let handed = catalog.end_frame();
assert_eq!(handed.geometry.len(), 1);
assert_eq!(
catalog.id_of(&Cube.into()),
handed.geometry[0].0,
"a drawn cube reuses the build"
);
assert_eq!(catalog.entries.len(), 1);
assert!(
catalog.end_frame().geometry.is_empty(),
"and the display thread is handed nothing twice"
);
}
#[derive(Clone)]
struct Slab {
key: u32,
builds: Rc<Cell<u32>>,
}
impl PartialEq for Slab {
fn eq(&self, other: &Self) -> bool {
self.key == other.key
}
}
impl Eq for Slab {}
impl core::hash::Hash for Slab {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.key.hash(state);
}
}
impl Catalog for Slab {
fn catalog() -> Vec<Self> {
Vec::new()
}
}
impl Mesh for Slab {
fn build(&self, _assets: &Assets) -> MeshData {
self.builds.set(self.builds.get() + 1);
MeshData::new(
vec![Vertex::new(Vec3::ZERO, Vec3::Y, Vec2::ZERO); CORNERS],
(0..CORNERS as u32).collect(),
)
}
}
meshes! { enum Slabs { Slab } }
fn slabs(memory: usize) -> (MeshCatalog<Slabs>, Rc<Cell<u32>>) {
let catalog = MeshCatalog::new(Arc::new(Assets::default()), memory);
(catalog, Rc::new(Cell::new(0)))
}
fn slab(builds: &Rc<Cell<u32>>, key: u32) -> Slabs {
Slab {
key,
builds: Rc::clone(builds),
}
.into()
}
#[test]
fn the_system_copies_are_held_inside_the_memory_the_catalog_was_given() {
let (mut catalog, builds) = slabs(3 * SLAB);
for key in 0..8 {
catalog.id_of(&slab(&builds, key));
catalog.end_frame();
}
assert_eq!(catalog.kept.len(), 3, "three of the eight are left");
assert_eq!(catalog.held, 3 * SLAB);
assert_eq!(builds.get(), 8, "and every key was built once");
}
#[test]
fn the_copy_the_frame_left_alone_is_the_one_that_drops() {
let (mut catalog, builds) = slabs(2 * SLAB);
let first = catalog.id_of(&slab(&builds, 0));
catalog.end_frame();
let second = catalog.id_of(&slab(&builds, 1));
catalog.end_frame();
catalog.id_of(&slab(&builds, 0));
catalog.id_of(&slab(&builds, 2));
assert!(catalog.geometry(first).is_some(), "the frame used this one");
assert!(catalog.geometry(second).is_none(), "and not this one");
assert_eq!(builds.get(), 3);
}
#[test]
fn a_dropped_mesh_is_built_again_once_and_handed_again() {
let (mut catalog, builds) = slabs(SLAB);
let first = catalog.id_of(&slab(&builds, 0));
catalog.end_frame();
catalog.id_of(&slab(&builds, 1));
let handed = catalog.end_frame();
assert!(
catalog.geometry(first).is_none(),
"the second took the room"
);
assert_eq!(
handed.dropped,
vec![first],
"and the display thread is told to drop it"
);
assert_eq!(
catalog.id_of(&slab(&builds, 0)),
first,
"and it keeps its id"
);
assert_eq!(builds.get(), 3, "built once more, and only once");
assert_eq!(
catalog.end_frame().geometry.len(),
1,
"and the display thread is handed the new copy"
);
catalog.id_of(&slab(&builds, 0));
catalog.end_frame();
catalog.id_of(&slab(&builds, 0));
assert_eq!(builds.get(), 3, "the copy stands while the game draws it");
}
#[test]
fn a_run_inside_the_memory_never_builds_a_mesh_twice() {
let (mut catalog, builds) = slabs(DEFAULT_MEMORY);
for _ in 0..64 {
for key in 0..4 {
catalog.id_of(&slab(&builds, key));
}
catalog.end_frame();
}
assert_eq!(builds.get(), 4);
assert_eq!(catalog.kept.len(), 4);
}
#[test]
fn an_insert_never_drops_what_the_frame_it_lands_in_took() {
let (mut catalog, builds) = slabs(SLAB / 2);
let alone = catalog.id_of(&slab(&builds, 0));
assert!(
catalog.geometry(alone).is_some(),
"a mesh past the memory on its own draws the frame that asked for it"
);
let second = catalog.id_of(&slab(&builds, 1));
assert!(
catalog.geometry(alone).is_some() && catalog.geometry(second).is_some(),
"and so does everything else that frame resolved"
);
catalog.end_frame();
catalog.id_of(&slab(&builds, 2));
assert_eq!(catalog.kept.len(), 1, "the frame after takes the room back");
}
}