In Silverlight, when a pixel shader is applied to an element, that element is rendered into a bitmap and then the pixel shader is applied to create the final result on screen. The DropShadowEffect class is a sibling of the ShaderEffect class (which powers pixel shaders) and effectively behaves the same way. With small UI elements, this implementation detail is a non-issue. With large, complex UI elements (those that tend to be large in size to accommodate all their child controls), this implementation detail is a dramatic performance problem.
To demonstrate, we’ll start off with a mock UI that accepts user input. To start with, let’s define the following UI:
And when rendered it looks like this:
When run, nothing unusual occurs. What if we make it more complex by adding 15 input controls (for the purpose of this demonstration, it will be all text boxes)? First we expand the drop shadowed Border and add a ScrollViewer and StackPanel to it:
And next we write some code to create multiple TextBoxes to simulate this being a big complex input form (big as in must be large in physical dimensions to accommodate everything):
And when run it looks like this:
If I’m lucky, I notice something unusual here. Being a fast typer (a bit over 100wpm) and being someone obsessed with the tiny little Task Manager performance graph in my notification area, I see the CPU spike a lot higher than I expect it should while I’m typing. I mean, I’m fast but I’m not so fast my CPU shouldn’t be able to keep up! I decide to hold a key down and see what the CPU does:
Wow! 50%! That’s one of the two cores in my computer fully engaged just with me holding the letter ‘k’. Because I already know how this story ends, I decide to try this with the drop shadow turned off. What would happen then?
It’s down to 28%, which still seems high right? Well one thing you can’t see from a static image on a website is that while I was holding the key down with the drop shadow enabled, the text box was filling up significantly slower than when the drop shadow was disabled. So with the drop shadow disabled it’s still using 28% of my CPU, the letters were flying across the screen, easily twice as fast as with the drop shadow enabled.
Why is this happening? As I explained above, a drop shadow is effectively a pixel shader and a pixel shader alters rendered images. What’s happening here is the Border is being rendered to a bitmap, the drop shadow is being applied, and the result of that is being rendered to the screen. In the examples above this happens with every new letter being entered into the TextBox. Every time a new ‘k’ appears on the screen, the Silverlight rendering pipeline has to render a bitmap of the Border, apply a pixel shader and render that to the screen. Normally it just renders directly to the screen, so we’re greatly increasing the work that has to be done.
Okay, all that said, this is a highly contrived example. Your user isn’t going to hold down keys on a TextBox and if they do, they deserve what they get right? Well then, imagine this: you finish your beautiful application and your boss demands a wiz-bang animation to be active on the screen. Perhaps it’s only active during loading or submitting or maybe it’s active all the time. Regardless you do what you’re asked and come up with this:
A very simple animation that fades a rectangle from 0 to 1 opacity and back, infinitely. Seems simple enough right? What kind of performance can we see from this?
With zero user interaction and just the animation running, my Silverlight application is now using 65%, or more than one core in my CPU. With the drop shadow disabled, surely there must be some similar performance hit:
Without a drop shadow it only uses 5% of the CPU. Mind you that it was mostly hovering around 2% but I waited for a higher number to take the screenshot.
What if there are no text boxes? Will it still perform poorly? I’ll leave that to you to alter the code, but the answer is “it depends”. If the Border is large enough (for instance your browser window is maximized) then your CPU usage will be high. If the Border is small (say if your browser window is only 200×200) then the CPU usage will be significantly lower:
What can be done about this? All you need to do is restructure your XAML and be sure the elements with the drop shadow do not have any children and thus never have to redraw (except on a resize event). In my case it’s easy, wrap the Border in a Grid and create a 2nd border that sits behind it that has the drop shadow:
It looks exactly the same, but its performance characteristics are dramatically different:
With a little restructuring you can completely eliminate performance issues like we’ve seen above. Want to know the worst part of all this? This performance problem is spelled out in the MSDN documentation but I still didn’t understand until I ran into it myself.
Pixel shader effects in Silverlight are rendered in software. Any object that applies an effect will also be rendered in software. Performance degrades the most when effects are applied to large visuals or when properties of an effect are animated. Therefore, you should be careful when you use effects and thoroughly test them to make sure that your users are getting the experience you expect.