トップページ > 過去ログ > 記事閲覧
60FPSを保つ為には?
名前:dixq 日時: 2008/06/16 00:11

毎回お世話になっております。今回は60FPSに保つ事が出来ないので質問させていただきます。 まず、60FPSを保つ為には16.666ミリ秒待機する必要があると思います。 つまり16666マイクロ秒待機する事と同義ですよね。 またキャプチャソフトなどの同時処理をさせる為に待機を軽くしたいと考えています。 そこで、16ミリ秒はSleepで待機し、666マイクロ秒はwhileで待機させ、 合計16.666ミリ秒待機する事を考えます。 そこで以下のようなプログラムを書いてみました。 処理は上記の内容を素直に実装しただけです。 変数の意味や処理内容は注釈をご覧下さい。 ****プログラムは次の記事のように変更しましたので**** ****こちらのコードは削除しました**** printfDxの部分でこれを表示した結果をこちらに示します。 wait=10243,miri_wait=10,micro_wait= 243,fps_wait=16602 wait= 6257,miri_wait= 6,micro_wait= 257,fps_wait=16745 wait= 3998,miri_wait= 3,micro_wait= 998,fps_wait=17441 wait=15805,miri_wait=15,micro_wait= 805,fps_wait=16400 wait= 7410,miri_wait= 7,micro_wait= 410,fps_wait=17382 wait= 5591,miri_wait= 5,micro_wait= 591,fps_wait=17458 wait=15832,miri_wait=15,micro_wait= 832,fps_wait=17306 wait=11357,miri_wait=11,micro_wait= 357,fps_wait=17451 wait= 8015,miri_wait= 8,micro_wait= 15,fps_wait=16651 wait= 5143,miri_wait= 5,micro_wait= 143,fps_wait=17518 wait= 1856,miri_wait= 1,micro_wait= 856,fps_wait=17691 wait=11426,miri_wait=11,micro_wait= 426,fps_wait=17411 wait= 8528,miri_wait= 8,micro_wait= 528,fps_wait=17346 wait= 6314,miri_wait= 6,micro_wait= 314,fps_wait=16814 wait=15897,miri_wait=15,micro_wait= 897,fps_wait=17287 wait=10711,miri_wait=10,micro_wait= 711,fps_wait=16958 wait= 3450,miri_wait= 3,micro_wait= 450,fps_wait=16710 wait= 9268,miri_wait= 9,micro_wait= 268,fps_wait=17240 wait= 3855,miri_wait= 3,micro_wait= 855,fps_wait=17407 wait= 8483,miri_wait= 8,micro_wait= 483,fps_wait=17058 fps_waitは実質1周にかかった時間です。 16666になるように処理をしたはずなのに、そうなっていないのは何故なのでしょうか・・。 XP,VS2005Proです。よろしくお願いいたします。

Page: 1 |

Re: 60FPSを保つ為には? ( No.1 )
名前:dixq 日時:2008/06/16 00:18

少々変更しました。 Sleep関数は正確にその時間待機するわけではないという事がわかりましたので、 この誤差はSleep関数の待機時間誤差によるものであると推測しました。 そこで、Sleep関数で待機する時間を1ミリ秒減らし、 後はwhile文で待機する事にしました。 void wait_fanc(){//60fps以上にならないようにする関数 //static unsigned int wt,cnt=0,wwt=0; LONGLONG miri_wait=0,micro_wait=0,wait=0,wait2=0,start=0; static LONGLONG before_time=0; static int cnt=0; if(cnt!=0){//最初はbefore_timeに値が入っていないので飛ばす //まずSleep関数の誤差を踏まえて16666-1000の15666μ秒待機したい。 //待機すべき時間=15666-(かかった時間)により //waitに待機したい時間を格納する。 wait=15666-(GetNowHiPerformanceCount()-before_time); if(wait>0){//待機すべき時間があれば //15666マイクロ秒の、まず15ミリ秒になるまでSleepする。 miri_wait=wait/1000; Sleep(miri_wait); } //次の正確な待機はwhileで行う。 //待機すべき時間=15666-(かかった時間)により //wait2にマイクロ秒待機したい時間を格納する。 wait2=16666-(GetNowHiPerformanceCount()-before_time); if(wait2>0){ micro_wait=wait2; start=GetNowHiPerformanceCount(); while(GetNowHiPerformanceCount()-start<micro_wait); } //結局1周にかかった時間をfps_waitに格納する。16666が入る事を期待する。 //fps_waitはunsigned intのグローバス変数 fps_wait=(unsigned int)(GetNowHiPerformanceCount()-before_time); if(cnt%60==0) printfDx("wait=%lld,wait2=%lld,miri_wait=%lld,micro_wait=%lld,fps_wait=%d\n" ,wait,wait2,miri_wait,micro_wait,fps_wait); } before_time=GetNowHiPerformanceCount(); cnt++; return; } 先ほど同様結果はこちらです。 wait=7537, wait2=976, miri_wait=7, micro_wait=976, fps_wait=16727 wait=13401,wait2=1666,miri_wait=13,micro_wait=1666,fps_wait=16743 wait=4586, wait2=781, miri_wait=4, micro_wait=781, fps_wait=16736 wait=9907, wait2=1191,miri_wait=9, micro_wait=1191,fps_wait=16728 wait=3158, wait2=517, miri_wait=3, micro_wait=517, fps_wait=16742 wait=9613, wait2=860, miri_wait=9, micro_wait=860, fps_wait=16739 wait=2272, wait2=521, miri_wait=2, micro_wait=521, fps_wait=16720 wait=8207, wait2=1025,miri_wait=8, micro_wait=1025,fps_wait=16733 wait=2854, wait2=1071,miri_wait=2, micro_wait=1071,fps_wait=16712 wait=9711, wait2=1623,miri_wait=9, micro_wait=1623,fps_wait=16715 wait=2575, wait2=1411,miri_wait=2, micro_wait=1411,fps_wait=16717 wait=8357, wait2=964, miri_wait=8, micro_wait=964, fps_wait=16740 wait=14376,wait2=1545,miri_wait=14,micro_wait=1545,fps_wait=16721 wait=6968, wait2=1385,miri_wait=6, micro_wait=1385,fps_wait=16731 wait=12970,wait2=1298,miri_wait=12,micro_wait=1298,fps_wait=16741 wait=5650, wait2=930, miri_wait=5, micro_wait=930, fps_wait=16730 wait=12063,wait2=1094,miri_wait=12,micro_wait=1094,fps_wait=16746 wait=3089, wait2=570, miri_wait=3, micro_wait=570, fps_wait=16710 wait=7657, wait2=1674,miri_wait=7, micro_wait=1674,fps_wait=16723 wait=12558,wait2=1155,miri_wait=12,micro_wait=1155,fps_wait=16729 先ほどより幾分よくなりました。 fps_waitの値は16666に近づいたものの、やはりまだ16666とは違います。 FPSも59.7FPS位になり、ピッタリ60FPSになりません。 windowsで計る時間はあてにならないと聞いたことがありますが、 正確な測定は無理なのでしょうか?
Re: 60FPSを保つ為には? ( No.2 )
名前:Will 日時:2008/06/16 10:09

WindowsはμITRONのようにリアルタイムOSではありませんので正確に時間を計ることは出来ません。 参照(真ん中くらいにSleepの説明があります) http://www.bugbearr.jp/?Windows%2F%E6%99%82%E5%88%BB
Re: 60FPSを保つ為には? ( No.3 )
名前: 日時:2008/06/16 16:33

>まず、60FPSを保つ為には16.666ミリ秒待機する >必要があると思います。つまり16666マイクロ秒 >待機する事と同義ですよね。 >そこで、16ミリ秒はSleepで待機し、 >666マイクロ秒はwhileで待機させ、 >合計16.666ミリ秒待機する事を考えます。 これは描画能力によってある程度環境で 上下するため、一概に流布する事はできません。 実行環境によって実行速度が不安定になるのを 防ぐために作りこむ処理なので、固有の環境に 合わせた数値では意味がありません。 60FPSを超え待機する時間をを測ってから 待機する必要があると思います。 (プログラムに計算処理を入れるという意味) また、前回の質問の時に書いた通りループの処理は 実行環境によってその実行時間が異り、 他の割り込みによって簡単に崩れる為、 時間経過を計測する事や待機などに使うことは できません。 >windowsで計る時間はあてにならないと >聞いたことがありますが正確な測定は >無理なのでしょうか? Yes。 実行時間やその他の割り込み処理により、 タイマ精度に誤差が生じます。 >正確な測定は無理なのでしょうか? >この誤差はSleep関数の待機時間誤差に >よるものであると推測しました。 逆にちょっと気になるのですが、 そこまで正確に測定する必要があるのでしょうか? FPSとはあくまで目安であって、それにしないと いけないというような物ではないと思うのですが? あとFPS60というのは秒間に60枚更新をかければ 良いことになると思いますが、Sleepを使うと それ以外のゲーム的処理は本当に止まって良いの? というところもあると思います。 特に時間の計測を必要とするゲームでは、 これは結構問題になりうると思いますし。。。
Re: 60FPSを保つ為には? ( No.4 )
名前:Euris 日時:2008/06/16 21:30

Sleep関数に誤差があるのは確かですが、 もう一つ、GetNowHiPerformanceCount 関数についても考える必要があります。 この関数は、あまり効率的な処理ではないため、 1回の呼び出しにつき、およそ40,000〜50,000CPUサイクル(Hz)を消費します。(※私の環境での計測です) これは 2GHz のCPU上で 25μ秒にあたります。 上のソースを見ると、 > start=GetNowHiPerformanceCount(); において1回、 > while(GetNowHiPerformanceCount()-start<micro_wait); において左辺値が右辺値より大きいと評価される時に、その1回の超過分、 > fps_wait=(unsigned int)(GetNowHiPerformanceCount()-before_time); においてもう1回、 合わせて3回余計にGetNowHiPerformanceCountを呼び出しているために、 およそ60μ秒の遅れが生じていると推測されます。 ところで、ここまできて残念なのですが、 > while(GetNowHiPerformanceCount()-start<micro_wait); はループ中この処理を何度も実行するだけで、負荷の軽減にはつながりません。 ScreenFlip 関数は自動で60fpsになるよう調節してくれるのですが、 もしScreenFlipの待機時間にCPUパワーが使われているのならば、 15m秒ではなく、10m秒程度までのSleepが適当だと思います。
Re: 60FPSを保つ為には? ( No.5 )
名前:dixq 日時:2008/06/17 02:51

後からみてみると修正しなおしたほうのコードの 字下げがおかしくなっていました。 みにくいコードを投稿してしまい、すみませんでした。 >>Wii様 なるほど、よくわかりました。 また、表現精度と実精度の関係もこの記事でよくわかりました。 ありがとうございます。 >>通様 ありがとうございます。 今回の件で、時間関係の処理は簡単に考えて実装してはいけないという事が解りました。 whileの件も含め、今後参考にさせていただきます。 >そこまで正確に測定する必要があるのでしょうか? 私が何かしなくてもいい処理を考えているのかもしれないですが、 例えばDDRやビートマニアのような音とマッチさせるゲームや演出では、正確な時間が必要なんです。 現在の私が作っている処理はフレーム数にのみ依存するので、FPSがくるってしまうと 音がずれてしまうという問題がありますので。 ただそこは数フレーム置きに時間を調整するなどすれば そこまで正確な時間を測定しなくても済みそうですね。 他にも色々と試してみようと思います。 >Sleepを使うとそれ以外のゲーム的処理は本当に止まって良いの? すみません、Sleepを使うとどのような欠点があるのでしょうか? 16.666msに1回描画したいので、1回の処理が1msで終わっても 15ms以上待たないといけないと思うので、普通にSleepすればいいなと思ったのですが、 何かこれに悪い面がありますでしょうか? キャプチャソフトなどとの同時処理も考えてSleepした方がいいかなと 思ったのですが。 >>Eruis様 GetNowHiPerformanceCount();の処理時間、誤差の件よくわかりました。ありがとうございます。 また、Sleepは処理を軽くする事に貢献するのですよね? 例えばプレイ動画をキャプチャソフトでとりたい、などの場合が考えられるので、出来るだけ軽くしたいのです。 そういう時はどのように待機させるのが適切なのでしょうか?
Re: 60FPSを保つ為には? ( No.6 )
名前: 日時:2008/06/17 10:20

>すみません、Sleepを使うと >どのような欠点があるのでしょうか? SleepはCPU時間を現在のスレッドから 他のスレッドに譲与します。 つまり、待つという作業が再描画だけで無く そのスレッドが行うすべての処理を 止めるということです。 描画以外は動かしておきたい場合があると Sleepでは対応できないということです。 #あくまでそういう処理があるならですが。
Re: 60FPSを保つ為には? ( No.7 )
名前:dixq 日時:2008/06/17 12:27

今のところ待機中にしなければならない処理はありません。 本来は何か処理をさせるはずなのでしょうか? ふさわしいゲームプログラムの書き方とは違うのかもしれませんので、 何か非効率な事をしてる可能性は大きいです・・。
Re: 60FPSを保つ為には? ( No.8 )
名前: 日時:2008/06/17 14:43

>本来は何か処理をさせるはずなのでしょうか? いえいえ、そういうわけではありません。 他に処理させるべきものがないのなら、 わざわざそうする必要はありません。 #言い回しに語弊があって申し訳ないです。 特定の時間に勝手にスレッドを起こしたり、 イベントを投げたりしてバックグラウンド的な 処理になると思います。
Re: 60FPSを保つ為には? ( No.9 )
名前:Euris 日時:2008/06/17 16:48

結局のところ、キャプチャソフトのパフォーマンス向上については、 > while(GetNowHiPerformanceCount()-start<micro_wait); は効果がないことと、Sleepには1m秒以上の誤差があることを考慮したうえで、 以下のことを実際に試してみる以外に方法はありません。 ・ScreenFlipで待機する ・最大 1m秒 Sleepで待機する ・最大 2m秒 Sleepで待機する ... ・最大16m秒 Sleepで待機する 処理落ちの少なさとキャプチャソフトのパフォーマンスのバランスを見極めてください。 ところで、待機中の時間には、次のフレームにおける処理を先取りし、 次のフレームの処理落ちを防ぐという使い方があるのですが、 たいていは計算量が膨大なゲームで使われており、 リソース消費がひっ迫しているのでなければ、必要ありません。
Re: 60FPSを保つ為には? ( No.10 )
名前:dixq 日時:2008/06/18 01:46

>> while(GetNowHiPerformanceCount()-start<micro_wait); >は効果がないことと、 Sleep関数では待機する事の出来ない端数分の待機をwhileでさせているので、 whileで処理が軽くなることは期待してないのですが、 とりあえず色々試行錯誤してみようと思います。 後、上記実行結果でどれ位何に時間かかったか確認出来、 実際に待機すべき時間はwaitに示されています。 この時間が12ms位だったり2ms位だったりすごく 振れ幅があるのですが、こんなもんなのでしょうか・・。 たんなるメニュー画面での結果なので、もう少しばらつきが落ち着いてもいいと思うのですが・・。 2msって・・下手すると計16ms超えてしまうフレームもあるんじゃ・・と危惧しています。
Re: 60FPSを保つ為には? ( No.11 )
名前:Will 日時:2008/06/18 19:14

> たんなるメニュー画面での結果なので、もう少しばらつきが落ち着いてもいいと思うのですが・・。 私はばらついて当たり前だと思います。 私が勘違いしているだけかもしれないですが、自分のアプリケーション以外がまったく動いていない 特殊な環境であればそうかもしれませんが、いまどきのPCであればウィルスチェックソフトだったり Windows Updateだったり、ユーザの目に見えないところでいろいろなソフトが動いています。 当然それらのソフトも同じCPU上で動いているので、OSはタイムシェアリングで各タスクに対してCPU の使用権を割り振っていきます。 たまたま、ほかのソフトが動いていなくてすぐに自分に順番が回ってきた場合はwaitは大きくなるで しょうし、ちょうどたくさんのソフトが起床するタイミングが重なればなかなか順番が回ってこず waitは小さくなるでしょう。 最悪、16ms以内に帰ってこないこともありえないわけではないでしょう。 通さんの最初の回答にもありましたが、FPSはあくまでも目安とするものであって固執するものではな いと思います。 同期をとることが絶対に必要であるのであれば、1フレームの処理時間で同期を取るのではない別の方 法を考えられたほうが現実的な気がします。 (じゃあ、どうすればいいの?って聞かれてもノーアイデアで申し訳ないですが^^;) #余談 プログラムには時にはあいまいさも必要です。 ゲームのように人が操作するソフトの場合、ミリ秒単位までこだわったところで人はたいてい認識でき ません。 DDRを例に挙げられていましたが、プログラムの処理が速い場合は厳密に判断し、追いつかない場合は 多少ライン上からずれているタイミングであってもOKとするとかいろいろ考え方はあると思います。 (画面はその状況に合うように適宜微調整する) 長文失礼しました。
Re: 60FPSを保つ為には? ( No.12 )
名前:dixq 日時:2008/06/20 03:35

ご回答いただきありがとうございます。 なるほど、そうですね。 16msを過ぎてしまったとしても、総合的に見て、 1秒に60fpsになるようにするにはいくらでも他で調整できますし、 1フレームの時間や計算に固執するのはやめることにします。 今回の質問を通して色々と勉強になりました。 ありがとうございました!

Page: 1 |