線やポリゴンなど、大量の2D要素をWPFで描画する必要があります。彼らの立場も絶えず更新する必要があります。
ここでは、DrawingVisualの使用またはOnRender関数のオーバーライドを提案する回答の多くを見てきました。これらの方法をテストするために、10000の楕円をレンダリングする単純なパーティクルシステムを実装しましたが、これらの両方のアプローチを使用しても、描画パフォーマンスはまだひどいことに気づきました。私のPCでは、1秒あたり5〜10フレームを超えることはできません。これは、他のテクノロジーを使用してスムーズに200万個の粒子をスムーズに描画できると考えると、まったく受け入れられません。
だから私の質問は、私はここでWPFの技術的な制限に直面しているか、何か不足しているのですか?他に使用できるものはありますか?どんな提案も歓迎します。
ここで私が試したコード
mainWindow.xamlの内容:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="500" Width="500" Loaded="Window_Loaded">
<Grid Name="xamlGrid">
</Grid>
</Window>
mainWindow.xaml.csの内容:
using System.Windows.Threading;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
EllipseBounce[] _particles;
DispatcherTimer _timer = new DispatcherTimer();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//particles with Ellipse Geometry
_particles = new EllipseBounce[10000];
//define area particles can bounce around in
Rect stage = new Rect(0, 0, 500, 500);
//seed particles with random velocity and position
Random Rand = new Random();
//populate
for (int i = 0; i < _particles.Length; i++)
{
Point pos = new Point((float)(Rand.NextDouble() * stage.Width + stage.X), (float)(Rand.NextDouble() * stage.Height + stage.Y));
Point vel = new Point((float)(Rand.NextDouble() * 5 - 2.5), (float)(Rand.NextDouble() * 5 - 2.5));
_particles[i] = new EllipseBounce(stage, pos, vel, 2);
}
//add to particle system - this will draw particles via onrender method
ParticleSystem ps = new ParticleSystem(_particles);
//at this element to the grid (assumes we have a Grid in xaml named 'xmalGrid'
xamlGrid.Children.Add(ps);
//set up and update function for the particle position
_timer.Tick += _timer_Tick;
_timer.Interval = new TimeSpan(0, 0, 0, 0, 1000 / 60); //update at 60 fps
_timer.Start();
}
void _timer_Tick(object sender, EventArgs e)
{
for (int i = 0; i < _particles.Length; i++)
{
_particles[i].Update();
}
}
}
/// <summary>
/// Framework elements that draws particles
/// </summary>
public class ParticleSystem : FrameworkElement
{
private DrawingGroup _drawingGroup;
public ParticleSystem(EllipseBounce[] particles)
{
_drawingGroup = new DrawingGroup();
for (int i = 0; i < particles.Length; i++)
{
EllipseGeometry eg = particles[i].EllipseGeometry;
Brush col = Brushes.Black;
col.Freeze();
GeometryDrawing Gd = new GeometryDrawing(col, null, eg);
_drawingGroup.Children.Add(Gd);
}
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
drawingContext.DrawDrawing(_drawingGroup);
}
}
/// <summary>
/// simple class that implements 2d particle movements that bounce from walls
/// </summary>
public class SimpleBounce2D
{
protected Point _position;
protected Point _velocity;
protected Rect _stage;
public SimpleBounce2D(Rect stage, Point pos,Point vel)
{
_stage = stage;
_position = pos;
_velocity = vel;
}
public double X
{
get
{
return _position.X;
}
}
public double Y
{
get
{
return _position.Y;
}
}
public virtual void Update()
{
UpdatePosition();
BoundaryCheck();
}
private void UpdatePosition()
{
_position.X += _velocity.X;
_position.Y += _velocity.Y;
}
private void BoundaryCheck()
{
if (_position.X > _stage.Width + _stage.X)
{
_velocity.X = -_velocity.X;
_position.X = _stage.Width + _stage.X;
}
if (_position.X < _stage.X)
{
_velocity.X = -_velocity.X;
_position.X = _stage.X;
}
if (_position.Y > _stage.Height + _stage.Y)
{
_velocity.Y = -_velocity.Y;
_position.Y = _stage.Height + _stage.Y;
}
if (_position.Y < _stage.Y)
{
_velocity.Y = -_velocity.Y;
_position.Y = _stage.Y;
}
}
}
/// <summary>
/// extend simplebounce2d to add ellipse geometry and update position in the WPF construct
/// </summary>
public class EllipseBounce : SimpleBounce2D
{
protected EllipseGeometry _ellipse;
public EllipseBounce(Rect stage,Point pos, Point vel, float radius)
: base(stage, pos, vel)
{
_ellipse = new EllipseGeometry(pos, radius, radius);
}
public EllipseGeometry EllipseGeometry
{
get
{
return _ellipse;
}
}
public override void Update()
{
base.Update();
_ellipse.Center = _position;
}
}
}
提供されているサンプルコードは、それが得られるものとほぼ同じであり、フレームワークの制限を示していると思います。私の測定では、15〜25ミリ秒の平均コストがレンダリングオーバーヘッドに起因することをプロファイルしました。本質的に、ここでは中心(依存関係)プロパティの変更についてのみ説明します。これは非常にコストがかかります。変更をmil-coreに直接伝達するため、コストがかかると思います。
重要な注意点の1つは、オーバーヘッドコストは、シミュレーションで位置が変更されるオブジェクトの量に比例することです。オブジェクトの大部分が時間的にコヒーレントである場合、つまり、位置を変更しない場合、それ自体で大量のオブジェクトをレンダリングすることは問題ではありません。
この状況に最適な代替アプローチは、Windows Presentation FoundationがDirectXでレンダリングされた情報を提示するための要素である D3DImage を使用することです。一般に、そのアプローチは効果的でパフォーマンスに優れていると言われています。
WriteableBitmapを試して、バックグラウンドスレッドでより高速なコードを使用して画像を生成できます。ただし、ビットマップデータをコピーすることしかできないため、独自のプリミティブ描画ルーチンをコーディングするか、(場合によっては機能する)「スタンプ」イメージを作成して、パーティクルのすべての場所にコピーする必要があります。行く...
私が見つけた最速のWPF描画方法は次のとおりです。
Windows.Forms ..から来た私にとってこれについて驚くべきことは、描画グループを更新できることです後中に描画コンテキストに追加しましたOnRender()。これにより、WPF描画ツリー内の既存の保持描画コマンドが更新され、効率的な再描画がトリガーされます。
Windows.FormsとWPFの両方でコーディングした単純なアプリ( SoundLevelMonitor )の場合、このメソッドのパフォーマンスは、即時のOnPaint()GDI drawingとかなり似ています。 。
WPFはメソッドOnRender()を呼び出すことによってサービスを停止したと思います。AccumulateDrawingObjects()
これは基本的に次のようになります。
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}
また、RenderTargetBitmapとWriteableBitmapを両方ともImage.Sourceに使用して、DrawingContextに直接書き込んでみました。上記の方法はより高速です。
Windowsフォームでは、このようなことで後戻りしました。
WPFがこれをサポートしているかどうかは不明です。