質問者
WPF 線の大量描画で速度改善がされない

質問
-
ソースは下記に示します。
ベジエ曲線を2000本ほど高速で描画する処理を考えています。
canvas.Children.Add()でshape型オブジェクトを一つ一つ追加する方法を使用していましたが、レンダリングに1秒近く掛かるので高速化を考えております。
ネットで調べたところでは、Geometryを使ってオブジェクトを構成し、OnRenderを呼ぶことで高速化できるという記事があったので試用してみたのですが、レンダリングに8秒も掛かり、圧倒的な遅さでした。
ネット上の記事ではshape型オブジェクトよりも早い処理結果が出ると記載されているのですが、自分で書いたプログラムでは速度が出ないのは、なぜでしょうか。
● xaml
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid x:Name="grid1">
<Label x:Name="xLabel" Background="#FFC93F3F" Margin="474,332,134,44"/>
<Canvas x:Name="canvas1" HorizontalAlignment="Left" Height="344" Margin="57,32,0,0" VerticalAlignment="Top" Width="511">
<local:view2 x:Name="xView"/> '←この行でXAMLのエラーが出るのだが原因が分からない コンパイルは通るのでそのまま動かしている
</Canvas>
</Grid>
</Window>
●VB source
Class MainWindow
Dim rand As New Random()
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim points3 = New List(Of Tuple(Of Point, Point, Point))
Dim i As Integer
Dim 動作モード As Integer = 1
'1:onrenderを使う 2:shapeを1本ずつ追加
Dim 実行回数 As Integer = 400
If 動作モード = 1 Then '8秒
For i = 0 To 実行回数
Dim n2 = Tuple.Create(Of Point, Point, Point)(getRandomPoint, getRandomPoint,
getRandomPoint)
points3.Add(n2)
Next
Me.xView.points_bej = points3
Me.xView.InvalidateVisual() 'これを実行することでOnRenderが実行される
End If
If 動作モード = 2 Then '1秒
For i = 0 To 実行回数
drawベジエ2D(getRandomPoint, getRandomPoint, getRandomPoint, Media.Brushes.LightCoral)
Next
End If
End Sub
Public Function getRandomPoint() As Point
Return (New Point(rand.Next(800), rand.Next(450)))
End Function
Private Sub drawベジエ2D(ByVal 始点 As Point, ByVal 制御点 As Point, ByVal 終点 As Point, ByVal br As Brush)
Dim p1 = New System.Windows.Shapes.Path
Dim PF = New PathFigure()
Dim PG = New PathGeometry()
PF.StartPoint = 始点
PF.Segments.Add(New QuadraticBezierSegment(制御点, 終点, True))
PG.Figures.Add(PF)
p1.Stroke = br
p1.StrokeThickness = 2
p1.Opacity = 1
p1.Data = PG
Me.canvas1.Children.Add(p1)
End Sub
End Class
Public Class view2
Inherits Control
Public Property points_bej As List(Of Tuple(Of Point, Point, Point))
Protected Overrides Sub OnRender(ByVal drawingContext As DrawingContext)
Dim geo = New StreamGeometry
MyBase.OnRender(drawingContext)
If points_bej Is Nothing Then Exit Sub
Dim pen = New Pen(Brushes.Aqua, 2)
Using contex As StreamGeometryContext = geo.Open
'point型、3点を指示してベジエ曲線を描画
For Each tp3 As Tuple(Of Point, Point, Point) In points_bej
contex.BeginFigure(tp3.Item1, False, False)
contex.QuadraticBezierTo(tp3.Item2, tp3.Item3, True, False)
Next
End Using
geo.Freeze()
drawingContext.DrawGeometry(Nothing, pen, geo)
End Sub
End Class
すべての返信
-
小生もWPFで複雑なグラフ(しかも時々刻々変化するトレンドグラフのようなもの)を描こうとして、描画速度の改善に四苦八苦しています。その経験に基づいて、の参考情報です。あくまで「考え方」
お尋ねの件(提示されたプログラム)では、OnRenderが走るごとに、StreamGeometryにベジェ曲線を複数組み込んで、描画して、捨てているように見えます。プログラムの動作開始時にGeometryにベジェ曲線を複数組み込んで、そのGeometryを保持しておくのが良いのではないでしょうか? もっと言えば、Geometryを内包するVisualを用意して、VisualをFormに配置しておけば良いです。そうすれば、OnRenderでGeometryを作り直すコストは不要になります。(Shapeを使わずにVisualを使うことがミソ)
WPFは基本的に「保持型」のアーキテクチャです。GDI+は「その場その場で描画する」アーキテクチャでした。WPFでOnRenderを使うことで、GDI+風の「その場その場で描画する」ことができるように見えますが、そこで、Geometryを使ってしまうとWPFの「保持型」アーキテクチャの仕組みを持ち込むことになり遅くなると思います。
-
外池様、御回答ありがとうございます。
現在開発中のプロジェクトは、3Dオブジェクトを2Dに投影するというもので、マウスドラッグで3Dオブジェクトをグリグリと回転させます。扱う線分は通常1,000本前後、多い場合で3,000本を超えます。表示の書き換えは毎秒10回以上を目標にしていますので、現在の動作速度ではとても追従できません。この様な目的ですから、Geometryは刻々と変化しますのでバッファリングはできないと思います。
shapeを一つ一つ追加する方が高速であるならば、これで更なる高速化を図りたいと思っています。ネット上の情報では、shapeのmin/maxのheight/width全てを固定値にするという方法も見つかりましたが、やってみたらパフォーマンスが3割も悪化しました。このほかにも有効な改善策があれば試してみたいと思っています。なにか助言をお願いします。
>そこで、Geometryを使ってしまうとWPFの「保持型」アーキテクチャの仕組みを持ち込むことになり遅くなると思います。
Geometryを使わずに、もっと有効な解決方法があるのでしょうか?
- 編集済み huahi11112 2021年3月11日 5:29 誤記
-
目指すところをご説明頂いて、回答もしやすくなったと思います。「2Dに投影する」の意味が、いまいち曖昧ですが、単に表示することを仰ってますか? それとも立体的な構造のオブジェクトについて二次元平面への投影図を作成し、「グリグリ」回転に同期させてリアルタイムで投影図を再描画するようなことを仰ってますか?
とりあえず、前者のみ、3Dオブジェクトの表示だけを考えます。かつ、3Dオブジェクトは静的なもので変形はせず、全体の形を保ったままグリグリ回転させる表示ができれば良いと。ならば、WPFの3Dグラフィックスの機能を使うことを検討されてはいかがでしょう?
小生の経験(自習)がだいぶ前なので正確なクラス名忘れてしまいましたが、要するに、1) Geometryの様に3Dオブジェクトの形(相当に複雑になり得る)と表面(の材質?)を保持するオブジェクト、2) これを照らす照明のオブジェクト、3) 視点(カメラ)のオブジェクトをセットすれば表示されます。かつ、これらのオブジェクトのパラメータを変更する(オブジェクトそのものを捨てて作り直すのではなく、パラメータを変更するだけ、というのがミソ)と、それに応じて表示もリアルタイムで変わります。
グラフィックボードの表示機能・性能に強く結びついたWPFの機能ですので、高速です。
小生、マウスと連動させて「グリグリ」やったことはないですが、数百程度の線分で球を作って、表面をはりつけて、一定の速さで自転させることはやりました。「ビュンビュン」回せます。回す方向や回す量を、マウスの動きと連動させれば、ご希望の動作は可能かと思います。毎秒10フレームどころか、グラフィックボードのリフレッシュレートで再描画されます。
- 編集済み 外池 2021年3月11日 22:07
-
外池様、御回答ありがとうございます。
プロジェクトについて更に述べますと、外観的には2D-CADです。3Dオブジェクトを2Dに投影し、画面上に表示させるのは線分のみです。線分を変形させると元の3Dモデルにも反映されます。
以前、このフォーラムでWPFの3Dグラフィックス機能で3D→2Dの投影方法が非常に手間の掛かる方法で、もっと簡単な手続きで処理できないのかについて質問させていただきましたが、回答が付かなかったことと、2D-CADの現在の機能上で3Dオブジェクトを扱う機能は必要無いことから、WPFの3Dグラフィックスを使用することは考えておりません。
質問の当初に戻りますが、shapeを追加するという方法が(残念ですが)最速なので、これで開発を進めます。 -
huahi11112さん、こんにちは。フォーラムオペレーターのKumoです。
MSDNフォーラムにご投稿くださいましてありがとうございます。
カーブを個別にレンダリングする方法を検討してみてはいかがでしょうか。
たとえば、最初に画面上のグラフィックの一部をレンダリングし、最初の部分が終了したら次のグラフィックのバッチをレンダリングします。
どうぞよろしくお願いいたします。MSDN/ TechNet Community Support Kumo ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、 ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~
-
kumo-msft様、御回答ありがとうございます。
HitTestを必要としない線分オブジェクトも多数あり、これらはWriteableBitmapExで描画処理することにしましたので、処理速度としては随分改善されたことをお伝えします。
カーブを個別にレンダリングという方法ですが、具体的な方法が分かりません。
10,000本のベジエ曲線があったら、2,000本ずつ分割して処理するということですか?
ベジエ、直線などが混在していたら種別毎にレンダリングするということですか?
教えていただいたならば、こちらでも速度改善の実験をしてみたいと思います。
サポートをよろしくお願いいたします。