KTX2Loader.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. /**
  2. * References:
  3. * - KTX: http://github.khronos.org/KTX-Specification/
  4. * - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor
  5. *
  6. * To do:
  7. * - [ ] High-quality demo
  8. * - [ ] Documentation
  9. * - [ ] (Optional) Include BC5
  10. * - [ ] (Optional) Include EAC RG on mobile (WEBGL_compressed_texture_etc)
  11. * - [ ] (Optional) Include two-texture output mode (see: clearcoat + clearcoatRoughness)
  12. * - [ ] (Optional) Support Web Workers, after #18234
  13. */
  14. import {
  15. CompressedTexture,
  16. CompressedTextureLoader,
  17. FileLoader,
  18. LinearEncoding,
  19. LinearFilter,
  20. LinearMipmapLinearFilter,
  21. MathUtils,
  22. RGBAFormat,
  23. RGBA_ASTC_4x4_Format,
  24. RGBA_BPTC_Format,
  25. RGBA_ETC2_EAC_Format,
  26. RGBA_PVRTC_4BPPV1_Format,
  27. RGBA_S3TC_DXT5_Format,
  28. RGB_ETC1_Format,
  29. RGB_ETC2_Format,
  30. RGB_PVRTC_4BPPV1_Format,
  31. RGB_S3TC_DXT1_Format,
  32. UnsignedByteType,
  33. sRGBEncoding,
  34. } from '../../../build/three.module.js';
  35. import { ZSTDDecoder } from '../libs/zstddec.module.js';
  36. // Data Format Descriptor (DFD) constants.
  37. const DFDModel = {
  38. ETC1S: 163,
  39. UASTC: 166,
  40. };
  41. const DFDChannel = {
  42. ETC1S: {
  43. RGB: 0,
  44. RRR: 3,
  45. GGG: 4,
  46. AAA: 15,
  47. },
  48. UASTC: {
  49. RGB: 0,
  50. RGBA: 3,
  51. RRR: 4,
  52. RRRG: 5
  53. },
  54. };
  55. //
  56. class KTX2Loader extends CompressedTextureLoader {
  57. constructor( manager ) {
  58. super( manager );
  59. this.basisModule = null;
  60. this.basisModulePending = null;
  61. this.transcoderConfig = {};
  62. }
  63. detectSupport( renderer ) {
  64. this.transcoderConfig = {
  65. astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ),
  66. etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ),
  67. etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ),
  68. dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ),
  69. bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ),
  70. pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' )
  71. || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' )
  72. };
  73. return this;
  74. }
  75. initModule() {
  76. if ( this.basisModulePending ) {
  77. return;
  78. }
  79. var scope = this;
  80. // The Emscripten wrapper returns a fake Promise, which can cause
  81. // infinite recursion when mixed with native Promises. Wrap the module
  82. // initialization to return a native Promise.
  83. scope.basisModulePending = new Promise( function ( resolve ) {
  84. MSC_TRANSCODER().then( function ( basisModule ) { // eslint-disable-line no-undef
  85. scope.basisModule = basisModule;
  86. basisModule.initTranscoders();
  87. resolve();
  88. } );
  89. } );
  90. }
  91. load( url, onLoad, onProgress, onError ) {
  92. var scope = this;
  93. var texture = new CompressedTexture();
  94. var bufferPending = new Promise( function ( resolve, reject ) {
  95. new FileLoader( scope.manager )
  96. .setPath( scope.path )
  97. .setResponseType( 'arraybuffer' )
  98. .load( url, resolve, onProgress, reject );
  99. } );
  100. // parse() will call initModule() again, but starting the process early
  101. // should allow the WASM to load in parallel with the texture.
  102. this.initModule();
  103. Promise.all( [ bufferPending, this.basisModulePending ] )
  104. .then( function ( [ buffer ] ) {
  105. scope.parse( buffer, function ( _texture ) {
  106. texture.copy( _texture );
  107. texture.needsUpdate = true;
  108. if ( onLoad ) onLoad( texture );
  109. }, onError );
  110. } )
  111. .catch( onError );
  112. return texture;
  113. }
  114. parse( buffer, onLoad, onError ) {
  115. var scope = this;
  116. // load() may have already called initModule(), but call it again here
  117. // in case the user called parse() directly. Method is idempotent.
  118. this.initModule();
  119. this.basisModulePending.then( function () {
  120. var BasisLzEtc1sImageTranscoder = scope.basisModule.BasisLzEtc1sImageTranscoder;
  121. var UastcImageTranscoder = scope.basisModule.UastcImageTranscoder;
  122. var TextureFormat = scope.basisModule.TextureFormat;
  123. var ktx = new KTX2Container( scope.basisModule, buffer );
  124. // TODO(donmccurdy): Should test if texture is transcodable before attempting
  125. // any transcoding. If supercompressionScheme is KTX_SS_BASIS_LZ and dfd
  126. // colorModel is ETC1S (163) or if dfd colorModel is UASTCF (166)
  127. // then texture must be transcoded.
  128. var transcoder = ktx.getTexFormat() === TextureFormat.UASTC4x4
  129. ? new UastcImageTranscoder()
  130. : new BasisLzEtc1sImageTranscoder();
  131. ktx.initMipmaps( transcoder, scope.transcoderConfig )
  132. .then( function () {
  133. var texture = new CompressedTexture(
  134. ktx.mipmaps,
  135. ktx.getWidth(),
  136. ktx.getHeight(),
  137. ktx.transcodedFormat,
  138. UnsignedByteType
  139. );
  140. texture.encoding = ktx.getEncoding();
  141. texture.premultiplyAlpha = ktx.getPremultiplyAlpha();
  142. texture.minFilter = ktx.mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter;
  143. texture.magFilter = LinearFilter;
  144. onLoad( texture );
  145. } )
  146. .catch( onError );
  147. } );
  148. return this;
  149. }
  150. }
  151. class KTX2Container {
  152. constructor( basisModule, arrayBuffer ) {
  153. this.basisModule = basisModule;
  154. this.arrayBuffer = arrayBuffer;
  155. this.zstd = new ZSTDDecoder();
  156. this.zstd.init();
  157. this.mipmaps = null;
  158. this.transcodedFormat = null;
  159. // Confirm this is a KTX 2.0 file, based on the identifier in the first 12 bytes.
  160. var idByteLength = 12;
  161. var id = new Uint8Array( this.arrayBuffer, 0, idByteLength );
  162. if ( id[ 0 ] !== 0xAB || // '´'
  163. id[ 1 ] !== 0x4B || // 'K'
  164. id[ 2 ] !== 0x54 || // 'T'
  165. id[ 3 ] !== 0x58 || // 'X'
  166. id[ 4 ] !== 0x20 || // ' '
  167. id[ 5 ] !== 0x32 || // '2'
  168. id[ 6 ] !== 0x30 || // '0'
  169. id[ 7 ] !== 0xBB || // 'ª'
  170. id[ 8 ] !== 0x0D || // '\r'
  171. id[ 9 ] !== 0x0A || // '\n'
  172. id[ 10 ] !== 0x1A || // '\x1A'
  173. id[ 11 ] !== 0x0A // '\n'
  174. ) {
  175. throw new Error( 'THREE.KTX2Loader: Missing KTX 2.0 identifier.' );
  176. }
  177. // TODO(donmccurdy): If we need to support BE, derive this from typeSize.
  178. var littleEndian = true;
  179. ///////////////////////////////////////////////////
  180. // Header.
  181. ///////////////////////////////////////////////////
  182. var headerByteLength = 17 * Uint32Array.BYTES_PER_ELEMENT;
  183. var headerReader = new KTX2BufferReader( this.arrayBuffer, idByteLength, headerByteLength, littleEndian );
  184. this.header = {
  185. vkFormat: headerReader.nextUint32(),
  186. typeSize: headerReader.nextUint32(),
  187. pixelWidth: headerReader.nextUint32(),
  188. pixelHeight: headerReader.nextUint32(),
  189. pixelDepth: headerReader.nextUint32(),
  190. arrayElementCount: headerReader.nextUint32(),
  191. faceCount: headerReader.nextUint32(),
  192. levelCount: headerReader.nextUint32(),
  193. supercompressionScheme: headerReader.nextUint32(),
  194. dfdByteOffset: headerReader.nextUint32(),
  195. dfdByteLength: headerReader.nextUint32(),
  196. kvdByteOffset: headerReader.nextUint32(),
  197. kvdByteLength: headerReader.nextUint32(),
  198. sgdByteOffset: headerReader.nextUint64(),
  199. sgdByteLength: headerReader.nextUint64(),
  200. };
  201. if ( this.header.pixelDepth > 0 ) {
  202. throw new Error( 'THREE.KTX2Loader: Only 2D textures are currently supported.' );
  203. }
  204. if ( this.header.arrayElementCount > 1 ) {
  205. throw new Error( 'THREE.KTX2Loader: Array textures are not currently supported.' );
  206. }
  207. if ( this.header.faceCount > 1 ) {
  208. throw new Error( 'THREE.KTX2Loader: Cube textures are not currently supported.' );
  209. }
  210. ///////////////////////////////////////////////////
  211. // Level index
  212. ///////////////////////////////////////////////////
  213. var levelByteLength = this.header.levelCount * 3 * 8;
  214. var levelReader = new KTX2BufferReader( this.arrayBuffer, idByteLength + headerByteLength, levelByteLength, littleEndian );
  215. this.levels = [];
  216. for ( var i = 0; i < this.header.levelCount; i ++ ) {
  217. this.levels.push( {
  218. byteOffset: levelReader.nextUint64(),
  219. byteLength: levelReader.nextUint64(),
  220. uncompressedByteLength: levelReader.nextUint64(),
  221. } );
  222. }
  223. ///////////////////////////////////////////////////
  224. // Data Format Descriptor (DFD)
  225. ///////////////////////////////////////////////////
  226. var dfdReader = new KTX2BufferReader(
  227. this.arrayBuffer,
  228. this.header.dfdByteOffset,
  229. this.header.dfdByteLength,
  230. littleEndian
  231. );
  232. const sampleStart = 6;
  233. const sampleWords = 4;
  234. this.dfd = {
  235. vendorId: dfdReader.skip( 4 /* totalSize */ ).nextUint16(),
  236. versionNumber: dfdReader.skip( 2 /* descriptorType */ ).nextUint16(),
  237. descriptorBlockSize: dfdReader.nextUint16(),
  238. colorModel: dfdReader.nextUint8(),
  239. colorPrimaries: dfdReader.nextUint8(),
  240. transferFunction: dfdReader.nextUint8(),
  241. flags: dfdReader.nextUint8(),
  242. texelBlockDimension: {
  243. x: dfdReader.nextUint8() + 1,
  244. y: dfdReader.nextUint8() + 1,
  245. z: dfdReader.nextUint8() + 1,
  246. w: dfdReader.nextUint8() + 1,
  247. },
  248. bytesPlane0: dfdReader.nextUint8(),
  249. numSamples: 0,
  250. samples: [],
  251. };
  252. this.dfd.numSamples = ( this.dfd.descriptorBlockSize / 4 - sampleStart ) / sampleWords;
  253. dfdReader.skip( 7 /* bytesPlane[1-7] */ );
  254. for ( var i = 0; i < this.dfd.numSamples; i ++ ) {
  255. this.dfd.samples[ i ] = {
  256. channelID: dfdReader.skip( 3 /* bitOffset + bitLength */ ).nextUint8(),
  257. // ... remainder not implemented.
  258. };
  259. dfdReader.skip( 12 /* samplePosition[0-3], lower, upper */ );
  260. }
  261. if ( this.header.vkFormat !== 0x00 /* VK_FORMAT_UNDEFINED */ &&
  262. ! ( this.header.supercompressionScheme === 1 /* BasisLZ */ ||
  263. this.dfd.colorModel === DFDModel.UASTC ) ) {
  264. throw new Error( 'THREE.KTX2Loader: Only Basis Universal supercompression is currently supported.' );
  265. }
  266. ///////////////////////////////////////////////////
  267. // Key/Value Data (KVD)
  268. ///////////////////////////////////////////////////
  269. // Not implemented.
  270. this.kvd = {};
  271. ///////////////////////////////////////////////////
  272. // Supercompression Global Data (SGD)
  273. ///////////////////////////////////////////////////
  274. this.sgd = {};
  275. if ( this.header.sgdByteLength <= 0 ) return;
  276. var sgdReader = new KTX2BufferReader(
  277. this.arrayBuffer,
  278. this.header.sgdByteOffset,
  279. this.header.sgdByteLength,
  280. littleEndian
  281. );
  282. this.sgd.endpointCount = sgdReader.nextUint16();
  283. this.sgd.selectorCount = sgdReader.nextUint16();
  284. this.sgd.endpointsByteLength = sgdReader.nextUint32();
  285. this.sgd.selectorsByteLength = sgdReader.nextUint32();
  286. this.sgd.tablesByteLength = sgdReader.nextUint32();
  287. this.sgd.extendedByteLength = sgdReader.nextUint32();
  288. this.sgd.imageDescs = [];
  289. this.sgd.endpointsData = null;
  290. this.sgd.selectorsData = null;
  291. this.sgd.tablesData = null;
  292. this.sgd.extendedData = null;
  293. for ( var i = 0; i < this.header.levelCount; i ++ ) {
  294. this.sgd.imageDescs.push( {
  295. imageFlags: sgdReader.nextUint32(),
  296. rgbSliceByteOffset: sgdReader.nextUint32(),
  297. rgbSliceByteLength: sgdReader.nextUint32(),
  298. alphaSliceByteOffset: sgdReader.nextUint32(),
  299. alphaSliceByteLength: sgdReader.nextUint32(),
  300. } );
  301. }
  302. var endpointsByteOffset = this.header.sgdByteOffset + sgdReader.offset;
  303. var selectorsByteOffset = endpointsByteOffset + this.sgd.endpointsByteLength;
  304. var tablesByteOffset = selectorsByteOffset + this.sgd.selectorsByteLength;
  305. var extendedByteOffset = tablesByteOffset + this.sgd.tablesByteLength;
  306. this.sgd.endpointsData = new Uint8Array( this.arrayBuffer, endpointsByteOffset, this.sgd.endpointsByteLength );
  307. this.sgd.selectorsData = new Uint8Array( this.arrayBuffer, selectorsByteOffset, this.sgd.selectorsByteLength );
  308. this.sgd.tablesData = new Uint8Array( this.arrayBuffer, tablesByteOffset, this.sgd.tablesByteLength );
  309. this.sgd.extendedData = new Uint8Array( this.arrayBuffer, extendedByteOffset, this.sgd.extendedByteLength );
  310. }
  311. async initMipmaps( transcoder, config ) {
  312. await this.zstd.init();
  313. var TranscodeTarget = this.basisModule.TranscodeTarget;
  314. var TextureFormat = this.basisModule.TextureFormat;
  315. var ImageInfo = this.basisModule.ImageInfo;
  316. var scope = this;
  317. var mipmaps = [];
  318. var width = this.getWidth();
  319. var height = this.getHeight();
  320. var texFormat = this.getTexFormat();
  321. var hasAlpha = this.getAlpha();
  322. var isVideo = false;
  323. // PVRTC1 transcoders (from both ETC1S and UASTC) only support power of 2 dimensions.
  324. var pvrtcTranscodable = MathUtils.isPowerOfTwo( width ) && MathUtils.isPowerOfTwo( height );
  325. if ( texFormat === TextureFormat.ETC1S ) {
  326. var numEndpoints = this.sgd.endpointCount;
  327. var numSelectors = this.sgd.selectorCount;
  328. var endpoints = this.sgd.endpointsData;
  329. var selectors = this.sgd.selectorsData;
  330. var tables = this.sgd.tablesData;
  331. transcoder.decodePalettes( numEndpoints, endpoints, numSelectors, selectors );
  332. transcoder.decodeTables( tables );
  333. }
  334. var targetFormat;
  335. if ( config.astcSupported ) {
  336. targetFormat = TranscodeTarget.ASTC_4x4_RGBA;
  337. this.transcodedFormat = RGBA_ASTC_4x4_Format;
  338. } else if ( config.bptcSupported && texFormat === TextureFormat.UASTC4x4 ) {
  339. targetFormat = TranscodeTarget.BC7_M5_RGBA;
  340. this.transcodedFormat = RGBA_BPTC_Format;
  341. } else if ( config.dxtSupported ) {
  342. targetFormat = hasAlpha ? TranscodeTarget.BC3_RGBA : TranscodeTarget.BC1_RGB;
  343. this.transcodedFormat = hasAlpha ? RGBA_S3TC_DXT5_Format : RGB_S3TC_DXT1_Format;
  344. } else if ( config.pvrtcSupported && pvrtcTranscodable ) {
  345. targetFormat = hasAlpha ? TranscodeTarget.PVRTC1_4_RGBA : TranscodeTarget.PVRTC1_4_RGB;
  346. this.transcodedFormat = hasAlpha ? RGBA_PVRTC_4BPPV1_Format : RGB_PVRTC_4BPPV1_Format;
  347. } else if ( config.etc2Supported ) {
  348. targetFormat = hasAlpha ? TranscodeTarget.ETC2_RGBA : TranscodeTarget.ETC1_RGB/* subset of ETC2 */;
  349. this.transcodedFormat = hasAlpha ? RGBA_ETC2_EAC_Format : RGB_ETC2_Format;
  350. } else if ( config.etc1Supported ) {
  351. targetFormat = TranscodeTarget.ETC1_RGB;
  352. this.transcodedFormat = RGB_ETC1_Format;
  353. } else {
  354. console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' );
  355. targetFormat = TranscodeTarget.RGBA32;
  356. this.transcodedFormat = RGBAFormat;
  357. }
  358. if ( ! this.basisModule.isFormatSupported( targetFormat, texFormat ) ) {
  359. throw new Error( 'THREE.KTX2Loader: Selected texture format not supported by current transcoder build.' );
  360. }
  361. var imageDescIndex = 0;
  362. for ( var level = 0; level < this.header.levelCount; level ++ ) {
  363. var levelWidth = Math.max( 1, Math.floor( width / Math.pow( 2, level ) ) );
  364. var levelHeight = Math.max( 1, Math.floor( height / Math.pow( 2, level ) ) );
  365. var numImagesInLevel = 1; // TODO(donmccurdy): Support cubemaps, arrays and 3D.
  366. var imageOffsetInLevel = 0;
  367. var imageInfo = new ImageInfo( texFormat, levelWidth, levelHeight, level );
  368. var levelByteLength = this.levels[ level ].byteLength;
  369. var levelUncompressedByteLength = this.levels[ level ].uncompressedByteLength;
  370. for ( var imageIndex = 0; imageIndex < numImagesInLevel; imageIndex ++ ) {
  371. var result;
  372. var encodedData;
  373. if ( texFormat === TextureFormat.UASTC4x4 ) {
  374. // UASTC
  375. imageInfo.flags = 0;
  376. imageInfo.rgbByteOffset = 0;
  377. imageInfo.rgbByteLength = levelUncompressedByteLength;
  378. imageInfo.alphaByteOffset = 0;
  379. imageInfo.alphaByteLength = 0;
  380. encodedData = new Uint8Array( this.arrayBuffer, this.levels[ level ].byteOffset + imageOffsetInLevel, levelByteLength );
  381. if ( this.header.supercompressionScheme === 2 /* ZSTD */ ) {
  382. encodedData = this.zstd.decode( encodedData, levelUncompressedByteLength );
  383. }
  384. result = transcoder.transcodeImage( targetFormat, encodedData, imageInfo, 0, hasAlpha, isVideo );
  385. } else {
  386. // ETC1S
  387. var imageDesc = this.sgd.imageDescs[ imageDescIndex ++ ];
  388. imageInfo.flags = imageDesc.imageFlags;
  389. imageInfo.rgbByteOffset = 0;
  390. imageInfo.rgbByteLength = imageDesc.rgbSliceByteLength;
  391. imageInfo.alphaByteOffset = imageDesc.alphaSliceByteOffset > 0 ? imageDesc.rgbSliceByteLength : 0;
  392. imageInfo.alphaByteLength = imageDesc.alphaSliceByteLength;
  393. encodedData = new Uint8Array( this.arrayBuffer, this.levels[ level ].byteOffset + imageDesc.rgbSliceByteOffset, imageDesc.rgbSliceByteLength + imageDesc.alphaSliceByteLength );
  394. result = transcoder.transcodeImage( targetFormat, encodedData, imageInfo, 0, isVideo );
  395. }
  396. if ( result.transcodedImage === undefined ) {
  397. throw new Error( 'THREE.KTX2Loader: Unable to transcode image.' );
  398. }
  399. // Transcoded image is written in memory allocated by WASM. We could avoid copying
  400. // the image by waiting until the image is uploaded to the GPU, then calling
  401. // delete(). However, (1) we don't know if the user will later need to re-upload it
  402. // e.g. after calling texture.clone(), and (2) this code will eventually be in a
  403. // Web Worker, and transferring WASM's memory seems like a very bad idea.
  404. var levelData = result.transcodedImage.get_typed_memory_view().slice();
  405. result.transcodedImage.delete();
  406. mipmaps.push( { data: levelData, width: levelWidth, height: levelHeight } );
  407. imageOffsetInLevel += levelByteLength;
  408. }
  409. }
  410. scope.mipmaps = mipmaps;
  411. }
  412. getWidth() {
  413. return this.header.pixelWidth;
  414. }
  415. getHeight() {
  416. return this.header.pixelHeight;
  417. }
  418. getEncoding() {
  419. return this.dfd.transferFunction === 2 /* KHR_DF_TRANSFER_SRGB */
  420. ? sRGBEncoding
  421. : LinearEncoding;
  422. }
  423. getTexFormat() {
  424. var TextureFormat = this.basisModule.TextureFormat;
  425. return this.dfd.colorModel === DFDModel.UASTC ? TextureFormat.UASTC4x4 : TextureFormat.ETC1S;
  426. }
  427. getAlpha() {
  428. var TextureFormat = this.basisModule.TextureFormat;
  429. // TODO(donmccurdy): Handle all channelIDs (i.e. the R & R+G cases),
  430. // choosing appropriate transcode target formats or providing queries
  431. // for applications so they know what to do with the content.
  432. if ( this.getTexFormat() === TextureFormat.UASTC4x4 ) {
  433. // UASTC
  434. if ( ( this.dfd.samples[ 0 ].channelID & 0xF ) === DFDChannel.UASTC.RGBA ) {
  435. return true;
  436. }
  437. return false;
  438. }
  439. // ETC1S
  440. if ( this.dfd.numSamples === 2 && ( this.dfd.samples[ 1 ].channelID & 0xF ) === DFDChannel.ETC1S.AAA ) {
  441. return true;
  442. }
  443. return false;
  444. }
  445. getPremultiplyAlpha() {
  446. return !! ( this.dfd.flags & 1 /* KHR_DF_FLAG_ALPHA_PREMULTIPLIED */ );
  447. }
  448. }
  449. class KTX2BufferReader {
  450. constructor( arrayBuffer, byteOffset, byteLength, littleEndian ) {
  451. this.dataView = new DataView( arrayBuffer, byteOffset, byteLength );
  452. this.littleEndian = littleEndian;
  453. this.offset = 0;
  454. }
  455. nextUint8() {
  456. var value = this.dataView.getUint8( this.offset, this.littleEndian );
  457. this.offset += 1;
  458. return value;
  459. }
  460. nextUint16() {
  461. var value = this.dataView.getUint16( this.offset, this.littleEndian );
  462. this.offset += 2;
  463. return value;
  464. }
  465. nextUint32() {
  466. var value = this.dataView.getUint32( this.offset, this.littleEndian );
  467. this.offset += 4;
  468. return value;
  469. }
  470. nextUint64() {
  471. // https://stackoverflow.com/questions/53103695/
  472. var left = this.dataView.getUint32( this.offset, this.littleEndian );
  473. var right = this.dataView.getUint32( this.offset + 4, this.littleEndian );
  474. var value = this.littleEndian ? left + ( 2 ** 32 * right ) : ( 2 ** 32 * left ) + right;
  475. if ( ! Number.isSafeInteger( value ) ) {
  476. console.warn( 'THREE.KTX2Loader: ' + value + ' exceeds MAX_SAFE_INTEGER. Precision may be lost.' );
  477. }
  478. this.offset += 8;
  479. return value;
  480. }
  481. skip( bytes ) {
  482. this.offset += bytes;
  483. return this;
  484. }
  485. }
  486. export { KTX2Loader };