//========= 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 &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 &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