Combien de temps faut-il pour qu'OpenGL mette réellement à jour l'écran?

9

J'ai une application de test OpenGL simple en C qui dessine différentes choses en réponse à une entrée clé. (Mesa 8.0.4, essayé avec Mesa-EGL et avec GLFW, Ubuntu 12.04LTS sur un PC avec NVIDIA GTX650). Les tirages sont assez simples / rapides (type de truc en triangle tournant). Mon code de test ne limite pas le framerate délibérément, il ressemble à ceci:

while (true)
{
    draw();
    swap_buffers();
}

J'ai chronométré cela très soigneusement, et je trouve que le temps d'un appel eglSwapBuffers()(ou de la glfwSwapBuffersmême chose) au suivant est d'environ 16,6 millisecondes. Le temps qui s'écoule entre un appel et eglSwapBuffers()juste avant le prochain appel n'est que légèrement inférieur, même si ce qui est dessiné est très simple. Le temps nécessaire à l'appel des tampons d'échange est bien inférieur à 1 ms.

Cependant, le temps écoulé entre l'application qui change ce qu'elle dessine en réponse à la pression de la touche et le changement qui apparaît réellement à l'écran est> 150 ms (valeur d'environ 8 à 9 images). Ceci est mesuré avec un enregistrement par caméra de l'écran et du clavier à 60 images par seconde.

Par conséquent, les questions:

  1. Où les tirages sont-ils mis en mémoire tampon entre un appel à échanger des tampons et apparaître réellement à l'écran? Pourquoi le retard? Il semble que l'application dessine à tout moment de nombreuses images devant l'écran.

  2. Que peut faire une application OpenGL pour provoquer un tirage immédiat à l'écran? (c'est-à-dire: pas de mise en mémoire tampon, juste bloquer jusqu'à ce que le tirage soit terminé; je n'ai pas besoin d'un débit élevé, j'ai besoin d'une faible latence)

  3. Que peut faire une application pour que le tirage immédiat ci-dessus se produise le plus rapidement possible?

  4. Comment une application peut-elle savoir ce qui est actuellement affiché à l'écran? (Ou, combien de temps / combien d'images le délai de mise en mémoire tampon actuel est-il?)

Alex I
la source
Pouvez-vous séparer le temps nécessaire au clavier pour notifier votre application du temps nécessaire pour le rendre réellement?
ThorinII
@ThorinII: Juste point. Je peux probablement configurer quelque chose de hacky en utilisant une LED sur un port parallèle, etc. pour obtenir une indication du moment où l'application obtient la touche. Pourtant, c'est juste fgetc (stdin), à quel point cela peut-il être lent? : /
Alex I
J'avais l'impression que glFlush était utilisé pour provoquer un vidage immédiat de toutes les commandes.
Programmdude
1
@AlexI vérifiez que gamedev.stackexchange.com/questions/66543/… pourrait vous aider
concept3d
1
@ concept3d: Oui, il est fondamentalement répondu, pensais juste que vous aimeriez un représentant supplémentaire :) Apparemment, je dois attendre un jour pour l'attribuer.
Alex I

Réponses:

6

Toute fonction d'API de dessin appelée à partir du CPU sera soumise au tampon d'anneau de commande du GPU pour être exécutée ultérieurement par le GPU. Cela signifie que les fonctions OpenGL sont principalement des fonctions non bloquantes. Ainsi, le CPU et le GPU fonctionneront en parallèle.

La chose la plus importante à noter est que votre application peut être liée au CPU ou au GPU. une fois que vous appelez glFinish, le processeur attendra que le GPU termine ses commandes de dessin, si le GPU prend plus de temps et peut / provoque le blocage du processeur, vos applications sont liées au GPU. Si le GPU termine ses commandes de dessin et que le CPU prend trop de temps à glFinish, votre application est liée au CPU.

Et notez qu'il y a une différence entre glFlushet glFinish.

glFlush: indique que toutes les commandes qui ont été précédemment envoyées au GL doivent se terminer en un temps fini.

glFinish: force toutes les commandes GL précédentes à se terminer. Terminer ne revient que lorsque tous les effets des commandes précédemment émises sur l'état du client et du serveur GL et sur le framebuffer sont pleinement réalisés. "

glXSwapBuffers effectue un glFlush implicite avant son retour. Les commandes OpenGL suivantes peuvent être émises immédiatement après l'appel de glXSwapBuffers, mais ne sont pas exécutées tant que l'échange de tampon n'est pas terminé.

Le temps de trame réel sera très probablement déterminé par lequel des deux CPU / GPU prend plus de temps pour terminer son travail.

concept3d
la source
C'est très utile. Quelques corrections possibles: opengl.org/sdk/docs/man2/xhtml/glXSwapBuffers.xml "glXSwapBuffers effectue un glFlush implicite avant qu'il ne revienne", il semble que cela ne fasse pas réellement un glFinish, donc le tampon de commande peut avoir pas mal de des trucs dedans quand les tampons d'échange reviennent.
Alex I
... De plus, je pense que le point le plus intéressant est que mon application de test très simple n'est ni CPU ni GPU limitée - ni a beaucoup de travail à faire ici - mais autre chose, elle se retrouve en quelque sorte avec à la fois une fréquence d'images basse (exactement la même que celle du moniteur taux de rafraîchissement ??) et latence élevée (à cause du tampon de commande, en fait vous avez très bien expliqué cette partie).
Alex I
@AlexI both low framerate (exactly same as monitor refresh rate??non, sauf si vous utilisez explicitement VSync.
concept3d
@AlexI Je tiens également à souligner que le temps de trame est différent de FPS, utilisez le temps de trame pour profiler votre application car il est linéaire. Le FPS d'autre part est une mauvaise mesure qui n'est pas linéaire.
concept3d
Je n'utilise pas explicitement vsync. Je ne fais rien pour changer les paramètres par défaut vsync, je ne sais même pas où les changer dans OpenGL. J'obtiens exactement 60fps (à un pour cent près).
Alex I
4

Techniquement, OpenGL ne met jamais à jour l'écran.

Il existe une API de système de fenêtres distincte de GL (par exemple GLX, WGL, CGL, EGL) qui fait cela. Les échanges de tampons utilisant ces API invoquent généralement implicitement glFlush (...)mais dans certaines implémentations (par exemple le rasterizer GDI sur Windows), il fait un plein glFinish (...):

 

    * Sur le côté gauche, se trouve le chemin ICD (matériel) SwapBuffers (...). Sur le côté droit, le chemin GDI (logiciel).

Si vous avez activé VSYNC et la double mise en mémoire tampon, toute commande qui modifierait le tampon arrière avant qu'un échange en attente ne se produise doit se bloquer jusqu'à ce que l'échange. Il y a une profondeur limitée dans la file d'attente de commandes, donc cette commande bloquée finira par provoquer un embouteillage dans le pipeline. Cela pourrait signifier qu'au lieu de bloquer SwapBuffers (...)votre application, il bloque en fait certaines commandes GL non liées jusqu'à ce que VBLANK se déplace. Cela se résume vraiment au nombre de tampons en arrière que vous avez dans votre chaîne de swap.

Tant que tous les tampons arrière sont pleins d'images finies qui n'ont pas encore été déplacées vers l'avant, les tampons d'échange entraîneront implicitement un blocage. Malheureusement, il n'y a aucun moyen de contrôler explicitement le nombre de tampons arrière utilisés par la plupart des API du système de fenêtres GL (à part 0 à tampon unique ou 1 à double tampon). Le pilote est libre d'utiliser 2 tampons arrière s'il le souhaite (triple tampon), mais vous ne pouvez pas le demander au niveau de l'application en utilisant quelque chose comme GLX ou WGL.

Andon M. Coleman
la source
Andon: Bonne info, merci. Je pense que ce que je vois est en partie une double mise en mémoire tampon, mais: "essayez de modifier la mémoire tampon arrière avant que l'échange ne se produise doit bloquer" - pourquoi? il semble que tout puisse être envoyé au tampon de commande, y compris le swap :)
Alex I
"contrôler explicitement le nombre de tampons arrière utilisés par la plupart des API du système de fenêtres GL (à part 0 à tampon unique ou 1 à double tampon)" - comment contrôler un tampon simple / double?
Alex I
@AlexI: J'ai clarifié le langage en ce qui concerne les raisons pour lesquelles la commande peut entraîner un blocage. Dans d'autres API, il existe un concept appelé rendu à l'avance, qui correspond en fait à la profondeur de la file d'attente de commandes. Quant au contrôle du tampon simple / double, il est contrôlé par le format de pixel que vous sélectionnez lorsque vous créez votre contexte de rendu. Dans WGL, vous pouvez sélectionner un tampon simple ou double, mais si vous sélectionnez un tampon double, le pilote peut en fait créer 2 tampons arrière (donc la double mise en tampon devient une triple mise en mémoire tampon) si l'utilisateur configure son pilote pour ce faire.
Andon M. Coleman du
En fait, vous ne voulez pas rendre trop d'images à l'avance, car bien que cela réduise le blocage, cela augmente également la latence. La meilleure façon de minimiser la latence est en fait d'appeler glFinish (...)immédiatement après l'échange des tampons. Cela effacera la file d'attente de commandes, mais cela signifie également que le GPU et le CPU seront synchronisés (ce qui n'est pas bon si vous souhaitez que le GPU fonctionne à tout moment).
Andon M. Coleman,
1

Je suppose que vous connaissez cette expérience ?

Essentiellement, John Carmack faisait quelque chose de similaire, enregistrant l'écran et chronométrant les pixels envoyés à l'écran. Il a constaté qu'une grande partie de la latence provenait de l'écran. D'autres facteurs étaient le retard d'entrée du clavier, les pilotes vidéo et / ou bien sûr l'exécution du programme lui-même.

MichaelHouse
la source