mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 18:16:40 +00:00
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:
@@ -235,6 +235,10 @@ void releaseWgpuModelGpuData(WgpuModelGpuData& m, WgpuBufferPool& pool) {
|
||||
m.mesh_chunk_local_base_vertex.clear();
|
||||
m.mesh_chunk_local_ebo_first_u32.clear();
|
||||
m.mesh_chunk_local_lod1_first_u32.clear();
|
||||
m.instance_chunk_idx.clear();
|
||||
m.instance_base_vertex.clear();
|
||||
m.instance_ebo_first_u32.clear();
|
||||
m.instance_lod1_first_u32.clear();
|
||||
if (m.mesh_storage) { wgpuBufferRelease(m.mesh_storage); m.mesh_storage = nullptr; }
|
||||
if (m.instance_storage) { wgpuBufferRelease(m.instance_storage); m.instance_storage = nullptr; }
|
||||
m.vertex_bytes = 0;
|
||||
@@ -863,6 +867,26 @@ void WgpuViewportWindow::applyCachedModelStreaming(uint32_t model_id,
|
||||
c.instance_ids.push_back(inst_idx);
|
||||
}
|
||||
|
||||
// Resolve per-instance chunk lookups by translating from the per-mesh
|
||||
// arrays. Cull reads these directly, so the spatial-bucket planner
|
||||
// (which can place the same mesh in multiple chunks under #55) will
|
||||
// populate them without going through mesh_chunk_idx[].
|
||||
{
|
||||
const size_t n_inst = m.instances.size();
|
||||
m.instance_chunk_idx.assign(n_inst, 0);
|
||||
m.instance_base_vertex.assign(n_inst, 0);
|
||||
m.instance_ebo_first_u32.assign(n_inst, 0);
|
||||
m.instance_lod1_first_u32.assign(n_inst, 0);
|
||||
for (size_t i = 0; i < n_inst; ++i) {
|
||||
const uint32_t mi = m.instances[i].mesh_id;
|
||||
if (mi >= m.mesh_chunk_idx.size()) continue;
|
||||
m.instance_chunk_idx[i] = m.mesh_chunk_idx[mi];
|
||||
m.instance_base_vertex[i] = m.mesh_chunk_local_base_vertex[mi];
|
||||
m.instance_ebo_first_u32[i] = m.mesh_chunk_local_ebo_first_u32[mi];
|
||||
m.instance_lod1_first_u32[i] = m.mesh_chunk_local_lod1_first_u32[mi];
|
||||
}
|
||||
}
|
||||
|
||||
auto [inserted, _] = models_gpu_.emplace(model_id, std::move(m));
|
||||
WgpuModelGpuData& mref = inserted->second;
|
||||
|
||||
@@ -1189,6 +1213,26 @@ void WgpuViewportWindow::applyCachedModel(uint32_t model_id, SidecarData data) {
|
||||
c.instance_ids.push_back(inst_idx);
|
||||
}
|
||||
|
||||
// Resolve per-instance chunk lookups by translating from the per-mesh
|
||||
// arrays. Cull reads these directly, so the spatial-bucket planner
|
||||
// (which can place the same mesh in multiple chunks under #55) will
|
||||
// populate them without going through mesh_chunk_idx[].
|
||||
{
|
||||
const size_t n_inst = m.instances.size();
|
||||
m.instance_chunk_idx.assign(n_inst, 0);
|
||||
m.instance_base_vertex.assign(n_inst, 0);
|
||||
m.instance_ebo_first_u32.assign(n_inst, 0);
|
||||
m.instance_lod1_first_u32.assign(n_inst, 0);
|
||||
for (size_t i = 0; i < n_inst; ++i) {
|
||||
const uint32_t mi = m.instances[i].mesh_id;
|
||||
if (mi >= m.mesh_chunk_idx.size()) continue;
|
||||
m.instance_chunk_idx[i] = m.mesh_chunk_idx[mi];
|
||||
m.instance_base_vertex[i] = m.mesh_chunk_local_base_vertex[mi];
|
||||
m.instance_ebo_first_u32[i] = m.mesh_chunk_local_ebo_first_u32[mi];
|
||||
m.instance_lod1_first_u32[i] = m.mesh_chunk_local_lod1_first_u32[mi];
|
||||
}
|
||||
}
|
||||
|
||||
auto [inserted, _] = models_gpu_.emplace(model_id, std::move(m));
|
||||
WgpuModelGpuData& mref = inserted->second;
|
||||
buildModelBindGroup(mref);
|
||||
@@ -2738,7 +2782,7 @@ uint32_t WgpuViewportWindow::cullModelCpuCompute(WgpuModelGpuData& m,
|
||||
// This is the signal driveStreamingLoads keys residency on — stable
|
||||
// across frames when the camera doesn't move, so the loader doesn't
|
||||
// thrash on HiZ visibility flicker.
|
||||
++m.chunks[m.mesh_chunk_idx[inst.mesh_id]].frustum_visible_count;
|
||||
++m.chunks[m.instance_chunk_idx[i]].frustum_visible_count;
|
||||
|
||||
const MeshInfo& mesh = m.meshes[inst.mesh_id];
|
||||
|
||||
@@ -2779,23 +2823,25 @@ uint32_t WgpuViewportWindow::cullModelCpuCompute(WgpuModelGpuData& m,
|
||||
&& mesh.lod1_index_count > 0
|
||||
&& projected_px < lod1_threshold_px;
|
||||
|
||||
// Emit one VisibleDraw entry into the chunk that owns this mesh's
|
||||
// vertex range. base_vertex AND ebo_first_u32 are both CHUNK-LOCAL
|
||||
// — the chunk's bind group points at its own vertex_storage and
|
||||
// index_buffer slices so the shader indexes them directly. When
|
||||
// use_lod1, ebo_first_u32 routes into the LOD1 section of the
|
||||
// chunk's index slice (which is packed after the LOD0 section at
|
||||
// chunk-build time); the shader is oblivious to the LOD split.
|
||||
const uint32_t chunk_idx = m.mesh_chunk_idx[inst.mesh_id];
|
||||
// Emit one VisibleDraw entry into the chunk that owns this
|
||||
// instance's vertex range. base_vertex AND ebo_first_u32 are both
|
||||
// CHUNK-LOCAL — the chunk's bind group points at its own
|
||||
// vertex_storage and index_buffer slices so the shader indexes
|
||||
// them directly. When use_lod1, ebo_first_u32 routes into the LOD1
|
||||
// section of the chunk's index slice (which is packed after the
|
||||
// LOD0 section at chunk-build time); the shader is oblivious to
|
||||
// the LOD split. Lookups are per-INSTANCE (not per-mesh) so the
|
||||
// spatial-bucket planner (#55) can duplicate a mesh into multiple
|
||||
// chunks; each instance still resolves to exactly one chunk.
|
||||
const uint32_t chunk_idx = m.instance_chunk_idx[i];
|
||||
WgpuModelGpuData::Chunk& c = m.chunks[chunk_idx];
|
||||
|
||||
WgpuModelGpuData::VisibleDrawGpu d;
|
||||
d.mesh_id = inst.mesh_id;
|
||||
d.instance_idx = i;
|
||||
d.ebo_first_u32 = use_lod1
|
||||
? m.mesh_chunk_local_lod1_first_u32[inst.mesh_id]
|
||||
: m.mesh_chunk_local_ebo_first_u32[inst.mesh_id];
|
||||
d.base_vertex = m.mesh_chunk_local_base_vertex[inst.mesh_id];
|
||||
d.ebo_first_u32 = use_lod1 ? m.instance_lod1_first_u32[i]
|
||||
: m.instance_ebo_first_u32[i];
|
||||
d.base_vertex = m.instance_base_vertex[i];
|
||||
c.visible_draws_scratch.push_back(d);
|
||||
|
||||
const uint32_t entry_vert_count = use_lod1 ? mesh.lod1_index_count
|
||||
|
||||
Reference in New Issue
Block a user