ifcviewer: GPU LOD0/LOD1 selection in compute cull (step 3c)

The compact shader now computes per-instance pixel radius and routes
survivors to LOD1 buckets when the projected sphere falls below the
LOD1 threshold (default 30 px, same as CPU path, tunable via
IFC_LOD1_PX).

Layout expanded from 2 to 4 buckets per mesh:
  [0..M)   fwd_lod0   [M..2M)   fwd_lod1
  [2M..3M) rev_lod0   [3M..4M)  rev_lod1

Two MDIs per model: CCW for [0..2M), CW for [2M..4M).  Per-mesh
has_lod1 flags live in a new gpu_mesh_flags_ssbo (binding 4).

Contribution cull refactored: the compact shader now computes
pixelRadius() once and uses it for both the min_pixel_radius rejection
and LOD routing, matching the CPU path's logic.

Visible-buffer worst case is 2 × total_instances (each LOD bucket
reserves the full fwd/rev capacity per mesh, since LOD selection is
dynamic).

Tri count drops ~60% on the test dataset (53M → 22M) thanks to LOD1
decimated meshes.  FPS recovers from 16 to 36 despite 690k sub_draws
(4M layout).  MDI compaction remains the final perf fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-04-17 07:58:30 +10:00
parent 069ef20c46
commit e5ed7b53d4
3 changed files with 166 additions and 100 deletions
+9 -6
View File
@@ -108,18 +108,21 @@ struct ModelGpuData {
// the instanceCount field of gpu_indirect_buffer is rewritten by the
// cull shader (zeroed by the reset shader, atomically incremented as
// survivors are appended into gpu_visible_ssbo at mesh_base[i] + local).
// Layout per model:
// commands[0..M) fwd bucket (non-reflected, CCW winding)
// commands[M..2M) rev bucket (reflected, CW winding)
// gpu_mesh_command_count = 2M; gpu_forward_command_count = M.
// Each bucket gets its own mesh_base[] slot and its own visible[]
// range, sized to the exact per-mesh count of fwd / rev instances.
// Layout per model — 4 buckets of M commands each:
// [0..M) fwd_lod0 (non-reflected, LOD0, CCW winding)
// [M..2M) fwd_lod1 (non-reflected, LOD1, CCW winding)
// [2M..3M) rev_lod0 (reflected, LOD0, CW winding)
// [3M..4M) rev_lod1 (reflected, LOD1, CW winding)
// gpu_mesh_command_count = 4M; gpu_forward_command_count = M.
// Two MDIs: CCW for [0..2M), CW for [2M..4M).
GLuint gpu_indirect_buffer = 0;
size_t gpu_indirect_capacity = 0;
GLuint gpu_visible_ssbo = 0;
size_t gpu_visible_capacity = 0;
GLuint gpu_mesh_base_ssbo = 0;
size_t gpu_mesh_base_capacity = 0;
GLuint gpu_mesh_flags_ssbo = 0;
size_t gpu_mesh_flags_capacity = 0;
uint32_t gpu_mesh_command_count = 0;
uint32_t gpu_forward_command_count = 0;