|
@@ -0,0 +1,539 @@
|
|
|
+( function () {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * THREE.Loader for KTX 2.0 GPU Texture containers.
|
|
|
+ *
|
|
|
+ * KTX 2.0 is a container format for various GPU texture formats. The loader
|
|
|
+ * supports Basis Universal GPU textures, which can be quickly transcoded to
|
|
|
+ * a wide variety of GPU texture compression formats. While KTX 2.0 also allows
|
|
|
+ * other hardware-specific formats, this loader does not yet parse them.
|
|
|
+ *
|
|
|
+ * References:
|
|
|
+ * - KTX: http://github.khronos.org/KTX-Specification/
|
|
|
+ * - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor
|
|
|
+ */
|
|
|
+ const KTX2TransferSRGB = 2;
|
|
|
+ const KTX2_ALPHA_PREMULTIPLIED = 1;
|
|
|
+
|
|
|
+ const _taskCache = new WeakMap();
|
|
|
+
|
|
|
+ class KTX2Loader extends THREE.Loader {
|
|
|
+
|
|
|
+ constructor( manager ) {
|
|
|
+
|
|
|
+ super( manager );
|
|
|
+ this.transcoderPath = '';
|
|
|
+ this.transcoderBinary = null;
|
|
|
+ this.transcoderPending = null;
|
|
|
+ this.workerPool = new THREE.WorkerPool();
|
|
|
+ this.workerSourceURL = '';
|
|
|
+ this.workerConfig = null;
|
|
|
+
|
|
|
+ if ( typeof MSC_TRANSCODER !== 'undefined' ) {
|
|
|
+
|
|
|
+ console.warn( 'THREE.KTX2Loader: Please update to latest "basis_transcoder".' + ' "msc_basis_transcoder" is no longer supported in three.js r125+.' );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ setTranscoderPath( path ) {
|
|
|
+
|
|
|
+ this.transcoderPath = path;
|
|
|
+ return this;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ setWorkerLimit( num ) {
|
|
|
+
|
|
|
+ this.workerPool.setWorkerLimit( num );
|
|
|
+ return this;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ detectSupport( renderer ) {
|
|
|
+
|
|
|
+ this.workerConfig = {
|
|
|
+ astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ),
|
|
|
+ etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ),
|
|
|
+ etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ),
|
|
|
+ dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ),
|
|
|
+ bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ),
|
|
|
+ pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' )
|
|
|
+ };
|
|
|
+ return this;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ dispose() {
|
|
|
+
|
|
|
+ this.workerPool.dispose();
|
|
|
+ if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL );
|
|
|
+ return this;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ init() {
|
|
|
+
|
|
|
+ if ( ! this.transcoderPending ) {
|
|
|
+
|
|
|
+ // Load transcoder wrapper.
|
|
|
+ const jsLoader = new THREE.FileLoader( this.manager );
|
|
|
+ jsLoader.setPath( this.transcoderPath );
|
|
|
+ jsLoader.setWithCredentials( this.withCredentials );
|
|
|
+ const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' ); // Load transcoder WASM binary.
|
|
|
+
|
|
|
+ const binaryLoader = new THREE.FileLoader( this.manager );
|
|
|
+ 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 = KTX2Loader.BasisWorker.toString();
|
|
|
+ const body = [ '/* constants */', 'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ), 'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ), 'let _BasisFormat = ' + JSON.stringify( KTX2Loader.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;
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.transcoderPending;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ load( url, onLoad, onProgress, onError ) {
|
|
|
+
|
|
|
+ const loader = new THREE.FileLoader( this.manager );
|
|
|
+ loader.setResponseType( 'arraybuffer' );
|
|
|
+ loader.setWithCredentials( this.withCredentials );
|
|
|
+ const texture = new THREE.CompressedTexture();
|
|
|
+ loader.load( url, buffer => {
|
|
|
+
|
|
|
+ // Check for an existing task using this buffer. A transferred buffer cannot be transferred
|
|
|
+ // again from this thread.
|
|
|
+ if ( _taskCache.has( buffer ) ) {
|
|
|
+
|
|
|
+ const cachedTask = _taskCache.get( buffer );
|
|
|
+
|
|
|
+ return cachedTask.promise.then( onLoad ).catch( onError );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ this._createTexture( [ buffer ] ).then( function ( _texture ) {
|
|
|
+
|
|
|
+ texture.copy( _texture );
|
|
|
+ texture.needsUpdate = true;
|
|
|
+ if ( onLoad ) onLoad( texture );
|
|
|
+
|
|
|
+ } ).catch( onError );
|
|
|
+
|
|
|
+ }, onProgress, onError );
|
|
|
+ return texture;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ createTextureFrom( transcodeResult ) {
|
|
|
+
|
|
|
+ const {
|
|
|
+ mipmaps,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ format,
|
|
|
+ type,
|
|
|
+ error,
|
|
|
+ dfdTransferFn,
|
|
|
+ dfdFlags
|
|
|
+ } = transcodeResult;
|
|
|
+ if ( type === 'error' ) return Promise.reject( error );
|
|
|
+ const texture = new THREE.CompressedTexture( mipmaps, width, height, format, THREE.UnsignedByteType );
|
|
|
+ texture.minFilter = mipmaps.length === 1 ? THREE.LinearFilter : THREE.LinearMipmapLinearFilter;
|
|
|
+ texture.magFilter = THREE.LinearFilter;
|
|
|
+ texture.generateMipmaps = false;
|
|
|
+ texture.needsUpdate = true;
|
|
|
+ texture.encoding = dfdTransferFn === KTX2TransferSRGB ? THREE.sRGBEncoding : THREE.LinearEncoding;
|
|
|
+ texture.premultiplyAlpha = !! ( dfdFlags & KTX2_ALPHA_PREMULTIPLIED );
|
|
|
+ return texture;
|
|
|
+
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * @param {ArrayBuffer[]} buffers
|
|
|
+ * @param {object?} config
|
|
|
+ * @return {Promise<CompressedTexture>}
|
|
|
+ */
|
|
|
+
|
|
|
+
|
|
|
+ _createTexture( buffers, config = {} ) {
|
|
|
+
|
|
|
+ const taskConfig = config;
|
|
|
+ const texturePending = this.init().then( () => {
|
|
|
+
|
|
|
+ return this.workerPool.postMessage( {
|
|
|
+ type: 'transcode',
|
|
|
+ buffers,
|
|
|
+ taskConfig: taskConfig
|
|
|
+ }, buffers );
|
|
|
+
|
|
|
+ } ).then( e => this.createTextureFrom( e.data ) ); // Cache the task result.
|
|
|
+
|
|
|
+ _taskCache.set( buffers[ 0 ], {
|
|
|
+ promise: texturePending
|
|
|
+ } );
|
|
|
+
|
|
|
+ return texturePending;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ dispose() {
|
|
|
+
|
|
|
+ URL.revokeObjectURL( this.workerSourceURL );
|
|
|
+ this.workerPool.dispose();
|
|
|
+ return this;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ /* CONSTANTS */
|
|
|
+
|
|
|
+
|
|
|
+ KTX2Loader.BasisFormat = {
|
|
|
+ ETC1S: 0,
|
|
|
+ UASTC_4x4: 1
|
|
|
+ };
|
|
|
+ KTX2Loader.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
|
|
|
+ };
|
|
|
+ KTX2Loader.EngineFormat = {
|
|
|
+ RGBAFormat: THREE.RGBAFormat,
|
|
|
+ RGBA_ASTC_4x4_Format: THREE.RGBA_ASTC_4x4_Format,
|
|
|
+ RGBA_BPTC_Format: THREE.RGBA_BPTC_Format,
|
|
|
+ RGBA_ETC2_EAC_Format: THREE.RGBA_ETC2_EAC_Format,
|
|
|
+ RGBA_PVRTC_4BPPV1_Format: THREE.RGBA_PVRTC_4BPPV1_Format,
|
|
|
+ RGBA_S3TC_DXT5_Format: THREE.RGBA_S3TC_DXT5_Format,
|
|
|
+ RGB_ETC1_Format: THREE.RGB_ETC1_Format,
|
|
|
+ RGB_ETC2_Format: THREE.RGB_ETC2_Format,
|
|
|
+ RGB_PVRTC_4BPPV1_Format: THREE.RGB_PVRTC_4BPPV1_Format,
|
|
|
+ RGB_S3TC_DXT1_Format: THREE.RGB_S3TC_DXT1_Format
|
|
|
+ };
|
|
|
+ /* WEB WORKER */
|
|
|
+
|
|
|
+ KTX2Loader.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( 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( 'THREE.KTX2Loader: 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( 'THREE.KTX2Loader: 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( 'THREE.KTX2Loader: Invalid texture' );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( ! ktx2File.startTranscoding() ) {
|
|
|
+
|
|
|
+ cleanup();
|
|
|
+ throw new Error( 'THREE.KTX2Loader: .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( 'THREE.KTX2Loader: .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, TranscoderFormat.ETC1 ],
|
|
|
+ engineFormat: [ EngineFormat.RGB_ETC1_Format, 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 ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue;
|
|
|
+ transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ];
|
|
|
+ engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ];
|
|
|
+ return {
|
|
|
+ transcoderFormat,
|
|
|
+ engineFormat
|
|
|
+ };
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ console.warn( 'THREE.KTX2Loader: 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;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ THREE.KTX2Loader = KTX2Loader;
|
|
|
+
|
|
|
+} )();
|