introduction
Dans cette série d'articles, nous couvrirons le portage de Detroit: Become Human de PlayStation 4 vers PC.
Detroit: Become Human est sorti sur PlayStation 4 en mai 2018. Nous avons commencé à travailler sur la version PC en juillet 2018 et l'avons publiée en décembre 2019. Il s'agit d'un jeu d'aventure avec trois personnages jouables et de nombreux scénarios. Il a des graphiques de très haute qualité et la plupart de la technologie graphique a été développée par Quantic Dream lui-même.
Le moteur 3D possède d'excellentes fonctionnalités:
- Rendu réaliste des personnages.
- Éclairage PBR.
- Post-traitement de haute qualité comme la profondeur de champ (DOF), le flou de mouvement, etc.
- Anti-aliasing temporaire.
Detroit: Become Human
Dès le début, le moteur 3D du jeu a été conçu spécifiquement pour la PlayStation, et nous n'avions aucune idée qu'il prendrait plus tard en charge d'autres plates-formes. Par conséquent, la version PC était un défi pour nous.
- Le responsable du moteur 3D, Ronan Marshalot, et les responsables du moteur 3D, Nicholas Viseri et Jonathan Siret de Quantic Dream , parleront des aspects du rendu du jeu porté. Ils vous expliqueront quelles optimisations pourraient être transférées de manière transparente de la PlayStation 4 vers le PC et quelles difficultés ils ont rencontrées en raison des différences entre les plates-formes.
- Lou Kramer est ingénieur en développement technologique chez AMD . Elle nous a aidés à optimiser le jeu, elle parlera donc en détail de l'indexation non uniforme des ressources sur PC et, en particulier, dans les cartes AMD.
Choisir une API graphique
Nous avions déjà une version OpenGL du moteur, que nous avons utilisée dans nos outils de développement.
Mais nous ne voulions pas sortir le jeu en OpenGL:
- Nous avions de nombreuses extensions propriétaires qui n'étaient pas ouvertes à tous les fabricants de GPU.
- Le moteur avait de très faibles performances en OpenGL, bien que, bien sûr, il puisse être optimisé.
- Dans OpenGL, il existe de nombreuses façons d'implémenter différents aspects, donc c'était un cauchemar d'implémenter correctement différents aspects sur toutes les plateformes.
- OpenGL . , , .
En raison de l'utilisation intensive de ressources non liées, nous n'avons pas pu porter le jeu sur DirectX11. Il n'a pas assez d'emplacements de ressources, et il serait très difficile d'obtenir des performances décentes si nous devions refaire les shaders pour utiliser moins de ressources.
Nous avons choisi entre DirectX 12 et Vulkan, qui ont un ensemble de fonctionnalités très similaire. Vulkan nous permettrait en outre de fournir un support pour Linux et les téléphones mobiles, et DirectX 12 fournirait un support pour Microsoft Xbox. Nous savions que nous devrons éventuellement implémenter la prise en charge des deux API, mais il serait plus logique que le port se concentre sur une seule API.
Vulkan prend en charge Windows 7 et Windows 8. Depuis que nous voulions faire Detroit: Become Humanaccessible au plus grand nombre d'acteurs, c'est devenu un argument très fort. Cependant, le portage a pris un an, et cet argument est déjà sans importance, car Windows 10 est désormais largement utilisé!
Divers concepts de l'API graphique
OpenGL et les anciennes versions de DirectX ont un modèle de contrôle GPU très simple. Ces API sont faciles à comprendre et très bien adaptées à l'apprentissage. Ils demandent au pilote de faire beaucoup de travail caché au développeur. Par conséquent, il sera très difficile d'optimiser un moteur 3D entièrement fonctionnel en eux.
En revanche, l'API PlayStation 4 est très légère et très proche du matériel.
Vulkan est quelque part entre les deux. Il a également des abstractions car il fonctionne sur différents GPU, mais les développeurs ont plus de contrôle. Disons que nous avons une tâche pour implémenter la gestion de la mémoire ou le cache de shader. Comme il reste moins de travail au conducteur, nous devons le faire! Cependant, nous avons développé des projets sur la PlayStation, et donc c'est plus pratique pour nous quand nous pouvons tout contrôler.
Des difficultés
Le processeur PlayStation 4 est une AMD Jaguar avec 8 cœurs. Évidemment plus lent que le matériel PC plus récent; cependant, la PlayStation 4 présente des avantages importants, en particulier un accès très rapide au matériel. Nous pensons que l'API graphique PlayStation 4 est bien plus efficace que toutes les API sur PC. Il est très simple et gaspille peu de ressources. Cela signifie que nous pouvons réaliser un grand nombre d'appels de tirage par image. Nous savions que les appels à tirage élevé peuvent être un problème sur les PC plus lents.
Un autre avantage important était que tous les shaders de la PlayStation 4 pouvaient être compilés à l'avance, ce qui signifiait qu'ils étaient chargés presque instantanément. Sur un PC, le pilote doit compiler les shaders au moment du démarrage: en raison du grand nombre de configurations de GPU et de pilotes pris en charge, ce processus ne peut pas être effectué à l'avance.
Pendant le développement de Detroit: Become Human sur PlayStation 4, les artistes ont pu créer des arbres d'ombrage uniques pour tous les matériaux. Cela a produit une quantité insensée de vertex et de pixel shaders, donc nous savions dès le début du portage que ce serait un énorme problème.
Pipelines de shader
Comme nous le savons grâce à notre moteur OpenGL, la compilation de shaders peut prendre du temps sur un PC. Lors de la production du jeu, nous avons généré un cache de shader basé sur le modèle GPU de nos postes de travail. Générer un cache de shader complet pour Detroit: Become Human a pris une nuit entière! Tous les employés ont eu accès à ce cache de shader le matin. Mais le jeu a quand même ralenti, car le pilote devait convertir ce code en code assembleur natif du shader GPU.
Il s'est avéré que Vulkan gère ce problème beaucoup mieux qu'OpenGL.
Premièrement, Vulkan n'utilise pas directement un langage de shader de haut niveau comme HLSL, mais utilise à la place un langage de shader intermédiaire appelé SPIR-V. SPIR-V accélère la compilation de shader et facilite l'optimisation pour le compilateur de shader du pilote. En fait, en termes de performances, il est comparable au système de cache de shader OpenGL.
Dans Vulkan, les shaders doivent être liés à la forme
VkPipeline
. Par exemple, VkPipeline
vous pouvez créer à partir d'un vertex et d'un pixel shader. Il contient également des informations sur l'état du rendu (tests de profondeur, gabarit, fusion, etc.) et les formats cibles de rendu. Ces informations sont importantes pour le pilote afin qu'il puisse compiler les shaders aussi efficacement que possible.
Dans OpenGL, la compilation de shaders ne connaît pas le contexte d'utilisation des shaders. Le pilote doit attendre un appel de dessin pour générer le binaire GPU, c'est pourquoi le premier appel de dessin avec un nouveau shader peut prendre beaucoup de temps sur le processeur.
Dans Vulkan, le pipeline
VkPipeline
fournit un contexte d'utilisation, de sorte que le pilote dispose de toutes les informations dont il a besoin pour générer le binaire GPU, et le premier appel de dessin ne gaspille pas de ressources. En outre, nous pouvons mettre VkPipelineCache
à jour la création VkPipeline
.
Au départ, nous avons essayé de créer
VkPipelines
la première fois que nous en avons besoin. Cela a provoqué des ralentissements similaires à la situation avec les pilotes OpenGL. Ensuite, il a été VkPipelineCache
mis à jour et le freinage a disparu jusqu'au prochain appel de tirage au sort.
Ensuite, nous avons prédit que nous pourrions créer
VkPipelines
au moment du démarrage, mais quand cela VkPipelineCache
n'était pas pertinent, c'était si lent que la stratégie de chargement en arrière-plan ne pouvait pas être mise en œuvre.
Finalement, nous avons décidé de tout générer
VkPipeline
lors du premier lancement du jeu. Cela a complètement éliminé les problèmes de freinage, mais maintenant nous sommes confrontés à une nouvelle difficulté: la génération a VkPipelineCache
pris beaucoup de temps.
Detroit: Become Human en contient environ 99 500
VkPipeline
! Le jeu utilise le rendu avant, de sorte que les shaders de matériaux contiennent tout le code d'éclairage. Par conséquent, la compilation de chaque shader peut prendre beaucoup de temps.
Nous avons proposé plusieurs idées pour optimiser le processus:
- , SPIR-V.
- SPIR-V SPIR-V.
- , CPU 100%
VkPipeline
.
En outre, une optimisation importante a été suggérée par Jeff Boltz de NVIDIA, et dans notre cas, elle s'est avérée très efficace.
Beaucoup sont
VkPipeline
très similaires. Par exemple, certains VkPipeline
peuvent avoir les mêmes ombrages de sommets et de pixels, ne différant que dans quelques états de rendu, tels que les paramètres de gabarit. Dans ce cas, le pilote peut les traiter comme un seul pipeline. Mais si nous les créons en même temps, l'un des threads sera simplement inactif, attendant que l'autre termine la tâche. De par sa nature, notre processus transmettait tous les similaires VkPipeline
en même temps. Pour résoudre ce problème, nous venons de modifier l'ordre de tri VkPipeline
. Les "clones" ont été placés à la fin, et par conséquent, leur création a pris beaucoup moins de temps.
Performance de création
VkPipelines
varie considérablement. En particulier, il dépend fortement du nombre de threads matériels disponibles. Sur AMD Ryzen Threadripper avec 64 threads matériels, cela peut prendre aussi peu que deux minutes. Mais sur les PC faibles, ce processus peut malheureusement prendre plus de 20 minutes.
Ce dernier était trop long pour nous. Malheureusement, le seul moyen de réduire davantage ce temps était de réduire le nombre de shaders. Nous aurions besoin de changer la façon dont nous créons les matériaux afin que le plus grand nombre possible d'entre eux soient partagés. Pour Detroit: Become Human, c'était impossible, car les artistes devraient refaire tous les matériaux. Nous prévoyons de mettre en œuvre une instanciation matérielle appropriée dans le prochain match, mais il était trop tard pour Detroit: Become Human .
Indexation des descripteurs
Pour optimiser la vitesse des appels de tirage sur le PC, nous avons utilisé l'indexation des descripteurs à l'aide de l'extension
VK_EXT_descriptor_indexing
. Son principe est simple: on peut créer un ensemble de descripteurs contenant tous les tampons et textures utilisés dans le cadre. Ensuite, nous pouvons accéder aux tampons et aux textures via des index. Le principal avantage de ceci est que les ressources ne sont liées qu'une seule fois par image, même si elles sont utilisées dans plusieurs appels de dessin. Ceci est très similaire à l'utilisation de ressources indépendantes dans OpenGL.
Nous créons des tableaux de ressources pour tous les types de ressources utilisées:
- Un tableau pour toutes les textures 2D.
- Un tableau pour toutes les textures 3D.
- Un tableau pour toutes les textures cubiques.
- Un tableau pour tous les tampons de matériaux.
Nous n'avons qu'un tampon principal qui change entre les appels de dessin (il est implémenté comme un tampon circulaire) contenant un index de descripteur qui fait référence au tampon de matériau souhaité et aux matrices requises. Chaque tampon de matériau contient des indices des textures utilisées.
Grâce à cette stratégie, nous avons pu conserver un petit nombre d'ensembles de descripteurs communs à tous les appels de dessin et contenant toutes les informations nécessaires pour dessiner le cadre.
Optimisation des mises à jour des ensembles de descripteurs
Même avec un petit nombre d'ensembles de descripteurs, leur mise à jour restait un goulot d'étranglement. La mise à jour d'un ensemble de descripteurs peut être très coûteuse s'il contient de nombreuses ressources. Par exemple, dans un cadre Detroit: Become Human , il peut y avoir plus de quatre mille textures.
Nous avons implémenté des mises à jour incrémentielles des ensembles de descripteurs, en gardant une trace des ressources qui deviennent visibles et invisibles dans le cadre actuel. En outre, cela limite la taille des tableaux de descripteurs, car ils ont une capacité suffisante pour gérer les ressources visibles à l'heure actuelle. Le suivi de la visibilité gaspille peu de ressources car nous n'utilisons pas d'algorithme coûteux pour calculer les intersections avec
O(n.log(n))
... Au lieu de cela, nous utilisons deux listes, une pour l'image actuelle et une pour la précédente. Le déplacement des ressources visibles restantes d'une liste à une autre et l'examen des ressources restantes dans la première liste permet de déterminer quelles ressources entrent et disparaissent de la pyramide de visibilité.
Les deltas obtenus lors de ces calculs sont stockés pour quatre images - nous utilisons un triple buffering, et pour calculer les vecteurs de mouvement des objets avec skinning, une image supplémentaire est nécessaire. L'ensemble de descripteurs doit rester inchangé pendant au moins quatre images avant de pouvoir être modifié à nouveau, car il peut toujours être utile au GPU. Par conséquent, nous appliquons des deltas à des groupes de quatre cadres.
En fin de compte, cette optimisation a réduit le temps de mise à jour des ensembles de descripteurs d'un à deux ordres de grandeur.
Butching primitifs
L'utilisation de l'indexation des descripteurs nous permet de regrouper plusieurs primitives par lots en un seul appel à l'aide de
vkCmdDrawIndexedIndirect
. Nous utilisons gl_InstanceID
pour accéder aux index souhaités dans le tampon principal. Les primitives peuvent être regroupées en lots si elles ont le même ensemble de descripteurs, le même pipeline de shader et le même tampon de vertex. Ceci est très efficace, surtout lors des passages en profondeur et dans l'ombre. Le nombre total d'appels de tirage est réduit de 60%.
Ceci conclut la première partie de la série d'articles. Dans la partie 2, l'ingénieur en technologie Lou Kramer parlera de l'indexation des ressources hétérogènes sur les PC et les cartes AMD en particulier.