| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- #pragma once
- #include <wrl.h>
- namespace DX
- {
- // Helper class for animation and simulation timing.
- class StepTimer
- {
- public:
- StepTimer() :
- m_elapsedTicks(0),
- m_totalTicks(0),
- m_leftOverTicks(0),
- m_frameCount(0),
- m_framesPerSecond(0),
- m_framesThisSecond(0),
- m_qpcSecondCounter(0),
- m_isFixedTimeStep(false),
- m_targetElapsedTicks(TicksPerSecond / 60)
- {
- if (!QueryPerformanceFrequency(&m_qpcFrequency))
- {
- throw ref new Platform::FailureException();
- }
- if (!QueryPerformanceCounter(&m_qpcLastTime))
- {
- throw ref new Platform::FailureException();
- }
- // Initialize max delta to 1/10 of a second.
- m_qpcMaxDelta = m_qpcFrequency.QuadPart / 10;
- }
- // Get elapsed time since the previous Update call.
- uint64 GetElapsedTicks() const { return m_elapsedTicks; }
- double GetElapsedSeconds() const { return TicksToSeconds(m_elapsedTicks); }
- // Get total time since the start of the program.
- uint64 GetTotalTicks() const { return m_totalTicks; }
- double GetTotalSeconds() const { return TicksToSeconds(m_totalTicks); }
- // Get total number of updates since start of the program.
- uint32 GetFrameCount() const { return m_frameCount; }
- // Get the current framerate.
- uint32 GetFramesPerSecond() const { return m_framesPerSecond; }
- // Set whether to use fixed or variable timestep mode.
- void SetFixedTimeStep(bool isFixedTimestep) { m_isFixedTimeStep = isFixedTimestep; }
- // Set how often to call Update when in fixed timestep mode.
- void SetTargetElapsedTicks(uint64 targetElapsed) { m_targetElapsedTicks = targetElapsed; }
- void SetTargetElapsedSeconds(double targetElapsed) { m_targetElapsedTicks = SecondsToTicks(targetElapsed); }
- // Integer format represents time using 10,000,000 ticks per second.
- static const uint64 TicksPerSecond = 10000000;
- static double TicksToSeconds(uint64 ticks) { return static_cast<double>(ticks) / TicksPerSecond; }
- static uint64 SecondsToTicks(double seconds) { return static_cast<uint64>(seconds * TicksPerSecond); }
- // After an intentional timing discontinuity (for instance a blocking IO operation)
- // call this to avoid having the fixed timestep logic attempt a set of catch-up
- // Update calls.
- void ResetElapsedTime()
- {
- if (!QueryPerformanceCounter(&m_qpcLastTime))
- {
- throw ref new Platform::FailureException();
- }
- m_leftOverTicks = 0;
- m_framesPerSecond = 0;
- m_framesThisSecond = 0;
- m_qpcSecondCounter = 0;
- }
- // Update timer state, calling the specified Update function the appropriate number of times.
- template<typename TUpdate>
- void Tick(const TUpdate& update)
- {
- // Query the current time.
- LARGE_INTEGER currentTime;
- if (!QueryPerformanceCounter(¤tTime))
- {
- throw ref new Platform::FailureException();
- }
- uint64 timeDelta = currentTime.QuadPart - m_qpcLastTime.QuadPart;
- m_qpcLastTime = currentTime;
- m_qpcSecondCounter += timeDelta;
- // Clamp excessively large time deltas (e.g. after paused in the debugger).
- if (timeDelta > m_qpcMaxDelta)
- {
- timeDelta = m_qpcMaxDelta;
- }
- // Convert QPC units into a canonical tick format. This cannot overflow due to the previous clamp.
- timeDelta *= TicksPerSecond;
- timeDelta /= m_qpcFrequency.QuadPart;
- uint32 lastFrameCount = m_frameCount;
- if (m_isFixedTimeStep)
- {
- // Fixed timestep update logic
- // If the app is running very close to the target elapsed time (within 1/4 of a millisecond) just clamp
- // the clock to exactly match the target value. This prevents tiny and irrelevant errors
- // from accumulating over time. Without this clamping, a game that requested a 60 fps
- // fixed update, running with vsync enabled on a 59.94 NTSC display, would eventually
- // accumulate enough tiny errors that it would drop a frame. It is better to just round
- // small deviations down to zero to leave things running smoothly.
- if (abs(static_cast<int64>(timeDelta - m_targetElapsedTicks)) < TicksPerSecond / 4000)
- {
- timeDelta = m_targetElapsedTicks;
- }
- m_leftOverTicks += timeDelta;
- while (m_leftOverTicks >= m_targetElapsedTicks)
- {
- m_elapsedTicks = m_targetElapsedTicks;
- m_totalTicks += m_targetElapsedTicks;
- m_leftOverTicks -= m_targetElapsedTicks;
- m_frameCount++;
- update();
- }
- }
- else
- {
- // Variable timestep update logic.
- m_elapsedTicks = timeDelta;
- m_totalTicks += timeDelta;
- m_leftOverTicks = 0;
- m_frameCount++;
- update();
- }
- // Track the current framerate.
- if (m_frameCount != lastFrameCount)
- {
- m_framesThisSecond++;
- }
- if (m_qpcSecondCounter >= static_cast<uint64>(m_qpcFrequency.QuadPart))
- {
- m_framesPerSecond = m_framesThisSecond;
- m_framesThisSecond = 0;
- m_qpcSecondCounter %= m_qpcFrequency.QuadPart;
- }
- }
- private:
- // Source timing data uses QPC units.
- LARGE_INTEGER m_qpcFrequency;
- LARGE_INTEGER m_qpcLastTime;
- uint64 m_qpcMaxDelta;
- // Derived timing data uses a canonical tick format.
- uint64 m_elapsedTicks;
- uint64 m_totalTicks;
- uint64 m_leftOverTicks;
- // Members for tracking the framerate.
- uint32 m_frameCount;
- uint32 m_framesPerSecond;
- uint32 m_framesThisSecond;
- uint64 m_qpcSecondCounter;
- // Members for configuring fixed timestep mode.
- bool m_isFixedTimeStep;
- uint64 m_targetElapsedTicks;
- };
- }
|