Thursday, August 28, 2008

Banshee "Now Playing" Animations

Aaron has been complaining lately that writing fancy graphics hacks for Banshee by hand using low-level Cairo calls is too "hard" and too tiresome to do.

Wimp.

For Hack Week, this week, I've been fixing up the GtkSharp Moonlight widget (aka GtkSilver) to work again so that The Great Bocky can write fancy graphics hacks using XAML instead.

One area that Aaron has mentioned that he'd love to do fancier animations in is the "Now Playing" view which currently is limited to a fade-in and a fade-out of the current track info (album cover w/ a reflection), track title, artist, and album name.

It took him far too long to write the code that displays that view (+ time spent optimizing it to get better than 11 FPS) and if he ever gets around to implementing anything fancier, he'll be popping those Excedrin pills like candy.

Lucky for him, I mocked up a slightly improved version of his "Now Playing" view in XAML (no code required except to update the text, album art, and to invoke the animations). All animations and graphics are represented in just 70 lines of XAML.

The great thing about designing this view in XAML is that it is easy to change the layout or adjust the animations without having to write any new code. Users can customize their "Now Playing" screens in any way they want to, all they have to do is keep the names of the controls and animations consistent with what the application expects. It's a lot like Glade XML files, only it allows the user to do fancy things like animations as well.

The demo I hacked up uses the following XAML:

<Canvas xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Background="Black" Width="700" Height="550">
  <Canvas x:Name="Display">
    <TextBlock Canvas.Left="10" Canvas.Top="200" x:Name="TrackInfo" Width="280"
               FontFamily="Sans" FontSize="14" TextAlignment="Right" Foreground="White">
      <Run x:Name="Title" FontSize="24">Wonderboy</Run><LineBreak/>
      <Run x:Name="By" FontSize="12" Foreground="Gray">by</Run>
      <Run x:Name="Artist">Tenacious D</Run><LineBreak/>
      <Run x:Name="From" FontSize="12" Foreground="Gray">from</Run>
      <Run x:Name="Album">Tenacious D</Run>
    </TextBlock>
    <Rectangle x:Name="AlbumArt" Canvas.Left="300" Canvas.Top="50" Width="300" Height="300">
      <Rectangle.Fill>
        <ImageBrush x:Name="AlbumArtBrush" Stretch="Fill" ImageSource="tenaciousd.jpg"/>
      </Rectangle.Fill>
    </Rectangle>
    <Rectangle x:Name="AlbumArtReflection" Canvas.Left="300" Canvas.Top="50" Width="300" Height="300"
               RenderTransformOrigin="0,1">
      <Rectangle.Fill>
        <ImageBrush x:Name="AlbumArtReflectionBrush" Stretch="Fill" ImageSource="tenaciousd.jpg"/>
      </Rectangle.Fill>
      <Rectangle.OpacityMask>
        <LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
          <LinearGradientBrush.GradientStops>
            <GradientStop Color="#8f8f8f8f" Offset="0"/>
            <GradientStop Color="#00000000" Offset="0.75"/>
            <GradientStop Color="#00000000" Offset="1"/>
          </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
      </Rectangle.OpacityMask>
      <Rectangle.RenderTransform>
        <TransformGroup>
          <ScaleTransform ScaleX="1" ScaleY="-0.35"/>
          <SkewTransform x:Name="SkewTransform" AngleX="45" AngleY="0"/>
        </TransformGroup>
      </Rectangle.RenderTransform>
    </Rectangle>
    <Canvas.Resources>
      <Storyboard x:Name="ReflectionAnimation">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="AlbumArtReflection"
         Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[1].(SkewTransform.AngleX)">
          <SplineDoubleKeyFrame KeyTime="00:00:00" Value="-45" KeySpline="0.25,0.00 0.75,0.75"/>
          <SplineDoubleKeyFrame KeyTime="00:00:02" Value="45" KeySpline="0.25,0.75 0.75,1.0"/>
        </DoubleAnimationUsingKeyFrames>
      </Storyboard>
      <Storyboard x:Name="FadeIn">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Display"
         Storyboard.TargetProperty="Opacity">
          <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
          <SplineDoubleKeyFrame KeyTime="00:00:01" Value="1"/>
        </DoubleAnimationUsingKeyFrames>
      </Storyboard>
      <Storyboard x:Name="FadeOut">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Display"
         Storyboard.TargetProperty="Opacity">
          <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
          <SplineDoubleKeyFrame KeyTime="00:00:01" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
      </Storyboard>
    </Canvas.Resources>
  </Canvas>
</Canvas>

To see this in action, you'll need to grab Moonlight out of SVN (I ended up using some Silverlight 2.0 TextBlock features like TextAlignment="Right" so that no code was needed to align the text like what Aaron has to calculate manually in the current code). Once you have that installed, you should be able to view my Banshee "Now Playing" mockup in the iframe below. Mousing over the iframe should cause the fade-in effect to start and mousing out of the iframe area should cause the fade-out to take effect.

Oh, XAML, what is the secret of your power? Won't you take me far away from the mucky-muck man?

Update: It has been pointed out that the above iframe could have been done in pure XAML using EventTriggers for MouseEnter/Leave events to trigger the animations, however I designed the XAML to be used by Banshee where Aaron will likely want to be able to control when the animations start/stop programatically and not based on mouse hover events ;-)

Update: The XAML should now work with Silverlight (apparently it didn't like my LineStackingStrategy or LineHeight property on the TextBlock).

Saturday, August 16, 2008

GMime Devel List

The GNOME admins have just approved and created a mailing list for discussing GMime development (meant both for contributors to the GMime library as well as developers using GMime).

You can subscribe to the mailing-list at http://mail.gnome.org/mailman/listinfo/gmime-devel-list.

Friday, August 15, 2008

Thursday, August 14, 2008

Moonlight Performance Enhancements

Spent today working with Sebastien "Master Profiler" Pouliot on finding and resolving one of our longest outstanding performance bottlenecks in Moonlight for which the GlyphMap Utility is a perfect test case.

Months ago, I had rewritten the font caching a bit so that we prevented (in most cases) the loading of the same font file that were shared between multiple textual XAML elements (Glyphs and TextBlock). At the time, font loading was at the top of the performance profile. While this fix did help a little as far as rendering speed went, it was barely noticeable visually. It wasn't a waste, however, because it greatly reduced memory overhead and later allowed for some better font scaling optimization tricks that I implemented.

Next up, I rewrote the Glyphs XAML element's ::ComputeBounds() routine such that not only did it calculate the rendering extents of the Glyphs element, but also cached the glyph string layout in a Cairo path such that later calls to ::Render() could simply blit the path rather than having to do its own layout.

Still, visual rendering performance seemed barely affected.

Then, today, Sebastien decided to take a look into the problem again and had discovered that sorting UIElements based on ZIndex was toward the top of the list as far as time-eaters went. The fix for this was to delay sorting the newly added UIElements until the engine went to render the next frame.

This bumped the sorting code right out of the time-eaters list but still no visual improvement :(

Sebastien reported back to me that we seemed to be idling between Glyphs draws which immediately made me recall that our Downloaders asyncronously download the referenced font files that each Glyphs element references and that at each render tick, we only popped a single request from our async queue.

I immediately went to work and fixed the async queue logic to pop as many frames as we could in 1/30th of a second (this value may need to be tweaked a slight bit, but it should typically be acceptable).

The GlyphMap table now renders instantly.

Wednesday, August 13, 2008

Moonlighting the Olympics

There's been a lot of interest in Moonlight lately, largely by people who hope to use it to watch the Olympics at http://nbcolympics.com, unfortunately Moonlight isn't quite ready to view this content :-(

The good news is that we are furiously hacking away on Moonlight 2.0 in the hopes of making it usable as quickly as we can.

The first roadblock is that NBC Olympics site is using an old Silverlight.js initialization script and currently requires one of the following browser/OS combinations to work:

  • Internet Explorer 6, 7 for Windows (Vista, XP SP2 or greater and 2003)
  • Firefox 1.5, 2, 3 for Windows (Vista, XP SP2 or greater and 2003)
  • Firefox 1.5, 2, 3 for Mac (OS 10.4.8 or greater, Intel only)
  • Safari 2 & 3 for Mac (OS 10.4.8 or greater, Intel only)

To get past this roadblock for use with Moonlight, you first need to download and install the User Agent Switcher Add-On. Once you install that and restart your Firefox browser, you'll need to go to Tools/User Agent Switcher/Options/Options. This will bring up a configuration dialog allowing you to add custom User Agent strings. You'll want to click on the Add... button and enter the following information:

Description: Firefox 3.0 (Mac)
User Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9) Gecko/2008061004 Firefox/3.0
App Name: Firefox
App Version: 3.0 [en] (Intel Mac OS X 10.5; U)
Platform: Macintosh

I think the only required field is the User Agent, the other fields can probably be set to whatever you want (thanks to Larry Ewing for the User-Agent string and for explaining to me how to do this).

Now that you have a Firefox 3.0/Mac User-Agent string, you'll need to select the "Firefox 3.0 (Mac)" radio button in the Tools/User Agent Switcher Firefox menu. Once you've done that, you should be able to navigate to the NBC Olympics video pages (although you still won't be able to view the video content quite yet... we're still working on writing the code to make that work).

Friday, August 8, 2008

Time-off This Past Week

Took this week off from work to go visit family and did a bit of cycling in my quest to "get back into shape" (or some semblance of shape, anyway).

Started off doing an 8.5-mile hill climb to the top of the eastern hill at Blue Hills Reservation in Milton, Mass which is about a 6% grade or so. Nothing awe-inspiring, but a bit of a workout for me. On Monday I continued with a 10-mile ride accompanied by relatives up in Rochester, NH - no major hills this time but quite a bit of ups and downs, so it was still a workout since I pushed fairly hard. Tuesday and Wednesday I did some 21-mile rides pushing hard and by the end of Wednesday's ride (in the pouring rain, btw) my legs could push no farther so I decided to take Thursday off. Traveled back home and cleaned as much of the sand/grit out of my bike as I could, de-greased and re-lubed my chain and derailleurs (looks like I picked up a little rust on my chain, but nothing major). Then today I went out to take on Blue Hills again, this time managing to push farther, so ended up climbing the eastern hill twice, once from each side. Coming from the west, the incline averages a good 7 or 8% iirc, so not too shabby. That puts me at about 70 miles so far this week.

Code Snippet Licensing

All code posted to this blog is licensed under the MIT/X11 license unless otherwise stated in the post itself.