Apple Metal chez MAPS.ME

image Bonjour à tous!

Dans le monde, il existe un grand nombre d'applications sur OpenGL, et il semble qu'Apple ne soit pas tout à fait d'accord avec cela. Depuis iOS 12 et MacOS Mojave, OpenGL est obsolète. Nous avons intégré Apple Metal dans MAPS.ME et sommes prêts à partager notre expérience et nos résultats. Nous vous dirons comment refaçonner notre moteur graphique, quelles difficultés nous avons dû affronter et, surtout, combien de FPS nous avons maintenant.

Tous ceux qui sont intéressés ou envisagent d'ajouter le support Apple Metal au moteur graphique sont les bienvenus à cat.

Problème


Notre moteur graphique a été conçu comme multiplateforme, et comme OpenGL est, en fait, la seule API graphique multiplateforme pour l'ensemble des plates-formes qui nous intéressent (iOS, Android, MacOS et Linux), nous l'avons choisi comme base. Nous n'avons pas fait un niveau d'abstraction supplémentaire qui cacherait les fonctionnalités spécifiques à OpenGL, mais, heureusement, nous avons laissé le potentiel de sa mise en œuvre.

Avec l'avènement de la nouvelle génération d'API graphiques Apple Metal et Vulkan, nous avons bien sûr envisagé la possibilité de leur apparition dans notre application, cependant, nous avons été arrêtés par ce qui suit:

  1. Vulkan ne pouvait fonctionner que sur Android et Linux, et Apple Metal ne pouvait fonctionner que sur iOS et MacOS. Nous ne voulions pas perdre la multiplicité des plates-formes au niveau de l'API graphique, cela compliquerait les processus de développement et de débogage, augmenterait la quantité de travail.
  2. Une application sur Apple Metal ne peut pas être construite et exécutée sur un simulateur iOS (à propos, jusqu'à présent), ce qui compliquerait également notre développement et ne nous permettrait pas de nous débarrasser complètement d'OpenGL.
  3. Le framework Qt, que nous utilisons pour créer des outils internes, ne supportait que OpenGL ( Vulkan est maintenant supporté ).
  4. Apple Metal n'avait pas et n'a pas d'API C ++, ce qui nous obligerait à proposer des abstractions non seulement pour le runtime, mais aussi pour la phase de construction de l'application, lorsqu'une partie du moteur est compilée en Objective-C ++, et une autre, beaucoup plus grande, en C ++.
  5. Nous n'étions pas prêts à créer un moteur séparé ou une branche de code distincte spécifiquement pour iOS.
  6. La mise en œuvre a été évaluée au moins six mois dans le travail d'un développeur graphique.

Lorsqu'au printemps 2018, Apple a annoncé le transfert d'OpenGL au statut obsolète, il est devenu clair qu'il n'était plus possible de reporter, et les problèmes ci-dessus devaient être résolus d'une manière ou d'une autre. De plus, nous travaillons depuis longtemps à l'optimisation de la vitesse d'application et de la consommation d'énergie, et Apple Metal semblait être en mesure de vous aider.

Sélection de décision


Presque immédiatement, nous avons remarqué MoltenVK . Ce framework émule l'API Vulkan en utilisant Apple Metal, et son code source a été récemment ouvert. L'utilisation de MoltenVK, semble-t-il, permettrait de remplacer OpenGL par Vulkan, et n'aurait pas du tout à gérer l'intégration séparée d'Apple Metal. De plus, les développeurs de Qt ont refusé un support séparé pour le rendu sur Apple Metal en faveur de MoltenVK. Cependant, nous avons été arrêtés:

  • la nécessité de prendre en charge les appareils Android sur lesquels Vulkan n'est pas disponible;
  • l'impossibilité de démarrer sur le simulateur iOS sans la présence de repli sur OpenGL;
  • l'incapacité à utiliser les outils Apple pour le débogage, le profilage et la précompilation des shaders, car MoltenVK génère des shaders en temps réel pour Apple Metal à partir des codes source SPIR-V ou GLSL;
  • la nécessité d'attendre les mises à jour et les corrections de bugs de MoltenVK lors de la sortie de nouvelles versions de Metal;
  • l'impossibilité d'une subtile optimisation spécifique au Metal, mais non spécifique ou inexistante pour Vulkan.

Il s'est avéré que nous devons enregistrer OpenGL, ce qui signifie que nous ne pouvons pas nous passer de l'abstraction du moteur de l'API graphique. Apple Metal, OpenGL ES et à l'avenir Vulkan seront utilisés pour créer des composants internes indépendants du moteur graphique, qui peuvent être complètement interchangeables. OpenGL jouera le rôle d'option de secours lorsque Metal ou Vulkan ne sont pas disponibles pour une raison ou une autre.

Le plan de mise en œuvre était le suivant:

  1. Refactorisation du moteur graphique pour abstraire l'API graphique utilisée.
  2. Rendre à Apple Metal pour la version iOS de l'application.
  3. Faites des repères appropriés pour la vitesse de rendu et la consommation d'énergie pour voir si les API graphiques modernes de niveau inférieur peuvent bénéficier au produit.

Différences clés entre OpenGL et Metal


Pour comprendre comment abstraire l'API graphique, déterminons d'abord quelles sont les principales différences conceptuelles entre OpenGL et Metal.

  1. On pense, et non sans raison, que Metal est une API de niveau inférieur. Cependant, cela ne signifie pas que vous devez écrire dans l'assembleur ou implémenter la rastérisation vous-même. Le métal peut être appelé une API de bas niveau dans le sens où il effectue un très petit nombre d'actions implicites, c'est-à-dire que presque toutes les actions doivent être écrites sur le programmeur lui-même. OpenGL fait beaucoup de choses implicitement, à commencer par la prise en charge d'une référence implicite à un contexte OpenGL et la liaison de ce contexte au flux dans lequel il a été créé.
  2. En Metal, "non" validation en temps réel des équipes. En mode débogage, la validation, bien sûr, existe et est bien meilleure que dans de nombreuses autres API, principalement en raison de son intégration étroite avec Xcode. Mais lorsque le programme est envoyé à l'utilisateur, il n'y a plus de validation, le programme se bloque simplement sur la toute première erreur. Inutile de dire qu'OpenGL ne plante que dans les cas les plus extrêmes. La pratique la plus courante: ignorer l'erreur et continuer à travailler.
  3. Metal peut précompiler les shaders et créer des bibliothèques à partir d'eux. Dans OpenGL, les shaders sont compilés à partir de la source dans le processus du programme, car cela est responsable de l'implémentation spécifique de bas niveau d'OpenGL sur un périphérique particulier. Des différences et / ou des erreurs dans la mise en œuvre des compilateurs de shaders conduisent parfois à des bugs fantastiques, en particulier sur les appareils Android de marques chinoises.
  4. OpenGL utilise activement la machine à états, qui ajoute des effets secondaires à presque toutes les fonctions. Ainsi, les fonctions OpenGL ne sont pas des fonctions pures et l'ordre et l'historique des appels sont souvent importants. Metal n'utilise pas implicitement les états et ne les conserve pas plus longtemps que nécessaire pour le rendu. Les états existent en tant qu'objets précréés et défaillants.

Refonte et intégration du moteur graphique Métal


Le processus de refactorisation du moteur graphique consistait essentiellement à trouver la meilleure solution pour se débarrasser des fonctionnalités OpenGL que notre moteur utilisait activement. Embedding Metal, à partir de l'une des étapes, s'est déroulé en parallèle.

  • Comme déjà indiqué, l'API OpenGL possède une entité implicite appelée un contexte. Le contexte est associé à un thread spécifique, et la fonction OpenGL appelée dans ce thread lui-même trouve et utilise ce contexte. Metal, Vulkan (oui, et d'autres API, par exemple, Direct3D) ne fonctionnent pas de cette façon, ils ont des objets explicites similaires appelés périphérique ou instance. L'utilisateur crée lui-même ces objets et est responsable de leur transfert vers différents sous-systèmes. C'est à travers ces objets que tous les appels aux commandes graphiques sont effectués.

    Nous avons appelé notre objet abstrait un contexte graphique, et dans le cas d'OpenGL il décore simplement les appels des commandes OpenGL, et dans le cas de Metal il contient l'interface racine MTLDevice à travers laquelle les commandes Metal sont appelées.

    Bien sûr, j'ai dû distribuer cet objet (et puisque nous avons un rendu multi-thread, puis même plusieurs de ces objets) sur tous les sous-systèmes.

    Nous avons caché la création de files d'attente de commandes, d'encodeurs et leur gestion dans le contexte graphique, afin de ne pas propager des entités qui n'existent tout simplement pas dans OpenGL.
  • La perspective de la disparition de la validation des commandes graphiques sur les appareils des utilisateurs ne nous plaisait pas ouvertement. Une large gamme d'appareils et de versions de système d'exploitation n'a pas pu être entièrement couverte par notre service d'assurance qualité. Par conséquent, nous avons dû ajouter des journaux détaillés où nous avions précédemment reçu une erreur significative de l'API graphique. Bien sûr, cette validation n'a été ajoutée qu'aux emplacements potentiellement dangereux et critiques du moteur graphique, car couvrir l'intégralité du moteur avec un code de diagnostic est pratiquement impossible et généralement préjudiciable aux performances. La nouvelle réalité est que les tests utilisateur et le débogage avec les journaux sont désormais du passé, du moins en termes de rendu.
  • Notre précédent système de shaders n'était pas adapté à la refactorisation; j'ai dû le réécrire complètement. Il ne s'agit pas seulement de la précompilation des shaders et de leur validation au stade de l'assemblage du projet. OpenGL utilise les variables dites uniformes pour passer des paramètres aux shaders. Le transfert de données structuré n'est disponible qu'avec OpenGL ES 3.0, et comme nous prenons toujours en charge OpenGL ES 2.0, nous n'avons tout simplement pas utilisé cette méthode. Le métal nous a fait utiliser des structures de données pour passer des paramètres, et pour OpenGL nous avons dû trouver des mappages de champs de structure à des variables uniformes. De plus, j'ai dû réécrire chacun des shaders dans le Metal Shading Language.
  • Lors de l'utilisation d'objets d'état, nous devions opter pour une astuce. Dans OpenGL, tous les états, en règle générale, sont définis immédiatement avant le rendu, et dans Metal, il doit s'agir d'un objet précédemment créé et validé. Notre moteur, évidemment, utilisait l'approche OpenGL, et le refactoring avec la création préliminaire d'objets d'état était comparable à une réécriture complète du moteur. Pour couper ce nœud, nous avons créé un cache d'état à l'intérieur du contexte graphique. La première fois qu'une combinaison unique de paramètres d'état est générée, un objet d'état est créé dans Metal et placé dans le cache. Pour la deuxième fois et les suivantes, l'objet est simplement récupéré du cache. Cela fonctionne dans nos cartes, car le nombre de combinaisons différentes de paramètres d'état n'est pas trop grand (environ 20-30). Pour un moteur graphique de jeu complexe, cette méthode ne convient guère.

En conséquence, après environ 5 mois de travail, nous avons pu lancer MAPS.ME pour la première fois avec un rendu complet sur Apple Metal. Il était temps de découvrir ce qui s'était passé.

Test de vitesse de rendu


Technique expérimentale


Nous avons utilisé différentes générations d'appareils Apple dans l'expérience. Tous ont été mis à jour vers iOS 12. Le même script utilisateur a été exécuté sur la navigation sur toutes les cartes (déplacement et mise à l'échelle). Le script a été conçu pour garantir une identité presque complète des processus au sein de l'application chaque fois qu'il a été démarré sur chaque appareil. Comme lieu de test, nous avons choisi la zone de Los Angeles - l'une des zones les plus chargées de MAPS.ME.

Tout d'abord, le script a été exécuté avec un rendu sur OpenGL ES 3.0, puis sur le même appareil avec un rendu sur Apple Metal. Entre les démarrages, l'application a été complètement déchargée de la mémoire.
Les indicateurs suivants ont été mesurés:

  • FPS (images par seconde) pour l'ensemble de l'image;
  • FPS pour la partie de la trame qui est uniquement engagée dans le rendu, à l'exclusion de la préparation des données et des autres opérations trame par trame;
  • Le pourcentage de trames lentes (supérieures à ~ 30 ms), c'est-à-dire ceux que l'œil humain peut percevoir comme des saccades.

Lors de la mesure des FPS, le dessin directement sur l'écran de l'appareil a été exclu, car la synchronisation verticale avec le taux de rafraîchissement de l'écran ne permet pas d'obtenir des résultats fiables. Par conséquent, le cadre a été dessiné dans la texture en mémoire. Pour synchroniser le CPU et le GPU, OpenGL a utilisé un appel supplémentaire à glFinish , tandis qu'Apple Metal a utilisé waitUntilCompleted pour MTLFrameCommandBuffer .

iPhone 6siPhone 7+iPhone 8
OpenglMétalOpenglMétalOpenglMétal
Fps106160159221196298
FPS (rendu uniquement)157596247597271833
Fraction d'images lentes (<30 ips)4,13%1,25%5,45%0,76%1,5%0,29%

iPhone XiPad Pro 12,9 pouces
OpenglMétalOpenglMétal
Fps145210104137
FPS (rendu uniquement)248705147463
Fraction d'images lentes (<30 ips)0,15%0,15%17,52%4,46%

iPhone 6siPhone 7+iPhone 8iPhone XiPad Pro 12,9 pouces
Accélération du cadre sur métal (N fois)1,51,391,521,451,32
Accélération du rendu sur Metal (N fois)3,782,413.072,843,15
Amélioration des images lentes (N fois)3.37.175.1713,93

Analyse des résultats


En moyenne, le gain de performances du cadre avec Apple Metal était de 43%. La valeur minimale est fixée sur l'iPad Pro 12,9 '- 32%, le maximum - 52% sur l'iPhone 8. Il y a une dépendance: plus la résolution d'écran est basse, plus Apple Metal dépasse OpenGL ES 3.0.

Si nous évaluons la partie du cadre directement responsable du rendu, la vitesse de rendu moyenne sur Apple Metal a été multipliée par 3. Cela indique une organisation nettement meilleure et, par conséquent, l'efficacité de l'API Apple Metal par rapport à OpenGL ES 3.0.

Le nombre d'images lentes (plus de ~ 30 ms) sur Apple Metal a été réduit d'environ 4 fois. Cela signifie que la perception des animations et des déplacements sur la carte est devenue plus fluide. Le pire résultat a été enregistré sur l'iPad Pro 12,9 'avec une résolution de 2732 x 2048 pixels: OpenGL ES 3.0 donne environ 17,5% d'images lentes, tandis qu'Apple Metal - seulement 4,5%.

Test de puissance


Technique expérimentale


La consommation d'énergie a été testée sur iPhone 8 sur iOS 12. Le même scénario utilisateur a été exécuté - navigation sur la carte (déplacement et mise à l'échelle) pendant 1 heure. Le script a été conçu pour garantir une identité presque complète des processus au sein de l'application à chaque démarrage. La région de Los Angeles a également été choisie comme lieu de test.

Nous avons utilisé l'approche suivante pour mesurer la consommation d'énergie. L'appareil n'est pas connecté au chargement. Dans les paramètres du développeur, la journalisation de l'alimentation est activée. Avant de commencer l'expérience, l'appareil est complètement chargé. L'expérience se termine à la fin du script. À la fin de l'expérience, l'état de charge de la batterie a été enregistré et les journaux de consommation d'énergie ont été importés dans l'utilitaire pour profiler la batterie dans Xcode. Nous avons enregistré combien de frais ont été dépensés pour le GPU. De plus, nous avons ici pondéré le rendu en incluant l'affichage du schéma de métro et l'anticrénelage plein écran.

La luminosité de l'écran n'a pas changé dans tous les cas. Aucun autre processus que le système et MAPS.ME n'a été exécuté. Le mode avion a été activé, le Wi-Fi et le GPS ont été désactivés. De plus, plusieurs mesures de contrôle ont été effectuées.

En conséquence, pour chacun des indicateurs, une comparaison de Metal avec OpenGL a été formée, puis les ratios ont été moyennés pour obtenir une estimation agrégée.

OpenglMétalGain
Drain de batterie32%28%12,5%
Profilage de l'utilisation de la batterie dans Xcode1,95%1,83%6,16%

Analyse des résultats


En moyenne, la consommation d'énergie de la version de rendu sur Apple Metal s'est légèrement améliorée. La consommation d'énergie de notre application GPU n'est pas très affectée, environ 2%, car MAPS.ME ne peut pas être qualifié de très chargé en termes d'utilisation du GPU. Un petit gain est probablement obtenu en réduisant les coûts de calcul lors de la préparation d'instructions pour le GPU sur le CPU, qui, malheureusement, ne peuvent pas être distingués à l'aide d'outils de profilage.

Résumé


L'intégration du métal nous a coûté 5 mois de développement. Cependant, deux développeurs l'ont fait presque à tour de rôle. Évidemment, nous avons gagné de manière significative en performance de rendu, nous avons gagné un peu en consommation d'énergie. De plus, nous avons eu l'occasion d'intégrer de nouvelles API graphiques, en particulier Vulkan, avec beaucoup moins d'efforts. Presque complètement «trié» le moteur graphique, en conséquence, nous avons trouvé et corrigé plusieurs vieux bogues et problèmes de performances.

À la question de savoir si notre projet a vraiment besoin d'être rendu sur Apple Metal, nous sommes prêts à répondre par l'affirmative. Ce n'est pas tant que nous aimons l'innovation, ou qu'Apple puisse enfin abandonner OpenGL. Nous sommes juste en 2018, et OpenGL est apparu dans le lointain 1997, il est grand temps de passer à l'étape suivante.

PS Jusqu'à ce que nous lancions la fonctionnalité sur tous les appareils iOS. Pour l'activer manuellement, tapez ?metal dans la barre de recherche et redémarrez l'application. Pour retourner le rendu à OpenGL, entrez la commande ?gl et redémarrez l'application.

PPS MAPS.ME est un projet open-source. Vous pouvez lire le code source sur github .

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


All Articles