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