| April 13, 2009 4:00 PM PDT | |
Résumé
Cet article propose une solution de parallélisation simple et efficace pour accélérer le rendu de divers types de modèles 3D animés. Cette solution optimise les performances d’une animation filaire par le recours à un pool de fils, un double tamponnage et des mises à jour intermittentes. Le jeu de techniques correspondant assure l’animation filaire sur CPU à un niveau de performances très comparables à celles d’un traitement sur GPU. Il a de plus l’avantage de constituer une solution alternative pour les systèmes multicœurs.
Introduction
L’animation joue un rôle déterminant dans les jeux vidéo, et l’animation filaire est actuellement l’une des techniques d’animation en temps réel les plus courantes et les plus pointues. Elle considère le maillage triangulaire d’un modèle comme un habillage (skin) et définit une structure sous-jacente (« squelette » ou « armature ») qui conditionne la position des sommets (vertices, singulier vertex) du maillage d’origine. Un set d’animation transforme le squelette au moyen d’une série d’insertions (poses). Sachant que chaque sommet du maillage d’origine est transformé par un ou plusieurs os (bones) d’influence, le rendu de modèles 3D animés constitue dont un sérieux frein potentiel aux performances, cela d’autant plus dans le cas de plusieurs animations concurrentes [1]. Les scènes de jeu vidéo sont en effet de plus en plus complexes, par exemple dans les jeux de rôle multijoueurs en ligne (MMORPG), où les environnements de champ de bataille ou de paysage urbain se composent d’un grand nombre d’animations.
Un certain nombre de technologies logicielles et matérielles ont déjà été employées pour optimiser les performances des animations filaires. Régler le niveau de détail (Level of Detail, LOD) des maillages et des squelettes est ainsi susceptible de simplifier l’animation de modèles lointains. Les extensions SSE (Streaming SIMD Extensions) peuvent par ailleurs multiplier par deux voire plus les performances d’une implémentation en C. [1] Le traitement de l’animation peut également être délégué du CPU à un GPU gérant un nuanceur de sommets (vertex shader), ce qui assure des gains de performances tout à fait significatifs.
Ces dernières années, deux grandes tendances se sont dessinées dans le secteur micro-informatique. La majorité des processeurs qui équipent des PC de bureau ou portables intègrent ainsi désormais plusieurs cœurs d’exécution, ce qui revient à dire qu’un jeu vidéo élaboré aujourd’hui sort inévitablement sur un marché où les plates-formes courantes sont multicœurs. De plus, les ventes de PC portables enregistrent une progression plus forte que celles des PC de bureau et une grande partie de ces portables, qui sont d’ailleurs nombreux à être équipés d’un moteur graphique intégré (par opposition à une carte vidéo distincte), sont de plus en plus largement utilisés pour les jeux. Par conséquent, savoir comment tirer le meilleur parti des ressources CPU et GPU respectives des systèmes multicœurs est une question importante pour les développeurs de jeux.
Cet article présente ainsi une solution de parallélisation simple et efficace pour animer des modèles 3D en stimulant les performances des jeux par exploitation du multicœur. Cette solution consiste à paralléliser le pipelining de rendu d’une animation filaire par le recours à un pool de fils (thread pool), un double tamponnage (double buffering) et une mise à jour intermittente. Pour valider l’intérêt de cette solution, une application de démo qui permet le rendu d’un grand nombre de modèles animés a été développée (voir en appendice). Les tests de performances réalisés à partir de cette démo indiquent en l’occurrence que la solution profite largement à l’animation filaire, qu’elle soit d’ailleurs traitée par le GPU ou par le CPU, mais tout particulièrement dans le second cas. De plus, l’animation sur CPU enregistre alors des performances tout à fait comparables à celles que l’on obtient sur des cartes graphiques haut de gamme et en net progrès par rapport aux résultats des moteurs graphiques intégrés courants.
Cet article" s’articule de la manière suivante :
- La deuxième partie présente la technique de l’animation filaire et compare ses implémentations sur CPU et sur GPU.
- La troisième partie présente la solution et sa mise en œuvre.
- La quatrième partie propose une démonstration de la solution.
- La cinquième partie reprend les résultats à des tests comparatifs et l’analyse des performances.
- La sixième partie, enfin, aborde certaines considérations complémentaires.
Animation filaire
L’animation filaire est actuellement l’une des techniques d’animation en temps réel les plus courantes et les plus pointues. Il s’agit de transformer un modèle en une représentation simplifiée (« squelettisation »), composée d’un certain nombre d’os hiérarchiquement agencés et définissant le « squelette » ou « armature » du modèle en question. L’animation se définit dans ce cas comme un positionnement des os sur un certain nombre d’images-clés (keyframes), précisées sous forme de transformations relatives pour chaque os. La position des os peut alors s’interpoler entre images-clés. L’habillage du modèle — qui est un maillage polygonal — se superpose par couches sur les os. Chaque sommet du maillage subit l’influence d’un ou plusieurs des os du squelette. L’habillement (skinning), qui consiste à faire correspondre l’habillage du modèle à la position des os à un instant T, modifie la position de chaque sommet en fonction de la position des os à ce moment-là. A l’exécution, le moteur du jeu détermine la séquence et la durée voulues de l’animation, transforme le squelette pour lui donner la position voulue, habille le modèle et, enfin, le dessine.
Pour déterminer la position du squelette dans une image de rendu donnée, ce moteur identifie les animations qui s’appliquent à ce moment-là au modèle (un modèle peut subit plusieurs animations en même temps) et extrait les images-clés voulues pour les séquences d’animation. Une image-clé comporte des informations pour la transformation de chaque os, de sa position de référence à celle que l’image prévoit. Pour chaque animation, les transformations des os sont interpolées à partir des deux images-clés limitrophes. Si le modèle subit plusieurs animations, toutes sont mélangées pour parvenir aux transformations finales du squelette. C’est en général le CPU qui réalise cette tâche.
L’habillement déplace le maillage polygonal du modèle jusqu’à la position relative voulue dans l’animation. Chaque sommet est d’abord transformé à partir de la position de référence du maillage par rapport à celle de l’os correspondant. Ensuite, le sommet se déplace jusqu’à sa position finale à l’aide de la transformation de l’os. Si le sommet subit l’influence de plusieurs os, leur incidence respective se calcule indépendamment, puis il s’opère un mélange (blending), le cas échéant avec une pondération différente pour chaque os, afin de déterminer la position finale du sommet. L’habillement peut être traité par un nuanceur de sommets sur le GPU (animation filaire sur GPU) ou bien sur le CPU (animation filaire sur CPU).
Sachant que les calculs d’habillement pour chaque sommet peuvent s’effectuer indépendamment les uns des autres, l’animation filaire sur GPU a à sa disposition le débit très élevé des sous-systèmes graphiques actuels. L’habillement est plus rapide sur les GPU de milieu de gamme et au-delà, et nécessite moins de mémoire que sur CPU. Pour certaines applications, il peut en revanche se révéler plus intéressant de réaliser l’habillement sur le CPU [1], ce qui renforce la compatibilité de celles-ci pour une large palette de systèmes. Les systèmes plus anciens peuvent en effet être équipés de cartes qui ne gèrent pas nécessairement les fonctions nécessaires à l’habillement sur GPU. Or ces cartes risquent de subir des limitations qui imposent la subdivision des grands modèles filaires en de multiples maillages, ce qui nuit au rendement de traitement.
Certaines techniques de rendu imposent le dessin répété d’un maillage, souvent dans des tampons ou sur des surfaces temporaires. Dans ce cas, toutes les transformations de sommets doivent être recalculées sur le GPU à chaque passe de rendu, car les résultats intermédiaires ne s’enregistrent pas, ce qui risque de conduire l’exécution du nuanceur de sommets à brider les performances sur les GPU courants actuels. Si tel est le cas, on peut améliorer les performances à la transformation des sommets sur le CPU, car la répétition de cette opértion n’est en l’espèce pas nécessaire. En outre, les instructions SSE et l’optimisation par parallélisation sont susceptibles de produire des performances du même niveau que les implémentations (partielles) sur GPU. Ce sont les raisons pour lesquelles certains jeux vidéo, comme Doom III* ou Quake 4*, effectuent leurs opérations d’habillement sur CPU, ce qui leur confère une compatibilité avec une très large palette de configurations. En outre, les données du maillage animé sont indispensables à de nombreux algorithmes généralement implémentés sur CPU (shadow volumes et détection des collisions, par exemple). Pourtant, les cartes graphiques actuelles ne permettent pas la récupération de ces données après leur traitement, l’animation sur CPU procurant ainsi des avantages supplémentaires.
La dernière étape du pipelining pour l’animation de modèles 3D est celle du dessin à l’écran des sommets de l’habillage. A ce stade, les sommets transformés passent par des opérations de clipping, de rastérisation, d’ombrage, etc., qui sont réalisées sur le GPU.
La solution
Le principal objectif de la solution envisagée ici est d’exploiter au maximum le parallélisme du pipelining de rendu pour les animations filaires logicielles. Son élaboration participe de trois observations importantes. En premier lieu, le pipelining est un processus lourd en traitement en raison des calculs matriciels massifs qu’il nécessite. Ensuite, l’animation de plusieurs modèles animés s’effectue en général indépendamment pour chacun, ce qui fait profiter le pipelining de rendu des avantages de la parallélisation. Enfin, dans de nombreux cas (surtout pour les MMORPG), plusieurs images successives dans l’animation d’un modèle sont souvent si semblables qu’il est inutile de mettre à jour le modèle en question à chaque image. Or une mise à jour par intermittence est susceptible de réduire le trafic sur le bus, de renforcer le parallélisme entre le CPU et le GPU ainsi que d’éviter les calculs redondants.
La figure 1 illustre l’architecture de la solution, qui comporte trois composantes : pool de fils (thread pool), double tamponnage (double buffering) et mise à jour intermittente. Le pool de fils regroupe plusieurs fils d’animation qui effectuent les calculs de transformation des os et l’habillement. C’est le fil principal qui assure le dessin (drawing), car les appels à une interface API graphique telle que DirectX* ou OpenGL* ne sont pas adaptés à une invocation à partir de fils distincts parce qu’ils constituent alors un frein aux performances. [4] Le recours au double tamponnage optimise le parallélisme et simplifie la synchronisation entre les fils d’animation et les appels de dessin finaux. Enfin, la mise à jour intermittente, facultative, pilote la fréquence de rafraîchissement de l’animation.
Les parties qui suivent abordent ces composantes en détail ainsi que la stratégie de gestion des ressources qui a de pair avec la solution.
Figure 1. Architecture de la solution.
Pool de fils
Lors de l’animation d’un modèle, un fil d’animation distinct lui est attribué pour traiter les transformations du squelette et l’habillement. On a recours à ce pool pour assurer en parallèle le rendu de plusieurs modèles animé, afin d’éviter la lourdeur que représentent la création et la suppression répétitives des fils d’animation. Le nombre de ces fils que comporte le pool évolue par ailleurs en fonction du nombre de modèles animés et de cœurs du processeur. En outre, il constitue une interface de programmation directe, grâce à laquelle le fil principal n’est nécessaire que pour l’envoi des modèles dans la file des tâches, dont il gère automatiquement l’ordonnancement. En faisant appel à un pool bien encapsulé, tel que celui d’un système d’exploitation Windows* 32 bits, la définition d’un modèle parallélisé est alors simple et directe, comme le montre l’extrait de code ci-dessous.
Le modèle est soumis au pool par l’appel QueueUserWorkItem [2], qui est l’API de pool de fils Win32, dans la fonction de classe de modèles Animate(). Le troisième paramètre de cette fonction précise le nombre maximum de fils pour le pool. En général, le nombre des fils actifs d’une application ne doit ainsi pas dépasser le nombre de cœurs disponibles. En raison des temps systèmes que représente leur ordonnancement, la surexploitation des fils n’augmente pas les performances.
Dans la mesure où le fil principal doit être synchrone par rapport aux fils d’animation, la solution propose une méthode simple pour la synchronisation initiale. Seuls le fil principal et les fils d’animation accèdent au compteur de modèles du pool de fils, par les fonctions de synchronisation légères de Win32 avec « interblocage » (interlock). Lorsque ce compteur est à zéro, le fil principal entame le dessin des modèles.
Double tamponnage
Même si la méthode de synchronisation abordée ci-dessus évite les courses aux données, elle n’en impose pas moins que l’ensemble de l’habillement intervienne en préalable au rendu. Sachant que les calculs d’habillement sont indépendants pour chaque image, le double tamponnage autorise la réalisation simultanée de l’habillement et du rendu. Il est attribué deux tampons à chaque modèle animé pour l’habillement des données : l’un d’eux stocke celles-ci pour l’image en cours et sert au fil principal pour le dessin ; l’autre stocke les données pour l’image suivante et un fil d’animation y écrit.
Figure 2. Mécanisme du double tamponnage.
Lorsqu’un modèle entre dans le cadre, les données d’animation du tampon de lecture peuvent ne pas être prêtes ou bien être trop anciennes pour être dessinées par le fil principal. Pour éviter cette situation, chacun des deux tampons possède une « durée de vie » : il lui est attribué une nouvelle « vie » lorsque les données d’animation les plus récentes s’y stockent, cette durée de vie diminuant alors à chacune des images suivantes. Le fil principal n’utilise ainsi, pour le dessin, que le tampon dont la durée de vie est non nulle.
Les extraits de code ci-dessous reprennent l’une des implémentations possibles du double tamponnage dans la classe des modèles.
Au début de la boucle du jeu, le fil principal met à jour l’état des deux tampons. L’extrait de code suivant implémente le double tamponnage dans cette boucle.
Le double tamponnage favorise également la programmation parallèle, car on peut alors partir du principe que des données d’animation valides existent pour un modèle. Ainsi, un fil d’animation qui traite un modèle peut obtenir les données d’animation d’un autre modèle à partir de son tampon de lecture, ce qui peut permettre l’exécution concurrente d’autres processus, par exemple la détection des collisions entre modèles. Le principal inconvénient du double tamponnage est cependant le surcroît de consommation mémoire qu’il implique. Ainsi, dans le cas du rendu de cent modèles avec habillement sur CPU et en supposant qu’ils sont composés de 5 000 sommets codés sur 32 bits chacun, le tampon additionnel occupe environ 16 Mo en mémoire.
Mise à jour intermittente
Si les implémentations de l’habillement sur CPU sont plus lentes que sur GPU, c’est surtout parce que les sommets habillés se chargent sur la carte vidéo à une telle fréquence que le bus devient un goulet d’étranglement. Or, pour de nombreuses scènes de jeu, il n’est pas nécessaire de mettre à jour les données d’habillement à chaque image. C’est par exemple le cas dans une grande scène de bataille d’un MMORPG où toute une série de modèles animés sont visibles en même temps. En l’occurrence, une mise à jour à chaque image n’est pas utile à la fluidité de l’animation. En outre, comme le « coût » de l’animation s’amortit sur plusieurs images, la complexification des animation n’a pas d’incidence sur la fréquence image.
L’extrait de code suivant ajoute des mises à jour intermittentes à la boucle principale du jeu et à la classe des modèles.
La fréquence effective de mise à jour de l’animation des modèles est égale à la fréquence image divisée par l’intervalle entre deux mises à jour. Ainsi, si la valeur de cet intervalle est 1, la solution revient à animer chaque modèle à chaque image. Si l’habillement freine les performances, le choix d’intervalles plus long viendra alors augmenter la fréquence image tout en diminuant la fréquence de mise à jour du modèle. Dans le cadre des jeux, il convient donc de choisir un intervalle qui permette à la fréquence de mise à jour de rester au-dessus d’un seuil minimum (quinze par seconde, par exemple).
Gestion des ressources
Au cours du rendu d’un modèle animé, le CPU et le GPU accèdent fréquemment aux ressources qu’exploitent les modèles 3D (tampons de sommets et d’indices, textures, etc.) Or l’emplacement et le taux d’utilisation de ces ressources ont une incidence considérable sur les performances du pipelining de rendu d’une animation filaire. [6] Pour l’animation sur GPU, la majeure partie de celle-ci intervient sur ce sous-système. En général, sous DirectX 9, les ressources sont placées en mémoire vidéo locale et considérées comme des « ressources par défaut » (« default resources »), afin que le GPU puisse y accéder rapidement. Or cette stratégie est tout à fait adaptée à la solution envisagée ici.
Pour l’animation sur CPU, les ressources telles que les tampons des sommets de maillage sont fréquemment mises à jour par le processeur. En général, elles sont placées en mémoire vidéo non locale (mémoire AGP, par exemple) et considérées comme « dynamic default resource » de DirectX 9.Ainsi, dans le cadre de l’implémentation abordée ici, on utilise le flag POOL_DEFAULT et le hint USAGE_DYNAMIC de Directx 9 pour créer le tampon des sommets du maillage d’un modèle. [5] Même si la stratégie d’exploitation des ressources ci-dessus profite surtout à l’animation classique sur CPU, les performances sur lesquelles elle débouche demeurent, dans la majorité des cas, moins bonnes que sur GPU. La raison en est que les GPU peuvent accéder bien plus rapidement à la mémoire vidéo si elle est locale et que l’animation classique sur CPU charge les sommets du maillage à chaque image, ce qui crée un goulet d’étranglement au niveau du bus.
Or, sachant que la solution envisagée ici ne met à jour le modèle que par intermittence, les ressources ne se chargent pas à chaque image. Grâce à cette intermittence, les ressources dynamiques de l’animation du modèle deviennent semi-dynamiques et peuvent être stockées sous la forme d’une « managed resource » au lieu d’une « dynamic default resource ». DirectX 9 les place à l’endroit voulu pour que le CPU et le GPU puissent y accéder efficacement.
Application de démo
Pour évaluer l’intérêt de cette solution, nous avons réalisé une démo qui fait intervenir le rendu de toute une série de modèles 3D. Dérivée de l’exemple MultiAnimation de DirectX, [7] cette démo compare l’animation filaire sur CPU et sur GPU, avec à chaque fois deux implémentations : sans parallélisation (monofil) et avec parallélisation (multifil).
Dans cette démo, un certain nombre de modèles se déplacent sur un sol plan (voir figure 3). Ils sont pilotés soit par l’application (par défaut), soit par l’utilisateur. A chacun d’entre eux correspondent trois sets d’animation : Loiter (flâner), Walk (marcher) et Run (courir). Le contrôleur d’animation mélange (blending) les sets pour assurer la fluidité de la transition de l’un à l’autre. Il s’effectue également une détection des collisions, un modèle ayant ainsi la possibilité de bloquer le déplacement des autres. Dans cette démo, c’est le mélange des sets d’animation — animation filaire et habillement (via la solution sur CPU) — qui compose la tâche du fil d’animation. Les modèles hors cadre ne sont pas animés. Dans le mode où c’est l’application qui pilote l’animation, on peut diriger la caméra à l’aide des touches fléchées ; dans celui où c’est l’utilisateur, on peut sélectionner un modèle et le déplacer à l’aide des touches A, W et D, la caméra suivant alors le modèle concerné.
Figure 3. Capture d’écran de la démo.
On utilise un fichier de configuration pour initialiser l’application en différents modes d’exécution. Les options de configuration de la démo sont les suivantes :
- Méthode. Choix entre l’habillement sur CPU ou sur GPU.
- Fils. Nombre de fils d’animation du pool de fils. Une valeur non nulle pour ce paramètre correspond à la mise en œuvre de la solution de parallélisation envisagée dans cet article ; une valeur nulle correspond à une implémentation monofil classique.
- Modèles. Nombre initial des modèles de la démo.
- Intervalle. Nombre d’images dans un intervalle de mise à jour. Cette option n’est possible qu’en cas d’implémentation de la solution envisagée ici.
On trouvera en appendice le lien pour télécharger le code source de cette démo.
Analyse des performances
La démo a été testée sur deux configurations :
|
Configuration |
Processeur |
Mémoire vive |
Carte graphique |
Système d’exploitation |
|
A |
Processeur Intel® Core™2 Quad à 2,6 GHz (4 cœurs) |
DDR2 2 Go |
ATI Radeon* série X1900 |
Windows* XP |
|
B |
Processeur Intel® Core™2 Duo à 2,4 GHz (2 cœurs) |
DDR2 2 Go |
Cœur graphique Intel® GM965 |
Windows XP |
Tableau 1. Configuration des plates-formes de test.
NB. Les tests et les indices de performances portent sur des configurations spécifiques, leurs éléments ou les deux. Ils rendent compte approximativement des performances des produits Intel mesurées par ces tests. Une différence dans la configuration matérielle ou logicielle est ainsi susceptible d’avoir une incidence sur les performances effectives. Pour en savoir plus sur les tests de performances subis par les produits Intel® et leurs performances effectives, consultez la rubrique « Intel Performance Benchmark Limitations » à l’adresse www.intel.com/performance/resources/benchmark_limitations.htm.
La Configuration A est un PC de bureau équipé d’un processeur multicœur et d’une carte graphique haut de gamme. Les performances de référence pour le test comparatif correspondent à l’implémentation monofil classique d’une animation filaire. Les résultats de ces tests sont présentés en figure 4. Le test n° 1 correspond aux performances de l’animation filaire sur CPU, avec habillement sur CPU aussi. Le test n° 2 correspond aux performances de l’animation filaire sur GPU, avec habillement sur GPU également. On constate que l’implémentation sur CPU est largement plus lente que sur GPU.
Figure 4. Test comparatif des performances en animation filaire monofil classique sur Configuration A.
Ensuite, ce sont les performances des implémentations multifils de l’animation filaire que l’on soumet au test comparatif. Les résultats de ces tests sont présentés en figure 5. Pour l’animation sur CPU, la solution se révèle largement plus performante que l’implémentation monofil classique (comparer les tests n° 3, 4 et 5 du test n° 1), avec une accélération d’un facteur d’environ 2,69. Pour l’animation GPU, la solution affiche des gains de performances évidents par rapport à l’approche monofil (comparer les tests n° 6, 7 et 8 du test n° 2), mais ces gains ne sont toutefois pas aussi importants qu’avec l’animation sur CPU. La solution se traduit, pour l’animation sur CPU, par des performances comparables (comparer les tests n° 4 et 7) voire supérieures à celle d’un habillement sur GPU (comparer les tests n° 4 et 2 ou bien 5 et 8). Dans la mesure où l’animation sur CPU confie une plus grande partie du pipelining de rendu au processus que celle sur GPU, elle tire parti du multicœur et le trafic diminue au niveau du bus.
Figure 5. Essais comparatifs de la solution sur Configuration A (trois fils d’animation).
La figure 5 montre également que la mise à jour intermittente n’a pas d’incidence positive sur les performances applicatives. Néanmoins, comme l’illustre la figure 5, si l’allongement de l’intervalle de mise à jour se traduit par une augmentation de la fréquence image, il fait aussi baisser la fréquence de ces mises à jour. En revanche, le confort visuel risque de diminuer en raison de la faible fréquence de rafraîchissement de l’animation (par exemple en dessous de 15). Dans le cadre du test n° 4, une mise à jour intervient toutes les six images pour donner à l’animation sur CPU des performances comparables à celle sur GPU ainsi que pour maintenir la fréquence de rafraîchissement entre 21 et 26 images.
C’est l’outil Intel® Thread Profiler [8] qui a servi à capturer en détail le comportement des fils dans le cadre de la solution. Il s’agit d’un outil d’analyse de la parallélisation d’un logiciel, qui exploite l’analyseur de performances VTune™. Il établit un profil de répartition des charges entre les fils et repère les risques d’allongement des temps système dus à la parallélisation. En s’en servant sur la démo, on constate (figure 6) que les fils d’animation et le fil principal travaillent presque totalement en parallèle (cf. barre vert clair « Fully Utilized ») avec de très faibles temps systèmes (« Overhead ») dus au double tamponnage. Grâce à l’ordonnancement dynamique du pool de fils, les charges (barre « Active » en vert foncé) sont relativement bien réparties entre les fils d’animation. En outre, la différence de charge entre le fil principal et les fils d’animation suggère que ces derniers peuvent assumer des tâches d’animation plus lourdes et dont la réalisation est plus longue.
Figure 6. Résultats moyens obtenus avec l’outil Intel® Thread Profiler lors du profilage de la démo de la solution.
Pour valider la stratégie d’exploitation des ressources mise en œuvre par la solution pour l’animation sur CPU, nous avons réalisé des essais comparatifs sur la démo avec diverses options de création de maillage DirectX. Les résultats (tableau 2) montrent que c’est la gestion des ressources qui procure les meilleures performances pour la solution et que d’autres stratégies en la matière risquent de déboucher sur le blocage de l’application en contexte multifil.
|
Test n° (images par seconde) |
Option de maillage |
Fils d’animation = 0 |
Fils d’animation = 3 |
|
9 |
D3DXMESH_MANAGED |
165 |
216 |
|
10 |
D3DXMESH_DYNAMIC |
90 |
Blocage |
|
11 |
D3DXMESH_SYSTEMMEM |
91 |
Blocage |
|
12 |
D3DXMESH_WRITEONLY |
12 |
Blocage |
Tableau 2. Incidence de diverses stratégies de gestion des ressources sur les performances de l’animation sur CPU appliquant la solution.
La Configuration B est un PC portable équipé d’un processeur double cœur et d’un sous-système graphique intégré. Les essais comparatifs correspondants portent sur les performances des implémentations multifils de l’animation filaire au moyen de la solution.
Figure 7. Essais comparatifs de la solution sur Configuration B (un fil d’animation).
Les résultats montrent que les performances sur CPU avec la solution sont nettement meilleures (multipliées par un facteur compris entre 3,5 et 4,2 environ) que sur GPU. Ici, c’est le GPU qui constitue le goulet d’étranglement, aussi l’habillement sur le CPU décharge-t-il le processeur. L’intervalle de mise à jour n’a quant à lui pratiquement aucune incidence pour l’animation sur la Configuration B, que celle-ci intervienne sur le GPU ou sur le CPU. Les solutions graphiques intégrées d’Intel exploitant une architecture mémoire UMA (Unified Memory Architecture), où le GPU ne dispose d’aucune mémoire vidéo locale et accède donc systématiquement à la mémoire vive pour y chercher les données, un intervalle de mise à jour plus long n’économise donc pas la bande passante.
Autres considérations
L’implémentation de la démo n’autorise la fixation que d’un seul et même intervalle pour l’ensemble des modèles. Pourtant, les moteurs de jeu réels exigent souvent un pilotage plus fin. Or il existe une méthode directe pour obtenir deux intervalles différents, à savoir le rendu séquentiel par le fil principal des modèles qui nécessitent une mise à jour immédiate, les autres étant traités par le pool de fils par défaut. On peut aussi envisager une méthode plus souple, avec un pool assorti de plusieurs files d’attente hiérarchisées. Les files à forte priorité stockent alors les modèles à animation rapide, tandis que les files à faible priorité stockent les modèles à animation plus lente. Les fils d’animation du pool traitent ainsi les files prioritaires avant les autres. Dans la mesure où le fil principal est synchrone par rapport aux fils d’animation correspondant à chaque intervalle, les priorités peuvent se rééquilibrer de façon à ce que les tâches à faible niveau de priorité ne soient pas ignorées.
Même si la solution est prévue pour l’animation filaire, elle n’en reste pas moins applicable à d’autres types d’animation : morphage, effets de particules, modélisation de tissus, dynamiques des corps mous, textures animées, etc. Par ailleurs, outre la transformation du squelette et de l’habillage, la tâche du fil d’animation, dans le cadre de la solution, peut recouvrir d’autres opérations lourdes en calcul : simulation physique, moteur d’intelligence artificielle (AI), détection des collisions, extrusion des shadow volumes, etc.
Conclusion
Cet article propose une solution de parallélisation simple et efficace pour stimuler les performances de l’animation filaire, en tirant parti de la puissance du multicœur. Cette solution consiste à paralléliser le pipelining de rendu par le recours à un pool de fils, un double tamponnage et une mise à jour intermittente. Elle se révèle profiter tant à l’animation filaire sur CPU que sur GPU, et d’autant plus dans le premier cas. Les performances qu’elle dégage sont en l’occurrence très largement supérieures à celle d’une implémentation classique, sans parallélisation. De plus, elle se traduit, dans le cas de l’animation sur CPU, par des performances tout à fait comparables à celles que l’on obtient sur des cartes graphiques haut de gamme, et en net progrès par rapport aux résultats des moteurs graphiques intégrés courants. Elle exploite le surcroît de puissance des processeurs multicœurs et ménage les ressources GPU dans le cas du rendu de scènes complexes. Elle convient donc tout à fait aux jeux vidéo qui exploitent surtout le GPU ainsi qu’à ceux qui demandent une large compatibilité graphique. Il est également possible d’en faire profiter d’autres types d’animations. Dans le cadre du développement de jeux vidéo, on pourra également l’utiliser conjointement à d’autres méthodes d’optimisation, afin d’équilibrer la charge entre le CPU et le GPU et ainsi obtenir des performances optimales sur systèmes multicœurs.
Remerciements
Les auteurs souhaitent remercier Adam Lake, Bruce Zhang, Clay Breshears et Ron Fosner pour avoir effectué la révision technique de cet article.
Bibliographie
[1] J.M.P van Waveren (Id Software), « Optimizing the Rendering Pipeline of Animated Models Using the Intel Streaming SIMD Extensions ».
[2] MSDN : fonction QueueUserWorkItem
(http://msdn2.microsoft.com/en-us/library/ms684957(VS.85).aspx).
[3] MSDN : fonction QueueUserWorkItem
(http://msdn2.microsoft.com/en-us/library/ms686360(VS.85).aspx).
[4] Documentation Microsoft DirectX pour C++ : D3DCREATE_MULTITHREADED.
[5] Documentation Microsoft DirectX pour C++ : « Performance Optimizations » (Optimisation des performances).
[6] Documentation Microsoft DirectX pour C++ : « Resource Management Best Practices » (Bonnes pratiques de gestion des ressources).
[7] SDK Microsoft DirectX (avril 2007) : exemple MultiAnimation en C++ (septembre 2003).
[8] Intel® Thread Profiler (http://www3.intel.com/cd/software/products/asmo-na/eng/219690.htm).
Appendice
Code source téléchargeable de la démo [ZIP de 1,1 Mo]
Quelques mots sur les auteurs
Sheng Guo est ingénieur applicatif à la division Developer Relations d’Intel. Il est chargé de faire profiter les éditeurs de jeux des technologies de pointe d’Intel en la matière et de leur permettre d’optimiser leurs titres pour les plates-formes Intel. Il travaille dans le domaine du graphisme 3D en temps réel depuis plusieurs années. Il est titulaire d’une maîtrise d’informatique de l’université de Nankin (Chine).
Dave Bookout est ingénieur en logiciels graphiques à la division Visual Computing Software d’Intel. Il travaille sur les algorithmes graphiques qui accompagneront la plate-forme Larrabee (nom de code). Il est titulaire d’un M.S. (Master of Science) d’informatique de l’Oregon Graduate Institute ainsi que d’un B.A. (Bachelor of Arts) d’anglais de l’université Purdue (Indiana).
Reportez-vous à notre Notice d'optimisation pour plus d'informations sur les choix et l'optimisation des performances dans les produits logiciels Intel.
Commentaires (0) 
Trackbacks (0)
Réagir 
Day Kol (Intel)
|
