New Toronto Group
 - New Toronto Group

Fast Line Rendering in WPF

| Posted on February 14th, 2012 by Sean Hopen


Fast Rendering in WPF using GDI using a Background Thread

 The Problem

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.

The Code

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:
Xaml Code for  graphics canvas

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.

 

 

Posted in .NET, Silverlight, WPF  | Comments (12)

Comments (12)

  1. ct:
    Feb 18, 2012 at 09:12 PM

    Rendering performance for WPF is a big concern for me. Thanks for posting this. I hope they seriously look at optimizing things and have more options to not tessellate, etc.

  2. seanh:
    Mar 08, 2012 at 12:21 PM

    Thanks Michael,
    Try changing the pixel format by doing a search and replace from PixelFormats.Bgr32 to PixelFormats.Bgra32 to include an alpha channel.
    By the way, an even simpler solution is to put the grid on top. I found when the lines were dense the grid was hidden, and not so useful.

  3. Michael:
    Mar 08, 2012 at 07:48 AM

    Nice post, but i have a concern if i use a image how can i get the background as transparent as i may have chart axis grid behind the series.

  4. seanh:
    Apr 11, 2013 at 01:22 PM

    Its true, you have to deallocate memory before you initialize the next frame.
    GdiGraphics.Dispose();
    UnmapViewOfFile(MapViewPtr);
    CloseHandle(MapFileHandle);

    I posted a solution for that. Download the test code again. Have a look at the deallocate() method.

  5. Josh:
    Nov 26, 2012 at 11:55 PM

    One more thing - the link to Jeremiah Morril's article is broken. Google turned up this: http://jeremiahmorrill.wordpress.com/2011/02/14/a-critical-deep-dive-into-the-wpf-rendering-system/

    1. seanh:
      Feb 12, 2013 at 05:53 PM

      thanks! I've updated the link above.

  6. Josh:
    Nov 26, 2012 at 11:51 PM

    I wrote a line graph for kicks to track application performance at work. Everything was great until I hooked it up to live data. It never crossed my mind that WPF would struggle rendering lines. Thanks for taking the time to share this.

  7. Vinoth Kumar:
    Feb 28, 2013 at 02:22 AM

    Hi,
    This leads to high memory usage and goes 100% memory usage. The memory drops every seconds and goes to full memory usage.
    Can you fix this?

    Regards,
    Vinoth Kumar J

  8. Pasi Tuomainen:
    Feb 12, 2013 at 05:07 PM

    Nice article, which really highlights WPF's poor rendering power. I'd like to present my findings here too. The best approach to render lines is to render everything in Direct3D (yes, also 2D lines), and use D3DImage control of WPF to bring the scene as WPF content.

    It sounds quite complex, and actually is when configuring all all power-saving states, Remote desktop support, Windows Lock states and so on.

    We have done it in LightningChart Ultimate WPF and we are very satisfied with the results. It is about 10-20% slower than our chart Direct3D chart for WinForms, but beats other WPF charts very clearly.

  9. Zhu Kun:
    Jun 04, 2013 at 05:31 AM

    Hi,
    during the curve drawing the page fault is always increasing. Do you have any idea to avoid the page fault?
    Regards!
    Kun

    1. seanh:
      Jun 04, 2013 at 10:25 AM

      Do you mean you have a memory leak, or are you trying to improve performance by preventing hits to virtual memory? (I thought after Vinoth's comment I'd tested pretty carefully to rule out any memory leaks.)

  10. Prasanna:
    Sep 16, 2013 at 11:09 AM

    Hi sean,
    Thanks for your article.
    But can u clarify if interobleBitmap will work fine if i have user interactions like mouse hover, zoomin, scrolling ,etc. in my graph ? or should i use WriteableBitmap which may have same WPF issues ?
    Beacuse I found in Tamir's link this ->(if there is user interaction (mouse over, etc), you probably should check WritableBitmap. If not, use InteropBitmap).
    Your help is appreciated.
    Regards,
    Prasan.


Add a Comment





To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey

Allowed tags: <b><i><br>Add a new comment: