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
undpt_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änderlichensync_fence
undsync_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
odersync_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
- Core-Implementierung:
- Der Anbieter muss die geeigneten Synchronisations-Zäune als Parameter für die
validateDisplay()
undpresentDisplay()
Funktionen in der HAL bereitstellen. - Zwei fence-bezogene GL-Erweiterungen (
EGL_ANDROID_native_fence_sync
undEGL_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. Zum Beispiel:
/* * 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 inEGLSyncKHR
Objekten einzuschließen oder zu erstellen. -
EGL_ANDROID_wait_sync
ermöglicht eher GPU-seitige Verzögerungen als CPU-seitige, wodurch die GPU aufEGLSyncKHR
warten muss. Die ErweiterungEGL_ANDROID_wait_sync
ist mit der ErweiterungEGL_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
undsetClientTarget
. 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 demgetReleaseFences
-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 gibtpresentDisplay
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.