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 );
|
|
|
|
}
|
|
|
|
}
|