Support BPTC (BC7) textures in .DDS

Can have better image quality than S3TC (DXT1-5).

based on a patch by Github user "Manoa1911":
https://github.com/dhewm/dhewm3/issues/447#issuecomment-2254369525

Only supports DXGI_FORMAT_BC7_UNORM - I hope that's enough?
BC7/BPTC is supported by all GPUs that support DX11 (or newer)
or OpenGL 4.2 (or newer). That should be the case for Radeon HD 5000
and newer, Geforce 400 and newer and Intel iGPUs from Ivy Bridge on.
Those GPUs are from 2009/2010, 2012 for Intel.

fix #447
This commit is contained in:
Daniel Gibson 2025-03-05 05:56:46 +01:00
parent 2b20aadda5
commit 0f4b6e7245
6 changed files with 64 additions and 5 deletions

View file

@ -125,6 +125,23 @@ typedef struct
unsigned int dwReserved2[3];
} ddsFileHeader_t;
// DG: additional header that's right behind the ddsFileHeader_t
// ONLY IF ddsHeader.ddspf.dwFourCC == 'DX10'
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header-dxt10
typedef struct
{
// https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
unsigned int dxgiFormat; // we only support DXGI_FORMAT_BC7_UNORM = 98;
// we *could* probably support DXGI_FORMAT_BC1_UNORM = 71, DXGI_FORMAT_BC2_UNORM = 74, DXGI_FORMAT_BC3_UNORM = 77
// and map that to the old S3TC stuff, but I hope that tools writing those formats
// stick to just DX9-style ddsFileHeader_t to be more compatible?
unsigned int resourceDimension; // 0: unknown, 2: Texture1D, 3: Texture2D, 4: Texture3D
unsigned int miscFlag; // 4 if 2D texture is cubemap, else 0
unsigned int arraySize; // number of elements in texture array
unsigned int miscFlags2; // must be 0 for DX10, for DX11 has info about alpha channel (in lower 3 bits)
} ddsDXT10addHeader_t;
// increasing numeric values imply more information is stored
typedef enum {

View file

@ -1217,7 +1217,7 @@ void R_ListImages_f( const idCmdArgs &args ) {
if ( uncompressedOnly ) {
if ( ( image->internalFormat >= GL_COMPRESSED_RGB_S3TC_DXT1_EXT && image->internalFormat <= GL_COMPRESSED_RGBA_S3TC_DXT5_EXT )
|| image->internalFormat == GL_COLOR_INDEX8_EXT ) {
|| image->internalFormat == GL_COLOR_INDEX8_EXT || image->internalFormat == GL_COMPRESSED_RGBA_BPTC_UNORM_ARB ) {
continue;
}
}

View file

@ -38,7 +38,7 @@ PROBLEM: compressed textures may break the zero clamp rule!
static bool FormatIsDXT( int internalFormat ) {
if ( internalFormat < GL_COMPRESSED_RGB_S3TC_DXT1_EXT
|| internalFormat > GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ) {
|| internalFormat > GL_COMPRESSED_RGBA_BPTC_UNORM ) {
return false;
}
return true;
@ -86,6 +86,8 @@ int idImage::BitsForInternalFormat( int internalFormat ) const {
return 8;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
return 8;
case GL_COMPRESSED_RGBA_BPTC_UNORM:
return 8;
case GL_RGBA4:
return 16;
case GL_RGB5:
@ -1368,7 +1370,7 @@ bool idImage::CheckPrecompressedImage( bool fullLoad ) {
}
int len = f->Length();
if ( len < sizeof( ddsFileHeader_t ) ) {
if ( len < sizeof( ddsFileHeader_t ) + 4 ) { // +4 for the magic 'DDS ' fourcc at the beginning
fileSystem->CloseFile( f );
return false;
}
@ -1392,6 +1394,7 @@ bool idImage::CheckPrecompressedImage( bool fullLoad ) {
unsigned int magic = LittleInt( *(unsigned int *)data );
ddsFileHeader_t *_header = (ddsFileHeader_t *)(data + 4);
int ddspf_dwFlags = LittleInt( _header->ddspf.dwFlags );
unsigned int ddspf_dwFourCC = LittleInt( _header->ddspf.dwFourCC );
if ( magic != DDS_MAKEFOURCC('D', 'D', 'S', ' ')) {
common->Printf( "CheckPrecompressedImage( %s ): magic != 'DDS '\n", imgName.c_str() );
@ -1401,11 +1404,27 @@ bool idImage::CheckPrecompressedImage( bool fullLoad ) {
// if we don't support color index textures, we must load the full image
// should we just expand the 256 color image to 32 bit for upload?
if ( ddspf_dwFlags & DDSF_ID_INDEXCOLOR && !glConfig.sharedTexturePaletteAvailable ) {
if ( (ddspf_dwFlags & DDSF_ID_INDEXCOLOR) && !glConfig.sharedTexturePaletteAvailable ) {
R_StaticFree( data );
return false;
}
// DG: same if this is a BC7 (BPTC) texture but the GPU doesn't support that
// or if it uses the additional DX10 header and is *not* a BC7 texture
if ( ddspf_dwFourCC == DDS_MAKEFOURCC( 'D', 'X', '1', '0' ) ) {
ddsDXT10addHeader_t *dx10Header = (ddsDXT10addHeader_t *)( data + 4 + sizeof(ddsFileHeader_t) );
unsigned int dxgiFormat = LittleInt( dx10Header->dxgiFormat );
if ( dxgiFormat != 98 // DXGI_FORMAT_BC7_UNORM
|| !glConfig.bptcTextureCompressionAvailable ) {
if (dxgiFormat != 98) {
common->Warning( "Image file '%s' has unsupported dxgiFormat %d - dhewm3 only supports DXGI_FORMAT_BC7_UNORM (98)!",
filename, dxgiFormat);
}
R_StaticFree( data );
return false;
}
}
// upload all the levels
UploadPrecompressedImage( data, len );
@ -1455,6 +1474,7 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
uploadWidth = header->dwWidth;
uploadHeight = header->dwHeight;
size_t additionalHeaderOffset = 0; // used if the DDS has a DDS_HEADER_DXT10
if ( header->ddspf.dwFlags & DDSF_FOURCC ) {
switch ( header->ddspf.dwFourCC ) {
case DDS_MAKEFOURCC( 'D', 'X', 'T', '1' ):
@ -1473,6 +1493,12 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
case DDS_MAKEFOURCC( 'R', 'X', 'G', 'B' ):
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
case DDS_MAKEFOURCC( 'D', 'X', '1', '0' ): // BC7 aka BPTC
additionalHeaderOffset = 20;
// Note: this is a bit hacky, but in CheckPrecompressedImage() we made sure
// that only BC7 UNORM is accepted if the FourCC is 'DX10'
internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM;
break;
default:
common->Warning( "Invalid compressed internal format\n" );
return;
@ -1515,7 +1541,7 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
int skipMip = 0;
GetDownsize( uploadWidth, uploadHeight );
byte *imagedata = data + sizeof(ddsFileHeader_t) + 4;
byte *imagedata = data + sizeof(ddsFileHeader_t) + 4 + additionalHeaderOffset;
for ( int i = 0 ; i < numMipmaps; i++ ) {
int size = 0;
@ -2140,6 +2166,9 @@ void idImage::Print() const {
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
common->Printf( "DXT5 " );
break;
case GL_COMPRESSED_RGBA_BPTC_UNORM:
common->Printf( "BC7 " );
break;
case GL_RGBA4:
common->Printf( "RGBA4 " );
break;

View file

@ -63,6 +63,7 @@ typedef struct glconfig_s {
bool multitextureAvailable;
bool textureCompressionAvailable;
bool bptcTextureCompressionAvailable; // DG: for GL_ARB_texture_compression_bptc (BC7)
bool anisotropicAvailable;
bool textureLODBiasAvailable;
bool textureEnvAddAvailable;

View file

@ -431,8 +431,12 @@ static void R_CheckPortableExtensions( void ) {
glConfig.textureCompressionAvailable = true;
qglCompressedTexImage2DARB = (PFNGLCOMPRESSEDTEXIMAGE2DARBPROC)GLimp_ExtensionPointer( "glCompressedTexImage2DARB" );
qglGetCompressedTexImageARB = (PFNGLGETCOMPRESSEDTEXIMAGEARBPROC)GLimp_ExtensionPointer( "glGetCompressedTexImageARB" );
if ( R_CheckExtension( "GL_ARB_texture_compression_bptc" ) ) {
glConfig.bptcTextureCompressionAvailable = true;
}
} else {
glConfig.textureCompressionAvailable = false;
glConfig.bptcTextureCompressionAvailable = false;
}
// GL_EXT_texture_filter_anisotropic

View file

@ -110,6 +110,14 @@ extern PFNGLSTENCILOPSEPARATEPROC qglStencilOpSeparate;
extern PFNGLCOMPRESSEDTEXIMAGE2DARBPROC qglCompressedTexImage2DARB;
extern PFNGLGETCOMPRESSEDTEXIMAGEARBPROC qglGetCompressedTexImageARB;
// ARB_texture_compression_bptc - uses ARB_texture_compression, just adds new constants
// that might be missing in old OpenGL headers
#ifndef GL_COMPRESSED_RGBA_BPTC_UNORM_ARB
// currently the only one we use, there's also COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB (0x8E8D)
// and COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB (0x8E8E) and COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB (0x8E8F)
#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C
#endif
// ARB_vertex_program / ARB_fragment_program
extern PFNGLVERTEXATTRIBPOINTERARBPROC qglVertexAttribPointerARB;
extern PFNGLENABLEVERTEXATTRIBARRAYARBPROC qglEnableVertexAttribArrayARB;