Synchronisations-Framework

Das Synchronisationsframework beschreibt explizit Abhängigkeiten zwischen verschiedenen asynchronen Operationen im Android-Grafiksystem. Das Framework stellt eine API bereit, mit der Komponenten angeben können, wann Puffer freigegeben werden. Das Framework ermöglicht auch die Weitergabe von Synchronisationsprimitiven zwischen Treibern vom Kernel zum Userspace und zwischen Userspace-Prozessen selbst.

Beispielsweise kann eine Anwendung in der GPU auszuführende Arbeit in eine Warteschlange stellen. Die GPU beginnt mit dem Zeichnen dieses Bildes. Obwohl das Bild noch nicht in den Speicher gezeichnet wurde, wird der Pufferzeiger zusammen mit einem Zaun an den Fensterkompositor übergeben, der anzeigt, wann die GPU-Arbeit abgeschlossen sein wird. Der Window-Compositor beginnt vorzeitig mit der Verarbeitung und übergibt die Arbeit an den Display-Controller. Auf ähnliche Weise wird die CPU-Arbeit im Voraus erledigt. Sobald die GPU fertig ist, zeigt der Display-Controller sofort das Bild an.

Das Synchronisations-Framework ermöglicht es Implementierern auch, Synchronisationsressourcen in ihren eigenen Hardwarekomponenten zu nutzen. Schließlich bietet das Framework Einblick in die Grafikpipeline, um beim Debuggen zu helfen.

Explizite Synchronisierung

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

Zu den Vorteilen der expliziten Synchronisierung gehören:

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

Das Synchronisierungs-Framework hat drei Objekttypen:

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

sync_timeline ist eine monoton ansteigende Zeitachse, die Anbieter für jede Treiberinstanz implementieren sollten, z. B. einen GL-Kontext, einen Anzeigecontroller oder einen 2D-Blitter. sync_timeline zählt Jobs, die für eine bestimmte Hardware an den Kernel gesendet werden. sync_timeline bietet Garantien über die Reihenfolge der Operationen und ermöglicht hardwarespezifische Implementierungen.

Befolgen Sie diese Richtlinien bei der Implementierung sync_timeline :

  • Geben Sie nützliche Namen für alle Treiber, Zeitachsen und Zäune 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 driver_data , um Userspace-Bibliotheken wie der GL-Bibliothek bei Bedarf Zugriff auf private Zeitachsendaten zu gewähren. data_driver ermöglicht es Anbietern, Informationen über die unveränderlichen sync_fence und sync_pts , 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 anhält.
  • Greifen Sie nicht explizit auf die Elemente sync_timeline , sync_pt oder sync_fence . Die API stellt alle erforderlichen 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 die signalisierten oder Fehlerzustände über. Wenn beispielsweise ein Bildkonsument keinen Puffer mehr benötigt, wird ein sync_pt signalisiert, damit ein Bildproduzent weiß, dass es in Ordnung ist, wieder in den Puffer zu schreiben.

sync_fence

sync_fence ist eine Sammlung von sync_pt Werten, die häufig unterschiedliche sync_timeline Eltern haben (z. B. für den Anzeigecontroller 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, sind alle vor dem Fence ausgegebenen Befehle garantiert vollständig, da der Kerneltreiber oder der Hardwareblock die Befehle der Reihe nach ausführt.

Das Sync-Framework ermöglicht es mehreren Consumern oder Producern, zu signalisieren, wann sie fertig sind, indem sie einen Puffer verwenden und die Abhängigkeitsinformationen mit einem Funktionsparameter übermitteln. Zäune werden von einem Dateideskriptor unterstützt und vom Kernel-Space zum Userspace weitergegeben. Beispielsweise kann ein Fence 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 Verbraucher mit dem Konsumieren fertig sind.

Zäune beginnen wie sync_pt Werte aktiv und ändern den Status basierend auf dem Status ihrer Punkte. Wenn alle sync_pt Werte signalisiert werden, wird der 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 von 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 der dritte Zaun ebenfalls nicht in einem signalisierten Zustand.

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

  • Ein Kernelspace-Subsystem, das das Synchronisationsframework für einen bestimmten Hardwaretreiber implementiert. Treiber, die Fence-bewusst sein müssen, sind im Allgemeinen alles, was auf den Hardware Composer zugreift oder mit ihm kommuniziert. Zu den Schlüsseldateien gehören:
    • Core-Implementierung:
      • 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 geeigneten Synchronisations-Zäune als Parameter für die validateDisplay() und presentDisplay() Funktionen 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 eines Anzeigetreibers

Um die API zu verwenden, die die Synchronisierungsfunktion unterstützt, entwickeln Sie einen Anzeigetreiber, der eine Anzeigepufferfunktion hat. Bevor das Synchronisations-Framework existierte, würde diese Funktion dma-buf Objekte empfangen, diese Puffer auf dem Display anzeigen und blockieren, während der Puffer sichtbar war. Beispielsweise:

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

Beim Synchronisations-Framework ist die Funktion display_buffer komplexer. Beim Ausstellen eines Puffers wird der Puffer mit einem Zaun verknüpft, der anzeigt, wann der Puffer fertig sein wird. Sie können sich anstellen und mit der Arbeit beginnen, nachdem der Zaun freigegeben wurde.

Das Anstehen und Beginnen der Arbeit nach dem Freimachen des Zauns blockiert nichts. Sie geben sofort Ihren eigenen Zaun zurück, der garantiert, wann der Puffer von der Anzeige entfernt ist. Während Sie Puffer in die Warteschlange stellen, listet der Kernel Abhängigkeiten mit dem Synchronisations-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);

Sync-Integration

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

Integrationskonventionen

Befolgen Sie die Konventionen für die 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 Anbietertreiber oder die HAL einen Dateideskriptor, der ein sync_pt enthält, an eine API-Funktion weitergibt, darf der Anbietertreiber oder die HAL den Dateideskriptor nicht schließen.
  • Um den Deskriptor der Fence-Datei weiterhin zu verwenden, muss der Herstellertreiber oder die HAL den Deskriptor duplizieren.

Ein Fence-Objekt wird jedes Mal umbenannt, wenn es BufferQueue passiert. Die Kernel-Fence-Unterstützung ermöglicht Zäunen, Zeichenfolgen für Namen zu 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 Quelle 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 Objekten einzuschließen oder zu erstellen.
  • EGL_ANDROID_wait_sync ermöglicht eher GPU-seitige Verzögerungen als CPU-seitige, wodurch die GPU auf EGLSyncKHR warten muss. Die Erweiterung EGL_ANDROID_wait_sync ist mit der Erweiterung EGL_KHR_wait_sync .

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 EGL_ANDROID_wait_sync Erweiterung in Ihrem Treiber. Die EGL_ANDROID_native_fence_sync Erweiterung besteht aus einem eindeutigen EGLSyncKHR Objekttyp für den nativen Zaun. 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 EGL_ANDROID_native_fence_sync Erweiterung 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 einem 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, hält sich jedoch an die Konvention, dass der Empfänger den Zaun schließt (daher die doppelte Operation). Schließlich schließt das Zerstören des EGLSyncKHR Objekts das interne Fence-Attribut.

Hardware Composer-Integration

Der Hardware Composer verarbeitet drei Arten von Sync-Fences:

  • Erfassungszäune werden zusammen mit Eingabepuffern an die setLayerBuffer und setClientTarget . Diese stellen einen anstehenden Schreibvorgang in den Puffer dar und müssen signalisieren, bevor der SurfaceFlinger oder der HWC versucht, aus dem zugeordneten Puffer zu lesen, um eine Zusammensetzung durchzuführen.
  • Freigabezäune werden nach dem Aufruf von presentDisplay mit dem getReleaseFences -Aufruf abgerufen. Diese stellen einen anstehenden Lesevorgang aus dem vorherigen Puffer auf derselben Schicht dar. Ein Freigabezaun signalisiert, wenn der HWC den vorherigen Puffer nicht mehr verwendet, da der aktuelle Puffer den vorherigen Puffer auf der Anzeige 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.
  • Vorhandene Zäune werden als Teil des Aufrufs von presentDisplay zurückgegeben, einer pro Frame. Vorhandene Zäune stellen dar, wann die Zusammensetzung dieses Rahmens abgeschlossen ist, oder alternativ, wann das Zusammensetzungsergebnis des vorherigen Rahmens nicht länger benötigt wird. Bei physischen Anzeigen gibt presentDisplay aktuelle Zäune zurück, wenn das aktuelle Bild auf dem Bildschirm erscheint. Nachdem vorhandene Zäune zurückgegeben wurden, können Sie gegebenenfalls erneut in den SurfaceFlinger-Zielpuffer schreiben. Bei virtuellen Anzeigen werden vorhandene Zäune zurückgegeben, wenn es sicher ist, aus dem Ausgabepuffer zu lesen.