浏览代码

Merge pull request #21131 from donmccurdy/feat-basistextureloader-reuse

BasisTextureLoader: Refactor
Mr.doob 4 年之前
父节点
当前提交
f4f930cd72
共有 2 个文件被更改,包括 735 次插入280 次删除
  1. 365 140
      examples/js/loaders/BasisTextureLoader.js
  2. 370 140
      examples/jsm/loaders/BasisTextureLoader.js

+ 365 - 140
examples/js/loaders/BasisTextureLoader.js

@@ -22,15 +22,7 @@ THREE.BasisTextureLoader = function ( manager ) {
 	this.workerPool = [];
 	this.workerPool = [];
 	this.workerNextTaskID = 1;
 	this.workerNextTaskID = 1;
 	this.workerSourceURL = '';
 	this.workerSourceURL = '';
-	this.workerConfig = {
-		format: null,
-		astcSupported: false,
-		bptcSupported: false,
-		etcSupported: false,
-		dxtSupported: false,
-		pvrtcSupported: false,
-	};
-
+	this.workerConfig = null;
 };
 };
 
 
 THREE.BasisTextureLoader.taskCache = new WeakMap();
 THREE.BasisTextureLoader.taskCache = new WeakMap();
@@ -57,40 +49,15 @@ THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.
 
 
 	detectSupport: function ( renderer ) {
 	detectSupport: function ( renderer ) {
 
 
-		var config = this.workerConfig;
-
-		config.astcSupported = renderer.extensions.has( 'WEBGL_compressed_texture_astc' );
-		config.bptcSupported = renderer.extensions.has( 'EXT_texture_compression_bptc' );
-		config.etcSupported = renderer.extensions.has( 'WEBGL_compressed_texture_etc1' );
-		config.dxtSupported = renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' );
-		config.pvrtcSupported = renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' )
-			|| renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' );
-
-		if ( config.astcSupported ) {
-
-			config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4;
-
-		} else if ( config.bptcSupported ) {
-
-			config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC7_M5;
-
-		} else if ( config.dxtSupported ) {
-
-			config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3;
-
-		} else if ( config.pvrtcSupported ) {
-
-			config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA;
-
-		} else if ( config.etcSupported ) {
-
-			config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFETC1;
-
-		} else {
-
-			throw new Error( 'THREE.BasisTextureLoader: No suitable compressed texture format found.' );
-
-		}
+		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;
 		return this;
 
 
@@ -103,6 +70,8 @@ THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.
 		loader.setResponseType( 'arraybuffer' );
 		loader.setResponseType( 'arraybuffer' );
 		loader.setWithCredentials( this.withCredentials );
 		loader.setWithCredentials( this.withCredentials );
 
 
+		var texture = new THREE.CompressedTexture();
+
 		loader.load( url, ( buffer ) => {
 		loader.load( url, ( buffer ) => {
 
 
 			// Check for an existing task using this buffer. A transferred buffer cannot be transferred
 			// Check for an existing task using this buffer. A transferred buffer cannot be transferred
@@ -115,25 +84,58 @@ THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.
 
 
 			}
 			}
 
 
-			this._createTexture( buffer, url )
-				.then( onLoad )
+			this._createTexture( [ buffer ] )
+				.then( function ( _texture ) {
+
+					texture.copy( _texture );
+					texture.needsUpdate = true;
+
+					if ( onLoad ) onLoad( texture );
+
+				} )
 				.catch( onError );
 				.catch( onError );
 
 
 		}, onProgress, onError );
 		}, onProgress, onError );
 
 
+		return texture;
+
+	},
+
+	/** Low-level transcoding API, exposed for use by THREE.KTX2Loader. */
+	parseInternalAsync: function ( options ) {
+
+		var { levels, hasAlpha, basisFormat } = options;
+
+		var buffers = new Set();
+
+		for ( var i = 0; i < levels.length; i ++ ) {
+
+			buffers.add( levels[ i ].data.buffer );
+
+		}
+
+		return this._createTexture( Array.from( buffers ), { ...options, lowLevel: true } );
+
 	},
 	},
 
 
 	/**
 	/**
-	 * @param	{ArrayBuffer} buffer
-	 * @param	{string} url
+	 * @param {ArrayBuffer[]} buffers
+	 * @param {object?} config
 	 * @return {Promise<THREE.CompressedTexture>}
 	 * @return {Promise<THREE.CompressedTexture>}
 	 */
 	 */
-	_createTexture: function ( buffer, url ) {
+	_createTexture: function ( buffers, config ) {
 
 
 		var worker;
 		var worker;
 		var taskID;
 		var taskID;
 
 
-		var taskCost = buffer.byteLength;
+		var taskConfig = config || {};
+		var taskCost = 0;
+
+		for ( var i = 0; i < buffers.length; i ++ ) {
+
+			taskCost += buffers[ i ].byteLength;
+
+		}
 
 
 		var texturePending = this._allocateWorker( taskCost )
 		var texturePending = this._allocateWorker( taskCost )
 			.then( ( _worker ) => {
 			.then( ( _worker ) => {
@@ -145,7 +147,7 @@ THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.
 
 
 					worker._callbacks[ taskID ] = { resolve, reject };
 					worker._callbacks[ taskID ] = { resolve, reject };
 
 
-					worker.postMessage( { type: 'transcode', id: taskID, buffer }, [ buffer ] );
+					worker.postMessage( { type: 'transcode', id: taskID, buffers: buffers, taskConfig: taskConfig }, buffers );
 
 
 				} );
 				} );
 
 
@@ -154,36 +156,9 @@ THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.
 
 
 				var config = this.workerConfig;
 				var config = this.workerConfig;
 
 
-				var { width, height, mipmaps, format } = message;
-
-				var texture;
-
-				switch ( format ) {
-
-					case THREE.BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4:
-						texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGBA_ASTC_4x4_Format );
-						break;
-					case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC7_M5:
-						texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGBA_BPTC_Format );
-						break;
-					case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1:
-					case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3:
-						texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.BasisTextureLoader.DXT_FORMAT_MAP[ config.format ], THREE.UnsignedByteType );
-						break;
-					case THREE.BasisTextureLoader.BASIS_FORMAT.cTFETC1:
-						texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_ETC1_Format );
-						break;
-					case THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB:
-						texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_PVRTC_4BPPV1_Format );
-						break;
-					case THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA:
-						texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGBA_PVRTC_4BPPV1_Format );
-						break;
-					default:
-						throw new Error( 'THREE.BasisTextureLoader: No supported format available.' );
-
-				}
+				var { mipmaps, width, height, format } = message;
 
 
+				var texture = new THREE.CompressedTexture( mipmaps, width, height, format, THREE.UnsignedByteType );
 				texture.minFilter = mipmaps.length === 1 ? THREE.LinearFilter : THREE.LinearMipmapLinearFilter;
 				texture.minFilter = mipmaps.length === 1 ? THREE.LinearFilter : THREE.LinearMipmapLinearFilter;
 				texture.magFilter = THREE.LinearFilter;
 				texture.magFilter = THREE.LinearFilter;
 				texture.generateMipmaps = false;
 				texture.generateMipmaps = false;
@@ -208,12 +183,7 @@ THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.
 			} );
 			} );
 
 
 		// Cache the task result.
 		// Cache the task result.
-		THREE.BasisTextureLoader.taskCache.set( buffer, {
-
-			url: url,
-			promise: texturePending
-
-		} );
+		THREE.BasisTextureLoader.taskCache.set( buffers[ 0 ], { promise: texturePending } );
 
 
 		return texturePending;
 		return texturePending;
 
 
@@ -250,6 +220,10 @@ THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.
 					var fn = THREE.BasisTextureLoader.BasisWorker.toString();
 					var fn = THREE.BasisTextureLoader.BasisWorker.toString();
 
 
 					var body = [
 					var body = [
+						'/* constants */',
+						'var _EngineFormat = ' + JSON.stringify( BasisTextureLoader.EngineFormat ),
+						'var _TranscoderFormat = ' + JSON.stringify( BasisTextureLoader.TranscoderFormat ),
+						'var _BasisFormat = ' + JSON.stringify( BasisTextureLoader.BasisFormat ),
 						'/* basis_transcoder.js */',
 						'/* basis_transcoder.js */',
 						jsContent,
 						jsContent,
 						'/* worker */',
 						'/* worker */',
@@ -345,39 +319,44 @@ THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.
 
 
 /* CONSTANTS */
 /* CONSTANTS */
 
 
-THREE.BasisTextureLoader.BASIS_FORMAT = {
-	cTFETC1: 0,
-	cTFETC2: 1,
-	cTFBC1: 2,
-	cTFBC3: 3,
-	cTFBC4: 4,
-	cTFBC5: 5,
-	cTFBC7_M6_OPAQUE_ONLY: 6,
-	cTFBC7_M5: 7,
-	cTFPVRTC1_4_RGB: 8,
-	cTFPVRTC1_4_RGBA: 9,
-	cTFASTC_4x4: 10,
-	cTFATC_RGB: 11,
-	cTFATC_RGBA_INTERPOLATED_ALPHA: 12,
-	cTFRGBA32: 13,
-	cTFRGB565: 14,
-	cTFBGR565: 15,
-	cTFRGBA4444: 16,
+THREE.BasisTextureLoader.BasisFormat = {
+	ETC1S: 0,
+	UASTC_4x4: 1,
 };
 };
 
 
-// DXT formats, from:
-// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
-THREE.BasisTextureLoader.DXT_FORMAT = {
-	COMPRESSED_RGB_S3TC_DXT1_EXT: 0x83F0,
-	COMPRESSED_RGBA_S3TC_DXT1_EXT: 0x83F1,
-	COMPRESSED_RGBA_S3TC_DXT3_EXT: 0x83F2,
-	COMPRESSED_RGBA_S3TC_DXT5_EXT: 0x83F3,
+THREE.BasisTextureLoader.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,
 };
 };
-THREE.BasisTextureLoader.DXT_FORMAT_MAP = {};
-THREE.BasisTextureLoader.DXT_FORMAT_MAP[ THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1 ] =
-	THREE.BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGB_S3TC_DXT1_EXT;
-THREE.BasisTextureLoader.DXT_FORMAT_MAP[ THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3 ] =
-	THREE.BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGBA_S3TC_DXT5_EXT;
+
+THREE.BasisTextureLoader.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 */
 /* WEB WORKER */
 
 
@@ -385,7 +364,11 @@ THREE.BasisTextureLoader.BasisWorker = function () {
 
 
 	var config;
 	var config;
 	var transcoderPending;
 	var transcoderPending;
-	var _BasisFile;
+	var BasisModule;
+
+	var EngineFormat = _EngineFormat; // eslint-disable-line no-undef
+	var TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef
+	var BasisFormat = _BasisFormat; // eslint-disable-line no-undef
 
 
 	onmessage = function ( e ) {
 	onmessage = function ( e ) {
 
 
@@ -403,7 +386,9 @@ THREE.BasisTextureLoader.BasisWorker = function () {
 
 
 					try {
 					try {
 
 
-						var { width, height, hasAlpha, mipmaps, format } = transcode( message.buffer );
+						var { width, height, hasAlpha, mipmaps, format } = message.taskConfig.lowLevel
+							? transcodeLowLevel( message.taskConfig )
+							: transcode( message.buffers[ 0 ] );
 
 
 						var buffers = [];
 						var buffers = [];
 
 
@@ -432,7 +417,6 @@ THREE.BasisTextureLoader.BasisWorker = function () {
 
 
 	function init( wasmBinary ) {
 	function init( wasmBinary ) {
 
 
-		var BasisModule;
 		transcoderPending = new Promise( ( resolve ) => {
 		transcoderPending = new Promise( ( resolve ) => {
 
 
 			BasisModule = { wasmBinary, onRuntimeInitialized: resolve };
 			BasisModule = { wasmBinary, onRuntimeInitialized: resolve };
@@ -440,20 +424,119 @@ THREE.BasisTextureLoader.BasisWorker = function () {
 
 
 		} ).then( () => {
 		} ).then( () => {
 
 
-			var { BasisFile, initializeBasis } = BasisModule;
+			BasisModule.initializeBasis();
+
+		} );
+
+	}
 
 
-			_BasisFile = BasisFile;
+	function transcodeLowLevel ( taskConfig ) {
 
 
-			initializeBasis();
+		var { basisFormat, width, height, hasAlpha } = taskConfig;
 
 
-		} );
+		var { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha );
+
+		var blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat );
+
+		assert( BasisModule.isFormatSupported( transcoderFormat ), 'THREE.BasisTextureLoader: Unsupported format.' );
+
+		var mipmaps = [];
+
+		if ( basisFormat === BasisFormat.ETC1S ) {
+
+			var transcoder = new BasisModule.LowLevelETC1SImageTranscoder();
+
+			var { endpointCount, endpointsData, selectorCount, selectorsData, tablesData } = taskConfig.globalData;
+
+			try {
+
+				var ok;
+
+				ok = transcoder.decodePalettes( endpointCount, endpointsData, selectorCount, selectorsData );
+
+				assert( ok, 'THREE.BasisTextureLoader: decodePalettes() failed.' );
+
+				ok = transcoder.decodeTables( tablesData );
+
+				assert( ok, 'THREE.BasisTextureLoader: decodeTables() failed.' );
+
+				for ( var i = 0; i < taskConfig.levels.length; i ++ ) {
+
+					var level = taskConfig.levels[ i ];
+					var imageDesc = taskConfig.globalData.imageDescs[ i ];
+
+					var dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height );
+					var dst = new Uint8Array( dstByteLength );
+
+					ok = transcoder.transcodeImage(
+						transcoderFormat,
+						dst, dstByteLength / blockByteLength,
+						level.data,
+						getWidthInBlocks( transcoderFormat, level.width ),
+						getHeightInBlocks( transcoderFormat, level.height ),
+						level.width, level.height, level.index,
+						imageDesc.rgbSliceByteOffset, imageDesc.rgbSliceByteLength,
+						imageDesc.alphaSliceByteOffset, imageDesc.alphaSliceByteLength,
+						imageDesc.imageFlags,
+						hasAlpha,
+						false,
+						0, 0
+					);
+
+					assert( ok, 'THREE.BasisTextureLoader: transcodeImage() failed for level ' + level.index + '.' );
+
+					mipmaps.push( { data: dst, width: level.width, height: level.height } );
+
+				}
+
+			} finally {
+
+				transcoder.delete();
+
+			}
+
+		} else {
+
+			for ( var i = 0; i < taskConfig.levels.length; i ++ ) {
+
+				var level = taskConfig.levels[ i ];
+
+				var dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height );
+				var dst = new Uint8Array( dstByteLength );
+
+				var ok = BasisModule.transcodeUASTCImage(
+					transcoderFormat,
+					dst, dstByteLength / blockByteLength,
+					level.data,
+					getWidthInBlocks( transcoderFormat, level.width ),
+					getHeightInBlocks( transcoderFormat, level.height ),
+					level.width, level.height, level.index,
+					0,
+					level.data.byteLength,
+					0,
+					hasAlpha,
+					false,
+					0, 0,
+					-1, -1
+				);
+
+				assert( ok, 'THREE.BasisTextureLoader: transcodeUASTCImage() failed for level ' + level.index + '.' );
+
+				mipmaps.push( { data: dst, width: level.width, height: level.height } );
+
+			}
+
+		}
+
+		return { width, height, hasAlpha, mipmaps, format: engineFormat };
 
 
 	}
 	}
 
 
 	function transcode( buffer ) {
 	function transcode( buffer ) {
 
 
-		var basisFile = new _BasisFile( new Uint8Array( buffer ) );
+		var basisFile = new BasisModule.BasisFile( new Uint8Array( buffer ) );
 
 
+		var basisFormat = basisFile.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;
 		var width = basisFile.getImageWidth( 0, 0 );
 		var width = basisFile.getImageWidth( 0, 0 );
 		var height = basisFile.getImageHeight( 0, 0 );
 		var height = basisFile.getImageHeight( 0, 0 );
 		var levels = basisFile.getNumLevels( 0 );
 		var levels = basisFile.getNumLevels( 0 );
@@ -466,24 +549,12 @@ THREE.BasisTextureLoader.BasisWorker = function () {
 
 
 		}
 		}
 
 
-		if ( ! hasAlpha ) {
-
-			switch ( config.format ) {
-
-				case 9: // Hardcoded: THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA
-					config.format = 8; // Hardcoded: THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB;
-					break;
-				default:
-					break;
-
-			}
-
-		}
+		var { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha );
 
 
 		if ( ! width || ! height || ! levels ) {
 		if ( ! width || ! height || ! levels ) {
 
 
 			cleanup();
 			cleanup();
-			throw new Error( 'THREE.BasisTextureLoader:	Invalid .basis file' );
+			throw new Error( 'THREE.BasisTextureLoader:	Invalid texture' );
 
 
 		}
 		}
 
 
@@ -500,13 +571,13 @@ THREE.BasisTextureLoader.BasisWorker = function () {
 
 
 			var mipWidth = basisFile.getImageWidth( 0, mip );
 			var mipWidth = basisFile.getImageWidth( 0, mip );
 			var mipHeight = basisFile.getImageHeight( 0, mip );
 			var mipHeight = basisFile.getImageHeight( 0, mip );
-			var dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, config.format ) );
+			var dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, transcoderFormat ) );
 
 
 			var status = basisFile.transcodeImage(
 			var status = basisFile.transcodeImage(
 				dst,
 				dst,
 				0,
 				0,
 				mip,
 				mip,
-				config.format,
+				transcoderFormat,
 				0,
 				0,
 				hasAlpha
 				hasAlpha
 			);
 			);
@@ -524,7 +595,161 @@ THREE.BasisTextureLoader.BasisWorker = function () {
 
 
 		cleanup();
 		cleanup();
 
 
-		return { width, height, hasAlpha, mipmaps, format: config.format };
+		return { width, height, hasAlpha, mipmaps, format: engineFormat };
+
+	}
+
+	//
+
+	// 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.
+	var 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,
+		},
+	];
+
+	var ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { return a.priorityETC1S - b.priorityETC1S; } );
+	var UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { return a.priorityUASTC - b.priorityUASTC; } );
+
+	function getTranscoderFormat ( basisFormat, width, height, hasAlpha ) {
+
+		var transcoderFormat;
+		var engineFormat;
+
+		var options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;
+
+		for ( var i = 0; i < options.length; i ++ ) {
+
+			var 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.BasisTextureLoader: No suitable compressed texture format found. Decoding to RGBA32.' );
+
+		transcoderFormat = TranscoderFormat.RGBA32;
+		engineFormat = EngineFormat.RGBAFormat;
+
+		return { transcoderFormat, engineFormat };
+
+	}
+
+	function assert ( ok, message ) {
+
+		if ( ! ok ) throw new Error( message );
+
+	}
+
+	function getWidthInBlocks ( transcoderFormat, width ) {
+
+		return Math.ceil( width / BasisModule.getFormatBlockWidth( transcoderFormat ) );
+
+	}
+
+	function getHeightInBlocks ( transcoderFormat, height ) {
+
+		return Math.ceil( height / BasisModule.getFormatBlockHeight( transcoderFormat ) );
+
+	}
+
+	function getTranscodedImageByteLength ( transcoderFormat, width, height ) {
+
+		var blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat );
+
+		if ( BasisModule.formatIsUncompressed( transcoderFormat ) ) {
+
+			return width * height * blockByteLength;
+
+		}
+
+		if ( transcoderFormat === TranscoderFormat.PVRTC1_4_RGB
+				|| transcoderFormat === TranscoderFormat.PVRTC1_4_RGBA ) {
+
+			// GL requires extra padding for very small textures:
+			// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
+			var paddedWidth = ( width + 3 ) & ~ 3;
+			var paddedHeight = ( height + 3 ) & ~ 3;
+
+			return ( Math.max( 8, paddedWidth ) * Math.max( 8, paddedHeight ) * 4 + 7 ) / 8;
+
+		}
+
+		return ( getWidthInBlocks( transcoderFormat, width )
+			* getHeightInBlocks( transcoderFormat, height )
+			* blockByteLength );
+
+	}
+
+	function isPowerOfTwo ( value ) {
+
+		if ( value <= 2 ) return true;
+
+		return ( value & ( value - 1 ) ) === 0 && value !== 0;
 
 
 	}
 	}
 
 

+ 370 - 140
examples/jsm/loaders/BasisTextureLoader.js

@@ -4,11 +4,16 @@ import {
 	LinearFilter,
 	LinearFilter,
 	LinearMipmapLinearFilter,
 	LinearMipmapLinearFilter,
 	Loader,
 	Loader,
+	RGBAFormat,
 	RGBA_ASTC_4x4_Format,
 	RGBA_ASTC_4x4_Format,
 	RGBA_BPTC_Format,
 	RGBA_BPTC_Format,
+	RGBA_ETC2_EAC_Format,
 	RGBA_PVRTC_4BPPV1_Format,
 	RGBA_PVRTC_4BPPV1_Format,
+	RGBA_S3TC_DXT5_Format,
 	RGB_ETC1_Format,
 	RGB_ETC1_Format,
+	RGB_ETC2_Format,
 	RGB_PVRTC_4BPPV1_Format,
 	RGB_PVRTC_4BPPV1_Format,
+	RGB_S3TC_DXT1_Format,
 	UnsignedByteType
 	UnsignedByteType
 } from '../../../build/three.module.js';
 } from '../../../build/three.module.js';
 
 
@@ -36,15 +41,7 @@ var BasisTextureLoader = function ( manager ) {
 	this.workerPool = [];
 	this.workerPool = [];
 	this.workerNextTaskID = 1;
 	this.workerNextTaskID = 1;
 	this.workerSourceURL = '';
 	this.workerSourceURL = '';
-	this.workerConfig = {
-		format: null,
-		astcSupported: false,
-		bptcSupported: false,
-		etcSupported: false,
-		dxtSupported: false,
-		pvrtcSupported: false,
-	};
-
+	this.workerConfig = null;
 };
 };
 
 
 BasisTextureLoader.taskCache = new WeakMap();
 BasisTextureLoader.taskCache = new WeakMap();
@@ -71,40 +68,15 @@ BasisTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 
 
 	detectSupport: function ( renderer ) {
 	detectSupport: function ( renderer ) {
 
 
-		var config = this.workerConfig;
-
-		config.astcSupported = renderer.extensions.has( 'WEBGL_compressed_texture_astc' );
-		config.bptcSupported = renderer.extensions.has( 'EXT_texture_compression_bptc' );
-		config.etcSupported = renderer.extensions.has( 'WEBGL_compressed_texture_etc1' );
-		config.dxtSupported = renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' );
-		config.pvrtcSupported = renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' )
-			|| renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' );
-
-		if ( config.astcSupported ) {
-
-			config.format = BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4;
-
-		} else if ( config.bptcSupported ) {
-
-			config.format = BasisTextureLoader.BASIS_FORMAT.cTFBC7_M5;
-
-		} else if ( config.dxtSupported ) {
-
-			config.format = BasisTextureLoader.BASIS_FORMAT.cTFBC3;
-
-		} else if ( config.pvrtcSupported ) {
-
-			config.format = BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA;
-
-		} else if ( config.etcSupported ) {
-
-			config.format = BasisTextureLoader.BASIS_FORMAT.cTFETC1;
-
-		} else {
-
-			throw new Error( 'THREE.BasisTextureLoader: No suitable compressed texture format found.' );
-
-		}
+		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;
 		return this;
 
 
@@ -117,6 +89,8 @@ BasisTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 		loader.setResponseType( 'arraybuffer' );
 		loader.setResponseType( 'arraybuffer' );
 		loader.setWithCredentials( this.withCredentials );
 		loader.setWithCredentials( this.withCredentials );
 
 
+		var texture = new CompressedTexture();
+
 		loader.load( url, ( buffer ) => {
 		loader.load( url, ( buffer ) => {
 
 
 			// Check for an existing task using this buffer. A transferred buffer cannot be transferred
 			// Check for an existing task using this buffer. A transferred buffer cannot be transferred
@@ -129,25 +103,58 @@ BasisTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 
 
 			}
 			}
 
 
-			this._createTexture( buffer, url )
-				.then( onLoad )
+			this._createTexture( [ buffer ] )
+				.then( function ( _texture ) {
+
+					texture.copy( _texture );
+					texture.needsUpdate = true;
+
+					if ( onLoad ) onLoad( texture );
+
+				} )
 				.catch( onError );
 				.catch( onError );
 
 
 		}, onProgress, onError );
 		}, onProgress, onError );
 
 
+		return texture;
+
+	},
+
+	/** Low-level transcoding API, exposed for use by KTX2Loader. */
+	parseInternalAsync: function ( options ) {
+
+		var { levels, hasAlpha, basisFormat } = options;
+
+		var buffers = new Set();
+
+		for ( var i = 0; i < levels.length; i ++ ) {
+
+			buffers.add( levels[ i ].data.buffer );
+
+		}
+
+		return this._createTexture( Array.from( buffers ), { ...options, lowLevel: true } );
+
 	},
 	},
 
 
 	/**
 	/**
-	 * @param	{ArrayBuffer} buffer
-	 * @param	{string} url
+	 * @param {ArrayBuffer[]} buffers
+	 * @param {object?} config
 	 * @return {Promise<CompressedTexture>}
 	 * @return {Promise<CompressedTexture>}
 	 */
 	 */
-	_createTexture: function ( buffer, url ) {
+	_createTexture: function ( buffers, config ) {
 
 
 		var worker;
 		var worker;
 		var taskID;
 		var taskID;
 
 
-		var taskCost = buffer.byteLength;
+		var taskConfig = config || {};
+		var taskCost = 0;
+
+		for ( var i = 0; i < buffers.length; i ++ ) {
+
+			taskCost += buffers[ i ].byteLength;
+
+		}
 
 
 		var texturePending = this._allocateWorker( taskCost )
 		var texturePending = this._allocateWorker( taskCost )
 			.then( ( _worker ) => {
 			.then( ( _worker ) => {
@@ -159,7 +166,7 @@ BasisTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 
 
 					worker._callbacks[ taskID ] = { resolve, reject };
 					worker._callbacks[ taskID ] = { resolve, reject };
 
 
-					worker.postMessage( { type: 'transcode', id: taskID, buffer }, [ buffer ] );
+					worker.postMessage( { type: 'transcode', id: taskID, buffers: buffers, taskConfig: taskConfig }, buffers );
 
 
 				} );
 				} );
 
 
@@ -168,36 +175,9 @@ BasisTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 
 
 				var config = this.workerConfig;
 				var config = this.workerConfig;
 
 
-				var { width, height, mipmaps, format } = message;
-
-				var texture;
-
-				switch ( format ) {
-
-					case BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4:
-						texture = new CompressedTexture( mipmaps, width, height, RGBA_ASTC_4x4_Format );
-						break;
-					case BasisTextureLoader.BASIS_FORMAT.cTFBC7_M5:
-						texture = new CompressedTexture( mipmaps, width, height, RGBA_BPTC_Format );
-						break;
-					case BasisTextureLoader.BASIS_FORMAT.cTFBC1:
-					case BasisTextureLoader.BASIS_FORMAT.cTFBC3:
-						texture = new CompressedTexture( mipmaps, width, height, BasisTextureLoader.DXT_FORMAT_MAP[ config.format ], UnsignedByteType );
-						break;
-					case BasisTextureLoader.BASIS_FORMAT.cTFETC1:
-						texture = new CompressedTexture( mipmaps, width, height, RGB_ETC1_Format );
-						break;
-					case BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB:
-						texture = new CompressedTexture( mipmaps, width, height, RGB_PVRTC_4BPPV1_Format );
-						break;
-					case BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA:
-						texture = new CompressedTexture( mipmaps, width, height, RGBA_PVRTC_4BPPV1_Format );
-						break;
-					default:
-						throw new Error( 'THREE.BasisTextureLoader: No supported format available.' );
-
-				}
+				var { mipmaps, width, height, format } = message;
 
 
+				var texture = new CompressedTexture( mipmaps, width, height, format, UnsignedByteType );
 				texture.minFilter = mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter;
 				texture.minFilter = mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter;
 				texture.magFilter = LinearFilter;
 				texture.magFilter = LinearFilter;
 				texture.generateMipmaps = false;
 				texture.generateMipmaps = false;
@@ -222,12 +202,7 @@ BasisTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 			} );
 			} );
 
 
 		// Cache the task result.
 		// Cache the task result.
-		BasisTextureLoader.taskCache.set( buffer, {
-
-			url: url,
-			promise: texturePending
-
-		} );
+		BasisTextureLoader.taskCache.set( buffers[ 0 ], { promise: texturePending } );
 
 
 		return texturePending;
 		return texturePending;
 
 
@@ -264,6 +239,10 @@ BasisTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 					var fn = BasisTextureLoader.BasisWorker.toString();
 					var fn = BasisTextureLoader.BasisWorker.toString();
 
 
 					var body = [
 					var body = [
+						'/* constants */',
+						'var _EngineFormat = ' + JSON.stringify( BasisTextureLoader.EngineFormat ),
+						'var _TranscoderFormat = ' + JSON.stringify( BasisTextureLoader.TranscoderFormat ),
+						'var _BasisFormat = ' + JSON.stringify( BasisTextureLoader.BasisFormat ),
 						'/* basis_transcoder.js */',
 						'/* basis_transcoder.js */',
 						jsContent,
 						jsContent,
 						'/* worker */',
 						'/* worker */',
@@ -359,39 +338,44 @@ BasisTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 
 
 /* CONSTANTS */
 /* CONSTANTS */
 
 
-BasisTextureLoader.BASIS_FORMAT = {
-	cTFETC1: 0,
-	cTFETC2: 1,
-	cTFBC1: 2,
-	cTFBC3: 3,
-	cTFBC4: 4,
-	cTFBC5: 5,
-	cTFBC7_M6_OPAQUE_ONLY: 6,
-	cTFBC7_M5: 7,
-	cTFPVRTC1_4_RGB: 8,
-	cTFPVRTC1_4_RGBA: 9,
-	cTFASTC_4x4: 10,
-	cTFATC_RGB: 11,
-	cTFATC_RGBA_INTERPOLATED_ALPHA: 12,
-	cTFRGBA32: 13,
-	cTFRGB565: 14,
-	cTFBGR565: 15,
-	cTFRGBA4444: 16,
+BasisTextureLoader.BasisFormat = {
+	ETC1S: 0,
+	UASTC_4x4: 1,
 };
 };
 
 
-// DXT formats, from:
-// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
-BasisTextureLoader.DXT_FORMAT = {
-	COMPRESSED_RGB_S3TC_DXT1_EXT: 0x83F0,
-	COMPRESSED_RGBA_S3TC_DXT1_EXT: 0x83F1,
-	COMPRESSED_RGBA_S3TC_DXT3_EXT: 0x83F2,
-	COMPRESSED_RGBA_S3TC_DXT5_EXT: 0x83F3,
+BasisTextureLoader.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,
 };
 };
-BasisTextureLoader.DXT_FORMAT_MAP = {};
-BasisTextureLoader.DXT_FORMAT_MAP[ BasisTextureLoader.BASIS_FORMAT.cTFBC1 ] =
-	BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGB_S3TC_DXT1_EXT;
-BasisTextureLoader.DXT_FORMAT_MAP[ BasisTextureLoader.BASIS_FORMAT.cTFBC3 ] =
-	BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGBA_S3TC_DXT5_EXT;
+
+BasisTextureLoader.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 */
 /* WEB WORKER */
 
 
@@ -399,7 +383,11 @@ BasisTextureLoader.BasisWorker = function () {
 
 
 	var config;
 	var config;
 	var transcoderPending;
 	var transcoderPending;
-	var _BasisFile;
+	var BasisModule;
+
+	var EngineFormat = _EngineFormat; // eslint-disable-line no-undef
+	var TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef
+	var BasisFormat = _BasisFormat; // eslint-disable-line no-undef
 
 
 	onmessage = function ( e ) {
 	onmessage = function ( e ) {
 
 
@@ -417,7 +405,9 @@ BasisTextureLoader.BasisWorker = function () {
 
 
 					try {
 					try {
 
 
-						var { width, height, hasAlpha, mipmaps, format } = transcode( message.buffer );
+						var { width, height, hasAlpha, mipmaps, format } = message.taskConfig.lowLevel
+							? transcodeLowLevel( message.taskConfig )
+							: transcode( message.buffers[ 0 ] );
 
 
 						var buffers = [];
 						var buffers = [];
 
 
@@ -446,7 +436,6 @@ BasisTextureLoader.BasisWorker = function () {
 
 
 	function init( wasmBinary ) {
 	function init( wasmBinary ) {
 
 
-		var BasisModule;
 		transcoderPending = new Promise( ( resolve ) => {
 		transcoderPending = new Promise( ( resolve ) => {
 
 
 			BasisModule = { wasmBinary, onRuntimeInitialized: resolve };
 			BasisModule = { wasmBinary, onRuntimeInitialized: resolve };
@@ -454,20 +443,119 @@ BasisTextureLoader.BasisWorker = function () {
 
 
 		} ).then( () => {
 		} ).then( () => {
 
 
-			var { BasisFile, initializeBasis } = BasisModule;
+			BasisModule.initializeBasis();
+
+		} );
+
+	}
 
 
-			_BasisFile = BasisFile;
+	function transcodeLowLevel ( taskConfig ) {
 
 
-			initializeBasis();
+		var { basisFormat, width, height, hasAlpha } = taskConfig;
 
 
-		} );
+		var { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha );
+
+		var blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat );
+
+		assert( BasisModule.isFormatSupported( transcoderFormat ), 'THREE.BasisTextureLoader: Unsupported format.' );
+
+		var mipmaps = [];
+
+		if ( basisFormat === BasisFormat.ETC1S ) {
+
+			var transcoder = new BasisModule.LowLevelETC1SImageTranscoder();
+
+			var { endpointCount, endpointsData, selectorCount, selectorsData, tablesData } = taskConfig.globalData;
+
+			try {
+
+				var ok;
+
+				ok = transcoder.decodePalettes( endpointCount, endpointsData, selectorCount, selectorsData );
+
+				assert( ok, 'THREE.BasisTextureLoader: decodePalettes() failed.' );
+
+				ok = transcoder.decodeTables( tablesData );
+
+				assert( ok, 'THREE.BasisTextureLoader: decodeTables() failed.' );
+
+				for ( var i = 0; i < taskConfig.levels.length; i ++ ) {
+
+					var level = taskConfig.levels[ i ];
+					var imageDesc = taskConfig.globalData.imageDescs[ i ];
+
+					var dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height );
+					var dst = new Uint8Array( dstByteLength );
+
+					ok = transcoder.transcodeImage(
+						transcoderFormat,
+						dst, dstByteLength / blockByteLength,
+						level.data,
+						getWidthInBlocks( transcoderFormat, level.width ),
+						getHeightInBlocks( transcoderFormat, level.height ),
+						level.width, level.height, level.index,
+						imageDesc.rgbSliceByteOffset, imageDesc.rgbSliceByteLength,
+						imageDesc.alphaSliceByteOffset, imageDesc.alphaSliceByteLength,
+						imageDesc.imageFlags,
+						hasAlpha,
+						false,
+						0, 0
+					);
+
+					assert( ok, 'THREE.BasisTextureLoader: transcodeImage() failed for level ' + level.index + '.' );
+
+					mipmaps.push( { data: dst, width: level.width, height: level.height } );
+
+				}
+
+			} finally {
+
+				transcoder.delete();
+
+			}
+
+		} else {
+
+			for ( var i = 0; i < taskConfig.levels.length; i ++ ) {
+
+				var level = taskConfig.levels[ i ];
+
+				var dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height );
+				var dst = new Uint8Array( dstByteLength );
+
+				var ok = BasisModule.transcodeUASTCImage(
+					transcoderFormat,
+					dst, dstByteLength / blockByteLength,
+					level.data,
+					getWidthInBlocks( transcoderFormat, level.width ),
+					getHeightInBlocks( transcoderFormat, level.height ),
+					level.width, level.height, level.index,
+					0,
+					level.data.byteLength,
+					0,
+					hasAlpha,
+					false,
+					0, 0,
+					-1, -1
+				);
+
+				assert( ok, 'THREE.BasisTextureLoader: transcodeUASTCImage() failed for level ' + level.index + '.' );
+
+				mipmaps.push( { data: dst, width: level.width, height: level.height } );
+
+			}
+
+		}
+
+		return { width, height, hasAlpha, mipmaps, format: engineFormat };
 
 
 	}
 	}
 
 
 	function transcode( buffer ) {
 	function transcode( buffer ) {
 
 
-		var basisFile = new _BasisFile( new Uint8Array( buffer ) );
+		var basisFile = new BasisModule.BasisFile( new Uint8Array( buffer ) );
 
 
+		var basisFormat = basisFile.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;
 		var width = basisFile.getImageWidth( 0, 0 );
 		var width = basisFile.getImageWidth( 0, 0 );
 		var height = basisFile.getImageHeight( 0, 0 );
 		var height = basisFile.getImageHeight( 0, 0 );
 		var levels = basisFile.getNumLevels( 0 );
 		var levels = basisFile.getNumLevels( 0 );
@@ -480,24 +568,12 @@ BasisTextureLoader.BasisWorker = function () {
 
 
 		}
 		}
 
 
-		if ( ! hasAlpha ) {
-
-			switch ( config.format ) {
-
-				case 9: // Hardcoded: BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA
-					config.format = 8; // Hardcoded: BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB;
-					break;
-				default:
-					break;
-
-			}
-
-		}
+		var { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha );
 
 
 		if ( ! width || ! height || ! levels ) {
 		if ( ! width || ! height || ! levels ) {
 
 
 			cleanup();
 			cleanup();
-			throw new Error( 'THREE.BasisTextureLoader:	Invalid .basis file' );
+			throw new Error( 'THREE.BasisTextureLoader:	Invalid texture' );
 
 
 		}
 		}
 
 
@@ -514,13 +590,13 @@ BasisTextureLoader.BasisWorker = function () {
 
 
 			var mipWidth = basisFile.getImageWidth( 0, mip );
 			var mipWidth = basisFile.getImageWidth( 0, mip );
 			var mipHeight = basisFile.getImageHeight( 0, mip );
 			var mipHeight = basisFile.getImageHeight( 0, mip );
-			var dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, config.format ) );
+			var dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, transcoderFormat ) );
 
 
 			var status = basisFile.transcodeImage(
 			var status = basisFile.transcodeImage(
 				dst,
 				dst,
 				0,
 				0,
 				mip,
 				mip,
-				config.format,
+				transcoderFormat,
 				0,
 				0,
 				hasAlpha
 				hasAlpha
 			);
 			);
@@ -538,7 +614,161 @@ BasisTextureLoader.BasisWorker = function () {
 
 
 		cleanup();
 		cleanup();
 
 
-		return { width, height, hasAlpha, mipmaps, format: config.format };
+		return { width, height, hasAlpha, mipmaps, format: engineFormat };
+
+	}
+
+	//
+
+	// 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.
+	var 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,
+		},
+	];
+
+	var ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { return a.priorityETC1S - b.priorityETC1S; } );
+	var UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { return a.priorityUASTC - b.priorityUASTC; } );
+
+	function getTranscoderFormat ( basisFormat, width, height, hasAlpha ) {
+
+		var transcoderFormat;
+		var engineFormat;
+
+		var options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;
+
+		for ( var i = 0; i < options.length; i ++ ) {
+
+			var 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.BasisTextureLoader: No suitable compressed texture format found. Decoding to RGBA32.' );
+
+		transcoderFormat = TranscoderFormat.RGBA32;
+		engineFormat = EngineFormat.RGBAFormat;
+
+		return { transcoderFormat, engineFormat };
+
+	}
+
+	function assert ( ok, message ) {
+
+		if ( ! ok ) throw new Error( message );
+
+	}
+
+	function getWidthInBlocks ( transcoderFormat, width ) {
+
+		return Math.ceil( width / BasisModule.getFormatBlockWidth( transcoderFormat ) );
+
+	}
+
+	function getHeightInBlocks ( transcoderFormat, height ) {
+
+		return Math.ceil( height / BasisModule.getFormatBlockHeight( transcoderFormat ) );
+
+	}
+
+	function getTranscodedImageByteLength ( transcoderFormat, width, height ) {
+
+		var blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat );
+
+		if ( BasisModule.formatIsUncompressed( transcoderFormat ) ) {
+
+			return width * height * blockByteLength;
+
+		}
+
+		if ( transcoderFormat === TranscoderFormat.PVRTC1_4_RGB
+				|| transcoderFormat === TranscoderFormat.PVRTC1_4_RGBA ) {
+
+			// GL requires extra padding for very small textures:
+			// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
+			var paddedWidth = ( width + 3 ) & ~ 3;
+			var paddedHeight = ( height + 3 ) & ~ 3;
+
+			return ( Math.max( 8, paddedWidth ) * Math.max( 8, paddedHeight ) * 4 + 7 ) / 8;
+
+		}
+
+		return ( getWidthInBlocks( transcoderFormat, width )
+			* getHeightInBlocks( transcoderFormat, height )
+			* blockByteLength );
+
+	}
+
+	function isPowerOfTwo ( value ) {
+
+		if ( value <= 2 ) return true;
+
+		return ( value & ( value - 1 ) ) === 0 && value !== 0;
 
 
 	}
 	}