Comment fonctionne exactement le SpriteBatch de XNA?

38

Pour être plus précis, si j'avais besoin de recréer cette fonctionnalité à partir de zéro dans une autre API (par exemple, dans OpenGL), de quoi aurait-elle besoin?

J'ai une idée générale de certaines étapes, par exemple comment préparer une matrice de projection orthographique et créer un quad pour chaque appel de tirage.

Je ne connais toutefois pas trop le processus de traitement par lots lui-même. Est-ce que tous les quads sont stockés dans le même vertex buffer? A-t-il besoin d'un tampon d'index? Comment sont traitées les différentes textures?

Si possible, merci de me guider tout au long du processus, à partir du moment où SpriteBatch.Begin () est appelé jusqu'à SpriteBatch.End (), du moins lorsque vous utilisez le mode différé par défaut.

David Gouveia
la source
Jetez un oeil à la façon dont il est mis en œuvre monogame via OpenGL: github.com/mono/MonoGame/blob/master/MonoGame.Framework/...
Den
Avez-vous essayé d'utiliser DotPeek ou Reflector sur Microsoft.Xna.Graphics (je ne suggère cependant pas de faire quoi que ce soit d'illégal :)?
Den
Merci pour le lien. Et cette pensée m’a traversé (j’ai même dotPeek ici), mais j’ai pensé qu’il serait peut-être plus judicieux de demander une description générale à quelqu'un qui l’aurait déjà fait auparavant et qui connaît la procédure de mémoire. De cette façon, la réponse serait préservée ici et disponible pour d’autres personnes. Si personne ne fournit une telle description, je vais faire l'analyse moi-même et poster une réponse à ma propre question, mais j'attendrai un peu plus pour l'instant.
David Gouveia
Oui, c'est la raison pour laquelle j'ai utilisé un commentaire. Je pense que Andrew Russell pourrait être une bonne personne pour répondre à cette question. Il est actif dans cette communauté et travaille sur ExEn, une alternative à MonoGame: andrewrussell.net/exen .
Den
Ouais, c’est le genre de question que Andrew Russel (ou Shawn Hargreaves au forum XNA) serait généralement en mesure d’apporter de nombreuses informations.
David Gouveia

Réponses:

42

J'ai en quelque sorte reproduit le comportement de SpriteBatch en mode différé pour un moteur multiplate-forme sur lequel je travaille. Voici donc les étapes pour lesquelles j'ai effectué une ingénierie inverse jusqu'à présent:

  1. Constructeur SpriteBatch: crée un DynamicIndexBuffer, DynamicVertexBufferet un ensemble de la VertexPositionColorTexturetaille fixe (dans ce cas, la taille du lot maximale - 2048 pour les sprites et 8192 pour les sommets).

    • Le tampon d'index est rempli avec les indices de sommet des quads qui seront dessinés (0-1-2, 0-2-3, 4-5-6, 4-6-7, etc.).
    • Un tableau interne de SpriteInfostructures est également créé. Cela stockera les paramètres de sprite temporels à utiliser lors de la mise en lot.
  2. SpriteBatch.Begin: stocke en interne les valeurs de BlendState, SamplerStateetc. spécifiées et vérifie si elle a été appelée deux fois sans SpriteBatch.Endintermédiaire.

  3. SpriteBatch.Draw: prend toutes les informations sur les sprites (texture, position, couleur) et les copie dans un fichierSpriteInfo . Si la taille maximale du lot est atteinte, le lot entier est dessiné pour laisser la place à de nouveaux sprites.

    • SpriteBatch.DrawStringémet simplement un Drawpour chaque caractère de la chaîne, en tenant compte du crénage et de l'espacement.
  4. SpriteBatch.End: effectue les opérations suivantes:

    • Définit les états de rendu spécifiés dans Begin.
    • Crée la matrice de projection orthographique.
    • Applique le SpriteBatchshader.
    • Lie le DynamicVertexBufferet DynamicIndexBuffer.
    • Effectue l'opération de mise en lot suivante:

      startingOffset = 0;
      currentTexture, oldTexture = null;
      
      // Iterate through all sprites
      foreach SpriteInfo in SpriteBuffer
      {
          // Store sprite index and texture
          spriteIndex = SpriteBuffer.IndexOf(SpriteInfo);
          currentTexture = SpriteInfo.Texture;
      
          // Issue draw call if batch count > 0 and there is a texture change
          if (currentTexture != oldTexture)
          {
              if (spriteIndex > startingOffset)
              {
                  RenderBatch(currentTexture, SpriteBuffer, startingOffset,
                              spriteIndex - startingOffset);
              }
              startingOffset = spriteIndex;
              oldTexture = currentTexture;
          }
      }
      
      // Draw remaining batch and clear the sprite data
      RenderBatch(currentTexture, SpriteBuffer, startingOffset,
                  SpriteBuffer.Count - startingOffset);
      SpriteBuffer.Clear();
  5. SpriteBatch.RenderBatch: exécute les opérations suivantes pour chacun des SpriteInfolots du lot:

    • Prend la position de l'image-objet et calcule la position finale des quatre sommets en fonction de l'origine et de la taille. Applique la rotation existante.
    • Calcule les coordonnées UV et applique les valeurs spécifiées SpriteEffects.
    • Copie la couleur du sprite.
    • Ces valeurs sont ensuite stockées dans le tableau des VertexPositionColorTextureéléments créés précédemment. Lorsque tous les sprites ont été calculés, SetDataest appelé sur DynamicVertexBufferet un DrawIndexedPrimitivesappel est émis.
    • Le vertex shader n'effectue qu'une opération de transformation, et le pixel shader applique la teinte sur la couleur extraite de la texture.
r2d2rigo
la source
C'est exactement ce dont j'avais besoin, merci beaucoup! Il m'a fallu un peu pour absorber tout cela, mais je pense que j'ai enfin eu tous les détails. Une des choses qui a attiré mon attention et que je n’étais pas tout à fait au courant, c’est que même en mode différé, si je peux en quelque sorte trier mes appels SpriteBatch.Draw par Texture (c’est-à-dire si je ne me soucie pas de l’ordre de ces appels), réduisez le nombre d'opérations RenderBatch et accélérez probablement le rendu.
David Gouveia
À propos, je n'arrive pas à trouver dans la documentation - quelle est la limite des appels Draw que vous pouvez faire avant que la mémoire tampon ne soit pleine? Et l'appel de DrawText remplit-il ce tampon avec le nombre total de caractères de la chaîne?
David Gouveia
Impressionnant! Comment votre moteur se comparera-t-il à ExEn et à MonoGame? Faudra-t-il également utiliser MonoTouch et MonoAndroid? Merci.
Den
@DavidGouveia: 2048 sprites correspond à la taille maximale du lot - a modifié la réponse et l'a ajoutée pour plus de commodité. Oui, trier par texture et laisser le z-buffer se charger du classement des sprites utiliserait les appels de tirage minimum. Si vous en avez besoin, essayez d'implémenter SpriteSortMode.Texturepour dessiner dans l'ordre que vous voulez et laissez-les SpriteBatchfaire le tri.
r2d2rigo
1
@Den: ExEn et MonoGame sont des API de niveau inférieur qui permettent à du code XNA de s'exécuter sur une plateforme autre que Windows. Le projet sur lequel je travaille est un moteur à base de composants purement écrit en C #, mais nous avons besoin d'une couche très fine pour fournir un accès aux API du système (c'est là que je travaille). Nous avons simplement choisi de réimplémenter SpriteBatchpour plus de commodité. Et oui, il nécessite MonoTouch / MonoDroid car tout est en C #!
r2d2rigo
13

Je veux juste souligner que le code XNA SpriteBatch a été porté sur DirectX et publié en tant que logiciel libre par un ancien membre de l'équipe XNA. Donc, si vous voulez voir exactement comment cela fonctionne dans XNA , c'est parti.

ClassiqueThunder
la source
+1 pour "code que je n'utiliserai jamais, mais il est intéressant de survoler"
Kyle Baran
La dernière version de XNA SpriteBatch en C ++ natif est disponible sur GitHub h / cpp . En bonus, une version DirectX12 est également disponible h / cpp
Chuck Walbourn