Wie genau funktioniert SpriteBatch von XNA?

38

Genauer gesagt, wenn ich diese Funktionalität in einer anderen API (z. B. in OpenGL) von Grund auf neu erstellen müsste, wozu müsste sie fähig sein?

Ich habe eine allgemeine Vorstellung von einigen der Schritte, z. B. wie eine orthografische Projektionsmatrix erstellt und für jeden Draw-Aufruf ein Quad erstellt wird.

Ich bin jedoch nicht sehr vertraut mit dem Batching-Prozess. Sind alle Quads im selben Vertex-Puffer gespeichert? Benötigt es einen Indexpuffer? Wie werden unterschiedliche Texturen behandelt?

Wenn möglich, wäre ich Ihnen dankbar, wenn Sie mich von SpriteBatch.Begin () bis SpriteBatch.End () durch den Prozess führen könnten, zumindest wenn Sie den Standardmodus "Zurückgestellt" verwenden.

David Gouveia
quelle
Schauen Sie sich an, wie es in MonoGame über OpenGL implementiert wird: github.com/mono/MonoGame/blob/master/MonoGame.Framework/…
Den
Haben Sie versucht, DotPeek oder Reflector auf Microsoft.Xna.Graphics zu verwenden (ich empfehle jedoch nichts Illegales :)?
Den
Danke für den Link. Und dieser Gedanke kam mir in den Sinn (ich habe sogar dotPeek hier zur Hand), aber ich dachte, es wäre eine bessere Idee, von jemandem, der das vielleicht schon einmal getan hat und den Vorgang auswendig weiß, eine allgemeine Beschreibung anzufordern. Auf diese Weise würde die Antwort hier erhalten bleiben und anderen Menschen zur Verfügung stehen. In dem Fall, dass niemand eine solche Beschreibung bereitstellt, werde ich die Analyse selbst durchführen und eine Antwort auf meine eigene Frage posten, aber ich werde noch ein bisschen warten.
David Gouveia
Ja, das ist der Grund, warum ich einen Kommentar verwendet habe. Ich denke, Andrew Russell könnte eine gute Person sein, um diese Frage zu beantworten. Er ist in dieser Community aktiv und arbeitet an ExEn, einer Alternative zu MonoGame: andrewrussell.net/exen .
Den
Ja, das ist die Art von Frage, über die Andrew Russel (oder Shawn Hargreaves im XNA-Forum) normalerweise viel Aufschluss geben kann.
David Gouveia

Antworten:

42

Ich habe das Verhalten von SpriteBatch für eine plattformübergreifende Engine, an der ich arbeite, im verzögerten Modus nachgebildet. Hier sind die Schritte, die ich bisher rückgängig gemacht habe:

  1. Spritebatch Konstruktor: schafft ein DynamicIndexBuffer, DynamicVertexBufferund das Array von VertexPositionColorTexturefester Größe (in diesem Fall die maximale Chargengröße - 2048 für Sprites und 8192 für die Eckpunkte).

    • Der Indexpuffer wird mit den Eckpunkten der zu zeichnenden Quads gefüllt (0-1-2, 0-2-3, 4-5-6, 4-6-7 usw.).
    • Auch ein internes Array von SpriteInfoStrukturen wird erstellt. Hier werden temporäre Sprite-Einstellungen gespeichert, die beim Stapeln verwendet werden.
  2. SpriteBatch.Begin: intern speichert die Werte BlendState, SamplerStateusw. festgelegt und überprüft , ob es zweimal ohne ein aufgerufen wurde SpriteBatch.Endzwischendurch .

  3. SpriteBatch.Draw: Nimmt alle Sprite-Informationen (Textur, Position, Farbe) und kopiert sie in eine SpriteInfo. Wenn die maximale Stapelgröße erreicht ist, wird der gesamte Stapel gezeichnet, um Platz für neue Sprites zu schaffen.

    • SpriteBatch.DrawStringGibt einfach ein Drawfür jedes Zeichen der Zeichenfolge aus, wobei Kerning und Abstand berücksichtigt werden.
  4. SpriteBatch.End: Führt die folgenden Vorgänge aus:

    • Legt die in angegebenen Renderstatus fest Begin.
    • Erstellt die orthographische Projektionsmatrix.
    • Wendet den SpriteBatchShader an.
    • Bindet das DynamicVertexBufferund DynamicIndexBuffer.
    • Führt den folgenden Stapelvorgang aus:

      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: Führt die folgenden Vorgänge für jeden SpriteInfoim Stapel aus:

    • Nimmt die Position des Sprites und berechnet die endgültige Position der vier Scheitelpunkte entsprechend dem Ursprung und der Größe. Wendet die vorhandene Drehung an.
    • Berechnet die UV-Koordinaten und wendet SpriteEffectssie an.
    • Kopiert die Sprite-Farbe.
    • Diese Werte werden dann in dem VertexPositionColorTexturezuvor erstellten Array von Elementen gespeichert . Wenn alle Sprites berechnet wurden, SetDatawird am angerufen DynamicVertexBufferund ein DrawIndexedPrimitivesAnruf ausgelöst.
    • Der Vertex-Shader führt nur eine Transformationsoperation aus, und der Pixel-Shader wendet die Tönung auf die aus der Textur abgerufene Farbe an.
r2d2rigo
quelle
Genau das habe ich gebraucht, vielen Dank! Ich habe ein bisschen gebraucht, um all das in mich aufzunehmen, aber ich glaube, ich habe endlich alle Details verstanden. Eines, das meine Aufmerksamkeit auf sich gezogen hat und mir vorher nicht ganz bewusst war, ist, dass es auch im verzögerten Modus funktioniert, wenn ich meine SpriteBatch.Draw-Aufrufe nach Textur sortieren kann (dh wenn mir die Reihenfolge dieser Aufrufe egal ist) Reduzieren Sie die Anzahl der RenderBatch-Vorgänge und beschleunigen Sie möglicherweise das Rendern.
David Gouveia
Ich kann es übrigens nicht in der Dokumentation finden. Wie viele Draw-Aufrufe können Sie ausführen, bevor der Puffer voll ist? Füllt der Aufruf von DrawText diesen Puffer mit der gesamten Anzahl von Zeichen in der Zeichenfolge?
David Gouveia
Beeindruckend! Wie wird deine Engine mit ExEn und MonoGame verglichen? Benötigt es auch MonoTouch und MonoAndroid? Vielen Dank.
Den
@DavidGouveia: 2048 Sprites ist die maximale Stapelgröße - die Antwort wurde bearbeitet und der Einfachheit halber hinzugefügt. Ja, wenn Sie nach Textur sortieren und den Z-Buffer für die Sprite-Sortierung sorgen lassen, werden die minimalen Draw-Aufrufe verwendet. Wenn Sie es brauchen, versuchen Sie es mit der Implementierung SpriteSortMode.Textureund zeichnen Sie in der gewünschten Reihenfolge. Lassen SpriteBatchSie dann die Sortierung durchführen.
r2d2rigo
1
@Den: ExEn und MonoGame sind APIs auf niedrigerer Ebene, mit denen XNA-Code auf Nicht-Windows-Plattformen ausgeführt werden kann. Das Projekt, an dem ich arbeite, ist eine komponentenbasierte Engine, die ausschließlich in C # geschrieben ist. Wir benötigen jedoch eine sehr dünne Ebene, um auf System-APIs zugreifen zu können (an dieser Stelle arbeite ich). Wir haben uns SpriteBatchaus Bequemlichkeitsgründen für eine Neuimplementierung entschieden . Und ja, es benötigt MonoTouch / MonoDroid, da es nur C # ist!
r2d2rigo
13

Ich möchte nur darauf hinweisen, dass der XNA SpriteBatch-Code nach DirectX portiert und von einem früheren Mitglied des XNA-Teams als OSS veröffentlicht wurde. Also, wenn Sie genau sehen möchten, wie es in XNA funktioniert , dann gehen Sie.

ClassicThunder
quelle
+1 für "Code werde ich nie verwenden, aber es ist interessant zu überfliegen"
Kyle Baran
Die neueste Version des XNA SpriteBatch als natives C ++ finden Sie auf GitHub h / cpp . Als Bonus ist eine DirectX12 - Version ebenfalls verfügbar h / cav
Chuck Walbourn