Skip to main content

pfx_bake/detail/
ktx2.rs

1use super::atlas::{Format, Texture, level_size};
2
3pub const IDENTIFIER: [u8; 12] = [
4    0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A,
5];
6
7const HEADER: usize = 80;
8const LEVEL_ENTRY: usize = 24;
9
10fn colour_model(format: Format) -> u32 {
11    match format {
12        Format::Rgba8 | Format::Rgba8Srgb => 1,
13        Format::Bc4 => 131,
14        Format::Bc5 => 132,
15        Format::Bc7Srgb => 134,
16    }
17}
18
19fn transfer(format: Format) -> u32 {
20    match format {
21        Format::Rgba8Srgb | Format::Bc7Srgb => 2,
22        Format::Rgba8 | Format::Bc4 | Format::Bc5 => 1,
23    }
24}
25
26fn samples(format: Format) -> Vec<[u32; 4]> {
27    let sample = |offset: u32, length: u32, channel: u32, upper: u32| {
28        [offset | ((length - 1) << 16) | (channel << 24), 0, 0, upper]
29    };
30    match format {
31        Format::Rgba8 => vec![
32            sample(0, 8, 0, 255),
33            sample(8, 8, 1, 255),
34            sample(16, 8, 2, 255),
35            sample(24, 8, 15, 255),
36        ],
37        Format::Rgba8Srgb => vec![
38            sample(0, 8, 0, 255),
39            sample(8, 8, 1, 255),
40            sample(16, 8, 2, 255),
41            sample(24, 8, 15 | 0x10, 255),
42        ],
43        Format::Bc4 => vec![sample(0, 64, 0, u32::MAX)],
44        Format::Bc5 => vec![sample(0, 64, 0, u32::MAX), sample(64, 64, 1, u32::MAX)],
45        Format::Bc7Srgb => vec![sample(0, 128, 0, u32::MAX)],
46    }
47}
48
49fn descriptor(format: Format) -> Vec<u8> {
50    let samples = samples(format);
51    let block_size = 24 + 16 * samples.len() as u32;
52    let dimension = if format.block() == 4 { 3 } else { 0 };
53    let mut words = vec![
54        4 + block_size,
55        0,
56        2 | (block_size << 16),
57        colour_model(format) | (1 << 8) | (transfer(format) << 16),
58        dimension | (dimension << 8),
59        format.block_bytes(),
60        0,
61    ];
62    for sample in samples {
63        words.extend(sample);
64    }
65    words.iter().flat_map(|word| word.to_le_bytes()).collect()
66}
67
68fn alignment(format: Format) -> usize {
69    let block = format.block_bytes() as usize;
70    if block.is_multiple_of(4) {
71        block
72    } else {
73        block * 4
74    }
75}
76
77pub fn encode(texture: &Texture) -> Result<Vec<u8>, String> {
78    texture.check()?;
79    let count = texture.levels.len();
80    let descriptor = descriptor(texture.format);
81    let descriptor_at = HEADER + LEVEL_ENTRY * count;
82    let align = alignment(texture.format);
83    let mut offsets = vec![0usize; count];
84    let mut end = descriptor_at + descriptor.len();
85    for level in (0..count).rev() {
86        end = end.div_ceil(align) * align;
87        offsets[level] = end;
88        end += texture.levels[level].len();
89    }
90    let mut out = Vec::with_capacity(end);
91    out.extend_from_slice(&IDENTIFIER);
92    for value in [
93        texture.format.vk(),
94        1,
95        texture.width,
96        texture.height,
97        0,
98        0,
99        1,
100        count as u32,
101        0,
102    ] {
103        out.extend_from_slice(&value.to_le_bytes());
104    }
105    for value in [descriptor_at as u32, descriptor.len() as u32, 0, 0] {
106        out.extend_from_slice(&value.to_le_bytes());
107    }
108    out.extend_from_slice(&0u64.to_le_bytes());
109    out.extend_from_slice(&0u64.to_le_bytes());
110    for (level, bytes) in texture.levels.iter().enumerate() {
111        let length = bytes.len() as u64;
112        out.extend_from_slice(&(offsets[level] as u64).to_le_bytes());
113        out.extend_from_slice(&length.to_le_bytes());
114        out.extend_from_slice(&length.to_le_bytes());
115    }
116    out.extend_from_slice(&descriptor);
117    for level in (0..count).rev() {
118        out.resize(offsets[level], 0);
119        out.extend_from_slice(&texture.levels[level]);
120    }
121    Ok(out)
122}
123
124fn word(bytes: &[u8], at: usize) -> Result<u32, String> {
125    bytes
126        .get(at..at + 4)
127        .map(|slice| u32::from_le_bytes(slice.try_into().unwrap()))
128        .ok_or_else(|| "KTX2 file is truncated".to_string())
129}
130
131fn long(bytes: &[u8], at: usize) -> Result<u64, String> {
132    bytes
133        .get(at..at + 8)
134        .map(|slice| u64::from_le_bytes(slice.try_into().unwrap()))
135        .ok_or_else(|| "KTX2 file is truncated".to_string())
136}
137
138pub fn decode(bytes: &[u8]) -> Result<Texture, String> {
139    if bytes.get(..12) != Some(&IDENTIFIER[..]) {
140        return Err("not a KTX2 file".into());
141    }
142    let vk = word(bytes, 12)?;
143    let format = Format::from_vk(vk)
144        .ok_or_else(|| format!("KTX2 format {vk} is not a detail map format"))?;
145    let width = word(bytes, 20)?;
146    let height = word(bytes, 24)?;
147    if word(bytes, 16)? != 1
148        || word(bytes, 28)? != 0
149        || word(bytes, 32)? != 0
150        || word(bytes, 36)? != 1
151        || word(bytes, 44)? != 0
152    {
153        return Err("KTX2 detail map must be one 2D image without supercompression".into());
154    }
155    let count = word(bytes, 40)? as usize;
156    if width == 0 || height == 0 || count == 0 || count > 32 {
157        return Err("KTX2 detail map has no image".into());
158    }
159    let descriptor_at = word(bytes, 48)? as usize;
160    let descriptor_length = word(bytes, 52)? as usize;
161    let expected = descriptor(format);
162    if bytes.get(descriptor_at..descriptor_at + descriptor_length) != Some(&expected[..]) {
163        return Err("KTX2 data format descriptor differs from its format".into());
164    }
165    let mut levels = Vec::with_capacity(count);
166    for level in 0..count {
167        let entry = HEADER + LEVEL_ENTRY * level;
168        let offset = usize::try_from(long(bytes, entry)?).map_err(|e| e.to_string())?;
169        let length = usize::try_from(long(bytes, entry + 8)?).map_err(|e| e.to_string())?;
170        let [w, h] = level_size(width, height, level as u32);
171        if length != format.level_bytes(w, h) || long(bytes, entry + 16)? != length as u64 {
172            return Err(format!("KTX2 level {level} has the wrong size"));
173        }
174        let data = bytes
175            .get(offset..offset.checked_add(length).ok_or("KTX2 level overflows")?)
176            .ok_or("KTX2 file is truncated")?;
177        levels.push(data.to_vec());
178    }
179    let texture = Texture {
180        format,
181        width,
182        height,
183        levels,
184    };
185    texture.check()?;
186    Ok(texture)
187}