Team Fortress 2 Source Code as on 22/4/2020

982 lines
23 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "tier0/vcrmode.h"
  7. #undef PROTECT_FILEIO_FUNCTIONS
  8. #include "tier0/vprof.h"
  9. #include "utldict.h"
  10. #include "client.h"
  11. #include "cmd.h"
  12. #include "filesystem_engine.h"
  13. #include "vprof_record.h"
  14. #ifdef VPROF_ENABLED
  15. #if defined( _XBOX )
  16. extern CVProfile *g_pVProfileForDisplay;
  17. #else
  18. CVProfile *g_pVProfileForDisplay = &g_VProfCurrentProfile;
  19. // memdbgon must be the last include file in a .cpp file!!!
  20. #include "tier0/memdbgon.h"
  21. #endif
  22. long GetFileSize( FILE *fp )
  23. {
  24. int curPos = ftell( fp );
  25. fseek( fp, 0, SEEK_END );
  26. long ret = ftell( fp );
  27. fseek( fp, curPos, SEEK_SET );
  28. return ret;
  29. }
  30. // ------------------------------------------------------------------------------------------------------------------------------------ //
  31. // 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
  32. // show the data from the recording instead of the real data.
  33. // ------------------------------------------------------------------------------------------------------------------------------------ //
  34. class CVProfRecorder : public CVProfile
  35. {
  36. public:
  37. CVProfRecorder()
  38. {
  39. m_Mode = Mode_None;
  40. m_hFile = NULL;
  41. m_nQueuedStarts = 0;
  42. m_nQueuedStops = 0;
  43. m_iPlaybackTick = -1;
  44. }
  45. ~CVProfRecorder()
  46. {
  47. Assert( m_Mode == Mode_None );
  48. }
  49. void Shutdown()
  50. {
  51. Stop();
  52. }
  53. void Stop()
  54. {
  55. if ( (m_Mode == Mode_Record || m_Mode == Mode_Playback) && m_hFile != NULL )
  56. {
  57. if ( m_Mode == Mode_Record )
  58. ++m_nQueuedStops;
  59. g_pFileSystem->Close( m_hFile );
  60. }
  61. m_Mode = Mode_None;
  62. m_hFile = NULL;
  63. g_pVProfileForDisplay = &g_VProfCurrentProfile; // Stop using us for vprofile displays.
  64. m_iPlaybackTick = -1;
  65. m_bNodesChanged = true;
  66. Term(); // clear the vprof data
  67. }
  68. bool IsPlayingBack()
  69. {
  70. return m_Mode == Mode_Playback;
  71. }
  72. // RECORD FUNCTIONS.
  73. public:
  74. bool Record_Start( const char *pFilename )
  75. {
  76. Stop();
  77. char tempFilename[512];
  78. if ( !strchr( pFilename, '.' ) )
  79. {
  80. Q_snprintf( tempFilename, sizeof( tempFilename ), "%s.vprof", pFilename );
  81. pFilename = tempFilename;
  82. }
  83. m_iLastUniqueNodeID = -1;
  84. m_hFile = g_pFileSystem->Open( pFilename, "wb" );
  85. m_Mode = Mode_Record;
  86. if ( m_hFile == NULL )
  87. {
  88. return false;
  89. }
  90. else
  91. {
  92. // Write the version number.
  93. int version = VPROF_FILE_VERSION;
  94. g_pFileSystem->Write( &version, sizeof( version ), m_hFile );
  95. // Write the root node ID.
  96. int nodeID = g_VProfCurrentProfile.GetRoot()->GetUniqueNodeID();
  97. g_pFileSystem->Write( &nodeID, sizeof( nodeID ), m_hFile );
  98. ++m_nQueuedStarts;
  99. // Make sure vprof is recrding.
  100. Cbuf_AddText( "vprof_on\n" );
  101. return true;
  102. }
  103. }
  104. void Record_WriteToken( char val )
  105. {
  106. g_pFileSystem->Write( &val, sizeof( val ), m_hFile );
  107. }
  108. void Record_MatchTree_R( CVProfNode *pOut, const CVProfNode *pIn, CVProfile *pInProfile )
  109. {
  110. // Add any new nodes at the beginning of the list..
  111. if ( pIn->m_pChild )
  112. {
  113. while ( !pOut->m_pChild || pIn->m_pChild->GetUniqueNodeID() != pOut->m_pChild->GetUniqueNodeID() )
  114. {
  115. // Find the last new node in the list.
  116. const CVProfNode *pToAdd = NULL;
  117. const CVProfNode *pCur = pIn->m_pChild;
  118. while ( pCur )
  119. {
  120. // If the out node has no children then we add the last one in the input node.
  121. if ( pOut->m_pChild && pCur->GetUniqueNodeID() == pOut->m_pChild->GetUniqueNodeID() )
  122. break;
  123. pToAdd = pCur;
  124. pCur = pCur->m_pSibling;
  125. }
  126. Assert( pToAdd );
  127. // Write this to the file.
  128. int budgetGroupID = pToAdd->m_BudgetGroupID;
  129. int parentNodeID = pIn->GetUniqueNodeID();
  130. int nodeID = pToAdd->GetUniqueNodeID();
  131. Record_WriteToken( Token_AddNode );
  132. g_pFileSystem->Write( &parentNodeID, sizeof( parentNodeID ), m_hFile ); // Parent node ID.
  133. g_pFileSystem->Write( pToAdd->m_pszName, strlen( pToAdd->m_pszName ) + 1, m_hFile ); // Name of the new node.
  134. g_pFileSystem->Write( &budgetGroupID, sizeof( budgetGroupID ), m_hFile );
  135. g_pFileSystem->Write( &nodeID, sizeof( nodeID ), m_hFile );
  136. // There's a new one here.
  137. const char *pBudgetGroupName = g_VProfCurrentProfile.GetBudgetGroupName( pToAdd->m_BudgetGroupID );
  138. int budgetGroupFlags = g_VProfCurrentProfile.GetBudgetGroupFlags( pToAdd->m_BudgetGroupID );
  139. CVProfNode *pNewNode = pOut->GetSubNode( pToAdd->m_pszName, 0, pBudgetGroupName, budgetGroupFlags );
  140. pNewNode->SetBudgetGroupID( pToAdd->m_BudgetGroupID );
  141. pNewNode->SetUniqueNodeID( pToAdd->GetUniqueNodeID() );
  142. }
  143. }
  144. // Recurse.
  145. CVProfNode *pOutChild = pOut->m_pChild;
  146. const CVProfNode *pInChild = pIn->m_pChild;
  147. while ( pOutChild && pInChild )
  148. {
  149. Assert( Q_stricmp( pInChild->m_pszName, pOutChild->m_pszName ) == 0 );
  150. Assert( pInChild->GetUniqueNodeID() == pOutChild->GetUniqueNodeID() );
  151. Record_MatchTree_R( pOutChild, pInChild, pInProfile );
  152. pOutChild = pOutChild->m_pSibling;
  153. pInChild = pInChild->m_pSibling;
  154. }
  155. }
  156. void Record_MatchBudgetGroups( CVProfile *pInProfile )
  157. {
  158. Assert( GetNumBudgetGroups() <= pInProfile->GetNumBudgetGroups() );
  159. int nOriginalGroups = GetNumBudgetGroups();
  160. for ( int i=nOriginalGroups; i < pInProfile->GetNumBudgetGroups(); i++ )
  161. {
  162. const char *pName = pInProfile->GetBudgetGroupName( i );
  163. int flags = pInProfile->GetBudgetGroupFlags( i );
  164. Record_WriteToken( Token_AddBudgetGroup );
  165. g_pFileSystem->Write( pName, strlen( pName ) + 1, m_hFile );
  166. g_pFileSystem->Write( &flags, sizeof( flags ), m_hFile );
  167. AddBudgetGroupName( pName, flags );
  168. }
  169. }
  170. void Record_WriteTimings_R( const CVProfNode *pIn )
  171. {
  172. unsigned short curCalls = min( pIn->m_nCurFrameCalls, (unsigned)0xFFFF );
  173. if ( curCalls >= 255 )
  174. {
  175. unsigned char token = 255;
  176. g_pFileSystem->Write( &token, sizeof( token ), m_hFile );
  177. g_pFileSystem->Write( &curCalls, sizeof( curCalls ), m_hFile );
  178. }
  179. else
  180. {
  181. // Get away with one byte if we can.
  182. unsigned char token = (char)curCalls;
  183. g_pFileSystem->Write( &token, sizeof( token ), m_hFile );
  184. }
  185. // This allows us to write 2 bytes unless it's > 256 milliseconds (unlikely).
  186. unsigned long nMicroseconds = pIn->m_CurFrameTime.GetMicroseconds() / 4;
  187. if ( nMicroseconds >= 0xFFFF )
  188. {
  189. unsigned short token = 0xFFFF;
  190. g_pFileSystem->Write( &token, sizeof( token ), m_hFile );
  191. g_pFileSystem->Write( &nMicroseconds, sizeof( nMicroseconds ), m_hFile );
  192. }
  193. else
  194. {
  195. unsigned short token = (unsigned short)nMicroseconds;
  196. g_pFileSystem->Write( &token, sizeof( token ), m_hFile );
  197. }
  198. for ( const CVProfNode *pChild = pIn->m_pChild; pChild; pChild = pChild->m_pSibling )
  199. Record_WriteTimings_R( pChild );
  200. }
  201. void Record_Snapshot()
  202. {
  203. CVProfile *pInProfile = &g_VProfCurrentProfile;
  204. // Don't record the overhead of writing in the filesystem here.
  205. pInProfile->Pause();
  206. // Record the tick count and start of frame.
  207. Record_WriteToken( Token_StartFrame );
  208. #ifdef SWDS
  209. g_pFileSystem->Write( &host_tickcount, sizeof( host_tickcount ), m_hFile );
  210. #else
  211. g_pFileSystem->Write( &g_ClientGlobalVariables.tickcount, sizeof( g_ClientGlobalVariables.tickcount ), m_hFile );
  212. #endif
  213. // Record all the changes to get our tree and budget groups to g_VProfCurrentProfile.
  214. Record_MatchBudgetGroups( pInProfile );
  215. if ( m_iLastUniqueNodeID != CVProfNode::s_iCurrentUniqueNodeID )
  216. {
  217. Record_MatchTree_R( GetRoot(), pInProfile->GetRoot(), pInProfile );
  218. }
  219. // Now that we have a matching tree, write all the timings.
  220. Record_WriteToken( Token_Timings );
  221. Record_WriteTimings_R( pInProfile->GetRoot() );
  222. Record_WriteToken( Token_EndOfFrame );
  223. pInProfile->Resume();
  224. }
  225. // PLAYBACK FUNCTIONS.
  226. public:
  227. #define Playback_Assert( theTest ) Playback_AssertFn( !!(theTest), __LINE__ )
  228. bool Playback_AssertFn( bool bTest, int iLine )
  229. {
  230. if ( bTest )
  231. {
  232. return true;
  233. }
  234. else
  235. {
  236. Stop();
  237. Warning( "VPROF PLAYBACK ASSERT (%s, line %d) - stopping playback.\n", __FILE__, iLine );
  238. return false;
  239. }
  240. }
  241. bool Playback_Start( const char *pFilename )
  242. {
  243. Stop();
  244. char tempFilename[512];
  245. if ( !strchr( pFilename, '.' ) )
  246. {
  247. Q_snprintf( tempFilename, sizeof( tempFilename ), "%s.vprof", pFilename );
  248. pFilename = tempFilename;
  249. }
  250. m_iLastUniqueNodeID = -1;
  251. m_hFile = g_pFileSystem->Open( pFilename, "rb" );
  252. m_Mode = Mode_Playback;
  253. m_bPlaybackPaused = true;
  254. if ( m_hFile == NULL )
  255. {
  256. Warning( "vprof_playback_start: Open( %s ) failed.\n", pFilename );
  257. return false;
  258. }
  259. else
  260. {
  261. int version;
  262. g_pFileSystem->Read( &version, sizeof( version ), m_hFile );
  263. if ( !Playback_Assert( version == VPROF_FILE_VERSION ) )
  264. return false;
  265. // Read the root node ID.
  266. int nodeID;
  267. g_pFileSystem->Read( &nodeID, sizeof( nodeID ), m_hFile );
  268. GetRoot()->SetUniqueNodeID( nodeID );
  269. m_iSkipPastHeaderPos = g_pFileSystem->Tell( m_hFile );
  270. m_iLastTick = -1; // We don't know the last tick in the file yet.
  271. m_FileLen = g_pFileSystem->Size( m_hFile );
  272. m_enabled = true; // So IsEnabled() returns true..
  273. Playback_ReadTick();
  274. g_pVProfileForDisplay = this; // Start using this CVProfile for displays.
  275. return true;
  276. }
  277. }
  278. void Playback_Restart()
  279. {
  280. if ( m_Mode != Mode_Playback )
  281. {
  282. Assert( false );
  283. return;
  284. }
  285. // Clear the data and restart playback.
  286. m_iPlaybackTick = -1;
  287. Term(); // clear the vprof data
  288. m_bNodesChanged = true;
  289. g_pFileSystem->Seek( m_hFile, m_iSkipPastHeaderPos, FILESYSTEM_SEEK_HEAD );
  290. Playback_ReadTick(); // Read in one tick's worth of data.
  291. }
  292. char Playback_ReadToken()
  293. {
  294. Assert( m_Mode == Mode_Playback );
  295. char token;
  296. if ( g_pFileSystem->Read( &token, 1, m_hFile ) != 1 )
  297. token = TOKEN_FILE_FINISHED;
  298. return token;
  299. }
  300. bool Playback_ReadString( char *pOut, int maxLen )
  301. {
  302. int i = 0;
  303. while ( 1 )
  304. {
  305. char ch;
  306. if ( g_pFileSystem->Read( &ch, 1, m_hFile ) == 0 )
  307. {
  308. Playback_Assert( false );
  309. return false;
  310. }
  311. if ( ch == 0 )
  312. {
  313. pOut[i] = 0;
  314. break;
  315. }
  316. else
  317. {
  318. if ( i < (maxLen-1) )
  319. {
  320. pOut[i] = ch;
  321. ++i;
  322. }
  323. }
  324. }
  325. return true;
  326. }
  327. bool Playback_ReadAddBudgetGroup()
  328. {
  329. char name[512];
  330. if ( !Playback_ReadString( name, sizeof( name ) ) )
  331. return false;
  332. int flags = 0;
  333. g_pFileSystem->Read( &flags, sizeof( flags ), m_hFile );
  334. AddBudgetGroupName( name, flags );
  335. return true;
  336. }
  337. CVProfNode* FindVProfNodeByID_R( CVProfNode *pNode, int id )
  338. {
  339. if ( pNode->GetUniqueNodeID() == id )
  340. return pNode;
  341. for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
  342. {
  343. CVProfNode *pTest = FindVProfNodeByID_R( pCur, id );
  344. if ( pTest )
  345. return pTest;
  346. }
  347. return NULL;
  348. }
  349. bool Playback_ReadAddNode()
  350. {
  351. int budgetGroupID;
  352. int parentNodeID;
  353. int nodeID;
  354. char nodeName[512];
  355. g_pFileSystem->Read( &parentNodeID, sizeof( parentNodeID ), m_hFile ); // Parent node ID.
  356. if ( !Playback_ReadString( nodeName, sizeof( nodeName ) ) )
  357. return false;
  358. g_pFileSystem->Read( &budgetGroupID, sizeof( budgetGroupID ), m_hFile );
  359. g_pFileSystem->Read( &nodeID, sizeof( nodeID ), m_hFile );
  360. // Now find the parent node.
  361. CVProfNode *pParentNode = FindVProfNodeByID_R( GetRoot(), parentNodeID );
  362. if ( !Playback_Assert( pParentNode != NULL ) )
  363. return false;
  364. const char *pBudgetGroupName = GetBudgetGroupName( 0 );
  365. int budgetGroupFlags = 0;
  366. CVProfNode *pNewNode = pParentNode->GetSubNode( PoolString( nodeName ), 0, pBudgetGroupName, budgetGroupFlags );
  367. pNewNode->SetBudgetGroupID( budgetGroupID );
  368. pNewNode->SetUniqueNodeID( nodeID );
  369. m_bNodesChanged = true;
  370. return true;
  371. }
  372. bool Playback_ReadTimings_R( CVProfNode *pNode )
  373. {
  374. // Read the timing.
  375. unsigned char token;
  376. if ( g_pFileSystem->Read( &token, sizeof( token ), m_hFile ) != sizeof( token ) )
  377. return false;
  378. if ( token == 255 )
  379. {
  380. unsigned short curCalls;
  381. if ( g_pFileSystem->Read( &curCalls, sizeof( curCalls ), m_hFile ) != sizeof( curCalls ) )
  382. return false;
  383. pNode->m_nCurFrameCalls = curCalls;
  384. }
  385. else
  386. {
  387. pNode->m_nCurFrameCalls = token;
  388. }
  389. pNode->m_nPrevFrameCalls = pNode->m_nCurFrameCalls;
  390. // This allows us to write 2 bytes unless it's > 256 milliseconds (unlikely).
  391. unsigned short microsecondsToken;
  392. if ( g_pFileSystem->Read( &microsecondsToken, sizeof( microsecondsToken ), m_hFile ) != sizeof( microsecondsToken ) )
  393. return false;
  394. if ( microsecondsToken == 0xFFFF )
  395. {
  396. unsigned long nMicroseconds;
  397. if ( g_pFileSystem->Read( &nMicroseconds, sizeof( nMicroseconds ), m_hFile ) != sizeof( nMicroseconds ) )
  398. return false;
  399. pNode->m_CurFrameTime.SetMicroseconds( nMicroseconds * 4 );
  400. }
  401. else
  402. {
  403. pNode->m_CurFrameTime.SetMicroseconds( (unsigned long)microsecondsToken * 4 );
  404. }
  405. pNode->m_PrevFrameTime = pNode->m_CurFrameTime;
  406. // Recurse.
  407. for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
  408. {
  409. if ( !Playback_ReadTimings_R( pCur ) )
  410. return false;
  411. }
  412. return true;
  413. }
  414. // Read the next tick. If iDontGoPast is set, then it will abort IF the next tick's index
  415. // is greater than iDontGoPast. In that case, sets pWouldHaveGonePast to true,
  416. // stays where it was before the call, and returns true.
  417. bool Playback_ReadTick( int iDontGoPast = -1, bool *pWouldHaveGonePast = NULL )
  418. {
  419. if ( pWouldHaveGonePast )
  420. *pWouldHaveGonePast = false;
  421. if ( m_Mode != Mode_Playback )
  422. return false;
  423. // Read the next tick..
  424. int token = Playback_ReadToken();
  425. if ( token == TOKEN_FILE_FINISHED )
  426. {
  427. Msg( "VPROF playback finished.\n" );
  428. m_iLastTick = m_iPlaybackTick; // Now we know our last tick.
  429. return true;
  430. }
  431. if ( !Playback_Assert( token == Token_StartFrame ) )
  432. return false;
  433. int iPlaybackTick = m_iPlaybackTick;
  434. g_pFileSystem->Read( &iPlaybackTick, sizeof( iPlaybackTick ), m_hFile );
  435. // First test if this tick would go past the number they don't want us to go past.
  436. if ( iDontGoPast != -1 && iPlaybackTick > iDontGoPast )
  437. {
  438. *pWouldHaveGonePast = true;
  439. g_pFileSystem->Seek( m_hFile, -5, FILESYSTEM_SEEK_CURRENT );
  440. return true;
  441. }
  442. else
  443. {
  444. m_iPlaybackTick = iPlaybackTick;
  445. }
  446. while ( 1 )
  447. {
  448. token = Playback_ReadToken();
  449. if ( token == Token_EndOfFrame )
  450. break;
  451. if ( token == Token_AddBudgetGroup )
  452. {
  453. if ( !Playback_ReadAddBudgetGroup() )
  454. return false;
  455. }
  456. else if ( token == Token_AddNode )
  457. {
  458. if ( !Playback_ReadAddNode() )
  459. return false;
  460. }
  461. else if ( token == Token_Timings )
  462. {
  463. if ( !Playback_ReadTimings_R( GetRoot() ) )
  464. return false;
  465. }
  466. else
  467. {
  468. Playback_Assert( false );
  469. return false;
  470. }
  471. }
  472. return true;
  473. }
  474. void Playback_Snapshot()
  475. {
  476. if ( m_Mode == Mode_Playback && !m_bPlaybackPaused )
  477. Playback_ReadTick();
  478. }
  479. void Playback_Step()
  480. {
  481. Playback_ReadTick();
  482. }
  483. class CNodeAverage
  484. {
  485. public:
  486. CVProfNode *m_pNode;
  487. CCycleCount m_CurFrameTime_Total;
  488. int m_nCurFrameCalls_Total;
  489. int m_nSamples;
  490. };
  491. CNodeAverage* FindNodeAverage( CUtlVector<CNodeAverage> &averages, CVProfNode *pNode )
  492. {
  493. for ( int i=0; i < averages.Count(); i++ )
  494. {
  495. if ( averages[i].m_pNode == pNode )
  496. return &averages[i];
  497. }
  498. return NULL;
  499. }
  500. void UpdateAverages_R( CUtlVector<CNodeAverage> &averages, CVProfNode *pNode )
  501. {
  502. CNodeAverage *pAverage = FindNodeAverage( averages, pNode );
  503. if ( !pAverage )
  504. {
  505. pAverage = &averages[ averages.AddToTail() ];
  506. memset( pAverage, 0, sizeof( *pAverage ) );
  507. pAverage->m_pNode = pNode;
  508. }
  509. pAverage->m_CurFrameTime_Total += pNode->m_CurFrameTime;
  510. pAverage->m_nCurFrameCalls_Total += pNode->m_nCurFrameCalls;
  511. pAverage->m_nSamples++;
  512. // Recurse.
  513. for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
  514. UpdateAverages_R( averages, pCur );
  515. }
  516. void DumpAverages_R( CUtlVector<CNodeAverage> &averages, CVProfNode *pNode )
  517. {
  518. CNodeAverage *pAverage = FindNodeAverage( averages, pNode );
  519. if ( pAverage )
  520. {
  521. pNode->m_CurFrameTime.m_Int64 = pAverage->m_CurFrameTime_Total.m_Int64 / pAverage->m_nSamples;
  522. pNode->m_nCurFrameCalls = pAverage->m_nCurFrameCalls_Total / pAverage->m_nSamples;
  523. }
  524. pNode->m_PrevFrameTime = pNode->m_CurFrameTime;
  525. pNode->m_nPrevFrameCalls = pNode->m_nCurFrameCalls;
  526. // Recurse.
  527. for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
  528. DumpAverages_R( averages, pCur );
  529. }
  530. void Playback_Average( int nFrames )
  531. {
  532. // Remember where we started.
  533. unsigned long seekPos = g_pFileSystem->Tell( m_hFile );
  534. int iOldLastTick = m_iLastTick;
  535. int iOldPlaybackTick = m_iPlaybackTick;
  536. // Take the average of the next N ticks.
  537. CUtlVector<CNodeAverage> averages;
  538. while ( nFrames > 0 && m_iLastTick == -1 )
  539. {
  540. Playback_ReadTick();
  541. UpdateAverages_R( averages, GetRoot() );
  542. --nFrames;
  543. }
  544. DumpAverages_R( averages, GetRoot() );
  545. // Now seek back to where we started.
  546. g_pFileSystem->Seek( m_hFile, seekPos, FILESYSTEM_SEEK_HEAD );
  547. m_iLastTick = iOldLastTick;
  548. m_iPlaybackTick = iOldPlaybackTick;
  549. }
  550. int Playback_SetPlaybackTick( int iTick )
  551. {
  552. if ( m_Mode != Mode_Playback )
  553. return 0;
  554. m_bNodesChanged = false; // We want to pickup changes to this, so reset it here.
  555. if ( iTick == m_iPlaybackTick )
  556. {
  557. return 1;
  558. }
  559. else if ( iTick < m_iPlaybackTick )
  560. {
  561. // Crap.. have to go back. Restart and seek to this tick.
  562. Playback_Restart();
  563. // If this tick has a smaller value than the first tick in the file, then we can't seek forward to it...
  564. if ( iTick <= m_iPlaybackTick )
  565. {
  566. return 1 + m_bNodesChanged; // return 2 if the nodes changed
  567. }
  568. }
  569. // Now seek forward to the tick they want.
  570. while ( m_iPlaybackTick < iTick )
  571. {
  572. bool bWouldHaveGonePast;
  573. if ( !Playback_ReadTick( iTick, &bWouldHaveGonePast ) )
  574. return 0; // error
  575. // If reading this tick would have gone past the tick they're asking us to go for,
  576. // stay on the current tick.
  577. if ( bWouldHaveGonePast )
  578. break;
  579. // If we went to the last tick in the file, then stop here.
  580. if ( m_iLastTick != -1 && m_iPlaybackTick >= m_iLastTick )
  581. return 1 + m_bNodesChanged;
  582. }
  583. return 1 + m_bNodesChanged;
  584. }
  585. // 0-1 value.
  586. float Playback_GetCurrentPercent()
  587. {
  588. return (float)g_pFileSystem->Tell( m_hFile ) / m_FileLen;
  589. }
  590. int Playback_SeekToPercent( float flWantedPercent )
  591. {
  592. if ( m_Mode != Mode_Playback )
  593. return 0; // error
  594. m_bNodesChanged = false; // We want to pickup changes to this, so reset it here.
  595. float flCurPercent = Playback_GetCurrentPercent();
  596. if ( flWantedPercent < flCurPercent )
  597. {
  598. // Crap.. have to go back. Restart and seek to this tick.
  599. Playback_Restart();
  600. // If this tick has a smaller value than the first tick in the file, then we can't seek forward to it...
  601. if ( flWantedPercent <= 0 )
  602. return 1 + m_bNodesChanged; // return 2 if nodes changed
  603. }
  604. // Now seek forward to the tick they want.
  605. while ( Playback_GetCurrentPercent() < flWantedPercent )
  606. {
  607. if ( !Playback_ReadTick() )
  608. return 0; // error
  609. // If we went to the last tick in the file, then stop here.
  610. if ( m_iLastTick != -1 && m_iPlaybackTick >= m_iLastTick )
  611. return 1 + m_bNodesChanged; // return 2 if nodes changed
  612. }
  613. return 1 + m_bNodesChanged;
  614. }
  615. int Playback_GetCurrentTick()
  616. {
  617. return m_iPlaybackTick;
  618. }
  619. // OTHER FUNCTIONS.
  620. public:
  621. void Snapshot()
  622. {
  623. if ( m_Mode == Mode_Record )
  624. Record_Snapshot();
  625. else if ( m_Mode == Mode_Playback )
  626. Playback_Snapshot();
  627. }
  628. void StartOrStop()
  629. {
  630. while ( m_nQueuedStarts > 0 )
  631. {
  632. --m_nQueuedStarts;
  633. g_VProfCurrentProfile.Start();
  634. }
  635. while ( m_nQueuedStops > 0 )
  636. {
  637. --m_nQueuedStops;
  638. g_VProfCurrentProfile.Stop();
  639. }
  640. }
  641. inline CVProfile* GetActiveProfile()
  642. {
  643. if ( m_Mode == Mode_Playback )
  644. return this;
  645. else
  646. return &g_VProfCurrentProfile;
  647. }
  648. private:
  649. const char* PoolString( const char *pStr )
  650. {
  651. int i = m_PooledStrings.Find( pStr );
  652. if ( i == m_PooledStrings.InvalidIndex() )
  653. i = m_PooledStrings.Insert( pStr, 0 );
  654. return m_PooledStrings.GetElementName( i );
  655. }
  656. private:
  657. enum
  658. {
  659. Token_StartFrame=0,
  660. Token_AddNode,
  661. Token_AddBudgetGroup,
  662. Token_Timings,
  663. Token_EndOfFrame,
  664. TOKEN_FILE_FINISHED
  665. };
  666. enum
  667. {
  668. VPROF_FILE_VERSION = 1
  669. };
  670. enum
  671. {
  672. Mode_None,
  673. Mode_Record,
  674. Mode_Playback
  675. };
  676. CUtlDict<int,int> m_PooledStrings;
  677. int m_Mode;
  678. FileHandle_t m_hFile;
  679. int m_iLastUniqueNodeID;
  680. int m_iPlaybackTick; // Our current tick.
  681. int m_iSkipPastHeaderPos;
  682. int m_iLastTick; // We only know this when we hit the end of the file.
  683. int m_FileLen;
  684. bool m_bNodesChanged; // Set if the nodes were added or removed.
  685. int m_nQueuedStarts;
  686. int m_nQueuedStops;
  687. bool m_bPlaybackPaused;
  688. };
  689. static CVProfRecorder g_VProfRecorder;
  690. CON_COMMAND( vprof_record_start, "Start recording vprof data for playback later." )
  691. {
  692. if ( args.ArgC() != 2 )
  693. {
  694. Warning( "vprof_record_start requires a filename\n" );
  695. return;
  696. }
  697. g_VProfRecorder.Record_Start( args[1] );
  698. }
  699. CON_COMMAND( vprof_record_stop, "Stop recording vprof data" )
  700. {
  701. Warning( "Stopping vprof recording...\n" );
  702. g_VProfRecorder.Stop();
  703. }
  704. CON_COMMAND( vprof_playback_start, "Start playing back a recorded .vprof file." )
  705. {
  706. if ( args.ArgC() < 2 )
  707. {
  708. Warning( "vprof_playback_start requires a filename\n" );
  709. return;
  710. }
  711. // Console parser treats colons as a break, so join all the tokens together here.
  712. char fullFilename[512];
  713. fullFilename[0] = 0;
  714. for ( int i=1; i < args.ArgC(); i++ )
  715. {
  716. Q_strncat( fullFilename, args[i], sizeof( fullFilename ), COPY_ALL_CHARACTERS );
  717. }
  718. g_VProfRecorder.Playback_Start( fullFilename );
  719. }
  720. CON_COMMAND( vprof_playback_stop, "Stop playing back a recorded .vprof file." )
  721. {
  722. Warning( "Stopping vprof playback...\n" );
  723. g_VProfRecorder.Stop();
  724. }
  725. CON_COMMAND( vprof_playback_step, "While playing back a .vprof file, step to the next tick." )
  726. {
  727. VProfPlayback_Step();
  728. }
  729. CON_COMMAND( vprof_playback_stepback, "While playing back a .vprof file, step to the previous tick." )
  730. {
  731. VProfPlayback_StepBack();
  732. }
  733. CON_COMMAND( vprof_playback_average, "Average the next N frames." )
  734. {
  735. if ( args.ArgC() >= 2 )
  736. {
  737. int nFrames = atoi( args[ 1 ] );
  738. if ( nFrames == -1 )
  739. nFrames = 9999999;
  740. g_VProfRecorder.Playback_Average( nFrames );
  741. }
  742. else
  743. {
  744. Warning( "vprof_playback_average [# frames]\n" );
  745. Warning( "If # frames is -1, then it will average all the remaining frames in the vprof file.\n" );
  746. }
  747. }
  748. void VProfRecord_Snapshot()
  749. {
  750. g_VProfRecorder.Snapshot();
  751. }
  752. void VProfRecord_StartOrStop()
  753. {
  754. g_VProfRecorder.StartOrStop();
  755. }
  756. void VProfRecord_Shutdown()
  757. {
  758. g_VProfRecorder.Shutdown();
  759. }
  760. bool VProfRecord_IsPlayingBack()
  761. {
  762. return g_VProfRecorder.IsPlayingBack();
  763. }
  764. int VProfPlayback_GetCurrentTick()
  765. {
  766. return g_VProfRecorder.Playback_GetCurrentTick();
  767. }
  768. float VProfPlayback_GetCurrentPercent()
  769. {
  770. return g_VProfRecorder.Playback_GetCurrentPercent();
  771. }
  772. int VProfPlayback_SetPlaybackTick( int iTick )
  773. {
  774. return g_VProfRecorder.Playback_SetPlaybackTick( iTick );
  775. }
  776. int VProfPlayback_SeekToPercent( float percent )
  777. {
  778. return g_VProfRecorder.Playback_SeekToPercent( percent );
  779. }
  780. void VProfPlayback_Step()
  781. {
  782. g_VProfRecorder.Playback_Step();
  783. }
  784. int VProfPlayback_StepBack()
  785. {
  786. return g_VProfRecorder.Playback_SetPlaybackTick( g_VProfRecorder.Playback_GetCurrentTick() - 1 );
  787. }
  788. #endif // VPROF_ENABLED