¿Cómo funciona exactamente el SpriteBatch de XNA?

38

Para ser más precisos, si tuviera que recrear esta funcionalidad desde cero en otra API (por ejemplo, en OpenGL), ¿qué tendría que ser capaz de hacer?

Tengo una idea general de algunos de los pasos, como cómo prepara una matriz de proyección ortográfica y crea un quad para cada llamada de sorteo.

Sin embargo, no estoy muy familiarizado con el proceso de procesamiento por lotes. ¿Están todos los quads almacenados en el mismo búfer de vértices? ¿Necesita un búfer de índice? ¿Cómo se manejan las diferentes texturas?

Si es posible, agradecería que me guiara a través del proceso desde que se llama a SpriteBatch.Begin () hasta SpriteBatch.End (), al menos cuando se usa el modo diferido predeterminado.

David Gouveia
fuente
Eche un vistazo a cómo se implementa en MonoGame a través de OpenGL: github.com/mono/MonoGame/blob/master/MonoGame.Framework/…
Den
¿Intentó usar DotPeek o Reflector en Microsoft.Xna.Graphics (aunque no estoy sugiriendo que haga algo ilegal :)?
Den
Gracias por el enlace. Y ese pensamiento me atravesó (incluso tengo dotPeek aquí a la mano), pero pensé que podría ser una mejor idea pedir una descripción general de alguien que ya lo haya hecho antes y conozca el procedimiento de memoria. De esa manera, la respuesta se conservaría aquí y estaría disponible para otras personas. En el caso de que nadie proporcione tal descripción, haré el análisis yo mismo y publicaré una respuesta a mi propia pregunta, pero esperaré un poco más por ahora.
David Gouveia
Sí, esa es la razón por la que usé un comentario. Creo que Andrew Russell podría ser una buena persona para responder esta pregunta. Está activo en esta comunidad y está trabajando en ExEn, que es una alternativa a MonoGame: andrewrussell.net/exen .
Den
Sí, este es el tipo de pregunta que Andrew Russel (o Shawn Hargreaves en el foro de XNA) generalmente podría proporcionar una gran cantidad de información.
David Gouveia

Respuestas:

42

He replicado el comportamiento de SpriteBatch en modo diferido para un motor multiplataforma en el que estoy trabajando, así que aquí están los pasos que he realizado en ingeniería inversa hasta ahora:

  1. SpriteBatch constructor: crea una DynamicIndexBuffer, DynamicVertexBuffery la matriz de VertexPositionColorTexturede tamaño fijo (en este caso, el tamaño de lote máximo - 2048 para los sprites y 8192 para vértices).

    • El búfer de índice se llena con los índices de vértice de los quads que se dibujarán (0-1-2, 0-2-3, 4-5-6, 4-6-7 y así sucesivamente).
    • También SpriteInfose crea una matriz interna de estructuras. Esto almacenará la configuración de sprites temporales que se utilizará al procesar lotes.
  2. SpriteBatch.Begin: almacena internamente los valores de BlendState, SamplerStateetc. especificados y comprueba si se ha llamado dos veces sin un SpriteBatch.Endintermedio.

  3. SpriteBatch.Draw: toma toda la información del sprite (textura, posición, color) y la copia en a SpriteInfo. Si se alcanza el tamaño máximo de lote, se dibuja todo el lote para dejar espacio para nuevos sprites.

    • SpriteBatch.DrawStringsolo emite un Drawpara cada carácter de la cadena, teniendo en cuenta el interletraje y el espaciado.
  4. SpriteBatch.End: realiza las siguientes operaciones:

    • Establece los estados de representación especificados en Begin.
    • Crea la matriz de proyección ortográfica.
    • Aplica el SpriteBatchsombreador.
    • Se une a DynamicVertexBuffery DynamicIndexBuffer.
    • Realiza la siguiente operación de procesamiento por lotes:

      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: ejecuta las siguientes operaciones para cada uno de SpriteInfolos lotes:

    • Toma la posición del sprite y calcula la posición final de los cuatro vértices según el origen y el tamaño. Aplica rotación existente.
    • Calcula las coordenadas UV y las aplica especificadas SpriteEffectsa ellas.
    • Copia el color del sprite.
    • Estos valores se almacenan en la matriz de VertexPositionColorTextureelementos creados previamente. Cuando se han calculado todos los sprites, SetDatase llama al DynamicVertexBuffery DrawIndexedPrimitivesse emite una llamada.
    • El sombreador de vértices solo realiza una operación de transformación, y el sombreador de píxeles aplica el tinte en el color obtenido de la textura.
r2d2rigo
fuente
Eso es exactamente lo que necesitaba, ¡muchas gracias! Me tomó un poco absorber todo eso, pero creo que finalmente obtuve todos los detalles. Una que me llamó la atención y que antes no conocía del todo es que, incluso en modo diferido, si de alguna manera puedo ordenar mi SpriteBatch. Dibuje las llamadas por Texture (es decir, si no me importa el orden de estas llamadas) lo hará. reduzca el número de operaciones de RenderBatch y probablemente acelere el renderizado.
David Gouveia
Por cierto, parece que no puedo encontrar en la documentación: ¿cuál es el límite de llamadas de Draw que puede hacer antes de que se llene el búfer? ¿Y llamar a DrawText llena ese búfer por la cantidad total de caracteres en la cadena?
David Gouveia
¡Impresionante! ¿Cómo se comparará su motor con ExEn y MonoGame? ¿También requerirá MonoTouch y MonoAndroid? Gracias.
Den
@DavidGouveia: 2048 sprites es el tamaño máximo de lote: editó la respuesta y la agregó por conveniencia. Sí, ordenar por textura y dejar que el z-buffer se encargue del pedido de sprites usaría las llamadas mínimas de extracción. Si lo necesita, intente implementar SpriteSortMode.Texturepara dibujar en el orden que desee y deje que SpriteBatchhaga la clasificación.
r2d2rigo
1
@Den: ExEn y MonoGame son API de nivel inferior que permiten ejecutar código XNA en plataformas que no son de Windows. El proyecto en el que estoy trabajando es un motor basado en componentes puramente escrito en C #, pero necesitamos una capa muy delgada para proporcionar acceso a las API del sistema (ahí es donde estoy trabajando). Elegimos reimplementar SpriteBatchpor conveniencia. Y sí, ¡requiere MonoTouch / MonoDroid ya que todo es C #!
r2d2rigo
13

Solo quiero señalar que el código XNA SpriteBatch ha sido portado a DirectX y lanzado como OSS por un miembro anterior del equipo XNA. Entonces, si quieres ver exactamente cómo funciona en XNA, aquí tienes.

Trueno clásico
fuente
+1 para "código que nunca usaré pero es interesante pasarlo por alto"
Kyle Baran
La última versión de XNA SpriteBatch como C ++ nativo se puede encontrar en GitHub h / cpp . Como beneficio adicional, también está disponible una versión DirectX12 h / cpp
Chuck Walbourn