15

I'm working on a game-like app which has up to a thousand shapes (ellipses and lines) that constantly change at 60fps. Having read an excellent article on rendering many moving shapes, I implemented this using a custom Canvas descendant that overrides OnRender to do the drawing via a DrawingContext. The performance is quite reasonable, although the CPU usage stays high.

However, the article suggests that the most efficient approach for constantly moving shapes is to use lots of DrawingVisual instances instead of OnRender. Unfortunately though it doesn't explain why that should be faster for this scenario.

Changing the implementation in this way is not a small effort, so I'd like to understand the reasons and whether they are applicable to me before deciding to make the switch. Why could the DrawingVisual approach result in lower CPU usage than the OnRender approach in this scenario?

Roman Starkov
  • 59,298
  • 38
  • 251
  • 324
  • Romkyns, you could create some simplified version with DrawingVisual and Canvas.OnRender() to match performance, before you delve into big changes. As for answer - I'm totally concur with Charlie. – Anvaka Feb 23 '10 at 16:32

4 Answers4

16

From Pro WPF in C# 2008:

The problem posed by these applications isn't the complexity of the art, but the sheer number of individual graphic elements. Even if you replace your Path elements with lighter weight Geometry objects, the overhead will still hamper the application's performance. The WPF solution for this sort of situation is to use the lower-level visual layer model. The basic idea is that you define each graphical element as a Visual object, which is an extremely lightweight ingredient that has less overhead than a Geometry object or a Path object.

What it boils down to is that every single one of those ellipses and lines you're creating is a separate FrameworkElement; that means it supports not only hit testing, but also layout, input, focus, events, styles, data-binding, resources, and animation. That's a pretty heavy-weight object for what you're trying to do! The Visual object skips all of that and inherits directly from DependencyObject. It still provides support for hit-testing, coordinate transformation, and bounding-box calculations, but none of the other stuff that the shapes support. It's far more lightweight and would probably improve your performance immensely.

EDIT:

Ok, I misread your question the first time around.

In the case that you are using OnRender, it really depends how you are creating the visuals and displaying them. If you are using a DrawingContext and adding all of the visuals to a single element, this is no different than using the DrawingVisual approach. If you were creating a separate element for each Visual created, then this would be a problem. It seems to me that you are doing things the right way.

Charlie
  • 15,069
  • 3
  • 64
  • 70
  • 1
    Wait, wait, I'm not creating any FrameworkElements! :) I'm using `OnRender` + `DrawingContext`, so unless DrawingContext creates a bunch of FrameworkElements behind the scenes (which I doubt) this isn't the case. – Roman Starkov Feb 23 '10 at 17:55
  • 2
    The edit is also wrong, when using DrawingContext rendering you do not create any Visual, you simply draw shapes without keeping any reference. – user275587 Jun 13 '10 at 10:17
11

Everyone in the answers got it wrong. The question is whether rendering shapes directly in the drawing context is faster than creating DrawingVisual. The answer is obviously 'yes'. Functions such as DrawLine, DrawEllipse, DrawRectangle etc. do not create any UI Element. DrawingVisual is much slower because it does create a UI Element, although a lightweight one. The confusion in the answers is because people simply copy/paste the DrawingVisual performs better than distinct UIElement shapes statement from MSDN.

user275587
  • 690
  • 8
  • 21
  • 5
    I'm not entirely sure the answer is this obvious. In cases where most shapes don't move, isn't the drawing context *slower*? It redraws everything, whereas the DrawingVisual approach allows the framework to cache and reuse the stuff drawn earlier. Or is this not so? – Roman Starkov Jun 13 '10 at 10:55
  • 4
    The DrawingContext doesn't redraw everything because WPF has a retained drawing model. DrawingVisual includes logic that control whether or not it should redraw the Visual. You can implement the same logic in OnRender() method and draw only when it is necessary. WPF won't invalidate your drawing at 60 fps, OnRender() is called only when you call InvalidateVisual() or when WPF needs to invalidate your drawing. – user275587 Jun 13 '10 at 21:07
9

I thought Petzold explains in this paragraph;

The ScatterPlotVisual class works by creating a DrawingVisual object for each DataPoint. When the properties of a DataPoint object change, the class only needs to alter the DrawingVisual associated with that DataPoint.

Which builds on an earlier explanation;

Whenever the ItemsSource property changes, or the collection changes, or a property of the DataPoint objects in the collection changes, ScatterPlotRender calls InvalidateVisual. This generates a call to OnRender, which draws the entire scatter plot.

Is this what your asking about?

By the way, this is a fairly recent high-performance WPF tutorial, many tens of thousands of points in that plot, it is 3D rendered and animated also (even uses mouse input to drive some of the transforms).

RandomNickName42
  • 5,923
  • 1
  • 36
  • 35
  • 2
    Yes, it seems I was confused by not realising that the `DrawingVisual` approach is only said to be the "best" method for this particular scenario - where many points change, but not the whole lot. When everything moves (which is the case for me) `OnRender` sounds like the best approach. Nice link, too - thanks. – Roman Starkov Feb 26 '10 at 15:19
  • There is another approach which I have found it to be the best performance for mutating an app with "lots of constantly changing shapes," which was the OP's question. Your app can manipulate a tree of DrawingGroups. You only have to call RenderOpen/DrawDrawing once, upon app startup, on the root of this tree. From then on, WPF notices all edits to the tree and updates automatically, including transforms and insert/remove of deeply nested DrawingGroup children. You'll need a data structure that tracks/maintains the state of the tree. See my comment at http://stackoverflow.com/a/716469/147511. – Glenn Slayden Nov 27 '12 at 00:05
4

In my tests however (panning animations), I notice no difference in speed. I would say that using a host element for many drawing visuals is a bit faster. This approach where you build your visual tree with many visuals gives you more control. Moreover, when you want to do a complex hit testing, the filtering process is faster because you can skip entire "branches" of visuals

Theodore Zographos
  • 2,215
  • 1
  • 24
  • 23