]> git.cworth.org Git - vogl/blob - src/voglcore/vogl_ktx_texture.cpp
8e753d2e6b84158e336cfb31590ab3846a16d6c1
[vogl] / src / voglcore / vogl_ktx_texture.cpp
1 /**************************************************************************
2  *
3  * Copyright 2013-2014 RAD Game Tools and Valve Software
4  * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
5  * All Rights Reserved.
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in
15  * all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23  * THE SOFTWARE.
24  *
25  **************************************************************************/
26
27 // File: vogl_ktx_texture.cpp
28 #include "vogl_core.h"
29 #include "vogl_ktx_texture.h"
30 #include "vogl_console.h"
31 #include "vogl_strutils.h"
32
33 // Set #if VOGL_KTX_PVRTEX_WORKAROUNDS to 1 to enable various workarounds for oddball KTX files written by PVRTexTool.
34 #define VOGL_KTX_PVRTEX_WORKAROUNDS 1
35
36 namespace vogl
37 {
38     const uint8 s_ktx_file_id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A };
39
40     // true if the specified internal pixel format is compressed
41     bool ktx_is_compressed_ogl_fmt(uint32 ogl_fmt)
42     {
43         switch (ogl_fmt)
44         {
45             case KTX_COMPRESSED_RED_RGTC1:
46             case KTX_COMPRESSED_SIGNED_RED_RGTC1_EXT:
47             case KTX_COMPRESSED_LUMINANCE_LATC1_EXT:
48             case KTX_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT:
49             case KTX_ETC1_RGB8_OES:
50             case KTX_RGB_S3TC:
51             case KTX_RGB4_S3TC:
52             case KTX_COMPRESSED_RGB_S3TC_DXT1_EXT:
53             case KTX_COMPRESSED_RGBA_S3TC_DXT1_EXT:
54             case KTX_COMPRESSED_SRGB_S3TC_DXT1_EXT:
55             case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
56             case KTX_COMPRESSED_R11_EAC:
57             case KTX_COMPRESSED_SIGNED_R11_EAC:
58             case KTX_COMPRESSED_RGB8_ETC2:
59             case KTX_COMPRESSED_SRGB8_ETC2:
60             case KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
61             case KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
62             case KTX_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT:
63             case KTX_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT:
64             case KTX_COMPRESSED_RED_GREEN_RGTC2_EXT:
65             case KTX_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:
66             case KTX_RGBA_S3TC:
67             case KTX_RGBA4_S3TC:
68             case KTX_COMPRESSED_RGBA_S3TC_DXT3_EXT:
69             case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
70             case KTX_COMPRESSED_RGBA_S3TC_DXT5_EXT:
71             case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
72             case KTX_RGBA_DXT5_S3TC:
73             case KTX_RGBA4_DXT5_S3TC:
74             case KTX_COMPRESSED_RGBA8_ETC2_EAC:
75             case KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
76             case KTX_COMPRESSED_RG11_EAC:
77             case KTX_COMPRESSED_SIGNED_RG11_EAC:
78             case KTX_COMPRESSED_RGBA_BPTC_UNORM_ARB:
79             case KTX_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
80             case KTX_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB:
81             case KTX_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB:
82                 VOGL_ASSERT(ktx_get_ogl_compressed_base_internal_fmt(ogl_fmt) != 0);
83                 return true;
84             default:
85                 VOGL_ASSERT(ktx_get_ogl_compressed_base_internal_fmt(ogl_fmt) == 0);
86                 return false;
87         }
88     }
89
90     bool ktx_is_packed_pixel_ogl_type(uint32 ogl_type)
91     {
92         switch (ogl_type)
93         {
94             case KTX_UNSIGNED_BYTE_3_3_2:
95             case KTX_UNSIGNED_BYTE_2_3_3_REV:
96             case KTX_UNSIGNED_SHORT_5_6_5:
97             case KTX_UNSIGNED_SHORT_5_6_5_REV:
98             case KTX_UNSIGNED_SHORT_4_4_4_4:
99             case KTX_UNSIGNED_SHORT_4_4_4_4_REV:
100             case KTX_UNSIGNED_SHORT_5_5_5_1:
101             case KTX_UNSIGNED_SHORT_1_5_5_5_REV:
102             case KTX_UNSIGNED_INT_8_8_8_8:
103             case KTX_UNSIGNED_INT_8_8_8_8_REV:
104             case KTX_UNSIGNED_INT_10_10_10_2:
105             case KTX_UNSIGNED_INT_2_10_10_10_REV:
106             case KTX_UNSIGNED_INT_24_8:
107             case KTX_UNSIGNED_INT_10F_11F_11F_REV:
108             case KTX_UNSIGNED_INT_5_9_9_9_REV:
109             case KTX_FLOAT_32_UNSIGNED_INT_24_8_REV:
110                 return true;
111         }
112         return false;
113     }
114
115     uint ktx_get_ogl_type_size(uint32 ogl_type)
116     {
117         switch (ogl_type)
118         {
119             case KTX_UNSIGNED_BYTE:
120             case KTX_BYTE:
121                 return 1;
122             case KTX_HALF_FLOAT:
123             case KTX_UNSIGNED_SHORT:
124             case KTX_SHORT:
125                 return 2;
126             case KTX_FLOAT:
127             case KTX_UNSIGNED_INT:
128             case KTX_INT:
129                 return 4;
130             case KTX_UNSIGNED_BYTE_3_3_2:
131             case KTX_UNSIGNED_BYTE_2_3_3_REV:
132                 return 1;
133             case KTX_UNSIGNED_SHORT_5_6_5:
134             case KTX_UNSIGNED_SHORT_5_6_5_REV:
135             case KTX_UNSIGNED_SHORT_4_4_4_4:
136             case KTX_UNSIGNED_SHORT_4_4_4_4_REV:
137             case KTX_UNSIGNED_SHORT_5_5_5_1:
138             case KTX_UNSIGNED_SHORT_1_5_5_5_REV:
139                 return 2;
140             case KTX_UNSIGNED_INT_8_8_8_8:
141             case KTX_UNSIGNED_INT_8_8_8_8_REV:
142             case KTX_UNSIGNED_INT_10_10_10_2:
143             case KTX_UNSIGNED_INT_2_10_10_10_REV:
144             case KTX_UNSIGNED_INT_24_8:
145             case KTX_UNSIGNED_INT_10F_11F_11F_REV:
146             case KTX_UNSIGNED_INT_5_9_9_9_REV:
147             case KTX_FLOAT_32_UNSIGNED_INT_24_8_REV: // this was 8, probably a mistake
148                 return 4;
149         }
150         return 0;
151     }
152
153     uint32 ktx_get_ogl_compressed_base_internal_fmt(uint32 ogl_fmt)
154     {
155         switch (ogl_fmt)
156         {
157             case KTX_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT:
158             case KTX_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT:
159                 // Should this be RG? I dunno.
160                 return KTX_LUMINANCE_ALPHA;
161
162             case KTX_COMPRESSED_LUMINANCE_LATC1_EXT:
163             case KTX_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT:
164             case KTX_COMPRESSED_R11_EAC:
165             case KTX_COMPRESSED_SIGNED_R11_EAC:
166             case KTX_COMPRESSED_RED_RGTC1:
167             case KTX_COMPRESSED_SIGNED_RED_RGTC1:
168                 return KTX_RED;
169
170             case KTX_COMPRESSED_RG11_EAC:
171             case KTX_COMPRESSED_SIGNED_RG11_EAC:
172             case KTX_COMPRESSED_RG_RGTC2:
173             case KTX_COMPRESSED_SIGNED_RG_RGTC2:
174                 return KTX_RG;
175
176             case KTX_ETC1_RGB8_OES:
177             case KTX_RGB_S3TC:
178             case KTX_RGB4_S3TC:
179             case KTX_COMPRESSED_RGB_S3TC_DXT1_EXT:
180             case KTX_COMPRESSED_SRGB_S3TC_DXT1_EXT:
181             case KTX_COMPRESSED_RGB8_ETC2:
182             case KTX_COMPRESSED_SRGB8_ETC2:
183             case KTX_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB:
184             case KTX_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB:
185                 return KTX_RGB;
186
187             case KTX_COMPRESSED_RGBA_S3TC_DXT1_EXT:
188             case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
189             case KTX_RGBA_S3TC:
190             case KTX_RGBA4_S3TC:
191             case KTX_COMPRESSED_RGBA_S3TC_DXT3_EXT:
192             case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
193             case KTX_COMPRESSED_RGBA_S3TC_DXT5_EXT:
194             case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
195             case KTX_RGBA_DXT5_S3TC:
196             case KTX_RGBA4_DXT5_S3TC:
197             case KTX_COMPRESSED_RGBA_BPTC_UNORM_ARB:
198             case KTX_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
199             case KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
200             case KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
201             case KTX_COMPRESSED_RGBA8_ETC2_EAC:
202             case KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
203                 return KTX_RGBA;
204         }
205         return 0;
206     }
207
208     bool ktx_get_ogl_fmt_desc(uint32 ogl_fmt, uint32 ogl_type, uint &block_dim, uint &bytes_per_block)
209     {
210         uint ogl_type_size = ktx_get_ogl_type_size(ogl_type);
211
212         block_dim = 1;
213         bytes_per_block = 0;
214
215         switch (ogl_fmt)
216         {
217             case KTX_COMPRESSED_RED_RGTC1:
218             case KTX_COMPRESSED_SIGNED_RED_RGTC1_EXT:
219             case KTX_COMPRESSED_LUMINANCE_LATC1_EXT:
220             case KTX_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT:
221             case KTX_ETC1_RGB8_OES:
222             case KTX_RGB_S3TC:
223             case KTX_RGB4_S3TC:
224             case KTX_COMPRESSED_RGB_S3TC_DXT1_EXT:
225             case KTX_COMPRESSED_RGBA_S3TC_DXT1_EXT:
226             case KTX_COMPRESSED_SRGB_S3TC_DXT1_EXT:
227             case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
228             case KTX_COMPRESSED_R11_EAC:
229             case KTX_COMPRESSED_SIGNED_R11_EAC:
230             case KTX_COMPRESSED_RGB8_ETC2:
231             case KTX_COMPRESSED_SRGB8_ETC2:
232             case KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
233             case KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
234             {
235                 block_dim = 4;
236                 bytes_per_block = 8;
237                 break;
238             }
239             case KTX_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT:
240             case KTX_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT:
241             case KTX_COMPRESSED_RED_GREEN_RGTC2_EXT:
242             case KTX_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:
243             case KTX_RGBA_S3TC:
244             case KTX_RGBA4_S3TC:
245             case KTX_COMPRESSED_RGBA_S3TC_DXT3_EXT:
246             case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
247             case KTX_COMPRESSED_RGBA_S3TC_DXT5_EXT:
248             case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
249             case KTX_RGBA_DXT5_S3TC:
250             case KTX_RGBA4_DXT5_S3TC:
251             case KTX_COMPRESSED_RGBA8_ETC2_EAC:
252             case KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
253             case KTX_COMPRESSED_RG11_EAC:
254             case KTX_COMPRESSED_SIGNED_RG11_EAC:
255             case KTX_COMPRESSED_RGBA_BPTC_UNORM_ARB:
256             case KTX_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
257             case KTX_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB:
258             case KTX_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB:
259             {
260                 block_dim = 4;
261                 bytes_per_block = 16;
262                 break;
263             }
264             case 1:
265             case KTX_ALPHA:
266             case KTX_RED:
267             case KTX_GREEN:
268             case KTX_BLUE:
269             case KTX_RED_INTEGER:
270             case KTX_GREEN_INTEGER:
271             case KTX_BLUE_INTEGER:
272             case KTX_ALPHA_INTEGER:
273             case KTX_LUMINANCE:
274             case KTX_DEPTH_COMPONENT:
275             case KTX_LUMINANCE_INTEGER_EXT:
276             {
277                 bytes_per_block = ogl_type_size;
278                 break;
279             }
280             case KTX_R8:
281             case KTX_R8UI:
282             case KTX_ALPHA8:
283             case KTX_LUMINANCE8:
284             {
285                 VOGL_ASSERT(ogl_type_size == 1);
286                 bytes_per_block = 1;
287                 break;
288             }
289             case 2:
290             case KTX_RG:
291             case KTX_RG_INTEGER:
292             case KTX_LUMINANCE_ALPHA:
293             case KTX_LUMINANCE_ALPHA_INTEGER_EXT:
294             {
295                 bytes_per_block = 2 * ogl_type_size;
296                 break;
297             }
298             case KTX_RG8:
299             case KTX_LUMINANCE8_ALPHA8:
300             {
301                 VOGL_ASSERT(ogl_type_size == 1);
302                 bytes_per_block = 2;
303                 break;
304             }
305             case 3:
306             case KTX_SRGB:
307             case KTX_RGB:
308             case KTX_BGR:
309             case KTX_RGB_INTEGER:
310             case KTX_BGR_INTEGER:
311             {
312                 bytes_per_block = ktx_is_packed_pixel_ogl_type(ogl_type) ? ogl_type_size : (3 * ogl_type_size);
313                 break;
314             }
315             case KTX_RGB8:
316             case KTX_SRGB8:
317             {
318                 VOGL_ASSERT(ogl_type_size == 1);
319                 bytes_per_block = 3;
320                 break;
321             }
322             case 4:
323             case KTX_RGBA:
324             case KTX_BGRA:
325             case KTX_RGBA_INTEGER:
326             case KTX_BGRA_INTEGER:
327             case KTX_SRGB_ALPHA:
328             {
329                 bytes_per_block = ktx_is_packed_pixel_ogl_type(ogl_type) ? ogl_type_size : (4 * ogl_type_size);
330                 break;
331             }
332             case KTX_SRGB8_ALPHA8:
333             case KTX_RGBA8:
334             {
335                 VOGL_ASSERT(ogl_type_size == 1);
336                 bytes_per_block = 4;
337                 break;
338             }
339             case KTX_DEPTH_STENCIL:
340             {
341                 bytes_per_block = ktx_is_packed_pixel_ogl_type(ogl_type) ? ogl_type_size : (2 * ogl_type_size);
342                 break;
343             }
344             default:
345                 return false;
346         }
347         return true;
348     }
349
350     bool ktx_texture::compute_pixel_info()
351     {
352         if ((!m_header.m_glType) || (!m_header.m_glFormat))
353         {
354             if (!ktx_is_compressed_ogl_fmt(m_header.m_glInternalFormat))
355                 return false;
356
357             // Must be a compressed format.
358             if ((m_header.m_glType) || (m_header.m_glFormat))
359                 return false;
360
361             if (!ktx_get_ogl_fmt_desc(m_header.m_glInternalFormat, m_header.m_glType, m_block_dim, m_bytes_per_block))
362                 return false;
363
364             if (m_block_dim == 1)
365                 return false;
366         }
367         else
368         {
369             // Must be an uncompressed format.
370             if (ktx_is_compressed_ogl_fmt(m_header.m_glInternalFormat))
371                 return false;
372
373             if (!ktx_get_ogl_fmt_desc(m_header.m_glFormat, m_header.m_glType, m_block_dim, m_bytes_per_block))
374                 return false;
375
376             if (m_block_dim > 1)
377                 return false;
378         }
379         return true;
380     }
381
382     bool ktx_texture::read_from_stream(data_stream_serializer &serializer)
383     {
384         clear();
385
386         // Read header
387         if (serializer.read(&m_header, 1, sizeof(m_header)) != sizeof(ktx_header))
388             return false;
389
390         // Check header
391         if (memcmp(s_ktx_file_id, m_header.m_identifier, sizeof(m_header.m_identifier)))
392             return false;
393
394         if ((m_header.m_endianness != KTX_OPPOSITE_ENDIAN) && (m_header.m_endianness != KTX_ENDIAN))
395             return false;
396
397         m_opposite_endianness = (m_header.m_endianness == KTX_OPPOSITE_ENDIAN);
398         if (m_opposite_endianness)
399         {
400             m_header.endian_swap();
401
402             if ((m_header.m_glTypeSize != sizeof(uint8)) && (m_header.m_glTypeSize != sizeof(uint16)) && (m_header.m_glTypeSize != sizeof(uint32)))
403                 return false;
404         }
405
406         if (!check_header())
407             return false;
408
409         if (!compute_pixel_info())
410         {
411 #if VOGL_KTX_PVRTEX_WORKAROUNDS
412             // rg [9/10/13] - moved this check into here, instead of in compute_pixel_info(), but need to retest it.
413             if ((!m_header.m_glInternalFormat) && (!m_header.m_glType) && (!m_header.m_glTypeSize) && (!m_header.m_glBaseInternalFormat))
414             {
415                 // PVRTexTool writes bogus headers when outputting ETC1.
416                 console::warning("ktx_texture::compute_pixel_info: Header doesn't specify any format, assuming ETC1 and hoping for the best\n");
417                 m_header.m_glBaseInternalFormat = KTX_RGB;
418                 m_header.m_glInternalFormat = KTX_ETC1_RGB8_OES;
419                 m_header.m_glTypeSize = 1;
420                 m_block_dim = 4;
421                 m_bytes_per_block = 8;
422             }
423             else
424 #endif
425                 return false;
426         }
427
428         uint8 pad_bytes[3];
429
430         // Read the key value entries
431         uint num_key_value_bytes_remaining = m_header.m_bytesOfKeyValueData;
432         while (num_key_value_bytes_remaining)
433         {
434             if (num_key_value_bytes_remaining < sizeof(uint32))
435                 return false;
436
437             uint32 key_value_byte_size;
438             if (serializer.read(&key_value_byte_size, 1, sizeof(uint32)) != sizeof(uint32))
439                 return false;
440
441             num_key_value_bytes_remaining -= sizeof(uint32);
442
443             if (m_opposite_endianness)
444                 key_value_byte_size = utils::swap32(key_value_byte_size);
445
446             if (key_value_byte_size > num_key_value_bytes_remaining)
447                 return false;
448
449             uint8_vec key_value_data;
450             if (key_value_byte_size)
451             {
452                 key_value_data.resize(key_value_byte_size);
453                 if (serializer.read(&key_value_data[0], 1, key_value_byte_size) != key_value_byte_size)
454                     return false;
455             }
456
457             m_key_values.push_back(key_value_data);
458
459             uint padding = 3 - ((key_value_byte_size + 3) % 4);
460             if (padding)
461             {
462                 if (serializer.read(pad_bytes, 1, padding) != padding)
463                     return false;
464             }
465
466             num_key_value_bytes_remaining -= key_value_byte_size;
467             if (num_key_value_bytes_remaining < padding)
468                 return false;
469             num_key_value_bytes_remaining -= padding;
470         }
471
472         // Now read the mip levels
473         uint total_faces = get_num_mips() * get_array_size() * get_num_faces() * get_depth();
474         if ((!total_faces) || (total_faces > 65535))
475             return false;
476
477 // See Section 2.8 of KTX file format: No rounding to block sizes should be applied for block compressed textures.
478 // OK, I'm going to break that rule otherwise KTX can only store a subset of textures that DDS can handle for no good reason.
479 #if 0
480         const uint mip0_row_blocks = m_header.m_pixelWidth / m_block_dim;
481         const uint mip0_col_blocks = VOGL_MAX(1, m_header.m_pixelHeight) / m_block_dim;
482 #else
483         const uint mip0_row_blocks = (m_header.m_pixelWidth + m_block_dim - 1) / m_block_dim;
484         const uint mip0_col_blocks = (VOGL_MAX(1, m_header.m_pixelHeight) + m_block_dim - 1) / m_block_dim;
485 #endif
486         if ((!mip0_row_blocks) || (!mip0_col_blocks))
487             return false;
488
489         const uint mip0_depth = VOGL_MAX(1, m_header.m_pixelDepth);
490         VOGL_NOTE_UNUSED(mip0_depth);
491
492         bool has_valid_image_size_fields = true;
493         bool disable_mip_and_cubemap_padding = false;
494
495 #if VOGL_KTX_PVRTEX_WORKAROUNDS
496         {
497             // PVRTexTool has a bogus KTX writer that doesn't write any imageSize fields. Nice.
498             size_t expected_bytes_remaining = 0;
499             for (uint mip_level = 0; mip_level < get_num_mips(); mip_level++)
500             {
501                 uint mip_width, mip_height, mip_depth;
502                 get_mip_dim(mip_level, mip_width, mip_height, mip_depth);
503
504                 const uint mip_row_blocks = (mip_width + m_block_dim - 1) / m_block_dim;
505                 const uint mip_col_blocks = (mip_height + m_block_dim - 1) / m_block_dim;
506                 if ((!mip_row_blocks) || (!mip_col_blocks))
507                     return false;
508
509                 expected_bytes_remaining += sizeof(uint32);
510
511                 if ((!m_header.m_numberOfArrayElements) && (get_num_faces() == 6))
512                 {
513                     for (uint face = 0; face < get_num_faces(); face++)
514                     {
515                         uint slice_size = mip_row_blocks * mip_col_blocks * m_bytes_per_block;
516                         expected_bytes_remaining += slice_size;
517
518                         uint num_cube_pad_bytes = 3 - ((slice_size + 3) % 4);
519                         expected_bytes_remaining += num_cube_pad_bytes;
520                     }
521                 }
522                 else
523                 {
524                     uint total_mip_size = 0;
525                     for (uint array_element = 0; array_element < get_array_size(); array_element++)
526                     {
527                         for (uint face = 0; face < get_num_faces(); face++)
528                         {
529                             for (uint zslice = 0; zslice < mip_depth; zslice++)
530                             {
531                                 uint slice_size = mip_row_blocks * mip_col_blocks * m_bytes_per_block;
532                                 total_mip_size += slice_size;
533                             }
534                         }
535                     }
536                     expected_bytes_remaining += total_mip_size;
537
538                     uint num_mip_pad_bytes = 3 - ((total_mip_size + 3) % 4);
539                     expected_bytes_remaining += num_mip_pad_bytes;
540                 }
541             }
542
543             if (serializer.get_stream()->get_remaining() < expected_bytes_remaining)
544             {
545                 has_valid_image_size_fields = false;
546                 disable_mip_and_cubemap_padding = true;
547                 console::warning("ktx_texture::read_from_stream: KTX file size is smaller than expected - trying to read anyway without imageSize fields\n");
548             }
549         }
550 #endif
551
552         for (uint mip_level = 0; mip_level < get_num_mips(); mip_level++)
553         {
554             uint mip_width, mip_height, mip_depth;
555             get_mip_dim(mip_level, mip_width, mip_height, mip_depth);
556
557             const uint mip_row_blocks = (mip_width + m_block_dim - 1) / m_block_dim;
558             const uint mip_col_blocks = (mip_height + m_block_dim - 1) / m_block_dim;
559             if ((!mip_row_blocks) || (!mip_col_blocks))
560                 return false;
561
562             uint32 image_size = 0;
563             if (!has_valid_image_size_fields)
564             {
565                 if ((!m_header.m_numberOfArrayElements) && (get_num_faces() == 6))
566                 {
567                     // The KTX file format has an exception for plain cubemap textures, argh.
568                     image_size = mip_row_blocks * mip_col_blocks * m_bytes_per_block;
569                 }
570                 else
571                 {
572                     image_size = mip_depth * mip_row_blocks * mip_col_blocks * m_bytes_per_block * get_array_size() * get_num_faces();
573                 }
574             }
575             else
576             {
577                 if (serializer.read(&image_size, 1, sizeof(image_size)) != sizeof(image_size))
578                     return false;
579
580                 if (m_opposite_endianness)
581                     image_size = utils::swap32(image_size);
582             }
583
584             if (!image_size)
585                 return false;
586
587             uint total_mip_size = 0;
588
589             // The KTX file format has an exception for plain cubemap textures, argh.
590             if ((!m_header.m_numberOfArrayElements) && (get_num_faces() == 6))
591             {
592                 // plain non-array cubemap
593                 for (uint face = 0; face < get_num_faces(); face++)
594                 {
595                     VOGL_ASSERT(m_image_data.size() == get_image_index(mip_level, 0, face, 0));
596
597                     m_image_data.push_back(uint8_vec());
598                     uint8_vec &image_data = m_image_data.back();
599
600                     image_data.resize(image_size);
601                     if (serializer.read(&image_data[0], 1, image_size) != image_size)
602                         return false;
603
604                     if (m_opposite_endianness)
605                         utils::endian_swap_mem(&image_data[0], image_size, m_header.m_glTypeSize);
606
607                     uint num_cube_pad_bytes = disable_mip_and_cubemap_padding ? 0 : (3 - ((image_size + 3) % 4));
608                     if (serializer.read(pad_bytes, 1, num_cube_pad_bytes) != num_cube_pad_bytes)
609                         return false;
610
611                     total_mip_size += image_size + num_cube_pad_bytes;
612                 }
613             }
614             else
615             {
616                 uint num_image_bytes_remaining = image_size;
617
618                 // 1D, 2D, 3D (normal or array texture), or array cubemap
619                 for (uint array_element = 0; array_element < get_array_size(); array_element++)
620                 {
621                     for (uint face = 0; face < get_num_faces(); face++)
622                     {
623                         for (uint zslice = 0; zslice < mip_depth; zslice++)
624                         {
625                             uint slice_size = mip_row_blocks * mip_col_blocks * m_bytes_per_block;
626                             if ((!slice_size) || (slice_size > num_image_bytes_remaining))
627                                 return false;
628
629                             uint image_index = get_image_index(mip_level, array_element, face, zslice);
630                             m_image_data.ensure_element_is_valid(image_index);
631
632                             uint8_vec &image_data = m_image_data[image_index];
633
634                             image_data.resize(slice_size);
635                             if (serializer.read(&image_data[0], 1, slice_size) != slice_size)
636                                 return false;
637
638                             if (m_opposite_endianness)
639                                 utils::endian_swap_mem(&image_data[0], slice_size, m_header.m_glTypeSize);
640
641                             num_image_bytes_remaining -= slice_size;
642
643                             total_mip_size += slice_size;
644                         }
645                     }
646                 }
647
648                 if (num_image_bytes_remaining)
649                 {
650                     VOGL_ASSERT_ALWAYS;
651                     return false;
652                 }
653             }
654
655             uint num_mip_pad_bytes = disable_mip_and_cubemap_padding ? 0 : (3 - ((total_mip_size + 3) % 4));
656             if (serializer.read(pad_bytes, 1, num_mip_pad_bytes) != num_mip_pad_bytes)
657                 return false;
658         }
659         return true;
660     }
661
662     bool ktx_texture::write_to_stream(data_stream_serializer &serializer, bool no_keyvalue_data) const
663     {
664         if (!consistency_check())
665         {
666             VOGL_ASSERT_ALWAYS;
667             return false;
668         }
669
670         memcpy(m_header.m_identifier, s_ktx_file_id, sizeof(m_header.m_identifier));
671         m_header.m_endianness = m_opposite_endianness ? KTX_OPPOSITE_ENDIAN : KTX_ENDIAN;
672
673         if (m_block_dim == 1)
674         {
675             m_header.m_glTypeSize = ktx_get_ogl_type_size(m_header.m_glType);
676             m_header.m_glBaseInternalFormat = m_header.m_glFormat;
677         }
678         else
679         {
680             m_header.m_glBaseInternalFormat = ktx_get_ogl_compressed_base_internal_fmt(m_header.m_glInternalFormat);
681         }
682
683         m_header.m_bytesOfKeyValueData = 0;
684         if (!no_keyvalue_data)
685         {
686             for (uint i = 0; i < m_key_values.size(); i++)
687                 m_header.m_bytesOfKeyValueData += sizeof(uint32) + ((m_key_values[i].size() + 3) & ~3);
688         }
689
690         if (m_opposite_endianness)
691             m_header.endian_swap();
692
693         bool success = (serializer.write(&m_header, sizeof(m_header), 1) == 1);
694
695         if (m_opposite_endianness)
696             m_header.endian_swap();
697
698         if (!success)
699             return success;
700
701         uint total_key_value_bytes = 0;
702         const uint8 padding[3] = { 0, 0, 0 };
703
704         if (!no_keyvalue_data)
705         {
706             for (uint i = 0; i < m_key_values.size(); i++)
707             {
708                 uint32 key_value_size = m_key_values[i].size();
709
710                 if (m_opposite_endianness)
711                     key_value_size = utils::swap32(key_value_size);
712
713                 success = (serializer.write(&key_value_size, sizeof(key_value_size), 1) == 1);
714                 total_key_value_bytes += sizeof(key_value_size);
715
716                 if (m_opposite_endianness)
717                     key_value_size = utils::swap32(key_value_size);
718
719                 if (!success)
720                     return false;
721
722                 if (key_value_size)
723                 {
724                     if (serializer.write(&m_key_values[i][0], key_value_size, 1) != 1)
725                         return false;
726                     total_key_value_bytes += key_value_size;
727
728                     uint num_padding = 3 - ((key_value_size + 3) % 4);
729                     if ((num_padding) && (serializer.write(padding, num_padding, 1) != 1))
730                         return false;
731                     total_key_value_bytes += num_padding;
732                 }
733             }
734             (void)total_key_value_bytes;
735         }
736
737         VOGL_ASSERT(total_key_value_bytes == m_header.m_bytesOfKeyValueData);
738
739         for (uint mip_level = 0; mip_level < get_num_mips(); mip_level++)
740         {
741             uint mip_width, mip_height, mip_depth;
742             get_mip_dim(mip_level, mip_width, mip_height, mip_depth);
743
744             const uint mip_row_blocks = (mip_width + m_block_dim - 1) / m_block_dim;
745             const uint mip_col_blocks = (mip_height + m_block_dim - 1) / m_block_dim;
746             if ((!mip_row_blocks) || (!mip_col_blocks))
747                 return false;
748
749             uint32 image_size = mip_row_blocks * mip_col_blocks * m_bytes_per_block;
750             if ((m_header.m_numberOfArrayElements) || (get_num_faces() == 1))
751                 image_size *= (get_array_size() * get_num_faces() * mip_depth);
752
753             if (!image_size)
754             {
755                 VOGL_ASSERT_ALWAYS;
756                 return false;
757             }
758
759             if (m_opposite_endianness)
760                 image_size = utils::swap32(image_size);
761
762             success = (serializer.write(&image_size, sizeof(image_size), 1) == 1);
763
764             if (m_opposite_endianness)
765                 image_size = utils::swap32(image_size);
766
767             if (!success)
768                 return false;
769
770             uint total_mip_size = 0;
771             uint total_image_data_size = 0;
772
773             if ((!m_header.m_numberOfArrayElements) && (get_num_faces() == 6))
774             {
775                 // plain non-array cubemap
776                 for (uint face = 0; face < get_num_faces(); face++)
777                 {
778                     const uint8_vec &image_data = get_image_data(get_image_index(mip_level, 0, face, 0));
779                     if ((!image_data.size()) || (image_data.size() != image_size))
780                         return false;
781
782                     if (m_opposite_endianness)
783                     {
784                         uint8_vec tmp_image_data(image_data);
785                         utils::endian_swap_mem(&tmp_image_data[0], tmp_image_data.size(), m_header.m_glTypeSize);
786                         if (serializer.write(&tmp_image_data[0], tmp_image_data.size(), 1) != 1)
787                             return false;
788                     }
789                     else if (serializer.write(&image_data[0], image_data.size(), 1) != 1)
790                         return false;
791
792                     // Not +=, but =, because of the silly image_size plain cubemap exception in the KTX file format
793                     total_image_data_size = image_data.size();
794
795                     uint num_cube_pad_bytes = 3 - ((image_data.size() + 3) % 4);
796                     if ((num_cube_pad_bytes) && (serializer.write(padding, num_cube_pad_bytes, 1) != 1))
797                         return false;
798
799                     total_mip_size += image_size + num_cube_pad_bytes;
800                 }
801             }
802             else
803             {
804                 // 1D, 2D, 3D (normal or array texture), or array cubemap
805                 for (uint array_element = 0; array_element < get_array_size(); array_element++)
806                 {
807                     for (uint face = 0; face < get_num_faces(); face++)
808                     {
809                         for (uint zslice = 0; zslice < mip_depth; zslice++)
810                         {
811                             const uint8_vec &image_data = get_image_data(get_image_index(mip_level, array_element, face, zslice));
812                             if (!image_data.size())
813                                 return false;
814
815                             if (m_opposite_endianness)
816                             {
817                                 uint8_vec tmp_image_data(image_data);
818                                 utils::endian_swap_mem(&tmp_image_data[0], tmp_image_data.size(), m_header.m_glTypeSize);
819                                 if (serializer.write(&tmp_image_data[0], tmp_image_data.size(), 1) != 1)
820                                     return false;
821                             }
822                             else if (serializer.write(&image_data[0], image_data.size(), 1) != 1)
823                                 return false;
824
825                             total_image_data_size += image_data.size();
826
827                             total_mip_size += image_data.size();
828                         }
829                     }
830                 }
831
832                 uint num_mip_pad_bytes = 3 - ((total_mip_size + 3) % 4);
833                 if ((num_mip_pad_bytes) && (serializer.write(padding, num_mip_pad_bytes, 1) != 1))
834                     return false;
835                 total_mip_size += num_mip_pad_bytes;
836             }
837
838             VOGL_ASSERT((total_mip_size & 3) == 0);
839             VOGL_ASSERT(total_image_data_size == image_size);
840         }
841
842         return true;
843     }
844
845     bool ktx_texture::init_1D(uint width, uint num_mips, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type)
846     {
847         clear();
848
849         m_header.m_pixelWidth = width;
850         m_header.m_numberOfMipmapLevels = num_mips;
851         m_header.m_glInternalFormat = ogl_internal_fmt;
852         m_header.m_glFormat = ogl_fmt;
853         m_header.m_glType = ogl_type;
854         m_header.m_numberOfFaces = 1;
855
856         if (!compute_pixel_info())
857             return false;
858
859         return true;
860     }
861
862     bool ktx_texture::init_1D_array(uint width, uint num_mips, uint array_size, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type)
863     {
864         clear();
865
866         m_header.m_pixelWidth = width;
867         m_header.m_numberOfMipmapLevels = num_mips;
868         m_header.m_numberOfArrayElements = array_size;
869         m_header.m_glInternalFormat = ogl_internal_fmt;
870         m_header.m_glFormat = ogl_fmt;
871         m_header.m_glType = ogl_type;
872         m_header.m_numberOfFaces = 1;
873
874         if (!compute_pixel_info())
875             return false;
876
877         return true;
878     }
879
880     bool ktx_texture::init_2D(uint width, uint height, uint num_mips, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type)
881     {
882         clear();
883
884         m_header.m_pixelWidth = width;
885         m_header.m_pixelHeight = height;
886         m_header.m_numberOfMipmapLevels = num_mips;
887         m_header.m_glInternalFormat = ogl_internal_fmt;
888         m_header.m_glFormat = ogl_fmt;
889         m_header.m_glType = ogl_type;
890         m_header.m_numberOfFaces = 1;
891
892         if (!compute_pixel_info())
893             return false;
894
895         return true;
896     }
897
898     bool ktx_texture::init_2D_array(uint width, uint height, uint num_mips, uint array_size, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type)
899     {
900         clear();
901
902         m_header.m_pixelWidth = width;
903         m_header.m_pixelHeight = height;
904         m_header.m_numberOfMipmapLevels = num_mips;
905         m_header.m_numberOfArrayElements = array_size;
906         m_header.m_glInternalFormat = ogl_internal_fmt;
907         m_header.m_glFormat = ogl_fmt;
908         m_header.m_glType = ogl_type;
909         m_header.m_numberOfFaces = 1;
910
911         if (!compute_pixel_info())
912             return false;
913
914         return true;
915     }
916
917     bool ktx_texture::init_3D(uint width, uint height, uint depth, uint num_mips, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type)
918     {
919         clear();
920
921         m_header.m_pixelWidth = width;
922         m_header.m_pixelHeight = height;
923         m_header.m_pixelDepth = depth;
924         m_header.m_numberOfMipmapLevels = num_mips;
925         m_header.m_glInternalFormat = ogl_internal_fmt;
926         m_header.m_glFormat = ogl_fmt;
927         m_header.m_glType = ogl_type;
928         m_header.m_numberOfFaces = 1;
929
930         if (!compute_pixel_info())
931             return false;
932
933         return true;
934     }
935
936     bool ktx_texture::init_cubemap(uint dim, uint num_mips, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type)
937     {
938         clear();
939
940         m_header.m_pixelWidth = dim;
941         m_header.m_pixelHeight = dim;
942         m_header.m_numberOfMipmapLevels = num_mips;
943         m_header.m_glInternalFormat = ogl_internal_fmt;
944         m_header.m_glFormat = ogl_fmt;
945         m_header.m_glType = ogl_type;
946         m_header.m_numberOfFaces = 6;
947
948         if (!compute_pixel_info())
949             return false;
950
951         return true;
952     }
953
954     bool ktx_texture::check_header() const
955     {
956         if (((get_num_faces() != 1) && (get_num_faces() != 6)) || (!m_header.m_pixelWidth))
957             return false;
958
959         if ((!m_header.m_pixelHeight) && (m_header.m_pixelDepth))
960             return false;
961
962         if ((get_num_faces() == 6) && ((m_header.m_pixelDepth) || (!m_header.m_pixelHeight)))
963             return false;
964
965         if (m_header.m_numberOfMipmapLevels)
966         {
967             const uint max_mipmap_dimension = 1U << (m_header.m_numberOfMipmapLevels - 1U);
968             if (max_mipmap_dimension > (VOGL_MAX(VOGL_MAX(m_header.m_pixelWidth, m_header.m_pixelHeight), m_header.m_pixelDepth)))
969                 return false;
970         }
971
972         return true;
973     }
974
975     uint ktx_texture::get_expected_image_size(uint mip_level) const
976     {
977         uint mip_width, mip_height, mip_depth;
978         get_mip_dim(mip_level, mip_width, mip_height, mip_depth);
979
980         const uint mip_row_blocks = (mip_width + m_block_dim - 1) / m_block_dim;
981         const uint mip_col_blocks = (mip_height + m_block_dim - 1) / m_block_dim;
982         if ((!mip_row_blocks) || (!mip_col_blocks))
983         {
984             VOGL_ASSERT_ALWAYS;
985             return 0;
986         }
987
988         return mip_row_blocks * mip_col_blocks * m_bytes_per_block;
989     }
990
991     uint ktx_texture::get_total_images() const
992     {
993         if (!is_valid() || !get_num_mips())
994             return 0;
995
996         // bogus:
997         //return get_num_mips() * (get_depth() * get_num_faces() * get_array_size());
998
999         // Naive algorithm, could just compute based off the # of mips
1000         uint max_index = 0;
1001         for (uint mip_level = 0; mip_level < get_num_mips(); mip_level++)
1002         {
1003             uint total_zslices = math::maximum<uint>(get_depth() >> mip_level, 1U);
1004             uint index = get_image_index(mip_level, get_array_size() - 1, get_num_faces() - 1, total_zslices - 1);
1005             max_index = math::maximum<uint>(max_index, index);
1006         }
1007
1008         return max_index + 1;
1009     }
1010
1011     bool ktx_texture::consistency_check() const
1012     {
1013         if (!check_header())
1014             return false;
1015
1016         uint block_dim = 0, bytes_per_block = 0;
1017         if ((!m_header.m_glType) || (!m_header.m_glFormat))
1018         {
1019             if ((m_header.m_glType) || (m_header.m_glFormat))
1020                 return false;
1021             if (!ktx_get_ogl_fmt_desc(m_header.m_glInternalFormat, m_header.m_glType, block_dim, bytes_per_block))
1022                 return false;
1023             if (block_dim == 1)
1024                 return false;
1025             //if ((get_width() % block_dim) || (get_height() % block_dim))
1026             //   return false;
1027         }
1028         else
1029         {
1030             if (!ktx_get_ogl_fmt_desc(m_header.m_glFormat, m_header.m_glType, block_dim, bytes_per_block))
1031                 return false;
1032             if (block_dim > 1)
1033                 return false;
1034         }
1035         if ((m_block_dim != block_dim) || (m_bytes_per_block != bytes_per_block))
1036             return false;
1037
1038         uint total_expected_images = get_total_images();
1039         if (m_image_data.size() != total_expected_images)
1040             return false;
1041
1042         for (uint mip_level = 0; mip_level < get_num_mips(); mip_level++)
1043         {
1044             uint mip_width, mip_height, mip_depth;
1045             get_mip_dim(mip_level, mip_width, mip_height, mip_depth);
1046
1047             const uint mip_row_blocks = (mip_width + m_block_dim - 1) / m_block_dim;
1048             const uint mip_col_blocks = (mip_height + m_block_dim - 1) / m_block_dim;
1049             if ((!mip_row_blocks) || (!mip_col_blocks))
1050                 return false;
1051
1052             for (uint array_element = 0; array_element < get_array_size(); array_element++)
1053             {
1054                 for (uint face = 0; face < get_num_faces(); face++)
1055                 {
1056                     for (uint zslice = 0; zslice < mip_depth; zslice++)
1057                     {
1058                         const uint8_vec &image_data = get_image_data(get_image_index(mip_level, array_element, face, zslice));
1059
1060                         uint expected_image_size = mip_row_blocks * mip_col_blocks * m_bytes_per_block;
1061                         if (image_data.size() != expected_image_size)
1062                             return false;
1063                     }
1064                 }
1065             }
1066         }
1067
1068         return true;
1069     }
1070
1071     void ktx_texture::get_keys(dynamic_string_array &keys) const
1072     {
1073         keys.resize(0);
1074         keys.reserve(m_key_values.size());
1075
1076         for (uint i = 0; i < m_key_values.size(); i++)
1077         {
1078             const uint8_vec &v = m_key_values[i];
1079
1080             keys.enlarge(1)->set(reinterpret_cast<const char *>(v.get_ptr()));
1081         }
1082     }
1083
1084     const uint8_vec *ktx_texture::find_key(const char *pKey) const
1085     {
1086         const uint n = vogl_strlen(pKey) + 1;
1087         for (uint i = 0; i < m_key_values.size(); i++)
1088         {
1089             const uint8_vec &v = m_key_values[i];
1090             if ((v.size() >= n) && (!memcmp(&v[0], pKey, n)))
1091                 return &v;
1092         }
1093
1094         return NULL;
1095     }
1096
1097     bool ktx_texture::get_key_value_data(const char *pKey, uint8_vec &data) const
1098     {
1099         const uint8_vec *p = find_key(pKey);
1100         if (!p)
1101         {
1102             data.resize(0);
1103             return false;
1104         }
1105
1106         const uint ofs = vogl_strlen(pKey) + 1;
1107         const uint8 *pValue = p->get_ptr() + ofs;
1108         const uint n = p->size() - ofs;
1109
1110         data.resize(n);
1111         if (n)
1112             memcpy(data.get_ptr(), pValue, n);
1113         return true;
1114     }
1115
1116     bool ktx_texture::get_key_value_as_string(const char *pKey, dynamic_string &str) const
1117     {
1118         const uint8_vec *p = find_key(pKey);
1119         if (!p)
1120         {
1121             str.clear();
1122             return false;
1123         }
1124
1125         const uint ofs = vogl_strlen(pKey) + 1;
1126         const uint8 *pValue = p->get_ptr() + ofs;
1127         const uint n = p->size() - ofs;
1128
1129         uint i;
1130         for (i = 0; i < n; i++)
1131             if (!pValue[i])
1132                 break;
1133
1134         str.set_from_buf(pValue, i);
1135         return true;
1136     }
1137
1138     uint ktx_texture::add_key_value(const char *pKey, const void *pVal, uint val_size)
1139     {
1140         const uint idx = m_key_values.size();
1141         m_key_values.resize(idx + 1);
1142         uint8_vec &v = m_key_values.back();
1143         v.append(reinterpret_cast<const uint8 *>(pKey), vogl_strlen(pKey) + 1);
1144         v.append(static_cast<const uint8 *>(pVal), val_size);
1145         return idx;
1146     }
1147
1148     bool ktx_texture::operator==(const ktx_texture &rhs) const
1149     {
1150         if (this == &rhs)
1151             return true;
1152
1153 // This is not super deep because I want to avoid poking around into internal state (such as the header)
1154
1155 #define CMP(x)      \
1156     if (x != rhs.x) \
1157         return false;
1158         CMP(get_ogl_internal_fmt());
1159         CMP(get_width());
1160         CMP(get_height());
1161         CMP(get_depth());
1162         CMP(get_num_mips());
1163         CMP(get_array_size());
1164         CMP(get_num_faces());
1165         CMP(is_compressed());
1166         CMP(get_block_dim());
1167
1168         // The image fmt/type shouldn't matter with compressed textures.
1169         if (!is_compressed())
1170         {
1171             CMP(get_ogl_fmt());
1172             CMP(get_ogl_type());
1173         }
1174
1175         CMP(get_total_images());
1176
1177         CMP(get_opposite_endianness());
1178
1179         // Do an order insensitive key/value comparison.
1180         dynamic_string_array lhs_keys;
1181         get_keys(lhs_keys);
1182
1183         dynamic_string_array rhs_keys;
1184         rhs.get_keys(rhs_keys);
1185
1186         if (lhs_keys.size() != rhs_keys.size())
1187             return false;
1188
1189         lhs_keys.sort(dynamic_string_less_than_case_sensitive());
1190         rhs_keys.sort(dynamic_string_less_than_case_sensitive());
1191
1192         for (uint i = 0; i < lhs_keys.size(); i++)
1193             if (lhs_keys[i].compare(rhs_keys[i], true) != 0)
1194                 return false;
1195
1196         for (uint i = 0; i < lhs_keys.size(); i++)
1197         {
1198             uint8_vec lhs_data, rhs_data;
1199             if (!get_key_value_data(lhs_keys[i].get_ptr(), lhs_data))
1200                 return false;
1201             if (!get_key_value_data(lhs_keys[i].get_ptr(), rhs_data))
1202                 return false;
1203             if (lhs_data != rhs_data)
1204                 return false;
1205         }
1206
1207         // Compare images.
1208         for (uint l = 0; l < get_num_mips(); l++)
1209         {
1210             for (uint a = 0; a < get_array_size(); a++)
1211             {
1212                 for (uint f = 0; f < get_num_faces(); f++)
1213                 {
1214                     for (uint z = 0; z < get_depth(); z++)
1215                     {
1216                         const uint8_vec &lhs_img = get_image_data(l, a, f, z);
1217                         const uint8_vec &rhs_img = rhs.get_image_data(l, a, f, z);
1218
1219                         if (lhs_img != rhs_img)
1220                             return false;
1221                     }
1222                 }
1223             }
1224         }
1225 #undef CMP
1226         return true;
1227     }
1228
1229 } // namespace vogl