- Warning
- Deprecated. Use the container independent transcoder from the Binomial LLC repo instead: https://github.com/BinomialLLC/basis_universal. This JS wrapper was designed to use an underlying C++ API that accepted image info in a structure. The API actually added to basis_universal uses explicit parameters so users of this transcoder will be packing the info into a struct from which it will be immediately unpacked before the underlying transcoder is called.
WebIDL for the binding
void initTranscoders();
bool isFormatSupported(TranscodeTarget targetFormat, TextureFormat texFormat);
interface BasisTranscoderState {
void BasisTranscoderState();
};
interface TranscodedImage {
ArrayBufferView get_typed_memory_view();
};
interface TranscodeResult {
TranscodedImage transcodedImage;
};
interface BasisLzEtc1sImageTranscoder {
void BasisLzEtc1sImageTranscoder();
uint32_t getBytesPerBlock(TranscodeTarget format);
bool decode_palettes(uint32_t num_endpoints,
const Uint8Array endpoints,
uint32_t num_selectors,
const Uint8Array selectors);
bool decode_tables(const Uint8Array tableData);
TranscodeResult transcode_image(
TranscodeTarget targetFormat,
const Uint8Array jsInSlices,
ImageInfo imageInfo,
uint32_t decodeFlags = 0,
bool isVideo = false);
};
interface BasisUastcImageTranscoder {
void BasisUastcImageTranscoder();
uint32_t getBytesPerBlock(const TranscodeTarget format);
TranscodeResult transcode_image(
TranscodeTarget targetFormat,
const Uint8Array jsInImage,
basisu_image_desc& imageDesc,
uint32_t decodeFlags = 0,
bool hasAlpha = false,
bool isVideo = false);
interface ImageInfo = {
ImageInfo(TextureFormat texFormat, uint32_t width, uint32_t height,
uint32_t level);
attribute uint32_t flags;
attribute long rgbByteOffset;
attribute long rgbByteLength;
attribute long alphaByteOffset;
attribute long alphaByteLength;
attribute uint32_t width;
attribute uint32_t height;
attribute uint32_t numBlocksX;
attribute uint32_t numBlocksY;
attribute uint32_t level;
};
// Some targets may not be available depending on options used when compiling
// the web assembly.
enum TranscodeTarget = {
"ETC1_RGB",
"BC1_RGB",
"BC4_R",
"BC5_RG",
"BC3_RGBA",
"PVRTC1_4_RGB",
"PVRTC1_4_RGBA",
"BC7_RGBA",
"BC7_M6_RGB", //Deprecated. Use BC7_RGBA.
"BC7_M5_RGBA", //Deprecated. Use BC7_RGBA.
"ETC2_RGBA",
"ASTC_4x4_RGBA",
"RGBA32",
"RGB565",
"BGR565",
"RGBA4444",
"PVRTC2_4_RGB",
"PVRTC2_4_RGBA",
"EAC_R11",
"EAC_RG11"
};
enum TextureFormat = {
"ETC1S",
"UASTC4x4",
};
enum TranscodeFlagBits =
"TRANSCODE_ALPHA_DATA_TO_OPAQUE_FORMATS",
"HIGH_QUALITY",
};
enum TranscodeFlagBits = {
"TRANSCODE_ALPHA_DATA_TO_OPAQUE_FORMATS",
"HIGH_QUALITY"
};
How to use
Put msc_basis_transcoder.js and msc_basis_transcoder.wasm in a directory on your server. Create a script tag with msc_basis_tranacoder.js as the src
as shown below, changing the path as necessary for the relative locations of your .html file and the script source. msc_basis_transcoder.js will automatically load msc_basis_transcoder.wasm.
Create an instance of the MSC_TRANSCODER module
For example, add this to the .html file to initialize the transcoder and make it available on the main window.
<script src="msc_transcoder_wrapper.js"></script>
<script type="text/javascript">
MSC_TRANSCODER().then(module => {
window.MSC_TRANSCODER = module;
module.initTranscoders();
// Call a function to begin loading or transcoding..
</script>
After the module is initialized, invoke code that will directly or indirectly cause a function with code like the following to be executed.
Somewhere in the loader/transcoder
Assume a KTX file is fetched via an XMLHttpRequest which deposits the data into a Uint8Array, "buData"...
- Note
- The names of the data items used in the following code are those from the KTX2 specification but the actual data is not specific to that container format.
const {
BasisLzEtc1sImageTranscoder,
BasisUastcImageTranscoder,
TranscodeTarget
} = MSC_TRANSCODER;
// Determine from the KTX2 header information in buData if
// the data format is BasisU or Uastc.
// supercompressionScheme value == 1, it's TextureFormat.ETC1S.
// DFD colorModel == 166, it's TextureFormat.UASTC4x4.
const texFormat = ...
// Determine appropriate transcode format from available targets,
// info about the texture, e.g. texture.numComponents, and
// expected use. Use values from TranscodeTarget.
const targetFormat = ...
if ( !MSC_TRANSCODER.isFormatSupported(targetFormat, texFormat) {
throw new Error( ... );
}
if (TextureFormat.UASTC4x4) {
var result = transcodeUastc(targetFormat);
} else {
var result = transcodeEtc1s(targetFormat);
}
if ( result.transcodedImage === undefined ) {
throw new Error( 'Unable to transcode image.' );
}
This is the function for transcoding etc1s.
transcodeEtc1s(targetFormat) {
// Locate the supercompression global data and compresssed
// mip level data within buData.
var bit = new BasisLzEtc1sImageTranscoder();
// Find the index of the starts of the endpoints, selectors and tables
// data within buData...
var endpointsStart = ...
var selectorsStart = ...
var tablesStart = ...
// The numbers of endpoints & selectors and their byteLengths are items
// within buData. They are in the header of a .ktx2 file's
// supercompressionGlobalData and in the header of a .basis file.
var endpoints = new Uint8Array(buData, endpointsStart,
endpointsByteLength);
var selectors = new Uint8Array(buData, selectorsStart,
selectorsByteLength);
bit.decodePalettes(numEndpoints, endpoints,
numSelectors, selectors);
var tables = new UInt8Array(buData, tablesStart, tablesByteLength);
bit.decodeTables(tables);
// Determine if the file contains a video sequence...
var isVideo = ...
// Calculate the total number of images in the data
var numImages = ...
// Set up a subarray pointing at the deflated image descriptions
// in buData. This is for .ktx2 containers. The image descriptions
// are located in supercompressionGlobalData. .basis containers will
// require different code to locate the slice descriptions within
// the file.
var imageDescsStart = ...:
// An imageDesc has 5 uint32 values.
var imageDescs = new Uint32Data(buData, imageDescsStart,
numImages * 5 * 4);
var curImageIndex = 0;
// Pseudo code for processing the levels of a .ktx2 container...
foreach level {
var leveWidth = width of image at this level
var levelHeight = height of image at this level
imageInfo = new ImageInfo(TextureFormat::ETC1S, levelWidth, levelHeight,
level);
foreach image in level {
// In KTX2 container locate the imageDesc for this image.
var imageDesc = imageDescs[curImageIndex++];
imageInfo.flags = imageDesc.imageFlags;
imageInfo.rgbByteOffset = 0;
imageInfo.rgbByteLength = imageDesc.rgbSliceByteLength;
imageInfo.alphaByteOffset = imageDesc.alphaSliceByteOffset > 0 ? imageDesc.rgbSliceByteLength : 0;
imageInfo.alphaByteLength = imageDesc.alphaSliceByteLength;
// Determine the location in the ArrayBuffer of the start
// of the deflated data for level.
var levelOffset = ...
// Make a .subarray of the rgb slice data.
var levelData = new Uint8Array(
buData,
levelOffset + imageDesc.rgbSliceByteOffset,
imageDesc.rgbSliceByteLength + imageDesc.alphaByteLength
);
var result = bit.transcodeImage(
targetFormat,
levelData,
imageInfo,
0,
isVideo);
if ( result.transcodedImage === undefined ) {
throw new Error( ... );
}
let imgData = transcodedImage.get_typed_memory_view();
// Upload data in imgData to WebGL...
// Do not call delete() until data has been uploaded
// or otherwise copied.
transcodedImage.delete();
}
}
// For .basis containers, it is necessary to locate the slice
// description(s) for the image and set the values in imageInfo
// from them. Use of the .basis-specific transcoder is recommended.
// The definition of the basis_slice_desc struct makes it difficult
// to create JS interface for it with embind.
This is the function for transcoding Uastc.
transcodeUastc(targetFormat) {
var uit = new UastcImageTranscoder();
// Determine if the data is supercompressed.
var zstd = (supercompressionScheme == 2);
// Determine if the data has alpha.
var hasAlpha = (Channel ID of sample[0] in DFD == 1);
var dctx;
if (zstd) {
// Initialize the zstd decoder. Zstd JS wrapper + wasm is
// a separate package.
dctx = ZSTD_createDCtx();
}
// Pseudo code for processing the levels of a .ktx2 container...
foreach level {
// Determine the location in the ArrayBuffer buData of the
// start of the deflated data for the level.
var levelData = ...
if (zstd) {
// Inflate the level data
levelData = ZSTD_decompressDCtx(dctx, levelData, ... );
}
var levelWidth = width of image at this level
var levelHeight = height of image at this level
var depth = depth of texture at this level
var levelImageCount = number of layers * number of faces * depth;
var imageOffsetInLevel = 0;
var imageInfo = new ImageInfo(TextureFormat::UASTC4x4,
levelWidth, levelHeight, level);
var levelImageByteLength = imageInfo.numBlocksX * imageInfo.numBlocksY * DFD bytesPlane0;
foreach image in level {
inImage = Uint8Array(levelData, imageOffsetInLevel, levelImageByteLength);
imageInfo.flags = 0;
imageInfo.rgbByteOffset = 0;
imageInfo.rgbByteLength = levelImageByteLength;
imageInfo.alphaByteOffset = 0;
imageInfo.alphaByteLength = 0;
const {transcodedImage} = uit.transcodeImage(
targetFormat,
inImage,
imageInfo,
0,
hasAlpha,
isVideo);
if ( result.transcodedImage === undefined ) {
throw new Error( ... );
}
let imgData = transcodedImage.get_typed_memory_view();
// Upload data in imgData to WebGL...
// Do not call delete() until data has been uploaded
// or otherwise copied.
transcodedImage.delete();
imageOffsetInLevel += levelImageByteLength;
}
}
// For .basis containers, as with ETC1S, it is necessary to locate
// the slice description for the image and set the values in imageInfo
// from it.
}