BabylonJS/Babylon.js · error
updateWrappedWebGLTexture: target InternalTexture was not pr
Error message
updateWrappedWebGLTexture: target InternalTexture was not produced by wrapWebGLTexture.
What it means
updateWrappedWebGLTexture re-points an InternalTexture previously produced by wrapWebGLTexture to a new WebGLTexture handle (typically after context restore). It validates internalTexture.source === InternalTextureSource.External; textures created normally (via createTexture, render targets, etc.) are not 'External' and cannot be re-wrapped, so Babylon throws to protect state.
Source
Thrown at packages/dev/core/src/Engines/engine.pure.ts:815
* references held by materials, render-target wrappers, particle systems, etc.
*
* The new handle must describe a texture with the same dimensions the wrapped texture was created with. A WebGL
* handle is opaque (the dimensions can't be introspected), so we can't validate this -- passing a mismatched
* handle is undefined behaviour. Sampling mode and mip-map flag are properties of the logical wrapped texture and
* are re-applied to the new resource. Any render-target wrapper holding this texture as its color attachment has
* its framebuffer rebuilt with the new handle (including a fresh depth/stencil renderbuffer, since the old one
* came from the dead context). If the wrapper is multisampled, the MSAA framebuffer + color renderbuffer + MSAA
* depth/stencil buffer are rebuilt too.
*
* Throws if the target was not produced by {@link wrapWebGLTexture}, if the wrapped texture is part of a multi
* render-target wrapper, or if the wrapper has a depth/stencil texture (these are not supported in this version;
* dispose and re-wrap).
* @param internalTexture defines the wrapped InternalTexture to repoint
* @param texture defines the new WebGL handle to wrap
*/
public updateWrappedWebGLTexture(internalTexture: InternalTexture, texture: WebGLTexture): void {
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.");
}
View on GitHub (pinned to 0592b347b8)
Solutions
- Only pass textures returned by engine.wrapWebGLTexture (source === InternalTextureSource.External)
- Filter your texture list before restoring: skip textures whose source !== InternalTextureSource.External
- For non-external textures, recreate them normally (reload from source) after context restore instead of re-wrapping
- Track which textures you wrapped originally and restore only those
Example fix
// before
textures.forEach(t => engine.updateWrappedWebGLTexture(t, newHandle)); // throws for created textures
// after
import { InternalTextureSource } from 'core/Materials/Textures/internalTexture';
textures.filter(t => t.source === InternalTextureSource.External)
.forEach(t => engine.updateWrappedWebGLTexture(t, newHandle)); Defensive patterns
Strategy: type-guard
Validate before calling
import { InternalTextureSource } from 'core/Materials/Textures/internalTexture';
if (internalTexture.source !== InternalTextureSource.External) {
throw new Error('Only textures from wrapWebGLTexture can be re-wrapped');
} Type guard
function isWrappedExternal(t: InternalTexture): boolean {
return t.source === InternalTextureSource.External;
} Try / catch
try {
engine.updateWrappedWebGLTexture(t, newHandle);
} catch (e) {
if (e instanceof Error && e.message.includes('not produced by wrapWebGLTexture')) {
t.dispose();
recreateTextureFromSource(t); // normal reload path for non-external textures
} else throw e;
} Prevention
- Maintain a separate registry of wrapped textures at wrapWebGLTexture call time
- Check internalTexture.source before any re-wrap logic
- Recreate created textures via their original loader after context loss
- Never pass render-target or dynamic textures to updateWrappedWebGLTexture
When it happens
Trigger: Calling engine.updateWrappedWebGLTexture(internalTexture, newWebGLTexture) with an InternalTexture that came from createTexture / render target / dynamic texture instead of wrapWebGLTexture.
Common situations: Context-restoration recovery code that iterates ALL engine textures instead of only wrapped ones; mixing wrapped and created textures in the same collection; using a texture from a render target wrapper by mistake.
Related errors
- updateWrappedWebGLTexture: wrapped texture is part of a mult
- Atmosphere is not supported on WebGL ${engine.version}.
- Sample2DRgbaToRef: widthPx and heightPx must be positive.
- Instanced arrays are required for MSDF text rendering.
- At least one mesh is needed to create the nav mesh.
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/5c1414318340531f.
Report an issue: GitHub.