La bibliothèque Frame Pacing   Composante d'Android Game Development Kit.

La bibliothèque Android Frame Pacing, également appelée Swappy, fait partie des bibliothèques AGDK. Elle permet aux jeux OpenGL et Vulkan de bénéficier d'un rendu fluide et d'un frame pacing optimal sur Android. Ce document définit la notion de "frame pacing", c'est-à-dire d'optimisation de la cadence d'affichage, décrit les situations dans lesquelles cette optimisation est nécessaire et montre comment la bibliothèque est utile dans chaque cas. Si vous souhaitez passer directement à l'implémentation du frame pacing dans votre jeu, consultez Étape suivante.

Contexte

Le frame pacing désigne la synchronisation de la logique et de la boucle de rendu d'un jeu avec le sous-système d'affichage d'un système d'exploitation et le matériel utilisé pour l'affichage. Le sous-système d'affichage Android a été conçu pour éviter les artefacts visuels (tearing) qui peuvent se produire lorsque le matériel d'affichage passe à une nouvelle image avant d'avoir terminé l'affichage de la précédente. Pour éviter ces artefacts, le sous-système d'affichage effectue les opérations suivantes :

  • Met en mémoire tampon les images précédentes
  • Détecte les envois d'images tardifs
  • Réaffiche des images passées en cas d'images tardives détectées

Un jeu informe SurfaceFlinger, le compositeur du sous-système d'affichage, qu'il a envoyé tous les appels de dessin requis pour une image (en appelant eglSwapBuffers ou vkQueuePresentKHR). SurfaceFlinger indique la disponibilité d'une image au matériel d'affichage à l'aide d'un outil de déverrouillage. Le matériel d'affichage affiche ensuite l'image obtenue. Le matériel d'affichage reste à un débit constant, par exemple 60 Hz. Si aucune image n'est disponible lorsqu'il en a besoin, le matériel affiche à nouveau l'image précédente.

Les temps de rendu irréguliers se produisent souvent lorsqu'une boucle de rendu de jeu s'affiche à une fréquence différente de celle du matériel d'affichage natif. Si un jeu à 30 FPS tente de générer son rendu sur un appareil compatible nativement avec 60 FPS, la boucle de rendu du jeu ne comprend pas qu'une image répétée reste à l'écran pendant 16 millisecondes supplémentaires. Ce décalage perturbe souvent significativement les temps de rendu, qui peuvent ainsi varier et passer, par exemple, de 49 ms à 16 ms, puis à 33 ms. Les scènes trop complexes aggravent ce problème, car elles donnent lieu à des images manquées.

Solutions non optimales

Les solutions suivantes pour optimiser le frame pacing ont été utilisées pour les jeux par le passé. Elles entraînent généralement des délais de rendu irréguliers et une latence d'entrée accrue.

Envoyer des images aussi rapidement que le permet l'API de rendu

Cette approche contraint le jeu à se caler sur une activité SurfaceFlinger variable et introduit une latence supplémentaire d'une image. Le pipeline d'affichage contient une file d'attente d'images (généralement de taille 2), qui se remplit si le jeu tente d'afficher trop rapidement les images. Comme il n'y a plus d'espace dans la file d'attente, la boucle de jeu (ou au moins le thread de rendu) est bloquée par un appel OpenGL ou Vulkan. Le jeu est ensuite forcé d'attendre que le matériel d'affichage affiche une image, ce qui permet de synchroniser les deux composants. Cette situation est appelée remplissage de mémoire tampon ou remplissage de la file d'attente. Étant donné que le processus de rendu ne reçoit pas d'information sur ce processus, l'affichage des images devient encore plus irrégulier. Si le jeu échantillonne des entrées avant l'image, la latence d'entrée s'aggrave.

Utiliser Android Choreographer seul

Les jeux utilisent également le Android Choreographer pour la synchronisation. Disponible en Java à partir de l'API 16 et en C++ à partir de l'API 24, ce composant fournit des ticks réguliers à la même fréquence que le sous-système d'affichage. Il reste cependant des réglages délicats pour assurer la coordination entre ce tick et le VSYNC matériel réel, avec des décalages variables selon les appareils. Le remplissage de la mémoire tampon peut toujours se produire pour certaines images longues.

Avantages de la bibliothèque Frame Pacing

La bibliothèque Frame Pacing utilise le Android Choreographer pour la synchronisation et gère la variabilité de l'envoi des ticks à votre place. Il utilise des codes temporels de présentation pour s'assurer que les images s'affichent au bon moment et des périmètres de synchronisation pour éviter le remplissage de la mémoire tampon. La bibliothèque utilise l'outil NDK Choreographer s'il est disponible ou, à défaut, le chorégraphe Java.

La bibliothèque gère plusieurs fréquences d'actualisation si celles-ci sont compatibles avec l'appareil, ce qui offre plus de flexibilité dans la présentation de chaque image pour un jeu donné. Par exemple, pour un appareil compatible avec une fréquence d'actualisation de 60 Hz et de 90 Hz, un jeu ne pouvant pas produire 60 images par seconde peut passer à 45 FPS au lieu de 30 FPS pour rester fluide. La bibliothèque détecte la fréquence d'images de jeu prévue et ajuste automatiquement les temps de présentation des frames en conséquence. La bibliothèque Frame Pacing améliore également l'autonomie de la batterie, car elle évite les mises à jour inutiles de l'affichage. Par exemple, si un jeu effectue un rendu à 60 FPS, mais que l'écran est mis à jour à 120 Hz, il est mis à jour deux fois pour chaque frame. La bibliothèque Frame Pacing évite ce problème en définissant la fréquence d'actualisation sur la valeur acceptée par l'appareil le plus proche de la fréquence d'images cible.

Fonctionnement

Dans les sections suivantes, nous allons voir comment la bibliothèque Frame Pacing résout les problèmes posés par les images longues et courtes afin d'atteindre le frame pacing souhaité.

Cadence d'affichage correcte à 30 Hz

Lors d'un rendu à 30 Hz sur un appareil 60 Hz, la situation idéale sur Android est illustrée à la figure 1. SurfaceFlinger ajoute de nouveaux tampons graphiques, s'ils sont présents (la mention "NB" sur le diagramme signifie "No Buffer" [Aucun tampon], et l'image précédente est répétée).

Frame pacing idéal à 30 Hz sur un appareil 60 Hz

Figure 1 : Frame pacing idéal à 30 Hz sur un appareil 60 Hz

Les images de jeu courtes provoquent du stuttering

Sur la plupart des appareils modernes, les moteurs de jeu s'appuient sur le chorégraphe de la plate-forme pour fournir des ticks rythmant l'envoi d'images. Toutefois, il reste possible que le frame pacing soit médiocre en raison des images courtes, comme illustré dans la figure 2. Les images courtes suivies d'images longues provoquent un effet de stuttering pour le joueur.

Images de jeu courtes

Figure 2 : L'image de jeu courte C force l'image B à ne présenter qu'une image, suivie de plusieurs images C

La bibliothèque Frame Pacing résout le problème en utilisant des codes temporels de présentation. Elle utilise les extensions de code temporel de présentation EGL_ANDROID_presentation_time et VK_GOOGLE_display_timing afin que les images ne soient pas présentées trop tôt, comme dans la figure 3.

Codes temporels des présentations

Figure 3 : Image de jeu B présentée deux fois pour un affichage plus fluide

Les images de jeu longues provoquent du stuttering et de la latence

Lorsque la charge de travail d'affichage demande plus de temps que celle de l'application, des images supplémentaires sont ajoutées à la file d'attente. Cela entraîne, ici encore, du stuttering et peut également causer une latence supplémentaire due au remplissage de la mémoire tampon (voir la figure 4). La bibliothèque élimine le stuttering et la latence due à l'image supplémentaire.

Images de jeu longues

Figure 4 : L'image longue B perturbe la cadence de deux images (A et B)

La bibliothèque résout ce problème en utilisant des périmètres de synchronisation (EGL_KHR_fence_sync et VkFence) pour injecter des temps d'attente dans l'application afin de permettre au pipeline d'affichage de rattraper son retard plutôt que faire monter la pression. L'image A présente toujours une image supplémentaire, mais la présentation de l'image B s'effectue à une cadence correcte, comme illustré sur la figure 5.

Délai d'attente ajouté à la couche d'application

Figure 5 : Les images C et D sont en attente de présentation

Modes de fonctionnement compatibles

Vous pouvez configurer la bibliothèque Frame Pacing pour qu'elle fonctionne dans l'un des trois modes suivants :

  • Mode automatique désactivé + pipeline
  • Mode automatique activé + pipeline
  • Mode automatique activé + mode avec pipeline automatique (avec/sans pipeline)

Vous pouvez tester les modes automatique et pipeline, mais vous devez d'abord les désactiver et inclure les éléments suivants après avoir initialisé Swappy :

  swappyAutoSwapInterval(false);
  swappyAutoPipelineMode(false);
  swappyEnableStats(false);
  swappySwapIntervalNS(1000000000L/yourPreferredFrameRateInHz);

Mode avec pipeline

Pour coordonner les charges de travail du moteur, la bibliothèque utilise généralement un modèle de pipeline permettant de séparer les charges de travail du processeur et du GPU indépendamment des limites VSYNC.

Mode avec pipeline

Figure 6 : Mode avec pipeline

Mode sans pipeline

En général, cette approche réduit la latence de l'écran d'entrée et la rend plus prévisible. Dans les cas où le temps de rendu d'un jeu est très bas, les charges de travail du processeur et du GPU peuvent s'intégrer dans un seul intervalle d'échange. Une approche sans pipeline permettrait alors d'obtenir une latence réduite de l'écran d'entrée.

Mode sans pipeline

Figure 7 : Mode sans pipeline

Mode auto

La plupart des jeux ne sont pas conçus pour choisir leur intervalle d'échange, c'est-à-dire la durée pour laquelle chaque image est présentée (par exemple, 33,3 ms pour 30 Hz). Un jeu peut effectuer un rendu à 60 FPS sur certains appareils et à une valeur inférieure sur d'autres. Le mode automatique mesure les fréquences du processeur et du GPU pour effectuer les opérations suivantes :

  • Sélectionner automatiquement les intervalles d'échange : les jeux dont l'affichage atteint 30 Hz dans certaines scènes et 60 Hz dans d'autres peuvent autoriser la bibliothèque à ajuster cet intervalle de manière dynamique.
  • Désactiver le pipeline pour les images ultrarapides : cette option offre une latence d'écran d'entrée optimale dans tous les cas.

Fréquences d'actualisation multiples

Les appareils qui acceptent plusieurs fréquences d'actualisation offrent une plus grande flexibilité pour choisir un intervalle d'échange au résultat fluide :

  • Sur les appareils 60 Hz : 60 FPS/30 FPS/20 FPS
  • Sur les appareils 60 Hz + 90 Hz : 90 FPS/60 FPS/ 45 FPS/30 FPS
  • Sur les appareils 60 Hz + 90 Hz + 120 Hz : 120 FPS/90 FPS/60 FPS/45 FPS/40 FPS/30 FPS

La bibliothèque choisit la fréquence d'actualisation qui correspond le mieux à la durée de rendu réelle des images d'un jeu, ce qui améliore l'expérience visuelle.

Pour en savoir plus sur les frame pacing à fréquences d'actualisation multiple, consultez l'article de blog consacré au rendu à fréquence d'actualisation élevée sur Android.

Statistiques de cadence

La bibliothèque Frame Pacing offre les statistiques suivantes pour le débogage et le profilage :

  • Un histogramme du nombre d'actualisations de l'écran en attente dans la file d'attente du compositeur une fois le rendu terminé.
  • Un histogramme du nombre d'actualisations d'écran transmises entre l'heure de la présentation demandée et l'heure réelle actuelle.
  • Un histogramme du nombre d'actualisations d'écran transmises entre deux images consécutives.
  • Un histogramme du nombre d'actualisations de l'écran transmises entre le début du travail du processeur pour cette image et l'instant effectif de présentation.

Étape suivante

Consultez l'un des guides suivants pour intégrer la bibliothèque Android Frame Pacing à votre jeu :