Frame Pacing Library Teil des Android Game Development Kits.
Die Android Frame Pacing-Bibliothek, auch Swappy genannt, ist Teil der AGDK-Bibliotheken. So können OpenGL- und Vulkan-Spiele auf Android flüssig gerendert und die Framerate korrekt angepasst werden. In diesem Dokument wird die Framerate-Steuerung definiert, Situationen beschrieben, in denen sie erforderlich ist, und gezeigt, wie die Bibliothek diese Situationen angeht. Wenn Sie direkt zur Implementierung des Frame-Pacings in Ihrem Spiel springen möchten, lesen Sie den nächsten Schritt.
Hintergrund
Frame Scheduling ist die Synchronisierung der Logik und des Rendering-Loops eines Spiels mit dem Anzeigesubsystem eines Betriebssystems und der zugrunde liegenden Anzeigehardware. Das Android-Display-Subsystem wurde entwickelt, um visuelle Artefakte (Tearing) zu vermeiden, die auftreten können, wenn die Displayhardware während eines Updates auf einen neuen Frame umschaltet. Um diese Artefakte zu vermeiden, führt das Display-Subsystem Folgendes aus:
- Puffert vergangene Frames intern
- Erkennt verspätet eingereichte Frames
- Wiederholt die Anzeige vergangener Frames, wenn verspätete Frames erkannt werden
Ein Spiel informiert SurfaceFlinger, den Renderer im Display-Subsystem, dass es alle für einen Frame erforderlichen Zeichenaufrufe gesendet hat (durch Aufrufen von eglSwapBuffers
oder vkQueuePresentKHR
). SurfaceFlinger signalisiert der Displayhardware über eine Lasche, dass ein Frame verfügbar ist. Die Displayhardware zeigt dann den angegebenen Frame an. Die Displayhardware tickt mit einer konstanten Rate, z. B. 60 Hz. Wenn kein neuer Frame vorhanden ist, wenn die Hardware einen benötigt, zeigt die Hardware den vorherigen Frame noch einmal an.
Inkonsistente Framezeiten treten häufig auf, wenn ein Game-Render-Loop mit einer anderen Rate als die native Displayhardware rendert. Wenn ein Spiel mit 30 fps auf einem Gerät gerendert wird, das nativ 60 fps unterstützt, erkennt der Render-Loop des Spiels nicht, dass ein wiederholter Frame noch 16 Millisekunden lang auf dem Bildschirm angezeigt wird. Diese Unterbrechung führt in der Regel zu erheblichen Unstimmigkeiten bei den Frameraten, z. B. 49 Millisekunden, 16 Millisekunden, 33 Millisekunden. Zu komplexe Szenen verschlimmern dieses Problem noch, da sie zu fehlenden Frames führen.
Nicht optimale Lösungen
Die folgenden Lösungen für das Frame-Pacing wurden in der Vergangenheit in Spielen verwendet und führen in der Regel zu inkonsistenten Frameraten und erhöhter Eingabelatenz.
Frames so schnell wie möglich über die Rendering-API senden
Bei diesem Ansatz wird ein Spiel an variable SurfaceFlinger-Aktivitäten gebunden und es kommt zu einer zusätzlichen Frame-Latenz. Die Displaypipeline enthält eine Frame-Warteschlange, die normalerweise zwei Elemente enthält. Sie füllt sich, wenn das Spiel versucht, Frames zu schnell zu präsentieren. Da in der Warteschlange kein Platz mehr ist, wird die Game Loop (oder zumindest der Rendering-Thread) durch einen OpenGL- oder Vulkan-Aufruf blockiert. Das Spiel muss dann warten, bis die Displayhardware einen Frame anzeigt. Dieser Rückstau synchronisiert die beiden Komponenten. Diese Situation wird als Buffer-Stuffing oder Queue-Stuffing bezeichnet. Der Renderingprozess erkennt das Problem nicht, sodass die Framerate-Inkonsistenz schlimmer wird. Wenn das Spiel die Eingabe vor dem Frame erfasst, wird die Eingabelatenz größer.
Android Choreographer allein verwenden
Spiele verwenden Android Choreographer auch für die Synchronisierung. Diese Komponente, die in Java ab API 16 und in C++ ab API 24 verfügbar ist, liefert regelmäßige Ticks mit derselben Frequenz wie das Display-Subsystem. Es gibt jedoch noch Feinheiten, wann dieser Tick relativ zur tatsächlichen Hardware-VSYNC-Funktion gesendet wird. Diese Abweichungen variieren je nach Gerät. Bei langen Frames kann es jedoch weiterhin zu Buffer-Stuffing kommen.
Vorteile der Frame-Pacing-Bibliothek
Die Frame-Pacing-Bibliothek verwendet Android Choreographer für die Synchronisierung und gleicht die Variabilität bei der Tick-Übermittlung für Sie aus. Es verwendet Präsentationszeitstempel, um sicherzustellen, dass Frames zur richtigen Zeit präsentiert werden, und Synchronisationsschranken, um ein Buffer-Stuffing zu vermeiden. Die Bibliothek verwendet den NDK-Choreographer, wenn er verfügbar ist, und wechselt andernfalls zum Java-Choreographer.
Die Bibliothek unterstützt mehrere Bildwiederholraten, sofern sie vom Gerät unterstützt werden. Dadurch kann ein Frame in einem Spiel flexibler dargestellt werden. Bei einem Gerät, das sowohl eine Bildwiederholrate von 60 Hz als auch 90 Hz unterstützt, kann beispielsweise ein Spiel, das nicht 60 Bilder pro Sekunde liefern kann, auf 45 fps statt 30 fps herabgesetzt werden, um flüssig zu bleiben. Die Bibliothek erkennt die erwartete Framerate des Spiels und passt die Framepräsentationszeiten automatisch an. Die Frame-Pacing-Bibliothek verbessert auch die Akkulaufzeit, da unnötige Displayaktualisierungen vermieden werden. Wenn ein Spiel beispielsweise mit 60 fps gerendert wird, das Display aber mit 120 Hz aktualisiert wird, wird der Bildschirm für jeden Frame zweimal aktualisiert. Die Frame-Pacing-Bibliothek vermeidet dies, indem sie die Bildwiederholrate auf den vom Gerät unterstützten Wert festlegt, der der Zielframerate am nächsten kommt.
Funktionsweise
In den folgenden Abschnitten wird gezeigt, wie die Frame Pacing-Bibliothek mit langen und kurzen Spielframes umgeht, um ein korrektes Frame Pacing zu erreichen.
Korrekte Frame-Taktzeit bei 30 Hz
Die ideale Situation beim Rendern mit 30 Hz auf einem 60-Hz-Gerät ist in Abbildung 1 dargestellt. SurfaceFlinger lagert neue Grafik-Buffer, falls vorhanden, in den Cache ein. Im Diagramm wird „Kein Buffer vorhanden“ angezeigt und der vorherige wird wiederholt.
Abbildung 1: Ideale Frame-Taktung bei 30 Hz auf einem 60-Hz-Gerät.
Kurze Spielframes führen zu Rucklern
Auf den meisten modernen Geräten nutzen Game-Engines den Plattform-Choreographen, der Ticks liefert, um die Einreichung von Frames zu steuern. Es besteht jedoch weiterhin die Möglichkeit, dass aufgrund kurzer Frames eine schlechte Framerate entsteht, wie in Abbildung 2 dargestellt. Kurze Frames, gefolgt von langen Frames, werden vom Player als Ruckeln wahrgenommen.
Abbildung 2: Ein kurzer Spielframe C führt dazu, dass Frame B nur einen Frame enthält, gefolgt von mehreren C-Frames.
Die Frame Pacing-Bibliothek löst dieses Problem mithilfe von Präsentationszeitstempeln. Die Bibliothek verwendet die Erweiterungen für den Präsentationszeitstempel EGL_ANDROID_presentation_time
und VK_GOOGLE_display_timing
, damit Frames nicht zu früh präsentiert werden, wie in Abbildung 3 dargestellt.
Abbildung 3: Spielframe B wird zweimal dargestellt, um eine flüssigere Darstellung zu ermöglichen.
Lange Frames führen zu Rucklern und Latenz
Wenn die Displayarbeitslast länger als die Anwendungsarbeitslast dauert, werden einer Warteschlange zusätzliche Frames hinzugefügt. Das führt wieder zu Rucklern und kann aufgrund von Buffer-Stuffing zu einer zusätzlichen Frame-Latenz führen (siehe Abbildung 4). Die Bibliothek beseitigt sowohl das Stottern als auch die zusätzliche Latenz.
Abbildung 4: Langer Frame B führt zu einer falschen Taktung für zwei Frames – A und B
Die Bibliothek löst dieses Problem mithilfe von Synchronisationssperren (EGL_KHR_fence_sync
und VkFence
), um Wartezeiten in die Anwendung einzuschleusen, die es der Displaypipeline ermöglichen, aufzuschließen, anstatt einen Rückstau zuzulassen. Frame A enthält weiterhin einen zusätzlichen Frame, Frame B wird jetzt jedoch korrekt dargestellt, wie in Abbildung 5 zu sehen.
Abbildung 5: Frames C und D warten auf die Präsentation.
Unterstützte Betriebsmodi
Sie können die Frame-Pacing-Bibliothek für einen der folgenden drei Modi konfigurieren:
- Automatikmodus aus + Pipeline
- Automatischer Modus aktiviert + Pipeline
- Automatikmodus aktiviert + automatischer Pipeline-Modus (Pipeline/Nicht-Pipeline)
Empfohlener Modus
Sie können mit dem automatischen Modus und dem Pipeline-Modus experimentieren. Deaktivieren Sie sie jedoch zuerst und fügen Sie nach der Initialisierung von Swappy Folgendes hinzu:
swappyAutoSwapInterval(false);
swappyAutoPipelineMode(false);
swappyEnableStats(false);
swappySwapIntervalNS(1000000000L/yourPreferredFrameRateInHz);
Pipelinemodus
Zur Koordinierung der Engine-Arbeitslasten verwendet die Bibliothek in der Regel ein Pipeline-Modell, das die CPU- und GPU-Arbeitslasten über VSYNC-Grenzen hinweg trennt.
Abbildung 6 Pipelinemodus
Modus ohne Pipeline
Im Allgemeinen führt dieser Ansatz zu einer geringeren, besser vorhersehbaren Latenz des Eingabebildschirms. Wenn ein Spiel eine sehr niedrige Frame Time hat, können sowohl die CPU- als auch die GPU-Arbeitslasten in ein einzelnes Auslagerungsintervall passen. In diesem Fall würde ein nicht gepipelineter Ansatz tatsächlich zu einer geringeren Latenz des Eingabebildschirms führen.
Abbildung 7. Modus ohne Pipeline.
Automatischer Modus
Die meisten Spiele können das Swap-Intervall nicht auswählen. Das ist die Dauer, für die jeder Frame angezeigt wird (z. B. 33, 3 ms bei 30 Hz). Auf einigen Geräten kann ein Spiel mit 60 fps gerendert werden, während es auf einem anderen auf einen niedrigeren Wert herabgesetzt werden muss. Im automatischen Modus werden CPU- und GPU-Zeiten gemessen, um Folgendes zu tun:
- Automatische Auswahl von Auslagerungsintervallen: Bei Spielen, die in einigen Szenen 30 Hz und in anderen 60 Hz liefern, kann die Bibliothek dieses Intervall dynamisch anpassen.
- Pipelining für ultraschnelle Frames deaktivieren: Ermöglicht in allen Fällen eine optimale Eingabebildschirm-Latenz.
Mehrere Aktualisierungsraten
Geräte, die mehrere Bildwiederholraten unterstützen, bieten mehr Flexibilität bei der Auswahl eines flüssigen Auslagerungsintervalls:
- Auf Geräten mit 60 Hz: 60 fps / 30 fps / 20 fps
- Auf Geräten mit 60 Hz und 90 Hz: 90 fps / 60 fps / 45 fps / 30 fps
- Auf Geräten mit 60 Hz, 90 Hz und 120 Hz: 120 fps, 90 fps, 60 fps, 45 fps, 40 fps und 30 fps
Die Bibliothek wählt die Aktualisierungsrate aus, die am besten zur tatsächlichen Renderingdauer der Frames eines Spiels passt, um die visuelle Qualität zu verbessern.
Weitere Informationen zum Frame-Pacing mit mehreren Bildwiederholraten finden Sie im Blogpost Rendering mit hoher Bildwiederholrate auf Android-Geräten.
Frame-Statistiken
Die Frame Pacing-Bibliothek bietet die folgenden Statistiken für Debugging- und Profiling-Zwecke:
- Ein Histogramm der Anzahl der Bildschirmaktualisierungen, die ein Frame in der Compositor-Warteschlange nach Abschluss des Renderings gewartet hat.
- Ein Histogramm der Anzahl der Bildschirmaktualisierungen, die zwischen der angeforderten Präsentationszeit und der aktuellen Zeit vergangen sind.
- Ein Histogramm der Anzahl der Bildschirmaktualisierungen, die zwischen zwei aufeinanderfolgenden Frames übergeben wurden.
- Ein Histogramm der Anzahl der Bildschirmaktualisierungen, die zwischen dem Beginn der CPU-Arbeit für diesen Frame und der aktuellen Zeit vergangen sind.
Nächster Schritt
In den folgenden Anleitungen erfahren Sie, wie Sie die Android Frame Pacing-Bibliothek in Ihr Spiel einbinden:
- Android-Frame-Pacing in Ihren OpenGL-Renderer einbinden
- Android-Frame-Pacing in Ihren Vulkan-Renderer einbinden
Weitere Informationen
- Mir 2 verbessert die Renderingleistung durch die Verwendung von Swappy und reduziert die Rate langsamer Sitzungen von 40% auf 10%.