# Potential WebGPU \`visibleInstances\` leak?

**URL:** <https://forum.babylonjs.com/t/potential-webgpu-visibleinstances-leak/62660>\
**Category:** Questions\
**Tags:** webgpu\
**Created:** [March 4, 2026, 8:54am UTC](https://forum.babylonjs.com/t/potential-webgpu-visibleinstances-leak/62660 "2026-03-04T08:54:36Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![regna](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/regna/32/23915_2.png) [@regna](https://forum.babylonjs.com/u/regna)\
**Post date:** [March 4, 2026, 8:54am UTC](https://forum.babylonjs.com/t/potential-webgpu-visibleinstances-leak/62660/1 "2026-03-04T08:54:36Z")

</div>

Hi folks! I’m seeing a potential memory leak in WebGPU. After several minutes, Chrome eventually crashes with the **“Aw, Snap!” out of memory error**.

To test, I captured 2 Heap snapshots taken a **few seconds apart** and compared them. Several tens of thousands of new Arrays were generated and retained (several Megabytes), and they were from [here in the source](https://github.com/BabylonJS/Babylon.js/blob/master/packages/dev/core/src/Meshes/mesh.ts#L96):

```typescript
class _InstanceDataStorageRenderPass {
    /** @internal */
    public visibleInstances: Nullable<IInstanceDataStorageRenderPassVisibleInstances>;
    // ...
}

```

If I leave it running for several minutes, there will be millions of these new Arrays generated and retained (few Gigabytes) until the “Aw, Snap” out of memory crash.

Please see the video below of the Heap snapshot capturing and analyzing:

[![](https://i.ytimg.com/vi/GjdhHOPIkDs/hqdefault.jpg "WebGPU visibleInstances leak") ](https://www.youtube.com/watch?v=GjdhHOPIkDs)

* * *

I asked Claude AI if there could be a leak with `visibleInstances`, and it said:

> Looking at the [`_InstanceDataStorageRenderPass.visibleInstances`]mesh.ts ) and how it’s managed, I can see a potential leak specific to **WebGPU** (non-mono render pass mode).
> 
> ## **The Leak**
> 
> In [`_registerInstanceForRenderId()`]mesh.ts ):
> 
> ```typescript
> if (!instanceDataStorage.visibleInstances[renderId]) {
> if (instanceDataStorage.previousRenderId !== undefined && this._instanceDataStorage.isFrozen) {
> instanceDataStorage.visibleInstances[instanceDataStorage.previousRenderId] = null;
> }
> instanceDataStorage.previousRenderId = renderId;
> instanceDataStorage.visibleInstances[renderId] = new Array<InstancedMesh>();
> }
> 
> ```
> 
> The cleanup (visibleInstances[previousRenderId] = null) only fires when isFrozen **is true**. When not frozen, old render IDs accumulate indefinitely on the visibleInstances object — one entry per render ID, every frame.
> 
> ## **Why It’s WebGPU-Specific**
> 
> In the [`Mesh` mesh.ts ):
> 
> ```typescript
> this._instanceDataStorage.useMonoDataStorageRenderPass = !this._instanceDataStorage.engine.isWebGPU;
> 
> ```
> 
> - **WebGL** : uses dataStorageRenderPass (single object, cleared via \_preActivate setting visibleInstances = null)
> 
> - **WebGPU** : uses renderPasses[renderPassId] map — one [`_InstanceDataStorageRenderPass`]mesh.ts ) per render pass. Each pass has its **own** visibleInstances object.
> 
> In [`_preActivate()`]mesh.ts ):
> 
> ```typescript
> if (this._instanceDataStorage.useMonoDataStorageRenderPass) {
> this._instanceDataStorage.dataStorageRenderPass.visibleInstances = null; // ← WebGL path: clears entire object
> } else {
> this._getInstanceDataStorage().visibleInstances = null; // ← WebGPU path: only clears CURRENT render pass
> }
> 
> ```
> 
> On WebGPU, \_preActivate only resets visibleInstances for the **currently active render pass** at pre-activation time. But \_registerInstanceForRenderId can be called for **different render IDs** within the same render pass object. Since the cleanup in \_registerInstanceForRenderId is gated on isFrozen, those old renderId keys accumulate on the visibleInstances index object and are never cleaned up.
> 
> ## **The Fix**
> 
> The cleanup should not be gated on isFrozen:
> 
> ```typescript
> // ...existing code...
> public _registerInstanceForRenderId(instance: InstancedMesh, renderId: number): Mesh {
> const instanceDataStorage = this._instanceDataStorage.useMonoDataStorageRenderPass ? this._instanceDataStorage.dataStorageRenderPass : this._getInstanceDataStorage();
> if (!instanceDataStorage.visibleInstances) {
> instanceDataStorage.visibleInstances = {
> defaultRenderId: renderId,
> selfDefaultRenderId: this._renderId,
> intermediateDefaultRenderId: -1,
> };
> }
> 
> if (!instanceDataStorage.visibleInstances[renderId]) {
> if (instanceDataStorage.previousRenderId !== undefined) { // Remove the isFrozen guard
> instanceDataStorage.visibleInstances[instanceDataStorage.previousRenderId] = null;
> }
> instanceDataStorage.previousRenderId = renderId;
> instanceDataStorage.visibleInstances[renderId] = new Array<InstancedMesh>();
> }
> 
> instanceDataStorage.visibleInstances[renderId].push(instance);
> return this;
> }
> // ...existing code...
> 
> ```
> 
> The isFrozen guard was likely added to preserve the cached batch when frozen, but visibleInstances is separate from previousBatch — the batch cache is preserved via [`instanceDataStorage.previousBatch`]mesh.ts ) in \_getInstancesRenderList(), so nulling out old render ID entries in visibleInstances is safe even when frozen.

* * *

I’m totally new to WebGPU and am unsure if Claude is on the right path here.

It’s interesting how Claude mentioned this leak only applies to WebGPU. **Indeed, when I switched to WebGL2 and performed the same Heap snapshots, I did not see this leak.** ☑

I would be grateful if anyone could help with this. I am always happy to help test out any changes. Thank you!!

---

<div class="post-metadata">

**Author:** ![Evgeni\_Popov](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/evgeni_popov/32/4432_2.png) [@Evgeni\_Popov](https://forum.babylonjs.com/u/Evgeni_Popov)\
**Post date:** [March 4, 2026, 9:29am UTC](https://forum.babylonjs.com/t/potential-webgpu-visibleinstances-leak/62660/2 "2026-03-04T09:29:26Z")

</div>

Assigned @Evgeni\_Popov on March 4, 2026, 9:29am UTC



---

<div class="post-metadata">

**Author:** ![Evgeni\_Popov](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/evgeni_popov/32/4432_2.png) [@Evgeni\_Popov](https://forum.babylonjs.com/u/Evgeni_Popov)\
**Post date:** [March 4, 2026, 2:05pm UTC](https://forum.babylonjs.com/t/potential-webgpu-visibleinstances-leak/62660/3 "2026-03-04T14:05:10Z")

</div>

Well spotted!

There was also another minor leak, this PR fixes them all:

> <https://github.com/BabylonJS/Babylon.js/pull/18022>
>
> See https://forum.babylonjs.com/t/potential-webgpu-visibleinstances-leak/62660

---

<div class="post-metadata">

**Author:** ![Evgeni\_Popov](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/evgeni_popov/32/4432_2.png) [@Evgeni\_Popov](https://forum.babylonjs.com/u/Evgeni_Popov)\
**Post date:** [March 4, 2026, 2:05pm UTC](https://forum.babylonjs.com/t/potential-webgpu-visibleinstances-leak/62660/4 "2026-03-04T14:05:13Z")

</div>

Unassigned @Evgeni\_Popov on March 4, 2026, 2:05pm UTC



---

<div class="post-metadata">

**Author:** ![regna](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/regna/32/23915_2.png) [@regna](https://forum.babylonjs.com/u/regna)\
**Post date:** [March 4, 2026, 5:59pm UTC](https://forum.babylonjs.com/t/potential-webgpu-visibleinstances-leak/62660/5 "2026-03-04T17:59:04Z")

</div>

OMG thank you so much, Evgeni!!
