One Bad Day with WPF

2011-07-21

Be warned: this is a rant, and I may not have solutions for the problems outlined.

Right now I am trying to use WPF 4 to implement a concept mapping editor. I was seriously prepared to handle some unexpected WPF issues by my own. But some details just pissed me off:

Font Rendering

The first obvious problem was the font quality (again). By default, WPF uses Cleartype to render fonts; obviously a custom implementation, because WPF just ignored that I switched Cleartype off, system wide.

WPFDefaultFontRendering WPFDefaultFontRendering2

(screenshots of a WPF performance tool, the one below is zoomed 2 times)

This is – by far – the worst font quality rendering I’ve ever seen on any platform I can remember. (note that the top screenshot above may actually look even worse if you don’t have the same pixel format like my monitor, and the maximized one is just for reference to see how Cleartype works).

Cleartype can be switched off by changing a simple property (a recent feature introduced in WPF 4), but the anti-aliased results without font hinting and Cleartype were too blurry and also a lot worse than any other anti-aliased font rendering I’ve seen before. The only acceptable setting was to draw the fonts with font aligning (hinting) and gray scale anti-aliasing. The result looked slightly odd, but the readability itself was a lot better now.

Drop Shadow Performance

BrainsharperNodeFor my concept mapping editor I render drop shadows at each node to increase the plasticity a bit. Everything went fine until I implemented a gesture that allowed to move the entire canvas around. Performance was dropping below 20 frames per seconds for a small number of nodes (about 40), which was very unexpected given the hardware GPU acceleration available. I first verified that everything was indeed rendered in hardware and found out that a lot of temporary render targets were created.

Drawing a WPF visual with a drop shadow works by first drawing the visual to an intermediate render target and then drawing the contents of render target together and the shadow on the screen. But for whatever reason, these intermediate render targets seem to be very, very slow. Switching the shadows off got me back to 80 frames per seconds.

Now there are two primary ways to optimize drop shadow rendering in WPF.

  1. Render one drop shadow for the canvas in which the nodes are contained. In XAML this is just a copy and paste, but the result was even slower. It might help a little when the visible size of the window are is not too large, but on a 1920 window, it was slower.
  2. Use bitmap caching, also a new feature of WPF 4. Nice, I thought, but this just turned out into another nightmare. While the implementation of the bitmap caching seems to work quite well - it’s effectively one line of XAML and handles all invalidation and refreshing logic –, there was no way to pixel-perfect render the content anymore. Though there is a property “SnapsToDevicePixels”, it just did not work as expected and a full screen bitmap cache always made the content look blurry and even worse: subpixel alignment was inconsistent at different location on the screen. It looked like an off-by one error in computing the texture coordinates. Not only was the text-quality worse (btw: the font rendering options needed to be reset below the markup element that uses bitmap caching… which was unexpected), no, everything looked blurry, the borders, overlays, etc. …. My conclusion: fast, but unusable.

And now the funny thing which really reflects back on the bad implementation quality of WPF. Switching from hardware rendering to software rendering actually improved the framerate, not enough, but considerable. Can’t be, I thought. What a mess.

My solution here is just to turn off all drop shadows while the user is moving the canvas. Simple but effective.

Update: Using intermediate render targets seems to be a lot faster in software. You can find out about the number of render targets your app uses with Perforator’s HW IRTs Per Frame History Graph.

Tooling Bugs

To actually find out why the performance problem existed, I used the WPF Performance Suite by Microsoft, but initially were blocked by two strange bugs (here and here).

Now, after all this bad experience, and with the plan to port all the UI code to Mac OS X, I am reconsidering to use Silverlight or Moonlight. If this does not turn out well, I need to accept that there is currently no decent user interface library available for Windows, except.. yes, may be Qt again, but this will be another story.