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}