Synchronisierungs-Framework

Das Synchronisierungs-Framework beschreibt explizit Abhängigkeiten zwischen verschiedenen asynchronen Vorgängen im Android-Grafiksystem. Das Framework bietet eine API, mit der Komponenten angeben können, wann Puffer freigegeben werden. Außerdem können Synchronisierungsprimitive zwischen Treibern vom Kernel zum Userspace und zwischen Userspace-Prozessen selbst übergeben werden.

Eine Anwendung kann beispielsweise Arbeit in die Warteschlange stellen, die auf der GPU ausgeführt werden soll. Die GPU beginnt mit dem Zeichnen dieses Bildes. Obwohl das Bild noch nicht in den Arbeitsspeicher gezeichnet wurde, wird der Pufferzeiger zusammen mit einer Fence an den Window Compositor übergeben, die angibt, wann die GPU-Arbeit abgeschlossen ist. Der Window Compositor beginnt mit der Verarbeitung und übergibt die Arbeit an den Display Controller. Auf ähnliche Weise wird die CPU-Arbeit vorzeitig erledigt. Sobald die GPU fertig ist, zeigt der Display Controller das Bild sofort an.

Mit dem Synchronisierungs-Framework können Implementierer auch Synchronisierungsressourcen in ihren eigenen Hardwarekomponenten verwenden. Außerdem bietet das Framework Einblick in die Grafikpipeline, um die Fehlerbehebung zu erleichtern.

Explizite Synchronisierung

Mit der expliziten Synchronisierung können Ersteller und Nutzer von Grafikpuffern signalisieren, wann sie einen Puffer nicht mehr verwenden. Die explizite Synchronisierung wird im Kernel-Space implementiert.

Die Vorteile der expliziten Synchronisierung sind:

  • Weniger Verhaltensunterschiede zwischen Geräten
  • Bessere Unterstützung bei der Fehlerbehebung
  • Verbesserte Testmesswerte

Das Synchronisierungs-Framework hat drei Objekttypen:

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

sync_timeline ist eine monoton steigende Zeitachse, die Anbieter für jede Treiberinstanz implementieren sollten, z. B. einen GL-Kontext, einen Display Controller oder einen 2D-Blitter. sync_timeline zählt Jobs, die für eine bestimmte Hardware an den Kernel gesendet wurden. sync_timeline sorgt für die Reihenfolge der Vorgänge und ermöglicht hardwarespezifische Implementierungen.

Beachten Sie bei der Implementierung von sync_timeline die folgenden Richtlinien:

  • Geben Sie allen Treibern, Zeitachsen und Fences aussagekräftige Namen, um die Fehlerbehebung zu vereinfachen.
  • Implementieren Sie die timeline_value_str und pt_value_str Operatoren in Zeitachsen, um die Debugging-Ausgabe lesbarer zu machen.
  • Implementieren Sie die Füllung driver_data, um Userspace-Bibliotheken, wie die GL-Bibliothek, bei Bedarf Zugriff auf private Zeitachsendaten zu gewähren. data_driver ermöglicht es Anbietern, Informationen zu den unveränderlichen sync_fence und sync_pts zu übergeben, um Befehlszeilen darauf aufzubauen.
  • Erlauben Sie nicht, dass der Userspace explizit eine Fence erstellt oder signalisiert. Das explizite Erstellen von Signalen/Fences führt zu einem Denial-of-Service-Angriff, der die Pipeline-Funktionalität unterbricht.
  • Greifen Sie nicht explizit auf die Elemente sync_timeline, sync_pt oder sync_fence zu. Die API bietet alle erforderlichen Funktionen.

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 ein Bildnutzer beispielsweise keinen Puffer mehr benötigt, wird ein sync_pt signalisiert, damit ein Bildersteller weiß, dass er wieder in den Puffer schreiben kann.

sync_fence

sync_fence ist eine Sammlung von sync_pt-Werten, die oft unterschiedliche sync_timeline-Eltern haben (z. B. für den Display Controller und die GPU). sync_fence, sync_pt und sync_timeline sind die wichtigsten Primitiven, mit denen Treiber und Userspace ihre Abhängigkeiten kommunizieren. Wenn eine Fence signalisiert wird, sind alle Befehle, die vor der Fence ausgegeben wurden, abgeschlossen, da der Kerneltreiber oder der Hardwareblock Befehle in der Reihenfolge ausführt.

Mit dem Synchronisierungs-Framework können mehrere Nutzer oder Ersteller signalisieren, wann sie einen Puffer nicht mehr verwenden. Die Abhängigkeitsinformationen werden mit einem Funktionsparameter übergeben. Fences werden durch einen Dateideskriptor gesichert und vom Kernel-Space zum Userspace übergeben. Eine Fence kann beispielsweise zwei sync_pt-Werte enthalten, die angeben, wann zwei separate Bildnutzer einen Puffer nicht mehr lesen. Wenn die Fence signalisiert wird, wissen die Bildersteller, dass beide Nutzer die Nutzung abgeschlossen haben.

Fences beginnen wie sync_pt-Werte im aktiven Zustand und ändern ihren Zustand basierend auf dem Zustand ihrer Punkte. Wenn alle sync_pt-Werte signalisiert werden, wird auch die sync_fence signalisiert. Wenn ein sync_pt in einen Fehlerzustand gerät, hat die gesamte sync_fence einen Fehlerzustand.

Die Mitgliedschaft in einer sync_fence ist nach der Erstellung der Fence unveränderlich. Um mehr als einen Punkt in einer Fence zu erhalten, wird eine Zusammenführung durchgeführt, bei der Punkte aus zwei verschiedenen Fences zu einer dritten Fence hinzugefügt werden. Wenn einer dieser Punkte in der ursprünglichen Fence signalisiert wurde und der andere nicht, wird auch die dritte Fence nicht signalisiert.

Geben Sie Folgendes an, um die explizite Synchronisierung zu implementieren:

  • Ein Kernel-Space-Subsystem, das das Synchronisierungs-Framework für einen bestimmten Hardwaretreiber implementiert. Treiber, die Fence-fähig sein müssen, sind im Allgemeinen alle, die auf den Hardware Composer (HWC) zugreifen oder mit ihm kommunizieren. Wichtige Dateien:
    • Kernimplementierung:
      • kernel/common/include/linux/sync.h
      • kernel/common/drivers/base/sync.c
    • Dokumentation unter kernel/common/Documentation/sync.txt
    • Bibliothek für die Kommunikation mit dem Kernel-Space in platform/system/core/libsync
  • Der Anbieter muss die entsprechenden Synchronisierungs-Fences als Parameter an die Funktionen validateDisplay() und presentDisplay() in der Hardwareabstraktionsschicht (HAL) übergeben.
  • Zwei Fence-bezogene GL-Erweiterungen (EGL_ANDROID_native_fence_sync und EGL_ANDROID_wait_sync) und Fence-Unterstützung im Grafiktreiber.

Fallstudie: Displaytreiber implementieren

Wenn Sie die API verwenden möchten, die die Synchronisierungsfunktion unterstützt, entwickeln Sie einen Displaytreiber mit einer Displaypufferfunktion. Bevor das Synchronisierungs-Framework existierte, erhielt diese Funktion dma-buf-Objekte, platzierte diese Puffer auf dem Display und blockierte, während der Puffer sichtbar war. 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 Synchronisierungs-Framework ist die Funktion display_buffer komplexer. Während ein Puffer auf dem Display platziert wird, wird er mit einer Fence verknüpft, die angibt, wann der Puffer bereit ist. Sie können die Arbeit in die Warteschlange stellen und starten, nachdem die Fence gelöscht wurde.

Das In-die-Warteschlange-Stellen und Starten der Arbeit nach dem Löschen der Fence blockiert nichts. Sie geben sofort Ihre eigene Fence zurück, die signalisiert, wann der Puffer nicht mehr auf dem Display angezeigt wird. Wenn Sie Puffer in die Warteschlange stellen, listet der Kernel Abhängigkeiten mit dem Synchronisierungs-Framework 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);

Synchronisierungs-Integration

In diesem Abschnitt wird erläutert, wie Sie das Kernel-Space-Synchronisierungs-Framework in Userspace-Teile des Android-Frameworks und die Treiber integrieren, die miteinander kommunizieren müssen. Kernel-Space-Objekte werden im Userspace als Dateideskriptoren dargestellt.

Integrationskonventionen

Beachten 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 Treiber des Anbieters oder die HAL einen Dateideskriptor, der einen sync_pt enthält, an eine API-Funktion übergibt, darf der Treiber des Anbieters oder die HAL den Dateideskriptor nicht schließen.
  • Wenn der Dateideskriptor der Fence weiterhin verwendet werden soll, muss der Treiber des Anbieters oder die HAL den Deskriptor duplizieren.

Ein Fence-Objekt wird jedes Mal umbenannt, wenn es BufferQueue durchläuft. Die Kernel-Fence-Unterstützung ermöglicht es, dass Fences Strings für Namen haben. Daher verwendet das Synchronisierungs-Framework den Fensternamen und den Pufferindex, der in die Warteschlange gestellt wird, um die Fence zu benennen, z. B. SurfaceView:0. Dies ist bei der Fehlerbehebung hilfreich, um die Quelle einer Deadlock zu identifizieren, da die Namen in der Ausgabe von /d/sync und in Fehlerberichten angezeigt werden.

ANativeWindow-Integration

ANativeWindow ist Fence-fähig. dequeueBuffer, queueBuffer und cancelBuffer haben Fence-Parameter.

OpenGL ES-Integration

Die OpenGL ES-Synchronisierungs-Integration basiert auf zwei EGL-Erweiterungen:

  • EGL_ANDROID_native_fence_sync bietet eine Möglichkeit, native Android-Fence-Dateideskriptoren in EGLSyncKHR Objekte einzuschließen oder zu erstellen.
  • EGL_ANDROID_wait_sync ermöglicht GPU-seitige anstelle von CPU-seitigen Stalls, sodass die GPU auf EGLSyncKHR wartet. Die EGL_ANDROID_wait_sync Erweiterung entspricht der EGL_KHR_wait_sync Erweiterung.

Wenn Sie diese Erweiterungen unabhängig voneinander verwenden möchten, 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 separaten 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 bei der Erstellung festgelegt werden kann und nicht direkt aus einem vorhandenen Synchronisierungsobjekt abgefragt werden kann. Für dieses Attribut kann einer von zwei Modi festgelegt werden:

  • Ein gültiger Fence-Dateideskriptor schließt einen vorhandenen nativen Android-Fence-Dateideskriptor in ein EGLSyncKHR-Objekt ein.
  • -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 dasselbe Ergebnis wie das Abfragen des festgelegten Attributs, entspricht aber der Konvention, dass der Empfänger die Fence schließt (daher der Duplizierungsvorgang). Wenn Sie das EGLSyncKHR-Objekt löschen, wird schließlich das interne Fence-Attribut geschlossen.

Hardware Composer-Integration

Der HWC verarbeitet drei Arten von Synchronisierungs-Fences:

  • Acquire-Fences werden zusammen mit Eingabepuffern an die Aufrufe setLayerBuffer und setClientTarget übergeben. Sie stellen einen ausstehenden Schreibvorgang in den Puffer dar und müssen signalisieren, bevor der SurfaceFlinger oder der HWC versucht, aus dem zugehörigen Puffer zu lesen, um die Komposition durchzuführen.
  • Release-Fences werden nach dem Aufruf von presentDisplay mit dem getReleaseFences Aufruf abgerufen. Sie stellen einen ausstehenden Lesevorgang aus dem vorherigen Puffer auf derselben Ebene dar. Eine Release-Fence signalisiert, wenn der HWC den vorherigen Puffer nicht mehr verwendet, da der aktuelle Puffer den vorherigen Puffer auf dem Display ersetzt hat. Release-Fences werden zusammen mit den vorherigen Puffern, die während der aktuellen Komposition ersetzt werden, an die App zurückgegeben. Die App muss warten, bis eine Release-Fence signalisiert, bevor sie neue Inhalte in den Puffer schreibt, der an sie zurückgegeben wurde.
  • Present-Fences werden als Teil des Aufrufs von presentDisplay für jeden Frame zurückgegeben. Present-Fences geben an, wann die Komposition dieses Frames abgeschlossen ist oder wann das Kompositionsergebnis des vorherigen Frames nicht mehr benötigt wird. Bei physischen Displays gibt presentDisplay Present-Fences zurück, wenn der aktuelle Frame auf dem Bildschirm angezeigt wird. Nachdem Present-Fences zurückgegeben wurden, können Sie gegebenenfalls wieder in den SurfaceFlinger-Zielpuffer schreiben. Bei virtuellen Displays werden Present-Fences zurückgegeben, wenn es sicher ist, aus dem Ausgabepuffer zu lesen.