フレームレートを制御する
Direct3Dに限った話ではありませんが、PCのアプリは、フレームレートを制御する必要があります。 ゲーム機のように、固定されたリフレッシュレートに合わせてフレームレートを決めることができる場合は、 VSyncを待つだけでフレームレートを制御できますが、PCのモニターはリフレッシュレートが様々なので、アプリ自身がフレームレートを制御する必要がでてきます。
一番簡単な方法は、前フレームからの時間を計測しておき、その時間が指定したフレームレートより早い場合に、Sleepさせる方法だと思います。
こんな感じです。
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)
{
// Sleepの精度を変更
timeBeginPeriod(1);
mSleepCounter = 0;
// 前フレームからの時間を計測
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
double duration_sec = static_cast<double>(now.QuadPart - mPrevious.QuadPart) / mFrequency.QuadPart;
if (fps > 0.0f)
{
// Sleepさせてフレーム間隔を制御
double limit_sec = 1.0 / fps;
while (duration_sec < limit_sec)
{
// 残り時間の半分までSleepさせる
const double SLEEP_RATIO = 0.5;
int sleep_msec = static_cast<int>(1000.0 * (limit_sec - duration_sec) * SLEEP_RATIO);
Sleep(sleep_msec);
++mSleepCounter;
// 時間を再計測
QueryPerformanceCounter(&now);
duration_sec = static_cast<double>(now.QuadPart - mPrevious.QuadPart) / mFrequency.QuadPart;
}
}
// Sleepの精度を戻す
timeEndPeriod(1);
// フレームレートを更新(平均とるのは面倒なのでWEIGHTを掛ける)
float current_fps = float(1.0 / duration_sec);
float FPS_WEIGHT = 0.1f;
mFPS = mFPS * (1.0f - FPS_WEIGHT) + current_fps * FPS_WEIGHT;
// 前フレームの時間を更新
mPrevious = now;
return mFPS;
}
// デバッグ用
int GetSleepCounter() const
{
return mSleepCounter;
}
private:
LARGE_INTEGER mFrequency;
LARGE_INTEGER mPrevious;
LARGE_INTEGER mStart;
float mFPS;
// デバッグ用
int mSleepCounter;
};
使い方は、フレームの先頭で、以下のようにUpdateを呼びます。
float frame_rate = frameController.Update(100.0f);
試してみたところ、Sleepの精度が低く、1msよりも細かい単位ではSleepさせることはできませんでした。また、デフォルトでは、1msでも無理でしたので、 以下のように精度を1ms単位と指定しました。
// Sleepの精度を変更
timeBeginPeriod(1);
// Sleep
Sleep(sleep_msec);
// Sleepの精度を戻す
timeEndPeriod(1);
なお、このコードは、Sleepを徐々に短い時間で眠らせて、正確な時間のSleepを実現したかったのですが、実際は、数千回以上の Sleep(0) のビジーループで 正確な時間が経つのを待っています。もっと良いやり方もあるかもしれませんが、とりあえす、こんな感じで。