@h00w/basis-universal-transcoder
v2.0.2
Published
WebAssembly-based Basis Universal texture transcoder with TypeScript bindings
Maintainers
Readme
@h00w/basis-universal-transcoder
A WebAssembly-based Basis Universal texture transcoder with TypeScript bindings.
Note: This library requires explicit WASM loading to prevent automatic WASM inlining during bundling, which could cause issues when users compile this library in their own projects.
Features
- 🚀 High-performance WebAssembly implementation
- 📦 TypeScript support with full type definitions
- 🎯 Support for KTX2 files only
- 🔧 Multiple output formats (BC1-7, ASTC, PVRTC, ETC, uncompressed)
- 🌐 Works in both browser and Node.js environments
- ⚡ Built with Vite
Installation
npm install @h00w/basis-universal-transcoderQuick Start
Basic Usage
import { BasisUniversal } from '@h00w/basis-universal-transcoder';
import wasmUrl from '@h00w/basis-universal-transcoder/basis_capi_transcoder.wasm';
// Helper function to create WASM instantiator
function createWasmInstantiator(wasmUrl: string) {
return async (imports: WebAssembly.Imports) => {
const fetchPromise = fetch(wasmUrl);
// Try streaming instantiation first (better performance)
if (WebAssembly.instantiateStreaming) {
try {
return await WebAssembly.instantiateStreaming(fetchPromise, imports);
} catch (e) {
console.warn('Streaming instantiation failed, falling back to ArrayBuffer method:', e);
}
}
// Fallback to manual compilation
const response = await fetchPromise;
const buffer = await response.arrayBuffer();
return WebAssembly.instantiate(buffer, imports);
};
}
// Initialize the module
const basisUniversal = await BasisUniversal.getInstance(createWasmInstantiator(wasmUrl));
// Load a KTX2 file
const fileData = await fetch('texture.ktx2').then(r => r.arrayBuffer());
const ktx2Data = new Uint8Array(fileData);
// Create a KTX2 transcoder
const ktx2Transcoder = basisUniversal.createKTX2Transcoder();
// Initialize with KTX2 data
if (ktx2Transcoder.init(ktx2Data) && ktx2Transcoder.startTranscoding()) {
// Transcode to RGBA32 format
const result = ktx2Transcoder.transcodeImageLevel({
format: TranscoderTextureFormat.cTFRGBA32,
level: 0 // mip level 0 (full size)
});
if (result) {
console.log('Transcoded:', result.width, 'x', result.height);
console.log('Data size:', result.data.length, 'bytes');
// Use the transcoded data...
displayTexture(result.data, result.width, result.height);
}
}
// Clean up
ktx2Transcoder.dispose();Format Detection
import { detectBestFormat, isFormatSupported, getFormatName } from '@h00w/basis-universal-transcoder';
// Detect the best format for the current platform (requires WebGL context)
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const bestFormat = detectBestFormat(gl);
console.log('Best format:', getFormatName(bestFormat));
// Check if a specific format is supported
if (isFormatSupported(TranscoderTextureFormat.cTFBC7_RGBA)) {
console.log('BC7 is supported on this platform');
}Advanced Usage
import { BasisUniversal, KTX2Transcoder, TranscoderTextureFormat } from '@h00w/basis-universal-transcoder';
import wasmUrl from '@h00w/basis-universal-transcoder/basis_capi_transcoder.wasm';
// Use the createWasmInstantiator helper function from the basic usage example
const transcoder = await BasisUniversal.getInstance(createWasmInstantiator(wasmUrl));
const ktx2Transcoder = transcoder.createKTX2Transcoder();
ktx2Transcoder.init(data);
// Get basis texture format
const basisFormat = ktx2Transcoder.getBasisTextureFormat();
console.log('Basis format:', basisFormat);
// Transcode multiple mip levels
const results = [];
ktx2Transcoder.startTranscoding();
for (let level = 0; level < 4; level++) { // Adjust based on your texture
const result = ktx2Transcoder.transcodeImageLevel({
format: TranscoderTextureFormat.cTFBC7_RGBA,
level: level
});
if (result) {
results.push(result);
}
}
ktx2Transcoder.dispose();⚠️ Important: Memory Management
Data Persistence Warning
Critical: The TranscodeResult.data returned by transcodeImageLevel() references WASM-managed memory and will become invalid after the next call to transcodeImageLevel().
If you need to persist the transcoded data, you must create a copy:
const result = ktx2Transcoder.transcodeImageLevel({
format: TranscoderTextureFormat.cTFBC7_RGBA,
level: 0
});
if (result) {
// ❌ WRONG: This data will become invalid after next transcodeImageLevel() call
const imageData = result.data;
// ✅ CORRECT: Create a copy to persist the data
const persistentData = new Uint8Array(result.data);
// or
const persistentData = result.data.slice();
}Performance Optimization
Tip: Reuse the same KTX2Transcoder instance for multiple textures by calling init() multiple times. This reduces memory allocation overhead:
import { BasisUniversal, TranscoderTextureFormat } from '@h00w/basis-universal-transcoder';
import wasmUrl from '@h00w/basis-universal-transcoder/basis_capi_transcoder.wasm';
// Use the createWasmInstantiator helper function from the basic usage example
const transcoder = await BasisUniversal.getInstance(createWasmInstantiator(wasmUrl));
const ktx2Transcoder = transcoder.createKTX2Transcoder();
// Process multiple textures efficiently
async function processTextures(textureDataArray: Uint8Array[]) {
for (const textureData of textureDataArray) {
// Reuse the same transcoder instance
if (ktx2Transcoder.init(textureData)) {
ktx2Transcoder.startTranscoding();
const result = ktx2Transcoder.transcodeImageLevel({
format: TranscoderTextureFormat.cTFBC7_RGBA,
level: 0
});
if (result) {
// Create copy if you need to persist the data
const persistentData = new Uint8Array(result.data);
// Process the texture data...
}
}
}
}
// Don't forget to dispose when done
ktx2Transcoder.dispose();API Reference
Classes
BasisUniversal
Main class for managing the transcoder module.
static getInstance(instantiateWasmAsync: InstantiateWasmAsync): Promise<BasisUniversal>createKTX2Transcoder(): KTX2Transcoder
KTX2Transcoder
Handles KTX2 file transcoding. Can be reused for multiple textures by calling init() multiple times for better performance.
init(data: Uint8Array): boolean- Initialize with KTX2 file data (can be called multiple times)getBasisTextureFormat(): BasisTextureFormat- Get the basis texture formatstartTranscoding(): boolean- Start transcoding (call after init)transcodeImageLevel(options: TranscodeOptions): TranscodeResult | null- Transcode a specific leveldispose(): void- Clean up resources
Enums
TranscoderTextureFormat
Supported output texture formats:
cTFBC1_RGB- BC1 RGB (DXT1)cTFBC3_RGBA- BC3 RGBA (DXT5)cTFBC7_RGBA- BC7 RGBAcTFASTC_4x4_RGBA- ASTC 4x4 RGBAcTFPVRTC1_4_RGBA- PVRTC1 4bpp RGBAcTFRGBA32- 32-bit RGBA uncompressed- And more...
Type Definitions
type InstantiateWasmAsync = (imports: WebAssembly.Imports) => Promise<WebAssembly.WebAssemblyInstantiatedSource>;Direct WASM Access
You can also directly import and use the WASM file for custom loading scenarios:
// Import WASM file directly
import wasmUrl from '@h00w/basis-universal-transcoder/basis_capi_transcoder.wasm';
// Custom WASM loading with your own instantiator
const basisUniversal = await BasisUniversal.getInstance(createWasmInstantiator(wasmUrl));Utility Functions
detectBestFormat(gl: WebGLRenderingContext): TranscoderTextureFormat- Detect best format for WebGL contextisFormatSupported(format: TranscoderTextureFormat): boolean- Check if format is supported on current platformgetFormatName(format: TranscoderTextureFormat): string- Get human-readable format name
Development
Building from Source
# Install dependencies
npm install
# Build WASM module (requires Emscripten)
../../scripts/build-wasm.sh
# Build everything (WASM + package)
../../scripts/build-all.sh
# Start development server
npm run devDemo
The package includes a comprehensive demo that shows how to use the transcoder:
npm run devThen open your browser to see the interactive demo.
Browser Support
- Modern browsers with WebAssembly support
- Chrome 57+, Firefox 52+, Safari 11+, Edge 16+
Node.js Support
- Node.js 16+ with WebAssembly support
License
MIT
Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
Credits
This package is built on top of the excellent Basis Universal library by Binomial LLC.
