use crate::gfx::mesh_payload::Vertex;
use crate::gfx::render_types::{DrawObject, InstancedCluster};
use alloc::vec::Vec;
const VERTEX_STRIDE: usize = core::mem::size_of::<Vertex>();
const INDEX_STRIDE: usize = core::mem::size_of::<u32>();
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MeshSeedRegion {
pub vtx_offset: u64,
pub vtx_bytes: u64,
pub idx_offset: u64,
pub idx_bytes: u64,
}
pub fn plan_seed_bytes(mesh_byte_sizes: &[(u64, u64)], cap: usize) -> Option<(u64, u64)> {
let n = mesh_byte_sizes.len();
let cap = cap.max(1);
if cap >= n {
return None;
}
let margin = (cap / 4).max(1);
let residency = (cap + margin).min(n);
if residency >= n {
return None;
}
let mut vtx: Vec<u64> = mesh_byte_sizes.iter().map(|&(v, _)| v).collect();
let mut idx: Vec<u64> = mesh_byte_sizes.iter().map(|&(_, i)| i).collect();
vtx.sort_unstable_by(|a, b| b.cmp(a));
idx.sort_unstable_by(|a, b| b.cmp(a));
let seed_vtx: u64 = vtx.iter().take(residency).sum();
let seed_idx: u64 = idx.iter().take(residency).sum();
Some((seed_vtx, seed_idx))
}
struct Region<'a> {
v_off_bytes: usize,
v_count: usize,
lod0: (usize, usize),
alts: &'a [(usize, usize)],
}
fn relocate_region(
src_v: &[Vertex],
src_i: &[u32],
dst_v: &mut Vec<Vertex>,
dst_i: &mut Vec<u32>,
region: Region,
) -> (usize, usize, Vec<usize>) {
let Region {
v_off_bytes,
v_count,
lod0,
alts,
} = region;
let old_vbase = v_off_bytes / VERTEX_STRIDE;
let new_vbase = dst_v.len();
dst_v.extend_from_slice(&src_v[old_vbase..old_vbase + v_count]);
let delta = new_vbase as i64 - old_vbase as i64;
let rebase = |i: u32| -> u32 { (i as i64 + delta) as u32 };
let (i0_off, i0_count) = lod0;
let new_i0_off = dst_i.len();
for &idx in &src_i[i0_off..i0_off + i0_count] {
dst_i.push(rebase(idx));
}
let mut new_alt_offsets = Vec::with_capacity(alts.len());
for &(a_off, a_count) in alts {
let new_a_off = dst_i.len();
for &idx in &src_i[a_off..a_off + a_count] {
dst_i.push(rebase(idx));
}
new_alt_offsets.push(new_a_off);
}
(new_vbase * VERTEX_STRIDE, new_i0_off, new_alt_offsets)
}
pub fn compact_for_streaming(
vertices: &mut Vec<Vertex>,
indices: &mut Vec<u32>,
draw_objects: &mut [DrawObject],
clusters: &mut [InstancedCluster],
streamed: &[bool],
seed_vtx_bytes: u64,
seed_idx_bytes: u64,
) -> MeshSeedRegion {
let mut new_v: Vec<Vertex> = Vec::with_capacity(vertices.len());
let mut new_i: Vec<u32> = Vec::with_capacity(indices.len());
for (i, obj) in draw_objects.iter_mut().enumerate() {
if streamed.get(i).copied().unwrap_or(false) {
obj.vertex_offset = 0;
obj.index_offset = 0;
obj.resident = false;
obj.lod_alternates.clear();
continue;
}
let alts: Vec<(usize, usize)> = obj
.lod_alternates
.iter()
.map(|s| (s.index_offset, s.index_count))
.collect();
let (new_v_off, new_i_off, new_alt_offs) = relocate_region(
vertices,
indices,
&mut new_v,
&mut new_i,
Region {
v_off_bytes: obj.vertex_offset,
v_count: obj.vertex_count,
lod0: (obj.index_offset, obj.index_count),
alts: &alts,
},
);
obj.vertex_offset = new_v_off;
obj.index_offset = new_i_off;
for (slice, new_off) in obj.lod_alternates.iter_mut().zip(new_alt_offs) {
slice.index_offset = new_off;
}
}
for c in clusters.iter_mut() {
let alts: Vec<(usize, usize)> = c
.lod_alternates
.iter()
.map(|s| (s.index_offset, s.index_count))
.collect();
let (new_v_off, new_i_off, new_alt_offs) = relocate_region(
vertices,
indices,
&mut new_v,
&mut new_i,
Region {
v_off_bytes: c.vertex_offset,
v_count: c.vertex_count,
lod0: (c.index_offset, c.index_count),
alts: &alts,
},
);
c.vertex_offset = new_v_off;
c.index_offset = new_i_off;
for (slice, new_off) in c.lod_alternates.iter_mut().zip(new_alt_offs) {
slice.index_offset = new_off;
}
}
let vtx_offset = (new_v.len() * VERTEX_STRIDE) as u64;
let idx_offset = (new_i.len() * INDEX_STRIDE) as u64;
let zero_v = Vertex {
pos: [0.0; 3],
normal: [0.0; 3],
tangent: [0.0; 3],
color: [0.0; 3],
uv: [0.0; 2],
};
let seed_v_count = (seed_vtx_bytes as usize) / VERTEX_STRIDE;
let seed_i_count = (seed_idx_bytes as usize) / INDEX_STRIDE;
new_v.extend(core::iter::repeat_n(zero_v, seed_v_count));
new_i.extend(core::iter::repeat_n(0u32, seed_i_count));
*vertices = new_v;
*indices = new_i;
MeshSeedRegion {
vtx_offset,
vtx_bytes: seed_vtx_bytes,
idx_offset,
idx_bytes: seed_idx_bytes,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gfx::render_types::{LodSlice, MaterialUniforms};
use alloc::vec;
fn vtx(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 push_verts(v: &mut Vec<Vertex>, n: usize, tag: f32) -> (usize, u32) {
let base = v.len() as u32;
for k in 0..n {
v.push(vtx(tag + k as f32 / 100.0));
}
(base as usize * VERTEX_STRIDE, base)
}
fn push_idx(i: &mut Vec<u32>, base: u32, n: usize) -> (usize, usize) {
let off = i.len();
for k in 0..n {
i.push(base + (k as u32 % 3));
}
(off, n)
}
fn draw(
v_off: usize,
v_count: usize,
i_off: usize,
i_count: usize,
resident: bool,
lods: Vec<LodSlice>,
) -> DrawObject {
DrawObject {
vertex_offset: v_off,
vertex_count: v_count,
index_offset: i_off,
index_count: i_count,
shader_bucket: 0,
base_vertex: 0,
geometry_generation: 0,
model: [[0.0; 4]; 4],
texture_slot: 0,
normal_map_slot: 0,
material: MaterialUniforms::DEFAULT,
visible: true,
resident,
bb_min: [0.0; 3],
bb_max: [0.0; 3],
cull_distance: 0.0,
lod_alternates: lods,
}
}
fn cluster(v_off: usize, v_count: usize, i_off: usize, i_count: usize) -> InstancedCluster {
InstancedCluster {
vertex_offset: v_off,
vertex_count: v_count,
index_offset: i_off,
index_count: i_count,
texture_slot: 0,
normal_map_slot: 0,
material: MaterialUniforms::DEFAULT,
cluster_bb_min: [0.0; 3],
cluster_bb_max: [0.0; 3],
local_bb_min: [0.0; 3],
local_bb_max: [0.0; 3],
cull_distance: 0.0,
instances: vec![[[0.0; 4]; 4]],
lod_alternates: Vec::new(),
}
}
#[test]
fn plan_returns_none_when_cap_holds_every_mesh() {
let sizes = vec![(100, 40), (100, 40), (100, 40)];
assert_eq!(plan_seed_bytes(&sizes, 4), None);
assert_eq!(plan_seed_bytes(&sizes, 3), None);
}
#[test]
fn plan_returns_none_when_margin_covers_the_set() {
let sizes = vec![(100, 40); 5];
assert_eq!(plan_seed_bytes(&sizes, 4), None);
}
#[test]
fn plan_sizes_seed_to_the_largest_residency_meshes() {
let sizes = vec![(100u64, 40u64); 8];
let (sv, si) = plan_seed_bytes(&sizes, 4).expect("shrink");
assert_eq!(sv, 100 * 5);
assert_eq!(si, 40 * 5);
assert!(sv < 100 * 8);
assert!(si < 40 * 8);
}
#[test]
fn plan_picks_the_biggest_meshes_independently_per_buffer() {
let sizes = vec![
(1000, 4), (4, 1000), (10, 10), (10, 10),
];
let (sv, si) = plan_seed_bytes(&sizes, 1).expect("shrink");
assert_eq!(sv, 1010);
assert_eq!(si, 1010);
}
#[test]
fn compact_removes_streamed_gaps_and_rewrites_resident_offsets() {
let mut v: Vec<Vertex> = Vec::new();
let mut i: Vec<u32> = Vec::new();
let (a_voff, a_base) = push_verts(&mut v, 4, 1.0);
let (a_ioff, a_ic) = push_idx(&mut i, a_base, 6);
let (b_voff, b_base) = push_verts(&mut v, 10, 2.0);
let (b_ioff, b_ic) = push_idx(&mut i, b_base, 12);
let (c_voff, c_base) = push_verts(&mut v, 5, 3.0);
let (c_ioff, c_ic) = push_idx(&mut i, c_base, 9);
let (d_voff, d_base) = push_verts(&mut v, 6, 4.0);
let (d_ioff, d_ic) = push_idx(&mut i, d_base, 6);
let mut draws = vec![
draw(a_voff, 4, a_ioff, a_ic, true, vec![]),
draw(b_voff, 10, b_ioff, b_ic, true, vec![]),
draw(c_voff, 5, c_ioff, c_ic, true, vec![]),
];
let streamed = [false, true, false];
let mut clusters = vec![cluster(d_voff, 6, d_ioff, d_ic)];
let pos = |s: &[Vertex]| -> Vec<[f32; 3]> { s.iter().map(|x| x.pos).collect() };
let a_verts = pos(&v[a_base as usize..a_base as usize + 4]);
let c_verts = pos(&v[c_base as usize..c_base as usize + 5]);
let d_verts = pos(&v[d_base as usize..d_base as usize + 6]);
let region = compact_for_streaming(
&mut v,
&mut i,
&mut draws,
&mut clusters,
&streamed,
0,
0,
);
assert!(!draws[1].resident);
assert_eq!(draws[1].vertex_offset, 0);
assert_eq!(draws[1].index_offset, 0);
assert!(draws[0].resident);
assert_eq!(draws[0].vertex_offset, 0);
let a0 = draws[0].vertex_offset / VERTEX_STRIDE;
assert_eq!(pos(&v[a0..a0 + 4]), a_verts);
let c0 = draws[2].vertex_offset / VERTEX_STRIDE;
assert_eq!(pos(&v[c0..c0 + 5]), c_verts);
for k in 0..c_ic {
let idx = i[draws[2].index_offset + k] as usize;
assert!(
idx >= c0 && idx < c0 + 5,
"C index {} out of its region",
idx
);
}
let d0 = clusters[0].vertex_offset / VERTEX_STRIDE;
assert_eq!(pos(&v[d0..d0 + 6]), d_verts);
for k in 0..d_ic {
let idx = i[clusters[0].index_offset + k] as usize;
assert!(
idx >= d0 && idx < d0 + 6,
"D index {} out of its region",
idx
);
}
assert_eq!(v.len(), 4 + 5 + 6); assert_eq!(i.len(), a_ic + c_ic + d_ic);
assert_eq!(region.vtx_offset, (v.len() * VERTEX_STRIDE) as u64);
assert_eq!(region.vtx_bytes, 0);
}
#[test]
fn compact_appends_seed_headroom_after_resident_geometry() {
let mut v: Vec<Vertex> = Vec::new();
let mut i: Vec<u32> = Vec::new();
let (a_voff, a_base) = push_verts(&mut v, 3, 1.0);
let (a_ioff, a_ic) = push_idx(&mut i, a_base, 3);
let mut draws = vec![draw(a_voff, 3, a_ioff, a_ic, true, vec![])];
let mut clusters: Vec<InstancedCluster> = Vec::new();
let streamed = [false];
let seed_v_bytes = (10 * VERTEX_STRIDE) as u64;
let seed_i_bytes = (24 * INDEX_STRIDE) as u64;
let region = compact_for_streaming(
&mut v,
&mut i,
&mut draws,
&mut clusters,
&streamed,
seed_v_bytes,
seed_i_bytes,
);
assert_eq!(region.vtx_offset, (3 * VERTEX_STRIDE) as u64);
assert_eq!(region.idx_offset, (3 * INDEX_STRIDE) as u64);
assert_eq!(region.vtx_bytes, seed_v_bytes);
assert_eq!(region.idx_bytes, seed_i_bytes);
assert_eq!(v.len(), 3 + 10);
assert_eq!(i.len(), 3 + 24);
assert_eq!(region.vtx_offset as usize % VERTEX_STRIDE, 0);
}
#[test]
fn compact_relocates_lod_alternate_index_ranges() {
let mut v: Vec<Vertex> = Vec::new();
let mut i: Vec<u32> = Vec::new();
let (a_voff, a_base) = push_verts(&mut v, 8, 1.0);
let (a_ioff, a_ic) = push_idx(&mut i, a_base, 12);
let (b_voff, b_base) = push_verts(&mut v, 4, 2.0);
let (b_ioff, b_ic) = push_idx(&mut i, b_base, 6); let (b_alt_off, b_alt_ic) = push_idx(&mut i, b_base, 3);
let mut draws = vec![
draw(a_voff, 8, a_ioff, a_ic, true, vec![]),
draw(
b_voff,
4,
b_ioff,
b_ic,
true,
vec![LodSlice {
index_offset: b_alt_off,
index_count: b_alt_ic,
switch_distance: 10.0,
}],
),
];
let mut clusters: Vec<InstancedCluster> = Vec::new();
let streamed = [true, false];
compact_for_streaming(&mut v, &mut i, &mut draws, &mut clusters, &streamed, 0, 0);
let b0 = draws[1].vertex_offset / VERTEX_STRIDE;
for k in 0..b_ic {
let idx = i[draws[1].index_offset + k] as usize;
assert!(idx >= b0 && idx < b0 + 4);
}
let alt = draws[1].lod_alternates[0];
for k in 0..alt.index_count {
let idx = i[alt.index_offset + k] as usize;
assert!(idx >= b0 && idx < b0 + 4);
}
assert_eq!(alt.switch_distance, 10.0);
}
}