Reference Source

src/viewer/scene/utils/textureTranscoders/KTX2TextureTranscoder/KTX2TextureTranscoder.js

  1. import {FileLoader} from "../../FileLoader.js";
  2. import {WorkerPool} from "../../WorkerPool.js";
  3. import {
  4. LinearEncoding,
  5. LinearFilter,
  6. LinearMipmapLinearFilter,
  7. RGB_ETC1_Format,
  8. RGB_ETC2_Format,
  9. RGB_PVRTC_4BPPV1_Format,
  10. RGB_S3TC_DXT1_Format,
  11. RGBA_ASTC_4x4_Format,
  12. RGBA_BPTC_Format,
  13. RGBA_ETC2_EAC_Format,
  14. RGBA_PVRTC_4BPPV1_Format,
  15. RGBA_S3TC_DXT5_Format,
  16. RGBAFormat,
  17. sRGBEncoding
  18. } from "../../../constants/constants.js";
  19.  
  20. const KTX2TransferSRGB = 2;
  21. const KTX2_ALPHA_PREMULTIPLIED = 1;
  22.  
  23. let activeTranscoders = 0;
  24.  
  25. /**
  26. * Transcodes texture data from KTX2.
  27. *
  28. * ## Overview
  29. *
  30. * * Uses the [Basis Universal GPU Texture Codec](https://github.com/BinomialLLC/basis_universal) to
  31. * transcode [KTX2](https://github.khronos.org/KTX-Specification/) textures.
  32. * * {@link XKTLoaderPlugin} uses a KTX2TextureTranscoder to load textures in XKT files.
  33. * * {@link VBOSceneModel} uses a KTX2TextureTranscoder to enable us to add KTX2-encoded textures.
  34. * * Loads the Basis Codec from [CDN](https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/) by default, but can
  35. * also be configured to load the Codec from local files.
  36. * * 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).
  37. *
  38. * ## What is KTX2?
  39. *
  40. * 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
  41. * a texture asset compressed with Basis Universal (BasisU) supercompression that can be transcoded to different formats
  42. * depending on the support provided by the target devices. KTX2 provides a lightweight format for distributing texture
  43. * assets to GPUs. Due to BasisU compression, KTX2 files can store any image format supported by GPUs.
  44. *
  45. * ## Loading XKT files containing KTX2 textures
  46. *
  47. * {@link XKTLoaderPlugin} uses a KTX2TextureTranscoder to load textures in XKT files. An XKTLoaderPlugin has its own
  48. * 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
  49. * KTX2TextureTranscoder, configured to load the Codec locally.
  50. *
  51. * In the example below, we'll create a {@link Viewer} and add an {@link XKTLoaderPlugin}
  52. * configured with a KTX2TextureTranscoder. Then we'll use the XKTLoaderPlugin to load an
  53. * XKT file that contains KTX2 textures, which the plugin will transcode using
  54. * its KTX2TextureTranscoder.
  55. *
  56. * We'll configure our KTX2TextureTranscoder to load the Basis Codec from a local directory. If we were happy with loading the
  57. * 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
  58. * KTX2TextureTranscoder altogether, and let the XKTLoaderPlugin use its internal default KTX2TextureTranscoder, which is configured to
  59. * 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.
  60. *
  61. * <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>
  62. *
  63. * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/buildings/#xkt_vbo_textures_HousePlan)]
  64. *
  65. * ````javascript
  66. * const viewer = new Viewer({
  67. * canvasId: "myCanvas",
  68. * transparent: true
  69. * });
  70. *
  71. * viewer.camera.eye = [-2.56, 8.38, 8.27];
  72. * viewer.camera.look = [13.44, 3.31, -14.83];
  73. * viewer.camera.up = [0.10, 0.98, -0.14];
  74. *
  75. * const textureTranscoder = new KTX2TextureTranscoder({
  76. * viewer,
  77. * transcoderPath: "https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/" // <------ Path to Basis Universal transcoder
  78. * });
  79. *
  80. * const xktLoader = new XKTLoaderPlugin(viewer, {
  81. * textureTranscoder // <<------------- Transcodes KTX2 textures in XKT files
  82. * });
  83. *
  84. * const sceneModel = xktLoader.load({
  85. * id: "myModel",
  86. * src: "./HousePlan.xkt" // <<------ XKT file with KTX2 textures
  87. * });
  88. * ````
  89. *
  90. * ## Loading KTX2 files into a VBOSceneModel
  91. *
  92. * A {@link SceneModel} that is configured with a KTX2TextureTranscoder will
  93. * allow us to load textures into it from KTX2-transcoded buffers or files.
  94. *
  95. * In the example below, we'll create a {@link Viewer}, containing a {@link VBOSceneModel} configured with a
  96. * KTX2TextureTranscoder.
  97. *
  98. * We'll then programmatically create a simple object within the VBOSceneModel, consisting of
  99. * a single box mesh with a texture loaded from a KTX2 file, which our VBOSceneModel internally transcodes, using
  100. * its KTX2TextureTranscoder.
  101. *
  102. * As in the previous example, we'll configure our KTX2TextureTranscoder to load the Basis Codec from a local directory.
  103. *
  104. * * [Run a similar example](https://xeokit.github.io/xeokit-sdk/examples/scenemodel/#vbo_batching_autocompressed_triangles_textures_ktx2)
  105. *
  106. * ````javascript
  107. * const viewer = new Viewer({
  108. * canvasId: "myCanvas",
  109. * transparent: true
  110. * });
  111. *
  112. * viewer.scene.camera.eye = [-21.80, 4.01, 6.56];
  113. * viewer.scene.camera.look = [0, -5.75, 0];
  114. * viewer.scene.camera.up = [0.37, 0.91, -0.11];
  115. *
  116. * const textureTranscoder = new KTX2TextureTranscoder({
  117. * viewer,
  118. * transcoderPath: "https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/" // <------ Path to BasisU transcoder module
  119. * });
  120. *
  121. * const vboSceneModel = new VBOSceneModel(viewer.scene, {
  122. * id: "myModel",
  123. * textureTranscoder // <<-------------------- Configure model with our transcoder
  124. * });
  125. *
  126. * vboSceneModel.createTexture({
  127. * id: "myColorTexture",
  128. * src: "../assets/textures/compressed/sample_uastc_zstd.ktx2" // <<----- KTX2 texture asset
  129. * });
  130. *
  131. * vboSceneModel.createTexture({
  132. * id: "myMetallicRoughnessTexture",
  133. * src: "../assets/textures/alpha/crosshatchAlphaMap.jpg" // <<----- JPEG texture asset
  134. * });
  135. *
  136. * vboSceneModel.createTextureSet({
  137. * id: "myTextureSet",
  138. * colorTextureId: "myColorTexture",
  139. * metallicRoughnessTextureId: "myMetallicRoughnessTexture"
  140. * });
  141. *
  142. * vboSceneModel.createMesh({
  143. * id: "myMesh",
  144. * textureSetId: "myTextureSet",
  145. * primitive: "triangles",
  146. * positions: [1, 1, 1, ...],
  147. * normals: [0, 0, 1, 0, ...],
  148. * uv: [1, 0, 0, ...],
  149. * indices: [0, 1, 2, ...],
  150. * });
  151. *
  152. * vboSceneModel.createEntity({
  153. * id: "myEntity",
  154. * meshIds: ["myMesh"]
  155. * });
  156. *
  157. * vboSceneModel.finalize();
  158. * ````
  159. *
  160. * ## Loading KTX2 ArrayBuffers into a VBOSceneModel
  161. *
  162. * A {@link SceneModel} that is configured with a KTX2TextureTranscoder will also allow us to load textures into
  163. * it from KTX2 ArrayBuffers.
  164. *
  165. * In the example below, we'll create a {@link Viewer}, containing a {@link VBOSceneModel} configured with a
  166. * KTX2TextureTranscoder.
  167. *
  168. * We'll then programmatically create a simple object within the VBOSceneModel, consisting of
  169. * a single mesh with a texture loaded from a KTX2 ArrayBuffer, which our VBOSceneModel internally transcodes, using
  170. * its KTX2TextureTranscoder.
  171. *
  172. * ````javascript
  173. * const viewer = new Viewer({
  174. * canvasId: "myCanvas",
  175. * transparent: true
  176. * });
  177. *
  178. * viewer.scene.camera.eye = [-21.80, 4.01, 6.56];
  179. * viewer.scene.camera.look = [0, -5.75, 0];
  180. * viewer.scene.camera.up = [0.37, 0.91, -0.11];
  181. *
  182. * const textureTranscoder = new KTX2TextureTranscoder({
  183. * viewer,
  184. * transcoderPath: "https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/" // <------ Path to BasisU transcoder module
  185. * });
  186. *
  187. * const vboSceneModel = new VBOSceneModel(viewer.scene, {
  188. * id: "myModel",
  189. * textureTranscoder // <<-------------------- Configure model with our transcoder
  190. * });
  191. *
  192. * utils.loadArraybuffer("../assets/textures/compressed/sample_uastc_zstd.ktx2",(arrayBuffer) => {
  193. *
  194. * vboSceneModel.createTexture({
  195. * id: "myColorTexture",
  196. * buffers: [arrayBuffer] // <<----- KTX2 texture asset
  197. * });
  198. *
  199. * vboSceneModel.createTexture({
  200. * id: "myMetallicRoughnessTexture",
  201. * src: "../assets/textures/alpha/crosshatchAlphaMap.jpg" // <<----- JPEG texture asset
  202. * });
  203. *
  204. * vboSceneModel.createTextureSet({
  205. * id: "myTextureSet",
  206. * colorTextureId: "myColorTexture",
  207. * metallicRoughnessTextureId: "myMetallicRoughnessTexture"
  208. * });
  209. *
  210. * vboSceneModel.createMesh({
  211. * id: "myMesh",
  212. * textureSetId: "myTextureSet",
  213. * primitive: "triangles",
  214. * positions: [1, 1, 1, ...],
  215. * normals: [0, 0, 1, 0, ...],
  216. * uv: [1, 0, 0, ...],
  217. * indices: [0, 1, 2, ...],
  218. * });
  219. *
  220. * vboSceneModel.createEntity({
  221. * id: "myEntity",
  222. * meshIds: ["myMesh"]
  223. * });
  224. *
  225. * vboSceneModel.finalize();
  226. * });
  227. * ````
  228. *
  229. * @implements {TextureTranscoder}
  230. */
  231. class KTX2TextureTranscoder {
  232.  
  233. /**
  234. * Creates a new KTX2TextureTranscoder.
  235. *
  236. * @param {Viewer} viewer The Viewer that our KTX2TextureTranscoder will be used with. This KTX2TextureTranscoder
  237. * must only be used to transcode textures for this Viewer. This is because the Viewer's capabilities will decide
  238. * what target GPU formats this KTX2TextureTranscoder will transcode to.
  239. * @param {String} [transcoderPath="https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/"] Path to the Basis
  240. * transcoder module that internally does the heavy lifting for our KTX2TextureTranscoder. If we omit this configuration,
  241. * then our KTX2TextureTranscoder will load it from ````https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/```` by
  242. * default. Therefore, make sure your application is connected to the internet if you wish to use the default transcoder path.
  243. * @param {Number} [workerLimit] The maximum number of Workers to use for transcoding.
  244. */
  245. constructor({viewer, transcoderPath, workerLimit}) {
  246.  
  247. this._transcoderPath = transcoderPath || "https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/";
  248. this._transcoderBinary = null;
  249. this._transcoderPending = null;
  250. this._workerPool = new WorkerPool();
  251. this._workerSourceURL = '';
  252.  
  253. if (workerLimit) {
  254. this._workerPool.setWorkerLimit(workerLimit);
  255. }
  256.  
  257. const viewerCapabilities = viewer.capabilities;
  258.  
  259. this._workerConfig = {
  260. astcSupported: viewerCapabilities.astcSupported,
  261. etc1Supported: viewerCapabilities.etc1Supported,
  262. etc2Supported: viewerCapabilities.etc2Supported,
  263. dxtSupported: viewerCapabilities.dxtSupported,
  264. bptcSupported: viewerCapabilities.bptcSupported,
  265. pvrtcSupported: viewerCapabilities.pvrtcSupported
  266. };
  267.  
  268. this._supportedFileTypes = ["xkt2"];
  269. }
  270.  
  271. _init() {
  272. if (!this._transcoderPending) {
  273. const jsLoader = new FileLoader();
  274. jsLoader.setPath(this._transcoderPath);
  275. jsLoader.setWithCredentials(this.withCredentials);
  276. const jsContent = jsLoader.loadAsync('basis_transcoder.js');
  277. const binaryLoader = new FileLoader();
  278. binaryLoader.setPath(this._transcoderPath);
  279. binaryLoader.setResponseType('arraybuffer');
  280. binaryLoader.setWithCredentials(this.withCredentials);
  281. const binaryContent = binaryLoader.loadAsync('basis_transcoder.wasm');
  282. this._transcoderPending = Promise.all([jsContent, binaryContent])
  283. .then(([jsContent, binaryContent]) => {
  284. const fn = KTX2TextureTranscoder.BasisWorker.toString();
  285. const body = [
  286. '/* constants */',
  287. 'let _EngineFormat = ' + JSON.stringify(KTX2TextureTranscoder.EngineFormat),
  288. 'let _TranscoderFormat = ' + JSON.stringify(KTX2TextureTranscoder.TranscoderFormat),
  289. 'let _BasisFormat = ' + JSON.stringify(KTX2TextureTranscoder.BasisFormat),
  290. '/* basis_transcoder.js */',
  291. jsContent,
  292. '/* worker */',
  293. fn.substring(fn.indexOf('{') + 1, fn.lastIndexOf('}'))
  294. ].join('\n');
  295. this._workerSourceURL = URL.createObjectURL(new Blob([body]));
  296. this._transcoderBinary = binaryContent;
  297. this._workerPool.setWorkerCreator(() => {
  298. const worker = new Worker(this._workerSourceURL);
  299. const transcoderBinary = this._transcoderBinary.slice(0);
  300. worker.postMessage({
  301. type: 'init',
  302. config: this._workerConfig,
  303. transcoderBinary
  304. }, [transcoderBinary]);
  305. return worker;
  306. });
  307. });
  308. if (activeTranscoders > 0) {
  309. console.warn('KTX2TextureTranscoder: Multiple active KTX2TextureTranscoder may cause performance issues.' + ' Use a single KTX2TextureTranscoder instance, or call .dispose() on old instances.');
  310. }
  311. activeTranscoders++;
  312. }
  313. return this._transcoderPending;
  314. }
  315.  
  316. /**
  317. * Transcodes texture data from transcoded buffers into a {@link Texture2D}.
  318. *
  319. * @param {ArrayBuffer[]} buffers Transcoded texture data. Given as an array of buffers so that we can support multi-image textures, such as cube maps.
  320. * @param {*} config Transcoding options.
  321. * @param {Texture2D} texture The texture to load.
  322. * @returns {Promise} Resolves when the texture has loaded.
  323. */
  324. transcode(buffers, texture, config = {}) {
  325. return new Promise((resolve, reject) => {
  326. const taskConfig = config;
  327. this._init().then(() => {
  328. return this._workerPool.postMessage({
  329. type: 'transcode',
  330. buffers,
  331. taskConfig: taskConfig
  332. }, buffers);
  333. }).then((e) => {
  334. const transcodeResult = e.data;
  335. const {mipmaps, width, height, format, type, error, dfdTransferFn, dfdFlags} = transcodeResult;
  336. if (type === 'error') {
  337. return reject(error);
  338. }
  339. texture.setCompressedData({
  340. mipmaps,
  341. props: {
  342. format: format,
  343. minFilter: mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter,
  344. magFilter: mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter,
  345. encoding: dfdTransferFn === KTX2TransferSRGB ? sRGBEncoding : LinearEncoding,
  346. premultiplyAlpha: !!(dfdFlags & KTX2_ALPHA_PREMULTIPLIED)
  347. }
  348. });
  349. resolve()
  350. });
  351. });
  352. }
  353.  
  354. /**
  355. * Destroys this KTX2TextureTranscoder
  356. */
  357. destroy() {
  358. URL.revokeObjectURL(this._workerSourceURL);
  359. this._workerPool.destroy();
  360. activeTranscoders--;
  361. }
  362. }
  363.  
  364. /**
  365. * @private
  366. */
  367. KTX2TextureTranscoder.BasisFormat = {
  368. ETC1S: 0,
  369. UASTC_4x4: 1
  370. };
  371.  
  372. /**
  373. * @private
  374. */
  375. KTX2TextureTranscoder.TranscoderFormat = {
  376. ETC1: 0,
  377. ETC2: 1,
  378. BC1: 2,
  379. BC3: 3,
  380. BC4: 4,
  381. BC5: 5,
  382. BC7_M6_OPAQUE_ONLY: 6,
  383. BC7_M5: 7,
  384. PVRTC1_4_RGB: 8,
  385. PVRTC1_4_RGBA: 9,
  386. ASTC_4x4: 10,
  387. ATC_RGB: 11,
  388. ATC_RGBA_INTERPOLATED_ALPHA: 12,
  389. RGBA32: 13,
  390. RGB565: 14,
  391. BGR565: 15,
  392. RGBA4444: 16
  393. };
  394.  
  395. /**
  396. * @private
  397. */
  398. KTX2TextureTranscoder.EngineFormat = {
  399. RGBAFormat: RGBAFormat,
  400. RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format,
  401. RGBA_BPTC_Format: RGBA_BPTC_Format,
  402. RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format,
  403. RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format,
  404. RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format,
  405. RGB_ETC1_Format: RGB_ETC1_Format,
  406. RGB_ETC2_Format: RGB_ETC2_Format,
  407. RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format,
  408. RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format
  409. };
  410.  
  411. /* WEB WORKER */
  412.  
  413. /**
  414. * @private
  415. * @constructor
  416. */
  417. KTX2TextureTranscoder.BasisWorker = function () {
  418.  
  419. let config;
  420. let transcoderPending;
  421. let BasisModule;
  422.  
  423. const EngineFormat = _EngineFormat; // eslint-disable-line no-undef
  424. const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef
  425. const BasisFormat = _BasisFormat; // eslint-disable-line no-undef
  426.  
  427. self.addEventListener('message', function (e) {
  428. const message = e.data;
  429. switch (message.type) {
  430. case 'init':
  431. config = message.config;
  432. init(message.transcoderBinary);
  433. break;
  434. case 'transcode':
  435. transcoderPending.then(() => {
  436. try {
  437. const {
  438. width,
  439. height,
  440. hasAlpha,
  441. mipmaps,
  442. format,
  443. dfdTransferFn,
  444. dfdFlags
  445. } = transcode(message.buffers[0]);
  446. const buffers = [];
  447. for (let i = 0; i < mipmaps.length; ++i) {
  448. buffers.push(mipmaps[i].data.buffer);
  449. }
  450. self.postMessage({
  451. type: 'transcode',
  452. id: message.id,
  453. width,
  454. height,
  455. hasAlpha,
  456. mipmaps,
  457. format,
  458. dfdTransferFn,
  459. dfdFlags
  460. }, buffers);
  461. } catch (error) {
  462. console.error(`[KTX2TextureTranscoder.BasisWorker]: ${error}`);
  463. self.postMessage({type: 'error', id: message.id, error: error.message});
  464. }
  465. });
  466. break;
  467. }
  468. });
  469.  
  470. function init(wasmBinary) {
  471. transcoderPending = new Promise(resolve => {
  472. BasisModule = {
  473. wasmBinary,
  474. onRuntimeInitialized: resolve
  475. };
  476. BASIS(BasisModule); // eslint-disable-line no-undef
  477. }).then(() => {
  478. BasisModule.initializeBasis();
  479. if (BasisModule.KTX2File === undefined) {
  480. console.warn('KTX2TextureTranscoder: Please update Basis Universal transcoder.');
  481. }
  482. });
  483. }
  484.  
  485. function transcode(buffer) {
  486. const ktx2File = new BasisModule.KTX2File(new Uint8Array(buffer));
  487.  
  488. function cleanup() {
  489. ktx2File.close();
  490. ktx2File.delete();
  491. }
  492.  
  493. if (!ktx2File.isValid()) {
  494. cleanup();
  495. throw new Error('KTX2TextureTranscoder: Invalid or unsupported .ktx2 file');
  496. }
  497. const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;
  498. const width = ktx2File.getWidth();
  499. const height = ktx2File.getHeight();
  500. const levels = ktx2File.getLevels();
  501. const hasAlpha = ktx2File.getHasAlpha();
  502. const dfdTransferFn = ktx2File.getDFDTransferFunc();
  503. const dfdFlags = ktx2File.getDFDFlags();
  504. const {transcoderFormat, engineFormat} = getTranscoderFormat(basisFormat, width, height, hasAlpha);
  505. if (!width || !height || !levels) {
  506. cleanup();
  507. throw new Error('KTX2TextureTranscoder: Invalid texture');
  508. }
  509. if (!ktx2File.startTranscoding()) {
  510. cleanup();
  511. throw new Error('KTX2TextureTranscoder: .startTranscoding failed');
  512. }
  513. const mipmaps = [];
  514. for (let mip = 0; mip < levels; mip++) {
  515. const levelInfo = ktx2File.getImageLevelInfo(mip, 0, 0);
  516. const mipWidth = levelInfo.origWidth;
  517. const mipHeight = levelInfo.origHeight;
  518. const dst = new Uint8Array(ktx2File.getImageTranscodedSizeInBytes(mip, 0, 0, transcoderFormat));
  519. const status = ktx2File.transcodeImage(dst, mip, 0, 0, transcoderFormat, 0, -1, -1);
  520. if (!status) {
  521. cleanup();
  522. throw new Error('KTX2TextureTranscoder: .transcodeImage failed.');
  523. }
  524. mipmaps.push({data: dst, width: mipWidth, height: mipHeight});
  525. }
  526. cleanup();
  527. return {width, height, hasAlpha, mipmaps, format: engineFormat, dfdTransferFn, dfdFlags};
  528. }
  529.  
  530. // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC),
  531. // device capabilities, and texture dimensions. The list below ranks the formats separately
  532. // for ETC1S and UASTC.
  533. //
  534. // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at
  535. // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently
  536. // chooses RGBA32 only as a last resort and does not expose that option to the caller.
  537.  
  538. const FORMAT_OPTIONS = [{
  539. if: 'astcSupported',
  540. basisFormat: [BasisFormat.UASTC_4x4],
  541. transcoderFormat: [TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4],
  542. engineFormat: [EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format],
  543. priorityETC1S: Infinity,
  544. priorityUASTC: 1,
  545. needsPowerOfTwo: false
  546. }, {
  547. if: 'bptcSupported',
  548. basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],
  549. transcoderFormat: [TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5],
  550. engineFormat: [EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format],
  551. priorityETC1S: 3,
  552. priorityUASTC: 2,
  553. needsPowerOfTwo: false
  554. }, {
  555. if: 'dxtSupported',
  556. basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],
  557. transcoderFormat: [TranscoderFormat.BC1, TranscoderFormat.BC3],
  558. engineFormat: [EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format],
  559. priorityETC1S: 4,
  560. priorityUASTC: 5,
  561. needsPowerOfTwo: false
  562. }, {
  563. if: 'etc2Supported',
  564. basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],
  565. transcoderFormat: [TranscoderFormat.ETC1, TranscoderFormat.ETC2],
  566. engineFormat: [EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format],
  567. priorityETC1S: 1,
  568. priorityUASTC: 3,
  569. needsPowerOfTwo: false
  570. }, {
  571. if: 'etc1Supported',
  572. basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],
  573. transcoderFormat: [TranscoderFormat.ETC1],
  574. engineFormat: [EngineFormat.RGB_ETC1_Format],
  575. priorityETC1S: 2,
  576. priorityUASTC: 4,
  577. needsPowerOfTwo: false
  578. }, {
  579. if: 'pvrtcSupported',
  580. basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],
  581. transcoderFormat: [TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA],
  582. engineFormat: [EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format],
  583. priorityETC1S: 5,
  584. priorityUASTC: 6,
  585. needsPowerOfTwo: true
  586. }];
  587. const ETC1S_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) {
  588. return a.priorityETC1S - b.priorityETC1S;
  589. });
  590. const UASTC_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) {
  591. return a.priorityUASTC - b.priorityUASTC;
  592. });
  593.  
  594. function getTranscoderFormat(basisFormat, width, height, hasAlpha) {
  595. let transcoderFormat;
  596. let engineFormat;
  597. const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;
  598. for (let i = 0; i < options.length; i++) {
  599. const opt = options[i];
  600. if (!config[opt.if]) continue;
  601. if (!opt.basisFormat.includes(basisFormat)) continue;
  602. if (hasAlpha && opt.transcoderFormat.length < 2) continue;
  603. if (opt.needsPowerOfTwo && !(isPowerOfTwo(width) && isPowerOfTwo(height))) continue;
  604. transcoderFormat = opt.transcoderFormat[hasAlpha ? 1 : 0];
  605. engineFormat = opt.engineFormat[hasAlpha ? 1 : 0];
  606. return {
  607. transcoderFormat,
  608. engineFormat
  609. };
  610. }
  611. console.warn('KTX2TextureTranscoder: No suitable compressed texture format found. Decoding to RGBA32.');
  612. transcoderFormat = TranscoderFormat.RGBA32;
  613. engineFormat = EngineFormat.RGBAFormat;
  614. return {
  615. transcoderFormat,
  616. engineFormat
  617. };
  618. }
  619.  
  620. function isPowerOfTwo(value) {
  621. if (value <= 2) return true;
  622. return (value & value - 1) === 0 && value !== 0;
  623. }
  624. };
  625.  
  626. const cachedTranscoders = {};
  627.  
  628. /**
  629. * Returns a new {@link KTX2TextureTranscoder}.
  630. *
  631. * The ````transcoderPath```` config will be set to: "https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/"
  632. *
  633. * @private
  634. */
  635. function getKTX2TextureTranscoder(viewer) {
  636. const sceneId = viewer.scene.id;
  637. let transcoder = cachedTranscoders[sceneId];
  638. if (!transcoder) {
  639. transcoder = new KTX2TextureTranscoder({viewer});
  640. cachedTranscoders[sceneId] = transcoder;
  641. viewer.scene.on("destroyed", () => {
  642. delete cachedTranscoders[sceneId];
  643. transcoder.destroy();
  644. });
  645. }
  646. return transcoder;
  647. }
  648.  
  649. export {getKTX2TextureTranscoder, KTX2TextureTranscoder};
  650.