BabylonJS/Babylon.js · error

updateWrappedWebGLTexture: wrapped texture is part of a mult

Error message

updateWrappedWebGLTexture: wrapped texture is part of a multi render-target; not supported. Dispose and re-wrap.

What it means

When restoring a wrapped texture, Babylon checks render-target wrappers that reference it. If the texture is an attachment of a multi render-target (rtWrapper.isMulti) or has a depth/stencil attachment, simply swapping the GL handle would leave sibling attachments inconsistent, so v1 rejects and asks the caller to dispose and re-wrap the whole construct.

Source

Thrown at packages/dev/core/src/Engines/engine.pure.ts:826

     * 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.");
            }
        }

        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;

View on GitHub (pinned to 0592b347b8)

Solutions

  1. Dispose the render target wrapper and recreate it, wrapping the new WebGL textures via wrapWebGLTexture
  2. Exclude textures belonging to isMulti render-target wrappers (and those with depth/stencil attachments) from handle-swap restoration
  3. Restructure so wrapped textures used with MRT are restored through the public render-target creation API
  4. Check rtWrapper.isMulti / rtWrapper._depthStencilTexture before calling updateWrappedWebGLTexture and branch to dispose+re-wrap

Example fix

// before
engine.updateWrappedWebGLTexture(mrtTexture, newHandle); // throws: part of MRT
// after
if (rtWrapper.isMulti || rtWrapper._depthStencilTexture) {
  rtWrapper.dispose();
  const newRt = engine.createMultipleRenderTarget(size, mrtOptions); // then wrap/rebind handles
} else {
  engine.updateWrappedWebGLTexture(mrtTexture, newHandle);
}
Defensive patterns

Strategy: validation

Validate before calling

const wrapper = engine._renderTargetWrapperCache.find(rtw => rtw.textures?.includes(internalTexture));
if (wrapper && (wrapper.isMulti || wrapper._depthStencilTexture)) {
  // dispose and recreate instead of updateWrappedWebGLTexture
}

Type guard

function canRepointInPlace(engine: AbstractEngine, t: InternalTexture): boolean {
  return !engine._renderTargetWrapperCache.some(rtw => rtw.textures?.includes(t) && (rtw.isMulti || !!rtw._depthStencilTexture));
}

Try / catch

try {
  engine.updateWrappedWebGLTexture(t, newHandle);
} catch (e) {
  if (e instanceof Error && e.message.includes('multi render-target')) {
    rtWrapper.dispose();
    const rt = engine.createMultipleRenderTarget(size, mrtOptions); // dispose + re-wrap path
  } else throw e;
}

Prevention

When it happens

Trigger: Calling engine.updateWrappedWebGLTexture on a texture that is an attachment of a multi-render-target wrapper (multiple textures) or whose wrapper has a _depthStencilTexture, e.g. MRT pipelines or depth-enabled wrapped RTs after context loss.

Common situations: Context-restoration recovery for MRT (G-buffer) setups using wrapped textures; deferred rendering engines that wrapped each MRT attachment and try to repoint them one by one.

Related errors


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