Effect stuck not-ready forever after disposing a same-key Effect (WebGL2 pipeline cache)

Versions: 9.15.0 and 9.20.0 (both reproduce) · Chrome 151 / Windows / NVIDIA RTX 3060 Ti · WebGL2

Symptom (as seen in production): on cold load, every PBRMaterial renders black/invisible while StandardMaterial and ShaderMaterial render fine. No console errors. Refresh usually fixes it.

Cause (traced to the WEBGL2 pipeline cache): cachedPipelines registers a pipeline before its program finishes parallel-compiling, and cached pipelines are shared across Effect instances with no reference tracking. Two concrete defects:

  1. An Effect constructed with the same key while the first is still compiling takes the cache-hit branch and reports isReady() === true while the driver still reports COMPLETION_STATUS_KHR === false — and it is never polled.
  2. Disposing that second Effect while the shared program is still compiling calls resetCachedPipeline + _deletePipelineContext, destroying the pipeline under the surviving Effect. The survivor is permanently isReady() === false with getCompilationError() === "", its retry poll spins silently for 120s, and executeWhenCompiled never fires. Since the whole .env decode hangs on executeWhenCompiled of a rgbdDecode postprocess effect, scene.environmentTexture never becomes ready → all PBR invisible, zero errors.

Repro: a single self-contained HTML file — no build step, no external assets (the .env is generated in-page via CreateEnvTextureAsync). It instruments driver state vs. Babylon state per 250ms sample and prints per-scenario hit rates. Both defects reproduce 2/2 per run, on 9.15.0 and 9.20.0.

Readable source (Babylon pinned from CDN; run from any static server):

Live demo, 9.20.0 (runs the suite in your browser; results render on the page, ~2 min):
https://pub.hyperagent.com/p/lxG_hGCuCJwnqGr80Q-Ms1qMgoIp9vVZ0JYQHSoFb3k?delay=700&iters=2
Add &fix=1 to see a proposed fix (refcounted shared pipelines + deferred cache-hit readiness) bring every scenario to 0/2 with the healthy baseline unchanged.
(The live-demo copy has the Babylon bundle inlined because that host blocks CDN scripts — the gist is the canonical readable version.)

Not a playground repro because the instrumentation must install before Engine creation, which the playground doesn’t allow.

I have a patch ready (4 files, 8 hunks, applies cleanly to both 9.15.0 and 9.20.0) plus instrumented JSON results — happy to open an issue + PR if this is confirmed.

This sounds really strange as we often create simialr material without ever facing the issue.

A repro in the playground should be possible ?

Filed as Shared WEBGL2 pipeline cache breaks Effect readiness: dispose of a same-key Effect permanently bricks live effects (silent), cache-hit effects report isReady while still compiling · Issue #18783 · BabylonJS/Babylon.js · GitHub

The repro has tons of extra code making it hard to validate. Can you share a simpler version in the babylon playground ?

let me try, sec

(for what it’s worth, AI helped me author this, but seems reasonably straightforward):
https://playground.babylonjs.com/#7668KH

  • big red “BUG REPRODUCED” banner on hit (~8s).

Not sure if you had a chance yet @sebavan, but the patch I sent fixes it in my real app- i’m loading that patch on top of 9.20.0. My surfaces aren’t going black anymore. Along the way I hit a second issue: the environment BRDF texture can come back completely black while still reporting ready, which also blacks out all PBR materials with no error. I’m working around it by loading my own BRDF texture and letting the shader decode it directly, instead of Babylon’s built-in expansion step. If you want to dig into it I can file a separate issue with a playground repro.

Having a look now, will report back ASAP

Effect are not meant to be used directly this way which could explain your issue.

I would recommend to always go through createEffect on the engine instance like here: https://playground.babylonjs.com/#7668KH#1

Then if you want the ref count to work just call dispose and not dispose(true) which bypasses the internal refcount as intended.

Any specific reason the use Effect directly ?

thank you! Will take a look…

Sorry, it took me a minute to look through notes of how I got to where we’re at.
The short version is- the repro just uses it as the shortest way to force the bugged state. My real app never touches Effect, and everything in the failing scene was created via createEffect.

In my app, every PBR surface in the room would half the time render black or skipped (invisible), while StandardMaterial and ShaderMaterial surfaces rendered ok. No errors, and a refresh usually fixed it, so it took a while to track it down. Once I got it into the bugged state, I ran a few checks in the devtools console.

Babylon’s internal postprocess+rgbdDecode Effect was stuck at isReady() === false, but checking its WebGL program in devtools showed both LINK_STATUS and COMPLETION_STATUS_KHR as true. Calling engine._isRenderingStateCompiled(effect._pipelineContext) once flipped the effect ready and the whole room appeared / black walls colorized etc.

Calling _isRenderingStateCompiled was not just checking the status. That method also finalizes a completed pipeline and fires its compile callback. One call from devtools made Babylon recognize the already-finished program, after which the Effect became ready and the room rendered normally.

I never caught the exact trigger, which is why the repro forces the same “type” of bug instead of replaying my scene. With the patch I sent, one Effect can no longer dispose a pipeline that another Effect still uses.

It would be amazing if you have a repro of your app somewhere as it seems that smthg is really buggy in the way we managed our rdbd texture or pbr material. I would really like to patch the root cause vs the current effect it has on your app.

The code path relying on the pipeline key should normally only be used in a prebuild scenario not similar to what would be addressed here.