Cadre de synchronisation

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

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

L'infrastructure de synchronisation permet également aux implémenteurs d'exploiter les ressources de synchronisation dans leurs propres composants matériels. Enfin, le framework offre une visibilité sur le pipeline graphique pour aider au 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 noyau.

Les avantages de la synchronisation explicite incluent :

  • Moins de variation de comportement entre les appareils
  • Meilleure prise en charge du débogage
  • Métriques de test améliorées

Le framework de synchronisation a trois types d'objets :

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

sync_timeline est une ligne de temps croissant de façon monotone que les fournisseurs doivent mettre en oeuvre pour chaque instance de pilote, comme un cadre de GL, contrôleur d'affichage, ou 2D blitter. sync_timeline compte les emplois soumis au noyau pour un morceau particulier de matériel. sync_timeline fournit des garanties sur l'ordre des opérations et permet des implémentations spécifiques du matériel.

Suivez ces lignes directrices lors de la mise en œuvre sync_timeline :

  • Fournissez des noms utiles pour tous les pilotes, les chronologies et les clôtures afin de simplifier le débogage.
  • Mettre en œuvre le timeline_value_str et pt_value_str opérateurs dans les délais pour rendre la sortie de débogage plus lisible.
  • Mettre en oeuvre le remplissage driver_data pour donner des bibliothèques userspace, comme la bibliothèque GL, l' accès aux données de timeline privées, si on le souhaite. data_driver permet vendeurs transmettre des informations sur l'immuable sync_fence et sync_pts de construire des lignes de commande basées sur eux.
  • Ne permettez pas à l'espace utilisateur de créer ou de signaler explicitement une clôture. La création explicite de signaux/barrières entraîne une attaque par déni de service qui interrompt la fonctionnalité du pipeline.
  • Ne pas accès sync_timeline , sync_pt ou sync_fence éléments explicitement. L'API fournit toutes les fonctions requises.

sync_pt

sync_pt est une valeur unique ou un point sur une sync_timeline . Un point a 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'image non plus besoin d' un tampon, un sync_pt est signalé si un producteur d'image sait qu'il est correct d'écrire dans la mémoire tampon à nouveau.

sync_clôture

sync_fence est une collection de sync_pt valeurs qui ont souvent des sync_timeline parents ( par exemple pour le contrôleur d'affichage et GPU). sync_fence , sync_pt et sync_timeline sont les principales primitives que les conducteurs et l' utilisation de 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 d'être terminées car le pilote du noyau ou le bloc matériel exécute les commandes dans l'ordre.

Le cadre de synchronisation permet à plusieurs consommateurs ou producteurs de signaler quand ils ont fini d'utiliser un tampon, en communiquant les informations de dépendance avec un paramètre de fonction. Les clôtures sont soutenues par un descripteur de fichier et sont transmises de l'espace noyau à l'espace utilisateur. Par exemple, une clôture peut contenir deux sync_pt valeurs qui signifient lorsque deux consommateurs d'images séparés sont fait la lecture d' un tampon. Lorsque la clôture est signalée, les producteurs d'images savent que les deux consommateurs ont fini de consommer.

Les clôtures, comme sync_pt valeurs, commencent à l' état actif et le changement en fonction de l'état de leurs points. Si toutes les sync_pt valeurs deviennent signalées, le sync_fence devient signe. Si l' on sync_pt tombe dans un état d'erreur, l'ensemble sync_fence a un état d'erreur.

L' appartenance à un sync_fence est immuable après la clôture est créée. Pour obtenir plus d'un point dans une clôture, une fusion est effectuée où 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 l'autre ne l'était pas, la troisième clôture ne sera pas non plus dans un état signalé.

Pour mettre en œuvre une synchronisation explicite, fournissez les éléments suivants :

  • Un sous-système de l'espace noyau qui implémente le cadre de synchronisation pour un pilote matériel particulier. Les pilotes qui doivent être sensibles aux clôtures sont généralement tout ce qui accède ou communique avec le compositeur matériel. Les fichiers clés comprennent :
    • Implémentation de base :
      • kernel/common/include/linux/sync.h
      • kernel/common/drivers/base/sync.c
    • Documentation à kernel/common/Documentation/sync.txt
    • Bibliothèque pour communiquer avec l'espace du noyau dans la platform/system/core/libsync - platform/system/core/libsync
  • Le fournisseur doit fournir les clôtures de synchronisation appropriées en tant que paramètres à la validateDisplay() et presentDisplay() des fonctions dans le HAL.
  • Deux extensions de GL liées clôture ( EGL_ANDROID_native_fence_sync et EGL_ANDROID_wait_sync ) et le soutien de la clôture dans le pilote graphique.

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

Pour utiliser l'API prenant en charge la fonction de synchronisation, développez un pilote d'affichage doté d'une fonction de tampon d'affichage. Avant le cadre de synchronisation existait, cette fonction ne recevrait dma-buf objets, mettre ces tampons sur l'écran, et le bloc tandis que le tampon était visible. Par 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 cadre de la synchronisation, la display_buffer fonction est plus complexe. Lors de l'affichage d'un tampon, le tampon est associé à une clôture qui indique quand le tampon sera prêt. Vous pouvez faire la queue et commencer le travail une fois la clôture dégagée.

Faire la queue et commencer les travaux une fois la clôture dégagée ne bloque rien. Vous retournez immédiatement votre propre clôture, ce qui garantit que le tampon sera hors de l'écran. Lorsque vous mettez des tampons en file d'attente, le noyau répertorie les dépendances 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 synchronisation

Cette section explique comment intégrer le framework de synchronisation de l'espace noyau avec les parties de l'espace utilisateur du framework Android et les pilotes qui doivent communiquer entre eux. Les objets de l'espace noyau sont représentés sous forme de descripteurs de fichiers dans l'espace utilisateur.

Conventions d'intégration

Suivez les conventions de l'interface Android HAL :

  • Si l'API fournit un descripteur de fichier qui fait référence à un sync_pt , le conducteur du vendeur ou la couche d' abstraction en utilisant l'API doit fermer le descripteur de fichier.
  • Si le pilote du fournisseur ou HAL passe un descripteur de fichier qui contient un sync_pt à une fonction API, le pilote du fournisseur ou HAL doit pas fermer le descripteur de fichier.
  • Pour continuer à utiliser le descripteur de fichier fence, le pilote du fournisseur ou le HAL doit dupliquer le descripteur.

Un objet fence est renommé chaque fois qu'il passe par BufferQueue. Support de clôture du noyau permet des clôtures d'avoir des chaînes de noms, de sorte que le cadre de synchronisation utilise le nom de la fenêtre et le tampon index qui est en cours de mise en attente pour nommer la clôture, comme SurfaceView:0 . Ceci est utile pour le débogage pour identifier la source d'une impasse que les noms apparaissent dans la sortie de /d/sync et rapports bogues.

Intégration ANativeWindow

ANativeWindow est sensible aux clôtures. dequeueBuffer , queueBuffer et cancelBuffer ont des paramètres de clôture.

Intégration OpenGL ES

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

  • EGL_ANDROID_native_fence_sync fournit un moyen d'envelopper ou de créer des descripteurs de fichiers clôture Android natifs dans EGLSyncKHR objets.
  • EGL_ANDROID_wait_sync permet stalles côté GPU plutôt que du côté du processeur, ce qui rend l'attente GPU pour EGLSyncKHR . La EGL_ANDROID_wait_sync extension est la même que celle EGL_KHR_wait_sync l' extension.

Pour utiliser ces extensions indépendamment, mettre en œuvre la EGL_ANDROID_native_fence_sync l' extension ainsi que le support du noyau associé. Ensuite, activez la EGL_ANDROID_wait_sync extension dans votre pilote. La EGL_ANDROID_native_fence_sync l' extension se compose d'une clôture native distincte EGLSyncKHR type d'objet. Par conséquent, les extensions existantes applicables aux EGLSyncKHR types d'objets ne sont pas forcément à EGL_ANDROID_native_fence objets, en évitant les interactions indésirables.

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

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

Utilisez le DupNativeFenceFD() appel de fonction pour extraire le EGLSyncKHR objet du descripteur de fichier natif Android clôture. Cela a le même résultat que l'interrogation de l'attribut set, mais adhère à la convention selon laquelle le destinataire ferme la clôture (d'où l'opération de duplication). Enfin, la destruction de l' EGLSyncKHR objet ferme l'attribut de clôture interne.

Intégration de Hardware Composer

Le Compositeur matériel gère trois types de clôtures de synchronisation :

  • Clôtures Acquire sont transmises avec des tampons d'entrée aux setLayerBuffer et setClientTarget appels. Ceux-ci représentent une écriture en attente dans le tampon et doivent signaler avant que SurfaceFlinger ou le HWC ne tente de lire à partir du tampon associé pour effectuer la composition.
  • Clôtures de sortie sont récupérés après l'appel à presentDisplay à l' aide du getReleaseFences appel. Ceux-ci représentent une lecture en attente du tampon précédent sur la même couche. Une clôture de libération signale lorsque le HWC n'utilise plus le tampon précédent car le tampon actuel a remplacé le tampon précédent sur l'affichage. Les barrières de libération sont renvoyées à l'application avec les tampons précédents qui seront remplacés lors de la composition actuelle. L'application doit attendre qu'une clôture de libération soit signalée avant d'écrire de nouveaux contenus dans le tampon qui leur a été renvoyé.
  • Clôtures actuelles sont retournées, un par cadre, dans le cadre de l'appel à presentDisplay . Les clôtures actuelles représentent lorsque la composition de cette trame est terminée, ou alternativement, lorsque le résultat de la composition de la trame précédente n'est plus nécessaire. Pour les écrans physiques, presentDisplay retourne les clôtures présentes lorsque le cadre actuel apparaît à l'écran. Une fois les clôtures actuelles renvoyées, vous pouvez réécrire en toute sécurité dans le tampon cible SurfaceFlinger, le cas échéant. Pour les affichages virtuels, les clôtures présentes sont renvoyées lorsqu'il est possible de lire en toute sécurité à partir du tampon de sortie.