From 24cd2f1b4ed22fb7e65553a7a63cb3a4b1c2aed5 Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Tue, 8 Apr 2025 05:57:09 +0200 Subject: [PATCH] BPTC modes for image_usePrecompressedTextures + image_useCompression Both can now be set to 2 instead of just 0/1. For image_usePrecompressedTextures 1 now means "use .dds files no matter how they're compressed" and 2 means "only use .dds files if they are compressed with BPTC/BC7 or are uncompressed". For image_useCompression 1 means "compress textures with S3TC on upload" (just like before) and 2 means "compress with BPTC/BC7 if available" I wasn't sure whether this option makes sense for image_useCompression (over always using BPTC if available), but I can imagine that loading takes longer with BPTC (the driver has to compress the raw image data and compressing to S3TC might be faster than for BPTC) --- neo/renderer/Image.h | 2 +- neo/renderer/Image_init.cpp | 7 ++-- neo/renderer/Image_load.cpp | 67 +++++++++++++++++++++++++++++-------- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/neo/renderer/Image.h b/neo/renderer/Image.h index ec7f5d0d..31dd8d2e 100644 --- a/neo/renderer/Image.h +++ b/neo/renderer/Image.h @@ -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 diff --git a/neo/renderer/Image_init.cpp b/neo/renderer/Image_init.cpp index f591b030..1e0f640a 100644 --- a/neo/renderer/Image_init.cpp +++ b/neo/renderer/Image_init.cpp @@ -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" ); diff --git a/neo/renderer/Image_load.cpp b/neo/renderer/Image_load.cpp index 06dfc30c..f9ae0021 100644 --- a/neo/renderer/Image_load.cpp +++ b/neo/renderer/Image_load.cpp @@ -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'