1// tImageDDS.cpp 
2// 
3// This class knows how to load Direct Draw Surface (.dds) files. Saving is not implemented yet. 
4// It does zero processing of image data. It knows the details of the dds file format and loads the data into tLayers. 
5// Currently it does not compress or decompress the image data if it is compressed (DXTn), it simply keeps it in the 
6// same format as the source file. The layers may be 'stolen' from a tImageDDS so that excessive memcpys are avoided. 
7// After they are stolen the tImageDDS is invalid. 
8// 
9// Copyright (c) 2006, 2017, 2019, 2020 Tristan Grimmer. 
10// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby 
11// granted, provided that the above copyright notice and this permission notice appear in all copies. 
12// 
13// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 
14// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
15// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 
16// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
17// PERFORMANCE OF THIS SOFTWARE. 
18 
19#include <Foundation/tString.h> 
20#include "Image/tImageDDS.h" 
21#define STRICT_DDS_HEADER_CHECKING 
22#define FourCC(ch0, ch1, ch2, ch3) (uint(uint8(ch0)) | (uint(uint8(ch1)) << 8) | (uint(uint8(ch2)) << 16) | (uint(uint8(ch3)) << 24)) 
23namespace tImage 
24{ 
25 
26 
27tImageDDS::tImageDDS() : 
28 Filename(), 
29 PixelFormat(tPixelFormat::Invalid), 
30 IsCubeMap(false), 
31 NumImages(0), 
32 NumMipmapLayers(0) 
33{ 
34 tStd::tMemset(MipmapLayers, 0, sizeof(MipmapLayers)); 
35} 
36 
37 
38tImageDDS::tImageDDS(const tString& ddsFile, bool reverseRowOrder) : 
39 Filename(ddsFile), 
40 PixelFormat(tPixelFormat::Invalid), 
41 IsCubeMap(false), 
42 NumImages(0), 
43 NumMipmapLayers(0) 
44{ 
45 tStd::tMemset(MipmapLayers, 0, sizeof(MipmapLayers)); 
46 Load(ddsFile, reverseRowOrder); 
47} 
48 
49 
50tImageDDS::tImageDDS(const uint8* ddsFileInMemory, int numBytes, bool reverseRowOrder) : 
51 Filename(), 
52 PixelFormat(tPixelFormat::Invalid), 
53 IsCubeMap(false), 
54 NumImages(0), 
55 NumMipmapLayers(0) 
56{ 
57 tStd::tMemset(MipmapLayers, 0, sizeof(MipmapLayers)); 
58 Load(ddsFileInMemory, numBytes, reverseRowOrder); 
59} 
60 
61 
62void tImageDDS::Clear() 
63{ 
64 for (int image = 0; image < NumImages; image++) 
65 { 
66 for (int layer = 0; layer < NumMipmapLayers; layer++) 
67 { 
68 delete MipmapLayers[layer][image]; 
69 MipmapLayers[layer][image] = 0; 
70 } 
71 } 
72 
73 Filename.Clear(); 
74 PixelFormat = tPixelFormat::Invalid; 
75 IsCubeMap = false; 
76 NumImages = 0; 
77 NumMipmapLayers = 0; 
78} 
79 
80 
81bool tImageDDS::IsOpaque() const 
82{ 
83 switch (PixelFormat) 
84 { 
85 case tPixelFormat::R8G8B8A8: 
86 case tPixelFormat::B8G8R8A8: 
87 case tPixelFormat::BC1_DXT1BA: 
88 case tPixelFormat::BC2_DXT3: 
89 case tPixelFormat::BC3_DXT5: 
90 case tPixelFormat::G3B5A1R5G2: 
91 case tPixelFormat::G4B4A4R4: 
92 return false; 
93 
94 default: 
95 return true; 
96 } 
97 
98 return true; 
99} 
100 
101 
102bool tImageDDS::StealTextureLayers(tList<tLayer>& layers) 
103{ 
104 if (!IsValid() || IsCubemap() || (NumImages <= 0)) 
105 return false; 
106 
107 for (int mip = 0; mip < NumMipmapLayers; mip++) 
108 { 
109 layers.Append(MipmapLayers[mip][0]); 
110 MipmapLayers[mip][0] = nullptr; 
111 } 
112 
113 Clear(); 
114 return true; 
115} 
116 
117 
118int tImageDDS::StealCubemapLayers(tList<tLayer> layerLists[tSurfIndex_NumSurfaces], uint32 sideFlags) 
119{ 
120 if (!IsValid() || !IsCubemap() || !sideFlags) 
121 return 0; 
122 
123 int sideCount = 0; 
124 for (int side = 0; side < tSurfIndex_NumSurfaces; side++) 
125 { 
126 uint32 sideFlag = 1 << side; 
127 if (!(sideFlag & sideFlags)) 
128 continue; 
129 
130 tList<tLayer>& layers = layerLists[side]; 
131 for (int mip = 0; mip < NumMipmapLayers; mip++) 
132 { 
133 layers.Append( MipmapLayers[mip][side] ); 
134 MipmapLayers[mip][side] = nullptr; 
135 } 
136 sideCount++; 
137 } 
138 
139 Clear(); 
140 return sideCount; 
141} 
142 
143 
144enum tDDSPixelFormatFlag 
145{ 
146 // May be used in the DDSPixelFormat struct to indicate alphas present for RGB formats. 
147 tDDSPixelFormatFlag_Alpha = 0x00000001, 
148 
149 // A DDS file may contain this type of data (pixel format). eg. DXT1 is a fourCC format. 
150 tDDSPixelFormatFlag_FourCC = 0x00000004, 
151 
152 // A DDS file may contain this type of data (pixel format). eg. A8R8G8B8 
153 tDDSPixelFormatFlag_RGB = 0x00000040 
154}; 
155 
156 
157#pragma pack(push, 4) 
158struct tDDSPixelFormat 
159{ 
160 // Must be 32. 
161 uint32 Size; 
162 
163 // See tDDSPixelFormatFlag. Flags to indicate valid fields. Uncompressed formats will usually use 
164 // tDDSPixelFormatFlag_RGB to indicate an RGB format, while compressed formats will use tDDSPixelFormatFlag_FourCC 
165 // with a four-character code. 
166 uint32 Flags; 
167 
168 // "DXT1", "DXT3", and "DXT5" are examples. m_flags should have DDSPixelFormatFlag_FourCC. 
169 uint32 FourCC; 
170 
171 // Valid if flags has DDSPixelFormatFlag_RGB. For RGB formats this is the total number of bits per pixel. This 
172 // value is usually 16, 24, or 32. For A8R8G8B8, this value would be 32. 
173 uint32 RGBBitCount; 
174 
175 // For RGB formats these three fields contain the masks for the red, green, and blue channels. For A8R8G8B8 these 
176 // values would be 0x00FF0000, 0x0000FF00, and 0x000000FF respectively. 
177 uint32 MaskRed; 
178 uint32 MaskGreen; 
179 uint32 MaskBlue; 
180 
181 // If the flags have DDSPixelFormatFlag_Alpha set, this is valid and contains tha alpha mask. Eg. For A8R8G8B8 this 
182 // value would be 0xFF000000. 
183 uint32 MaskAlpha; 
184}; 
185#pragma pack(pop) 
186 
187 
188enum tDDSCapsBasic 
189{ 
190 tDDSCapsBasic_Complex = 0x00000008, 
191 tDDSCapsBasic_Texture = 0x00001000, 
192 tDDSCapsBasic_Mipmap = 0x00400000 
193}; 
194 
195 
196enum tDDSCapsExtra 
197{ 
198 tDDSCapsExtra_CubeMap = 0x00000200, 
199 tDDSCapsExtra_CubeMapPosX = 0x00000400, 
200 tDDSCapsExtra_CubeMapNegX = 0x00000800, 
201 tDDSCapsExtra_CubeMapPosY = 0x00001000, 
202 tDDSCapsExtra_CubeMapNegY = 0x00002000, 
203 tDDSCapsExtra_CubeMapPosZ = 0x00004000, 
204 tDDSCapsExtra_CubeMapNegZ = 0x00008000, 
205 tDDSCapsExtra_Volume = 0x00200000 
206}; 
207 
208 
209#pragma pack(push, 4) 
210struct tDDSCapabilities 
211{ 
212 // DDS files should always include tDDSCapsBasic_Texture. If the file contains mipmaps tDDSCapsBasic_Mipmap should 
213 // be set. For any dds file with more than one main surface, such as a mipmap, cubic environment map, or volume 
214 // texture, DDSCapsBasic_Complex should also be set. 
215 uint32 FlagsCapsBasic; 
216 
217 // For cubic environment maps tDDSCapsExtra_CubeMap should be included as well as one or more faces of the map 
218 // (tDDSCapsExtra_CubeMapPosX, etc). For volume textures tDDSCapsExtra_Volume should be included. 
219 uint32 FlagsCapsExtra; 
220 uint32 Unused[2]; 
221}; 
222#pragma pack(pop) 
223 
224 
225enum tDDSFlag 
226{ 
227 tDDSFlag_Caps = 0x00000001, // Always included. 
228 tDDSFlag_Height = 0x00000002, // Always included. Height of largest image if mipmaps included. 
229 tDDSFlag_Width = 0x00000004, // Always included. Width of largest image if mipmaps included. 
230 tDDSFlag_Pitch = 0x00000008, 
231 tDDSFlag_PixelFormat = 0x00001000, // Always included. 
232 tDDSFlag_MipmapCount = 0x00020000, 
233 tDDSFlag_LinearSize = 0x00080000, 
234 tDDSFlag_Depth = 0x00800000 
235}; 
236 
237 
238enum tD3DFORMAT 
239{ 
240 tD3DFMT_UNKNOWN = 0, 
241 
242 tD3DFMT_R8G8B8 = 20, 
243 tD3DFMT_A8R8G8B8 = 21, 
244 tD3DFMT_X8R8G8B8 = 22, 
245 tD3DFMT_R5G6B5 = 23, 
246 tD3DFMT_X1R5G5B5 = 24, 
247 tD3DFMT_A1R5G5B5 = 25, 
248 tD3DFMT_A4R4G4B4 = 26, 
249 tD3DFMT_R3G3B2 = 27, 
250 tD3DFMT_A8 = 28, 
251 tD3DFMT_A8R3G3B2 = 29, 
252 tD3DFMT_X4R4G4B4 = 30, 
253 tD3DFMT_A2B10G10R10 = 31, 
254 tD3DFMT_A8B8G8R8 = 32, 
255 tD3DFMT_X8B8G8R8 = 33, 
256 tD3DFMT_G16R16 = 34, 
257 tD3DFMT_A2R10G10B10 = 35, 
258 tD3DFMT_A16B16G16R16 = 36, 
259 
260 tD3DFMT_A8P8 = 40, 
261 tD3DFMT_P8 = 41, 
262 
263 tD3DFMT_L8 = 50, 
264 tD3DFMT_A8L8 = 51, 
265 tD3DFMT_A4L4 = 52, 
266 
267 tD3DFMT_V8U8 = 60, 
268 tD3DFMT_L6V5U5 = 61, 
269 tD3DFMT_X8L8V8U8 = 62, 
270 tD3DFMT_Q8W8V8U8 = 63, 
271 tD3DFMT_V16U16 = 64, 
272 tD3DFMT_A2W10V10U10 = 67, 
273 
274 tD3DFMT_UYVY = FourCC('U', 'Y', 'V', 'Y'), 
275 tD3DFMT_R8G8_B8G8 = FourCC('R', 'G', 'B', 'G'), 
276 tD3DFMT_YUY2 = FourCC('Y', 'U', 'Y', '2'), 
277 tD3DFMT_G8R8_G8B8 = FourCC('G', 'R', 'G', 'B'), 
278 tD3DFMT_DXT1 = FourCC('D', 'X', 'T', '1'), 
279 tD3DFMT_DXT2 = FourCC('D', 'X', 'T', '2'), 
280 tD3DFMT_DXT3 = FourCC('D', 'X', 'T', '3'), 
281 tD3DFMT_DXT4 = FourCC('D', 'X', 'T', '4'), 
282 tD3DFMT_DXT5 = FourCC('D', 'X', 'T', '5'), 
283 
284 tD3DFMT_D16_LOCKABLE = 70, 
285 tD3DFMT_D32 = 71, 
286 tD3DFMT_D15S1 = 73, 
287 tD3DFMT_D24S8 = 75, 
288 tD3DFMT_D24X8 = 77, 
289 tD3DFMT_D24X4S4 = 79, 
290 tD3DFMT_D16 = 80, 
291 
292 tD3DFMT_D32F_LOCKABLE = 82, 
293 tD3DFMT_D24FS8 = 83, 
294 
295 tD3DFMT_D32_LOCKABLE = 84, 
296 tD3DFMT_S8_LOCKABLE = 85, 
297 
298 tD3DFMT_L16 = 81, 
299 
300 tD3DFMT_VERTEXDATA = 100, 
301 tD3DFMT_INDEX16 = 101, 
302 tD3DFMT_INDEX32 = 102, 
303 
304 tD3DFMT_Q16W16V16U16 = 110, 
305 
306 tD3DFMT_MULTI2_ARGB8 = FourCC('M','E','T','1'), 
307 
308 tD3DFMT_R16F = 111, 
309 tD3DFMT_G16R16F = 112, 
310 tD3DFMT_A16B16G16R16F = 113, 
311 
312 tD3DFMT_R32F = 114, 
313 tD3DFMT_G32R32F = 115, 
314 tD3DFMT_A32B32G32R32F = 116, 
315 
316 tD3DFMT_CxV8U8 = 117, 
317 
318 tD3DFMT_FORCE_DWORD = 0x7fffffff 
319}; 
320 
321 
322// Default packing is 8 bytes but the header is 128 bytes (mult of 4), so we make it all work here. 
323#pragma pack(push, 4) 
324struct tDDSHeader 
325{ 
326 uint32 Size; // Must be set to 124. 
327 uint32 Flags; // See tDDSFlags. 
328 uint32 Height; // Height of main image. 
329 uint32 Width; // Width of main image. 
330 
331 // For uncompressed formats, this is the number of bytes per scan line (32-bit aligned) for the main image. dwFlags 
332 // should include DDSD_PITCH in this case. For compressed formats, this is the total number of bytes for the main 
333 // image. m_flags should have tDDSFlag_LinearSize in this case. 
334 uint32 PitchLinearSize; 
335 uint32 Depth; // For volume textures. tDDSFlag_Depth is set for this to be valid. 
336 uint32 MipmapCount; // Valid if tDDSFlag_MipmapCount set. @todo Count includes main image? 
337 uint32 UnusedA[11]; 
338 tDDSPixelFormat PixelFormat; // 32 Bytes. 
339 tDDSCapabilities Capabilities; // 16 Bytes. 
340 uint32 UnusedB; 
341}; 
342#pragma pack(pop) 
343 
344 
345// These DXT blocks are needed so that the tImageDDS class can re-order the rows by messing with each block's lookup 
346// table and alpha tables. This is because DDS files have the rows of their textures upside down (texture origin in 
347// OpenGL is lower left, while in DirectX it is upper left). See: http://en.wikipedia.org/wiki/S3_Texture_Compression 
348#pragma pack(push, 1) 
349 
350 
351// This block is used for both DXT1 and DXT1 with binary alpha. It's also used as the colour information block in the 
352// DXT 2, 3, 4 and 5 formats. Size is 64 bits. 
353struct tDXT1Block 
354{ 
355 uint16 Colour0; // R5G6B5 
356 uint16 Colour1; // R5G6B5 
357 uint8 LookupTableRows[4]; 
358}; 
359 
360 
361// This one is the same for DXT2 and 3, although we don't support 2 (premultiplied alpha). Size is 128 bits. 
362struct tDXT3Block 
363{ 
364 uint16 AlphaTableRows[4]; // Each alpha is 4 bits. 
365 tDXT1Block ColourBlock; 
366}; 
367 
368 
369// This one is the same for DXT4 and 5, although we don't support 4 (premultiplied alpha). Size is 128 bits. 
370struct tDXT5Block 
371{ 
372 uint8 Alpha0; 
373 uint8 Alpha1; 
374 uint8 AlphaTable[6]; // Each of the 4x4 pixel entries is 3 bits. 
375 tDXT1Block ColourBlock; 
376 
377 // These accessors are needed because of the unusual alignment of the 3bit alpha indexes. They each return or set a 
378 // value in [0, 2^12) which represents a single row. The row variable should be in [0, 3] 
379 uint16 GetAlphaRow(int row) 
380 { 
381 tAssert(row < 4); 
382 switch (row) 
383 { 
384 case 1: 
385 return (AlphaTable[2] << 4) | (0x0F & (AlphaTable[1] >> 4)); 
386 
387 case 0: 
388 return ((AlphaTable[1] & 0x0F) << 8) | AlphaTable[0]; 
389 
390 case 3: 
391 return (AlphaTable[5] << 4) | (0x0F & (AlphaTable[4] >> 4)); 
392 
393 case 2: 
394 return ((AlphaTable[4] & 0x0F) << 8) | AlphaTable[3]; 
395 } 
396 return 0; 
397 } 
398 
399 void SetAlphaRow(int row, uint16 val) 
400 { 
401 tAssert(row < 4); 
402 tAssert(val < 4096); 
403 switch (row) 
404 { 
405 case 1: 
406 AlphaTable[2] = val >> 4; 
407 AlphaTable[1] = (AlphaTable[1] & 0x0F) | ((val & 0x000F) << 4); 
408 break; 
409 
410 case 0: 
411 AlphaTable[1] = (AlphaTable[1] & 0xF0) | (val >> 8); 
412 AlphaTable[0] = val & 0x00FF; 
413 break; 
414 
415 case 3: 
416 AlphaTable[5] = val >> 4; 
417 AlphaTable[4] = (AlphaTable[4] & 0x0F) | ((val & 0x000F) << 4); 
418 break; 
419 
420 case 2: 
421 AlphaTable[4] = (AlphaTable[4] & 0xF0) | (val >> 8); 
422 AlphaTable[3] = val & 0x00FF; 
423 break; 
424 } 
425 } 
426}; 
427#pragma pack(pop) 
428 
429 
430void tImageDDS::Load(const tString& ddsFile, bool reverseRowOrder) 
431{ 
432 Clear(); 
433 if (tSystem::tGetFileType(ddsFile) != tSystem::tFileType::DDS) 
434 throw tDDSError(tDDSError::tCode::IncorrectExtension, tSystem::tGetFileName(ddsFile)); 
435 
436 if (!tSystem::tFileExists(ddsFile)) 
437 throw tDDSError(tDDSError::tCode::FileNonexistent, tSystem::tGetFileName(ddsFile)); 
438 
439 int ddsSizeBytes; 
440 uint8* ddsData = (uint8*)tSystem::tLoadFile(ddsFile, 0, &ddsSizeBytes); 
441 LoadFromMemory(ddsData, ddsSizeBytes, reverseRowOrder); 
442 
443 delete[] ddsData; 
444} 
445 
446 
447void tImageDDS::Load(const uint8* ddsFileInMemory, int ddsSizeBytes, bool reverseRowOrder) 
448{ 
449 Clear(); 
450 LoadFromMemory(ddsFileInMemory, ddsSizeBytes, reverseRowOrder); 
451} 
452 
453 
454void tImageDDS::LoadFromMemory(const uint8* ddsData, int ddsSizeBytes, bool reverseRowOrder) 
455{ 
456 tString baseName = tSystem::tGetFileName(Filename); 
457 
458 // This will deal with zero-sized files properly as well. 
459 if (ddsSizeBytes < int(sizeof(tDDSHeader)+4)) 
460 { 
461 delete[] ddsData; 
462 throw tDDSError(tDDSError::tCode::IncorrectFileSize, baseName); 
463 } 
464 
465 const uint8* ddsCurr = ddsData; 
466 uint32& magic = *((uint32*)ddsCurr); ddsCurr += sizeof(uint32); 
467 
468 if (magic != ' SDD') 
469 { 
470 delete[] ddsData; 
471 throw tDDSError(tDDSError::tCode::Magic); 
472 } 
473 
474 tDDSHeader& header = *((tDDSHeader*)ddsCurr); ddsCurr += sizeof(header); 
475 tAssert(sizeof(tDDSHeader) == 124); 
476 const uint8* pixelData = ddsCurr; 
477 
478 if (header.Size != 124) 
479 { 
480 delete[] ddsData; 
481 throw tDDSError(tDDSError::tCode::IncorrectHeaderSize, baseName); 
482 } 
483 
484 uint32 flags = header.Flags; 
485 int mainWidth = header.Width; // Main image. 
486 int mainHeight = header.Height; // Main image. 
487 
488 if (!tMath::tIsPower2(mainWidth) || !tMath::tIsPower2(mainHeight)) 
489 { 
490 delete[] ddsData; 
491 throw tDDSError(tDDSError::tCode::LoaderSupportsPowerOfTwoDimsOnly, baseName); 
492 } 
493 
494 // It seems ATI tools like GenCubeMap don't set the correct bits. 
495 #ifdef STRICT_DDS_HEADER_CHECKING 
496 int pitch = 0; // Num bytes per line on main image (uncompressed images only). 
497 int linearSize = 0; // Num bytes total main image (compressed images only). 
498 
499 if (flags & tDDSFlag_Pitch) 
500 pitch = header.PitchLinearSize; 
501 
502 if (flags & tDDSFlag_LinearSize) 
503 linearSize = header.PitchLinearSize; 
504 
505 // Linear size xor pitch must be specified. 
506 if ((!linearSize && !pitch) || (linearSize && pitch)) 
507 { 
508 delete[] ddsData; 
509 throw tDDSError(tDDSError::tCode::PitchOrLinearSize, baseName); 
510 } 
511 #endif 
512 
513 // Volume textures are not supported. 
514 if (flags & tDDSFlag_Depth) 
515 { 
516 delete[] ddsData; 
517 throw tDDSError(tDDSError::tCode::VolumeTexturesNotSupported, baseName); 
518 } 
519 
520 // Determine the expected number of layers by looking at the mipmap count if it is supplied. We assume a single layer 
521 // if it's not specified. 
522 NumMipmapLayers = 1; 
523 bool hasMipmaps = (header.Capabilities.FlagsCapsBasic & tDDSCapsBasic_Mipmap) ? true : false; 
524 if ((flags & tDDSFlag_MipmapCount) && hasMipmaps) 
525 NumMipmapLayers = header.MipmapCount; 
526 
527 if (NumMipmapLayers > MaxMipmapLayers) 
528 { 
529 delete[] ddsData; 
530 throw tDDSError(tDDSError::tCode::MaxNumMipmapLevelsExceeded); 
531 } 
532 
533 // Determine if this is a cubemap dds with 6 images. No need to check which images are present since they are 
534 // required to be all there by the dds standard. All tools these days seem to write them all. If there are complaints 
535 // when using legacy files we can fix this. 
536 if (header.Capabilities.FlagsCapsExtra & tDDSCapsExtra_CubeMap) 
537 { 
538 IsCubeMap = true; 
539 NumImages = 6; 
540 } 
541 else 
542 { 
543 IsCubeMap = false; 
544 NumImages = 1; 
545 } 
546 
547 // Determine if we support the pixel format and which one it is. 
548 PixelFormat = tPixelFormat::Invalid; 
549 tDDSPixelFormat& format = header.PixelFormat; 
550 
551 if (format.Size != 32) 
552 { 
553 delete[] ddsData; 
554 throw tDDSError(tDDSError::tCode::IncorrectPixelFormatSize, baseName); 
555 } 
556 
557 // Has alpha should be true if the pixel format is uncompressed (RGB) and there is an alpha channel. 
558 bool rgbHasAlpha = (format.Flags & tDDSPixelFormatFlag_Alpha) ? true : false; 
559 bool rgbFormat = (format.Flags & tDDSPixelFormatFlag_RGB) ? true : false; 
560 bool fourCCFormat = (format.Flags & tDDSPixelFormatFlag_FourCC) ? true : false; 
561 
562 if ((!rgbFormat && !fourCCFormat) || (rgbFormat && fourCCFormat)) 
563 { 
564 delete[] ddsData; 
565 throw tDDSError(tDDSError::tCode::InconsistentPixelFormat, baseName); 
566 } 
567 
568 if (fourCCFormat) 
569 { 
570 switch (format.FourCC) 
571 { 
572 case FourCC('D','X','T','1'): 
573 // Note that during inspecition of the individual layer data, the DXT1 pixel format might be modified 
574 // to DXT1BA (binary alpha). 
575 PixelFormat = tPixelFormat::BC1_DXT1; 
576 break; 
577 
578 case FourCC('D','X','T','3'): 
579 PixelFormat = tPixelFormat::BC2_DXT3; 
580 break; 
581 
582 case FourCC('D','X','T','5'): 
583 PixelFormat = tPixelFormat::BC3_DXT5; 
584 break; 
585 
586 case tD3DFMT_R32F: 
587 PixelFormat = tPixelFormat::R32F; 
588 break; 
589 
590 case tD3DFMT_G32R32F: 
591 PixelFormat = tPixelFormat::G32R32F; 
592 break; 
593 
594 case tD3DFMT_A32B32G32R32F: 
595 PixelFormat = tPixelFormat::A32B32G32R32F; 
596 break; 
597 
598 case FourCC('D','X','1','0'): 
599 default: 
600 delete[] ddsData; 
601 throw tDDSError(tDDSError::tCode::UnsupportedFourCCPixelFormat, baseName); 
602 } 
603 } 
604 
605 // It must be an RGB format. 
606 else 
607 { 
608 // Remember this is a little endian machine, so the masks are lying. Eg. 0xFF0000 in memory is 00 00 FF, so the red 
609 // is last. 
610 switch (format.RGBBitCount) 
611 { 
612 case 16: 
613 // Supports G3B5A1R5G2, G4B4A4R4, and G3B5R5G3. 
614 if 
615 ( 
616 rgbHasAlpha && 
617 (format.MaskAlpha == 0x8000) && 
618 (format.MaskRed == 0x7C00) && 
619 (format.MaskGreen == 0x03E0) && 
620 (format.MaskBlue == 0x001F) 
621 ) 
622 { 
623 PixelFormat = tPixelFormat::G3B5A1R5G2; 
624 } 
625 
626 else if 
627 ( 
628 rgbHasAlpha && 
629 (format.MaskAlpha == 0xF000) && 
630 (format.MaskRed == 0x0F00) && 
631 (format.MaskGreen == 0x00F0) && 
632 (format.MaskBlue == 0x000F) 
633 ) 
634 { 
635 PixelFormat = tPixelFormat::G4B4A4R4; 
636 } 
637 
638 else if 
639 ( 
640 !rgbHasAlpha && 
641 (format.MaskRed == 0xF800) && 
642 (format.MaskGreen == 0x07E0) && 
643 (format.MaskBlue == 0x001F) 
644 ) 
645 { 
646 PixelFormat = tPixelFormat::G3B5R5G3; 
647 } 
648 
649 else 
650 { 
651 delete[] ddsData; 
652 throw tDDSError(tDDSError::tCode::UnsupportedRGBPixelFormat, baseName); 
653 } 
654 
655 break; 
656 
657 case 24: 
658 // Supports B8G8R8. 
659 if 
660 ( 
661 !rgbHasAlpha && 
662 (format.MaskRed == 0xFF0000) && 
663 (format.MaskGreen == 0x00FF00) && 
664 (format.MaskBlue == 0x0000FF) 
665 ) 
666 { 
667 PixelFormat = tPixelFormat::B8G8R8; 
668 } 
669 
670 else 
671 { 
672 delete[] ddsData; 
673 throw tDDSError(tDDSError::tCode::UnsupportedRGBPixelFormat, baseName); 
674 } 
675 
676 break; 
677 
678 case 32: 
679 // Supports B8G8R8A8. This is a little endian machine so the masks are lying. 0xFF000000 in memory is 
680 // 00 00 00 FF with alpha last. 
681 if 
682 ( 
683 rgbHasAlpha && 
684 (format.MaskAlpha == 0xFF000000) && 
685 (format.MaskRed == 0x00FF0000) && 
686 (format.MaskGreen == 0x0000FF00) && 
687 (format.MaskBlue == 0x000000FF) 
688 ) 
689 { 
690 PixelFormat = tPixelFormat::B8G8R8A8; 
691 } 
692 else 
693 { 
694 delete[] ddsData; 
695 throw tDDSError(tDDSError::tCode::UnsupportedRGBPixelFormat, baseName); 
696 } 
697 break; 
698 
699 default: 
700 delete[] ddsData; 
701 throw tDDSError(tDDSError::tCode::UnsupportedRGBPixelFormat, baseName); 
702 } 
703 } 
704 
705 // @todo We do not yet support these formats. 
706 if ((PixelFormat == tPixelFormat::R32F) || (PixelFormat == tPixelFormat::G32R32F) || (PixelFormat == tPixelFormat::A32B32G32R32F)) 
707 { 
708 delete[] ddsData; 
709 throw tDDSError(tDDSError::tCode::UnsupportedFourCCPixelFormat, baseName); 
710 } 
711 
712 tAssert(PixelFormat != tPixelFormat::Invalid); 
713 if (!rgbFormat && ((mainWidth%4) || (mainHeight%4))) 
714 { 
715 delete[] ddsData; 
716 throw tDDSError(tDDSError::tCode::UnsupportedDXTDimensions, baseName); 
717 } 
718 
719 for (int image = 0; image < NumImages; image++) 
720 { 
721 int width = mainWidth; 
722 int height = mainHeight; 
723 
724 for (int layer = 0; layer < NumMipmapLayers; layer++) 
725 { 
726 int numBytes; 
727 if (rgbFormat) 
728 { 
729 numBytes = width*height*format.RGBBitCount/8; 
730 
731 // Deal with the reverseRowOrder for these RGB formats as well. 
732 if (reverseRowOrder) 
733 { 
734 uint8* reversedPixelData = new uint8[numBytes]; 
735 uint8* dstData = reversedPixelData; 
736 
737 // We only support pixel formats that contain a whole number of bytes per pixel. That will cover 
738 // all reasonable formats. 
739 int bytesPerPixel = format.RGBBitCount/8; 
740 
741 for (int row = height-1; row >= 0; row--) 
742 { 
743 for (int col = 0; col < width; col++) 
744 { 
745 const uint8* srcData = pixelData + row*bytesPerPixel*width + col*bytesPerPixel; 
746 for (int byte = 0; byte < bytesPerPixel; byte++, dstData++, srcData++) 
747 *dstData = *srcData; 
748 } 
749 } 
750 
751 // We can simply get the layer to steal the memory (the last true arg). 
752 MipmapLayers[layer][image] = new tLayer(PixelFormat, width, height, reversedPixelData, true); 
753 } 
754 else 
755 { 
756 MipmapLayers[layer][image] = new tLayer(PixelFormat, width, height, (uint8*)pixelData); 
757 } 
758 tAssert(MipmapLayers[layer][image]->GetDataSize() == numBytes); 
759 } 
760 else 
761 { 
762 // Otherwise it's a FourCC DXTn format. Each block encodes a 4x4 square of pixels. DXT2,3,4,5 use 128 
763 // bits per block. DXT1 and DXT1BA use 64bits per block. 
764 int dxtBlockSize = 16; 
765 if ((PixelFormat == tPixelFormat::BC1_DXT1BA) || (PixelFormat == tPixelFormat::BC1_DXT1)) 
766 dxtBlockSize = 8; 
767 
768 int numBlocks = tMath::tMax(1, width/4) * tMath::tMax(1, height/4); 
769 numBytes = numBlocks * dxtBlockSize; 
770 
771 // Here's where we possibly modify the opaque DXT1 texture to be DXT1BA if there are blocks with binary 
772 // transparency. We only bother checking the main layer. If it's opaque we assume all the others are too. 
773 if ((layer == 0) && (PixelFormat == tPixelFormat::BC1_DXT1) && DoDXT1BlocksHaveBinaryAlpha((tDXT1Block*)pixelData, numBlocks)) 
774 PixelFormat = tPixelFormat::BC1_DXT1BA; 
775 
776 // DDS files store textures upside down. In the OpenGL RH coord system, the lower left of the texture 
777 // is the origin and consecutive rows go up. For this reason we need to read each row of blocks from 
778 // the top to the bottom row. We also need to flip the rows within the 4x4 block by flipping the lookup 
779 // tables. This should be fairly fast as there is no encoding or encoding going on. Width and height 
780 // will go down to 1x1, which will still use a 4x4 DXT pixel-block. 
781 if (reverseRowOrder) 
782 { 
783 int heightBlocks = height / 4; 
784 if (height % 4) 
785 heightBlocks++; 
786 
787 int widthBlocks = width / 4; 
788 if (width % 4) 
789 widthBlocks++; 
790 
791 uint8* reversedPixelData = new uint8[numBytes]; 
792 uint8* dstData = reversedPixelData; 
793 
794 for (int row = heightBlocks-1; row >= 0; row--) 
795 { 
796 for (int col = 0; col < widthBlocks; col++) 
797 { 
798 const uint8* srcData = pixelData + row*dxtBlockSize*widthBlocks + col*dxtBlockSize; 
799 for (int byte = 0; byte < dxtBlockSize; byte++, dstData++, srcData++) 
800 *dstData = *srcData; 
801 } 
802 } 
803 
804 // Now we flip the inter-block rows by messing with the block's lookup-table. We need to handle all 
805 // three types of blocks: 1) DXT1, DXT1BA 2) DXT2, DXT3 3) DXT4, DXT5 
806 int totalBlocks = widthBlocks * heightBlocks; 
807 
808 switch (PixelFormat) 
809 { 
810 case tPixelFormat::BC1_DXT1BA: 
811 case tPixelFormat::BC1_DXT1: 
812 { 
813 tDXT1Block* block = (tDXT1Block*)reversedPixelData; 
814 for (int b = 0; b < totalBlocks; b++, block++) 
815 { 
816 // Reorder each row's colour indexes. 
817 tStd::tSwap(block->LookupTableRows[0], block->LookupTableRows[3]); 
818 tStd::tSwap(block->LookupTableRows[1], block->LookupTableRows[2]); 
819 } 
820 break; 
821 } 
822 
823 case tPixelFormat::BC2_DXT3: 
824 { 
825 tDXT3Block* block = (tDXT3Block*)reversedPixelData; 
826 for (int b = 0; b < totalBlocks; b++, block++) 
827 { 
828 // Reorder the explicit alphas AND the colour indexes. 
829 tStd::tSwap(block->AlphaTableRows[0], block->AlphaTableRows[3]); 
830 tStd::tSwap(block->AlphaTableRows[1], block->AlphaTableRows[2]); 
831 tStd::tSwap(block->ColourBlock.LookupTableRows[0], block->ColourBlock.LookupTableRows[3]); 
832 tStd::tSwap(block->ColourBlock.LookupTableRows[1], block->ColourBlock.LookupTableRows[2]); 
833 } 
834 break; 
835 } 
836 
837 case tPixelFormat::BC3_DXT5: 
838 { 
839 tDXT5Block* block = (tDXT5Block*)reversedPixelData; 
840 for (int b = 0; b < totalBlocks; b++, block++) 
841 { 
842 // Reorder the alpha indexes AND the colour indexes. 
843 uint16 orig0 = block->GetAlphaRow(0); 
844 block->SetAlphaRow(0, block->GetAlphaRow(3)); 
845 block->SetAlphaRow(3, orig0); 
846 
847 uint16 orig1 = block->GetAlphaRow(1); 
848 block->SetAlphaRow(1, block->GetAlphaRow(2)); 
849 block->SetAlphaRow(2, orig1); 
850 
851 tStd::tSwap(block->ColourBlock.LookupTableRows[0], block->ColourBlock.LookupTableRows[3]); 
852 tStd::tSwap(block->ColourBlock.LookupTableRows[1], block->ColourBlock.LookupTableRows[2]); 
853 } 
854 break; 
855 } 
856 
857 case tPixelFormat::R32F: 
858 case tPixelFormat::G32R32F: 
859 case tPixelFormat::A32B32G32R32F: 
860 { 
861 delete[] ddsData; 
862 throw tDDSError(tDDSError::tCode::UnsuportedFloatingPointPixelFormat, baseName); 
863 } 
864 
865 default: 
866 { 
867 delete[] ddsData; 
868 throw tDDSError(tDDSError::tCode::UnsupportedFourCCPixelFormat, baseName); 
869 } 
870 } 
871 
872 // Finally we can append a layer with the massaged dxt data. We can simply get the layer to steal the memory (the 
873 // last true arg). 
874 MipmapLayers[layer][image] = new tLayer(PixelFormat, width, height, reversedPixelData, true); 
875 } 
876 else 
877 { 
878 // If reverseRowOrder is false we want the data to go straight in so we use the pixelData directly. 
879 MipmapLayers[layer][image] = new tLayer(PixelFormat, width, height, (uint8*)pixelData); 
880 } 
881 tAssert(MipmapLayers[layer][image]->GetDataSize() == numBytes); 
882 } 
883 
884 pixelData += numBytes; 
885 
886 // @todo Does this assume power-of-2 dimensions? Can we avoid this assumption in this low-level class? 
887 width /= 2; 
888 if (width < 1) 
889 width = 1; 
890 
891 height /= 2; 
892 if (height < 1) 
893 height = 1; 
894 } 
895 } 
896} 
897 
898 
899bool tImageDDS::DoDXT1BlocksHaveBinaryAlpha(tDXT1Block* block, int numBlocks) 
900{ 
901 // The only way to check if the DXT1 format has alpha is by checking each block individually. If the block uses 
902 // alpha, the min and max colours are ordered in a particular order. 
903 for (int b = 0; b < numBlocks; b++) 
904 { 
905 if (block->Colour0 <= block->Colour1) 
906 { 
907 // OK, well, that's annoying. It seems that at least the nVidia DXT compressor can generate an opaque DXT1 
908 // block with the colours in the order for a transparent one. This forces us to check all the indexes to 
909 // see if the alpha index (11 in binary) is used -- if not then it's still an opaque block. 
910 for (int row = 0; row < 4; row++) 
911 { 
912 uint8 bits = block->LookupTableRows[row]; 
913 if 
914 ( 
915 ((bits & 0x03) == 0x03) || 
916 ((bits & 0x0C) == 0x0C) || 
917 ((bits & 0x30) == 0x30) || 
918 ((bits & 0xC0) == 0xC0) 
919 ) 
920 return true; 
921 } 
922 } 
923 
924 block++; 
925 } 
926 
927 return false; 
928} 
929 
930 
931} 
932 
933 
934const char* tDDSError::CodeStrings[int(tCode::NumCodes)] = 
935{ 
936 "Unknown.", 
937 "File doesn't exist.", 
938 "Incorrect DDS extension.", 
939 "Filesize incorrect.", 
940 "Magic FourCC Incorrect.", 
941 "Incorrect DDS header size.", 
942 "One of Pitch or LinearSize must be specified.", 
943 "Volume textures unsupported.", 
944 "Pixel format size incorrect.", 
945 "Pixel format must be either an RGB format or a FourCC format.", 
946 "Unsupported FourCC pixel format. Supported FourCC formats include DXT1, DXT3, DXT5.", 
947 "Unsupported RGB pixel format. Supported formats include A1R5G5B5, A4R4G4B4, R5G6B5, R8G8B8, and A8R8G8B8.", 
948 "Incorrect DXT pixel data size.", 
949 "DXT Texture dimensions must be divisible by 4.", 
950 "Current DDS loader only supports power-of-2 dimensions.", 
951 "Maximum number of mipmap levels exceeded.", 
952 "Floating point pixel formats not supported yet." 
953}; 
954