PageSourceSearch

https://marcychen.com/Scripts/three/addons/loaders/KTX2Loader.js

js marcychen.com collected 2026-10-05 10:33:43 UTC 21,510 bytes, 868 lines download raw bytes

vendor: 7,224 bytes, lines 1-281
1/**
2 * Loader for KTX 2.0 GPU Texture containers.
3 *
4 * KTX 2.0 is a container format for various GPU texture formats. The loader
5 * supports Basis Universal GPU textures, which can be quickly transcoded to
6 * a wide variety of GPU texture compression formats, as well as some
7 * uncompressed DataTexture and Data3DTexture formats.
8 *
9 * References:
10 * - KTX: http://github.khronos.org/KTX-Specification/
11 * - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor
12 */
13
14import {
15	CompressedTexture,
16	CompressedArrayTexture,
17	Data3DTexture,
18	DataTexture,
19	FileLoader,
20	FloatType,
21	HalfFloatType,
22	NoColorSpace,
23	LinearFilter,
24	LinearMipmapLinearFilter,
25	Loader,
26	RedFormat,
27	RGB_ETC1_Format,
28	RGB_ETC2_Format,
29	RGB_PVRTC_4BPPV1_Format,
30	RGB_S3TC_DXT1_Format,
31	RGBA_ASTC_4x4_Format,
32	RGBA_BPTC_Format,
33	RGBA_ETC2_EAC_Format,
34	RGBA_PVRTC_4BPPV1_Format,
35	RGBA_S3TC_DXT5_Format,
36	RGBAFormat,
37	RGFormat,
38	SRGBColorSpace,
39	UnsignedByteType,
40} from 'three';
41import { WorkerPool } from '../utils/WorkerPool.js';
42import {
43	read,
44	KHR_DF_FLAG_ALPHA_PREMULTIPLIED,
45	KHR_DF_TRANSFER_SRGB,
46	KHR_SUPERCOMPRESSION_NONE,
47	KHR_SUPERCOMPRESSION_ZSTD,
48	VK_FORMAT_UNDEFINED,
49	VK_FORMAT_R16_SFLOAT,
50	VK_FORMAT_R16G16_SFLOAT,
51	VK_FORMAT_R16G16B16A16_SFLOAT,
52	VK_FORMAT_R32_SFLOAT,
53	VK_FORMAT_R32G32_SFLOAT,
54	VK_FORMAT_R32G32B32A32_SFLOAT,
55	VK_FORMAT_R8_SRGB,
56	VK_FORMAT_R8_UNORM,
57	VK_FORMAT_R8G8_SRGB,
58	VK_FORMAT_R8G8_UNORM,
59	VK_FORMAT_R8G8B8A8_SRGB,
60	VK_FORMAT_R8G8B8A8_UNORM,
61} from '../libs/ktx-parse.module.js';
62import { ZSTDDecoder } from '../libs/zstddec.module.js';
63
64const _taskCache = new WeakMap();
65
66let _activeLoaders = 0;
67
68let _zstd;
69
70class KTX2Loader extends Loader {
71
72	constructor( manager ) {
73
74		super( manager );
75
76		this.transcoderPath = '';
77		this.transcoderBinary = null;
78		this.transcoderPending = null;
79
80		this.workerPool = new WorkerPool();
81		this.workerSourceURL = '';
82		this.workerConfig = null;
83
84		if ( typeof MSC_TRANSCODER !== 'undefined' ) {
85
86			console.warn(
87
88				'THREE.KTX2Loader: Please update to latest "basis_transcoder".'
89				+ ' "msc_basis_transcoder" is no longer supported in three.js r125+.'
90
91			);
92
93		}
94
95	}
96
97	setTranscoderPath( path ) {
98
99		this.transcoderPath = path;
100
101		return this;
102
103	}
104
105	setWorkerLimit( num ) {
106
107		this.workerPool.setWorkerLimit( num );
108
109		return this;
110
111	}
112
113	detectSupport( renderer ) {
114
115		if ( renderer.isWebGPURenderer === true ) {
116
117			this.workerConfig = {
118				astcSupported: renderer.hasFeature( 'texture-compression-astc' ),
119				etc1Supported: false,
120				etc2Supported: renderer.hasFeature( 'texture-compression-etc2' ),
121				dxtSupported: renderer.hasFeature( 'texture-compression-bc' ),
122				bptcSupported: false,
123				pvrtcSupported: false
124			};
125
126		} else {
127
128			this.workerConfig = {
129				astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ),
130				etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ),
131				etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ),
132				dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ),
133				bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ),
134				pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' )
135					|| renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' )
136			};
137
138			if ( renderer.capabilities.isWebGL2 ) {
139
140				// https://github.com/mrdoob/three.js/pull/22928
141				this.workerConfig.etc1Supported = false;
142
143			}
144
145		}
146
147		return this;
148
149	}
150
151	init() {
152
153		if ( ! this.transcoderPending ) {
154
155			// Load transcoder wrapper.
156			const jsLoader = new FileLoader( this.manager );
157			jsLoader.setPath( this.transcoderPath );
158			jsLoader.setWithCredentials( this.withCredentials );
159			const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' );
160
161			// Load transcoder WASM binary.
162			const binaryLoader = new FileLoader( this.manager );
163			binaryLoader.setPath( this.transcoderPath );
164			binaryLoader.setResponseType( 'arraybuffer' );
165			binaryLoader.setWithCredentials( this.withCredentials );
166			const binaryContent = binaryLoader.loadAsync( 'basis_transcoder.wasm' );
167
168			this.transcoderPending = Promise.all( [ jsContent, binaryContent ] )
169				.then( ( [ jsContent, binaryContent ] ) => {
170
171					const fn = KTX2Loader.BasisWorker.toString();
172
173					const body = [
174						'/* constants */',
175						'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ),
176						'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ),
177						'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ),
178						'/* basis_transcoder.js */',
179						jsContent,
180						'/* worker */',
181						fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
182					].join( '\n' );
183
184					this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
185					this.transcoderBinary = binaryContent;
186
187					this.workerPool.setWorkerCreator( () => {
188
189						const worker = new Worker( this.workerSourceURL );
190						const transcoderBinary = this.transcoderBinary.slice( 0 );
191
192						worker.postMessage( { type: 'init', config: this.workerConfig, transcoderBinary }, [ transcoderBinary ] );
193
194						return worker;
195
196					} );
197
198				} );
199
200			if ( _activeLoaders > 0 ) {
201
202				// Each instance loads a transcoder and allocates workers, increasing network and memory cost.
203
204				console.warn(
205
206					'THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues.'
207					+ ' Use a single KTX2Loader instance, or call .dispose() on old instances.'
208
209				);
210
211			}
212
213			_activeLoaders ++;
214
215		}
216
217		return this.transcoderPending;
218
219	}
220
221	load( url, onLoad, onProgress, onError ) {
222
223		if ( this.workerConfig === null ) {
224
225			throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' );
226
227		}
228
229		const loader = new FileLoader( this.manager );
230
231		loader.setResponseType( 'arraybuffer' );
232		loader.setWithCredentials( this.withCredentials );
233
234		loader.load( url, ( buffer ) => {
235
236			// Check for an existing task using this buffer. A transferred buffer cannot be transferred
237			// again from this thread.
238			if ( _taskCache.has( buffer ) ) {
239
240				const cachedTask = _taskCache.get( buffer );
241
242				return cachedTask.promise.then( onLoad ).catch( onError );
243
244			}
245
246			this._createTexture( buffer )
247				.then( ( texture ) => onLoad ? onLoad( texture ) : null )
248				.catch( onError );
249
250		}, onProgress, onError );
251
252	}
253
254	_createTextureFrom( transcodeResult, container ) {
255
256		const { faces, width, height, format, type, error, dfdTransferFn, dfdFlags } = transcodeResult;
257
258		if ( type === 'error' ) return Promise.reject( error );
259
260		let texture;
261
262		if ( container.faceCount === 6 ) {
263
264			texture = new CompressedTexture();
265			texture.image = faces;
266			texture.format = format;
267			texture.type = UnsignedByteType;
268
269		} else {
270
271			const mipmaps = faces[ 0 ].mipmaps;
272
273			texture = container.layerCount > 1
274				? new CompressedArrayTexture( mipmaps, width, height, container.layerCount, format, UnsignedByteType )
275				: new CompressedTexture( mipmaps, width, height, format, UnsignedByteType );
276
277		}
278
279		texture.minFilter = faces[ 0 ].mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter;
280		texture.magFilter = LinearFilter;
281		texture.generateMipmaps = false;
282
283		texture.needsUpdate = true;
284		// TODO: Detect NoColorSpace vs. LinearSRGBColorSpace based on primaries.
285		texture.colorSpace = dfdTransferFn === KHR_DF_TRANSFER_SRGB ? SRGBColorSpace : NoColorSpace;
286		texture.premultiplyAlpha = !! ( dfdFlags & KHR_DF_FLAG_ALPHA_PREMULTIPLIED );
287
288		return texture;
289
290	}
291
292	/**
293	 * @param {ArrayBuffer} buffer
294	 * @param {object?} config
295	 * @return {Promise<CompressedTexture|CompressedArrayTexture|DataTexture|Data3DTexture>}
296	 */
297	async _createTexture( buffer, config = {} ) {
298
299		const container = read( new Uint8Array( buffer ) );
300
301		if ( container.vkFormat !== VK_FORMAT_UNDEFINED ) {
302
303			const mipmaps = [];
304			const pendings = [];
305
306			for ( let levelIndex = 0; levelIndex < container.levels.length; levelIndex ++ ) {
307
308				pendings.push( createDataTexture( container, levelIndex ).then( function ( dataTexture ) {
309
310					mipmaps[ levelIndex ] = dataTexture;
311
312				} ) );
313
314			}
315
316			await Promise.all( pendings );
317
318			const texture = mipmaps[ 0 ];
319			texture.mipmaps = mipmaps.map( dt => {
320				return {
321					data: dt.source.data,
322					width: dt.source.data.width,
323					height: dt.source.data.height,
324					depth: dt.source.data.depth
325				};
326			} );
327			return texture;
328
329		}
330
331		//
332		const taskConfig = config;
333		const texturePending = this.init().then( () => {
334
335			return this.workerPool.postMessage( { type: 'transcode', buffer, taskConfig: taskConfig }, [ buffer ] );
336
337		} ).then( ( e ) => this._createTextureFrom( e.data, container ) );
338
339		// Cache the task result.
340		_taskCache.set( buffer, { promise: texturePending } );
341
342		return texturePending;
343
344	}
345
346	dispose() {
347
348		this.workerPool.dispose();
349		if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL );
350
351		_activeLoaders --;
352
353		return this;
354
355	}
356
357}
358
359
360/* CONSTANTS */
361
362KTX2Loader.BasisFormat = {
363	ETC1S: 0,
364	UASTC_4x4: 1,
365};
366
367KTX2Loader.TranscoderFormat = {
368	ETC1: 0,
369	ETC2: 1,
370	BC1: 2,
371	BC3: 3,
372	BC4: 4,
373	BC5: 5,
374	BC7_M6_OPAQUE_ONLY: 6,
375	BC7_M5: 7,
376	PVRTC1_4_RGB: 8,
377	PVRTC1_4_RGBA: 9,
378	ASTC_4x4: 10,
379	ATC_RGB: 11,
380	ATC_RGBA_INTERPOLATED_ALPHA: 12,
381	RGBA32: 13,
382	RGB565: 14,
383	BGR565: 15,
384	RGBA4444: 16,
385};
386
387KTX2Loader.EngineFormat = {
388	RGBAFormat: RGBAFormat,
389	RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format,
390	RGBA_BPTC_Format: RGBA_BPTC_Format,
391	RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format,
392	RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format,
393	RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format,
394	RGB_ETC1_Format: RGB_ETC1_Format,
395	RGB_ETC2_Format: RGB_ETC2_Format,
396	RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format,
397	RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format,
398};
399
400
401/* WEB WORKER */
402
403KTX2Loader.BasisWorker = function () {
404
405	let config;
406	let transcoderPending;
407	let BasisModule;
408
409	const EngineFormat = _EngineFormat; // eslint-disable-line no-undef
410	const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef
411	const BasisFormat = _BasisFormat; // eslint-disable-line no-undef
412
413	self.addEventListener( 'message', function ( e ) {
414
415		const message = e.data;
416
417		switch ( message.type ) {
418
419			case 'init':
420				config = message.config;
421				init( message.transcoderBinary );
422				break;
423
424			case 'transcode':
425				transcoderPending.then( () => {
426
427					try {
428
429						const { faces, buffers, width, height, hasAlpha, format, dfdTransferFn, dfdFlags } = transcode( message.buffer );
430
431						self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdTransferFn, dfdFlags }, buffers );
432
433					} catch ( error ) {
434
435						console.error( error );
436
437						self.postMessage( { type: 'error', id: message.id, error: error.message } );
438
439					}
440
441				} );
442				break;
443
444		}
445
446	} );
447
448	function init( wasmBinary ) {
449
450		transcoderPending = new Promise( ( resolve ) => {
451
452			BasisModule = { wasmBinary, onRuntimeInitialized: resolve };
453			BASIS( BasisModule ); // eslint-disable-line no-undef
454
455		} ).then( () => {
456
457			BasisModule.initializeBasis();
458
459			if ( BasisModule.KTX2File === undefined ) {
460
461				console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' );
462
463			}
464
465		} );
466
467	}
468
469	function transcode( buffer ) {
470
471		const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) );
472
473		function cleanup() {
474
475			ktx2File.close();
476			ktx2File.delete();
477
478		}
479
480		if ( ! ktx2File.isValid() ) {
481
482			cleanup();
483			throw new Error( 'THREE.KTX2Loader:	Invalid or unsupported .ktx2 file' );
484
485		}
486
487		const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;
488		const width = ktx2File.getWidth();
489		const height = ktx2File.getHeight();
490		const layerCount = ktx2File.getLayers() || 1;
491		const levelCount = ktx2File.getLevels();
492		const faceCount = ktx2File.getFaces();
493		const hasAlpha = ktx2File.getHasAlpha();
494		const dfdTransferFn = ktx2File.getDFDTransferFunc();
495		const dfdFlags = ktx2File.getDFDFlags();
496
497		const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha );
498
499		if ( ! width || ! height || ! levelCount ) {
500
501			cleanup();
502			throw new Error( 'THREE.KTX2Loader:	Invalid texture' );
503
504		}
505
506		if ( ! ktx2File.startTranscoding() ) {
507
508			cleanup();
509			throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' );
510
511		}
512
513		const faces = [];
514		const buffers = [];
515
516		for ( let face = 0; face < faceCount; face ++ ) {
517
518			const mipmaps = [];
519
520			for ( let mip = 0; mip < levelCount; mip ++ ) {
521
522				const layerMips = [];
523
524				let mipWidth, mipHeight;
525
526				for ( let layer = 0; layer < layerCount; layer ++ ) {
527
528					const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face );
529
530					if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) {
531
532						console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' );
533
534					}
535
536					if ( levelCount > 1 ) {
537
538						mipWidth = levelInfo.origWidth;
539						mipHeight = levelInfo.origHeight;
540
541					} else {
542
543						// Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with
544						// mipmaps must use multiple-of-four dimensions, for some texture formats and APIs.
545						// See mrdoob/three.js#25908.
546						mipWidth = levelInfo.width;
547						mipHeight = levelInfo.height;
548
549					}
550
551					const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) );
552					const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 );
553
554					if ( ! status ) {
555
556						cleanup();
557						throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' );
558
559					}
560
561					layerMips.push( dst );
562
563				}
564
565				const mipData = concat( layerMips );
566
567				mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } );
568				buffers.push( mipData.buffer );
569
570			}
571
572			faces.push( { mipmaps, width, height, format: engineFormat } );
573
574		}
575
576		cleanup();
577
578		return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdTransferFn, dfdFlags };
579
580	}
581
582	//
583
584	// Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC),
585	// device capabilities, and texture dimensions. The list below ranks the formats separately
586	// for ETC1S and UASTC.
587	//
588	// In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at
589	// significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently
590	// chooses RGBA32 only as a last resort and does not expose that option to the caller.
591	const FORMAT_OPTIONS = [
592		{
593			if: 'astcSupported',
594			basisFormat: [ BasisFormat.UASTC_4x4 ],
595			transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ],
596			engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ],
597			priorityETC1S: Infinity,
598			priorityUASTC: 1,
599			needsPowerOfTwo: false,
600		},
601		{
602			if: 'bptcSupported',
603			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
604			transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ],
605			engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ],
606			priorityETC1S: 3,
607			priorityUASTC: 2,
608			needsPowerOfTwo: false,
609		},
610		{
611			if: 'dxtSupported',
612			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
613			transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ],
614			engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ],
615			priorityETC1S: 4,
616			priorityUASTC: 5,
617			needsPowerOfTwo: false,
618		},
619		{
620			if: 'etc2Supported',
621			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
622			transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ],
623			engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ],
624			priorityETC1S: 1,
625			priorityUASTC: 3,
626			needsPowerOfTwo: false,
627		},
628		{
629			if: 'etc1Supported',
630			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
631			transcoderFormat: [ TranscoderFormat.ETC1 ],
632			engineFormat: [ EngineFormat.RGB_ETC1_Format ],
633			priorityETC1S: 2,
634			priorityUASTC: 4,
635			needsPowerOfTwo: false,
636		},
637		{
638			if: 'pvrtcSupported',
639			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
640			transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ],
641			engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ],
642			priorityETC1S: 5,
643			priorityUASTC: 6,
644			needsPowerOfTwo: true,
645		},
646	];
647
648	const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) {
649
650		return a.priorityETC1S - b.priorityETC1S;
651
652	} );
653	const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) {
654
655		return a.priorityUASTC - b.priorityUASTC;
656
657	} );
658
659	function getTranscoderFormat( basisFormat, width, height, hasAlpha ) {
660
661		let transcoderFormat;
662		let engineFormat;
663
664		const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;
665
666		for ( let i = 0; i < options.length; i ++ ) {
667
668			const opt = options[ i ];
669
670			if ( ! config[ opt.if ] ) continue;
671			if ( ! opt.basisFormat.includes( basisFormat ) ) continue;
672			if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue;
673			if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue;
674
675			transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ];
676			engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ];
677
678			return { transcoderFormat, engineFormat };
679
680		}
681
682		console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' );
683
684		transcoderFormat = TranscoderFormat.RGBA32;
685		engineFormat = EngineFormat.RGBAFormat;
686
687		return { transcoderFormat, engineFormat };
688
689	}
690
691	function isPowerOfTwo( value ) {
692
693		if ( value <= 2 ) return true;
694
695		return ( value & ( value - 1 ) ) === 0 && value !== 0;
696
697	}
698
699	/** Concatenates N byte arrays. */
700	function concat( arrays ) {
701
702		if ( arrays.length === 1 ) return arrays[ 0 ];
703
704		let totalByteLength = 0;
705
706		for ( let i = 0; i < arrays.length; i ++ ) {
707
708			const array = arrays[ i ];
709			totalByteLength += array.byteLength;
710
711		}
712
713		const result = new Uint8Array( totalByteLength );
714
715		let byteOffset = 0;
716
717		for ( let i = 0; i < arrays.length; i ++ ) {
718
719			const array = arrays[ i ];
720			result.set( array, byteOffset );
721
722			byteOffset += array.byteLength;
723
724		}
725
726		return result;
727
728	}
729
730};
731
732//
733// DataTexture and Data3DTexture parsing.
734
735const FORMAT_MAP = {
736
737	[ VK_FORMAT_R32G32B32A32_SFLOAT ]: RGBAFormat,
738	[ VK_FORMAT_R16G16B16A16_SFLOAT ]: RGBAFormat,
739	[ VK_FORMAT_R8G8B8A8_UNORM ]: RGBAFormat,
740	[ VK_FORMAT_R8G8B8A8_SRGB ]: RGBAFormat,
741
742	[ VK_FORMAT_R32G32_SFLOAT ]: RGFormat,
743	[ VK_FORMAT_R16G16_SFLOAT ]: RGFormat,
744	[ VK_FORMAT_R8G8_UNORM ]: RGFormat,
745	[ VK_FORMAT_R8G8_SRGB ]: RGFormat,
746
747	[ VK_FORMAT_R32_SFLOAT ]: RedFormat,
748	[ VK_FORMAT_R16_SFLOAT ]: RedFormat,
749	[ VK_FORMAT_R8_SRGB ]: RedFormat,
750	[ VK_FORMAT_R8_UNORM ]: RedFormat,
751
752};
753
754const TYPE_MAP = {
755
756	[ VK_FORMAT_R32G32B32A32_SFLOAT ]: FloatType,
757	[ VK_FORMAT_R16G16B16A16_SFLOAT ]: HalfFloatType,
758	[ VK_FORMAT_R8G8B8A8_UNORM ]: UnsignedByteType,
759	[ VK_FORMAT_R8G8B8A8_SRGB ]: UnsignedByteType,
760
761	[ VK_FORMAT_R32G32_SFLOAT ]: FloatType,
762	[ VK_FORMAT_R16G16_SFLOAT ]: HalfFloatType,
763	[ VK_FORMAT_R8G8_UNORM ]: UnsignedByteType,
764	[ VK_FORMAT_R8G8_SRGB ]: UnsignedByteType,
765
766	[ VK_FORMAT_R32_SFLOAT ]: FloatType,
767	[ VK_FORMAT_R16_SFLOAT ]: HalfFloatType,
768	[ VK_FORMAT_R8_SRGB ]: UnsignedByteType,
769	[ VK_FORMAT_R8_UNORM ]: UnsignedByteType,
770
771};
772
773const COLOR_SPACE_MAP = {
774
775	[ VK_FORMAT_R8G8B8A8_SRGB ]: SRGBColorSpace,
776	[ VK_FORMAT_R8G8_SRGB ]: SRGBColorSpace,
777	[ VK_FORMAT_R8_SRGB ]: SRGBColorSpace,
778
779};
780
781async function createDataTexture( container, levelIndex = 0 ) {
782
783	const { vkFormat } = container;
784	const pixelWidth = Math.max( 1, container.pixelWidth >> levelIndex );
785	const pixelHeight = Math.max( 1, container.pixelHeight >> levelIndex );
786	const pixelDepth = Math.max( 1, container.pixelDepth >> levelIndex );
787
788	if ( FORMAT_MAP[ vkFormat ] === undefined ) {
789
790		throw new Error( 'THREE.KTX2Loader: Unsupported vkFormat.' );
791
792	}
793
794	const level = container.levels[ levelIndex ];
795
796	let levelData;
797	let view;
798
799	if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_NONE ) {
800
801		levelData = level.levelData;
802
803	} else if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) {
804
805		if ( ! _zstd ) {
806
807			_zstd = new Promise( async ( resolve ) => {
808
809				const zstd = new ZSTDDecoder();
810				await zstd.init();
811				resolve( zstd );
812
813			} );
814
815		}
816
817		levelData = ( await _zstd ).decode( level.levelData, level.uncompressedByteLength );
818
819	} else {
820
821		throw new Error( 'THREE.KTX2Loader: Unsupported supercompressionScheme.' );
822
823	}
824
825	if ( TYPE_MAP[ vkFormat ] === FloatType ) {
826
827		view = new Float32Array(
828
829			levelData.buffer,
830			levelData.byteOffset,
831			levelData.byteLength / Float32Array.BYTES_PER_ELEMENT
832
833		);
834
835	} else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) {
836
837		view = new Uint16Array(
838
839			levelData.buffer,
840			levelData.byteOffset,
841			levelData.byteLength / Uint16Array.BYTES_PER_ELEMENT
842
843		);
844
845	} else {
846
847		view = levelData;
848
849	}
850	//
851
852	const texture = pixelDepth === 0
853		? new DataTexture( view, pixelWidth, pixelHeight )
854		: new Data3DTexture( view, pixelWidth, pixelHeight, pixelDepth );
855
856	texture.type = TYPE_MAP[ vkFormat ];
857	texture.format = FORMAT_MAP[ vkFormat ];
858	texture.colorSpace = COLOR_SPACE_MAP[ vkFormat ] || NoColorSpace;
859
860	texture.needsUpdate = true;
861
862	//
863
864	return Promise.resolve( texture );
865
866}
867
868export { KTX2Loader };

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.