【问题标题】:Performance of DrawingVisual vs Canvas.OnRender for lots of constantly changing shapesDrawingVisual vs Canvas.OnRender 对许多不断变化的形状的性能
【发布时间】:2010-02-23 15:36:47
【问题描述】:

我正在开发一个类似游戏的应用程序,它有多达一千种形状(椭圆和线条),它们以 60fps 的速度不断变化。在阅读了excellent article on rendering many moving shapes 之后,我使用自定义的 Canvas 后代实现了这一点,该后代覆盖了 OnRender 以通过 DrawingContext 进行绘图。尽管 CPU 使用率很高,但性能相当合理。

但是,文章建议不断移动形状的最有效方法是使用大量DrawingVisual 实例而不是OnRender。不幸的是,虽然它没有解释 为什么 在这种情况下应该更快。

以这种方式更改实现是一项不小的努力,所以我想在决定进行切换之前了解原因以及它们是否适用于我。在这种情况下,为什么DrawingVisual 方法比OnRender 方法的 CPU 使用率更低?

【问题讨论】:

  • Romkyns,在深入研究重大更改之前,您可以使用 DrawingVisual 和 Canvas.OnRender() 创建一些简化版本以匹配性能。至于答案——我完全同意查理。

标签: wpf performance


【解决方案1】:

来自 C# 2008 中的 Pro WPF:

这些带来的问题 应用程序的复杂性不是 艺术,但绝对数量 单个图形元素。即使 您将 Path 元素替换为 重量更轻的几何对象, 开销仍然会阻碍 应用程序的性能。 WPF 这种情况的解决方案是 使用较低级别的可视层 模型。基本的想法是你 将每个图形元素定义为 视觉对象,这是一个非常 重量轻的成分较少 开销比几何对象或 路径对象。

归结为,您创建的每一个椭圆和线条都是一个单独的FrameworkElement;这意味着它不仅支持命中测试,还支持布局、输入、焦点、事件、样式、数据绑定、资源和动画。对于您正在尝试做的事情来说,这是一个相当重的对象! Visual 对象跳过所有这些并直接从 DependencyObject 继承。它仍然提供对命中测试、坐标转换和边界框计算的支持,但不支持形状支持的其他内容。它更轻量级,可能会极大地提高您的性能。

编辑:

好吧,我第一次看错了你的问题。

如果您使用OnRender,这实际上取决于您如何创建视觉效果并显示它们。如果您使用DrawingContext 并将所有视觉效果添加到单个元素,这与使用DrawingVisual 方法没有什么不同。如果您为每个创建的Visual 创建一个单独的元素,那么这将是一个问题。在我看来,您做事的方式是正确的。

【讨论】:

  • 等等,等等,我没有创建任何 FrameworkElements! :) 我使用的是OnRender + DrawingContext,所以除非 DrawingContext 在幕后创建了一堆 FrameworkElements(我对此表示怀疑),否则情况并非如此。
  • 编辑也是错误的,使用DrawingContext渲染时没有创建任何Visual,只是简单地绘制形状而不保留任何参考。
【解决方案2】:

答案中的每个人都弄错了。问题是直接在绘图上下文中渲染形状是否比创建 DrawingVisual 更快。答案显然是“是”。 DrawLine、DrawEllipse、DrawRectangle 等函数不会创建任何 UI 元素。 DrawingVisual 慢得多,因为它确实创建了一个 UI 元素,尽管它是一个轻量级元素。答案中的混淆是因为人们简单地复制/粘贴 DrawingVisual 比 MSDN 中不同的 UIElement 形状语句表现更好。

【讨论】:

  • 我不完全确定答案是否显而易见。在大多数形状不移动的情况下,绘图上下文不是更慢吗?它重绘所有内容,而 DrawingVisual 方法允许框架缓存和重用之前绘制的内容。还是不是这样?
  • DrawingContext 不会重绘所有内容,因为 WPF 具有保留的绘图模型。 DrawingVisual 包括控制是否应该重绘 Visual 的逻辑。您可以在 OnRender() 方法中实现相同的逻辑并仅在必要时绘制。 WPF 不会以 60 fps 使您的绘图无效,仅当您调用 InvalidateVisual() 或 WPF 需要使您的绘图无效时才会调用 OnRender()。
【解决方案3】:

我认为 Petzold 在这一段中解释过;

ScatterPlotVisual 类的工作原理是 为 每个数据点。当属性 一个DataPoint对象的变化,类 只需要改变 DrawingVisual 与该数据点相关联。

这建立在较早的解释之上;

只要 ItemsSource 属性 更改,或集合更改,或 DataPoint 对象的属性 收藏发生变化, ScatterPlotRender 调用 无效视觉。这会产生一个 调用 OnRender,绘制 整个散点图

这是你要问的吗?

顺便说一句,this 是一个相当新的高性能 WPF 教程,该图中有数万个点,它也是 3D 渲染和动画的(甚至使用鼠标输入来驱动一些变换)。

【讨论】:

  • 是的,我似乎很困惑,因为我没有意识到 DrawingVisual 方法仅被认为是这种特定场景的“最佳”方法——其中很多点都发生了变化,但不是全部。当一切都移动时(对我来说就是这种情况)OnRender 听起来是最好的方法。链接也不错 - 谢谢。
  • 还有另一种方法,我发现它是改变具有“许多不断变化的形状”的应用程序的最佳性能,这是 OP 的问题。您的应用程序可以操作绘图组树。您只需在应用程序启动时,在此树的根部调用一次 RenderOpen/DrawDrawing。从那时起,WPF 会注意到对树的所有编辑并自动更新,包括深度嵌套的 DrawingGroup 子项的转换和插入/删除。您将需要一个跟踪/维护树状态的数据结构。请参阅stackoverflow.com/a/716469/147511我的评论。
【解决方案4】:

然而,在我的测试中(平移动画),我发现速度没有差异。我会说为许多绘图视觉效果使用宿主元素要快一些。这种使用许多视觉效果构建视觉树的方法为您提供了更多控制权。此外,当您想要进行复杂的命中测试时,过滤过程会更快,因为您可以跳过整个视觉“分支”

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-23
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    • 2011-12-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多