【问题标题】:How may I improve the way I am currently applying clipping to a canvas area?如何改进我当前将剪辑应用到画布区域的方式?
【发布时间】:2016-05-22 20:34:43
【问题描述】:

概述

我需要在画布上绘制大量图形,在本例中是TPaintBox 画布。油漆盒是TScrollBox 的子级,油漆盒的高度和宽度可能非常大,例如 5000x5000。

我有一个TList,其中包含我自己的对象,每个对象都有自己的 X 和 Y 属性以及自己的图形。在paintbox 的OnPaint 方法中,我遍历列表中的每个对象,然后将每个对象图形绘制到paintbox 画布上存储在对象X 和Y 位置的坐标处。

显然,绘制所有这些会减慢应用程序的速度并且变得非常沉重,所以我需要一种方法来优化它。

显而易见的方法是以某种方式利用 GPU 而不是 CPU,但这可能要复杂得多,Firemonkey 可以帮助我,但我严格运行在 VCL 类型的项目中,而不是 FMX。

可能的解决方案?

我遇到了CreateRectRgn,如果我理解正确的话,我想如果我可以在带有自定义区域的画框上进行绘制,例如在滚动框的可见区域中,这应该会大大提高性能。因此,考虑到这一点,我尝试了以下方式:

procedure TForm1.PaintBox1Paint(Sender: TObject);    
var
  MyRgn: HRGN;
begin
  // iterate and draw objects onto FBuffer (offscreen bitmap) first,
  // note: FBuffer size is the same as the scrollboxes clientwidth
  // and clientheight
  //begin
  //  ...
  //end;

  // create and paint on a region (visible area of the scrollbox) on
  // the paintbox rather than painting the whole paintbox.
  MyRgn := CreateRectRgn(0, 0, ScrollBox1.ClientWidth, ScrollBox1.ClientHeight);
  try
    SelectClipRgn(PaintBox1.Canvas.Handle, MyRgn);
    PaintBox1.Canvas.Draw(ScrollBox1.HorzScrollBar.Position,
      ScrollBox1.VertScrollBar.Position, FBuffer);
    SelectClipRgn(PaintBox1.Canvas.Handle, HRGN(nil));
finally
  DeleteObject(MyRgn);
end;

问题

我想首先也是最重要的是,这是否是优化我在颜料盒画布上绘画方式的好方法,我还有哪些其他可能的选择?我唯一需要注意的另一件事是检查每个对象的 X 和 Y,如果它在滚动框的可见区域之外,我不绘制它。

我仍然对创建剪辑区域的想法很熟悉,但是上面的代码示例感觉不对。我不喜欢不断创建和删除区域的想法,尤其是在绘制方法中。

我的项目变得相当大,实际上我将其放入自定义控件中,但是当处理数百个对象并将它们绘制在画盒画​​布上时,我逐渐变慢了,我很确定这与要么是我没有正确实现剪辑区域,要么是我不断创建、绘制然后从 OnPaint 方法中删除区域的方式。

有没有更实用的方法来实现这一点?例如,也许可以在表单创建时创建区域并在表单销毁(或从自定义控件构造函数/析构函数)销毁它?但是,如果调整了表单/控件的大小,我将如何调整剪辑区域的大小?

我真的可以使用一些建议并弄清楚我面临的这个问题,以帮助我更好地了解我可以做得更好或不同的地方。

谢谢。

【问题讨论】:

  • GraphicControl (PaintBox) 在为其父级 (ScrollBox) 获得的 DC 上运行。我的意思是,绘画已经被剪辑到滚动框的客户区。更多详情,请参阅TWinControl.PaintControls
  • @SertacAkyuz 哇,我从来不知道。如果是这种情况,那么我需要找到另一种优化绘画的方法,因为我确信在我尝试添加自己的剪辑区域之前它会更慢。无论如何,我的问题和疑虑仍然存在,特别是关于我是否实际上正确设置了剪切区域,无论画盒的父级是否为我这样做。我确信我的任务可以以更好的方式完成,但在此过程中学习或纠正错误总是受欢迎的。
  • @SertacAkyuz 另外,假设很久以后当我真正将所有内容放入自定义控件时,我最好在滚动框上发布画布并直接在其上绘制而不是使用子画框,那么我肯定还需要滚动框画布的剪切区域?
  • 另一个修改应该考虑到速度的增加。通常情况下,系统忽略的绘图不应占用大量时间,也许是为该绘图做准备 - 计算等......您应该分析并查看需要时间的内容。 ...我很难理解你的设计。你的离屏位图就是滚动框的大小,那么你在画框的不可见部分画的是什么呢?
  • 一个自己的画布或一个油漆盒子组件 - 性能方面无关紧要,油漆盒是一个非常轻量级的组件。相信我,wincontrol 在让子控件绘制自己之前将其 DC 剪辑到其子控件的大小。

标签: delphi delphi-xe7


【解决方案1】:

根据您的代码示例和其中的注释,您似乎首先将所有对象绘制到屏幕外位图中。

如果是这种情况,那么使用或不使用剪辑区域不会有太大的不同,因为瓶颈在于将所有对象绘制到屏幕外位图上的代码。

优化代码的最佳解决方案是仅绘制可见区域内的对象。

所以现在您的主要问题应该是确定哪些对象可见哪些不可见的最佳方法是什么。

因为在您的 cmets 中您提到您正在制作类似地图的控件,我认为最好的方法是将您的地图划分为由多个扇区组成的网格,然后根据对象位置存储每个扇区的对象数据。每个扇区都是一个单独的 TList。

这将允许您仅基于可见扇区迭代少量对象,这应该已经大大提高了您的性能。

现在这些扇区的大小应取决于对象的常见大小。

此外,在决定要绘制哪些扇区时,请务必确保您也绘制了一点超出可见区域的内容,以便仍然绘制位置跨越扇区边界并且可能仅部分可见的对象。如果您的对象尺寸都没有超过单个扇区的大小,那么绘制所有可见扇区加上向外的一行或一行就足够了。

【讨论】:

  • 这是一个可行的解决方案,但我会让对象回答它们是否可见。
  • @NGLN 询问每个对象是否可见仅在您拥有少量对象时才可行。但是,如果您有很多“询问”,则每一个都可能成为瓶颈。我建议的方法的主要优点是您不需要“询问”每个对象是否可见,而只需检查某个扇区是否可见,然后为您提供有关哪些对象可见的大致信息,因为只有部分扇区是在可见范围内。但这仍然比迭代和检查每个对象的可见性要快得多......
  • .. 信不信由你,但大多数拥有大型游戏地图的电脑游戏在确定需要渲染哪些对象时,甚至在某些情况下甚至需要将哪些对象存储在内存中或正在处理。作为一名游戏开发者,我可以直接向您确认这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-06
  • 1970-01-01
  • 1970-01-01
  • 2013-09-09
  • 1970-01-01
  • 1970-01-01
  • 2019-03-11
相关资源
最近更新 更多