mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-19 11:43:53 +00:00
wgpu: spatial instance bucketing for streaming (env-gated prototype)
WGPU_SPATIAL_BUCKETS=1 swaps the applyCachedModelStreaming planner from mesh-keyed Morton+greedy to octree-style instance bucketing. Default behaviour unchanged (env var unset → mesh-keyed planner runs). Phase 1 of #55 / #56. The mesh-keyed planner produces chunks whose AABBs are the union of all instances of the chunk's meshes — for heavily-deduplicated IFC meshes (a "standard floor tile" used 800 times across a federation) the mesh's "centroid" is a mean of scattered instance positions and the chunk's AABB ends up spanning the entire model. Symptom: chunk-level frustum cull rarely fires (AABB always intersects view), and the screen-area priority metric under-rates big-AABB chunks because their corners straddle the near plane. Visible objects pop in/out as the camera tilts, even though they're fully on screen. The spatial planner bucketises INSTANCES directly. Each leaf bucket contains its instance list + the unique mesh data those instances reference. A mesh whose instances scatter into multiple buckets gets its vertex/index data uploaded into multiple pool slices — duplication is the cost for tight bucket AABBs. For IFC this is acceptable: heavily-shared meshes tend to be small (fittings, fasteners), so per-bucket duplication adds tens-of-MB not GB. Octree implementation (planSpatialChunks): - work-stack subdivision: for each (instance subset, AABB), split into 8 octants around centre and recurse - stop conditions: bucket fits WGPU_CHUNK_VERTEX_BYTES_LIMIT for union vertex bytes AND ≤ spatial_max_instances_ instances; OR single instance left; OR every instance falls into the same octant (pathological — emit as leaf rather than infinite recurse) - spatial_max_instances_ default 5000, overridable via WGPU_SPATIAL_BUCKET_MAX_INSTS env var so the prototype can be swept without rebuilding Data-model adjustment beyond what dc2927997 prepared: - Per-chunk per-mesh chunk-local offset table (chunk_mesh_offsets) built during the chunk-construction loop. The mesh-keyed per-mesh global arrays (mesh_chunk_idx etc.) still get populated for legacy reads, but under spatial bucketing they're overwritten when the same mesh appears in multiple chunks — harmless because cull reads the per-instance arrays exclusively (per dc2927997). - Post-construction, per-instance arrays are populated from chunk_mesh_offsets via (instance_to_chunk[i], inst.mesh_id) lookup. Mesh-keyed planner derives identical values to before (pixel-identical); spatial planner now writes the correct per-bucket offsets even when the mesh appears in multiple chunks. basic.ifc parity on all three paths confirmed (non-streaming mesh-keyed, streaming mesh-keyed, streaming spatial all produce 0 pixel diff vs the reference). Spatial planner produced 1 bucket on basic.ifc (3 instances, well under thresholds) as expected. Non-streaming applyCachedModel left unchanged — the prototype targets the streaming path which is where the federation-scale missing-objects issue lives. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -142,6 +142,19 @@ private:
|
||||
float chunkScreenAreaPx(const WgpuModelGpuData::Chunk& c,
|
||||
const QMatrix4x4& vp_mat) const;
|
||||
|
||||
// Octree-style spatial planner. Produces (a) per-bucket mesh_ids — a
|
||||
// mesh may appear in multiple buckets if its instances scattered, and
|
||||
// (b) per-instance bucket assignment. Each bucket carries its own
|
||||
// mesh data in its chunk pool slice (duplicated when shared). Stop
|
||||
// condition: bucket's union vertex bytes ≤ WGPU_CHUNK_VERTEX_BYTES_LIMIT
|
||||
// and instance count ≤ spatial_max_instances_. See task #55.
|
||||
struct SpatialPlan {
|
||||
std::vector<std::vector<uint32_t>> chunk_mesh_ids;
|
||||
std::vector<uint32_t> instance_to_chunk;
|
||||
};
|
||||
SpatialPlan planSpatialChunks(const std::vector<InstanceCpu>& instances,
|
||||
const std::vector<MeshInfo>& meshes) const;
|
||||
|
||||
public:
|
||||
|
||||
// Queue a one-shot framebuffer capture: the next rendered frame is
|
||||
@@ -505,6 +518,20 @@ private:
|
||||
// and is per-model the right granularity?).
|
||||
bool cull_threads_enabled_ = true;
|
||||
|
||||
// WGPU_SPATIAL_BUCKETS=1 swaps the streaming chunk planner from the
|
||||
// mesh-keyed Morton+greedy algorithm to an octree-style spatial
|
||||
// subdivision of INSTANCES. Same mesh may appear in multiple buckets
|
||||
// (data duplicated) when its instances scatter — this is the central
|
||||
// trade for tight per-chunk AABBs that actually match what cull and
|
||||
// priority code want. See task #55.
|
||||
bool spatial_buckets_enabled_ = false;
|
||||
// Stop-subdividing thresholds for the octree planner. A cell becomes a
|
||||
// leaf bucket when (a) the union vertex bytes of meshes its instances
|
||||
// reference fits the chunk budget, AND (b) instance count is below the
|
||||
// cap. Tunable via WGPU_SPATIAL_BUCKET_MAX_INSTS env var so we can
|
||||
// sweep without rebuild during the prototype phase.
|
||||
uint32_t spatial_max_instances_ = 5000;
|
||||
|
||||
public:
|
||||
// Master switch for HiZ occlusion. Set false to skip the depth resolve
|
||||
// + readback + cull test entirely (matches IFC_NO_HIZ in the GL backend).
|
||||
|
||||
Reference in New Issue
Block a user