use super::types::{GpuWayMeta, Vertex};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ChunkSpan {
pub way_lo: usize,
pub way_hi: usize,
pub vert_lo: u32,
pub vert_hi: u32,
}
impl ChunkSpan {
pub fn vert_count(&self) -> u32 {
self.vert_hi - self.vert_lo
}
}
pub fn chunk_budget(max_buffer_size: u64) -> u64 {
let vbytes = std::mem::size_of::<Vertex>() as u64;
max_buffer_size.saturating_sub(256).max(vbytes)
}
pub fn partition_chunks(way_meta: &[GpuWayMeta], budget: u64) -> Vec<ChunkSpan> {
if way_meta.is_empty() {
return Vec::new();
}
let vbytes = std::mem::size_of::<Vertex>() as u64;
let mut chunks: Vec<ChunkSpan> = Vec::new();
let mut way_lo = 0usize;
let mut cur_vert_lo = way_meta[0].vert_start;
for (wi, m) in way_meta.iter().enumerate() {
let chunk_verts = (m.vert_start + m.vert_count) as u64 - cur_vert_lo as u64;
if chunk_verts * vbytes > budget && wi > way_lo {
let prev = &way_meta[wi - 1];
chunks.push(ChunkSpan {
way_lo,
way_hi: wi,
vert_lo: cur_vert_lo,
vert_hi: prev.vert_start + prev.vert_count,
});
way_lo = wi;
cur_vert_lo = m.vert_start;
}
}
let last = way_meta.last().unwrap();
chunks.push(ChunkSpan {
way_lo,
way_hi: way_meta.len(),
vert_lo: cur_vert_lo,
vert_hi: last.vert_start + last.vert_count,
});
chunks
}
pub fn rebase_chunk_meta(way_meta: &[GpuWayMeta], span: ChunkSpan) -> Vec<GpuWayMeta> {
way_meta[span.way_lo..span.way_hi]
.iter()
.map(|m| GpuWayMeta { vert_start: m.vert_start - span.vert_lo, ..*m })
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn meta(vert_start: u32, vert_count: u32) -> GpuWayMeta {
GpuWayMeta {
bbox_min: [0.0, 0.0],
bbox_max: [1.0, 1.0],
vert_start,
vert_count,
lod: 0,
_pad: 0,
}
}
#[test]
fn partition_splits_within_budget_and_never_splits_a_way() {
let metas: Vec<GpuWayMeta> = (0..10).map(|i| meta(i * 100, 100)).collect();
let vbytes = std::mem::size_of::<Vertex>() as u64;
let one = partition_chunks(&metas, 1024 * 1024);
assert_eq!(one.len(), 1);
assert_eq!(one[0], ChunkSpan { way_lo: 0, way_hi: 10, vert_lo: 0, vert_hi: 1000 });
let budget = 300 * vbytes; let chunks = partition_chunks(&metas, budget);
assert!(chunks.len() > 1, "tiny budget forces a split, got {}", chunks.len());
let mut expect_lo = 0u32;
for c in &chunks {
assert_eq!(c.vert_lo, expect_lo, "chunks are contiguous");
assert!((c.vert_count() as u64) * vbytes <= budget, "chunk within budget");
assert_eq!(c.vert_lo % 100, 0, "boundary on a way edge (no way split)");
assert_eq!(c.vert_hi % 100, 0, "boundary on a way edge (no way split)");
expect_lo = c.vert_hi;
}
assert_eq!(expect_lo, 1000, "chunks tile the whole vertex range");
}
#[test]
fn a_single_oversized_way_gets_its_own_chunk() {
let metas = vec![meta(0, 1_000_000)];
let chunks = partition_chunks(&metas, 16); assert_eq!(chunks.len(), 1, "the lone way still gets a chunk");
assert_eq!(chunks[0].vert_count(), 1_000_000);
}
#[test]
fn rebase_makes_chunk_meta_relative() {
let metas: Vec<GpuWayMeta> = (0..6).map(|i| meta(i * 100, 100)).collect();
let budget = 300 * std::mem::size_of::<Vertex>() as u64;
let chunks = partition_chunks(&metas, budget);
let second = chunks[1];
let rebased = rebase_chunk_meta(&metas, second);
assert_eq!(rebased[0].vert_start, 0, "first way of the chunk is at 0");
for (i, m) in rebased.iter().enumerate() {
assert_eq!(m.vert_start, i as u32 * 100);
}
}
#[test]
fn budget_reserves_headroom() {
assert_eq!(chunk_budget(256 * 1024 * 1024), 256 * 1024 * 1024 - 256);
assert_eq!(chunk_budget(0), std::mem::size_of::<Vertex>() as u64);
}
#[test]
fn empty_meta_yields_no_chunks() {
assert!(partition_chunks(&[], 1024).is_empty());
}
}