167 lines
8.0 KiB
JavaScript
167 lines
8.0 KiB
JavaScript
import { TYPES, FORMATS, BufferResource } from "@pixi/core";
|
|
import { INTERNAL_FORMAT_TO_BYTES_PER_PIXEL } from "../const.mjs";
|
|
import "../resources/index.mjs";
|
|
import { CompressedTextureResource } from "../resources/CompressedTextureResource.mjs";
|
|
const FILE_IDENTIFIER = [171, 75, 84, 88, 32, 49, 49, 187, 13, 10, 26, 10], ENDIANNESS = 67305985, KTX_FIELDS = {
|
|
FILE_IDENTIFIER: 0,
|
|
ENDIANNESS: 12,
|
|
GL_TYPE: 16,
|
|
GL_TYPE_SIZE: 20,
|
|
GL_FORMAT: 24,
|
|
GL_INTERNAL_FORMAT: 28,
|
|
GL_BASE_INTERNAL_FORMAT: 32,
|
|
PIXEL_WIDTH: 36,
|
|
PIXEL_HEIGHT: 40,
|
|
PIXEL_DEPTH: 44,
|
|
NUMBER_OF_ARRAY_ELEMENTS: 48,
|
|
NUMBER_OF_FACES: 52,
|
|
NUMBER_OF_MIPMAP_LEVELS: 56,
|
|
BYTES_OF_KEY_VALUE_DATA: 60
|
|
}, FILE_HEADER_SIZE = 64, TYPES_TO_BYTES_PER_COMPONENT = {
|
|
[TYPES.UNSIGNED_BYTE]: 1,
|
|
[TYPES.UNSIGNED_SHORT]: 2,
|
|
[TYPES.INT]: 4,
|
|
[TYPES.UNSIGNED_INT]: 4,
|
|
[TYPES.FLOAT]: 4,
|
|
[TYPES.HALF_FLOAT]: 8
|
|
}, FORMATS_TO_COMPONENTS = {
|
|
[FORMATS.RGBA]: 4,
|
|
[FORMATS.RGB]: 3,
|
|
[FORMATS.RG]: 2,
|
|
[FORMATS.RED]: 1,
|
|
[FORMATS.LUMINANCE]: 1,
|
|
[FORMATS.LUMINANCE_ALPHA]: 2,
|
|
[FORMATS.ALPHA]: 1
|
|
}, TYPES_TO_BYTES_PER_PIXEL = {
|
|
[TYPES.UNSIGNED_SHORT_4_4_4_4]: 2,
|
|
[TYPES.UNSIGNED_SHORT_5_5_5_1]: 2,
|
|
[TYPES.UNSIGNED_SHORT_5_6_5]: 2
|
|
};
|
|
function parseKTX(url, arrayBuffer, loadKeyValueData = !1) {
|
|
const dataView = new DataView(arrayBuffer);
|
|
if (!validate(url, dataView))
|
|
return null;
|
|
const littleEndian = dataView.getUint32(KTX_FIELDS.ENDIANNESS, !0) === ENDIANNESS, glType = dataView.getUint32(KTX_FIELDS.GL_TYPE, littleEndian), glFormat = dataView.getUint32(KTX_FIELDS.GL_FORMAT, littleEndian), glInternalFormat = dataView.getUint32(KTX_FIELDS.GL_INTERNAL_FORMAT, littleEndian), pixelWidth = dataView.getUint32(KTX_FIELDS.PIXEL_WIDTH, littleEndian), pixelHeight = dataView.getUint32(KTX_FIELDS.PIXEL_HEIGHT, littleEndian) || 1, pixelDepth = dataView.getUint32(KTX_FIELDS.PIXEL_DEPTH, littleEndian) || 1, numberOfArrayElements = dataView.getUint32(KTX_FIELDS.NUMBER_OF_ARRAY_ELEMENTS, littleEndian) || 1, numberOfFaces = dataView.getUint32(KTX_FIELDS.NUMBER_OF_FACES, littleEndian), numberOfMipmapLevels = dataView.getUint32(KTX_FIELDS.NUMBER_OF_MIPMAP_LEVELS, littleEndian), bytesOfKeyValueData = dataView.getUint32(KTX_FIELDS.BYTES_OF_KEY_VALUE_DATA, littleEndian);
|
|
if (pixelHeight === 0 || pixelDepth !== 1)
|
|
throw new Error("Only 2D textures are supported");
|
|
if (numberOfFaces !== 1)
|
|
throw new Error("CubeTextures are not supported by KTXLoader yet!");
|
|
if (numberOfArrayElements !== 1)
|
|
throw new Error("WebGL does not support array textures");
|
|
const blockWidth = 4, blockHeight = 4, alignedWidth = pixelWidth + 3 & -4, alignedHeight = pixelHeight + 3 & -4, imageBuffers = new Array(numberOfArrayElements);
|
|
let imagePixels = pixelWidth * pixelHeight;
|
|
glType === 0 && (imagePixels = alignedWidth * alignedHeight);
|
|
let imagePixelByteSize;
|
|
if (glType !== 0 ? TYPES_TO_BYTES_PER_COMPONENT[glType] ? imagePixelByteSize = TYPES_TO_BYTES_PER_COMPONENT[glType] * FORMATS_TO_COMPONENTS[glFormat] : imagePixelByteSize = TYPES_TO_BYTES_PER_PIXEL[glType] : imagePixelByteSize = INTERNAL_FORMAT_TO_BYTES_PER_PIXEL[glInternalFormat], imagePixelByteSize === void 0)
|
|
throw new Error("Unable to resolve the pixel format stored in the *.ktx file!");
|
|
const kvData = loadKeyValueData ? parseKvData(dataView, bytesOfKeyValueData, littleEndian) : null;
|
|
let mipByteSize = imagePixels * imagePixelByteSize, mipWidth = pixelWidth, mipHeight = pixelHeight, alignedMipWidth = alignedWidth, alignedMipHeight = alignedHeight, imageOffset = FILE_HEADER_SIZE + bytesOfKeyValueData;
|
|
for (let mipmapLevel = 0; mipmapLevel < numberOfMipmapLevels; mipmapLevel++) {
|
|
const imageSize = dataView.getUint32(imageOffset, littleEndian);
|
|
let elementOffset = imageOffset + 4;
|
|
for (let arrayElement = 0; arrayElement < numberOfArrayElements; arrayElement++) {
|
|
let mips = imageBuffers[arrayElement];
|
|
mips || (mips = imageBuffers[arrayElement] = new Array(numberOfMipmapLevels)), mips[mipmapLevel] = {
|
|
levelID: mipmapLevel,
|
|
// don't align mipWidth when texture not compressed! (glType not zero)
|
|
levelWidth: numberOfMipmapLevels > 1 || glType !== 0 ? mipWidth : alignedMipWidth,
|
|
levelHeight: numberOfMipmapLevels > 1 || glType !== 0 ? mipHeight : alignedMipHeight,
|
|
levelBuffer: new Uint8Array(arrayBuffer, elementOffset, mipByteSize)
|
|
}, elementOffset += mipByteSize;
|
|
}
|
|
imageOffset += imageSize + 4, imageOffset = imageOffset % 4 !== 0 ? imageOffset + 4 - imageOffset % 4 : imageOffset, mipWidth = mipWidth >> 1 || 1, mipHeight = mipHeight >> 1 || 1, alignedMipWidth = mipWidth + blockWidth - 1 & ~(blockWidth - 1), alignedMipHeight = mipHeight + blockHeight - 1 & ~(blockHeight - 1), mipByteSize = alignedMipWidth * alignedMipHeight * imagePixelByteSize;
|
|
}
|
|
return glType !== 0 ? {
|
|
uncompressed: imageBuffers.map((levelBuffers) => {
|
|
let buffer = levelBuffers[0].levelBuffer, convertToInt = !1;
|
|
return glType === TYPES.FLOAT ? buffer = new Float32Array(
|
|
levelBuffers[0].levelBuffer.buffer,
|
|
levelBuffers[0].levelBuffer.byteOffset,
|
|
levelBuffers[0].levelBuffer.byteLength / 4
|
|
) : glType === TYPES.UNSIGNED_INT ? (convertToInt = !0, buffer = new Uint32Array(
|
|
levelBuffers[0].levelBuffer.buffer,
|
|
levelBuffers[0].levelBuffer.byteOffset,
|
|
levelBuffers[0].levelBuffer.byteLength / 4
|
|
)) : glType === TYPES.INT && (convertToInt = !0, buffer = new Int32Array(
|
|
levelBuffers[0].levelBuffer.buffer,
|
|
levelBuffers[0].levelBuffer.byteOffset,
|
|
levelBuffers[0].levelBuffer.byteLength / 4
|
|
)), {
|
|
resource: new BufferResource(
|
|
buffer,
|
|
{
|
|
width: levelBuffers[0].levelWidth,
|
|
height: levelBuffers[0].levelHeight
|
|
}
|
|
),
|
|
type: glType,
|
|
format: convertToInt ? convertFormatToInteger(glFormat) : glFormat
|
|
};
|
|
}),
|
|
kvData
|
|
} : {
|
|
compressed: imageBuffers.map((levelBuffers) => new CompressedTextureResource(null, {
|
|
format: glInternalFormat,
|
|
width: pixelWidth,
|
|
height: pixelHeight,
|
|
levels: numberOfMipmapLevels,
|
|
levelBuffers
|
|
})),
|
|
kvData
|
|
};
|
|
}
|
|
function validate(url, dataView) {
|
|
for (let i = 0; i < FILE_IDENTIFIER.length; i++)
|
|
if (dataView.getUint8(i) !== FILE_IDENTIFIER[i])
|
|
return console.error(`${url} is not a valid *.ktx file!`), !1;
|
|
return !0;
|
|
}
|
|
function convertFormatToInteger(format) {
|
|
switch (format) {
|
|
case FORMATS.RGBA:
|
|
return FORMATS.RGBA_INTEGER;
|
|
case FORMATS.RGB:
|
|
return FORMATS.RGB_INTEGER;
|
|
case FORMATS.RG:
|
|
return FORMATS.RG_INTEGER;
|
|
case FORMATS.RED:
|
|
return FORMATS.RED_INTEGER;
|
|
default:
|
|
return format;
|
|
}
|
|
}
|
|
function parseKvData(dataView, bytesOfKeyValueData, littleEndian) {
|
|
const kvData = /* @__PURE__ */ new Map();
|
|
let bytesIntoKeyValueData = 0;
|
|
for (; bytesIntoKeyValueData < bytesOfKeyValueData; ) {
|
|
const keyAndValueByteSize = dataView.getUint32(FILE_HEADER_SIZE + bytesIntoKeyValueData, littleEndian), keyAndValueByteOffset = FILE_HEADER_SIZE + bytesIntoKeyValueData + 4, valuePadding = 3 - (keyAndValueByteSize + 3) % 4;
|
|
if (keyAndValueByteSize === 0 || keyAndValueByteSize > bytesOfKeyValueData - bytesIntoKeyValueData) {
|
|
console.error("KTXLoader: keyAndValueByteSize out of bounds");
|
|
break;
|
|
}
|
|
let keyNulByte = 0;
|
|
for (; keyNulByte < keyAndValueByteSize && dataView.getUint8(keyAndValueByteOffset + keyNulByte) !== 0; keyNulByte++)
|
|
;
|
|
if (keyNulByte === -1) {
|
|
console.error("KTXLoader: Failed to find null byte terminating kvData key");
|
|
break;
|
|
}
|
|
const key = new TextDecoder().decode(
|
|
new Uint8Array(dataView.buffer, keyAndValueByteOffset, keyNulByte)
|
|
), value = new DataView(
|
|
dataView.buffer,
|
|
keyAndValueByteOffset + keyNulByte + 1,
|
|
keyAndValueByteSize - keyNulByte - 1
|
|
);
|
|
kvData.set(key, value), bytesIntoKeyValueData += 4 + keyAndValueByteSize + valuePadding;
|
|
}
|
|
return kvData;
|
|
}
|
|
export {
|
|
FORMATS_TO_COMPONENTS,
|
|
TYPES_TO_BYTES_PER_COMPONENT,
|
|
TYPES_TO_BYTES_PER_PIXEL,
|
|
parseKTX
|
|
};
|
|
//# sourceMappingURL=parseKTX.mjs.map
|