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();
}
Beispiel #6
0
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] );
		}
	}
}
Beispiel #10
0
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;
		}
	}
}
Beispiel #13
0
//-----------------------------------------------------------------------------
// 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 );
}