StepTimer.h 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. #pragma once
  2. #include <wrl.h>
  3. namespace DX
  4. {
  5. // Helper class for animation and simulation timing.
  6. class StepTimer
  7. {
  8. public:
  9. StepTimer() :
  10. m_elapsedTicks(0),
  11. m_totalTicks(0),
  12. m_leftOverTicks(0),
  13. m_frameCount(0),
  14. m_framesPerSecond(0),
  15. m_framesThisSecond(0),
  16. m_qpcSecondCounter(0),
  17. m_isFixedTimeStep(false),
  18. m_targetElapsedTicks(TicksPerSecond / 60)
  19. {
  20. if (!QueryPerformanceFrequency(&m_qpcFrequency))
  21. {
  22. throw ref new Platform::FailureException();
  23. }
  24. if (!QueryPerformanceCounter(&m_qpcLastTime))
  25. {
  26. throw ref new Platform::FailureException();
  27. }
  28. // Initialize max delta to 1/10 of a second.
  29. m_qpcMaxDelta = m_qpcFrequency.QuadPart / 10;
  30. }
  31. // Get elapsed time since the previous Update call.
  32. uint64 GetElapsedTicks() const { return m_elapsedTicks; }
  33. double GetElapsedSeconds() const { return TicksToSeconds(m_elapsedTicks); }
  34. // Get total time since the start of the program.
  35. uint64 GetTotalTicks() const { return m_totalTicks; }
  36. double GetTotalSeconds() const { return TicksToSeconds(m_totalTicks); }
  37. // Get total number of updates since start of the program.
  38. uint32 GetFrameCount() const { return m_frameCount; }
  39. // Get the current framerate.
  40. uint32 GetFramesPerSecond() const { return m_framesPerSecond; }
  41. // Set whether to use fixed or variable timestep mode.
  42. void SetFixedTimeStep(bool isFixedTimestep) { m_isFixedTimeStep = isFixedTimestep; }
  43. // Set how often to call Update when in fixed timestep mode.
  44. void SetTargetElapsedTicks(uint64 targetElapsed) { m_targetElapsedTicks = targetElapsed; }
  45. void SetTargetElapsedSeconds(double targetElapsed) { m_targetElapsedTicks = SecondsToTicks(targetElapsed); }
  46. // Integer format represents time using 10,000,000 ticks per second.
  47. static const uint64 TicksPerSecond = 10000000;
  48. static double TicksToSeconds(uint64 ticks) { return static_cast<double>(ticks) / TicksPerSecond; }
  49. static uint64 SecondsToTicks(double seconds) { return static_cast<uint64>(seconds * TicksPerSecond); }
  50. // After an intentional timing discontinuity (for instance a blocking IO operation)
  51. // call this to avoid having the fixed timestep logic attempt a set of catch-up
  52. // Update calls.
  53. void ResetElapsedTime()
  54. {
  55. if (!QueryPerformanceCounter(&m_qpcLastTime))
  56. {
  57. throw ref new Platform::FailureException();
  58. }
  59. m_leftOverTicks = 0;
  60. m_framesPerSecond = 0;
  61. m_framesThisSecond = 0;
  62. m_qpcSecondCounter = 0;
  63. }
  64. // Update timer state, calling the specified Update function the appropriate number of times.
  65. template<typename TUpdate>
  66. void Tick(const TUpdate& update)
  67. {
  68. // Query the current time.
  69. LARGE_INTEGER currentTime;
  70. if (!QueryPerformanceCounter(&currentTime))
  71. {
  72. throw ref new Platform::FailureException();
  73. }
  74. uint64 timeDelta = currentTime.QuadPart - m_qpcLastTime.QuadPart;
  75. m_qpcLastTime = currentTime;
  76. m_qpcSecondCounter += timeDelta;
  77. // Clamp excessively large time deltas (e.g. after paused in the debugger).
  78. if (timeDelta > m_qpcMaxDelta)
  79. {
  80. timeDelta = m_qpcMaxDelta;
  81. }
  82. // Convert QPC units into a canonical tick format. This cannot overflow due to the previous clamp.
  83. timeDelta *= TicksPerSecond;
  84. timeDelta /= m_qpcFrequency.QuadPart;
  85. uint32 lastFrameCount = m_frameCount;
  86. if (m_isFixedTimeStep)
  87. {
  88. // Fixed timestep update logic
  89. // If the app is running very close to the target elapsed time (within 1/4 of a millisecond) just clamp
  90. // the clock to exactly match the target value. This prevents tiny and irrelevant errors
  91. // from accumulating over time. Without this clamping, a game that requested a 60 fps
  92. // fixed update, running with vsync enabled on a 59.94 NTSC display, would eventually
  93. // accumulate enough tiny errors that it would drop a frame. It is better to just round
  94. // small deviations down to zero to leave things running smoothly.
  95. if (abs(static_cast<int64>(timeDelta - m_targetElapsedTicks)) < TicksPerSecond / 4000)
  96. {
  97. timeDelta = m_targetElapsedTicks;
  98. }
  99. m_leftOverTicks += timeDelta;
  100. while (m_leftOverTicks >= m_targetElapsedTicks)
  101. {
  102. m_elapsedTicks = m_targetElapsedTicks;
  103. m_totalTicks += m_targetElapsedTicks;
  104. m_leftOverTicks -= m_targetElapsedTicks;
  105. m_frameCount++;
  106. update();
  107. }
  108. }
  109. else
  110. {
  111. // Variable timestep update logic.
  112. m_elapsedTicks = timeDelta;
  113. m_totalTicks += timeDelta;
  114. m_leftOverTicks = 0;
  115. m_frameCount++;
  116. update();
  117. }
  118. // Track the current framerate.
  119. if (m_frameCount != lastFrameCount)
  120. {
  121. m_framesThisSecond++;
  122. }
  123. if (m_qpcSecondCounter >= static_cast<uint64>(m_qpcFrequency.QuadPart))
  124. {
  125. m_framesPerSecond = m_framesThisSecond;
  126. m_framesThisSecond = 0;
  127. m_qpcSecondCounter %= m_qpcFrequency.QuadPart;
  128. }
  129. }
  130. private:
  131. // Source timing data uses QPC units.
  132. LARGE_INTEGER m_qpcFrequency;
  133. LARGE_INTEGER m_qpcLastTime;
  134. uint64 m_qpcMaxDelta;
  135. // Derived timing data uses a canonical tick format.
  136. uint64 m_elapsedTicks;
  137. uint64 m_totalTicks;
  138. uint64 m_leftOverTicks;
  139. // Members for tracking the framerate.
  140. uint32 m_frameCount;
  141. uint32 m_framesPerSecond;
  142. uint32 m_framesThisSecond;
  143. uint64 m_qpcSecondCounter;
  144. // Members for configuring fixed timestep mode.
  145. bool m_isFixedTimeStep;
  146. uint64 m_targetElapsedTicks;
  147. };
  148. }