Optimisations des performances (Direct3D 9)
Chaque développeur qui crée des applications en temps réel qui utilisent des graphiques 3D s’inquiète de l’optimisation des performances. Cette section fournit des instructions pour obtenir les meilleures performances à partir de votre code.
- conseils généraux sur les performances
- bases de données et culling
- primitives batching
- conseils d’éclairage
- taille de texture
- transformations de matrice
- à l’aide de textures dynamiques
- à l’aide de mémoires tampons dynamiques de vertex et d’index
- à l’aide de maillages
- de performances de la mémoire tampon Z
Conseils généraux sur les performances
- Effacez uniquement quand vous devez.
- Réduisez les modifications d’état et regroupez les modifications d’état restantes.
- Utilisez des textures plus petites, si vous pouvez le faire.
- Dessinez des objets dans votre scène de l’avant vers l’arrière.
- Utilisez des bandes de triangles plutôt que des listes et des ventilateurs. Pour optimiser les performances du cache de vertex, organisez les bandes pour réutiliser les sommets de triangle plus tôt, plutôt que plus tard.
- Dégrader correctement les effets spéciaux qui nécessitent une part disproportionnée des ressources système.
- Testez constamment les performances de votre application.
- Réduisez les commutateurs de mémoire tampon de vertex.
- Utilisez les mémoires tampons de vertex statiques si possible.
- Utilisez une mémoire tampon de vertex statique volumineuse par FVF pour les objets statiques, plutôt qu’une par objet.
- Si votre application a besoin d’un accès aléatoire dans la mémoire tampon de vertex dans la mémoire AGP, choisissez une taille de format de vertex qui est un multiple de 32 octets. Sinon, sélectionnez le format le plus petit approprié.
- Dessinez à l’aide de primitives indexées. Cela peut permettre une mise en cache de vertex plus efficace au sein du matériel.
- Si le format de mémoire tampon de profondeur contient un canal de gabarit, effacez toujours la profondeur et les canaux de gabarit en même temps.
- Combinez l’instruction de nuanceur et la sortie de données, le cas échéant. Par exemple:
// Rather than doing a multiply and add, and then output the data with // two instructions: mad r2, r1, v0, c0 mov oD0, r2 // Combine both in a single instruction, because this eliminates an // additional register copy. mad oD0, r1, v0, c0
Bases de données et culling
La création d’une base de données fiable des objets dans votre monde est essentielle à d’excellentes performances dans Direct3D. Il est plus important que les améliorations apportées à la rastérisation ou au matériel.
Vous devez conserver le nombre de polygones le plus bas que vous pouvez éventuellement gérer. Conception d’un nombre de polygones faible en créant des modèles à faible polygone à partir du début. Ajoutez des polygones si vous pouvez le faire sans sacrifier les performances ultérieurement dans le processus de développement. N’oubliez pas que les polygones les plus rapides sont ceux que vous ne dessinez pas.
Primitives de traitement par lots
Pour obtenir les meilleures performances de rendu pendant l’exécution, essayez de travailler avec des primitives dans des lots et de conserver le nombre de modifications d’état de rendu aussi bas que possible. Par exemple, si vous avez un objet avec deux textures, regroupez les triangles qui utilisent la première texture et suivez-les avec l’état de rendu nécessaire pour modifier la texture. Regroupez ensuite tous les triangles qui utilisent la deuxième texture. La prise en charge matérielle la plus simple pour Direct3D est appelée avec des lots d’états de rendu et des lots de primitives via la couche d’abstraction matérielle (HAL). Plus les instructions sont traitées par lots, moins d’appels HAL sont effectués pendant l’exécution.
Conseils d’éclairage
Étant donné que les lumières ajoutent un coût par vertex à chaque trame rendue, vous pouvez améliorer considérablement les performances en faisant attention à la façon dont vous les utilisez dans votre application. La plupart des conseils suivants dérivent de la maxima : « le code le plus rapide est le code qui n’est jamais appelé ».
- Utilisez autant de sources de lumière que possible. Pour augmenter le niveau d’éclairage global, par exemple, utilisez la lumière ambiante au lieu d’ajouter une nouvelle source de lumière.
- Les lumières directionnelles sont plus efficaces que les lumières pointées ou les projecteurs. Pour les lumières directionnelles, la direction vers la lumière est fixe et n’a pas besoin d’être calculée sur une base par vertex.
- Les projecteurs peuvent être plus efficaces que les lumières de point, car la zone en dehors du cône de lumière est calculée rapidement. Si les projecteurs sont plus efficaces ou non dépendent de la quantité de votre scène est allumée par le projecteur.
- Utilisez le paramètre de plage pour limiter vos lumières aux seules parties de la scène que vous devez illuminer. Tous les types de lumière sortent assez tôt quand ils sont hors de portée.
- Specular met en surbrillance presque double le coût d’une lumière. Utilisez-les uniquement quand vous devez. Définissez l’état de rendu D3DRS_SPECULARENABLE sur 0, la valeur par défaut, dans la mesure du possible. Lorsque vous définissez des matériaux, vous devez définir la valeur de puissance spéculaire sur zéro pour désactiver les surbrillances spéculaires pour ce matériau ; il suffit de définir la couleur spéculaire sur 0,0,0 n’est pas suffisant.
Taille de texture
Les performances de mappage de texture dépendent fortement de la vitesse de mémoire. Il existe plusieurs façons d’optimiser les performances du cache des textures de votre application.
- Conservez les textures petites. Plus les textures sont réduites, plus elles ont de chances d’être conservées dans le cache secondaire du processeur principal.
- Ne modifiez pas les textures par primitive. Essayez de conserver les polygones regroupés dans l’ordre des textures qu’ils utilisent.
- Utilisez des textures carrées dans la mesure du possible. Les textures dont les dimensions sont 256 x 256 sont les plus rapides. Si votre application utilise quatre textures 128x128, par exemple, essayez de s’assurer qu’elles utilisent la même palette et placez-les dans une texture de 256 x 256. Cette technique réduit également la quantité de permutation de texture. Bien sûr, vous ne devez pas utiliser de textures de 256 x 256, sauf si votre application nécessite une grande quantité de texte, car, comme mentionné, les textures doivent être conservées aussi petites que possible.
Transformations de matrice
Direct3D utilise les matrices mondiales et d’affichage que vous avez définies pour configurer plusieurs structures de données internes. Chaque fois que vous définissez une nouvelle matrice de monde ou d’affichage, le système recalcule les structures internes associées. La définition de ces matrices fréquemment ( par exemple, des milliers de fois par trame) prend beaucoup de temps. Vous pouvez réduire le nombre de calculs requis en concaténant vos matrices de monde et d’affichage dans une matrice d’affichage mondial que vous définissez comme matrice mondiale, puis en définissant la matrice d’affichage sur l’identité. Conservez les copies mises en cache des matrices de monde et d’affichage individuelles afin que vous puissiez modifier, concaténer et réinitialiser la matrice mondiale si nécessaire. Pour plus de clarté dans cette documentation, les exemples Direct3D utilisent rarement cette optimisation.
Utilisation de textures dynamiques
Pour savoir si le pilote prend en charge les textures dynamiques, vérifiez l’indicateur D3DCAPS2_DYNAMICTEXTURES de la structure D3DCAPS9.
Gardez à l’esprit les éléments suivants lors de l’utilisation de textures dynamiques.
- Ils ne peuvent pas être gérés. Par exemple, leur pool ne peut pas être D3DPOOL_MANAGED.
- Les textures dynamiques peuvent être verrouillées, même si elles sont créées dans D3DPOOL_DEFAULT.
- D3DLOCK_DISCARD est un indicateur de verrou valide pour les textures dynamiques.
Il est judicieux de créer une seule texture dynamique par format et éventuellement par taille. Les mipmaps dynamiques, les cubes et les volumes ne sont pas recommandés en raison de la surcharge supplémentaire dans le verrouillage de chaque niveau. Pour les mipmaps, D3DLOCK_DISCARD est autorisé uniquement au niveau supérieur. Tous les niveaux sont ignorés en verrouillant uniquement le niveau supérieur. Ce comportement est le même pour les volumes et les cubes. Pour les cubes, le niveau supérieur et le visage 0 sont verrouillés.
Le pseudocode suivant montre un exemple d’utilisation d’une texture dynamique.
DrawProceduralTexture(pTex)
{
// pTex should not be very small because overhead of
// calling driver every D3DLOCK_DISCARD will not
// justify the performance gain. Experimentation is encouraged.
pTex->Lock(D3DLOCK_DISCARD);
<Overwrite *entire* texture>
pTex->Unlock();
pDev->SetTexture();
pDev->DrawPrimitive();
}
Utilisation de tampons de vertex et d’index dynamiques
Le verrouillage d’une mémoire tampon de vertex statique pendant que le processeur graphique utilise la mémoire tampon peut avoir une pénalité significative en matière de performances. L’appel de verrou doit attendre que le processeur graphique soit terminé de lire les données de vertex ou d’index à partir de la mémoire tampon avant de pouvoir revenir à l’application appelante, un délai important. Le verrouillage, puis le rendu à partir d’une mémoire tampon statique plusieurs fois par image empêche également le processeur graphique de mettre en mémoire tampon les commandes de rendu, car il doit terminer les commandes avant de renvoyer le pointeur de verrou. Sans commandes mises en mémoire tampon, le processeur graphique reste inactif tant que l’application n’a pas terminé de remplir la mémoire tampon de vertex ou la mémoire tampon d’index et émet une commande de rendu.
Dans l’idéal, les données de vertex ou d’index ne changent jamais, mais cela n’est pas toujours possible. Il existe de nombreuses situations où l’application doit modifier les données de vertex ou d’indexer toutes les images, peut-être même plusieurs fois par image. Pour ces situations, la mémoire tampon de vertex ou d’index doit être créée avec D3DUSAGE_DYNAMIC. Cet indicateur d’utilisation entraîne l’optimisation de Direct3D pour les opérations de verrouillage fréquentes. D3DUSAGE_DYNAMIC est utile uniquement lorsque la mémoire tampon est verrouillée fréquemment ; les données qui restent constantes doivent être placées dans une mémoire tampon de vertex ou d’index statique.
Pour recevoir une amélioration des performances lors de l’utilisation de tampons de vertex dynamiques, l’application doit appeler IDirect3DVertexBuffer9 ::Lock ou IDirect3DIndexBuffer9 ::Lock avec les indicateurs appropriés. D3DLOCK_DISCARD indique que l’application n’a pas besoin de conserver les anciennes données de vertex ou d’index dans la mémoire tampon. Si le processeur graphique utilise toujours la mémoire tampon lorsque le verrou est appelé avec D3DLOCK_DISCARD, un pointeur vers une nouvelle région de mémoire est retourné au lieu des anciennes données de mémoire tampon. Cela permet au processeur graphique de continuer à utiliser les anciennes données pendant que l’application place les données dans la nouvelle mémoire tampon. Aucune gestion supplémentaire de la mémoire n’est requise dans l’application ; l’ancienne mémoire tampon est réutilisée ou détruite automatiquement lorsque le processeur graphique est terminé avec celui-ci. Notez que le verrouillage d’une mémoire tampon avec D3DLOCK_DISCARD ignore toujours l’intégralité de la mémoire tampon, en spécifiant un décalage différent de zéro ou un champ de taille limitée, ne conserve pas les informations dans les zones déverrouillées de la mémoire tampon.
Il existe des cas où la quantité de données dont l’application a besoin pour stocker par verrou est petite, par exemple l’ajout de quatre sommets pour afficher un sprite. D3DLOCK_NOOVERWRITE indique que l’application ne remplacera pas les données déjà utilisées dans la mémoire tampon dynamique. L’appel de verrou retourne un pointeur vers les anciennes données, ce qui permet à l’application d’ajouter de nouvelles données dans des régions inutilisées du vertex ou de la mémoire tampon d’index. L’application ne doit pas modifier les sommets ou les index utilisés dans une opération de dessin, car elles peuvent toujours être utilisées par le processeur graphique. L’application doit ensuite utiliser D3DLOCK_DISCARD une fois la mémoire tampon dynamique pleine pour recevoir une nouvelle région de mémoire, en ignorant les anciennes données de vertex ou d’index une fois le processeur graphique terminé.
Le mécanisme de requête asynchrone est utile pour déterminer si les sommets sont toujours utilisés par le processeur graphique. Émettez une requête de type D3DQUERYTYPE_EVENT après le dernier appel DrawPrimitive qui utilise les sommets. Les sommets ne sont plus utilisés lorsque IDirect3DQuery9 ::GetData retourne S_OK. Le verrouillage d’une mémoire tampon avec D3DLOCK_DISCARD ou aucun indicateur garantit toujours que les sommets sont synchronisés correctement avec le processeur graphique. Toutefois, l’utilisation du verrou sans indicateur entraîne la pénalité de performances décrite précédemment. D’autres appels d’API tels que IDirect3DDevice9 ::BeginScene, IDirect3DDevice9 ::EndSceneet IDirect3DDevice9 ::P resent ne garantissent pas que le processeur graphique est terminé à l’aide de sommets.
Vous trouverez ci-dessous des façons d’utiliser des mémoires tampons dynamiques et des indicateurs de verrouillage appropriés.
// USAGE STYLE 1
// Discard the entire vertex buffer and refill with thousands of vertices.
// Might contain multiple objects and/or require multiple DrawPrimitive
// calls separated by state changes, etc.
// Determine the size of data to be moved into the vertex buffer.
UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
// Discard and refill the used portion of the vertex buffer.
CONST DWORD dwLockFlags = D3DLOCK_DISCARD;
// Lock the vertex buffer.
BYTE* pBytes;
if( FAILED( m_pVertexBuffer->Lock( 0, 0, &pBytes, dwLockFlags ) ) )
return false;
// Copy the vertices into the vertex buffer.
memcpy( pBytes, pVertices, nSizeOfData );
m_pVertexBuffer->Unlock();
// Render the primitives.
m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, nNumberOfVertices/3)
// USAGE STYLE 2
// Reusing one vertex buffer for multiple objects
// Determine the size of data to be moved into the vertex buffer.
UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
// No overwrite will be used if the vertices can fit into
// the space remaining in the vertex buffer.
DWORD dwLockFlags = D3DLOCK_NOOVERWRITE;
// Check to see if the entire vertex buffer has been used up yet.
if( m_nNextVertexData > m_nSizeOfVB - nSizeOfData )
{
// No space remains. Start over from the beginning
// of the vertex buffer.
dwLockFlags = D3DLOCK_DISCARD;
m_nNextVertexData = 0;
}
// Lock the vertex buffer.
BYTE* pBytes;
if( FAILED( m_pVertexBuffer->Lock( (UINT)m_nNextVertexData, nSizeOfData,
&pBytes, dwLockFlags ) ) )
return false;
// Copy the vertices into the vertex buffer.
memcpy( pBytes, pVertices, nSizeOfData );
m_pVertexBuffer->Unlock();
// Render the primitives.
m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST,
m_nNextVertexData/m_nVertexStride, nNumberOfVertices/3)
// Advance to the next position in the vertex buffer.
m_nNextVertexData += nSizeOfData;
Utilisation de maillages
Vous pouvez optimiser les maillages à l’aide de triangles indexés Direct3D au lieu de bandes de triangles indexées. Le matériel découvrira que 95 pour cent des triangles successifs forment réellement des bandes et s’ajustent en conséquence. De nombreux pilotes le font également pour le matériel plus ancien.
Les objets de maillage D3DX peuvent avoir chaque triangle, ou visage, marqué avec un DWORD, appelé attribut de ce visage. La sémantique de DWORD est définie par l’utilisateur. Ils sont utilisés par D3DX pour classifier le maillage en sous-ensembles. L’application définit des attributs par visage à l’aide de l’appel ID3DXMesh ::LockAttributeBuffer. La méthode ID3DXMesh ::Optimize a la possibilité de regrouper les sommets de maillage et les visages sur les attributs à l’aide de l’option D3DXMESHOPT_ATTRSORT. Lorsque cela est fait, l’objet mesh calcule une table d’attributs qui peut être obtenue par l’application en appelant ID3DXBaseMesh ::GetAttributeTable. Cet appel retourne 0 si le maillage n’est pas trié par attributs. Il n’existe aucun moyen pour une application de définir une table d’attributs, car elle est générée par la méthode ID3DXMesh ::Optimize . Le tri d’attribut est sensible aux données. Par conséquent, si l’application sait qu’un maillage est trié, il doit toujours appeler ID3DXMesh ::Optimize pour générer la table d’attributs.
Les rubriques suivantes décrivent les différents attributs d’un maillage.
ID d’attribut
Un ID d’attribut est une valeur qui associe un groupe de visages à un groupe d’attributs. Cet ID décrit le sous-ensemble de visages ID3DXBaseMesh ::D rawSubset doit dessiner. Les ID d’attribut sont spécifiés pour les visages dans la mémoire tampon d’attribut. Les valeurs réelles des ID d’attribut peuvent être tout ce qui correspond à 32 bits, mais il est courant d’utiliser 0 à n où n correspond au nombre d’attributs.
Mémoire tampon d’attribut
La mémoire tampon d’attribut est un tableau de DWORDs (un par visage) qui spécifie le groupe d’attributs auquel appartient chaque visage. Cette mémoire tampon est initialisée à zéro lors de la création d’un maillage, mais elle est remplie par les routines de chargement ou doit être remplie par l’utilisateur si plusieurs attributs avec l’ID 0 sont souhaités. Cette mémoire tampon contient les informations utilisées pour trier le maillage en fonction des attributs dans ID3DXMesh ::Optimize. Si aucune table d’attribut n’est présente, ID3DXBaseMesh ::D rawSubset analyse cette mémoire tampon pour sélectionner les visages de l’attribut donné à dessiner.
Table d’attributs
La table d’attributs est une structure détenue et gérée par le maillage. La seule façon d’en générer une consiste à appeler ID3DXMesh ::Optimize avec le tri des attributs ou une optimisation plus forte activée. La table d’attributs est utilisée pour lancer rapidement un appel primitif de dessin unique pour ID3DXBaseMesh ::D rawSubset. La seule autre utilisation est que la progression des maillages conserve également cette structure. Il est donc possible de voir quels visages et sommets sont actifs au niveau actuel des détails.
Performances de la mémoire tampon Z
Les applications peuvent augmenter les performances lors de l’utilisation de z-buffering et de texturing en veillant à ce que les scènes soient rendues de l’avant à l’arrière. Les primitives en mémoire tampon z texture sont prétestées par rapport à la mémoire tampon z sur une ligne d’analyse. Si une ligne d’analyse est masquée par un polygone précédemment rendu, le système le rejette rapidement et efficacement. La mise en mémoire tampon Z peut améliorer les performances, mais la technique est la plus utile lorsqu’une scène dessine les mêmes pixels plusieurs fois. Il est difficile de calculer exactement, mais vous pouvez souvent effectuer une approximation étroite. Si les mêmes pixels sont dessinés moins de deux fois, vous pouvez obtenir les meilleures performances en activant la mise en mémoire tampon z et en rendant la scène de retour en arrière vers l’avant.
Rubriques connexes