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

  1. Dispose the InternalTexture and recreate it via the public API (createRenderTargetTexture / createDepthStencilTexture) after restore, which rebuilds the depth/stencil attachment too.
  2. Re-wrap the color texture first, then create a new depth/stencil texture and re-attach it to the render target wrapper manually.
  3. 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

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


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