use std::collections::HashMap;
use concinnity_core::gfx::mesh_payload::Vertex;
use concinnity_core::gfx::mesh_seed::{self, MeshSeedRegion};
use concinnity_core::gfx::render_types::{DrawObject, InstancedCluster};
use crate::gfx::streaming::mesh::DecodedMesh;
pub(super) fn strip_streamed_lod_alternates(
draw_objects: &mut [DrawObject],
stream_draw_indices: &[usize],
) {
for &draw_idx in stream_draw_indices {
if let Some(obj) = draw_objects.get_mut(draw_idx) {
obj.lod_alternates.clear();
}
}
}
pub(super) fn plan_mesh_seed_bytes(
mesh_payloads: &[DecodedMesh],
stream_draw_indices: &[usize],
draw_to_handle: &HashMap<usize, usize>,
deferred_counts: &HashMap<u32, (u32, u32)>,
mesh_cap: usize,
has_deferred_seeds: bool,
) -> Option<(u64, u64)> {
let vertex_bytes = std::mem::size_of::<Vertex>() as u64;
let index_bytes = std::mem::size_of::<u32>() as u64;
let sizes: Vec<(u64, u64)> = mesh_payloads
.iter()
.zip(stream_draw_indices)
.map(|(m, draw_idx)| {
if !m.vertices.is_empty() {
return (
m.vertices.len() as u64 * vertex_bytes,
m.indices.len() as u64 * index_bytes,
);
}
draw_to_handle
.get(draw_idx)
.and_then(|h| deferred_counts.get(&(*h as u32)))
.map(|&(vc, ic)| (vc as u64 * vertex_bytes, ic as u64 * index_bytes))
.unwrap_or((0, 0))
})
.collect();
mesh_seed::plan_seed_bytes(&sizes, mesh_cap).or_else(|| {
has_deferred_seeds.then(|| {
(
sizes.iter().map(|s| s.0).sum(),
sizes.iter().map(|s| s.1).sum(),
)
})
})
}
pub(super) struct MeshSeedCompaction<'a> {
pub(super) vertices: &'a mut Vec<Vertex>,
pub(super) indices: &'a mut Vec<u32>,
pub(super) draw_objects: &'a mut Vec<DrawObject>,
pub(super) instanced_clusters: &'a mut Vec<InstancedCluster>,
pub(super) stream_draw_indices: &'a [usize],
}
pub(super) fn compact_streamed_geometry(
inputs: MeshSeedCompaction<'_>,
seed: (u64, u64),
mesh_cap: usize,
) -> MeshSeedRegion {
let MeshSeedCompaction {
vertices,
indices,
draw_objects,
instanced_clusters,
stream_draw_indices,
} = inputs;
let (seed_vtx, seed_idx) = seed;
let mut streamed = vec![false; draw_objects.len()];
for &idx in stream_draw_indices {
if let Some(s) = streamed.get_mut(idx) {
*s = true;
}
}
let region = mesh_seed::compact_for_streaming(
vertices,
indices,
draw_objects,
instanced_clusters,
&streamed,
seed_vtx,
seed_idx,
);
tracing::info!(
"GraphicsSystem: shrinkable seed VRAM -- {} streamed mesh(es), cap {}, seed headroom {} KiB vtx + {} KiB idx",
stream_draw_indices.len(),
mesh_cap,
seed_vtx / 1024,
seed_idx / 1024,
);
region
}
#[cfg(test)]
mod tests {
use super::*;
use concinnity_core::gfx::render_types::{LodSlice, MaterialUniforms, NO_NORMAL_MAP_SLOT};
const VERTEX_BYTES: u64 = std::mem::size_of::<Vertex>() as u64;
const INDEX_BYTES: u64 = std::mem::size_of::<u32>() as u64;
fn draw(
first_vertex: usize,
vertex_count: usize,
index_offset: usize,
index_count: usize,
) -> DrawObject {
DrawObject {
vertex_offset: first_vertex * VERTEX_BYTES as usize,
vertex_count,
index_offset,
index_count,
base_vertex: 0,
geometry_generation: 0,
shader_bucket: 0,
model: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
texture_slot: 0,
normal_map_slot: NO_NORMAL_MAP_SLOT,
material: MaterialUniforms::DEFAULT,
visible: true,
resident: true,
bb_min: [0.0; 3],
bb_max: [1.0; 3],
cull_distance: 0.0,
lod_alternates: Vec::new(),
}
}
fn vert(x: f32) -> Vertex {
Vertex {
pos: [x, 0.0, 0.0],
normal: [0.0, 1.0, 0.0],
tangent: [1.0, 0.0, 0.0],
color: [1.0, 1.0, 1.0],
uv: [0.0, 0.0],
}
}
fn mesh(vertices: usize, indices: usize) -> DecodedMesh {
DecodedMesh {
vertices: (0..vertices).map(|i| vert(i as f32)).collect(),
indices: vec![0; indices],
}
}
#[test]
fn only_streamed_draws_lose_their_lod_alternates() {
let mut draws = vec![draw(0, 3, 0, 3), draw(3, 3, 3, 3), draw(6, 3, 6, 3)];
for d in &mut draws {
d.lod_alternates.push(LodSlice {
index_offset: d.index_offset,
index_count: d.index_count,
switch_distance: 10.0,
});
}
strip_streamed_lod_alternates(&mut draws, &[1, 9]);
assert_eq!(draws[0].lod_alternates.len(), 1);
assert!(draws[1].lod_alternates.is_empty());
assert_eq!(draws[2].lod_alternates.len(), 1);
}
#[test]
fn a_deferred_mesh_with_an_empty_payload_contributes_its_baked_counts() {
let payloads = [mesh(4, 6), mesh(0, 0)];
let draw_to_handle = HashMap::from([(0, 2), (1, 5)]);
let counts = HashMap::from([(5, (10, 30))]);
let seed = plan_mesh_seed_bytes(&payloads, &[0, 1], &draw_to_handle, &counts, 8, true);
assert_eq!(
seed,
Some((14 * VERTEX_BYTES, 36 * INDEX_BYTES)),
"a cap covering the set still reserves the whole set when a mesh is deferred"
);
}
#[test]
fn a_cap_covering_the_set_with_nothing_deferred_plans_no_seed() {
let payloads = [mesh(4, 6), mesh(3, 3)];
let draw_to_handle = HashMap::from([(0, 0), (1, 1)]);
let seed = plan_mesh_seed_bytes(
&payloads,
&[0, 1],
&draw_to_handle,
&HashMap::new(),
8,
false,
);
assert_eq!(seed, None);
}
#[test]
fn compaction_ignores_an_out_of_range_stream_index_and_returns_the_seed() {
let mut vertices: Vec<Vertex> = (0..6).map(|i| vert(i as f32)).collect();
let mut indices: Vec<u32> = vec![0, 1, 2, 3, 4, 5];
let mut draw_objects = vec![draw(0, 3, 0, 3), draw(3, 3, 3, 3)];
let mut instanced_clusters = Vec::new();
let seed = (3 * VERTEX_BYTES, 3 * INDEX_BYTES);
let region = compact_streamed_geometry(
MeshSeedCompaction {
vertices: &mut vertices,
indices: &mut indices,
draw_objects: &mut draw_objects,
instanced_clusters: &mut instanced_clusters,
stream_draw_indices: &[1, 7],
},
seed,
1,
);
assert_eq!(
region,
MeshSeedRegion {
vtx_offset: 3 * VERTEX_BYTES,
vtx_bytes: seed.0,
idx_offset: 3 * INDEX_BYTES,
idx_bytes: seed.1,
}
);
assert!(draw_objects[0].resident);
assert!(!draw_objects[1].resident);
assert_eq!(
vertices.len(),
6,
"resident geometry plus the seed headroom"
);
assert_eq!(indices.len(), 6);
}
}