use crate::api::AstcBlock;
use alloc::vec::Vec;
const KTX2_MAGIC: [u8; 12] = [
0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A,
];
const KTX2_KDF_DF_MODEL_ASTC: u32 = 162;
const KTX2_KDF_DF_MODEL_ETC1S: u32 = 163;
const KTX2_KDF_DF_MODEL_UASTC: u32 = 166;
const KTX2_KDF_DF_MODEL_UASTC_HDR_4X4: u32 = 167;
const KTX2_KDF_DF_MODEL_UASTC_HDR_6X6_INTERMEDIATE: u32 = 168;
const KTX2_KDF_DF_MODEL_XUASTC_LDR_INTERMEDIATE: u32 = 169;
const KTX2_FORMAT_ASTC_4X4_SFLOAT_BLOCK: u32 = 1000066000;
const KTX2_FORMAT_ASTC_6X6_SFLOAT_BLOCK: u32 = 1000066004;
const KTX2_KHR_DF_TRANSFER_LINEAR: u32 = 1;
const KTX2_KHR_DF_TRANSFER_SRGB: u32 = 2;
const KTX2_FORMAT_ASTC_LDR_FIRST: u32 = 157;
const KTX2_FORMAT_ASTC_LDR_LAST: u32 = 184;
fn astc_ldr_block_from_unorm_vk(vk: u32) -> Option<AstcBlock> {
Some(match vk {
157 => AstcBlock::B4x4,
159 => AstcBlock::B5x4,
161 => AstcBlock::B5x5,
163 => AstcBlock::B6x5,
165 => AstcBlock::B6x6,
167 => AstcBlock::B8x5,
169 => AstcBlock::B8x6,
171 => AstcBlock::B8x8,
173 => AstcBlock::B10x5,
175 => AstcBlock::B10x6,
177 => AstcBlock::B10x8,
179 => AstcBlock::B10x10,
181 => AstcBlock::B12x10,
183 => AstcBlock::B12x12,
_ => return None,
})
}
const KTX2_MAX_SUPPORTED_LEVEL_COUNT: u32 = 16;
const KTX2_SS_ZSTANDARD: u32 = 2;
pub(crate) const KTX2_SS_UASTC_HDR_6X6I: u32 = 4;
pub(crate) const KTX2_SS_XUASTC_LDR: u32 = 5;
const KTX2_DF_CHANNEL_UASTC_RGBA: u32 = 3;
const KTX2_DF_CHANNEL_UASTC_RRRG: u32 = 5;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BasisFormat {
Etc1s,
Uastc,
UastcHdr4x4,
AstcLdr(AstcBlock),
AstcHdr6x6,
UastcHdr6x6,
XuastcLdr(AstcBlock),
}
impl BasisFormat {
pub fn block_dims(self) -> (u32, u32) {
match self {
BasisFormat::Etc1s | BasisFormat::Uastc | BasisFormat::UastcHdr4x4 => (4, 4),
BasisFormat::AstcLdr(b) => b.dims(),
BasisFormat::AstcHdr6x6 | BasisFormat::UastcHdr6x6 => (6, 6),
BasisFormat::XuastcLdr(b) => b.dims(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct LevelIndex {
pub byte_offset: u64,
pub byte_length: u64,
pub uncompressed_byte_length: u64,
}
#[derive(Clone, Debug)]
pub struct Ktx2Header {
pub width: u32,
pub height: u32,
pub layer_count: u32,
pub face_count: u32,
pub level_count: u32,
pub supercompression: u32,
pub format: BasisFormat,
pub has_alpha: bool,
pub astc_is_srgb: bool,
pub levels: Vec<LevelIndex>,
pub sgd_byte_offset: u64,
pub sgd_byte_length: u64,
pub kvd_byte_offset: u32,
pub kvd_byte_length: u32,
pub has_anim_key: bool,
}
fn rd32(d: &[u8], o: usize) -> u32 {
u32::from_le_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]])
}
fn rd64(d: &[u8], o: usize) -> u64 {
let mut b = [0u8; 8];
b.copy_from_slice(&d[o..o + 8]);
u64::from_le_bytes(b)
}
impl Ktx2Header {
pub fn parse(data: &[u8]) -> Option<Ktx2Header> {
if data.len() < 80 || data[..12] != KTX2_MAGIC {
return None;
}
let vk_format = rd32(data, 12);
let vk_is_astc_ldr =
(KTX2_FORMAT_ASTC_LDR_FIRST..=KTX2_FORMAT_ASTC_LDR_LAST).contains(&vk_format);
if vk_format != 0
&& vk_format != KTX2_FORMAT_ASTC_4X4_SFLOAT_BLOCK
&& vk_format != KTX2_FORMAT_ASTC_6X6_SFLOAT_BLOCK
&& !vk_is_astc_ldr
{
return None;
}
if rd32(data, 16) != 1 {
return None;
}
let width = rd32(data, 20);
let height = rd32(data, 24);
let depth = rd32(data, 28);
let layer_count = rd32(data, 32);
let face_count = rd32(data, 36);
let level_count = rd32(data, 40);
let supercompression = rd32(data, 44);
if width < 1 || height < 1 || depth > 0 {
return None;
}
if face_count != 1 && face_count != 6 {
return None;
}
if face_count > 1 && width != height {
return None;
}
if !(1..=KTX2_MAX_SUPPORTED_LEVEL_COUNT).contains(&level_count) {
return None;
}
if supercompression > KTX2_SS_ZSTANDARD
&& supercompression != KTX2_SS_UASTC_HDR_6X6I
&& supercompression != KTX2_SS_XUASTC_LDR
{
return None;
}
let dfd_byte_offset = rd32(data, 48) as usize;
let dfd_byte_length = rd32(data, 52) as usize;
let kvd_byte_offset = rd32(data, 56);
let kvd_byte_length = rd32(data, 60);
let sgd_byte_offset = rd64(data, 64);
let sgd_byte_length = rd64(data, 72);
let num_levels = level_count.max(1) as usize;
let li_end = 80 + num_levels * 24;
if li_end > data.len() {
return None;
}
let mut levels = Vec::with_capacity(num_levels);
for i in 0..num_levels {
let o = 80 + i * 24;
let l = LevelIndex {
byte_offset: rd64(data, o),
byte_length: rd64(data, o + 8),
uncompressed_byte_length: rd64(data, o + 16),
};
match supercompression {
KTX2_SS_ZSTANDARD if l.uncompressed_byte_length == 0 => return None,
1 | KTX2_SS_UASTC_HDR_6X6I | KTX2_SS_XUASTC_LDR
if l.uncompressed_byte_length != 0 =>
{
return None
}
_ => {}
}
levels.push(l);
}
if dfd_byte_length != 44 && dfd_byte_length != 60 {
return None;
}
if dfd_byte_offset + 32 > data.len() {
return None;
}
let dfd_bits = rd32(data, dfd_byte_offset + 12);
let color_model = dfd_bits & 255;
let transfer_func = (dfd_bits >> 16) & 255;
let texel_block_dimensions = rd32(data, dfd_byte_offset + 16);
let dfd_block_width = (texel_block_dimensions & 0xFF) + 1;
let dfd_block_height = ((texel_block_dimensions >> 8) & 0xFF) + 1;
let sample_channel0 = rd32(data, dfd_byte_offset + 28);
if transfer_func != KTX2_KHR_DF_TRANSFER_LINEAR
&& transfer_func != KTX2_KHR_DF_TRANSFER_SRGB
{
return None;
}
let (format, has_alpha, astc_is_srgb) = if vk_is_astc_ldr {
if color_model != KTX2_KDF_DF_MODEL_ASTC {
return None;
}
let is_srgb = (vk_format & 1) == 0;
let unorm = if is_srgb { vk_format - 1 } else { vk_format };
let block = astc_ldr_block_from_unorm_vk(unorm)?;
if block.dims() != (dfd_block_width, dfd_block_height) {
return None;
}
let chan0 = (sample_channel0 >> 24) & 15;
(
BasisFormat::AstcLdr(block),
chan0 == KTX2_DF_CHANNEL_UASTC_RGBA || chan0 == KTX2_DF_CHANNEL_UASTC_RRRG,
is_srgb,
)
} else if color_model == KTX2_KDF_DF_MODEL_ETC1S {
if vk_format != 0 {
return None;
}
(BasisFormat::Etc1s, dfd_byte_length == 60, false)
} else if color_model == KTX2_KDF_DF_MODEL_UASTC {
if vk_format != 0 {
return None;
}
let chan0 = (sample_channel0 >> 24) & 15;
(
BasisFormat::Uastc,
chan0 == KTX2_DF_CHANNEL_UASTC_RGBA || chan0 == KTX2_DF_CHANNEL_UASTC_RRRG,
false,
)
} else if color_model == KTX2_KDF_DF_MODEL_UASTC_HDR_4X4 {
if vk_format != KTX2_FORMAT_ASTC_4X4_SFLOAT_BLOCK {
return None;
}
(BasisFormat::UastcHdr4x4, false, false)
} else if color_model == KTX2_KDF_DF_MODEL_ASTC {
if vk_format != KTX2_FORMAT_ASTC_6X6_SFLOAT_BLOCK {
return None;
}
(BasisFormat::AstcHdr6x6, false, false)
} else if color_model == KTX2_KDF_DF_MODEL_UASTC_HDR_6X6_INTERMEDIATE {
if vk_format != 0 {
return None;
}
(BasisFormat::UastcHdr6x6, false, false)
} else if color_model == KTX2_KDF_DF_MODEL_XUASTC_LDR_INTERMEDIATE {
if vk_format != 0 {
return None;
}
let block = AstcBlock::ALL
.into_iter()
.find(|b| b.dims() == (dfd_block_width, dfd_block_height))?;
(BasisFormat::XuastcLdr(block), false, false)
} else {
return None;
};
let has_anim_key = has_kvd_key(data, kvd_byte_offset, kvd_byte_length, b"KTXanimData");
Some(Ktx2Header {
width,
height,
layer_count,
face_count,
level_count,
supercompression,
format,
has_alpha,
astc_is_srgb,
levels,
sgd_byte_offset,
sgd_byte_length,
kvd_byte_offset,
kvd_byte_length,
has_anim_key,
})
}
pub fn is_uastc(&self) -> bool {
self.format == BasisFormat::Uastc
}
}
fn has_kvd_key(data: &[u8], kvd_offset: u32, kvd_length: u32, key: &[u8]) -> bool {
if kvd_length == 0 {
return false;
}
let start = kvd_offset as usize;
let end = start + kvd_length as usize;
if end > data.len() {
return false;
}
let mut p = start;
while end - p > 4 {
let l = u32::from_le_bytes([data[p], data[p + 1], data[p + 2], data[p + 3]]) as usize;
p += 4;
if l < 2 || end - p < l {
return false;
}
let blob = &data[p..p + l];
if let Some(nul) = blob.iter().position(|&b| b == 0) {
if &blob[..nul] == key {
return true;
}
}
p += l;
p += (4 - (p & 3)) & 3;
}
false
}