Wie lange dauert es, bis OpenGL den Bildschirm tatsächlich aktualisiert?

9

Ich habe eine einfache OpenGL-Test-App in C, die verschiedene Dinge als Reaktion auf Tasteneingaben zeichnet. (Mesa 8.0.4, getestet mit Mesa-EGL und mit GLFW, Ubuntu 12.04LTS auf einem PC mit NVIDIA GTX650). Die Draws sind recht einfach / schnell (rotierendes Dreieck). Mein Testcode schränkt die Framerate in keiner Weise absichtlich ein, er sieht einfach so aus:

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

Ich habe dies sehr sorgfältig geplant und finde, dass die Zeit von einem eglSwapBuffers()(oder glfwSwapBuffersdemselben) Anruf zum nächsten ~ 16,6 Millisekunden beträgt. Die Zeit von nach einem Anruf bis eglSwapBuffers()kurz vor dem nächsten Anruf ist nur ein bisschen kürzer, obwohl das, was gezeichnet wird, sehr einfach ist. Die Zeit, die der Aufruf der Swap-Puffer benötigt, liegt deutlich unter 1 ms.

Die Zeit zwischen dem Ändern der Zeichnung der App als Reaktion auf den Tastendruck und der tatsächlich auf dem Bildschirm angezeigten Änderung beträgt jedoch> 150 ms (ca. 8-9 Frames). Dies wird mit einer Kameraaufnahme des Bildschirms und der Tastatur mit 60 fps gemessen.

Daher die Fragen:

  1. Wo werden Draws zwischen einem Aufruf zum Austauschen von Puffern und dem tatsächlichen Anzeigen auf dem Bildschirm gepuffert? Warum die Verzögerung? Es sieht sicher so aus, als würde die App jederzeit viele Frames vor dem Bildschirm zeichnen.

  2. Was kann eine OpenGL-Anwendung tun, um eine sofortige Auslosung auf dem Bildschirm zu bewirken? (dh: keine Pufferung, nur blockieren, bis das Zeichnen abgeschlossen ist; ich brauche keinen hohen Durchsatz, ich brauche eine niedrige Latenz)

  3. Was kann eine Anwendung tun, um die oben genannte sofortige Auslosung so schnell wie möglich durchzuführen?

  4. Wie kann eine Anwendung wissen, was gerade auf dem Bildschirm angezeigt wird? (Oder wie lang / wie viele Frames ist die aktuelle Pufferverzögerung?)

Alex ich
quelle
Können Sie die Zeit, die die Tastatur benötigt, um Ihre Anwendung zu benachrichtigen, von der Zeit trennen, die zum tatsächlichen Rendern benötigt wird?
ThorinII
@ThorinII: Fairer Punkt. Ich kann wahrscheinlich etwas Hackiges einrichten, indem ich eine LED an einem parallelen Anschluss usw. verwende, um anzuzeigen, wann die App tatsächlich den Tastendruck erhält. Trotzdem ist es nur fgetc (stdin), wie langsam kann das sein? : /
Alex I
Ich hatte den Eindruck, dass glFlush verwendet wurde, um alle Befehle sofort zu löschen.
Programmdude
1
@AlexIch überprüfe diese gamedev.stackexchange.com/questions/66543/… könnte dir helfen
concept3d
1
@ concept3d: Ja, es ist im Grunde genommen beantwortet, ich dachte nur, du möchtest einen zusätzlichen Repräsentanten :) Anscheinend muss ich einen Tag warten, um es zu vergeben.
Alex I

Antworten:

6

Jede von der CPU aufgerufene Zeichnungs-API-Funktion wird an den GPU- Befehlsringpuffer gesendet , um später von der GPU ausgeführt zu werden. Dies bedeutet, dass OpenGL-Funktionen meist nicht blockierende Funktionen sind. Die CPU und die GPU arbeiten also parallel.

Das Wichtigste ist, dass Ihre Anwendung CPU- oder GPU-gebunden sein kann. Sobald Sie glFinish aufrufen, sollte die CPU warten, bis die GPU ihre Zeichenbefehle ausgeführt hat. Wenn die GPU mehr Zeit benötigt und die CPU möglicherweise blockiert, sind Ihre Anwendungen an die GPU gebunden. Wenn die GPU das Zeichnen von Befehlen beendet und die CPU zu lange braucht, um fertig zu werden, ist Ihre Anwendung CPU-gebunden.

Und beachten Sie, dass es einen Unterschied zwischen glFlushund gibt glFinish.

glFlush: Gibt an, dass alle Befehle, die zuvor an den GL gesendet wurden, in endlicher Zeit ausgeführt werden müssen.

glFinish: Erzwingt den Abschluss aller vorherigen GL-Befehle. Finish wird erst zurückgegeben, wenn alle Auswirkungen zuvor ausgegebener Befehle auf den GL-Client- und -Serverstatus und den Framebuffer vollständig realisiert wurden. "

glXSwapBuffers führt einen impliziten glFlush durch, bevor er zurückkehrt. Nachfolgende OpenGL-Befehle können unmittelbar nach dem Aufruf von glXSwapBuffers ausgegeben werden, werden jedoch erst ausgeführt, wenn der Pufferaustausch abgeschlossen ist.

Die tatsächliche Frame-Zeit wird höchstwahrscheinlich davon abhängen, welche der beiden CPUs / GPUs mehr Zeit benötigt, um ihre Arbeit abzuschließen.

concept3d
quelle
Das ist sehr hilfreich. Einige mögliche Korrekturen: opengl.org/sdk/docs/man2/xhtml/glXSwapBuffers.xml "glXSwapBuffers führt ein implizites glFlush durch, bevor es zurückkehrt" Es scheint, dass dies kein glFinish bewirkt, sodass der Befehlspuffer ziemlich viel enthalten kann Zeug drin, wenn Swap-Puffer zurückkehren.
Alex I
... Ich denke auch, dass der interessanteste Punkt ist, dass meine sehr einfache Testanwendung weder CPU noch GPU-beschränkt ist - noch viel Arbeit hier zu tun hat -, aber etwas anderes, es endet irgendwie mit beiden niedrigen Frameraten (genau wie Monitor) Bildwiederholfrequenz ??) und hohe Latenz (aufgrund des Befehlspuffers haben Sie diesen Teil eigentlich recht gut erklärt).
Alex I
@AlexI both low framerate (exactly same as monitor refresh rate??nein, es sei denn, Sie verwenden explizit VSync.
Konzept3d
@AlexI Ich möchte auch darauf hinweisen, dass sich die Frame-Zeit von FPS unterscheidet. Verwenden Sie die Frame-Zeit, um Ihre Anwendung zu profilieren, da sie linear ist. FPS hingegen ist eine schlechte Messung, die nicht linear ist.
Konzept3d
Ich verwende vsync nicht explizit. Ich unternehme nichts, um die Standardeinstellungen für vsync zu ändern. Ich weiß nicht einmal, wo ich diese in OpenGL ändern soll. Ich bekomme nur genau 60 fps (auf ein Prozent genau).
Alex I
4

OpenGL aktualisiert den Bildschirm technisch nie.

Es gibt eine Fenstersystem-API, die von GL getrennt ist (z. B. GLX, WGL, CGL, EGL), die dies tut. Buffer Swaps, die diese APIs verwenden, werden im Allgemeinen implizit aufgerufen, glFlush (...)aber in einigen Implementierungen (z. B. dem GDI-Rasterizer unter Windows) wird Folgendes ausgeführt glFinish (...):

 

    * Auf der linken Seite befindet sich der ICD-Pfad (Hardware) SwapBuffers (...). Auf der rechten Seite der GDI-Pfad (Software).

Wenn Sie VSYNC aktiviert und Double-Buffering, dann jeder Befehl, der die Back - Puffer vor einer anstehenden Swap auftritt ändern würde Muss Stall bis zum Swap. Die Befehlswarteschlange hat eine begrenzte Tiefe, sodass dieser blockierte Befehl möglicherweise einen Stau in der Pipeline verursacht. Dies könnte bedeuten, dass anstelle der Blockierung SwapBuffers (...)Ihrer Anwendung tatsächlich ein nicht verwandter GL-Befehl blockiert wird, bis VBLANK herumrollt. Worauf es wirklich ankommt, ist, wie viele Rückpuffer Sie in Ihrer Swap-Kette haben.

Solange alle hinteren Puffer voll mit fertigen Frames sind, die noch nach vorne verschoben werden müssen, verursachen Swap-Puffer implizit eine Blockierung. Leider gibt es keine Möglichkeit, die Anzahl der von den meisten GL-Fenstersystem-APIs verwendeten Backpuffer explizit zu steuern (abgesehen von 0 einfach gepufferten oder 1 doppelt gepufferten). Dem Treiber steht es frei, 2 Backpuffer zu verwenden, wenn er dies wünscht (dreifache Pufferung). Sie können dies jedoch nicht auf Anwendungsebene mit GLX oder WGL anfordern.

Andon M. Coleman
quelle
Andon: Gute Infos, danke. Ich denke, was ich sehe, ist teilweise doppelte Pufferung, aber: "Der Versuch, den Rückpuffer zu ändern, bevor der Austausch stattfindet, muss blockieren" - warum? es hört sich so an, als ob alles an den Befehlspuffer gesendet werden kann, einschließlich des Austauschs :)
Alex I
"Steuern Sie explizit die Anzahl der von den meisten GL-Fenstersystem-APIs verwendeten Rückpuffer (abgesehen von 0 einfach gepufferten oder 1 doppelt gepufferten)" - wie steuert man einfach / doppelt gepuffert?
Alex I
@AlexI: Ich habe die Sprache klargestellt, warum der Befehl zum Blockieren führen kann. In anderen APIs gibt es ein Konzept, das als Render-Ahead bezeichnet wird. Dies ist effektiv die Tiefe der Befehlswarteschlange. Die Steuerung von einfach / doppelt gepuffert wird durch das Pixelformat gesteuert, das Sie beim Erstellen Ihres Renderkontexts auswählen. In WGL können Sie einfach oder doppelt gepuffert auswählen. Wenn Sie jedoch doppelt gepuffert auswählen, kann der Treiber tatsächlich zwei Rückpuffer erstellen (daher wird doppelte Pufferung zu dreifacher Pufferung), wenn der Benutzer seinen Treiber dafür einstellt.
Andon M. Coleman
Sie möchten eigentlich nicht zu viele Frames im Voraus rendern, da dies zwar die Blockierung verringert, aber auch die Latenz erhöht. Der beste Weg, um die Latenz zu minimieren, besteht darin, glFinish (...)unmittelbar nach dem Austauschen der Puffer aufzurufen . Dies löscht die Befehlswarteschlange, bedeutet aber auch, dass die GPU und die CPU synchronisiert werden (was nicht gut ist, wenn Sie möchten, dass die GPU jederzeit funktioniert).
Andon M. Coleman
1

Ich nehme an, Sie kennen dieses Experiment ?

Im Wesentlichen machte John Carmack etwas Ähnliches und zeichnete den Bildschirm und die Timing-Pixel auf, die an den Bildschirm gesendet wurden. Er stellte fest, dass ein Großteil der Latenz vom Bildschirm kam. Andere Faktoren waren die Eingangsverzögerung über die Tastatur, die Grafiktreiber und / oder natürlich die Ausführung des Programms selbst.

MichaelHouse
quelle