190 строки
7.0 KiB
C++
190 строки
7.0 KiB
C++
//
|
|
// StepTimer.h - A simple timer that provides elapsed time information
|
|
//
|
|
|
|
#pragma once
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <exception>
|
|
|
|
namespace DX
|
|
{
|
|
// Helper class for animation and simulation timing.
|
|
class StepTimer
|
|
{
|
|
public:
|
|
StepTimer() noexcept(false) :
|
|
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 std::exception( "QueryPerformanceFrequency" );
|
|
}
|
|
|
|
if (!QueryPerformanceCounter(&m_qpcLastTime))
|
|
{
|
|
throw std::exception( "QueryPerformanceCounter" );
|
|
}
|
|
|
|
// Initialize max delta to 1/10 of a second.
|
|
m_qpcMaxDelta = static_cast<uint64_t>(m_qpcFrequency.QuadPart / 10);
|
|
}
|
|
|
|
// Get elapsed time since the previous Update call.
|
|
uint64_t GetElapsedTicks() const noexcept { return m_elapsedTicks; }
|
|
double GetElapsedSeconds() const noexcept { return TicksToSeconds(m_elapsedTicks); }
|
|
|
|
// Get total time since the start of the program.
|
|
uint64_t GetTotalTicks() const noexcept { return m_totalTicks; }
|
|
double GetTotalSeconds() const noexcept { return TicksToSeconds(m_totalTicks); }
|
|
|
|
// Get total number of updates since start of the program.
|
|
uint32_t GetFrameCount() const noexcept { return m_frameCount; }
|
|
|
|
// Get the current framerate.
|
|
uint32_t GetFramesPerSecond() const noexcept { return m_framesPerSecond; }
|
|
|
|
// Set whether to use fixed or variable timestep mode.
|
|
void SetFixedTimeStep(bool isFixedTimestep) noexcept { m_isFixedTimeStep = isFixedTimestep; }
|
|
|
|
// Set how often to call Update when in fixed timestep mode.
|
|
void SetTargetElapsedTicks(uint64_t targetElapsed) noexcept { m_targetElapsedTicks = targetElapsed; }
|
|
void SetTargetElapsedSeconds(double targetElapsed) noexcept { m_targetElapsedTicks = SecondsToTicks(targetElapsed); }
|
|
|
|
// Integer format represents time using 10,000,000 ticks per second.
|
|
static const uint64_t TicksPerSecond = 10000000;
|
|
|
|
static constexpr double TicksToSeconds(uint64_t ticks) noexcept { return static_cast<double>(ticks) / TicksPerSecond; }
|
|
static constexpr uint64_t SecondsToTicks(double seconds) noexcept { return static_cast<uint64_t>(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 std::exception("QueryPerformanceCounter");
|
|
}
|
|
|
|
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 std::exception( "QueryPerformanceCounter" );
|
|
}
|
|
|
|
uint64_t timeDelta = static_cast<uint64_t>(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 /= static_cast<uint64_t>(m_qpcFrequency.QuadPart);
|
|
|
|
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 (static_cast<uint64_t>(std::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.QuadPart))
|
|
{
|
|
m_framesPerSecond = m_framesThisSecond;
|
|
m_framesThisSecond = 0;
|
|
m_qpcSecondCounter %= static_cast<uint64_t>(m_qpcFrequency.QuadPart);
|
|
}
|
|
}
|
|
|
|
private:
|
|
// Source timing data uses QPC units.
|
|
LARGE_INTEGER m_qpcFrequency;
|
|
LARGE_INTEGER 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;
|
|
};
|
|
}
|