{"record":{"id":"92420a81910c311a","repo":"BabylonJS/Babylon.js","slug":"failed-to-decode-the-ktx2-file-layer-mipmap-lay","errorCode":null,"errorMessage":"Failed to decode the KTX2 file: layer ${mipmap.layerIndex} of the base mip level is empty.","messagePattern":"Failed to decode the KTX2 file: layer (.+?) of the base mip level is empty\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/dev/core/src/Materials/Textures/rawTexture2DArray.functions.ts","lineNumber":180,"sourceCode":"\r\n    const layerCount = Math.max(decodedData.layerCount ?? 1, 1);\r\n    // mipmaps are ordered level by level, and within a level layer by layer, so the base level occupies\r\n    // the first layerCount entries and its layers are already in ascending order.\r\n    const baseLevel = decodedData.mipmaps.slice(0, layerCount);\r\n\r\n    if (baseLevel.length !== layerCount) {\r\n        throw new Error(`Failed to decode the KTX2 file: expected ${layerCount} layers for the base mip level but got ${baseLevel.length}.`);\r\n    }\r\n\r\n    // Every layer of a level shares the level's dimensions, and forceRGBA above means each one must be exactly\r\n    // width * height * 4 bytes. RawTexture2DArray takes all the layers as one flat buffer and cannot detect a\r\n    // short or mismatched layer, so validate here rather than uploading malformed data.\r\n    const { width, height } = baseLevel[0];\r\n    const expectedLayerByteLength = width * height * 4;\r\n\r\n    for (const mipmap of baseLevel) {\r\n        if (!mipmap.data) {\r\n            throw new Error(`Failed to decode the KTX2 file: layer ${mipmap.layerIndex} of the base mip level is empty.`);\r\n        }\r\n        if (mipmap.width !== width || mipmap.height !== height) {\r\n            throw new Error(\r\n                `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}.`\r\n            );\r\n        }\r\n        if (mipmap.data.byteLength !== expectedLayerByteLength) {\r\n            throw new Error(\r\n                `Failed to decode the KTX2 file: layer ${mipmap.layerIndex} of the base mip level holds ${mipmap.data.byteLength} bytes but ${expectedLayerByteLength} were expected.`\r\n            );\r\n        }\r\n    }\r\n\r\n    // A 2D array texture is uploaded as a single buffer holding every layer back to back.\r\n    const textureData = new Uint8Array(expectedLayerByteLength * layerCount);\r\n    let byteOffset = 0;\r\n    for (const mipmap of baseLevel) {\r\n        textureData.set(mipmap.data!, byteOffset);\r","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/core/src/Materials/Textures/rawTexture2DArray.functions.ts#L162-L198","documentation":"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.","triggerScenarios":"Calling CreateTexture2DArrayFromKTX2Async with a KTX2 file whose base mip level contains an array layer with no data (mipmap.data is null/undefined).","commonSituations":"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.","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."],"exampleFix":"// before: loading a suspect asset directly\nconst tex = await CreateTexture2DArrayFromKTX2Async(url, scene);\n// after: pre-validate the asset (e.g. with a tool) and catch decode errors\ntry {\n  const tex = await CreateTexture2DArrayFromKTX2Async(validatedUrl, scene);\n} catch (e) {\n  console.error(\"KTX2 asset invalid, re-export it:\", e);\n}","handlingStrategy":"validation","validationCode":"// Validate a KTX2 file before loading: ensure non-zero size and trusted source\nconst res = await fetch(ktx2Url);\nif (!res.ok) throw new Error(\"asset missing\");\nconst buf = await res.arrayBuffer();\nif (buf.byteLength < 100) throw new Error(\"KTX2 file truncated\");\n// then load from the validated buffer via the supported API","typeGuard":"function hasLayerData(m: { layerIndex: number; data: ArrayBuffer | null | undefined }): m is { layerIndex: number; data: ArrayBuffer } {\n  return m.data != null && m.data.byteLength > 0;\n}","tryCatchPattern":"try {\n  const tex = await CreateTexture2DArrayFromKTX2Async(url, scene);\n} catch (e) {\n  console.error(\"Invalid KTX2 asset, replacing with fallback:\", e);\n  texture = fallbackTexture;\n}","preventionTips":["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."],"tags":["ktx2","texture-array","asset-validation","gpu-upload"],"backgroundTag":"ktx2-invalid-layer-data","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}