@bitruvius/ktx2
v0.3.1
Published
KTX2/Basis texture transcoder seam for @bitruvius/gltf: transcode-only Basis Universal wasm (KHR_texture_basisu), vendored and hardened
Readme
@bitruvius/ktx2
The Basis Universal transcoder behind one injectable interface, so no parser in the SDK has to know KTX2 exists.
Internal building block. This package exists so that
@bitruvius/sdk-maplibreand the Bitruvius codecs can resolve their dependencies on npm. It has no standalone product story. Unless you are deliberately building against it, install the SDK instead.
It transcodes a KHR_texture_basisu KTX2 container (ETC1S or UASTC) into a GPU
texture format the renderer can upload, satisfying the Ktx2Transcoder contract
declared in @bitruvius/geo-core.
Why it is its own package
The contract lives in @bitruvius/geo-core. The wasm that satisfies it lives
here. That split is the reason the package exists.
Two decode paths need it, and neither may depend on the other.
@bitruvius/gltf meets KHR_texture_basisu on the 3D Tiles side.
@bitruvius/i3s meets the same textures on the I3S side, where 1.8+ services
exist that publish KTX2 and nothing else. If either parser imported Basis
directly, the other would have to duplicate the binary or take a dependency on a
parser it has no business knowing about. A separate package that depends only on
@bitruvius/geo-core closes both: no cycle, one copy of the wasm, and both
parsers stay codec-agnostic.
The payload is real. basis_transcoder.wasm is about 515 KiB. Content that
carries no KHR_texture_basisu should never pay for it, so the transcoder is
injected rather than imported, and the wasm is fetched lazily on the first
texture that actually asks for one.
The licence boundary is real too. The wasm is Binomial's Basis Universal transcoder under Apache-2.0, vendored transcode-only: the compressor is absent, and a publish-time check re-verifies that no encoder entry point crept back in. Keeping it in one package keeps the redistribution boundary, the licence texts and the attribution in one place, separate from the Bitruvius licence.
What is in it
| Export | What it does |
|---|---|
| Ktx2BasisTranscoder | The seam implementation. transcode(bytes, target), with lazy wasm init on the first call. |
| transcodeKtx2(module, bytes, target) | The pure path, for when you already hold an initialized Basis module. |
| ensureBasis(init?) | Instantiates and initializes the wasm once, then returns the shared module. A failed init clears the cache so the next call retries. |
| basisWasmUrl() | The best absolute URL for the wasm, for handing to a Web Worker, which cannot resolve a package-sibling asset itself. undefined when neither a configured base nor the packaged sibling resolves. |
Targets are named by a backend-neutral hint: rgba32 is the universal fallback
that uploads everywhere, and bc7, bc3, bc1, etc2, astc4x4 and pvrtc
are the matching GPU-compressed block formats, chosen per device by the
renderer. The whole mip chain is transcoded, one bulk fill per level straight
into a right-sized buffer, with no intermediate copies.
Wire it into the glTF decoder:
import { Ktx2BasisTranscoder } from '@bitruvius/ktx2';
import { GltfMeshDecoder } from '@bitruvius/gltf';
const decoder = new GltfMeshDecoder({ ktx2: new Ktx2BasisTranscoder() });Locating the wasm
The transcoder resolves its binary in precedence order: explicit wasmBytes,
then an explicit wasmUrl, then the SDK-configured CDN base, then the packaged
sibling asset. Point it at an explicit URL when a bundler or a cross-origin
worker cannot resolve the sibling for you.
import { configure } from '@bitruvius/geo-core';
configure({ wasmBaseUrl: 'https://cdn.example.com/bitruvius/wasm/' });
const ktx2 = new Ktx2BasisTranscoder({
wasmInit: { wasmUrl: 'https://cdn.example.com/bitruvius/wasm/basis_transcoder.wasm' },
});The module is a per-realm singleton, so the first call decides how it was
located. The raw asset is also reachable at the ./wasm/* subpath export if you
need to serve or preload it yourself.
Who should depend on it
Inside the SDK: @bitruvius/tiles3d-mesh, @bitruvius/i3s and
@bitruvius/sdk-maplibre, all of which construct one and pass it to a decoder.
@bitruvius/gltf deliberately does not: it declares the seam and never imports
the codec.
Outside the SDK, depend on it only if you are driving GltfMeshDecoder or the
I3S mesh decoder yourself and you need KHR_texture_basisu support. If you are
putting 3D content on a map, install
@bitruvius/sdk-maplibre,
which wires this in for you.
Third-party attribution
The wasm is Binomial's Basis Universal transcoder (basis_transcoder.wasm),
Apache-2.0, vendored transcode-only (no compressor). See
wasm/NOTICES, which ships in the published package alongside
the full Apache-2.0 text.
The Bitruvius LICENSE restricts redistribution and reverse engineering of the
SDK. Those restrictions do not reach the vendored Basis transcoder: it is
licensed to you directly by its copyright holder under Apache-2.0, and where the
two appear to conflict as applied to that file, the upstream licence governs.
Trademarks
Khronos, KTX and glTF are trademarks of The Khronos Group Inc. Basis Universal is a product of Binomial LLC. Esri, ArcGIS and I3S are trademarks of Environmental Systems Research Institute, Inc. Cesium and 3D Tiles are trademarks of Cesium GS, Inc. MapLibre is a trademark of the MapLibre organization. All other marks are the property of their respective owners.
These names are used solely to describe the data formats this software interoperates with. Bitruvius is not affiliated with, sponsored by, or endorsed by any of them, and no such relationship is implied.
License
Proprietary. The full terms ship as LICENSE inside this package, and are readable
before installing at cdn.bitruvius.com/legal/sdk-license-v1.txt.
© Bitruvius, Inc.
