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
undpt_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änderlichensync_fence
undsync_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
odersync_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
- Kernimplementierung:
- Der Anbieter muss die entsprechenden Synchronisationsgrenzen als Parameter für die Funktionen
validateDisplay()
undpresentDisplay()
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 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, solange 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 inEGLSyncKHR
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 aufEGLSyncKHR
wartet. Die ErweiterungEGL_ANDROID_wait_sync
ist mit der ErweiterungEGL_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
undsetClientTarget
ü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 desgetReleaseFences
-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 gibtpresentDisplay
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.