浏览代码

Merge pull request #16675 from donmccurdy/feat-basistextureloader-pt2

BasisTextureLoader: Documentation and cleanup.
Mr.doob 6 年之前
父节点
当前提交
232c1dcc53

+ 138 - 0
docs/examples/loaders/BasisTextureLoader.html

@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		[page:Loader] &rarr;
+		<h1>[name]</h1>
+
+		<p class="desc">
+			Loader for Basis Universal GPU Texture Codec.<br><br>
+
+			[link:https://github.com/BinomialLLC/basis_universal/ Basis Universal] is a
+			"supercompressed" GPU texture and texture video compression system that
+			outputs a highly compressed intermediate file format (.basis) that can be
+			quickly transcoded to a wide variety of GPU texture compression formats.
+		</p>
+
+		<p>
+			This loader parallelizes the transcoding process across a configurable number
+			of web workers, before transferring the transcoded compressed texture back
+			to the main thread. The required WASM transcoder and JS wrapper are available from the
+			[link:https://github.com/mrdoob/three.js/tree/dev/examples/js/libs/basis examples/js/libs/basis]
+			directory.
+		</p>
+
+		<h2>Example</h2>
+
+		<code>
+		var basisLoader = new THREE.BasisTextureLoader();
+		basisLoader.setTranscoderPath( 'examples/js/libs/basis/' );
+		basisLoader.detectSupport( renderer );
+		basisLoader.load( 'diffuse.basis', function ( texture ) {
+
+			var material = new THREE.MeshStandardMaterial( { map: texture } );
+
+		}, function () {
+
+			console.log( 'onProgress' );
+
+		}, function ( e ) {
+
+			console.error( e );
+
+		} );
+		</code>
+
+		[example:webgl_loader_texture_basis]
+
+		<h2>Browser compatibility</h2>
+
+		<p>
+			BasisTextureLoader transcodes input textures in '.basis' format to an
+			appropriate compressed texture format for the target device, where
+			possible. This allows the same source texture to be served across
+			desktop, Android, and iOS devices, and transcoded into DXT, ETC1, or
+			PVRTC1. Other output formats may be supported in the future.
+		</p>
+		<p>
+			This loader relies on ES6 Promises and Web Assembly, which are not
+			supported in IE11.
+		</p>
+
+		<br>
+		<hr>
+
+		<h2>Constructor</h2>
+
+		<h3>[name]( [param:LoadingManager manager] )</h3>
+		<p>
+		[page:LoadingManager manager] — The [page:LoadingManager] for the loader to use. Default is [page:LoadingManager THREE.DefaultLoadingManager].
+		</p>
+		<p>
+		Creates a new [name].
+		</p>
+
+		<h2>Methods</h2>
+
+		<h3>[method:null load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
+		<p>
+		[page:String url] — A string containing the path/URL of the <em>.basis</em> file.<br />
+		[page:Function onLoad] — A function to be called after the loading is successfully completed.<br />
+		[page:Function onProgress] — (optional) A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, that contains .[page:Integer total] and .[page:Integer loaded] bytes.<br />
+		[page:Function onError] — (optional) A function to be called if an error occurs during loading. The function receives error as an argument.<br />
+		</p>
+		<p>
+		Load from url and call the <em>onLoad</em> function with the transcoded [page:CompressedTexture].
+		</p>
+
+		<h3>[method:this detectSupport]( [param:WebGLRenderer renderer] )</h3>
+		<p>
+		[page:WebGLRenderer renderer] — A renderer instance.
+		</p>
+		<p>
+		Detects hardware support for available compressed texture formats, to determine
+		the output format for the transcoder. Must be called before loading a texture.
+		</p>
+
+		<h3>[method:this setCrossOrigin]( [param:String crossOrigin] )</h3>
+		<p>
+		[page:String crossOrigin] — Options are '', 'anonymous', or 'use-credentials'. Default is 'anonymous'.
+		</p>
+		<p>
+		Sets options for CORS requests.
+		</p>
+
+		<h3>[method:this setTranscoderPath]( [param:String path] )</h3>
+		<p>
+		[page:String path] — Path to folder containing the WASM transcoder and JS wrapper.
+		</p>
+		<p>
+		The WASM transcoder and JS wrapper are available from the
+		[link:https://github.com/mrdoob/three.js/tree/dev/examples/js/libs/basis examples/js/libs/basis]
+		directory.
+		</p>
+
+		<h3>[method:this setWorkerLimit]( [param:Number limit] )</h3>
+		<p>
+		[page:Number limit] — Maximum number of workers. Default is '4'.
+		</p>
+		<p>
+		Sets the maximum number of web workers to be allocated by this instance.
+		</p>
+
+		<h3>[method:this dispose]()</h3>
+		<p>
+		Disposes the loader object, de-allocating any Web Workers created.
+		</p>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/BasisTextureLoader.js examples/js/loaders/BasisTextureLoader.js]
+	</body>
+</html>

+ 1 - 0
docs/list.js

@@ -365,6 +365,7 @@ var list = {
 
 			"Loaders": {
 				"BabylonLoader": "examples/loaders/BabylonLoader",
+				"BasisTextureLoader": "examples/loaders/BasisTextureLoader",
 				"DRACOLoader": "examples/loaders/DRACOLoader",
 				"GLTFLoader": "examples/loaders/GLTFLoader",
 				"MMDLoader": "examples/loaders/MMDLoader",

+ 3 - 3
examples/js/libs/basis/README.md

@@ -22,15 +22,15 @@ basisLoader.setTranscoderPath( 'examples/js/libs/basis/' );
 basisLoader.detectSupport( renderer );
 basisLoader.load( 'diffuse.basis', function ( texture ) {
 
-  var material = new THREE.MeshStandardMaterial( { map: texture } );
+	var material = new THREE.MeshStandardMaterial( { map: texture } );
 
 }, function () {
 
-  console.log( 'onProgress' );
+	console.log( 'onProgress' );
 
 }, function ( e ) {
 
-  console.error( e );
+	console.error( e );
 
 } );
 ```

+ 146 - 74
examples/js/loaders/BasisTextureLoader.js

@@ -16,46 +16,66 @@
  * of web workers, before transferring the transcoded compressed texture back
  * to the main thread.
  */
-// TODO(donmccurdy): Don't use ES6 classes.
-THREE.BasisTextureLoader = class BasisTextureLoader {
+THREE.BasisTextureLoader = function ( manager ) {
 
-	constructor ( manager ) {
+	this.manager = manager || THREE.DefaultLoadingManager;
 
-		// TODO(donmccurdy): Loading manager is unused.
-		this.manager = manager || THREE.DefaultLoadingManager;
+	this.crossOrigin = 'anonymous';
 
-		this.transcoderPath = '';
-		this.transcoderBinary = null;
-		this.transcoderPending = null;
+	this.transcoderPath = '';
+	this.transcoderBinary = null;
+	this.transcoderPending = null;
 
-		this.workerLimit = 4;
-		this.workerPool = [];
-		this.workerNextTaskID = 1;
-		this.workerSourceURL = '';
-		this.workerConfig = {
-			format: null,
-			etcSupported: false,
-			dxtSupported: false,
-			pvrtcSupported: false,
-		};
+	this.workerLimit = 4;
+	this.workerPool = [];
+	this.workerNextTaskID = 1;
+	this.workerSourceURL = '';
+	this.workerConfig = {
+		format: null,
+		etcSupported: false,
+		dxtSupported: false,
+		pvrtcSupported: false,
+	};
 
-	}
+};
+
+THREE.BasisTextureLoader.prototype = {
+
+	constructor: THREE.BasisTextureLoader,
+
+	setCrossOrigin: function ( crossOrigin ) {
+
+		this.crossOrigin = crossOrigin;
 
-	setTranscoderPath ( path ) {
+		return this;
+
+	},
+
+	setTranscoderPath: function ( path ) {
 
 		this.transcoderPath = path;
 
-	}
+		return this;
+
+	},
+
+	setWorkerLimit: function ( workerLimit ) {
 
-	detectSupport ( renderer ) {
+		this.workerLimit = workerLimit;
+
+		return this;
+
+	},
+
+	detectSupport: function ( renderer ) {
 
 		var context = renderer.context;
 		var config = this.workerConfig;
 
-		config.etcSupported = !! context.getExtension('WEBGL_compressed_texture_etc1');
-		config.dxtSupported = !! context.getExtension('WEBGL_compressed_texture_s3tc');
-		config.pvrtcSupported = !! context.getExtension('WEBGL_compressed_texture_pvrtc')
-			|| !! context.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc');
+		config.etcSupported = !! context.getExtension( 'WEBGL_compressed_texture_etc1' );
+		config.dxtSupported = !! context.getExtension( 'WEBGL_compressed_texture_s3tc' );
+		config.pvrtcSupported = !! context.getExtension( 'WEBGL_compressed_texture_pvrtc' )
+			|| !! context.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' );
 
 		if ( config.etcSupported ) {
 
@@ -77,36 +97,44 @@ THREE.BasisTextureLoader = class BasisTextureLoader {
 
 		return this;
 
-	}
+	},
 
-	load ( url, onLoad, onProgress, onError ) {
+	load: function ( url, onLoad, onProgress, onError ) {
 
-		// TODO(donmccurdy): Use THREE.FileLoader.
-		fetch( url )
-			.then( ( res ) => res.arrayBuffer() )
-			.then( ( buffer ) => this._createTexture( buffer ) )
-			.then( onLoad )
-			.catch( onError );
+		var loader = new THREE.FileLoader( this.manager );
 
-	}
+		loader.setResponseType( 'arraybuffer' );
+
+		loader.load( url, ( buffer ) => {
+
+			this._createTexture( buffer )
+				.then( onLoad )
+				.catch( onError );
+
+		}, onProgress, onError );
+
+	},
 
 	/**
 	 * @param  {ArrayBuffer} buffer
 	 * @return {Promise<THREE.CompressedTexture>}
 	 */
-	_createTexture ( buffer ) {
+	_createTexture: function ( buffer ) {
+
+		var worker;
+		var taskID;
 
-		return this.getWorker()
-			.then( ( worker ) => {
+		var texturePending = this._getWorker()
+			.then( ( _worker ) => {
 
-				return new Promise( ( resolve ) => {
+				worker = _worker;
+				taskID = this.workerNextTaskID ++;
 
-					var taskID = this.workerNextTaskID++;
+				return new Promise( ( resolve, reject ) => {
 
-					worker._callbacks[ taskID ] = resolve;
+					worker._callbacks[ taskID ] = { resolve, reject };
 					worker._taskCosts[ taskID ] = buffer.byteLength;
 					worker._taskLoad += worker._taskCosts[ taskID ];
-					worker._taskCount++;
 
 					worker.postMessage( { type: 'transcode', id: taskID, buffer }, [ buffer ] );
 
@@ -146,20 +174,47 @@ THREE.BasisTextureLoader = class BasisTextureLoader {
 
 				return texture;
 
-			});
+			} );
 
-	}
+		texturePending
+			.finally( () => {
+
+				if ( worker && taskID ) {
+
+					worker._taskLoad -= worker._taskCosts[ taskID ];
+					delete worker._callbacks[ taskID ];
+					delete worker._taskCosts[ taskID ];
 
-	_initTranscoder () {
+				}
+
+			} );
+
+		return texturePending;
+
+	},
+
+	_initTranscoder: function () {
 
 		if ( ! this.transcoderBinary ) {
 
-			// TODO(donmccurdy): Use THREE.FileLoader.
-			var jsContent = fetch( this.transcoderPath + 'basis_transcoder.js' )
-				.then( ( response ) => response.text() );
+			// Load transcoder wrapper.
+			var jsLoader = new THREE.FileLoader( this.manager );
+			jsLoader.setPath( this.transcoderPath );
+			var jsContent = new Promise( ( resolve, reject ) => {
+
+				jsLoader.load( 'basis_transcoder.js', resolve, undefined, reject );
+
+			} );
 
-			var binaryContent = fetch( this.transcoderPath + 'basis_transcoder.wasm' )
-				.then( ( response ) => response.arrayBuffer() );
+			// Load transcoder WASM binary.
+			var binaryLoader = new THREE.FileLoader( this.manager );
+			binaryLoader.setPath( this.transcoderPath );
+			binaryLoader.setResponseType( 'arraybuffer' );
+			var binaryContent = new Promise( ( resolve, reject ) => {
+
+				binaryLoader.load( 'basis_transcoder.wasm', resolve, undefined, reject );
+
+			} );
 
 			this.transcoderPending = Promise.all( [ jsContent, binaryContent ] )
 				.then( ( [ jsContent, binaryContent ] ) => {
@@ -187,9 +242,9 @@ THREE.BasisTextureLoader = class BasisTextureLoader {
 
 		return this.transcoderPending;
 
-	}
+	},
 
-	getWorker () {
+	_getWorker: function () {
 
 		return this._initTranscoder().then( () => {
 
@@ -200,7 +255,6 @@ THREE.BasisTextureLoader = class BasisTextureLoader {
 				worker._callbacks = {};
 				worker._taskCosts = {};
 				worker._taskLoad = 0;
-				worker._taskCount = 0;
 
 				worker.postMessage( {
 					type: 'init',
@@ -215,24 +269,29 @@ THREE.BasisTextureLoader = class BasisTextureLoader {
 					switch ( message.type ) {
 
 						case 'transcode':
-							worker._callbacks[ message.id ]( message );
-							worker._taskLoad -= worker._taskCosts[ message.id ];
-							delete worker._callbacks[ message.id ];
-							delete worker._taskCosts[ message.id ];
+							worker._callbacks[ message.id ].resolve( message );
+							break;
+
+						case 'error':
+							worker._callbacks[ message.id ].reject( message );
 							break;
 
 						default:
-							throw new Error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' );
+							console.error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' );
 
 					}
 
-				}
+				};
 
 				this.workerPool.push( worker );
 
 			} else {
 
-				this.workerPool.sort( function ( a, b ) { return a._taskLoad > b._taskLoad ? -1 : 1; } );
+				this.workerPool.sort( function ( a, b ) {
+
+					return a._taskLoad > b._taskLoad ? - 1 : 1;
+
+				} );
 
 			}
 
@@ -240,11 +299,11 @@ THREE.BasisTextureLoader = class BasisTextureLoader {
 
 		} );
 
-	}
+	},
 
-	dispose () {
+	dispose: function () {
 
-		for ( var i = 0; i < this.workerPool.length; i++ ) {
+		for ( var i = 0; i < this.workerPool.length; i ++ ) {
 
 			this.workerPool[ i ].terminate();
 
@@ -252,8 +311,10 @@ THREE.BasisTextureLoader = class BasisTextureLoader {
 
 		this.workerPool.length = 0;
 
+		return this;
+
 	}
-}
+};
 
 /* CONSTANTS */
 
@@ -285,6 +346,7 @@ THREE.BasisTextureLoader.DXT_FORMAT_MAP[ THREE.BasisTextureLoader.BASIS_FORMAT.c
 /* WEB WORKER */
 
 THREE.BasisTextureLoader.BasisWorker = function () {
+
 	var config;
 	var transcoderPending;
 	var _BasisFile;
@@ -303,17 +365,27 @@ THREE.BasisTextureLoader.BasisWorker = function () {
 			case 'transcode':
 				transcoderPending.then( () => {
 
-					var { width, height, mipmaps } = transcode( message.buffer );
+					try {
 
-					var buffers = [];
+						var { width, height, mipmaps } = transcode( message.buffer );
 
-					for ( var i = 0; i < mipmaps.length; ++i ) {
+						var buffers = [];
 
-						buffers.push( mipmaps[i].data.buffer );
+						for ( var i = 0; i < mipmaps.length; ++ i ) {
 
-					}
+							buffers.push( mipmaps[ i ].data.buffer );
+
+						}
+
+						self.postMessage( { type: 'transcode', id: message.id, width, height, mipmaps }, buffers );
+
+					} catch ( error ) {
 
-					self.postMessage( { type: 'transcode', id: message.id, width, height, mipmaps }, buffers );
+						console.error( error );
+
+						self.postMessage( { type: 'error', id: message.id, error: error.message } );
+
+					}
 
 				} );
 				break;
@@ -322,7 +394,7 @@ THREE.BasisTextureLoader.BasisWorker = function () {
 
 	};
 
-	function init ( wasmBinary ) {
+	function init( wasmBinary ) {
 
 		transcoderPending = new Promise( ( resolve ) => {
 
@@ -348,7 +420,7 @@ THREE.BasisTextureLoader.BasisWorker = function () {
 
 	}
 
-	function transcode ( buffer ) {
+	function transcode( buffer ) {
 
 		var basisFile = new _BasisFile( new Uint8Array( buffer ) );
 
@@ -356,7 +428,7 @@ THREE.BasisTextureLoader.BasisWorker = function () {
 		var height = basisFile.getImageHeight( 0, 0 );
 		var levels = basisFile.getNumLevels( 0 );
 
-		function cleanup () {
+		function cleanup() {
 
 			basisFile.close();
 			basisFile.delete();
@@ -379,7 +451,7 @@ THREE.BasisTextureLoader.BasisWorker = function () {
 
 		var mipmaps = [];
 
-		for ( var mip = 0; mip < levels; mip++ ) {
+		for ( var mip = 0; mip < levels; mip ++ ) {
 
 			var mipWidth = basisFile.getImageWidth( 0, mip );
 			var mipHeight = basisFile.getImageHeight( 0, mip );

+ 4 - 0
examples/webgl_loader_texture_basis.html

@@ -57,6 +57,10 @@
 					material.map = texture;
 					material.needsUpdate = true;
 
+				}, undefined, function ( error ) {
+
+					console.error( error );
+
 				} );
 
 				window.addEventListener( 'resize', onWindowResize, false );