Controlling the frame rate
Although not limited to Direct3D, PC applications need to control the frame rate. If the frame rate can be determined according to a fixed refresh rate, as in a game console, You can control the frame rate by simply waiting for VSync, but PC monitors have a variety of refresh rates, so the app itself will need to control the frame rate.
The easiest way would be to measure the time since the previous frame, and if that time is faster than the specified frame rate, then Sleep.
It looks like this
class FrameController {
public:
FrameController() : mFPS(60.0f), mSleepCounter(0) {
QueryPerformanceFrequency(&mFrequency);
QueryPerformanceCounter(&mPrevious);
mStart = mPrevious;
}
virtual ~FrameController() {}
double GetTimeFromStart() const
{
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
double duration_sec = static_cast<double>(now.QuadPart - mStart.QuadPart) / mFrequency.QuadPart;
return duration_sec;
}
float Update(float fps = 0.0f)
{
// Change Sleep accuracy
timeBeginPeriod(1);
mSleepCounter = 0;
// Measure time from previous frame
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
double duration_sec = static_cast<double>(now.QuadPart - mPrevious.QuadPart) / mFrequency.QuadPart;
if (fps > 0.0f)
{
// Sleep to control frame interval
double limit_sec = 1.0 / fps;
while (duration_sec < limit_sec)
{
// Sleep until half of the remaining time
const double SLEEP_RATIO = 0.5;
int sleep_msec = static_cast<int>(1000.0 * (limit_sec - duration_sec) * SLEEP_RATIO);
Sleep(sleep_msec);
++mSleepCounter;
// Remeasure time
QueryPerformanceCounter(&now);
duration_sec = static_cast<double>(now.QuadPart - mPrevious.QuadPart) / mFrequency.QuadPart;
}
}
// Return to Sleep Accuracy
timeEndPeriod(1);
// Update frame rate (multiply by WEIGHT since averaging is tedious)
float current_fps = float(1.0 / duration_sec);
float FPS_WEIGHT = 0.1f;
mFPS = mFPS * (1.0f - FPS_WEIGHT) + current_fps * FPS_WEIGHT;
// Update the time of the previous frame
mPrevious = now;
return mFPS;
}
// For debugging
int GetSleepCounter() const
{
return mSleepCounter;
}
private:
LARGE_INTEGER mFrequency;
LARGE_INTEGER mPrevious;
LARGE_INTEGER mStart;
float mFPS;
// For debugging
int mSleepCounter;
};
To use, call Update at the top of the frame as follows
float frame_rate = frameController.Update(100.0f);
When we tried, Sleep was not very precise and could not be made to Sleep in units finer than 1 ms. Also, by default, even 1 ms was impossible, So we specified the precision to be in units of 1 ms as shown below.
// Changing Sleep Precision
timeBeginPeriod(1);
// Sleep
Sleep(sleep_msec);
// Return Sleep accuracy
timeEndPeriod(1);
Note that although this code wanted to make Sleep sleep for progressively shorter periods of time to achieve accurate time Sleep, it was actually a busy loop on Sleep(0) more than a few thousand times to waiting for the exact time to pass. There may be a better way to do this, but for now, here it is.