【问题标题】:WPF rendering is too slowWPF 渲染太慢
【发布时间】:2014-11-12 20:45:15
【问题描述】:

我在尝试使用 WPF 渲染多条折线时遇到了一个奇怪的问题(64 条折线,每个折线在 2300x1024 画布上大约 400-500 个顶点)。折线每 50 毫秒更新一次。

由于某种原因,我的应用程序 UI 变得非常缓慢,几乎对用户输入没有响应。

我正在使用以下类以避免在显示点集合时更新它:

class DoubleBufferPlot
    {
        /// <summary>
        /// Double-buffered point collection
        /// </summary>
        private readonly PointCollection[] mLineBuffer =
        {
            new PointCollection(),
            new PointCollection()
        };

        private int mWorkingBuffer; //index of the workign buffer (buffer being modified)

        #region Properties

        //Polyline displayed
        public Polyline Display { get; private set; }

        /// <summary>
        /// index operator to access points
        /// </summary>
        /// <param name="aIndex">index</param>
        /// <returns>Point at aIndex</returns>
        public Point this[int aIndex]
        {
            get { return mLineBuffer[mWorkingBuffer][aIndex]; }
            set { mLineBuffer[mWorkingBuffer][aIndex] = value; }
        }

        /// <summary>
        /// Number of points in the working buffer
        /// </summary>
        public int WorkingPointCount
        {
            get { return mLineBuffer[mWorkingBuffer].Count; }

            set
            {
                SetCollectionSize(mLineBuffer[mWorkingBuffer], value);
            }
        }
        #endregion

        public DoubleBufferPlot(int numPoints = 0)
        {
            Display = new Polyline {Points = mLineBuffer[1]};

            if (numPoints > 0)
            {
                SetCollectionSize(mLineBuffer[0], numPoints);
                SetCollectionSize(mLineBuffer[1], numPoints);
            }
        }

        /// <summary>
        /// Swap working and display buffer
        /// </summary>
        public void Swap()
        {
            Display.Points = mLineBuffer[mWorkingBuffer];  //display workign buffer

            mWorkingBuffer = (mWorkingBuffer + 1) & 1; //swap

            //adjust buffer size if needed
            if (Display.Points.Count != mLineBuffer[mWorkingBuffer].Count)
            {
                SetCollectionSize(mLineBuffer[mWorkingBuffer], Display.Points.Count);
            }
        }

        private static void SetCollectionSize(IList<Point> collection, int newSize)
        {
            while (collection.Count > newSize)
            {
                collection.RemoveAt(collection.Count - 1);
            }

            while (collection.Count < newSize)
            {
                collection.Add(new Point());
            }
        }
    }

我在屏幕外更新工作缓冲区,然后调用 Swap() 使其显示。所有 64 条折线 (DoubleBufferPlot.Display) 作为子项添加到 Canvas。

我使用 Visual Studio 并发分析器工具查看发生了什么,发现每次更新后主线程花费 46 毫秒执行一些与 WPF 相关的任务:System.Widnows.ContextLayoutManager.UpdateLayout() 和 System.Windows.Media.MediaContex 。使成为()。

我还发现有另一个线程在运行几乎不间断的渲染 wpfgfx_v0400.dll!CPartitionThread::ThreadMain ... wpfgfx_v0400.dll!CDrawingContext::Render ... 等等

我阅读了许多关于 WPF 的文章,其中包括:Can WPF render a line path with 300,000 points on it in a performance-sensitive environment? 还有这篇文章http://msdn.microsoft.com/en-us/magazine/dd483292.aspx

我(或者我的公司)试图避免使用 DrawingVisual,因为项目的其余部分使用 WPF 形状 API。

知道为什么这么慢吗?我什至尝试禁用抗锯齿 (RenderOptions.SetEdgeMode(mCanvas, EdgeMode.Aliased)),但没有太大帮助。

为什么布局更新需要这么长时间。有谁是 WPF 内部的专家?

非常感谢。

【问题讨论】:

  • 反复添加和删除形状不会很快。形状类似于 WPF 的控件,因为无论何时添加或删除画布,都需要重新计算画布的视觉范围。
  • 我不添加和删除形状。 DoubleBufferPlot.Display 添加一次,我只更新点集合(Polyline.Points)。

标签: wpf


【解决方案1】:

在尝试了包括 DrawingVisual 在内的不同方法后,似乎用这么多顶点绘制折线效率太低了。

我最终实现了仅在每个像素有 1 个或更少顶点时才绘制折线的方法。否则我手动渲染到 WriteableBitmap 对象。这令人惊讶地效率更高。

【讨论】:

  • 如果形状大部分是静态的,您还可以在它们上启用缓存合成,这有效地完成了同样的事情:WPF 将渲染的几何图形缓存在内存中的纹理中,并且仅在视觉无效。查看CacheMode 属性和BitmapCache 类。它可能比渲染到软件WriteableBitmap 更容易、更高效。
  • 是的,没错。但是,就我而言,形状每 25-50 毫秒更新一次。该应用程序类似于示波器,只是您可以查看最多 64 个通道的网格。
  • 不过,如果您将它们重绘到软件位图表面,缓存的合成可能会更快,并使您的代码更简单。我认为值得一试。
【解决方案2】:

我发现绘制频繁更新的几何图形的最快方法是创建一个DrawingGroup“backingStore”,在OnRender() 期间输出该备份存储,然后在我的数据需要更新时使用@更新该备份存储987654323@。 (见下面的代码)

在我的测试中,这比使用WriteableBitmapRenderTargetBitmap 更有效。

如果您的 UI 变得无响应,您如何每 50 毫秒触发一次重绘?是否有可能某些重绘时间超过 50 毫秒并使用重绘消息备份消息泵?避免这种情况的一种方法是在重绘循环期间关闭重绘计时器(或使其成为一次性计时器),并仅在最后启用它。另一种方法是在 CompositionTarget.Rendering 事件期间进行重绘,这发生在 WPF 重绘之前。

DrawingGroup backingStore = new DrawingGroup();

protected override void OnRender(DrawingContext drawingContext) {      
    base.OnRender(drawingContext);            

    Render(); // put content into our backingStore
    drawingContext.DrawDrawing(backingStore);
}

// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {            
    var drawingContext = backingStore.Open();
    Render(drawingContext);
    drawingContext.Close();            
}

【讨论】:

    猜你喜欢
    • 2014-08-21
    • 1970-01-01
    • 2020-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-22
    • 1970-01-01
    • 2019-01-28
    相关资源
    最近更新 更多