|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "pch_materialsystem.h"
// NOTE: currently this file is marked as "exclude from build"
//#define _CHECK_MATERIALS_FOR_PROBLEMS 1
#ifdef _CHECK_MATERIALS_FOR_PROBLEMS
#include "vtf/vtf.h"
#include "tier1/utlbuffer.h"
#include "tier1/utlstring.h"
void CheckMateralsInDirectoryRecursive( const char *pRoot, const char *pDirectory ); #endif
#ifdef _CHECK_MATERIALS_FOR_PROBLEMS
//-----------------------------------------------------------------------------
// Does a texture have alpha?
//-----------------------------------------------------------------------------
static bool DoesTextureUseAlpha( const char *pTextureName, const char *pMaterialName ) { if ( IsX360() ) { // not supporting
return false; }
// Special textures start with '_'..
if ( pTextureName[0] == '_' ) return false;
// The texture name doubles as the relative file name
// It's assumed to have already been set by this point
// Compute the cache name
char pCacheFileName[MATERIAL_MAX_PATH]; Q_snprintf( pCacheFileName, sizeof( pCacheFileName ), "materials/%s.vtf", pTextureName );
CUtlBuffer buf; FileHandle_t fileHandle = g_pFullFileSystem->Open( pCacheFileName, "rb" ); if ( fileHandle == FILESYSTEM_INVALID_HANDLE) { Warning( "Material \"%s\": can't open texture \"%s\"\n", pMaterialName, pCacheFileName ); return false; }
// Check the .vtf for an alpha channel
IVTFTexture *pVTFTexture = CreateVTFTexture();
int nHeaderSize = VTFFileHeaderSize( VTF_MAJOR_VERSION ); buf.EnsureCapacity( nHeaderSize );
// read the header first.. it's faster!!
g_pFullFileSystem->Read( buf.Base(), nHeaderSize, fileHandle ); buf.SeekPut( CUtlBuffer::SEEK_HEAD, nHeaderSize );
// Unserialize the header
bool bUsesAlpha = false;
if (!pVTFTexture->Unserialize( buf, true )) { Warning( "Error reading material \"%s\"\n", pCacheFileName ); g_pFullFileSystem->Close(fileHandle); } else { if ( pVTFTexture->Flags() & (TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA) ) { bUsesAlpha = true; } }
DestroyVTFTexture( pVTFTexture ); g_pFullFileSystem->Close( fileHandle ); return bUsesAlpha; }
//-----------------------------------------------------------------------------
// Does a texture have alpha?
//-----------------------------------------------------------------------------
static bool DoesTextureUseNormal( const char *pTextureName, const char *pMaterialName, bool &bUsesAlpha, bool &bIsCompressed, int &nSizeInBytes ) { nSizeInBytes = 0; bUsesAlpha = false;
if ( IsX360() ) { // not supporting
return false; }
// Special textures start with '_'..
if ( !pTextureName || ( pTextureName[0] == '_' ) || ( pTextureName[0] == 0 ) ) return false;
// The texture name doubles as the relative file name
// It's assumed to have already been set by this point
// Compute the cache name
char pCacheFileName[MATERIAL_MAX_PATH]; Q_snprintf( pCacheFileName, sizeof( pCacheFileName ), "materials/%s.vtf", pTextureName );
CUtlBuffer buf; FileHandle_t fileHandle = g_pFullFileSystem->Open( pCacheFileName, "rb" ); if ( fileHandle == FILESYSTEM_INVALID_HANDLE) { // Warning( "Material \"%s\": can't open texture \"%s\"\n", pMaterialName, pCacheFileName );
return false; }
// Check the .vtf for an alpha channel
IVTFTexture *pVTFTexture = CreateVTFTexture();
int nHeaderSize = VTFFileHeaderSize( VTF_MAJOR_VERSION ); buf.EnsureCapacity( nHeaderSize );
// read the header first.. it's faster!!
g_pFullFileSystem->Read( buf.Base(), nHeaderSize, fileHandle ); buf.SeekPut( CUtlBuffer::SEEK_HEAD, nHeaderSize );
// Unserialize the header
bool bUsesNormal = false; if ( !pVTFTexture->Unserialize( buf, true ) ) { Warning( "Error reading material \"%s\"\n", pCacheFileName ); } else { if ( pVTFTexture->Flags() & TEXTUREFLAGS_NORMAL ) { bUsesAlpha = false; bUsesNormal = true; bIsCompressed = ImageLoader::IsCompressed( pVTFTexture->Format() ) || ( pVTFTexture->Format() == IMAGE_FORMAT_A8 ); nSizeInBytes = pVTFTexture->ComputeTotalSize(); if ( pVTFTexture->Flags() & (TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA) ) { bUsesAlpha = true; } } }
DestroyVTFTexture( pVTFTexture ); g_pFullFileSystem->Close( fileHandle ); return bUsesNormal; }
//-----------------------------------------------------------------------------
// Is this a real texture
//-----------------------------------------------------------------------------
static bool IsTexture( const char *pTextureName ) { // Special textures start with '_'..
if ( pTextureName[0] == '_' ) return false;
// The texture name doubles as the relative file name
// It's assumed to have already been set by this point
// Compute the cache name
char pCacheFileName[MATERIAL_MAX_PATH]; Q_snprintf( pCacheFileName, sizeof( pCacheFileName ), "materials/%s.vtf", pTextureName );
FileHandle_t fileHandle = g_pFullFileSystem->Open( pCacheFileName, "rb" ); if ( fileHandle == FILESYSTEM_INVALID_HANDLE) return false;
g_pFullFileSystem->Close( fileHandle ); return true; }
//-----------------------------------------------------------------------------
// Scan material + all subsections for key
//-----------------------------------------------------------------------------
static float MaterialFloatKeyValue( KeyValues *pKeyValues, const char *pKeyName, float flDefault ) { float flValue = pKeyValues->GetFloat( pKeyName, flDefault ); if ( flValue != flDefault ) return flValue;
for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) { float flValue = MaterialFloatKeyValue( pSubKey, pKeyName, flDefault ); if ( flValue != flDefault ) return flValue; } return flDefault; }
int ParseVectorFromKeyValueString( KeyValues *pKeyValue, const char *pMaterialName, float vecVal[4] );
static bool AsVectorsEqual( int nDim1, float *pVector1, int nDim2, float *pVector2 ) { if ( nDim1 != nDim2 ) return false;
for ( int i = 0; i < nDim1; ++i ) { if ( fabs( pVector1[i] - pVector2[i] ) > 1e-3 ) return false; }
return true; }
static bool MaterialVectorKeyValue( KeyValues *pKeyValues, const char *pKeyName, int nDefaultDim, float *pDefault, int *pDim, float *pVector ) { int nDim; float retVal[4];
KeyValues *pValue = pKeyValues->FindKey( pKeyName ); if ( pValue ) { switch( pValue->GetDataType() ) { case KeyValues::TYPE_INT: { int nInt = pValue->GetInt(); for ( int i = 0; i < 4; ++i ) { retVal[i] = nInt; } if ( !AsVectorsEqual( nDefaultDim, pDefault, nDefaultDim, retVal ) ) { *pDim = nDefaultDim; memcpy( pVector, retVal, nDefaultDim * sizeof(float) ); return true; } } break;
case KeyValues::TYPE_FLOAT: { float flFloat = pValue->GetFloat(); for ( int i = 0; i < 4; ++i ) { retVal[i] = flFloat; } if ( !AsVectorsEqual( nDefaultDim, pDefault, nDefaultDim, retVal ) ) { *pDim = nDefaultDim; memcpy( pVector, retVal, nDefaultDim * sizeof(float) ); return true; } } break;
case KeyValues::TYPE_STRING: { nDim = ParseVectorFromKeyValueString( pValue, "", retVal ); if ( !AsVectorsEqual( nDefaultDim, pDefault, nDim, retVal ) ) { *pDim = nDim; memcpy( pVector, retVal, nDim * sizeof(float) ); return true; } } break; } }
for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) { if ( MaterialVectorKeyValue( pSubKey, pKeyName, nDefaultDim, pDefault, &nDim, retVal ) ) { *pDim = nDim; memcpy( pVector, retVal, nDim * sizeof(float) ); return true; } } *pDim = nDefaultDim; memcpy( pVector, pDefault, nDefaultDim * sizeof(float) ); return false; }
//-----------------------------------------------------------------------------
// Scan material + all subsections for key
//-----------------------------------------------------------------------------
static bool DoesMaterialHaveKey( KeyValues *pKeyValues, const char *pKeyName ) { if ( pKeyValues->GetString( pKeyName, NULL ) != NULL ) return true;
for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) { if ( DoesMaterialHaveKey( pSubKey, pKeyName ) ) return true; } return false; }
//-----------------------------------------------------------------------------
// Scan all materials for errors
//-----------------------------------------------------------------------------
static int s_nNormalBytes; static int s_nNormalCompressedBytes; static int s_nNormalPalettizedBytes; static int s_nNormalWithAlphaBytes; static int s_nNormalWithAlphaCompressedBytes;
struct VTFInfo_t { CUtlString m_VTFName; bool m_bFoundInVMT; };
void CheckKeyValues( KeyValues *pKeyValues, CUtlVector<VTFInfo_t> &vtf ) { for ( KeyValues *pSubKey = pKeyValues->GetFirstValue(); pSubKey; pSubKey = pSubKey->GetNextValue() ) { if ( pSubKey->GetDataType() != KeyValues::TYPE_STRING ) continue; if ( IsTexture( pSubKey->GetString() ) ) { int nLen = Q_strlen( pSubKey->GetString() ) + 1; char *pTemp = (char*)_alloca( nLen ); memcpy( pTemp, pSubKey->GetString(), nLen ); Q_FixSlashes( pTemp );
int nCount = vtf.Count(); for ( int i = 0; i < nCount; ++i ) { if ( Q_stricmp( vtf[i].m_VTFName, pTemp ) ) continue; vtf[i].m_bFoundInVMT = true; break; } } }
for ( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) { CheckKeyValues( pSubKey, vtf ); } }
void CheckMaterial( KeyValues *pKeyValues, const char *pRoot, const char *pFileName, CUtlVector<VTFInfo_t> &vtf ) { const char *pShaderName = pKeyValues->GetName(); /*
if ( Q_stristr( pShaderName, "Water" ) || Q_stristr( pShaderName, "Eyeball" ) || Q_stristr( pShaderName, "Shadow" ) || Q_stristr( pShaderName, "Refract" ) || Q_stristr( pShaderName, "Predator" ) || Q_stristr( pShaderName, "ParticleSphere" ) || Q_stristr( pShaderName, "DebugLuxels" ) || Q_stristr( pShaderName, "GooInGlass" ) || Q_stristr( pShaderName, "Modulate" ) || Q_stristr( pShaderName, "UnlitTwoTexture" ) || Q_stristr( pShaderName, "Cloud" ) || Q_stristr( pShaderName, "WorldVertexTransition" ) || Q_stristr( pShaderName, "DecalModulate" ) || Q_stristr( pShaderName, "DecalBaseTimesLightmapAlphaBlendSelfIllum" ) || Q_stristr( pShaderName, "Sprite" ) ) { return; }
// Check for alpha channels
const char *pBaseTextureName = pKeyValues->GetString( "$basetexture", NULL ); if ( pBaseTextureName != NULL ) { if ( DoesTextureUseAlpha( pBaseTextureName, pFileName ) ) { float flAlpha = MaterialFloatKeyValue( pKeyValues, "$alpha", 1.0f ); bool bHasVertexAlpha = DoesMaterialHaveKey( pKeyValues, "$vertexalpha" ); // Modulation always happens here whether we want it to or not
bool bHasAlphaTest = DoesMaterialHaveKey( pKeyValues, "$alphatest" ); bool bHasTranslucent = DoesMaterialHaveKey( pKeyValues, "$translucent" ); bool bHasSelfIllum = DoesMaterialHaveKey( pKeyValues, "$selfillum" ); bool bHasBaseAlphaEnvMapMask = DoesMaterialHaveKey( pKeyValues, "$basealphaenvmapmask" ); if ( (flAlpha == 1.0f) && !bHasVertexAlpha && !bHasAlphaTest && !bHasTranslucent && !bHasSelfIllum && !bHasBaseAlphaEnvMapMask ) { Warning("Material \"%s\": BASETEXTURE \"%s\"\n", pFileName, pBaseTextureName ); } } } */
/*
// Check for bump, spec, and no normalmapalphaenvmapmask
const char *pBumpmapName = pKeyValues->GetString( "$bumpmap", NULL ); if ( pBumpmapName != NULL ) { if ( DoesTextureUseAlpha( pBumpmapName, pFileName ) ) { bool bHasEnvmap = DoesMaterialHaveKey( pKeyValues, "$envmap" ); bool bHasNormalMapAlphaEnvMapMask = DoesMaterialHaveKey( pKeyValues, "$normalmapalphaenvmapmask" ); if ( !bHasEnvmap || !bHasNormalMapAlphaEnvMapMask ) { Warning("Material \"%s\": BUMPMAP \"%s\"\n", pFileName, pBumpmapName ); } } } */
/*
if ( !Q_stristr( pShaderName, "LightmappedGeneric" ) && !Q_stristr( pShaderName, "VertexLitGeneric" ) ) { return; }
if ( DoesMaterialHaveKey( pKeyValues, "$envmap" ) && DoesMaterialHaveKey( pKeyValues, "$bumpmap" ) ) { int nDim; float retVal[4]; float defaultVal[4] = { 1, 1, 1, 1 }; if ( MaterialVectorKeyValue( pKeyValues, "$envmaptint", 3, defaultVal, &nDim, retVal ) ) { Warning("ENVMAP + ENVMAPTINT : Material \"%s\"\n", pFileName ); } // else
// {
// Warning("ENVMAP only: Material \"%s\"\n", pFileName );
// }
} */
/*
if ( !Q_stristr( pShaderName, "Refract" ) ) { return; }
if ( !DoesMaterialHaveKey( pKeyValues, "$envmap" ) ) { bool bUsesAlpha, bIsCompressed, bIsPalettized; int nSizeInBytes; if ( DoesTextureUseNormal( pKeyValues->GetString( "$normalmap" ), pFileName, bUsesAlpha, bIsCompressed, bIsPalettized, nSizeInBytes ) ) { if ( bIsCompressed ) { Warning("Bad : Material compressed \"%s\"\n", pFileName ); } else { Warning("Bad : Material \"%s\"\n", pFileName ); } } } */
/*
if ( !Q_stristr( pShaderName, "WorldTwoTextureBlend" ) ) { return; }
if ( DoesMaterialHaveKey( pKeyValues, "$envmap" ) || DoesMaterialHaveKey( pKeyValues, "$parallaxmap" ) || DoesMaterialHaveKey( pKeyValues, "$bumpmap" ) || DoesMaterialHaveKey( pKeyValues, "$vertexcolor" ) || DoesMaterialHaveKey( pKeyValues, "$basetexture2" ) ) { Warning("Bad : Material \"%s\"\n", pFileName ); } */
for ( KeyValues *pSubKey = pKeyValues->GetFirstValue(); pSubKey; pSubKey = pSubKey->GetNextValue() ) { // Msg( " Checking %s\n", pSubKey->GetString() );
if ( pSubKey->GetDataType() != KeyValues::TYPE_STRING ) continue; bool bUsesAlpha, bIsCompressed; int nSizeInBytes; if ( DoesTextureUseNormal( pSubKey->GetString(), pFileName, bUsesAlpha, bIsCompressed, nSizeInBytes ) ) { if ( bUsesAlpha ) { if ( bIsCompressed ) { s_nNormalWithAlphaCompressedBytes += nSizeInBytes; } else { s_nNormalWithAlphaBytes += nSizeInBytes; Msg( "Normal texture w alpha uncompressed %s\n", pSubKey->GetString() ); } } else { if ( bIsCompressed ) { s_nNormalCompressedBytes += nSizeInBytes; } else { s_nNormalBytes += nSizeInBytes; } } } }
/*
if ( !Q_stristr( pShaderName, "VertexLitGeneric" ) ) return;
if ( !DoesMaterialHaveKey( pKeyValues, "$envmap" ) && DoesMaterialHaveKey( pKeyValues, "$bumpmap" ) ) { Warning("BUMPMAP + no ENVMAP : Material \"%s\"\n", pFileName ); } */
// CheckKeyValues( pKeyValues, vtf );
}
//-----------------------------------------------------------------------------
// Build list of all VTFs
//-----------------------------------------------------------------------------
void CheckVTFInDirectoryRecursive( const char *pRoot, const char *pDirectory, CUtlVector< VTFInfo_t > &vtf ) { #define BUF_SIZE 1024
char buf[BUF_SIZE]; WIN32_FIND_DATA wfd; HANDLE findHandle; sprintf( buf, "%s/%s/*.vtf", pRoot, pDirectory );
findHandle = FindFirstFile( buf, &wfd ); if ( findHandle != INVALID_HANDLE_VALUE ) { do { int i = vtf.AddToTail( );
char buf[MAX_PATH]; char buf2[MAX_PATH]; Q_snprintf( buf, MAX_PATH, "%s/%s", pDirectory, wfd.cFileName ); Q_FixSlashes( buf );
Q_StripExtension( buf, buf2, sizeof(buf2) ); Assert( !Q_strnicmp( buf2, "materials\\", 10 ) );
vtf[i].m_VTFName = &buf2[10]; vtf[i].m_bFoundInVMT = false;
} while ( FindNextFile ( findHandle, &wfd ) ); FindClose ( findHandle ); }
// do subdirectories
sprintf( buf, "%s/%s/*.*", pRoot, pDirectory ); findHandle = FindFirstFile( buf, &wfd ); if ( findHandle != INVALID_HANDLE_VALUE ) { do { if( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { if( ( strcmp( wfd.cFileName, ".." ) == 0 ) || ( strcmp( wfd.cFileName, "." ) == 0 ) ) { continue; }
char buf[MAX_PATH]; Q_snprintf( buf, MAX_PATH, "%s/%s", pDirectory, wfd.cFileName ); CheckVTFInDirectoryRecursive( pRoot, buf, vtf ); } } while ( FindNextFile ( findHandle, &wfd ) ); FindClose ( findHandle ); }
#undef BUF_SIZE
}
//-----------------------------------------------------------------------------
// Scan all materials for errors
//-----------------------------------------------------------------------------
void _CheckMateralsInDirectoryRecursive( const char *pRoot, const char *pDirectory, CUtlVector< VTFInfo_t > &vtf ) { #define BUF_SIZE 1024
char buf[BUF_SIZE]; WIN32_FIND_DATA wfd; HANDLE findHandle; sprintf( buf, "%s/%s/*.vmt", pRoot, pDirectory ); findHandle = FindFirstFile( buf, &wfd ); if ( findHandle != INVALID_HANDLE_VALUE ) { do { KeyValues * vmtKeyValues = new KeyValues("vmt");
char pFileName[MAX_PATH]; Q_snprintf( pFileName, sizeof( pFileName ), "%s/%s", pDirectory, wfd.cFileName ); if ( !vmtKeyValues->LoadFromFile( g_pFullFileSystem, pFileName, "GAME" ) ) { Warning( "CheckMateralsInDirectoryRecursive: can't open \"%s\"\n", pFileName ); continue; }
CheckMaterial( vmtKeyValues, pRoot, pFileName, vtf );
vmtKeyValues->deleteThis();
} while ( FindNextFile ( findHandle, &wfd ) ); FindClose ( findHandle ); }
// do subdirectories
sprintf( buf, "%s/%s/*.*", pRoot, pDirectory ); findHandle = FindFirstFile( buf, &wfd ); if ( findHandle != INVALID_HANDLE_VALUE ) { do { if( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { if( ( strcmp( wfd.cFileName, ".." ) == 0 ) || ( strcmp( wfd.cFileName, "." ) == 0 ) ) { continue; }
char buf[MAX_PATH]; Q_snprintf( buf, MAX_PATH, "%s/%s", pDirectory, wfd.cFileName ); _CheckMateralsInDirectoryRecursive( pRoot, buf, vtf ); } } while ( FindNextFile ( findHandle, &wfd ) ); FindClose ( findHandle ); }
// Msg( "Normal only %d/%d/%d Normal w alpha %d/%d\n", s_nNormalBytes, s_nNormalPalettizedBytes, s_nNormalCompressedBytes, s_nNormalWithAlphaBytes, s_nNormalWithAlphaCompressedBytes );
#undef BUF_SIZE
}
void CheckMateralsInDirectoryRecursive( const char *pRoot, const char *pDirectory ) { CUtlVector< VTFInfo_t > vtfNames; // CheckVTFInDirectoryRecursive( pRoot, pDirectory, vtfNames );
_CheckMateralsInDirectoryRecursive( pRoot, pDirectory, vtfNames );
/*
int nCount = vtfNames.Count(); for ( int i = 0; i < nCount; ++i ) { if ( !vtfNames[i].m_bFoundInVMT ) { Msg( "Unused VTF %s\n", vtfNames[i].m_VTFName ); } } */ }
#endif // _CHECK_MATERIALS_FOR_PROBLEMS
|