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 le 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 passé 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 que le GPU a 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 faciliter 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 noyau.
Les avantages de la synchronisation explicite incluent :
- Moins de variation de comportement entre les appareils
- Meilleure prise en charge du débogage
- Mesures de test améliorées
L'infrastructure de synchronisation comporte trois types d'objet :
-
sync_timeline
-
sync_pt
-
sync_fence
sync_timeline
sync_timeline
est une chronologie croissante de manière monotone que 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
compte les travaux soumis au noyau pour un matériel particulier. sync_timeline
fournit des garanties sur l'ordre des opérations et permet des implémentations spécifiques au matériel.
Suivez ces directives lors de la mise en œuvre sync_timeline
:
- Fournissez des noms utiles pour tous les pilotes, chronologies et clôtures afin de simplifier le débogage.
- Implémentez les opérateurs
timeline_value_str
etpt_value_str
dans les chronologies pour rendre la sortie de débogage plus lisible. - Implémentez le remplissage
driver_data
pour donner 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 les immuablessync_fence
etsync_pts
pour créer des lignes de commande basées sur eux. - N'autorisez pas l'espace utilisateur à créer ou à signaler explicitement une clôture. La création explicite de signaux/clôtures entraîne une attaque par déni de service qui interrompt la fonctionnalité du pipeline.
- N'accédez pas explicitement aux éléments
sync_timeline
,sync_pt
ousync_fence
. 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 en erreur. Les points commencent à l'état actif et passent à l'état signalé ou d'erreur. Par exemple, lorsqu'un consommateur d'image n'a plus besoin d'un tampon, un sync_pt
est signalé afin qu'un producteur d'image sache qu'il est correct d'écrire à nouveau dans le tampon.
sync_fence
sync_fence
est une collection de valeurs sync_pt
qui ont souvent des parents sync_timeline
différents (comme pour le contrôleur d'affichage et le GPU). sync_fence
, sync_pt
et sync_timeline
sont les principales primitives que les pilotes et l'espace utilisateur utilisent 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 lorsqu'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 valeurs sync_pt
qui indiquent quand deux consommateurs d'images distincts ont fini de lire 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 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 signalé. Si un 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 création de la barrière. 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 que l'autre ne l'était pas, la troisième clôture ne sera pas non plus dans un état signalé.
Pour implémenter la synchronisation explicite, fournissez les éléments suivants :
- Un sous-système de l'espace noyau qui implémente la structure de synchronisation pour un pilote matériel particulier. Les pilotes qui doivent être conscients de la barrière sont généralement tout ce qui accède ou communique avec le Hardware Composer. Les fichiers clés incluent :
- 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 pour communiquer avec l'espace noyau dans
platform/system/core/libsync
- Implémentation de base :
- Le fournisseur doit fournir les clôtures de synchronisation appropriées en tant que paramètres des fonctions
validateDisplay()
etpresentDisplay()
dans HAL. - Deux extensions GL liées à la clôture (
EGL_ANDROID_native_fence_sync
etEGL_ANDROID_wait_sync
) et prise en charge 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 que le cadre de synchronisation n'existe, cette fonction recevait des objets dma-buf
, affichait ces tampons et les bloquait tant 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 synchronisation, la fonction display_buffer
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 lancer les travaux une fois la clôture dégagée.
La mise en file d'attente et le lancement des travaux après le dégagement de la clôture ne bloquent rien. Vous retournez immédiatement votre propre clôture, ce qui garantit quand le tampon sera hors de l'affichage. 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 la synchronisation
Cette section explique comment intégrer l'infrastructure de synchronisation de l'espace noyau avec les parties de l'espace utilisateur de l'infrastructure Android et les pilotes qui doivent communiquer entre eux. Les objets de l'espace noyau sont représentés comme des 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 pilote du fournisseur ou la HAL utilisant l'API doit fermer le descripteur de fichier. - Si le pilote du fournisseur ou la HAL transmet un descripteur de fichier qui contient un
sync_pt
à une fonction API, le pilote du fournisseur ou la HAL ne doit pas fermer le descripteur de fichier. - Pour continuer à utiliser le descripteur de fichier fence, le pilote du fournisseur ou la HAL doit dupliquer le descripteur.
Un objet fence est renommé chaque fois qu'il passe par BufferQueue. La prise en charge des clôtures du noyau permet aux clôtures d'avoir des chaînes pour les noms, de sorte que l'infrastructure de synchronisation utilise le nom de la fenêtre et l'index de tampon mis en file d'attente pour nommer la clôture, comme SurfaceView:0
. Ceci est utile dans le débogage pour identifier la source d'un blocage car les noms apparaissent dans la sortie de /d/sync
et des rapports de bogue.
Intégration ANativeWindow
ANativeWindow est conscient de la clôture. dequeueBuffer
, queueBuffer
et cancelBuffer
ont des paramètres de clôture.
Intégration OpenGL ES
L'intégration de synchronisation OpenGL ES repose sur deux extensions EGL :
-
EGL_ANDROID_native_fence_sync
fournit un moyen d'encapsuler ou de créer des descripteurs de fichiers de clôture Android natifs dans des objetsEGLSyncKHR
. -
EGL_ANDROID_wait_sync
autorise les décrochages côté GPU plutôt que côté CPU, obligeant le GPU à attendreEGLSyncKHR
. L'extensionEGL_ANDROID_wait_sync
est identique à l'extensionEGL_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 de clôture native distinct EGLSyncKHR
. Par conséquent, les extensions qui s'appliquent aux types d'objets EGLSyncKHR
existants ne s'appliquent pas nécessairement aux objets EGL_ANDROID_native_fence
, évitant ainsi les interactions indésirables.
L'extension EGL_ANDROID_native_fence_sync
utilise un attribut de descripteur de fichier 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 :
- Un descripteur de fichier fence valide encapsule un descripteur de fichier fence Android natif existant dans un objet
EGLSyncKHR
. - -1 crée un descripteur de fichier de clôture Android natif à partir d'un objet
EGLSyncKHR
.
Utilisez l'appel de fonction DupNativeFenceFD()
pour extraire l'objet EGLSyncKHR
du descripteur de fichier clôture Android natif. Cela a le même résultat que l'interrogation de l'attribut set, mais respecte la convention selon laquelle le destinataire ferme la clôture (d'où l'opération de duplication). Enfin, la destruction de l'objet EGLSyncKHR
ferme l'attribut de clôture interne.
Intégration de Hardware Composer
Hardware Composer gère trois types de barrières de synchronisation :
- Les clôtures d'acquisition sont transmises avec les tampons d'entrée aux appels
setLayerBuffer
etsetClientTarget
. Ceux-ci représentent une écriture en attente dans la mémoire tampon et doivent signaler avant que le SurfaceFlinger ou le HWC ne tente de lire à partir de la mémoire tampon associée pour effectuer la composition. - Les clôtures de libération sont récupérées après l'appel à
presentDisplay
à l'aide de l'appelgetReleaseFences
. 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 parce que le tampon actuel a remplacé le tampon précédent sur l'affichage. Les clôtures de libération sont renvoyées à l'application avec les tampons précédents qui seront remplacés lors de la composition en cours. L'application doit attendre qu'une barrière de libération signale avant d'écrire de nouveaux contenus dans la mémoire tampon qui leur a été renvoyée. - Les clôtures actuelles sont renvoyées, une par image, dans le cadre de l'appel à
presentDisplay
. Les clôtures présentes représentent le moment où la composition de ce cadre est terminée, ou alternativement, le moment où le résultat de composition du cadre précédent n'est plus nécessaire. Pour les affichages physiques,presentDisplay
renvoie les clôtures présentes lorsque l'image actuelle apparaît à l'écran. Une fois les clôtures actuelles renvoyées, vous pouvez écrire à nouveau dans le tampon cible SurfaceFlinger en toute sécurité, le cas échéant. Pour les affichages virtuels, les clôtures présentes sont renvoyées lorsqu'il est sûr de lire à partir du tampon de sortie.