Framework de synchronisation

Le framework de synchronisation décrit explicitement les dépendances différentes opérations asynchrones dans le système graphique Android. Le cadre fournit une API qui permet aux composants d'indiquer à quel moment les tampons sont libérés. Le cadre permet de transmettre les primitives de synchronisation entre les pilotes du noyau à l'espace utilisateur et entre les processus d'espace utilisateur eux-mêmes.

Par exemple, une application peut mettre en file d'attente des tâches à effectuer dans le GPU. Le GPU commence à dessiner cette image. Bien que l'image n'ait pas été dessinée en mémoire, le pointeur de tampon est transmis à la fenêtre compositeur avec une clôture qui indique à quel moment le travail GPU terminer. Le compositeur de fenêtre commence le traitement à l'avance et transmet le travail au contrôleur d'affichage. De même, le travail du processeur est effectué à l'avance. Une fois le GPU terminé, le contrôleur d'affichage affiche immédiatement l'image.

Le framework de synchronisation permet aussi aux responsables de l'implémentation d'exploiter de synchronisation dans leurs propres composants matériels. Enfin, la fonction offre une visibilité sur le pipeline graphique pour vous aider le débogage.

Synchronisation explicite

La synchronisation explicite permet aux producteurs et aux consommateurs de tampons graphiques de signaler quand ils ont fini d'utiliser un tampon. La synchronisation explicite est implémentée dans l'espace kernel.

Les avantages d'une synchronisation explicite sont les suivants:

  • Moins de variations de comportement entre les appareils
  • Meilleure compatibilité avec le débogage
  • Amélioration des métriques de test

Le framework de synchronisation comporte trois types d'objets :

  • sync_timeline
  • sync_pt
  • sync_fence

synchronisation_chronologie

sync_timeline est une chronologie à augmentation monotone qui les fournisseurs doivent implémenter pour chaque instance de pilote, comme un contexte GL, un contrôleur d'affichage ou un blitter 2D. sync_timeline décomptes des jobs envoyés au noyau pour un matériel spécifique. sync_timeline fournit des garanties sur l'ordre des opérations. et permet des implémentations spécifiques au matériel.

Suivez ces consignes lors de l'implémentation de sync_timeline:

  • Attribuez des noms utiles à tous les pilotes, délais et barrières pour simplifier le débogage.
  • Implémentez les opérateurs timeline_value_str et pt_value_str dans les chronologies pour rendre la sortie de débogage plus lisible.
  • Implémentez le remplissage driver_data pour accorder aux bibliothèques de l'espace utilisateur, telles que la bibliothèque GL, l'accès aux données de chronologie privées, si vous le souhaitez. data_driver permet aux fournisseurs de transmettre des informations sur l'objet immuable sync_fence et sync_pts pour créer des lignes de commande sur ces données.
  • N'autorisez pas l'espace utilisateur à créer ou à signaler explicitement une clôture. Contenu explicite la création de signaux ou de clôtures entraîne une attaque par déni de service qui interrompt le fonctionnement du pipeline.
  • N'accédez pas explicitement aux éléments sync_timeline, sync_pt ou sync_fence. L'API fournit toutes les fonctions requises.

sync_pt

sync_pt est une valeur ou un point unique sur un sync_timeline. Un point peut avoir trois états : actif, signalé et erreur. Les points commencent à l'état actif et passent aux états signalés ou d'erreur. Par exemple, lorsqu'un consommateur d'images n'a plus besoin d'un tampon, un sync_pt est signalé afin qu'un producteur d'images sache qu'il est possible d'écrire à nouveau dans le tampon.

sync_fence

sync_fence est un ensemble de valeurs sync_pt qui ont souvent des parents sync_timeline différents (par exemple, pour le contrôleur d'affichage et le GPU). sync_fence, sync_pt et sync_timeline sont les principales primitives utilisées par les pilotes et l'espace utilisateur pour communiquer leurs dépendances. Lorsqu'une clôture est signalée, toutes les commandes émises avant la clôture sont garanties comme étant terminées, car le pilote du noyau ou le bloc matériel exécute les commandes dans l'ordre.

Le framework de synchronisation permet à plusieurs consommateurs ou producteurs de signaler terminé d'utiliser un tampon, en communiquant les informations de dépendance avec une fonction . Les clôtures reposent sur un descripteur de fichier et sont transmises en espace utilisateur. Par exemple, une clôture peut contenir deux Valeurs sync_pt qui signifient que deux utilisateurs d'images distincts ont terminé la lecture d'une mémoire tampon. Lorsque la clôture est signalée, les producteurs d'images savent que les consommateurs ont fini de consommer.

Les clôtures, comme les valeurs sync_pt, commencent à être actives et changent d'état en fonction de l'état de leurs points. Si toutes les valeurs sync_pt sont signalées, sync_fence est également signalé. Si une sync_pt tombe en état d'erreur, l'ensemble de sync_fence présente un état d'erreur.

L'appartenance à un sync_fence est immuable une fois la clôture créée. Pour placer plus d'un point dans une clôture, une fusion est par laquelle les points de deux clôtures distinctes sont ajoutés à une troisième clôture. Si l'un de ces points a été signalé dans la clôture d'origine et que l'autre ne l'a pas été, la troisième clôture ne sera pas non plus dans un état signalé.

Pour implémenter une synchronisation explicite, fournissez les éléments suivants:

  • Sous-système d'espace noyau qui implémente le framework de synchronisation pour un pilote matériel particulier. Les conducteurs qui doivent tenir compte de la clôture sont généralement tout ce qui accède au Composer du matériel ou qui communique avec lui. Voici les principaux fichiers :
    • Implémentation de base :
      • kernel/common/include/linux/sync.h
      • kernel/common/drivers/base/sync.c
    • Documentation sur kernel/common/Documentation/sync.txt
    • Bibliothèque permettant de communiquer avec l'espace du kernel dans platform/system/core/libsync
  • Le fournisseur doit fournir les barrières de synchronisation appropriées en tant que paramètres aux fonctions validateDisplay() et presentDisplay() dans le HAL.
  • Deux extensions GL liées aux barrières (EGL_ANDROID_native_fence_sync et EGL_ANDROID_wait_sync) et prise en charge des barrières dans le pilote graphique.

Étude de cas : Implémenter un pilote d'affichage

Pour utiliser l'API prenant en charge la fonction de synchronisation, développer un pilote d'affichage avec une fonction de tampon d'affichage. Avant le de synchronisation existant, cette fonction recevrait dma-buf des objets, placez ces tampons sur l'écran et bloquez-les tant que le tampon était visible. Exemple :

/*
 * assumes buffer is ready to be displayed.  returns when buffer is no longer on
 * screen.
 */
void display_buffer(struct dma_buf *buffer);

Avec le framework de synchronisation, la fonction display_buffer est plus complexe. Lors de l'affichage d'un tampon, celui-ci est associé avec une clôture qui indique à quel moment le tampon sera prêt. Vous pouvez ajouter et commencer le travail une fois la clôture levée.

La mise en file d'attente et le lancement du travail après la clôture de la clôture ne bloquent rien. Vous renvoyez immédiatement votre propre clôture, ce qui garantit que le tampon s'affiche à l'écran. Lorsque vous mettez des tampons en file d'attente, le noyau répertorie avec le framework de synchronisation:

/*
 * displays buffer when fence is signaled.  returns immediately with a fence
 * that signals when buffer is no longer displayed.
 */
struct sync_fence* display_buffer(struct dma_buf *buffer, struct sync_fence
*fence);

Intégration de la synchronisation

Cette section explique comment intégrer le framework de synchronisation de l'espace noyau aux parties de l'espace utilisateur du framework Android et aux pilotes qui doivent communiquer entre eux. Les objets de l'espace du noyau sont représentés par des descripteurs de fichier dans de l'espace utilisateur.

Conventions d'intégration

Respectez les conventions de l'interface HAL Android :

  • Si l'API fournit un descripteur de fichier qui fait référence à un sync_pt, le pilote du fournisseur ou le HAL utilisant l'API doit fermer le descripteur de fichier.
  • Si le pilote du fournisseur ou le HAL transmet un descripteur de fichier contenant un sync_pt à une fonction d'API, le pilote du fournisseur ou le HAL ne doivent pas fermer le descripteur de fichier.
  • Pour continuer à utiliser le descripteur de fichier de clôture, le pilote du fournisseur ou le HAL doivent dupliquer le descripteur.

Un objet fence est renommé chaque fois qu'il passe par BufferQueue. La prise en charge des barrières de kernel permet aux barrières d'avoir des chaînes pour noms. Le framework de synchronisation utilise donc le nom de la fenêtre et l'indice de tampon mis en file d'attente pour nommer la barrière, par exemple SurfaceView:0. Cela est utile pour le débogage afin d'identifier la source d'un blocage, car les noms apparaissent dans la sortie de /d/sync et les rapports de bugs.

Intégration d'ANativeWindow

ANativeWindow est compatible avec les cloisonnements. dequeueBuffer, queueBuffer et cancelBuffer comportent des paramètres de cloisonnement.

Intégration d'OpenGL ES

L'intégration de la synchronisation OpenGL ES repose sur deux extensions EGL:

  • EGL_ANDROID_native_fence_sync permet de encapsuler ou créer des descripteurs de fichier de cloisonnement Android natifs dans Objets EGLSyncKHR.
  • EGL_ANDROID_wait_sync permet les arrêts côté GPU plutôt que côté CPU, ce qui oblige le GPU à attendre EGLSyncKHR. La L'extension EGL_ANDROID_wait_sync est identique à l'extension Extension EGL_KHR_wait_sync.

Pour utiliser ces extensions indépendamment, implémentez l'extension EGL_ANDROID_native_fence_sync avec la prise en charge du noyau associée. Ensuite, activez l'extension EGL_ANDROID_wait_sync dans votre pilote. L'extension EGL_ANDROID_native_fence_sync se compose d'un type d'objet EGLSyncKHR de clôture native distinct. Par conséquent, les extensions qui s'appliquent aux EGLSyncKHR existants les types d'objets ne s'appliquent pas nécessairement à EGL_ANDROID_native_fence en évitant les interactions indésirables.

L'extension EGL_ANDROID_native_fence_sync utilise un attribut de descripteur de fichier de clôture natif correspondant qui ne peut être défini qu'au moment de la création et ne peut pas être interrogé directement à partir d'un objet de synchronisation existant. Cet attribut peut être défini sur l'un des deux modes suivants :

  • Un descripteur de fichier de cloisonnement valide encapsule un élément natif existant. Descripteur de fichier de clôture Android dans un objet EGLSyncKHR.
  • -1 crée un descripteur de fichier de cloisonnement Android natif à partir d'un EGLSyncKHR.

Utilisez l'appel de fonction DupNativeFenceFD() pour extraire l'objet EGLSyncKHR du descripteur de fichier de clôture Android natif. Le résultat est le même que celui obtenu en interrogeant l'attribut défini, mais il respecte la convention selon laquelle le destinataire ferme la clôture (d'où l'opération en double). Enfin, la destruction de l'objet EGLSyncKHR ferme l'attribut de clôture interne.

Intégration du Compositeur de matériel

Le Compositeur de matériel gère trois types de barrières de synchronisation :

  • Les barrières d'acquisition sont transmises avec les tampons d'entrée aux appels setLayerBuffer et setClientTarget. Ils représentent une écriture en attente dans le tampon et doivent signaler avant l'événement SurfaceFlinger ou le HWC tente de lire le tampon associé effectuer la composition.
  • Les barrières de protection sont récupérées après l'appel de la fonction presentDisplay via l'appel getReleaseFences. Ils représentent une lecture en attente à partir du tampon précédent sur la même couche. A libère des signaux de cloisonnement lorsque le HWC n'utilise plus le tampon précédent car le tampon actuel a remplacé le tampon précédent sur l'écran. Les barrières de libération sont transmises à l'application avec les tampons précédents qui seront remplacés lors de la composition actuelle. L'application doit attendre qu'un signal de barrière de libération soit envoyé avant d'écrire de nouveaux contenus dans la mémoire tampon qui lui a été renvoyée.
  • Les barrières présentes sont renvoyées, une par frame, dans le cadre de l'appel de presentDisplay. Les clôtures actuelles représentent le moment où le la composition de cette image est terminée, ou inversement, lorsque la le résultat de composition du frame précédent n'est plus nécessaire. Pour les écrans physiques, presentDisplay renvoie les barrières présentes lorsque le frame actuel s'affiche à l'écran. Une fois les clôtures actuelles renvoyées, vous pouvez écrire à nouveau dans le tampon cible SurfaceFlinger en toute sécurité, si applicables. Pour les écrans virtuels, les clôtures actuelles sont renvoyées lorsque en toute sécurité dans le tampon de sortie.