2010-01-14 129 views
6

我有一些動畫在OpenGL上下文窗口中運行,所以我需要不斷重繪它。所以我想出了以下代碼:在60 FPS的.NET重繪?

void InitializeRedrawTimer() 
    { 
     var timer = new Timer(); 
     timer.Interval = 1000/60; 
     timer.Tick += new EventHandler(timer_Tick); 
     timer.Start(); 
    } 

    void timer_Tick(object sender, EventArgs e) 
    { 
     glWin.Draw(); 
    } 

雖然這隻給了我40 FPS。但是,如果我將間隔設置爲1毫秒,則可以達到60個。那麼其他20毫秒的時間到了哪裏?這僅僅是由於計時器的準確性差或什麼?如果我想讓我的程序儘可能快地運行,有什麼辦法可以不斷調用draw func?

回答

7

你可以嘗試實現一個遊戲循環。

public void MainLoop() 
{ 
     // Hook the application’s idle event 
     System.Windows.Forms.Application.Idle += new EventHandler(OnApplicationIdle); 
     System.Windows.Forms.Application.Run(myForm); 
}  

private void OnApplicationIdle(object sender, EventArgs e) 
{ 
    while (AppStillIdle) 
    { 
     // Render a frame during idle time (no messages are waiting) 
     UpdateEnvironment(); 
     Render3DEnvironment(); 
    } 
} 

private bool AppStillIdle 
{ 
    get 
    { 
     NativeMethods.Message msg; 
     return !NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0); 
    } 
} 

//And the declarations for those two native methods members:   
[StructLayout(LayoutKind.Sequential)] 
public struct Message 
{ 
    public IntPtr hWnd; 
    public WindowMessage msg; 
    public IntPtr wParam; 
    public IntPtr lParam; 
    public uint time; 
    public System.Drawing.Point p; 
} 

[System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously 
[DllImport(「User32.dll」, CharSet=CharSet.Auto)] 
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags); 

簡潔,精緻,高效:

http://blogs.msdn.com/tmiller/archive/2005/05/05/415008.aspx

的基本循環(略從他的原始版本,並在新的SDK版本爲了便於閱讀修改) 。沒有額外的分配,沒有額外的集合,它只是工作。當隊列中沒有消息時,Idle事件觸發,然後處理程序不斷循環直到消息出現,在這種情況下它停止。一旦所有消息處理後,空閒事件再次被觸發,並且該過程重新開始。

此鏈接描述使用應用程序的空閒事件。它可能是有用的。您可以簡單地執行一些時間測試或睡眠,以便將其減慢至您所需的fps。嘗試使用System.Diagnostics.StopWatch類來獲得最準確的計時器。

我希望這會有所幫助。

+0

不錯!使用Application.Idle似乎比1 ms定時器更優雅。我將不得不在明天嘗試這個'StopWatch'。謝謝! **編輯:**其實,我撒謊。當您調整窗口大小或移動窗口時,它似乎停止空轉,從而停止重新繪製。這基本上降低了繪圖的優先級。也許我會堅持一個計時器來強制執行最低重繪速度......沒有理由我無法將兩種方法結合起來。 – mpen 2010-01-14 09:43:46

+3

另外,StopWatch僅適用於計時事物,不適用於回調事件。 – mpen 2010-01-14 09:52:04

+3

您可以嘗試在空閒偶數結束時啓動計時器,然後在空閒事件觸發時再次停止/檢查計時器。你可以用這種方式很接近。 – Nanook 2010-01-14 10:11:54

2

Windows窗體計時器最終限制在您的計算機的標準系統時鐘分辨率的分辨率。這種情況因計算機而異,但一般在1毫秒到20毫秒之間。

在的WinForms,當你在代碼請求定時器的時間間隔,Windows將只保證您的計時器將被稱爲沒有更多的時候然後指定的毫秒數。它不能保證你的定時器將在你指定的毫秒級被調用。

這是因爲Windows是一個分時操作系統,而不是實時的。其他進程和線程將被安排爲同時運行,因此您的線程將被迫定期等待處理器的使用。因此,您不應編寫依賴於精確間隔或延遲毫秒的WinForms計時器代碼。

在您的情況下,將間隔設置爲1 ms實際上只是告訴Windows儘可能頻繁地觸發此計時器。正如你所看到的,這絕對少於1毫秒(60fps =大約17毫秒)。

如果您需要更精確和更高分辨率的計時器,Windows API確實會提供高分辨率/性能計時器,如果您的硬件支持它,請參閱How to use the high resolution timer。這樣做的缺點是您的應用程序CPU使用率將會增加以支持更高的性能。

總的來說,我認爲你最好實現一個硬件/系統無關的遊戲循環。這樣,無論實際的幀速率如何,您的場景的感知動畫都會顯示爲常量。欲瞭解更多信息these articles給出了一個很好的背景。

3

根據你在做什麼,看看WPF和它的動畫支持,它可能是一個更好的解決方案,在WinForms中編寫自己的動畫。

另外考慮使用DirectX,因爲它現在有一個.NET包裝並且速度非常快,但是使用WPF或WinForms更加困難。

要不斷打電話給你繪製函數:

  • 做圖中重繪方法
  • 添加在重繪方法結束做的BeginInvoke
  • 在你傳遞給BeginInvoke的委託,無效您正在繪製的控件

但是,您的用戶可能不喜歡你殺死他們的電腦!

Application.Idle也是另一個不錯的選擇,但是我喜歡這樣的事實,即Windows會在需要時減慢發送WmPaint消息的速率。

+0

我也發現在繪圖後立即失效會產生比定時器方法更高的幀速率(由於某些原因,65 fps是帶定時器的最高值)。 – 2013-10-17 15:58:27

-1

我注意到在該行給出的示例代碼:

timer.Interval = 1000/60; 

它採用整數除法運算,所以結果將四捨五入到整數並不會精確16.6666ms,但17MS 。因此,計時器比屏幕刷新更大的滴答聲。

+0

整數除法截斷,而不是舍入。 1000/60因此是16([proof](https://repl.it/GGNW/0))。如果計時器準確,我應該得到〜62.5 FPS。 – mpen 2017-03-03 16:05:26

+0

哦。我已經忘記了。 但無論如何,如果它是16ms,滴答不與顯示器同步。 – Dennis 2017-03-06 09:01:57

+1

這取決於顯示器。很確定這不是gsync/freesync的問題,但無論如何,我很確定'timer.Interval'不是這裏的答案。 – mpen 2017-03-06 16:41:22