Graphics.DrawImage对于更大的图像来说太慢了吗?

我正在制作游戏,我希望有一个带有背景图像的主菜单。

但是,我发现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()无法用于绘制更大的图像? 如果是这样,我该怎么做才能提高游戏性能?

是的,它太慢了。

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

可以强制WinForms使用GDI而不是GDI +,甚至可以避免在幕后创建Graphics对象,此时你可以在其上层叠另一个渲染工具包(例如Direct2D)。 但是,这并不简单。 我在Paint.NET中这样做,你可以在SystemLayer DLL中GdiPaintControl在类似GdiPaintControl的类上使用像Reflector这样的东西,但是对于你正在做的事情,我认为这是最后的选择。

但是,您正在使用的位图大小(800×1200)在GDI +中仍然可以正常运行,而不必采用高级互操作,除非您的目标是低至300MHz Pentium II。 以下是一些可能有用的提示:

  • 如果你在调用Graphics.DrawImage()使用不透明的位图(没有alpha /透明度),特别是如果它是带有alpha通道的32位位图(但你知道它是不透明的,或者你不关心) ,然后在调用DrawImage()之前将Graphics.CompositingMode设置为CompositingMode.SourceCopy (确保将其设置回原始值之后,否则常规绘图基元将看起来非常难看)。 这会为每个像素跳过大量额外的混合数学运算。
  • 确保Graphics.InterpolationMode未设置为InterpolationMode.HighQualityBicubic 。 使用NearestNeighbor将是最快的,虽然如果有任何拉伸它可能看起来不太好(除非它正好拉伸2x,3x,4x等)。 Bilinear通常是一个很好的折衷方案。 如果位图大小与您要绘制的区域匹配,则不应使用除NearestNeighbor任何内容,以像素为单位。
  • 总是绘制到OnPaint()给你的Graphics对象。
  • 始终在OnPaint进行绘图。 如果需要重绘区域,请调用Invalidate() 。 如果您现在需要绘图,请在Invalidate()之后调用Update() Invalidate() 。 这是一种合理的方法,因为WM_PAINT消息(导致对OnPaint()的调用)是“低优先级”消息。 窗口管理器的任何其他处理都将首先完成,因此最终可能会出现大量跳帧和挂钩的情况。
  • 使用System.Windows.Forms.Timer作为帧率/滴答计时器将无法正常工作。 这些是使用Win32的SetTimer实现的,并导致WM_TIMER消息,然后导致Timer.Tick事件被引发,WM_TIMER是另一个低优先级消息,仅在消息队列为空时发送。 你最好使用System.Threading.Timer ,然后使用Control.Invoke() (以确保你在正确的线程!)并调用Control.Update()
  • 通常,不要使用Control.CreateGraphics() 。 (“总是在OnPaint()绘制”和“总是使用OnPaint() ‘给你的Graphics推论”)
  • 我建议不要使用Paint事件处理程序。 相反,在你正在编写的类中实现OnPaint() ,它应该从Control派生。 从另一个类派生,例如PictureBoxUserControl ,将不会为您添加任何值或将增加额外的开销。 (BTW PictureBox经常被误解。你可能几乎不想使用它。)

希望有所帮助。

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

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

你看过XNA / DirectX / OpenGL吗? 这些是为游戏开发而设计的框架,与使用WinForms或WPF等UI框架相比,其效率和灵活性要高出几个数量级。 这些库都提供C#绑定。

您可以使用BitBlt等函数将本地代码插入到本机代码中,但是也存在与跨越托管代码边界相关的开销。