.NET重绘速度为60 FPS?

时间:2010-01-14 09:24:29

标签: c# winforms

我在OpenGL上下文窗口中运行了一些动画,所以我需要不断重绘它。所以我想出了以下代码:

    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 ms,我可以达到60.那么其他20 ms的位置是什么?那只是由于计时器的准确性差或者是什么?如果我想让我的程序尽可能快地运行,有没有办法连续调用绘制函数?

4 个答案:

答案 0 :(得分:7)

您可以尝试实施游戏循环。

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

  

基本循环(稍微修改了原始版本和新SDK中的版本以便于阅读):

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);
  

简单,优雅,有效。没有额外的分配,没有额外的集合,它只是工作..当队列中没有消息时,Idle事件触发,然后处理程序继续循环,直到出现消息,在这种情况下它停止..一旦所有消息都是处理完毕后,再次触发idle事件,并重新开始该过程。

此链接描述了使用应用程序的空闲事件的链接。它可能是有用的。您可以简单地执行一点时间测试或睡眠以将其减慢到所需的fps。尝试使用System.Diagnostics.StopWatch类来获得最准确的计时器。

我希望这会有所帮助。

答案 1 :(得分:3)

根据您正在做的事情,看看WPF及其动画支持,在WinForms中编写自己的动画可能是更好的解决方案。

另外考虑使用DirectX,因为它现在有一个.NET包装器并且非常快,但是使用WPF或WinForms更难。

连续调用绘图功能:

  • 在重绘方法中执行绘图
  • 在重绘方法的末尾添加一个BeginInvoke
  • 在您传递给BeginInvoke的委托中,使您正在绘制的控件无效

但是,您的用户可能不喜欢您杀死他们的计算机!

Application.Idle也是另一个不错的选择,但我喜欢这样的事实,Windows会降低它在需要时发送WmPaint消息的速度。

答案 2 :(得分:2)

Windows窗体计时器最终的分辨率受限于计算机的标准系统时钟分辨率。这在计算机之间有所不同,但通常在1毫秒到20毫秒之间。

在WinForms中,每当您在代码中请求计时器间隔时,Windows将仅保证您的计时器将被调用不再经常,然后指定的毫秒数。它不能保证您的Timer将在您指定的毫秒时间内被调用。

这是因为Windows是一个分时操作系统,而不是实时操作系统。其他进程和线程将被安排为并发运行,因此您的线程将被强制等待处理器的使用。因此,您不应编写依赖于精确间隔或毫秒延迟的WinForms计时器代码。

在您的情况下,将间隔设置为1毫秒实际上只是告诉Windows尽可能频繁地触发此计时器。正如您所看到的,这绝对不是1毫秒(60fps =约17毫秒)。

如果您需要更准确,分辨率更高的计时器,Windows API会提供高分辨率/高性能计时器,如果您的硬件支持,请参阅How to use the high resolution timer。这样做的一个缺点是,您的应用程序CPU使用率将会增加,以支持更高的性能。

总的来说,我认为你最好实现一个独立于硬件/系统的游戏循环。这样,无论实际的帧速率如何,场景的感知动画看起来都是恒定的。有关详细信息,请these articles提供良好的背景信息。

答案 3 :(得分:-1)

我在行中的给定示例代码中注意到了:

timer.Interval = 1000 / 60;

它使用整数除法运算,因此结果将四舍五入为整数,不会精确为16.6666ms,而是17ms。因此,计时器比屏幕刷新更大。