Apple Metal en MAPS.ME

imagen Hola a todos!

En el mundo hay una gran cantidad de aplicaciones en OpenGL, y parece que Apple no está de acuerdo con esto. A partir de iOS 12 y MacOS Mojave, OpenGL ha quedado en desuso. Hemos integrado Apple Metal en MAPS.ME y estamos listos para compartir nuestra experiencia y resultados. Le diremos cómo se refactorizó nuestro motor de gráficos, qué dificultades tuvimos que enfrentar y, lo más importante, cuántos FPS tenemos ahora.

Todos los que estén interesados ​​o estén considerando agregar soporte de Apple Metal al motor gráfico son bienvenidos a cat.

Problema


Nuestro motor de gráficos fue diseñado como multiplataforma, y ​​dado que OpenGL es, de hecho, la única API de gráficos multiplataforma para el conjunto de plataformas que nos interesa (iOS, Android, MacOS y Linux), lo elegimos como base. No hicimos un nivel adicional de abstracción que ocultara las características específicas de OpenGL, pero, afortunadamente, dejamos el potencial para su implementación.

Con el advenimiento de la nueva generación de API de gráficos Apple Metal y Vulkan, por supuesto, consideramos la posibilidad de su aparición en nuestra aplicación, sin embargo, nos detuvimos por lo siguiente:

  1. Vulkan solo podía funcionar en Android y Linux, y Apple Metal solo podía funcionar en iOS y MacOS. No queríamos perder multiplataforma a nivel de la API gráfica, esto complicaría los procesos de desarrollo y depuración, aumentaría la cantidad de trabajo.
  2. Una aplicación en Apple Metal no se puede construir y ejecutar en un simulador de iOS (por cierto, hasta ahora), lo que también complicaría nuestro desarrollo y no nos permitiría deshacernos por completo de OpenGL.
  3. Qt Framework, que utilizamos para crear herramientas internas, solo admite OpenGL ( ahora se admite Vulkan ).
  4. Apple Metal no tenía y no tiene una API de C ++, lo que nos obligaría a presentar abstracciones no solo para el tiempo de ejecución, sino también para la fase de compilación de la aplicación, cuando parte del motor se compila en Objective-C ++ y otra, sustancialmente más grande, en C ++.
  5. No estábamos listos para hacer un motor separado o una rama de código separada específicamente para iOS.
  6. La implementación se evaluó al menos seis meses en el trabajo de un desarrollador gráfico.

Cuando, en la primavera de 2018, Apple anunció la transferencia de OpenGL a un estado en desuso, quedó claro que ya no era posible posponer, y los problemas anteriores tenían que resolverse de una forma u otra. Además, hemos estado trabajando durante mucho tiempo para optimizar tanto la velocidad de la aplicación como el consumo de energía, y Apple Metal parecía poder ayudar.

Selección de decisiones


Casi de inmediato nos dimos cuenta de MoltenVK . Este marco emula la API de Vulkan usando Apple Metal, y su código fuente se abrió recientemente. Al parecer, usar MoltenVK permitiría reemplazar OpenGL con Vulkan, y no tener que lidiar con la integración separada de Apple Metal. Además, los desarrolladores de Qt han rechazado el soporte por separado para renderizar en Apple Metal a favor de MoltenVK. Sin embargo, nos detuvieron:

  • la necesidad de soportar dispositivos Android en los que Vulkan no está disponible;
  • la incapacidad de comenzar en el simulador de iOS sin la presencia de respaldo en OpenGL;
  • la incapacidad de usar herramientas de Apple para depurar, perfilar y precompilar sombreadores, ya que MoltenVK genera sombreadores en tiempo real para Apple Metal a partir de códigos fuente SPIR-V o GLSL;
  • la necesidad de esperar actualizaciones y correcciones de errores de MoltenVK cuando se lanzan nuevas versiones de Metal;
  • La imposibilidad de una optimización sutil específica para Metal, pero no específica o inexistente para Vulkan.

Resultó que necesitamos guardar OpenGL, lo que significa que no podemos hacerlo sin abstraer el motor de la API gráfica. Apple Metal, OpenGL ES y, en el futuro, Vulkan, se utilizarán para crear componentes internos independientes del motor de gráficos, que pueden ser completamente intercambiables. OpenGL desempeñará el papel de opción de reserva cuando Metal o Vulkan no esté disponible por una razón u otra.

El plan de implementación fue el siguiente:

  1. Refactorización del motor de gráficos para abstraer la API de gráficos utilizada.
  2. Renderiza a Apple Metal para la versión iOS de la aplicación.
  3. Haga los puntos de referencia apropiados para la velocidad de representación y el consumo de energía para ver si las API de gráficos modernos de nivel inferior pueden beneficiar al producto.

Diferencias clave entre OpenGL y Metal


Para entender cómo abstraer la API gráfica, primero determinemos cuáles son las diferencias conceptuales clave entre OpenGL y Metal.

  1. Se cree, y no sin razón, que Metal es una API de nivel inferior. Sin embargo, esto no significa que deba escribir en el ensamblador o implementar la rasterización usted mismo. Metal puede llamarse una API de bajo nivel en el sentido de que realiza un número muy pequeño de acciones implícitas, es decir, casi todas las acciones deben escribirse en el programador mismo. OpenGL hace muchas cosas implícitamente, comenzando por admitir una referencia implícita a un contexto de OpenGL y vincular este contexto a la secuencia en la que se creó.
  2. En Metal, "no" validación en tiempo real de los equipos. En el modo de depuración, la validación, por supuesto, existe y se hace mucho mejor que en muchas otras API, en gran parte debido a su estrecha integración con Xcode. Pero cuando el programa se envía al usuario, ya no hay validación, el programa simplemente falla en el primer error. No hace falta decir que OpenGL se bloquea solo en los casos más extremos. La práctica más común: ignorar el error y continuar trabajando.
  3. Metal puede precompilar sombreadores y construir bibliotecas a partir de ellos. En OpenGL, los sombreadores se compilan desde la fuente en el proceso del programa, del cual es responsable la implementación específica de bajo nivel de OpenGL en un dispositivo en particular. Las diferencias y / o errores en la implementación de compiladores de sombreadores a veces conducen a errores fantásticos, especialmente en dispositivos Android de marcas chinas.
  4. OpenGL usa activamente la máquina de estado, que agrega efectos secundarios a casi todas las funciones. Por lo tanto, las funciones de OpenGL no son funciones puras, y el orden y el historial de llamadas a menudo son importantes. El metal no usa estados implícitamente y no los conserva más tiempo del necesario para el renderizado. Los estados existen como objetos pre-creados y fallidos.

Motor gráfico refactorizado e incrustado de metal


El proceso de refactorización del motor de gráficos consistió básicamente en encontrar la mejor solución para deshacerse de las características de OpenGL que nuestro motor ha estado utilizando activamente. Embedded Metal, a partir de una de las etapas, fue en paralelo.

  • Como ya se señaló, la API de OpenGL tiene una entidad implícita llamada contexto. El contexto está asociado con un hilo específico, y la función OpenGL llamada en ese hilo mismo encuentra y usa este contexto. Metal, Vulkan (sí, y otras API, por ejemplo, Direct3D) no funcionan de esta manera, tienen objetos explícitos similares llamados dispositivo o instancia. El usuario mismo crea estos objetos y es responsable de su transferencia a diferentes subsistemas. Es a través de estos objetos que se realizan todas las llamadas a comandos gráficos.

    Llamamos a nuestro objeto abstracto un contexto gráfico, y en el caso de OpenGL simplemente decora las llamadas de los comandos de OpenGL, y en el caso de Metal contiene la interfaz raíz MTLDevice a través de la cual se llaman los comandos de Metal.

    Por supuesto, tuve que distribuir este objeto (y dado que tenemos renderizado multiproceso, incluso varios de estos objetos) en todos los subsistemas.

    Ocultamos la creación de colas de comandos, codificadores y su gestión dentro del contexto gráfico, para no difundir entidades que simplemente no existen en OpenGL.
  • La perspectiva de la desaparición de la validación de comandos gráficos en los dispositivos de los usuarios no nos agradó abiertamente. Nuestro departamento de control de calidad no puede cubrir completamente una amplia gama de dispositivos y versiones de SO. Por lo tanto, tuvimos que agregar registros detallados donde previamente recibimos un error significativo de la API gráfica. Por supuesto, esta validación se agregó solo a ubicaciones potencialmente peligrosas y críticas del motor de gráficos, ya que cubrir todo el motor con un código de diagnóstico es prácticamente imposible y generalmente dañino para el rendimiento. La nueva realidad es que las pruebas de usuario y la depuración con registros están ahora en el pasado, al menos en términos de representación.
  • Nuestro sistema de sombreado anterior no era adecuado para la refactorización; tuve que reescribirlo por completo. El punto aquí no es solo la precompilación de sombreadores y su validación en la etapa de ensamblaje del proyecto. OpenGL utiliza las llamadas variables uniformes para pasar parámetros a los sombreadores. La transferencia de datos estructurados solo está disponible con OpenGL ES 3.0, y dado que todavía admitimos OpenGL ES 2.0, simplemente no utilizamos este método. Metal nos hizo usar estructuras de datos para pasar parámetros, y para OpenGL tuvimos que idear campos de estructura de mapeo para variables uniformes. Además, tuve que volver a escribir cada uno de los sombreadores en el Metal Shading Language.
  • Cuando usamos objetos de estado, tuvimos que hacer un truco. En OpenGL, todos los estados, como regla, se establecen inmediatamente antes de la representación, y en Metal, este debería ser un objeto previamente creado y validado. Nuestro motor, obviamente, utilizó el enfoque OpenGL, y la refactorización con la creación preliminar de objetos de estado fue comparable con una reescritura completa del motor. Para cortar este nodo, creamos un caché de estado dentro del contexto gráfico. La primera vez que se genera una combinación única de parámetros de estado, se crea un objeto de estado en Metal y se coloca en la memoria caché. Por segunda vez y posteriores, el objeto simplemente se recupera de la memoria caché. Esto funciona en nuestros mapas, ya que el número de diferentes combinaciones de parámetros de estado no es demasiado grande (aproximadamente 20-30). Para un motor gráfico de juego complejo, este método no es adecuado.

Como resultado, después de aproximadamente 5 meses de trabajo, pudimos lanzar MAPS.ME por primera vez con renderizado completo en Apple Metal. Era hora de averiguar qué pasó.

Prueba de velocidad de renderizado


Técnica experimental


Usamos diferentes generaciones de dispositivos Apple en el experimento. Todos ellos se actualizaron a iOS 12. El mismo script de usuario se ejecutó en todos: navegación de mapa (movimiento y escala). El script fue creado para garantizar una identidad casi completa de los procesos dentro de la aplicación cada vez que se inició en cada dispositivo. Como lugar de prueba, elegimos el área de Los Ángeles, una de las áreas con mayor carga en MAPS.ME.

Primero, el script se ejecutó con renderizado en OpenGL ES 3.0, luego en el mismo dispositivo con renderizado en Apple Metal. Entre inicios, la aplicación se descargó completamente de la memoria.
Se midieron los siguientes indicadores:

  • FPS (cuadros por segundo) para todo el cuadro;
  • FPS para la parte del cuadro que se dedica solo a la representación, excluyendo la preparación de datos y otras operaciones cuadro por cuadro;
  • El porcentaje de cuadros lentos (mayores de ~ 30 ms), es decir aquellos que el ojo humano puede percibir como idiotas.

Al medir FPS, se excluía dibujar directamente en la pantalla del dispositivo, ya que la sincronización vertical con la frecuencia de actualización de la pantalla no permite obtener resultados confiables. Por lo tanto, el marco se dibujó en la textura en la memoria. Para sincronizar la CPU y la GPU, OpenGL usó una llamada adicional para glFinish , mientras que Apple Metal usó waitUntilCompleted para MTLFrameCommandBuffer .

iPhone 6siPhone 7+iPhone 8
OpenglMetalOpenglMetalOpenglMetal
Fps106160159221196298
FPS (solo renderizado)157596247597271833
Fracción de fotogramas lentos (<30 fps)4,13%1,25%5,45%0,76%1,5%0.29%

iPhone XiPad Pro 12.9 '
OpenglMetalOpenglMetal
Fps145210104137
FPS (solo renderizado)248705147463
Fracción de fotogramas lentos (<30 fps)0,15%0,15%17.52%4,46%

iPhone 6siPhone 7+iPhone 8iPhone XiPad Pro 12.9 '
Aceleración del marco en Metal (N veces)1,51,391,521,451,32
Aceleración de renderizado en Metal (N veces)3.782,413,072,843.15
Mejora en cuadros lentos (N veces)3,37.175.1713.93

Análisis de resultados


En promedio, la ganancia de rendimiento del marco con Apple Metal fue del 43%. El valor mínimo se fija en el iPad Pro 12.9 '- 32%, el máximo - 52% en el iPhone 8. Existe una dependencia: cuanto menor es la resolución de la pantalla, más Apple Metal supera a OpenGL ES 3.0.

Si evaluamos la parte del marco que es directamente responsable del renderizado, la velocidad promedio de renderizado en Apple Metal ha aumentado 3 veces. Esto indica una organización significativamente mejor y, como resultado, la eficiencia de la API de Apple Metal en comparación con OpenGL ES 3.0.

El número de fotogramas lentos (más de ~ 30 ms) en Apple Metal se redujo en aproximadamente 4 veces. Esto significa que la percepción de las animaciones y moverse por el mapa se ha vuelto más suave. El peor resultado se registró en el iPad Pro 12.9 'con una resolución de 2732 x 2048 píxeles: OpenGL ES 3.0 ofrece aproximadamente 17.5% de fotogramas lentos, mientras que Apple Metal - solo 4.5%.

Prueba de potencia


Técnica experimental


El consumo de energía se probó en el iPhone 8 en iOS 12. Se ejecutó el mismo escenario de usuario: navegación de mapa (movimiento y escala) durante 1 hora. El script fue programado para garantizar una identidad casi completa de los procesos dentro de la aplicación en cada inicio. El área de Los Ángeles también fue elegida como lugar de prueba.

Utilizamos el siguiente enfoque para medir el consumo de energía. El dispositivo no está conectado a la carga. En la configuración del desarrollador, el registro de energía está habilitado. Antes de comenzar el experimento, el dispositivo está completamente cargado. El experimento termina al final del guión. Al final del experimento, se registró el estado de la carga de la batería y se importaron los registros de consumo de energía a la utilidad para perfilar la batería en Xcode. Registramos cuánto de la carga se gastó en la GPU. Además, aquí también ponderamos el renderizado al incluir la visualización del esquema de metro y el suavizado de pantalla completa.

El brillo de la pantalla no cambió en todos los casos. No se ejecutaron otros procesos excepto el sistema y MAPS.ME. El modo avión estaba activado, el wifi y el GPS estaban desactivados. Además, se llevaron a cabo varias mediciones de control.

Como resultado, para cada uno de los indicadores, se formó una comparación de Metal con OpenGL, y luego se promediaron las razones para obtener una estimación agregada.

OpenglMetalGanar
Drenaje de la batería32%28%12,5%
Perfilado del uso de la batería en Xcode1,95%1,83%6,16%

Análisis de resultados


En promedio, el consumo de energía de la versión de renderizado en Apple Metal ha mejorado ligeramente. El consumo de energía de nuestra aplicación GPU no se ve muy afectado, alrededor del 2%, porque MAPS.ME no se puede llamar altamente cargado en términos de uso de la GPU. Probablemente se logre una pequeña ganancia al reducir los costos computacionales al preparar instrucciones para la GPU en la CPU, que, desafortunadamente, no se pueden distinguir con la ayuda de herramientas de creación de perfiles.

Resumen


Embedded Metal nos costó 5 meses de desarrollo. Dos desarrolladores hicieron esto, sin embargo, casi siempre a su vez. Obviamente, ganamos significativamente en rendimiento de renderizado, ganamos un poco en consumo de energía. Además, tuvimos la oportunidad de incorporar nuevas API de gráficos, en particular, Vulkan, con mucho menos esfuerzo. Casi completamente "resuelto" el motor de gráficos, como resultado, encontramos y reparamos varios errores antiguos y problemas de rendimiento.

A la pregunta de si nuestro proyecto realmente necesita renderizarse en Apple Metal, estamos listos para responder afirmativamente. No es tanto que nos encante la innovación, o que Apple finalmente pueda abandonar OpenGL. Es solo 2018, y OpenGL apareció en el lejano 1997, ya es hora de dar el siguiente paso.

PD: Hasta que lanzamos la función en todos los dispositivos iOS. Para habilitarlo manualmente, escriba ?metal en la barra de búsqueda y reinicie la aplicación. Para devolver el renderizado a OpenGL, ingrese el comando ?gl y reinicie la aplicación.

PPS MAPS.ME es un proyecto de código abierto. Puedes leer el código fuente en github .

Source: https://habr.com/ru/post/es430850/


All Articles