Ktx2Texture.parse constructor
Ktx2Texture.parse(
- Uint8List bytes, {
- UniversalTarget? universalTarget,
Reads bytes as a KTX2 file.
Throws Ktx2FormatException rather than returning null: a caller that
picked this decoder has already decided the bytes are a .ktx2, and a
silent null would surface later as a missing texture with no reason.
universalTarget is the GPU format a universal-block file is turned
into on the way past — gfx-83n. It is required for such a file and
ignored for every other, because the choice is the device's and this
package cannot see one: universalBlockFormat answers whether a file
needs it, and the engine's own wrapper picks the target from what the
device says it samples.
Implementation
factory Ktx2Texture.parse(
Uint8List bytes, {
UniversalTarget? universalTarget,
}) {
if (bytes.lengthInBytes < kKtx2LevelIndexOffset) {
throw Ktx2FormatException(
'File is ${bytes.lengthInBytes} bytes, too short for a KTX2 header.',
);
}
for (var i = 0; i < kKtx2Identifier.length; i++) {
if (bytes[i] != kKtx2Identifier[i]) {
throw Ktx2FormatException(
'Not a KTX2 file: byte $i is 0x${bytes[i].toRadixString(16)}, '
'expected 0x${kKtx2Identifier[i].toRadixString(16)}.',
);
}
}
final view = ByteData.view(
bytes.buffer,
bytes.offsetInBytes,
bytes.lengthInBytes,
);
int header(int field) =>
view.getUint32(kKtx2HeaderOffset + field, Endian.little);
final vkFormat = header(Ktx2HeaderField.vkFormat);
final pixelWidth = header(Ktx2HeaderField.pixelWidth);
final pixelHeight = header(Ktx2HeaderField.pixelHeight);
final pixelDepth = header(Ktx2HeaderField.pixelDepth);
final layerCount = header(Ktx2HeaderField.layerCount);
final faceCount = header(Ktx2HeaderField.faceCount);
final levelCount = header(Ktx2HeaderField.levelCount);
final supercompressionScheme = header(
Ktx2HeaderField.supercompressionScheme,
);
// Shape checks that apply whichever way the pixels are stored — moved
// ahead of the format branch below so a texture array or a cube map is
// refused by the same message whether it is a plain format or Basis
// Universal.
if (pixelDepth != 0) {
throw Ktx2FormatException(
'3D textures (pixelDepth=$pixelDepth) are not supported yet.',
);
}
if (layerCount != 0) {
throw Ktx2FormatException(
'Texture arrays (layerCount=$layerCount) are not supported yet.',
);
}
if (faceCount != 1) {
throw Ktx2FormatException(
'Cube maps (faceCount=$faceCount) are not supported yet.',
);
}
final keyValues = _checkKeyValues(bytes, view);
// `vkFormat == 0` (VK_FORMAT_UNDEFINED) is how a KTX2 file says "this is
// Basis Universal" — the real format then lives in the supercompression
// global data below, not in this field.
if (vkFormat == VkFormat.undefined) {
final universal = keyValues[kUniversalBlockKey];
if (universal != null) {
if (levelCount == 0) {
throw const Ktx2FormatException(
'levelCount is 0, which asks the loader to generate mip levels at '
'load time — not implemented yet.',
);
}
if (supercompressionScheme != Ktx2SupercompressionScheme.none) {
throw Ktx2FormatException(
'A universal-block file is ${_supercompressionName(supercompressionScheme)}-'
'compressed, and only an uncompressed one is read here — the '
'transcode and the decompression would both have to run on the '
'load, and nothing writes this combination yet.',
);
}
return _parseUniversal(
bytes,
view,
pixelWidth,
pixelHeight,
levelCount,
universal,
universalTarget,
);
}
// **Which Basis Universal, from the data format descriptor — `gfx-78n`.**
// An undefined `vkFormat` says "Basis" and nothing more. The
// supercompression scheme used to stand in for the rest — Basis-LZ
// means ETC1S, anything else must be UASTC — which was only ever a
// guess, and the descriptor's colour model is the field that says. A
// file with no descriptor at all still gets the old inference for
// Basis-LZ, which cannot be anything but ETC1S: the codebooks it names
// are ETC1S's.
final colorModel = _colorModelOf(bytes, view);
if (colorModel == Ktx2ColorModel.uastc) {
return _parseUastc(
bytes,
view,
pixelWidth,
pixelHeight,
levelCount,
supercompressionScheme,
);
}
if ((colorModel != null && colorModel != Ktx2ColorModel.etc1s) ||
supercompressionScheme != Ktx2SupercompressionScheme.basisLZ) {
final named = colorModel == null
? 'there is no descriptor'
: 'it names colour model $colorModel '
'(${Ktx2ColorModel.nameOf(colorModel)})';
throw Ktx2FormatException(
'vkFormat is undefined, so the pixels are Basis Universal and the '
'data format descriptor says which kind: $named, under '
'supercompression scheme $supercompressionScheme '
'(${_supercompressionName(supercompressionScheme)}). What is read '
'here is ETC1S under Basis-LZ, and UASTC LDR 4x4 under none, '
'Zstandard or ZLIB.',
);
}
if (levelCount == 0) {
throw const Ktx2FormatException(
'levelCount is 0, which asks the loader to generate mip levels at '
'load time — not implemented yet.',
);
}
return _parseBasisEtc1s(bytes, view, pixelWidth, pixelHeight, levelCount);
}
return Ktx2Texture._(
pixelWidth,
pixelHeight,
vkFormat,
_readLevels(bytes, view, levelCount, supercompressionScheme),
);
}