Added BC7 compression.

This commit is contained in:
Justin Marshall
2026-05-29 19:41:58 -07:00
parent fae1adf30d
commit 1bc7df9614
49 changed files with 448379 additions and 426132 deletions
+16
View File
@@ -117,6 +117,17 @@ typedef struct
unsigned long dwReserved2[3];
} ddsFileHeader_t;
typedef struct
{
unsigned long dxgiFormat;
unsigned long resourceDimension;
unsigned long miscFlag;
unsigned long arraySize;
unsigned long miscFlags2;
} ddsFileHeaderDX10_t;
const unsigned long DDS_DIMENSION_TEXTURE2D = 3;
const unsigned long DDS_DXGI_FORMAT_BC7_UNORM = 98;
// increasing numeric values imply more information is stored
typedef enum {
@@ -203,6 +214,8 @@ public:
void WritePrecompressedImage();
bool CheckPrecompressedImage( bool fullLoad );
void UploadPrecompressedImage( byte *data, int len );
bool CheckGeneratedBC7Image( bool fullLoad, ID_TIME_T sourceTimestamp );
bool CookGeneratedBC7Image( const byte *pic, int width, int height, ID_TIME_T sourceTimestamp );
void ActuallyLoadImage( bool checkForPrecompressed, bool fromBackEnd );
void StartBackgroundImageLoad();
int BitsForInternalFormat( int internalFormat ) const;
@@ -376,6 +389,9 @@ public:
static idCVar image_useAllFormats; // allow alpha/intensity/luminance/luminance+alpha
static idCVar image_usePrecompressedTextures; // use .dds files if present
static idCVar image_writePrecompressedTextures; // write .dds files if necessary
static idCVar image_useBC7GeneratedTextures; // use generated BC7 DDS files if present
static idCVar image_cookBC7GeneratedTextures; // cook generated BC7 DDS files from source data
static idCVar image_bc7NormalMaps; // include normal maps in BC7 generated texture path
static idCVar image_writeNormalTGA; // debug tool to write out .tgas of the final normal maps
static idCVar image_writeNormalTGAPalletized; // debug tool to write out palletized versions of the final normal maps
static idCVar image_writeTGA; // debug tool to write out .tgas of the non normal maps
+3
View File
@@ -54,6 +54,9 @@ idCVar idImageManager::image_useAllFormats( "image_useAllFormats", "1", CVAR_REN
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_writePrecompressedTextures( "image_writePrecompressedTextures", "0", CVAR_RENDERER | CVAR_BOOL, "write .dds files if necessary" );
idCVar idImageManager::image_useBC7GeneratedTextures( "image_useBC7GeneratedTextures", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "use generated BC7 DDS files if present" );
idCVar idImageManager::image_cookBC7GeneratedTextures( "image_cookBC7GeneratedTextures", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "cook generated BC7 DDS files from source data when missing or stale" );
idCVar idImageManager::image_bc7NormalMaps( "image_bc7NormalMaps", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "include normal maps in the generated BC7 path" );
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" );
idCVar idImageManager::image_writeTGA( "image_writeTGA", "0", CVAR_RENDERER | CVAR_BOOL, "write .tgas of the non normal maps for debugging" );
+576 -5
View File
@@ -43,6 +43,26 @@ static bool FormatIsDXT( int internalFormat ) {
return true;
}
static bool FormatIsBC7( int internalFormat ) {
return internalFormat == GL_COMPRESSED_RGBA_BPTC_UNORM_ARB || internalFormat == GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB;
}
static bool FormatIsBlockCompressed( int internalFormat ) {
return FormatIsDXT( internalFormat ) || FormatIsBC7( internalFormat );
}
static int BlockBytesForInternalFormat( int internalFormat ) {
if ( internalFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT || internalFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ) {
return 8;
}
if ( internalFormat == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
internalFormat == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ||
FormatIsBC7( internalFormat ) ) {
return 16;
}
return 0;
}
static void R_SetImageAverageColor( idImage *image, const byte *pic, int width, int height ) {
if ( !image || !pic || width <= 0 || height <= 0 ) {
return;
@@ -140,6 +160,9 @@ int idImage::BitsForInternalFormat( int internalFormat ) const {
return 8;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
return 8;
case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
return 8;
case GL_RGBA4:
return 16;
case GL_RGB5:
@@ -1006,6 +1029,498 @@ void idImage::ImageProgramStringToCompressedFileName( const char *imageProg, cha
strcat( fileName, ".dds" );
}
static const char *R_BC7DepthFolder( textureDepth_t depth ) {
switch ( depth ) {
case TD_SPECULAR:
return "specular";
case TD_BUMP:
return "normal";
case TD_HIGH_QUALITY:
return "high";
case TD_DIFFUSE:
return "diffuse";
case TD_DEFAULT:
default:
return "default";
}
}
static bool R_DepthWantsGeneratedBC7( textureDepth_t depth ) {
if ( depth == TD_HIGH_QUALITY ) {
return false;
}
if ( depth == TD_BUMP && !globalImages->image_bc7NormalMaps.GetBool() ) {
return false;
}
return depth == TD_DIFFUSE || depth == TD_DEFAULT || depth == TD_SPECULAR || depth == TD_BUMP;
}
static void R_ImageProgramStringToGeneratedBC7FileName( const char *imageProg, textureDepth_t depth, char *fileName ) {
const char *s;
char *f;
idStr::snPrintf( fileName, MAX_IMAGE_NAME, "generated/images/bc7/%s/", R_BC7DepthFolder( depth ) );
f = fileName + strlen( fileName );
int folderDepth = 0;
for ( s = imageProg ; *s && ( f - fileName ) < MAX_IMAGE_NAME - 6 ; s++ ) {
if ( *s == '/' || *s == '\\' || *s == '(' ) {
if ( folderDepth < 4 ) {
*f = '/';
folderDepth++;
} else {
*f = '_';
}
f++;
} else if ( *s == '<' || *s == '>' || *s == ':' || *s == '|' || *s == '"' || *s == '.' ) {
*f++ = '_';
} else if ( *s == ' ' && f > fileName && *( f - 1 ) == '/' ) {
} else if ( *s == ')' || *s == ',' ) {
} else {
*f++ = *s;
}
}
*f = 0;
idStr::Copynz( f, ".dds", MAX_IMAGE_NAME - (int)( f - fileName ) );
}
static void R_WriteBitsLSB( byte *block, int &bit, unsigned int value, int count ) {
for ( int i = 0; i < count; i++ ) {
if ( value & ( 1u << i ) ) {
block[bit >> 3] |= (byte)( 1u << ( bit & 7 ) );
}
bit++;
}
}
static int R_BC7Luma( const byte *rgba ) {
return rgba[0] * 54 + rgba[1] * 183 + rgba[2] * 19;
}
static byte *R_MipMapNormalMap( const byte *in, int width, int height, bool preserveBorder ) {
if ( width < 1 || height < 1 || ( width + height == 2 ) ) {
common->FatalError( "R_MipMapNormalMap called with size %i,%i", width, height );
}
const byte border[4] = { in[0], in[1], in[2], in[3] };
const int newWidth = ( width > 1 ) ? width >> 1 : 1;
const int newHeight = ( height > 1 ) ? height >> 1 : 1;
byte *out = (byte *)R_StaticAlloc( newWidth * newHeight * 4 );
for ( int y = 0; y < newHeight; y++ ) {
for ( int x = 0; x < newWidth; x++ ) {
float nx = 0.0f;
float ny = 0.0f;
float nz = 0.0f;
int alpha = 0;
int samples = 0;
for ( int yy = 0; yy < 2; yy++ ) {
const int sy = ( height == 1 ) ? 0 : idMath::ClampInt( 0, height - 1, y * 2 + yy );
for ( int xx = 0; xx < 2; xx++ ) {
const int sx = ( width == 1 ) ? 0 : idMath::ClampInt( 0, width - 1, x * 2 + xx );
const byte *p = in + ( sy * width + sx ) * 4;
nx += p[0] * ( 2.0f / 255.0f ) - 1.0f;
ny += p[1] * ( 2.0f / 255.0f ) - 1.0f;
nz += p[2] * ( 2.0f / 255.0f ) - 1.0f;
alpha += p[3];
samples++;
}
}
float len = idMath::Sqrt( nx * nx + ny * ny + nz * nz );
if ( len > 1.0e-6f ) {
len = 1.0f / len;
nx *= len;
ny *= len;
nz *= len;
} else {
nx = 0.0f;
ny = 0.0f;
nz = 1.0f;
}
byte *dst = out + ( y * newWidth + x ) * 4;
dst[0] = (byte)idMath::ClampInt( 0, 255, (int)( ( nx * 0.5f + 0.5f ) * 255.0f + 0.5f ) );
dst[1] = (byte)idMath::ClampInt( 0, 255, (int)( ( ny * 0.5f + 0.5f ) * 255.0f + 0.5f ) );
dst[2] = (byte)idMath::ClampInt( 0, 255, (int)( ( nz * 0.5f + 0.5f ) * 255.0f + 0.5f ) );
dst[3] = (byte)idMath::ClampInt( 0, 255, ( alpha + samples / 2 ) / samples );
}
}
if ( preserveBorder ) {
R_SetBorderTexels( out, newWidth, newHeight, border );
}
return out;
}
static byte *R_MipMapForBC7Depth( const byte *in, int width, int height, bool preserveBorder, textureDepth_t depth ) {
return ( depth == TD_BUMP ) ? R_MipMapNormalMap( in, width, height, preserveBorder ) : R_MipMap( in, width, height, preserveBorder );
}
static float R_NormalProjection( const byte *rgba, const idVec3 &axis ) {
return ( rgba[0] * ( 2.0f / 255.0f ) - 1.0f ) * axis.x +
( rgba[1] * ( 2.0f / 255.0f ) - 1.0f ) * axis.y +
( rgba[2] * ( 2.0f / 255.0f ) - 1.0f ) * axis.z;
}
static void R_EncodeBC7Mode6Block( const byte *rgba, int width, int height, int x, int y, bool normalMap, byte *block ) {
memset( block, 0, 16 );
byte minColor[4] = { 255, 255, 255, 255 };
byte maxColor[4] = { 0, 0, 0, 0 };
int minLuma = 2147483647;
int maxLuma = -2147483648;
float minProjection = idMath::INFINITY;
float maxProjection = -idMath::INFINITY;
idVec3 averageNormal( 0.0f, 0.0f, 0.0f );
idVec3 variance( 0.0f, 0.0f, 0.0f );
idVec3 axis( 1.0f, 0.0f, 0.0f );
if ( normalMap ) {
for ( int by = 0; by < 4; by++ ) {
const int sy = ( y + by < height ) ? y + by : height - 1;
for ( int bx = 0; bx < 4; bx++ ) {
const int sx = ( x + bx < width ) ? x + bx : width - 1;
const byte *p = rgba + ( sy * width + sx ) * 4;
averageNormal.x += p[0] * ( 2.0f / 255.0f ) - 1.0f;
averageNormal.y += p[1] * ( 2.0f / 255.0f ) - 1.0f;
averageNormal.z += p[2] * ( 2.0f / 255.0f ) - 1.0f;
}
}
averageNormal *= 1.0f / 16.0f;
for ( int by = 0; by < 4; by++ ) {
const int sy = ( y + by < height ) ? y + by : height - 1;
for ( int bx = 0; bx < 4; bx++ ) {
const int sx = ( x + bx < width ) ? x + bx : width - 1;
const byte *p = rgba + ( sy * width + sx ) * 4;
const float nx = p[0] * ( 2.0f / 255.0f ) - 1.0f;
const float ny = p[1] * ( 2.0f / 255.0f ) - 1.0f;
const float nz = p[2] * ( 2.0f / 255.0f ) - 1.0f;
variance.x += ( nx - averageNormal.x ) * ( nx - averageNormal.x );
variance.y += ( ny - averageNormal.y ) * ( ny - averageNormal.y );
variance.z += ( nz - averageNormal.z ) * ( nz - averageNormal.z );
}
}
if ( variance.y > variance.x && variance.y >= variance.z ) {
axis.Set( 0.0f, 1.0f, 0.0f );
} else if ( variance.z > variance.x && variance.z > variance.y ) {
axis.Set( 0.0f, 0.0f, 1.0f );
}
}
for ( int by = 0; by < 4; by++ ) {
const int sy = ( y + by < height ) ? y + by : height - 1;
for ( int bx = 0; bx < 4; bx++ ) {
const int sx = ( x + bx < width ) ? x + bx : width - 1;
const byte *p = rgba + ( sy * width + sx ) * 4;
if ( normalMap ) {
const float projection = R_NormalProjection( p, axis );
if ( projection < minProjection ) {
minProjection = projection;
memcpy( minColor, p, 4 );
}
if ( projection > maxProjection ) {
maxProjection = projection;
memcpy( maxColor, p, 4 );
}
continue;
}
const int luma = R_BC7Luma( p );
if ( luma < minLuma ) {
minLuma = luma;
memcpy( minColor, p, 4 );
}
if ( luma > maxLuma ) {
maxLuma = luma;
memcpy( maxColor, p, 4 );
}
}
}
int bit = 0;
R_WriteBitsLSB( block, bit, 0x40, 7 ); // BC7 mode 6: six zero mode bits followed by one.
const byte *ep[2] = { minColor, maxColor };
for ( int c = 0; c < 4; c++ ) {
R_WriteBitsLSB( block, bit, ep[0][c] >> 1, 7 );
R_WriteBitsLSB( block, bit, ep[1][c] >> 1, 7 );
}
R_WriteBitsLSB( block, bit, ep[0][0] & 1, 1 );
R_WriteBitsLSB( block, bit, ep[1][0] & 1, 1 );
const int range = maxLuma - minLuma;
const float projectionRange = maxProjection - minProjection;
for ( int i = 0; i < 16; i++ ) {
const int bx = i & 3;
const int by = i >> 2;
const int sx = ( x + bx < width ) ? x + bx : width - 1;
const int sy = ( y + by < height ) ? y + by : height - 1;
const byte *p = rgba + ( sy * width + sx ) * 4;
int index = 0;
if ( normalMap && projectionRange > 1.0e-6f ) {
index = idMath::ClampInt( 0, 15, (int)( ( ( R_NormalProjection( p, axis ) - minProjection ) / projectionRange ) * 15.0f + 0.5f ) );
} else if ( range > 0 ) {
index = idMath::ClampInt( 0, 15, ( ( R_BC7Luma( p ) - minLuma ) * 15 + range / 2 ) / range );
}
if ( i == 0 ) {
R_WriteBitsLSB( block, bit, index & 7, 3 );
} else {
R_WriteBitsLSB( block, bit, index, 4 );
}
}
}
static void R_EncodeBC7Image( const byte *rgba, int width, int height, textureDepth_t depth, idList<byte> &out ) {
const int blocksWide = ( width + 3 ) / 4;
const int blocksHigh = ( height + 3 ) / 4;
out.SetNum( blocksWide * blocksHigh * 16 );
const bool normalMap = ( depth == TD_BUMP );
byte *dst = out.Ptr();
for ( int by = 0; by < blocksHigh; by++ ) {
for ( int bx = 0; bx < blocksWide; bx++ ) {
R_EncodeBC7Mode6Block( rgba, width, height, bx * 4, by * 4, normalMap, dst );
dst += 16;
}
}
}
static void R_AppendBytes( idList<byte> &dst, const void *src, int bytes ) {
const int oldNum = dst.Num();
dst.SetNum( oldNum + bytes );
memcpy( dst.Ptr() + oldNum, src, bytes );
}
static unsigned long R_GeneratedBC7CookVersion() {
return DDS_MAKEFOURCC( 'B', '7', 'C', '3' );
}
static bool R_GetDDSAverageColor( const byte *data, int len, float averageColor[4] ) {
if ( len < 4 + (int)sizeof( ddsFileHeader_t ) ) {
return false;
}
const ddsFileHeader_t *header = (const ddsFileHeader_t *)( data + 4 );
if ( LittleLong( header->dwReserved1[0] ) != DDS_MAKEFOURCC( 'A', 'V', 'G', '1' ) ) {
return false;
}
for ( int i = 0; i < 4; i++ ) {
unsigned long bits = LittleLong( header->dwReserved1[1 + i] );
memcpy( &averageColor[i], &bits, sizeof( float ) );
}
return true;
}
bool idImage::CheckGeneratedBC7Image( bool fullLoad, ID_TIME_T sourceTimestamp ) {
if ( !globalImages->image_useBC7GeneratedTextures.GetBool() ||
!R_DepthWantsGeneratedBC7( depth ) ||
fileSystem->PerformingCopyFiles() ||
!renderSystem->IsOpenGLRunning() ||
cubeFiles != CF_2D ) {
return false;
}
char filename[MAX_IMAGE_NAME];
R_ImageProgramStringToGeneratedBC7FileName( imgName, depth, filename );
ID_TIME_T generatedTimestamp;
fileSystem->ReadFile( filename, NULL, &generatedTimestamp );
if ( generatedTimestamp == FILE_NOT_FOUND_TIMESTAMP ) {
return false;
}
if ( sourceTimestamp != FILE_NOT_FOUND_TIMESTAMP && generatedTimestamp < sourceTimestamp ) {
return false;
}
idFile *f = fileSystem->OpenFileRead( filename );
if ( !f ) {
return false;
}
int len = f->Length();
if ( len < 4 + (int)sizeof( ddsFileHeader_t ) + (int)sizeof( ddsFileHeaderDX10_t ) ) {
fileSystem->CloseFile( f );
return false;
}
if ( !fullLoad && len > globalImages->image_cacheMinK.GetInteger() * 1024 ) {
len = globalImages->image_cacheMinK.GetInteger() * 1024;
}
byte *data = (byte *)R_StaticAlloc( len );
f->Read( data, len );
fileSystem->CloseFile( f );
if ( LittleLong( *(unsigned long *)data ) != DDS_MAKEFOURCC( 'D', 'D', 'S', ' ' ) ) {
R_StaticFree( data );
return false;
}
const ddsFileHeader_t *header = (const ddsFileHeader_t *)( data + 4 );
if ( LittleLong( header->dwReserved1[5] ) != R_GeneratedBC7CookVersion() ) {
R_StaticFree( data );
return false;
}
UploadPrecompressedImage( data, len );
timestamp = generatedTimestamp;
R_StaticFree( data );
return true;
}
bool idImage::CookGeneratedBC7Image( const byte *pic, int width, int height, ID_TIME_T sourceTimestamp ) {
if ( !globalImages->image_cookBC7GeneratedTextures.GetBool() ||
!globalImages->image_useBC7GeneratedTextures.GetBool() ||
!R_DepthWantsGeneratedBC7( depth ) ||
globalImages->keepPixelsResident ||
cubeFiles != CF_2D ||
!pic || width <= 0 || height <= 0 ) {
return false;
}
R_SetImageAverageColor( this, pic, width, height );
float sourceAverageColor[4];
for ( int i = 0; i < 4; i++ ) {
sourceAverageColor[i] = averageColor[i];
}
bool preserveBorder = ( repeat == TR_CLAMP_TO_ZERO );
int scaled_width = width;
int scaled_height = height;
GetDownsize( scaled_width, scaled_height );
byte *scaledBuffer = NULL;
if ( scaled_width == width && scaled_height == height ) {
scaledBuffer = (byte *)R_StaticAlloc( sizeof( unsigned ) * scaled_width * scaled_height );
memcpy( scaledBuffer, pic, width * height * 4 );
} else {
scaledBuffer = R_MipMapForBC7Depth( pic, width, height, preserveBorder, depth );
width >>= 1;
height >>= 1;
if ( width < 1 ) {
width = 1;
}
if ( height < 1 ) {
height = 1;
}
while ( width > scaled_width || height > scaled_height ) {
byte *shrunk = R_MipMapForBC7Depth( scaledBuffer, width, height, preserveBorder, depth );
R_StaticFree( scaledBuffer );
scaledBuffer = shrunk;
width >>= 1;
height >>= 1;
if ( width < 1 ) {
width = 1;
}
if ( height < 1 ) {
height = 1;
}
}
scaled_width = width;
scaled_height = height;
}
if ( repeat == TR_CLAMP_TO_ZERO ) {
byte rgba[4] = { 0, 0, 0, 255 };
if ( depth == TD_BUMP ) {
rgba[0] = 128;
rgba[1] = 128;
rgba[2] = 255;
}
R_SetBorderTexels( scaledBuffer, scaled_width, scaled_height, rgba );
}
if ( repeat == TR_CLAMP_TO_ZERO_ALPHA ) {
byte rgba[4] = { 255, 255, 255, 0 };
if ( depth == TD_BUMP ) {
rgba[0] = 128;
rgba[1] = 128;
rgba[2] = 255;
}
R_SetBorderTexels( scaledBuffer, scaled_width, scaled_height, rgba );
}
for ( int i = 0; i < 4; i++ ) {
averageColor[i] = sourceAverageColor[i];
}
idList<byte> fileBytes;
fileBytes.SetGranularity( 1024 );
const unsigned long magic = DDS_MAKEFOURCC( 'D', 'D', 'S', ' ' );
const int numLevels = NumLevelsForImageSize( scaled_width, scaled_height );
ddsFileHeader_t header;
memset( &header, 0, sizeof( header ) );
header.dwSize = sizeof( header );
header.dwFlags = DDSF_CAPS | DDSF_PIXELFORMAT | DDSF_WIDTH | DDSF_HEIGHT | DDSF_LINEARSIZE;
header.dwHeight = scaled_height;
header.dwWidth = scaled_width;
header.dwPitchOrLinearSize = ( ( scaled_width + 3 ) / 4 ) * 16;
header.dwMipMapCount = numLevels;
header.dwFlags |= DDSF_MIPMAPCOUNT;
header.dwCaps1 = DDSF_TEXTURE | DDSF_MIPMAP | DDSF_COMPLEX;
header.ddspf.dwSize = sizeof( header.ddspf );
header.ddspf.dwFlags = DDSF_FOURCC;
header.ddspf.dwFourCC = DDS_MAKEFOURCC( 'D', 'X', '1', '0' );
header.dwReserved1[0] = DDS_MAKEFOURCC( 'A', 'V', 'G', '1' );
for ( int i = 0; i < 4; i++ ) {
memcpy( &header.dwReserved1[1 + i], &averageColor[i], sizeof( float ) );
}
header.dwReserved1[5] = R_GeneratedBC7CookVersion();
ddsFileHeaderDX10_t header10;
memset( &header10, 0, sizeof( header10 ) );
header10.dxgiFormat = DDS_DXGI_FORMAT_BC7_UNORM;
header10.resourceDimension = DDS_DIMENSION_TEXTURE2D;
header10.arraySize = 1;
R_AppendBytes( fileBytes, &magic, sizeof( magic ) );
R_AppendBytes( fileBytes, &header, sizeof( header ) );
R_AppendBytes( fileBytes, &header10, sizeof( header10 ) );
byte *mipBuffer = scaledBuffer;
int mipWidth = scaled_width;
int mipHeight = scaled_height;
for ( int level = 0; level < numLevels; level++ ) {
idList<byte> encoded;
R_EncodeBC7Image( mipBuffer, mipWidth, mipHeight, depth, encoded );
R_AppendBytes( fileBytes, encoded.Ptr(), encoded.Num() );
if ( level + 1 < numLevels ) {
byte *shrunk = R_MipMapForBC7Depth( mipBuffer, mipWidth, mipHeight, preserveBorder, depth );
if ( mipBuffer != scaledBuffer ) {
R_StaticFree( mipBuffer );
}
mipBuffer = shrunk;
mipWidth >>= 1;
mipHeight >>= 1;
if ( mipWidth < 1 ) {
mipWidth = 1;
}
if ( mipHeight < 1 ) {
mipHeight = 1;
}
}
}
if ( mipBuffer != scaledBuffer ) {
R_StaticFree( mipBuffer );
}
R_StaticFree( scaledBuffer );
char filename[MAX_IMAGE_NAME];
R_ImageProgramStringToGeneratedBC7FileName( imgName, depth, filename );
fileSystem->WriteFile( filename, fileBytes.Ptr(), fileBytes.Num(), "fs_savepath" );
UploadPrecompressedImage( fileBytes.Ptr(), fileBytes.Num() );
if ( texnum != TEXTURE_NOT_LOADED ) {
glTextureAverageColorQD3D12( texnum, averageColor[0], averageColor[1], averageColor[2], averageColor[3] );
}
timestamp = sourceTimestamp;
precompressedFile = true;
return texnum != TEXTURE_NOT_LOADED;
}
/*
==================
NumLevelsForImageSize
@@ -1430,6 +1945,8 @@ has completed
*/
void idImage::UploadPrecompressedImage( byte *data, int len ) {
ddsFileHeader_t *header = (ddsFileHeader_t *)(data + 4);
float ddsAverageColor[4];
const bool hasDDSAverageColor = R_GetDDSAverageColor( data, len, ddsAverageColor );
// ( not byte swapping dwReserved1 dwReserved2 )
header->dwSize = LittleLong( header->dwSize );
@@ -1453,8 +1970,16 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
// generate the texture number
glGenTextures( 1, &texnum );
if ( hasDDSAverageColor ) {
for ( int i = 0; i < 4; i++ ) {
averageColor[i] = ddsAverageColor[i];
}
glTextureAverageColorQD3D12( texnum, averageColor[0], averageColor[1], averageColor[2], averageColor[3] );
}
int externalFormat = 0;
const ddsFileHeaderDX10_t *header10 = NULL;
int dataOffset = sizeof( ddsFileHeader_t ) + 4;
precompressedFile = true;
@@ -1478,6 +2003,22 @@ 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' ):
if ( len < (int)( sizeof( ddsFileHeader_t ) + 4 + sizeof( ddsFileHeaderDX10_t ) ) ) {
common->Warning( "Invalid DX10 DDS header\n" );
return;
}
header10 = (const ddsFileHeaderDX10_t *)( data + sizeof( ddsFileHeader_t ) + 4 );
switch ( LittleLong( header10->dxgiFormat ) ) {
case DDS_DXGI_FORMAT_BC7_UNORM:
internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
break;
default:
common->Warning( "Unsupported DX10 DDS format %i\n", LittleLong( header10->dxgiFormat ) );
return;
}
dataOffset += sizeof( ddsFileHeaderDX10_t );
break;
default:
common->Warning( "Invalid compressed internal format\n" );
return;
@@ -1525,13 +2066,13 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
int skipMip = 0;
GetDownsize( uploadWidth, uploadHeight );
byte *imagedata = data + sizeof(ddsFileHeader_t) + 4;
byte *imagedata = data + dataOffset;
for ( int i = 0 ; i < numMipmaps; i++ ) {
int size = 0;
if ( FormatIsDXT( internalFormat ) ) {
if ( FormatIsBlockCompressed( internalFormat ) ) {
size = ( ( uw + 3 ) / 4 ) * ( ( uh + 3 ) / 4 ) *
(internalFormat <= GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? 8 : 16);
BlockBytesForInternalFormat( internalFormat );
} else {
size = uw * uh * (header->ddspf.dwRGBBitCount / 8);
}
@@ -1539,7 +2080,7 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
if ( uw > uploadWidth || uh > uploadHeight ) {
skipMip++;
} else {
if ( FormatIsDXT( internalFormat ) ) {
if ( FormatIsBlockCompressed( internalFormat ) ) {
glCompressedTexImage2DARB( GL_TEXTURE_2D, i - skipMip, internalFormat, uw, uh, 0, size, imagedata );
} else {
glTexImage2D( GL_TEXTURE_2D, i - skipMip, internalFormat, uw, uh, 0, externalFormat, GL_UNSIGNED_BYTE, imagedata );
@@ -1612,9 +2153,21 @@ void idImage::ActuallyLoadImage( bool checkForPrecompressed, bool fromBackEnd )
}
}
} else {
ID_TIME_T sourceTimestamp = FILE_NOT_FOUND_TIMESTAMP;
const bool bc7GeneratedWanted = globalImages->image_useBC7GeneratedTextures.GetBool() && R_DepthWantsGeneratedBC7( depth );
if ( bc7GeneratedWanted ) {
R_LoadImageProgram( imgName, NULL, NULL, NULL, &sourceTimestamp, NULL );
if ( CheckGeneratedBC7Image( true, sourceTimestamp ) ) {
return;
}
}
// see if we have a pre-generated image file that is
// already image processed and compressed
if ( checkForPrecompressed && globalImages->image_usePrecompressedTextures.GetBool() ) {
const bool shouldCookBC7FromSource = bc7GeneratedWanted &&
globalImages->image_cookBC7GeneratedTextures.GetBool() &&
sourceTimestamp != FILE_NOT_FOUND_TIMESTAMP;
if ( !shouldCookBC7FromSource && checkForPrecompressed && globalImages->image_usePrecompressedTextures.GetBool() ) {
if ( CheckPrecompressedImage( true ) ) {
// we got the precompressed image
return;
@@ -1647,6 +2200,18 @@ void idImage::ActuallyLoadImage( bool checkForPrecompressed, bool fromBackEnd )
// may not be strictly necessary, but some code uses it, so let's leave it in
imageHash = MD4_BlockChecksum( pic, width * height * 4 );
if ( CookGeneratedBC7Image( pic, width, height, timestamp ) ) {
if ( globalImages->keepPixelsResident ) {
if ( residentPixels ) {
R_StaticFree( residentPixels );
}
residentPixels = pic;
} else {
R_StaticFree( pic );
}
return;
}
GenerateImage( pic, width, height, filter, allowDownSize, repeat, depth );
timestamp = timestamp;
precompressedFile = false;
@@ -2173,6 +2738,12 @@ void idImage::Print() const {
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
common->Printf( "DXT5 " );
break;
case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
common->Printf( "BC7 " );
break;
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
common->Printf( "BC7s " );
break;
case GL_RGBA4:
common->Printf( "RGBA4 " );
break;