Asset Lifecycle Management for glTF and GLB in Long-Running Babylon.js Scenes
Load glTF and GLB assets with AssetContainer, dispose them correctly, and treat the files as untrusted data in long-running Babylon.js scenes.
17 Aug 2025, 08:45 UTC

The problem and the practical takeaway
In a long-running Babylon.js scene—such as a product configurator, level editor, or game with load and unload cycles—loading a glTF or GLB file and later removing its meshes does not guarantee that GPU memory is released. Meshes can disappear from the scene while textures, buffers, and shaders remain allocated by the graphics driver. glTF is the GL Transmission Format; GLB is its binary container.
The useful takeaway is to load every asset through a Babylon.js AssetContainer, call container.dispose() when the asset is unloaded, perform all loading outside the render loop, and treat glTF files as untrusted data because Babylon.js does not sandbox them.
Requirements
- Load glTF and GLB assets without stalling or partially rendering the scene.
- Release meshes, materials, textures, and GPU buffers when an asset is removed.
- Support repeated load and unload cycles in a single browser session.
- Reject malformed or hostile asset files before they reach the renderer.
Smallest suitable design
Use BABYLON.SceneLoader.LoadAssetContainerAsync to load an asset into an AssetContainer, then add its contents to the scene. When the asset is no longer needed, call container.dispose(). An AssetContainer groups meshes, materials, textures, and related data from one asset file so they can be managed as a unit.
// Run this in a loader service or module scope. Do not place it inside
// engine.runRenderLoop, where a slow load can stall the renderer.
async function loadGltfAsset(url, scene) {
const lower = url.toLowerCase();
if (!lower.endsWith('.gltf') && !lower.endsWith('.glb')) {
throw new Error('Unsupported asset extension: ' + url);
}
const container = await BABYLON.SceneLoader.LoadAssetContainerAsync(url, scene);
container.addAllToScene();
return container;
}
async function unloadGltfAsset(container) {
// AssetContainer.dispose() removes meshes from the scene, but it does not
// stop animations you started on those meshes or remove listeners you
// attached to them. Stop those first if your code created them.
container.dispose();
}
Usage before rendering starts:
const chair = await loadGltfAsset('/models/chair.glb', scene);
// ... later, when the chair is no longer visible ...
await unloadGltfAsset(chair);
If you repeat the same asset many times, use InstancedMesh or thin instances instead of duplicating meshes. They share geometry and material data, which reduces draw calls and GPU memory compared with loading separate GLB copies.
Trust and data boundaries
Treat glTF and GLB as untrusted data. Babylon.js parses and renders the file but does not sandbox embedded content. Before loading:
- Validate the file extension and, where possible, the MIME type.
- Cap the download size before passing the payload to the loader.
- Sanitize URIs referenced inside the asset.
- Do not execute scripts or executable content embedded in the file.
Cross-origin textures may load but fail during post-processes or pixel readback unless the server sends CORS headers allowing the origin.
Operational checks
- Wrap every load call in
try/catchand show a placeholder or error state when the promise rejects. - Assert that the load promise resolves before
engine.runRenderLoopstarts, so the first frame is not missing resources. - Simulate network failure during loading to verify the application does not crash.
- After
container.dispose(), inspectscene.meshesandscene.texturesin browser DevTools to confirm the asset's entries are gone. - Use a frame debugger such as Spector.js after disposal to verify that no residual textures, buffers, or shaders remain for the removed asset.
- Confirm engine creation selects WebGL2, the Babylon.js 5.x default, and test a forced WebGPU initialization failure to verify the application falls back to WebGL2 without crashing.
Failure modes
- Partial frames. Loading inside the render loop can produce frames with missing meshes or textures while the promise is pending.
- GPU memory growth. Texture uploads are driver-managed. JavaScript garbage collection and
dispose()do not immediately free GPU memory, so repeated cycles may still grow memory until the driver reclaims it. - Dangling animations.
AssetContainer.dispose()removes meshes but does not automatically stop animations running on them. - External GPU resources.
scene.dispose()may not release every external resource, such as particle systems, post-processes, and render targets created at runtime. Dispose those separately. - Cross-origin readback failures. Textures loaded from another origin can fail in post-processes or pixel readback without CORS headers.
Rollback
Loading an asset changes GPU and scene state. The rollback for a loaded container is container.dispose(). If the load succeeds but the asset must be removed, call dispose and re-render the previous scene state. If the load fails, the catch block should leave the scene in its pre-load state.
Conditions that change the design
- Large or streaming worlds. Loading whole GLBs and relying on disposal is unsuitable for big levels. Use Draco-compressed glTF, mesh quantization, level of detail, and progressive loading instead.
- WebGPU rendering. WebGPU exists in Babylon.js but is version-sensitive, may require explicit engine selection, and needs a WebGL2 fallback path. Do not assume WebGPU is available.
- Repeated assets. For many copies of the same model, InstancedMesh or thin instances are more appropriate than repeated container load and dispose cycles.
Limitations and verification
AssetContainer.dispose() does not stop animations or remove listeners you attached to the meshes. GPU memory may not fall immediately after disposal because the graphics driver manages allocations independently of JavaScript. Babylon.js APIs change between major releases, so avoid undocumented internal classes and do not assume method signatures remain stable across versions.
A practical verification is to open the browser task manager, load a GLB with SceneLoader.AppendAsync or LoadAssetContainerAsync, call container.dispose(), and confirm that scene.meshes and scene.textures no longer contain the asset while GPU memory trends downward. Pair this with Spector.js to inspect residual GPU objects.
Version assumption: Babylon.js 5.x with WebGL2 as the default engine. Mark any WebGPU-specific behavior for review before production use.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.