C# Graphics.DrawImage对于较大的图像是否太慢?

C# Graphics.DrawImage对于较大的图像是否太慢?,c#,image,c#-4.0,gdi+,C#,Image,C# 4.0,Gdi+,我目前正在做一个游戏,我希望有一个带有背景图片的主菜单 但是,我发现方法Graphics.DrawImage()非常慢。我做了一些测量。假设MenuBackground是我的资源图像,分辨率为800 x 1200像素。我将把它画到另一个800 x 1200的位图上(我先把所有的东西都渲染成一个缓冲位图,然后缩放它,最后把它画到屏幕上——这就是我处理多个播放器分辨率可能性的方法。但它不应该以任何方式影响它,见下一段) 因此,我测量了以下代码: Stopwatch SW = new Stopwatc

我目前正在做一个游戏,我希望有一个带有背景图片的主菜单

但是,我发现方法
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");
结果让我非常惊讶,
秒表测量的时间在
40-50毫秒之间。而且因为背景图像不是唯一要绘制的东西,整个菜单的显示时间大约超过100毫秒,这意味着可以观察到滞后

我曾尝试将其绘制到Paint event给定的图形对象,但结果是
30-40毫秒
-没有太大变化


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

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

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

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

您可以使用函数pInvoke进入本机代码,如,但跨越托管代码边界也会带来开销。

是的,速度太慢

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

可以强制WinForms使用GDI而不是GDI+,甚至可以避免在幕后创建
图形
对象,此时您可以将另一个渲染工具包(例如Direct2D)置于其之上。然而,这并不简单。我在Pr.net中这样做,您可以看到在StultLealDLL中使用类反射器之类的东西称为<代码> GdiPrimtTrime<代码>,但是对于您所做的,我认为这是最后的选择。
但是,您使用的位图大小(800x1200)在GDI+中应该仍然可以正常工作,而不必求助于高级互操作,除非您的目标是300MHz的奔腾II。以下是一些可能会有所帮助的提示:

  • 如果在调用
    Graphics.DrawImage()
    时使用不透明位图(无alpha/透明度),尤其是带有alpha通道的32位位图(但您知道它是不透明的,或者您不在乎),则在调用
    DrawImage()之前,请将
    Graphics.CompositingMode
    设置为
    CompositingMode.SourceCopy
    (请确保在之后将其设置回原始值,否则常规图形图元将看起来非常难看)。这跳过了许多额外的每像素混合数学
  • 确保未将
    Graphics.InterpolationMode
    设置为类似
    InterpolationMode.HighQualityBicubic
    。使用
    nearestneighbork
    将是最快的,尽管如果有任何拉伸,它看起来可能不是很好(除非它正好拉伸了2倍、3倍、4倍等等)。
    Bilinear
    通常是一个很好的折衷方案。如果位图大小与要绘制的区域相匹配(以像素为单位),则不应使用任何东西,而应使用
    NearestNeighbor
  • 始终在
    OnPaint()
    中提供给您的
    Graphics
    对象中绘制
  • 始终在
    OnPaint
    中进行绘图。如果需要重新绘制区域,请调用
    Invalidate()
    。如果需要立即绘制,请在
    Invalidate()
    之后调用
    Update()
    。这是一种合理的方法,因为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()
    提供给您的
    图形”
  • 我建议不要使用止痛药
            using (Graphics graphics = CreateGraphics())
            {
                graphicsBuffer = BufferedGraphicsManager.Current.Allocate(graphics, new Rectangle(0,0,Screen.PrimaryScreen.Bounds.Width,Screen.PrimaryScreen.Bounds.Height));
            }
    
        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);
        }