教団「二次元愛」

リアルワールドに見切りをつけ、二次元に生きる男の生き様 (ニコニコでは「てとろでP」)

Windows上での時間の計測

2013-04-21 00:26:14 | 科学
Windowsのプログラミングにちょっとづつ手を染め始めたのだが。
画面の更新間隔のところでいきなり深みにハマってしまいそうだ。
見始めたばかりの初心者なので、どっか間違った変なこと書いているかもしれんが、現時点の知識である程度整理してみた。



画面上で滑らかに画像表示させたい。
たとえばゲームみたいな。

モニタの画面の更新間隔は垂直同期(VSYNC)で決まっている。
おおむね秒間60回の更新(60fps)だが、環境によってそれは変わることもある。
プログラムから設定できるのは全画面表示のときのみ。

だが。
垂直同期で割り込みを発生させて特定の関数に飛ばす、みたいなことをやろうとしても、そういう検出はできないようだ。
なんてこったい。
マイコンならそれくらいカンタンにできそうなもんだし、なかったとしても外部割込みで配線1本追加すりゃあすぐできそうなもんだが、それがインテルのCPUではできんとか…。

Presentメソッドによる画面の更新タイミングが垂直同期になるよう設定できるらしい。
なら、現在の時間を見て、前の更新タイミングより1フレーム分(=1000ms÷60=16.6ms)経過するまで時間を待つようにループを組めばよろしい。

時間を待つ、というかスレッドを一定時間中断させるための関数はSleep()。
1ms単位で入力できるので、1フレーム待ちたいくらいの単位にはちょうどよさそうだ。

しかし。
スレッドの切り替えに10数msかかるんだとか。
それじゃあ次のフレームが来るまでの待ち時間には使えないじゃん。

じゃあ、1フレーム以上スキップされることがあるのが普通という前提でコードを書く必要があるわけだ。
まずは前回描画したタイミングからどんだけ時間がたったかを測定しようか。

timegettime関数を使えば1msの分解能で時間を測定できるらしい。
でも騙されないぞ。
精度はSleepなみじゃあないだろうな?

ググるとtimegettimeの精度は10msくらいだという意見が多いようだ。
あと1桁ほど分解能が欲しい。

DXライブラリに1マイクロ秒単位で測定できる
DxLib::GetNowHiPerformanceCount();
というものがあるのを見つけた。

ソースコードをおっかけてみる。
アセンブラのとこは読んでもわからんかった。
だがQueryPerformanceCounterとQueryPerformanceFrequencyを使えばけっこうな精度で測定できそうだとわかった。
ようするにこれCPU内臓のバイナリカウンタで、それを入力周波数で割ってやれば時間が高精度で求まるってことだな。
よしよし。

しかし。
それじゃあダメだという意見を見つけた。
今のCPUは負荷状態にあわせて動的にクロック周波数を可変させているようだ。
Turbo boostってやつがこれか。
だがQueryPerformanceFrequencyで取得できるものは固定値。
だからクロックのカウンタでは正しくは時間がわからんとか。

ようし。
それじゃあためしに実機で見てみるか。

LARGE_INTEGER quFreq;
QueryPerformanceFrequency(&quFreq);
LARGE_INTEGER strHp, endHp;
DWORD strGt, endGt;
QueryPerformanceCounter(&strHp);
strGt = timeGetTime();
Sleep(3);
QueryPerformanceCounter(&endHp);
endGt = timeGetTime();
double tHp, tGt;
tHp = (double)(endHp.QuadPart - strHp.QuadPart) / (double)quFreq.QuadPart *1000.0;
tGt = (double)(endGt - strGt);

何回か繰り返してみた。
i7+Windows8の環境。
Sleep関数で3msのウエイトをはさみ、その前後で時間を測定した。
QueryPerformanceFrequencyだと、測定値3~4msくらい。
timeGetTimeだと、だいたい3msだが、ときどき4msや5msが出ることもある。

なんだ。
それなりに大丈夫そうだな。

じゃあ次。
セレロン+XPの環境。
だいたい13~16msくらいなんだが、たまに変なのがでて3msとか30msとかもとることがある。
しかもQueryPerformanceFrequencyとtimeGetTimeで10msも違うこともある。
どうやら分解能は15msくらいらしく、15ms×0回待ちだと3ms、15ms×1回待ちだと15ms、15ms×2回待ちだと30ms、という理屈で離散的な分布になるように思われる。

なるほど。
CPUのせいなのかOSのせいなのかわからんが、今のマシンのほうがだいぶ進化しているようだ。
XPではマトモに動きませんという前提で作ればそれほど変なことにはならないかもしれないな。