| WPF Drawing PerformanceStarting with version 3.0, the .NET Framework provides two incompatible and unrelated graphics APIs, both aimed at general GUI application development:
WPF was developed for Windows Vista whose new Desktop Window Manager (DWM) is likewise based on DirectX rather than GDI. The DWM is enabled by switching to the Aero desktop theme (the default on most editions), and disabled by switching to the Basic theme which emulates Windows XP. Rumor has it that Vista was originally intended to use WPF for its entire GUI, but the performance of the new API was not up to the task. Certainly, developers outside Microsoft have frequently criticized WPF for its sluggish performance, especially compared to GDI/GDI+ on Windows XP. On this page I attempt to measure the performance of simple drawing operations in both WPF and Windows Forms (i.e. GDI+) under a variety of conditions. For comparison, I implemented the same operations in Java’s AWT (Abstract Window Toolkit). Hopefully the results and conclusions will prove useful to other developers. The test application and its source code are available for download, so you can run your own tests and modify the test cases as desired.
Before moving on, I’d like to recommend Jeremiah Morrill’s analysis of the WPF rendering system. This post is largely unrelated to the following discussion, but it’s a fascinating examination of WPF performance at the lowest level. Measuring WPF PerformanceAttempting to measure the time WPF takes to fully render a window is surprisingly difficult. This is because two different threads collaborate in this task. To explain what that means, here’s a quick overview of how WPF shows things on the screen.
An immediate mode API can simulate a primitive sort of retained mode by drawing to a memory buffer which is later copied to the screen. Such buffering is frequently used for better performance or smoother animations. Our drawing test application provides test windows for both direct and buffered GDI+. Since WPF uses retained mode, all new display content passes through two stages before appearing on the screen: first internal preparation, then the actual rendering. WPF implements these stages as follows:
Measuring Windows Forms is easy: we start a timer before showing a test window, and stop it at the end of the window’s Measuring WPF is more difficult. Once again, we start a timer before showing a test window. But now we need to measure both stages of WPF’s retained mode to find the total time until the window has actually been rendered to screen. Measuring Preparation
This stage is complete when the UI thread’s message loop has processed all pending messages, which (we assume and hope) all originated from our drawing operations. WPF exposes a Measuring Rendering
We cannot directly access the rendering thread but WPF does offer one indirect point of access, namely through the
However, the
This trick is not foolproof. Sometimes the Drawing Test ApplicationAll results shown below were obtained with a small test application. The download package DrawingTest.zip (31.8 KB) comprises the precompiled application for the .NET Framework 4.0 and the complete source code for Visual Studio 2010, as well as a version for Java’s AWT library. This is a standard ZIP archive containing long file names. The test application draws 10,000 triangles to a window’s client area, sized 400x400 screen pixels (for GDI+ and AWT) or device-independent units (for WPF). Each triangle is rotated 1° clockwise compared to the previous one. Triangles are drawn either as outlines using pens (“Pens Only”), filled shapes using brushes (“Brushes Only”), or both with different colors (“Pens & Brushes”). All colors are solid, with no patterns, shading, or animation effects of any kind. The test application for Java’s AWT library is located in a separate folder and run from the command line – please see the enclosed ReadMe file for instructions. The test application for GDI+ and WPF provides a GUI with five buttons on the left start each test window, as follows:
To minimize interference with the test timer, I recommend that you move the mouse cursor away from the application and test windows, and start all tests with keyboard shortcuts rather than mouse clicks. If you use high DPI mode, you’ll notice that the Windows Forms and AWT windows appear smaller than the WPF windows. This is correct and due to the fact that WPF automatically scales all coordinates by the current DPI setting, whereas Windows Forms and AWT do not. Anti-AliasingAnti-aliasing, i.e. smoothing the edges of diagonal lines, turns out to have a huge performance impact on all measured drawing APIs. Anti-aliasing is disabled by default for GDI+ and AWT, and enabled by default for WPF. Use “Anti-Aliasing On/Off” to change this setting which is implemented as follows:
Note that direct (unbuffered) GDI+ on Windows XP does not support anti-aliasing at all; the corresponding WPF FreezingThe figures and geometries created by the WPF Path & Stream tests are always frozen. Testing showed that leaving them unfrozen makes no discernable difference. However, freezing the pens and brushes used by the three WPF windows makes a very big difference, so this feature is controlled by one last option. All WPF pens and brushes are initially unfrozen until you click the “Freeze WPF” button, at which point they remain frozen until the application is closed. LimitationsThe application tests exactly one thing: drawing the outlines and/or interiors of many triangles in solid colors. It does not test anything else, including the following:
If you are interested in the performance of some specific drawing operation that is not covered by the application, you should modify its source code to run your own customized tests on your target system. This is ultimately the only way to find reliable answers. Should you discover any results that contradict my own or are otherwise surprising, please let me know. Sample Test ResultsThe following tables show sample test results on my system, comprising an Intel DX58SO motherboard with an Intel Core i7 920 CPU (2.67 GHz), 6 GB RAM (DDR3-1333), and an AMD Radeon HD 6970 (2 GB) graphics card, with current AMD and DirectX drivers. The tests were not conducted with any kind of scientific rigor; I simply ran each test several times and picked a nice round median value. In each table, the first three rows were measured with anti-aliasing disabled and the last three rows (“AA +”) with anti-aliasing enabled. The first table shows the test results, in milliseconds, for Windows XP SP3 (32 bit, 96 dpi, DirectX 9.0c) running in Virtual PC on Windows 7 SP1 (64 bit).
The second table shows the test results, in milliseconds, for Windows 7 SP1 (64 bit, 120 dpi) with the Desktop Window Manager disabled (Windows 7 Basic scheme).
The third table shows the test results, in milliseconds, for Windows 7 SP1 (64 bit, 120 dpi) with the Desktop Window Manager enabled (Windows 7 Aero scheme).
This table also shows test results for Java, using the standard AWT library from the Sun/Oracle JDK 1.6u26. As you can see, AWT’s performance is roughly comparable to buffered GDI+. Test ConclusionsI make two assumptions in my following attempt to interpret these results:
Once again, I encourage you to download the test application and try it on your own system(s), modifying the test code to your own requirements if necessary. Still, based on my test results as they stand, I’m inclined to draw the following conclusions: Direct GDI+ is extremely system-dependent. The architectural changes between XP and Vista slowed direct GDI+ operations by two orders of magnitude, whereas WPF shows a significant but smaller variance only in fill rates (see below). On Windows XP, unbuffered GDI+ is 3–67 times faster than WPF; on Windows 7 Basic, between 3 times faster and 37 times slower; and on Windows 7 Aero, never faster and up to 146 times slower! Conclusion: Unbuffered GDI+ is a great choice for custom drawing on XP (if you don’t need anti-aliasing), but it’s completely useless on newer systems where WPF is usually faster. Buffered GDI+, on the other hand, delivers consistent and competitive performance across all systems – and also supports anti-aliasing on Windows XP. Anti-aliasing is (almost) always extremely slow. Surprisingly, the fact that WPF enables anti-aliasing by default is the single biggest factor in its apparent slowness compared to other APIs. Turning off AA improves performance in most tests by an order of magnitude, and using identical AA settings dramatically shrinks the performance difference between all three APIs. Conclusion: Good drawing performance in any of the tested APIs requires disabling anti-aliasing. Once you do that, the choice of API is nearly irrelevant. If you require good performance with AA enabled, however, you’ll need to write raw DirectX or OpenGL code that utilizes your video card’s hardware AA (but see below on WPF brushes).
Freezing WPF pens & brushes is always a good idea. The basic
Conclusion: Always immediately call
More complex WPF APIs are not necessarily faster. The complex “low-level” APIs
Conclusion: Don’t expect miracles from the complex WPF brushes can be much faster than WPF pens. In most tests, filling triangles with brushes is about as fast as drawing their outlines. This changes dramatically for WPF on Windows 7: using the same drawing technique, brushes are always 2–26 times faster than pens. Even more intriguing, the usual anti-aliasing penalty vanishes completely for WPF brushes – but not for pens! I believe that we observe here the fabled “DirectX acceleration” of WPF, so lamentably unnoticeable in most operations, and that the slower fill rates on XP are due to the graphics card emulation provided by Virtual PC. Conclusion: When running on modern systems with fast graphics cards, try using WPF brushes instead of WPF pens where possible. This may even allow you to keep anti-aliasing enabled. Unfortunately, this trick is system-dependent and probably won’t work on cheap laptops or old office desktops. EpilogueWhy does WPF have a reputation for being slow? As far as drawing geometric objects is concerned, the apparent reason is that its designers chose two unusual default values: all objects are drawn with anti-aliasing, and most object data is retrieved from expensive mutable dependency properties. There are good reasons for both choices. Enabling AA by default is necessary since WPF supports automatic display scaling, but its enormous performance impact is virtually unknown and should have received more publicity. WPF objects must remain mutable until all properties have been initialized, but most objects are never animated or otherwise changed afterward. Perhaps pens & brushes created by parameterized constructors should be frozen by default – or perhaps WPF would have been better off without the elegant but slow dependency property mechanism.
Fortunately, once these two big performance stumbling blocks are known they are easy to work around. Calling
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||