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}