Android triple buffering – comportement attendu?

Je suis en train d'enquêter sur la performance de mon application car je l'ai remarqué en laissant tomber des images en défilement. J'ai couru systrace (sur un Nexus 4 en cours d'exécution 4,3) et j'ai remarqué une section intéressante dans la sortie.

Tout d'abord, tout va bien. En faisant un zoom sur la partie gauche , on peut voir que le dessin commence sur chaque vsync, finit avec le temps à épargner et attend jusqu'au prochain vsync. Puisqu'il s'agit d'un tampon triple, il devrait être généré dans un tampon qui sera affiché sur le vsync suivant après sa fin.

Sur le 4ème vsync dans la capture d'écran agrandie, l'application fait du travail et l'opération de tirage ne se termine pas à temps pour le prochain vsync. Cependant, nous ne déposons pas de cadres car les tirages précédents ont travaillé un cadre à l'avance.

Après cela, les opérations de tirage ne compensent pas le vsync manqué. Au lieu de cela, une seule opération de tirage commence par vsync, et maintenant elles ne dessinent plus un cadre.

En s'inspirant de la partie droite , l'application fait plus de travail et manque un autre vsync. Comme nous n'établions pas un cadre à l'avance, un cadre se laisse tomber ici. Après cela, il revient à dessiner un cadre à l'avance.

Ce comportement est-il attendu? Ma compréhension était que la mise en mémoire tampon triple vous a permis de récupérer si vous avez manqué un vsync, mais ce comportement semble qu'il tombe un cadre une fois tous les deux vsyncs vous manquez.


Questions de suivi

  1. Sur le côté droit de cette capture d'écran , l'application rend effectivement des tampons plus rapidement que l'affichage les consume. Pendant l'exécution de Traversals # 1 (étiqueté dans la capture d'écran), disons que le tampon A est affiché et le tampon B est rendu. # 1 finit bien avant le vsync et met le tampon B dans la file d'attente. À ce stade, l'application ne devrait-elle pas commencer immédiatement à créer un tampon C? Au lieu de cela, performTraversals # 2 ne démarre pas avant le prochain vsync, perdant le temps précieux entre les deux.

  2. Dans le même ordre d'idées, je suis un peu confus quant à la nécessité d'attendre plus loin sur le côté gauche ici . Disons que le tampon A est affiché, le tampon B est dans la file d'attente et le tampon C est en cours de rendu. Lorsque le tampon C est terminé, pourquoi n'est-il pas ajouté immédiatement à la file d'attente? Au lieu de cela, il fait un waitForever jusqu'à ce que le tampon B soit supprimé de la file d'attente, auquel cas il ajoute le tampon C, c'est pourquoi la file d'attente semble toujours rester à la taille 1 quelle que soit la rapidité avec laquelle l'application est en train de créer des tampons.

  • Déterminer et empêcher le délai de planification Android
  • Cause de la récursion performTraversals, longue période de permutation egl
  • One Solution collect form web for “Android triple buffering – comportement attendu?”

    La quantité de tampon fournie ne concerne que si vous gardez les tampons complets. Cela signifie rendre plus rapide que l'affichage les consomme.

    Les étiquettes n'apparaissent pas dans vos images, mais je suppose que la ligne pourpre au-dessus de la ligne verte vsync est l'état BufferQueue. Vous pouvez voir qu'il a généralement 0 ou 1 tampons complets à tout moment. À la gauche de l'image "zoomé à gauche", vous pouvez voir qu'il a deux buffers, mais après cela, il n'en a qu'un, et 3/4 du chemin à travers l'écran, vous voyez une très courte barre violet Indique qu'il n'a guère rendu le cadre à temps.

    Voir cette publication et cette publication pour l'arrière-plan.

    Mise à jour pour les questions ajoutées …

    Le détail dans l'autre poste a rarement rayé la surface. Nous devons aller plus loin.

    Le compte BufferQueue montré en systrace est le nombre de buffers en attente, c'est-à-dire le nombre de tampons contenant du contenu. Lorsque SurfaceFlinger gère un tampon pour l'affichage, il libère le tampon immédiatement, change son état en "libre". Ceci est particulièrement intéressant lorsque le tampon est affiché sur une superposition, car l'affichage est rendu directement à partir du tampon (par opposition à la composition dans une mémoire tampon et l'affichage).

    Permettez-moi de le dire à nouveau: le tampon à partir duquel l'affichage active la lecture des données pour l'affichage sur l'écran est marqué comme «gratuit» dans BufferQueue. Le tampon a une clôture associée qui est initialement "active". Bien qu'il soit actif, personne n'est autorisé à modifier le contenu de la mémoire tampon. Lorsque l'affichage n'a plus besoin du tampon, il signale la clôture.

    Donc, la raison pour laquelle le code sur la gauche de votre trace est en waitForever() est parce qu'il attend la signalisation de la clôture. Lorsque VSYNC frappe, l'affichage passe à un tampon différent, signale la clôture et votre application peut commencer à utiliser le tampon immédiatement. Cela élimine la latence qui serait engagée si vous deviez attendre que SurfaceFlinger se réveille, voir que le tampon n'était plus utilisé, envoyer un IPC via BufferQueue pour libérer le tampon, etc.

    Notez que les appels à waitForever() ne s'affichent que lorsque vous ne vous waitForever() pas (côté gauche et côté droit de la trace). Je ne suis pas sûr de la raison pour laquelle il se passe du tout lorsque la file d'attente n'a que 1 tampon complet – il devrait décocher le tampon le plus ancien, ce qui devrait déjà être signalé.

    En bout de ligne, vous ne verrez jamais le BufferQueue aller au-dessus de deux pour le tampon triple.

    Tous les appareils ne fonctionnent pas comme décrit ci-dessus. Nexus 7 (2012) n'utilise pas le mécanisme de "synchronisation explicite", et les périphériques pré-ICS n'ont pas BufferQueues du tout.

    En revenant à votre capture d'écran numérotée, oui, il y a beaucoup de temps entre '1' et '2' où votre application pourrait exécuter performTraversals (). Il est difficile de le dire sans savoir ce que fait votre application, mais je devine que vous avez un cycle d'animation de Choreographer qui éveille chaque VSYNC et fonctionne. Il ne fonctionne pas plus souvent que cela.

    Si vous pouvez simplifier Android Breakout, vous pouvez voir ce que cela ressemble lorsque vous rendez aussi rapidement que possible («filetage en file d'attente») et reposez sur la contre-pression BufferQueue pour réguler la vitesse du jeu.

    Il est particulièrement intéressant de comparer N4 en cours d'exécution 4.3 avec N4 fonctionnant 4.4. Sur 4.3, la trace est semblable à la vôtre, la file d'attente étant en grande partie sur 1, avec des chutes régulières à 0 et des pointes occasionnelles à 2. Sur 4.4, la file d'attente est presque toujours à 2 avec une chute occasionelle à 1. Dans les deux cas, c'est eglSwapBuffers() dans eglSwapBuffers() ; En 4.3, la trace affiche généralement waitForever() ci-dessous, tandis qu'en 4.4 il montre dequeueBuffer() . (Je ne connais pas le motif de cette situation).

    Mise à jour 2: la raison de la différence entre 4.3 et 4.4 semble être un changement de pilote Nexus 4. Le pilote 4.3 a utilisé l'ancien appel de dequeueBuffer, qui se transforme en dequeueBuffer_DEPRECATED() ( surface.cpp ligne 112 ). L'ancienne interface ne prend pas la clôture en tant que paramètre "out", donc l'appel doit appeler waitForever() lui-même. L'interface plus récente renvoie la clôture au pilote GL, ce qui fait l'attente quand il le faut (ce qui pourrait ne pas être tout de suite).

    Mise à jour 3: Une explication encore plus longue est maintenant disponible ici .

    coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.