c# wpf performance drawing drawingvisual

c# - Gráficos 2D rápidos en WPF



performance drawing (5)

Necesito dibujar una gran cantidad de elementos 2D en WPF, como líneas y polígonos. Su posición también necesita ser actualizada constantemente.

He analizado muchas de las respuestas aquí que en su mayoría sugirieron utilizar DrawingVisual o anular la función de OnRender. Para probar estos métodos, he implementado un sistema de partículas simple que genera 10000 elipsis y encuentro que el rendimiento del dibujo sigue siendo realmente terrible utilizando estos dos enfoques. En mi PC no puedo obtener mucho más de 5-10 cuadros por segundo. lo cual es totalmente inaceptable si se considera que fácilmente extraigo 1/2 millón de partículas utilizando otras tecnologías.

Así que mi pregunta es, ¿estoy corriendo en contra de una limitación técnica de WPF o me estoy perdiendo algo? ¿Hay algo más que pueda usar? Cualquier sugerencia de bienvenida.

Aquí el código que probé

contenido de 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>

contenido de 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; } } }


Creo que el código de muestra proporcionado es bastante bueno y muestra los límites del marco. En mis mediciones, he perfilado un costo promedio de 15-25ms que se atribuye a la sobrecarga de procesamiento. En esencia, aquí hablamos solo de la modificación de la propiedad del centro (dependencia), que es bastante costosa. Supongo que es caro porque propaga los cambios a mil-core directamente.

Una nota importante es que el costo general es proporcional a la cantidad de objetos cuya posición se cambia en la simulación. La representación de una gran cantidad de objetos en sí misma no es un problema cuando la mayoría de los objetos tienen coherencia temporal, es decir, no cambian de posición.

El mejor enfoque alternativo para esta situación es recurrir a D3DImage , que es un elemento para que Windows Presentation Foundation presente la información presentada con DirectX. En general, dicho enfoque debe ser eficaz, en cuanto a rendimiento.


El método de dibujo WPF más rápido que he encontrado es:

  1. crear un DrawingGroup "backingStore".
  2. durante OnRender (), dibuje mi grupo de dibujo en el contexto de dibujo
  3. Siempre que lo desee, realice una copia de seguridad de Store.Open () y dibuje nuevos objetos gráficos en él

Lo sorprendente de esto para mí, proveniente de Windows.Forms ... es que puedo actualizar mi DrawingGroup después de haberlo agregado a DrawingContext durante OnRender (). Esto es actualizar los comandos de dibujo retenidos existentes en el árbol de dibujo de WPF y desencadenar un repintado eficiente.

En una aplicación simple que he codificado tanto en Windows.Forms como en WPF ( SoundLevelMonitor ), este método se siente empíricamente muy similar en rendimiento al dibujo inmediato de OnPaint () GDI.

Creo que WPF hizo un dis-servicio llamando al método OnRender (), podría denominarse mejor AccumulateDrawingObjects()

Esto básicamente se ve como:

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(); }

También he intentado usar RenderTargetBitmap y WriteableBitmap, tanto para Image.Source como para escribir directamente en DrawingContext. El método anterior es más rápido.


En las formas de las ventanas este tipo de cosas me hicieron retroceder;

  • Establecer Visible = Falso para el contenedor de nivel más alto (por ejemplo, lienzo del formulario en sí)
  • Dibujar mucho
  • Establecer Visible = Verdadero

No estoy seguro si WPF soporta esto.


Estas son algunas de las cosas que puede probar: (las probé con su muestra y parece que se ve más rápido (al menos en mi sistema)).

  • Use Canvas en lugar de Grid (a menos que tenga otras razones). Juega BitmapScalingMode y CachingHint:

    <Canvas Name="xamlGrid" RenderOptions.BitmapScalingMode="LowQuality" RenderOptions.CachingHint="Cache" IsHitTestVisible = "False"> </Canvas>

  • Agregue un StaticResource para el pincel utilizado en GeometryDrawing:

    <SolidColorBrush x:Key="MyBrush" Color="DarkBlue"/>

en código de uso como:

GeometryDrawing gd = new GeometryDrawing((SolidColorBrush)this.FindResource("MyBrush"), null, eg);

Espero que esto ayude.


Puedes probar un WriteableBitmap y producir la imagen usando un código más rápido en un hilo de fondo. Sin embargo, lo único que puede hacer con esto es copiar datos de mapa de bits, por lo que debe codificar sus propias rutinas de dibujo primitivas, o (lo que incluso podría funcionar en su caso) crear una imagen de "sello" que copie en todas partes sus partículas ir...