mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-10 12:41:35 -10:00
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
502 lines
12 KiB
C++
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?";
|
|
}
|