src/viewer/scene/utils/textureTranscoders/KTX2TextureTranscoder/KTX2TextureTranscoder.js
import {FileLoader} from "../../FileLoader.js";
import {WorkerPool} from "../../WorkerPool.js";
import {
LinearEncoding,
LinearFilter,
LinearMipmapLinearFilter,
RGB_ETC1_Format,
RGB_ETC2_Format,
RGB_PVRTC_4BPPV1_Format,
RGB_S3TC_DXT1_Format,
RGBA_ASTC_4x4_Format,
RGBA_BPTC_Format,
RGBA_ETC2_EAC_Format,
RGBA_PVRTC_4BPPV1_Format,
RGBA_S3TC_DXT5_Format,
RGBAFormat,
sRGBEncoding
} from "../../../constants/constants.js";
const KTX2TransferSRGB = 2;
const KTX2_ALPHA_PREMULTIPLIED = 1;
let activeTranscoders = 0;
/**
* Transcodes texture data from KTX2.
*
* ## Overview
*
* * Uses the [Basis Universal GPU Texture Codec](https://github.com/BinomialLLC/basis_universal) to
* transcode [KTX2](https://github.khronos.org/KTX-Specification/) textures.
* * {@link XKTLoaderPlugin} uses a KTX2TextureTranscoder to load textures in XKT files.
* * {@link VBOSceneModel} uses a KTX2TextureTranscoder to enable us to add KTX2-encoded textures.
* * Loads the Basis Codec from [CDN](https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/) by default, but can
* also be configured to load the Codec from local files.
* * We also bundle the Basis Codec with the xeokit-sdk npm package, and in the [repository](https://github.com/xeokit/xeokit-sdk/tree/master/dist/basis).
*
* ## What is KTX2?
*
* A [KTX2](https://github.khronos.org/KTX-Specification/) file stores GPU texture data in the Khronos Texture 2.0 (KTX2) container format. It contains image data for
* a texture asset compressed with Basis Universal (BasisU) supercompression that can be transcoded to different formats
* depending on the support provided by the target devices. KTX2 provides a lightweight format for distributing texture
* assets to GPUs. Due to BasisU compression, KTX2 files can store any image format supported by GPUs.
*
* ## Loading XKT files containing KTX2 textures
*
* {@link XKTLoaderPlugin} uses a KTX2TextureTranscoder to load textures in XKT files. An XKTLoaderPlugin has its own
* default KTX2TextureTranscoder, configured to load the Basis Codec from the [CDN](https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/). If we wish, we can override that with our own
* KTX2TextureTranscoder, configured to load the Codec locally.
*
* In the example below, we'll create a {@link Viewer} and add an {@link XKTLoaderPlugin}
* configured with a KTX2TextureTranscoder. Then we'll use the XKTLoaderPlugin to load an
* XKT file that contains KTX2 textures, which the plugin will transcode using
* its KTX2TextureTranscoder.
*
* We'll configure our KTX2TextureTranscoder to load the Basis Codec from a local directory. If we were happy with loading the
* Codec from our [CDN](https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/) (ie. our app will always have an Internet connection) then we could just leave out the
* KTX2TextureTranscoder altogether, and let the XKTLoaderPlugin use its internal default KTX2TextureTranscoder, which is configured to
* load the Codec from the CDN. We'll stick with loading our own Codec, in case we want to run our app without an Internet connection.
*
* <a href="https://xeokit.github.io/xeokit-sdk/examples/buildings/#xkt_vbo_textures_HousePlan"><img src="https://xeokit.github.io/xeokit-sdk/assets/images/xktWithTextures.png"></a>
*
* * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/buildings/#xkt_vbo_textures_HousePlan)]
*
* ````javascript
* const viewer = new Viewer({
* canvasId: "myCanvas",
* transparent: true
* });
*
* viewer.camera.eye = [-2.56, 8.38, 8.27];
* viewer.camera.look = [13.44, 3.31, -14.83];
* viewer.camera.up = [0.10, 0.98, -0.14];
*
* const textureTranscoder = new KTX2TextureTranscoder({
* viewer,
* transcoderPath: "https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/" // <------ Path to Basis Universal transcoder
* });
*
* const xktLoader = new XKTLoaderPlugin(viewer, {
* textureTranscoder // <<------------- Transcodes KTX2 textures in XKT files
* });
*
* const sceneModel = xktLoader.load({
* id: "myModel",
* src: "./HousePlan.xkt" // <<------ XKT file with KTX2 textures
* });
* ````
*
* ## Loading KTX2 files into a VBOSceneModel
*
* A {@link SceneModel} that is configured with a KTX2TextureTranscoder will
* allow us to load textures into it from KTX2-transcoded buffers or files.
*
* In the example below, we'll create a {@link Viewer}, containing a {@link VBOSceneModel} configured with a
* KTX2TextureTranscoder.
*
* We'll then programmatically create a simple object within the VBOSceneModel, consisting of
* a single box mesh with a texture loaded from a KTX2 file, which our VBOSceneModel internally transcodes, using
* its KTX2TextureTranscoder.
*
* As in the previous example, we'll configure our KTX2TextureTranscoder to load the Basis Codec from a local directory.
*
* * [Run a similar example](https://xeokit.github.io/xeokit-sdk/examples/scenemodel/#vbo_batching_autocompressed_triangles_textures_ktx2)
*
* ````javascript
* const viewer = new Viewer({
* canvasId: "myCanvas",
* transparent: true
* });
*
* viewer.scene.camera.eye = [-21.80, 4.01, 6.56];
* viewer.scene.camera.look = [0, -5.75, 0];
* viewer.scene.camera.up = [0.37, 0.91, -0.11];
*
* const textureTranscoder = new KTX2TextureTranscoder({
* viewer,
* transcoderPath: "https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/" // <------ Path to BasisU transcoder module
* });
*
* const vboSceneModel = new VBOSceneModel(viewer.scene, {
* id: "myModel",
* textureTranscoder // <<-------------------- Configure model with our transcoder
* });
*
* vboSceneModel.createTexture({
* id: "myColorTexture",
* src: "../assets/textures/compressed/sample_uastc_zstd.ktx2" // <<----- KTX2 texture asset
* });
*
* vboSceneModel.createTexture({
* id: "myMetallicRoughnessTexture",
* src: "../assets/textures/alpha/crosshatchAlphaMap.jpg" // <<----- JPEG texture asset
* });
*
* vboSceneModel.createTextureSet({
* id: "myTextureSet",
* colorTextureId: "myColorTexture",
* metallicRoughnessTextureId: "myMetallicRoughnessTexture"
* });
*
* vboSceneModel.createMesh({
* id: "myMesh",
* textureSetId: "myTextureSet",
* primitive: "triangles",
* positions: [1, 1, 1, ...],
* normals: [0, 0, 1, 0, ...],
* uv: [1, 0, 0, ...],
* indices: [0, 1, 2, ...],
* });
*
* vboSceneModel.createEntity({
* id: "myEntity",
* meshIds: ["myMesh"]
* });
*
* vboSceneModel.finalize();
* ````
*
* ## Loading KTX2 ArrayBuffers into a VBOSceneModel
*
* A {@link SceneModel} that is configured with a KTX2TextureTranscoder will also allow us to load textures into
* it from KTX2 ArrayBuffers.
*
* In the example below, we'll create a {@link Viewer}, containing a {@link VBOSceneModel} configured with a
* KTX2TextureTranscoder.
*
* We'll then programmatically create a simple object within the VBOSceneModel, consisting of
* a single mesh with a texture loaded from a KTX2 ArrayBuffer, which our VBOSceneModel internally transcodes, using
* its KTX2TextureTranscoder.
*
* ````javascript
* const viewer = new Viewer({
* canvasId: "myCanvas",
* transparent: true
* });
*
* viewer.scene.camera.eye = [-21.80, 4.01, 6.56];
* viewer.scene.camera.look = [0, -5.75, 0];
* viewer.scene.camera.up = [0.37, 0.91, -0.11];
*
* const textureTranscoder = new KTX2TextureTranscoder({
* viewer,
* transcoderPath: "https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/" // <------ Path to BasisU transcoder module
* });
*
* const vboSceneModel = new VBOSceneModel(viewer.scene, {
* id: "myModel",
* textureTranscoder // <<-------------------- Configure model with our transcoder
* });
*
* utils.loadArraybuffer("../assets/textures/compressed/sample_uastc_zstd.ktx2",(arrayBuffer) => {
*
* vboSceneModel.createTexture({
* id: "myColorTexture",
* buffers: [arrayBuffer] // <<----- KTX2 texture asset
* });
*
* vboSceneModel.createTexture({
* id: "myMetallicRoughnessTexture",
* src: "../assets/textures/alpha/crosshatchAlphaMap.jpg" // <<----- JPEG texture asset
* });
*
* vboSceneModel.createTextureSet({
* id: "myTextureSet",
* colorTextureId: "myColorTexture",
* metallicRoughnessTextureId: "myMetallicRoughnessTexture"
* });
*
* vboSceneModel.createMesh({
* id: "myMesh",
* textureSetId: "myTextureSet",
* primitive: "triangles",
* positions: [1, 1, 1, ...],
* normals: [0, 0, 1, 0, ...],
* uv: [1, 0, 0, ...],
* indices: [0, 1, 2, ...],
* });
*
* vboSceneModel.createEntity({
* id: "myEntity",
* meshIds: ["myMesh"]
* });
*
* vboSceneModel.finalize();
* });
* ````
*
* @implements {TextureTranscoder}
*/
class KTX2TextureTranscoder {
/**
* Creates a new KTX2TextureTranscoder.
*
* @param {Viewer} viewer The Viewer that our KTX2TextureTranscoder will be used with. This KTX2TextureTranscoder
* must only be used to transcode textures for this Viewer. This is because the Viewer's capabilities will decide
* what target GPU formats this KTX2TextureTranscoder will transcode to.
* @param {String} [transcoderPath="https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/"] Path to the Basis
* transcoder module that internally does the heavy lifting for our KTX2TextureTranscoder. If we omit this configuration,
* then our KTX2TextureTranscoder will load it from ````https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/```` by
* default. Therefore, make sure your application is connected to the internet if you wish to use the default transcoder path.
* @param {Number} [workerLimit] The maximum number of Workers to use for transcoding.
*/
constructor({viewer, transcoderPath, workerLimit}) {
this._transcoderPath = transcoderPath || "https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/";
this._transcoderBinary = null;
this._transcoderPending = null;
this._workerPool = new WorkerPool();
this._workerSourceURL = '';
if (workerLimit) {
this._workerPool.setWorkerLimit(workerLimit);
}
const viewerCapabilities = viewer.capabilities;
this._workerConfig = {
astcSupported: viewerCapabilities.astcSupported,
etc1Supported: viewerCapabilities.etc1Supported,
etc2Supported: viewerCapabilities.etc2Supported,
dxtSupported: viewerCapabilities.dxtSupported,
bptcSupported: viewerCapabilities.bptcSupported,
pvrtcSupported: viewerCapabilities.pvrtcSupported
};
this._supportedFileTypes = ["xkt2"];
}
_init() {
if (!this._transcoderPending) {
const jsLoader = new FileLoader();
jsLoader.setPath(this._transcoderPath);
jsLoader.setWithCredentials(this.withCredentials);
const jsContent = jsLoader.loadAsync('basis_transcoder.js');
const binaryLoader = new FileLoader();
binaryLoader.setPath(this._transcoderPath);
binaryLoader.setResponseType('arraybuffer');
binaryLoader.setWithCredentials(this.withCredentials);
const binaryContent = binaryLoader.loadAsync('basis_transcoder.wasm');
this._transcoderPending = Promise.all([jsContent, binaryContent])
.then(([jsContent, binaryContent]) => {
const fn = KTX2TextureTranscoder.BasisWorker.toString();
const body = [
'/* constants */',
'let _EngineFormat = ' + JSON.stringify(KTX2TextureTranscoder.EngineFormat),
'let _TranscoderFormat = ' + JSON.stringify(KTX2TextureTranscoder.TranscoderFormat),
'let _BasisFormat = ' + JSON.stringify(KTX2TextureTranscoder.BasisFormat),
'/* basis_transcoder.js */',
jsContent,
'/* worker */',
fn.substring(fn.indexOf('{') + 1, fn.lastIndexOf('}'))
].join('\n');
this._workerSourceURL = URL.createObjectURL(new Blob([body]));
this._transcoderBinary = binaryContent;
this._workerPool.setWorkerCreator(() => {
const worker = new Worker(this._workerSourceURL);
const transcoderBinary = this._transcoderBinary.slice(0);
worker.postMessage({
type: 'init',
config: this._workerConfig,
transcoderBinary
}, [transcoderBinary]);
return worker;
});
});
if (activeTranscoders > 0) {
console.warn('KTX2TextureTranscoder: Multiple active KTX2TextureTranscoder may cause performance issues.' + ' Use a single KTX2TextureTranscoder instance, or call .dispose() on old instances.');
}
activeTranscoders++;
}
return this._transcoderPending;
}
/**
* Transcodes texture data from transcoded buffers into a {@link Texture2D}.
*
* @param {ArrayBuffer[]} buffers Transcoded texture data. Given as an array of buffers so that we can support multi-image textures, such as cube maps.
* @param {*} config Transcoding options.
* @param {Texture2D} texture The texture to load.
* @returns {Promise} Resolves when the texture has loaded.
*/
transcode(buffers, texture, config = {}) {
return new Promise((resolve, reject) => {
const taskConfig = config;
this._init().then(() => {
return this._workerPool.postMessage({
type: 'transcode',
buffers,
taskConfig: taskConfig
}, buffers);
}).then((e) => {
const transcodeResult = e.data;
const {mipmaps, width, height, format, type, error, dfdTransferFn, dfdFlags} = transcodeResult;
if (type === 'error') {
return reject(error);
}
texture.setCompressedData({
mipmaps,
props: {
format: format,
minFilter: mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter,
magFilter: mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter,
encoding: dfdTransferFn === KTX2TransferSRGB ? sRGBEncoding : LinearEncoding,
premultiplyAlpha: !!(dfdFlags & KTX2_ALPHA_PREMULTIPLIED)
}
});
resolve()
});
});
}
/**
* Destroys this KTX2TextureTranscoder
*/
destroy() {
URL.revokeObjectURL(this._workerSourceURL);
this._workerPool.destroy();
activeTranscoders--;
}
}
/**
* @private
*/
KTX2TextureTranscoder.BasisFormat = {
ETC1S: 0,
UASTC_4x4: 1
};
/**
* @private
*/
KTX2TextureTranscoder.TranscoderFormat = {
ETC1: 0,
ETC2: 1,
BC1: 2,
BC3: 3,
BC4: 4,
BC5: 5,
BC7_M6_OPAQUE_ONLY: 6,
BC7_M5: 7,
PVRTC1_4_RGB: 8,
PVRTC1_4_RGBA: 9,
ASTC_4x4: 10,
ATC_RGB: 11,
ATC_RGBA_INTERPOLATED_ALPHA: 12,
RGBA32: 13,
RGB565: 14,
BGR565: 15,
RGBA4444: 16
};
/**
* @private
*/
KTX2TextureTranscoder.EngineFormat = {
RGBAFormat: RGBAFormat,
RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format,
RGBA_BPTC_Format: RGBA_BPTC_Format,
RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format,
RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format,
RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format,
RGB_ETC1_Format: RGB_ETC1_Format,
RGB_ETC2_Format: RGB_ETC2_Format,
RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format,
RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format
};
/* WEB WORKER */
/**
* @private
* @constructor
*/
KTX2TextureTranscoder.BasisWorker = function () {
let config;
let transcoderPending;
let BasisModule;
const EngineFormat = _EngineFormat; // eslint-disable-line no-undef
const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef
const BasisFormat = _BasisFormat; // eslint-disable-line no-undef
self.addEventListener('message', function (e) {
const message = e.data;
switch (message.type) {
case 'init':
config = message.config;
init(message.transcoderBinary);
break;
case 'transcode':
transcoderPending.then(() => {
try {
const {
width,
height,
hasAlpha,
mipmaps,
format,
dfdTransferFn,
dfdFlags
} = transcode(message.buffers[0]);
const buffers = [];
for (let i = 0; i < mipmaps.length; ++i) {
buffers.push(mipmaps[i].data.buffer);
}
self.postMessage({
type: 'transcode',
id: message.id,
width,
height,
hasAlpha,
mipmaps,
format,
dfdTransferFn,
dfdFlags
}, buffers);
} catch (error) {
console.error(`[KTX2TextureTranscoder.BasisWorker]: ${error}`);
self.postMessage({type: 'error', id: message.id, error: error.message});
}
});
break;
}
});
function init(wasmBinary) {
transcoderPending = new Promise(resolve => {
BasisModule = {
wasmBinary,
onRuntimeInitialized: resolve
};
BASIS(BasisModule); // eslint-disable-line no-undef
}).then(() => {
BasisModule.initializeBasis();
if (BasisModule.KTX2File === undefined) {
console.warn('KTX2TextureTranscoder: Please update Basis Universal transcoder.');
}
});
}
function transcode(buffer) {
const ktx2File = new BasisModule.KTX2File(new Uint8Array(buffer));
function cleanup() {
ktx2File.close();
ktx2File.delete();
}
if (!ktx2File.isValid()) {
cleanup();
throw new Error('KTX2TextureTranscoder: Invalid or unsupported .ktx2 file');
}
const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;
const width = ktx2File.getWidth();
const height = ktx2File.getHeight();
const levels = ktx2File.getLevels();
const hasAlpha = ktx2File.getHasAlpha();
const dfdTransferFn = ktx2File.getDFDTransferFunc();
const dfdFlags = ktx2File.getDFDFlags();
const {transcoderFormat, engineFormat} = getTranscoderFormat(basisFormat, width, height, hasAlpha);
if (!width || !height || !levels) {
cleanup();
throw new Error('KTX2TextureTranscoder: Invalid texture');
}
if (!ktx2File.startTranscoding()) {
cleanup();
throw new Error('KTX2TextureTranscoder: .startTranscoding failed');
}
const mipmaps = [];
for (let mip = 0; mip < levels; mip++) {
const levelInfo = ktx2File.getImageLevelInfo(mip, 0, 0);
const mipWidth = levelInfo.origWidth;
const mipHeight = levelInfo.origHeight;
const dst = new Uint8Array(ktx2File.getImageTranscodedSizeInBytes(mip, 0, 0, transcoderFormat));
const status = ktx2File.transcodeImage(dst, mip, 0, 0, transcoderFormat, 0, -1, -1);
if (!status) {
cleanup();
throw new Error('KTX2TextureTranscoder: .transcodeImage failed.');
}
mipmaps.push({data: dst, width: mipWidth, height: mipHeight});
}
cleanup();
return {width, height, hasAlpha, mipmaps, format: engineFormat, dfdTransferFn, dfdFlags};
}
// Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC),
// device capabilities, and texture dimensions. The list below ranks the formats separately
// for ETC1S and UASTC.
//
// In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at
// significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently
// chooses RGBA32 only as a last resort and does not expose that option to the caller.
const FORMAT_OPTIONS = [{
if: 'astcSupported',
basisFormat: [BasisFormat.UASTC_4x4],
transcoderFormat: [TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4],
engineFormat: [EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format],
priorityETC1S: Infinity,
priorityUASTC: 1,
needsPowerOfTwo: false
}, {
if: 'bptcSupported',
basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],
transcoderFormat: [TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5],
engineFormat: [EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format],
priorityETC1S: 3,
priorityUASTC: 2,
needsPowerOfTwo: false
}, {
if: 'dxtSupported',
basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],
transcoderFormat: [TranscoderFormat.BC1, TranscoderFormat.BC3],
engineFormat: [EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format],
priorityETC1S: 4,
priorityUASTC: 5,
needsPowerOfTwo: false
}, {
if: 'etc2Supported',
basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],
transcoderFormat: [TranscoderFormat.ETC1, TranscoderFormat.ETC2],
engineFormat: [EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format],
priorityETC1S: 1,
priorityUASTC: 3,
needsPowerOfTwo: false
}, {
if: 'etc1Supported',
basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],
transcoderFormat: [TranscoderFormat.ETC1],
engineFormat: [EngineFormat.RGB_ETC1_Format],
priorityETC1S: 2,
priorityUASTC: 4,
needsPowerOfTwo: false
}, {
if: 'pvrtcSupported',
basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],
transcoderFormat: [TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA],
engineFormat: [EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format],
priorityETC1S: 5,
priorityUASTC: 6,
needsPowerOfTwo: true
}];
const ETC1S_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) {
return a.priorityETC1S - b.priorityETC1S;
});
const UASTC_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) {
return a.priorityUASTC - b.priorityUASTC;
});
function getTranscoderFormat(basisFormat, width, height, hasAlpha) {
let transcoderFormat;
let engineFormat;
const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;
for (let i = 0; i < options.length; i++) {
const opt = options[i];
if (!config[opt.if]) continue;
if (!opt.basisFormat.includes(basisFormat)) continue;
if (hasAlpha && opt.transcoderFormat.length < 2) continue;
if (opt.needsPowerOfTwo && !(isPowerOfTwo(width) && isPowerOfTwo(height))) continue;
transcoderFormat = opt.transcoderFormat[hasAlpha ? 1 : 0];
engineFormat = opt.engineFormat[hasAlpha ? 1 : 0];
return {
transcoderFormat,
engineFormat
};
}
console.warn('KTX2TextureTranscoder: No suitable compressed texture format found. Decoding to RGBA32.');
transcoderFormat = TranscoderFormat.RGBA32;
engineFormat = EngineFormat.RGBAFormat;
return {
transcoderFormat,
engineFormat
};
}
function isPowerOfTwo(value) {
if (value <= 2) return true;
return (value & value - 1) === 0 && value !== 0;
}
};
const cachedTranscoders = {};
/**
* Returns a new {@link KTX2TextureTranscoder}.
*
* The ````transcoderPath```` config will be set to: "https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/"
*
* @private
*/
function getKTX2TextureTranscoder(viewer) {
const sceneId = viewer.scene.id;
let transcoder = cachedTranscoders[sceneId];
if (!transcoder) {
transcoder = new KTX2TextureTranscoder({viewer});
cachedTranscoders[sceneId] = transcoder;
viewer.scene.on("destroyed", () => {
delete cachedTranscoders[sceneId];
transcoder.destroy();
});
}
return transcoder;
}
export {getKTX2TextureTranscoder, KTX2TextureTranscoder};