aboutsummaryrefslogtreecommitdiffstats
path: root/support/hololens/ServoApp/Common/StepTimer.h
diff options
context:
space:
mode:
Diffstat (limited to 'support/hololens/ServoApp/Common/StepTimer.h')
-rw-r--r--support/hololens/ServoApp/Common/StepTimer.h179
1 files changed, 179 insertions, 0 deletions
diff --git a/support/hololens/ServoApp/Common/StepTimer.h b/support/hololens/ServoApp/Common/StepTimer.h
new file mode 100644
index 00000000000..36102d680db
--- /dev/null
+++ b/support/hololens/ServoApp/Common/StepTimer.h
@@ -0,0 +1,179 @@
+#pragma once
+
+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) {
+ m_qpcFrequency = GetPerformanceFrequency();
+
+ // Initialize max delta to 1/10 of a second.
+ m_qpcMaxDelta = m_qpcFrequency / 10;
+ }
+
+ // Get elapsed time since the previous Update call.
+ uint64_t GetElapsedTicks() const { return m_elapsedTicks; }
+ double GetElapsedSeconds() const { return TicksToSeconds(m_elapsedTicks); }
+
+ // Get total time since the start of the program.
+ uint64_t GetTotalTicks() const { return m_totalTicks; }
+ double GetTotalSeconds() const { return TicksToSeconds(m_totalTicks); }
+
+ // Get total number of updates since start of the program.
+ uint32_t GetFrameCount() const { return m_frameCount; }
+
+ // Get the current framerate.
+ uint32_t 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_t 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_t TicksPerSecond = 10'000'000;
+
+ static double TicksToSeconds(uint64_t ticks) {
+ return static_cast<double>(ticks) / TicksPerSecond;
+ }
+ static uint64_t SecondsToTicks(double seconds) {
+ return static_cast<uint64_t>(seconds * TicksPerSecond);
+ }
+
+ // Convenient wrapper for QueryPerformanceFrequency. Throws an exception if
+ // the call to QueryPerformanceFrequency fails.
+ static inline uint64_t GetPerformanceFrequency() {
+ LARGE_INTEGER freq;
+ if (!QueryPerformanceFrequency(&freq)) {
+ winrt::throw_last_error();
+ }
+ return freq.QuadPart;
+ }
+
+ // Gets the current number of ticks from QueryPerformanceCounter. Throws an
+ // exception if the call to QueryPerformanceCounter fails.
+ static inline int64_t GetTicks() {
+ LARGE_INTEGER ticks;
+ if (!QueryPerformanceCounter(&ticks)) {
+ winrt::throw_last_error();
+ }
+ return ticks.QuadPart;
+ }
+
+ // 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() {
+ m_qpcLastTime = GetTicks();
+
+ 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.
+ uint64_t currentTime = GetTicks();
+ uint64_t timeDelta = currentTime - m_qpcLastTime;
+
+ 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;
+
+ uint32_t 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_t>(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_t>(m_qpcFrequency)) {
+ m_framesPerSecond = m_framesThisSecond;
+ m_framesThisSecond = 0;
+ m_qpcSecondCounter %= m_qpcFrequency;
+ }
+ }
+
+private:
+ // Source timing data uses QPC units.
+ uint64_t m_qpcFrequency;
+ uint64_t m_qpcLastTime;
+ uint64_t m_qpcMaxDelta;
+
+ // Derived timing data uses a canonical tick format.
+ uint64_t m_elapsedTicks;
+ uint64_t m_totalTicks;
+ uint64_t m_leftOverTicks;
+
+ // Members for tracking the framerate.
+ uint32_t m_frameCount;
+ uint32_t m_framesPerSecond;
+ uint32_t m_framesThisSecond;
+ uint64_t m_qpcSecondCounter;
+
+ // Members for configuring fixed timestep mode.
+ bool m_isFixedTimeStep;
+ uint64_t m_targetElapsedTicks;
+};
+} // namespace DX