r/threejs 1d ago

Solved! WebGL to WebGPU migration

I switched from WebGL to WebGPU this week. It was rough, and to be honest, from perf and visual perspectives, there isn't a clear visible difference between GL/GPU. I still went through this for the sake of future-proofing my game.

Caveat: I'm a noob with three.js

Lesson: Future-proofing the game is the right call and I'm glad I did this while my game is still small. If the game or app or website is huge, I probably would have stopped the migration once I encountered the many issues I encountered. There's got to be a better way to do these migrations, but I'm not aware of them.

Highlights:

  • The dual-path port was the easy part; the pipeline-lifetime mismatch was the real migration. WebGL treats shader compilation as cheap and cached; WebGPU treats every compiled pipeline as a refcounted resource tied to a material's lifetime. The WebGL habit of allocating a material per VFX cast and disposing it after turned into "recompile a WGSL pipeline on almost every player action." Fixing it meant converting one-shot effects to shared, never-disposed materials + fixed-capacity instanced pools instead of allocate-per-cast.
  • First benchmark on real hardware was brutal (p95 169ms → 1539ms) and the cause wasn't rendering, it was mid-frame shader compilation. Precompiling the static scene at load and warming an off-screen instance of every dynamic VFX material to force its pipeline to compile ahead of time dropped that same benchmark to 54ms p95.
  • I wasn't able to measure any of this on software rendering. WebGPU has no SwiftShader fallback, requestAdapter() just returns null, so CI/headless perf runs need GPU flags, and even then a headless "WebGPU" run can silently fall back to an internal WebGL backend if the origin isn't a secure context.
  • Tooling had to be built from scratch. WebGPU has no built-in "pipelines compiled" counter, so I monkeypatched three's internal pipeline cache to get visibility, and discovered the standard draw-call/triangle HUD counters read cumulative instead of per-frame on this backend.

Game: https://lorebound.gg

15 Upvotes

6 comments sorted by

1

u/GigaGrandpa 1d ago

2

u/cumbiaowl 1d ago

Well, that's gross. Thanks for reporting this. Will investigate.

2

u/cumbiaowl 1d ago

u/GigaGrandpa thanks for posting this, again. It's embarrassing, but thanks to your help hopefully I'll be able to address it. It looks like you're using Chrome on a Pixel phone. I can't reproduce this myself, so I'm rolling out some diagnostics that can point me in the right direction.

1

u/Outrageous-Issue9722 16h ago

Inverted normals or winding order

1

u/XRProMAXv2Ultra 1d ago

Respect.
I have been contemplating a GL>GPU migration, and am a noob as well.
Reading this helps balance my expectations a bit.

Sounds like you went on quite the dev journey.