Coverage Report

Created: 2025-11-11 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ogre/OgreMain/src/OgreProfiler.cpp
Line
Count
Source
1
/*
2
-----------------------------------------------------------------------------
3
This source file is part of OGRE
4
    (Object-oriented Graphics Rendering Engine)
5
For the latest info, see http://www.ogre3d.org/
6
7
Copyright (c) 2000-2014 Torus Knot Software Ltd
8
9
Permission is hereby granted, free of charge, to any person obtaining a copy
10
of this software and associated documentation files (the "Software"), to deal
11
in the Software without restriction, including without limitation the rights
12
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
copies of the Software, and to permit persons to whom the Software is
14
furnished to do so, subject to the following conditions:
15
16
The above copyright notice and this permission notice shall be included in
17
all copies or substantial portions of the Software.
18
19
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
THE SOFTWARE.
26
-----------------------------------------------------------------------------
27
*/
28
#include "OgreStableHeaders.h"
29
/*
30
31
    Although the code is original, many of the ideas for the profiler were borrowed from 
32
"Real-Time In-Game Profiling" by Steve Rabin which can be found in Game Programming
33
Gems 1.
34
35
    This code can easily be adapted to your own non-Ogre project. The only code that is 
36
Ogre-dependent is in the visualization/logging routines and the use of the Timer class.
37
38
    Enjoy!
39
40
*/
41
42
#include "OgreTimer.h"
43
44
#ifdef USE_REMOTERY
45
#include "Remotery.h"
46
static Remotery* rmt;
47
#endif
48
49
namespace Ogre {
50
    //-----------------------------------------------------------------------
51
    // PROFILE DEFINITIONS
52
    //-----------------------------------------------------------------------
53
    template<> Profiler* Singleton<Profiler>::msSingleton = 0;
54
    Profiler* Profiler::getSingletonPtr(void)
55
0
    {
56
0
        return msSingleton;
57
0
    }
58
    Profiler& Profiler::getSingleton(void)
59
0
    {  
60
0
        assert( msSingleton );  return ( *msSingleton );  
61
0
    }
62
63
    //-----------------------------------------------------------------------
64
    // PROFILER DEFINITIONS
65
    //-----------------------------------------------------------------------
66
    Profiler::Profiler() 
67
0
        : mCurrent(&mRoot)
68
0
        , mLast(NULL)
69
0
        , mRoot()
70
0
        , mInitialized(false)
71
0
        , mUpdateDisplayFrequency(10)
72
0
        , mCurrentFrame(0)
73
0
        , mTimer(0)
74
0
        , mTotalFrameTime(0)
75
0
        , mEnabled(false)
76
0
        , mNewEnableState(false)
77
0
        , mProfileMask(0xFFFFFFFF)
78
0
        , mMaxTotalFrameTime(0)
79
0
        , mAverageFrameTime(0)
80
0
        , mResetExtents(false)
81
0
    {
82
0
        mRoot.hierarchicalLvl = 0 - 1;
83
84
#ifdef USE_REMOTERY
85
        rmt_Settings()->reuse_open_port = true;
86
        if(auto error = rmt_CreateGlobalInstance(&rmt))
87
        {
88
            LogManager::getSingleton().logError("Could not launch Remotery - RMT_ERROR " + std::to_string(error));
89
            return;
90
        }
91
        rmt_SetCurrentThreadName("Ogre Main");
92
#endif
93
0
    }
94
    //-----------------------------------------------------------------------
95
    ProfileInstance::ProfileInstance(void)
96
0
        : parent(NULL)
97
0
        , frameNumber(0)
98
0
        , accum(0)
99
0
        , hierarchicalLvl(0)
100
0
    {
101
0
        history.numCallsThisFrame = 0;
102
0
        history.totalTimePercent = 0;
103
0
        history.totalTimeMillisecs = 0;
104
0
        history.totalCalls = 0;
105
0
        history.maxTimePercent = 0;
106
0
        history.maxTimeMillisecs = 0;
107
0
        history.minTimePercent = 1;
108
0
        history.minTimeMillisecs = 100000;
109
0
        history.currentTimePercent = 0;
110
0
        history.currentTimeMillisecs = 0;
111
112
0
        frame.frameTime = 0;
113
0
        frame.calls = 0;
114
0
    }
115
    ProfileInstance::~ProfileInstance(void)
116
0
    {                                        
117
0
        for(auto & it : children)
118
0
        {
119
0
            ProfileInstance* instance = it.second;
120
0
            OGRE_DELETE instance;
121
0
        }
122
0
        children.clear();
123
0
    }
124
    //-----------------------------------------------------------------------
125
    Profiler::~Profiler()
126
0
    {
127
#ifdef USE_REMOTERY
128
        rmt_DestroyGlobalInstance(rmt);
129
#else
130
0
        if (!mRoot.children.empty()) 
131
0
        {
132
            // log the results of our profiling before we quit
133
0
            logResults();
134
0
        }
135
136
        // clear all our lists
137
0
        mDisabledProfiles.clear();
138
0
#endif
139
0
    }
140
    //-----------------------------------------------------------------------
141
    void Profiler::setTimer(Timer* t)
142
0
    {
143
0
        mTimer = t;
144
0
    }
145
    //-----------------------------------------------------------------------
146
    Timer* Profiler::getTimer()
147
0
    {
148
0
        assert(mTimer && "Timer not set!");
149
0
        return mTimer;
150
0
    }
151
    //-----------------------------------------------------------------------
152
    void Profiler::setEnabled(bool enabled) 
153
0
    {
154
0
        if (!mInitialized && enabled) 
155
0
        {
156
0
            for(auto & l : mListeners)
157
0
                l->initializeSession();
158
159
0
            mInitialized = true;
160
0
        }
161
0
        else if (mInitialized)
162
0
        {
163
0
            for(auto & l : mListeners)
164
0
                l->finializeSession();
165
166
0
            mInitialized = false;
167
0
            mEnabled = false;
168
0
        }
169
        // We store this enable/disable request until the frame ends
170
        // (don't want to screw up any open profiles!)
171
0
        mNewEnableState = enabled;
172
0
    }
173
    //-----------------------------------------------------------------------
174
    bool Profiler::getEnabled() const
175
0
    {
176
0
        return mEnabled;
177
0
    }
178
    //-----------------------------------------------------------------------
179
    void Profiler::changeEnableState() 
180
0
    {
181
0
        for(auto & l : mListeners)
182
0
            l->changeEnableState(mNewEnableState);
183
184
0
        mEnabled = mNewEnableState;
185
0
    }
186
    //-----------------------------------------------------------------------
187
    void Profiler::disableProfile(const String& profileName)
188
0
    {
189
        // even if we are in the middle of this profile, endProfile() will still end it.
190
0
        mDisabledProfiles.insert(profileName);
191
0
    }
192
    //-----------------------------------------------------------------------
193
    void Profiler::enableProfile(const String& profileName) 
194
0
    {
195
0
        mDisabledProfiles.erase(profileName);
196
0
    }
197
    //-----------------------------------------------------------------------
198
    void Profiler::beginProfile(const String& profileName, uint32 groupID) 
199
0
    {
200
#ifdef USE_REMOTERY
201
        // mask groups
202
        if ((groupID & mProfileMask) == 0)
203
            return;
204
205
        rmt_BeginCPUSampleDynamic(profileName.c_str(), RMTSF_Aggregate);
206
#else
207
        // if the profiler is enabled
208
0
        if (!mEnabled)
209
0
            return;
210
211
        // mask groups
212
0
        if ((groupID & mProfileMask) == 0)
213
0
            return;
214
215
        // empty string is reserved for the root
216
        // not really fatal anymore, however one shouldn't name one's profile as an empty string anyway.
217
0
        assert ((profileName != "") && ("Profile name can't be an empty string"));
218
219
        // we only process this profile if isn't disabled
220
0
        if (mDisabledProfiles.find(profileName) != mDisabledProfiles.end())
221
0
            return;
222
223
        // regardless of whether or not we are enabled, we need the application's root profile (ie the first profile started each frame)
224
        // we need this so bogus profiles don't show up when users enable profiling mid frame
225
        // so we check
226
227
        // this would be an internal error.
228
0
        assert (mCurrent);
229
230
        // need a timer to profile!
231
0
        assert (mTimer && "Timer not set!");
232
233
0
        ProfileInstance*& instance = mCurrent->children[profileName];
234
0
        if(instance)
235
0
        {   // found existing child.
236
237
            // Sanity check.
238
0
            assert(instance->name == profileName);
239
240
0
            if(instance->frameNumber != mCurrentFrame)
241
0
            {   // new frame, reset stats
242
0
                instance->frame.calls = 0;
243
0
                instance->frame.frameTime = 0;
244
0
                instance->frameNumber = mCurrentFrame;
245
0
            }
246
0
        }
247
0
        else
248
0
        {   // new child!
249
0
            instance = OGRE_NEW ProfileInstance();
250
0
            instance->name = profileName;
251
0
            instance->parent = mCurrent;
252
0
            instance->hierarchicalLvl = mCurrent->hierarchicalLvl + 1;
253
0
        }
254
255
0
        instance->frameNumber = mCurrentFrame;
256
257
0
        mCurrent = instance;
258
259
        // we do this at the very end of the function to get the most
260
        // accurate timing results
261
0
        mCurrent->currTime = mTimer->getMicroseconds();
262
0
#endif
263
0
    }
264
    //-----------------------------------------------------------------------
265
    void Profiler::endProfile(const String& profileName, uint32 groupID) 
266
0
    {
267
#ifdef USE_REMOTERY
268
        // mask groups
269
        if ((groupID & mProfileMask) == 0)
270
            return;
271
272
        rmt_EndCPUSample();
273
#else
274
0
        if(!mEnabled) 
275
0
        {
276
            // if the profiler received a request to be enabled or disabled
277
0
            if(mNewEnableState != mEnabled) 
278
0
            {   // note mNewEnableState == true to reach this.
279
0
                changeEnableState();
280
281
                // NOTE we will be in an 'error' state until the next begin. ie endProfile will likely get invoked using a profileName that was never started.
282
                // even then, we can't be sure that the next beginProfile will be the true start of a new frame
283
0
            }
284
285
0
            return;
286
0
        }
287
0
        else
288
0
        {
289
0
            if(mNewEnableState != mEnabled) 
290
0
            {   // note mNewEnableState == false to reach this.
291
0
                changeEnableState();
292
293
                // unwind the hierarchy, should be easy enough
294
0
                mCurrent = &mRoot;
295
0
                mLast = NULL;
296
0
            }
297
298
0
            if(&mRoot == mCurrent && mLast)
299
0
            {   // profiler was enabled this frame, but the first subsequent beginProfile was NOT the beinging of a new frame as we had hoped.
300
                // we have bogus ProfileInstance in our hierarchy, we will need to remove it, then update the overlays so as not to confuse ze user
301
302
0
                for(auto& e : mRoot.children)
303
0
                {
304
0
                    OGRE_DELETE e.second;
305
0
                }
306
0
                mRoot.children.clear();
307
308
0
                mLast = NULL;
309
310
0
                processFrameStats();
311
0
                displayResults();
312
0
            }
313
0
        }
314
315
0
        if(&mRoot == mCurrent)
316
0
            return;
317
318
        // mask groups
319
0
        if ((groupID & mProfileMask) == 0)
320
0
            return;
321
322
        // need a timer to profile!
323
0
        assert (mTimer && "Timer not set!");
324
325
        // get the end time of this profile
326
        // we do this as close the beginning of this function as possible
327
        // to get more accurate timing results
328
0
        const ulong endTime = mTimer->getMicroseconds();
329
330
        // empty string is reserved for designating an empty parent
331
0
        assert ((profileName != "") && ("Profile name can't be an empty string"));
332
333
        // we only process this profile if isn't disabled
334
        // we check the current instance name against the provided profileName as a guard against disabling a profile name /after/ said profile began
335
0
        if(mCurrent->name != profileName && mDisabledProfiles.find(profileName) != mDisabledProfiles.end()) 
336
0
            return;
337
338
        // calculate the elapsed time of this profile
339
0
        const ulong timeElapsed = endTime - mCurrent->currTime;
340
341
        // update parent's accumulator if it isn't the root
342
0
        if (&mRoot != mCurrent->parent) 
343
0
        {
344
            // add this profile's time to the parent's accumlator
345
0
            mCurrent->parent->accum += timeElapsed;
346
0
        }
347
348
0
        mCurrent->frame.frameTime += timeElapsed;
349
0
        ++mCurrent->frame.calls;
350
351
0
        mLast = mCurrent;
352
0
        mCurrent = mCurrent->parent;
353
354
0
        if (&mRoot == mCurrent) 
355
0
        {
356
            // the stack is empty and all the profiles have been completed
357
            // we have reached the end of the frame so process the frame statistics
358
359
            // we know that the time elapsed of the main loop is the total time the frame took
360
0
            mTotalFrameTime = timeElapsed;
361
362
0
            if(timeElapsed > mMaxTotalFrameTime)
363
0
                mMaxTotalFrameTime = timeElapsed;
364
365
            // we got all the information we need, so process the profiles
366
            // for this frame
367
0
            processFrameStats();
368
369
            // we display everything to the screen
370
0
            displayResults();
371
0
        }
372
0
#endif
373
0
    }
374
    //-----------------------------------------------------------------------
375
    void Profiler::processFrameStats(ProfileInstance* instance, Real& maxFrameTime)
376
0
    {
377
        // calculate what percentage of frame time this profile took
378
0
        const Real framePercentage = (Real) instance->frame.frameTime / (Real) mTotalFrameTime;
379
380
0
        const Real frameTimeMillisecs = (Real) instance->frame.frameTime / 1000.0f;
381
382
        // update the profile stats
383
0
        instance->history.currentTimePercent = framePercentage;
384
0
        instance->history.currentTimeMillisecs = frameTimeMillisecs;
385
0
        if(mResetExtents)
386
0
        {
387
0
            instance->history.totalTimePercent = framePercentage;
388
0
            instance->history.totalTimeMillisecs = frameTimeMillisecs;
389
0
            instance->history.totalCalls = 1;
390
0
        }
391
0
        else
392
0
        {
393
0
            instance->history.totalTimePercent += framePercentage;
394
0
            instance->history.totalTimeMillisecs += frameTimeMillisecs;
395
0
            instance->history.totalCalls++;
396
0
        }
397
0
        instance->history.numCallsThisFrame = instance->frame.calls;
398
399
        // if we find a new minimum for this profile, update it
400
0
        if (frameTimeMillisecs < instance->history.minTimeMillisecs || mResetExtents)
401
0
        {
402
0
            instance->history.minTimePercent = framePercentage;
403
0
            instance->history.minTimeMillisecs = frameTimeMillisecs;
404
0
        }
405
406
        // if we find a new maximum for this profile, update it
407
0
        if (frameTimeMillisecs > instance->history.maxTimeMillisecs || mResetExtents)
408
0
        {
409
0
            instance->history.maxTimePercent = framePercentage;
410
0
            instance->history.maxTimeMillisecs = frameTimeMillisecs;
411
0
        }
412
413
0
        if (instance->frame.frameTime > maxFrameTime)
414
0
            maxFrameTime = (Real)instance->frame.frameTime;
415
416
0
        for (auto& i : instance->children)
417
0
        {
418
0
            ProfileInstance* child = i.second;
419
420
            // we set the number of times each profile was called per frame to 0
421
            // because not all profiles are called every frame
422
0
            child->history.numCallsThisFrame = 0;
423
424
0
            if(child->frame.calls > 0)
425
0
            {
426
0
                processFrameStats(child, maxFrameTime);
427
0
            }
428
0
        }
429
0
    }
430
    //-----------------------------------------------------------------------
431
    void Profiler::processFrameStats(void) 
432
0
    {
433
0
        Real maxFrameTime = 0;
434
435
0
        for (auto& i : mRoot.children)
436
0
        {
437
0
            ProfileInstance* child = i.second;
438
439
            // we set the number of times each profile was called per frame to 0
440
            // because not all profiles are called every frame
441
0
            child->history.numCallsThisFrame = 0;
442
443
0
            if(child->frame.calls > 0)
444
0
            {
445
0
                processFrameStats(child, maxFrameTime);
446
0
            }
447
0
        }
448
449
        // Calculate whether the extents are now so out of date they need regenerating
450
0
        if (mCurrentFrame == 0)
451
0
            mAverageFrameTime = maxFrameTime;
452
0
        else
453
0
            mAverageFrameTime = (mAverageFrameTime + maxFrameTime) * 0.5f;
454
455
0
        if ((Real)mMaxTotalFrameTime > mAverageFrameTime * 4)
456
0
        {
457
0
            mResetExtents = true;
458
0
            mMaxTotalFrameTime = (ulong)mAverageFrameTime;
459
0
        }
460
0
        else
461
0
            mResetExtents = false;
462
0
    }
463
    //-----------------------------------------------------------------------
464
    void Profiler::displayResults() 
465
0
    {
466
        // if its time to update the display
467
0
        if (!(mCurrentFrame % mUpdateDisplayFrequency)) 
468
0
        {
469
            // ensure the root won't be culled
470
0
            mRoot.frame.calls = 1;
471
472
0
            for(auto& l : mListeners)
473
0
                l->displayResults(mRoot, mMaxTotalFrameTime);
474
0
        }
475
0
        ++mCurrentFrame;
476
0
    }
477
    //-----------------------------------------------------------------------
478
    bool Profiler::watchForMax(const String& profileName) 
479
0
    {
480
0
        assert ((profileName != "") && ("Profile name can't be an empty string"));
481
482
0
        return mRoot.watchForMax(profileName);
483
0
    }
484
    //-----------------------------------------------------------------------
485
    bool ProfileInstance::watchForMax(const String& profileName) 
486
0
    {
487
0
        for(auto& i : children)
488
0
        {
489
0
            ProfileInstance* child = i.second;
490
0
            if( (child->name == profileName && child->watchForMax()) || child->watchForMax(profileName))
491
0
                return true;
492
0
        }
493
0
        return false;
494
0
    }
495
    //-----------------------------------------------------------------------
496
    bool Profiler::watchForMin(const String& profileName) 
497
0
    {
498
0
        assert ((profileName != "") && ("Profile name can't be an empty string"));
499
0
        return mRoot.watchForMin(profileName);
500
0
    }
501
    //-----------------------------------------------------------------------
502
    bool ProfileInstance::watchForMin(const String& profileName) 
503
0
    {
504
0
        for(auto& i : children)
505
0
        {
506
0
            ProfileInstance* child = i.second;
507
0
            if( (child->name == profileName && child->watchForMin()) || child->watchForMin(profileName))
508
0
                return true;
509
0
        }
510
0
        return false;
511
0
    }
512
    //-----------------------------------------------------------------------
513
    bool Profiler::watchForLimit(const String& profileName, Real limit, bool greaterThan) 
514
0
    {
515
0
        assert ((profileName != "") && ("Profile name can't be an empty string"));
516
0
        return mRoot.watchForLimit(profileName, limit, greaterThan);
517
0
    }
518
    //-----------------------------------------------------------------------
519
    bool ProfileInstance::watchForLimit(const String& profileName, Real limit, bool greaterThan) 
520
0
    {
521
0
        for(auto& i : children)
522
0
        {
523
0
            ProfileInstance* child = i.second;
524
0
            if( (child->name == profileName && child->watchForLimit(limit, greaterThan)) || child->watchForLimit(profileName, limit, greaterThan))
525
0
                return true;
526
0
        }
527
0
        return false;
528
0
    }
529
    //-----------------------------------------------------------------------
530
    void Profiler::logResults() 
531
0
    {
532
0
        LogManager::getSingleton().logMessage("----------------------Profiler Results----------------------");
533
534
0
        for(auto& it : mRoot.children)
535
0
        {
536
0
            it.second->logResults();
537
0
        }
538
539
0
        LogManager::getSingleton().logMessage("------------------------------------------------------------");
540
0
    }
541
    //-----------------------------------------------------------------------
542
    void ProfileInstance::logResults() 
543
0
    {
544
0
        LogManager::getSingleton().logMessage(StringUtil::format(
545
0
            "%*s%s\t| Min %.2f | Max %.2f | Avg %.2f", hierarchicalLvl * 4, "", name.c_str(), history.minTimePercent,
546
0
            history.maxTimePercent, history.totalTimePercent / history.totalCalls));
547
548
0
        for(auto& it : children)
549
0
        {
550
0
            it.second->logResults();
551
0
        }
552
0
    }
553
    //-----------------------------------------------------------------------
554
    void Profiler::reset() 
555
0
    {
556
0
        mRoot.reset();
557
0
        mMaxTotalFrameTime = 0;
558
0
    }
559
    //-----------------------------------------------------------------------
560
    void ProfileInstance::reset(void)
561
0
    {
562
0
        history.currentTimePercent = history.maxTimePercent = history.totalTimePercent = 0;
563
0
        history.currentTimeMillisecs = history.maxTimeMillisecs = history.totalTimeMillisecs = 0;
564
0
        history.numCallsThisFrame = history.totalCalls = 0;
565
566
0
        history.minTimePercent = 1;
567
0
        history.minTimeMillisecs = 100000;
568
0
        for(auto& it : children)
569
0
        {
570
0
            it.second->reset();
571
0
        }
572
0
    }
573
    //-----------------------------------------------------------------------
574
    void Profiler::setUpdateDisplayFrequency(uint freq)
575
0
    {
576
0
        mUpdateDisplayFrequency = freq;
577
0
    }
578
    //-----------------------------------------------------------------------
579
    uint Profiler::getUpdateDisplayFrequency() const
580
0
    {
581
0
        return mUpdateDisplayFrequency;
582
0
    }
583
    //-----------------------------------------------------------------------
584
    void Profiler::addListener(ProfileSessionListener* listener)
585
0
    {
586
0
        if (std::find(mListeners.begin(), mListeners.end(), listener) == mListeners.end())
587
0
            mListeners.push_back(listener);
588
0
    }
589
    //-----------------------------------------------------------------------
590
    void Profiler::removeListener(ProfileSessionListener* listener)
591
0
    {
592
0
        TProfileSessionListener::iterator i = std::find(mListeners.begin(), mListeners.end(), listener);
593
0
        if (i != mListeners.end())
594
0
            mListeners.erase(i);
595
0
    }
596
    //-----------------------------------------------------------------------
597
}