BabylonJS/Babylon.js · error · Error
Failed to decode the KTX2 file: layer ${mipmap.layerIndex} o
Error message
Failed to decode the KTX2 file: layer ${mipmap.layerIndex} of the base mip level is empty. What it means
When creating a 2D texture array from a KTX2 file, Babylon.js decodes the container and validates every layer of the base mip level before uploading it to the GPU. This error means one layer's data buffer is missing/null, so the file is malformed or the transcoder failed to produce data for that layer.
Source
Thrown at packages/dev/core/src/Materials/Textures/rawTexture2DArray.functions.ts:180
const layerCount = Math.max(decodedData.layerCount ?? 1, 1);
// mipmaps are ordered level by level, and within a level layer by layer, so the base level occupies
// the first layerCount entries and its layers are already in ascending order.
const baseLevel = decodedData.mipmaps.slice(0, layerCount);
if (baseLevel.length !== layerCount) {
throw new Error(`Failed to decode the KTX2 file: expected ${layerCount} layers for the base mip level but got ${baseLevel.length}.`);
}
// Every layer of a level shares the level's dimensions, and forceRGBA above means each one must be exactly
// width * height * 4 bytes. RawTexture2DArray takes all the layers as one flat buffer and cannot detect a
// short or mismatched layer, so validate here rather than uploading malformed data.
const { width, height } = baseLevel[0];
const expectedLayerByteLength = width * height * 4;
for (const mipmap of baseLevel) {
if (!mipmap.data) {
throw new Error(`Failed to decode the KTX2 file: layer ${mipmap.layerIndex} of the base mip level is empty.`);
}
if (mipmap.width !== width || mipmap.height !== height) {
throw new Error(
`Failed to decode the KTX2 file: layer ${mipmap.layerIndex} of the base mip level is ${mipmap.width}x${mipmap.height} but layer 0 is ${width}x${height}.`
);
}
if (mipmap.data.byteLength !== expectedLayerByteLength) {
throw new Error(
`Failed to decode the KTX2 file: layer ${mipmap.layerIndex} of the base mip level holds ${mipmap.data.byteLength} bytes but ${expectedLayerByteLength} were expected.`
);
}
}
// A 2D array texture is uploaded as a single buffer holding every layer back to back.
const textureData = new Uint8Array(expectedLayerByteLength * layerCount);
let byteOffset = 0;
for (const mipmap of baseLevel) {
textureData.set(mipmap.data!, byteOffset);
View on GitHub (pinned to 0592b347b8)
Solutions
- Re-export or re-encode the KTX2 file with a reliable tool (e.g. toktx / gltf-transform) and verify all array layers are populated.
- Check the KTX2 file is fully downloaded and not truncated (compare Content-Length / byte size).
- Update @babylonjs/core and the KTX2 transcoder (basis transcoder) to matching versions so all layers decode.
- If the file is valid, report the failing encoder settings to the Babylon.js team with the file.
Example fix
// before: loading a suspect asset directly
const tex = await CreateTexture2DArrayFromKTX2Async(url, scene);
// after: pre-validate the asset (e.g. with a tool) and catch decode errors
try {
const tex = await CreateTexture2DArrayFromKTX2Async(validatedUrl, scene);
} catch (e) {
console.error("KTX2 asset invalid, re-export it:", e);
} Defensive patterns
Strategy: validation
Validate before calling
// Validate a KTX2 file before loading: ensure non-zero size and trusted source
const res = await fetch(ktx2Url);
if (!res.ok) throw new Error("asset missing");
const buf = await res.arrayBuffer();
if (buf.byteLength < 100) throw new Error("KTX2 file truncated");
// then load from the validated buffer via the supported API Type guard
function hasLayerData(m: { layerIndex: number; data: ArrayBuffer | null | undefined }): m is { layerIndex: number; data: ArrayBuffer } {
return m.data != null && m.data.byteLength > 0;
} Try / catch
try {
const tex = await CreateTexture2DArrayFromKTX2Async(url, scene);
} catch (e) {
console.error("Invalid KTX2 asset, replacing with fallback:", e);
texture = fallbackTexture;
} Prevention
- Re-export KTX2 assets with maintained tooling (toktx/gltf-transform) and add them to CI validation.
- Checksum downloadable assets to catch truncation.
- Keep the basis/KTX2 transcoder WASM in lockstep with @babylonjs/core versions.
When it happens
Trigger: Calling CreateTexture2DArrayFromKTX2Async with a KTX2 file whose base mip level contains an array layer with no data (mipmap.data is null/undefined).
Common situations: Corrupt or truncated KTX2 downloads, KTX2 files saved by tooling that leaves layers empty, unsupported supercompression/transcoder paths that skip layers, hand-edited KTX2 containers.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Failed to decode the KTX2 file: layer ${mipmap.layerIndex} o
- Failed to decode the KTX2 file: expected ${layerCount} layer
- Failed to decode the KTX2 file: layer ${mipmap.layerIndex} o
- All images must share the same dimensions. Image at index ${
- Failed to fetch KTX2 file "${data}": ${response.status} ${re
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/92420a81910c311a.
Report an issue: GitHub.