Optimización de Vulkan de Godot Engine para Android

Imagen de la mascota de Godot Engine

Descripción general

Godot Engine es un popular motor de juego multiplataforma de código abierto con una compatibilidad sólida con Android. Godot se puede usar para crear juegos de prácticamente cualquier género y es capaz de renderizar gráficos en 2D y 3D. La versión 4 de Godot introdujo un nuevo sistema de renderización con funciones avanzadas para gráficos de alta fidelidad. El procesador de Godot 4 está diseñado para APIs de gráficos modernas, como Vulkan.

La Fundación Godot contrató a los expertos en optimización de gráficos de The Forge Interactive y colaboró con Google para analizar y mejorar aún más el renderizador de Vulkan de Godot 4 y combinar esas optimizaciones en el repositorio del proyecto. Las optimizaciones ayudan a los desarrolladores a mejorar los renderizadores personalizados de Vulkan en Android.

Metodología y resultados de la optimización

En el proceso de optimización, se utilizaron dos escenas 3D diferentes en Godot como objetivos de comparativas. El tiempo de renderización de las escenas se midió en varios dispositivos durante cada iteración de optimización. Para poder incluirse, los cambios en el renderizador debían mostrar mejoras de rendimiento en, al menos, algunos dispositivos probados y no podían introducir regresiones de rendimiento en ningún dispositivo.

En las pruebas, se usaron varias arquitecturas de GPU de Android populares. Si bien muchas optimizaciones generaron mejoras generales, algunas tuvieron un mayor impacto en arquitecturas de GPU específicas. La suma total de todo el trabajo de optimización generó una reducción general del 10% al 20% en los tiempos de fotogramas de la GPU.

Optimización general de Vulkan

Forge realizó una refactorización arquitectónica general en el backend de renderización de Vulkan de Godot para mejorar el rendimiento y ayudar al backend a escalar con mayores demandas de renderización de contenido. Las optimizaciones no son específicas del hardware para dispositivos móviles, pero benefician a todas las plataformas de Godot Vulkan.

Compatibilidad con el desplazamiento dinámico de la UBO

Cuando se vincula un conjunto de descriptores que contiene un objeto de búfer uniforme dinámico (UBO), Vulkan permite que se especifiquen compensaciones dinámicas en el UBO en los parámetros de vinculación. Esta función se puede usar para empaquetar datos de varias operaciones de renderización en una sola UBO y volver a vincular el conjunto de descriptores con un desplazamiento dinámico diferente para seleccionar los datos adecuados para el sombreador. Se actualizó el renderizador Godot Vulkan para poder usar compensaciones dinámicas en lugar de inicializar siempre la compensación en cero. Esta mejora permite futuras optimizaciones de eficiencia.

Grupos de conjuntos de descriptores lineales

Anteriormente, el comportamiento predeterminado en el renderizador de Vulkan de Godot era crear todos los grupos de conjuntos de descriptores con la marca VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, lo que significa que las asignaciones de conjuntos de descriptores se podían liberar al grupo y se podían realizar reasignaciones desde el grupo. Este modo imponía una sobrecarga adicional en comparación con los grupos de conjuntos de descriptores, que solo permiten la asignación lineal seguida de un restablecimiento total del grupo.

Cuando sea posible, los grupos de conjuntos de descriptores ahora se crean como grupos lineales sin configurar VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT. Luego, los grupos lineales se restablecen por completo cuando sea necesario. Este trabajo también incluye optimizaciones adicionales para la vinculación de conjuntos de descriptores por lotes cuando sea posible, lo que reduce la cantidad de llamadas discretas a vkCmdBindDescriptorSets().

Compatibilidad con el generador de muestras inmutable

Los objetos de muestra que contienen datos de configuración de muestreo se vinculan tradicionalmente como parte de los datos del conjunto de descriptores. Este método permite que los objetos de muestreador se cambien de forma dinámica en los datos del conjunto de descriptores. Vulkan también admite muestreadores inmutables, que codifican los datos del muestreador directamente en el diseño del conjunto de descriptores. Esta configuración del muestreador se vincula cuando se crea el conjunto de descriptores y el estado de la canalización, y no se puede cambiar después de la creación.

Los samplers inmutables sacrifican la flexibilidad por no tener que administrar ni vincular objetos de sampler discretos. Se actualizó el renderizador de Vulkan de Godot para admitir el uso de samplers inmutables. Se cambió el uso de samplers para usar samplers inmutables cuando sea práctico.

Optimización centrada en dispositivos móviles

Se implementaron optimizaciones adicionales para mejorar específicamente el rendimiento de la renderización en el hardware gráfico para dispositivos móviles. Por lo general, las optimizaciones no son relevantes para el hardware de gráficos de clase de computadoras de escritorio debido a los diferentes diseños arquitectónicos.

Entre las optimizaciones, se incluyen las siguientes:

  • Reemplaza el uso de constantes de empuje grandes
  • Asignación de búfer diferida
  • Compatibilidad con búferes persistentes
  • Cambio de modo de decodificación ASTC
  • Rotación previa de la pantalla

Reemplaza el uso de constantes de empuje grandes

Las constantes push son una función que permite insertar valores constantes para el programa sombreador activo en el búfer de comandos. Las constantes push son convenientes porque no requieren la creación ni la propagación de búferes, y no están vinculadas a descriptores. Sin embargo, las constantes push tienen un tamaño máximo limitado y pueden afectar negativamente el rendimiento en el hardware para dispositivos móviles.

Durante las pruebas en dispositivos Android, se mejoró el rendimiento reemplazando el uso constante de push de más de 16 bytes por búferes uniformes. Los sombreadores que usaban 16 bytes o menos de datos constantes tenían un mejor rendimiento con constantes push. Además de las consideraciones de rendimiento, algunos hardwares gráficos tienen mínimos de alineación de 64 bytes para búferes uniformes, lo que reduce la eficiencia de la memoria debido al padding no utilizado en comparación con el uso de constantes push.

Asignación de búfer diferida

La mayoría del hardware gráfico para dispositivos móviles usa una arquitectura de renderización diferida basada en mosaicos (TBDR). Las GPUs que usan TBDR dividen la región de pantalla más grande en una cuadrícula de tarjetas más pequeñas y las renderizan por tarjeta. Cada tarjeta tiene una pequeña cantidad de RAM de alta velocidad que la GPU usa para el almacenamiento cuando renderiza una tarjeta. Con TBDR, los destinos de renderización que nunca se muestrean a otro destino fuera de su paso de renderización pueden permanecer completamente en la RAM de la tarjeta y no requieren un búfer para un almacenamiento en búfer de memoria principal.

Se agregó VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT durante la creación de destinos de renderización adecuados, como los destinos de color y profundidad principales, para evitar asignar memoria de búfer que nunca se usaría. Se midió que el ahorro de memoria de la asignación diferida en escenas de muestra es de aproximadamente 50 megabytes de RAM.

Compatibilidad con búferes persistentes

El hardware para dispositivos móviles usa una arquitectura de memoria unificada (UMA) en lugar de la diferenciación de hardware entre la RAM principal y la RAM de gráficos. Cuando la RAM principal y la RAM de gráficos están separadas, los datos deben transferirse de la RAM principal a la RAM de gráficos para que la GPU los use. Godot ya implementa este proceso de transferencia en su renderizador de Vulkan a través del uso de búferes de preparación. En el hardware de UMA, no se necesita un búfer de preparación para muchos tipos de datos, ya que la CPU y la GPU pueden usar la memoria. Forge implementó la compatibilidad con búferes compartidos persistentes en hardware compatible para eliminar la etapa de preparación cuando sea posible.

Imágenes de una escena de Godot que muestran información de generación de perfiles con y sin búferes persistentes habilitados.
Figura 1: Diferencias de generación de perfiles entre los búferes persistentes habilitados y los inhabilitados en una escena de muestra.

Cambio de modo de decodificación ASTC

La compresión de textura escalable y adaptable (ASTC) es el formato de compresión de texturas moderno preferido en el hardware para dispositivos móviles. Durante la descompresión, la GPU puede decodificar los texels en un valor intermedio que tiene una precisión mayor que la requerida para la fidelidad visual, lo que genera una pérdida de eficiencia en la texturización. Si el hardware de destino es compatible, se usa la extensión VK_EXT_astc_decode_mode para especificar valores no normalizados de 8 bits por componente cuando se decodifica en lugar de valores de punto flotante de 16 bits.

Rotación previa de la pantalla

Para obtener un rendimiento óptimo cuando se usa Vulkan en Android, los juegos deben conciliar la orientación del dispositivo de la pantalla con la orientación de la superficie de renderización. Este proceso se conoce como prerotación. Si no se realiza la rotación previa, el rendimiento puede disminuir debido a que el SO Android debe agregar un pase de compositor para rotar las imágenes de forma manual. Se agregó compatibilidad con la rotación previa en Android al renderizador de Godot.

Mejoras de depuración

Además de realizar optimizaciones de rendimiento, The Forge mejoró la experiencia de depuración de problemas gráficos en el renderizador de Godot con las siguientes incorporaciones:

  • Extensión de fallas del dispositivo
  • Rutas de navegación
  • Marcadores de depuración

Extensión de fallas del dispositivo

Cuando la GPU encuentra un problema durante las operaciones de renderización, el controlador de Vulkan puede mostrar un resultado VK_ERROR_DEVICE_LOST de una llamada a la API de Vulkan. De forma predeterminada, no se proporciona información contextual adicional sobre por qué el controlador devolvió VK_ERROR_DEVICE_LOST. La extensión VK_EXT_device_fault proporciona un mecanismo para que el controlador proporcione información adicional sobre la naturaleza de la falla. Godot agregó compatibilidad para habilitar la extensión de fallas del dispositivo (si está disponible) y para informar la información que muestra el controlador.

Puede ser difícil depurar una falla de GPU o una detención de ejecución. Para ayudar a identificar qué contenido gráfico se podría haber renderizado en el momento de una falla, se agregó compatibilidad con rutas de navegación al renderizador de Godot. El pan de miga son valores definidos por el usuario que se pueden adjuntar al contenido en las listas de dibujo del gráfico de renderización. Los datos del breadcrumb se escriben antes de que se inicie un nuevo pase de renderización. Si se produce una falla o una detención de ejecución, se puede usar el valor actual del breadcrumb para determinar qué datos pueden haber causado el problema.

Marcadores de depuración

Los marcadores de depuración, cuando el controlador los admite, se usan para nombrar recursos. Esto permite que las cadenas legibles por el usuario se asocien con operaciones como los pases de renderización y recursos como búferes y texturas cuando se usa una herramienta de gráficos como RenderDoc. Se agregó compatibilidad con la anotación de marcadores de depuración al renderizador de Godot Vulkan.

Blog de Godot Engine: Actualización sobre la colaboración con Google y The Forge

Solicitud de extracción de colaboración de Vulkan en Godot Engine