Windows Vista世代以降のプレゼンテーション技術にはグラフィック処理を専門とするGPUが積極的に活用されるようになってきました。並列技術の盛り上がりと共に、GPUをより汎用的な演算に応用するGPGPUといった発想が登場し、一方で汎用プロセッサを生業としているIntelがDirectXやOpenGLをサポートする次世代プロセッサLarrabeeの開発を進めているように、GPUの活用は一般的な業務アプリケーション開発者にとっても無関係ではなくなりつつあります。

通常Silverlightプラグインによって描画されるグラフィックはCPUで処理されます。図形や画像の作成や組み合わせ、変換、描画の一連の作業に必要な計算はCPUによって行われます。しかし、Silverlight 3からは、一部の処理に限定してGPUによるハードウェア・アクセラレーションが有効になりました。ただし、自動的にGPUが活用されるわけのではないので明示的にハードウェア・アクセラレーションを有効にしなければなりません。

まず、SilverlightアプリケーションがGPUを利用する場合は、プラグインに対してEnableGPUAccelerationパラメータを設定しなければなりません。このパラメータはobject要素に指定するparam子要素で次のように記述します。

EnableGPUAccelerationパラメータ

<param name="EnableGPUAcceleration" value="true" />

このとき、value属性の値がtrueであればアクセラレーションが有効になり、falseであれば無効になります。この設定が有効になっていない場合、アプリケーション側の要素の設定とは無関係にGPUの利用が無効化されます。

次に、プログラムの中でGPUに描画させたい要素に対してCacheModeプロパティを設定します。このプロパティは、可能であれば描画された内容をキャッシュする方法を表します。

UIElementクラス CacheModeプロパティ

public CacheMode CacheMode { get; set; }

このプロパティの型であるCacheModeクラスは、対象の要素に対するキャッシュ動作を表す抽象クラスです。キャッシュを有効化するには、このクラスから派生しているBitmapCacheクラスをインスタンス化して設定します。

System.Windows.Media.BitmapCacheクラス

public sealed class BitmapCache : CacheMode
System.Object
  System.Windows.DependencyObject
    System.Windows.Media.CacheMode
      System.Windows.Media.BitmapCache

BitmapCacheクラスは、視覚的要素をビットマップとしてキャッシュすることを表します。キャッシュされたビットマップを描画する際の倍率をRenderAtScaleプロパティに設定することができますが、通常はデフォルトの1を設定すれば問題ありません。キャッシュを有効化したUIElementオブジェクトを伸縮して描画する場合、このプロパティに同じ倍率を設定します。

BitmapCacheクラス RenderAtScaleプロパティ

public double RenderAtScale { get; set; }

ただし、この設定もハードウェア・アクセラレーションが有効な処理を行う要素に対してピンポイントで設定しなければなりません。例えば、要素ツリーのルートや親パネルをむやみにキャッシュしても効果は得られません。半透明や変換処理を行う子要素に対して個別に設定してください。

どのような処理がハードウェア・アクセラレーションの対象になるかはMSDN Blog Silverlight SDKの記事「Silverlight Hardware Acceleration」内のHow Hardware Acceleration Worksという項目にある図で詳細が書かれています。中でもピクセルシェーダを用いるエフェクトはGPUの処理対象にならないという点に注意してください。本来、DirectXで利用するシェーダはGPUを対象としたプログラムなのでアクセラレーションの対象になると誤解されがちです。

一般的なSilverlightアプリケーションの処理でハードウェア・アクセラレーションの有効化を検討するべきなのはOpacityプロパティによる半透明処理とRenderTransformプロパティによる座標変換処理です。これらのプロパティをアニメーションさせる場面では、ハードウェア・アクセラレーションが有効か否かでパフォーマンスに大きな違いが表れるでしょう。

コード01

private List<UIElement> elements = new List<UIElement>();
private void canvas_Loaded(object sender, RoutedEventArgs e)
{
    Random random = new Random();

    for (int i = 0; i < 1000; i++)
    {
        BitmapCache cache = new BitmapCache();
        cache.RenderAtScale = 1;

        int left = random.Next(800);
        int top = random.Next(600);

        Color color = new Color();
        color.A = 0xFF;
        color.R = (byte)random.Next(255);
        color.G = (byte)random.Next(255);
        color.B = (byte)random.Next(255);

        Ellipse ellipse = new Ellipse();
        ellipse.Fill = new SolidColorBrush(color);
        ellipse.Width = 10 + random.Next(90);
        ellipse.Height = 10 + random.Next(90);
        ellipse.CacheMode = cache;
        Canvas.SetLeft(ellipse, left);
        Canvas.SetTop(ellipse, top);
        canvas.Children.Add(ellipse);

        TimeSpan timeSpan = new TimeSpan(0, 0, 0, 0, 500 + random.Next(1500));
        DoubleAnimation timeline = new DoubleAnimation();
        timeline.Duration = new Duration(timeSpan);
        timeline.To = 0;
        timeline.AutoReverse = true;
        timeline.RepeatBehavior = RepeatBehavior.Forever;

        PropertyPath opacityPath = new PropertyPath("Opacity");
        Storyboard.SetTarget(timeline, ellipse);
        Storyboard.SetTargetProperty(timeline, opacityPath);

        Storyboard story = new Storyboard();
        story.Children.Add(timeline);
        story.Begin();
    }
}

コード01のcanvas_Loaded()メソッドは、Canvasオブジェクトが読み込まれたときに呼び出されるイベントハンドラです。この中で、大量の楕円形を生成して Canvas オブジェクトに追加します。この場では1,000個のオブジェクトを生成していますが、テストする環境のパフォーマンスに合わせて適当に調整してください。

楕円形を追加すると同時に、楕円形のOpacityプロパティに対するアニメーションを追加しています。個々の楕円形の位置やサイズ、色、アニメーション時間などは乱数で決めています。アプリケーションを実行すると、点滅する色とりどりの楕円形が画面に現れます。大量のオブジェクトの半透明合成アニメーションはCPUにとって負荷の大きい処理となるでしょう。

最初に(CacheModeプロパティの設定をコメントアウトするなどして)楕円形をキャッシュしない場合の結果を見てみましょう。実はSilverlightは描画やメディアの再生にマルチコアを有効に利用しています。そのため、GPUによるハードウェア・アクセラレーションが無効でもCPUのコア数が多ければ健闘してくれます。テスト環境のCPUはIntel Core2 Quad Q9550、GPUはNVIDIA GeForce 9800 GTです。

図01 キャッシュしない場合のパフォーマンス

画面上の左端に表示されている数字の一番左がFPS(1秒間に画面を更新した回数)で、その次がキロバイト単位のGPUで使用しているメモリサイズを表しています。CPUの各コアの使用率が高くなることからSilverlightがマルチコアに最適化されていることが確認できます。

一方、楕円形のビットマップキャッシュを有効にすると明らかにCPU使用率が低下し、同時にFPSの上昇も確認できました。GPUのメモリ使用率が上昇していることからも、楕円形のビットマップがキャッシュされていることが確認できます。精密なパフォーマンス測定はしていませんが、テスト環境では平均して2割ほど描画速度が向上しました。

図02 キャッシュした場合のパフォーマンス

ハードウェア・アクセラレーションを有効化することで、Atomのような性能の低いCPUを使っているネットブックでもGPUを搭載していれば大きなパフォーマンスの改善が期待できます。

著者プロフィール:赤坂玲音

フリーランスのテクニカルライタ兼アプリケーション開発者。主にクライアント技術、プレゼンテーション技術が専門。2005年から現在まで「Microsoft MVP Visual C++」受賞。技術解説書を中心に著書多数。近著に『Silverlight入門』(翔泳社)などがある。