プログラムの事とか

お約束ですが「掲載内容は私個人の見解です」

WPFで速い描画方法が知りたい!

WPFで描画処理書いていてもっと速く描きたい!(ていうか自分の書いた処理遅い!)ということで実験を兼ねてプロジェクト作ってあわよくばすごい人に速い方法教えてもらおう!

プロジェクトはこちら

github.com

簡単な説明

class CustomDrawControl : FrameworkElement
{
    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        switch (DrawType)
        {
        case DrawType.NotFreeze:
        case DrawType.Freeze:
        case DrawType.Grouping:
        case DrawType.BackingStore:
        }

        _counter++;
        Task.Run(async () =>
        {
            await Task.Delay(1);
            await Dispatcher.BeginInvoke((Action)this.InvalidateVisual);
        });
    }
}

こんな感じでOnRender内でいろんな方法で描画してみてFPSを確認してみます

FPSの算出方法間違ってるとかそもそもOnRender使っちゃダメとかそういう突っ込みあったら教えてください)

描画はフレーム毎に色が変わるペン(パレットは8色)で点(短い線)をたくさん描きます

計測

最大FPS

何も描かないで上の処理のようにOnRenderTask.Delay(1) -> InvalidateVisual()ってやるだけで大体64fpsでした

f:id:puni-o:20180411131559j:plain

もっと速くなると思っていたのでこの再描画方法がそもそも間違っているんじゃないか疑惑が・・・

FreezableをFreezeした場合としなかった場合

遅い → Freeze()シロ っていう流れはググるとよく出てくるので実際に比較してみました

Freezeしない

f:id:puni-o:20180411131908j:plain

最大で 20 fps くらい

Freezableな奴を片っ端からFreezeする

f:id:puni-o:20180411131923j:plain

最大 64 fps

Freezeシロ

点の数は3000個で実験したのですがここまで差が出るとは

ちゃんとFreezeしようね


3パターンで計測

本番

ですがネット調べたけど私には3パターンしか見つけられませんでした

ここからは点の数を10000個で実験です


2018/4/12

デバッグビルドで計測していたり、一つは完全に間違った処理だったりなので以下はスクショ入れ替えてます


1点ずつ描画

private void FreezeRender(DrawingContext drawingContext)
{
    foreach (var p in _particles)
    {
        drawingContext.DrawLine(penList[p.Pallete], new Point(p.X1, p.Y1), new Point(p.X2, p.Y2));
    }
}

こんな感じで描きます(余計なの消しているので全部見たければGitHubからどうぞ)

f:id:puni-o:20180412103100j:plain

結果は50 fpsちょいでした

パレット毎にまとめて描画

パスはGeometryにまとめて描画した方が速いみたいな書き込みをみたので、パレット毎に一気に描画してみます

private void GroupingRender(DrawingContext drawingContext)
{
    foreach (var g in _particles.GroupBy(p => p.Pallete))
    {
        var geometry = new StreamGeometry();
        using (var context = geometry.Open())
        {
            foreach (var p in g)
            {
                context.BeginFigure(new Point(p.X1, p.Y1), false, false);
                context.LineTo(new Point(p.X2, p.Y2), true, false);
            }
        }
        drawingContext.DrawGeometry(null, penList[g.Key], geometry);
    }
}

余計なの消していますが(以下略

f:id:puni-o:20180412103124j:plain

60 fps以上出ているのでまだ余裕がありそうです(UIの操作が普通にできるので)

ダブルバッファリング?

DrawingGroup作ってそっちに描いてOnRender時はそれを描け的なことを見かけたので実験

昔懐かしのダブルバッファリングなのだろうか?

readonly DrawingGroup _backingStore = new DrawingGroup();
private void BackingStoreRender(DrawingContext drawingContext)
{
    var context = _backingStore.Open();
    foreach (var p in _particles)
    {
        context.DrawLine(penList[p.Pallete], new Point(p.X1, p.Y1), new Point(p.X2, p.Y2));
    }
    context.Close();
    drawingContext.DrawDrawing(_backingStore);
}

f:id:puni-o:20180412103226j:plain

10 fpsくらい

backingStore使って~っていうのは多分こういう使い方じゃないんだよね(OnRenderで事前に描画しておいたのを表示とかそういう使い方かな?)

まとめて描画できるならまとめよう

とりあえずまとめて描けるならまとめた方が速そう


2018/4/16 追記

  • FPSCompositionTarget.Renderingで測るみたいなのでそっちに移しました

  • DrawType.WriteableBitmapを追加しました

スクショとかないけど一番速かったです

雑な測定結果はGitHubの方にまとめてあるのでそちらを参照ということで・・・


まとめ

WPFの便利なもろもろを使いつつ速い描画を求めるならWriteableBitmapになるんですかね?

これより速そうなのだとDirectXくらいしか思いつかないんですが、それだと自由度が無くなりすぎだと思うんですよね(透過できないのが痛い)