WPF渲染太慢了

时间:2014-11-12 20:45:15

标签: wpf

我正在遇到一个奇怪的问题,试图使用WPF渲染多条折线(在2300x1024 Canvas中,64条折线各约400-500个顶点)。折线每50ms更新一次。

由于某种原因,我的应用程序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 Concurrency Analyzer工具查看发生了什么,并发现每次更新后主线程花费46ms执行一些与WPF相关的任务:System.Widnows.ContextLayoutManager.UpdateLayout()和System.Windows.Media.MediaContex .Render()。

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

我在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内部专家?

非常感谢你。

2 个答案:

答案 0 :(得分:0)

在尝试不同的方法(包括DrawingVisual)后,似乎绘制具有如此多顶点的折线效率太低。

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

答案 1 :(得分:0)

我发现绘制频繁更新的几何图形的最快方法是创建DrawingGroup&#34; backingStore&#34;,在OnRender()期间输出支持存储,然后更新我需要更新数据时使用backingStore.Open()进行备份。 (见下面的代码)

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

如果你的用户界面变得反应迟钝,你怎么每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();            
}