Regression in 8.54.1: _releaseRenderPassId is not a function during render — breaks shadows, lighting, and dispose

Hi,

After upgrading from Babylon.js 8.53.1 to 8.54.1, we see a regression that breaks rendering and cleanup. Rolling back to 8.53.1 fixes it.

Error:

TypeError: i2._releaseRenderPassId is not a function
    at r2.$.releaseRenderPassId (…)
    at e3._releaseRenderPassId (…)
    at e3.dispose (…)
    at t3.dispose (…)
    at e2._disposeTextureAndPostProcesses (…)
    at e2.render (…)
    at e2._draw (…)
    at e2._drawCamera (…)
    at e3._renderForCamera (…)
    at e3._processSubCameras (…)

The error is thrown during the normal render loop when the engine/camera code disposes textures or post-processes and calls _releaseRenderPassId on the engine. In 8.54.1 the engine instance we get (e.g. from scene.getEngine() ) does not have _releaseRenderPassId , so the call fails.

Observed impact:

  • Shadows (e.g. ShadowGenerator) stop working or throw during dispose.
  • Lighting and related effects are affected (e.g. glow, volumetric/post-processes).
  • Dispose of meshes, materials, and render targets is unreliable: disposal paths that go through _releaseRenderPassId throw, so cleanup is incomplete and we see leaks or follow-up errors.

Environment:

  • Babylon.js 8.54.1 (bundled with Vite; we use both the babylonjs package and @babylonjs/core).
  • new Engine(canvas, ...) for the main engine; the error occurs when that engine is used for rendering and during dispose (e.g. camera/post-process/texture cleanup).

Workaround: We temporarily stayed on 8.53.1, where _releaseRenderPassId is present on the engine and the error does not occur.

Could you check whether 8.54.1 changed how _releaseRenderPassId is defined or attached to the engine (e.g. only on a specific engine subclass or in a code path we don’t hit), and restore the previous behavior so disposal and rendering work again?

Thanks.

_releaseRenderPassId is a method of AbstractMesh, not AbstractEngine, which has a releaseRenderPassId method. It’s during the execution of this method that AbstractMesh._releaseRenderPassId is called:

AbstractEngine.prototype.releaseRenderPassId = function (id: number): void {
    this._renderPassNames[id] = undefined as any;

    for (let s = 0; s < this.scenes.length; ++s) {
        const scene = this.scenes[s];
        for (let m = 0; m < scene.meshes.length; ++m) {
            const mesh = scene.meshes[m];
            mesh._releaseRenderPassId(id);
            if (mesh.subMeshes) {
                for (let b = 0; b < mesh.subMeshes.length; ++b) {
                    const subMesh = mesh.subMeshes[b];
                    subMesh._removeDrawWrapper(id);
                }
            }
        }
    }
};

Are you sure all meshes in scene.meshes have AbstractMesh as their base class? The only way I can see the code crashing is if a mesh doesn’t subclass AbstractMesh (which would be an error, because Scene.meshes is declared as AbstractMesh[]);

I tried to reproduce this one:

The shadow generator is disposed after 1s, but it doesn’t crash.

@Evgeni_Popov Thank you so much for your assistance.
:folded_hands:

IT IS NOT A BABYLONJS BUG.

The problem was definitely rooted in the update, but the real culprit was my package.json , which contained conflicting dependencies . I just got lucky in the past that they happened to work the same way.

We’ve tracked down the root cause on our side and it was not a Babylon.js bug but a bundling issue.
Our app was using both babylonjs and @babylonjs/core at the same time:

  • All rendering code (Engine, Scene, Mesh, ShadowGenerator, WaterMaterial, etc.) imported from babylonjs (+ babylonjs-materials).
  • A single type import Sound was coming from @babylonjs/core/Audio.
    With Vite this ended up pulling two independent copies of Babylon into the same bundle. Internal render‑pass code (renderPassIds / _releaseRenderPassId) assumed that all objects live in the same world, but in practice some objects came from the babylonjs instance and others from the @babylonjs/core instance. That mismatch eventually led to calling _releaseRenderPassId on an object that didn’t have it in its prototype chain, which is what produced:
    TypeError: i2._releaseRenderPassId is not a function
    This also explains why we could not reproduce the error in the Playground: the Playground always uses a single global Babylon instance, so this clash never happens there.
    The fix on our side was:
  1. Remove all runtime usage of @babylonjs/core (we only had one type import – we switched Sound to import type { Sound } from 'babylonjs').
  2. Remove @babylonjs/core from dependencies, keeping only the UMD packages:
    • babylonjs
    • babylonjs-materials
    • babylonjs-loaders
    • babylonjs-gui
    • plus our custom plugins
  3. Keep a Vite resolve.dedupe only for babylonjs:
    resolve: {
      dedupe: ['babylonjs'],
      alias: { ... }
    }
2 Likes