How to inject `@babylonjs/ktx2decoder` into Babylon?

According to the source code, Babylon’s KhronosTextureContainer2 automatically fetches ktx2decoderfrom the CDN (https://cdn.babylonjs.com/babylon.ktx2Decoder.js). However, I’d like to locally host all files and avoid any fetches from outside sources.

(I was on a plane recently and found out mid-flight that Babylon was failing to fetch ktx2decoderfrom the CDN, so I wasn’t able to work :sad_but_relieved_face: . I should have caught this earlier by turning off Wi-Fi to see if my Babylon app could load. I just started using gltfpack on some .glb files, which I just realized activates Babylon’s KTX 2.0 decoder.

Now I’m trying to figure out how to make Babylon use local ktx2decoderJS and WASM files (instead of CDN) to avoid this issue in the future.)

I see the npm package @babylonjs/ktx2decoder. Is there a simple way to use this npm package to load ktx2decoderand then inject this into Babylon (so that it uses the one loaded from npm instead of fetching from the CDN)?

Something along the lines of:

import * as KTX2Decoder from '@babylonjs/ktx2decoder';

globalThis.KTX2DECODER = KTX2Decoder;

// Is there an option to inject `KTX2DECODER` (and its WASM files) into Babylon?

I see another thread and documentation on using @babylonjs/ktx2decoder, however they all seem to be writing some new setup code instead of simply injecting the loaded ktx2decoderinto Babylon.

I really don’t want to be writing new code here, since Babylon’s existing KhronosTextureContainer2works great (but relies on CDN). Can we just make it use the JS and WASM files from the npm package (maybe an option to use an injection)?

Thank you all for your help! :smiley:

Check out this thread of solving similar issue with draco encoder:

Maybe that approach works with krx as well.

1 Like

Thank you for sharing, @qwiglydee ! Unfortunately, ktx2decoder doesn’t have a DefaultConfigurationlike Draco does. And I’d like to avoid Webpack configs or any change to the bundling process

I hope that the Babylon team would know if there exists (or if it’s possible) to have a one-line option to tell ktx2decoderto use local JS and WASM files from @babylonjs/ktx2decoder?

@RaananW thank you for helping on the previous thread! I was wondering if you would have an idea here? :smiley:

Let me add @alexchuber to have a look.

1 Like

Doing the following allows me to run fully locally without fetching from the CDN.

Instead of using @babylonjs/ktx2decoder, I just downloaded the JS and WASM files once from the CDN and told my code to use these local files:


Download Files

https://cdn.babylonjs.com/babylon.ktx2Decoder.js
https://cdn.babylonjs.com/ktx2Transcoders/1/msc_basis_transcoder.js
https://cdn.babylonjs.com/ktx2Transcoders/1/msc_basis_transcoder.wasm

main.js

import { initializeWebWorker } from '@babylonjs/core/Misc/khronosTextureContainer2Worker.js';


const wasmMSCTranscoder = await (await fetch('/lib/msc_basis_transcoder.wasm')).arrayBuffer();

BABYLON.KhronosTextureContainer2.WorkerPool = new BABYLON.AutoReleaseWorkerPool(4, () => {
	const worker = new Worker(new URL('./worker.js', import.meta.url), {
		// type: 'module', // commented out to use `importScripts()`
	});

	return initializeWebWorker(worker, {
		wasmMSCTranscoder,
	});
});

worker.js

import { workerFunction } from '@babylonjs/core/Misc/khronosTextureContainer2Worker.js';


importScripts('/lib/babylon.ktx2Decoder.js'); // sets `globalThis.KTX2DECODER`
importScripts('/lib/msc_basis_transcoder.js'); // sets `globalThis.MSC_TRANSCODER`

/*
	`temp` workaround is needed due to a likely bug in Babylon where `MSC_TRANSCODER` is expected to be undefined initially:
	- https://github.com/BabylonJS/Babylon.js/blob/master/packages/tools/ktx2Decoder/src/Transcoders/mscTranscoder.ts#L58
*/
const temp = MSC_TRANSCODER;
// @ts-expect-error
globalThis.MSC_TRANSCODER = undefined;

KTX2DECODER.MSCTranscoder.JSModule = temp;

workerFunction(KTX2DECODER);

Potential Bug in Babylon

In worker.js, I had to do a hacky workaround of temporarily unsetting globalThis.MSC_TRANSCODER.

I believe that typeof MSC_TRANSCODER === “undefined”in this line in the source code should be removed?

if (MSCTranscoder.JSModule /* && typeof MSC_TRANSCODER === "undefined" */) {
	// ...
}
2 Likes

Good questions! Here’s a braindump from what I remember about all this…


If the only goal is to avoid using the CDN, the simplest option is to rely on URLConfig. Just grab the bundled UMD files from the CDN, host them yourself (which you’ve already done :slight_smile: ), and point Babylon to your copies:

const baseUrl = "/lib";
KhronosTextureContainer2.URLConfig = {
    // Copy of https://cdn.babylonjs.com/babylon.ktx2Decoder.js
    jsDecoderModule: baseUrl + "/babylon.ktx2Decoder.js", 
    // Copy of https://cdn.babylonjs.com/ktx2Transcoders/1/msc_basis_transcoder.js 
    jsMSCTranscoder: baseUrl + "/ktx2Transcoders/1/msc_basis_transcoder.js",
    // Copy of https://cdn.babylonjs.com/ktx2Transcoders/1/msc_basis_transcoder.wasm
    wasmMSCTranscoder: baseUrl + "/ktx2Transcoders/1/msc_basis_transcoder.wasm",
};

More info: CDN Babylon.js Packages.

Do note, though, that the URLConfig approach won’t really work with @babylonjs/ktx2decoder. As the post you linked shows (“How do I use @babylonjs/ktx2decoder properly?”), you may run into this:

import ktx2DecoderUrl from '@babylonjs/ktx2decoder/ktx2Decoder.js?url';
import uastcAstcWasmUrl from "@babylonjs/ktx2decoder/wasm/uastc_astc.wasm?url";

KhronosTextureContainer2.URLConfig = {
    jsDecoderModule: ktx2DecoderUrl, // <- Errors
    wasmUASTCToASTC: uastcAstcWasmUrl; // OK
};

The issue is that the decoder URL ultimately gets loaded through LoadScript(), which expects a self‑contained module, and @babylonjs/ktx2decoder/ktx2Decoder.js alone isn’t structured that way.

You may have better luck pointing the URLs of the JS modules to their UMD counterparts over in the babylonjs-ktx2decoders NPM package, but I haven’t tested this.


There’s also the worker-based approach like you’ve taken, which is the most robust but complex option. It was originally added to support strict CSP environments, so it leans fully into supplying your own assets, including the worker itself. Because you’re providing the worker, you can import the decoder module however you need before injecting it. This avoids the aforementioned limitations of the URLConfig approach.


Normally there’d be a middle-ground solution-- like simple module injection without a worker, which you mentioned-- but that doesn’t exist yet. Some of the internal scaffolding is already there (like someone pointed out in the same thread), but the feature hasn’t been built out.

1 Like

Nice catch on the MSC_TRANSCODER bug! Do you want to open a PR for it? :slight_smile:

1 Like

Thank you so much for all of your help, @alexchuber !

I’ve created a PR: Fix: Remove enforced undefined for MSC_TRANSCODER by regnaio · Pull Request #17751 · BabylonJS/Babylon.js · GitHub


Also I was wondering if you could please help with something related:

The code above (and official documentation) use initializeWebWorker()and workerFunction()from @babylonjs/core/Misc/khronosTextureContainer2Worker.js:

import { initializeWebWorker } from '@babylonjs/core/Misc/khronosTextureContainer2Worker.js';

// ...

import { workerFunction } from '@babylonjs/core/Misc/khronosTextureContainer2Worker.js';

Is it possible for the global BABYLONobject to contain these 2 functions? I currently just use a local copy of the CDN babylon.js and would like to avoid importing @babylonjs/core just to use 2 functions.

Thank you so much!

Hey guys, my team is having issues with KTX2 textures since this change. We noticed it happening… as of this commit?

The issue can be demonstrated between these two GLB files when previewed in the Babylon Sandbox. The first one is using textures that are not KTX2 compressed, which is looking as expected:

And the same model with KTX2 texture compression, where it looks like… the normals are distorted or inside out? I’m not sure what’s going on here.

My team’s codebase has had no changes since this update, but when I was looking through the offsite dependencies, this one came up, and the HTTP headers responded that last-modified was Thu, 29 Jan 2026 18:48:06 GMT.

Here are the two GLBs, if that helps you work through the issue. Is there anything more I can do to help pinpoint, diagnose, or resolve the issue?

https://aroundar.sfo3.cdn.digitaloceanspaces.com/public/football_field_raw.glb

https://aroundar.sfo3.cdn.digitaloceanspaces.com/public/football_field_ktx2.glb

That’s not good… :grimacing:

Let me take a look. Just in case, cc @Evgeni_Popov for when he’s back.

I ll revert it and do a new release in a couple hours

We ll need a different fix for it

@regna you might need to find a different way :frowning:

1 Like

Ahh sorry about this :sad_but_relieved_face: I’m still confused how this could have caused the issue

@AdmiralPotato could you please let us know if the revert fixes your issue?

Bad news: It’s still not fixed. Now I’m left worrying whether I’ve been barking up the wrong tree, with which commit was actually the problem. Have any other changes been deployed to the CDN for Babylon’s GLTF parsing tools recently? I’m sorry for not being more familiar with the ecosystem or release patterns, but what I can say is that it was working before 11:00 MT this morning, then around 12:30 MT, every developer on my team that refreshed was effected by this issue.

I guess the other part that demonstrates that at least it wasn’t in our codebase is the fact that it’s happening in the Babylon Sandbox as well, and the last time the ktx2 version of this field was changed was months ago. Does anyone know how to inspect the ktx2 version of the model and see if perhaps it’s some other part of the way that file was compressed that might have been effected by a recent CDN code change?

Hmm the recent timing is definitely worrying :sad_but_relieved_face: Still can’t rule out if my fix attempt above caused this..


I tried running gltfpack on your football_field_raw.glbfile, which I believe should compress textures with KTX2:

gltfpack -i ~/Downloads/football_field_raw.glb -o ~/Downloads/football_field_raw2.glb -cc -tc -ac -ke -kn -vpf

You can find the resulting file football_field_raw2.glbattached here.

I dragged and dropped football_field_raw2.glbinto the Sandbox, and it seemed to load fine.

Maybe gltfpackcould work for you and your team? Though I’m still worried why things are no longer working for your flow as of yesterday..

You can try to find the latest working version by adding version=X.Y.Z to the url. For eg:

More of a follow up to AdmiralPotato’s problems than the original topic (maybe we should make a new post), but I’ve also been having trouble with ktx2 files the last day. They worked completely fine 24 hours ago, but 20 or so hours ago we started receiving reports of textures looking weird. I’ve pushed no updates to the projects within the last 48 hours, and haven’t updated Babylon since version 8.26.1.

Now, every project I have displays most* textures consisting of ktx2 files as completely black and shiny. For reference, these textures are loaded in dynamically, not baked into the models.

Using the URLConfig setup explained earlier in this thread I undid the changes to ktx2decoder in the following commit locally, which seemed to fix the problem.

*Not all files seem to be affected, could be the difference between using etc1s and uastc compression?

1 Like

cc @sebavan

Ohhh seems like i definitely broke it … could you share a broken file here so that i ll reintroduce the msc fix an address the regression.

Ps: I hate TS breaking changes, and also how bad I am to address them…