はかせのラボ

私の頭の中を書いていく雑記ブログです

C++ FPS固定と計測を作り直したので改めて解説してみる

あいさつ

どうも、はかせです。
私は以前FPS固定について色々調べたり
実装したりしてました。
hakase0274.hatenablog.com
hakase0274.hatenablog.com

こういった機能って色んなとこで使うし、
ほぼ処理が変わりません。

なので逐一コピペしなくても済むよう
クラス分けしそのクラスを放り込んで使うだけで
これらの機能が実装できるよう作り直しました。

今回は今までも取り扱ってきた内容ではありますが、
最近私を知ってくれた方もいると思いますし、
当時に比べて私自身の理解度も上がったと思うので
改めて簡単にですがFPSの固定と計測について解説します。

FPS固定

まずFPSの固定はいくつか方法があります。
今回私が実装したのは時間による固定です。

これは前フレームから現フレームまでの経過時間を取得し
その時間が1フレームにかけていられる時間内ならば
(60FPSなら1フレームにかけられる時間は約16ms)
余った時間分Sleep関数を使って処理を止める方法です。

時間の取得にはWIndowsAPIの
QueryPerformanceCounterを使うやり方が一般的でしょう。
このAPIはCPU内のクロックカウントを取得してきます。

この値をフレーム内の処理の前後で取得し、
後の値から前の値を引くことで1フレームの処理にかかった
クロックカウント数が取得できます。

あとはこの値をCPUの周波数で割ることで
1フレームの処理にかかった秒数がわかります。

コードに起こすとこんな感じ

//処理前にこれらのAPIを呼び出しておく
//周波数取得
QueryPerformanceFrequency(&mTimeFreq);
//計測開始時間の初期化
QueryPerformanceCounter(&mTimeStart);
//60FPSに固定する場合の1フレームにかけられる時間
FRAME_TIME = 1.0 / 60.0;

//なんか処理したと仮定

// 今の時間を取得
QueryPerformanceCounter(&mTimeEnd);
// (今の時間 - 前フレームの時間) / 周波数 = 経過時間(秒単位)
mFrameTime = static_cast<double>(mTimeEnd.QuadPart - mTimeStart.QuadPart) / static_cast<double>(mTimeFreq.QuadPart);
//処理時間に余裕がある場合はその分待つ
if (mFrameTime < FRAME_TIME)
{
	//Sleepの時間を計算
	DWORD sleepTime = static_cast<DWORD>((FRAME_TIME - mFrameTime) * 1000);
	timeBeginPeriod(1);
	//寝る
	Sleep(sleepTime);
	timeEndPeriod(1);
}

timeBeginPeriod、timeEndPeriodはタイマの精度を調整するものです。
今回は1msレベルに上げています。

時間によるFPS固定は上記のようなコードでできます。
(細かいとこは各自調整)

この方法の利点は何と言っても
環境を選ばないことです。

FPS固定には他にもvsyncを使ったものがありますが、
こちらは環境の影響をめちゃくちゃ受けます。

ディスプレイのリフレッシュレートが
144Hzとかいうお化けの場合は超速になったり、
逆に60Hzとかだと鈍足になったりする、
ということがよくあります。

それに対し今回選んだ時間による固定ならば
CPUが積まれていればどの環境でも問題なく動きます。
CPUのクロック周波数などは変わったとしても
プログラムで十分対応できますからね。

FPS計測

FPSってのは秒間何フレーム更新したかという値です。
確かにフレーム更新のたびにカウントアップしていって
1秒後にその値を見るでもいいんですが、
今回は数学チックにFPSを求めます(`・ω・´)キリッ

といっても難しいことはなく
フレーム更新にかかった時間から
瞬間のFPSを逆算する
だけです。

1秒をフレーム更新にかかった時間で割れば
秒間何フレーム更新できるのかという値が求められます
これすなわちFPSです。

フレーム更新にかかった時間は
さっきFPS固定をした時に計算済みなので
その値を使ってこんな感じ

fps = 1 / mFrameTime;

はい一行でFPSが求まりました。
楽ちんですね。

あとがき

今回はFPSの固定と計測の解説でした。

実はこれを作ってるときに
QueryPerformanceCounterで取得した値を
ローカル変数に持っていて
スコープ抜けて破棄されてしまうという
初心者でもしないようなミスをやらかし
3~4時間溶かしましたw

やっぱり成長したといっても
初心者-100から初心者-99.9になった程度ってことですかね。

少しでも早く初心者を脱せるよう頑張ります。

それでは今回はこの辺でノシ
今回作ったものはGitHubに上げました
github.com