diff options
Diffstat (limited to 'engine/vprof_record.cpp')
| -rw-r--r-- | engine/vprof_record.cpp | 982 |
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( µsecondsToken, 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 |