You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
5.4 KiB
283 lines
5.4 KiB
2 years ago
|
/**
|
||
|
* Loader for KTX 2.0 GPU Texture containers.
|
||
|
*
|
||
|
* KTX 2.0 is a container format for various GPU texture formats. The loader
|
||
|
* supports Basis Universal GPU textures, which can be quickly transcoded to
|
||
|
* a wide variety of GPU texture compression formats. While KTX 2.0 also allows
|
||
|
* other hardware-specific formats, this loader does not yet parse them.
|
||
|
*
|
||
|
* This loader parses the KTX 2.0 container and then relies on
|
||
|
* THREE.BasisTextureLoader to complete the transcoding process.
|
||
|
*
|
||
|
* References:
|
||
|
* - KTX: http://github.khronos.org/KTX-Specification/
|
||
|
* - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor
|
||
|
*/
|
||
|
|
||
|
import {
|
||
|
CompressedTexture,
|
||
|
CompressedTextureLoader,
|
||
|
FileLoader,
|
||
|
LinearEncoding,
|
||
|
sRGBEncoding,
|
||
|
} from '../../../build/three.module.js';
|
||
|
import {
|
||
|
read as readKTX,
|
||
|
KTX2ChannelETC1S,
|
||
|
KTX2ChannelUASTC,
|
||
|
KTX2Flags,
|
||
|
KTX2Model,
|
||
|
KTX2SupercompressionScheme,
|
||
|
KTX2Transfer
|
||
|
} from '../libs/ktx-parse.module.js';
|
||
|
import { BasisTextureLoader } from './BasisTextureLoader.js';
|
||
|
import { ZSTDDecoder } from '../libs/zstddec.module.js';
|
||
|
|
||
|
class KTX2Loader extends CompressedTextureLoader {
|
||
|
|
||
|
constructor( manager ) {
|
||
|
|
||
|
super( manager );
|
||
|
|
||
|
this.basisLoader = new BasisTextureLoader( manager );
|
||
|
this.zstd = new ZSTDDecoder();
|
||
|
|
||
|
this.zstd.init();
|
||
|
|
||
|
if ( typeof MSC_TRANSCODER !== 'undefined' ) {
|
||
|
|
||
|
console.warn(
|
||
|
|
||
|
'THREE.KTX2Loader: Please update to latest "basis_transcoder".'
|
||
|
+ ' "msc_basis_transcoder" is no longer supported in three.js r125+.'
|
||
|
|
||
|
);
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
setTranscoderPath( path ) {
|
||
|
|
||
|
this.basisLoader.setTranscoderPath( path );
|
||
|
|
||
|
return this;
|
||
|
|
||
|
}
|
||
|
|
||
|
setWorkerLimit( path ) {
|
||
|
|
||
|
this.basisLoader.setWorkerLimit( path );
|
||
|
|
||
|
return this;
|
||
|
|
||
|
}
|
||
|
|
||
|
detectSupport( renderer ) {
|
||
|
|
||
|
this.basisLoader.detectSupport( renderer );
|
||
|
|
||
|
return this;
|
||
|
|
||
|
}
|
||
|
|
||
|
dispose() {
|
||
|
|
||
|
this.basisLoader.dispose();
|
||
|
|
||
|
return this;
|
||
|
|
||
|
}
|
||
|
|
||
|
load( url, onLoad, onProgress, onError ) {
|
||
|
|
||
|
var scope = this;
|
||
|
|
||
|
var texture = new CompressedTexture();
|
||
|
|
||
|
var bufferPending = new Promise( function ( resolve, reject ) {
|
||
|
|
||
|
new FileLoader( scope.manager )
|
||
|
.setPath( scope.path )
|
||
|
.setResponseType( 'arraybuffer' )
|
||
|
.load( url, resolve, onProgress, reject );
|
||
|
|
||
|
} );
|
||
|
|
||
|
bufferPending
|
||
|
.then( function ( buffer ) {
|
||
|
|
||
|
scope.parse( buffer, function ( _texture ) {
|
||
|
|
||
|
texture.copy( _texture );
|
||
|
texture.needsUpdate = true;
|
||
|
|
||
|
if ( onLoad ) onLoad( texture );
|
||
|
|
||
|
}, onError );
|
||
|
|
||
|
} )
|
||
|
.catch( onError );
|
||
|
|
||
|
return texture;
|
||
|
|
||
|
}
|
||
|
|
||
|
parse( buffer, onLoad, onError ) {
|
||
|
|
||
|
var scope = this;
|
||
|
|
||
|
var ktx = readKTX( new Uint8Array( buffer ) );
|
||
|
|
||
|
if ( ktx.pixelDepth > 0 ) {
|
||
|
|
||
|
throw new Error( 'THREE.KTX2Loader: Only 2D textures are currently supported.' );
|
||
|
|
||
|
}
|
||
|
|
||
|
if ( ktx.layerCount > 1 ) {
|
||
|
|
||
|
throw new Error( 'THREE.KTX2Loader: Array textures are not currently supported.' );
|
||
|
|
||
|
}
|
||
|
|
||
|
if ( ktx.faceCount > 1 ) {
|
||
|
|
||
|
throw new Error( 'THREE.KTX2Loader: Cube textures are not currently supported.' );
|
||
|
|
||
|
}
|
||
|
|
||
|
var dfd = KTX2Utils.getBasicDFD( ktx );
|
||
|
|
||
|
KTX2Utils.createLevels( ktx, this.zstd ).then( function ( levels ) {
|
||
|
|
||
|
var basisFormat = dfd.colorModel === KTX2Model.UASTC
|
||
|
? BasisTextureLoader.BasisFormat.UASTC_4x4
|
||
|
: BasisTextureLoader.BasisFormat.ETC1S;
|
||
|
|
||
|
var parseConfig = {
|
||
|
|
||
|
levels: levels,
|
||
|
width: ktx.pixelWidth,
|
||
|
height: ktx.pixelHeight,
|
||
|
basisFormat: basisFormat,
|
||
|
hasAlpha: KTX2Utils.getAlpha( ktx ),
|
||
|
|
||
|
};
|
||
|
|
||
|
if ( basisFormat === BasisTextureLoader.BasisFormat.ETC1S ) {
|
||
|
|
||
|
parseConfig.globalData = ktx.globalData;
|
||
|
|
||
|
}
|
||
|
|
||
|
return scope.basisLoader.parseInternalAsync( parseConfig );
|
||
|
|
||
|
} ).then( function ( texture ) {
|
||
|
|
||
|
texture.encoding = dfd.transferFunction === KTX2Transfer.SRGB
|
||
|
? sRGBEncoding
|
||
|
: LinearEncoding;
|
||
|
texture.premultiplyAlpha = KTX2Utils.getPremultiplyAlpha( ktx );
|
||
|
|
||
|
onLoad( texture );
|
||
|
|
||
|
} ).catch( onError );
|
||
|
|
||
|
return this;
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
var KTX2Utils = {
|
||
|
|
||
|
createLevels: async function ( ktx, zstd ) {
|
||
|
|
||
|
if ( ktx.supercompressionScheme === KTX2SupercompressionScheme.ZSTD ) {
|
||
|
|
||
|
await zstd.init();
|
||
|
|
||
|
}
|
||
|
|
||
|
var levels = [];
|
||
|
var width = ktx.pixelWidth;
|
||
|
var height = ktx.pixelHeight;
|
||
|
|
||
|
for ( var levelIndex = 0; levelIndex < ktx.levels.length; levelIndex ++ ) {
|
||
|
|
||
|
var levelWidth = Math.max( 1, Math.floor( width / Math.pow( 2, levelIndex ) ) );
|
||
|
var levelHeight = Math.max( 1, Math.floor( height / Math.pow( 2, levelIndex ) ) );
|
||
|
var levelData = ktx.levels[ levelIndex ].levelData;
|
||
|
|
||
|
if ( ktx.supercompressionScheme === KTX2SupercompressionScheme.ZSTD ) {
|
||
|
|
||
|
levelData = zstd.decode( levelData, ktx.levels[ levelIndex ].uncompressedByteLength );
|
||
|
|
||
|
}
|
||
|
|
||
|
levels.push( {
|
||
|
|
||
|
index: levelIndex,
|
||
|
width: levelWidth,
|
||
|
height: levelHeight,
|
||
|
data: levelData,
|
||
|
|
||
|
} );
|
||
|
|
||
|
}
|
||
|
|
||
|
return levels;
|
||
|
|
||
|
},
|
||
|
|
||
|
getBasicDFD: function ( ktx ) {
|
||
|
|
||
|
// Basic Data Format Descriptor Block is always the first DFD.
|
||
|
return ktx.dataFormatDescriptor[ 0 ];
|
||
|
|
||
|
},
|
||
|
|
||
|
getAlpha: function ( ktx ) {
|
||
|
|
||
|
var dfd = this.getBasicDFD( ktx );
|
||
|
|
||
|
// UASTC
|
||
|
|
||
|
if ( dfd.colorModel === KTX2Model.UASTC ) {
|
||
|
|
||
|
if ( ( dfd.samples[ 0 ].channelID & 0xF ) === KTX2ChannelUASTC.RGBA ) {
|
||
|
|
||
|
return true;
|
||
|
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
|
||
|
}
|
||
|
|
||
|
// ETC1S
|
||
|
|
||
|
if ( dfd.samples.length === 2
|
||
|
&& ( dfd.samples[ 1 ].channelID & 0xF ) === KTX2ChannelETC1S.AAA ) {
|
||
|
|
||
|
return true;
|
||
|
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
|
||
|
},
|
||
|
|
||
|
getPremultiplyAlpha: function ( ktx ) {
|
||
|
|
||
|
var dfd = this.getBasicDFD( ktx );
|
||
|
|
||
|
return !! ( dfd.flags & KTX2Flags.ALPHA_PREMULTIPLIED );
|
||
|
|
||
|
},
|
||
|
|
||
|
};
|
||
|
|
||
|
export { KTX2Loader };
|