Mir*_*res 29 c# gdi+ image c#-4.0
我正在制作游戏,我希望有一个带有背景图像的主菜单.
但是,我觉得这个方法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");
Run Code Online (Sandbox Code Playgroud)
结果让我感到惊讶 - Stopwatch两者之间的措施40 - 50 milliseconds.并且因为背景图像不是唯一要绘制的东西,所以整个菜单需要大约100毫秒才能显示,这意味着可观察到的延迟.
我试图将它绘制到Paint事件给出的Graphics对象,但结果是30 - 40 milliseconds- 没有太大的改变.
那么,这是否意味着,这Graphics.DrawImage()对于绘制更大的图像是不可用的?如果是这样,我该怎么做才能提高游戏性能?
Ric*_*ter 114
是的,它太慢了.
几年前我在开发Paint.NET时遇到了这个问题(从一开始,实际上,这是相当令人沮丧的!).渲染性能非常糟糕,因为它总是与位图的大小成比例,而不是它被重新绘制的区域的大小.也就是说,当位图的大小上升时,帧率下降,并且当实现OnPaint()并调用Graphics.DrawImage()时,帧率从未上升,因为无效/重绘区域的大小下降.一个小的位图,比如800x600,总是很好,但是较大的图像(例如2400x1800)非常慢.(无论如何,对于前面的段落,您可以假设没有任何额外的事情发生,例如使用一些昂贵的双立方滤波器进行缩放,这会对性能产生负面影响.)
可以强制WinForms使用GDI而不是GDI +,甚至可以避免Graphics在幕后创建一个对象,此时你可以在其上层叠另一个渲染工具包(例如Direct2D).但是,这并不简单.我在Paint.NET中这样做,你可以看到GdiPaintControl在SystemLayer DLL中调用的类上使用像Reflector这样的东西需要什么,但是对于你正在做的事情,我认为这是最后的手段.
但是,您正在使用的位图大小(800x1200)在GDI +中仍然可以正常工作,而不必采用高级互操作,除非您的目标是低至300MHz的奔腾II.以下提示可能会有所帮助:
Graphics.DrawImage(),特别是如果它是带有alpha通道的32位位图(但你知道它是不透明的,或者你不关心),那么设置Graphics.CompositingMode为CompositingMode.SourceCopy之前调用DrawImage()(确保之后将其设置回原始值,否则常规绘图基元将看起来非常难看).这会为每个像素跳过很多额外的混合数学.Graphics.InterpolationMode没有设置为类似的东西InterpolationMode.HighQualityBicubic.使用NearestNeighbor将是最快的,虽然如果有任何拉伸它可能看起来不太好(除非它正好拉伸2倍,3倍,4倍等)Bilinear通常是一个很好的妥协.NearestNeighbor除非位图大小与您要绘制的区域匹配,否则不应使用任何内容,以像素为单位.Graphics给你的物体OnPaint().OnPaint.如果您需要重绘某个区域,请致电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()'和'总是用' Graphics给你OnPaint()')OnPaint()在你正在编写的类中实现应该从中派生出来Control.从另一个类派生,例如PictureBox或UserControl,将不会为您添加任何值或将增加额外的开销.(顺便说一句PictureBox经常被误解.你可能几乎从不想用它.)希望有所帮助.
虽然这是一个古老的问题和WinForms是一个古老的框架,我想和大家分享我刚才偶然发现:绘制一个位图到BufferedGraphics事后使其由OnPaint中提供的图形上下文的方式比绘制的位图快直接到 OnPaint 的图形上下文 - 至少在我的 Windows 10 机器上。
这是令人惊讶的,因为直觉上我认为复制数据两次会稍微慢一点(所以我认为这通常只有在想要手动进行双缓冲时才合理)。但显然 BufferedGraphics 对象有一些更复杂的东西。
因此,在 Control 的构造函数中创建一个 BufferedGraphics 来承载位图(在我的情况下,我想绘制一个 1920x1080 的全屏位图):
using (Graphics graphics = CreateGraphics())
{
graphicsBuffer = BufferedGraphicsManager.Current.Allocate(graphics, new Rectangle(0,0,Screen.PrimaryScreen.Bounds.Width,Screen.PrimaryScreen.Bounds.Height));
}
Run Code Online (Sandbox Code Playgroud)
并在 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);
}
Run Code Online (Sandbox Code Playgroud)
而不是天真地定义
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);
}
Run Code Online (Sandbox Code Playgroud)
有关生成的 MouseMove 事件频率的比较,请参见以下屏幕截图(我正在实现一个非常简单的位图素描控件)。顶部是直接绘制Bitmap的版本,底部是使用BufferedGraphics。在这两种情况下,我以大致相同的速度移动鼠标。