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 ( class
pas 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 SetData
et GetData
sur 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 Effect
s - 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 SetData
le 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 à RenderTarget2D
celui 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 :)