Informations sur le rendu, les lots, la carte graphique, les performances, etc. + XNA?

12

Je sais que le titre est un peu vague mais il est difficile de décrire ce que je recherche vraiment, mais voilà.

En ce qui concerne le rendu du processeur, les performances sont généralement faciles à estimer et simples, mais en ce qui concerne le GPU en raison de mon manque d'informations techniques de base, je ne sais rien. J'utilise XNA donc ce serait bien si la théorie pouvait être liée à ça.

Donc, ce que je veux vraiment savoir, c'est ce qui se passe quand et où (CPU / GPU) lorsque vous effectuez des actions de dessin spécifiques? Qu'est-ce qu'un lot? Quelle influence les effets, les projections, etc. ont-ils? Les données sont-elles conservées sur la carte graphique ou sont-elles transférées à chaque étape? Quand il est question de bande passante, parlez-vous d'une bande passante interne de carte graphique, ou du pipeline du CPU au GPU?
Remarque: Je ne recherche pas réellement d'informations sur la façon dont le processus de dessin se déroule, c'est l'affaire du GPU, je m'intéresse à tous les frais généraux qui précèdent.

J'aimerais comprendre ce qui se passe quand je fais l'action X, pour adapter mes architectures et mes pratiques à cela.

Tous les articles (éventuellement avec des exemples de code), informations, liens, tutoriels qui donnent plus d'informations sur la façon d'écrire de meilleurs jeux sont très appréciés. Merci :)

Aidiakapi
la source
2
Bien qu'il s'agissait à l'origine de XNA, j'ai ajouté la balise DirectX, car c'est la technologie sous-jacente - cela pourrait vous aider à obtenir de meilleures réponses. Consultez également cette réponse qui pourrait vous donner un bon point de départ.
Andrew Russell
@AndrewRussell Merci beaucoup :). J'ai en fait déjà lu divers articles sur le sujet, y compris celui-ci. Mais cela n'a pas couvert tout ce que j'aimerais savoir.
Aidiakapi

Réponses:

20

J'aime penser la performance en termes de " limites ". C'est un moyen pratique de conceptualiser un système interconnecté assez compliqué. Lorsque vous rencontrez un problème de performances, vous posez la question: "Quelles limites suis-je en train d'atteindre?" (Ou: "Suis-je lié au CPU / GPU?")

Vous pouvez le décomposer en plusieurs niveaux. Au plus haut niveau, vous avez le CPU et le GPU. Vous pouvez être lié au processeur (le GPU reste inactif en attente du processeur) ou lié au GPU (le processeur attend le GPU). Voici un bon article de blog sur le sujet.

Vous pouvez le décomposer davantage. Côté CPU , vous pouvez utiliser tous vos cycles sur des données déjà dans le cache CPU. Ou vous pouvez être limité en mémoire , laissant le processeur inactif en attendant que les données arrivent de la mémoire principale ( alors optimisez la disposition de vos données ). Vous pouvez le décomposer encore plus.

(Bien que je fasse un large aperçu des performances concernant XNA, je soulignerai qu'une allocation d'un type de référence ( classpas struct), bien que normalement bon marché, pourrait déclencher le garbage collector, qui brûlera beaucoup de cycles - en particulier sur Xbox 360 . Voir ici pour plus de détails).

Côté GPU , je vais commencer par vous signaler cet excellent article de blog qui contient beaucoup de détails. Si vous voulez un niveau fou de détails sur le pipeline, lisez cette série de billets de blog . (En voici une plus simple ).

Pour le dire ici simplement, certains des plus grands sont les suivants: " limite de remplissage " (combien de pixels vous pouvez écrire dans le backbuffer - souvent combien d'overdraw vous pouvez avoir), " limite de shader " (la complexité de vos shaders et la quantité de données que vous pouvez parcourir), " limite de texture-fetch / texture-bandwidth " (quantité de données de texture auxquelles vous pouvez accéder).

Et, maintenant, nous arrivons au grand - c'est ce que vous demandez vraiment - où le CPU et le GPU doivent interagir (via les différentes API et pilotes). En gros, il y a la " limite de lot " et la " bande passante ". (Notez que la première partie de la série que j'ai mentionnée précédemment va dans de nombreux détails.)

Mais, fondamentalement, un lot ( comme vous le savez déjà ) se produit chaque fois que vous appelez l'une des GraphicsDevice.Draw*fonctions (ou une partie de XNA, par exemple SpriteBatch, le fait pour vous). Comme vous l'avez sans doute déjà lu, vous en obtenez quelques milliers * par image. Il s'agit d'une limite de CPU - elle est donc en concurrence avec votre autre utilisation de CPU. Il s'agit essentiellement du pilote qui compile tout ce que vous lui avez dit de dessiner et l'envoie au GPU.

Et puis il y a la bande passante du GPU. C'est la quantité de données brutes que vous pouvez y transférer. Cela inclut toutes les informations d'état qui vont avec les lots - tout, de la définition de l'état de rendu et des constantes / paramètres de shader (qui incluent des choses comme les matrices de monde / vue / projet), aux sommets lors de l'utilisation des DrawUser*fonctions. Il comprend également tous les appels vers SetDataet GetDatasur les textures, les tampons de vertex, etc.

À ce stade, je dois dire que tout ce que vous pouvez appeler SetData(textures, tampons de vertex et d'index, etc.), ainsi que Effects - reste dans la mémoire du GPU. Il n'est pas constamment renvoyé au GPU. Une commande draw qui fait référence à ces données est simplement envoyée avec un pointeur vers ces données.

(Aussi: vous ne pouvez envoyer des commandes de dessin qu'à partir du thread principal, mais vous pouvez SetDatale faire sur n'importe quel thread.)

XNA complique les choses un peu avec ses classes de rendre publiques ( BlendState, DepthStencilState, etc.). Ces données d'état sont envoyées par appel de tirage (dans chaque lot). Je ne suis pas sûr à 100%, mais j'ai l'impression qu'il est envoyé paresseusement (il n'envoie que l'état qui change). Quoi qu'il en soit, les changements d'état sont bon marché au point d'être gratuits, par rapport au coût d'un lot.

Enfin, la dernière chose à mentionner est le pipeline interne du GPU . Vous ne voulez pas le forcer à vider en écrivant sur les données qu'il doit encore lire ou en lisant les données qu'il doit encore écrire. Un vidage de pipeline signifie qu'il attend la fin des opérations pour que tout soit dans un état cohérent lors de l'accès aux données.

Les deux cas particuliers à surveiller sont les suivants: Faire appel GetDataà quelque chose de dynamique - en particulier à RenderTarget2Dcelui sur lequel le GPU peut écrire. C'est extrêmement mauvais pour les performances - ne le faites pas.

L'autre cas fait appel SetDataà des tampons vertex / index. Si vous devez le faire souvent, utilisez un DynamicVertexBuffer(aussi DynamicIndexBuffer). Ceux-ci permettent au GPU de savoir qu'ils changeront souvent et de faire de la magie de mise en mémoire tampon en interne pour éviter la vidange du pipeline.

(Notez également que les tampons dynamiques sont plus rapides que les DrawUser*méthodes - mais ils doivent être préalloués à la taille maximale requise.)

... Et c'est à peu près tout ce que je sais sur les performances XNA :)

Andrew Russell
la source
Merci beaucoup! C'est exactement ce que je cherchais et espérais :).
Aidiakapi
1
Quelques centaines de lots par image semblent trop pessimistes. La règle générale que j'ai toujours entendue est de 2 000 à 3 000 lots par image. Certains jeux sont connus pour obtenir jusqu'à 10K sur PC, mais je pense que cela nécessite un grand soin.
Nathan Reed
Très bien. Le chiffre «quelques centaines» provient du papier «batch batch batch» - qui répertorie «25 000 lots / s à 100% d'un processeur à 1 GHz». Mais ce document a maintenant une décennie, et les pilotes et les processeurs se sont considérablement améliorés depuis lors. Je vais mettre à jour cela (et mes autres) pour lire "quelques milliers".
Andrew Russell