Reputation: 685
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
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
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