Skip to content

Commit b0e3f59

Browse files
authored
Merge pull request #661 from DanielGibson/bc7
Support BPTC (BC7) -compressed .dds textures.
2 parents 42cfb15 + 19b2070 commit b0e3f59

File tree

9 files changed

+157
-9
lines changed

9 files changed

+157
-9
lines changed

Changelog.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,20 @@ Note: Numbers starting with a "#" like #330 refer to the bugreport with that num
88
------------------------------------------------------------------------
99

1010
* Enable/disable Soft Particles when **loading** a graphics quality preset (only enabled in Ultra preset,
11-
though you can still configure it independently as before; #604)
11+
though you can still configure it independently like before; #604)
12+
* Support BC7-compressed (BPTC) .dds textures. They offer better quality than the older S3TC/DXT/BC1-3
13+
texture compression standard that Doom3 always supported. Mostly relevant for high-res retexturing
14+
packs, because they offer similar quality as uncompressed TGAs while being smaller, using only
15+
a quarter of the VRAM (TGA: 4 bytes per pixel, BC7: 1 byte per pixel) and loading *significantly*
16+
faster because mipmaps are contained and don't have to be generated on load.
17+
If you have such DDS files and want to use them (instead of TGAs), you must set
18+
`image_usePrecompressedTextures 1` and `image_useNormalCompression 1`.
19+
If you want to *create* .dds files with BC7 texture data, you can use any common texture compression
20+
tool, **except** for **normalmaps**, those must be created with my [**customized bc7enc**](https://github.com/DanielGibson/bc7enc_rdo)
21+
with the `-r2a` flag! *(Because Doom3 requires that normalmaps have the red channel moved into the
22+
alpha channel, id confusingly called that "RXGB", and AFAIK no other tool supports that for BC7.)*
23+
Just like the old DXT .dds files, they must be in the `dds/` subdirectory of a mod (either directly
24+
in the filesystem or in a .pk4).
1225
* Support SDL3 (SDL2 and, to some degree, SDL1.2 are also still supported)
1326
* Fix bugs on 64bit Big Endian platforms (#472, #625)
1427
* Fixes for high-poly models (use heap allocation instead of `alloca()` for big buffers; #528)

neo/framework/Common.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1463,7 +1463,7 @@ void Com_ExecMachineSpec_f( const idCmdArgs &args ) {
14631463
cvarSystem->SetCVarInteger( "s_maxSoundsPerShader", 0, CVAR_ARCHIVE );
14641464
cvarSystem->SetCVarInteger( "image_useNormalCompression", 0, CVAR_ARCHIVE );
14651465
if ( !nores ) // DG: added optional "nores" argument
1466-
cvarSystem->SetCVarInteger( "", 4, CVAR_ARCHIVE );
1466+
cvarSystem->SetCVarInteger( "r_mode", 4, CVAR_ARCHIVE );
14671467
cvarSystem->SetCVarInteger( "r_multiSamples", 0, CVAR_ARCHIVE );
14681468
} else if ( com_machineSpec.GetInteger() == 1 ) { // medium
14691469
cvarSystem->SetCVarString( "image_filter", "GL_LINEAR_MIPMAP_LINEAR", CVAR_ARCHIVE );

neo/framework/Dhewm3SettingsMenu.cpp

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1706,6 +1706,8 @@ static int initialMode = 0;
17061706
static int initialCustomVidRes[2];
17071707
static int initialMSAAmode = 0;
17081708
static int qualityPreset = 0;
1709+
static bool initialUsePrecomprTextures = false;
1710+
static int initialUseNormalCompr = false;
17091711

17101712
static void SetVideoStuffFromCVars()
17111713
{
@@ -1734,6 +1736,9 @@ static void SetVideoStuffFromCVars()
17341736
if ( qualityPreset == -1 )
17351737
qualityPreset = 1; // default to medium Quality
17361738
}
1739+
1740+
initialUsePrecomprTextures = globalImages->image_usePrecompressedTextures.GetBool();
1741+
initialUseNormalCompr = globalImages->image_useNormalCompression.GetInteger();
17371742
}
17381743

17391744
static bool VideoHasResettableChanges()
@@ -1754,6 +1759,12 @@ static bool VideoHasResettableChanges()
17541759
if ( initialMSAAmode != r_multiSamples.GetInteger() ) {
17551760
return true;
17561761
}
1762+
if ( initialUsePrecomprTextures != globalImages->image_usePrecompressedTextures.GetBool() ) {
1763+
return true;
1764+
}
1765+
if ( initialUseNormalCompr != globalImages->image_useNormalCompression.GetInteger() ) {
1766+
return true;
1767+
}
17571768

17581769
return false;
17591770
}
@@ -1775,13 +1786,29 @@ static bool VideoHasApplyableChanges()
17751786
return true;
17761787
}
17771788

1789+
if ( initialUsePrecomprTextures != globalImages->image_usePrecompressedTextures.GetBool() ) {
1790+
return true;
1791+
}
1792+
// Note: value of image_useNormalCompression is only relevant if image_usePrecompressedTextures is enabled
1793+
if ( initialUsePrecomprTextures
1794+
&& initialUseNormalCompr != globalImages->image_useNormalCompression.GetInteger() ) {
1795+
return true;
1796+
}
1797+
17781798
return false;
17791799
}
17801800

17811801

17821802
static void ApplyVideoSettings()
17831803
{
1784-
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart partial\n" );
1804+
const char* cmd = "vid_restart partial\n";
1805+
if ( initialUsePrecomprTextures != globalImages->image_usePrecompressedTextures.GetBool()
1806+
|| initialUseNormalCompr != globalImages->image_useNormalCompression.GetInteger() )
1807+
{
1808+
// these need a full restart (=> textures must be reloaded)
1809+
cmd = "vid_restart\n";
1810+
}
1811+
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, cmd );
17851812
}
17861813

17871814
static void VideoResetChanges()
@@ -1794,6 +1821,8 @@ static void VideoResetChanges()
17941821
r_fullscreenDesktop.SetBool( initialFullscreenDesktop );
17951822

17961823
r_multiSamples.SetInteger( initialMSAAmode );
1824+
globalImages->image_usePrecompressedTextures.SetBool( initialUsePrecomprTextures );
1825+
globalImages->image_useNormalCompression.SetInteger( initialUseNormalCompr );
17971826
}
17981827

17991828
static void InitVideoOptionsMenu()
@@ -1937,6 +1966,36 @@ static void DrawVideoOptionsMenu()
19371966
}
19381967
AddCVarOptionTooltips( r_multiSamples, "Note: Not all GPUs/drivers support all modes, esp. not 16x!" );
19391968

1969+
bool usePreComprTex = globalImages->image_usePrecompressedTextures.GetBool();
1970+
if ( ImGui::Checkbox( "Use precompressed textures", &usePreComprTex ) ) {
1971+
globalImages->image_usePrecompressedTextures.SetBool(usePreComprTex);
1972+
// by default I guess people also want compressed normal maps when using this
1973+
// especially relevant for retexturing packs that only ship BC7 DDS files
1974+
// (otherwise the lowres TGA normalmaps would be used)
1975+
if ( usePreComprTex ) {
1976+
cvarSystem->SetCVarInteger( "image_useNormalCompression", 2 );
1977+
}
1978+
}
1979+
const char* descr = "Use precompressed (.dds) textures. Faster loading, use less VRAM, possibly worse image quality.\n"
1980+
"May also be used by highres retexturing packs for BC7-compressed textures (there image quality is not impaired)";
1981+
AddCVarOptionTooltips( globalImages->image_usePrecompressedTextures, descr );
1982+
1983+
ImGui::BeginDisabled( !usePreComprTex );
1984+
bool useNormalCompr = globalImages->image_useNormalCompression.GetBool();
1985+
ImGui::Dummy( ImVec2(16, 0) );
1986+
ImGui::SameLine();
1987+
if ( ImGui::Checkbox( "Use precompressed normalmaps", &useNormalCompr ) ) {
1988+
// image_useNormalCompression 1 is not supported by modern GPUs
1989+
globalImages->image_useNormalCompression.SetInteger(useNormalCompr ? 2 : 0);
1990+
}
1991+
if ( usePreComprTex ) {
1992+
const char* descr = "Also use precompressed textures for normalmaps";
1993+
AddCVarOptionTooltips( globalImages->image_useNormalCompression, descr );
1994+
} else {
1995+
AddTooltip( "Can only be used if precompressed textures are enabled!" );
1996+
}
1997+
ImGui::EndDisabled();
1998+
19401999
// Apply Button
19412000
if ( !VideoHasApplyableChanges() ) {
19422001
ImGui::BeginDisabled();

neo/renderer/Image.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,23 @@ typedef struct
125125
unsigned int dwReserved2[3];
126126
} ddsFileHeader_t;
127127

128+
// DG: additional header that's right behind the ddsFileHeader_t
129+
// ONLY IF ddsHeader.ddspf.dwFourCC == 'DX10'
130+
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header-dxt10
131+
typedef struct
132+
{
133+
// https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
134+
unsigned int dxgiFormat; // we only support DXGI_FORMAT_BC7_UNORM = 98;
135+
// we *could* probably support DXGI_FORMAT_BC1_UNORM = 71, DXGI_FORMAT_BC2_UNORM = 74, DXGI_FORMAT_BC3_UNORM = 77
136+
// and map that to the old S3TC stuff, but I hope that tools writing those formats
137+
// stick to just DX9-style ddsFileHeader_t to be more compatible?
138+
139+
unsigned int resourceDimension; // 0: unknown, 2: Texture1D, 3: Texture2D, 4: Texture3D
140+
unsigned int miscFlag; // 4 if 2D texture is cubemap, else 0
141+
unsigned int arraySize; // number of elements in texture array
142+
unsigned int miscFlags2; // must be 0 for DX10, for DX11 has info about alpha channel (in lower 3 bits)
143+
} ddsDXT10addHeader_t;
144+
128145

129146
// increasing numeric values imply more information is stored
130147
typedef enum {

neo/renderer/Image_init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1217,7 +1217,7 @@ void R_ListImages_f( const idCmdArgs &args ) {
12171217

12181218
if ( uncompressedOnly ) {
12191219
if ( ( image->internalFormat >= GL_COMPRESSED_RGB_S3TC_DXT1_EXT && image->internalFormat <= GL_COMPRESSED_RGBA_S3TC_DXT5_EXT )
1220-
|| image->internalFormat == GL_COLOR_INDEX8_EXT ) {
1220+
|| image->internalFormat == GL_COLOR_INDEX8_EXT || image->internalFormat == GL_COMPRESSED_RGBA_BPTC_UNORM_ARB ) {
12211221
continue;
12221222
}
12231223
}

neo/renderer/Image_load.cpp

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ PROBLEM: compressed textures may break the zero clamp rule!
3737
*/
3838

3939
static bool FormatIsDXT( int internalFormat ) {
40-
if ( internalFormat < GL_COMPRESSED_RGB_S3TC_DXT1_EXT
41-
|| internalFormat > GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ) {
40+
if ( (internalFormat < GL_COMPRESSED_RGB_S3TC_DXT1_EXT
41+
|| internalFormat > GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
42+
&& internalFormat != GL_COMPRESSED_RGBA_BPTC_UNORM ) {
4243
return false;
4344
}
4445
return true;
@@ -86,6 +87,8 @@ int idImage::BitsForInternalFormat( int internalFormat ) const {
8687
return 8;
8788
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
8889
return 8;
90+
case GL_COMPRESSED_RGBA_BPTC_UNORM:
91+
return 8;
8992
case GL_RGBA4:
9093
return 16;
9194
case GL_RGB5:
@@ -1368,7 +1371,7 @@ bool idImage::CheckPrecompressedImage( bool fullLoad ) {
13681371
}
13691372

13701373
int len = f->Length();
1371-
if ( len < sizeof( ddsFileHeader_t ) ) {
1374+
if ( len < sizeof( ddsFileHeader_t ) + 4 ) { // +4 for the magic 'DDS ' fourcc at the beginning
13721375
fileSystem->CloseFile( f );
13731376
return false;
13741377
}
@@ -1392,6 +1395,7 @@ bool idImage::CheckPrecompressedImage( bool fullLoad ) {
13921395
unsigned int magic = LittleInt( *(unsigned int *)data );
13931396
ddsFileHeader_t *_header = (ddsFileHeader_t *)(data + 4);
13941397
int ddspf_dwFlags = LittleInt( _header->ddspf.dwFlags );
1398+
unsigned int ddspf_dwFourCC = LittleInt( _header->ddspf.dwFourCC );
13951399

13961400
if ( magic != DDS_MAKEFOURCC('D', 'D', 'S', ' ')) {
13971401
common->Printf( "CheckPrecompressedImage( %s ): magic != 'DDS '\n", imgName.c_str() );
@@ -1401,11 +1405,27 @@ bool idImage::CheckPrecompressedImage( bool fullLoad ) {
14011405

14021406
// if we don't support color index textures, we must load the full image
14031407
// should we just expand the 256 color image to 32 bit for upload?
1404-
if ( ddspf_dwFlags & DDSF_ID_INDEXCOLOR && !glConfig.sharedTexturePaletteAvailable ) {
1408+
if ( (ddspf_dwFlags & DDSF_ID_INDEXCOLOR) && !glConfig.sharedTexturePaletteAvailable ) {
14051409
R_StaticFree( data );
14061410
return false;
14071411
}
14081412

1413+
// DG: same if this is a BC7 (BPTC) texture but the GPU doesn't support that
1414+
// or if it uses the additional DX10 header and is *not* a BC7 texture
1415+
if ( ddspf_dwFourCC == DDS_MAKEFOURCC( 'D', 'X', '1', '0' ) ) {
1416+
ddsDXT10addHeader_t *dx10Header = (ddsDXT10addHeader_t *)( data + 4 + sizeof(ddsFileHeader_t) );
1417+
unsigned int dxgiFormat = LittleInt( dx10Header->dxgiFormat );
1418+
if ( dxgiFormat != 98 // DXGI_FORMAT_BC7_UNORM
1419+
|| !glConfig.bptcTextureCompressionAvailable ) {
1420+
if (dxgiFormat != 98) {
1421+
common->Warning( "Image file '%s' has unsupported dxgiFormat %d - dhewm3 only supports DXGI_FORMAT_BC7_UNORM (98)!",
1422+
filename, dxgiFormat);
1423+
}
1424+
R_StaticFree( data );
1425+
return false;
1426+
}
1427+
}
1428+
14091429
// upload all the levels
14101430
UploadPrecompressedImage( data, len );
14111431

@@ -1455,6 +1475,7 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
14551475

14561476
uploadWidth = header->dwWidth;
14571477
uploadHeight = header->dwHeight;
1478+
size_t additionalHeaderOffset = 0; // used if the DDS has a DDS_HEADER_DXT10
14581479
if ( header->ddspf.dwFlags & DDSF_FOURCC ) {
14591480
switch ( header->ddspf.dwFourCC ) {
14601481
case DDS_MAKEFOURCC( 'D', 'X', 'T', '1' ):
@@ -1473,6 +1494,12 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
14731494
case DDS_MAKEFOURCC( 'R', 'X', 'G', 'B' ):
14741495
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
14751496
break;
1497+
case DDS_MAKEFOURCC( 'D', 'X', '1', '0' ): // BC7 aka BPTC
1498+
additionalHeaderOffset = 20;
1499+
// Note: this is a bit hacky, but in CheckPrecompressedImage() we made sure
1500+
// that only BC7 UNORM is accepted if the FourCC is 'DX10'
1501+
internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM;
1502+
break;
14761503
default:
14771504
common->Warning( "Invalid compressed internal format\n" );
14781505
return;
@@ -1510,12 +1537,13 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
15101537

15111538
int uw = uploadWidth;
15121539
int uh = uploadHeight;
1540+
int lastUW = uw, lastUH = uh;
15131541

15141542
// We may skip some mip maps if we are downsizing
15151543
int skipMip = 0;
15161544
GetDownsize( uploadWidth, uploadHeight );
15171545

1518-
byte *imagedata = data + sizeof(ddsFileHeader_t) + 4;
1546+
byte *imagedata = data + sizeof(ddsFileHeader_t) + 4 + additionalHeaderOffset;
15191547

15201548
for ( int i = 0 ; i < numMipmaps; i++ ) {
15211549
int size = 0;
@@ -1535,6 +1563,8 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
15351563
qglTexImage2D( GL_TEXTURE_2D, i - skipMip, internalFormat, uw, uh, 0, externalFormat, GL_UNSIGNED_BYTE, imagedata );
15361564
}
15371565
}
1566+
lastUW = uw;
1567+
lastUH = uh;
15381568

15391569
imagedata += size;
15401570
uw /= 2;
@@ -1546,6 +1576,19 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
15461576
uh = 1;
15471577
}
15481578
}
1579+
// in case the mipmap chain is incomplete (doesn't go down to 1x1 pixel)
1580+
// the texture may be shown as black unless GL_TEXTURE_MAX_LEVEL is set accordingly
1581+
if ( lastUW > 1 || lastUH > 1 ) {
1582+
numMipmaps -= skipMip;
1583+
if ( numMipmaps == 1 ) {
1584+
// if there is only one mipmap, just don't use mipmapping for this texture
1585+
if ( filter == TF_DEFAULT ) {
1586+
filter = TF_LINEAR;
1587+
}
1588+
} else {
1589+
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, numMipmaps - 1 );
1590+
}
1591+
}
15491592

15501593
SetImageFilterAndRepeat();
15511594
}
@@ -2140,6 +2183,9 @@ void idImage::Print() const {
21402183
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
21412184
common->Printf( "DXT5 " );
21422185
break;
2186+
case GL_COMPRESSED_RGBA_BPTC_UNORM:
2187+
common->Printf( "BC7 " );
2188+
break;
21432189
case GL_RGBA4:
21442190
common->Printf( "RGBA4 " );
21452191
break;

neo/renderer/RenderSystem.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ typedef struct glconfig_s {
6363

6464
bool multitextureAvailable;
6565
bool textureCompressionAvailable;
66+
bool bptcTextureCompressionAvailable; // DG: for GL_ARB_texture_compression_bptc (BC7)
6667
bool anisotropicAvailable;
6768
bool textureLODBiasAvailable;
6869
bool textureEnvAddAvailable;

neo/renderer/RenderSystem_init.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,8 +431,12 @@ static void R_CheckPortableExtensions( void ) {
431431
glConfig.textureCompressionAvailable = true;
432432
qglCompressedTexImage2DARB = (PFNGLCOMPRESSEDTEXIMAGE2DARBPROC)GLimp_ExtensionPointer( "glCompressedTexImage2DARB" );
433433
qglGetCompressedTexImageARB = (PFNGLGETCOMPRESSEDTEXIMAGEARBPROC)GLimp_ExtensionPointer( "glGetCompressedTexImageARB" );
434+
if ( R_CheckExtension( "GL_ARB_texture_compression_bptc" ) ) {
435+
glConfig.bptcTextureCompressionAvailable = true;
436+
}
434437
} else {
435438
glConfig.textureCompressionAvailable = false;
439+
glConfig.bptcTextureCompressionAvailable = false;
436440
}
437441

438442
// GL_EXT_texture_filter_anisotropic

neo/renderer/qgl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ extern PFNGLSTENCILOPSEPARATEPROC qglStencilOpSeparate;
110110
extern PFNGLCOMPRESSEDTEXIMAGE2DARBPROC qglCompressedTexImage2DARB;
111111
extern PFNGLGETCOMPRESSEDTEXIMAGEARBPROC qglGetCompressedTexImageARB;
112112

113+
// ARB_texture_compression_bptc - uses ARB_texture_compression, just adds new constants
114+
// that might be missing in old OpenGL headers
115+
#ifndef GL_COMPRESSED_RGBA_BPTC_UNORM_ARB
116+
// currently the only one we use, there's also COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB (0x8E8D)
117+
// and COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB (0x8E8E) and COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB (0x8E8F)
118+
#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C
119+
#endif
120+
113121
// ARB_vertex_program / ARB_fragment_program
114122
extern PFNGLVERTEXATTRIBPOINTERARBPROC qglVertexAttribPointerARB;
115123
extern PFNGLENABLEVERTEXATTRIBARRAYARBPROC qglEnableVertexAttribArrayARB;

0 commit comments

Comments
 (0)