Files
fs2open.github.com/code/ddsutils/ddsutils.cpp
T
taylor 7a8789e334 OpenGL envmap support
newer OpenGL extension support
add GL_ARB_texture_rectangle support for non-power-of-2 textures as interface graphics
add cubemap reading and writing support to DDS loader
fix bug in DDS loader that made compressed images with mipmaps use more memory than they really required
add support for a default envmap named "cubemap.dds"
new mission flag "$Environment Map:" to use a pre-existing envmap
minor cleanup of compiler warning messages
get rid of wasteful math from gr_set_proj_matrix()
remove extra gr_set_*_matrix() calls from starfield.cpp as there was no longer a reason for them to be there
clean up bmpman flags in reguards to cubemaps and render targets
disable D3D envmap code until it can be upgraded to current level of code
remove bumpmap code from OpenGL stuff (sorry but it was getting in the way, if it was more than copy-paste it would be worth keeping)
replace gluPerspective() call with glFrustum() call, it's a lot less math this way and saves the extra function call
2006-05-13 07:29:52 +00:00

502 lines
12 KiB
C++

#include "ddsutils/ddsutils.h"
#include "cfile/cfile.h"
#include "osapi/osregistry.h"
/* 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)
* paletted 8-bit (256 colors)
*/
int Texture_compression_available = 0;
int Use_compressed_textures = 0;
// 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))) );
}
int dds_read_header(char *filename, CFILE *img_cfp, int *width, int *height, int *bpp, int *compression_type, int *levels, int *size, ubyte *palette)
{
DDSURFACEDESC2 dds_header;
int code = 0;
CFILE *ddsfile;
char real_name[MAX_FILENAME_LEN];
int retval = DDS_ERROR_NONE;
int ct = DDS_UNCOMPRESSED;
int bits = 0;
int i, trash, is_cubemap = 0;
int d_width = 0, d_height = 0, d_depth = 0, d_size = 0;
if (img_cfp == NULL) {
// this better not happen.. ever
Assert(filename != NULL);
// make sure there is an extension
strcpy(real_name, filename);
char *p = strchr(real_name, '.');
if (p) { *p=0; }
strcat(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;
}
// read the code
code = cfread_int(ddsfile);
// check it
if (code != DDS_FILECODE) {
retval = DDS_ERROR_BAD_HEADER;
goto Done;
}
// 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
for (i = 0; i < 11; i++)
trash = cfread_uint(ddsfile);
dds_header.ddpfPixelFormat.dwSize = cfread_uint(ddsfile);
dds_header.ddpfPixelFormat.dwFlags = cfread_uint(ddsfile);
dds_header.ddpfPixelFormat.dwFourCC = cfread_uint(ddsfile);
dds_header.ddpfPixelFormat.dwRGBBitCount = cfread_uint(ddsfile);
dds_header.ddpfPixelFormat.dwRBitMask = cfread_uint(ddsfile);
dds_header.ddpfPixelFormat.dwGBitMask = cfread_uint(ddsfile);
dds_header.ddpfPixelFormat.dwBBitMask = cfread_uint(ddsfile);
dds_header.ddpfPixelFormat.dwRGBAlphaBitMask = cfread_uint(ddsfile);
dds_header.ddsCaps.dwCaps1 = cfread_uint(ddsfile);
dds_header.ddsCaps.dwCaps2 = cfread_uint(ddsfile);
// sanity
if (dds_header.dwDepth == 0)
dds_header.dwDepth = 1;
if (dds_header.dwMipMapCount < 1)
dds_header.dwMipMapCount = 1;
d_width = dds_header.dwWidth;
d_height = dds_header.dwHeight;
d_depth = dds_header.dwDepth;
// calculate the type and size of the data
if (dds_header.ddpfPixelFormat.dwFlags & DDPF_FOURCC) {
// did I mention lately that I hate Microsoft?
// this is here to fix the situation where Microsoft doesn't follow their own docs
if ( dds_header.dwPitchOrLinearSize <= 0 ) {
// calculate the block size, mult by 8 for DXT1, 16 for DXT3 & 5
d_size = ((dds_header.dwWidth + 3)/4) * ((dds_header.dwHeight + 3)/4) * ((dds_header.dwDepth + 3)/4) * ((dds_header.ddpfPixelFormat.dwFourCC == FOURCC_DXT1) ? 8 : 16);
} else {
d_size = dds_header.dwPitchOrLinearSize;
}
// if we have mipmaps then compute them into the final size too
for (i = 1; i < (int)dds_header.dwMipMapCount; i++) {
// reduce size by half for the next pass
d_width /= 2;
d_height /= 2;
if (d_width <= 0)
d_width = 1;
if (d_height <= 0)
d_height = 1;
// size of data block (4x4)
d_size += ((d_width + 3) / 4) * ((d_height + 3) / 4) * ((dds_header.ddpfPixelFormat.dwFourCC == FOURCC_DXT1) ? 8 : 16);
}
if ( dds_header.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP ) {
if ( !(dds_header.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_ALLFACES) ) {
retval = DDS_ERROR_CUBEMAP_FACES;
ct = DDS_DXT_INVALID;
goto Done;
}
// it's a valid cubemap, probably
is_cubemap = 1;
// the previously computed size is just per face, mult by 6 for full size
d_size *= 6;
}
Assert( d_size > 0 );
*size = d_size;
switch (dds_header.ddpfPixelFormat.dwFourCC) {
case FOURCC_DXT1:
bits = 24;
ct = (is_cubemap) ? DDS_CUBEMAP_DXT1 : DDS_DXT1;
break;
case FOURCC_DXT3:
bits = 32;
ct = (is_cubemap) ? DDS_CUBEMAP_DXT3 : DDS_DXT3;
break;
case FOURCC_DXT5:
bits = 32;
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;
// none of the above
default:
retval = DDS_ERROR_INVALID_FORMAT;
ct = DDS_DXT_INVALID;
goto Done;
break;
}
} else if ( dds_header.ddpfPixelFormat.dwFlags & (DDPF_RGB | DDPF_PALETTEINDEXED8) ) {
d_size += d_width * d_height * d_depth * (dds_header.ddpfPixelFormat.dwRGBBitCount / 8);
// calculate full data size for all mipmap levels
for (i = 1; i < (int)dds_header.dwMipMapCount; i++) {
d_width /= 2;
d_height /= 2;
d_depth /= 2;
if (d_width < 1)
d_width = 1;
if (d_height < 1)
d_height = 1;
if (d_depth < 1)
d_depth = 1;
d_size += d_width * d_height * d_depth * (dds_header.ddpfPixelFormat.dwRGBBitCount / 8);
}
Assert( d_size > 0 );
if ( dds_header.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP ) {
if ( !(dds_header.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_ALLFACES) ) {
retval = DDS_ERROR_CUBEMAP_FACES;
ct = DDS_DXT_INVALID;
goto Done;
}
// it's a valid cubemap, probably
is_cubemap = 1;
// the previously computed size is just per face, mult by 6 for full size
d_size *= 6;
}
*size = d_size;
bits = dds_header.ddpfPixelFormat.dwRGBBitCount;
ct = (is_cubemap) ? DDS_CUBEMAP_UNCOMPRESSED : DDS_UNCOMPRESSED;
} else {
// it's not a readable format
retval = DDS_ERROR_INVALID_FORMAT;
goto Done;
}
// 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;
}
// make sure that the video card can handle compressed textures before using them
if ( !Use_compressed_textures && (ct != DDS_UNCOMPRESSED) && (ct != DDS_DXT_INVALID) ) {
retval = DDS_ERROR_NO_COMPRESSION;
goto Done;
}
// stuff important info
if (bpp)
*bpp = bits;
if (compression_type)
*compression_type = ct;
if (width)
*width = dds_header.dwWidth;
if (height)
*height = dds_header.dwHeight;
if (levels)
*levels = dds_header.dwMipMapCount;
if (palette && (bits == 8)) {
cfseek(ddsfile, DDS_OFFSET, CF_SEEK_SET);
cfread(palette, 1, 1024, ddsfile);
}
Done:
if (img_cfp == NULL) {
// close file and return
cfclose(ddsfile);
ddsfile = NULL;
}
return retval;
}
//reads pixel info from a dds file
int dds_read_bitmap(char *filename, ubyte *data, ubyte *bpp)
{
int retval;
int w,h,ct,lvl;
int size = 0, bits = 0;
CFILE *cfp;
char real_name[MAX_FILENAME_LEN];
// this better not happen.. ever
Assert(filename != NULL);
// make sure there is an extension
strcpy(real_name, filename);
char *p = strchr(real_name, '.');
if (p) { *p = 0; }
strcat(real_name, ".dds");
// open it up and go to the data section
cfp = cfopen(real_name, "rb");
// just in case
if (cfp == NULL)
return DDS_ERROR_INVALID_FILENAME;
// read the header -- if its at this stage, it should be legal.
retval = dds_read_header(real_name, cfp, &w, &h, &bits, &ct, &lvl, &size);
Assert(retval == DDS_ERROR_NONE);
// this really shouldn't be needed but better safe than sorry
if (retval != DDS_ERROR_NONE) {
return retval;
}
cfseek(cfp, DDS_OFFSET, CF_SEEK_SET);
// read in the data
cfread(data, 1, size, cfp);
if (bpp)
*bpp = (ubyte)bits;
// we look done here
cfclose(cfp);
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, char *filename)
{
DDSURFACEDESC2 dds_header;
char real_filename[MAX_FILENAME_LEN];
if (data == NULL) {
Int3();
return;
}
// header size check
if (sizeof(DDSURFACEDESC2) != 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(real_filename, filename);
char *p = strchr(real_filename, '.');
if (p) { *p = 0; }
strcat(real_filename, ".dds");
}
CFILE *image = cfopen( real_filename, "wb", CFILE_NORMAL, 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(DDSURFACEDESC2) );
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.ddpfPixelFormat.dwSize = 32;
dds_header.ddpfPixelFormat.dwFlags = pixel_flags;
dds_header.ddpfPixelFormat.dwFourCC = 0;
dds_header.ddpfPixelFormat.dwRGBBitCount = bpp;
dds_header.ddpfPixelFormat.dwRBitMask = 0x00ff0000;
dds_header.ddpfPixelFormat.dwGBitMask = 0x0000ff00;
dds_header.ddpfPixelFormat.dwBBitMask = 0x000000ff;
dds_header.ddpfPixelFormat.dwRGBAlphaBitMask = (bpp == 32) ? 0xff000000 : 0x00000000;
dds_header.ddsCaps.dwCaps1 = caps1;
dds_header.ddsCaps.dwCaps2 = caps2;
// now save it all...
uint dds_id = DDS_FILECODE;
cfwrite(&dds_id, 1, 4, image);
cfwrite(&dds_header, 1, sizeof(DDSURFACEDESC2), 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?";
}
//get a warning otherwise
return "Abort, retry, fail?";
}