ktx_async/lib.rs
1//! KTX Texture Format Loader
2//!
3//! https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
4
5/*
6File Structure:
7
8Byte[12] identifier
9UInt32 endianness
10UInt32 glType
11UInt32 glTypeSize
12UInt32 glFormat
13Uint32 glInternalFormat
14Uint32 glBaseInternalFormat
15UInt32 pixelWidth
16UInt32 pixelHeight
17UInt32 pixelDepth
18UInt32 numberOfArrayElements
19UInt32 numberOfFaces
20UInt32 numberOfMipmapLevels
21UInt32 bytesOfKeyValueData
22
23for each keyValuePair that fits in bytesOfKeyValueData
24 UInt32 keyAndValueByteSize
25 Byte keyAndValue[keyAndValueByteSize]
26 Byte valuePadding[3 - ((keyAndValueByteSize + 3) % 4)]
27end
28
29for each mipmap_level in numberOfMipmapLevels*
30 UInt32 imageSize;
31 for each array_element in numberOfArrayElements*
32 for each face in numberOfFaces
33 for each z_slice in pixelDepth*
34 for each row or row_of_blocks in pixelHeight*
35 for each pixel or block_of_pixels in pixelWidth
36 Byte data[format-specific-number-of-bytes]**
37 end
38 end
39 end
40 Byte cubePadding[0-3]
41 end
42 end
43 Byte mipPadding[3 - ((imageSize + 3) % 4)]
44end
45*/
46// # imageSize
47//
48// For most textures `imageSize` is the number of bytes of
49// pixel data in the current LOD level.
50//
51// This includes all array layers, all z slices, all faces,
52// all rows (or rows of blocks) and all pixels (or blocks) in
53// each row for the mipmap level. It does not include any
54// bytes in mipPadding.
55//
56// The exception is non-array cubemap textures
57// (any texture where numberOfFaces is 6 and
58// numberOfArrayElements is 0).
59//
60// For these textures imageSize is the number of bytes in
61// each face of the texture for the current LOD level,
62// not including bytes in cubePadding or mipPadding.
63//
64// # cubePadding
65//
66// For non-array cubemap textures (any texture where
67// numberOfFaces is 6 and numberOfArrayElements is 0)
68// cubePadding contains between 0 and 3 bytes of value 0x00
69// to ensure that the data in each face begins at a file offset
70// that is a multiple of 4.
71//
72// In all other cases cubePadding is empty (0 bytes long).
73//
74// This is empty in the non-array cubemap case as well.
75// The requirement of GL_UNPACK_ALIGNMENT = 4 means the
76// size of uncompressed textures will always be a multiple of
77// 4 bytes. All known compressed formats, that are usable for
78// cubemaps, have block sizes that are a multiple of 4 bytes.
79//
80// The field is still shown in case a compressed format emerges
81// with a block size that is not a multiple of 4 bytes.
82//
83// # mipPadding
84//
85// Between 0 and 3 bytes of value 0x00 to make sure that all
86// imageSize fields are at a file offset that is a multiple of 4.
87//
88// This is empty for all known texture formats for the reasons
89// given in cubePadding and is retained for the same reason.
90//
91
92#![recursion_limit = "256"]
93#![deny(unsafe_code)]
94
95extern crate async_stream;
96extern crate byteorder;
97extern crate error_chain;
98extern crate futures_core;
99extern crate tokio;
100
101use error_chain::{bail, error_chain};
102use futures_core::stream::Stream;
103use tokio::io::{AsyncRead, AsyncReadExt as _};
104
105/// KTX decoder
106pub struct Decoder<R> {
107 read: R,
108}
109
110impl<R> Decoder<R> {
111 pub fn new(read: R) -> Self {
112 Decoder { read }
113 }
114}
115
116impl<R> Decoder<R>
117where
118 R: AsyncRead + Unpin,
119{
120 /// Read the header and the following frames asynchronously
121 pub async fn read_async(
122 self,
123 ) -> Result<(
124 HeaderInfo,
125 impl Stream<Item = Result<(FrameInfo, Vec<u8>)>> + Unpin,
126 )> {
127 let mut read = self.read;
128
129 // Read the header
130 let info = read_header_async(&mut read).await?;
131
132 // Create the stream of the frames
133 let stream = new_async_stream(read, &info);
134
135 Ok((info, stream))
136 }
137}
138
139fn new_async_stream(
140 read: impl AsyncRead + Unpin,
141 info: &HeaderInfo,
142) -> impl Stream<Item = Result<(FrameInfo, Vec<u8>)>> + Unpin {
143 use async_stream::try_stream;
144 use byteorder::{ByteOrder as _, NativeEndian as NE};
145 use std::cmp::max;
146
147 // Prepare parameters for the stream
148 let pixel_width = info.pixel_width;
149 let pixel_height = info.pixel_height;
150 let pixel_depth = info.pixel_depth;
151 let nlayers = max(1, info.number_of_array_elements);
152 let nfaces = max(1, info.number_of_faces);
153 let nlevels = info.number_of_mipmap_levels;
154
155 // Check if it is a non-array cubemap
156 let is_cubemap = info.number_of_faces == 6 && info.number_of_array_elements == 0;
157
158 Box::pin(try_stream! {
159 let mut read = read;
160 for level in 0..nlevels {
161 let image_size = {
162 let mut buf = [0_u8; 4];
163 read.read_exact(&mut buf).await?;
164 NE::read_u32(&buf)
165 };
166
167 // FIXME: what if image_size is not 4-byte aligned?
168 assert!(image_size % 4 == 0);
169
170 // dimensions of the current mipmap level
171 let pixel_width = max(1, pixel_width >> level);
172 let pixel_height = max(1, pixel_height >> level);
173 let pixel_depth = max(1, pixel_depth >> level);
174
175 // Compute buffer size
176 let face_size = if is_cubemap {
177 image_size
178 } else {
179 assert!(image_size % nlayers == 0);
180 let layer_size = image_size / nlayers;
181 assert!(layer_size % 4 == 0);
182 assert!(layer_size % nfaces == 0);
183 layer_size / nfaces
184 };
185 assert!(face_size % 4 == 0);
186 let buf_size = face_size as usize;
187
188 // Read pixels
189 for layer in 0..nlayers {
190 for face in 0..nfaces {
191 let mut buf = vec![0_u8; buf_size];
192 read.read_exact(&mut buf).await?;
193 let frame_info = FrameInfo {
194 level,
195 layer,
196 face,
197 pixel_width,
198 pixel_height,
199 pixel_depth,
200 };
201 yield (frame_info, buf);
202 }
203 }
204 }
205 })
206}
207
208/// KTX Frame Info
209#[derive(Debug, Clone)]
210pub struct FrameInfo {
211 /// mip-map level
212 pub level: u32,
213 /// layer in texture array
214 pub layer: u32,
215 /// face in cubemap (+X, -X, +Y, -Y, +Z, -Z).
216 /// 0 if not cubemap.
217 pub face: u32,
218 pub pixel_width: u32,
219 pub pixel_height: u32,
220 pub pixel_depth: u32,
221}
222
223/// KTX Header Info
224#[derive(Debug, Clone)]
225pub struct HeaderInfo {
226 /// For compressed textures, glType must equal 0.
227 /// For uncompressed textures, glType specifies the type
228 /// parameter passed to glTex{,Sub}Image*D, usually one of
229 /// the values from table 8.2 of the OpenGL 4.4 specification
230 /// [OPENGL44] (UNSIGNED_BYTE, UNSIGNED_SHORT_5_6_5, etc.)
231 pub gl_type: u32,
232 /// glTypeSize specifies the data type size that should be used
233 /// when endianness conversion is required for the texture data
234 /// stored in the file. If glType is not 0, this should be the
235 /// size in bytes corresponding to glType. For texture data which
236 /// does not depend on platform endianness, including compressed
237 /// texture data, glTypeSize must equal 1.
238 pub gl_type_size: u32,
239 /// For compressed textures, glFormat must equal 0.
240 /// For uncompressed textures, glFormat specifies the format
241 /// parameter passed to glTex{,Sub}Image*D, usually one of
242 /// the values from table 8.3 of the OpenGL 4.4 specification
243 /// [OPENGL44] (RGB, RGBA, BGRA, etc.)
244 pub gl_format: u32,
245 /// For compressed textures, glInternalFormat must equal the
246 /// compressed internal format, usually one of the values from
247 /// table 8.14 of the OpenGL 4.4 specification [OPENGL44].
248 /// For uncompressed textures, glInternalFormat specifies the
249 /// internalformat parameter passed to glTexStorage*D or
250 /// glTexImage*D, usually one of the sized internal formats
251 /// from tables 8.12 & 8.13 of the OpenGL 4.4 specification
252 /// [OPENGL44].
253 /// The sized format should be chosen to match the bit depth of
254 /// the data provided. glInternalFormat is used when
255 /// loading both compressed and uncompressed textures,
256 /// exceptwhen loading into a context that does not support
257 /// sized formats, such as an unextended OpenGL ES 2.0 context
258 /// where the internalformat parameter is required to have the
259 /// same value as the format parameter.
260 pub gl_internal_format: u32,
261 /// For both compressed and uncompressed textures,
262 /// glBaseInternalFormat specifies the base internal
263 /// format of the texture, usually one of the values
264 /// from table 8.11 of the OpenGL 4.4 specification [OPENGL44]
265 /// (RGB, RGBA, ALPHA, etc.). For uncompressed textures,
266 /// this value will be the same as glFormat and is used as
267 /// the internalformat parameter when loading into a context
268 /// that does not support sized formats, such as an unextended
269 /// OpenGL ES 2.0 context.
270 pub gl_base_internal_format: u32,
271 /// The size of the texture image for level 0, in pixels.
272 /// No rounding to block sizes should be applied for block
273 /// compressed textures.
274 ///
275 /// For 1D textures pixelHeight and pixelDepth must be 0.
276 /// For 2D and cube textures pixelDepth must be 0.
277 pub pixel_width: u32,
278 /// See `pixel_width`
279 pub pixel_height: u32,
280 /// See `pixel_width`
281 pub pixel_depth: u32,
282 /// numberOfArrayElements specifies the number of array elements.
283 /// If the texture is not an array texture, numberOfArrayElements must equal 0.
284 pub number_of_array_elements: u32,
285 /// numberOfFaces specifies the number of cubemap faces.
286 /// For cubemaps and cubemap arrays this should be 6.
287 /// For non cubemaps this should be 1.
288 /// Cube map faces are stored in the order: +X, -X, +Y, -Y, +Z, -Z.
289 pub number_of_faces: u32,
290 /// numberOfMipmapLevels must equal 1 for non-mipmapped textures.
291 /// For mipmapped textures, it equals the number of mipmaps.
292 /// Mipmaps are stored in order from largest size to smallest size.
293 /// The first mipmap level is always level 0.
294 /// A KTX file does not need to contain a complete mipmap pyramid.
295 /// If numberOfMipmapLevels equals 0, it indicates that a full mipmap
296 /// pyramid should be generated from level 0 at load time (this is
297 /// usually not allowed for compressed formats).
298 pub number_of_mipmap_levels: u32,
299 /// keyAndValue contains 2 separate sections.
300 /// First it contains a key encoded in UTF-8 without
301 /// a byte order mark (BOM). The key must be terminated by a
302 /// NUL character (a single 0x00 byte). Keys that begin with
303 /// the 3 ascii characters 'KTX' or 'ktx' are reserved and must
304 /// not be used except as described by this spec (this version
305 /// of the KTX spec defines a single key). Immediately following
306 /// the NUL character that terminates the key is the Value data.
307 ///
308 /// The Value data may consist of any arbitrary data bytes.
309 /// Any byte value is allowed. It is encouraged that the value
310 /// be a NUL terminated UTF-8 string but this is not required.
311 /// UTF-8 strings must not contain BOMs. If the Value data is
312 /// binary, it is a sequence of bytes rather than of words.
313 /// It is up to the vendor defining the key to specify how
314 /// those bytes are to be interpreted (including the endianness
315 /// of any encoded numbers). If the Value data is a string of
316 /// bytes then the NUL termination should be included in the
317 /// keyAndValueByteSize byte count (but programs that read KTX
318 /// files must not rely on this).
319 pub key_value_data: KeyValueData,
320}
321
322impl HeaderInfo {
323 pub fn mipmap_size(&self, level: u32) -> (u32, u32, u32) {
324 use std::cmp::max;
325 let w = max(1, self.pixel_width >> level);
326 let h = max(1, self.pixel_height >> level);
327 let d = max(1, self.pixel_depth >> level);
328 (w, h, d)
329 }
330}
331
332async fn read_header_async(mut reader: impl AsyncRead + Unpin) -> Result<HeaderInfo> {
333 use byteorder::{ByteOrder as _, NativeEndian as NE};
334
335 let buf = {
336 let mut v = [0_u8; 64];
337 reader.read_exact(&mut v).await?;
338 v
339 };
340
341 // Check magic
342 {
343 let magic: &[u8] = &buf[0..12];
344 if magic != MAGIC {
345 let mut m = [0_u8; 12];
346 m.copy_from_slice(magic);
347 bail!(ErrorKind::InvalidFormat(m));
348 }
349 }
350
351 let endianness = NE::read_u32(&buf[12..16]);
352 let gl_type = NE::read_u32(&buf[16..20]);
353 let gl_type_size = NE::read_u32(&buf[20..24]);
354 let gl_format = NE::read_u32(&buf[24..28]);
355 let gl_internal_format = NE::read_u32(&buf[28..32]);
356 let gl_base_internal_format = NE::read_u32(&buf[32..36]);
357 let pixel_width = NE::read_u32(&buf[36..40]);
358 let pixel_height = NE::read_u32(&buf[40..44]);
359 let pixel_depth = NE::read_u32(&buf[44..48]);
360 let number_of_array_elements = NE::read_u32(&buf[48..52]);
361 let number_of_faces = NE::read_u32(&buf[52..56]);
362 let number_of_mipmap_levels = NE::read_u32(&buf[56..60]);
363 let bytes_of_key_value_data = NE::read_u32(&buf[60..64]);
364
365 if number_of_mipmap_levels == 0 {
366 bail!(ErrorKind::InvalidNumberOfMipmapLevels(
367 number_of_mipmap_levels
368 ));
369 }
370
371 if (endianness == ENDIANNESS) && (bytes_of_key_value_data % 4 == 0) {
372 let mut kvbuf = vec![0; bytes_of_key_value_data as usize];
373 reader.read_exact(&mut kvbuf).await?;
374 let info = HeaderInfo {
375 gl_type,
376 gl_type_size,
377 gl_format,
378 gl_internal_format,
379 gl_base_internal_format,
380 pixel_width,
381 pixel_height,
382 pixel_depth,
383 number_of_array_elements,
384 number_of_faces,
385 number_of_mipmap_levels,
386 key_value_data: KeyValueData { raw: kvbuf },
387 };
388 Ok(info)
389 } else {
390 bail!(ErrorKind::MismatchedEndianness(ENDIANNESS, endianness));
391 }
392}
393
394error_chain! {
395 types {
396 Error, ErrorKind, ResultExt, Result;
397 }
398 foreign_links {
399 Io(::std::io::Error);
400 }
401 errors {
402 InvalidFormat(magic: [u8;12]) {
403 }
404 MismatchedEndianness(expect: u32, actual: u32) {
405 }
406 InvalidNumberOfMipmapLevels(v: u32) {
407 }
408 }
409}
410
411const MAGIC: [u8; 12] = [
412 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A,
413];
414const ENDIANNESS: u32 = 0x0403_0201;
415
416#[derive(Clone)]
417pub struct KeyValueData {
418 raw: Vec<u8>,
419}
420
421pub struct Entries<'a>(&'a [u8]);
422
423impl KeyValueData {
424 pub fn iter(&self) -> Entries {
425 Entries(&self.raw)
426 }
427}
428
429impl<'a> Iterator for Entries<'a> {
430 type Item = (&'a str, &'a [u8]);
431
432 fn next(&mut self) -> Option<Self::Item> {
433 use byteorder::{ByteOrder, NativeEndian};
434 use std::str::from_utf8;
435
436 if self.0.is_empty() {
437 return None;
438 }
439 let (len_bytes, resting) = self.0.split_at(4);
440 let len = NativeEndian::read_u32(len_bytes);
441 let (kv, nextbuf) = resting.split_at(force_align(len) as usize);
442 let (kv, _padding) = kv.split_at(len as usize);
443 self.0 = nextbuf;
444 let nul_idx = kv
445 .iter()
446 .enumerate()
447 .filter(|(_, x)| **x == 0)
448 .map(|(i, _)| i)
449 .nth(0)
450 .unwrap();
451 let (key, value) = kv.split_at(nul_idx);
452 let value = value.split_at(1).1;
453 let key = from_utf8(key).unwrap();
454 Some((key, value))
455 }
456}
457
458impl std::fmt::Debug for KeyValueData {
459 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
460 write!(f, "KeyValueData[")?;
461 for (key, value) in self.iter() {
462 write!(f, "({:?}, bytes(len={})), ", key, value.len())?;
463 }
464 write!(f, "]")
465 }
466}
467
468#[inline]
469fn force_align(x: u32) -> u32 {
470 (x + 0x3) & 0xFFFF_FFFC
471}