/* eslint-disable no-useless-computed-key,camelcase */
import extensionChooser from '../extensionChooser';
const BASIS_FORMAT = {
cTFETC1: 0, // not support alpha
// cTFETC2: 1, // not WebGL
cTFBC1: 2, // not support alpha
cTFBC3: 3,
// cTFBC4: 4, // not WebGL
// cTFBC5: 5, // not WebGL
// cTFBC7_M6_OPAQUE_ONLY: 6 // not WebGL
// cTFBC7_M5 : 7 // not WebGL
cTFPVRTC1_4_RGB: 8, // not support alpha
cTFPVRTC1_4_RGBA: 9,
cTFASTC_4x4: 10, // mobile alpha! Ehooo
// cTFATC_RGB : 11 // not WebGL
// cTFATC_RGBA_INTERPOLATED_ALPHA : 12 not WebGL
cTFRGBA32: 11, // why not?
};
const BASIS_HAS_ALPHA = {
[3]: true,
[9]: true,
[10]: true,
[11]: true,
};
const NON_COMPRESSED = -1;
const COMPRESSED_RGB_ETC1_WEBGL = 0x8D64;
const COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
// const COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
// const COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
const COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
const COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00;
const COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02;
const COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;
const BASIS_TO_FMT = {
// fallback
[BASIS_FORMAT.cTFRGBA32]: NON_COMPRESSED,
[BASIS_FORMAT.cTFETC1]: COMPRESSED_RGB_ETC1_WEBGL,
[BASIS_FORMAT.cTFBC1]: COMPRESSED_RGB_S3TC_DXT1_EXT,
[BASIS_FORMAT.cTFBC3]: COMPRESSED_RGBA_S3TC_DXT5_EXT,
[BASIS_FORMAT.cTFPVRTC1_4_RGB]: COMPRESSED_RGB_PVRTC_4BPPV1_IMG,
[BASIS_FORMAT.cTFPVRTC1_4_RGBA]: COMPRESSED_RGBA_PVRTC_4BPPV1_IMG,
[BASIS_FORMAT.cTFASTC_4x4]: COMPRESSED_RGBA_ASTC_4x4_KHR,
};
const FMT_TO_BASIS = Object.keys(BASIS_TO_FMT).reduce((acc, next) => {
acc[BASIS_TO_FMT[+next]] = +next;
return acc;
}, {});
/**
*
* @example
* // 初始化压缩纹理插件
* Tiny.plugins.compressedTexture.init(app.renderer);
* // 此 BASIS 方法是 basis_transcoder.js 提供的 wasm 模块
* BASIS().then((Module) => {
* const { BasisFile, initializeBasis } = Module;
*
* // run module
* initializeBasis();
* // 执行绑定
* Tiny.plugins.compressedTexture.BASISLoader.bindTranscoder(BasisFile);
*
* const loader = new Tiny.loaders.Loader();
*
* loader
* .add({
* name: 'xxx',
* url: './res/xxx.png',
* // 设置 metadata.useFormat 为 basis,告诉加载器明确只使用 BASIS 纹理
* metadata: { useCompressedTexture: true, useFormat: 'basis' },
* })
* .load((loaderInstance, resources) => {
* });
* });
*
* @memberof Tiny.plugins.compressedTexture
*/
class BASISLoader {
constructor(image) {
this._image = image;
}
load(buffer) {
this._loadAsync(buffer);
return this._image;
}
_loadAsync(buffer) {
// const startTime = performance.now();
const BasisFileCtr = BASISLoader.BASIS_BINDING;
const basisFile = new BasisFileCtr(new Uint8Array(buffer));
const width = basisFile.getImageWidth(0, 0);
const height = basisFile.getImageHeight(0, 0);
// const images = await basisFile.getNumImages(); // not support yet
const levels = 1; //await basisFile.getNumLevels( 0 ); // not support yet
const hasAlpha = basisFile.getHasAlpha();
const dest = this._image;
if (!basisFile.startTranscoding()) {
throw new Error('[BASISLoader] Transcoding error!');
}
const target = hasAlpha ? BASISLoader.RGBA_FORMAT : BASISLoader.RGB_FORMAT;
// console.log('[BASISLoader] Grats! BASIS will be transcoded to:', target);
const dst = new Uint8Array(basisFile.getImageTranscodedSizeInBytes(0, 0, target.basis));
if (!basisFile.transcodeImage(dst, 0, 0, target.basis, !!0, !!0)) {
throw new Error('[BASISLoader] Transcoding error!');
}
// console.log('[BASISLoader] Totla transcoding time:', performance.now() - startTime);
const name = target.name.replace('COMPRESSED_', '');
dest._getLevelSize = (level) => {
return basisFile.getImageTranscodedSizeInBytes(0, level, FMT_TO_BASIS[target.native]);
};
return Promise.resolve(dest.init(dest.src, dst, 'BASIS|' + name, width, height, levels, target.native));
}
}
BASISLoader.BASIS_BINDING = undefined;
BASISLoader.test = function(array) {
const header = new Uint32Array(array, 0, 1)[0];
const decoder = !!BASISLoader.BASIS_BINDING;
const isValid = header === 0x134273 && decoder;
const isSupported = BASISLoader.RGB_FORMAT && BASISLoader.RGBA_FORMAT;
if (!isValid && isSupported) {
console.warn('[BASISLoader] Is Supported, but transcoder not binded or file is not BASIS file!');
}
return (isSupported && isValid);
};
/**
* @static
* @param {function} fileCtr - BASIS transcoder 返回的绑定句柄
*/
BASISLoader.bindTranscoder = function(fileCtr) {
const ext = extensionChooser.compressedExtensions;
if (!fileCtr || !ext) {
throw new Error('Invalid state! undef fileCtr or ext invalid!');
};
// fetch list of ALL extensions
const plain = Object.keys(ext)
.reduce((acc, key) => {
const val = ext[key];
if (!val) {
return acc;
};
return Object.assign(acc, val.__proto__); // eslint-disable-line
}, {});
if (Object.keys(plain).length === 0) {
return;
}
let latestOp;
let lastestAlpha;
// select support
for (let v in plain) {
const native = plain[v];
if (FMT_TO_BASIS[native] !== undefined) {
const basis = FMT_TO_BASIS[native];
if (BASIS_HAS_ALPHA[basis]) {
lastestAlpha = {
native,
name: v,
basis,
};
} else {
latestOp = {
native,
name: v,
basis,
};
}
}
}
BASISLoader._useWorker = false;
BASISLoader.RGB_FORMAT = latestOp || lastestAlpha;
BASISLoader.RGBA_FORMAT = lastestAlpha || latestOp;
BASISLoader.BASIS_BINDING = fileCtr;
};
export default BASISLoader;