KTX2Loader.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. /**
  2. * Loader for KTX 2.0 GPU Texture containers.
  3. *
  4. * KTX 2.0 is a container format for various GPU texture formats. The loader
  5. * supports Basis Universal GPU textures, which can be quickly transcoded to
  6. * a wide variety of GPU texture compression formats. While KTX 2.0 also allows
  7. * other hardware-specific formats, this loader does not yet parse them.
  8. *
  9. * This loader parses the KTX 2.0 container and then relies on
  10. * THREE.BasisTextureLoader to complete the transcoding process.
  11. *
  12. * References:
  13. * - KTX: http://github.khronos.org/KTX-Specification/
  14. * - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor
  15. */
  16. import {
  17. CompressedTexture,
  18. CompressedTextureLoader,
  19. FileLoader,
  20. LinearEncoding,
  21. LinearFilter,
  22. LinearMipmapLinearFilter,
  23. MathUtils,
  24. UnsignedByteType,
  25. sRGBEncoding,
  26. } from '../../../build/three.module.js';
  27. import { BasisTextureLoader } from './BasisTextureLoader.js';
  28. import { ZSTDDecoder } from '../libs/zstddec.module.js';
  29. import { read as readKTX } from '../libs/ktx-parse.module.js';
  30. // KTX 2.0 constants.
  31. var DFDModel = {
  32. ETC1S: 163,
  33. UASTC: 166,
  34. };
  35. var DFDChannel = {
  36. ETC1S: {
  37. RGB: 0,
  38. RRR: 3,
  39. GGG: 4,
  40. AAA: 15,
  41. },
  42. UASTC: {
  43. RGB: 0,
  44. RGBA: 3,
  45. RRR: 4,
  46. RRRG: 5
  47. },
  48. };
  49. var SupercompressionScheme = {
  50. ZSTD: 2
  51. }
  52. var Transfer = {
  53. SRGB: 2
  54. }
  55. //
  56. class KTX2Loader extends CompressedTextureLoader {
  57. constructor( manager ) {
  58. super( manager );
  59. this.basisLoader = new BasisTextureLoader( manager );
  60. this.zstd = new ZSTDDecoder();
  61. this.zstd.init();
  62. if ( typeof MSC_TRANSCODER !== 'undefined' ) {
  63. console.warn(
  64. 'THREE.KTX2Loader: Please update to latest "basis_transcoder".'
  65. + ' "msc_basis_transcoder" is no longer supported in three.js r125+.'
  66. );
  67. }
  68. }
  69. setTranscoderPath ( path ) {
  70. this.basisLoader.setTranscoderPath( path );
  71. return this;
  72. }
  73. setWorkerLimit ( path ) {
  74. this.basisLoader.setWorkerLimit( path );
  75. return this;
  76. }
  77. detectSupport ( renderer ) {
  78. this.basisLoader.detectSupport( renderer );
  79. return this;
  80. }
  81. dispose () {
  82. this.basisLoader.dispose();
  83. return this;
  84. }
  85. load( url, onLoad, onProgress, onError ) {
  86. var scope = this;
  87. var texture = new CompressedTexture();
  88. var bufferPending = new Promise( function ( resolve, reject ) {
  89. new FileLoader( scope.manager )
  90. .setPath( scope.path )
  91. .setResponseType( 'arraybuffer' )
  92. .load( url, resolve, onProgress, reject );
  93. } )
  94. bufferPending
  95. .then( function ( buffer ) {
  96. scope.parse( buffer, function ( _texture ) {
  97. texture.copy( _texture );
  98. texture.needsUpdate = true;
  99. if ( onLoad ) onLoad( texture );
  100. }, onError );
  101. } )
  102. .catch( onError );
  103. return texture;
  104. }
  105. parse( buffer, onLoad, onError ) {
  106. var scope = this;
  107. var ktx = readKTX( new Uint8Array( buffer ) );
  108. if ( ktx.pixelDepth > 0 ) {
  109. throw new Error( 'THREE.KTX2Loader: Only 2D textures are currently supported.' );
  110. }
  111. if ( ktx.layerCount > 1 ) {
  112. throw new Error( 'THREE.KTX2Loader: Array textures are not currently supported.' );
  113. }
  114. if ( ktx.faceCount > 1 ) {
  115. throw new Error( 'THREE.KTX2Loader: Cube textures are not currently supported.' );
  116. }
  117. var dfd = KTX2Utils.getBasicDFD( ktx );
  118. KTX2Utils.createLevels( ktx, this.zstd ).then( function ( levels ) {
  119. var basisFormat = dfd.colorModel === DFDModel.UASTC
  120. ? BasisTextureLoader.BasisFormat.UASTC_4x4
  121. : BasisTextureLoader.BasisFormat.ETC1S;
  122. var parseConfig = {
  123. levels: levels,
  124. width: ktx.pixelWidth,
  125. height: ktx.pixelHeight,
  126. basisFormat: basisFormat,
  127. hasAlpha: KTX2Utils.getAlpha( ktx ),
  128. };
  129. if ( basisFormat === BasisTextureLoader.BasisFormat.ETC1S ) {
  130. parseConfig.globalData = ktx.globalData;
  131. }
  132. return scope.basisLoader.parseInternalAsync( parseConfig );
  133. } ).then( function ( texture ) {
  134. texture.encoding = dfd.transferFunction === Transfer.SRGB
  135. ? sRGBEncoding
  136. : LinearEncoding;
  137. texture.premultiplyAlpha = KTX2Utils.getPremultiplyAlpha( ktx );
  138. onLoad( texture );
  139. } );
  140. return this;
  141. }
  142. }
  143. var KTX2Utils = {
  144. createLevels: async function ( ktx, zstd ) {
  145. if ( ktx.supercompressionScheme === SupercompressionScheme.ZSTD ) {
  146. await zstd.init();
  147. }
  148. var levels = [];
  149. var width = ktx.pixelWidth;
  150. var height = ktx.pixelHeight;
  151. for ( var levelIndex = 0; levelIndex < ktx.levels.length; levelIndex ++ ) {
  152. var levelWidth = Math.max( 1, Math.floor( width / Math.pow( 2, levelIndex ) ) );
  153. var levelHeight = Math.max( 1, Math.floor( height / Math.pow( 2, levelIndex ) ) );
  154. var levelData = ktx.levels[ levelIndex ].levelData;
  155. if ( ktx.supercompressionScheme === SupercompressionScheme.ZSTD ) {
  156. levelData = zstd.decode( levelData, ktx.levels[ levelIndex ].uncompressedByteLength );
  157. }
  158. levels.push( {
  159. index: levelIndex,
  160. width: levelWidth,
  161. height: levelHeight,
  162. data: levelData,
  163. } );
  164. }
  165. return levels;
  166. },
  167. getBasicDFD: function ( ktx ) {
  168. // Basic Data Format Descriptor Block is always the first DFD.
  169. return ktx.dataFormatDescriptor[ 0 ];
  170. },
  171. getAlpha: function ( ktx ) {
  172. var dfd = this.getBasicDFD( ktx );
  173. // UASTC
  174. if ( dfd.colorModel === DFDModel.UASTC ) {
  175. if ( ( dfd.samples[ 0 ].channelID & 0xF ) === DFDChannel.UASTC.RGBA ) {
  176. return true;
  177. }
  178. return false;
  179. }
  180. // ETC1S
  181. if ( dfd.samples.length === 2
  182. && ( dfd.samples[ 1 ].channelID & 0xF ) === DFDChannel.ETC1S.AAA ) {
  183. return true;
  184. }
  185. return false;
  186. },
  187. getPremultiplyAlpha: function ( ktx ) {
  188. var dfd = this.getBasicDFD( ktx );
  189. return !! ( dfd.flags & 1 /* KHR_DF_FLAG_ALPHA_PREMULTIPLIED */ );
  190. },
  191. }
  192. export { KTX2Loader };