Frame Pacing Library Teil des Android Game Development Kit.

Die Android Frame Pacing-Bibliothek, auch als Swappy bezeichnet, ist Teil der AGDK Libraries. Damit erreichen OpenGL- und Vulkan-Spiele unter Android ein reibungsloses Rendering und eine korrekte Frame-Taktung. In diesem Dokument wird die Frame-Taktung definiert, Situationen beschrieben, in denen Frame-Taktung erforderlich ist, und erläutert, wie die Bibliothek auf diese Situationen reagiert. Wenn Sie direkt mit der Implementierung der Frame-Taktung in Ihrem Spiel fortfahren möchten, lesen Sie Nächster Schritt.

Hintergrund

Die Frame-Taktung ist die Synchronisierung der Logik und der Rendering-Schleife eines Spiels mit dem Anzeigesubsystem eines Betriebssystems und der zugrunde liegenden Anzeigehardware. Das Android-Displaysubsystem wurde entwickelt, um visuelle Artefakte (sogenannte Tearing) zu vermeiden, die auftreten können, wenn die Anzeigehardware während einer Aktualisierung zu einem neuen Frame wechselt. Um diese Artefakte zu vermeiden, geht das Anzeigesubsystem so vor:

  • Zwischenspeichert ältere Frames intern
  • Erkennt spät eingereichte Frames
  • Wiederholt die Anzeige früherer Frames, wenn späte Frames erkannt werden

Ein Spiel informiert SurfaceFlinger, den Compositor im Display-Subsystem, darüber, dass es alle für einen Frame erforderlichen Zeichenaufrufe gesendet hat (durch Aufrufen von eglSwapBuffers oder vkQueuePresentKHR). OberflächenFlinger signalisiert die Verfügbarkeit eines Frames über einen Latch. Die Anzeigehardware zeigt dann den angegebenen Frame an. Die Displayhardware tickt mit einer konstanten Rate, z. B. 60 Hz. Wenn es keinen neuen Frame gibt, obwohl die Hardware einen solchen benötigt, zeigt die Hardware den vorherigen Frame noch einmal an.

Inkonsistente Framezeiten treten häufig auf, wenn eine Renderingschleife eines Spiels mit einer anderen Rate als die native Displayhardware gerendert wird. Wenn ein Spiel mit 30 fps versucht, auf einem Gerät zu rendern, das nativ 60 fps unterstützt, erkennt die Renderingschleife des Spiels nicht, dass ein wiederholter Frame weitere 16 Millisekunden auf dem Bildschirm verbleibt. Diese Verbindung führt in der Regel zu erheblichen Inkonsistenzen bei den Framezeiten, z. B. 49 Millisekunden, 16 Millisekunden, 33 Millisekunden. Durch übermäßig komplexe Szenen wird dieses Problem noch weiter verschärft, da sie dazu führen, dass verpasste Frames auftreten.

Nicht optimale Lösungen

Die folgenden Lösungen für die Frame-Taktung wurden in der Vergangenheit von Spielen verwendet und führen normalerweise zu uneinheitlichen Framezeiten und erhöhter Eingabelatenz.

Frames so schnell einreichen, wie die Rendering API es zulässt

Bei diesem Ansatz wird ein Spiel mit einer variablen SurfaceFlinger-Aktivität verknüpft und ein zusätzlicher Frame an Latenz eingeführt. Die Anzeigepipeline enthält eine Warteschlange von Frames, normalerweise Größe 2, die gefüllt ist, wenn das Spiel versucht, Frames zu schnell darzustellen. Da kein Platz mehr in der Warteschlange vorhanden ist, wird die Spielschleife (oder zumindest der Rendering-Thread) durch einen OpenGL- oder Vulkan-Aufruf blockiert. Das Spiel wird dann gezwungen, zu warten, bis die Anzeigehardware einen Frame anzeigt. Dadurch werden die beiden Komponenten synchronisiert. Diese Situation wird als Pufferstuffing oder Warteschlangen-Stuffing bezeichnet. Der Renderer erkennt das nicht, sodass die Framerate-Uneinheitlichkeiten sich verschlimmern. Wenn das Spiel eine Eingabe vor dem Frame abtastet, verschlechtert sich die Eingabelatenz.

Android Choreographer allein verwenden

In den Spielen wird auch Android Choreographer zur Synchronisierung verwendet. Diese Komponente, die in Java über API 16 und in C++ von API 24 verfügbar ist, liefert reguläre Ticks mit der gleichen Häufigkeit wie das Anzeigesubsystem. Es gibt noch Feinheiten in Bezug darauf, wann dieser Tick im Verhältnis zur tatsächlichen Hardware-VSYNC übergeben wird, und diese Offsets variieren je nach Gerät. Bei langen Frames kann es trotzdem zu Puffern kommen.

Vorteile der Frame Pacing-Bibliothek

Die Frame Pacing-Bibliothek verwendet Android Choreographer für die Synchronisierung und Deals mit der Variabilität in der Tick-Auslieferung. Es verwendet Präsentationszeitstempel, um sicherzustellen, dass Frames zum richtigen Zeitpunkt präsentiert werden, und synchronisiert Zäune, um Zwischenspeichern zu vermeiden. Die Bibliothek verwendet den NDK Choreographer (falls verfügbar) und gibt den Java Choreographer zurück, wenn er nicht verfügbar ist.

Die Bibliothek verarbeitet mehrere Aktualisierungsraten, sofern sie vom Gerät unterstützt werden. Dadurch kann ein Spiel einen Frame flexibler darstellen. Bei einem Gerät, das sowohl eine Aktualisierungsrate von 60 Hz als auch eine Aktualisierungsrate von 90 Hz unterstützt, kann ein Spiel, das keine 60 Bilder pro Sekunde erzeugen kann, auf 45 fps statt auf 30 fps fallen, um für eine reibungslose Wiedergabe zu sorgen. Die Bibliothek erkennt die erwartete Framerate des Spiels und passt die Frame-Darstellungszeiten automatisch entsprechend an. Die Frame Pacing-Bibliothek verbessert auch die Akkulaufzeit, da unnötige Displayupdates vermieden werden. Wenn ein Spiel beispielsweise mit 60 fps gerendert wird, die Anzeige aber mit 120 Hz aktualisiert wird, wird der Bildschirm für jeden Frame zweimal aktualisiert. In der Frame Pacing-Bibliothek kann dies vermieden werden, indem die Aktualisierungsrate auf den Wert festgelegt wird, der von dem Gerät unterstützt wird, das der Ziel-Framerate am nächsten ist.

So funktionierts

In den folgenden Abschnitten wird gezeigt, wie die Frame Pacing-Bibliothek lange und kurze Spielframes verarbeitet, um das richtige Frame-Taktungsverfahren zu erreichen.

Korrekte Frame-Taktung bei 30 Hz

Beim Rendern mit 30 Hz auf einem 60-Hz-Gerät wird die ideale Situation unter Android in Abbildung 1 dargestellt. SurfaceFlinger schließt neue Grafikpuffer, falls vorhanden. (NB im Diagramm gibt an, dass kein Zwischenspeicher vorhanden ist und der vorherige Zwischenspeicher wiederholt wird).

Ideale Frame-Taktung bei 30 Hz auf einem 60-Hz-Gerät

Abbildung 1: Ideale Frame-Taktung bei 30 Hz auf einem 60-Hz-Gerät

Kurze Spiel-Frames führen zu Ruckeln

Auf den meisten modernen Geräten sind Spiel-Engines darauf angewiesen, dass der Plattform-Choreograf Ticks sendet, um die Übermittlung von Frames voranzutreiben. Es besteht jedoch weiterhin die Möglichkeit einer schlechten Frame-Taktung aufgrund kurzer Frames, wie in Abbildung 2 dargestellt. Kurze Frames gefolgt von langen Frames werden vom Player als Ruckeln empfunden.

Frames für Kurzspiele

Abbildung 2: Kurzer Spiel-Frame C bewirkt, dass Frame B nur einen Frame zeigt, gefolgt von mehreren C-Frames.

Die Frame Pacing-Bibliothek löst dieses Problem durch die Verwendung von Zeitstempeln der Präsentation. Die Bibliothek verwendet die Erweiterungen EGL_ANDROID_presentation_time und VK_GOOGLE_display_timing für Präsentationszeitstempel, damit Frames nicht früh präsentiert werden (siehe Abbildung 3).

Zeitstempel von Präsentationen

Abbildung 3: Spiel-Frame B wird zweimal präsentiert, um die Anzeige flüssiger zu machen

Lange Frames führen zu Ruckeln und Latenz

Wenn die Anzeigearbeitslast länger als die Anwendungsarbeit dauert, werden zusätzliche Frames in eine Warteschlange aufgenommen. Dies führt wiederum wieder zu Rucklern und kann aufgrund von Puffern zu einem zusätzlichen Frame der Latenz führen (siehe Abbildung 4). Die Bibliothek beseitigt das Ruckeln und den zusätzlichen Frame an Latenz.

Long Game-Frames

Abbildung 4: Der lange Frame B ergibt eine falsche Taktung für 2 Frames: A und B.

Die Bibliothek löst dieses Problem, indem sie mithilfe von Synchronisierungszäunen (EGL_KHR_fence_sync und VkFence) Wartezeiten in die Anwendung einfügt, damit die Anzeigepipeline aufholt, anstatt sich einen Rückdruck aufzubauen. Frame A zeigt immer noch einen zusätzlichen Frame, aber Frame B wird jetzt korrekt dargestellt, wie in Abbildung 5 zu sehen.

In Anwendungsebene hinzugefügte Wartezeiten

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 drei folgenden Modi konfigurieren:

  • Automatischer Modus deaktiviert + Pipeline
  • Automatischer Modus aktiviert + Pipeline
  • Automatischer Modus aktiviert + Automatischer Pipelinemodus (Pipeline/Nicht-Pipeline)

Sie können mit dem automatischen Modus und den Pipelinemodi experimentieren. Zunächst deaktivieren Sie sie jedoch und führen nach der Initialisierung von Swappy Folgendes ein:

  swappyAutoSwapInterval(false);
  swappyAutoPipelineMode(false);
  swappyEnableStats(false);
  swappySwapIntervalNS(1000000000L/yourPreferredFrameRateInHz);

Pipelinemodus

Um Engine-Arbeitslasten zu koordinieren, verwendet die Bibliothek in der Regel ein Pipeline-Modell, das die CPU- und GPU-Arbeitslasten über VSYNC-Grenzen trennt.

Pipelinemodus

Abbildung 6: Pipelinemodus

Nicht-Pipeline-Modus

Im Allgemeinen führt dieser Ansatz zu einer niedrigeren, besser vorhersehbaren Latenz des Eingabebildschirms. In Fällen, in denen ein Spiel eine sehr kurze Framezeit hat, können sowohl die CPU- als auch die GPU-Arbeitslasten in ein einzelnes Auslagerungsintervall passen. In diesem Fall würde ein Ansatz ohne Pipeline tatsächlich eine niedrigere Latenz des Eingabebildschirms liefern.

Nicht-Pipeline-Modus

Abbildung 7: Nicht-Pipeline-Modus

Automatischer Modus

Die meisten Spiele wissen nicht, wie das Swap-Intervall ausgewählt wird. Das ist die Dauer, für die jeder Frame angezeigt wird (z. B. 33,3 ms für 30 Hz). Auf einigen Geräten kann ein Spiel mit 60 fps gerendert werden, auf einem anderen muss es eventuell auf einen niedrigeren Wert gesenkt werden. Im automatischen Modus werden CPU- und GPU-Zeiten für Folgendes gemessen:

  • Auslagerungsintervalle automatisch auswählen: Bei Spielen, die in einigen Szenen 30 Hz und in anderen 60 Hz liefern, kann die Bibliothek dieses Intervall dynamisch anpassen.
  • Pipeline für ultraschnelle Frames deaktivieren: Sorgt in allen Fällen für eine optimale Eingabebildschirm-Latenz.

Mehrere Aktualisierungsraten

Geräte, die mehrere Aktualisierungsraten unterstützen, bieten eine höhere Flexibilität bei der Auswahl eines flüssigen Austauschintervalls:

  • Bei Geräten mit 60 Hz: 60 fps / 30 fps / 20 fps
  • Bei Geräten mit 60 Hz + 90 Hz: 90 fps / 60 fps / 45 fps / 30 fps
  • Bei Geräten mit 60 Hz + 90 Hz + 120 Hz: 120 fps / 90 fps / 60 fps / 45 fps / 40 fps / 30 fps

Für ein besseres visuelles Erlebnis wählt die Bibliothek die Aktualisierungsrate aus, die am besten zur tatsächlichen Rendering-Dauer der Frames eines Spiels passt.

Weitere Informationen zur Frame-Taktung mit mehreren Aktualisierungsraten finden Sie im Blogpost Rendering mit hoher Aktualisierungsrate unter Android.

Framestatistiken

Die Frame Pacing-Bibliothek bietet die folgenden Statistiken zur Fehlerbehebung und Profilerstellung:

  • Histogramm mit der Anzahl der Bildschirmaktualisierungen eines Frames, der nach Abschluss des Renderings in der Compositor-Warteschlange gewartet hat.
  • Ein Histogramm der Anzahl der Bildschirmaktualisierungen, die zwischen der angeforderten Präsentationszeit und der tatsächlichen aktuellen Zeit durchgeführt wurden.
  • Ein Histogramm der Anzahl der Bildschirmaktualisierungen, die zwischen zwei aufeinanderfolgenden Frames durchgeführt wurden.
  • Histogramm der Anzahl der Bildschirmaktualisierungen, die zwischen dem Beginn der CPU-Arbeit für diesen Frame und der tatsächlichen aktuellen Zeit durchgeführt wurden.

Nächster Schritt

In den folgenden Anleitungen erfahren Sie, wie Sie die Android Frame Pacing-Bibliothek in Ihr Spiel integrieren: