use crate::mesh::{Indices, MorphTarget, Primitive, Topology};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CacheStats {
pub cache_size: usize,
pub triangles: usize,
pub vertices: usize,
pub misses: usize,
pub acmr: f64,
pub atvr: f64,
}
pub fn simulate_cache(indices: &[u32], cache_size: usize) -> CacheStats {
let cache_size = cache_size.max(1);
let mut cache = vec![u32::MAX; cache_size];
let mut head = 0usize; let mut misses = 0usize;
let mut any = false;
for &idx in indices {
any = true;
if cache.contains(&idx) {
continue; }
misses += 1;
cache[head] = idx;
head = (head + 1) % cache_size;
}
let vertices = if any {
let mut seen = std::collections::HashSet::new();
for &idx in indices {
seen.insert(idx);
}
seen.len()
} else {
0
};
let triangles = indices.len() / 3;
let acmr = if triangles > 0 {
misses as f64 / triangles as f64
} else {
f64::NAN
};
let atvr = if vertices > 0 {
misses as f64 / vertices as f64
} else {
f64::NAN
};
CacheStats {
cache_size,
triangles,
vertices,
misses,
acmr,
atvr,
}
}
pub const DEFAULT_CACHE_SIZE: usize = 32;
impl Primitive {
pub fn cache_stats(&self, cache_size: usize) -> CacheStats {
let flat: Vec<u32> = match self.topology {
Topology::Triangles | Topology::TriangleStrip | Topology::TriangleFan => self
.triangle_indices()
.into_iter()
.flat_map(|t| t.into_iter())
.collect(),
_ => self.draw_index_stream(),
};
simulate_cache(&flat, cache_size)
}
fn draw_index_stream(&self) -> Vec<u32> {
match &self.indices {
Some(Indices::U16(v)) => v.iter().map(|&i| i as u32).collect(),
Some(Indices::U32(v)) => v.clone(),
None => (0..self.positions.len() as u32).collect(),
}
}
pub fn optimize_vertex_cache(&self) -> Primitive {
self.optimize_vertex_cache_sized(DEFAULT_CACHE_SIZE)
}
pub fn optimize_vertex_cache_sized(&self, cache_size: usize) -> Primitive {
if !matches!(
self.topology,
Topology::Triangles | Topology::TriangleStrip | Topology::TriangleFan
) {
return self.clone();
}
let cache_size = cache_size.max(3);
let vcount = self.positions.len() as u32;
let tris: Vec<[u32; 3]> = self
.triangle_indices()
.into_iter()
.filter(|t| t[0] < vcount && t[1] < vcount && t[2] < vcount)
.collect();
if tris.is_empty() {
let mut out = self.clone();
out.topology = Topology::Triangles;
out.indices = Some(Indices::U32(Vec::new()));
return out;
}
let order = greedy_cache_order(&tris, self.positions.len(), cache_size);
let mut flat: Vec<u32> = Vec::with_capacity(order.len() * 3);
for &ti in &order {
flat.extend_from_slice(&tris[ti]);
}
let mut out = self.clone();
out.topology = Topology::Triangles;
out.indices = Some(pack_indices(flat, self.positions.len()));
out
}
pub fn optimize_vertex_fetch(&self) -> Primitive {
let n = self.positions.len();
let stream = self.draw_index_stream();
let mut remap = vec![u32::MAX; n];
let mut perm: Vec<usize> = Vec::with_capacity(n);
for &raw in &stream {
let s = raw as usize;
if s >= n {
continue; }
if remap[s] == u32::MAX {
remap[s] = perm.len() as u32;
perm.push(s);
}
}
for (s, slot) in remap.iter_mut().enumerate() {
if *slot == u32::MAX {
*slot = perm.len() as u32;
perm.push(s);
}
}
let new_stream: Vec<u32> = stream
.iter()
.filter_map(|&raw| {
let s = raw as usize;
if s < n {
Some(remap[s])
} else {
None
}
})
.collect();
let mut out = gather_vertices(self, &perm);
out.indices = Some(pack_indices(new_stream, perm.len()));
out
}
pub fn optimize_vertex_spatial(&self, bits: u32) -> Primitive {
let n = self.positions.len();
if n == 0 {
return self.clone();
}
let bits = bits.clamp(1, 10);
let levels = 1u32 << bits;
let mut lo = [f32::INFINITY; 3];
let mut hi = [f32::NEG_INFINITY; 3];
for p in &self.positions {
for a in 0..3 {
if p[a].is_finite() {
lo[a] = lo[a].min(p[a]);
hi[a] = hi[a].max(p[a]);
}
}
}
let mut inv = [0.0f32; 3];
for a in 0..3 {
let ext = hi[a] - lo[a];
if ext.is_finite() && ext > 0.0 {
inv[a] = (levels as f32 - 1.0) / ext;
}
}
let code = |p: &[f32; 3]| -> u64 {
if !p.iter().all(|c| c.is_finite()) {
return u64::MAX;
}
let mut c = [0u32; 3];
for a in 0..3 {
let q = ((p[a] - lo[a]) * inv[a]).round();
c[a] = (q.max(0.0) as u32).min(levels - 1);
}
morton3(c[0], c[1], c[2])
};
let mut perm: Vec<usize> = (0..n).collect();
perm.sort_by(|&a, &b| {
code(&self.positions[a])
.cmp(&code(&self.positions[b]))
.then(a.cmp(&b))
});
let mut remap = vec![0u32; n];
for (new_slot, &old) in perm.iter().enumerate() {
remap[old] = new_slot as u32;
}
let mut out = gather_vertices(self, &perm);
let stream = self.draw_index_stream();
let new_stream: Vec<u32> = stream
.iter()
.filter_map(|&raw| remap.get(raw as usize).copied())
.collect();
out.indices = Some(pack_indices(new_stream, n));
out
}
}
fn morton3(x: u32, y: u32, z: u32) -> u64 {
part1by2(x) | (part1by2(y) << 1) | (part1by2(z) << 2)
}
fn part1by2(v: u32) -> u64 {
let mut x = (v & 0x3ff) as u64; x = (x | (x << 16)) & 0x0000_0000_ff00_00ff;
x = (x | (x << 8)) & 0x0000_0000_0f00_f00f;
x = (x | (x << 4)) & 0x0000_0000_c30c_30c3;
x = (x | (x << 2)) & 0x0000_0000_4924_9249;
x
}
pub(crate) fn gather_vertices(src: &Primitive, perm: &[usize]) -> Primitive {
let g3 = |b: &Vec<[f32; 3]>| -> Vec<[f32; 3]> {
perm.iter()
.map(|&i| b.get(i).copied().unwrap_or([0.0; 3]))
.collect()
};
let g4 = |b: &Vec<[f32; 4]>| -> Vec<[f32; 4]> {
perm.iter()
.map(|&i| b.get(i).copied().unwrap_or([0.0; 4]))
.collect()
};
let mut out = src.clone();
out.positions = g3(&src.positions);
out.normals = src.normals.as_ref().map(&g3);
out.tangents = src.tangents.as_ref().map(&g4);
out.uvs = src
.uvs
.iter()
.map(|set| {
perm.iter()
.map(|&i| set.get(i).copied().unwrap_or([0.0; 2]))
.collect()
})
.collect();
out.colors = src.colors.iter().map(&g4).collect();
out.joints = src.joints.as_ref().map(|s| {
perm.iter()
.map(|&i| s.get(i).copied().unwrap_or([0; 4]))
.collect()
});
out.weights = src.weights.as_ref().map(&g4);
out.targets = src.targets.iter().map(|t| permute_morph(t, perm)).collect();
out
}
pub(crate) fn permute_morph(t: &MorphTarget, perm: &[usize]) -> MorphTarget {
t.map_buffers(|src| {
perm.iter()
.map(|&i| src.get(i).copied().unwrap_or([0.0; 3]))
.collect()
})
}
pub(crate) fn pack_indices(flat: Vec<u32>, vertex_count: usize) -> Indices {
if vertex_count <= u16::MAX as usize + 1 {
Indices::U16(flat.into_iter().map(|i| i as u16).collect())
} else {
Indices::U32(flat)
}
}
fn greedy_cache_order(tris: &[[u32; 3]], vertex_count: usize, cache_size: usize) -> Vec<usize> {
let nt = tris.len();
let mut valence = vec![0u32; vertex_count];
for t in tris {
for &v in t {
valence[v as usize] += 1;
}
}
let mut offset = vec![0usize; vertex_count + 1];
for v in 0..vertex_count {
offset[v + 1] = offset[v] + valence[v] as usize;
}
let mut incident = vec![0u32; offset[vertex_count]];
{
let mut cursor = offset.clone();
for (ti, t) in tris.iter().enumerate() {
for &v in t {
let v = v as usize;
incident[cursor[v]] = ti as u32;
cursor[v] += 1;
}
}
}
let mut live = valence.clone();
let mut last_push = vec![u32::MAX; vertex_count]; let mut clock: u32 = 0;
let mut emitted = vec![false; nt];
let mut order = Vec::with_capacity(nt);
let mut tri_score = vec![0.0f32; nt];
for ti in 0..nt {
tri_score[ti] = score_triangle(tris[ti], &live, &last_push, clock, cache_size);
}
let mut next_seed = 0usize; let mut best_hint: Option<usize> = None;
for _ in 0..nt {
let chosen = {
let mut best = best_hint.filter(|&t| !emitted[t]);
let mut best_val = best.map(|t| tri_score[t]).unwrap_or(f32::NEG_INFINITY);
for v in recent_vertices(&order, tris, cache_size) {
for &cand in &incident[offset[v]..offset[v + 1]] {
let cand = cand as usize;
if emitted[cand] {
continue;
}
let s = tri_score[cand];
if s > best_val {
best_val = s;
best = Some(cand);
}
}
}
match best {
Some(t) => t,
None => {
while next_seed < nt && emitted[next_seed] {
next_seed += 1;
}
next_seed
}
}
};
emitted[chosen] = true;
order.push(chosen);
let t = tris[chosen];
for &v in &t {
let v = v as usize;
if live[v] > 0 {
live[v] -= 1;
}
}
for &v in &t {
clock = clock.wrapping_add(1);
last_push[v as usize] = clock;
}
let mut hint: Option<usize> = None;
let mut hint_score = f32::NEG_INFINITY;
for &v in &t {
let v = v as usize;
for &inc in &incident[offset[v]..offset[v + 1]] {
let inc = inc as usize;
if emitted[inc] {
continue;
}
let s = score_triangle(tris[inc], &live, &last_push, clock, cache_size);
tri_score[inc] = s;
if s > hint_score {
hint_score = s;
hint = Some(inc);
}
}
}
best_hint = hint;
}
order
}
fn recent_vertices(order: &[usize], tris: &[[u32; 3]], cache_size: usize) -> Vec<usize> {
let span = (cache_size / 3).max(1);
let start = order.len().saturating_sub(span);
let mut out = Vec::with_capacity(span * 3);
for &ti in &order[start..] {
for &v in &tris[ti] {
out.push(v as usize);
}
}
out.sort_unstable();
out.dedup();
out
}
fn score_triangle(
t: [u32; 3],
live: &[u32],
last_push: &[u32],
clock: u32,
cache_size: usize,
) -> f32 {
t.iter()
.map(|&v| vertex_score(v as usize, live, last_push, clock, cache_size))
.sum()
}
fn vertex_score(v: usize, live: &[u32], last_push: &[u32], clock: u32, cache_size: usize) -> f32 {
let lp = last_push[v];
let recency = if lp == u32::MAX {
0.0
} else {
let rank = clock.wrapping_sub(lp);
if rank as usize >= cache_size {
0.0
} else if rank < 3 {
0.75
} else {
let scaler = 1.0 / (cache_size as f32 - 3.0);
let frac = (cache_size as f32 - rank as f32) * scaler;
frac.powf(1.5)
}
};
let l = live[v];
let valence = if l == 0 {
0.0
} else {
2.0 * (l as f32).powf(-0.5)
};
recency + valence
}