Optimisations 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 sur 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 du GPU sont légèrement différentes de la situation sur le CPU, car souvent vous ne pouvez modifier les performances qu'indirectement, en changeant les instructions que vous donnez au GPU, et il peut être plus difficile de prendre des mesures. Souvent, la seule façon de mesurer les performances est d'examiner les changements de fréquence d'images.

Drawcalls, 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, GLES2, GLES3, Vulkan). La communication et l'activité du pilote qui en découle peuvent être assez coûteuses, surtout dans OpenGL. 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. C'est pourquoi le jeu est de réduire ces instructions au strict minimum, et de regrouper autant que possible les objets similaires afin qu'ils puissent être rendus ensemble, ou avec le minimum de ces coûteux changements d'état.

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 en 2d est utilisé : plusieurs éléments similaires sont regroupé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 regrouper 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 d'un coût prohibitif. Le coût de leur assemblage dépasse rapidement les avantages éventuels, car le nombre de triangles par maille augmente. 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.

Le traitement par lots d'objets en 3d a également un coût. 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. Pour tenter de regrouper des objets en 3D, il faut donc tenir compte de leur emplacement et de leur effet sur l'occlusion. 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 mais, 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 ou dans le pire des cas, utilisez des atlas.
  • 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, donc chaque image tous les sommets :

  1. A dû être transformé par le CPU (y compris le clipping).
  2. Doit être envoyé à la mémoire du GPU depuis la RAM principale.

De nos jours, tout cela est géré à l'intérieur du GPU, donc les performances sont extrêmement élevées. 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 minuscule batterie, ils doivent donc être beaucoup plus économes en énergie.

Pour être plus efficaces, les GPU mobiles tentent d'éviter les overdraw. Cela signifie que 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).

Sur mobile, tirer plus de puissance n'est pas une option, donc une technique appelée "Tile Based Rendering" est utilisée, qui divise l'écran en grille. Chaque cellule conserve la liste des triangles qui y sont dessinés 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 soit terminée pour pouvoir afficher l'image.

Pour faire court, ne vous inquiétez pas trop du nombre de sommet sur mobile, mais évitez de concentrer les sommets sur de petites parties de l'écran. Si, par exemple, 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).

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 - taux de remplissage

Contrairement au traitement des sommets, le coût des sahders de fragments 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 vsync pour éviter de limiter le nombre d'images par seconde, puis comparez les images par seconde lorsque vous utilisez une grande fenêtre, à celles d'une fenêtre de la taille d'un timbre-poste (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 le nombre d'images par seconde augmente 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, le nombre d'images par seconde augmente peu ou pas du tout, alors votre goulot d'étranglement est ailleurs.

Vous pouvez augmenter les performances dans un projet avec 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.

Envisagez d'utiliser des shaders plus simples pour les mobiles.

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), et il faut également tenir compte du fait que le filtrage peut ajouter des frais à cette opération (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.

Compression de texture

Godot compresse les textures des modèles 3D lorsqu'il sont (compression VRAM) par défaut. 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.

Post-traitement / 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érents matériels.

La réduction de la taille des cartes des ombres peut améliorer les performances, tant en termes d'écriture que de lecture des cartes.

Transparence / mélange

Les éléments 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 visibles 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'élément qui se trouve à l'avant d'un pixel particulier.

La transparence est particulièrement coûteuse lorsque plusieurs éléments 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.

Rendus mobile / tuile

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 de tuiles. Ces derniers divisent l'écran en tuiles de taille normale 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, car certaines techniques peuvent être beaucoup plus compliquées et coûteuses à mettre en œuvre. Les tuiles qui reposent sur les résultats du rendu de différentes tuiles ou sur la préservation des résultats d'opérations antérieures peuvent être très lentes. Soyez très attentifs à tester les performances des shaders, des textures de viewport et du post-traitement.