Files
fs2open.github.com/code/ddsutils/ddsutils.cpp
T
2026-07-05 18:45:28 +02:00

842 lines
23 KiB
C++

#include "ddsutils/ddsutils.h"
#include "cfile/cfile.h"
#include "osapi/osregistry.h"
#ifdef USE_OPENGL_ES
#include "graphics/opengl/es_compatibility.h"
#endif
#ifdef WITH_OPENGL
#include <glad/glad.h>
#else
static constexpr int GLAD_GL_EXT_texture_compression_s3tc = 0;
static constexpr int GLAD_GL_ARB_texture_compression_bptc = 0;
#endif
#define BCDEC_IMPLEMENTATION 1
PUSH_SUPPRESS_WARNINGS
#include "ddsutils/bcdec.h"
POP_SUPPRESS_WARNINGS
/* Currently supported formats:
* DXT1a (compressed)
* DXT1c (compressed)
* DXT3 (compressed)
* DXT5 (compressed)
* uncompressed 1555 (16-bit, 1-bit being alpha)
* uncompressed 8888 (32-bit)
* uncompressed 888 (24-bit, no alpha)
* uncompressed R8 (8-bit, Red channel as luminance)
*/
// power of two check, to verify that a given DDS will always be usable since
// some may not be power-of-2
static inline int is_power_of_two(int w, int h)
{
return ( (w && !(w & (w-1))) && (h && !(h & (h-1))) );
}
static bool valid_dx10_format(const DDS_HEADER_DXT10 &header)
{
switch (header.dxgiFormat) {
case DXGI_FORMAT::DXGI_FORMAT_BC7_TYPELESS:
case DXGI_FORMAT::DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT::DXGI_FORMAT_BC7_UNORM_SRGB:
return true;
default:
return false;
}
}
static bool conversion_needed(const DDS_HEADER &dds_header)
{
if ((dds_header.ddspf.dwFlags & DDPF_FOURCC) != DDPF_FOURCC) {
return false;
}
switch (dds_header.ddspf.dwFourCC) {
case FOURCC_DXT1:
case FOURCC_DXT3:
case FOURCC_DXT5:
return !GLAD_GL_EXT_texture_compression_s3tc;
case FOURCC_DX10:
// anything other than BC7 will end up invalid
return !GLAD_GL_ARB_texture_compression_bptc;
default:
break;
}
// anything not specifically listed should end up invalid
return false;
}
// Memory usage for uncompressed textures is quite high. Some MVP assets can
// require well over a GB of VRAM for a single ship after conversion. To help
// alleviate this we need to resize those textures where possible. At 1024x1024
// the memory requirement is reduced to about 36 MB per ship with a full set
// of textures.
//
// NOTE: modifies header!!
//
// returns: number of mipmap levels to skip
static uint conversion_resize(DDS_HEADER &dds_header)
{
const size_t MAX_SIZE = 1024;
uint width, height, depth, offset = 0;
if (dds_header.dwMipMapCount <= 1) {
return 0;
}
width = dds_header.dwWidth;
height = dds_header.dwHeight;
depth = dds_header.dwDepth;
// drop levels until we get to an appropriate size, but make sure we have
// at least 1 mipmap level remaining at the end (in case there's not a full chain)
while (((width > MAX_SIZE) || (height > MAX_SIZE)) && (offset < dds_header.dwMipMapCount-1)) {
// this shouldn't happen, but catch the obscure case (like 8192x4)
if ((width <= 4) || (height <= 4)) {
break;
}
width >>= 1;
height >>= 1;
depth >>= 1;
++offset;
}
// no change needed
if ( !offset ) {
return offset;
}
// update header with new values
dds_header.dwWidth = std::max(1U, width);
dds_header.dwHeight = std::max(1U, height);
dds_header.dwDepth = std::max(1U, depth);
return offset;
}
static int _dds_read_header(CFILE *ddsfile, DDS_HEADER &dds_header, DDS_HEADER_DXT10 *dds_header_dx10 = nullptr)
{
DDS_HEADER_DXT10 dx10_header;
// read the code
int code = cfread_int(ddsfile);
// check it
if (code != DDS_FILECODE) {
return DDS_ERROR_BAD_HEADER;
}
// read header variables
dds_header.dwSize = cfread_uint(ddsfile);
dds_header.dwFlags = cfread_uint(ddsfile);
dds_header.dwHeight = cfread_uint(ddsfile);
dds_header.dwWidth = cfread_uint(ddsfile);
dds_header.dwPitchOrLinearSize = cfread_uint(ddsfile);
dds_header.dwDepth = cfread_uint(ddsfile);
dds_header.dwMipMapCount = cfread_uint(ddsfile);
// skip over the crap we don't care about
cfseek(ddsfile, 11*4, CF_SEEK_CUR);
dds_header.ddspf.dwSize = cfread_uint(ddsfile);
dds_header.ddspf.dwFlags = cfread_uint(ddsfile);
dds_header.ddspf.dwFourCC = cfread_uint(ddsfile);
dds_header.ddspf.dwRGBBitCount = cfread_uint(ddsfile);
dds_header.ddspf.dwRBitMask = cfread_uint(ddsfile);
dds_header.ddspf.dwGBitMask = cfread_uint(ddsfile);
dds_header.ddspf.dwBBitMask = cfread_uint(ddsfile);
dds_header.ddspf.dwABitMask = cfread_uint(ddsfile);
dds_header.dwCaps = cfread_uint(ddsfile);
dds_header.dwCaps2 = cfread_uint(ddsfile);
// Unused but need them checked for later
dds_header.dwCaps3 = cfread_uint(ddsfile);
dds_header.dwCaps4 = cfread_uint(ddsfile);
dds_header.dwReserved2 = cfread_uint(ddsfile);
if (dds_header.ddspf.dwFourCC == FOURCC_DX10) {
dx10_header.dxgiFormat = static_cast<DXGI_FORMAT>(cfread_uint(ddsfile));
dx10_header.resourceDimension = static_cast<D3D10_RESOURCE_DIMENSION>(cfread_uint(ddsfile));
dx10_header.miscFlag = cfread_uint(ddsfile);
dx10_header.arraySize = cfread_uint(ddsfile);
dx10_header.miscFlags2 = cfread_uint(ddsfile);
if (dds_header_dx10) {
memcpy(dds_header_dx10, &dx10_header, sizeof(DDS_HEADER_DXT10));
}
}
// sanitize values
dds_header.dwDepth = std::max(1U, dds_header.dwDepth);
dds_header.dwMipMapCount = std::max(1U, dds_header.dwMipMapCount);
#ifndef NDEBUG
if (dds_header.dwDepth > 1) {
Assertion(dds_header.dwFlags & DDSD_DEPTH, "ERROR: Volume texture missing proper flags!!");
}
#endif
// set pitch manually (per MS docs)
dds_header.dwFlags &= ~DDSD_LINEARSIZE;
dds_header.dwFlags |= DDSD_PITCH;
if (dds_header.ddspf.dwFlags & DDPF_FOURCC) {
int block_size = (dds_header.ddspf.dwFourCC == FOURCC_DXT1) ? 8 : 16;
dds_header.dwPitchOrLinearSize = std::max(1U, (dds_header.dwWidth+3)/4) * block_size;
} else {
// this isn't technically correct, but it's only wrong for formats we don't support
dds_header.dwPitchOrLinearSize = (dds_header.dwWidth * dds_header.ddspf.dwRGBBitCount + 7) / 8;
}
return DDS_ERROR_NONE;
}
static size_t compute_dds_size(const DDS_HEADER &dds_header, bool converting = false)
{
const uint block_sz = 4;
uint d_width = 0, d_height = 0, d_depth = 0;
size_t d_size = 0;
for (uint i = 0; i < dds_header.dwMipMapCount; i++) {
d_width = std::max(1U, dds_header.dwWidth >> i);
d_height = std::max(1U, dds_header.dwHeight >> i);
d_depth = std::max(1U, dds_header.dwDepth >> i);
// When converting we need to pad a bit to compensate for the decompression
// size on smaller mipmap levels. We need to ensure there is always enough
// room to decode an entire 4x4 block in rgba space
if (converting) {
auto sz = std::min(d_width, d_height);
if (sz < block_sz) {
d_size += ((block_sz * block_sz) - (sz * sz)) * d_depth * 4;
}
}
if (dds_header.ddspf.dwFlags & DDPF_FOURCC) {
// size of data block (4x4)
d_size += ((d_width + 3) / 4) * ((d_height + 3) / 4) * d_depth * ((dds_header.ddspf.dwFourCC == FOURCC_DXT1) ? 8 : 16);
} else {
d_size += d_width * d_height * d_depth * (dds_header.ddspf.dwRGBBitCount / 8);
}
}
// assumes valid cubemap (should be checked elsewhere)
if (dds_header.dwCaps2 & DDSCAPS2_CUBEMAP) {
// the previously computed size is just per face, mult by 6 for full size
d_size *= 6;
}
Assertion(d_size > 0, "ERROR: DDS size computed to 0!!");
return d_size;
}
static int get_bit_count(const DDS_HEADER &dds_header)
{
if (dds_header.ddspf.dwFlags & DDPF_RGB) {
return dds_header.ddspf.dwRGBBitCount;
} else if (dds_header.ddspf.dwFlags & DDPF_FOURCC) {
if (dds_header.ddspf.dwFourCC == FOURCC_DXT1) {
return 24;
} else {
return 32;
}
}
return 0;
}
int dds_read_header(const char *filename, CFILE *img_cfp, int *width, int *height, int *bpp, int *compression_type, int *levels, size_t *size)
{
DDS_HEADER dds_header;
DDS_HEADER_DXT10 dds_header_dx10;
CFILE *ddsfile;
char real_name[MAX_FILENAME_LEN];
int retval = DDS_ERROR_NONE;
int ct = DDS_UNCOMPRESSED;
int is_cubemap = 0;
bool convert = false;
if (img_cfp == NULL) {
// this better not happen.. ever
Assert(filename != NULL);
// make sure there is an extension
strcpy_s(real_name, filename);
char *p = strchr(real_name, '.');
if (p) { *p=0; }
strcat_s(real_name, ".dds");
// try to open the file
ddsfile = cfopen(real_name, "rb");
// file not found
if (ddsfile == NULL)
return DDS_ERROR_INVALID_FILENAME;
} else {
ddsfile = img_cfp;
}
retval = _dds_read_header(ddsfile, dds_header, &dds_header_dx10);
if (retval != DDS_ERROR_NONE) {
goto Done;
}
// check for valid cubemap (we require all 6 faces)
if (dds_header.dwCaps2 & DDSCAPS2_CUBEMAP) {
if ( !(dds_header.dwCaps2 & DDSCAPS2_CUBEMAP_ALLFACES) ) {
retval = DDS_ERROR_CUBEMAP_FACES;
ct = DDS_DXT_INVALID;
goto Done;
}
// it's a valid cubemap, probably
is_cubemap = 1;
}
// determine and verify the type of data for internal use
if (dds_header.ddspf.dwFlags & DDPF_FOURCC) {
switch (dds_header.ddspf.dwFourCC) {
case FOURCC_DXT1:
ct = (is_cubemap) ? DDS_CUBEMAP_DXT1 : DDS_DXT1;
break;
case FOURCC_DXT3:
ct = (is_cubemap) ? DDS_CUBEMAP_DXT3 : DDS_DXT3;
break;
case FOURCC_DXT5:
ct = (is_cubemap) ? DDS_CUBEMAP_DXT5 : DDS_DXT5;
break;
// dxt2 and dxt4 aren't supported
case FOURCC_DXT2:
case FOURCC_DXT4:
retval = DDS_ERROR_UNSUPPORTED;
ct = DDS_DXT_INVALID;
goto Done;
break;
case FOURCC_DX10:
if (valid_dx10_format(dds_header_dx10)) {
// Currently only BC7 is supported
ct = DDS_BC7;
} else {
retval = DDS_ERROR_UNSUPPORTED;
ct = DDS_DXT_INVALID;
goto Done;
}
break;
// none of the above
default:
retval = DDS_ERROR_INVALID_FORMAT;
ct = DDS_DXT_INVALID;
goto Done;
break;
}
} else if (dds_header.ddspf.dwFlags & DDPF_RGB) {
ct = (is_cubemap) ? DDS_CUBEMAP_UNCOMPRESSED : DDS_UNCOMPRESSED;
} else {
// it's not a readable format
retval = DDS_ERROR_INVALID_FORMAT;
goto Done;
}
// maybe do conversion if format not supported
convert = conversion_needed(dds_header);
if (convert) {
// switch to uncompressed format and reset vars
dds_header.ddspf.dwFlags &= ~DDPF_FOURCC;
dds_header.ddspf.dwFlags |= DDPF_RGB;
dds_header.ddspf.dwRGBBitCount = 32;
// NOTE: modifies header and returns offset for mipmap count
dds_header.dwMipMapCount -= conversion_resize(dds_header);
dds_header.dwMipMapCount = std::max(1U, dds_header.dwMipMapCount);
ct = (is_cubemap) ? DDS_CUBEMAP_UNCOMPRESSED : DDS_UNCOMPRESSED;
}
// we don't support compressed, non-power-of-2 images
if ( (ct != DDS_UNCOMPRESSED) && (!is_power_of_two(dds_header.dwWidth, dds_header.dwHeight)) ) {
retval = DDS_ERROR_NON_POWER_OF_2;
goto Done;
}
// stuff important info
if (size)
*size = compute_dds_size(dds_header, convert);
if (bpp)
*bpp = get_bit_count(dds_header);
if (compression_type)
*compression_type = ct;
if (width)
*width = dds_header.dwWidth;
if (height)
*height = dds_header.dwHeight;
if (levels)
*levels = dds_header.dwMipMapCount;
Done:
if (img_cfp == NULL) {
// close file and return
cfclose(ddsfile);
ddsfile = NULL;
}
return retval;
}
static void (*decompress_dds)(const void *in, void *out, int pitch) = nullptr;
static uint32_t BLOCK_SIZE = 0;
//reads pixel info from a dds file
int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
{
int retval;
size_t size = 0;
CFILE *cfp;
char real_name[MAX_FILENAME_LEN];
DDS_HEADER dds_header;
// this better not happen.. ever
Assert(filename != NULL);
// make sure there is an extension
strcpy_s(real_name, filename);
char *p = strchr(real_name, '.');
if (p) { *p = 0; }
strcat_s(real_name, ".dds");
// open it up and go to the data section
cfp = cfopen(real_name, "rb", cf_type);
// just in case
if (cfp == nullptr)
return DDS_ERROR_INVALID_FILENAME;
// read the header -- if its at this stage, it should be legal.
retval = _dds_read_header(cfp, dds_header);
Assert(retval == DDS_ERROR_NONE);
// this really shouldn't be needed but better safe than sorry
if (retval != DDS_ERROR_NONE) {
cfclose(cfp);
return retval;
}
cfseek(cfp, (dds_header.ddspf.dwFourCC == FOURCC_DX10) ? DX10_OFFSET : DDS_OFFSET, CF_SEEK_SET);
size = compute_dds_size(dds_header); // don't add padding on this one!!
// read in the data
if ( !conversion_needed(dds_header) ) {
cfread(data, 1, (int)size, cfp);
} else {
// Compression format not supported, convert to BGRA
ubyte *comp_data = (ubyte*)vm_malloc(size);
cfread(comp_data, 1, (int)size, cfp);
ubyte *src = comp_data;
ubyte *dst = data;
uint d_width, d_height, d_depth;
size_t data_offset = 0;
// NOTE: this alters the width, height, and depth values in the header,
// so we have to jump through some hoops to get the proper values
const uint mipmap_offset = conversion_resize(dds_header);
const int num_faces = (dds_header.dwCaps2 & DDSCAPS2_CUBEMAP) ? 6 : 1;
const bool has_depth = (dds_header.dwFlags & DDSD_DEPTH) == DDSD_DEPTH;
switch (dds_header.ddspf.dwFourCC) {
case FOURCC_DX10:
decompress_dds = bcdec_bc7;
BLOCK_SIZE = BCDEC_BC7_BLOCK_SIZE;
break;
case FOURCC_DXT5:
decompress_dds = bcdec_bc3;
BLOCK_SIZE = BCDEC_BC3_BLOCK_SIZE;
break;
case FOURCC_DXT1:
decompress_dds = bcdec_bc1;
BLOCK_SIZE = BCDEC_BC1_BLOCK_SIZE;
break;
case FOURCC_DXT3:
decompress_dds = bcdec_bc2;
BLOCK_SIZE = BCDEC_BC2_BLOCK_SIZE;
break;
default:
Error(LOCATION, "Invalid FourCC (%d) for DDS decompression!", dds_header.ddspf.dwFourCC);
break;
}
for (int f = 0; f < num_faces; ++f) {
// if we resized then skip over all of that data (altering values to match pre-resize)
for (uint x = 0; x < mipmap_offset; ++x) {
d_width = std::max(1U, dds_header.dwWidth << (mipmap_offset - x));
d_height = std::max(1U, dds_header.dwHeight << (mipmap_offset - x));
d_depth = has_depth ? std::max(1U, dds_header.dwDepth << (mipmap_offset - x)) : 1U;
src += ((d_width + 3) / 4) * ((d_height + 3) / 4) * d_depth * BLOCK_SIZE;
}
for (uint m = mipmap_offset; m < dds_header.dwMipMapCount; ++m) {
// width/height/depth at post-resize mipmap sizes, so compensate here
d_width = std::max(1U, dds_header.dwWidth >> (m - mipmap_offset));
d_height = std::max(1U, dds_header.dwHeight >> (m - mipmap_offset));
d_depth = std::max(1U, dds_header.dwDepth >> (m - mipmap_offset));
for (uint d = 0; d < d_depth; ++d) {
auto depth_offset = d * d_width * d_height * 4;
for (uint i = 0; i < d_height; i += 4) {
for (uint j = 0; j < d_width; j += 4) {
dst = data + data_offset + depth_offset + ((i * d_width + j) * 4);
decompress_dds(src, dst, d_width * 4);
src += BLOCK_SIZE;
}
}
}
// we need it in bgra, XOR to the rescue
for (size_t x = data_offset; x < (data_offset + (d_width * d_height * d_depth * 4)); x += 4) {
data[x] ^= data[x+2], data[x+2] ^= data[x], data[x] ^= data[x+2];
}
// bump data offset to next layer
data_offset += (d_width * d_height * d_depth * 4);
}
}
vm_free(comp_data);
comp_data = nullptr;
// switch to uncompressed format and reset vars (needed below to get correct bit count)
dds_header.ddspf.dwFlags &= ~DDPF_FOURCC;
dds_header.ddspf.dwFlags |= DDPF_RGB;
dds_header.ddspf.dwRGBBitCount = 32;
}
if (bpp)
*bpp = (ubyte)get_bit_count(dds_header);
// we look done here
cfclose(cfp);
return DDS_ERROR_NONE;
}
int dds_decompress_top_mip_bgra(const char *filename, int cf_type,
int *out_width, int *out_height,
SCP_vector<ubyte> &out_pixels)
{
Assert(filename != nullptr);
// normalize to a .dds extension, same as dds_read_bitmap
char real_name[MAX_FILENAME_LEN];
strcpy_s(real_name, filename);
char *p = strchr(real_name, '.');
if (p) { *p = 0; }
strcat_s(real_name, ".dds");
CFILE *cfp = cfopen(real_name, "rb", cf_type);
if (cfp == nullptr)
return DDS_ERROR_INVALID_FILENAME;
DDS_HEADER dds_header;
DDS_HEADER_DXT10 dx10_header;
int retval = _dds_read_header(cfp, dds_header, &dx10_header);
if (retval != DDS_ERROR_NONE) {
cfclose(cfp);
return retval;
}
// only 2D FOURCC-compressed images are supported here
if (!(dds_header.ddspf.dwFlags & DDPF_FOURCC) ||
(dds_header.dwCaps2 & (DDSCAPS2_CUBEMAP | DDSCAPS2_VOLUME)) ||
(dds_header.dwFlags & DDSD_DEPTH) ||
(dds_header.dwDepth > 1)) {
cfclose(cfp);
return DDS_ERROR_UNSUPPORTED;
}
void (*decode)(const void *, void *, int) = nullptr;
int block_size = 0;
switch (dds_header.ddspf.dwFourCC) {
case FOURCC_DXT1: decode = bcdec_bc1; block_size = BCDEC_BC1_BLOCK_SIZE; break;
case FOURCC_DXT3: decode = bcdec_bc2; block_size = BCDEC_BC2_BLOCK_SIZE; break;
case FOURCC_DXT5: decode = bcdec_bc3; block_size = BCDEC_BC3_BLOCK_SIZE; break;
case FOURCC_DX10:
if (!valid_dx10_format(dx10_header) ||
dx10_header.resourceDimension != D3D10_RESOURCE_DIMENSION::D3D10_RESOURCE_DIMENSION_TEXTURE2D ||
dx10_header.arraySize > 1) {
cfclose(cfp);
return DDS_ERROR_UNSUPPORTED;
}
decode = bcdec_bc7;
block_size = BCDEC_BC7_BLOCK_SIZE;
break;
default:
cfclose(cfp);
return DDS_ERROR_UNSUPPORTED;
}
const int w = static_cast<int>(dds_header.dwWidth);
const int h = static_cast<int>(dds_header.dwHeight);
if (w <= 0 || h <= 0) {
cfclose(cfp);
return DDS_ERROR_INVALID_FORMAT;
}
// BCn data is stored as ceil(w/4) * ceil(h/4) 4x4 blocks; dimensions
// don't have to be multiples of 4.
const int blocks_w = (w + 3) / 4;
const int blocks_h = (h + 3) / 4;
const int padded_w = blocks_w * 4;
const int padded_h = blocks_h * 4;
const size_t compressed_size = static_cast<size_t>(blocks_w) * blocks_h * block_size;
// _dds_read_header leaves the file positioned right after the header
// (including the DX10 sub-header if present), so the next read is the
// top mip's pixel data.
SCP_vector<ubyte> compressed(compressed_size);
const int got = cfread(compressed.data(), 1, static_cast<int>(compressed_size), cfp);
cfclose(cfp);
if (got != static_cast<int>(compressed_size))
return DDS_ERROR_INVALID_FORMAT;
// Decode into a padded buffer so edge blocks have room, then crop to w*h.
SCP_vector<ubyte> decoded(static_cast<size_t>(padded_w) * padded_h * 4);
const int dec_stride = padded_w * 4;
const ubyte *src = compressed.data();
for (int by = 0; by < blocks_h; ++by) {
for (int bx = 0; bx < blocks_w; ++bx) {
ubyte *blk_dst = decoded.data() + (by * 4) * dec_stride + (bx * 4) * 4;
decode(src, blk_dst, dec_stride);
src += block_size;
}
}
out_pixels.assign(static_cast<size_t>(w) * h * 4, 0);
const int dst_stride = w * 4;
for (int y = 0; y < h; ++y) {
std::memcpy(out_pixels.data() + static_cast<size_t>(y) * dst_stride,
decoded.data() + static_cast<size_t>(y) * dec_stride,
static_cast<size_t>(dst_stride));
}
// bcdec outputs RGBA byte-order; swap to BGRA
for (size_t x = 0; x < out_pixels.size(); x += 4) {
std::swap(out_pixels[x], out_pixels[x + 2]);
}
if (out_width) *out_width = w;
if (out_height) *out_height = h;
return DDS_ERROR_NONE;
}
// save some image data as a DDS image
// NOTE: we only support, uncompressed, 24-bit RGB and 32-bit RGBA images here!!
void dds_save_image(int width, int height, int bpp, int num_mipmaps, ubyte *data, int cubemap, const char *filename)
{
DDS_HEADER dds_header;
char real_filename[MAX_FILENAME_LEN];
if (data == NULL) {
Int3();
return;
}
// header size check
if (sizeof(DDS_HEADER) != 124) {
Int3();
return;
}
// not that that's out of the way we can get down to business, constructing a sane filename
memset( &real_filename, 0, sizeof(real_filename) );
int count = os_config_read_uint(NULL, "ImageExportNum", 0);
if (count > 999)
count = 0;
if (filename == NULL) {
if (cubemap) {
sprintf(real_filename, "cubemap%.3d.dds", count++);
} else {
sprintf(real_filename, "image%.3d.dds", count++);
}
os_config_write_uint(NULL, "ImageExportNum", count);
} else {
Assert( strlen(filename) < MAX_FILENAME_LEN-5 );
strcpy_s(real_filename, filename);
char *p = strchr(real_filename, '.');
if (p) { *p = 0; }
strcat_s(real_filename, ".dds");
}
CFILE *image = cfopen( real_filename, "wb", CF_TYPE_CACHE );
if (image == NULL) {
mprintf(("Unable to open DDS image for saving!!\n"));
return;
}
if (num_mipmaps < 1)
num_mipmaps = 1;
// we have a filename and file handle, so now lets create our DDS header...
memset( &dds_header, 0, sizeof(DDS_HEADER) );
uint flags = (DDSD_CAPS | DDSD_PITCH | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT);
uint pixel_flags = DDPF_RGB;
uint caps1 = DDSCAPS_TEXTURE;
uint caps2 = 0;
if (bpp == 32) {
pixel_flags |= DDPF_ALPHAPIXELS;
}
if (num_mipmaps > 1) {
flags |= DDSD_MIPMAPCOUNT;
caps1 |= (DDSCAPS_COMPLEX | DDSCAPS_MIPMAP);
}
if (cubemap) {
caps1 |= DDSCAPS_COMPLEX;
caps2 |= (DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_ALLFACES);
}
dds_header.dwSize = 124;
dds_header.dwFlags = flags;
dds_header.dwHeight = height;
dds_header.dwWidth = width;
dds_header.dwPitchOrLinearSize = (width * (bpp >> 3));
dds_header.dwDepth = 0;
dds_header.dwMipMapCount = num_mipmaps;
dds_header.ddspf.dwSize = 32;
dds_header.ddspf.dwFlags = pixel_flags;
dds_header.ddspf.dwFourCC = 0;
dds_header.ddspf.dwRGBBitCount = bpp;
dds_header.ddspf.dwRBitMask = 0x00ff0000;
dds_header.ddspf.dwGBitMask = 0x0000ff00;
dds_header.ddspf.dwBBitMask = 0x000000ff;
dds_header.ddspf.dwABitMask = (bpp == 32) ? 0xff000000 : 0x00000000;
dds_header.dwCaps = caps1;
dds_header.dwCaps2 = caps2;
// now save it all...
uint dds_id = DDS_FILECODE;
cfwrite(&dds_id, 1, 4, image);
cfwrite(&dds_header, 1, sizeof(DDS_HEADER), image);
int faces = (cubemap) ? 6 : 1;
int f_width = width;
int f_height = height;
int f_size = 0, f_offset = 0;
// cubemaps are written:
// face 1
// all face 1 mipmaps
// face 2
// all face 2 mipmaps
// ... etc.
for (int i = 0; i < faces; i++) {
for (int j = 0; j < num_mipmaps; j++) {
f_size = (f_width * f_height * (bpp >> 3));
cfwrite(data + f_offset, 1, f_size, image);
// increase offset for next mipmap level
f_offset += f_size;
// half width and height for next mipmap level
f_width /= 2;
f_height /= 2;
if (f_width < 1)
f_width = 1;
if (f_height < 1)
f_height = 1;
}
// reset width and height to original values for the next face
f_width = width;
f_height = height;
}
// done! now lets go get something to eat...
cfclose(image);
}
// returns string representation of DDS_** error code
const char *dds_error_string(int code)
{
switch (code)
{
case DDS_ERROR_NONE:
return "No error";
case DDS_ERROR_INVALID_FILENAME:
return "File not found";
case DDS_ERROR_BAD_HEADER:
return "Filecode did not equal \"DDS \"";
case DDS_ERROR_INVALID_FORMAT:
return "DDS was in an unsupported/unknown format";
case DDS_ERROR_UNSUPPORTED:
return "DDS format was known but is not supported (ie. DXT2/DXT4)";
case DDS_ERROR_NO_COMPRESSION:
return "DDS is compressed but compression support is not enabled";
case DDS_ERROR_NON_POWER_OF_2:
return "Cannot load DDS if not power-of-2";
case DDS_ERROR_CUBEMAP_FACES:
return "Cubemaps must have all 6 faces";
default:
return "Abort, retry, fail?";
}
}