1

単純なゲームループは次のように実行されます。

MSG msg;
while (running){
    if (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else
        try{
            onIdle();
        }
        catch(std::exception& e){
            onError(e.what());
            close();
        }
}

この質問から引用)

ここでは、例としてWindowsを使用していますが、どのプラットフォームでもかまいません。私が間違っている場合は訂正してください。ただし、このようなループは常にrunning変数の状態をチェックしているため、CPU /コアの100%を使用します(コンピューターの1つのコアの50%を使用します)。

私の質問は、OS(Windowsの例)のタイマー関数を使用してゲームループを実装し、1秒あたりに必要なゲームロジックのティック数に応じて必要な間隔を設定する方が(パフォーマンス的に)優れているでしょうか?タイマー機能はCPUのRTC割り込みを使用すると想定しているので、これを尋ねます。

4

4 に答える 4

3

通常、ゲームは、ユーザーが何もしなくても、常に新しいフレームを描画し続けます。これは通常、onIdle()呼び出しがある場合に発生します。ユーザーがボタンなどを押したとき、またはその間に散発的にゲームがウィンドウ/画面を更新する場合は、それMSGWaitForMultipleObjectsが適切なオプションです。

ただし、連続アニメーションゲームでは、通常、レンダリングスレッドでブロックしたりスリープしたりすることはできません。代わりに、最大速度でレンダリングし、スロットルとして垂直同期に依存する必要があります。その理由は、タイミング、ブロック、およびスリープがせいぜい不正確であり、最悪の場合は信頼性が低く、アニメーションに不快なアーティファクトが追加される可能性が高いためです。
通常は、フレームに属するすべてのものをグラフィックAPI(「任意のプラットフォーム」と言ったのでOpenGL)にできるだけ速くプッシュし、ワーカースレッドに信号を送って、ゲームのロジックや物理などを開始します。次に、SwapBuffersでブロックします。

すべてのタイマー、タイムアウト、およびスリープは、スケジューラーの解像度によって制限されます。これは、Windowsでは15ミリ秒です(を使用して1ミリ秒に設定できますtimeBeginPeriod)。60fpsでは、フレームは16.666msであるため、15msのブロッキングは壊滅的ですが、1msでもかなりの時間です。より良い解像度を得るためにできることは何もありません(これはLinuxではかなり良いです)。

Sleep、またはタイムアウトのあるブロッキング関数は、プロセスが少なくとも要求した時間だけスリープすることを保証します(実際、Posixシステムでは、割り込みが発生した場合、スリープが少なくなる可能性があります)。時間切れになるとすぐにスレッドが実行されるという保証や、その他の保証はありません。
WindowsでのSleep(0)はさらに悪いです。ドキュメントには、「スレッドは残りのタイムスライスを放棄しますが、準備はできています。準備ができたスレッドがすぐに実行されるとは限らないことに注意してください」と記載されています。現実には、ほとんどの場合は問題なく動作し、「まったくない」から5〜10ミリ秒までブロックされますが、Sleep(0)が100ミリ秒間ブロックされることもあります。これは、発生した場合の悲惨なことです。 。

使用するQueryPerformanceCounterことは問題を引き起こします-ただそれをしないでください。一部のシステム(最近のCPUを搭載したWindows 7)では、信頼性の高いHPETを使用しているため、問題なく動作しますが、他の多くのシステムでは、誰も説明できない、あらゆる種類の奇妙な「タイムトラベル」または「リバースタイム」効果が見られます。デバッグ。これは、これらのシステムでは、QPCの結果がCPUの周波数に依存するTSCカウンターを読み取るためです。CPU周波数は一定ではなく、マルチコア/マルチプロセッサシステムのTSC値は一貫している必要はありません。

次に、同期の問題があります。同じ周波数でティックしている場合でも、2つの別々のタイマーは必然的に非同期になります(「同じ」などがないため)。グラフィックカードには、すでに垂直同期を実行しているタイマーがあります。これを同期に使用すると、すべてが機能し、別の1つを使用すると、最終的に同期がとれなくなります。同期がとれていないと、まったく効果がない場合や、半分完成したフレームが描画される場合、または1フレーム全体がブロックされる場合などがあります。
フレームが欠落していることは通常それほど大きな問題ではありませんが(フレームが1つだけで、めったに発生しない場合)、この場合、そもそも完全に回避できるものです。

于 2011-04-06T18:45:19.013 に答える
1

誰のパフォーマンスを最適化するかによって異なります。ゲームで最速のフレーム レートが必要な場合は、投稿したループを使用します。ゲームが全画面表示の場合、これはおそらく正しいことです。

フレーム レートを制限し、デスクトップや他のアプリがいくつかのサイクルを取得できるようにする場合は、タイマー アプローチで十分です。Windows では、隠しウィンドウ、SetTimer 呼び出しを使用し、PeekMessage を GetMessage に変更するだけです。WM_TIMER 間隔は正確ではないことに注意してください。最後のフレームから実際にどれくらいの時間が経過したかを把握する必要がある場合は、時間間隔で正確に起床したと仮定するのではなく、GetTickCount を呼び出します。

ゲームとデスクトップ アプリに関してうまく機能するハイブリッド アプローチは、上で投稿したループを使用することですが、OnIdle 関数が戻った後に Sleep(0) を挿入します。

于 2011-04-06T06:44:24.067 に答える
1
  1. MSGWaitForMultipleObjects should be used in a windows game message loop as you can get it to automatically stop waiting for messages according to a definable timeout. This can throttle your OnIdle calls a lot while ensuring that the window responds to messages quickly.

  2. If your window is not full screen then a SetTimer calling a timer proc or posting WM_TIMER messages to your game's WindowProc is required. Unless you don't mind the animation stalling whenever the user, for example, clicks on your window's title bar. Or you pop up a modal dialog of some kind.

  3. On Windows there is no way to access actual timer interrupts. That stuff is buried away under many layers of hardware abstraction. timer interrupts are handled by drivers to signal kernel objects, that user mode code can use in calls to WaitForMultipleObjects. This means that the kernel will wake up your thread to handle a kernel timer associated with your thread when you called SetTimer(), at which point GetMessage/PeekMessage will, if the message queue is empty, synthesize a WM_TIMER message.

于 2011-04-06T12:51:07.420 に答える
0

ゲームのタイミングに GetTickCount または WM_TIMER を使用しないでください。これらの解像度はひどいものです。代わりに、QueryPerformanceCounter を使用してください。

于 2011-04-06T06:53:53.127 に答える