Vous venez de dépenser vos dollars durement gagnés pour le dernier jeu AAA à jouer sur votre précieux PC de jeu. Un long téléchargement plus tard, vous sautez directement dans le jeu, seulement pour être confronté à une longue attente au menu principal pendant qu’il se trie…
Ou peut-être que cela n’arrive pas. Mais vous vous retrouvez à souffrir de nombreux problèmes de fréquence d’images pendant le jeu. Désespéré de trouver une solution, vous parcourez le Web à la recherche d’une réponse et tombez sur une phrase souvent répétée : “compilation de shaders”.
Voulez-vous savoir ce qu’est la compilation de shaders, pourquoi est-elle nécessaire et pourquoi elle reçoit une telle presse négative ? Eh bien, c’est l’article parfait pour vous.
Bon, alors qu’est-ce qu’un shader?
Essentiellement, les shaders ne sont que des blocs de code. Dans le monde du graphisme et du rendu 3D, chacun est un petit programme conçu pour produire une sortie très spécifique. Il peut s’agir de transformer la forme ou la position d’un triangle, de mapper une texture à une forme ou de calculer un ensemble de valeurs requises pour d’autres shaders.
Lorsque des API telles que Direct3D et OpenGL ont pris en charge les shaders pour la première fois, leur taille et leur portée étaient extrêmement limitées. Par exemple, lorsque les pixel shaders sont apparus pour la première fois sur les PC, il y a plus de 20 ans, il n’y avait de la place que pour 4 instructions impliquant des textures et 8 pour faire des calculs.
Aujourd’hui, dans la dernière version de tous les shaders, il n’y a effectivement aucune limite au nombre d’instructions pouvant être intégrées au code…
Bien que les différentes API utilisées par les développeurs aient une terminologie différente, il existe sept catégories de shaders. Les shaders de vertex, de géométrie, de coque et de domaine consistent à gérer les sommets des triangles – en créant et en façonnant le monde 3D avant qu’il ne soit coloré.
Cette tâche est gérée par des shaders de pixels (et plus récemment, de rayons), qui traitent les calculs impliqués dans l’éclairage et la texturation. Enfin, les shaders de calcul consistent à effectuer un traitement arithmétique et logique à usage général, plutôt que quelque chose de spécifique aux graphiques.
Les jeux 3D modernes utilisent des milliers de shaders pour calculer les images affichées à l’écran. Certains sont très courts et simples, d’autres peuvent être extrêmement longs et très complexes, absorbant toutes les ressources qu’un GPU peut lui offrir.
Et chacun d’entre eux doit être traité avant que le GPU ne puisse en faire quoi que ce soit.
Comment et quand compiler les shaders
La compilation de logiciels est le processus de transformation du code source en code exécutable par la machine. Le code source peut être écrit dans n’importe quel langage de programmation, qui est ensuite transformé en binaire pour être exécuté par le matériel de l’ordinateur.
Les shaders ne sont pas différents, ils sont écrits dans une certaine langue, il est donc plus facile de les créer et de comprendre ce qui se passe. Un exemple de ceci peut être vu en utilisant ShaderToy. Pour utiliser la version en ligne de cet outil, le code est écrit en GLSL (OpenGL Shading Language) et utilise l’API graphique WebGL pour le rendu.
Dans l’exemple ci-dessous, le code affiché fait partie du processus requis pour générer la surface du terrain vue dans l’image finale — vous pouvez suivre comment tout cela a été créé en regardant cette vidéo.
Même si vous ne savez pas ce que signifie l’un de ces codes, cela n’a pas d’importance – votre puce graphique non plus. Il peut effectuer tous les calculs, mais il faut lui donner les instructions en binaire. Le code doit être compilé à partir de WebGL dans les instructions GPU – dans le cas de tout ce qui utilise un GPU pour obtenir le résultat final, la compilation est effectuée par les pilotes GPU, le CPU gérant cette charge de travail.
Cet exemple ShaderToy n’utilise qu’une poignée de shaders et un seul d’entre eux est raisonnablement long. Même ainsi, il ne faut qu’une fraction de seconde à un processeur de bureau moyen pour les compiler tous, donc cela se fait une fois, au moment où vous démarrez la démo.
Choisissez n’importe quel jeu 3D moderne, cependant, et c’est une toute autre histoire. Voyager dans l’environnement, passer à un niveau différent ou combattre un nouvel ennemi nécessite que le jeu charge de nouveaux shaders à partir du lecteur de stockage, puis les compile.
Les jeux sur consoles, tels que la PS5 ou la Xbox Series, ont généralement tous les shaders pré-compilés, mais pas toujours. En effet, la configuration matérielle est très statique – les développeurs créant un nouveau titre pour une machine de Sony ou Microsoft n’ont besoin de gérer qu’un seul type de GPU et son système d’exploitation et ses pilotes changent rarement.
Les ordinateurs de bureau, cependant, présentent un casse-tête majeur à cet égard, car il existe des milliers de combinaisons possibles de systèmes d’exploitation, de pilotes et de puces graphiques. Il serait impossible de pré-compiler les shaders pour chacun d’entre eux, les développeurs utilisent donc une variété d’approches pour résoudre ce casse-tête.
Les problèmes liés à la compilation des shaders
Une approche courante consiste à convertir tous les shaders qui seront utilisés par le jeu au fur et à mesure de son chargement – parfois pendant la séquence de démarrage principale, d’autres fois dans le menu principal ou au chargement du niveau initial.
Si un jeu contient des dizaines de milliers de shaders, les compiler tous au début peut potentiellement rendre ces temps de chargement excessivement longs. Une mauvaise gestion de la bibliothèque de shaders, lors de la création du jeu, ne fera qu’aggraver ce problème, car du temps et de l’espace de stockage seront gaspillés à compiler quelque chose qui ne sera jamais utilisé.
Cependant, cela signifie au moins que le code de shader est prêt à être utilisé par les pilotes, au moment où le moteur de rendu du jeu envoie des commandes au GPU. La méthode opposée à tout cela consiste à éviter de les convertir au début et à tout faire quand ils sont nécessaires, pendant le jeu.
Cela va évidemment accélérer le chargement mais la compilation en temps réel peut générer des blocages dans la chaîne de traitement graphique, le temps que le CPU exécute le processus de conversion.
Celles-ci se manifestent généralement sous la forme d’une baisse soudaine, mais très brève, de la fréquence d’images moyenne du jeu. Mieux connu sous le nom de bégaiement, ce problème peut aller d’un hoquet singulier et momentané à un gâchis régulièrement saccadé et horrible.
Le bégaiement dans les jeux n’est pas seulement causé par la compilation des shaders, car de nombreux aspects du rendu peuvent en résulter, mais ce problème spécifique est de plus en plus répandu.
En effet, la tendance des titres AAA est d’avoir des graphismes toujours plus réalistes ou visuellement impressionnants, en particulier dans le genre populaire du monde ouvert. Ainsi, les shaders deviennent plus longs et plus complexes, ainsi que de plus en plus nombreux, et ceux-ci prennent tous plus de temps à compiler.
Il existe de nombreux jeux qui adoptent une approche intermédiaire de la compilation des shaders, traitant les principaux lors du chargement ou du menu principal, et faisant le reste selon les besoins. Mais réussir cela sur la plate-forme PC est un défi en soi.
Les joueurs qui connaissent les titres créés à l’aide de l’Unreal Engine, en particulier la version 4, ne le sauront que trop bien, même si ce n’est pas un problème exclusif à UE4. Les lancements des ports PC d’Horizon Zero Dawn et de Death Stranding, tous deux réalisés à l’aide du moteur propriétaire Decima, ont été entachés de rapports fréquents de bégaiement incessant, dont une grande partie était attribuée à des problèmes de compilation de shader et résolue dans des correctifs post-lancement.
Alors, que peuvent faire les développeurs et les joueurs pour minimiser le problème ?
Malheureusement pour les joueurs, vous ne pouvez pas y faire grand-chose. Étant donné que c’est le processeur qui gère les tâches de compilation, il peut être utile de minimiser le nombre de tâches en arrière-plan, tout comme de réduire la qualité des graphismes du jeu, mais c’est à peu près tout ce que les utilisateurs peuvent faire.
Peu de jeux offrent une option pour modifier la gestion de la compilation des shaders. L’Horizon Zero Dawn susmentionné, avec le dernier correctif, effectuera la compilation des shaders dans le menu principal, mais vous permet de l’ignorer si cela prend trop de temps, ce qui sera le cas sur du matériel bas de gamme.
Quelque chose comme le récent Hogwarts Legacy ne vous donne pas une telle option et la majeure partie de la compilation est effectuée au démarrage du jeu, avant d’atteindre le menu principal. Aucune des deux approches ne semble être particulièrement optimale car les deux titres traiteront toujours sensiblement plus de shaders pendant que vous jouez.
Les fabricants de GPU jouent également un rôle ici, car c’est leur logiciel qui fait la compilation. Les pilotes AMD et Nvidia stockeront les shaders compilés des jeux couramment joués dans un dossier sur le lecteur de stockage principal, mais dès que le jeu ou les pilotes sont mis à jour, le processus doit être répété.
Vous pourriez penser que ces fournisseurs pourraient faire plus pour améliorer les performances de la compilation des shaders, mais de nos jours, c’est aussi rapide que possible.
À ce stade, vous pourriez également penser qu’une sorte de service en ligne, qui stocke des shaders compilés pour chaque configuration de jeu, de matériel et de pilote, vaudrait la peine d’avoir. Valve propose une telle chose, en tant que service de cache dans Steam, mais uniquement pour les jeux qui utilisent OpenGL ou Vulkan. Le plus souvent, cependant, le cache apparaît toujours vide.
En fin de compte, ce sont les développeurs de jeux qui sont chargés de minimiser l’impact de la compilation des shaders. Certains sont très bons dans ce domaine et semblent faire tout leur possible pour minimiser le bégaiement, tandis que d’autres sont beaucoup moins diligents à ce sujet (vous regarde, FromSoftware).
Les développeurs les plus avertis (par exemple Infinity Ward, les créateurs de CoD) sauront qu’ils doivent utiliser toutes les astuces du livre pour empêcher la conversion de code d’interrompre le flux du rendu, en le cachant dans les menus, les scènes coupées, les mi- étapes de chargement de niveau, et ainsi de suite.
Avoir beaucoup d’expérience et comprendre les détails les plus fins du moteur utilisé est également essentiel. Par exemple, lors de la création d’un jeu dans UE4, l’apparence de surface de chaque objet et section de l’environnement est générée par des éléments appelés matériaux. Par défaut, leur création génère à son tour une série de shaders, dont beaucoup peuvent être inutiles.
Si on ne gère pas cela correctement, alors le problème ne se présentera que lors des tests de qualité/performance. Les responsables du développement allouent des finances et du temps à de telles choses, bien sûr, mais cela ne va que si loin, et si le jeu rencontre de nombreux autres problèmes, la résolution du bégaiement dû à la compilation des shaders peut ne pas recevoir suffisamment d’attention à temps pour le lancement.
Les correctifs post-sortie tentent souvent d’atténuer ces problèmes, ce qui suggère fortement que les sociétés de développement ne donnent tout simplement pas suffisamment de tests d’assurance qualité aux versions PC de leurs jeux. Et étant donné que cela coûte de l’argent, tant que des millions de personnes achèteront les derniers jeux avec bégaiement, de nombreux managers ne seront tout simplement pas disposés à dépenser plus d’argent pour optimiser les choses.
Alors peut-être, juste peut-être, le problème peut être résolu par nous, tout cela grâce à la puissance de nos portefeuilles.