ifcviewer: add GPU frustum-cull validation shader (IFC_GPU_CULL=1)

First Phase 3E milestone: a compute shader that reads the per-instance
world-AABB SSBO added in the last commit, tests each instance against
the 6 frustum planes, and atomicAdds a global counter.  No visible list
or indirect-buffer writes yet — the output is just a survivor count,
cross-checked each frame against the CPU cull's numbers in the stats
line (`gpu_cull[Xms in=A surv=B]`) so we can verify the plumbing end-
to-end before we hand the GPU responsibility for the actual render data.

Dispatched from render() after the CPU cull completes, only when
IFC_GPU_CULL=1 and the camera moved (the skipped-cull still-frame path
doesn't re-check either).  The readback is synchronous — that's fine
for a validation path; it'll go away once the GPU writes indirect
commands directly.

Expected invariant: gpu_cull.surv >= cpu_cull.visible_objects, since
the GPU path does frustum-only and CPU adds contribution + HiZ cuts on
top.  A large mismatch (orders of magnitude, or surv < visible) means
the SSBO upload or shader logic is wrong.

No shader/buffer bindings overlap with the draw path (compute uses
bindings 0/1, restored before drawing; draw programs rebind 0/1/2).
This commit is contained in:
Dion Moult
2026-04-16 20:57:47 +10:00
parent 2f88778c9f
commit a0cc4b874b
2 changed files with 109 additions and 0 deletions
+9
View File
@@ -267,6 +267,15 @@ private:
GLuint pick_program_ = 0;
GLuint axis_program_ = 0;
// Phase 3E compute cull (frustum-only, validation). Runs alongside the
// CPU cull when IFC_GPU_CULL=1; result is cross-checked against CPU's
// visible_objects count. No draw-path side effects yet.
GLuint cull_program_ = 0;
GLuint gpu_cull_counter_ssbo_ = 0;
uint32_t gpu_cull_last_survivors_ = 0;
uint32_t gpu_cull_last_input_ = 0;
uint64_t gpu_cull_ns_ = 0; // per-window accumulator
// Axis gizmo
GLuint axis_vao_ = 0;
GLuint axis_vbo_ = 0;