BabylonJS/Babylon.js · error
updateWrappedWebGLTexture: wrapped texture's render-target w
Error message
updateWrappedWebGLTexture: wrapped texture's render-target wrapper has a depth/stencil texture; not supported. Dispose and re-wrap.
What it means
updateWrappedWebGLTexture() re-registers an externally-created WebGL texture after a context-loss/restore event. If the wrapped texture is the color attachment of a render-target wrapper that also owns a depth/stencil texture, that depth/stencil texture's GL handle was also lost and Babylonic v1 does not rebuild it automatically, so the engine throws instead of leaving the render target in a broken state. The comment in the source explicitly says re-attaching is feasible but non-trivial and deferred.
Source
Thrown at packages/dev/core/src/Engines/engine.pure.ts:832
if (internalTexture.source !== InternalTextureSource.External) {
throw new Error("updateWrappedWebGLTexture: target InternalTexture was not produced by wrapWebGLTexture.");
}
// Pre-validate before mutating any state so a thrown precondition leaves the InternalTexture untouched.
// Note: rtWrapper.texture only returns _textures[0]; walk every attachment to catch the multi-RT case where
// the wrapped texture is at index > 0.
for (const rtWrapper of this._renderTargetWrapperCache) {
if (!rtWrapper.textures?.includes(internalTexture)) {
continue;
}
if (rtWrapper.isMulti) {
throw new Error("updateWrappedWebGLTexture: wrapped texture is part of a multi render-target; not supported. Dispose and re-wrap.");
}
if (rtWrapper._depthStencilTexture) {
// The depth/stencil texture's GL handle was also lost on context restore. Rebuilding it from the
// wrapper's stored depth settings + re-attaching is feasible but non-trivial; v1 rejects and asks
// the caller to dispose + re-wrap (which also recreates the depth/stencil texture via the public API).
throw new Error("updateWrappedWebGLTexture: wrapped texture's render-target wrapper has a depth/stencil texture; not supported. Dispose and re-wrap.");
}
}
internalTexture._hardwareTexture = new WebGLHardwareTexture(texture, this._gl);
internalTexture.isReady = true;
// The new GL texture has default sampler state; clear the per-InternalTexture cached sampler params so the
// next _setTexture re-applies them, then drop any binding-cache slot pointing at this InternalTexture so the
// identity short-circuit (this._boundTexturesCache[channel] === internalTexture) doesn't skip the rebind.
internalTexture._cachedCoordinatesMode = null;
internalTexture._cachedWrapU = null;
internalTexture._cachedWrapV = null;
internalTexture._cachedWrapR = null;
internalTexture._cachedAnisotropicFilteringLevel = null;
for (const key in this._boundTexturesCache) {
if (this._boundTexturesCache[key] === internalTexture) {
this._boundTexturesCache[key] = null;
}
View on GitHub (pinned to 0592b347b8)
Solutions
- Dispose the InternalTexture and recreate it via the public API (createRenderTargetTexture / createDepthStencilTexture) after restore, which rebuilds the depth/stencil attachment too.
- Re-wrap the color texture first, then create a new depth/stencil texture and re-attach it to the render target wrapper manually.
- If you control target creation, drop the depth/stencil attachment for wrapped targets so this path is never taken.
Example fix
// before
engine.updateWrappedWebGLTexture(restoredColorTexture);
// after
rtWrapper.renderTarget?.dispose();
const newRt = engine.createRenderTargetTexture(size, {
depthTexture: true,
samples: 4
});
engine.updateWrappedWebGLTexture(newRt.getInternalTexture()); Defensive patterns
Strategy: validation
Validate before calling
function canUpdateWrappedTexture(engine, internalTexture) {
const rtw = internalTexture.getRenderTargetWrapper?.();
return !rtw || !rtw._depthStencilTexture;
} Type guard
function isUpdatableWrappedTexture(t) {
return !!t && !(t.getRenderTargetWrapper?.()?._depthStencilTexture);
} Try / catch
try {
engine.updateWrappedWebGLTexture(tex);
} catch (e) {
if (/depth\/stencil texture/.test(e.message)) {
tex.dispose();
tex = recreateRenderTargetWithDepth(engine);
}
} Prevention
- Track webglcontextrestored and always recreate render targets with depth/stencil attachments via the public API instead of re-wrapping.
- Avoid attaching depth/stencil textures to render targets whose color textures you re-wrap.
- Centralize context-restore recovery in one helper.
When it happens
Trigger: Calling engine.updateWrappedWebGLTexture(texture) on an InternalTexture whose renderTargetWrapper has a non-null _depthStencilTexture after a webglcontextrestored event.
Common situations: Apps that manually re-wrap restored WebGL textures after device loss (e.g. mobile browsers, headless rendering, texture sharing with other GL contexts) where the render target was created with a depth/stencil attachment (depthTexture: true or default depth buffer as a texture).
Related errors
- Unable to create multi sampled framebuffer
- Unable to create Occlusion Query
- Unable to create dummy framebuffer
- Unable to create multi sampled framebuffer
- Unable to create Transform Feedback
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/e7c821812a6f0bf3.
Report an issue: GitHub.