Dibujar una gran cantidad de fichas en Monogame (XNA) de manera eficiente

8

Estoy haciendo esta pregunta porque no he encontrado una respuesta definitiva.

Antes que nada, déjenme decir algunas cosas sobre el juego y lo que ya he hecho. El juego será un juego de estrategia en tiempo real en un mundo generado por procedimientos que utiliza ruido Simplex. El mundo está formado por trozos de 16 x 16 de tamaño hechos de sprites de 64 x 64. Me las he arreglado para cargar y descargar trozos dinámicamente, lo que funciona bien. El mundo se verá un poco como Rimworld, de arriba hacia abajo con diferentes capas de sprites (terreno primero, sprites de transición, árboles, calcomanías, etc.). Los mundos recién generados pueden contener entidades que pueden afectar el medio ambiente (por ejemplo, una aldea que se ha convertido en una ciudad) y, por lo tanto, la porción. Estoy seguro de que esto se puede calcular utilizando algún tipo de función, pero es algo a tener en cuenta.

El problema principal que tengo es cuando alejo, se dibujan más y más mosaicos que afectan gravemente el rendimiento. Con aproximadamente 30000 sprites, la sección de sorteo dura 8 ms, que es la mitad de lo que se requiere para correr a 60 FPS. Y ese es solo el terreno. Estoy usando atlas de textura para limitar el conteo de sorteos (30000 sprites dibujados en 6 conteos).

El objetivo es poder alejarse del pueblo / pueblo / ciudad hasta poder ver un país entero. Esto tiene que ser hecho de forma dinámica (por ejemplo. No haciendo clic en el icono de mini mapa, pero sólo tiene que desplazarse hacia atrás al igual que en Supreme Commander).

He leído muchos artículos sobre este tema, pero no he encontrado ni visto un ejemplo claro de dónde funcionaría. Aquí hay una lista de técnicas que he encontrado que se supone que funcionan:

  • Rectángulos sucios, como se describe aquí , donde solo dibujas cosas nuevas y mantienes el resto en el backbuffer. Esto tiene mucho sentido, pero no tengo idea de cómo implementar esto en Monogame.
  • Mi elección preferida: hacer un uso extensivo de RenderTargets, como se describe aquí , donde dibuje a un RenderTarget y luego guárdelo como una textura. En mi caso, un trozo de 16 x 16 formado por 64 x 64 crearía una textura de 1024 x 1024. Realmente dudo que esto funcione en términos de rendimiento, pero el resultado consistiría en texturas muy detalladas y también es excelente para usar teniendo en cuenta el hecho de que la mayoría es estático (terreno / árboles, etc.), y no cambia tanto. Pero esto también significa que cada vez que se realiza un cambio en un fragmento, Texture2D tiene que cambiarse usando SetData, que por lo que he experimentado es bastante intensivo en la CPU. Sin embargo, si las texturas fueran 16 x 16, en realidad podría funcionar, y también reduciría el uso de la memoria y el disco.
  • Hasta texturas especificando el SourceRectangle en SpriteBatch. Para grandes praderas / océanos esto es una ventaja, ya que solo se dibuja un sprite. Sin embargo, para terrenos detallados con diferentes colores y diferentes sprites mezclados (biomas y transiciones de biomas), me temo que no hará una gran diferencia.
  • Mi propia solución, que compromete los detalles, fue usar un mosaico blanco estándar de 64 x 64, darle el color de los 4 mosaicos circundantes y luego escalarlo para que cubra los 4 mosaicos anteriores. La diferencia aquí (además de que el mosaico tiene un color liso) es que el paisaje cambió visiblemente. También debo mencionar que los bosques aún deben dibujarse individualmente, ya que no son perfectamente cuadrados.

Si alguien tiene alguna idea sobre cómo abordar este problema, incluso si eso significa comprometer ciertas cosas (como detalles o usar sprites de 16 x 16), me encantaría escucharlo.

Plumas de Guguhl
fuente
No dejes que el usuario se aleje tanto
Bálint
También he experimentado problemas de rendimiento con mi proyecto MonoGame generado por procedimientos. Mi solución fue limitar lo que se renderizó (limitar el rango de alejamiento y solo dibujar lo que está en la pantalla). Parece que MonoGame está diseñado con un mantra de "dibujar todo cada cuadro", pero espero que alguien más experimentado con MonoGame me corrija.
Falacia lógica
¿Cómo se renderizan los mosaicos? Más específicamente: ¿usa SpriteBatch y cómo lo usa?
loodakrawa

Respuestas:

3

Las tarjetas gráficas están optimizadas para dibujar algunas cosas muchas veces en lugar de al revés.

En su caso, tiene muchas cosas (texturas diferentes) que desea dibujar muchas veces (número de mosaicos).

En mi opinión, las optimizaciones más simples que puede hacer para obtener ganancias significativas de rendimiento son:

  1. Combina tu textura en atlas. Esto debería acelerar significativamente las cosas ya que su tarjeta gráfica ahora solo está dibujando una (o un par) de texturas en lugar de muchas pequeñas.
  2. Lote sus mosaicos con SpriteBatch en el modo SpriteSortMode.Texture. Esto ordenará tus sprites por textura y minimizará el número de cambios de textura al número real de texturas diferentes. La desventaja de este enfoque es que sus sprites no se ordenarán por profundidad. Lo que presumiblemente está bien, ya que sus fichas están a la misma profundidad. Supongo que también tiene otras cosas, por lo que tiene sentido tener un SpriteBatch separado para los mosaicos y otro para todo lo demás (configurado en SpriteSortMode.BackToFront).

Yo recomendaría hacer ambas cosas. Buena suerte.

loodakrawa
fuente
2

Descubrí que SpriteBatch no es adecuado para dibujar mosaicos. Una razón es que los sprites están destinados a objetos dinámicos y se utilizan para múltiples propósitos. Otra razón es que está increíblemente vinculada a la CPU , por ejemplo. como se explica en el dibujo de descripción, 30,000 objetos cuestan 8 ms (mientras que mi conjunto de chips se maximizó al 30%). Además, se pueden dibujar hasta 3000 sprites antes de que se realice una nueva llamada de sorteo a la GPU (lote y todo).

La solución fue dibujar mis propios mosaicos usando quads texturizados . Esto en combinación con un VertexBuffer me permitió dibujar hasta 500,000 fichas texturizadas en 1 llamada de dibujo donde el método de dibujo consumió aproximadamente 1/20 de milisegundo. Por supuesto, probablemente no tenga que dibujar tantos, pero de cualquier manera finalmente conseguí que mi GPU tuviera una carga de trabajo del 75% a una velocidad constante de 60 fps.

Plumas de Guguhl
fuente
Suena muy bien. Me gustaría obtener más información al respecto: ¿siguió algún tutorial o tiene algún enlace con una descripción más detallada de este proceso?
loodakrawa
2
Aprendí la mayor parte de todo lo que menciona XNA / Monogame y quads texturizados. Sin embargo, riemers.net me ayudó mucho ya que está dirigido a personas que solo quieren configurar algo rápidamente que funcione. Querrás seguir específicamente su tutorial de terreno en 3D, ya que también cubre VertexBuffers e IndexBuffers. Convertirlo en un tipo de terreno en mosaico no debería ser un problema.
Guguhl Pluhs
Si eso es lo que responde más a su pregunta, puede marcarla como aceptada :)
Vaillancourt
@GuguhlPluhs Sé que es tarde, pero ¿todavía tienes más información sobre este tema? Estoy enfrentando el mismo problema.
Jelle