| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- /*
- ** Command & Conquer Generals Zero Hour(tm)
- ** Copyright 2025 Electronic Arts Inc.
- **
- ** This program is free software: you can redistribute it and/or modify
- ** it under the terms of the GNU General Public License as published by
- ** the Free Software Foundation, either version 3 of the License, or
- ** (at your option) any later version.
- **
- ** This program is distributed in the hope that it will be useful,
- ** but WITHOUT ANY WARRANTY; without even the implied warranty of
- ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ** GNU General Public License for more details.
- **
- ** You should have received a copy of the GNU General Public License
- ** along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // (c) 2001-2003 Electronic Arts Inc. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // FILE: PerfTimer.cpp ///////////////////////////////////////////////////////////////////////////
- // Author:
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
- #include "Common/PerfTimer.h"
- #include "Common/GlobalData.h"
- #include "GameClient/DebugDisplay.h"
- #include "GameClient/Display.h"
- #include "GameClient/GraphDraw.h"
- __forceinline void ProfileGetTime(__int64 &t)
- {
- _asm
- {
- mov ecx,[t]
- push eax
- push edx
- rdtsc
- mov [ecx],eax
- mov [ecx+4],edx
- pop edx
- pop eax
- };
- }
- #ifdef _INTERNAL
- // for occasional debugging...
- //#pragma optimize("", off)
- //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
- #endif
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- #if defined(PERF_TIMERS) || defined(DUMP_PERF_STATS)
- //-------------------------------------------------------------------------------------------------
- static Int64 s_ticksPerSec = 0;
- static double s_ticksPerMSec = 0;
- static double s_ticksPerUSec = 0;
- //-------------------------------------------------------------------------------------------------
- void GetPrecisionTimerTicksPerSec(Int64* t)
- {
- *t = s_ticksPerSec;
- }
- //Kris: Plugged in Martin's code to optimize timer setup.
- #define HOFFESOMMER_REPLACEMENT_CODE
- //-------------------------------------------------------------------------------------------------
- void InitPrecisionTimer()
- {
- #ifdef HOFFESOMMER_REPLACEMENT_CODE
- // measure clock cycles 3 times for 20 msec each
- // then take the 2 counts that are closest, average
- _int64 n[ 3 ];
- for( int k = 0; k < 3; k++ )
- {
- // wait for end of current tick
- unsigned timeEnd = timeGetTime() + 2;
- while( timeGetTime() < timeEnd ); //do nothing
-
- // get cycles
- _int64 start, startQPC, endQPC;
- QueryPerformanceCounter( (LARGE_INTEGER *)&startQPC );
- ProfileGetTime( start );
- timeEnd += 20;
- while( timeGetTime() < timeEnd ); //do nothing
- ProfileGetTime( n[ k ] );
- n[ k ] -= start;
-
- // convert to 1 second
- if( QueryPerformanceCounter( (LARGE_INTEGER*)&endQPC ) )
- {
- QueryPerformanceFrequency( (LARGE_INTEGER*)&s_ticksPerSec );
- n[ k ] = ( n[ k ] * s_ticksPerSec ) / ( endQPC - startQPC );
- }
- else
- {
- n[ k ] = ( n[ k ] * 1000 ) / 20;
- }
- }
-
- // find two closest values
- _int64 d01 = n[ 1 ] - n[ 0 ];
- _int64 d02 = n[ 2 ] - n[ 0 ];
- _int64 d12 = n[ 2 ] - n[ 1 ];
- if( d01 < 0 )
- {
- d01 = -d01;
- }
- if( d02 < 0 )
- {
- d02 = -d02;
- }
- if( d12 < 0 )
- {
- d12 = -d12;
- }
- _int64 avg;
- if( d01 < d02 )
- {
- avg = d01 < d12 ? n[ 0 ] + n[ 1 ] : n[ 1 ] + n[ 2 ];
- }
- else
- {
- avg = d02 < d12 ? n[ 0 ] + n[ 2 ] : n[ 1 ] + n[ 2 ];
- }
- //s_ticksPerMSec = 1.0 * TotalTicks / totalTime;
- s_ticksPerMSec = avg / 2000.0f;
- s_ticksPerSec = s_ticksPerMSec * 1000.0f;
- s_ticksPerUSec = s_ticksPerSec / 1000000.0f;
-
- #else
- //Kris: With total disrespect, this code costs 5 real seconds of init time
- //whenever we fire up the game.
- #ifdef USE_QPF
- QueryPerformanceFrequency((LARGE_INTEGER*)&s_ticksPerSec);
- #else
- // Init the precision timers
- Int64 totalTime = 0;
- Int64 TotalTicks = 0;
- static int TESTS = 5;
-
- for (int i = 0; i < TESTS; ++i)
- {
- int TimeStart;
- int TimeStop;
- Int64 StartTicks;
- Int64 EndTicks;
- TimeStart = timeGetTime();
- GetPrecisionTimer(&StartTicks);
- for(;;)
- {
- TimeStop = timeGetTime();
- if ((TimeStop - TimeStart) > 1000)
- {
- GetPrecisionTimer(&EndTicks);
- break;
- }
- }
- TotalTicks += (EndTicks - StartTicks);
- totalTime += (TimeStop - TimeStart);
- }
- s_ticksPerMSec = 1.0 * TotalTicks / totalTime;
- s_ticksPerSec = s_ticksPerMSec * 1000.0f;
- #endif
- s_ticksPerMSec = s_ticksPerSec / 1000.0f;
- s_ticksPerUSec = s_ticksPerSec / 1000000.0f;
- #ifdef NOT_IN_USE
- Int64 bogus[8];
- GetPrecisionTimer(&start);
- for (Int ii = 0; ii < ITERS; ++ii)
- {
- GetPrecisionTimer(&bogus[0]);
- GetPrecisionTimer(&bogus[1]);
- GetPrecisionTimer(&bogus[2]);
- GetPrecisionTimer(&bogus[3]);
- GetPrecisionTimer(&bogus[4]);
- GetPrecisionTimer(&bogus[5]);
- GetPrecisionTimer(&bogus[6]);
- GetPrecisionTimer(&bogus[7]);
- }
- TheTicksToGetTicks = (bogus[7] - start) / (ITERS*8);
- DEBUG_LOG(("TheTicksToGetTicks is %d (%f usec)\n",(int)TheTicksToGetTicks,TheTicksToGetTicks/s_ticksPerUSec));
- #endif
-
- #endif
- }
- #endif // defined(PERF_TIMERS) || defined(DUMP_PERF_STATS)
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- #ifdef PERF_TIMERS
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- /*static*/ Bool AutoPerfGatherIgnore::s_ignoring = false;
- //-------------------------------------------------------------------------------------------------
- typedef std::vector< std::pair< AsciiString, AsciiString > > StringPairVec;
- //-------------------------------------------------------------------------------------------------
- // PerfMetrics class. Basically, request a handle with your name and it will return. We use a vector
- // of pairs of asciistrings for this
- class PerfMetricsOutput
- {
- private:
- StringPairVec m_outputStats;
- public:
- AsciiString& getStatsString(const AsciiString& id)
- {
- for (int i = 0; i < m_outputStats.size(); ++i)
- {
- if (m_outputStats[i].first == id)
- return m_outputStats[i].second;
- }
- std::pair<AsciiString, AsciiString> newPair;
- newPair.first = id;
- m_outputStats.push_back(newPair);
- return m_outputStats.back().second;
- }
- void clearStatsString(const AsciiString& id)
- {
- for (int i = 0; i < m_outputStats.size(); ++i)
- {
- if (m_outputStats[i].first == id)
- {
- m_outputStats.erase(&m_outputStats[i]);
- return;
- }
- }
- }
- StringPairVec& friend_getAllStatsStrings() { return m_outputStats; }
- };
- //-------------------------------------------------------------------------------------------------
- static PerfMetricsOutput s_output;
- static FILE* s_perfStatsFile = NULL;
- static Int s_perfDumpOptions = 0;
- static UnsignedInt s_lastDumpedFrame = 0;
- static char s_buf[256] = "";
- PerfGather* PerfGather::m_active[MAX_ACTIVE_STACK] = { 0 };
- PerfGather** PerfGather::m_activeHead = &PerfGather::m_active[0];
- Int64 PerfGather::s_stopStartOverhead = -1;
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- /*static*/ PerfGather*& PerfGather::getHeadPtr()
- {
- // funky technique for order-of-init problem. trust me. (srj)
- static PerfGather* s_head = NULL;
- return s_head;
- }
- //-------------------------------------------------------------------------------------------------
- void PerfGather::addToList()
- {
- PerfGather*& head = getHeadPtr();
- this->m_next = head;
- if (head)
- head->m_prev = this;
- head = this;
- }
- //-------------------------------------------------------------------------------------------------
- void PerfGather::removeFromList()
- {
- PerfGather*& head = getHeadPtr();
- if (this->m_next)
- this->m_next->m_prev = this->m_prev;
- if (this->m_prev)
- this->m_prev->m_next = this->m_next;
- else
- head = this->m_next;
- this->m_prev = 0;
- this->m_next = 0;
- }
- //-------------------------------------------------------------------------------------------------
- PerfGather::PerfGather(const char *identifier) :
- m_identifier(identifier),
- m_startTime(0),
- m_runningTimeGross(0),
- m_runningTimeNet(0),
- m_callCount(0),
- m_next(0),
- m_prev(0)
- {
- //Added By Sadullah Nader
- //Initializations inserted
- m_ignore = FALSE;
- //
- DEBUG_ASSERTCRASH(strchr(m_identifier, ',') == NULL, ("PerfGather names must not contain commas"));
- addToList();
- }
- //-------------------------------------------------------------------------------------------------
- PerfGather::~PerfGather()
- {
- removeFromList();
- }
- //-------------------------------------------------------------------------------------------------
- void PerfGather::reset()
- {
- m_startTime = 0;
- m_runningTimeGross = 0;
- m_runningTimeNet = 0;
- m_callCount = 0;
- }
- //-------------------------------------------------------------------------------------------------
- /*static*/ void PerfGather::resetAll()
- {
- for (PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
- {
- head->reset();
- }
- }
- //-------------------------------------------------------------------------------------------------
- /*static*/ void PerfGather::initPerfDump(const char* fname, Int options)
- {
- PerfGather::termPerfDump();
- strcpy(s_buf, fname);
- char tmp[256];
- strcpy(tmp, s_buf);
- strcat(tmp, ".csv");
- s_perfStatsFile = fopen(tmp, "w");
- s_perfDumpOptions = options;
- if (s_perfStatsFile == NULL)
- {
- DEBUG_CRASH(("could not open/create perf file %s -- is it open in another app?",s_buf));
- return;
- }
-
- if (s_stopStartOverhead == -1)
- {
- const Int ITERS = 100000;
- Int64 start, end;
- PerfGather pf("timer");
- GetPrecisionTimer(&start);
- for (Int ii = 0; ii < ITERS; ++ii)
- {
- pf.startTimer(); pf.stopTimer();
- pf.startTimer(); pf.stopTimer();
- pf.startTimer(); pf.stopTimer();
- pf.startTimer(); pf.stopTimer();
- pf.startTimer(); pf.stopTimer();
- pf.startTimer(); pf.stopTimer();
- pf.startTimer(); pf.stopTimer();
- pf.startTimer(); pf.stopTimer();
- }
- GetPrecisionTimer(&end);
- s_stopStartOverhead = (end - start) / (ITERS*8);
- DEBUG_LOG(("s_stopStartOverhead is %d (%f usec)\n",(int)s_stopStartOverhead,s_stopStartOverhead/s_ticksPerUSec));
- }
- }
- //-------------------------------------------------------------------------------------------------
- /*static*/ void PerfGather::dumpAll(UnsignedInt frame)
- {
- if (frame < s_lastDumpedFrame)
- {
- // must have reset or started a new game.
- termPerfDump();
- initPerfDump(s_buf, s_perfDumpOptions);
- }
- if (!s_perfStatsFile)
- {
- DEBUG_CRASH(("not inited"));
- return;
- }
- if (frame >= 1 && frame <= 30)
- {
- // always skip the first second or so, since it loads everything and skews the results horribly
- }
- else
- {
- if (s_lastDumpedFrame == 0)
- {
- fprintf(s_perfStatsFile, "Frame");
- if (s_perfDumpOptions & PERF_GROSSTIME)
- {
- for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
- {
- fprintf(s_perfStatsFile, ",Gross:%s", head->m_identifier);
- }
- }
- if (s_perfDumpOptions & PERF_NETTIME)
- {
- for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
- {
- fprintf(s_perfStatsFile, ",Net:%s", head->m_identifier);
- }
- }
- if (s_perfDumpOptions & PERF_CALLCOUNT)
- {
- for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
- {
- fprintf(s_perfStatsFile, ",Count:%s", head->m_identifier);
- }
- }
- fprintf(s_perfStatsFile, "\n");
- }
-
- // a strange value so we can find it in the dump, if necessary.
- // there's nothing magic about this value, it's purely determined from sample dumps...
- // const Real CLIP_BIG_SPIKES = 1e10f;
- const Real CLIP_BIG_SPIKES = 100000.0f;
- // make this a nonnumeric thing so Excel won't try to graph it...
- fprintf(s_perfStatsFile, "Frame%08d", frame);
- if (s_perfDumpOptions & PERF_GROSSTIME)
- {
- for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
- {
- double t = head->m_runningTimeGross;
- t /= s_ticksPerUSec;
- if (t > CLIP_BIG_SPIKES)
- t = CLIP_BIG_SPIKES;
- fprintf(s_perfStatsFile, ",%f", t);
- }
- }
- if (s_perfDumpOptions & PERF_NETTIME)
- {
- for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
- {
- double t = head->m_runningTimeNet;
- t /= s_ticksPerUSec;
- if (t > CLIP_BIG_SPIKES)
- t = CLIP_BIG_SPIKES;
- fprintf(s_perfStatsFile, ",%f", t);
- }
- }
- if (s_perfDumpOptions & PERF_CALLCOUNT)
- {
- for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
- {
- fprintf(s_perfStatsFile, ",%d", head->m_callCount);
- }
- }
- fprintf(s_perfStatsFile, "\n");
- fflush(s_perfStatsFile);
- s_lastDumpedFrame = frame;
- }
- }
- //-------------------------------------------------------------------------------------------------
- // This function will queue up stuff to draw on the next frame. We also need to adjust the
- // perf timers to not include time spent paused by the script engine.
- /*static*/ void PerfGather::displayGraph(UnsignedInt frame)
- {
- if (!TheGraphDraw) {
- return;
- }
- if (frame >= 1 && frame <= 30)
- {
- // always skip the first second or so, since it loads everything and skews the results horribly
- }
- else
- {
- const Real CLIP_BIG_SPIKES = 100000.0f;
- if (s_perfDumpOptions & PERF_GROSSTIME)
- {
- for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
- {
- Real t = head->m_runningTimeGross;
- t /= s_ticksPerUSec;
- if (t > CLIP_BIG_SPIKES)
- t = CLIP_BIG_SPIKES;
- TheGraphDraw->addEntry(head->m_identifier, REAL_TO_INT(t));
- }
- }
- if (s_perfDumpOptions & PERF_NETTIME)
- {
- for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
- {
- Real t = head->m_runningTimeNet;
- t /= s_ticksPerUSec;
- if (t > CLIP_BIG_SPIKES)
- t = CLIP_BIG_SPIKES;
-
- TheGraphDraw->addEntry(head->m_identifier, REAL_TO_INT(t));
- }
- }
- if (s_perfDumpOptions & PERF_CALLCOUNT)
- {
- for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
- {
- Real t = head->m_callCount;
- TheGraphDraw->addEntry(head->m_identifier, REAL_TO_INT(t));
-
- }
- }
- }
- }
- //-------------------------------------------------------------------------------------------------
- /*static*/ void PerfGather::termPerfDump()
- {
- if (s_perfStatsFile)
- {
- fflush(s_perfStatsFile);
- fclose(s_perfStatsFile);
- s_perfStatsFile = NULL;
- }
- s_lastDumpedFrame = 0;
- }
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- PerfTimer::PerfTimer( const char *identifier, Bool crashWithInfo, Int startFrame, Int endFrame) :
- m_identifier(identifier),
- m_crashWithInfo(crashWithInfo),
- m_startFrame(startFrame),
- m_endFrame(endFrame),
- m_callCount(0),
- m_runningTime(0),
- m_outputInfo(true),
- //Added By Sadullah Nader
- //Intializations inserted
- m_lastFrame(-1)
- {
- }
- //-------------------------------------------------------------------------------------------------
- PerfTimer::~PerfTimer( )
- {
- if (m_endFrame == -1) {
- outputInfo();
- }
- }
- //-------------------------------------------------------------------------------------------------
- void PerfTimer::outputInfo( void )
- {
- if (TheGlobalData->m_showMetrics) {
- return;
- }
- if (m_outputInfo && !TheGlobalData->m_showMetrics) {
- m_outputInfo = false;
- } else {
- return;
- }
- if (!s_ticksPerSec) {
- // DEBUG_CRASH here
- return;
- }
- #if defined(_DEBUG) || defined(_INTERNAL)
- double totalTimeInMS = 1000.0 * m_runningTime / s_ticksPerSec;
- double avgTimePerFrame = totalTimeInMS / (m_lastFrame - m_startFrame + 1);
- double avgTimePerCall = totalTimeInMS / m_callCount;
- #endif
- if (m_crashWithInfo) {
- DEBUG_CRASH(("%s\n"
- "Average Time (per call): %.4f ms\n"
- "Average Time (per frame): %.4f ms\n"
- "Average calls per frame: %.2f\n"
- "Number of calls: %d\n"
- "Max possible FPS: %.4f\n",
- m_identifier,
- avgTimePerCall,
- avgTimePerFrame,
- 1.0f * m_callCount / (m_lastFrame - m_startFrame + 1),
- m_callCount,
- 1000.0f / avgTimePerFrame));
- } else {
- DEBUG_LOG(("%s\n"
- "Average Time (per call): %.4f ms\n"
- "Average Time (per frame): %.4f ms\n"
- "Average calls per frame: %.2f\n"
- "Number of calls: %d\n"
- "Max possible FPS: %.4f\n",
- m_identifier,
- avgTimePerCall,
- avgTimePerFrame,
- 1.0f * m_callCount / (m_lastFrame - m_startFrame + 1),
- m_callCount,
- 1000.0f / avgTimePerFrame));
- }
- }
- //-------------------------------------------------------------------------------------------------
- void PerfTimer::showMetrics( void )
- {
- #if defined(_DEBUG) || defined(_INTERNAL)
- double totalTimeInMS = 1000.0 * m_runningTime / s_ticksPerSec;
- double avgTimePerFrame = totalTimeInMS / (m_lastFrame - m_startFrame + 1);
- double avgTimePerCall = totalTimeInMS / m_callCount;
- #endif
- // we want to work on the thing in the array, so just store a reference.
- AsciiString &outputStats = s_output.getStatsString(m_identifier);
- outputStats.format("%s: %.2fms / call, %.2fms / frame \n",
- m_identifier,
- avgTimePerCall,
- avgTimePerFrame);
- m_callCount = 0;
- m_runningTime = 0;
- UnsignedInt frm = (TheGameLogic ? TheGameLogic->getFrame() : m_startFrame);
- m_startFrame = frm + 1;
- m_endFrame = m_startFrame + PERFMETRICS_BETWEEN_METRICS;
- }
- //-------------------------------------------------------------------------------StatMetricsDisplay
- void StatMetricsDisplay( DebugDisplayInterface *dd, void *, FILE *fp )
- {
- dd->printf("Performance Metrics: \n");
- // no copies will take place because we are storing a reference to the thing
- StringPairVec &stats = s_output.friend_getAllStatsStrings();
- for (int i = 0; i < stats.size(); ++i) {
- dd->printf("%s", stats[i].second.str());
- }
- }
- #endif // PERF_TIMERS
|