web-dev-qa-db-fra.com

Graphiques 2D rapides en WPF

Je dois dessiner une grande quantité d’éléments 2D dans WPF, tels que des lignes et des polygones. Leur position doit également être mise à jour en permanence. 

J'ai examiné de nombreuses réponses suggérant d'utiliser DrawingVisual ou de remplacer la fonction OnRender. Pour tester ces méthodes, j'ai implémenté un système de particules simple rendant 10000 ellipses et je constate que les performances de dessin sont toujours terribles avec ces deux approches. Sur mon PC, je ne peux pas obtenir beaucoup plus que 5 à 10 images par seconde. Ce qui est totalement inacceptable quand on considère que je puise facilement 1/2 million de particules en douceur en utilisant d'autres technologies.

Ma question est donc la suivante: suis-je contre une limitation technique de WPF ou manque-t-il quelque chose? Y at-il autre chose que je peux utiliser? toutes les suggestions sont les bienvenues. 

Voici le code que j'ai essayé

contenu 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>

contenu 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;
        }
    }
}
30
morishuz

Je crois que l'exemple de code fourni est à peu près aussi bon qu'il peut être et qu'il montre les limites du framework. Dans mes mesures, je profilais un coût moyen de 15-25 ms attribuable aux frais généraux de rendu. En substance, nous parlons ici de la modification de la propriété center (dependency-), qui est assez chère. Je suppose que cela coûte cher car il propage les modifications directement dans mil-core.

Une remarque importante est que les frais généraux sont proportionnels à la quantité d'objets dont la position est modifiée dans la simulation. Rendre une grande quantité d’objets sur lui-même n’est pas un problème lorsque la majorité des objets sont cohérents dans le temps, c’est-à-dire qu’ils ne changent pas de position. 

La meilleure approche alternative à cette situation consiste à faire appel à D3DImage , qui est un élément permettant à Windows Presentation Foundation de présenter les informations fournies avec DirectX. En règle générale, cette approche devrait être efficace et performante.

10
Lawrence Kok

Vous pouvez essayer un WriteableBitmap et produire l’image en utilisant un code plus rapide sur un fil d’arrière-plan. Cependant, la seule chose que vous pouvez faire avec elle est de copier des données bitmap. Vous devez donc coder vos propres routines de dessin primitives ou (ce qui pourrait même fonctionner dans votre cas) créer une image "tampon" que vous copiez partout où vos particules aller...

3
hbarck

La méthode de dessin WPF la plus rapide que j'ai trouvée consiste à:

  1. créer un groupe de dessin "backingStore". 
  2. pendant OnRender (), dessine mon groupe de dessin dans le contexte de dessin
  3. quand je veux, backingStore.Open () et dessine de nouveaux objets graphiques

La chose surprenante à ce sujet pour moi, provenant de Windows.Forms .. est que je peux mettre à jour mon groupe de dessin après que je l’ai ajouté au DrawingContext lors de OnRender (). Ceci met à jour les commandes de dessin conservées existantes dans l'arbre de dessin WPF et déclenche une nouvelle mise à jour efficace. 

Dans une application simple que j'ai codée à la fois sous Windows.Forms et WPF ( SoundLevelMonitor ), cette méthode a des performances qui s'apparentent empiriquement à celles du dessin immédiat OnPaint () GDI.

Je pense que WPF a fait une panne de service en appelant la méthode OnRender (), on pourrait plutôt l'appeler AccumulateDrawingObjects()

Cela ressemble fondamentalement à:

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

J'ai également essayé d'utiliser RenderTargetBitmap et WriteableBitmap, tous deux sur un Image.Source, et écrits directement sur un DrawingContext. La méthode ci-dessus est plus rapide. 

0
David Jeske

Dans les formes de fenêtres, ce genre de choses m'a fait tomber;

  • Set Visible = False pour le conteneur de niveau le plus élevé (par exemple, le canevas du formulaire lui-même)
  • Dessiner beaucoup
  • Set visible = true

Pas sûr que WPF le supporte. 

0
IvoTops