Add existing to tracked

This commit is contained in:
Jay
2026-08-11 09:53:42 -04:00
parent afe07f3055
commit ffd6e3d73c
8531 changed files with 4396230 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
export class BufferReader {
private _dataView: DataView;
private _littleEndian: boolean;
public _offset: number;
constructor(data: Uint8Array, byteOffset: number, byteLength: number, littleEndian: boolean) {
this._dataView = new DataView(data.buffer, data.byteOffset + byteOffset, byteLength);
this._littleEndian = littleEndian;
this._offset = 0;
}
_nextUint8(): number {
const value = this._dataView.getUint8(this._offset);
this._offset += 1;
return value;
}
_nextUint16(): number {
const value = this._dataView.getUint16(this._offset, this._littleEndian);
this._offset += 2;
return value;
}
_nextUint32(): number {
const value = this._dataView.getUint32(this._offset, this._littleEndian);
this._offset += 4;
return value;
}
_nextUint64(): number {
const left = this._dataView.getUint32(this._offset, this._littleEndian);
const right = this._dataView.getUint32(this._offset + 4, this._littleEndian);
// TODO(cleanup): Just test this...
// const value = this._littleEndian ? left + (2 ** 32 * right) : (2 ** 32 * left) + right;
const value = left + 2 ** 32 * right;
this._offset += 8;
return value;
}
_nextInt32(): number {
const value = this._dataView.getInt32(this._offset, this._littleEndian);
this._offset += 4;
return value;
}
_nextUint8Array(len: number): Uint8Array {
const value = new Uint8Array(this._dataView.buffer, this._dataView.byteOffset + this._offset, len);
this._offset += len;
return value;
}
_skip(bytes: number): this {
this._offset += bytes;
return this;
}
_scan(maxByteLength: number, term = 0x00): Uint8Array {
const byteOffset = this._offset;
let byteLength = 0;
while (this._dataView.getUint8(this._offset) !== term && byteLength < maxByteLength) {
byteLength++;
this._offset++;
}
if (byteLength < maxByteLength) this._offset++;
return new Uint8Array(this._dataView.buffer, this._dataView.byteOffset + byteOffset, byteLength);
}
}
+24
View File
@@ -0,0 +1,24 @@
///////////////////////////////////////////////////
// Common.
///////////////////////////////////////////////////
// Injected at compile time, from $npm_package_version.
declare const PACKAGE_VERSION: string;
export const KTX_WRITER: string = `KTX-Parse v${PACKAGE_VERSION}`;
export const NUL: Uint8Array = new Uint8Array([0x00]);
export type vec3 = [number, number, number];
export type vec2 = [number, number, number];
///////////////////////////////////////////////////
// KTX2 Header.
///////////////////////////////////////////////////
export const KTX2_ID: number[] = [
// '´', 'K', 'T', 'X', '2', '0', 'ª', '\r', '\n', '\x1A', '\n'
0xab, 0x4b, 0x54, 0x58, 0x20, 0x32, 0x30, 0xbb, 0x0d, 0x0a, 0x1a, 0x0a,
];
export const HEADER_BYTE_LENGTH = 68; // 13 * 4 + 2 * 8
+511
View File
@@ -0,0 +1,511 @@
///////////////////////////////////////////////////
// KTX2 Header.
///////////////////////////////////////////////////
export const KHR_SUPERCOMPRESSION_NONE = 0;
export const KHR_SUPERCOMPRESSION_BASISLZ = 1;
export const KHR_SUPERCOMPRESSION_ZSTD = 2;
export const KHR_SUPERCOMPRESSION_ZLIB = 3;
export type Supercompression =
| typeof KHR_SUPERCOMPRESSION_NONE
| typeof KHR_SUPERCOMPRESSION_BASISLZ
| typeof KHR_SUPERCOMPRESSION_ZSTD
| typeof KHR_SUPERCOMPRESSION_ZLIB;
///////////////////////////////////////////////////
// Data Format Descriptor (DFD).
///////////////////////////////////////////////////
export const KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT = 0;
export const KHR_DF_VENDORID_KHRONOS = 0;
export const KHR_DF_VERSION = 2;
export const KHR_DF_MODEL_UNSPECIFIED = 0;
export const KHR_DF_MODEL_RGBSDA = 1;
// ...
export const KHR_DF_MODEL_ETC1 = 160;
export const KHR_DF_MODEL_ETC2 = 161;
export const KHR_DF_MODEL_ASTC = 162;
export const KHR_DF_MODEL_ETC1S = 163;
export const KHR_DF_MODEL_UASTC = 166;
export const KHR_DF_FLAG_ALPHA_STRAIGHT = 0;
export const KHR_DF_FLAG_ALPHA_PREMULTIPLIED = 1;
export const KHR_DF_TRANSFER_UNSPECIFIED = 0;
export const KHR_DF_TRANSFER_LINEAR = 1;
export const KHR_DF_TRANSFER_SRGB = 2;
export const KHR_DF_TRANSFER_ITU = 3;
export const KHR_DF_TRANSFER_NTSC = 4;
export const KHR_DF_TRANSFER_SLOG = 5;
export const KHR_DF_TRANSFER_SLOG2 = 6;
export const KHR_DF_TRANSFER_BT1886 = 7;
export const KHR_DF_TRANSFER_HLG_OETF = 8;
export const KHR_DF_TRANSFER_HLG_EOTF = 9;
export const KHR_DF_TRANSFER_PQ_EOTF = 10;
export const KHR_DF_TRANSFER_PQ_OETF = 11;
export const KHR_DF_TRANSFER_DCIP3 = 12;
export const KHR_DF_TRANSFER_PAL_OETF = 13;
export const KHR_DF_TRANSFER_PAL625_EOTF = 14;
export const KHR_DF_TRANSFER_ST240 = 15;
export const KHR_DF_TRANSFER_ACESCC = 16;
export const KHR_DF_TRANSFER_ACESCCT = 17;
export const KHR_DF_TRANSFER_ADOBERGB = 18;
export type Transfer =
| typeof KHR_DF_TRANSFER_UNSPECIFIED
| typeof KHR_DF_TRANSFER_LINEAR
| typeof KHR_DF_TRANSFER_SRGB
| typeof KHR_DF_TRANSFER_ITU
| typeof KHR_DF_TRANSFER_NTSC
| typeof KHR_DF_TRANSFER_SLOG
| typeof KHR_DF_TRANSFER_SLOG2
| typeof KHR_DF_TRANSFER_BT1886
| typeof KHR_DF_TRANSFER_HLG_OETF
| typeof KHR_DF_TRANSFER_HLG_EOTF
| typeof KHR_DF_TRANSFER_PQ_EOTF
| typeof KHR_DF_TRANSFER_PQ_OETF
| typeof KHR_DF_TRANSFER_DCIP3
| typeof KHR_DF_TRANSFER_PAL_OETF
| typeof KHR_DF_TRANSFER_PAL625_EOTF
| typeof KHR_DF_TRANSFER_ST240
| typeof KHR_DF_TRANSFER_ACESCC
| typeof KHR_DF_TRANSFER_ACESCCT
| typeof KHR_DF_TRANSFER_ADOBERGB;
export const KHR_DF_PRIMARIES_UNSPECIFIED = 0;
export const KHR_DF_PRIMARIES_BT709 = 1;
export const KHR_DF_PRIMARIES_BT601_EBU = 2;
export const KHR_DF_PRIMARIES_BT601_SMPTE = 3;
export const KHR_DF_PRIMARIES_BT2020 = 4;
export const KHR_DF_PRIMARIES_CIEXYZ = 5;
export const KHR_DF_PRIMARIES_ACES = 6;
export const KHR_DF_PRIMARIES_ACESCC = 7;
export const KHR_DF_PRIMARIES_NTSC1953 = 8;
export const KHR_DF_PRIMARIES_PAL525 = 9;
export const KHR_DF_PRIMARIES_DISPLAYP3 = 10;
export const KHR_DF_PRIMARIES_ADOBERGB = 11;
export type Primaries =
| typeof KHR_DF_PRIMARIES_UNSPECIFIED
| typeof KHR_DF_PRIMARIES_BT709
| typeof KHR_DF_PRIMARIES_BT601_EBU
| typeof KHR_DF_PRIMARIES_BT601_SMPTE
| typeof KHR_DF_PRIMARIES_BT2020
| typeof KHR_DF_PRIMARIES_CIEXYZ
| typeof KHR_DF_PRIMARIES_ACES
| typeof KHR_DF_PRIMARIES_ACESCC
| typeof KHR_DF_PRIMARIES_NTSC1953
| typeof KHR_DF_PRIMARIES_PAL525
| typeof KHR_DF_PRIMARIES_DISPLAYP3
| typeof KHR_DF_PRIMARIES_ADOBERGB;
export const KHR_DF_CHANNEL_RGBSDA_RED = 0;
export const KHR_DF_CHANNEL_RGBSDA_GREEN = 1;
export const KHR_DF_CHANNEL_RGBSDA_BLUE = 2;
export const KHR_DF_CHANNEL_RGBSDA_STENCIL = 13;
export const KHR_DF_CHANNEL_RGBSDA_DEPTH = 14;
export const KHR_DF_CHANNEL_RGBSDA_ALPHA = 15;
export type Channel =
| typeof KHR_DF_CHANNEL_RGBSDA_RED
| typeof KHR_DF_CHANNEL_RGBSDA_GREEN
| typeof KHR_DF_CHANNEL_RGBSDA_BLUE
| typeof KHR_DF_CHANNEL_RGBSDA_STENCIL
| typeof KHR_DF_CHANNEL_RGBSDA_DEPTH
| typeof KHR_DF_CHANNEL_RGBSDA_ALPHA;
export const KHR_DF_SAMPLE_DATATYPE_FLOAT = 0x80;
export const KHR_DF_SAMPLE_DATATYPE_SIGNED = 0x40;
export const KHR_DF_SAMPLE_DATATYPE_EXPONENT = 0x20;
export const KHR_DF_SAMPLE_DATATYPE_LINEAR = 0x10;
export type SampleDatatype =
| typeof KHR_DF_SAMPLE_DATATYPE_FLOAT
| typeof KHR_DF_SAMPLE_DATATYPE_SIGNED
| typeof KHR_DF_SAMPLE_DATATYPE_EXPONENT
| typeof KHR_DF_SAMPLE_DATATYPE_LINEAR;
///////////////////////////////////////////////////
// VK FORMAT.
///////////////////////////////////////////////////
export const VK_FORMAT_UNDEFINED = 0;
export const VK_FORMAT_R4G4_UNORM_PACK8 = 1;
export const VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2;
export const VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3;
export const VK_FORMAT_R5G6B5_UNORM_PACK16 = 4;
export const VK_FORMAT_B5G6R5_UNORM_PACK16 = 5;
export const VK_FORMAT_R5G5B5A1_UNORM_PACK16 = 6;
export const VK_FORMAT_B5G5R5A1_UNORM_PACK16 = 7;
export const VK_FORMAT_A1R5G5B5_UNORM_PACK16 = 8;
export const VK_FORMAT_R8_UNORM = 9;
export const VK_FORMAT_R8_SNORM = 10;
export const VK_FORMAT_R8_UINT = 13;
export const VK_FORMAT_R8_SINT = 14;
export const VK_FORMAT_R8_SRGB = 15;
export const VK_FORMAT_R8G8_UNORM = 16;
export const VK_FORMAT_R8G8_SNORM = 17;
export const VK_FORMAT_R8G8_UINT = 20;
export const VK_FORMAT_R8G8_SINT = 21;
export const VK_FORMAT_R8G8_SRGB = 22;
export const VK_FORMAT_R8G8B8_UNORM = 23;
export const VK_FORMAT_R8G8B8_SNORM = 24;
export const VK_FORMAT_R8G8B8_UINT = 27;
export const VK_FORMAT_R8G8B8_SINT = 28;
export const VK_FORMAT_R8G8B8_SRGB = 29;
export const VK_FORMAT_B8G8R8_UNORM = 30;
export const VK_FORMAT_B8G8R8_SNORM = 31;
export const VK_FORMAT_B8G8R8_UINT = 34;
export const VK_FORMAT_B8G8R8_SINT = 35;
export const VK_FORMAT_B8G8R8_SRGB = 36;
export const VK_FORMAT_R8G8B8A8_UNORM = 37;
export const VK_FORMAT_R8G8B8A8_SNORM = 38;
export const VK_FORMAT_R8G8B8A8_UINT = 41;
export const VK_FORMAT_R8G8B8A8_SINT = 42;
export const VK_FORMAT_R8G8B8A8_SRGB = 43;
export const VK_FORMAT_B8G8R8A8_UNORM = 44;
export const VK_FORMAT_B8G8R8A8_SNORM = 45;
export const VK_FORMAT_B8G8R8A8_UINT = 48;
export const VK_FORMAT_B8G8R8A8_SINT = 49;
export const VK_FORMAT_B8G8R8A8_SRGB = 50;
export const VK_FORMAT_A2R10G10B10_UNORM_PACK32 = 58;
export const VK_FORMAT_A2R10G10B10_SNORM_PACK32 = 59;
export const VK_FORMAT_A2R10G10B10_UINT_PACK32 = 62;
export const VK_FORMAT_A2R10G10B10_SINT_PACK32 = 63;
export const VK_FORMAT_A2B10G10R10_UNORM_PACK32 = 64;
export const VK_FORMAT_A2B10G10R10_SNORM_PACK32 = 65;
export const VK_FORMAT_A2B10G10R10_UINT_PACK32 = 68;
export const VK_FORMAT_A2B10G10R10_SINT_PACK32 = 69;
export const VK_FORMAT_R16_UNORM = 70;
export const VK_FORMAT_R16_SNORM = 71;
export const VK_FORMAT_R16_UINT = 74;
export const VK_FORMAT_R16_SINT = 75;
export const VK_FORMAT_R16_SFLOAT = 76;
export const VK_FORMAT_R16G16_UNORM = 77;
export const VK_FORMAT_R16G16_SNORM = 78;
export const VK_FORMAT_R16G16_UINT = 81;
export const VK_FORMAT_R16G16_SINT = 82;
export const VK_FORMAT_R16G16_SFLOAT = 83;
export const VK_FORMAT_R16G16B16_UNORM = 84;
export const VK_FORMAT_R16G16B16_SNORM = 85;
export const VK_FORMAT_R16G16B16_UINT = 88;
export const VK_FORMAT_R16G16B16_SINT = 89;
export const VK_FORMAT_R16G16B16_SFLOAT = 90;
export const VK_FORMAT_R16G16B16A16_UNORM = 91;
export const VK_FORMAT_R16G16B16A16_SNORM = 92;
export const VK_FORMAT_R16G16B16A16_UINT = 95;
export const VK_FORMAT_R16G16B16A16_SINT = 96;
export const VK_FORMAT_R16G16B16A16_SFLOAT = 97;
export const VK_FORMAT_R32_UINT = 98;
export const VK_FORMAT_R32_SINT = 99;
export const VK_FORMAT_R32_SFLOAT = 100;
export const VK_FORMAT_R32G32_UINT = 101;
export const VK_FORMAT_R32G32_SINT = 102;
export const VK_FORMAT_R32G32_SFLOAT = 103;
export const VK_FORMAT_R32G32B32_UINT = 104;
export const VK_FORMAT_R32G32B32_SINT = 105;
export const VK_FORMAT_R32G32B32_SFLOAT = 106;
export const VK_FORMAT_R32G32B32A32_UINT = 107;
export const VK_FORMAT_R32G32B32A32_SINT = 108;
export const VK_FORMAT_R32G32B32A32_SFLOAT = 109;
export const VK_FORMAT_R64_UINT = 110;
export const VK_FORMAT_R64_SINT = 111;
export const VK_FORMAT_R64_SFLOAT = 112;
export const VK_FORMAT_R64G64_UINT = 113;
export const VK_FORMAT_R64G64_SINT = 114;
export const VK_FORMAT_R64G64_SFLOAT = 115;
export const VK_FORMAT_R64G64B64_UINT = 116;
export const VK_FORMAT_R64G64B64_SINT = 117;
export const VK_FORMAT_R64G64B64_SFLOAT = 118;
export const VK_FORMAT_R64G64B64A64_UINT = 119;
export const VK_FORMAT_R64G64B64A64_SINT = 120;
export const VK_FORMAT_R64G64B64A64_SFLOAT = 121;
export const VK_FORMAT_B10G11R11_UFLOAT_PACK32 = 122;
export const VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 = 123;
export const VK_FORMAT_D16_UNORM = 124;
export const VK_FORMAT_X8_D24_UNORM_PACK32 = 125;
export const VK_FORMAT_D32_SFLOAT = 126;
export const VK_FORMAT_S8_UINT = 127;
export const VK_FORMAT_D16_UNORM_S8_UINT = 128;
export const VK_FORMAT_D24_UNORM_S8_UINT = 129;
export const VK_FORMAT_D32_SFLOAT_S8_UINT = 130;
export const VK_FORMAT_BC1_RGB_UNORM_BLOCK = 131;
export const VK_FORMAT_BC1_RGB_SRGB_BLOCK = 132;
export const VK_FORMAT_BC1_RGBA_UNORM_BLOCK = 133;
export const VK_FORMAT_BC1_RGBA_SRGB_BLOCK = 134;
export const VK_FORMAT_BC2_UNORM_BLOCK = 135;
export const VK_FORMAT_BC2_SRGB_BLOCK = 136;
export const VK_FORMAT_BC3_UNORM_BLOCK = 137;
export const VK_FORMAT_BC3_SRGB_BLOCK = 138;
export const VK_FORMAT_BC4_UNORM_BLOCK = 139;
export const VK_FORMAT_BC4_SNORM_BLOCK = 140;
export const VK_FORMAT_BC5_UNORM_BLOCK = 141;
export const VK_FORMAT_BC5_SNORM_BLOCK = 142;
export const VK_FORMAT_BC6H_UFLOAT_BLOCK = 143;
export const VK_FORMAT_BC6H_SFLOAT_BLOCK = 144;
export const VK_FORMAT_BC7_UNORM_BLOCK = 145;
export const VK_FORMAT_BC7_SRGB_BLOCK = 146;
export const VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK = 147;
export const VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK = 148;
export const VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK = 149;
export const VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK = 150;
export const VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK = 151;
export const VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK = 152;
export const VK_FORMAT_EAC_R11_UNORM_BLOCK = 153;
export const VK_FORMAT_EAC_R11_SNORM_BLOCK = 154;
export const VK_FORMAT_EAC_R11G11_UNORM_BLOCK = 155;
export const VK_FORMAT_EAC_R11G11_SNORM_BLOCK = 156;
export const VK_FORMAT_ASTC_4x4_UNORM_BLOCK = 157;
export const VK_FORMAT_ASTC_4x4_SRGB_BLOCK = 158;
export const VK_FORMAT_ASTC_5x4_UNORM_BLOCK = 159;
export const VK_FORMAT_ASTC_5x4_SRGB_BLOCK = 160;
export const VK_FORMAT_ASTC_5x5_UNORM_BLOCK = 161;
export const VK_FORMAT_ASTC_5x5_SRGB_BLOCK = 162;
export const VK_FORMAT_ASTC_6x5_UNORM_BLOCK = 163;
export const VK_FORMAT_ASTC_6x5_SRGB_BLOCK = 164;
export const VK_FORMAT_ASTC_6x6_UNORM_BLOCK = 165;
export const VK_FORMAT_ASTC_6x6_SRGB_BLOCK = 166;
export const VK_FORMAT_ASTC_8x5_UNORM_BLOCK = 167;
export const VK_FORMAT_ASTC_8x5_SRGB_BLOCK = 168;
export const VK_FORMAT_ASTC_8x6_UNORM_BLOCK = 169;
export const VK_FORMAT_ASTC_8x6_SRGB_BLOCK = 170;
export const VK_FORMAT_ASTC_8x8_UNORM_BLOCK = 171;
export const VK_FORMAT_ASTC_8x8_SRGB_BLOCK = 172;
export const VK_FORMAT_ASTC_10x5_UNORM_BLOCK = 173;
export const VK_FORMAT_ASTC_10x5_SRGB_BLOCK = 174;
export const VK_FORMAT_ASTC_10x6_UNORM_BLOCK = 175;
export const VK_FORMAT_ASTC_10x6_SRGB_BLOCK = 176;
export const VK_FORMAT_ASTC_10x8_UNORM_BLOCK = 177;
export const VK_FORMAT_ASTC_10x8_SRGB_BLOCK = 178;
export const VK_FORMAT_ASTC_10x10_UNORM_BLOCK = 179;
export const VK_FORMAT_ASTC_10x10_SRGB_BLOCK = 180;
export const VK_FORMAT_ASTC_12x10_UNORM_BLOCK = 181;
export const VK_FORMAT_ASTC_12x10_SRGB_BLOCK = 182;
export const VK_FORMAT_ASTC_12x12_UNORM_BLOCK = 183;
export const VK_FORMAT_ASTC_12x12_SRGB_BLOCK = 184;
export const VK_FORMAT_R10X6_UNORM_PACK16 = 1000156007;
export const VK_FORMAT_R10X6G10X6_UNORM_2PACK16 = 1000156008;
export const VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16 = 1000156009;
export const VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16 = 1000156010;
export const VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16 = 1000156011;
export const VK_FORMAT_R12X4_UNORM_PACK16 = 1000156017;
export const VK_FORMAT_R12X4G12X4_UNORM_2PACK16 = 1000156018;
export const VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16 = 1000156019;
export const VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16 = 1000156020;
export const VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16 = 1000156021;
export const VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG = 1000054000;
export const VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG = 1000054001;
export const VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG = 1000054002;
export const VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG = 1000054003;
export const VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG = 1000054004;
export const VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG = 1000054005;
export const VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG = 1000054006;
export const VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG = 1000054007;
export const VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT = 1000066000;
export const VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT = 1000066001;
export const VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT = 1000066002;
export const VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT = 1000066003;
export const VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT = 1000066004;
export const VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT = 1000066005;
export const VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT = 1000066006;
export const VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT = 1000066007;
export const VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT = 1000066008;
export const VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT = 1000066009;
export const VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT = 1000066010;
export const VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT = 1000066011;
export const VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT = 1000066012;
export const VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT = 1000066013;
export const VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT = 1000340000;
export const VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT = 1000340001;
export type VKFormat =
| typeof VK_FORMAT_UNDEFINED
| typeof VK_FORMAT_R4G4_UNORM_PACK8
| typeof VK_FORMAT_R4G4B4A4_UNORM_PACK16
| typeof VK_FORMAT_B4G4R4A4_UNORM_PACK16
| typeof VK_FORMAT_R5G6B5_UNORM_PACK16
| typeof VK_FORMAT_B5G6R5_UNORM_PACK16
| typeof VK_FORMAT_R5G5B5A1_UNORM_PACK16
| typeof VK_FORMAT_B5G5R5A1_UNORM_PACK16
| typeof VK_FORMAT_A1R5G5B5_UNORM_PACK16
| typeof VK_FORMAT_R8_UNORM
| typeof VK_FORMAT_R8_SNORM
| typeof VK_FORMAT_R8_UINT
| typeof VK_FORMAT_R8_SINT
| typeof VK_FORMAT_R8_SRGB
| typeof VK_FORMAT_R8G8_UNORM
| typeof VK_FORMAT_R8G8_SNORM
| typeof VK_FORMAT_R8G8_UINT
| typeof VK_FORMAT_R8G8_SINT
| typeof VK_FORMAT_R8G8_SRGB
| typeof VK_FORMAT_R8G8B8_UNORM
| typeof VK_FORMAT_R8G8B8_SNORM
| typeof VK_FORMAT_R8G8B8_UINT
| typeof VK_FORMAT_R8G8B8_SINT
| typeof VK_FORMAT_R8G8B8_SRGB
| typeof VK_FORMAT_B8G8R8_UNORM
| typeof VK_FORMAT_B8G8R8_SNORM
| typeof VK_FORMAT_B8G8R8_UINT
| typeof VK_FORMAT_B8G8R8_SINT
| typeof VK_FORMAT_B8G8R8_SRGB
| typeof VK_FORMAT_R8G8B8A8_UNORM
| typeof VK_FORMAT_R8G8B8A8_SNORM
| typeof VK_FORMAT_R8G8B8A8_UINT
| typeof VK_FORMAT_R8G8B8A8_SINT
| typeof VK_FORMAT_R8G8B8A8_SRGB
| typeof VK_FORMAT_B8G8R8A8_UNORM
| typeof VK_FORMAT_B8G8R8A8_SNORM
| typeof VK_FORMAT_B8G8R8A8_UINT
| typeof VK_FORMAT_B8G8R8A8_SINT
| typeof VK_FORMAT_B8G8R8A8_SRGB
| typeof VK_FORMAT_A2R10G10B10_UNORM_PACK32
| typeof VK_FORMAT_A2R10G10B10_SNORM_PACK32
| typeof VK_FORMAT_A2R10G10B10_UINT_PACK32
| typeof VK_FORMAT_A2R10G10B10_SINT_PACK32
| typeof VK_FORMAT_A2B10G10R10_UNORM_PACK32
| typeof VK_FORMAT_A2B10G10R10_SNORM_PACK32
| typeof VK_FORMAT_A2B10G10R10_UINT_PACK32
| typeof VK_FORMAT_A2B10G10R10_SINT_PACK32
| typeof VK_FORMAT_R16_UNORM
| typeof VK_FORMAT_R16_SNORM
| typeof VK_FORMAT_R16_UINT
| typeof VK_FORMAT_R16_SINT
| typeof VK_FORMAT_R16_SFLOAT
| typeof VK_FORMAT_R16G16_UNORM
| typeof VK_FORMAT_R16G16_SNORM
| typeof VK_FORMAT_R16G16_UINT
| typeof VK_FORMAT_R16G16_SINT
| typeof VK_FORMAT_R16G16_SFLOAT
| typeof VK_FORMAT_R16G16B16_UNORM
| typeof VK_FORMAT_R16G16B16_SNORM
| typeof VK_FORMAT_R16G16B16_UINT
| typeof VK_FORMAT_R16G16B16_SINT
| typeof VK_FORMAT_R16G16B16_SFLOAT
| typeof VK_FORMAT_R16G16B16A16_UNORM
| typeof VK_FORMAT_R16G16B16A16_SNORM
| typeof VK_FORMAT_R16G16B16A16_UINT
| typeof VK_FORMAT_R16G16B16A16_SINT
| typeof VK_FORMAT_R16G16B16A16_SFLOAT
| typeof VK_FORMAT_R32_UINT
| typeof VK_FORMAT_R32_SINT
| typeof VK_FORMAT_R32_SFLOAT
| typeof VK_FORMAT_R32G32_UINT
| typeof VK_FORMAT_R32G32_SINT
| typeof VK_FORMAT_R32G32_SFLOAT
| typeof VK_FORMAT_R32G32B32_UINT
| typeof VK_FORMAT_R32G32B32_SINT
| typeof VK_FORMAT_R32G32B32_SFLOAT
| typeof VK_FORMAT_R32G32B32A32_UINT
| typeof VK_FORMAT_R32G32B32A32_SINT
| typeof VK_FORMAT_R32G32B32A32_SFLOAT
| typeof VK_FORMAT_R64_UINT
| typeof VK_FORMAT_R64_SINT
| typeof VK_FORMAT_R64_SFLOAT
| typeof VK_FORMAT_R64G64_UINT
| typeof VK_FORMAT_R64G64_SINT
| typeof VK_FORMAT_R64G64_SFLOAT
| typeof VK_FORMAT_R64G64B64_UINT
| typeof VK_FORMAT_R64G64B64_SINT
| typeof VK_FORMAT_R64G64B64_SFLOAT
| typeof VK_FORMAT_R64G64B64A64_UINT
| typeof VK_FORMAT_R64G64B64A64_SINT
| typeof VK_FORMAT_R64G64B64A64_SFLOAT
| typeof VK_FORMAT_B10G11R11_UFLOAT_PACK32
| typeof VK_FORMAT_E5B9G9R9_UFLOAT_PACK32
| typeof VK_FORMAT_D16_UNORM
| typeof VK_FORMAT_X8_D24_UNORM_PACK32
| typeof VK_FORMAT_D32_SFLOAT
| typeof VK_FORMAT_S8_UINT
| typeof VK_FORMAT_D16_UNORM_S8_UINT
| typeof VK_FORMAT_D24_UNORM_S8_UINT
| typeof VK_FORMAT_D32_SFLOAT_S8_UINT
| typeof VK_FORMAT_BC1_RGB_UNORM_BLOCK
| typeof VK_FORMAT_BC1_RGB_SRGB_BLOCK
| typeof VK_FORMAT_BC1_RGBA_UNORM_BLOCK
| typeof VK_FORMAT_BC1_RGBA_SRGB_BLOCK
| typeof VK_FORMAT_BC2_UNORM_BLOCK
| typeof VK_FORMAT_BC2_SRGB_BLOCK
| typeof VK_FORMAT_BC3_UNORM_BLOCK
| typeof VK_FORMAT_BC3_SRGB_BLOCK
| typeof VK_FORMAT_BC4_UNORM_BLOCK
| typeof VK_FORMAT_BC4_SNORM_BLOCK
| typeof VK_FORMAT_BC5_UNORM_BLOCK
| typeof VK_FORMAT_BC5_SNORM_BLOCK
| typeof VK_FORMAT_BC6H_UFLOAT_BLOCK
| typeof VK_FORMAT_BC6H_SFLOAT_BLOCK
| typeof VK_FORMAT_BC7_UNORM_BLOCK
| typeof VK_FORMAT_BC7_SRGB_BLOCK
| typeof VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK
| typeof VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK
| typeof VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK
| typeof VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK
| typeof VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK
| typeof VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK
| typeof VK_FORMAT_EAC_R11_UNORM_BLOCK
| typeof VK_FORMAT_EAC_R11_SNORM_BLOCK
| typeof VK_FORMAT_EAC_R11G11_UNORM_BLOCK
| typeof VK_FORMAT_EAC_R11G11_SNORM_BLOCK
| typeof VK_FORMAT_ASTC_4x4_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_4x4_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_5x4_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_5x4_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_5x5_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_5x5_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_6x5_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_6x5_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_6x6_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_6x6_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_8x5_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_8x5_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_8x6_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_8x6_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_8x8_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_8x8_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_10x5_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_10x5_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_10x6_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_10x6_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_10x8_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_10x8_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_10x10_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_10x10_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_12x10_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_12x10_SRGB_BLOCK
| typeof VK_FORMAT_ASTC_12x12_UNORM_BLOCK
| typeof VK_FORMAT_ASTC_12x12_SRGB_BLOCK
| typeof VK_FORMAT_R10X6_UNORM_PACK16
| typeof VK_FORMAT_R10X6G10X6_UNORM_2PACK16
| typeof VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16
| typeof VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16
| typeof VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16
| typeof VK_FORMAT_R12X4_UNORM_PACK16
| typeof VK_FORMAT_R12X4G12X4_UNORM_2PACK16
| typeof VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16
| typeof VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16
| typeof VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16
| typeof VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG
| typeof VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG
| typeof VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG
| typeof VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG
| typeof VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG
| typeof VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG
| typeof VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG
| typeof VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG
| typeof VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT
| typeof VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT
| typeof VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT;
+132
View File
@@ -0,0 +1,132 @@
import type { Supercompression, VKFormat } from './constants.js';
/**
* Represents an unpacked KTX 2.0 texture container. Data for individual mip levels are stored in
* the `.levels` array, typically compressed in Basis Universal formats. Additional properties
* provide metadata required to process, transcode, and upload these textures.
*/
export interface KTX2Container {
/**
* Specifies the image format using Vulkan VkFormat enum values. When using Basis Universal
* texture formats, `vkFormat` must be VK_FORMAT_UNDEFINED.
*/
vkFormat: VKFormat;
/**
* Size of the data type in bytes used to upload the data to a graphics API. When `vkFormat` is
* VK_FORMAT_UNDEFINED, `typeSize` must be 1.
*/
typeSize: number;
/** Width of the texture image for level 0, in pixels. */
pixelWidth: number;
/** Height of the texture image for level 0, in pixels. */
pixelHeight: number;
/** Depth of the texture image for level 0, in pixels (3D textures only). */
pixelDepth: number;
/** Number of array elements (array textures only). */
layerCount: number;
/**
* Number of cubemap faces. For cubemaps and cubemap arrays, `faceCount` must be 6. For all
* other textures, `faceCount` must be 1. Cubemap faces are stored in +X, -X, +Y, -Y, +Z, -Z
* order.
*/
faceCount: number;
/**
* Number of levels in the mipmap level array. Mipmap pyramid may not necessarily be complete.
* `levelCount = 1` implies that the file contains only the base level, and is not meant to have
* other levels. `levelCount = 0` implies that the file contains only the base level, and that
* other levels should be generated by the loader at runtime. For block-compressed formats,
* `levelCount = 0` is disallowed.
*/
levelCount: number;
/** Indicates which supercompression scheme has been applied to mip level images, if any. */
supercompressionScheme: Supercompression;
/** Mip levels, ordered largest (original) to smallest (~1px). */
levels: KTX2Level[];
/** Data Format Descriptor. */
dataFormatDescriptor: KTX2DataFormatDescriptorBasicFormat[];
/** Key/Value Data. */
keyValue: { [key: string]: string | Uint8Array };
/** Supercompression Global Data. */
globalData: KTX2GlobalDataBasisLZ | null;
}
///////////////////////////////////////////////////
// Mip Levels.
///////////////////////////////////////////////////
export interface KTX2Level {
/** Compressed data of the mip level. */
levelData: Uint8Array;
/**
* Size of the mip level after reflation from supercompression, if applicable. When
* `supercompressionType` is BASISLZ, `uncompressedByteLength` must be 0. When
* `supercompressionType` is `NONE`, `uncompressedByteLength` must match the `levelData` byte
* length.
*
* _**NOTICE:** this implies that for formats such as UASTC, `uncompressedByteLength` may
* indicate size after ZSTD reflation (and of transcoded ASTC data), but does _not_ indicate
* size of decoded RGBA32 pixels._
*/
uncompressedByteLength: number;
}
///////////////////////////////////////////////////
// Data Format Descriptor (DFD).
///////////////////////////////////////////////////
export interface KTX2DataFormatDescriptorBasicFormat {
vendorId: number;
descriptorType: number;
versionNumber: number;
colorModel: number;
colorPrimaries: number;
transferFunction: number;
flags: number;
texelBlockDimension: [number, number, number, number];
bytesPlane: [number, number, number, number, number, number, number, number];
samples: KTX2BasicFormatSample[];
}
export interface KTX2BasicFormatSample {
bitOffset: number;
bitLength: number;
channelType: number;
samplePosition: number[];
sampleLower: number;
sampleUpper: number;
}
///////////////////////////////////////////////////
// Supercompression Global Data.
///////////////////////////////////////////////////
export interface KTX2GlobalDataBasisLZ {
endpointCount: number;
selectorCount: number;
imageDescs: KTX2GlobalDataBasisLZImageDesc[];
endpointsData: Uint8Array;
selectorsData: Uint8Array;
tablesData: Uint8Array;
extendedData: Uint8Array;
}
interface KTX2GlobalDataBasisLZImageDesc {
imageFlags: number;
rgbSliceByteOffset: number;
rgbSliceByteLength: number;
alphaSliceByteOffset: number;
alphaSliceByteLength: number;
}
+49
View File
@@ -0,0 +1,49 @@
import {
KHR_DF_FLAG_ALPHA_STRAIGHT,
KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT,
KHR_DF_MODEL_UNSPECIFIED,
KHR_DF_PRIMARIES_BT709,
KHR_DF_TRANSFER_SRGB,
KHR_DF_VENDORID_KHRONOS,
KHR_DF_VERSION,
KHR_SUPERCOMPRESSION_NONE,
VK_FORMAT_UNDEFINED,
} from './constants.js';
import type { KTX2Container } from './container.js';
/**
* Creates a 'default' {@link KTX2Container} object, initialized with common
* configuration wfor BT709 primaries and sRGB transfer, without pixel data.
* There's nothing particularly special about the 'default' container; creating
* the KTX2Container object explicitly is also fine.
*/
export function createDefaultContainer(): KTX2Container {
return {
vkFormat: VK_FORMAT_UNDEFINED,
typeSize: 1,
pixelWidth: 0,
pixelHeight: 0,
pixelDepth: 0,
layerCount: 0,
faceCount: 1,
levelCount: 0,
supercompressionScheme: KHR_SUPERCOMPRESSION_NONE,
levels: [],
dataFormatDescriptor: [
{
vendorId: KHR_DF_VENDORID_KHRONOS,
descriptorType: KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT,
versionNumber: KHR_DF_VERSION,
colorModel: KHR_DF_MODEL_UNSPECIFIED,
colorPrimaries: KHR_DF_PRIMARIES_BT709,
transferFunction: KHR_DF_TRANSFER_SRGB,
flags: KHR_DF_FLAG_ALPHA_STRAIGHT,
texelBlockDimension: [0, 0, 0, 0],
bytesPlane: [0, 0, 0, 0, 0, 0, 0, 0],
samples: [],
},
],
keyValue: {},
globalData: null,
};
}
+5
View File
@@ -0,0 +1,5 @@
export * from './create-default-container.js';
export * from './container.js';
export * from './constants.js';
export * from './read.js';
export * from './write.js';
+226
View File
@@ -0,0 +1,226 @@
import { BufferReader } from './buffer-reader.js';
import { KTX2_ID } from './constants-internal.js';
import { KHR_DF_SAMPLE_DATATYPE_SIGNED, type Supercompression, type VKFormat } from './constants.js';
import type { KTX2BasicFormatSample, KTX2Container, KTX2DataFormatDescriptorBasicFormat } from './container.js';
import { createDefaultContainer } from './create-default-container.js';
import { decodeText } from './util.js';
/**
* Parses a KTX 2.0 file, returning an unpacked {@link KTX2Container} instance with all associated
* data. The container's mip levels and other binary data are pointers into the original file, not
* copies, so the original file should not be overwritten after reading.
*
* @param data Bytes of KTX 2.0 file, as Uint8Array or Buffer.
*/
export function read(data: Uint8Array): KTX2Container {
///////////////////////////////////////////////////
// KTX 2.0 Identifier.
///////////////////////////////////////////////////
const id = new Uint8Array(data.buffer, data.byteOffset, KTX2_ID.length);
if (
id[0] !== KTX2_ID[0] || // '´'
id[1] !== KTX2_ID[1] || // 'K'
id[2] !== KTX2_ID[2] || // 'T'
id[3] !== KTX2_ID[3] || // 'X'
id[4] !== KTX2_ID[4] || // ' '
id[5] !== KTX2_ID[5] || // '2'
id[6] !== KTX2_ID[6] || // '0'
id[7] !== KTX2_ID[7] || // 'ª'
id[8] !== KTX2_ID[8] || // '\r'
id[9] !== KTX2_ID[9] || // '\n'
id[10] !== KTX2_ID[10] || // '\x1A'
id[11] !== KTX2_ID[11] // '\n'
) {
throw new Error('Missing KTX 2.0 identifier.');
}
const container = createDefaultContainer();
///////////////////////////////////////////////////
// Header.
///////////////////////////////////////////////////
const headerByteLength = 17 * Uint32Array.BYTES_PER_ELEMENT;
const headerReader = new BufferReader(data, KTX2_ID.length, headerByteLength, true);
container.vkFormat = headerReader._nextUint32() as VKFormat;
container.typeSize = headerReader._nextUint32();
container.pixelWidth = headerReader._nextUint32();
container.pixelHeight = headerReader._nextUint32();
container.pixelDepth = headerReader._nextUint32();
container.layerCount = headerReader._nextUint32();
container.faceCount = headerReader._nextUint32();
container.levelCount = headerReader._nextUint32();
container.supercompressionScheme = headerReader._nextUint32() as Supercompression;
const dfdByteOffset = headerReader._nextUint32();
const dfdByteLength = headerReader._nextUint32();
const kvdByteOffset = headerReader._nextUint32();
const kvdByteLength = headerReader._nextUint32();
const sgdByteOffset = headerReader._nextUint64();
const sgdByteLength = headerReader._nextUint64();
///////////////////////////////////////////////////
// Level Index.
///////////////////////////////////////////////////
const levelByteLength = Math.max(container.levelCount, 1) * 3 * 8;
const levelReader = new BufferReader(data, KTX2_ID.length + headerByteLength, levelByteLength, true);
for (let i = 0, il = Math.max(container.levelCount, 1); i < il; i++) {
container.levels.push({
levelData: new Uint8Array(data.buffer, data.byteOffset + levelReader._nextUint64(), levelReader._nextUint64()),
uncompressedByteLength: levelReader._nextUint64(),
});
}
///////////////////////////////////////////////////
// Data Format Descriptor (DFD).
///////////////////////////////////////////////////
const dfdReader = new BufferReader(data, dfdByteOffset, dfdByteLength, true);
dfdReader._skip(4); // totalSize
const vendorId = dfdReader._nextUint16();
const descriptorType = dfdReader._nextUint16();
const versionNumber = dfdReader._nextUint16();
const descriptorBlockSize = dfdReader._nextUint16();
const colorModel = dfdReader._nextUint8();
const colorPrimaries = dfdReader._nextUint8();
const transferFunction = dfdReader._nextUint8();
const flags = dfdReader._nextUint8();
const texelBlockDimension = [
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
] as KTX2DataFormatDescriptorBasicFormat['texelBlockDimension'];
const bytesPlane = [
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
] as KTX2DataFormatDescriptorBasicFormat['bytesPlane'];
const samples = [] as KTX2BasicFormatSample[];
const dfd: KTX2DataFormatDescriptorBasicFormat = {
vendorId,
descriptorType,
versionNumber,
colorModel,
colorPrimaries,
transferFunction,
flags,
texelBlockDimension,
bytesPlane,
samples,
};
const sampleStart = 6;
const sampleWords = 4;
const numSamples = (descriptorBlockSize / 4 - sampleStart) / sampleWords;
for (let i = 0; i < numSamples; i++) {
const sample = {
bitOffset: dfdReader._nextUint16(),
bitLength: dfdReader._nextUint8(),
channelType: dfdReader._nextUint8(),
samplePosition: [dfdReader._nextUint8(), dfdReader._nextUint8(), dfdReader._nextUint8(), dfdReader._nextUint8()],
sampleLower: Number.NEGATIVE_INFINITY,
sampleUpper: Number.POSITIVE_INFINITY,
};
if (sample.channelType & KHR_DF_SAMPLE_DATATYPE_SIGNED) {
sample.sampleLower = dfdReader._nextInt32();
sample.sampleUpper = dfdReader._nextInt32();
} else {
sample.sampleLower = dfdReader._nextUint32();
sample.sampleUpper = dfdReader._nextUint32();
}
dfd.samples[i] = sample;
}
container.dataFormatDescriptor.length = 0;
container.dataFormatDescriptor.push(dfd);
///////////////////////////////////////////////////
// Key/Value Data (KVD).
///////////////////////////////////////////////////
const kvdReader = new BufferReader(data, kvdByteOffset, kvdByteLength, true);
while (kvdReader._offset < kvdByteLength) {
const keyValueByteLength = kvdReader._nextUint32();
const keyData = kvdReader._scan(keyValueByteLength);
const key = decodeText(keyData);
container.keyValue[key] = kvdReader._nextUint8Array(keyValueByteLength - keyData.byteLength - 1);
if (key.match(/^ktx/i)) {
const text = decodeText(container.keyValue[key] as Uint8Array);
container.keyValue[key] = text.substring(0, text.lastIndexOf('\x00'));
}
const kvPadding = keyValueByteLength % 4 ? 4 - (keyValueByteLength % 4) : 0; // align(4)
// 4-byte alignment.
kvdReader._skip(kvPadding);
}
///////////////////////////////////////////////////
// Supercompression Global Data (SGD).
///////////////////////////////////////////////////
if (sgdByteLength <= 0) return container;
const sgdReader = new BufferReader(data, sgdByteOffset, sgdByteLength, true);
const endpointCount = sgdReader._nextUint16();
const selectorCount = sgdReader._nextUint16();
const endpointsByteLength = sgdReader._nextUint32();
const selectorsByteLength = sgdReader._nextUint32();
const tablesByteLength = sgdReader._nextUint32();
const extendedByteLength = sgdReader._nextUint32();
const imageDescs = [];
for (let i = 0, il = Math.max(container.levelCount, 1); i < il; i++) {
imageDescs.push({
imageFlags: sgdReader._nextUint32(),
rgbSliceByteOffset: sgdReader._nextUint32(),
rgbSliceByteLength: sgdReader._nextUint32(),
alphaSliceByteOffset: sgdReader._nextUint32(),
alphaSliceByteLength: sgdReader._nextUint32(),
});
}
const endpointsByteOffset = sgdByteOffset + sgdReader._offset;
const selectorsByteOffset = endpointsByteOffset + endpointsByteLength;
const tablesByteOffset = selectorsByteOffset + selectorsByteLength;
const extendedByteOffset = tablesByteOffset + tablesByteLength;
const endpointsData = new Uint8Array(data.buffer, data.byteOffset + endpointsByteOffset, endpointsByteLength);
const selectorsData = new Uint8Array(data.buffer, data.byteOffset + selectorsByteOffset, selectorsByteLength);
const tablesData = new Uint8Array(data.buffer, data.byteOffset + tablesByteOffset, tablesByteLength);
const extendedData = new Uint8Array(data.buffer, data.byteOffset + extendedByteOffset, extendedByteLength);
container.globalData = {
endpointCount,
selectorCount,
imageDescs,
endpointsData,
selectorsData,
tablesData,
extendedData,
};
return container;
}
+217
View File
@@ -0,0 +1,217 @@
import type { vec3 } from './constants-internal.js';
import {
type VKFormat,
VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
VK_FORMAT_ASTC_5x4_UNORM_BLOCK,
VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
VK_FORMAT_ASTC_8x5_UNORM_BLOCK,
VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
VK_FORMAT_ASTC_8x8_UNORM_BLOCK,
VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
VK_FORMAT_ASTC_10x5_UNORM_BLOCK,
VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
VK_FORMAT_ASTC_10x6_UNORM_BLOCK,
VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
VK_FORMAT_ASTC_12x10_UNORM_BLOCK,
VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
VK_FORMAT_BC1_RGB_UNORM_BLOCK,
VK_FORMAT_BC7_SRGB_BLOCK,
VK_FORMAT_EAC_R11G11_SNORM_BLOCK,
VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK,
VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG,
VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG,
VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG,
VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG,
VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG,
VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG,
VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG,
VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG,
VK_FORMAT_UNDEFINED,
} from './constants.js';
import type { KTX2Container } from './container.js';
/** Encodes text to an ArrayBuffer. */
export function encodeText(text: string): Uint8Array {
return new TextEncoder().encode(text);
}
/** Decodes an ArrayBuffer to text. */
export function decodeText(buffer: Uint8Array): string {
return new TextDecoder().decode(buffer);
}
/** Concatenates N ArrayBuffers. */
export function concat(buffers: (ArrayBuffer | Uint8Array)[]): Uint8Array {
let totalByteLength = 0;
for (const buffer of buffers) {
totalByteLength += buffer.byteLength;
}
const result = new Uint8Array(totalByteLength);
let byteOffset = 0;
for (const buffer of buffers) {
result.set(new Uint8Array(buffer), byteOffset);
byteOffset += buffer.byteLength;
}
return result;
}
/** Returns the least common multiple (LCM) for two positive integers. */
export function leastCommonMultiple(a: number, b: number): number {
const max = Math.max(a, b);
const min = Math.min(a, b);
let lcm = max;
while (lcm % min !== 0) {
lcm += max;
}
return lcm;
}
/**
* Returns amount of padding, in bytes, required to pad a value V to N-byte
* boundaries. Both V and N must be positive integers.
*/
export function getPadding(v: number, n = 4): number {
return Math.ceil(v / n) * n - v;
}
/** Returns byte length per texel block. */
export function getBlockByteLength(container: KTX2Container): number {
return container.levels[0].levelData.byteLength / getBlockCount(container, 0);
}
/**
* Returns total number of blocks for given level. For VK_FORMAT_UNDEFINED, DFD is required.
*
* References:
* - https://github.khronos.org/KTX-Specification/ktxspec.v2.html#levelImages
*/
export function getBlockCount(container: KTX2Container, levelIndex: number): number {
let blockCount = 1;
const pixelDimensions = [container.pixelWidth, container.pixelHeight, container.pixelDepth];
const blockDimensions = getBlockDimensions(container);
for (let i = 0; i < 3; i++) {
if (pixelDimensions[i] > 0) {
const dimBlockCount = Math.ceil(Math.floor(pixelDimensions[i] * 2 ** -levelIndex) / blockDimensions[i]);
blockCount *= Math.max(1, dimBlockCount);
}
}
if (container.layerCount > 0) {
blockCount *= container.layerCount;
}
if (container.faceCount > 0) {
blockCount *= container.faceCount;
}
return blockCount;
}
/**
* Given a KTX2 container, returns block dimensions as [width, height, depth]. Requires valid DFD.
*/
export function getBlockDimensions(container: KTX2Container): vec3 {
const [x, y, z] = container.dataFormatDescriptor[0].texelBlockDimension;
return [x + 1, y + 1, z + 1];
}
/**
* Given `vkFormat`, returns block dimensions as [width, height, depth]. Does not support
* VK_FORMAT_UNDEFINED.
*
* References:
* - https://github.khronos.org/KTX-Specification/ktxspec.v2.html#_mippadding
* - https://registry.khronos.org/vulkan/specs/1.2-extensions/html/vkspec.html#formats-compatibility
*/
export function getBlockDimensionsByVKFormat(vkFormat: VKFormat): vec3 {
if (vkFormat === VK_FORMAT_UNDEFINED) {
throw new Error('Unknown block dimensions for VK_FORMAT_UNDEFINED.');
}
if (vkFormat >= VK_FORMAT_BC1_RGB_UNORM_BLOCK && vkFormat <= VK_FORMAT_BC7_SRGB_BLOCK) {
return [4, 4, 1];
}
if (vkFormat >= VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK && vkFormat <= VK_FORMAT_EAC_R11G11_SNORM_BLOCK) {
return [4, 4, 1];
}
if (vkFormat === VK_FORMAT_ASTC_4x4_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_4x4_SRGB_BLOCK) {
return [4, 4, 1];
}
if (vkFormat === VK_FORMAT_ASTC_5x4_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_5x4_SRGB_BLOCK) {
return [5, 4, 1];
}
if (vkFormat === VK_FORMAT_ASTC_5x5_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_5x5_SRGB_BLOCK) {
return [5, 5, 1];
}
if (vkFormat === VK_FORMAT_ASTC_6x5_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_6x5_SRGB_BLOCK) {
return [6, 5, 1];
}
if (vkFormat === VK_FORMAT_ASTC_6x6_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_6x6_SRGB_BLOCK) {
return [6, 6, 1];
}
if (vkFormat === VK_FORMAT_ASTC_8x5_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_8x5_SRGB_BLOCK) {
return [8, 5, 1];
}
if (vkFormat === VK_FORMAT_ASTC_8x6_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_8x6_SRGB_BLOCK) {
return [8, 6, 1];
}
if (vkFormat === VK_FORMAT_ASTC_8x8_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_8x8_SRGB_BLOCK) {
return [8, 8, 1];
}
if (vkFormat === VK_FORMAT_ASTC_10x5_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_10x5_SRGB_BLOCK) {
return [10, 5, 1];
}
if (vkFormat === VK_FORMAT_ASTC_10x6_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_10x6_SRGB_BLOCK) {
return [10, 6, 1];
}
if (vkFormat === VK_FORMAT_ASTC_10x8_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_10x8_SRGB_BLOCK) {
return [10, 8, 1];
}
if (vkFormat === VK_FORMAT_ASTC_10x10_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_10x10_SRGB_BLOCK) {
return [10, 10, 1];
}
if (vkFormat === VK_FORMAT_ASTC_12x10_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_12x10_SRGB_BLOCK) {
return [12, 10, 1];
}
if (vkFormat === VK_FORMAT_ASTC_12x12_UNORM_BLOCK || vkFormat === VK_FORMAT_ASTC_12x12_SRGB_BLOCK) {
return [12, 12, 1];
}
if (
vkFormat === VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG ||
vkFormat === VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG ||
vkFormat === VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG ||
vkFormat === VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG
) {
return [8, 4, 1];
}
if (
vkFormat === VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG ||
vkFormat === VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG ||
vkFormat === VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG ||
vkFormat === VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG
) {
return [4, 4, 1];
}
return [1, 1, 1];
}
+243
View File
@@ -0,0 +1,243 @@
import { HEADER_BYTE_LENGTH, KTX2_ID, KTX_WRITER, NUL } from './constants-internal.js';
import {
KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT,
KHR_DF_SAMPLE_DATATYPE_SIGNED,
KHR_SUPERCOMPRESSION_NONE,
} from './constants.js';
import type { KTX2Container } from './container.js';
import { concat, encodeText, getBlockByteLength, getPadding, leastCommonMultiple } from './util.js';
interface WriteOptions {
keepWriter?: boolean;
}
const DEFAULT_OPTIONS: WriteOptions = { keepWriter: false };
/**
* Serializes a {@link KTX2Container} instance to a KTX 2.0 file. Mip levels and other binary data
* are copied into the resulting Uint8Array, so the original container can safely be edited or
* destroyed after it is serialized.
*
* Options:
* - keepWriter: If true, 'KTXWriter' key/value field is written as provided by the container.
* Otherwise, a string for the current ktx-parse version is generated. Default: false.
*
* @param container
* @param options
*/
export function write(container: KTX2Container, options: WriteOptions = {}): Uint8Array {
// biome-ignore lint/style/noParameterAssign: Merging defaults only.
options = { ...DEFAULT_OPTIONS, ...options };
///////////////////////////////////////////////////
// Supercompression Global Data (SGD).
///////////////////////////////////////////////////
let sgdBuffer = new ArrayBuffer(0);
if (container.globalData) {
const sgdHeaderBuffer = new ArrayBuffer(20 + container.globalData.imageDescs.length * 5 * 4);
const sgdHeaderView = new DataView(sgdHeaderBuffer);
sgdHeaderView.setUint16(0, container.globalData.endpointCount, true);
sgdHeaderView.setUint16(2, container.globalData.selectorCount, true);
sgdHeaderView.setUint32(4, container.globalData.endpointsData.byteLength, true);
sgdHeaderView.setUint32(8, container.globalData.selectorsData.byteLength, true);
sgdHeaderView.setUint32(12, container.globalData.tablesData.byteLength, true);
sgdHeaderView.setUint32(16, container.globalData.extendedData.byteLength, true);
for (let i = 0; i < container.globalData.imageDescs.length; i++) {
const imageDesc = container.globalData.imageDescs[i];
sgdHeaderView.setUint32(20 + i * 5 * 4 + 0, imageDesc.imageFlags, true);
sgdHeaderView.setUint32(20 + i * 5 * 4 + 4, imageDesc.rgbSliceByteOffset, true);
sgdHeaderView.setUint32(20 + i * 5 * 4 + 8, imageDesc.rgbSliceByteLength, true);
sgdHeaderView.setUint32(20 + i * 5 * 4 + 12, imageDesc.alphaSliceByteOffset, true);
sgdHeaderView.setUint32(20 + i * 5 * 4 + 16, imageDesc.alphaSliceByteLength, true);
}
sgdBuffer = concat([
sgdHeaderBuffer,
container.globalData.endpointsData,
container.globalData.selectorsData,
container.globalData.tablesData,
container.globalData.extendedData,
]);
}
///////////////////////////////////////////////////
// Key/Value Data (KVD).
///////////////////////////////////////////////////
const keyValueData: Uint8Array[] = [];
const keyValueList = Object.entries({
...container.keyValue,
...(!options.keepWriter && { KTXwriter: KTX_WRITER }),
});
keyValueList.sort((a, b) => (a[0] > b[0] ? 1 : -1));
for (const [key, value] of keyValueList) {
const keyData = encodeText(key);
const valueData = typeof value === 'string' ? concat([encodeText(value), NUL]) : value;
const kvByteLength = keyData.byteLength + 1 + valueData.byteLength;
const kvPadding = getPadding(kvByteLength, 4); // align(4)
keyValueData.push(
concat([
new Uint32Array([kvByteLength]),
keyData,
NUL,
valueData,
new Uint8Array(kvPadding).fill(0x00), // align(4)
]),
);
}
const kvdBuffer = concat(keyValueData);
///////////////////////////////////////////////////
// Data Format Descriptor (DFD).
///////////////////////////////////////////////////
if (
container.dataFormatDescriptor.length !== 1 ||
container.dataFormatDescriptor[0].descriptorType !== KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT
) {
throw new Error('Only BASICFORMAT Data Format Descriptor output supported.');
}
const dfd = container.dataFormatDescriptor[0];
const dfdBuffer = new ArrayBuffer(28 + dfd.samples.length * 16);
const dfdView = new DataView(dfdBuffer);
const descriptorBlockSize = 24 + dfd.samples.length * 16;
dfdView.setUint32(0, dfdBuffer.byteLength, true);
dfdView.setUint16(4, dfd.vendorId, true);
dfdView.setUint16(6, dfd.descriptorType, true);
dfdView.setUint16(8, dfd.versionNumber, true);
dfdView.setUint16(10, descriptorBlockSize, true);
dfdView.setUint8(12, dfd.colorModel);
dfdView.setUint8(13, dfd.colorPrimaries);
dfdView.setUint8(14, dfd.transferFunction);
dfdView.setUint8(15, dfd.flags);
if (!Array.isArray(dfd.texelBlockDimension)) {
throw new Error('texelBlockDimension is now an array. For dimensionality `d`, set `d - 1`.');
}
dfdView.setUint8(16, dfd.texelBlockDimension[0]);
dfdView.setUint8(17, dfd.texelBlockDimension[1]);
dfdView.setUint8(18, dfd.texelBlockDimension[2]);
dfdView.setUint8(19, dfd.texelBlockDimension[3]);
for (let i = 0; i < 8; i++) dfdView.setUint8(20 + i, dfd.bytesPlane[i]);
for (let i = 0; i < dfd.samples.length; i++) {
const sample = dfd.samples[i];
const sampleByteOffset = 28 + i * 16;
dfdView.setUint16(sampleByteOffset + 0, sample.bitOffset, true);
dfdView.setUint8(sampleByteOffset + 2, sample.bitLength);
dfdView.setUint8(sampleByteOffset + 3, sample.channelType);
dfdView.setUint8(sampleByteOffset + 4, sample.samplePosition[0]);
dfdView.setUint8(sampleByteOffset + 5, sample.samplePosition[1]);
dfdView.setUint8(sampleByteOffset + 6, sample.samplePosition[2]);
dfdView.setUint8(sampleByteOffset + 7, sample.samplePosition[3]);
if (sample.channelType & KHR_DF_SAMPLE_DATATYPE_SIGNED) {
dfdView.setInt32(sampleByteOffset + 8, sample.sampleLower, true);
dfdView.setInt32(sampleByteOffset + 12, sample.sampleUpper, true);
} else {
dfdView.setUint32(sampleByteOffset + 8, sample.sampleLower, true);
dfdView.setUint32(sampleByteOffset + 12, sample.sampleUpper, true);
}
}
///////////////////////////////////////////////////
// Data alignment.
///////////////////////////////////////////////////
const dfdByteOffset = KTX2_ID.length + HEADER_BYTE_LENGTH + container.levels.length * 3 * 8;
const kvdByteOffset = dfdByteOffset + dfdBuffer.byteLength;
let sgdByteOffset = sgdBuffer.byteLength > 0 ? kvdByteOffset + kvdBuffer.byteLength : 0;
if (sgdByteOffset % 8) sgdByteOffset += 8 - (sgdByteOffset % 8); // align(8)
///////////////////////////////////////////////////
// Level Index.
///////////////////////////////////////////////////
const levelData: Uint8Array[] = [];
const levelIndex = new DataView(new ArrayBuffer(container.levels.length * 3 * 8));
const levelDataByteOffsets = new Uint32Array(container.levels.length);
let levelAlign = 0;
if (container.supercompressionScheme === KHR_SUPERCOMPRESSION_NONE) {
levelAlign = leastCommonMultiple(getBlockByteLength(container), 4);
}
// Level data is ordered small → large.
let levelDataByteOffset = (sgdByteOffset || kvdByteOffset + kvdBuffer.byteLength) + sgdBuffer.byteLength;
for (let i = container.levels.length - 1; i >= 0; i--) {
// Level padding.
if (levelDataByteOffset % levelAlign) {
const paddingBytes = getPadding(levelDataByteOffset, levelAlign);
levelData.push(new Uint8Array(paddingBytes));
levelDataByteOffset += paddingBytes;
}
// Level data.
const level = container.levels[i];
levelData.push(level.levelData);
levelDataByteOffsets[i] = levelDataByteOffset;
levelDataByteOffset += level.levelData.byteLength;
}
// Level index is ordered large → small.
for (let i = 0; i < container.levels.length; i++) {
const level = container.levels[i];
levelIndex.setBigUint64(i * 24 + 0, BigInt(levelDataByteOffsets[i]), true);
levelIndex.setBigUint64(i * 24 + 8, BigInt(level.levelData.byteLength), true);
levelIndex.setBigUint64(i * 24 + 16, BigInt(level.uncompressedByteLength), true);
}
///////////////////////////////////////////////////
// Header.
///////////////////////////////////////////////////
const headerBuffer = new ArrayBuffer(HEADER_BYTE_LENGTH);
const headerView = new DataView(headerBuffer);
headerView.setUint32(0, container.vkFormat, true);
headerView.setUint32(4, container.typeSize, true);
headerView.setUint32(8, container.pixelWidth, true);
headerView.setUint32(12, container.pixelHeight, true);
headerView.setUint32(16, container.pixelDepth, true);
headerView.setUint32(20, container.layerCount, true);
headerView.setUint32(24, container.faceCount, true);
headerView.setUint32(28, container.levelCount, true);
headerView.setUint32(32, container.supercompressionScheme, true);
headerView.setUint32(36, dfdByteOffset, true);
headerView.setUint32(40, dfdBuffer.byteLength, true);
headerView.setUint32(44, kvdByteOffset, true);
headerView.setUint32(48, kvdBuffer.byteLength, true);
headerView.setBigUint64(52, BigInt(sgdBuffer.byteLength > 0 ? sgdByteOffset : 0), true);
headerView.setBigUint64(60, BigInt(sgdBuffer.byteLength), true);
///////////////////////////////////////////////////
// Compose.
///////////////////////////////////////////////////
return new Uint8Array(
concat([
new Uint8Array(KTX2_ID).buffer,
headerBuffer,
levelIndex.buffer,
dfdBuffer,
kvdBuffer,
sgdByteOffset > 0
? new ArrayBuffer(sgdByteOffset - (kvdByteOffset + kvdBuffer.byteLength)) // align(8)
: new ArrayBuffer(0),
sgdBuffer,
...levelData,
]),
);
}