/
mojodds.c
502 lines (429 loc) · 15.4 KB
1
2
3
4
5
6
7
8
9
10
11
/**
* MojoDDS; tools for dealing with DDS files.
*
* Please see the file LICENSE.txt in the source's root directory.
*/
// Specs on DDS format: http://msdn.microsoft.com/en-us/library/bb943991.aspx/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
12
#include <assert.h>
13
14
15
16
17
18
19
20
21
22
#ifdef _MSC_VER
typedef unsigned __int8 uint8;
typedef unsigned __int32 uint32;
#else
#include <stdint.h>
typedef uint8_t uint8;
typedef uint32_t uint32;
#endif
23
24
25
26
#ifndef UINT32_MAX
#define UINT32_MAX 0xFFFFFFFF
#endif
27
28
#include "mojodds.h"
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#define STATICARRAYLEN(x) ( (sizeof ((x))) / (sizeof ((x)[0])) )
#define DDS_MAGIC 0x20534444 // 'DDS ' in littleendian.
#define DDS_HEADERSIZE 124
#define DDS_PIXFMTSIZE 32
#define DDSD_CAPS 0x1
#define DDSD_HEIGHT 0x2
#define DDSD_WIDTH 0x4
#define DDSD_PITCH 0x8
#define DDSD_FMT 0x1000
#define DDSD_MIPMAPCOUNT 0x20000
#define DDSD_LINEARSIZE 0x80000
#define DDSD_DEPTH 0x800000
#define DDSD_REQ (DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_FMT)
43
#define DDSCAPS_ALPHA 0x2
44
45
46
#define DDSCAPS_COMPLEX 0x8
#define DDSCAPS_MIPMAP 0x400000
#define DDSCAPS_TEXTURE 0x1000
47
48
49
50
51
52
53
54
#define DDSCAPS2_CUBEMAP 0x200
#define DDSCAPS2_CUBEMAP_POSITIVEX 0x400
#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x800
#define DDSCAPS2_CUBEMAP_POSITIVEY 0x1000
#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x2000
#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x4000
#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x8000
#define DDSCAPS2_VOLUME 0x200000
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#define DDPF_ALPHAPIXELS 0x1
#define DDPF_ALPHA 0x2
#define DDPF_FOURCC 0x4
#define DDPF_RGB 0x40
#define DDPF_YUV 0x200
#define DDPF_LUMINANCE 0x20000
#define FOURCC_DXT1 0x31545844
#define FOURCC_DXT2 0x32545844
#define FOURCC_DXT3 0x33545844
#define FOURCC_DXT4 0x34545844
#define FOURCC_DXT5 0x35545844
#define FOURCC_DX10 0x30315844
69
70
71
72
73
#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
#define GL_BGR 0x80E0
#define GL_BGRA 0x80E1
74
#define GL_LUMINANCE_ALPHA 0x190A
75
76
77
#define MAX( a, b ) ((a) > (b) ? (a) : (b))
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
typedef struct
{
uint32 dwSize;
uint32 dwFlags;
uint32 dwFourCC;
uint32 dwRGBBitCount;
uint32 dwRBitMask;
uint32 dwGBitMask;
uint32 dwBBitMask;
uint32 dwABitMask;
} MOJODDS_PixelFormat;
typedef struct
{
uint32 dwSize;
uint32 dwFlags;
uint32 dwHeight;
uint32 dwWidth;
uint32 dwPitchOrLinearSize;
uint32 dwDepth;
uint32 dwMipMapCount;
uint32 dwReserved1[11];
MOJODDS_PixelFormat ddspf;
uint32 dwCaps;
uint32 dwCaps2;
uint32 dwCaps3;
uint32 dwCaps4;
uint32 dwReserved2;
} MOJODDS_Header;
109
// https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn
110
static const uint32 MultiplyDeBruijnBitPosition[32] =
111
{
112
113
0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
114
115
116
};
117
118
static uint32 uintLog2(uint32 v)
{
119
120
121
122
123
124
v |= v >> 1; // first round down to one less than a power of 2
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
125
return MultiplyDeBruijnBitPosition[(uint32)(v * 0x07C4ACDDU) >> 27];
126
127
128
}
129
130
131
static uint32 readui32(const uint8 **_ptr, size_t *_len)
{
uint32 retval = 0;
132
if (*_len < sizeof (retval)) {
133
*_len = 0;
134
} else {
135
136
137
138
139
const uint8 *ptr = *_ptr;
retval = (((uint32) ptr[0]) << 0) | (((uint32) ptr[1]) << 8) |
(((uint32) ptr[2]) << 16) | (((uint32) ptr[3]) << 24) ;
*_ptr += sizeof (retval);
*_len -= sizeof (retval);
140
}
141
return retval;
142
}
143
144
static int parse_dds(MOJODDS_Header *header, const uint8 **ptr, size_t *len,
145
146
147
unsigned int *_glfmt, unsigned int *_miplevels,
unsigned int *_cubemapfacelen,
MOJODDS_textureType *_textureType)
148
{
149
150
151
152
153
const uint32 pitchAndLinear = (DDSD_PITCH | DDSD_LINEARSIZE);
uint32 width = 0;
uint32 height = 0;
uint32 calcSize = 0;
uint32 calcSizeFlag = DDSD_LINEARSIZE;
154
155
uint32 blockDim = 1;
uint32 blockSize = 0;
156
157
int i;
158
if (readui32(ptr, len) != DDS_MAGIC) { // Files start with magic value...
159
return 0; // not a DDS file.
160
} else if (*len < DDS_HEADERSIZE) { // Then comes the DDS header...
161
return 0;
162
}
163
164
165
166
167
168
169
170
header->dwSize = readui32(ptr, len);
header->dwFlags = readui32(ptr, len);
header->dwHeight = readui32(ptr, len);
header->dwWidth = readui32(ptr, len);
header->dwPitchOrLinearSize = readui32(ptr, len);
header->dwDepth = readui32(ptr, len);
header->dwMipMapCount = readui32(ptr, len);
171
for (i = 0; i < STATICARRAYLEN(header->dwReserved1); i++) {
172
header->dwReserved1[i] = readui32(ptr, len);
173
}
174
175
176
177
178
179
180
181
182
183
184
185
186
187
header->ddspf.dwSize = readui32(ptr, len);
header->ddspf.dwFlags = readui32(ptr, len);
header->ddspf.dwFourCC = readui32(ptr, len);
header->ddspf.dwRGBBitCount = readui32(ptr, len);
header->ddspf.dwRBitMask = readui32(ptr, len);
header->ddspf.dwGBitMask = readui32(ptr, len);
header->ddspf.dwBBitMask = readui32(ptr, len);
header->ddspf.dwABitMask = readui32(ptr, len);
header->dwCaps = readui32(ptr, len);
header->dwCaps2 = readui32(ptr, len);
header->dwCaps3 = readui32(ptr, len);
header->dwCaps4 = readui32(ptr, len);
header->dwReserved2 = readui32(ptr, len);
188
189
190
width = header->dwWidth;
height = header->dwHeight;
191
if (width == 0 || height == 0) {
192
193
194
return 0;
}
195
// check for overflow in width * height
196
if (height > 0xFFFFFFFFU / width) {
197
198
199
return 0;
}
200
201
header->dwCaps &= ~DDSCAPS_ALPHA; // we'll get this from the pixel format.
202
if (header->dwSize != DDS_HEADERSIZE) { // header size must be 124.
203
return 0;
204
} else if (header->ddspf.dwSize != DDS_PIXFMTSIZE) { // size must be 32.
205
return 0;
206
} else if ((header->dwFlags & DDSD_REQ) != DDSD_REQ) { // must have these bits.
207
return 0;
208
} else if ((header->dwCaps & DDSCAPS_TEXTURE) == 0) {
209
return 0;
210
} else if ((header->dwFlags & pitchAndLinear) == pitchAndLinear) {
211
return 0; // can't specify both.
212
}
213
214
215
*_miplevels = (header->dwCaps & DDSCAPS_MIPMAP) ? header->dwMipMapCount : 1;
216
unsigned int calculatedMipLevels = uintLog2(MAX(width, height)) + 1;
217
if (*_miplevels == 0) { // invalid, calculate it ourselves from size
218
*_miplevels = calculatedMipLevels;
219
220
} else if (*_miplevels > calculatedMipLevels) { // too many mip levels, several would be 1x1
return 0; // file is corrupted
221
}
222
223
224
if (header->ddspf.dwFlags & DDPF_FOURCC) {
switch (header->ddspf.dwFourCC) {
225
case FOURCC_DXT1:
226
*_glfmt = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
227
228
calcSize = ((width ? ((width + 3) / 4) : 1) * 8) *
(height ? ((height + 3) / 4) : 1);
229
230
blockDim = 4;
blockSize = 8;
231
break;
232
case FOURCC_DXT3:
233
*_glfmt = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
234
235
calcSize = ((width ? ((width + 3) / 4) : 1) * 16) *
(height ? ((height + 3) / 4) : 1);
236
237
blockDim = 4;
blockSize = 16;
238
break;
239
case FOURCC_DXT5:
240
*_glfmt = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
241
242
calcSize = ((width ? ((width + 3) / 4) : 1) * 16) *
(height ? ((height + 3) / 4) : 1);
243
244
blockDim = 4;
blockSize = 16;
245
break;
246
247
// !!! FIXME: DX10 is an extended header, introduced by DirectX 10.
248
249
250
251
//case FOURCC_DX10: do_something(); break;
//case FOURCC_DXT2: // premultiplied alpha unsupported.
//case FOURCC_DXT4: // premultiplied alpha unsupported.
252
253
default:
return 0; // unsupported data format.
254
}
255
256
} else if (header->ddspf.dwFlags & DDPF_RGB) { // no FourCC...uncompressed data.
257
258
if ( (header->ddspf.dwRBitMask != 0x00FF0000) ||
(header->ddspf.dwGBitMask != 0x0000FF00) ||
259
(header->ddspf.dwBBitMask != 0x000000FF) ) {
260
return 0; // !!! FIXME: deal with this.
261
}
262
263
if (header->ddspf.dwFlags & DDPF_ALPHAPIXELS) {
264
if ( (header->ddspf.dwRGBBitCount != 32) ||
265
(header->ddspf.dwABitMask != 0xFF000000) ) {
266
return 0; // unsupported.
267
}
268
*_glfmt = GL_BGRA;
269
blockSize = 4;
270
271
} else {
if (header->ddspf.dwRGBBitCount != 24) {
272
return 0; // unsupported.
273
}
274
*_glfmt = GL_BGR;
275
blockSize = 3;
276
}
277
278
279
calcSizeFlag = DDSD_PITCH;
calcSize = ((width * header->ddspf.dwRGBBitCount) + 7) / 8;
280
281
282
} else if (header->ddspf.dwFlags & (DDPF_LUMINANCE | DDPF_ALPHA) ) {
*_glfmt = GL_LUMINANCE_ALPHA;
283
calcSizeFlag = DDSD_PITCH;
284
blockSize = 2;
285
286
calcSize = ((width * header->ddspf.dwRGBBitCount) + 7) / 8;
}
287
288
289
290
//else if (header->ddspf.dwFlags & DDPF_LUMINANCE) // !!! FIXME
//else if (header->ddspf.dwFlags & DDPF_YUV) // !!! FIXME
//else if (header->ddspf.dwFlags & DDPF_ALPHA) // !!! FIXME
291
292
else {
293
return 0; // unsupported data format.
294
}
295
296
// no pitch or linear size? Calculate it.
297
298
if ((header->dwFlags & pitchAndLinear) == 0) {
if (!calcSizeFlag) {
299
300
assert(0 && "should have caught this up above");
return 0; // uh oh.
301
}
302
303
304
header->dwPitchOrLinearSize = calcSize;
header->dwFlags |= calcSizeFlag;
305
}
306
307
*_textureType = MOJODDS_TEXTURE_2D;
308
309
310
311
312
313
314
315
316
317
318
319
// figure out texture type.
if ( (header->dwCaps & DDSCAPS_COMPLEX) &&
(header->dwCaps2 & DDSCAPS2_CUBEMAP) &&
(header->dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEX) &&
(header->dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEX) &&
(header->dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEY) &&
(header->dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEY) &&
(header->dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEZ) &&
(header->dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ) ) {
*_textureType = MOJODDS_TEXTURE_CUBE;
} else if (header->dwCaps2 & DDSCAPS2_VOLUME) {
*_textureType = MOJODDS_TEXTURE_VOLUME;
320
321
322
}
// figure out how much memory makes up a single face mip chain.
323
if (*_textureType == MOJODDS_TEXTURE_CUBE) {
324
325
uint32 wd = header->dwWidth;
uint32 ht = header->dwHeight;
326
327
if (wd != ht) {
return 0; // cube maps must be square
328
}
329
330
*_cubemapfacelen = 0;
331
332
333
for (i = 0; i < (int)*_miplevels; i++) {
const uint32 mipLen = MAX((wd + blockDim - 1) / blockDim, 1) * MAX((ht + blockDim - 1) / blockDim, 1) * blockSize;
334
335
336
337
338
if (UINT32_MAX - mipLen < *_cubemapfacelen) {
// data size would overflow 32-bit uint, invalid file
return 0;
}
*_cubemapfacelen += mipLen;
339
340
341
wd >>= 1;
ht >>= 1;
}
342
343
if (*len < (*_cubemapfacelen) * 6) { // 6 because cube faces
344
345
return 0;
}
346
347
} else if (*_textureType == MOJODDS_TEXTURE_2D) {
348
349
350
351
// check that file contains enough data like the header says
// TODO: also do this for other texture types
uint32 wd = header->dwWidth;
uint32 ht = header->dwHeight;
352
uint32 dataLen = 0;
353
354
for (i = 0; i < (int)*_miplevels; i++) {
const uint32 mipLen = MAX((wd + blockDim - 1) / blockDim, 1) * MAX((ht + blockDim - 1) / blockDim, 1) * blockSize;
355
356
357
358
359
if (UINT32_MAX - mipLen < dataLen) {
// data size would overflow 32-bit uint, invalid file
return 0;
}
dataLen += mipLen;
360
361
362
363
364
365
366
367
wd >>= 1;
ht >>= 1;
}
if (*len < dataLen) {
return 0;
}
}
368
369
if (header->dwPitchOrLinearSize > *len) {
370
return 0; // dwPitchOrLinearSize is incorrect
371
372
}
373
374
if (calcSize > *len) { // there's not enough data to contain the advertised images
return 0; // trying to read mips would fail
375
376
}
377
return 1;
378
}
379
380
381
// !!! FIXME: improve the crap out of this API later.
382
383
384
385
386
int MOJODDS_isDDS(const void *_ptr, const unsigned long _len)
{
size_t len = (size_t) _len;
const uint8 *ptr = (const uint8 *) _ptr;
return (readui32(&ptr, &len) == DDS_MAGIC);
387
}
388
389
int MOJODDS_getTexture(const void *_ptr, const unsigned long _len,
390
391
const void **_tex, unsigned long *_texlen,
unsigned int *_glfmt, unsigned int *_w,
392
393
394
unsigned int *_h, unsigned int *_miplevels,
unsigned int *_cubemapfacelen,
MOJODDS_textureType *_textureType)
395
396
397
398
{
size_t len = (size_t) _len;
const uint8 *ptr = (const uint8 *) _ptr;
MOJODDS_Header header;
399
if (!parse_dds(&header, &ptr, &len, _glfmt, _miplevels, _cubemapfacelen, _textureType)) {
400
return 0;
401
}
402
403
*_tex = (const void *) ptr;
404
405
*_w = (unsigned int) header.dwWidth;
*_h = (unsigned int) header.dwHeight;
406
*_texlen = (unsigned long) header.dwPitchOrLinearSize;
407
408
if (header.dwFlags & DDSD_PITCH) {
409
*_texlen *= header.dwHeight;
410
}
411
412
return 1;
413
}
414
415
int MOJODDS_getMipMapTexture(unsigned int miplevel, unsigned int glfmt,
416
const void *_basetex,
417
418
419
420
unsigned int w, unsigned h,
const void **_tex, unsigned long *_texlen,
unsigned int *_texw, unsigned int *_texh)
{
421
unsigned int i;
422
const char* newtex;
423
424
425
unsigned long newtexlen;
unsigned int neww;
unsigned int newh;
426
427
uint32 blockDim = 1;
uint32 blockSize = 0;
428
429
switch (glfmt) {
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
blockDim = 4;
blockSize = 8;
break;
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
blockDim = 4;
blockSize = 16;
break;
case GL_BGR:
blockSize = 3;
break;
case GL_BGRA:
blockSize = 4;
break;
case GL_LUMINANCE_ALPHA:
blockSize = 2;
break;
452
453
454
455
default:
//assert(!"unsupported GL format");
return 0;
456
}
457
458
459
assert(blockSize != 0);
460
461
462
newtex = _basetex;
neww = w;
newh = h;
463
newtexlen = ((neww + blockDim - 1) / blockDim) * ((newh + blockDim - 1) / blockDim) * blockSize;
464
465
// Calculate size of miplevel
466
for (i = 0; i < miplevel; i++) {
467
468
469
470
471
472
473
// move position to next texture start
newtex += newtexlen;
// calculate texture size
neww >>= 1;
newh >>= 1;
if (neww < 1) neww = 1;
if (newh < 1) newh = 1;
474
newtexlen = ((neww + blockDim - 1) / blockDim) * ((newh + blockDim - 1) / blockDim) * blockSize;
475
}
476
477
478
479
480
481
482
*_tex = newtex;
if (_texlen) {
*_texlen = newtexlen;
}
*_texw = neww;
*_texh = newh;
483
484
return 1;
485
}
486
487
488
489
int MOJODDS_getCubeFace(MOJODDS_cubeFace cubeFace, unsigned int miplevel,
unsigned int glfmt, const void *_basetex,
490
unsigned long _cubemapfacelen, unsigned int w, unsigned h,
491
492
493
494
const void **_tex, unsigned long *_texlen,
unsigned int *_texw, unsigned int *_texh)
{
// pick correct face
495
const char *faceBaseTex = ((const char *) _basetex) + cubeFace * _cubemapfacelen;
496
497
// call MOJODDS_getMipMapTexture to get offset in that face
498
return MOJODDS_getMipMapTexture(miplevel, glfmt, faceBaseTex, w, h, _tex, _texlen, _texw, _texh);
499
500
}
501
// end of mojodds.c ...