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

  1. Only pass textures returned by engine.wrapWebGLTexture (source === InternalTextureSource.External)
  2. Filter your texture list before restoring: skip textures whose source !== InternalTextureSource.External
  3. For non-external textures, recreate them normally (reload from source) after context restore instead of re-wrapping
  4. 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

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


AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30). Data as JSON: /api/errors/5c1414318340531f. Report an issue: GitHub.