c# - 提高 DrawingContext 性能渲染几何(多边形和折线)

标签 c# wpf performance drawingcontext

这是我的第一个问题,但是我潜伏了很长时间。我将把它分成两部分,一部分解释我在做什么以及为什么我认为这是要走的路,第二部分是我自己无法解决的实际问题。

我在做什么?
我目前正在开发用于渲染旨在实时显示的二维特征的框架。您可以在浏览器中想象像 Google Maps 这样的应用程序,但是该框架旨在呈现各种地理数据(不仅仅是轴对齐的栅格数据,如那些 Google Tiles)。

该框架将集成到我们(公司的)最新产品中,该产品是适用于台式机和笔记本电脑的 WPF 应用程序。

因此我选择 WPF 仅用于实际渲染几何图形;可见性和遮挡剔除以及输入处理(鼠标拾取)、移动相机等都由我自己完成。

作为实时应用程序,它需要达到至少 30 FPS。该框架在渲染图像时表现良好:我可以毫无问题地每帧绘制数千个位图,但结果证明多边形数据是一个主要问题。

实际问题
我正在使用 WPF 渲染我相当数量的折线和多边形数据,特别是使用 DrawingContext 和 StreamGeometry。到目前为止,我的理解是,如果我需要性能,这是要走的路。但是,我无法达到预期的结果。

这就是我用实际数据填充 StreamGeometry 的方式:

using (StreamGeometryContext ctx = Geometry.Open())
{
        foreach (var segment in segments)
    {
        var first = ToWpf(segment[0]);
        ctx.BeginFigure(first, false, false);

        // Skip the first point, obviously
        List<Point> points = segment.Skip(1).Select(ToWpf).ToList();
        ctx.PolyLineTo(points, true, false);
    }
}
    Geometry.Freeze();

这就是我绘制几何图形的方式:
_dc.PushTransform(_mercatorToView);
_dc.DrawGeometry(null, _pen, polyline);
_dc.Pop();

作为测试,我将来自 OpenStreetMap 的 ESRI 形状加载到我的应用程序中以测试其性能,但我一点都不满意:
我的测试数据由 ~3500 条线段组成,总共 ~20k 行。

将每个段映射到它自己的 StreamGeometry 表现得非常糟糕,但我已经有点预期了:渲染大约需要 14 秒。

然后,我尝试使用多个图形将更多段打包到同一个 StreamGeometry 中:
80 StreamGeometry,渲染耗时约50ms。

但是,我无法得到比这更好的结果。将行数增加到 100k 左右使我的应用程序几乎无法使用:渲染需要超过 100 毫秒。
除了卡住几何图形和笔之外我还能做什么渲染矢量数据时?

我现在宁愿自己使用 DirectX,也不愿依靠 WPF 来处理它,因为某些事情似乎发生了严重的错误。

编辑

进一步澄清我在做什么:该应用程序实时可视化地理数据,非常像浏览器中的谷歌地图等应用程序:但是它应该可视化更多的数据。您可能知道,Google map 允许缩放和平移,这需要 > 25 FPS 才能显示为流畅的动画;任何少都感觉不流畅。

*
抱歉,我不应该在实际产品发布之前上传视频。然而,您可能会想到像谷歌地图这样的东西,但是有大量的矢量数据(多边形和折线)。
*

有两种解决方案,其中一种经常被提及:

在位图中缓存重绘图

实现似乎有点简单,但是我发现这种方法存在一些问题: 为了正确实现平移,我需要 避免 在每一帧绘制沉重的东西,因此我只能选择在平移相机时不更新缓存的位图,或者创建一个覆盖比视口(viewport)更大区域的位图,这样我只需要更新缓存的位图位图每隔一段时间。

第二个“问题”与缩放有关。然而,它更像是一种视觉伪像而不是真正的问题:由于缓存的位图无法以 30 FPS 的速度正确更新,因此在缩放时我也需要避免这种情况。我可以在缩放时很好地缩放位图,只在缩放结束时创建一个新的位图,但是折线的宽度会不是 有一个恒定的厚度,虽然他们应该。

MapInfo 似乎确实使用了这种方法,但是我不能说我太喜欢它。不过,它似乎是最容易实现的。

将几何图形拆分为不同的绘图视觉效果

这种方法似乎以不同的方式处理问题。我不确定这种方法是否有效:这取决于我是否正确理解 WPF 在该领域的工作方式。
我应该使用多个,而不是对所有需要绘制的东西使用一个 DrawingVisual,这样不是每个都需要 RenderOpened()。我可以简单地更改参数,例如上面示例中的矩阵,以反射(reflect)相机平移和移动。
但是,我也看到了这种方法的一些问题:平移相机将不可避免地将新几何体带入视口(viewport),因此我需要执行与第一种方法类似的操作,实际渲染当前不可见但可能变为可见的内容由于相机移位;绘制所有内容是不可能的,因为对于相当少量的数据可能需要大量的时间。

与这两种方法相关的问题
这两种方法都无法解决的一个大问题是,即使整体帧速率稳定,偶尔也会出现问题,无论是在更新缓存位图时(好吧,如果缓存位图仅在相机处于不再平移)或调用 RenderOpen 来绘制可见的几何体块,似乎是不可避免的。

到目前为止我的想法

由于这是我见过的唯一两个解决此问题的解决方案(我已经做了一年多的谷歌搜索),我想到目前为止唯一的解决方案是在最强大的 GPU 上接受帧速率提升(应该能够每秒光栅化数亿个图元),视口(viewport)的延迟更新(在位图仅在视口(viewport)不再移动时更新的情况下)或根本不使用 WPF 并求助于 DirectX直接地。

我很高兴得到帮助,但是到目前为止,我不能说 WPF 的渲染性能给我留下了深刻的印象。

最佳答案

要提高 2D WPF 渲染性能,您可以查看 RenderTargetBitmap (对于 WPF >= 3.5)或 BitmapCache类(对于 WPF >= 4)。

这些类用于 Cached Composition
来自 MSDN :

By using the new BitmapCache and BitmapCacheBrush classes, you can cache a complex part of the visual tree as a bitmap and greatly improve rendering time. The bitmap remains responsive to user input, such as mouse clicks, and you can paint it onto other elements just like any brush.

关于c# - 提高 DrawingContext 性能渲染几何(多边形和折线),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14807359/

相关文章:

c# - 在 ViewModel 中访问 XAML 对象

c# - 为什么将数组作为文字而不是参数传递要快得多?

c# - 将事件绑定(bind)到 ViewModel

java Tigase 性能

c# - 使用 Entity Framework 代码保存分离的对象图首先导致主键冲突

C# WPF 二进制读取

c# - 在这种情况下,垃圾收集器是否回收了该对象?

javascript - 使用 jQuery 脚本页面加载逐渐变慢

Java:获取输入整数数组的最有效方法

c# - visual studio 中的仅调试类和资源 - 这可能吗?