Winforms:如何加速 Invalidate()?
Posted
技术标签:
【中文标题】Winforms:如何加速 Invalidate()?【英文标题】:Winforms: How to speed up Invalidate()? 【发布时间】:2010-10-31 18:00:30 【问题描述】:我正在 GDI+ 中开发一个保留模式绘图应用程序。该应用程序可以在画布上绘制简单的形状并执行基本的编辑。执行此操作的数学优化到最后一个字节,这不是问题。我正在使用内置 Controlstyles.DoubleBuffer 的面板上绘图。
现在,如果我在大显示器(在我的情况下为高清)上最大化运行我的应用程序,我的问题就会出现。如果我尝试从(大)画布的一个角到对角线的另一个角画一条线,它将开始滞后并且 CPU 升高。
我的应用程序中的每个图形对象都有一个边界框。因此,当我使从最大化应用程序的一个角到对角对角线的一条线的边界框无效时,该边界框实际上与画布一样大。当用户绘制一条线时,边界框的这种无效因此发生在 mousemove 事件上,并且有明显的滞后可见。如果线条是画布上的唯一对象,也会存在这种滞后。
我已尝试通过多种方式对此进行优化。如果我画一条较短的线,CPU 和延迟就会下降。如果我删除 Invalidate() 并保留所有其他代码,则应用程序很快。如果我使用区域(仅跨越图形)而不是边界框来使无效,那么它同样慢。如果我将边界框拆分为一系列背靠背的小框,从而减少失效区域,则看不到明显的性能提升。
所以我在这里不知所措。如何加快失效速度?
另一方面,Paint.Net 和 Mspaint 都存在同样的缺点。然而,Word 和 PowerPoint 似乎能够像上面描述的那样绘制一条线,没有滞后,也没有 CPU 负载。这样就有可能达到预期的效果,问题是如何?
【问题讨论】:
Word 和 Powerpoint 不使用 GDI+...我不认为 GDI+ 因其速度而受到称赞(超过普通 GDI)。 您是否尝试过使用分析器查看减速实际发生的位置?我已经做了很多这种事情(很久以前,在 Delphi 中),但从未发现 Invalidate() 需要花费大量时间。另一方面,绘画是“有趣”的,因为它跑得足够快,可以用橡皮筋绑线。 好吧,无论如何,我相信 Delphi 对这类工作的速度要快得多。正如 Henk 所说,GDI+ 根本不以速度着称,更有可能是缺乏速度。但是,实际的绘制代码并不是瓶颈。大面积的无效化似乎是。我正在考虑使用自己的后缓冲区并手动对图像进行 blit 以查看是否可以获得性能提升。你能推荐任何专门针对 VS2005 的分析器吗? 【参考方案1】:对于像线条这样的基本显示项目,如果您绝对必须在每个绘图周期中使它们的整个边界无效,则应该考虑将它们分成几个部分。
原因是 GDI+(以及 GDI 本身)使矩形区域无效,正如您使用边界框指定的那样。您可以通过测试一些水平和垂直线与斜率与显示区域的方面相似的线来自己验证这一点。
所以,假设您的画布是 640x480。如果从 0,0 到 639,479 画一条线; Invalidate() 将使整个区域无效,从顶部的 0,0 到 639,0 到底部的 0,479 到 639,479。例如,从 0,100 到 639,100 的水平线会生成一个只有 1 像素高的矩形。
区域也会遇到同样的问题,因为区域被视为组合在一起的水平范围集。因此,对于从一个角到另一个角的大对角线,为了匹配您设置的边界框 - 一个区域必须指定每条垂直线上的每组像素或整个边界框。
因此,作为一种解决方案,如果您的线路非常长,请将其分成四分之一或八分之一,这样性能应该会大大提高。回顾上面的例子,如果你只是将两部分分成两半——你会将总无效区域减少到 0,0 x 319,239 加上 320,240 x 639,479。
这是一个四分之一分割的视觉示例。粉色区域是无效的。不幸的是,SO 不会让我发布图片或超过 1 个链接,但这应该足以解释一切。
(四等分线分割,总无效面积为表面的 1/4)
a 640x480 extent with 4 equal sized boxes carved behind a line drawn across the diagonal
或者,您可能需要考虑重写更新,以便仅绘制与必须更新的区域匹配的项目部分,而不是指定边界框。这实际上取决于需要多少对象参与绘制更新。如果给定帧中有数千个对象,您可能会考虑忽略所有无效区域并重新绘制整个场景。
【讨论】:
您好,感谢您的回答。不幸的是,如果您阅读了我对答案 #2 的回复,我已经尝试了您所描述的内容,但它不起作用。不过,这很令人惊讶。场景可能不会包含超过几十个项目,但这实际上取决于用户。这样做几乎没有任何收获。仅更新四分之一的行(作为测试)确实有所改善(如预期的那样),在这种情况下,CPU 和更新区域之间几乎存在线性相关性。 您好,我再次查看了您的示例,我认为您的技术可能会从您的绘图代码与活动处理程序中解耦中受益最大。我已将您可以检查的示例上传到以下位置。 amorph.com/stack-overflow/DrawTest1.zip 以下是您可能需要注意的一些差异: 1. 派生自 UserControl 而不是 Panel。 2.没有使用OptimizedDoubleBuffer。 3. 使用从控件获得的 Graphics 对象初始化个人后台缓冲区(以保留 HDC 颜色深度和设置)。顺便说一句,我在这里发帖是因为我还没有足够的代表。 :) 无效区域始终为矩形。当您使多个区域无效时,它们通常会合并到一个最大的矩形中,该矩形封装了所有等待更新的区域。但是,更糟糕的是,有时它们是按顺序接收的。因此,如果您经常重绘一个重要区域 - 就像在样本中一样;通常最好将惰性重绘与示例中的固定更新率结合起来。 哦,关于您的其他想法,DirectDraw/DirectX 在窗口环境中运行良好。 Internet Explorer 在内部使用 DirectDraw 已经有很长一段时间了。但是,要保持与 DirectDraw 的一致性需要做很多工作。您将不得不担心人们会改变您身上的颜色深度、重叠窗口、多显示器跨接等。您还会发现硬件加速器在处理这些情况时往往差异很大。如果动画是您应用中用户体验的重要组成部分,则值得切换。 不用担心。还可以尝试阅读冯远的《Windows 图形编程:Win32 GDI 和 DirectDraw》一书。尽管它是多年前出版的,但它是关于通过 GDI 在 Windows 中获得最佳图形编程结果的权威指南。这是亚马逊上这本书的链接。 amazon.com/…【参考方案2】:你无法真正加速 Invalidate。它之所以慢是因为它会将 WM_PAINT 事件发布到消息队列中。然后它会被过滤掉,最终你的 OnPaint 甚至会被调用。您需要做的是在 MouseMove 事件期间直接在控件中绘制。
在我所做的任何需要一定程度的流畅动画的控件中,我的 OnPaint 事件通常只调用 PaintMe 函数。这样我就可以随时使用该函数重绘控件。
【讨论】:
当然,我明白你在说什么。您的意思是,我应该根据某些 fps 方案 Update() 我的控件而不是使其无效。不过,这可能会使 CPU 更加紧张。请记住,这只是当我使大部分高清屏幕无效时才会出现的问题。我真的在寻找答案,例如是否可以通过手动 blitting 成功加快此过程,或者即如果在 GDI+ bmp 上绘画并使用 DX 将该图像绘制到 DX 表面会更快。【参考方案3】:澄清一下:用户画的是直线,还是你的线实际上是一堆连接鼠标点的线段?如果该线是从原点到当前鼠标点的直线,则不要 Invalidate() 而是使用 XOR 画笔绘制一条可撤消的线,然后取消绘制上一条线,仅在用户完成绘制时无效。
如果您要绘制一堆小线段,只需使最近线段的边界框无效。
【讨论】:
嗯,这是一个 CAD 应用程序,为了简单起见,我使用了“线”这个比喻。实际上,我正在以一个角度绘制一个窄矩形,因此它由四个 GDI+ 绘制线操作加上一个无效的画布组成。线条可以有任何颜色,并与舞台上的许多其他图形对象相交,所以我不确定 XOR 是正确的方法......【参考方案4】:如果有一个不同的线程“发布更新”到真实画布上。
Image paintImage;
private readonly object paintObject = new object();
public _ctor() paintImage = new Image();
override OnPaint(PaintEventArgs pea)
if (needUpdate)
new Thread(updateImage).Start();
lock(paintObject)
pea.DrawImage(image, 0, 0, Width, Height);
public void updateImage()
// don't draw to paintImage directly (it might cause threading issues)
Image img = new Image();
using (Graphics g = img.GetGraphics())
foreach (PaintableObject po in renderObjects)
g.DrawObject(po);
lock(paintObject)
using (Graphics g = paintImage.GetGraphics())
g.DrawImage(img, 0, 0, g.Width, g.Height);
needUpdate = false;
只是一个想法,所以我没有测试代码;-)
【讨论】:
这可能有效,但我对此表示怀疑。首先,我必须摆脱内置的 GDI+ 双缓冲(它显然具有相当好的性能),其次瓶颈似乎是失效本身。所有其他开销都可以忽略不计。由于 GDI+ 是纯软件图形处理(无硬件加速),它仅意味着将问题从一个线程转移到下一个线程。我很想知道是否有人有任何使用单独线程优化渲染的经验。另一种选择是使用硬件加速方法。 嗯,如果是 "Invalidate()" 很慢,调用 OnPaint(new PaintEventArgs(CreateGraphics(), null || constRectangle) 怎么样?以上是关于Winforms:如何加速 Invalidate()?的主要内容,如果未能解决你的问题,请参考以下文章
如何让 View.invalidate 在 Click Listener 中工作
使用 invalidate-session="false" 时如何获取 Spring Security 自动注销时间?
session.invalidate()和session.abandon()有啥不同,该如何选择?