views:

397

answers:

2

Does anyone know of any reliable (and, hopefully, extensive) books/websites that discuss GDI+ performance (beyond the obvious)?

For example, I recently came across this excellent experiment. I also recently noticed that Graphics.FillPath() is way, way faster than Graphics.DrawPath(). I'd love to know what other vital bits of information I'm missing.

Goodwill, David

+5  A: 

Hmmm. There is no gain in knowing that FillPath is faster than DrawPath if you need to draw the path's outline!

The best way to optimise GDI+ is exactly the same as for any other code: Firstly, don't optimise it. Instead:

  • Start by writing it so it simply works, and then decide if it is actually too slow.
  • Then examine your "algorithm":
    • Simplify what you are drawing (reduce the number of things you are drawing) and it will go faster (and in most cases will look better for having reduced the clutter).
    • Are you drawing your entire display every time, or are you using the clip rectangle to avoid drawing parts of the image that don't need to be updated?
    • Examine how you draw things. Are you creating and destroying resources (e.g. brushes and pens) for every redraw? Cache them in a member variable. Are you over-drawing the same pixel multiple times? (e.g. drawing the background then drawing a bitmap on top then drawing a rectangle on top of that - perhaps you can avoid some of these redraws). Are you drawing a curve using 100 polygon segments when it looks good enough with only 10 segments? When the window scrolls, do you get the OS to move the existing image so you only need to redraw the newly exposed strip, or do you waste time redrawing the entire window?
    • Are you using transforms or are you doing lengthy positioning calculations in your code?
    • Check any loops and make sure you move as much code out of them as possible - precalculate values you use within the loop, etc. Make sure your loops iterate over your data in a cpu-cache-friendly direction/manner where possible.
    • Is there anything in your data that you are processing during the redraw? Perhaps some of this can be precalculated or organised in a more rendering-optimal form. e.g. Would a different type of list be faster to enumerate your data from? Are you processing 1000 data items to find the 10 you need to draw?
    • Can you achieve the same look with a different approach? e.g. You can draw a chess board by drawing 64 squares in alternating black and white. It might be faster to draw 32 black and then 32 white squares so you avoid state changes between the rects. But you can actually draw it using a white background clear, 4 black rectangles, and 4 XOR rectangles (8 rects instead of 64 => much faster algorithm).
  • Are there parts of the image that don't change often? Try caching them in an offscreen bitmap to minimise the amount you have to "rebuild" for each redraw. Remember that you can still render the offscreen bitmap(s) and then layer graphics primitives on top of them, so you may find a lot more "static" image area than you realise.
  • Are you rendering bitmap images? Try converting them to the screen's pixel format and cache them in that "native" form rather than making GDI+ convert them every time they are drawn.
  • Turn down the quality settings if you are happy with the trade-off of lower quality for faster rendering

Once you have done all these things you can start looking for books about optimising the rendering. If it is still too slow, of course. Run a profiler to find out which parts of the rendering are the slowest.

Where you think you might be able to make gains, try different ways of rendering things (e.g. it's likely that Graphics.Clear() will be much faster than filling the background with FillRectangle()), or different rendering orders (draw all the things of one colour first, in case state changes cost you time - batching operations is often very important with modern graphics cards. A single call that draws multiple polygons is usually faster than making multiple single-polygon calls, so can you accumulate all your polys into a deferred-rendering buffer and then commit them all at the end of your rendering pass?)

After that, you may have to look at using GDI or DirectX to get closer to the hardware.

Jason Williams
Hi Jason, thanks for the extensive answer. Let me answer the points one by one, perhaps it will clarify a bit where I'm stuck:
David Rutten
"Simplify what you are drawing". I am, mostly. My GUI is actually a ZUI, so I'm already simplifying stuff when zooming out. I'm not however making decisions about whether or not to draw stuff based on how long it took last frame to draw it.
David Rutten
"Are you drawing your entire display every time". No, I'm not drawing objects which are not inside the visible area.
David Rutten
"Are you creating and destroying resources". Yes, actually. Quite a lot. I'm only caching Font objects, but pens and brushes get recreated hundreds of times for every redraw. I'll see if I can get an improvement by caching the most used ones.
David Rutten
"Are you drawing a curve using 100 polygon segments when it looks good enough with only 10 segments?" No, I've added a lot of zoom-aware code to prevent drawing unnecessarily complicated shapes.
David Rutten
"Are you using transforms". Yes, I've got a Matrix object attached to my Graphics object which controls both zooming and panning within the graphics context.
David Rutten
"Are you rendering bitmap images?". Yes, loads. But I'm already caching them in premultiplied pixelformats. Little to be gained here I'm afraid.
David Rutten
"Are there parts of the image that don't change often?". Unfortunately not. The system I've come up with so far is pretty decent. It only gets slow when I'm drawing LOADS of objects. The buffering, background drawing, blitting etc. is all running in half no-time.
David Rutten
Sounds like you've already addressed many of the more obvious points. You say you are rendering lots of bitmaps, so look to see if any of them can be combined (drawing one large bitmap is generally faster than drawing two half-sized ones) - either combined in a pre-process, or simply cached so that they aren't redrawn from scratch every time.
Jason Williams
If you are drawing LOADS of objects, then you could consider limiting the amount that is drawn (e.g. at a certain zoom level drop to a simplified rendering scheme so you show less detail but retain acceptable performance). Another possibility is to use a background or deferred rendering approach (so you render a very simplified view while scrolling and zooming, then redraw at the end of the process at high quality, so you retain interactivity while it is needed, but when you stop to look at the display it shows full detail.
Jason Williams
Another similar method is to break up the display into "tiles", so you render the center (main focus of attention) areas quickly and then draw the outlying tiles over time to fill in the view. This again allow shigh interactivity but as soon as the user pauses for a second or two the display gets filled in at high quality.
Jason Williams
And as I said at the very bottom of my answer, if you really want/need performance, GDI+ is not the best approach - GDI or DirectX will give much higher performance. (You will probably get a several-hundred-percent speed increase from switching away from GDI+, whereas most optimisations will shave off a few percent at most)
Jason Williams
Jason, thanks, there's enough here to keep me busy for a bit. Switching away from GDI+ is currently not a viable option. The performance problems aren't severe enough to warrant that kind of investment. I like the idea of having a zoom+pan mode with low quality images, that probably goes a long way towards a more fluid 'feel'.
David Rutten
+3  A: 

This may not be the answer you are looking for, but consider not using GDI+ at all. I had to recently rewrite the rendering stack of a 2D-CAM application, and the most important requirement was to get good anti-aliased lines fast (the anti-aliasing was what prompted us to rewrite the existing GDI renderer).

Here are some results I got with a render of 200 items (each item is itself some lines and small filled-shape markers). These are frame rates (on Windows 7), so higher is better:

200 items: GDI=51, GDI+=20, D2D=59, WPF=33, GL=59.

(D2D is Direct2D, GL is OpenGL). Already you can see that GDI+ is trailing. WPF is perhaps handicapped in being a retained mode API, but then OpenGL is double-buffered as well and looks just as smooth.

At 1000 items, the difference is more marked:

GDI=23, GDI+=5, D2D=17, WPF=2, GL=40.

That's right, WPF has fallen to 2 FPS by now, and GDI+ is crawling along at 5 FPS. So consider D2D, or simply go back to OpenGL. That's what we are using now and 3 months into the rewrite, I think we made the right choice. The programming model itself is a lot cleaner than D2D seems to be.

Note that the WPF render we are using is highly optimized; no callbacks, no events, no binding. We just get a DrawingContext and draw everything on it each frame using the lowest level primitives so there is no event overhead.

If you are interested, let me know, and I can send you the test suites I used so you can fiddle around with that.

(One reason I might steer clear of GDI+ is that it is unlikely to ever be hardware accelerated).

Tarydon
Tarydon, this is no longer an option I'm afraid. I've been working on this GUI for over 2 years and a substantial amount of that time was spend writing GDI+ code. It's reasonably fast 95% of the time, though I've seen cases where the framerate drops below 10 fps. I've been profiling the code a lot lately and have found some interesting (i.e. unexpected) bottlenecks, but I was hoping this work had been done by someone else already. Still, +1 for trying to help me. Thanks.
David Rutten
I'm confused about Win7, i thought that GDI is 100% software on this platform. But when i see that by your numbers even D2D is slower then GDI i wonder what is the truth beyond MSDN's marking messages. I use GDI currently only for the image loading/saving code.
Lothar