
Conseils de profilage de performance pour les développeurs de jeux
Une performance fluide est essentielle pour créer des expériences de jeu immersives pour les joueurs. En profilant et en perfectionnant la performance de votre jeu pour un large éventail de plateformes et d'appareils, vous pouvez élargir votre base de joueurs et augmenter vos chances de succès.
Cette page décrit un flux de travail général de profilage pour les développeurs de jeux. Il est extrait de l'e-book, Guide ultime du profilage des jeux Unity, disponible en téléchargement gratuit. L'e-book a été créé par des experts Unity externes et internes en développement de jeux, profilage et optimisation.
Lisez la suite pour en savoir plus sur les objectifs utiles à définir avec le profilage, les goulets d'étranglement de performance courants, tels que le fait d'être limité par le CPU ou le GPU, et comment identifier et examiner ces situations plus en détail.

Définir un budget de trame
Mesurer le taux de trame de votre jeu en images par seconde (fps) n'est pas idéal pour offrir des expériences cohérentes à vos joueurs. Considérez le scénario simplifié suivant :
Pendant l'exécution, votre jeu rend 59 images en 0,75 seconde. Cependant, la prochaine image prend 0,25 seconde à rendre. Le taux de trame moyen de 60 fps semble bon, mais en réalité, les joueurs remarqueront un effet de saccade puisque la dernière image prend un quart de seconde à rendre.
C'est l'une des raisons pour lesquelles il est important de viser un budget de temps spécifique par image. Cela vous donne un objectif solide à atteindre lors du profilage et de l'optimisation de votre jeu, et finalement, cela crée une expérience plus fluide et plus cohérente pour vos joueurs.
Chaque image aura un budget de temps basé sur votre fps cible. Une application visant 30 fps devrait toujours prendre moins de 33,33 ms par image (1000 ms / 30 fps). De même, un objectif de 60 fps laisse 16,66 ms par image (1000 ms / 60 fps).
Vous pouvez dépasser ce budget pendant les séquences non interactives, par exemple, lors de l'affichage des menus UI ou du chargement de scènes, mais pas pendant le gameplay. Même une seule image qui dépasse le budget de temps cible provoquera des saccades.
Remarque: Un taux de rafraîchissement constamment élevé dans les jeux VR est essentiel pour éviter de provoquer des nausées ou de l'inconfort chez les joueurs. Sans cela, vous risquez d'être rejeté par le titulaire de la plateforme lors de la certification de votre jeu.
Images par seconde : Une métrique trompeuse
Une façon courante pour les joueurs de mesurer la performance est avec le taux de rafraîchissement, ou images par seconde. Cependant, il est recommandé d'utiliser le temps d'image en millisecondes à la place. Pour comprendre pourquoi, regardez le graphique ci-dessus de fps par rapport au temps d'image.
Considérez ces chiffres :
1000 ms/sec / 900 fps = 1,111 ms par image
1000 ms/sec / 450 fps = 2,222 ms par image
1000 ms/sec / 60 fps = 16,666 ms par image
1000 ms/sec / 56,25 fps = 17,777 ms par image
Si votre application fonctionne à 900 fps, cela se traduit par un temps d'image de 1,111 millisecondes par image. À 450 fps, cela représente 2,222 millisecondes par image. Cela représente une différence de seulement 1,111 millisecondes par image, même si le taux de rafraîchissement semble diminuer de moitié.
Si vous regardez les différences entre 60 fps et 56,25 fps, cela se traduit par 16,666 millisecondes par image et 17,777 millisecondes par image, respectivement. Cela représente également 1,111 millisecondes supplémentaires par image, mais ici, la baisse du taux de rafraîchissement semble beaucoup moins dramatique en pourcentage.
C'est pourquoi les développeurs utilisent le temps moyen par image pour évaluer la vitesse du jeu plutôt que le fps.
Ne vous inquiétez pas du fps à moins que vous ne tombiez en dessous de votre taux de rafraîchissement cible. Concentrez-vous sur le temps par image pour mesurer la rapidité de votre jeu, puis restez dans votre budget d'images.
Lisez l'article original, "Robert Dunlop’s fps versus frame time," pour plus d'informations.

Défis mobiles
Le contrôle thermique est l'un des domaines les plus importants à optimiser lors du développement d'applications pour appareils mobiles. Si le CPU ou le GPU passent trop de temps à pleine puissance en raison d'une conception inefficace, ces puces vont chauffer. Pour éviter d'endommager les puces (et potentiellement de brûler les mains d'un joueur !), le système d'exploitation réduira la vitesse d'horloge de l'appareil pour lui permettre de refroidir, ce qui provoque des saccades d'images et une mauvaise expérience utilisateur. Cette réduction de performance est connue sous le nom de throttling thermique.
Des taux de rafraîchissement plus élevés et une exécution de code accrue (ou des opérations d'accès DRAM) entraînent une augmentation de la consommation de batterie et de la génération de chaleur. Une mauvaise performance peut également couper des segments entiers des appareils mobiles bas de gamme, ce qui peut entraîner des occasions de marché manquées, et donc, des ventes plus faibles.
Lorsqu'il s'agit de résoudre le problème des thermiques, considérez le budget dont vous disposez comme un budget global du système.
Combattez le throttling thermique et la consommation de batterie en utilisant une technique de profilage précoce pour optimiser votre jeu dès le départ. Ajustez les paramètres de votre projet pour le matériel de votre plateforme cible afin de lutter contre les problèmes de chaleur et de consommation de batterie.
Ajustez les budgets d'images sur mobile
Laisser un temps d'inactivité d'image d'environ 35 % est la recommandation typique pour lutter contre les problèmes thermiques des appareils lors de longues sessions de jeu. Cela donne aux puces mobiles le temps de refroidir et aide à prévenir une consommation excessive de batterie. En utilisant un temps de cadre cible de 33,33 ms par image (pour 30 fps), un budget de cadre typique pour les appareils mobiles sera d'environ 22 ms par image.
Le calcul ressemble à ceci : (1000 ms / 30) * 0.65 = 21.66 ms
Pour atteindre 60 fps sur mobile en utilisant le même calcul, il faudrait un temps de cadre cible de (1000 ms / 60) * 0.65 = 10.83 ms. C'est difficile à réaliser sur de nombreux appareils mobiles et cela viderait la batterie deux fois plus vite que de viser 30 fps. Pour ces raisons, la plupart des jeux mobiles visent 30 fps plutôt que 60. Utilisez Application.targetFrameRate pour contrôler ce paramètre, et consultez la section « Définir un budget de cadre » dans l'e-book pour plus de détails sur le temps de cadre.
L'échelle de fréquence sur les puces mobiles peut rendre difficile l'identification de vos allocations de budget de temps d'inactivité de cadre lors du profilage. Vos améliorations et optimisations peuvent avoir un effet net positif, mais l'appareil mobile pourrait réduire la fréquence, et par conséquent, fonctionner à une température plus basse. Utilisez des outils personnalisés tels que FTrace ou Perfetto pour surveiller les fréquences des puces mobiles, le temps d'inactivité et l'échelle avant et après les optimisations.
Tant que vous restez dans votre budget total de temps de cadre pour votre fps cible (33,33 ms pour 30 fps) et que vous voyez votre appareil travailler moins ou enregistrer des températures plus basses pour maintenir ce taux de trame, alors vous êtes sur la bonne voie.
Une autre raison d'ajouter de la marge au budget de cadre sur les appareils mobiles est de tenir compte des fluctuations de température dans le monde réel. Par une journée chaude, un appareil mobile va chauffer et avoir du mal à dissiper la chaleur, ce qui peut entraîner un throttling thermique et de mauvaises performances de jeu. Mettre de côté un pourcentage du budget de cadre aidera à éviter ce genre de scénarios.

Réduisez les opérations d'accès à la mémoire
L'accès à la DRAM est généralement une opération gourmande en énergie sur les appareils mobiles. Les conseils d'optimisation d'Arm pour le contenu graphique sur les appareils mobiles indiquent que l'accès à la mémoire LPDDR4 coûte environ 100 picojoules par octet.
Réduisez le nombre d'opérations d'accès à la mémoire par image en :
- Réduisant le taux de trame
- Réduisant la résolution d'affichage lorsque cela est possible
- Utilisant des maillages plus simples avec un nombre de sommets réduit et une précision d'attribut réduite
- Utiliser la compression de texture et le mipmapping
Lorsque vous devez vous concentrer sur les appareils utilisant le matériel Arm ou Arm Mali, l'outil Arm Mobile Studio (en particulier, Streamline Performance Analyzer) comprend d'excellents compteurs de performance pour identifier les problèmes de bande passante mémoire. Les compteurs sont listés et expliqués pour chaque génération de GPU Arm, par exemple, Mali-G78. Notez que le profilage GPU de Mobile Studio nécessite Arm Mali.
Établir des niveaux de matériel pour les benchmarks
En plus d'utiliser des outils de profilage spécifiques à la plateforme, établissez des niveaux ou un appareil de spécifications minimales pour chaque plateforme et niveau de qualité que vous souhaitez prendre en charge, puis profilez et optimisez les performances pour chacune de ces spécifications.
Par exemple, si vous ciblez des plateformes mobiles, vous pourriez décider de prendre en charge trois niveaux avec des contrôles de qualité qui activent ou désactivent des fonctionnalités en fonction du matériel cible. Vous optimisez ensuite pour la spécification de l'appareil la plus basse dans chaque niveau. Comme autre exemple, si vous développez un jeu pour PlayStation 4 et PlayStation 5, assurez-vous de profiler sur les deux.
Pour un guide complet d'optimisation mobile, jetez un œil à Optimisez les performances de votre jeu mobile. Ce livre électronique contient de nombreux conseils et astuces qui vous aideront à réduire le throttling thermique et à augmenter la durée de vie de la batterie des appareils mobiles exécutant vos jeux.
Du profilage de haut niveau à celui de bas niveau
Une approche de haut en bas fonctionne bien lors du profilage, en commençant par le profilage approfondi désactivé. Utilisez cette approche de haut niveau pour collecter des données et prendre des notes sur les scénarios qui causent des allocations gérées indésirables ou trop de temps CPU dans les zones de votre boucle de jeu principale.
Vous devrez d'abord rassembler des piles d'appels pour les marqueurs GC.Alloc. Si vous n'êtes pas familier avec ce processus, trouvez des conseils et astuces dans la section "Localiser les allocations de mémoire récurrentes sur la durée de vie de l'application" dans Guide ultime pour le profilage des jeux Unity.
Si les piles d'appels rapportées ne sont pas suffisamment détaillées pour identifier la source des allocations ou d'autres ralentissements, vous pouvez alors effectuer une deuxième session de profilage avec le profilage approfondi activé afin de trouver la source des allocations.
Lorsque vous collectez des notes sur les "contrevenants" du temps de trame, assurez-vous de noter comment ils se comparent par rapport au reste de la trame. Cet impact relatif sera affecté par l'activation du profilage approfondi.
Lisez-en plus sur le profilage approfondi dans Guide ultime pour le profilage des jeux Unity.
Profiler tôt
Les meilleurs gains de profilage sont réalisés lorsque vous commencez tôt dans le cycle de développement de votre projet.
Profilez tôt et souvent afin que vous et votre équipe compreniez et mémorisiez une « signature de performance » pour le projet. Si la performance chute, vous pourrez facilement repérer quand les choses tournent mal et remédier au problème.
Les résultats de profilage les plus précis proviennent toujours de l'exécution et du profilage des builds sur des appareils cibles, en utilisant des outils spécifiques à la plateforme pour examiner les caractéristiques matérielles de chaque plateforme. Cette combinaison vous fournira une vue d'ensemble de la performance de l'application sur tous vos appareils cibles.

Identifier les problèmes de performance
Téléchargez la version PDF imprimable de ce tableau ici.
Sur certaines plateformes, il est facile de déterminer si votre application est limitée par le CPU ou le GPU. Par exemple, lors de l'exécution d'un jeu iOS depuis Xcode, le panneau fps affiche un graphique à barres avec le temps total CPU et GPU afin que vous puissiez voir lequel est le plus élevé. Le temps CPU inclut le temps passé à attendre le VSync, qui est toujours activé sur les appareils mobiles.
Cependant, sur certaines plateformes, il peut être difficile d'obtenir des données de timing GPU. Heureusement, le Profiler Unity montre suffisamment d'informations pour identifier l'emplacement des goulets d'étranglement de performance. Le diagramme de flux ci-dessus illustre le processus de profilage initial, les sections qui le suivent fournissant des informations détaillées sur chaque étape. Ils présentent également des captures de Profiler provenant de projets Unity réels pour illustrer les types de choses à rechercher.
Pour obtenir une image globale de toute l'activité CPU, y compris lorsqu'elle attend le GPU, utilisez la vue Timeline<1>} dans le module CPU du Profiler. Familiarisez-vous avec les marqueurs Profiler courants<2>} pour interpréter correctement les captures. Certains des marqueurs Profiler peuvent apparaître différemment selon votre plateforme cible, alors passez du temps à explorer les captures de votre jeu sur chacune de vos plateformes cibles pour vous faire une idée de ce à quoi ressemble une capture « normale » pour votre projet.
- Si le temps d'image CPU (hors VSync) est de 25 ms et le temps GPU est de 20 ms, pas de problème ! Vous êtes limité par le CPU, mais tout est dans le budget, et optimiser les choses n'améliorera pas le taux de rafraîchissement (à moins que vous ne fassiez descendre à la fois le CPU et le GPU en dessous de 16,66 ms et que vous passiez à 60 fps).
- Si le temps de trame du CPU est de 40 ms et celui du GPU est de 20 ms, vous êtes limité par le CPU et devrez optimiser les performances du CPU. Optimiser les performances du GPU n'aidera pas ; en fait, vous voudrez peut-être déplacer une partie du travail du CPU vers le GPU, par exemple, en utilisant des shaders de calcul au lieu de code C# pour certaines choses, afin d'équilibrer les choses.
- Si le temps de trame du CPU est de 20 ms et celui du GPU est de 40 ms, vous êtes limité par le GPU et devez optimiser le travail du GPU.
- Si le CPU et le GPU sont tous deux à 40 ms, vous êtes limité par les deux et devrez optimiser les deux en dessous de 33,33 ms pour atteindre 30 fps.
Voir ces ressources qui explorent davantage le fait d'être limité par le CPU ou le GPU :

Êtes-vous dans le budget ?
Profiler et optimiser votre projet tôt et souvent tout au long du développement vous aidera à vous assurer que tous les threads CPU de votre application et le temps de trame global du GPU sont dans le budget de trame.
Ci-dessus se trouve une image d'une capture de Profiler d'un jeu mobile Unity développé par une équipe qui a effectué un profilage et une optimisation continus. Le jeu cible 60 fps sur des téléphones mobiles haut de gamme, et 30 fps sur des téléphones de milieu/bas de gamme, comme celui de cette capture.
Notez comment près de la moitié du temps sur la trame sélectionnée est occupée par le marqueur Profiler jaune WaitForTargetfps. L'application a défini Application.targetFrameRate à 30 fps, et VSync est activé. Le travail de traitement réel sur le thread principal se termine autour de la marque de 19 ms, et le reste du temps est passé à attendre que le reste des 33,33 ms s'écoule avant de commencer la prochaine trame. Bien que ce temps soit représenté par un marqueur Profiler, le thread principal du CPU est essentiellement inactif pendant ce temps, permettant au CPU de refroidir et utilisant un minimum d'énergie de la batterie.
Le marqueur à surveiller pourrait être différent sur d'autres plateformes ou si VSync est désactivé. L'important est de vérifier si le thread principal fonctionne dans votre budget de trame ou exactement sur votre budget de trame, avec une sorte de marqueur qui indique que l'application attend VSync et si les autres threads ont un temps d'inactivité.
Le temps d'inactivité est représenté par des marqueurs Profiler gris ou jaunes. La capture d'écran ci-dessus montre que le thread de rendu est inactif dans Gfx.WaitForGfxCommandsFromMainThread, ce qui indique les moments où il a fini d'envoyer des appels de dessin au GPU sur une trame, et attend plus de demandes d'appels de dessin du CPU sur la suivante. De même, bien que le thread Job Worker 0 passe un certain temps dans Canvas.GeometryJob, la plupart du temps il est inactif. Ce sont tous des signes d'une application qui est confortablement dans le budget d'images.
Si votre jeu est dans le budget d'images
Si vous êtes dans le budget d'images, y compris les ajustements apportés au budget pour tenir compte de l'utilisation de la batterie et du throttling thermique, vous avez terminé le profilage des performances jusqu'à la prochaine fois - félicitations. Envisagez d'exécuter le Profilage de la mémoire pour vous assurer que l'application est également dans son budget mémoire.
L'image ci-dessus montre un jeu fonctionnant confortablement dans le budget d'images d'environ 22 ms requis pour 30 fps. Notez le WaitForTargetfps qui remplit le temps du thread principal jusqu'à VSync et les temps d'inactivité gris dans le thread de rendu et le thread de travail. Notez également que l'intervalle VBlank peut être observé en regardant les temps de fin de Gfx.Present image par image, et que vous pouvez établir une échelle de temps dans la zone de la chronologie ou sur la règle de temps en haut pour mesurer d'un à l'autre.

Limité par le CPU
Si votre jeu n'est pas dans le budget d'images du CPU, la prochaine étape consiste à enquêter sur quelle partie du CPU est le goulet d'étranglement - en d'autres termes, quel thread est le plus occupé. Le but du profilage est d'identifier les goulets d'étranglement comme cibles pour l'optimisation ; si vous vous fiez à des suppositions, vous pouvez finir par optimiser des parties du jeu qui ne sont pas des goulets d'étranglement, ce qui entraîne peu ou pas d'amélioration des performances globales. Certaines "optimisations" pourraient même aggraver les performances globales de votre jeu.
Il est rare que la charge de travail totale du CPU soit le goulet d'étranglement. Les CPU modernes ont un certain nombre de cœurs différents, capables d'effectuer des travaux de manière indépendante et simultanée. Différents threads peuvent s'exécuter sur chaque cœur de CPU. Une application Unity complète utilise une gamme de threads à des fins différentes, mais les threads qui sont les plus courants pour trouver des problèmes de performance sont :
- Le thread principal : C'est là que toute la logique du jeu/scripts effectuent leur travail par défaut et où la majorité du temps est passé pour des fonctionnalités et des systèmes tels que la physique, l'animation, l'UI et le rendu.
- Le thread de rendu : Pendant le processus de rendu, le thread principal examine la scène et effectue le culling de la caméra, le tri de profondeur et le regroupement des appels de dessin, ce qui donne une liste de choses à rendre. Cette liste est transmise au thread de rendu, qui la traduit de la représentation interne agnostique de la plateforme de Unity aux appels d'API graphiques spécifiques nécessaires pour instruire le GPU sur une plateforme particulière.
- Les threads de travail du Job: Les développeurs peuvent utiliser le Système de Job C# pour planifier certains types de travail à exécuter sur des threads de travail, ce qui réduit la charge de travail sur le thread principal. Certains des systèmes et fonctionnalités de Unity utilisent également le système de job, tels que la physique, l'animation et le rendu.
Thread principal
L'image ci-dessus montre à quoi cela pourrait ressembler dans un projet qui est limité par le thread principal. Ce projet fonctionne sur un Meta Quest 2, qui cible normalement des budgets de trame de 13,88 ms (72 fps) ou même 8,33 ms (120 fps), car des taux de trame élevés sont importants pour éviter le mal des transports dans les appareils VR. Cependant, même si ce jeu visait 30 fps, il est clair que ce projet est en difficulté.
Bien que le thread de rendu et les threads de travail ressemblent à l'exemple qui est dans le budget de trame, le thread principal est clairement occupé par le travail pendant toute la trame. Même en tenant compte de la petite quantité de surcharge du Profiler à la fin de la trame, le thread principal est occupé pendant plus de 45 ms, ce qui signifie que ce projet atteint des taux de trame de moins de 22 fps. Il n'y a pas de marqueur qui montre le thread principal attendant inactif pour VSync ; il est occupé pendant toute la trame.
La prochaine étape de l'enquête consiste à identifier les parties de la trame qui prennent le plus de temps et à comprendre pourquoi c'est le cas. Sur cette trame, PostLateUpdate.FinishFrameRendering prend 16,23 ms, plus que le budget de trame entier. Un examen plus attentif révèle qu'il y a cinq instances d'un marqueur appelé Inl_RenderCameraStack, indiquant qu'il y a cinq caméras actives et rendant la scène. Puisque chaque caméra dans Unity invoque l'ensemble du pipeline de rendu, y compris le culling, le tri et le regroupement, la tâche de la plus haute priorité pour ce projet est de réduire le nombre de caméras actives, idéalement à une seule.
BehaviourUpdate, le marqueur qui englobe toutes les méthodes Update() de MonoBehaviour, prend 7,27 ms, et les sections magenta de la chronologie indiquent où les scripts allouent de la mémoire sur le tas géré. Passer à la vue Hiérarchie et filtrer en tapant GC.Alloc dans la barre de recherche montre que l'allocation de cette mémoire prend environ 0,33 ms dans cette trame. Cependant, c'est une mesure inexacte de l'impact que les allocations de mémoire ont sur les performances de votre CPU.
GC.Alloc les marqueurs ne sont pas réellement chronométrés en mesurant le temps d'un point de début à un point de fin. Pour garder leur surcharge faible, ils sont enregistrés comme juste leur horodatage de début, plus la taille de leur allocation. Le Profiler attribue un temps minimal à eux pour s'assurer qu'ils sont visibles. L'allocation réelle peut prendre plus de temps, surtout si une nouvelle plage de mémoire doit être demandée au système. Pour voir l'impact plus clairement, placez des marqueurs de Profiler autour du code qui effectue l'allocation, et dans le profilage approfondi, les écarts entre les échantillons GC.Alloc de couleur magenta dans la vue Timeline fournissent une indication de la durée qu'ils ont pu prendre.
De plus, l'allocation de nouvelle mémoire peut avoir des effets négatifs sur les performances qui sont plus difficiles à mesurer et à attribuer directement :
- Demander de la nouvelle mémoire au système peut affecter le budget énergétique d'un appareil mobile, ce qui pourrait amener le système à ralentir le CPU ou le GPU.
- La nouvelle mémoire doit probablement être chargée dans le cache L1 du CPU, ce qui pousse les lignes de cache existantes.
- La collecte de déchets incrémentielle ou synchrone peut être déclenchée directement ou avec un retard lorsque l'espace libre existant dans la mémoire gérée est finalement dépassé.
Au début de la trame, quatre instances de Physics.FixedUpdate s'additionnent à 4,57 ms. Plus tard, LateBehaviourUpdate (appels à MonoBehaviour.LateUpdate()) prend 4 ms, et les animateurs représentent environ 1 ms.
Pour garantir que ce projet respecte son budget et son taux de trame souhaités, tous ces problèmes de thread principal doivent être examinés pour trouver des optimisations appropriées. Les plus grands gains de performance seront réalisés en optimisant les choses qui prennent le plus de temps.
Les domaines suivants sont souvent des endroits fructueux pour chercher des optimisations dans les projets liés au thread principal :
- Physique
- Mises à jour de script MonoBehaviour
- Allocation et/ou collecte de déchets
- Culling et rendu de la caméra
- Mauvaise agrégation des appels de dessin
- Mises à jour de l'UI, mises en page et reconstructions
- Animation
En fonction du problème que vous souhaitez examiner, d'autres outils peuvent également être utiles :
- Pour les scripts MonoBehaviour qui prennent beaucoup de temps mais ne vous montrent pas exactement pourquoi c'est le cas, ajoutez des marqueurs de Profiler au code ou essayez le profilage approfondi pour voir la pile d'appels complète.
- Pour les scripts qui allouent de la mémoire gérée, activez les piles d'appels d'allocation pour voir exactement d'où proviennent les allocations. Alternativement, activez le profilage approfondi ou utilisez Project Auditor, qui montre les problèmes de code filtrés par mémoire, afin que vous puissiez identifier toutes les lignes de code qui entraînent des allocations gérées.
- Utilisez le Débogueur de cadre pour enquêter sur les causes d'un mauvais regroupement des appels de dessin.
Pour des conseils complets sur l'optimisation de votre jeu, téléchargez ces guides d'experts Unity gratuitement :

Limité par le CPU : Fil de rendu
La capture d'écran ci-dessus provient d'un projet limité par son thread de rendu. C'est un jeu de console avec un point de vue isométrique et un budget de cadre cible de 33,33 ms.
La capture du Profiler montre qu'avant que le rendu puisse commencer sur le cadre actuel, le thread principal attend le thread de rendu, comme l'indique le marqueur Gfx.WaitForPresentOnGfxThread. Le thread de rendu soumet encore des commandes d'appel de dessin du cadre précédent et n'est pas prêt à accepter de nouveaux appels de dessin du thread principal ; le thread de rendu passe du temps dans Camera.Render.
Vous pouvez faire la différence entre les marqueurs relatifs au cadre actuel et les marqueurs d'autres cadres, car ces derniers apparaissent plus sombres. Vous pouvez également voir qu'une fois que le thread principal est capable de continuer et de commencer à émettre des appels de dessin pour que le thread de rendu les traite, le thread de rendu met plus de 100 ms à traiter le cadre actuel, ce qui crée également un goulot d'étranglement lors du cadre suivant.
Une enquête plus approfondie a montré que ce jeu avait une configuration de rendu complexe, impliquant neuf caméras différentes et de nombreux passages supplémentaires causés par des shaders de remplacement. Le jeu rendait également plus de 130 lumières ponctuelles en utilisant un chemin de rendu avant, ce qui peut ajouter plusieurs appels de dessin transparents supplémentaires pour chaque lumière. Au total, ces problèmes combinés ont créé plus de 3000 appels de dessin par cadre.
Les causes suivantes sont courantes à enquêter pour les projets qui sont limités par le thread de rendu :
- Mauvais regroupement des appels de dessin, en particulier sur les anciennes API graphiques telles qu'OpenGL ou DirectX 11
- Trop de caméras. À moins que vous ne fassiez un jeu multijoueur en écran partagé, il y a de fortes chances que vous ne deviez avoir qu'une seule caméra active.
- Mauvaise culling, entraînant trop de choses dessinées. Enquêtez sur les dimensions du frustum de votre caméra et les masques de couche de culling. Envisagez d'activer le culling d'occlusion. Peut-être même créer votre propre système simple de culling d'occlusion basé sur ce que vous savez sur la façon dont les objets sont disposés dans votre monde. Regardez combien d'objets projetant des ombres il y a dans la scène - le culling des ombres se produit dans un passage séparé du culling "normal".
Le module Profiler de rendu montre un aperçu du nombre de lots d'appels de dessin et d'appels SetPass à chaque image. Le meilleur outil pour enquêter sur quels lots d'appels de dessin votre thread de rendu envoie au GPU est le Débogueur de trame.

Limité par le CPU : Fils de travail
Les projets liés par des threads CPU autres que les threads principaux ou de rendu ne sont pas si courants. Cependant, cela peut se produire si votre projet utilise le Data-Oriented Technology Stack (DOTS), surtout si le travail est déplacé du thread principal vers des threads de travail en utilisant le Système de travail C#.
La capture vue ci-dessus provient du mode Play dans l'éditeur, montrant un projet DOTS exécutant une simulation de fluide de particules sur le CPU.
Cela semble être un succès à première vue. Les threads de travail sont étroitement remplis de tâches compilées avec Burst, indiquant qu'une grande quantité de travail a été déplacée du thread principal. En général, c'est une décision judicieuse.
Cependant, dans ce cas, le temps de trame de 48,14 ms et le marqueur gris WaitForJobGroupID de 35,57 ms sur le thread principal sont des signes que tout ne va pas bien. WaitForJobGroupID indique que le thread principal a planifié des tâches à exécuter de manière asynchrone sur des threads de travail, mais il a besoin des résultats de ces tâches avant que les threads de travail aient terminé de les exécuter. Les marqueurs Profiler bleus sous WaitForJobGroupID montrent le thread principal exécutant des tâches pendant qu'il attend, dans une tentative de s'assurer que les tâches se terminent plus tôt.
Bien que les tâches soient compilées avec Burst, elles effectuent toujours beaucoup de travail. Peut-être que la structure de requête spatiale utilisée par ce projet pour trouver rapidement quelles particules sont proches les unes des autres devrait être optimisée ou échangée contre une structure plus efficace. Ou, les tâches de requête spatiale peuvent être planifiées pour la fin de la trame plutôt qu'au début, les résultats n'étant pas requis avant le début de la prochaine trame. Peut-être que ce projet essaie de simuler trop de particules. Une analyse plus approfondie du code des tâches est nécessaire pour trouver la solution, donc ajouter des marqueurs Profiler plus fins peut aider à identifier leurs parties les plus lentes.
Les tâches dans votre projet pourraient ne pas être aussi parallélisées que dans cet exemple. Peut-être que vous avez juste une longue tâche s'exécutant dans un seul thread de travail. C'est bien, tant que le temps entre la planification du travail et le moment où il doit être terminé est suffisamment long pour que le travail s'exécute. Si ce n'est pas le cas, vous verrez le thread principal se bloquer en attendant que le travail soit terminé, comme dans la capture d'écran ci-dessus.
Les causes courantes des points de synchronisation et des goulets d'étranglement des threads de travail incluent :
- Des travaux non compilés par le compilateur Burst
- Des travaux de longue durée sur un seul thread de travail au lieu d'être parallélisés sur plusieurs threads de travail
- Un temps insuffisant entre le moment dans la trame où un travail est planifié et le moment où le résultat est requis
- Plusieurs "points de synchronisation" dans une trame, qui nécessitent que tous les travaux soient terminés immédiatement
Vous pouvez utiliser la fonctionnalité Flow Events dans la vue Timeline du module Profiler d'utilisation du CPU pour enquêter sur le moment où les travaux sont planifiés et quand leurs résultats sont attendus par le thread principal. Pour plus d'informations sur l'écriture de code DOTS efficace, consultez le guide DOTS Best Practices.

Dépendance du GPU
Votre application est limitée par le GPU si le thread principal passe beaucoup de temps dans des marqueurs de Profiler tels que Gfx.WaitForPresentOnGfxThread, et que votre thread de rendu affiche simultanément des marqueurs tels que Gfx.PresentFrame ou .WaitForLastPresent.
La capture suivante a été réalisée sur un Samsung Galaxy S7, en utilisant l'API graphique Vulkan. Bien que certains des temps passés dans Gfx.PresentFrame dans cet exemple puissent être liés à l'attente de VSync, la longueur extrême de ce marqueur de Profiler indique que la majorité de ce temps est passée à attendre que le GPU termine le rendu de la trame précédente.
Dans ce jeu, certains événements de jeu ont déclenché l'utilisation d'un shader qui a triplé le nombre d'appels de dessin rendus par le GPU. Les problèmes courants à examiner lors du profilage des performances du GPU incluent :
- Des effets de post-traitement coûteux en plein écran, y compris des coupables courants comme l'occlusion ambiante et le bloom
- Des shaders de fragment coûteux causés par :
- Une logique de branchement
- L'utilisation d'une précision float complète plutôt que d'une précision demi
- Une utilisation excessive des registres qui affecte l'occupation des vagues des GPU
- Un sur-rendu dans la file d'attente de rendu transparent causé par une interface utilisateur inefficace, des systèmes de particules ou des effets de post-traitement.
- Des résolutions d'écran excessivement élevées, telles que celles trouvées dans les écrans 4K ou les écrans Retina sur les appareils mobiles
- Des micro-triangles causés par une géométrie de maillage dense ou un manque de LOD, ce qui est un problème particulier sur les GPU mobiles mais peut également affecter les GPU PC et console
- Des échecs de cache et une bande passante mémoire GPU gaspillée causés par des textures non compressées, ou des textures haute résolution sans mipmaps
- Des shaders de géométrie ou de tessellation, qui peuvent être exécutés plusieurs fois par image si les ombres dynamiques sont activées
Si votre application semble être limitée par le GPU, vous pouvez utiliser le Débogueur de Trames comme un moyen rapide de comprendre les lots d'appels de dessin envoyés au GPU. Cependant, cet outil ne peut pas présenter d'informations spécifiques sur le timing du GPU, seulement comment la scène globale est construite.
La meilleure façon d'examiner la cause des goulets d'étranglement du GPU est d'examiner une capture GPU d'un profileur GPU approprié. L'outil que vous utilisez dépend du matériel cible et de l'API graphique choisie.

Téléchargez l'e-book, Guide ultime pour le profilage des jeux Unity, gratuitement pour obtenir tous les conseils et meilleures pratiques.