Note: these are early notes based on some initial experiments with the Silverlight 5 beta, apply a pinch of salt to what you read.
Here’s a simple interface containing some rectangles, a couple of CheckBoxes and a Storyboard set up to rotate the rectangles;
<UserControl x:Class="SilverlightApplication3.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="LayoutRoot" Background="Silver"> <Grid.Resources> <Storyboard x:Name="sb1" x:Key="sb1" RepeatBehavior="Forever"> <DoubleAnimation Storyboard.TargetName="rotate" Storyboard.TargetProperty="Angle" By="360" Duration="00:00:00.25" /> <DoubleAnimation Storyboard.TargetName="rotate2" Storyboard.TargetProperty="Angle" By="360" Duration="00:00:00.25" /> <DoubleAnimation Storyboard.TargetName="rotate3" Storyboard.TargetProperty="Angle" By="360" Duration="00:00:00.25" /> </Storyboard> </Grid.Resources> <Grid.ColumnDefinitions> <ColumnDefinition Width="4*"/> <ColumnDefinition Width="2*"/> <ColumnDefinition Width="4*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Rectangle CacheMode="BitmapCache" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Black" Fill="Orange" RenderTransformOrigin="0.5,0.5"> <Rectangle.RenderTransform> <RotateTransform x:Name="rotate" /> </Rectangle.RenderTransform> </Rectangle> <Rectangle CacheMode="BitmapCache" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Black" Fill="Yellow" RenderTransformOrigin="0.5,0.5"> <Rectangle.RenderTransform> <RotateTransform x:Name="rotate2" Angle="120"/> </Rectangle.RenderTransform> </Rectangle> <Rectangle CacheMode="BitmapCache" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Black" Fill="red" RenderTransformOrigin="0.5,0.5"> <Rectangle.RenderTransform> <RotateTransform x:Name="rotate3" Angle="240"/> </Rectangle.RenderTransform> </Rectangle> <StackPanel Grid.ColumnSpan="3" Margin="6" Grid.Row="1" HorizontalAlignment="Center"> <CheckBox Checked="CheckboxRunAnimations" Unchecked="CheckboxRunAnimations" Content="Run Animation" /> <CheckBox Checked="CheckBoxSlowThingsDown" Unchecked="CheckBoxSlowThingsDown" Content="Slow Things Down"/> </StackPanel> </Grid> </UserControl>
and there’s code to start/stop the animation when the CheckBox is ticked and there’s also code which injects this delaying routine when the other CheckBox is ticked;
void DelayRoutine() { Thread.Sleep(250); if (this.slowThingsDown) { Dispatcher.BeginInvoke(DelayRoutine); } }
and so this routine deliberately sits on the Dispatcher thread and causes it to delay and that damages the animation performance that I’m getting, causing it to stutter. You can see the example running here in the page and try it for yourself;
In Silverlight 5, there are options around this. Silverlight 5 introduces 3D graphics and the 3D graphical work is done on a separate thread – the composition thread. You can find some of the details about this here on MSDN but I think the essence is that you have a Draw() method that’s called as frequently as you like on this composition thread.
In order to make use of 3D you have to switch on enableGPUAcceleration for the Silverlight plug-in and as part of switching that on, you get the injection of this composition thread.
Why is this relevant? Because in certain circumstances this composition thread can be used for regular Silverlight animation work (I don’t have a full list in my own head right now but this article on MSDN about hardware acceleration does help a little);
What does that mean? It means that if I revisit my previous sample and switch on enableGPUAcceleration and if I tell Silverlight that I want the rectangles that I’m animating to be cached on the GPU ( by using the BitmapCache option for CacheMode );
Then Silverlight moves these animations over to the GPU and you can see that our CheckBox which is hammering the Dispatcher thread with sleeps (not that those take any CPU cycles of course) is no longer having any effect on our animation as you can see in the modified sample below;
In some ways, this is work that is coming back into Silverlight 5 from the Windows Phone 7 where Silverlight had this capability and I can remember the early announcements around Windows Phone talking about how the graphics stack had been improved in this area.
Now, how can we be sure that this is happening on the composition thread? What if I change my UI to include a DrawingSurface for 3D work and simply use its Draw method to introduce some delays on that thread as in;
private void OnDraw(object sender, DrawEventArgs e) { if (this.slowThingsDown) { Thread.Sleep(250); } e.InvalidateSurface(); }
and with that code enabled you can see once again that we can make our animations stutter albeit for very different reasons;
because we’re now putting a Sleep onto the composition thread whereas previously we were putting a sleep onto the Dispatcher thread while the composition thread ran our animations.
I know I’ve already encountered scenarios ( even some that seemed like simple ones ) where my animations reverted back to running on the Dispatcher thread even though I didn’t want them to so it’d be good to dig into this a little more and figure out exactly what caused those scenarios to be taken off the GPU but, for now, it’s a good start to know that this work is present in Silverlight 5.
In the meantime, here’s some source-code to download which is similar to what I used for this post.