質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.77%

WPF CanvasコントロールにPathをたくさん描画すると非常に重い

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 14K+

score 259

お世話になっております.

WPFのCanvasにPathをAddしてCAD風に線分や円弧を表示しています.

量しだいではありますが,添付した画像くらいの量になるとだいぶ重く,
描画の更新に0.5sくらいかかってしまい,画面内での移動や拡大縮小に難があります
また,もっと多くの図形も表示させる必要があるのでもう少しパフォーマンスがあがらないかなと思い質問させていただきました.

Pathオブジェクトに関しては,一度Addしたらそれのdataプロパティを変更するようにし,毎回DeleteしてAddのようなことはさせていません.(Pathオブジェクトを使いまわしていると表現すればよいのでしょうか)
(画面の描画を繰り返してもGCがほとんど発生しませんし,メモリ消費量が右肩上がり,なんてこともありません)

現状DataTemplateのようなものはPathオブジェクトに対して使用しておらず(勉強中のためまだ自分でかけるほどの力がないのです・・・)

線分の描画を更新しているメソッドを参考のため載せておきます.
あらかじめ,Canvas.ChildにPathがAddされた状態で以下のメソッドを実行します.

private void drawLine(Point Start_, Point End_, Point Offset_, Color Color_, Path p_) {
    p_.Stroke = new SolidColorBrush(Color_);
    p_.StrokeThickness = 1;
    ((LineGeometry)p_.Data).StartPoint = new Point((Start_.X - Offset_.X) * Magnification, (Start_.Y - Offset_.Y) * Magnification);
    ((LineGeometry)p_.Data).EndPoint = new Point((End_.X - Offset_.X) * Magnification, (End_.Y - Offset_.Y) * Magnification);
}

また,二つのCanvasコントロールを重ねて,後のCanvasには図形を表示し,手前のCanvasにはグリッド線や範囲選択の長方形を表示したりと,別々のCanvasに分けたにもかかわらず後ろのCanvasが重いときは手前も重たくなりました.(後は一切描画更新をしない状態で手前だけ更新したとしても)

PCスペックは①i3-2370M+8GB-DDR3,②i7-4770HQ+8GB-DDR3の2台で確認しましたが動作速度に差は感じられなかったのでPCスペックに問題はないと思っております.

WPFの限界でしょうか・・・
何かヒントをいただければと思います
よろしくお願いいたします.

アプリ実行例

追記
余白でドラッグをグリグリやるときと,大量のPathの上空でグリグリやるときではCPUの使用量が2~3倍程度違うようでした.
CPU使用率で中央付近で山が高くなっていますが,左側はCanvas右上でのドラッグ,山の右側はCanvas左下でドラッグです.
赤い枠が写っていますが,これがマウスについてくる範囲選択の枠です
イメージ説明

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+1

まず、前提としてWPFのPathは描画が非常に遅いです。
これはベクターデータとしてリッチなコンテンツを描画することができるWPFの強みでもあり、パフォーマンスの低下という弱みでもあります。
パフォーマンス向上の上でPathなどのXXXGeometry コントロールは使用しないほうがいいと思います。

私も以前パフォーマンスに悩まされていたときは、以下のような方法で対処しました。

  1. Width/Height プロパティを固定値にする
  2. IsHitTestVisible プロパティをFalse にする
  3. XXXGeometry 系コントロールは使用しない
  4. UserControl は使用しない。
    特に3,4 の方法は効果が高かったように感じられました。

その上で、複雑な図形などを描画する場合は、カスタムコントロールで"OnRender"メソッドをオーバーライドして図形を書いていました。
例えば線分を描画する場合は以下のようになります。

    public class CustomControl1 : Control
    {
        static CustomControl1()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            // ここに描画処理を実装する。
            var pen = new Pen(BorderBrush, 1);
            drawingContext.DrawLine(pen, _previousPoint, _currentPoint);
        }
    }

図形の拡大、縮小については以下のページが参考になるのではないでしょうか
http://grabacr.net/archives/1723

何か解決のヒントになれば幸いです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/09 18:13

    回答ありがとうございます.

    OnRenderというメソッドがあるのですね
    調べて使ってみたいと思います

    キャンセル

checkベストアンサー

0

以前質問者さんが https://teratail.com/questions/80088http://" rel="nofollow" target="_blank">WPFで2D(将来的には3Dも)を扱いたい で質問されたとき、Canvasはパフォーマンスが良くないので事前に検証されることをお勧めしましたが、試されましたか?

Canvasにエレメントは配置していく通常のアプローチでは厳しい気がします。
IsHitTestVisible の無効化や、Freeze可能なものすべてFreezeしても、描画対象の絶対数が大きいと厳しいかと思います。
もし私だったら、

・WriteEbleBitmap等を使って画像として表示する。
WriteableBitmapExを使うとラクです。

・DrawingVisual で描画する

といったことを試すと思います。
どちらも画像として表示するものですので、例えばお絵かきした部品のマウスオーバーでTooltipを表示する、といったことはそのままではできませんが。。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/10 16:14

    回答ありがとうございます.

    > 事前に検証
    の結果が現在のこの状態だと思っていたのですが認識が間違っていましたでしょうか
    単にPathを追加していくだけだと現状のようにだいぶ重いです.(縮小時には図形を抽象化して表示する,くらいの工夫をすれば実用的な速度ではありますが)

    > もし私だったら、
    > ・WriteEbleBitmap等を使って画像として表示する。
    > ⇒WriteableBitmapExを使うとラクです。
    > ・DrawingVisual で描画する
    どうせマウスオーバーでTooltipを表示みたいな作業はそのつど画面(ビットマップ)を書き直すくらいの覚悟ではいたのでかまわないといえばかまわないですが・・・.

    いずれにせよちょっとメソッドを追加するとか設定を変えるとか,そんな感じに簡単にパフォーマンスに劇的な変化を与える方法はないようですね

    Bitmapで描画もぜひ試してみたいのですが,それ以前に実装しなければならない機能が山のように残っているため(描画とは関係ない内部処理),まずはそちらを実装してからになってしまいそうです

    なかなかすぐに結果をお返事できるような状況でなく申し訳ありません

    キャンセル

  • 2017/07/10 17:00

    検証は、取りあえずはCanvasが一つだけあるWindowのサンプルプロジェクトを作成し、そこの大量のPathを配置してどのくらいだとNGになるのか調べる、くらいでも目安になると思います(提示されたプログラムがそうだ、ということであれば悪しからず。。)

    Canvasのパフォーマンスは皆さん困っている箇所です(WPF Canvas Performanceで検索すると色々でてくるかと思います)
    ⇒つまり、プロパティをちょっと変えれば改善するようなことではないです。

    あまり参考にならない回答で申し訳ないですが、良いプログラムができるといいですね。頑張ってください。

    キャンセル

  • 2017/07/10 17:08 編集

    >検証は、取りあえずはCanvasが一つだけあるWindowのサンプルプロジェクトを作成し、そこの大量のPathを配置してどのくらいだとNGになるのか調べる、くらいでも目安になると思います(
    このような方法でよかったのですね(汗
    いろいろ実装してから「やっぱり遅いなぁ」となっていたので無駄な努力とまではいきませんが中々無駄でした(笑

    確かにWPFのレンダリング回りのパフォーマンスの話は検索するとたくさんヒットして驚きます
    Gridと比べてCanvasは早いのだろうと(座標が絶対座標になるので)勝手に思い込んでおりましたが...

    Ebiryo様にはほかにも多数の質問で回答いただき大変助けられ感謝しております
    ありがとうございました

    キャンセル

0

あてずっぽうのコメントであることをお断りせねばなりませんが、例えばPhotoShopのような画像ソフトで多数のレイヤーを重ねた状態で一番上にある画像を変更したり移動したりということをよく行うと思います。その際に下位のレイヤーが多少多くても(といっても数段ぐらいしかやったことないですが)一番上のレイヤーの編集に支障をきたすようなもたつきはありません。

PhotoShopがどのように最適化しているか知る由もありませんが、一つ考えられることは、動かない殆どの部分の画像のレンダリングは一旦オフスクリーンイメージバッファに対して行ってしまい、編集状態では常に1枚の背景画像と一番手前の編集レイヤーの2レイヤー」とすると再描画の際のレンダリングのオーバーヘッドは軽減されるのではないかと思いました。

本件でいえばCanvas上へパスを用いて多数のオブジェクトを配置するとオブジェクトを変更する度に(影響のある部分のクリッピングを行ったとしても)かなり多数のパスの再レンダリングが必要でありそのために遅くなっているのではないかと思えました。

実験として、背景に画像を置いた上でご質問にある画像の一部分の部品程度のパスのみ編集する実験をしてみてはいかがでしょうか?それで軽くなるようなら、「編集対象以外の図形をオフスクリーンバッファーへレンダリングしておく」といった工夫で高速化できる可能性があると思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/09 00:12

    回答ありがとうございます

    まず,手前にあるCanvasの背景を不透過にし,大量のPathがまったく見えない状況にしてみました.
    結果は失敗で,軽くなることはありませんでした.

    次に,二つのCanvasの間にImageコントロールを設置しWindowsのサンプルピクチャ(jpg)を表示,手前のCanvasの背景は不透過という状況にしてみました
    こちらも結果は失敗でした.

    そして気がついたことがあります.
    Canvas上をドラッグすると,ドラッグ範囲を示す長方形を描画しているのですが,Pathが大量にある部分の上空では急激に重たくなり,余白部分では非常にスムーズに動きます

    また,オフスクリーンバッファへのレンダリングはゲームプログラム(DxLib等)では何度か聞いたことがあるのですが,WPFでも利用できるものなのでしょうか

    キャンセル

  • 2017/07/09 00:28 編集

    Canvasの背景を不透明にしても重いということは、そのパスが最前面に見えるかどうかとは無関係にレンダリング自体は行われていると推測します。動かない部分の図形はCanvasやパスそのものには描画せずに背景画像のみへ描画すると効果がみえてくる気がします。

    > オフスクリーンバッファ
    WPFそのものを殆どさわったことがないので直接の知識はないですが、調べる価値はあると思います。WPFで画像が扱えなかったり、重なったレイヤーが実現不可能といった制約はないように思えます。

    キャンセル

0

http://proprogrammer.hatenadiary.jp/entry/2014/12/25/014448
この記事に書いてあることは試してみました?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/09 18:44 編集

    回答ありがとうございます

    試しているところですが,エラーに悩まされています・・・(別の質問として投稿させていただきました

    解決しました

    が,どうもやりたいこととは微妙に違うみたいなので保留中です

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.77%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る