Como exatamente o SpriteBatch da XNA funciona?

38

Para ser mais preciso, se eu precisasse recriar essa funcionalidade do zero em outra API (por exemplo, no OpenGL), o que seria necessário?

Eu tenho uma idéia geral de algumas das etapas, como a maneira como ela prepara uma matriz de projeção ortográfica e cria um quad para cada chamada de desenho.

No entanto, não estou muito familiarizado com o próprio processo de lote. Todos os quads são armazenados no mesmo buffer de vértice? Precisa de um buffer de índice? Como são tratadas as diferentes texturas?

Se possível, eu ficaria grato se você pudesse me orientar durante o processo desde quando SpriteBatch.Begin () é chamado até SpriteBatch.End (), pelo menos ao usar o modo Adiado padrão.

David Gouveia
fonte
Veja como ele é implementado no MonoGame via OpenGL: github.com/mono/MonoGame/blob/master/MonoGame.Framework/…
Den
Você tentou usar o DotPeek ou o Reflector no Microsoft.Xna.Graphics (não estou sugerindo fazer nada ilegal :)?
Den
Obrigado pelo link. E esse pensamento me passou pela cabeça (eu ainda tenho o dotPeek aqui em mãos), mas achei que seria melhor pedir uma descrição geral de alguém que já tivesse feito isso antes e que conheça o procedimento de memória. Dessa forma, a resposta seria preservada aqui e disponível para outras pessoas. No caso de ninguém fornecer essa descrição, eu mesmo farei a análise e postarei uma resposta para minha própria pergunta, mas esperarei um pouco mais por enquanto.
David Gouveia
Sim, foi por isso que usei um comentário. Acho que Andrew Russell pode ser uma boa pessoa para responder a essa pergunta. Ele é ativo nesta comunidade e está trabalhando no ExEn, que é uma alternativa ao MonoGame: andrewrussell.net/exen .
Den
Sim, esse é o tipo de pergunta em que Andrew Russel (ou Shawn Hargreaves no fórum do XNA) normalmente poderia fornecer muitas informações.
David Gouveia

Respostas:

42

Eu meio que repliquei o comportamento do SpriteBatch no modo adiado para um mecanismo de plataforma cruzada em que estou trabalhando, então, aqui estão as etapas que eu fiz a engenharia reversa até agora:

  1. SpriteBatch construtor: cria um DynamicIndexBuffer, DynamicVertexBuffere de matriz VertexPositionColorTexturede tamanho fixo (neste caso, o tamanho do lote máximo - 2048 para sprites e 8192 para os vértices).

    • O buffer do índice é preenchido com os índices de vértices dos quadríceps que serão desenhados (0-1-2, 0-2-3, 4-5-6, 4-6-7 e assim por diante).
    • Também SpriteInfoé criada uma matriz interna de estruturas. Isso armazenará as configurações de sprite temporal a serem usadas no lote.
  2. SpriteBatch.Begin: armazena internamente os valores de BlendState, SamplerState, etc. especificadas e verifica se ele foi chamado duas vezes sem um SpriteBatch.Endno meio.

  3. SpriteBatch.Draw: pega todas as informações do sprite (textura, posição, cor) e as copia para a SpriteInfo. Se o tamanho máximo do lote for atingido, o lote inteiro será desenhado para abrir espaço para novos sprites.

    • SpriteBatch.DrawStringapenas emite a Drawpara cada caractere da string, levando em consideração o kerning e o espaçamento.
  4. SpriteBatch.End: executa as seguintes operações:

    • Define os estados de renderização especificados em Begin.
    • Cria a matriz de projeção ortográfica.
    • Aplica o SpriteBatchshader.
    • Vincula o DynamicVertexBuffere DynamicIndexBuffer.
    • Executa a seguinte operação em lote:

      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: executa as seguintes operações para cada um dos SpriteInfolotes:

    • Toma a posição do sprite e calcula a posição final dos quatro vértices de acordo com a origem e o tamanho. Aplica a rotação existente.
    • Calcula as coordenadas UV e aplica-se SpriteEffectsa elas especificadas .
    • Copia a cor do sprite.
    • Esses valores são armazenados na matriz de VertexPositionColorTextureelementos criados anteriormente. Quando todos os sprites foram calculados, SetDataé chamado DynamicVertexBuffere uma DrawIndexedPrimitiveschamada é emitida.
    • O sombreador de vértice executa apenas uma operação de transformação e o sombreador de pixel aplica a tonalidade na cor buscada na textura.
r2d2rigo
fonte
Era exatamente disso que eu precisava, muito obrigado! Demorei um pouco para absorver tudo isso, mas acho que finalmente consegui todos os detalhes. Um que chamou minha atenção e eu ainda não tinha consciência disso antes é que, mesmo no modo adiado, se eu conseguir classificar minhas chamadas SpriteBatch.Draw de Texture (por exemplo, se eu não me importo com a ordem dessas chamadas), será reduza o número de operações RenderBatch e provavelmente acelere a renderização.
David Gouveia
A propósito, não consigo encontrar na documentação - qual é o limite de chamadas do Draw que você pode fazer antes que o buffer fique cheio? E chamar DrawText preenche esse buffer por toda a quantidade de caracteres na string?
David Gouveia
Impressionante! Como o seu mecanismo se compara ao ExEn e ao MonoGame? Será necessário o MonoTouch e o MonoAndroid também? Obrigado.
Den
@DavidGouveia: 2048 sprites é o tamanho máximo do lote - editou a resposta e a adicionou por conveniência. Sim, classificar por textura e deixar o buffer z cuidar da ordem dos sprites usaria as chamadas de empate mínimas. Se você precisar, tente implementar SpriteSortMode.Texturepara desenhar na ordem que quiser e deixe SpriteBatcha classificação.
R2d2rigo
1
@ Den: ExEn e MonoGame são APIs de nível inferior que permitem que o código XNA seja executado na plataforma não Windows. O projeto em que estou trabalhando é um mecanismo baseado em componentes puramente escrito em C #, mas precisamos de uma camada muito fina para fornecer acesso às APIs do sistema (é nisso que estou trabalhando). Acabamos de reimplementar SpriteBatchpor conveniência. E sim, requer MonoTouch / MonoDroid, pois é tudo em C #!
R2d2rigo
13

Só quero salientar que o código XNA SpriteBatch foi portado para o DirectX e lançado como OSS por um membro anterior da equipe do XNA. Então, se você quiser ver exatamente como ele funciona no XNA, aqui está.

ClassicThunder
fonte
+1 para "código que nunca irei usar, mas é interessante dar uma olhada"
Kyle Baran
A versão mais recente do XNA SpriteBatch como C ++ nativo pode ser encontrada no GitHub h / cpp . Como bônus, uma versão do DirectX12 também está disponível h / cpp
Chuck Walbourn