I need to draw a large amount of 2D elements in WPF, such as lines and polygons. Their position also needs to be updated constantly.
I have looked at many of the answers here which mostly suggested using DrawingVisual or overriding the OnRender function. To test these methods I've implemented a simple particle system rendering 10000 ellipses and I find that the drawing performance is still really terrible using both of these approaches. On my PC I can't get much above 5-10 frames a second. which is totally unacceptable when you consider that I easily draw 1/2 million particles smoothly using other technologies.
So my question is, am I running against a technical limitation here of WPF or am I missing something? Is there something else I can use? any suggestions welcome.
Here the code I tried
content of 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>
content of 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; } } }
A rendering tier value of 1 or 2 means that most of the graphics features of WPF will use hardware acceleration if the necessary system resources are available and have not been exhausted. This corresponds to a DirectX version that is greater than or equal to 9.0.
While WPF is over a decade old and has been improved greatly over the years, there are still several areas that can suffer from poor performance. The reasons for this poor performance include things such as bad coding practices, broken bindings, complex layouts, the lack of UI virtualization, and much more.
The GPU, or Graphics Processing Unit, is the chip that powers your video card. Essentially, hardware acceleration offloads the work of rendering graphics in your WPF application from your computer's main processor (CPU) to the video card's processor (GPU).
WPF uses DirectX and attempts to provide a consistent programming model for building applications.
I believe the sample code provided is pretty much as good as it gets, and is showcasing the limits of the framework. In my measurements I profiled an average cost of 15-25ms is attributed to render-overhead. In essence we speak here about just the modification of the centre (dependency-) property, which is quite expensive. I presume it is expensive because it propagates the changes to mil-core directly.
One important note is that the overhead cost is proportional to the amount of objects whose position are changed in the simulation. Rendering a large quantity of objects on itself is not an issue when a majority of objects are temporal coherent i.e. don't change positions.
The best alternative approach for this situation is to resort to D3DImage, which is an element for the Windows Presentation Foundation to present information rendered with DirectX. Generally spoken that approach should be effective, performance wise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With