33

我目前正在开发一款游戏,我希望有一个带有背景图像的主菜单。

但是,我发现这种方法Graphics.DrawImage()真的很慢。我做了一些测量。假设 MenuBackground 是我的资源图像,分辨率为 800 x 1200 像素。我会将它绘制到另一个 800 x 1200 位图上(我首先将所有内容渲染到缓冲区位图,然后缩放它,最后将其绘制到屏幕上 - 这就是我处理多个玩家分辨率的可能性的方式。但它不应该影响无论如何,请参阅下一段)。

所以我测量了以下代码:

Stopwatch SW = new Stopwatch();
SW.Start();

// First let's render background image into original-sized bitmap:

OriginalRenderGraphics.DrawImage(Properties.Resources.MenuBackground,
   new Rectangle(0, 0, Globals.OriginalScreenWidth, Globals.OriginalScreenHeight));

SW.Stop();
System.Windows.Forms.MessageBox.Show(SW.ElapsedMilliseconds + " milliseconds");

结果出乎我的意料——Stopwatch测量值介于40 - 50 milliseconds. 而且由于背景图像不是唯一要绘制的东西,整个菜单需要大约 100 毫秒才能显示,这意味着可观察到的滞后。

我试图将它绘制到 Paint 事件给出的 Graphics 对象,但结果是30 - 40 milliseconds- 没有太大变化。

那么,这是否意味着Graphics.DrawImage()不能用于绘制更大的图像?如果是这样,我应该怎么做才能提高我的游戏性能?

4

4 回答 4

135

是的,它太慢了。

几年前,我在开发 Paint.NET 时遇到了这个问题(实际上,从一开始,这很令人沮丧!)。渲染性能非常糟糕,因为它总是与位图的大小成正比,而不是与被告知要重绘的区域的大小成正比。也就是说,当执行 OnPaint() 和调用 Graphics.DrawImage() 时,帧率随着位图大小的增加而下降,而随着无效/重绘区域的大小下降,帧率永远不会上升。一个小的位图,比如 800x600,总是可以正常工作,但较大的图像(例如 2400x1800)非常慢。(无论如何,对于前面的段落,您可以假设没有发生任何额外的事情,例如使用一些昂贵的双三次滤波器进行缩放,这会对性能产生不利影响。)

可以强制 WinForms 使用 GDI 而不是 GDI+,甚至避免Graphics在幕后创建对象,此时您可以在其上分层另一个渲染工具包(例如 Direct2D)。然而,这并不简单。我在 Paint.NET 中执行此操作,您可以通过GdiPaintControl在 SystemLayer DLL 中调用的类上使用类似 Reflector 的东西来查看需要什么,但是对于您正在做的事情,我认为这是最后的手段。

但是,您使用的位图大小 (800x1200) 在 GDI+ 中应该仍然可以正常工作,而不必求助于高级互操作,除非您的目标是低至 300MHz Pentium II。以下是一些可能会有所帮助的提示:

  • 如果您在对 的调用中使用不透明位图(无 alpha/透明度)Graphics.DrawImage(),特别是如果它是带有 alpha 通道的 32 位位图(但您知道它是不透明的,或者您不在乎),则设置Graphics.CompositingModeCompositingMode.SourceCopybefore调用DrawImage()(一定要在之后将其设置回原始值,否则常规绘图图元会看起来很丑陋)。这会跳过每个像素的大量额外混合数学。
  • 确保Graphics.InterpolationMode未设置为InterpolationMode.HighQualityBicubic. 使用NearestNeighbor将是最快的,尽管如果有任何拉伸,它可能看起来不太好(除非它正好拉伸 2x、3x、4x 等)Bilinear通常是一个很好的折衷方案。您不应该使用任何东西,NearestNeighbor除非位图大小与您要绘制的区域相匹配,以像素为单位。
  • 总是画入Graphics给你的对象OnPaint()
  • 总是在OnPaint. 如果需要重绘区域,请调用Invalidate(). 如果您需要立即进行绘图,请致电Update()after Invalidate()。这是一种合理的方法,因为 WM_PAINT 消息(导致调用OnPaint())是“低优先级”消息。窗口管理器的任何其他处理都将首先完成,因此您最终可能会出现大量跳帧和卡顿。
  • 使用 aSystem.Windows.Forms.Timer作为帧速率/滴答计时器效果不佳。这些是使用 Win32 实现的SetTimer,并产生 WM_TIMER 消息,然后Timer.Tick引发事件,而 WM_TIMER 是另一个低优先级消息,仅在消息队列为空时发送。你最好使用System.Threading.Timer然后使用Control.Invoke()(以确保你在正确的线程上!)并调用Control.Update().
  • 一般来说,不要使用Control.CreateGraphics(). (推论到'总是画OnPaint()'和'总是使用Graphics给你的OnPaint()')
  • 我建议不要使用 Paint 事件处理程序。相反,OnPaint()在您正在编写的类中实现,该类应该派生自Control. 从另一个类派生,例如PictureBoxor UserControl,要么不会为您增加任何价值,要么会增加额外的开销。(顺便说一句PictureBox,经常被误解。你可能几乎永远不想使用它。)

希望有帮助。

于 2012-06-14T01:01:39.680 回答
7

虽然这是一个古老的问题,WinForms 是一个古老的框架,但我想分享一下我刚刚偶然发现的东西:将 Bitmap 绘制到 BufferedGraphics 中,然后将其渲染到 OnPaint 提供的图形上下文中绘制 Bitmap 快得多直接到 OnPaint 的图形上下文 - 至少在我的 Windows 10 机器上。

这很令人惊讶,因为直觉上我认为复制数据两次会稍微慢一些(所以我认为这通常只有在想要手动进行双缓冲时才合理)。但显然 BufferedGraphics 对象发生了一些更复杂的事情。

因此,在承载位图的控件的构造函数中创建一个 BufferedGraphics(在我的情况下,我想绘制一个全屏位图 1920x1080):

        using (Graphics graphics = CreateGraphics())
        {
            graphicsBuffer = BufferedGraphicsManager.Current.Allocate(graphics, new Rectangle(0,0,Screen.PrimaryScreen.Bounds.Width,Screen.PrimaryScreen.Bounds.Height));
        }

并在 OnPaint 中使用它(同时取消 OnPaintBackground)

    protected override void OnPaintBackground(PaintEventArgs e) {/* just rely on the bitmap to fill the screen */}

    protected override void OnPaint(PaintEventArgs e)
    {   
        Graphics g = graphicsBuffer.Graphics;

        g.DrawImage(someBitmap,0,0,bitmap.Width, bitmap.Height);

        graphicsBuffer.Render(e.Graphics);
    }

而不是天真地定义

    protected override void OnPaintBackground(PaintEventArgs e) {/* just rely on the bitmap to fill the screen */}

    protected override void OnPaint(PaintEventArgs e)
    {   
        e.Graphics.DrawImage(someBitmap,0,0,bitmap.Width, bitmap.Height);
    }

请参阅以下屏幕截图以比较生成的 MouseMove 事件频率(我正在实现一个非常简单的位图草图控件)。顶部是直接绘制Bitmap的版本,底部是使用BufferedGraphics。在这两种情况下,我以大致相同的速度移动鼠标。

在此处输入图像描述

于 2018-12-08T16:57:57.547 回答
3

GDI+ 可能不是游戏的最佳选择。应该首选 DirectX/XNA 或 OpenGL,因为它们利用任何可能的图形加速并且速度非常快。

于 2012-06-13T18:08:41.140 回答
3

GDI+ 无论如何都不是速度恶魔。任何严重的图像操作通常都必须进入事物的本机方面(pInvoke 调用和/或通过调用获得的指针进行操作LockBits。)

你看过 XNA/DirectX/OpenGL 吗?这些是为游戏开发而设计的框架,比使用 WinForms 或 WPF 等 UI 框架更高效、更灵活。这些库都提供 C# 绑定。

您可以使用诸如BitBlt之类的函数 pInvoke 进入本机代码,但也存在与跨越托管代码边界相关的开销。

于 2012-06-13T18:09:19.363 回答