¿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:
- El dibujo del mapa de bits de memoria al mapa de bits de video es dolorosamente lento, en allegro.
- 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.
fuente
Respuestas:
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.
fuente
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?
fuente
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.
fuente
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.
fuente
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.
fuente
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.
fuente