293 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			293 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | import { | ||
|  | 	FloatType, | ||
|  | 	HalfFloatType, | ||
|  | 	UnsignedByteType, | ||
|  | 	RGBAFormat, | ||
|  | 	RGFormat, | ||
|  | 	RGIntegerFormat, | ||
|  | 	RedFormat, | ||
|  | 	RedIntegerFormat, | ||
|  | 	NoColorSpace, | ||
|  | 	LinearSRGBColorSpace, | ||
|  | 	SRGBColorSpace, | ||
|  | 	DataTexture, | ||
|  | 	REVISION, | ||
|  | } from 'three'; | ||
|  | 
 | ||
|  | import { | ||
|  | 	write, | ||
|  | 	KTX2Container, | ||
|  | 	KHR_DF_CHANNEL_RGBSDA_ALPHA, | ||
|  | 	KHR_DF_CHANNEL_RGBSDA_BLUE, | ||
|  | 	KHR_DF_CHANNEL_RGBSDA_GREEN, | ||
|  | 	KHR_DF_CHANNEL_RGBSDA_RED, | ||
|  | 	KHR_DF_MODEL_RGBSDA, | ||
|  | 	KHR_DF_PRIMARIES_BT709, | ||
|  | 	KHR_DF_PRIMARIES_UNSPECIFIED, | ||
|  | 	KHR_DF_SAMPLE_DATATYPE_FLOAT, | ||
|  | 	KHR_DF_SAMPLE_DATATYPE_LINEAR, | ||
|  | 	KHR_DF_SAMPLE_DATATYPE_SIGNED, | ||
|  | 	KHR_DF_TRANSFER_LINEAR, | ||
|  | 	KHR_DF_TRANSFER_SRGB, | ||
|  | 	VK_FORMAT_R16_SFLOAT, | ||
|  | 	VK_FORMAT_R16G16_SFLOAT, | ||
|  | 	VK_FORMAT_R16G16B16A16_SFLOAT, | ||
|  | 	VK_FORMAT_R32_SFLOAT, | ||
|  | 	VK_FORMAT_R32G32_SFLOAT, | ||
|  | 	VK_FORMAT_R32G32B32A32_SFLOAT, | ||
|  | 	VK_FORMAT_R8_SRGB, | ||
|  | 	VK_FORMAT_R8_UNORM, | ||
|  | 	VK_FORMAT_R8G8_SRGB, | ||
|  | 	VK_FORMAT_R8G8_UNORM, | ||
|  | 	VK_FORMAT_R8G8B8A8_SRGB, | ||
|  | 	VK_FORMAT_R8G8B8A8_UNORM, | ||
|  | } from '../libs/ktx-parse.module.js'; | ||
|  | 
 | ||
|  | const VK_FORMAT_MAP = { | ||
|  | 
 | ||
|  | 	[ RGBAFormat ]: { | ||
|  | 		[ FloatType ]: { | ||
|  | 			[ NoColorSpace ]: VK_FORMAT_R32G32B32A32_SFLOAT, | ||
|  | 			[ LinearSRGBColorSpace ]: VK_FORMAT_R32G32B32A32_SFLOAT, | ||
|  | 		}, | ||
|  | 		[ HalfFloatType ]: { | ||
|  | 			[ NoColorSpace ]: VK_FORMAT_R16G16B16A16_SFLOAT, | ||
|  | 			[ LinearSRGBColorSpace ]: VK_FORMAT_R16G16B16A16_SFLOAT, | ||
|  | 		}, | ||
|  | 		[ UnsignedByteType ]: { | ||
|  | 			[ NoColorSpace ]: VK_FORMAT_R8G8B8A8_UNORM, | ||
|  | 			[ LinearSRGBColorSpace ]: VK_FORMAT_R8G8B8A8_UNORM, | ||
|  | 			[ SRGBColorSpace ]: VK_FORMAT_R8G8B8A8_SRGB, | ||
|  | 		}, | ||
|  | 	}, | ||
|  | 
 | ||
|  | 	[ RGFormat ]: { | ||
|  | 		[ FloatType ]: { | ||
|  | 			[ NoColorSpace ]: VK_FORMAT_R32G32_SFLOAT, | ||
|  | 			[ LinearSRGBColorSpace ]: VK_FORMAT_R32G32_SFLOAT, | ||
|  | 		}, | ||
|  | 		[ HalfFloatType ]: { | ||
|  | 			[ NoColorSpace ]: VK_FORMAT_R16G16_SFLOAT, | ||
|  | 			[ LinearSRGBColorSpace ]: VK_FORMAT_R16G16_SFLOAT, | ||
|  | 		}, | ||
|  | 		[ UnsignedByteType ]: { | ||
|  | 			[ NoColorSpace ]: VK_FORMAT_R8G8_UNORM, | ||
|  | 			[ LinearSRGBColorSpace ]: VK_FORMAT_R8G8_UNORM, | ||
|  | 			[ SRGBColorSpace ]: VK_FORMAT_R8G8_SRGB, | ||
|  | 		}, | ||
|  | 	}, | ||
|  | 
 | ||
|  | 	[ RedFormat ]: { | ||
|  | 		[ FloatType ]: { | ||
|  | 			[ NoColorSpace ]: VK_FORMAT_R32_SFLOAT, | ||
|  | 			[ LinearSRGBColorSpace ]: VK_FORMAT_R32_SFLOAT, | ||
|  | 		}, | ||
|  | 		[ HalfFloatType ]: { | ||
|  | 			[ NoColorSpace ]: VK_FORMAT_R16_SFLOAT, | ||
|  | 			[ LinearSRGBColorSpace ]: VK_FORMAT_R16_SFLOAT, | ||
|  | 		}, | ||
|  | 		[ UnsignedByteType ]: { | ||
|  | 			[ NoColorSpace ]: VK_FORMAT_R8_UNORM, | ||
|  | 			[ LinearSRGBColorSpace ]: VK_FORMAT_R8_UNORM, | ||
|  | 			[ SRGBColorSpace ]: VK_FORMAT_R8_SRGB, | ||
|  | 		}, | ||
|  | 	}, | ||
|  | 
 | ||
|  | }; | ||
|  | 
 | ||
|  | const KHR_DF_CHANNEL_MAP = { | ||
|  | 
 | ||
|  | 	0: KHR_DF_CHANNEL_RGBSDA_RED, | ||
|  | 	1: KHR_DF_CHANNEL_RGBSDA_GREEN, | ||
|  | 	2: KHR_DF_CHANNEL_RGBSDA_BLUE, | ||
|  | 	3: KHR_DF_CHANNEL_RGBSDA_ALPHA, | ||
|  | 
 | ||
|  | }; | ||
|  | 
 | ||
|  | const ERROR_INPUT = 'THREE.KTX2Exporter: Supported inputs are DataTexture, Data3DTexture, or WebGLRenderer and WebGLRenderTarget.'; | ||
|  | const ERROR_FORMAT = 'THREE.KTX2Exporter: Supported formats are RGBAFormat, RGFormat, or RedFormat.'; | ||
|  | const ERROR_TYPE = 'THREE.KTX2Exporter: Supported types are FloatType, HalfFloatType, or UnsignedByteType."'; | ||
|  | const ERROR_COLOR_SPACE = 'THREE.KTX2Exporter: Supported color spaces are SRGBColorSpace (UnsignedByteType only), LinearSRGBColorSpace, or NoColorSpace.'; | ||
|  | 
 | ||
|  | export class KTX2Exporter { | ||
|  | 
 | ||
|  | 	parse( arg1, arg2 ) { | ||
|  | 
 | ||
|  | 		let texture; | ||
|  | 
 | ||
|  | 		if ( arg1.isDataTexture || arg1.isData3DTexture ) { | ||
|  | 
 | ||
|  | 			texture = arg1; | ||
|  | 
 | ||
|  | 		} else if ( arg1.isWebGLRenderer && arg2.isWebGLRenderTarget ) { | ||
|  | 
 | ||
|  | 			texture = toDataTexture( arg1, arg2 ); | ||
|  | 
 | ||
|  | 		} else { | ||
|  | 
 | ||
|  | 			throw new Error( ERROR_INPUT ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if ( VK_FORMAT_MAP[ texture.format ] === undefined ) { | ||
|  | 
 | ||
|  | 			throw new Error( ERROR_FORMAT ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if ( VK_FORMAT_MAP[ texture.format ][ texture.type ] === undefined ) { | ||
|  | 
 | ||
|  | 			throw new Error( ERROR_TYPE ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if ( VK_FORMAT_MAP[ texture.format ][ texture.type ][ texture.colorSpace ] === undefined ) { | ||
|  | 
 | ||
|  | 			throw new Error( ERROR_COLOR_SPACE ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		const array = texture.image.data; | ||
|  | 		const channelCount = getChannelCount( texture ); | ||
|  | 		const container = new KTX2Container(); | ||
|  | 
 | ||
|  | 		container.vkFormat = VK_FORMAT_MAP[ texture.format ][ texture.type ][ texture.colorSpace ]; | ||
|  | 		container.typeSize = array.BYTES_PER_ELEMENT; | ||
|  | 		container.pixelWidth = texture.image.width; | ||
|  | 		container.pixelHeight = texture.image.height; | ||
|  | 
 | ||
|  | 		if ( texture.isData3DTexture ) { | ||
|  | 
 | ||
|  | 			container.pixelDepth = texture.image.depth; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		const basicDesc = container.dataFormatDescriptor[ 0 ]; | ||
|  | 
 | ||
|  | 		basicDesc.colorModel = KHR_DF_MODEL_RGBSDA; | ||
|  | 		basicDesc.colorPrimaries = texture.colorSpace === NoColorSpace | ||
|  | 			? KHR_DF_PRIMARIES_UNSPECIFIED | ||
|  | 			: KHR_DF_PRIMARIES_BT709; | ||
|  | 		basicDesc.transferFunction = texture.colorSpace === SRGBColorSpace | ||
|  | 			? KHR_DF_TRANSFER_SRGB | ||
|  | 			: KHR_DF_TRANSFER_LINEAR; | ||
|  | 
 | ||
|  | 		basicDesc.texelBlockDimension = [ 0, 0, 0, 0 ]; | ||
|  | 
 | ||
|  | 		basicDesc.bytesPlane = [ | ||
|  | 
 | ||
|  | 			container.typeSize * channelCount, 0, 0, 0, 0, 0, 0, 0, | ||
|  | 
 | ||
|  | 		]; | ||
|  | 
 | ||
|  | 		for ( let i = 0; i < channelCount; ++ i ) { | ||
|  | 
 | ||
|  | 			let channelType = KHR_DF_CHANNEL_MAP[ i ]; | ||
|  | 
 | ||
|  | 			if ( texture.colorSpace === LinearSRGBColorSpace || texture.colorSpace === NoColorSpace ) { | ||
|  | 
 | ||
|  | 				channelType |= KHR_DF_SAMPLE_DATATYPE_LINEAR; | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if ( texture.type === FloatType || texture.type === HalfFloatType ) { | ||
|  | 
 | ||
|  | 				channelType |= KHR_DF_SAMPLE_DATATYPE_FLOAT; | ||
|  | 				channelType |= KHR_DF_SAMPLE_DATATYPE_SIGNED; | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			basicDesc.samples.push( { | ||
|  | 
 | ||
|  | 				channelType: channelType, | ||
|  | 				bitOffset: i * array.BYTES_PER_ELEMENT, | ||
|  | 				bitLength: array.BYTES_PER_ELEMENT * 8 - 1, | ||
|  | 				samplePosition: [ 0, 0, 0, 0 ], | ||
|  | 				sampleLower: texture.type === UnsignedByteType ? 0 : - 1, | ||
|  | 				sampleUpper: texture.type === UnsignedByteType ? 255 : 1, | ||
|  | 
 | ||
|  | 			} ); | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		container.levels = [ { | ||
|  | 
 | ||
|  | 			levelData: new Uint8Array( array.buffer, array.byteOffset, array.byteLength ), | ||
|  | 			uncompressedByteLength: array.byteLength, | ||
|  | 
 | ||
|  | 		} ]; | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		container.keyValue[ 'KTXwriter' ] = `three.js ${ REVISION }`; | ||
|  | 
 | ||
|  | 		//
 | ||
|  | 
 | ||
|  | 		return write( container, { keepWriter: true } ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | function toDataTexture( renderer, rtt ) { | ||
|  | 
 | ||
|  | 	const channelCount = getChannelCount( rtt.texture ); | ||
|  | 
 | ||
|  | 	let view; | ||
|  | 
 | ||
|  | 	if ( rtt.texture.type === FloatType ) { | ||
|  | 
 | ||
|  | 		view = new Float32Array( rtt.width * rtt.height * channelCount ); | ||
|  | 
 | ||
|  | 	} else if ( rtt.texture.type === HalfFloatType ) { | ||
|  | 
 | ||
|  | 		view = new Uint16Array( rtt.width * rtt.height * channelCount ); | ||
|  | 
 | ||
|  | 	} else if ( rtt.texture.type === UnsignedByteType ) { | ||
|  | 
 | ||
|  | 		view = new Uint8Array( rtt.width * rtt.height * channelCount ); | ||
|  | 
 | ||
|  | 	} else { | ||
|  | 
 | ||
|  | 		throw new Error( ERROR_TYPE ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	renderer.readRenderTargetPixels( rtt, 0, 0, rtt.width, rtt.height, view ); | ||
|  | 
 | ||
|  | 	return new DataTexture( view, rtt.width, rtt.height, rtt.texture.format, rtt.texture.type ); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | function getChannelCount( texture ) { | ||
|  | 
 | ||
|  | 	switch ( texture.format ) { | ||
|  | 
 | ||
|  | 		case RGBAFormat: | ||
|  | 
 | ||
|  | 			return 4; | ||
|  | 
 | ||
|  | 		case RGFormat: | ||
|  | 		case RGIntegerFormat: | ||
|  | 
 | ||
|  | 			return 2; | ||
|  | 
 | ||
|  | 		case RedFormat: | ||
|  | 		case RedIntegerFormat: | ||
|  | 
 | ||
|  | 			return 1; | ||
|  | 
 | ||
|  | 		default: | ||
|  | 
 | ||
|  | 			throw new Error( ERROR_FORMAT ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | } |