Optimisation GPU

Introduction

La demande de nouvelles fonctionnalités graphiques et de progrès garantit presque que vous rencontrerez des goulots d'étranglement graphiques. Certains d'entre eux peuvent être du côté du CPU, par exemple dans les calculs à l'intérieur du moteur Godot pour préparer les objets pour le rendu. Des goulots d'étranglement peuvent également se produire dans le CPU dans le pilote graphique, qui trie les instructions à transmettre au GPU, et dans le transfert de ces instructions. Et enfin, des goulots d'étranglement se produisent également dans le GPU lui-même.

Les goulets d'étranglement dans le rendu sont très spécifiques au matériel. Les GPU mobiles en particulier peuvent avoir du mal à rendre des scènes qui s'exécutent facilement sur un ordinateur de bureau.

La compréhension et l'étude des goulets d'étranglement GPU sont légèrement différentes de la situation du CPU. En effet, souvent, vous ne pouvez modifier les performances qu'indirectement, en changeant les instructions que vous donnez au GPU. De plus, il peut être plus difficile de prendre des mesures. Dans de nombreux cas, la seule façon de mesurer les performances est d'examiner les changements dans le temps passé à rendre chaque image.

Draw calls, les changements d'état et APIs

Note

La section suivante ne concerne pas les utilisateurs finaux, mais elle est utile pour fournir des informations générales qui seront utiles dans les sections suivantes.

Godot envoie des instructions au GPU via une API graphique (OpenGL, OpenGL ES or Vulkan). La communication et l'activité du pilote qui en découle peuvent être assez coûteuses, surtout dans OpenGL and OpenGL ES. Si nous pouvons fournir ces instructions de la manière préférée par le pilote et le GPU, nous pouvons considérablement augmenter les performances.

Presque chaque commande API dans OpenGL nécessite une certaine quantité de validation, pour s'assurer que le GPU est dans le bon état. Même des commandes apparemment simples peuvent entraîner une avalanche de tâches internes en coulisses. Par conséquent, l'objectif est de réduire ces instructions au strict minimum et de grouper autant que possible les objets similaires afin qu'ils puissent être rendus ensemble, ou avec le minimum de ces changements d'état coûteux.

Traitement 2D par lots

En 2D, les coûts de traitement de chaque élément individuellement peuvent être prohibitifs - il peut facilement y en avoir des milliers à l'écran. C'est la raison pour laquelle le traitement par lots 2D est utilisé. Plusieurs éléments similaires sont groupés et rendus en un lot, par un seul drawcall, plutôt que de faire un drawcall distinct pour chaque élément. En outre, cela signifie que les changements d'état, de matériau, et de texture peuvent être réduits au minimum.

Pour plus d'informations sur le traitement 2D par lots, voir Optimisation à l'aide de traitement par lots.

Traitement 3D par lots

En 3D, nous visons toujours à minimiser les draw calls et les changements d'état. Cependant, il peut être plus difficile de grouper plusieurs objets en un seul draw call. Les maillages 3D ont tendance à comprendre des centaines ou des milliers de triangles, et combiner de grands maillages à l'exécution est un coût prohibitif. Les coûts de leur assemblage dépassent rapidement tous les avantages à mesure que le nombre de triangles augmente par maille. Une bien meilleure solution consiste à joindre les mailles à l'avance (mailles statiques les unes par rapport aux autres). Cela peut être fait soit par les artistes, soit de manière logiciel dans Godot.

Il y a également un coût au traitement par lots d'objets en 3d. Plusieurs objets rendus comme un seul ne peuvent pas être occultés individuellement. Une ville entière qui est hors écran sera quand même rendue si elle est jointe à un seul brin d'herbe qui est à l'écran. Par conséquent, vous devez prendre en compte l'emplacement des objets et de leur occlusion en compte quand vous tentez de traiter des objets 3D en lot. Malgré cela, les avantages de joindre des objets statiques l'emportent souvent sur d'autres considérations, en particulier pour un grand nombre d'objets à peu de polygones.

Pour plus d'informations sur les optimisations spécifiques à la 3D, voir Optimiser les performances 3D.

Réutilisation des Shaders et des Materials

Le moteur de rendu de Godot est un peu différent de ce qu'y existe ailleurs. Il est conçu pour minimiser autant que possible les changements d'état du GPU. SpatialMaterial fait un bon travail pour réutiliser les matériaux qui nécessitent des shaders similaires. Si des shaders personnalisés sont utilisés, assurez-vous de les réutiliser autant que possible. Les priorités de Godot sont :

  • Réutilisation des matériaux : Moins il y a de matériaux différents dans la scène, plus le rendu sera rapide. Si une scène a une quantité énorme d'objets (des centaines ou des milliers), essayez de réutiliser les matériaux. Dans le pire des cas, utilisez des atlas pour réduire la quantité de changements de texture.

  • **Reusing Shaders : ** Si les matériaux ne peuvent pas être réutilisés, essayez au moins de réutiliser les shaders (ou des SpatialMaterials avec des paramètres différents mais avec la même configuration).

Si une scène a, par exemple, 20,000 objets avec 20,000 matériaux différents chacun, le rendu sera lent. Si la même scène contient 20,000 objets, mais n'utilise que 100 matériaux, le rendu sera beaucoup plus rapide.

Coût en pixel contre coût en sommet

Vous avez peut-être entendu dire que plus le nombre de polygones dans un modèle est faible, plus le rendu est rapide. C'est vraiment relatif et cela dépend de nombreux facteurs.

Sur un PC et une console modernes, le coût du sommet est faible. À l'origine, les GPU ne rendaient que les triangles. Cela signifie que chaque image :

  1. Tous les sommets ont dû être transformés par le CPU (y compris le clipping).

  2. Tous les sommets ont dû être envoyés à la mémoire GPU depuis la RAM principale.

De nos jours, tout cela est géré à l'intérieur du GPU, augmentant grandement les performances. Les artistes 3D ont généralement le mauvais sentiment au sujet de la performance du nombre de polygone parce que les DCC 3D (tels que Blender, Max, etc.) ont besoin de garder la géométrie dans la mémoire CPU pour qu'elle puisse être éditée, réduisant ainsi la performance réelle. Les moteurs de jeu font davantage appel au GPU, ce qui leur permet de rendre de nombreux triangles de manière beaucoup plus efficace.

Sur les appareils mobiles, l'histoire est différente. Les GPU PC et Console sont des monstres de force brute qui peuvent tirer autant d'électricité qu'ils en ont besoin du réseau électrique. Les GPU mobiles sont limités à une petite batterie, ils doivent donc être beaucoup plus économes en énergie.

Pour être plus efficaces, les GPU mobiles tentent d'éviter l'overdraw. Overdraw arrive quand le même pixel à l'écran est rendu plus d'une fois. Imaginez une ville avec plusieurs bâtiments, les GPU ne savent pas ce qui est visible et ce qui est caché jusqu'à ce qu'ils le dessinent. Une maison peut être dessinée, puis une autre maison devant elle (le rendu a eu lieu deux fois pour le même pixel !). Les GPU PC ne s'en soucient normalement pas beaucoup et se contentent d'ajouter des processeurs de pixels au hardware pour augmenter les performances (mais cela augmente aussi la consommation d'énergie).

L’utilisation de plus d’énergie n’est pas une option sur mobile, donc les appareils mobiles utilisent une technique appelée tile-based rendering qui divise l’écran en grille. Chaque cellule conserve la liste des triangles qui y sont dessinés dessus et les trie par profondeur pour minimiser l'overdraw. Cette technique améliore les performances et réduit la consommation d'énergie, mais a un impact sur les performances des sommets. Par conséquent, moins de sommets et de triangles peuvent être traités pour le dessin.

De plus, le tile-based rendering a des difficultés quand il y a de petits objets avec beaucoup de géométrie dans une petite partie de l'écran. Cela oblige les GPU mobiles à mettre beaucoup de pression sur une seule cellule de l'écran, ce qui diminue considérablement les performances car toutes les autres cellules doivent attendre qu'elle termine pour pouvoir afficher l'image.

Pour faire court, ne vous inquiétez pas trop du nombre de sommet sur mobile, mais évitez les concentration de sommet sur de petites parties de l'écran. Si, un personnage, un PNJ, un véhicule, etc. est éloigné (donc il semble minuscule), utilisez plutôt un modèle à plus petit niveau de détail (LOD). Même sur les GPU de bureau, il est préférable d’éviter d’avoir des triangles plus petits que la taille d’un pixel à l’écran.

Faites attention au traitement supplémentaire des vertex requis lors de l'utilisation :

  • Skinning (animation squelettique)

  • Morphs (clés de forme)

  • Objets éclairés par les sommets (communs sur mobile)

Pixel/fragment shaders et taux de remplissage

Contrairement au traitement des sommets, le coût des sahders de fragments (par-pixel) a augmenté de façon spectaculaire au fil des ans. Les résolutions d'écran ont augmenté (la surface d'un écran 4K est de 8,294,400 pixels, contre 307,200 pixels pour un ancien écran VGA 640x480, soit 27 fois la surface), mais la complexité des shaders de fragments a également explosé. Le rendu basé sur la physique nécessite des calculs complexes pour chaque fragment.

Vous pouvez vérifier assez facilement si un projet est limité en termes de taux de remplissage. Désactivez V-Sync pour éviter de limiter le nombre d'images par seconde, puis comparez les images par seconde lorsque vous utilisez une grande fenêtre, à celles très petite fenêtre vous pouvez également réduire de la même manière la taille de votre carte des ombres si vous utilisez des ombres. En général, vous constaterez que les FPS augmentent considérablement lorsque vous utilisez une petite fenêtre, ce qui indique que votre taux de remplissage est limité dans une certaine mesure. Si en revanche, les FPS augmentent peu ou pas du tout, alors votre goulot d'étranglement est ailleurs.

Vous pouvez augmenter les performances dans un projet avec un taux de remplissage limité en réduisant la quantité de travail que le GPU doit effectuer. Vous pouvez le faire en simplifiant le shader (peut-être en désactivant les options coûteuses si vous utilisez un SpatialMaterial), ou en réduisant le nombre et la taille des textures utilisées.

Lorsque vous ciblez des appareils mobiles, pensez à utiliser les shaders les plus simples possibles que vous pouvez raisonnablement vous permettre d'utiliser.

Lecture des textures

L'autre facteur qui intervient dans les shaders de fragments est le coût de la lecture des textures. La lecture des textures est une opération coûteuse, en particulier la lecture de plusieurs textures dans un seul fragment shader. De plus, il faut tenir compte du fait que le filtrage peut le ralentir davantage (filtrage trilinéaire entre les mipmaps, et calcul de moyenne). La lecture des textures est également coûteuse en termes de puissance, ce qui est un gros problème sur les mobiles.

Si vous utilisez des shaders tiers ou écrivez vos propres shaders, essayez d'utiliser des algorithmes qui nécessitent le moins de lecture de texture possible.

Compression de texture

Par défaut, Godot compresse les textures des modèles 3D lorsqu'ils sont importés en utilisant la compression de la RAM vidéo (VRAM). La compression Video RAM n'est pas aussi efficace en taille que PNG ou JPG lorsqu'elle est stockée, mais augmente considérablement les performances lors du dessin.

En effet, l'objectif principal de la compression de texture est la réduction de la bande passante entre la mémoire et le GPU.

En 3D, les formes des objets dépendent plus de la géométrie que de la texture, la compression n'est donc généralement pas perceptible. En 2D, la compression dépend davantage des formes à l'intérieur des textures, de sorte que les artefacts résultant de la compression 2D sont plus visibles.

En guise d'avertissement, la plupart des appareils Android ne prennent pas en charge la compression de texture des textures avec transparence (seulement opaque), gardez ceci à l'esprit.

Note

Même en 3D, les textures "pixel art" devraient avoir la compression VRAM désactivée car elle affectera négativement leur apparence, sans améliorer significativement les performances en raison de leur faible résolution.

Post-traitement et ombres

Les effets de post-traitement et les ombres peuvent également être coûteux en termes d'activité des shaders de fragments. Testez toujours l'impact de ces effets sur différent hardware.

Réduire la taille des shadowmaps peut augmenter les performances, tant en termes d'écriture que de lecture des shadowmaps. De plus, la meilleure façon d'améliorer les performances des ombres est de désactiver les ombres pour le plus grand nombre possible de lumières et d'objets. Les OmniLights/SpotLights plus petits ou plus éloignés peuvent souvent voir leurs ombres désactivées avec un faible impact visuel.

Transparence et mélange

Les objets transparents posent des problèmes particuliers d'efficacité de rendu. Les objets opaques (surtout en 3D) peuvent être rendus dans n'importe quel ordre et le tampon Z garantit que seuls les objets les plus à l'avant sont traité par un shader. Les objets transparents ou mélangés sont différents. Dans la plupart des cas, ils ne peuvent pas compter sur le tampon Z et doivent être rendus dans "l'ordre du peintre" (c'est-à-dire de l'arrière vers l'avant) pour avoir une apparence correcte.

Les éléments transparents sont aussi particulièrement mauvais pour le taux de remplissage, car chaque élément doit être dessiné, même si plus tard des éléments transparents seront dessinés par-dessus.

Les objets opaques n'ont pas à faire cela. Ils peuvent généralement tirer parti du tampon Z en écrivant d'abord dans le tampon Z, puis en exécutant le shader de fragment sur le fragment "gagnant", l'objet qui se trouve à l'avant pour un pixel particulier.

La transparence est particulièrement coûteuse lorsque plusieurs objets transparents se chevauchent. Il est généralement préférable d'utiliser une zone transparente aussi petite que possible afin de minimiser ces exigences de taux de remplissage, en particulier sur les téléphones portables, où le taux de remplissage est très coûteux. En effet, dans de nombreuses situations, rendre une géométrie opaque plus complexe peut s'avérer plus rapide que d'utiliser la transparence pour "tricher".

Conseil multi-plateforme

Si vous avez l'intention de publier sur plusieurs plates-formes, testez au début et souvent sur toutes vos plates-formes, en particulier mobiles. Développer un jeu sur un ordinateur de bureau puis essayer de le porter sur le mobile à la dernière minute est une recette pour le désastre.

En général, vous devez concevoir votre jeu pour le plus petit dénominateur commun, puis ajouter des améliorations optionnelles pour des plates-formes plus puissantes. Par exemple, vous pouvez vouloir utiliser le backend GLES2 pour les plateformes de bureau et les plateformes mobiles dans le cas où vous ciblez les deux.

Rendu mobile/tuile

Comme décrit ci-dessus, les GPU des appareils mobiles fonctionnent de manière radicalement différente des GPU des ordinateurs de bureau. La plupart des appareils mobiles utilisent des rendus par tuiles. Ces derniers divisent l'écran en tuiles de taille régulière qui s'intègrent dans une mémoire cache super rapide et réduisent les opérations de lecture et d'écriture dans la mémoire principale.

Il y a cependant quelques inconvénients. Le rendu par tuile rend certaines techniques beaucoup plus compliquées et coûteuses à mettre en œuvre. Les tuiles qui comptent sur les résultats du rendu de différentes tuiles ou sur les résultats d'opérations antérieures qui ont été préservés peuvent être très lents. Soyez très attentifs à tester les performances des shaders, des textures de viewport et du post-traitement.