2011-04-05 59 views
1

一個簡單的遊戲循環運行有點像這樣:處理遊戲循環與OS定時器

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爲例這裏的例子的緣故(從this question拍攝)它可以是任何平臺。糾正我,如果我錯了,但這樣一個循環將使用100%的CPU /內核(使用我的電腦上的核心的50%),因爲它總是檢查running變量的狀態。

我的問題是,使用操作系統'(在Windows的示例中)定時器函數來實現遊戲循環會更好(性能方面),根據需要的遊戲邏輯的滴答數設置所需的時間間隔每秒?我問這是因爲我假設定時器功能使用CPU的RTC中斷。

回答

3

通常,即使用戶沒有做任何事情,遊戲也會不斷繪製新幀。這通常會發生在你有onIdle()調用的地方。如果你的遊戲只是在用戶按下按鈕等時或者偶爾在兩者之間更新窗口/屏幕,那麼MSGWaitForMultipleObjects是一個不錯的選擇。

但是,在連續動畫遊戲中,如果您可以提供幫助,您通常不會在渲染線程中屏蔽或休眠,而是希望以最高速度渲染並依賴垂直同步油門。其原因在於,時間,阻塞和睡眠最多是不準確的,最糟糕的是不可靠,並且很可能會給您的動畫添加令人不安的人爲因素。
你通常想要做的就是儘可能快地將屬於一個框架的所有東西都推送到圖形API(很可能是OpenGL,因爲你稱之爲「任何平臺」),表示工作線程開始執行遊戲邏輯和物理等,然後在SwapBuffers上阻塞。

所有定時器,超時和睡眠都受調度程序分辨率的限制,該分辨率在Windows下爲15ms(可使用timeBeginPeriod設置爲1ms)。在60fps時,一幀是16.666ms,所以阻塞15ms是災難性的,但即使是1ms也是相當長的時間。沒有什麼可以做到的,以獲得更好的解決方案(這在Linux下更好)。

Sleep,或有一個超時,保證你問你的過程至少只要睡任何閉鎖功能(其實,在POSIX系統,它可以睡如果發生了中斷) 。它不會保證您的線程在時間一到或任何其他保證時即可運行。
Windows下的睡眠(0)更糟糕。該文檔說「線程將放棄其時間片的剩餘部分,但仍保持就緒狀態。請注意,現成線程不能保證立即運行」。現實情況是,它在大多數情況下都能正常工作,阻止任何地方從「完全不」到5-10ms,但有時候我也看到睡眠(0)阻止了100ms,這在發生時是一種災難。

使用QueryPerformanceCounter正在尋求麻煩 - just don't do it。在一些系統上(最近有CPU的Windows 7),它可以正常工作,因爲它使用可靠的HPET,但在許多其他系統上,您將看到各種奇怪的「時間旅行」或「反向時間」調試。這是因爲在這些系統上,QPC的結果讀取取決於CPU頻率的TSC計數器。 CPU頻率不是恆定的,並且多核/多處理器系統上的TSC值不必一致。

然後出現同步問題。兩個單獨的計時器,即使以相同的頻率滴答時,也必然會變得不同步(因爲沒有「相同」的東西)。您的顯卡中有一個定時器已經在進行垂直同步。使用這一個進行同步意味着一切都會起作用,使用不同的將意味着事情最終會失去同步。不同步可能根本沒有任何效果,可能會畫出一半的完成幀,或者可能會阻擋一個完整幀,或者其他什麼。
雖然缺少一個框架通常沒什麼大不了(如果它只是一個並且很少發生),在這種情況下,它是您完全可以避免的東西。

+0

具有很多意義!此外,很高興知道'QueryPerformanceCounter()',我相信我一直在使用它(win7在這裏)沒有問題,但很好知道它不可靠。 – stelonix 2011-04-07 12:51:41

1

取決於您想優化的表現。如果你想爲你的遊戲提供最快的幀頻,那麼使用你發佈的循環。如果你的遊戲是全屏的話,這可能是正確的。

如果你想限制幀速率,並允許桌面和其他應用程序獲得一些cyles,那麼定時器方法就足夠了。在Windows中,您只需使用隱藏窗口,SetTimer調用,並將PeekMessage更改爲GetMessage。請記住WM_TIMER間隔不準確。當您需要確定自上一幀以來已經過去了多少實時時間時,請調用GetTickCount,而不是假設您在該時間間隔內完全醒來。

適用於遊戲和桌面應用程序的混合方法是使用上面發佈的循環,但在OnIdle函數返回後插入Sleep(0)。

0

絕對不要在遊戲中使用GetTickCount或WM_TIMER進行計時,它們會有可怕的分辨率。而是使用QueryPerformanceCounter。

+0

他將如何「喚醒」以調用QueryPerformanceCounter? – selbie 2011-04-06 07:04:36

+0

我會用一個緊張的遊戲循環,就像他爲此發佈的循環一樣。我不會觸及我提到的具有低分辨率的計時方法。 – korona 2011-04-06 08:23:41

+0

那麼如何解決他的表現問題呢? – selbie 2011-04-06 08:26:52

1
  1. MSGWaitForMultipleObjects應該在Windows遊戲消息循環使用,你可以讓它自動停止根據可定義的超時等待消息。這可以大大節省您的OnIdle調用,同時確保窗口快速響應消息。

  2. 如果你的窗口是不是全屏那麼SetTimer調用一個計時器PROC或張貼WM_TIMER消息到您的遊戲的WindowProc是需要。除非你不介意每當用戶停止動畫時,例如,點擊你窗口的標題欄。或者你彈出某種模式對話框。

  3. 在Windows上,無法訪問實際的定時器中斷。這些東西被硬件抽象層所掩蓋。定時器中斷由驅動程序處理以發送內核對象的信號,用戶模式代碼可用於調用WaitForMultipleObjects。這意味着當你調用SetTimer()時,內核會喚醒你的線程來處理與你的線程相關的內核定時器,此時,如果消息隊列爲空,GetMessage/PeekMessage將會合成WM_TIMER消息。