This commit is contained in:
Daniel Gibson 2025-04-09 21:06:14 +00:00 committed by GitHub
commit c57e620255
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 132 additions and 41 deletions

View file

@ -15,11 +15,15 @@ Note: Numbers starting with a "#" like #330 refer to the bugreport with that num
a quarter of the VRAM (TGA: 4 bytes per pixel, BC7: 1 byte per pixel) and loading *significantly*
faster because mipmaps are contained and don't have to be generated on load.
If you have such DDS files and want to use them (instead of TGAs), you must set
`image_usePrecompressedTextures 1` and `image_useNormalCompression 1`.
`image_usePrecompressedTextures 1` and `image_useNormalCompression 2`.
You can also set `image_usePrecompressedTextures 2`, then dhewm3 will only load .dds textures
with BC7 data - if it only finds an old one (with S3TC/DXT/BC-13 compression) it will use the
uncompressed TGA textures instead.
If you want to *create* .dds files with BC7 texture data, you can use any common texture compression
tool, **except** for **normalmaps**, those must be created with my [**customized bc7enc**](https://github.com/DanielGibson/bc7enc_rdo)
with the `-r2a` flag! *(Because Doom3 requires that normalmaps have the red channel moved into the
alpha channel, id confusingly called that "RXGB", and AFAIK no other tool supports that for BC7.)*
tool, **except** for **normalmaps**, those must be created with my
[**customized bc7enc**](https://github.com/DanielGibson/bc7enc_rdo) with the `-r2a` flag!
*(Because Doom3 requires that normalmaps have the red channel moved into the alpha channel,
id confusingly called that "RXGB", and AFAIK no other tool supports that for BC7.)*
Just like the old DXT .dds files, they must be in the `dds/` subdirectory of a mod (either directly
in the filesystem or in a .pk4).
* Support SDL3 (SDL2 and, to some degree, SDL1.2 are also still supported)

View file

@ -233,6 +233,29 @@ This can be configured with the following CVars:
- `r_glDebugContext` Enable OpenGL debug context and printing warnings/errors from the graphics driver.
Changing that CVar requires a `vid_restart` (or set it as startup argument)
- `image_usePrecompressedTextures` can now also be set to `2`.
- `1` Use precompressed textures (.dds files), no matter which format they're in
- `2` Only use precompressed textures if they're in BPTC (BC7) format, which has better quality
than the old ones shipped with Doom3 that use S3TC/DXT. If no BPTC/BC7 (or uncompressed) .dds file
is found, fall back to uncompressed .tga. Especially useful when using high-res texture
packs that use BC7 compression.
- `0` Don't use precompressed (.dds) textures but the uncompressed ones (.tga)
- `image_useCompression` can now also be set to `2`.
- `1` When loading an uncompressed (.tga) texture, let the GPU (driver) compress it to S3TC/DXT
so it uses less VRAM (Video memory on the GPU) but doesn't look as good as leaving it
uncompressed or using precompressed textures, if available.
**Note:** IMHO this only makes sense together with `image_usePrecompressedTextures 1`,
so .dds textures are preferred (they should have better encoding quality than what the GPU
driver produces on the fly *and* load faster) and only if a texture doesn't exist as .dds,
the uncompressed TGA is loaded and then compressed on upload.
- `2` When loading an uncompressed (.tga) texture, let the GPU (driver) compress it to BPTC/BC7
(if the GPU supports it). Has better quality than S3TC/DXT but loading textures might take longer.
*Probably only makes sense with high-res texturing packs that don't use BPTC/BC7, because
if your GPU supports BPTC, it most probably has enough VRAM for the uncompressed original TGA
textures, which look at least as good, but for high resolution textures saving VRAM by
compressing on load can help)*.
- `0` Don't compress uncompressed textures when loading them - best quality, but uses more VRAM.
- `s_alReverbGain` reduce strength of OpenAL (EAX-like) EFX reverb effects, `0.0` - `1.0` (default `0.5`)
- `s_alHRTF` Enable [HRTF](https://en.wikipedia.org/w/index.php?title=Head-related_transfer_function)
for better surround sound with stereo **headphones**. `0`: Disable, `1`: Enable, `-1`: Let OpenAL decide (default).
@ -243,7 +266,8 @@ This can be configured with the following CVars:
including the current HRTF state (if supported by your OpenAL version).
- `s_alOutputLimiter` Configure OpenAL's output-limiter which temporarily reduces the overall volume
when too many too loud sounds play at once, to avoid issues like clipping. `0`: Disable, `1`: Enable, `-1`: Let OpenAL decide (default)
- `s_scaleDownAndClamp` Clamp and reduce volume of all sounds to prevent clipping or temporary downscaling by OpenAL's output limiter (default `1`)
- `s_scaleDownAndClamp` Clamp and reduce volume of all sounds to prevent clipping or temporary
downscaling by OpenAL's output limiter (default `1`)
- `imgui_scale` Factor to scale ImGui menus by (especially relevant for HighDPI displays).
Should be a positive factor like `1.5` or `2`; or `-1` (the default) to let dhewm3 automatically

View file

@ -1706,8 +1706,9 @@ static int initialMode = 0;
static int initialCustomVidRes[2];
static int initialMSAAmode = 0;
static int qualityPreset = 0;
static bool initialUsePrecomprTextures = false;
static int initialUseNormalCompr = false;
static int initialUsePrecomprTextures = 0;
static int initialUseCompression = 0;
static int initialUseNormalCompr = 0;
static void SetVideoStuffFromCVars()
{
@ -1737,7 +1738,8 @@ static void SetVideoStuffFromCVars()
qualityPreset = 1; // default to medium Quality
}
initialUsePrecomprTextures = globalImages->image_usePrecompressedTextures.GetBool();
initialUsePrecomprTextures = globalImages->image_usePrecompressedTextures.GetInteger();
initialUseCompression = globalImages->image_useCompression.GetInteger();
initialUseNormalCompr = globalImages->image_useNormalCompression.GetInteger();
}
@ -1759,7 +1761,10 @@ static bool VideoHasResettableChanges()
if ( initialMSAAmode != r_multiSamples.GetInteger() ) {
return true;
}
if ( initialUsePrecomprTextures != globalImages->image_usePrecompressedTextures.GetBool() ) {
if ( initialUsePrecomprTextures != globalImages->image_usePrecompressedTextures.GetInteger() ) {
return true;
}
if( initialUseCompression != globalImages->image_useCompression.GetInteger() ) {
return true;
}
if ( initialUseNormalCompr != globalImages->image_useNormalCompression.GetInteger() ) {
@ -1786,12 +1791,14 @@ static bool VideoHasApplyableChanges()
return true;
}
if ( initialUsePrecomprTextures != globalImages->image_usePrecompressedTextures.GetBool() ) {
if ( initialUsePrecomprTextures != globalImages->image_usePrecompressedTextures.GetInteger() ) {
return true;
}
// Note: value of image_useNormalCompression is only relevant if image_usePrecompressedTextures is enabled
if ( initialUsePrecomprTextures
&& initialUseNormalCompr != globalImages->image_useNormalCompression.GetInteger() ) {
&& (initialUseNormalCompr != globalImages->image_useNormalCompression.GetInteger()
|| initialUseCompression != globalImages->image_useCompression.GetInteger()) )
{
return true;
}
@ -1802,8 +1809,9 @@ static bool VideoHasApplyableChanges()
static void ApplyVideoSettings()
{
const char* cmd = "vid_restart partial\n";
if ( initialUsePrecomprTextures != globalImages->image_usePrecompressedTextures.GetBool()
|| initialUseNormalCompr != globalImages->image_useNormalCompression.GetInteger() )
if ( initialUsePrecomprTextures != globalImages->image_usePrecompressedTextures.GetInteger()
|| initialUseNormalCompr != globalImages->image_useNormalCompression.GetInteger()
|| initialUseCompression != globalImages->image_useCompression.GetInteger() )
{
// these need a full restart (=> textures must be reloaded)
cmd = "vid_restart\n";
@ -1821,7 +1829,8 @@ static void VideoResetChanges()
r_fullscreenDesktop.SetBool( initialFullscreenDesktop );
r_multiSamples.SetInteger( initialMSAAmode );
globalImages->image_usePrecompressedTextures.SetBool( initialUsePrecomprTextures );
globalImages->image_usePrecompressedTextures.SetInteger( initialUsePrecomprTextures );
globalImages->image_useCompression.SetInteger( initialUseCompression );
globalImages->image_useNormalCompression.SetInteger( initialUseNormalCompr );
}
@ -1966,9 +1975,11 @@ static void DrawVideoOptionsMenu()
}
AddCVarOptionTooltips( r_multiSamples, "Note: Not all GPUs/drivers support all modes, esp. not 16x!" );
bool usePreComprTex = globalImages->image_usePrecompressedTextures.GetBool();
if ( ImGui::Checkbox( "Use precompressed textures", &usePreComprTex ) ) {
globalImages->image_usePrecompressedTextures.SetBool(usePreComprTex);
int usePreComprTex = globalImages->image_usePrecompressedTextures.GetInteger();
if ( ImGui::Combo( "Use precompressed (.dds) textures", &usePreComprTex,
"No, only uncompressed\0Yes, no matter which format\0Only if high quality (BPCT/BC7)\0" ) )
{
globalImages->image_usePrecompressedTextures.SetInteger(usePreComprTex);
// by default I guess people also want compressed normal maps when using this
// especially relevant for retexturing packs that only ship BC7 DDS files
// (otherwise the lowres TGA normalmaps would be used)
@ -1977,22 +1988,32 @@ static void DrawVideoOptionsMenu()
}
}
const char* descr = "Use precompressed (.dds) textures. Faster loading, use less VRAM, possibly worse image quality.\n"
"May also be used by highres retexturing packs for BC7-compressed textures (there image quality is not impaired)";
"May also be used by highres retexturing packs for BC7-compressed textures (there image quality is not noticeably impaired)";
AddCVarOptionTooltips( globalImages->image_usePrecompressedTextures, descr );
ImGui::BeginDisabled( !usePreComprTex );
int useCompression = globalImages->image_useCompression.GetInteger();
if ( ImGui::Combo( "Compress uncompressed textures on load", &useCompression,
"Leave uncompressed (best quality)\0Compress with S3TC (aka DXT aka BC1-3)\0Compress with BPCT (BC7)\0" ) )
{
globalImages->image_useCompression.SetInteger(useCompression);
}
descr = "When loading non-precompressed textures, compress them so they use less VRAM.\n"
"Uncompressed has best quality. BC7 has better quality than S3TC, but may increase loading times";
AddCVarOptionTooltips( globalImages->image_useCompression, descr );
ImGui::BeginDisabled( !usePreComprTex && !useCompression );
bool useNormalCompr = globalImages->image_useNormalCompression.GetBool();
ImGui::Dummy( ImVec2(16, 0) );
ImGui::SameLine();
if ( ImGui::Checkbox( "Use precompressed normalmaps", &useNormalCompr ) ) {
if ( ImGui::Checkbox( "Use compressed normalmaps", &useNormalCompr ) ) {
// image_useNormalCompression 1 is not supported by modern GPUs
globalImages->image_useNormalCompression.SetInteger(useNormalCompr ? 2 : 0);
}
if ( usePreComprTex ) {
const char* descr = "Also use precompressed textures for normalmaps";
const char* descr = "Also use precompressed textures for normalmaps or compress them on load.\n"
"Uncompressed often has better quality, but uses more VRAM.\n"
"When using highres retexturing packs, you should definitely enable this.";
AddCVarOptionTooltips( globalImages->image_useNormalCompression, descr );
} else {
AddTooltip( "Can only be used if precompressed textures are enabled!" );
AddTooltip( "Can only be used if (pre)compressed textures are enabled!" );
}
ImGui::EndDisabled();

View file

@ -386,7 +386,7 @@ public:
static idCVar image_roundDown; // round bad sizes down to nearest power of two
static idCVar image_colorMipLevels; // development aid to see texture mip usage
static idCVar image_downSize; // controls texture downsampling
static idCVar image_useCompression; // 0 = force everything to high quality
static idCVar image_useCompression; // 0 = force everything to high quality 1 = compress with S3TC (DXT) 2 = compress with BPTC if possible
static idCVar image_filter; // changes texture filtering on mipmapped images
static idCVar image_anisotropy; // set the maximum texture anisotropy if available
static idCVar image_lodbias; // change lod bias on mipmapped images

View file

@ -53,10 +53,13 @@ idCVar idImageManager::image_forceDownSize( "image_forceDownSize", "0", CVAR_REN
idCVar idImageManager::image_roundDown( "image_roundDown", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "round bad sizes down to nearest power of two" );
idCVar idImageManager::image_colorMipLevels( "image_colorMipLevels", "0", CVAR_RENDERER | CVAR_BOOL, "development aid to see texture mip usage" );
idCVar idImageManager::image_preload( "image_preload", "1", CVAR_RENDERER | CVAR_BOOL | CVAR_ARCHIVE, "if 0, dynamically load all images" );
idCVar idImageManager::image_useCompression( "image_useCompression", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "0 = force everything to high quality" );
idCVar idImageManager::image_useCompression( "image_useCompression", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER,
"Compress textures on load so they use less VRAM. 1 = compress with S3TC/DXT when uploading 2 = compress with BPTC when uploading (if available) "
"0 = upload uncompressed (unless image_usePrecompressedTextures is 1 and it's loaded from a precompressed .dds file)" );
idCVar idImageManager::image_useAllFormats( "image_useAllFormats", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "allow alpha/intensity/luminance/luminance+alpha" );
idCVar idImageManager::image_useNormalCompression( "image_useNormalCompression", "2", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "2 = use rxgb compression for normal maps, 1 = use 256 color compression for normal maps if available" );
idCVar idImageManager::image_usePrecompressedTextures( "image_usePrecompressedTextures", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "use .dds files if present" );
idCVar idImageManager::image_usePrecompressedTextures( "image_usePrecompressedTextures", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER,
"1 = use .dds files if present 2 = only use .dds files if they contain BPTC (BC7) textures (those have higher quality than S3TC/DXT) 0 = use uncompressed textures" );
idCVar idImageManager::image_writePrecompressedTextures( "image_writePrecompressedTextures", "0", CVAR_RENDERER | CVAR_BOOL, "write .dds files if necessary" );
idCVar idImageManager::image_writeNormalTGA( "image_writeNormalTGA", "0", CVAR_RENDERER | CVAR_BOOL, "write .tgas of the final normal maps for debugging" );
idCVar idImageManager::image_writeNormalTGAPalletized( "image_writeNormalTGAPalletized", "0", CVAR_RENDERER | CVAR_BOOL, "write .tgas of the final palletized normal maps for debugging" );

View file

@ -215,6 +215,11 @@ GLenum idImage::SelectInternalFormat( const byte **dataPtrs, int numDataPtrs, in
int rgbOr, rgbAnd, aOr, aAnd;
int rgbDiffer, rgbaDiffer;
// TODO: or always use BC7 if available? do textures take longer to load then?
// would look better at least...
const bool useBC7compression = glConfig.bptcTextureCompressionAvailable
&& globalImages->image_useCompression.GetInteger() == 2;
// determine if the rgb channels are all the same
// and if either all rgb or all alpha are 255
c = width*height;
@ -262,12 +267,17 @@ GLenum idImage::SelectInternalFormat( const byte **dataPtrs, int numDataPtrs, in
// catch normal maps first
if ( minimumDepth == TD_BUMP ) {
if ( globalImages->image_useCompression.GetBool() && globalImages->image_useNormalCompression.GetInteger() == 1 && glConfig.sharedTexturePaletteAvailable ) {
// DG: put the glConfig.sharedTexturePaletteAvailable check first because nowadays it's usually false
if ( glConfig.sharedTexturePaletteAvailable && globalImages->image_useCompression.GetBool() && globalImages->image_useNormalCompression.GetInteger() == 1 ) {
// image_useNormalCompression should only be set to 1 on nv_10 and nv_20 paths
return GL_COLOR_INDEX8_EXT;
} else if ( globalImages->image_useCompression.GetBool() && globalImages->image_useNormalCompression.GetInteger() && glConfig.textureCompressionAvailable ) {
// image_useNormalCompression == 2 uses rxgb format which produces really good quality for medium settings
return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
if ( useBC7compression ) {
return GL_COMPRESSED_RGBA_BPTC_UNORM;
} else {
// image_useNormalCompression == 2 uses rxgb format which produces really good quality for medium settings
return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
}
} else {
// we always need the alpha channel for bump maps for swizzling
return GL_RGBA8;
@ -282,7 +292,7 @@ GLenum idImage::SelectInternalFormat( const byte **dataPtrs, int numDataPtrs, in
if ( minimumDepth == TD_SPECULAR ) {
// we are assuming that any alpha channel is unintentional
if ( glConfig.textureCompressionAvailable ) {
return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
return useBC7compression ? GL_COMPRESSED_RGBA_BPTC_UNORM : GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
} else {
return GL_RGB5;
}
@ -290,6 +300,9 @@ GLenum idImage::SelectInternalFormat( const byte **dataPtrs, int numDataPtrs, in
if ( minimumDepth == TD_DIFFUSE ) {
// we might intentionally have an alpha channel for alpha tested textures
if ( glConfig.textureCompressionAvailable ) {
if ( useBC7compression ) {
return GL_COMPRESSED_RGBA_BPTC_UNORM;
}
if ( !needAlpha ) {
return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
} else {
@ -319,7 +332,8 @@ GLenum idImage::SelectInternalFormat( const byte **dataPtrs, int numDataPtrs, in
return GL_RGB8; // four bytes
}
if ( glConfig.textureCompressionAvailable ) {
return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; // half byte
return useBC7compression ? GL_COMPRESSED_RGBA_BPTC_UNORM // 1byte/pixel
: GL_COMPRESSED_RGB_S3TC_DXT1_EXT; // half byte
}
return GL_RGB5; // two bytes
}
@ -327,7 +341,7 @@ GLenum idImage::SelectInternalFormat( const byte **dataPtrs, int numDataPtrs, in
// cases with alpha
if ( !rgbaDiffer ) {
if ( minimumDepth != TD_HIGH_QUALITY && glConfig.textureCompressionAvailable ) {
return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; // one byte
return useBC7compression ? GL_COMPRESSED_RGBA_BPTC_UNORM : GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; // one byte
}
return GL_INTENSITY8; // single byte for all channels
}
@ -346,7 +360,7 @@ GLenum idImage::SelectInternalFormat( const byte **dataPtrs, int numDataPtrs, in
return GL_RGBA8; // four bytes
}
if ( glConfig.textureCompressionAvailable ) {
return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; // one byte
return useBC7compression ? GL_COMPRESSED_RGBA_BPTC_UNORM : GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; // one byte
}
if ( !rgbDiffer ) {
return GL_LUMINANCE8_ALPHA8; // two bytes, max quality
@ -1166,6 +1180,9 @@ void idImage::WritePrecompressedImage() {
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
header.ddspf.dwFourCC = DDS_MAKEFOURCC('D','X','T','5');
break;
case GL_COMPRESSED_RGBA_BPTC_UNORM:
header.ddspf.dwFourCC = DDS_MAKEFOURCC('B','C','7','0');
break;
}
} else {
header.ddspf.dwFlags = ( internalFormat == GL_COLOR_INDEX8_EXT ) ? DDSF_RGB | DDSF_ID_INDEXCOLOR : DDSF_RGB;
@ -1412,15 +1429,33 @@ bool idImage::CheckPrecompressedImage( bool fullLoad ) {
// 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
bool isBC7 = false;
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);
}
if ( dxgiFormat == 98 ) {
isBC7 = true;
} else {
common->Warning( "Image file '%s' has unsupported dxgiFormat %d - dhewm3 only supports DXGI_FORMAT_BC7_UNORM (98)!",
filename, dxgiFormat);
R_StaticFree( data );
return false;
}
} else if ( ddspf_dwFourCC == DDS_MAKEFOURCC( 'B', 'C', '7', '0' )
|| ddspf_dwFourCC == DDS_MAKEFOURCC( 'B', 'C', '7', 'L' ) )
{
isBC7 = true;
}
if ( isBC7 && !glConfig.bptcTextureCompressionAvailable ) {
R_StaticFree( data );
return false;
}
if ( glConfig.bptcTextureCompressionAvailable
&& globalImages->image_usePrecompressedTextures.GetInteger() == 2 )
{
// only high quality compressed textures, i.e. BC7 (BPTC), are welcome
// or uncompressed ones (that have no FOURCC flag set)
if ( !isBC7 && (ddspf_dwFlags & DDSF_FOURCC) != 0 ) {
R_StaticFree( data );
return false;
}
@ -1494,7 +1529,11 @@ 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
case DDS_MAKEFOURCC( 'B', 'C', '7', '0' ): // BC7 aka BPTC - inofficial FourCCs
case DDS_MAKEFOURCC( 'B', 'C', '7', 'L' ):
internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM;
break;
case DDS_MAKEFOURCC( 'D', 'X', '1', '0' ): // BC7 aka BPTC - the official dxgi way
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'