Come funziona esattamente SpriteBatch di XNA?


38

Per essere più precisi, se avessi bisogno di ricreare questa funzionalità da zero in un'altra API (ad esempio in OpenGL) cosa dovrebbe essere in grado di fare?

Ho un'idea generale di alcuni passaggi, come il modo in cui prepara una matrice di proiezione ortografica e crea un quad per ogni chiamata di disegno.

Tuttavia, non ho familiarità con il processo di batching stesso. Tutti i quad sono memorizzati nello stesso buffer dei vertici? Ha bisogno di un buffer di indice? Come vengono gestite le diverse trame?

Se possibile, ti sarei grato se potessi guidarmi attraverso il processo da quando SpriteBatch.Begin () viene chiamato fino a SpriteBatch.End (), almeno quando si utilizza la modalità differita predefinita.


Dai un'occhiata a come viene implementato in MonoGame tramite OpenGL: github.com/mono/MonoGame/blob/master/MonoGame.Framework/…
Den

Hai provato a usare DotPeek o Reflector su Microsoft.Xna.Graphics (non sto suggerendo di fare qualcosa di illegale però :)?
Den

Grazie per il link E quel pensiero mi ha attraversato (ho anche dotPeek qui a portata di mano), ma ho pensato che potrebbe essere una migliore idea chiedere una descrizione generale a qualcuno che potrebbe averlo già fatto prima e conosce la procedura a memoria. In questo modo la risposta sarebbe conservata qui e disponibile per altre persone. Nel caso in cui nessuno fornisca una tale descrizione, farò io stesso l'analisi e pubblicherò una risposta alla mia domanda, ma aspetterò ancora un po 'per ora.
David Gouveia,

Sì, questo è il motivo per cui ho usato un commento. Penso che Andrew Russell potrebbe essere una brava persona a rispondere a questa domanda. È attivo in questa comunità e sta lavorando su ExEn, che è un'alternativa a MonoGame: andrewrussell.net/exen .
Den,

Sì, questo è il tipo di domanda su cui Andrew Russel (o Shawn Hargreaves tornerà al forum XNA) di solito sarebbe in grado di fornire molte informazioni.
David Gouveia,

Risposte:


42

Ho replicato in qualche modo il comportamento di SpriteBatch in modalità differita per un motore multipiattaforma su cui sto lavorando, quindi ecco i passaggi che ho invertito finora:

  1. SpriteBatch costruttore: crea DynamicIndexBuffer, DynamicVertexBuffere la matrice di VertexPositionColorTexturedi dimensione fissa (in questo caso, la dimensione del lotto massima - 2048 per sprites e 8192 per i vertici).

    • Il buffer dell'indice viene riempito con gli indici dei vertici dei quad che verranno disegnati (0-1-2, 0-2-3, 4-5-6, 4-6-7 e così via).
    • Viene SpriteInfoinoltre creata una matrice interna di strutture. Ciò memorizzerà le impostazioni di sprite temporali da utilizzare durante il batch.
  2. SpriteBatch.Begin: memorizza internamente i valori BlendState, SamplerStateecc determinate e verifica se è stato chiamato due volte senza SpriteBatch.Endin mezzo.

  3. SpriteBatch.Draw: prende tutte le informazioni di sprite (trama, posizione, colore) e le copia in a SpriteInfo. Se viene raggiunta la dimensione massima del lotto, l'intero lotto viene disegnato per fare spazio ai nuovi sprite.

    • SpriteBatch.DrawStringemette solo a Drawper ogni carattere della stringa, tenendo conto della crenatura e della spaziatura.
  4. SpriteBatch.End: esegue le seguenti operazioni:

    • Imposta gli stati di rendering specificati in Begin.
    • Crea la matrice di proiezione ortografica.
    • Applica lo SpriteBatchshader.
    • Lega il DynamicVertexBuffere DynamicIndexBuffer.
    • Esegue la seguente operazione di batch:

      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: esegue le seguenti operazioni per ciascuno dei SpriteInfobatch:

    • Prende la posizione dello sprite e calcola la posizione finale dei quattro vertici in base all'origine e alle dimensioni. Applica la rotazione esistente.
    • Calcola le coordinate UV e applica le specifiche specificate SpriteEffects.
    • Copia il colore dello sprite.
    • Questi valori vengono quindi memorizzati nella matrice di VertexPositionColorTextureelementi precedentemente creati. Quando tutti gli sprite sono stati calcolati, SetDataviene chiamato DynamicVertexBuffere DrawIndexedPrimitivesviene emessa una chiamata.
    • Il vertex shader esegue solo un'operazione di trasformazione e il pixel shader applica la colorazione sul colore prelevato dalla trama.

È esattamente quello di cui avevo bisogno, grazie mille! Mi ci è voluto un po 'per assorbire tutto ciò, ma penso di aver finalmente ottenuto tutti i dettagli. Uno che ha attirato la mia attenzione e di cui non ero completamente consapevole prima è che anche in modalità differita, se posso in qualche modo ordinare le mie chiamate SpriteBatch.Draw da Texture (cioè se non mi interessa l'ordine di queste chiamate) ridurre il numero di operazioni RenderBatch e probabilmente accelerare il rendering.
David Gouveia, il

A proposito, non riesco a trovare nella documentazione: qual è il limite di chiamate Draw che puoi effettuare prima che il buffer si riempia? E chiamare DrawText riempie quel buffer per l'intera quantità di caratteri nella stringa?
David Gouveia,

Impressionante! Come si confronta il tuo motore con ExEn e MonoGame? Richiederà anche MonoTouch e MonoAndroid? Grazie.
Den,

@DavidGouveia: 2048 sprite è la dimensione massima del batch - ha modificato la risposta e l'ha aggiunta per comodità. Sì, l'ordinamento per trama e lasciando che il buffer z si occupasse dell'ordinamento degli sprite utilizzerebbe le chiamate di disegno minime. Se ne hai bisogno, prova a implementare SpriteSortMode.Textureper disegnare nell'ordine che preferisci e lascia SpriteBatchfare l'ordinamento.
r2d2rigo,

1
@Den: ExEn e MonoGame sono API di livello inferiore che consentono l'esecuzione del codice XNA in piattaforma non Windows. Il progetto a cui sto lavorando è un motore basato su componenti puramente scritto in C #, ma abbiamo bisogno di un livello molto sottile per fornire l'accesso alle API di sistema (ecco dove sto lavorando). Abbiamo appena scelto di reimplementare SpriteBatchper comodità. E sì, richiede MonoTouch / MonoDroid poiché è tutto C #!
r2d2rigo,

13

Voglio solo sottolineare che il codice XNA SpriteBatch è stato portato su DirectX e rilasciato come OSS da un membro precedente del team XNA. Quindi, se vuoi vedere esattamente come funziona in XNA qui vai.


+1 per "il codice che non userò mai ma è interessante scavare"
Kyle Baran,

L'ultima versione di XNA SpriteBatch come C ++ nativo è disponibile su GitHub h / cpp . Come bonus, è disponibile anche una versione DirectX12 h / cpp
Chuck Walbourn
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.