mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 07:28:22 -10:00
538 lines
13 KiB
C++
538 lines
13 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))) );
|
|
}
|
|
|
|
// This function is used to verify that the file format specified in the DX10 Header
|
|
// is supported by the OpenGL extensions
|
|
bool dds_read_dx10_header(CFILE* dds_file, DDS_HEADER_DXT10* header)
|
|
{
|
|
header->dxgiFormat = static_cast<DXGI_FORMAT>(cfread_uint(dds_file));
|
|
header->resourceDimension = static_cast<D3D10_RESOURCE_DIMENSION>(cfread_uint(dds_file));
|
|
header->miscFlag = cfread_uint(dds_file);
|
|
header->arraySize = cfread_uint(dds_file);
|
|
header->miscFlags2 = cfread_uint(dds_file);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
int dds_read_header(const char *filename, CFILE *img_cfp, int *width, int *height, int *bpp, int *compression_type, int *levels, size_t *size, ubyte *palette)
|
|
{
|
|
DDS_HEADER dds_header;
|
|
DDS_HEADER_DXT10 dds_header_dx10;
|
|
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, is_cubemap = 0;
|
|
uint32_t d_width = 0, d_height = 0, d_depth = 0;
|
|
size_t d_size = 0;
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
// 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++)
|
|
cfread_uint(ddsfile);
|
|
|
|
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);
|
|
|
|
// 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.ddspf.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.dwFlags & (DDSD_LINEARSIZE | DDSD_PITCH) ) || 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.ddspf.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.ddspf.dwFourCC == FOURCC_DXT1) ? 8 : 16);
|
|
}
|
|
|
|
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;
|
|
|
|
// 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.ddspf.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;
|
|
|
|
case FOURCC_DX10:
|
|
if (dds_read_dx10_header(ddsfile, &dds_header_dx10)) {
|
|
// Currently only BC7 is supported
|
|
bits = 32;
|
|
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 | DDPF_PALETTEINDEXED8) ) {
|
|
d_size += d_width * d_height * d_depth * (dds_header.ddspf.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.ddspf.dwRGBBitCount / 8);
|
|
}
|
|
|
|
Assert( d_size > 0 );
|
|
|
|
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;
|
|
|
|
// the previously computed size is just per face, mult by 6 for full size
|
|
d_size *= 6;
|
|
}
|
|
|
|
*size = d_size;
|
|
|
|
bits = dds_header.ddspf.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, (ct == DDS_BC7) ? DX10_OFFSET : 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(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
|
|
{
|
|
int retval;
|
|
int w,h,ct,lvl;
|
|
size_t size = 0;
|
|
int 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_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", CFILE_NORMAL, cf_type);
|
|
|
|
// 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, (ct == DDS_BC7) ? DX10_OFFSET : DDS_OFFSET, CF_SEEK_SET);
|
|
|
|
// read in the data
|
|
cfread(data, 1, (int)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, 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", 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(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?";
|
|
}
|
|
}
|