WPFで速い描画方法が知りたい!
WPFで描画処理書いていてもっと速く描きたい!(ていうか自分の書いた処理遅い!)ということで実験を兼ねてプロジェクト作ってあわよくばすごい人に速い方法教えてもらおう!
プロジェクトはこちら
簡単な説明
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
何も描かないで上の処理のようにOnRender
でTask.Delay(1) -> InvalidateVisual()
ってやるだけで大体64fpsでした
もっと速くなると思っていたのでこの再描画方法がそもそも間違っているんじゃないか疑惑が・・・
FreezableをFreezeした場合としなかった場合
遅い → Freeze()シロ っていう流れはググるとよく出てくるので実際に比較してみました
Freezeしない
最大で 20 fps くらい
Freezableな奴を片っ端からFreezeする
最大 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からどうぞ)
結果は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); } }
余計なの消していますが(以下略
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); }
10 fpsくらい
backingStore使って~っていうのは多分こういう使い方じゃないんだよね(OnRenderで事前に描画しておいたのを表示とかそういう使い方かな?)
まとめて描画できるならまとめよう
とりあえずまとめて描けるならまとめた方が速そう
2018/4/16 追記
FPSは
CompositionTarget.Rendering
で測るみたいなのでそっちに移しましたDrawType.WriteableBitmapを追加しました
スクショとかないけど一番速かったです
雑な測定結果はGitHubの方にまとめてあるのでそちらを参照ということで・・・
まとめ
WPFの便利なもろもろを使いつつ速い描画を求めるならWriteableBitmapになるんですかね?
これより速そうなのだとDirectXくらいしか思いつかないんですが、それだと自由度が無くなりすぎだと思うんですよね(透過できないのが痛い)