void CTextureManager::DebugPrintUsedTextures( void ) { for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) { ITextureInternal *pTexture = m_TextureList[i]; Msg( "Texture: '%s' RefCount: %d\n", pTexture->GetName(), pTexture->GetReferenceCount() ); } if ( m_TextureExcludes.Count() ) { Msg( "\nExcluded Textures: (%d)\n", m_TextureExcludes.Count() ); for ( int i = m_TextureExcludes.First(); i != m_TextureExcludes.InvalidIndex(); i = m_TextureExcludes.Next( i ) ) { char buff[256]; const char *pName = m_TextureExcludes.GetElementName( i ); V_snprintf( buff, sizeof( buff ), "Excluded: %d '%s' \n", m_TextureExcludes[i], pName ); // an excluded texture is valid, but forced tiny if ( IsTextureLoaded( pName ) ) { Msg( buff ); } else { // warn as unknown, could be a spelling error Warning( buff ); } } } }
void CTextureManager::ResetTextureFilteringState( ) { for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) { m_TextureList[i]->SetFilteringAndClampingMode(); } }
void CTextureManager::UpdateExcludedTextures( void ) { for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) { m_TextureList[i]->UpdateExcludedState(); } }
//----------------------------------------------------------------------------- // Reloads all textures //----------------------------------------------------------------------------- void CTextureManager::ReloadTextures() { for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) { // Put the texture back onto the board m_TextureList[i]->Download(); } }
void ResetTriggerFactoryDatabase( void ) { for ( int i=m_TriggerFactoryDatabase.First(); i != m_TriggerFactoryDatabase.InvalidIndex(); i=m_TriggerFactoryDatabase.Next( i ) ) { delete m_TriggerFactoryDatabase[ i ]; } m_TriggerFactoryDatabase.RemoveAll(); }
void ClearKeyValuesCache() { MEM_ALLOC_CREDIT(); for ( int i=g_KeyValuesCache.First(); i != g_KeyValuesCache.InvalidIndex(); i=g_KeyValuesCache.Next( i ) ) { g_KeyValuesCache[i]->deleteThis(); } g_KeyValuesCache.Purge(); }
//----------------------------------------------------------------------------- // Releases all textures (cause we've lost video memory) //----------------------------------------------------------------------------- void CTextureManager::ReleaseTextures( void ) { g_pShaderAPI->SetFullScreenTextureHandle( INVALID_SHADERAPI_TEXTURE_HANDLE ); for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) { // Release the texture... m_TextureList[i]->Release(); } }
void CTextureManager::ReloadFilesInList( IFileList *pFilesToReload ) { if ( !IsPC() ) return; for ( int i=m_TextureList.First(); i != m_TextureList.InvalidIndex(); i=m_TextureList.Next( i ) ) { ITextureInternal *pTex = m_TextureList[i]; pTex->ReloadFilesInList( pFilesToReload ); } }
//----------------------------------------------------------------------------- // Restore all textures (cause we've got video memory again) //----------------------------------------------------------------------------- void CTextureManager::RestoreNonRenderTargetTextures( ) { // 360 should not have gotten here Assert( !IsX360() ); for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) { if ( !m_TextureList[i]->IsRenderTarget() ) { RestoreTexture( m_TextureList[i] ); } } }
PyEntityFactory* CClassMap::PyGetFactoryByMapName( const char *mapname ) { unsigned short idx = m_ClassDict.First(); while( idx != m_ClassDict.InvalidIndex() ) { if( !Q_stricmp (m_ClassDict[idx].GetMapName(), mapname ) ) { return m_ClassDict[idx].pyfactory; } idx = m_ClassDict.Next( idx ); } return NULL; }
void ResetEntityFactoryDatabase( void ) { #ifdef CLIENT_DLL #ifdef LUA_SDK GetClassMap().RemoveAllScripted(); #endif #else for ( int i=m_EntityFactoryDatabase.First(); i != m_EntityFactoryDatabase.InvalidIndex(); i=m_EntityFactoryDatabase.Next( i ) ) { delete m_EntityFactoryDatabase[ i ]; } m_EntityFactoryDatabase.RemoveAll(); #endif }
void CTextureManager::RemoveTexture( ITextureInternal *pTexture ) { Assert( pTexture->GetReferenceCount() <= 0 ); for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) { // search by object if ( m_TextureList[i] == pTexture ) { ITextureInternal::Destroy( m_TextureList[i] ); m_TextureList.RemoveAt( i ); break; } } }
//----------------------------------------------------------------------------- // Loads up a file containing metaclass definitions //----------------------------------------------------------------------------- void CPanelMetaClassMgrImp::LoadMetaClassDefinitionFile( const char *pFileName ) { MEM_ALLOC_CREDIT(); // Blat out previous metaclass definitions read in from this file... int i = m_MetaClassKeyValues.Find( pFileName ); if (i != m_MetaClassKeyValues.InvalidIndex() ) { // Blow away the previous keyvalues from that file unsigned short j = m_MetaClassDict.First(); while ( j != m_MetaClassDict.InvalidIndex() ) { unsigned short next = m_MetaClassDict.Next(j); if ( m_MetaClassDict[j].m_KeyValueIndex == i) { m_MetaClassDict.RemoveAt(j); } j = next; } m_MetaClassKeyValues[i]->deleteThis(); m_MetaClassKeyValues.RemoveAt(i); } // Create a new keyvalues entry KeyValues* pKeyValues = new KeyValues(pFileName); int idx = m_MetaClassKeyValues.Insert( pFileName, pKeyValues ); // Read in all metaclass definitions... // Load the file if ( !pKeyValues->LoadFromFile( filesystem, pFileName ) ) { Warning( "Couldn't find metaclass definition file %s\n", pFileName ); pKeyValues->deleteThis(); m_MetaClassKeyValues.RemoveAt(idx); return; } else { // Go ahead and parse the data now if ( !ParseMetaClassList( pFileName, pKeyValues, idx ) ) { Warning( "Detected one or more errors parsing %s\n", pFileName ); } } }
DISPATCHFUNCTION CClassMap::FindFactory( const char *classname ) { for ( int i=m_ClassDict.First(); i != m_ClassDict.InvalidIndex(); i=m_ClassDict.Next( i ) ) { classentry_t *lookup = &m_ClassDict[ i ]; if ( !lookup ) continue; if ( Q_stricmp( lookup->GetMapName(), classname ) ) continue; return lookup->factory; } return NULL; }
void CTextureManager::ReleaseTempRenderTargetBits( void ) { if( IsX360() ) //only sane on 360 { int iNext; for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = iNext ) { iNext = m_TextureList.Next( i ); if ( m_TextureList[i]->IsTempRenderTarget() ) { m_TextureList[i]->Release(); } } } }
void CTextureManager::RemoveUnusedTextures( void ) { int iNext; for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = iNext ) { iNext = m_TextureList.Next( i ); #ifdef _DEBUG if ( m_TextureList[i]->GetReferenceCount() < 0 ) { Warning( "RemoveUnusedTextures: pTexture->m_referenceCount < 0 for %s\n", m_TextureList[i]->GetName() ); } #endif if ( m_TextureList[i]->GetReferenceCount() <= 0 ) { ITextureInternal::Destroy( m_TextureList[i] ); m_TextureList.RemoveAt( i ); } } }
//----------------------------------------------------------------------------- // Reloads all textures //----------------------------------------------------------------------------- void CTextureManager::ForceAllTexturesIntoHardware( void ) { if ( IsX360() ) return; IMaterial *pMaterial = MaterialSystem()->FindMaterial( "engine/preloadtexture", "texture preload" ); pMaterial = ((IMaterialInternal *)pMaterial)->GetRealTimeVersion(); //always work with the realtime material internally bool bFound; IMaterialVar *pBaseTextureVar = pMaterial->FindVar( "$basetexture", &bFound ); if( !bFound ) { return; } for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) { // Put the texture back onto the board ForceTextureIntoHardware( m_TextureList[i], pMaterial, pBaseTextureVar ); } }
//----------------------------------------------------------------------------- // Restore just the render targets (cause we've got video memory again) //----------------------------------------------------------------------------- void CTextureManager::RestoreRenderTargets() { // 360 should not have gotten here Assert( !IsX360() ); for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) { if ( m_TextureList[i]->IsRenderTarget() ) { RestoreTexture( m_TextureList[i] ); } } if ( m_pFullScreenTexture ) { g_pShaderAPI->SetFullScreenTextureHandle( m_pFullScreenTexture->GetTextureHandle( 0 ) ); } CacheExternalStandardRenderTargets(); }
void CClassMap::Add( const char *mapname, const char *classname, int size, DISPATCHFUNCTION factory = 0 ) #endif { #if defined ( LUA_SDK ) for ( int i=m_ClassDict.First(); i != m_ClassDict.InvalidIndex(); i=m_ClassDict.Next( i ) ) { classentry_t *lookup = &m_ClassDict[ i ]; if ( !lookup ) continue; if ( !Q_stricmp( lookup->GetMapName(), mapname ) ) { m_ClassDict.RemoveAt( i ); } } #else const char *map = Lookup( classname ); if ( map && !Q_strcasecmp( mapname, map ) ) return; if ( map ) { int index = m_ClassDict.Find( classname ); Assert( index != m_ClassDict.InvalidIndex() ); m_ClassDict.RemoveAt( index ); } #endif classentry_t element; element.SetMapName( mapname ); element.factory = factory; element.size = size; #if defined ( LUA_SDK ) element.SetClassName( classname ); element.scripted = scripted; m_ClassDict.Insert( mapname, element ); #else m_ClassDict.Insert( classname, element ); #endif }
int CTextureManager::FindNext( int iIndex, ITextureInternal **pTexInternal ) { if ( iIndex == -1 && m_TextureList.Count() ) { iIndex = m_TextureList.First(); } else if ( !m_TextureList.Count() || !m_TextureList.IsValidIndex( iIndex ) ) { *pTexInternal = NULL; return -1; } *pTexInternal = m_TextureList[iIndex]; iIndex = m_TextureList.Next( iIndex ); if ( iIndex == m_TextureList.InvalidIndex() ) { // end of list iIndex = -1; } return iIndex; }
void CTextureManager::Shutdown() { FreeStandardRenderTargets(); // These checks added because it's possible for shutdown to be called before the material system is // fully initialized. if ( m_pWhiteTexture ) { m_pWhiteTexture->DecrementReferenceCount(); m_pWhiteTexture = NULL; } if ( m_pBlackTexture ) { m_pBlackTexture->DecrementReferenceCount(); m_pBlackTexture = NULL; } if ( m_pGreyTexture ) { m_pGreyTexture->DecrementReferenceCount(); m_pGreyTexture = NULL; } if ( m_pGreyAlphaZeroTexture ) { m_pGreyAlphaZeroTexture->DecrementReferenceCount(); m_pGreyAlphaZeroTexture = NULL; } if ( m_pNormalizationCubemap ) { m_pNormalizationCubemap->DecrementReferenceCount(); m_pNormalizationCubemap = NULL; } if ( m_pSignedNormalizationCubemap ) { m_pSignedNormalizationCubemap->DecrementReferenceCount(); m_pSignedNormalizationCubemap = NULL; } if ( m_pShadowNoise2D ) { m_pShadowNoise2D->DecrementReferenceCount(); m_pShadowNoise2D = NULL; } if ( m_pIdentityLightWarp ) { m_pIdentityLightWarp->DecrementReferenceCount(); m_pIdentityLightWarp = NULL; } if ( m_pErrorTexture ) { m_pErrorTexture->DecrementReferenceCount(); m_pErrorTexture = NULL; } ReleaseTextures(); if ( m_pErrorRegen ) { m_pErrorRegen->Release(); m_pErrorRegen = NULL; } for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) { ITextureInternal::Destroy( m_TextureList[i] ); } m_TextureList.RemoveAll(); for( int i = m_TextureAliases.First(); i != m_TextureAliases.InvalidIndex(); i = m_TextureAliases.Next( i ) ) { delete []m_TextureAliases[i]; } m_TextureAliases.RemoveAll(); m_TextureExcludes.RemoveAll(); }
void CTextureManager::SetExcludedTextures( const char *pScriptName ) { // clear all exisiting texture's exclusion for ( int i = m_TextureExcludes.First(); i != m_TextureExcludes.InvalidIndex(); i = m_TextureExcludes.Next( i ) ) { ITextureInternal *pTexture = FindTexture( m_TextureExcludes.GetElementName( i ) ); if ( pTexture ) { pTexture->MarkAsExcluded( false, 0 ); } } m_TextureExcludes.RemoveAll(); MEM_ALLOC_CREDIT(); // get optional script CUtlBuffer excludeBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER ); if ( g_pFullFileSystem->ReadFile( pScriptName, NULL, excludeBuffer ) ) { char szToken[MAX_PATH]; while ( 1 ) { // must support spaces in names without quotes // have to brute force parse up to a valid line while ( 1 ) { excludeBuffer.EatWhiteSpace(); if ( !excludeBuffer.EatCPPComment() ) { // not a comment break; } } excludeBuffer.GetLine( szToken, sizeof( szToken ) ); int tokenLength = strlen( szToken ); if ( !tokenLength ) { // end of list break; } // remove all trailing whitespace while ( tokenLength > 0 ) { tokenLength--; if ( isgraph( szToken[tokenLength] ) ) { break; } szToken[tokenLength] = '\0'; } // first optional token may be a dimension limit hint int nDimensionsLimit = 0; char *pTextureName = szToken; if ( pTextureName[0] != 0 && isdigit( pTextureName[0] ) ) { nDimensionsLimit = atoi( pTextureName ); // skip forward to name for ( ;; ) { char ch = *pTextureName; if ( !ch || ( !isdigit( ch ) && !isspace( ch ) ) ) { break; } pTextureName++; } } char szCleanName[MAX_PATH]; NormalizeTextureName( pTextureName, szCleanName, sizeof( szCleanName ) ); if ( m_TextureExcludes.Find( szCleanName ) != m_TextureExcludes.InvalidIndex() ) { // avoid duplicates continue; } m_TextureExcludes.Insert( szCleanName, nDimensionsLimit ); // set any existing texture's exclusion // textures that don't exist yet will get caught during their creation path ITextureInternal *pTexture = FindTexture( szCleanName ); if ( pTexture ) { pTexture->MarkAsExcluded( ( nDimensionsLimit == 0 ), nDimensionsLimit ); } } } }
void Correlate( CUtlRBTree< ReferencedFile, int >& referencedfiles, CUtlVector< FileEntry >& contentfiles, const char *modname ) { int i; int c = contentfiles.Count(); double totalDiskSize = 0; double totalReferencedDiskSize = 0; double totalWhiteListDiskSize = 0; for ( i = 0; i < c; ++i ) { totalDiskSize += contentfiles [ i ].size; } vprint( 0, "Content tree size on disk %s\n", Q_pretifymem( totalDiskSize, 3 ) ); // Analysis is to walk tree and see which files on disk are referenced in the .lst files // Need a fast lookup from file symbol to referenced list CUtlRBTree< ReferencedFile, int > tree( 0, 0, ReferencedFileLessFunc ); c = referencedfiles.Count(); for ( i = 0 ; i < c; ++i ) { tree.Insert( referencedfiles[ i ] ); } // Now walk the on disk file and see check off resources which are in referenced c = contentfiles.Count(); int invalidindex = tree.InvalidIndex(); unsigned int refcounted = 0; unsigned int whitelisted = 0; filesystem->RemoveFile( CFmtStr( "%swhitelist.lst", g_szReslistDir ), "GAME" ); for ( i = 0; i < c; ++i ) { FileEntry & entry = contentfiles[ i ]; ReferencedFile foo; foo.sym = entry.sym; bool gameref = tree.Find( foo ) != invalidindex; char const *fn = g_Analysis.symbols.String( entry.sym ); bool whitelist = g_WhiteList.Find( entry.sym ) != g_WhiteList.InvalidIndex(); if ( gameref || whitelist ) { entry.referenced = gameref ? REFERENCED_GAME : REFERENCED_WHITELIST; totalReferencedDiskSize += entry.size; if ( entry.referenced == REFERENCED_WHITELIST ) { logprint( CFmtStr( "%swhitelist.lst", g_szReslistDir ), "\"%s\\%s\"\n", modname, fn ); totalWhiteListDiskSize += entry.size; ++whitelisted; } ++refcounted; } } vprint( 0, "Found %i referenced (%i whitelist) files in tree, %s\n", refcounted, whitelisted, Q_pretifymem( totalReferencedDiskSize, 2 ) ); vprint( 0, "%s appear unused\n", Q_pretifymem( totalDiskSize - totalReferencedDiskSize, 2 ) ); // Now sort and dump the unreferenced ones.. vprint( 0, "Sorting unreferenced files list...\n" ); CUtlRBTree< FileEntry, int > unreftree( 0, 0, FileEntryLessFunc ); for ( i = 0; i < c; ++i ) { FileEntry & entry = contentfiles[ i ]; if ( entry.referenced != REFERENCED_NO ) continue; unreftree.Insert( entry ); } // Now walk the unref tree in order i = unreftree.FirstInorder(); invalidindex = unreftree.InvalidIndex(); int index = 0; while ( i != invalidindex ) { FileEntry & entry = unreftree[ i ]; if ( showreferencedfiles ) { vprint( 1, "%6i %12s: %s\n", ++index, Q_pretifymem( entry.size, 2 ), g_Analysis.symbols.String( entry.sym ) ); } i = unreftree.NextInorder( i ); } if ( showmapfileusage ) { vprint( 0, "Writing referenced.csv...\n" ); // Now walk the list of referenced files and print out how many and which maps reference them i = tree.FirstInorder(); invalidindex = tree.InvalidIndex(); index = 0; while ( i != invalidindex ) { ReferencedFile & entry = tree[ i ]; char ext[ 32 ]; Q_ExtractFileExtension( g_Analysis.symbols.String( entry.sym ), ext, sizeof( ext ) ); logprint( "referenced.csv", "\"%s\",\"%s\",%d", g_Analysis.symbols.String( entry.sym ), ext, entry.maplist.Count() ); int mapcount = entry.maplist.Count(); for ( int j = 0 ; j < mapcount; ++j ) { char basemap[ 128 ]; Q_FileBase( g_Analysis.symbols.String( entry.maplist[ j ] ), basemap, sizeof( basemap ) ); logprint( "referenced.csv", ",\"%s\"", basemap ); } logprint( "referenced.csv", "\n" ); i = tree.NextInorder( i ); } } vprint( 0, "\nBuilding directory summary list...\n" ); // Now build summaries by root branch off of gamedir (e.g., for sound, materials, models, etc.) CUtlDict< DirEntry, int > directories; invalidindex = directories.InvalidIndex(); for ( i = 0; i < c; ++i ) { FileEntry & entry = contentfiles[ i ]; // Get the dir name char const *dirname = g_Analysis.symbols.String( entry.sym ); const char *backslash = strstr( dirname, "\\" ); char dir[ 256 ]; if ( !backslash ) { dir[0] = 0; } else { Q_strncpy( dir, dirname, backslash - dirname + 1); } int idx = directories.Find( dir ); if ( idx == invalidindex ) { DirEntry foo; idx = directories.Insert( dir, foo ); } DirEntry & de = directories[ idx ]; de.total += entry.size; if ( entry.referenced == REFERENCED_NO ) { de.unreferenced += entry.size; } if ( entry.referenced == REFERENCED_WHITELIST ) { de.whitelist += entry.size; } } if ( spewdeletions ) { // Spew deletion commands to console if ( immediatedelete ) { vprint( 0, "\n\nDeleting files...\n" ); } else { vprint( 0, "\n\nGenerating deletions.bat\n" ); } i = unreftree.FirstInorder(); invalidindex = unreftree.InvalidIndex(); float deletionSize = 0.0f; int deletionCount = 0; while ( i != invalidindex ) { FileEntry & entry = unreftree[ i ]; i = unreftree.NextInorder( i ); // Don't delete stuff that's in the white list if ( g_WhiteList.Find( entry.sym ) != g_WhiteList.InvalidIndex() ) { if ( verbose ) { vprint( 0, "whitelist blocked deletion of %s\n", g_Analysis.symbols.String( entry.sym ) ); } continue; } ++deletionCount; deletionSize += entry.size; if ( immediatedelete ) { if ( _chmod( g_Analysis.symbols.String( entry.sym ), _S_IWRITE ) == -1 ) { vprint( 0, "Could not find file %s\n", g_Analysis.symbols.String( entry.sym ) ); } if ( _unlink( g_Analysis.symbols.String( entry.sym ) ) == -1 ) { vprint( 0, "Could not delete file %s\n", g_Analysis.symbols.String( entry.sym ) ); } if ( deletionCount % 1000 == 0 ) { vprint( 0, "...deleted %i files\n", deletionCount ); } } else { logprint( "deletions.bat", "del \"%s\" /f\n", g_Analysis.symbols.String( entry.sym ) ); } } vprint( 0, "\nFile deletion (%d files, %s)\n\n", deletionCount, Q_pretifymem(deletionSize, 2) ); } double grand_total = 0; double grand_total_unref = 0; double grand_total_white = 0; char totalstring[ 20 ]; char unrefstring[ 20 ]; char refstring[ 20 ]; char whiteliststring[ 20 ]; vprint( 0, "---------------------------------------- Summary ----------------------------------------\n" ); vprint( 0, "% 15s % 15s % 15s % 15s %12s\n", "Referenced", "WhiteListed", "Unreferenced", "Total", "Directory" ); // Now walk the dictionary in order i = directories.First(); while ( i != invalidindex ) { DirEntry & de = directories[ i ]; double remainder = de.total - de.unreferenced; float percent_unref = 0.0f; float percent_white = 0.0f; if ( de.total > 0 ) { percent_unref = 100.0f * (float)de.unreferenced / (float)de.total; percent_white = 100.0f * (float)de.whitelist / (float)de.total; } Q_strncpy( totalstring, Q_pretifymem( de.total, 2 ), sizeof( totalstring ) ); Q_strncpy( unrefstring, Q_pretifymem( de.unreferenced, 2 ), sizeof( unrefstring ) ); Q_strncpy( refstring, Q_pretifymem( remainder, 2 ), sizeof( refstring ) ); Q_strncpy( whiteliststring, Q_pretifymem( de.whitelist, 2 ), sizeof( whiteliststring ) ); vprint( 0, "%15s (%8.3f%%) %15s (%8.3f%%) %15s (%8.3f%%) %15s => dir: %s\n", refstring, 100.0f - percent_unref, whiteliststring, percent_white, unrefstring, percent_unref, totalstring, directories.GetElementName( i ) ); grand_total += de.total; grand_total_unref += de.unreferenced; grand_total_white += de.whitelist; i = directories.Next( i ); } Q_strncpy( totalstring, Q_pretifymem( grand_total, 2 ), sizeof( totalstring ) ); Q_strncpy( unrefstring, Q_pretifymem( grand_total_unref, 2 ), sizeof( unrefstring ) ); Q_strncpy( refstring, Q_pretifymem( grand_total - grand_total_unref, 2 ), sizeof( refstring ) ); Q_strncpy( whiteliststring, Q_pretifymem( grand_total_white, 2 ), sizeof( whiteliststring ) ); double percent_unref = 100.0 * grand_total_unref / grand_total; double percent_white = 100.0 * grand_total_white / grand_total; vprint( 0, "-----------------------------------------------------------------------------------------\n" ); vprint( 0, "%15s (%8.3f%%) %15s (%8.3f%%) %15s (%8.3f%%) %15s\n", refstring, 100.0f - percent_unref, whiteliststring, percent_white, unrefstring, percent_unref, totalstring ); }