1/** 2 * @author Don McCurdy / https://www.donmccurdy.com 3 * @author Austin Eng / https://github.com/austinEng 4 * @author Shrek Shao / https://github.com/shrekshao 5 */ 6 7/** 8 * Loader for Basis Universal GPU Texture Codec. 9 * 10 * Basis Universal is a "supercompressed" GPU texture and texture video 11 * compression system that outputs a highly compressed intermediate file format 12 * (.basis) that can be quickly transcoded to a wide variety of GPU texture 13 * compression formats. 14 * 15 * This loader parallelizes the transcoding process across a configurable number 16 * of web workers, before transferring the transcoded compressed texture back 17 * to the main thread. 18 */ 19THREE.BasisTextureLoader = function ( manager ) { 20 21 THREE.Loader.call( this, manager ); 22 23 this.transcoderPath = ''; 24 this.transcoderBinary = null; 25 this.transcoderPending = null; 26 27 this.workerLimit = 4; 28 this.workerPool = []; 29 this.workerNextTaskID = 1; 30 this.workerSourceURL = ''; 31 this.workerConfig = { 32 format: null, 33 astcSupported: false, 34 etcSupported: false, 35 dxtSupported: false, 36 pvrtcSupported: false, 37 }; 38 39}; 40 41THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { 42 43 constructor: THREE.BasisTextureLoader, 44 45 setTranscoderPath: function ( path ) { 46 47 this.transcoderPath = path; 48 49 return this; 50 51 }, 52 53 setWorkerLimit: function ( workerLimit ) { 54 55 this.workerLimit = workerLimit; 56 57 return this; 58 59 }, 60 61 detectSupport: function ( renderer ) { 62 63 var config = this.workerConfig; 64 65 config.bc7Supported = !! renderer.extensions.get( 'EXT_texture_compression_bptc' ); 66 config.astcSupported = !! renderer.extensions.get( 'WEBGL_compressed_texture_astc' ); 67 config.etcSupported = !! renderer.extensions.get( 'WEBGL_compressed_texture_etc1' ); 68 config.dxtSupported = !! renderer.extensions.get( 'WEBGL_compressed_texture_s3tc' ); 69 config.pvrtcSupported = !! renderer.extensions.get( 'WEBGL_compressed_texture_pvrtc' ) 70 || !! renderer.extensions.get( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ); 71 72 if ( config.astcSupported ) { 73 74 config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4; 75 76 } else if ( config.bc7Supported ) { 77 78 config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC7_M6_OPAQUE_ONLY; 79 80 } else if ( config.dxtSupported ) { 81 82 config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3; 83 84 } else if ( config.etcSupported ) { 85 86 config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFETC1; 87 88 } else if ( config.pvrtcSupported ) { 89 90 config.format = this.useAlpha ? THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA : THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB; 91 92 } else { 93 94 throw new Error( 'THREE.BasisTextureLoader: No suitable compressed texture format found.' ); 95 96 } 97 98 return this; 99 100 }, 101 102 load: function ( url, onLoad, onProgress, onError ) { 103 104 var loader = new THREE.FileLoader( this.manager ); 105 106 loader.setResponseType( 'arraybuffer' ); 107 108 loader.load( url, ( buffer ) => { 109 110 this._createTexture( buffer ) 111 .then( onLoad ) 112 .catch( onError ); 113 114 }, onProgress, onError ); 115 116 }, 117 118 /** 119 * @param {ArrayBuffer} buffer 120 * @return {Promise<THREE.CompressedTexture>} 121 */ 122 _createTexture: function ( buffer ) { 123 124 var worker; 125 var taskID; 126 127 var taskCost = buffer.byteLength; 128 129 var texturePending = this._allocateWorker( taskCost ) 130 .then( ( _worker ) => { 131 132 worker = _worker; 133 taskID = this.workerNextTaskID ++; 134 135 return new Promise( ( resolve, reject ) => { 136 137 worker._callbacks[ taskID ] = { resolve, reject }; 138 139 worker.postMessage( { type: 'transcode', id: taskID, buffer }, [ buffer ] ); 140 141 } ); 142 143 } ) 144 .then( ( message ) => { 145 146 var config = this.workerConfig; 147 148 var { width, height, mipmaps, format } = message; 149 150 var texture; 151 152 switch ( format ) { 153 154 case THREE.BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4: 155 texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGBA_ASTC_4x4_Format ); 156 break; 157 case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC7_M6_OPAQUE_ONLY: 158 texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.BasisTextureLoader.COMPRESSED_RGBA_BPTC_UNORM ); 159 break; 160 case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1: 161 case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3: 162 texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.BasisTextureLoader.DXT_FORMAT_MAP[ config.format ], THREE.UnsignedByteType ); 163 break; 164 case THREE.BasisTextureLoader.BASIS_FORMAT.cTFETC1: 165 texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_ETC1_Format ); 166 break; 167 case THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB: 168 texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_PVRTC_4BPPV1_Format ); 169 break; 170 case THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA: 171 texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGBA_PVRTC_4BPPV1_Format ); 172 break; 173 default: 174 throw new Error( 'THREE.BasisTextureLoader: No supported format available.' ); 175 176 } 177 178 texture.minFilter = mipmaps.length === 1 ? THREE.LinearFilter : THREE.LinearMipmapLinearFilter; 179 texture.magFilter = THREE.LinearFilter; 180 texture.generateMipmaps = false;
181 texture.needsUpdate = true; 182 183 return texture; 184 185 } ); 186 187 texturePending 188 .finally( () => { 189 190 if ( worker && taskID ) { 191 192 worker._taskLoad -= taskCost; 193 delete worker._callbacks[ taskID ]; 194 195 } 196 197 } ); 198 199 return texturePending; 200 201 }, 202 203 _initTranscoder: function () { 204 205 if ( ! this.transcoderPending ) { 206 207 // Load transcoder wrapper. 208 var jsLoader = new THREE.FileLoader( this.manager ); 209 jsLoader.setPath( this.transcoderPath ); 210 var jsContent = new Promise( ( resolve, reject ) => { 211 212 jsLoader.load( 'basis_transcoder.js', resolve, undefined, reject ); 213 214 } ); 215 216 // Load transcoder WASM binary. 217 var binaryLoader = new THREE.FileLoader( this.manager ); 218 binaryLoader.setPath( this.transcoderPath ); 219 binaryLoader.setResponseType( 'arraybuffer' ); 220 var binaryContent = new Promise( ( resolve, reject ) => { 221 222 binaryLoader.load( 'basis_transcoder.wasm', resolve, undefined, reject ); 223 224 } ); 225 226 this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ) 227 .then( ( [ jsContent, binaryContent ] ) => { 228 229 var fn = THREE.BasisTextureLoader.BasisWorker.toString(); 230 231 var body = [ 232 '/* basis_transcoder.js */', 233 jsContent, 234 '/* worker */', 235 fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) 236 ].join( '\n' ); 237 238 this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); 239 this.transcoderBinary = binaryContent; 240 241 } ); 242 243 } 244 245 return this.transcoderPending; 246 247 }, 248 249 _allocateWorker: function ( taskCost ) { 250 251 return this._initTranscoder().then( () => { 252 253 if ( this.workerPool.length < this.workerLimit ) { 254 255 var worker = new Worker( this.workerSourceURL ); 256 257 worker._callbacks = {}; 258 worker._taskLoad = 0; 259 260 worker.postMessage( { 261 type: 'init', 262 config: this.workerConfig, 263 transcoderBinary: this.transcoderBinary, 264 } ); 265 266 worker.onmessage = function ( e ) { 267 268 var message = e.data; 269 270 switch ( message.type ) { 271 272 case 'transcode': 273 worker._callbacks[ message.id ].resolve( message ); 274 break; 275 276 case 'error': 277 worker._callbacks[ message.id ].reject( message ); 278 break; 279 280 default: 281 console.error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' ); 282 283 } 284 285 }; 286 287 this.workerPool.push( worker ); 288 289 } else { 290 291 this.workerPool.sort( function ( a, b ) { 292 293 return a._taskLoad > b._taskLoad ? - 1 : 1; 294 295 } ); 296 297 } 298 299 var worker = this.workerPool[ this.workerPool.length - 1 ]; 300 301 worker._taskLoad += taskCost; 302 303 return worker; 304 305 } ); 306 307 }, 308 309 dispose: function () { 310 311 for ( var i = 0; i < this.workerPool.length; i ++ ) { 312 313 this.workerPool[ i ].terminate(); 314 315 } 316 317 this.workerPool.length = 0; 318 319 return this; 320 321 } 322 323} ); 324 325/* CONSTANTS */ 326 327THREE.BasisTextureLoader.BASIS_FORMAT = { 328 cTFETC1: 0, 329 cTFETC2: 1, 330 cTFBC1: 2, 331 cTFBC3: 3, 332 cTFBC4: 4, 333 cTFBC5: 5, 334 cTFBC7_M6_OPAQUE_ONLY: 6, 335 cTFBC7_M5: 7, 336 cTFPVRTC1_4_RGB: 8, 337 cTFPVRTC1_4_RGBA: 9, 338 cTFASTC_4x4: 10, 339 cTFATC_RGB: 11, 340 cTFATC_RGBA_INTERPOLATED_ALPHA: 12, 341 cTFRGBA32: 13, 342 cTFRGB565: 14, 343 cTFBGR565: 15, 344 cTFRGBA4444: 16, 345}; 346 347// DXT formats, from: 348// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ 349THREE.BasisTextureLoader.DXT_FORMAT = { 350 COMPRESSED_RGB_S3TC_DXT1_EXT: 0x83F0, 351 COMPRESSED_RGBA_S3TC_DXT1_EXT: 0x83F1, 352 COMPRESSED_RGBA_S3TC_DXT3_EXT: 0x83F2, 353 COMPRESSED_RGBA_S3TC_DXT5_EXT: 0x83F3, 354}; 355THREE.BasisTextureLoader.DXT_FORMAT_MAP = {}; 356THREE.BasisTextureLoader.DXT_FORMAT_MAP[ THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1 ] = 357 THREE.BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGB_S3TC_DXT1_EXT; 358THREE.BasisTextureLoader.DXT_FORMAT_MAP[ THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3 ] = 359 THREE.BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGBA_S3TC_DXT5_EXT; 360 361 // ASTC formats, from: 362// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/ 363THREE.BasisTextureLoader.COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0; 364 365// BC7/BPTC format, from: 366// https://www.khronos.org/registry/webgl/extensions/EXT_texture_compression_bptc/ 367THREE.BasisTextureLoader.COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C; 368 369/* WEB WORKER */ 370 371THREE.BasisTextureLoader.BasisWorker = function () { 372 373 var config; 374 var transcoderPending; 375 var _BasisFile; 376 377 onmessage = function ( e ) { 378 379 var message = e.data; 380 381 switch ( message.type ) { 382 383 case 'init': 384 config = message.config; 385 init( message.transcoderBinary ); 386 break; 387 388 case 'transcode': 389 transcoderPending.then( () => { 390 391 try { 392 393 var { width, height, hasAlpha, mipmaps, format } = transcode( message.buffer ); 394 395 var buffers = []; 396 397 for ( var i = 0; i < mipmaps.length; ++ i ) { 398 399 buffers.push( mipmaps[ i ].data.buffer ); 400 401 } 402 403 self.postMessage( { type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps, format }, buffers ); 404 405 } catch ( error ) { 406 407 console.error( error ); 408 409 self.postMessage( { type: 'error', id: message.id, error: error.message } ); 410 411 } 412 413 } ); 414 break; 415 416 } 417 418 }; 419 420 function init( wasmBinary ) { 421 422 var BasisModule; 423 transcoderPending = new Promise( ( resolve ) => { 424 425 BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; 426 BASIS( BasisModule ); 427 428 } ).then( () => { 429 430 var { BasisFile, initializeBasis } = BasisModule; 431 432 _BasisFile = BasisFile; 433 434 initializeBasis(); 435 436 } ); 437 438 } 439 440 function transcode( buffer ) { 441 442 var basisFile = new _BasisFile( new Uint8Array( buffer ) ); 443 444 var width = basisFile.getImageWidth( 0, 0 ); 445 var height = basisFile.getImageHeight( 0, 0 ); 446 var levels = basisFile.getNumLevels( 0 ); 447 var hasAlpha = basisFile.getHasAlpha(); 448 449 function cleanup() { 450 451 basisFile.close(); 452 basisFile.delete(); 453 454 } 455 456 if ( ! hasAlpha ) { 457 458 switch ( config.format ) { 459 460 case 9: // Hardcoded: THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA 461 config.format = 8; // Hardcoded: THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB; 462 break; 463 default: 464 break; 465 466 } 467 468 } 469 470 if ( ! width || ! height || ! levels ) { 471 472 cleanup(); 473 throw new Error( 'THREE.BasisTextureLoader: Invalid .basis file' ); 474 475 } 476 477 if ( ! basisFile.startTranscoding() ) { 478 479 cleanup(); 480 throw new Error( 'THREE.BasisTextureLoader: .startTranscoding failed' ); 481 482 } 483 484 var mipmaps = []; 485 486 for ( var mip = 0; mip < levels; mip ++ ) { 487 488 var mipWidth = basisFile.getImageWidth( 0, mip ); 489 var mipHeight = basisFile.getImageHeight( 0, mip ); 490 var dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, config.format ) ); 491 492 var status = basisFile.transcodeImage( 493 dst, 494 0, 495 mip, 496 config.format, 497 0, 498 hasAlpha 499 ); 500 501 if ( ! status ) { 502 503 cleanup(); 504 throw new Error( 'THREE.BasisTextureLoader: .transcodeImage failed.' ); 505 506 } 507 508 mipmaps.push( { data: dst, width: mipWidth, height: mipHeight } ); 509 510 } 511 512 cleanup(); 513 514 return { width, height, hasAlpha, mipmaps, format: config.format }; 515 516 } 517 518};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.