Skip to content

Add a WebGPU rendering path so Phoenix can handle very large events (HL-LHC / HGCAL scale) #941

Description

@rx18-eng

What this is about

Right now Phoenix renders everything with three.js's WebGLRenderer. I checked the code: renderer-manager.ts only ever does new WebGLRenderer(...) in one place, and there is no WebGPU anywhere in the source.

WebGL has served us well, but it has a ceiling on how many objects we can draw, and a lot of the per-object work (cuts, culling, picking) runs as CPU loops every frame.

The events we will need to show keep getting bigger:

  • HL-LHC (starting around 2029) stacks ~200 pileup collisions into one event
  • High Granularity Calorimeters (CMS HGCAL and similar) have on the order of millions of channels
  • Heavy-ion events are very dense

So the real question is whether Phoenix can still show these in a few years?

Why now (the CERN angle)

ROOT's own event display (ROOT-EVE / REve) is already moving its renderer to WebGPU. Their engine, RenderCore, uses WebGPU compute shaders, and in their CHEP paper they show 1.2 million HGCAL hexagonal prisms at interactive frame rates on a 10-year-old GPU (GTX 970). The catch is that REve requires a C++/ROOT server running behind it.

Phoenix's whole point is zero install (just a URL, runs fully in the browser, can be hosted on GitHub Pages). If we add a WebGPU path, Phoenix could be the first event display that reaches that kind of scale with no server at all. That is a genuine differentiator, not just catching up.

The timing also lines up:

  • WebGPU is now supported in all major browsers (Chrome, Firefox, Safari 26, Edge, roughly 95% of users), with WebGL2 fallback for the rest.
  • Three.js already ships WebGPURenderer.
  • We are already on Three.js r178, and I checked node_modules: it already includes the three/webgpu and three/tsl builds, so there is no dependency upgrade needed just to begin.

Proposal

Add a second renderer path using three.js's WebGPURenderer, selected automatically when the browser supports it, with automatic fallback to the current WebGLRenderer otherwise.

Nothing changes for users without WebGPU or for existing experiment routes.

Then progressively move the heavy per-object work (cuts, culling, picking) onto the GPU instead of running CPU loops every frame.

To keep this reviewable, I'd split it into three phases.

Phase 1: WebGPU renderer path + fallback (foundation)

  • Detect WebGPU support.
  • When available, construct a WebGPURenderer instead of WebGLRenderer at the single renderer construction site (renderer-manager.ts). Otherwise continue using today's WebGLRenderer.
  • Verify the materials Phoenix already uses render correctly on WebGPU. From what I found these are almost entirely standard Three.js materials (MeshBasicMaterial, MeshPhongMaterial, LineBasicMaterial, MeshToonMaterial, PointsMaterial), which automatically convert to node materials under WebGPURenderer.
  • Port the small number of custom shaders to TSL (Three.js Shading Language), so one shader source works on both WebGPU and WebGL. I found only three custom shader locations:
    • TracksMaterial (tracks.ts)
    • the ShaderMaterial in phoenix-objects.ts
    • the hover outline shader in effects-manager.ts
  • Push the existing InstancedMesh CaloCells path (added in Enabling calo cells causes a big performance hit #474 / fix: use InstancedMesh for CaloCells to prevent WebGL crash (#474) #849) beyond its current limits.
  • Add a benchmark so we can compare cells rendered, FPS, and draw calls before and after.

This phase needs no compute shaders. It is mostly "swap the renderer, keep the fallback, confirm everything still renders, scale the instanced path up, and measure." It also tells us exactly which WebGPU features still need work, since Three.js still marks WebGPURenderer as experimental.

Phase 2: Move cuts and culling onto the GPU

  • Use TSL compute shaders to evaluate collection cuts (energy thresholds, η/φ windows) on the GPU instead of CPU loops.
  • Perform frustum and occlusion culling on the GPU so off-screen and hidden objects cost essentially nothing.

This is where large object counts become much smoother because the per-frame CPU work is no longer O(n).

Phase 3: GPU picking for instanced collections

  • Today selection walks the scene graph (selection-manager.ts), which is O(n).
  • For large instanced collections, replace this with GPU picking by rendering object IDs into an off-screen buffer and reading back the ID under the cursor.

Hover and click remain responsive even with millions of rendered objects.

Why this is safe

Out of scope

  • Rewriting Phoenix around WebGPU.
  • Removing WebGL (it remains the fallback renderer).
  • Changing the data model, loaders, or UI panels.

References

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions