summaryrefslogtreecommitdiff
path: root/engine/vprof_record.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/vprof_record.cpp')
-rw-r--r--engine/vprof_record.cpp982
1 files changed, 982 insertions, 0 deletions
diff --git a/engine/vprof_record.cpp b/engine/vprof_record.cpp
new file mode 100644
index 0000000..99cac70
--- /dev/null
+++ b/engine/vprof_record.cpp
@@ -0,0 +1,982 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "tier0/vcrmode.h"
+#undef PROTECT_FILEIO_FUNCTIONS
+#include "tier0/vprof.h"
+#include "utldict.h"
+#include "client.h"
+#include "cmd.h"
+#include "filesystem_engine.h"
+#include "vprof_record.h"
+
+
+#ifdef VPROF_ENABLED
+
+#if defined( _XBOX )
+
+ extern CVProfile *g_pVProfileForDisplay;
+
+#else
+
+ CVProfile *g_pVProfileForDisplay = &g_VProfCurrentProfile;
+
+ // memdbgon must be the last include file in a .cpp file!!!
+ #include "tier0/memdbgon.h"
+
+#endif
+
+
+long GetFileSize( FILE *fp )
+{
+ int curPos = ftell( fp );
+ fseek( fp, 0, SEEK_END );
+ long ret = ftell( fp );
+ fseek( fp, curPos, SEEK_SET );
+ return ret;
+}
+
+
+// ------------------------------------------------------------------------------------------------------------------------------------ //
+// VProf record mode. Turn it on to record all the vprof data, then when you're playing back, the engine's budget and vprof panels
+// show the data from the recording instead of the real data.
+// ------------------------------------------------------------------------------------------------------------------------------------ //
+
+class CVProfRecorder : public CVProfile
+{
+public:
+ CVProfRecorder()
+ {
+ m_Mode = Mode_None;
+ m_hFile = NULL;
+ m_nQueuedStarts = 0;
+ m_nQueuedStops = 0;
+ m_iPlaybackTick = -1;
+ }
+
+ ~CVProfRecorder()
+ {
+ Assert( m_Mode == Mode_None );
+ }
+
+ void Shutdown()
+ {
+ Stop();
+ }
+
+ void Stop()
+ {
+ if ( (m_Mode == Mode_Record || m_Mode == Mode_Playback) && m_hFile != NULL )
+ {
+ if ( m_Mode == Mode_Record )
+ ++m_nQueuedStops;
+
+ g_pFileSystem->Close( m_hFile );
+ }
+
+ m_Mode = Mode_None;
+ m_hFile = NULL;
+ g_pVProfileForDisplay = &g_VProfCurrentProfile; // Stop using us for vprofile displays.
+ m_iPlaybackTick = -1;
+ m_bNodesChanged = true;
+ Term(); // clear the vprof data
+ }
+
+
+ bool IsPlayingBack()
+ {
+ return m_Mode == Mode_Playback;
+ }
+
+
+// RECORD FUNCTIONS.
+public:
+
+ bool Record_Start( const char *pFilename )
+ {
+ Stop();
+
+ char tempFilename[512];
+ if ( !strchr( pFilename, '.' ) )
+ {
+ Q_snprintf( tempFilename, sizeof( tempFilename ), "%s.vprof", pFilename );
+ pFilename = tempFilename;
+ }
+
+ m_iLastUniqueNodeID = -1;
+ m_hFile = g_pFileSystem->Open( pFilename, "wb" );
+ m_Mode = Mode_Record;
+ if ( m_hFile == NULL )
+ {
+ return false;
+ }
+ else
+ {
+ // Write the version number.
+ int version = VPROF_FILE_VERSION;
+ g_pFileSystem->Write( &version, sizeof( version ), m_hFile );
+
+ // Write the root node ID.
+ int nodeID = g_VProfCurrentProfile.GetRoot()->GetUniqueNodeID();
+ g_pFileSystem->Write( &nodeID, sizeof( nodeID ), m_hFile );
+
+ ++m_nQueuedStarts;
+
+ // Make sure vprof is recrding.
+ Cbuf_AddText( "vprof_on\n" );
+ return true;
+ }
+ }
+
+ void Record_WriteToken( char val )
+ {
+ g_pFileSystem->Write( &val, sizeof( val ), m_hFile );
+ }
+
+ void Record_MatchTree_R( CVProfNode *pOut, const CVProfNode *pIn, CVProfile *pInProfile )
+ {
+ // Add any new nodes at the beginning of the list..
+ if ( pIn->m_pChild )
+ {
+ while ( !pOut->m_pChild || pIn->m_pChild->GetUniqueNodeID() != pOut->m_pChild->GetUniqueNodeID() )
+ {
+ // Find the last new node in the list.
+ const CVProfNode *pToAdd = NULL;
+ const CVProfNode *pCur = pIn->m_pChild;
+ while ( pCur )
+ {
+ // If the out node has no children then we add the last one in the input node.
+ if ( pOut->m_pChild && pCur->GetUniqueNodeID() == pOut->m_pChild->GetUniqueNodeID() )
+ break;
+
+ pToAdd = pCur;
+ pCur = pCur->m_pSibling;
+ }
+
+ Assert( pToAdd );
+
+ // Write this to the file.
+ int budgetGroupID = pToAdd->m_BudgetGroupID;
+ int parentNodeID = pIn->GetUniqueNodeID();
+ int nodeID = pToAdd->GetUniqueNodeID();
+
+ Record_WriteToken( Token_AddNode );
+ g_pFileSystem->Write( &parentNodeID, sizeof( parentNodeID ), m_hFile ); // Parent node ID.
+ g_pFileSystem->Write( pToAdd->m_pszName, strlen( pToAdd->m_pszName ) + 1, m_hFile ); // Name of the new node.
+ g_pFileSystem->Write( &budgetGroupID, sizeof( budgetGroupID ), m_hFile );
+ g_pFileSystem->Write( &nodeID, sizeof( nodeID ), m_hFile );
+
+ // There's a new one here.
+ const char *pBudgetGroupName = g_VProfCurrentProfile.GetBudgetGroupName( pToAdd->m_BudgetGroupID );
+ int budgetGroupFlags = g_VProfCurrentProfile.GetBudgetGroupFlags( pToAdd->m_BudgetGroupID );
+ CVProfNode *pNewNode = pOut->GetSubNode( pToAdd->m_pszName, 0, pBudgetGroupName, budgetGroupFlags );
+ pNewNode->SetBudgetGroupID( pToAdd->m_BudgetGroupID );
+ pNewNode->SetUniqueNodeID( pToAdd->GetUniqueNodeID() );
+ }
+ }
+
+ // Recurse.
+ CVProfNode *pOutChild = pOut->m_pChild;
+ const CVProfNode *pInChild = pIn->m_pChild;
+ while ( pOutChild && pInChild )
+ {
+ Assert( Q_stricmp( pInChild->m_pszName, pOutChild->m_pszName ) == 0 );
+ Assert( pInChild->GetUniqueNodeID() == pOutChild->GetUniqueNodeID() );
+ Record_MatchTree_R( pOutChild, pInChild, pInProfile );
+
+ pOutChild = pOutChild->m_pSibling;
+ pInChild = pInChild->m_pSibling;
+ }
+ }
+
+ void Record_MatchBudgetGroups( CVProfile *pInProfile )
+ {
+ Assert( GetNumBudgetGroups() <= pInProfile->GetNumBudgetGroups() );
+
+ int nOriginalGroups = GetNumBudgetGroups();
+ for ( int i=nOriginalGroups; i < pInProfile->GetNumBudgetGroups(); i++ )
+ {
+ const char *pName = pInProfile->GetBudgetGroupName( i );
+ int flags = pInProfile->GetBudgetGroupFlags( i );
+ Record_WriteToken( Token_AddBudgetGroup );
+ g_pFileSystem->Write( pName, strlen( pName ) + 1, m_hFile );
+ g_pFileSystem->Write( &flags, sizeof( flags ), m_hFile );
+
+ AddBudgetGroupName( pName, flags );
+ }
+ }
+
+ void Record_WriteTimings_R( const CVProfNode *pIn )
+ {
+ unsigned short curCalls = min( pIn->m_nCurFrameCalls, (unsigned)0xFFFF );
+ if ( curCalls >= 255 )
+ {
+ unsigned char token = 255;
+ g_pFileSystem->Write( &token, sizeof( token ), m_hFile );
+ g_pFileSystem->Write( &curCalls, sizeof( curCalls ), m_hFile );
+ }
+ else
+ {
+ // Get away with one byte if we can.
+ unsigned char token = (char)curCalls;
+ g_pFileSystem->Write( &token, sizeof( token ), m_hFile );
+ }
+
+ // This allows us to write 2 bytes unless it's > 256 milliseconds (unlikely).
+ unsigned long nMicroseconds = pIn->m_CurFrameTime.GetMicroseconds() / 4;
+ if ( nMicroseconds >= 0xFFFF )
+ {
+ unsigned short token = 0xFFFF;
+ g_pFileSystem->Write( &token, sizeof( token ), m_hFile );
+ g_pFileSystem->Write( &nMicroseconds, sizeof( nMicroseconds ), m_hFile );
+ }
+ else
+ {
+ unsigned short token = (unsigned short)nMicroseconds;
+ g_pFileSystem->Write( &token, sizeof( token ), m_hFile );
+ }
+
+ for ( const CVProfNode *pChild = pIn->m_pChild; pChild; pChild = pChild->m_pSibling )
+ Record_WriteTimings_R( pChild );
+ }
+
+ void Record_Snapshot()
+ {
+ CVProfile *pInProfile = &g_VProfCurrentProfile;
+
+ // Don't record the overhead of writing in the filesystem here.
+ pInProfile->Pause();
+
+ // Record the tick count and start of frame.
+ Record_WriteToken( Token_StartFrame );
+#ifdef SWDS
+ g_pFileSystem->Write( &host_tickcount, sizeof( host_tickcount ), m_hFile );
+#else
+ g_pFileSystem->Write( &g_ClientGlobalVariables.tickcount, sizeof( g_ClientGlobalVariables.tickcount ), m_hFile );
+#endif
+
+ // Record all the changes to get our tree and budget groups to g_VProfCurrentProfile.
+ Record_MatchBudgetGroups( pInProfile );
+ if ( m_iLastUniqueNodeID != CVProfNode::s_iCurrentUniqueNodeID )
+ {
+ Record_MatchTree_R( GetRoot(), pInProfile->GetRoot(), pInProfile );
+ }
+
+ // Now that we have a matching tree, write all the timings.
+ Record_WriteToken( Token_Timings );
+ Record_WriteTimings_R( pInProfile->GetRoot() );
+ Record_WriteToken( Token_EndOfFrame );
+
+ pInProfile->Resume();
+ }
+
+
+// PLAYBACK FUNCTIONS.
+public:
+
+ #define Playback_Assert( theTest ) Playback_AssertFn( !!(theTest), __LINE__ )
+ bool Playback_AssertFn( bool bTest, int iLine )
+ {
+ if ( bTest )
+ {
+ return true;
+ }
+ else
+ {
+ Stop();
+ Warning( "VPROF PLAYBACK ASSERT (%s, line %d) - stopping playback.\n", __FILE__, iLine );
+ return false;
+ }
+ }
+
+
+ bool Playback_Start( const char *pFilename )
+ {
+ Stop();
+
+ char tempFilename[512];
+ if ( !strchr( pFilename, '.' ) )
+ {
+ Q_snprintf( tempFilename, sizeof( tempFilename ), "%s.vprof", pFilename );
+ pFilename = tempFilename;
+ }
+
+ m_iLastUniqueNodeID = -1;
+ m_hFile = g_pFileSystem->Open( pFilename, "rb" );
+ m_Mode = Mode_Playback;
+ m_bPlaybackPaused = true;
+ if ( m_hFile == NULL )
+ {
+ Warning( "vprof_playback_start: Open( %s ) failed.\n", pFilename );
+ return false;
+ }
+ else
+ {
+ int version;
+ g_pFileSystem->Read( &version, sizeof( version ), m_hFile );
+ if ( !Playback_Assert( version == VPROF_FILE_VERSION ) )
+ return false;
+
+ // Read the root node ID.
+ int nodeID;
+ g_pFileSystem->Read( &nodeID, sizeof( nodeID ), m_hFile );
+ GetRoot()->SetUniqueNodeID( nodeID );
+
+ m_iSkipPastHeaderPos = g_pFileSystem->Tell( m_hFile );
+ m_iLastTick = -1; // We don't know the last tick in the file yet.
+ m_FileLen = g_pFileSystem->Size( m_hFile );
+
+ m_enabled = true; // So IsEnabled() returns true..
+ Playback_ReadTick();
+ g_pVProfileForDisplay = this; // Start using this CVProfile for displays.
+ return true;
+ }
+ }
+
+ void Playback_Restart()
+ {
+ if ( m_Mode != Mode_Playback )
+ {
+ Assert( false );
+ return;
+ }
+
+ // Clear the data and restart playback.
+ m_iPlaybackTick = -1;
+ Term(); // clear the vprof data
+ m_bNodesChanged = true;
+
+ g_pFileSystem->Seek( m_hFile, m_iSkipPastHeaderPos, FILESYSTEM_SEEK_HEAD );
+ Playback_ReadTick(); // Read in one tick's worth of data.
+ }
+
+ char Playback_ReadToken()
+ {
+ Assert( m_Mode == Mode_Playback );
+ char token;
+ if ( g_pFileSystem->Read( &token, 1, m_hFile ) != 1 )
+ token = TOKEN_FILE_FINISHED;
+
+ return token;
+ }
+
+
+ bool Playback_ReadString( char *pOut, int maxLen )
+ {
+ int i = 0;
+ while ( 1 )
+ {
+ char ch;
+ if ( g_pFileSystem->Read( &ch, 1, m_hFile ) == 0 )
+ {
+ Playback_Assert( false );
+ return false;
+ }
+ if ( ch == 0 )
+ {
+ pOut[i] = 0;
+ break;
+ }
+ else
+ {
+ if ( i < (maxLen-1) )
+ {
+ pOut[i] = ch;
+ ++i;
+ }
+ }
+ }
+ return true;
+ }
+
+
+ bool Playback_ReadAddBudgetGroup()
+ {
+ char name[512];
+ if ( !Playback_ReadString( name, sizeof( name ) ) )
+ return false;
+
+ int flags = 0;
+ g_pFileSystem->Read( &flags, sizeof( flags ), m_hFile );
+
+ AddBudgetGroupName( name, flags );
+ return true;
+ }
+
+
+ CVProfNode* FindVProfNodeByID_R( CVProfNode *pNode, int id )
+ {
+ if ( pNode->GetUniqueNodeID() == id )
+ return pNode;
+
+ for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
+ {
+ CVProfNode *pTest = FindVProfNodeByID_R( pCur, id );
+ if ( pTest )
+ return pTest;
+ }
+
+ return NULL;
+ }
+
+
+ bool Playback_ReadAddNode()
+ {
+ int budgetGroupID;
+ int parentNodeID;
+ int nodeID;
+
+ char nodeName[512];
+
+ g_pFileSystem->Read( &parentNodeID, sizeof( parentNodeID ), m_hFile ); // Parent node ID.
+ if ( !Playback_ReadString( nodeName, sizeof( nodeName ) ) )
+ return false;
+ g_pFileSystem->Read( &budgetGroupID, sizeof( budgetGroupID ), m_hFile );
+ g_pFileSystem->Read( &nodeID, sizeof( nodeID ), m_hFile );
+
+ // Now find the parent node.
+ CVProfNode *pParentNode = FindVProfNodeByID_R( GetRoot(), parentNodeID );
+ if ( !Playback_Assert( pParentNode != NULL ) )
+ return false;
+
+ const char *pBudgetGroupName = GetBudgetGroupName( 0 );
+ int budgetGroupFlags = 0;
+ CVProfNode *pNewNode = pParentNode->GetSubNode( PoolString( nodeName ), 0, pBudgetGroupName, budgetGroupFlags );
+ pNewNode->SetBudgetGroupID( budgetGroupID );
+ pNewNode->SetUniqueNodeID( nodeID );
+
+ m_bNodesChanged = true;
+ return true;
+ }
+
+
+ bool Playback_ReadTimings_R( CVProfNode *pNode )
+ {
+ // Read the timing.
+ unsigned char token;
+ if ( g_pFileSystem->Read( &token, sizeof( token ), m_hFile ) != sizeof( token ) )
+ return false;
+
+ if ( token == 255 )
+ {
+ unsigned short curCalls;
+ if ( g_pFileSystem->Read( &curCalls, sizeof( curCalls ), m_hFile ) != sizeof( curCalls ) )
+ return false;
+
+ pNode->m_nCurFrameCalls = curCalls;
+ }
+ else
+ {
+ pNode->m_nCurFrameCalls = token;
+ }
+ pNode->m_nPrevFrameCalls = pNode->m_nCurFrameCalls;
+
+ // This allows us to write 2 bytes unless it's > 256 milliseconds (unlikely).
+ unsigned short microsecondsToken;
+ if ( g_pFileSystem->Read( &microsecondsToken, sizeof( microsecondsToken ), m_hFile ) != sizeof( microsecondsToken ) )
+ return false;
+
+ if ( microsecondsToken == 0xFFFF )
+ {
+ unsigned long nMicroseconds;
+ if ( g_pFileSystem->Read( &nMicroseconds, sizeof( nMicroseconds ), m_hFile ) != sizeof( nMicroseconds ) )
+ return false;
+
+ pNode->m_CurFrameTime.SetMicroseconds( nMicroseconds * 4 );
+ }
+ else
+ {
+ pNode->m_CurFrameTime.SetMicroseconds( (unsigned long)microsecondsToken * 4 );
+ }
+ pNode->m_PrevFrameTime = pNode->m_CurFrameTime;
+
+ // Recurse.
+ for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
+ {
+ if ( !Playback_ReadTimings_R( pCur ) )
+ return false;
+ }
+
+ return true;
+ }
+
+
+ // Read the next tick. If iDontGoPast is set, then it will abort IF the next tick's index
+ // is greater than iDontGoPast. In that case, sets pWouldHaveGonePast to true,
+ // stays where it was before the call, and returns true.
+ bool Playback_ReadTick( int iDontGoPast = -1, bool *pWouldHaveGonePast = NULL )
+ {
+ if ( pWouldHaveGonePast )
+ *pWouldHaveGonePast = false;
+
+ if ( m_Mode != Mode_Playback )
+ return false;
+
+ // Read the next tick..
+ int token = Playback_ReadToken();
+ if ( token == TOKEN_FILE_FINISHED )
+ {
+ Msg( "VPROF playback finished.\n" );
+ m_iLastTick = m_iPlaybackTick; // Now we know our last tick.
+ return true;
+ }
+
+ if ( !Playback_Assert( token == Token_StartFrame ) )
+ return false;
+
+ int iPlaybackTick = m_iPlaybackTick;
+ g_pFileSystem->Read( &iPlaybackTick, sizeof( iPlaybackTick ), m_hFile );
+
+ // First test if this tick would go past the number they don't want us to go past.
+ if ( iDontGoPast != -1 && iPlaybackTick > iDontGoPast )
+ {
+ *pWouldHaveGonePast = true;
+ g_pFileSystem->Seek( m_hFile, -5, FILESYSTEM_SEEK_CURRENT );
+ return true;
+ }
+ else
+ {
+ m_iPlaybackTick = iPlaybackTick;
+ }
+
+ while ( 1 )
+ {
+ token = Playback_ReadToken();
+ if ( token == Token_EndOfFrame )
+ break;
+
+ if ( token == Token_AddBudgetGroup )
+ {
+ if ( !Playback_ReadAddBudgetGroup() )
+ return false;
+ }
+ else if ( token == Token_AddNode )
+ {
+ if ( !Playback_ReadAddNode() )
+ return false;
+ }
+ else if ( token == Token_Timings )
+ {
+ if ( !Playback_ReadTimings_R( GetRoot() ) )
+ return false;
+ }
+ else
+ {
+ Playback_Assert( false );
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void Playback_Snapshot()
+ {
+ if ( m_Mode == Mode_Playback && !m_bPlaybackPaused )
+ Playback_ReadTick();
+ }
+
+
+ void Playback_Step()
+ {
+ Playback_ReadTick();
+ }
+
+
+ class CNodeAverage
+ {
+ public:
+ CVProfNode *m_pNode;
+
+ CCycleCount m_CurFrameTime_Total;
+ int m_nCurFrameCalls_Total;
+
+ int m_nSamples;
+ };
+
+ CNodeAverage* FindNodeAverage( CUtlVector<CNodeAverage> &averages, CVProfNode *pNode )
+ {
+ for ( int i=0; i < averages.Count(); i++ )
+ {
+ if ( averages[i].m_pNode == pNode )
+ return &averages[i];
+ }
+ return NULL;
+ }
+
+ void UpdateAverages_R( CUtlVector<CNodeAverage> &averages, CVProfNode *pNode )
+ {
+ CNodeAverage *pAverage = FindNodeAverage( averages, pNode );
+ if ( !pAverage )
+ {
+ pAverage = &averages[ averages.AddToTail() ];
+ memset( pAverage, 0, sizeof( *pAverage ) );
+ pAverage->m_pNode = pNode;
+ }
+ pAverage->m_CurFrameTime_Total += pNode->m_CurFrameTime;
+ pAverage->m_nCurFrameCalls_Total += pNode->m_nCurFrameCalls;
+ pAverage->m_nSamples++;
+
+ // Recurse.
+ for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
+ UpdateAverages_R( averages, pCur );
+ }
+
+ void DumpAverages_R( CUtlVector<CNodeAverage> &averages, CVProfNode *pNode )
+ {
+ CNodeAverage *pAverage = FindNodeAverage( averages, pNode );
+ if ( pAverage )
+ {
+ pNode->m_CurFrameTime.m_Int64 = pAverage->m_CurFrameTime_Total.m_Int64 / pAverage->m_nSamples;
+ pNode->m_nCurFrameCalls = pAverage->m_nCurFrameCalls_Total / pAverage->m_nSamples;
+ }
+ pNode->m_PrevFrameTime = pNode->m_CurFrameTime;
+ pNode->m_nPrevFrameCalls = pNode->m_nCurFrameCalls;
+
+ // Recurse.
+ for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
+ DumpAverages_R( averages, pCur );
+ }
+
+
+ void Playback_Average( int nFrames )
+ {
+ // Remember where we started.
+ unsigned long seekPos = g_pFileSystem->Tell( m_hFile );
+ int iOldLastTick = m_iLastTick;
+ int iOldPlaybackTick = m_iPlaybackTick;
+
+ // Take the average of the next N ticks.
+ CUtlVector<CNodeAverage> averages;
+ while ( nFrames > 0 && m_iLastTick == -1 )
+ {
+ Playback_ReadTick();
+ UpdateAverages_R( averages, GetRoot() );
+ --nFrames;
+ }
+ DumpAverages_R( averages, GetRoot() );
+
+ // Now seek back to where we started.
+ g_pFileSystem->Seek( m_hFile, seekPos, FILESYSTEM_SEEK_HEAD );
+ m_iLastTick = iOldLastTick;
+ m_iPlaybackTick = iOldPlaybackTick;
+ }
+
+
+ int Playback_SetPlaybackTick( int iTick )
+ {
+ if ( m_Mode != Mode_Playback )
+ return 0;
+
+ m_bNodesChanged = false; // We want to pickup changes to this, so reset it here.
+ if ( iTick == m_iPlaybackTick )
+ {
+ return 1;
+ }
+ else if ( iTick < m_iPlaybackTick )
+ {
+ // Crap.. have to go back. Restart and seek to this tick.
+ Playback_Restart();
+
+ // If this tick has a smaller value than the first tick in the file, then we can't seek forward to it...
+ if ( iTick <= m_iPlaybackTick )
+ {
+ return 1 + m_bNodesChanged; // return 2 if the nodes changed
+ }
+ }
+
+ // Now seek forward to the tick they want.
+ while ( m_iPlaybackTick < iTick )
+ {
+ bool bWouldHaveGonePast;
+ if ( !Playback_ReadTick( iTick, &bWouldHaveGonePast ) )
+ return 0; // error
+
+ // If reading this tick would have gone past the tick they're asking us to go for,
+ // stay on the current tick.
+ if ( bWouldHaveGonePast )
+ break;
+
+ // If we went to the last tick in the file, then stop here.
+ if ( m_iLastTick != -1 && m_iPlaybackTick >= m_iLastTick )
+ return 1 + m_bNodesChanged;
+ }
+
+ return 1 + m_bNodesChanged;
+ }
+
+
+ // 0-1 value.
+ float Playback_GetCurrentPercent()
+ {
+ return (float)g_pFileSystem->Tell( m_hFile ) / m_FileLen;
+ }
+
+
+ int Playback_SeekToPercent( float flWantedPercent )
+ {
+ if ( m_Mode != Mode_Playback )
+ return 0; // error
+
+ m_bNodesChanged = false; // We want to pickup changes to this, so reset it here.
+
+ float flCurPercent = Playback_GetCurrentPercent();
+ if ( flWantedPercent < flCurPercent )
+ {
+ // Crap.. have to go back. Restart and seek to this tick.
+ Playback_Restart();
+
+ // If this tick has a smaller value than the first tick in the file, then we can't seek forward to it...
+ if ( flWantedPercent <= 0 )
+ return 1 + m_bNodesChanged; // return 2 if nodes changed
+ }
+
+ // Now seek forward to the tick they want.
+ while ( Playback_GetCurrentPercent() < flWantedPercent )
+ {
+ if ( !Playback_ReadTick() )
+ return 0; // error
+
+ // If we went to the last tick in the file, then stop here.
+ if ( m_iLastTick != -1 && m_iPlaybackTick >= m_iLastTick )
+ return 1 + m_bNodesChanged; // return 2 if nodes changed
+ }
+
+ return 1 + m_bNodesChanged;
+ }
+
+
+ int Playback_GetCurrentTick()
+ {
+ return m_iPlaybackTick;
+ }
+
+
+
+// OTHER FUNCTIONS.
+public:
+
+ void Snapshot()
+ {
+ if ( m_Mode == Mode_Record )
+ Record_Snapshot();
+ else if ( m_Mode == Mode_Playback )
+ Playback_Snapshot();
+ }
+
+ void StartOrStop()
+ {
+ while ( m_nQueuedStarts > 0 )
+ {
+ --m_nQueuedStarts;
+ g_VProfCurrentProfile.Start();
+ }
+
+ while ( m_nQueuedStops > 0 )
+ {
+ --m_nQueuedStops;
+ g_VProfCurrentProfile.Stop();
+ }
+ }
+
+ inline CVProfile* GetActiveProfile()
+ {
+ if ( m_Mode == Mode_Playback )
+ return this;
+ else
+ return &g_VProfCurrentProfile;
+ }
+
+
+private:
+
+ const char* PoolString( const char *pStr )
+ {
+ int i = m_PooledStrings.Find( pStr );
+ if ( i == m_PooledStrings.InvalidIndex() )
+ i = m_PooledStrings.Insert( pStr, 0 );
+
+ return m_PooledStrings.GetElementName( i );
+ }
+
+
+private:
+ enum
+ {
+ Token_StartFrame=0,
+ Token_AddNode,
+ Token_AddBudgetGroup,
+ Token_Timings,
+ Token_EndOfFrame,
+ TOKEN_FILE_FINISHED
+ };
+
+ enum
+ {
+ VPROF_FILE_VERSION = 1
+ };
+
+ enum
+ {
+ Mode_None,
+ Mode_Record,
+ Mode_Playback
+ };
+
+ CUtlDict<int,int> m_PooledStrings;
+
+ int m_Mode;
+ FileHandle_t m_hFile;
+ int m_iLastUniqueNodeID;
+ int m_iPlaybackTick; // Our current tick.
+ int m_iSkipPastHeaderPos;
+ int m_iLastTick; // We only know this when we hit the end of the file.
+ int m_FileLen;
+ bool m_bNodesChanged; // Set if the nodes were added or removed.
+
+ int m_nQueuedStarts;
+ int m_nQueuedStops;
+
+ bool m_bPlaybackPaused;
+};
+
+
+
+static CVProfRecorder g_VProfRecorder;
+
+
+
+
+CON_COMMAND( vprof_record_start, "Start recording vprof data for playback later." )
+{
+ if ( args.ArgC() != 2 )
+ {
+ Warning( "vprof_record_start requires a filename\n" );
+ return;
+ }
+
+ g_VProfRecorder.Record_Start( args[1] );
+}
+
+CON_COMMAND( vprof_record_stop, "Stop recording vprof data" )
+{
+ Warning( "Stopping vprof recording...\n" );
+ g_VProfRecorder.Stop();
+}
+
+CON_COMMAND( vprof_playback_start, "Start playing back a recorded .vprof file." )
+{
+ if ( args.ArgC() < 2 )
+ {
+ Warning( "vprof_playback_start requires a filename\n" );
+ return;
+ }
+
+ // Console parser treats colons as a break, so join all the tokens together here.
+ char fullFilename[512];
+ fullFilename[0] = 0;
+ for ( int i=1; i < args.ArgC(); i++ )
+ {
+ Q_strncat( fullFilename, args[i], sizeof( fullFilename ), COPY_ALL_CHARACTERS );
+ }
+
+ g_VProfRecorder.Playback_Start( fullFilename );
+}
+
+CON_COMMAND( vprof_playback_stop, "Stop playing back a recorded .vprof file." )
+{
+ Warning( "Stopping vprof playback...\n" );
+ g_VProfRecorder.Stop();
+}
+
+CON_COMMAND( vprof_playback_step, "While playing back a .vprof file, step to the next tick." )
+{
+ VProfPlayback_Step();
+}
+
+CON_COMMAND( vprof_playback_stepback, "While playing back a .vprof file, step to the previous tick." )
+{
+ VProfPlayback_StepBack();
+}
+
+CON_COMMAND( vprof_playback_average, "Average the next N frames." )
+{
+ if ( args.ArgC() >= 2 )
+ {
+ int nFrames = atoi( args[ 1 ] );
+ if ( nFrames == -1 )
+ nFrames = 9999999;
+
+ g_VProfRecorder.Playback_Average( nFrames );
+ }
+ else
+ {
+ Warning( "vprof_playback_average [# frames]\n" );
+ Warning( "If # frames is -1, then it will average all the remaining frames in the vprof file.\n" );
+ }
+}
+
+
+void VProfRecord_Snapshot()
+{
+ g_VProfRecorder.Snapshot();
+}
+
+
+void VProfRecord_StartOrStop()
+{
+ g_VProfRecorder.StartOrStop();
+}
+
+
+void VProfRecord_Shutdown()
+{
+ g_VProfRecorder.Shutdown();
+}
+
+
+
+bool VProfRecord_IsPlayingBack()
+{
+ return g_VProfRecorder.IsPlayingBack();
+}
+
+
+int VProfPlayback_GetCurrentTick()
+{
+ return g_VProfRecorder.Playback_GetCurrentTick();
+}
+
+
+float VProfPlayback_GetCurrentPercent()
+{
+ return g_VProfRecorder.Playback_GetCurrentPercent();
+}
+
+
+int VProfPlayback_SetPlaybackTick( int iTick )
+{
+ return g_VProfRecorder.Playback_SetPlaybackTick( iTick );
+}
+
+
+int VProfPlayback_SeekToPercent( float percent )
+{
+ return g_VProfRecorder.Playback_SeekToPercent( percent );
+}
+
+void VProfPlayback_Step()
+{
+ g_VProfRecorder.Playback_Step();
+}
+
+int VProfPlayback_StepBack()
+{
+ return g_VProfRecorder.Playback_SetPlaybackTick( g_VProfRecorder.Playback_GetCurrentTick() - 1 );
+}
+
+
+#endif // VPROF_ENABLED