wgpu cull: refactor per-mesh chunk lookups to per-instance

Preparatory refactor for spatial instance bucketing (#55). Cull
previously routed through per-mesh tables (mesh_chunk_idx,
mesh_chunk_local_base_vertex, _ebo_first_u32, _lod1_first_u32) to
find the chunk and chunk-local offsets for each instance. That
assumes a mesh lives in EXACTLY ONE chunk — the assumption holds
under the current mesh-keyed planner but breaks under spatial
bucketing, where the same mesh can be duplicated across multiple
buckets if its instances are scattered.

Adds four per-instance arrays (instance_chunk_idx,
instance_base_vertex, instance_ebo_first_u32, instance_lod1_first_u32)
populated at planning time. The current mesh-keyed planner derives
them by translation:
    instance_chunk_idx[i] = mesh_chunk_idx[instances[i].mesh_id]
The spatial-bucket planner (next commit) will populate them directly,
allowing the same mesh_id to map to different chunks for different
instances.

cullModelCpuCompute now reads the per-instance arrays:
- frustum_visible_count uses chunks[instance_chunk_idx[i]]
- VisibleDrawGpu uses instance_base_vertex / instance_ebo_first_u32
  / instance_lod1_first_u32
- LOD-select branch and use_lod1 logic unchanged.

Per-mesh tables stay (used by makeChunkRequest, debug logs, the
planner itself). Memory cost: 16 bytes × N instances ≈ 16 MB on a
1M-instance scene. Pixel-identical on basic.ifc in both non-streaming
and streaming modes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-05-29 16:06:58 +10:00
parent 3eaa35d748
commit 1f8f6bffc4
2 changed files with 74 additions and 13 deletions
+15
View File
@@ -232,6 +232,21 @@ struct WgpuModelGpuData {
// entries for meshes without LOD1 are 0 and unused.
std::vector<uint32_t> mesh_chunk_local_lod1_first_u32;
// Per-INSTANCE chunk lookup tables. Mirror the per-mesh arrays above,
// but resolved at planning time so cull can read them directly without
// routing through mesh_id. The split exists because the spatial-
// bucketing planner (#55) can place the same mesh in multiple chunks
// (mesh data duplicated when its instances live in different buckets)
// — under that scheme `mesh_chunk_idx[mesh_id]` is ambiguous, but
// `instance_chunk_idx[instance_id]` is always exactly one chunk.
// The mesh-keyed planner populates these by translation
// (instance_chunk_idx[i] = mesh_chunk_idx[instances[i].mesh_id]);
// the spatial-bucket planner populates them directly.
std::vector<uint32_t> instance_chunk_idx;
std::vector<uint32_t> instance_base_vertex;
std::vector<uint32_t> instance_ebo_first_u32;
std::vector<uint32_t> instance_lod1_first_u32;
// Model-shared buffers. Mesh + instance storage are small (<10 MB on
// any real scene we've seen); the chunked index buffer lives in Chunk
// alongside vertex_storage so streaming can defer both together.