Top > Programming > .NetFramework > WPF > AnimationDrawingWithCompositionTargetRendering
Last-modified: Sun, 11 May 2014 20:10:43 JST
Counter:4700 Today:1 Yesterday:0 Online:7
このエントリーをはてなブックマークに追加

CompositionTarget.Renderingを使ったアニメーションの描画

About

ここではCompositionTargetクラスのRenderingイベントを利用した描画の更新について解説します。CompositionTarget.Renderingイベントは対象の描画面(サーフェース)が更新される度に通知されるイベントです。CompositionTarget.Renderingによる描画面の更新通知は、ウィンドウの移動やサイズの変更などに関わらず常に発生し続けます。描画面の更新は、公式には"フレームの更新"とされます。

ふつうWPFで何かを動かす描画(アニメーション)を実現するときは、Storyboardクラス(コンポーネント)を利用するか、あるいは定期的に処理を実行する実装(Timerクラス)によって、一定時間ごとに描画を更新するなどの処理を行う必要があります。あるいはMicrosoftが提供するDirectXやXNAなどのAPIないしフレームワークでは、ゲームループと呼ばれる仕組みが提供されていますから、それらを利用する方法もあります。CompositionTarget.Renderingイベントによる実装では、これらの処理を実装するための煩わしさがなく簡単に実装できる代わりに、フレームレートに多少の乱れが生じ、制御する機能も標準的に用意されていない、という特徴があります。

  • &ref(): File not found: "WpfApplication_CompositionTargetRendering.zip" at page "Programming/.NetFramework/WPF/AnimationDrawingWithCompositionTargetRendering";
    • VisualStudio 2012
    • .Netframework 4.5

サンプルの実行結果

サンプルの実行結果は次の通りです。Windows8にCorei7(3.4GHz)で実行していますが、フレームレートは56~59fps当たりを行き来して、大体60fpsに近い値で動作しています。またウィンドウを移動する瞬間などに値が跳ね上がることが確認できています。

0.png

サンプル

解説することはほとんどありません。公式のサンプルも比較的シンプルです。Windowを含むGUIコンポーネントのコンストラクタなどでCompositionTarget.Renderingイベントにイベントハンドラを追加します。追加されたイベントハンドラは対象のコンポーネントが更新されるタイミング、フレームの更新のタイミングで実行されます。

サンプルに用意したイベントハンドラではフレームレート(fps)を計測し、適当な数の適当なEllipseをランダムに生成してCanvasへ追加しています。フレームレートの算出方法やその他の実装内容についてはここでは解説しません。生成するEllipseの数などを変更して負荷の変化を確認するなどしてみてください。

        CompositionTarget.Rendering += CompositionTarget_Rendering;

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            frameCounter++;
            
            //1sec毎のfps計測を簡単に実装するとき(およそのfpsが分かる)
            //if (this.stopwatch.Elapsed.Seconds == 0)
            //    frameCounter = 0;
            //else
            //    this.frameRate = (int)(frameCounter / this.stopwatch.Elapsed.Seconds);

            //1msec毎のfps計測
            double diffMsec = this.stopwatch.Elapsed.TotalMilliseconds
                                  - this.previousTimeMsec;
            this.previousTimeMsec = this.stopwatch.ElapsedMilliseconds;
            this.frameRate = (int)(1000 / diffMsec);

            //UpdateUI
            this.Label_FrameCount.Content = "FrameCount : " + frameCounter;
            this.Label_FrameRate.Content = "FrameRate(fps) : " + frameRate;

            //適当な描画
            this.Canvas_Main.Children.Clear();
            Random random = new Random();
            for (int i = 0; i < 100; i++)
            {
                Ellipse ellipse = new Ellipse();
                ellipse.Fill = new SolidColorBrush
                    (Color.FromRgb((byte)random.Next(),
                    (byte)random.Next(),(byte)random.Next()));
                ellipse.Width = random.Next(5,30);
                ellipse.Height = ellipse.Width;
                Canvas.SetTop
                   (ellipse, random.Next((int)(this.Canvas_Main.ActualHeight)));
                Canvas.SetLeft
                   (ellipse, random.Next((int)(this.Canvas_Main.ActualWidth)));
                this.Canvas_Main.Children.Add(ellipse);
            }
        }

CompositionTarget.Renderingの使い道

使える場面

別のスレッドなどで定期実行されている処理を描画面に反映するために利用する場合には、非常に有効であると言えます。定期実行される処理の実装を行わなくて良いので、実装に手間がかかりません。別スレッドで定期実行されているのであればそちらの処理で描画するように設計すればよい、というようにも思えますが、WPFではGUIコンポーネントのオブジェクトは異なるスレッドから操作できない、という決まりがあります。このような問題は通常であればInvokeなどを利用して回避するのですが、CompositionTarget.Renderingで解決できるのであれば、一番簡単でしょう。

またフレームレートを厳密に考慮しなくて良いが、ほぼ固定化されたフレームレートが必要で、そのための実装に手間をかけたくない、という場合には非常に有用化と思います。

ゲームには使えないか

まずフレームレート(FPS)をシビアに考慮しなければならないゲームなどの実装を目的として利用するべきではありません。例えばXNAが提供するゲームループのように、固定されたフレームレートを実現することが標準ではできません。フレームレートを維持するために処理を待たせたりスキップしたりする枠組みを実装する必要が出てきます。CompositionTargetを利用することも出来るでしょうが、他に定期実行するための処理を実装する方法もありますから、CompositionTargetを選択する理由にはならないハズです。