Alex Gdalevich
Alex Gdalevich

Reputation: 685

WPF rendering is too slow

I am experiencing a strange problem trying to use WPF to render a number of polylines (64 polylines about 400-500 vertices in each on a 2300x1024 Canvas). Polylines are updated every 50ms.

For some reason my application UI becomes very sluggish and almost unresponsive to user input.

I am using to following class to avoid updating the point collection while it is displayed:

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());
            }
        }
    }

I update the working buffer offscreen and then call Swap() to have it displayed. All 64 polylines (DoubleBufferPlot.Display) are added to a Canvas as children.

I used Visual Studio Concurrency Analyzer tool to see what's going on and discovered that after each update the main thread spends 46ms performing some WPF-related tasks: System.Widnows.ContextLayoutManager.UpdateLayout() and System.Windows.Media.MediaContex.Render().

I also discovered that there is another thread that's running almost non-stop rendering wpfgfx_v0400.dll!CPartitionThread::ThreadMain ... wpfgfx_v0400.dll!CDrawingContext::Render ... etc.

I read a number of articles on WPF including this: Can WPF render a line path with 300,000 points on it in a performance-sensitive environment? and also this article http://msdn.microsoft.com/en-us/magazine/dd483292.aspx.

I am (or my company rather) trying to avoid DrawingVisual since the rest of the project uses WPF shapes API.

Any idea why this is so slow? I even tried disabling anti-aliasing (RenderOptions.SetEdgeMode(mCanvas, EdgeMode.Aliased)) but it did not help very much.

Why does layout update takes so long. Anyone who is an expert in WPF internals?

Thank you very much.

Upvotes: 3

Views: 2203

Answers (2)

David Jeske
David Jeske

Reputation: 2466

The fastest way I've found to draw frequently updated geometry is to create a DrawingGroup "backingStore", output that backing store during OnRender(), and then update that backingStore when my data needs to update, by using backingStore.Open(). (see code below)

In my tests, this was more efficient than using WriteableBitmap or RenderTargetBitmap.

If your UI is becoming unresponsive, how are you triggering your redraw every 50ms? Is it possible some of the redraw is taking longer than 50ms and backing up the message-pump with redraw messages? One method to avoid this is to shut off your redraw timer during your redraw loop (or make it a one-shot timer), and only enable it at the end. Another method is to do your redraw during a CompositionTarget.Rendering event, which happens right before the WPF redraw.

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();            
}

Upvotes: 0

Alex Gdalevich
Alex Gdalevich

Reputation: 685

After trying different approaches including DrawingVisual it seems that drawing polylines with so many vertices is too inefficient.

I ended up implementing at approach where I draw polylines only when there 1 or fewer vertices per pixel. Otherwise I render manually to a WriteableBitmap object. This is surprisingly much more efficient.

Upvotes: 1

Related Questions