wgpu backend: BVH cull (opt-in via --bvh, default off)

Stage 15 implementation lands but doesn't pay off as default-on. On a
562k-instance / 18-model scene with a centred camera, the BVH walk
adds ~10 ms of cull cost without rejecting enough subtrees to
compensate — every interior node's AABB straddles the frustum, so
descents go all the way to leaves anyway. Linear scan beats it by
that 10 ms.

GL's BVH works better mainly because they do full cull (frustum + HiZ
+ contribution) at every node — their per-test cost is lower (likely
SIMD-vectorised) and they get more subtree rejections. My current
impl does frustum-only at interior nodes (HiZ there cost more than
it saved on the smaller dataset).

For now, gate the whole BVH walk behind --bvh, default off. The
infrastructure (BvhAccel build at applyCachedModel, walk in cull,
release) stays in place so it's a one-flag toggle to measure either
side. Real default-on requires further tuning — see updated task #15.

Measured on 562k-instance scene:
  --bvh on  → 25.9ms total (cull 25.4ms)
  --bvh off → 15.4ms total (cull 14.5ms)   ← default

For comparison, GL on the same scene + camera:
  GL → 18.2ms total (cull 8.5ms wall, multi-threaded BVH)

Net: wgpu beats GL by ~3ms total despite slower cull, because the
GPU side (no edge-pass cost, async HiZ readback, lean main pipeline)
gives back more than the cull deficit.

Also added task #17 (GPU compute-shader cull) as the asymptotic
answer — both backends hit CPU cull as the ceiling on ≥500k scenes;
moving it to a compute shader drops it to sub-ms regardless.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-05-27 22:04:44 +10:00
parent 7dc13eb104
commit a1693259b8
5 changed files with 91 additions and 8 deletions
+5
View File
@@ -54,12 +54,17 @@ int main(int argc, char* argv[]) {
"Request the WebGPU mandatory floor limits (128MB max storage binding) "
"instead of the adapter's actual max. Use to verify scenes fit through "
"browser constraints."});
parser.addOption({"bvh",
"Enable BVH-walk cull. Off by default — currently a regression on "
"dense camera-looking-at-everything scenes; may help on sprawling "
"federations where most of the scene is off-screen."});
parser.process(app);
auto* viewport = new WgpuViewportWindow;
viewport->resize(1280, 800);
if (parser.isSet("no-hiz")) viewport->hiz_enabled_ = false;
if (parser.isSet("web-limits")) viewport->web_limits_ = true;
if (parser.isSet("bvh")) viewport->bvh_enabled_ = true;
QWidget* container = QWidget::createWindowContainer(viewport);
container->setMinimumSize(320, 240);