C# : System.Drawing et performances...

C# : System.Drawing et performances... - C#/.NET managed - Programmation

Marsh Posté le 11-04-2006 à 21:24:58    

Salut, j'ai un "léger" souci de performances avec System.Drawing
 
Bon, ok, je cherche le diable à bosser en plein écran sur mon 24" mais bon...
 
Le but du jeu, c'est d'avoir à l'écran un truc fluide à l'écran, et sans clignotements.
 
J'ai suivi cet article (cet section "Double Buffering Technique The .NET Way" ) afin de virer les clignottements. Ca marche bien, et autant c'est rapide et sans consommation CPU dans une fenêtre de la taille par défaut, autant quand je passe en plein écran... 100% CPU sur un AMD 3500+ et ça oublie de dessiner toutes les 10ms... C'est plutôt toutes les 20/30ms...
 
(ok, je laisserai peut-être pas un ticker de 10ms, ça sert pas à grand chose maintenant que les gens sont pour la plupart en CRT à 60 ou 75Hz max)
 
Après tests (viré tous mes éléments à l'affichage sauf 1, et shooté tous mes traîtements pour remplacer par un déplacement d'une image de 10x10 de façon rectiligne), ça ramme toujours à donf.
Il semble donc que ce soit le coup du "DrawImage" de mon image tampon qui fout tout à plat. D'après l'article, c'est censé être performant, et malgré certaines optimisations que j'ai tenté d'apporter, ça ramme toujours autant.
 
D'après l'auteur, passer par la lib Win32 est plus lent, et surtout c'est plus gore, donc j'ai pas testé (et aucune envie de le faire).
 
Y'a un bout que j'ai mal recopié ? Ou des méga-conneries dans mon code et j'en suis pas conscient ?
L'article date de 2002... Je suppose que depuis, ça a dû évoluer... Il doit se baser sur la version 1.0 du FrameWork, et on en est à la 2.0... Y'a une meilleure solution qui a vu le jour depuis ?
 
Voici les bouts de mon code qui sont concernés :
 

Code :
  1. /* Classe Form1 */
  2. private void Form1_Load(object sender, EventArgs ea)
  3. {
  4.     e = new Engine(this.CreateGraphics());
  5.     e.size = this.Size;
  6.     launch();
  7.     t = new Timer();
  8.     t.Interval = 10;
  9.     t.Tick +=new EventHandler(Ticker);
  10.     t.Start();
  11. }
  12. public void launch()
  13. {
  14.     s1 = new AntSprite(new PointF(0f, 0f), 0);
  15.     e.addItem(s1);
  16. }
  17. private void Form1_ReSize(object sender, EventArgs ea)
  18. {
  19.     e.size = this.Size;
  20.     e.g = this.CreateGraphics();
  21. }
  22. private void Ticker(object sender, EventArgs ea)
  23. {
  24.     e.PreRender();
  25.     PointF pos = s1.position;
  26.     pos.X += .1f;
  27.     pos.Y += .05f;
  28.     s1.position = pos;
  29. //    s1.direction = (s1.direction + (Math.PI / 360)) % (Math.PI * 2);
  30.     e.Render();
  31. }
  32. /* Classe AntSprite */
  33. public class AntSprite : EngineItem
  34. {
  35.     public AntSprite(PointF position, double direction)
  36.     {
  37.         this.position = position;
  38.         this.direction = direction;
  39.         System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
  40.         resources.ApplyResources(this, "$this" );
  41.         this.picture = (Image)resources.GetObject("ant" );
  42.     }
  43. }
  44. /* Classe EngineItem */
  45. public abstract class EngineItem
  46. {
  47.     /// <summary>
  48.     /// This abstract class represents an item to draw on the 2D Engine
  49.     /// </summary>
  50.     /// <remarks>Direction is a radians angle</remarks>
  51.     public PointF position;
  52.     public double direction;
  53.     public Image picture;
  54.     /// <summary>
  55.     /// Render the item on the 2D Engine.
  56.     /// </summary>
  57.     /// <param name="graphics">Graphics of the control to fill with the item</param>
  58.     public void Render(Graphics graphics)
  59.     {
  60.         graphics.DrawImage(this.picture, new PointF((this.position.X - ((float)this.picture.Width / 2f)), (this.position.Y - ((float)this.picture.Height / 2f))));
  61.         if (this.direction >= 0)
  62.         {
  63.             graphics.DrawLine(Pens.Red, this.position, new PointF(this.position.X + 10f * (float)Math.Cos(this.direction - (Math.PI / 4d)), this.position.Y + 10f * (float)Math.Sin(this.direction - (Math.PI / 4d))));
  64.             graphics.DrawLine(Pens.Red, this.position, new PointF(this.position.X + 10f * (float)Math.Cos(this.direction + (Math.PI / 4d)), this.position.Y + 10f * (float)Math.Sin(this.direction + (Math.PI / 4d))));
  65.         }
  66.     }
  67. }
  68. /* Classe Engine */
  69.     public class Engine
  70.     {
  71.         private Graphics _g;
  72.         private Graphics _offScreenG;
  73.         private Bitmap _bmp;
  74.         private List<EngineItem> _items = new List<EngineItem>();
  75.         public Size size
  76.         {
  77.             set
  78.             {
  79.                 this._bmp = new Bitmap(value.Width, value.Height);
  80.                 this._offScreenG = Graphics.FromImage(this._bmp);
  81.                 this._offScreenG.Clear(Color.Wheat);
  82.             }
  83.         }
  84.         public Graphics g
  85.         {
  86.             get
  87.             {
  88.                 return this._g;
  89.             }
  90.             set
  91.             {
  92.                 this._g = value;
  93.             }
  94.         }
  95.         public Engine(Graphics g)
  96.         {
  97.             this._g = g;
  98.         }
  99.         ~Engine()
  100.         {
  101.             this._offScreenG.Dispose();
  102.             this._offScreenG = null;
  103.             this._bmp.Dispose();
  104.             this._bmp = null;
  105.             this._items.RemoveRange(0, this._items.Count);
  106.             this._g.Dispose();
  107.             this._g = null;
  108.         }
  109.         public void PreRender()
  110.         {
  111.             EngineItem ei = this._items[0];
  112.             this._offScreenG.Clip = new Region(new RectangleF(new PointF(ei.position.X - 15f, ei.position.Y - 15f), new SizeF(30f, 30f)));
  113.             this._offScreenG.Clear(Color.Wheat);
  114. /*
  115.             foreach (EngineItem ei in this._items)
  116.             {
  117.                 this._offScreenG.Clip = new Region(new RectangleF(new PointF(ei.position.X - 15f, ei.position.Y - 15f), new SizeF(30f, 30f)));
  118.                 this._offScreenG.Clear(Color.Wheat);
  119.             }
  120. */
  121.         }
  122.         public void Render()
  123.         {
  124.             this._offScreenG.ResetClip();
  125.             this._items[0].Render(this._offScreenG);
  126. /*
  127.             foreach (EngineItem ei in this._items)
  128.             {
  129.                 ei.Render(this._offScreenG);
  130.             }
  131. */
  132.             this._g.DrawImage(this._bmp, 0, 0);
  133.         }
  134.         public void addItem(EngineItem item)
  135.         {
  136.             this._items.Add(item);
  137.         }
  138.         public void delItem(EngineItem item)
  139.         {
  140.             this._items.Remove(item);
  141.         }
  142.     }


 
En bref, plutôt que de recréer un Graphics new à chaque ittération du "Render", je lui efface les zones des anciens sprites dans le "PreRender" via des Clip, puis je redessine les sprites une fois qu'ils ont bougé.
Ceci dit, ça ne change rien :D
A priori, c'est bien la recopie du gros Bitmap temporaire qui plombe tout.


Message édité par Arjuna le 11-04-2006 à 21:30:12
Reply

Marsh Posté le 11-04-2006 à 21:24:58   

Reply

Marsh Posté le 12-04-2006 à 07:52:31    

Bon ben euh ils font pareil... :sweat:
 
http://www.codeproject.com/cs/medi [...] rawing.asp

Reply

Marsh Posté le 12-04-2006 à 09:14:51    

T'as essayé de profiler pour voir ?

Reply

Marsh Posté le 12-04-2006 à 09:37:39    

Euh... :??: :D

Reply

Marsh Posté le 12-04-2006 à 09:41:18    

Reply

Marsh Posté le 12-04-2006 à 10:42:37    

Ouais, mais c'est quoi au juste ?
 
J'ai VS 2005 Team Suite Tester / (ou Developer) chais pas au juste
-> Et j'ai tout un tas d'assistants qui me permettent d'auditer mon code pour voir s'il est rapide, bien écrit et tout ça. C'est la même chose ou ça n'a rien à voir ? En fait, je bute sur le mot "profiling" ;)

Reply

Marsh Posté le 12-04-2006 à 13:14:46    

Genre tu lances nProf, t'ouvres ton .exe, t'exécute normalement ton machin, puis tu quitte. Là il t'affichera tout le déroulement de ton prog, avec le temps pris par chaque fonction. Comme ça tu pourra voir laquelle est celle qui prend le plus de temps

Reply

Marsh Posté le 12-04-2006 à 14:27:25    

ben c'est tout vu, c'est le "drawimage" qui recopie le bitmap du double buffer dans le graphics de ma form. mes tests confirment ce point à 100%, aucun doute possible.
 
mais c'est justement là le problème : comment réduire ce temps au minimum possible ?
 
je peux modifier en tentant de jouer avec la zone à redessiner, mais rapidement les éléments qui bougent vont prendre toute la fenêtre, donc à la base ça va plus alourdir les traîtements qu'autrechose, pour un gain vraiment faible...

Reply

Marsh Posté le 12-04-2006 à 15:08:24    

Reply

Marsh Posté le 12-04-2006 à 15:08:37    

J'ai failli le proposer :D

Reply

Marsh Posté le 12-04-2006 à 15:08:37   

Reply

Marsh Posté le 12-04-2006 à 15:08:54    

Sinon ch'ais pas, en utilisant l'évènement OnPaint plutôt :??:

Reply

Marsh Posté le 12-04-2006 à 16:35:52    


DirectDraw n'existe plus, et c'est remplacé par GDI+ :o
 
Et je vais pas me repalucher un moteur 3D pour faire de la 2D :o

Reply

Marsh Posté le 12-04-2006 à 16:37:28    

FlorentG a écrit :

Sinon ch'ais pas, en utilisant l'évènement OnPaint plutôt :??:


Tu ferais ça comment ? Parceque là, moi j'ai pas d'idée, à la base il se lance au moment du render, mais pas si je fais pas un invalidate() manuel (si je vais pas le render)
Et le invalidate() ça fait encore pire : non seulement ça ramme, mais ça fait du flickering même avec le double buffer :o

Reply

Marsh Posté le 12-04-2006 à 20:46:53    

Pour en revenir au problème de performances, j'ai donc pas pris "nProf", puisque VS 2005 a déjà tout ce qu'il faut.
 
Voici le résultat (grosse n'image) :
http://www.manga-torii.com/images/performance.PNG
 
Donc, comme je disais, en "exclusive", l'EXE principal bouffe seulement... 0% de l'occupation CPU globale, et le moteur 2D 1,768%.
Par contre, en "inclusive", on voit bien en effet que le moteur 2D bouffe 83,929%.
 
Quand je rentre dans le détail, et que je regarde les fonctions appelée/appelantes, c'est bien le "DrawImage" qui est directement dans "Engine.Render()" qui bouffe 66,667% alors que les multiples appels dans "EngineItem.Render()" ne bouffent en tout que 9% environ...
 
J'ai donc bien une couille dans le potage avec cette histoire de double buffer : autant la génération de l'image "shadow" est rapide et ne pose pas de problème de performances, autant la simple recopie de cette image dans le Graphics de ma forme fait tout s'effondrer.
 
A noter que j'ai fait le test en mode fenêtré, avec une petite fenêtre. En plein écran ça doit être largement pire/flagrant étant donné que ça arrive à ralentir toute l'application.

Reply

Marsh Posté le 12-04-2006 à 21:31:31    

P'tain si j'avais le temps, j'aurais donné un essai :( Peut-être demain soir ou vendredi j'peux essayer d'essayer...

Reply

Marsh Posté le 12-04-2006 à 23:33:53    

C'est pas un problème. Merci si tu peux me filer un coup de main, mais sinon c'est pas grave ;)
 
Là je viens de tout essayer, pas moyen de trouver ce qui cloche.
 
Une âme charitable m'a filé les sources d'un moteur 2D basé sur D3D. D'expérience, parceque j'avais déjà bidouillé un peu avec D3D, ça va solutionner mon problème. Mais j'aurais bien voulu trouver ce qui déconnait avec GDI, c'est pas normal que ça ramme comme ça.
 
Je crois que j'ai tout tenté là, pas moyen de trouver ce qui cloche. J'ai clippé tout ce que je pouvais, boundé comme un malade, jusqu'à ce que plus rien ne marche... Mais que pouic, ça n'a rien changé.
 
J'ai suivi à la lettre les warning d'audit du code de VS... Au point d'avoir une application ne voulant plus démarrer faute de permissions... J'ai quasiment plus aucun "new" de goret inutile (genre un new d'un Region -type extrêment gourmand en mémoire- dans un while). Bref, tout mon code est à priori optimisé au maximum de ce que je pouvais faire, mais j'ai pas vu un pouillème de différence :D
 
(bon, je vous rassure j'ai pas passé toute la soirée dessus... 2 heures max, c'est déjà trop vu le résultat :D)

Reply

Marsh Posté le 12-04-2006 à 23:40:25    

Si tu veux partir de ce que j'ai fais pour trouver ce qui cloche...
 
http://www.manga-torii.com/images/ant.zip (1,1 Mo - projet complet, programme + moteur 2D. J'ai pas fait de nettoyage)
-- Je viens de voir que j'aurais pu virer mon "Visual Studio Performance Report" avant de zipper... Il fait 10 Mo :D
 
T'étonne pas du truc, à la base c'est pour faire un "simulateur de fourmis", genre 1000 trucs qui se balladent à l'écran et cherchent quelquechose puis retournent d'où ils viennent avec, afin de me familiariser avec différentes techniques de findingpath et de gestion des collisions.
 
Eh oui, le but du jeu c'était pas de me prendre la tête avec GDI... Mais si ça ramme avant même de faire le moindre bout d'algo de FindingPath, c'est même pas la peine :o


Message édité par Arjuna le 12-04-2006 à 23:42:33
Reply

Marsh Posté le 04-05-2006 à 00:10:00    

tu le compiles en mode debug ?  
Parce que j'ai déja remarqué d'énorme différence si on utilise le mode debug ( c'est beaucoup beaucoup plus lent :D )

Reply

Marsh Posté le 04-05-2006 à 12:03:39    

Ben mes tests étaient avec VS Express. Et j'ai pas trouvé comment compiler en release avec cette version.
 
Ce soir, j'aurai enfin les DVD MSDN qu'on attend depuis 3 mois, verrai ce que ça donne avec.

Reply

Marsh Posté le 22-02-2008 à 16:41:39    

Bonjour,
 
j'ai le même problème et je me suis rendu compte que la technique du double buffering était inutile dans mon cas également.
Après de nombreux tests et avoir logger les temps d'éxécution des méthodes, j'ai l'impression qu'il y a un temps d'attente incomprésible lors du premier appel à des fonctions tels que DrawImage (probableùment du à l'initialisation de composant ou chargement de DLL).
 
J'appele plusieurs fois la méthode Drawimage avec la même image (toujours sur le même fond) et à la même position : premier appel de 0,1 à 0,2 s tous les suivants (une centaine) moins de : 0,0001 s.  
 
Ce que je ne comprend pas c'est que si je fais une boucle par dessus ces appels, je me tape pour chaque boucle cet appel 0,1 à 0,2 s suivi d'une centaine de 0,0001 s !

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed