Fast Rendering in WPF using GDI using a Background Thread
Rendering tens of thousands of lines was taking several seconds to render in WPF.
I was trying to draw a graph with a dozen series or so in real-time (the data comes in at 10Hz, but we’d settle for 1 or 2 frames per second as a worst case). Each series had over 1,000 data points which I represented as a line graph, and implemented with a Path object that had a StreamGeometry. This should have been the fastest way to draw a path, and profiling my code it was pretty fast. However, the actual rendering on the render thread took a many seconds and this made the UI unresponsive (it needs the render thread too, of course).
What I did
I looked at Petzold’s article and it made drawing the paths very fast. The hang up was still in the render thread. That solution helps if you have a lot of objects. I actually have about 12 paths, but they are composed of 10s of 1000′s of lines. My best theory is that the GPU is turning all those lines into pairs of triangles and that’s surprising slower than doing Bresenham’s algorithm in software. See Jeremiah Morril’s deep dive into wpf rendering.
It’s shocking (to me) that software rendering would be so much faster than hardware rendering. I haven’t verified this, but I believe the GPU is rendering each line as two triangles as if it were a rectangle. And apparently this is much slower than drawing a simple line.
Using GDI+ to draw the lines on a bitmap gave me performance improvement of two orders of magnitude. (I guess GPU development has been driven by the game world where fast rendering of polygons for 3D is where the money is at.)
I’m hoping to get some time to try out Direct2D to see how it performs at drawing lines. Alas, that wouldn’t work for my project because Direct2D isn’t supported in older versions of windows. I don’t know if it would help since it uses the two-triangle method of drawing lines.
A huge bonus using the GDI library is that I can chose which thread to do the line rendering. Using a background thread keeps the rest of the UI responsive. Once the bitmap is rendered, it’s copied to the screen in an Image control (the render thread then renders this image, which is a super fast bitmap copy). Since I only allow one background thread, additional requests to draw are ignored until my renderer has finished: A simple way to adjust the frame-rate for the CPU.
Acknowledgements and References
Thanks for Tamir Khason for pointing me in the right direction for creating an interop bitmap. Thanks to Dwayne Need for getting me going with a HostVisual to draw into.
And see this blog for an excellent analysis of the speed of different techniques. For me drawing direct aliased lines was the key. So GDI+ was the way to go. I considered Direct2D, but if it uses the GPU, it may turn out to have the same problem WPF did. I’m hoping to try that eventually, but Direct2D doesn’t work in XP, and we have to stay compatible.
See my test program here
Here are some of the key bits in the code:
GraphCanvas.xaml is the user control for drawing the lines. All it has is an image control to host the bitmap. Just this:
The code behind GraphControl.cs has a BackgroundWorker called RenderWorker that draws the paths with a couple of GDI calls.
When that’s done, you just copy the bitmap into the image control.
RenderData is my own structure, like Path it has figures and points to draw between. Normally I’d use a Path but it’s a dependency object and doesn’t move easily between threads.
Initialize() creates the bitmaps in a few lines of code. This is what Tamir and Dwayne Need’s blogs helped me with. See the code for the details.
The background rendering technique is a really powerful technique whenever you have to draw something that takes any length of time. The rest is all about getting fast lines.
I hope you find that useful. Please leave me a reply if you do.