¿Cómo resolvemos los grandes requisitos de memoria de video en un juego 2D?

40

¿Cómo resolvemos los grandes requisitos de memoria de video en un juego 2D?


Estamos desarrollando un juego 2D (Factorio) en allegro C / C ++, y enfrentamos un problema con el aumento de los requisitos de memoria de video a medida que aumenta el contenido del juego.

Actualmente recopilamos toda la información sobre las imágenes que se utilizarán primero, recortamos todas estas imágenes tanto como sea posible y las organizamos en grandes atlas de la forma más ajustada posible. Estos atlas se almacenan en la memoria de video, cuyo tamaño depende de las limitaciones del sistema; Actualmente, generalmente son 2 imágenes de hasta 8192x8192, por lo que requieren una memoria de video de 256Mb a 512Mb.

Este sistema funciona bastante bien para nosotros, ya que con algunas optimizaciones personalizadas y dividiendo el hilo de renderizado y actualización, podemos dibujar decenas de miles de imágenes en la pantalla en 60 fps; Tenemos muchos objetos en la pantalla, y permitir un gran alejamiento es un requisito crítico. Como nos gustaría agregar más, habrá algunos problemas con los requisitos de memoria de video, por lo que este sistema no puede funcionar.

Una de las cosas que queríamos probar es tener un atlas con las imágenes más comunes y el segundo como caché. Las imágenes se moverían allí desde el mapa de bits de la memoria, bajo demanda. Hay dos problemas con este enfoque:

  1. El dibujo del mapa de bits de memoria al mapa de bits de video es dolorosamente lento, en allegro.
  2. No es posible trabajar con un mapa de bits de video que no sea el hilo principal, en allegro, por lo que es prácticamente inutilizable.

Aquí hay algunos requisitos adicionales que tenemos:

  • El juego debe ser determinista, por lo que los problemas de rendimiento / tiempos de carga nunca pueden alterar el estado del juego.
  • El juego es en tiempo real y pronto también será multijugador. Debemos evitar incluso el tartamudeo más pequeño a toda costa.
  • La mayor parte del juego es un mundo abierto continuo.

La prueba consistió en dibujar 10 000 sprites en un lote para tamaños de 1x1 a 300x300, varias veces para cada configuración. Hice las pruebas en la Nvidia Geforce GTX 760.

  • El dibujo de mapa de bits de video a mapa de bits de video tomó 0.1us por sprite, cuando el mapa de bits de origen no estaba cambiando entre mapas de bits individuales (la variante atlas); el tamaño no importaba
  • El dibujo de mapa de bits de video a mapa de bits de video, mientras que el mapa de bits de origen se cambió entre dibujos (variante no atlas), tomó 0.56us por sprite; el tamaño tampoco importaba.
  • El dibujo de mapa de bits de memoria a mapa de bits de video era realmente sospechoso. Los tamaños de 1x1 a 200x200 tomaron 0.3us por mapa de bits, por lo que no es tan terriblemente lento. Para tamaños más grandes, el tiempo comenzó a aumentar dramáticamente, a 9us para 201x201 a 3116us para 291x291.

El uso de atlas aumenta el rendimiento en un factor mayor que 5. Si tuviera 10 ms para el renderizado, con un atlas estoy limitado a 100 000 sprites por cuadro, y sin él, un límite de 20 000 sprites. Esto sería problemático.

También estaba tratando de encontrar una forma de probar la compresión de mapa de bits y el formato de mapa de bits de 1 bpp para sombras, pero no pude encontrar una forma de hacerlo en allegro.

Marwin
fuente
1
Gran fanático de tu juego, respaldé la campaña de Indiegogo. Me atrapo cada pocos meses. Buen trabajo hasta ahora! Eliminé las preguntas de "qué tecnología usar" que están fuera del tema del sitio. Las preguntas restantes siguen siendo bastante amplias, si tiene algo más específico, debe intentar reducir el alcance.
MichaelHouse
Gracias por el apoyo. Entonces, ¿dónde está el lugar para preguntar qué tecnología utilizar? No busco una respuesta con una recomendación específica del motor, pero no pude encontrar una comparación en profundidad de los motores 2d e inspeccionarlos manualmente uno por uno, incluidas las pruebas de rendimiento y usabilidad, llevaría años.
Marwin
Consulte la parte inferior de esta página para ver algunos lugares para hacer preguntas como "qué tecnología usar". Tiene una pregunta totalmente válida y razonable, simplemente no es el tipo de pregunta que tratamos en este sitio. Aunque no esté buscando un motor específico, esa es realmente la única forma de responder a la pregunta "¿Hay alguna tecnología que haga X?". Alguien podría responder "sí" y no dar una recomendación para una específica, pero eso no sería muy útil. ¡Suerte con ello!
MichaelHouse
2
¿Estás comprimiendo tus texturas?
GuyRT
3
@Marwin, las texturas comprimidas pueden funcionar mucho mejor que las texturas sin comprimir porque reducen el ancho de banda de memoria necesario (esto es especialmente cierto en plataformas móviles donde el ancho de banda es mucho menor). Podría ahorrar una gran cantidad de memoria simplemente comprimiendo sus texturas. Realmente, el único inconveniente son los artefactos que se introducen inevitablemente.
GuyRT

Respuestas:

17

Tenemos un caso similar con nuestro RTS (KaM Remake). Todas las unidades y casas son sprites. Tenemos 18 000 sprites para unidades y casas y terreno, más otros ~ 6 000 para colores de equipo (aplicados como máscaras). De largo alcance también tenemos unos ~ 30 000 caracteres utilizados en las fuentes.

Entonces, hay algunas optimizaciones contra los atlas RGBA32 que está utilizando:

  • Divida su grupo de sprites en muchos atlas más pequeños primero y úselos a pedido como se cubre en otras respuestas. Eso también permite utilizar diferentes técnicas de optimización para cada atlas individualmente . Sospecho que tendrá un poco menos de RAM desperdiciada, porque cuando se empaqueta con texturas tan grandes, generalmente hay áreas sin usar en la parte inferior;

  • Intente usar texturas paletizadas . Si usa sombreadores, puede "aplicar" la paleta en el código de sombreadores;

  • Puede considerar agregar una opción para usar RGB5_A1 en lugar de RGBA8 (si, por ejemplo, las sombras de tablero de ajedrez están bien para su juego). Evite Alpha de 8 bits cuando sea posible y use RGB5_A1 o formatos equivalentes con menor precisión (como RGBA4), ocupan la mitad del espacio;

  • Asegúrese de empacar bien los sprites en atlas (vea los algoritmos de Empaque de contenedores), gire los sprites cuando sea necesario y vea si puede superponer esquinas transparentes para los sprites de rombo;

  • Puede probar los formatos de compresión de hardware (DXT, S3TC, etc.), pueden reducir drásticamente el uso de RAM, pero verificar los artefactos de compresión, en algunas imágenes la diferencia puede pasar desapercibida (puede usar esto selectivamente como se describe en el primer punto), pero en algunos - muy pronunciado. Los diferentes formatos de compresión causan diferentes artefactos, por lo que puede elegir uno que sea mejor para su estilo de arte.

  • Busque dividir sprites grandes (por supuesto, no manualmente, sino dentro de su paquete de atlas de texturas) en sprites de fondo estáticos y sprites más pequeños para partes animadas.

Kromster dice que apoya a Mónica
fuente
2
+1 por usar DXT, es algo muy bueno tener. Gran compresión y utilizada directamente por la GPU, por lo que la sobrecarga es mínima.
1
Estoy de acuerdo con dxt. También podría consultar el soporte DXT7 (hardware DX11 +), que es del mismo tamaño que DXT1 pero (aparentemente) de mayor calidad. Sin embargo, necesitaría tener el doble de texturas (una DXT7 y una DXT1), o comprimir / descomprimir durante la carga.
Programmdude
5

En primer lugar, debe usar más atlas de textura más pequeños. Cuantas menos texturas tenga, más difícil y rígida será la administración de la memoria. Sugeriría un tamaño de atlas de 1024, en cuyo caso tendría 128 texturas en lugar de 2, o 2048 en cuyo caso tendría 32 texturas, que podría cargar y descargar según sea necesario.

La mayoría de los juegos hacen esta gestión de recursos al tener límites de nivel, mientras que una pantalla de carga muestra todos los recursos que ya no se necesitan en el siguiente nivel se descargan y los recursos que se necesitan se cargan.

Otra opción es la carga a pedido, que se hace necesaria si los límites de nivel no son deseados o incluso si un solo nivel es demasiado grande para caber en la memoria. En este caso, el juego intentará predecir lo que el jugador verá en el futuro y cargarlo en segundo plano. (Por ejemplo: cosas que actualmente están a 2 pantallas de distancia del jugador). Al mismo tiempo, las cosas que no se usaron por más tiempo se descargarán.

Sin embargo, hay un problema: ¿qué sucede cuando sucede algo inesperado que el juego no pudo prever?

  • Pánico y visualización de una pantalla de carga hasta que se carguen todas las cosas necesarias. Esto puede ser perjudicial para la experiencia.
  • Tenga sprites de baja resolución para todo precargado, continúe el juego y reemplácelos tan pronto como los sprites de alta resolución terminen de cargarse. Esto puede parecer barato para el jugador.
  • Haz que impacte el juego y retrasa el evento el tiempo que sea necesario. Por ejemplo, no engendres a ese enemigo hasta que se carguen sus gráficos. No abras ese cofre del tesoro antes de que se carguen todos los gráficos para ese botín, etc.
API-Bestia
fuente
Agregué algunos de los requisitos que omití. La pantalla de carga, o cualquier tipo de carga no es posible. Todo se debe hacer en segundo plano, o entre ticks individuales (menos de 15 ms para cada uno), mientras que la mayoría de las veces ya se usa habitualmente en los preparativos de renderizado y la actualización del juego. De todos modos, dividirse en partes más pequeñas podría agregar algo de flexibilidad en el cambio, seguramente sería más rápido. La pregunta es cuánto perjudica el rendimiento al renderizar, ya que cambiar el mapa de bits de origen mientras se dibuja ralentiza el renderizado. Tendría que hacer una medición exacta para decir cuánto.
Marwin el
@Marwin Impacto en el rendimiento, sí, pero como se trata de 2D, todavía debería estar lejos de convertirse en un problema. Si el renderizado actualmente toma 1 ms por fotograma, y ​​mediante el uso de texturas más pequeñas, de repente toma 2 ms, entonces todavía es más que lo suficientemente rápido como para alcanzar 60 FPS consistentes. (16 ms)
API-Beast
@Marwin Multiplayer es un negocio complicado, siempre lo fue, siempre lo será. Es muy probable que tenga que hacer compromisos allí. Tendrá tartamudeos, simplemente porque tiene que transferir datos a través de Internet, se perderán los paquetes, los pings pueden aumentar repentinamente, etc. El tartamudeo es inevitable, por lo que lo más importante es hacer que el modelo de red sea resistente al tartamudeo. Saber cuándo esperar y cómo esperar a otros jugadores.
API-Beast
Hola, el tartamudeo es casi evitable en el modo multijugador, estamos trabajando en esa área en este momento y creo que tenemos un buen plan. Incluso podría publicar y responder mi propia pregunta que describe lo que investigamos en detalle más adelante :) Puede ser una sorpresa, pero el tiempo de renderizado es realmente un problema. Hemos realizado muchas optimizaciones para agilizar el renderizado. El renderizado principal ahora se realiza en hilos separados y otros pequeños ajustes. No olvides que con el zoom máximo, el jugador puede ver fácilmente decenas de miles de sprites al mismo tiempo. E incluso nos gustaría permitir niveles de zoom aún más altos más adelante.
Marwin
@Marwin Hm, los objetos de 10k generalmente no deberían ser un problema para una PC o computadora portátil moderna si usa un lote adecuado, ¿ha perfilado su código de representación?
API-Beast
2

Wow, eso es una cantidad considerable de sprites de animación, generados a partir de modelos 3D, supongo.

Realmente no deberías hacer este juego en 2D sin formato. Cuando tiene una perspectiva fija, sucede algo curioso, puede mezclar a la perfección sprites y fondos renderizados previamente con modelos 3D renderizados en vivo, que algunos juegos han utilizado mucho. Si quieres animaciones tan finas, esa es la forma más natural de hacerlo. Obtenga un motor 3D, configúrelo para usar perspectiva isométrica y renderice los objetos para los que continúa usando sprites como superficies planas simples con una imagen en ellos. Y puede usar la compresión de textura con un motor 3D, eso solo es un gran paso adelante.

No creo que cargar y descargar haga mucho por ti, ya que puedes tener casi todo en la pantalla al mismo tiempo.

aaaaaaaaaaaa
fuente
2

En primer lugar, encuentre el formato de textura más eficiente que pueda sin dejar de estar contento con las imágenes del juego, ya sea RGBA4444, compresión DXT, etc. Si no está satisfecho con los artefactos generados en una imagen comprimida alfa DXT, ¿sería viable? para hacer que las imágenes no sean transparentes utilizando la compresión DXT1 para el color combinado con una textura de enmascaramiento de escala de grises de 4 u 8 bits para el alfa? Me imagino que te quedarías en RGBA8888 para la GUI.

Abogo por dividir las cosas en texturas más pequeñas usando el formato que haya decidido. Determine los elementos que siempre están en la pantalla y, por lo tanto, siempre están cargados, este podría ser el terreno y los atlas GUI. Luego dividiría los elementos restantes que comúnmente se procesan juntos tanto como sea posible. No me imagino que perderías demasiado rendimiento incluso subiendo hasta 50-100 llamadas de extracción en la PC, pero corrígeme si me equivoco.

El siguiente paso será generar las versiones de mipmap de estas texturas como alguien señaló anteriormente. No los almacenaría en un solo archivo sino por separado. Por lo tanto, terminaría con 1024x1024, 512x512, 256x256, etc., versiones de cada archivo y lo haría hasta alcanzar el nivel de detalle más bajo que quisiera que se muestre.

Ahora que tiene las texturas separadas, puede crear un sistema de nivel de detalle (LOD) que cargue texturas para el nivel de zoom actual y descargue texturas si no se usa. No se utiliza una textura si el elemento que se representa no está en la pantalla o no es requerido por el nivel de zoom actual. Intente cargar las texturas en la RAM de video en un hilo separado de los hilos de actualización / renderizado. Puede mostrar la textura LOD más baja hasta que se cargue la requerida. Esto a veces puede dar como resultado un cambio visible entre una textura de bajo detalle / alto detalle, pero imagino que esto solo ocurrirá cuando realice un alejamiento y un acercamiento extremadamente rápidos mientras se mueve por el mapa. Puede hacer que el sistema sea inteligente al intentar precargar donde cree que la persona se moverá o acercará y cargará tanto como sea posible dentro de las restricciones de memoria actuales.

Ese es el tipo de cosas que probaría para ver si ayuda. Me imagino que para obtener niveles de zoom extremos, inevitablemente necesitará un sistema LOD.

cristiano
fuente
1

Creo que el mejor enfoque es dividir la textura en muchos archivos y cargarlos a pedido. Probablemente su problema es que está tratando de cargar texturas más grandes que necesitaría para una escena 3D completa y está usando Allegro para eso.

Para el gran alejamiento que desea poder aplicar, debe usar mapas MIP. Mipmaps son versiones de baja resolución de sus texturas que se utilizan cuando los objetos están lo suficientemente lejos de la cámara. Esto significa que podría guardar su 8192x8192 como 4096x4096 y luego otro de 2048x2048 y así sucesivamente, y cambiar a las resoluciones más bajas cuanto más pequeño vea el sprite en la pantalla. Puede guardarlos como texturas separadas o cambiar su tamaño al cargarlos (pero generar mapas MIP durante el tiempo de ejecución aumentará los tiempos de carga para su juego).

Un sistema de administración adecuado cargaría los archivos requeridos a pedido y liberaría los recursos cuando nadie los esté usando, además de otras cosas. La gestión de recursos es un tema importante en el desarrollo del juego y está reduciendo su gestión a un mapeo de coordenadas simple a una sola textura, que está cerca de no tener ninguna gestión.

Pablo Ariel
fuente
1
Al dividir en archivos, ¿te refieres a archivos en HDD? Supongo que podría almacenar todas las imágenes en la RAM para empezar, e incluso copiar desde el mapa de bits de memoria al mapa de bits de video es demasiado lento actualmente, por lo que la carga desde el HDD sería aún más lenta. Tener mimpaps no me ayudará, ya que todavía tendré la mayor resolución en vram.
Marwin
Sí, no tienes que cargar todo, solo tienes que cargar lo que usas. Siempre que desee cambiar un píxel en una textura cargada en VRAM, el sistema debe mover TODA LA TEXTURA a RAM, solo para que pueda modificar un solo píxel, luego volver a moverlo a VRAM. Si tiene todo en una sola textura, esto implica mover 256 MB a RAM y luego nuevamente a VRAM, que bloquea toda la computadora. Separarlo en diferentes archivos y texturas es la forma correcta de hacerlo.
Pablo Ariel
La modificación de la textura que desencadena la copia en la memoria y de nuevo en la memoria RAM se aplica solo a los mapas de bits persistentes, el caché probablemente no se establecerá en persistente, el único inconveniente sería la necesidad de actualizarlo cuando se pierde / encuentra la pantalla. Pero en el allegro, incluso una simple copia de la imagen 640X480 de vram al mapa de bits de memoria (guardar la vista previa del juego) lleva bastante tiempo.
Marwin
1
Necesito tener todo en una gran textura para optimizar el dibujo en sí, sin él, el efecto de cambiar el contexto entre sprites individuales ralentiza el renderizado demasiado, al menos en allegro. No me malinterpretes, pero eres un capitán obvio aquí, ya que me estás sugiriendo vagamente que haga algo que pido en esta pregunta.
Marwin
1
Tener estas texturas mapeadas en mip en diferentes archivos me obligaría a volver a cargar todo el atlas cuando el jugador haga zoom. Como el motor tiene solo unas pocas unidades de ms como máximo, no veo la forma de hacerlo.
Marwin
0

Recomiendo crear más archivos de atlas que puedan comprimirse con zlib y fluir fuera de la compresión para cada atlas, y al tener más archivos de atlas y archivos de menor tamaño, puede restringir la cantidad de datos de imagen activos en la memoria de video. Además, implemente el mecanismo de triple buffer para que esté preparando cada cuadro de dibujo antes y tenga la oportunidad de completarlo más rápido para que los tartamudeos no aparezcan en la pantalla.

Darxval
fuente