Le framework 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 vers l'espace utilisateur et entre les processus de l'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 encore été dessinée en mémoire, le pointeur de tampon est transmis au compositeur de fenêtres avec une clôture qui indique quand le travail du GPU sera terminé. Le compositeur de fenêtres 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 également aux intégrateurs 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 du noyau.
La synchronisation explicite présente les avantages suivants :
- Moins de variations de comportement entre les appareils
- Meilleure compatibilité avec le débogage
- Métriques de test améliorées
Le framework de synchronisation comporte trois types d'objets :
sync_timeline
sync_pt
sync_fence
sync_timeline
sync_timeline
est une chronologie qui augmente de façon monotone et que les fournisseurs doivent implémenter pour chaque instance de pilote, telle qu'un contexte GL, un contrôleur d'affichage ou un blitter 2D. sync_timeline
comptabilise les tâches envoyées 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 lorsque vous implémentez sync_timeline
:
- Donnez des noms utiles à tous les pilotes, chronologies et clôtures pour simplifier le débogage.
- Implémentez les opérateurs
timeline_value_str
etpt_value_str
dans les timelines pour rendre la sortie de débogage plus lisible. - Implémentez le remplissage
driver_data
pour donner accès aux bibliothèques de l'espace utilisateur, telles que la bibliothèque GL, aux données privées de la timeline, si vous le souhaitez.data_driver
permet aux fournisseurs de transmettre des informations sur lessync_fence
etsync_pts
immuables pour créer des lignes de commande en fonction de ces informations. - Ne pas autoriser l'espace utilisateur à créer ou à 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.
- 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 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 à l'état signalé ou d'erreur. Par exemple, lorsqu'un consommateur d'images n'a plus besoin d'un tampon, un sync_pt
est signalé pour qu'un producteur d'images sache qu'il peut à nouveau écrire 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 barrière est signalée, toutes les commandes émises avant la barrière sont garanties d'être terminées, car le pilote du noyau ou le bloc matériel exécutent les commandes dans l'ordre.
Le framework 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 du noyau à l'espace utilisateur. Par exemple, une barrière 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 terminé leur consommation.
Les clôtures, comme les valeurs sync_pt
, sont actives au départ 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
est en état d'erreur, l'ensemble du sync_fence
est en état d'erreur.
L'appartenance à un sync_fence
est immuable une fois la clôture créée. Pour obtenir plusieurs points 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'a pas été, 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 :
- Sous-système de l'espace noyau qui implémente le framework de synchronisation pour un pilote matériel spécifique. Les pilotes qui doivent être compatibles avec les clôtures sont généralement ceux qui accèdent au compositeur matériel ou communiquent avec lui.
Voici les principaux fichiers :
- Implémentation principale :
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 noyau dans
platform/system/core/libsync
- Implémentation principale :
- Le fournisseur doit fournir les barrières de synchronisation appropriées en tant que paramètres aux fonctions
validateDisplay()
etpresentDisplay()
dans le HAL. - Deux extensions GL liées aux clôtures (
EGL_ANDROID_native_fence_sync
etEGL_ANDROID_wait_sync
) et la prise en charge des clôtures dans le pilote graphique.
Étude de cas : implémenter un pilote d'affichage
Pour utiliser l'API compatible avec la fonction de synchronisation, développez un pilote d'affichage doté d'une fonction de tampon d'affichage. Avant l'existence du framework de synchronisation, cette fonction recevait des objets dma-buf
, plaçait ces tampons sur l'écran et bloquait pendant 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. Lorsqu'un tampon est placé sur l'écran, il est associé à une barrière qui indique quand le tampon sera prêt. Vous pouvez mettre en file d'attente et lancer le travail une fois la barrière franchie.
La mise en file d'attente et le lancement du travail après l'effacement de la barrière ne bloquent rien. Vous renvoyez immédiatement votre propre clôture, ce qui garantit que le tampon sera désactivé sur l'écran. Lorsque vous mettez en file d'attente des tampons, le noyau liste 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 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 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 pilote du fournisseur ou la HAL utilisant l'API doivent fermer le descripteur de fichier. - Si le pilote du fournisseur ou la HAL transmet un descripteur de fichier contenant un
sync_pt
à une fonction d'API, le pilote du fournisseur ou la 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 doit dupliquer le descripteur.
Un objet de clôture 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. Le framework de synchronisation utilise donc le nom de la fenêtre et l'index du tampon mis en file d'attente pour nommer la clôture, 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 dans les rapports de bug.
Intégration d'ANativeWindow
ANativeWindow tient compte des clôtures. dequeueBuffer
, queueBuffer
et cancelBuffer
ont des paramètres de clôture.
Intégration d'OpenGL ES
L'intégration de la synchronisation OpenGL ES repose sur deux extensions EGL :
EGL_ANDROID_native_fence_sync
permet d'encapsuler ou de créer des descripteurs de fichier de clôture Android natifs dans des objetsEGLSyncKHR
.EGL_ANDROID_wait_sync
permet les blocages côté GPU plutôt que côté CPU, ce qui fait que le GPU attendEGLSyncKHR
. L'extensionEGL_ANDROID_wait_sync
est identique à l'extensionEGL_KHR_wait_sync
.
Pour utiliser ces extensions de manière indépendante, 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
distinct pour les clôtures natives. 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
, ce qui évite 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 directement interrogé à 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 clôture valide encapsule un descripteur de fichier de clôture 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 de 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 en double). Enfin, la destruction de l'objet EGLSyncKHR
ferme l'attribut de clôture interne.
Intégration de Hardware Composer
Le compositeur 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
etsetClientTarget
. Ils représentent une écriture en attente dans le tampon et doivent signaler avant que SurfaceFlinger ou HWC ne tentent de lire à partir du tampon associé pour effectuer la composition. - Les barrières de libération sont récupérées après l'appel à
presentDisplay
à l'aide de l'appelgetReleaseFences
. Elles représentent une lecture en attente à partir du tampon précédent sur la même couche. Une barrière de libération indique quand 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 renvoyées à l'application avec les tampons précédents qui seront remplacés lors de la composition actuelle. L'application doit attendre les signaux de barrière de publication avant d'écrire de nouveaux contenus dans le tampon qui lui a été renvoyé. - Les barrières de présentation sont renvoyées, une par frame, dans l'appel à
presentDisplay
. Les clôtures de présentation représentent le moment où la composition de ce frame est terminée ou, à l'inverse, le moment où le résultat de la composition du frame précédent n'est plus nécessaire. Pour les écrans physiques,presentDisplay
renvoie les clôtures actuelles lorsque le frame actuel s'affiche à l'écran. Une fois les clôtures de présentation renvoyées, vous pouvez à nouveau écrire dans le tampon cible SurfaceFlinger, le cas échéant. Pour les affichages virtuels, les clôtures de présentation sont renvoyées lorsqu'il est possible de lire à partir du tampon de sortie.