Synchronisations-Framework

Das Synchronisationsframework beschreibt explizit Abhängigkeiten zwischen verschiedenen asynchronen Vorgängen im Android-Grafiksystem. Das Framework stellt eine API bereit, die es Komponenten ermöglicht, anzuzeigen, wann Puffer freigegeben werden. Das Framework ermöglicht auch die Weitergabe von Synchronisierungsprimitiven zwischen Treibern vom Kernel an den Userspace und zwischen Userspace-Prozessen selbst.

Beispielsweise kann eine Anwendung Aufgaben in die Warteschlange stellen, die in der GPU ausgeführt werden sollen. Die GPU beginnt mit dem Zeichnen dieses Bildes. Obwohl das Bild noch nicht in den Speicher geladen wurde, wird der Pufferzeiger zusammen mit einem Zaun, der angibt, wann die GPU-Arbeit abgeschlossen sein wird, an den Fenster-Compositor übergeben. Der Fenstercompositor beginnt vorzeitig mit der Verarbeitung und übergibt die Arbeit an den Anzeigecontroller. Auf ähnliche Weise wird die CPU-Arbeit im Voraus erledigt. Sobald die GPU fertig ist, zeigt der Display-Controller das Bild sofort an.

Das Synchronisierungs-Framework ermöglicht es Implementierern auch, Synchronisierungsressourcen in ihren eigenen Hardwarekomponenten zu nutzen. Schließlich bietet das Framework Einblick in die Grafikpipeline, um das Debuggen zu erleichtern.

Explizite Synchronisierung

Durch die explizite Synchronisierung können Produzenten und Konsumenten von Grafikpuffern signalisieren, wenn sie mit der Verwendung eines Puffers fertig sind. Die explizite Synchronisierung wird im Kernel-Space implementiert.

Zu den Vorteilen der expliziten Synchronisierung gehören:

  • Weniger Verhaltensunterschiede zwischen Geräten
  • Bessere Debugging-Unterstützung
  • Verbesserte Testmetriken

Das Synchronisierungsframework verfügt über drei Objekttypen:

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

sync_timeline ist eine monoton steigende Zeitleiste, die Anbieter für jede Treiberinstanz implementieren sollten, z. B. einen GL-Kontext, einen Anzeigecontroller oder einen 2D-Blitter. sync_timeline zählt die an den Kernel übermittelten Jobs für eine bestimmte Hardware. sync_timeline bietet Garantien für die Reihenfolge von Vorgängen und ermöglicht hardwarespezifische Implementierungen.

Befolgen Sie diese Richtlinien bei der Implementierung sync_timeline :

  • Geben Sie nützliche Namen für alle Treiber, Zeitleisten und Fences an, um das Debuggen zu vereinfachen.
  • Implementieren Sie die Operatoren timeline_value_str und pt_value_str in Zeitleisten, um die Debugging-Ausgabe besser lesbar zu machen.
  • Implementieren Sie die Füllung driver_data , um Userspace-Bibliotheken, wie z. B. der GL-Bibliothek, bei Bedarf Zugriff auf private Timeline-Daten zu gewähren. data_driver können Anbieter Informationen über die unveränderlichen sync_fence und sync_pts weitergeben, um darauf basierende Befehlszeilen zu erstellen.
  • Erlauben Sie dem Benutzerbereich nicht, explizit einen Zaun zu erstellen oder zu signalisieren. Das explizite Erstellen von Signalen/Zäunen führt zu einem Denial-of-Service-Angriff, der die Pipeline-Funktionalität stoppt.
  • Greifen Sie nicht explizit auf die Elemente sync_timeline , sync_pt oder sync_fence zu. Die API stellt alle benötigten Funktionen bereit.

sync_pt

sync_pt ist ein einzelner Wert oder Punkt auf einer sync_timeline . Ein Punkt hat drei Zustände: aktiv, signalisiert und Fehler. Punkte beginnen im aktiven Zustand und gehen in den signalisierten oder Fehlerzustand über. Wenn beispielsweise ein Bildkonsument keinen Puffer mehr benötigt, wird ein sync_pt signalisiert, sodass ein Bildproduzent weiß, dass es in Ordnung ist, erneut in den Puffer zu schreiben.

sync_fence

sync_fence ist eine Sammlung von sync_pt Werten, die häufig unterschiedliche übergeordnete sync_timeline Werte haben (z. B. für den Display-Controller und die GPU). sync_fence , sync_pt und sync_timeline sind die wichtigsten Grundelemente, die Treiber und Userspace verwenden, um ihre Abhängigkeiten zu kommunizieren. Wenn ein Fence signalisiert wird, ist garantiert, dass alle vor dem Fence ausgegebenen Befehle vollständig sind, da der Kernel-Treiber oder der Hardwareblock die Befehle der Reihe nach ausführt.

Das Synchronisierungsframework ermöglicht es mehreren Verbrauchern oder Produzenten, zu signalisieren, wann sie mit der Verwendung eines Puffers fertig sind, und die Abhängigkeitsinformationen mit einem Funktionsparameter zu kommunizieren. Zäune werden durch einen Dateideskriptor unterstützt und vom Kernel-Space an den Userspace übergeben. Beispielsweise kann ein Zaun zwei sync_pt -Werte enthalten, die angeben, wann zwei separate Bildkonsumenten mit dem Lesen eines Puffers fertig sind. Wenn der Zaun signalisiert wird, wissen die Bildproduzenten, dass beide Konsumenten mit dem Konsumieren fertig sind.

Zäune beginnen wie sync_pt Werte aktiv und ändern ihren Status basierend auf dem Status ihrer Punkte. Wenn alle sync_pt Werte signalisiert werden, wird sync_fence signalisiert. Wenn ein sync_pt in einen Fehlerzustand fällt, hat der gesamte sync_fence einen Fehlerzustand.

Die Mitgliedschaft in einem sync_fence ist unveränderlich, nachdem der Zaun erstellt wurde. Um mehr als einen Punkt in einem Zaun zu erhalten, wird eine Zusammenführung durchgeführt, bei der Punkte aus zwei unterschiedlichen Zäunen zu einem dritten Zaun hinzugefügt werden. Wenn einer dieser Punkte im ursprünglichen Zaun signalisiert wurde und der andere nicht, befindet sich auch der dritte Zaun nicht in einem signalisierten Zustand.

Um eine explizite Synchronisierung zu implementieren, stellen Sie Folgendes bereit:

  • Ein Kernel-Space-Subsystem, das das Synchronisierungsframework für einen bestimmten Hardwaretreiber implementiert. Treiber, die Fence-fähig sein müssen, sind im Allgemeinen alles, was auf den Hardware Composer zugreift oder mit ihm kommuniziert. Zu den Schlüsseldateien gehören:
    • Kernimplementierung:
      • kernel/common/include/linux/sync.h
      • kernel/common/drivers/base/sync.c
    • Dokumentation unter kernel/common/Documentation/sync.txt
    • Bibliothek zur Kommunikation mit dem Kernel-Space in platform/system/core/libsync
  • Der Anbieter muss die entsprechenden Synchronisationsgrenzen als Parameter für die Funktionen validateDisplay() und presentDisplay() in der HAL bereitstellen.
  • Zwei Fence-bezogene GL-Erweiterungen ( EGL_ANDROID_native_fence_sync und EGL_ANDROID_wait_sync ) und Fence-Unterstützung im Grafiktreiber.

Fallstudie: Implementieren Sie einen Anzeigetreiber

Um die API zur Unterstützung der Synchronisierungsfunktion zu verwenden, entwickeln Sie einen Anzeigetreiber mit einer Anzeigepufferfunktion. Bevor es das Synchronisationsframework gab, empfing diese Funktion dma-buf Objekte, stellte diese Puffer auf dem Display dar und blockierte, während der Puffer sichtbar war. Zum Beispiel:

/*
 * assumes buffer is ready to be displayed.  returns when buffer is no longer on
 * screen.
 */
void display_buffer(struct dma_buf *buffer);

Mit dem Synchronisationsframework ist die Funktion display_buffer komplexer. Beim Ausstellen eines Puffers wird der Puffer mit einem Zaun verknüpft, der anzeigt, wann der Puffer bereit sein wird. Sie können sich anstellen und mit der Arbeit beginnen, nachdem der Zaun geräumt ist.

Das Anstehen und Einleiten von Arbeiten nach der Freigabe des Zauns blockiert nichts. Sie geben Ihren eigenen Zaun sofort zurück, was garantiert, dass der Puffer nicht mehr angezeigt wird. Während Sie Puffer in die Warteschlange stellen, listet der Kernel Abhängigkeiten mit dem Synchronisierungsframework auf:

/*
 * displays buffer when fence is signaled.  returns immediately with a fence
 * that signals when buffer is no longer displayed.
 */
struct sync_fence* display_buffer(struct dma_buf *buffer, struct sync_fence
*fence);

Synchronisierungsintegration

In diesem Abschnitt wird erläutert, wie das Kernel-Space-Synchronisierungsframework mit Userspace-Teilen des Android-Frameworks und den Treibern integriert wird, die miteinander kommunizieren müssen. Kernel-Space-Objekte werden als Dateideskriptoren im Userspace dargestellt.

Integrationskonventionen

Befolgen Sie die Konventionen der Android-HAL-Schnittstelle:

  • Wenn die API einen Dateideskriptor bereitstellt, der auf einen sync_pt verweist, muss der Treiber des Anbieters oder die HAL, die die API verwendet, den Dateideskriptor schließen.
  • Wenn der Herstellertreiber oder die HAL einen Dateideskriptor, der ein sync_pt enthält, an eine API-Funktion übergibt, darf der Herstellertreiber oder die HAL den Dateideskriptor nicht schließen.
  • Um den Fence-Dateideskriptor weiterhin verwenden zu können, muss der Herstellertreiber oder die HAL den Deskriptor duplizieren.

Ein Zaunobjekt wird jedes Mal umbenannt, wenn es BufferQueue durchläuft. Durch die Kernel-Fence-Unterstützung können Zäune Zeichenfolgen für Namen haben, sodass das Synchronisierungsframework den Fensternamen und den Pufferindex verwendet, der in die Warteschlange gestellt wird, um den Zaun zu benennen, z. B. SurfaceView:0 . Dies ist beim Debuggen hilfreich, um die Ursache eines Deadlocks zu identifizieren, da die Namen in der Ausgabe von /d/sync und Fehlerberichten erscheinen.

ANativeWindow-Integration

ANativeWindow ist zaunbewusst. dequeueBuffer , queueBuffer und cancelBuffer haben Fence-Parameter.

OpenGL ES-Integration

Die OpenGL ES-Synchronisierungsintegration basiert auf zwei EGL-Erweiterungen:

  • EGL_ANDROID_native_fence_sync bietet eine Möglichkeit, native Android-Fence-Dateideskriptoren in EGLSyncKHR Objekte einzubinden oder zu erstellen.
  • EGL_ANDROID_wait_sync ermöglicht Verzögerungen auf der GPU-Seite statt auf der CPU-Seite, sodass die GPU auf EGLSyncKHR wartet. Die Erweiterung EGL_ANDROID_wait_sync ist mit der Erweiterung EGL_KHR_wait_sync identisch.

Um diese Erweiterungen unabhängig zu verwenden, implementieren Sie die Erweiterung EGL_ANDROID_native_fence_sync zusammen mit der zugehörigen Kernel-Unterstützung. Aktivieren Sie als Nächstes die Erweiterung EGL_ANDROID_wait_sync in Ihrem Treiber. Die Erweiterung EGL_ANDROID_native_fence_sync besteht aus einem eindeutigen nativen Fence-Objekttyp EGLSyncKHR . Daher gelten Erweiterungen, die für vorhandene EGLSyncKHR Objekttypen gelten, nicht unbedingt für EGL_ANDROID_native_fence -Objekte, wodurch unerwünschte Interaktionen vermieden werden.

Die Erweiterung EGL_ANDROID_native_fence_sync verwendet ein entsprechendes natives Fence-Dateideskriptorattribut, das nur zum Zeitpunkt der Erstellung festgelegt werden kann und nicht direkt von einem vorhandenen Synchronisierungsobjekt abgefragt werden kann. Dieses Attribut kann auf einen von zwei Modi eingestellt werden:

  • Ein gültiger Fence-Dateideskriptor umschließt einen vorhandenen nativen Android-Fence-Dateideskriptor in ein EGLSyncKHR Objekt.
  • -1 erstellt einen nativen Android-Fence-Dateideskriptor aus einem EGLSyncKHR Objekt.

Verwenden Sie den Funktionsaufruf DupNativeFenceFD() , um das EGLSyncKHR Objekt aus dem nativen Android-Fence-Dateideskriptor zu extrahieren. Dies hat das gleiche Ergebnis wie die Abfrage des Set-Attributs, unterliegt jedoch der Konvention, dass der Empfänger den Zaun schließt (daher der Duplikatvorgang). Schließlich wird durch die Zerstörung des EGLSyncKHR Objekts das interne Zaunattribut geschlossen.

Hardware Composer-Integration

Der Hardware Composer verarbeitet drei Arten von Synchronisierungsgrenzen:

  • Erfassungszäune werden zusammen mit Eingabepuffern an die Aufrufe setLayerBuffer und setClientTarget übergeben. Diese stellen einen ausstehenden Schreibvorgang in den Puffer dar und müssen ein Signal senden, bevor der SurfaceFlinger oder der HWC versucht, aus dem zugehörigen Puffer zu lesen, um eine Komposition durchzuführen.
  • Freigabezäune werden nach dem Aufruf von presentDisplay mithilfe des getReleaseFences -Aufrufs abgerufen. Diese stellen einen ausstehenden Lesevorgang aus dem vorherigen Puffer auf derselben Ebene dar. Ein Freigabezaun signalisiert, wenn der HWC den vorherigen Puffer nicht mehr verwendet, weil der aktuelle Puffer den vorherigen Puffer auf dem Display ersetzt hat. Freigabezäune werden zusammen mit den vorherigen Puffern, die während der aktuellen Komposition ersetzt werden, an die App zurückgegeben. Die App muss warten, bis ein Freigabezaun signalisiert, bevor sie neue Inhalte in den Puffer schreibt, der an sie zurückgegeben wurde.
  • Im Rahmen des Aufrufs von presentDisplay werden vorhandene Zäune zurückgegeben, einer pro Frame. Aktuelle Zäune stellen dar, wann die Komposition dieses Frames abgeschlossen ist oder alternativ, wenn das Kompositionsergebnis des vorherigen Frames nicht mehr benötigt wird. Bei physischen Anzeigen gibt presentDisplay aktuelle Zäune zurück, wenn der aktuelle Frame auf dem Bildschirm erscheint. Nachdem die aktuellen Zäune zurückgegeben wurden, ist es sicher, erneut in den SurfaceFlinger-Zielpuffer zu schreiben, falls zutreffend. Bei virtuellen Anzeigen werden aktuelle Zäune zurückgegeben, wenn das Lesen aus dem Ausgabepuffer sicher ist.