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

时间:2012-06-13 18:03:46

标签: c# image c#-4.0 gdi+

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

但是,我发现方法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 个答案:

答案 0 :(得分:97)

是的,它太慢了。

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

可以强制WinForms使用GDI而不是GDI +,甚至可以避免在幕后创建Graphics对象,此时你可以在其上层叠另一个渲染工具包(例如Direct2D)。但是,这并不简单。我在Paint.NET中执行此操作,您可以在SystemLayer DLL中的类GdiPaintControl上使用类似Reflector的内容查看所需的内容,但是对于您正在执行的操作,我认为这是最后的手段。 / p>

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

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

希望有所帮助。

答案 1 :(得分:3)

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

答案 2 :(得分:3)

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

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

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

答案 3 :(得分:2)

尽管这是一个古老的问题,而WinForms是一个古老的Framework,但我想分享一下我刚刚偶然发现的内容:将Bitmap绘制到BufferedGraphics中,然后将其渲染到OnPaint提供的图形上下文是比将位图直接绘制到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事件频率(我正在实现一个非常简单的位图草绘控件)。顶部是直接绘制位图的版本,底部是BufferedGraphics。在两种情况下,我都以相同的速度移动了鼠标。

enter image description here