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. Das Framework ermöglicht auch die Übergabe von Synchronisierungsprimitiven zwischen Treibern vom Kernel in den Nutzerbereich und zwischen Nutzerbereichsprozessen.
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 einem Fence, der angibt, wann die GPU-Arbeit abgeschlossen ist, an den Window Compositor übergeben. Der Window Compositor beginnt 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 Displaycontroller 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 Debugging zu erleichtern.
Explizite Synchronisierung
Durch die explizite 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 explizite Synchronisierung bietet unter anderem folgende Vorteile:
- Weniger Verhaltensvariationen zwischen Geräten
- Bessere Unterstützung beim Debugging
- 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. für einen GL-Kontext, einen Display-Controller oder einen 2D-Blitter. sync_timeline
counts
jobs submitted to the kernel for a particular piece of hardware.
sync_timeline
bietet Garantien für die Reihenfolge der Vorgänge und ermöglicht hardwarespezifische Implementierungen.
Beachte die folgenden Richtlinien, wenn du sync_timeline
implementierst:
- Geben Sie aussagekräftige Namen für alle Treiber, Zeitachsen und Fences an, um das Debugging zu vereinfachen.
- Implementieren Sie die Operatoren
timeline_value_str
undpt_value_str
in Zeitachsen, um die Debugging-Ausgabe lesbarer zu machen. - Implementieren Sie die Fill-Methode
driver_data
, um Userspace-Bibliotheken wie der GL-Bibliothek bei Bedarf Zugriff auf private Zeitachsendaten zu gewähren. Mitdata_driver
können Anbieter Informationen zum unveränderlichensync_fence
undsync_pts
übergeben, um darauf basierende Befehlszeilen zu erstellen. - Der Userspace darf keinen Fence explizit erstellen oder signalisieren. Das explizite Erstellen von Signalen/Fences führt zu einem Denial-of-Service-Angriff, der die Pipelinefunktionen unterbricht.
- Greifen Sie nicht explizit auf die Elemente
sync_timeline
,sync_pt
odersync_fence
zu. Die API bietet alle erforderlichen Funktionen.
sync_pt
sync_pt
ist ein einzelner Wert oder Punkt auf einer sync_timeline
. Ein Punkt kann drei Status haben: aktiv, signalisiert und Fehler. Punkte beginnen im aktiven Status und gehen in die Status „Signaliert“ oder „Fehler“ über. Wenn ein Bild-Consumer beispielsweise keinen Puffer mehr benötigt, wird ein sync_pt
signalisiert, damit ein Bild-Producer 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
-Übergeordneten Elemente haben (z. B. für den Displaycontroller und die GPU). sync_fence
, sync_pt
und sync_timeline
sind die wichtigsten Primitiven, mit denen Treiber und Userspace ihre Abhängigkeiten kommunizieren. Wenn ein Fence signalisiert wird, sind alle Befehle, die vor dem Fence ausgegeben wurden, garantiert abgeschlossen, da der Kerneltreiber oder Hardwareblock Befehle in der richtigen Reihenfolge ausführt.
Das Synchronisierungs-Framework ermöglicht es mehreren Consumern oder Producers, zu signalisieren, wenn sie einen Puffer nicht mehr benötigen. Die Abhängigkeitsinformationen werden dabei über einen Funktionsparameter übermittelt. Zäune werden durch einen Dateideskriptor gesichert und vom Kernel- in den Nutzerbereich übergeben. Ein Fence kann beispielsweise zwei sync_pt
-Werte enthalten, die angeben, wann zwei separate Bild-Consumer einen Puffer gelesen haben. Wenn das Signal für die Trennlinie gegeben wird, wissen die Bildproduzenten, dass beide Nutzer die Inhalte angesehen haben.
Zäune, z. B. sync_pt
-Werte, sind anfangs 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 Fehlerstatus wechselt, hat das gesamte sync_fence
einen Fehlerstatus.
Die Mitgliedschaft in einem sync_fence
kann nach der Erstellung des Geofence nicht mehr geändert werden. Wenn Sie mehr als einen Punkt in einem Zaun erhalten möchten, werden Punkte aus zwei verschiedenen Zäunen in einen dritten Zaun eingefügt.
Wenn einer dieser Punkte im ursprünglichen Fence signalisiert wurde und der andere nicht, wird auch der dritte Fence nicht signalisiert.
Für die Implementierung der expliziten Synchronisierung müssen Sie Folgendes angeben:
- Ein Kernel-Space-Subsystem, das das Synchronisierungs-Framework für einen bestimmten Hardwaretreiber implementiert. Treiber, die auf den Fence-Status achten müssen, sind in der Regel alle, die auf den Hardware Composer zugreifen oder mit ihm kommunizieren.
Zu den wichtigsten Dateien gehören:
- 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
- Kernimplementierung:
- Der Anbieter muss die entsprechenden Synchronisations-Fences als Parameter für die Funktionen
validateDisplay()
undpresentDisplay()
im HAL bereitstellen. - Zwei GL-Erweiterungen im Zusammenhang mit Fences (
EGL_ANDROID_native_fence_sync
undEGL_ANDROID_wait_sync
) und Unterstützung für Fences im Grafiktreiber.
Fallstudie: Displaytreiber implementieren
Wenn Sie die API verwenden möchten, die die Synchronisierungsfunktion unterstützt, müssen Sie einen Displaytreiber mit einer Displaypufferfunktion entwickeln. Bevor das Synchronisierungs-Framework existierte, empfing 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 display_buffer
-Funktion komplexer. Wenn ein Puffer angezeigt wird, ist er mit einem Fence verknüpft, der angibt, wann der Puffer bereit ist. Sie können die Arbeit in die Warteschlange stellen und starten, sobald die Sperre aufgehoben wird.
Das Anstellen und Initiieren von Arbeit nach dem Entfernen des Zauns blockiert nichts. Sie geben sofort Ihren eigenen Fence zurück, der garantiert, wann der Puffer nicht mehr auf dem Display angezeigt wird. Wenn 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 beschrieben, wie das Kernel-Space-Synchronisierungs-Framework in die Userspace-Teile des Android-Frameworks und die Treiber integriert wird, die miteinander kommunizieren müssen. Kernel-Space-Objekte werden im User-Space als Dateideskriptoren dargestellt.
Konventionen für die Integration
Halten Sie sich an die Android-HAL-Schnittstellenkonventionen:
- Wenn die API einen Dateideskriptor bereitstellt, der auf ein
sync_pt
verweist, muss der Treiber des Anbieters oder die HAL, die die API verwendet, den Dateideskriptor schließen. - Wenn der Anbieter-Treiber oder die HAL einen Dateideskriptor mit einem
sync_pt
an eine API-Funktion übergibt, darf der Anbieter-Treiber oder die HAL den Dateideskriptor nicht schließen. - Wenn der Anbieter-Treiber oder die HAL den Fence-Datei-Deskriptor weiterhin verwenden soll, muss der Deskriptor dupliziert werden.
Ein Fence-Objekt wird jedes Mal umbenannt, wenn es BufferQueue durchläuft.
Durch die Unterstützung von Kernel-Fences können Fences String-Namen haben. Das Synchronisierungs-Framework verwendet den Fenster- und Pufferindex, der in die Warteschlange gestellt wird, um den Fence zu benennen, z. B. SurfaceView:0
. Das ist beim Debuggen hilfreich, um die Quelle eines Deadlocks zu ermitteln, da die Namen in der Ausgabe von /d/sync
und in Fehlerberichten angezeigt werden.
ANativeWindow-Integration
ANativeWindow ist Fence-kompatibel. dequeueBuffer
, queueBuffer
und cancelBuffer
haben Fence-Parameter.
OpenGL ES-Integration
Die OpenGL ES-Synchronisierung basiert auf zwei EGL-Erweiterungen:
EGL_ANDROID_native_fence_sync
bietet eine Möglichkeit, native Android-Dateideskriptoren für Zäune inEGLSyncKHR
-Objekten zu umschließen oder zu erstellen.- Mit
EGL_ANDROID_wait_sync
werden GPU-seitige anstelle von CPU-seitigen Stalls zugelassen, sodass die GPU aufEGLSyncKHR
wartet. Die ErweiterungEGL_ANDROID_wait_sync
ist mit der ErweiterungEGL_KHR_wait_sync
identisch.
Wenn Sie diese Erweiterungen unabhängig voneinander verwenden möchten, müssen Sie die EGL_ANDROID_native_fence_sync
-Erweiterung zusammen mit der zugehörigen Kernel-Unterstützung implementieren. Aktivieren Sie als Nächstes die EGL_ANDROID_wait_sync
-Erweiterung in Ihrem Treiber. Die EGL_ANDROID_native_fence_sync
-Erweiterung 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 EGL_ANDROID_native_fence_sync
-Erweiterung verwendet ein entsprechendes natives Attribut für den Dateideskriptor des Fences, das nur bei der Erstellung festgelegt werden kann und nicht direkt von einem vorhandenen Synchronisierungsobjekt abgefragt werden kann. Für dieses Attribut kann einer von zwei Modi festgelegt werden:
- Ein gültiger Fence-Datei-Deskriptor umschließt einen vorhandenen nativen Android-Fence-Datei-Deskriptor in einem
EGLSyncKHR
-Objekt. - Mit -1 wird ein nativer Android-Fence-Dateideskriptor aus einem
EGLSyncKHR
-Objekt erstellt.
Verwenden Sie den DupNativeFenceFD()
-Funktionsaufruf, um das EGLSyncKHR
-Objekt aus dem nativen Android-Fence-Dateideskriptor zu extrahieren.
Dies hat dasselbe Ergebnis wie das Abfragen des Attributs „set“, entspricht aber der Konvention, dass der Empfänger den Fence schließt (daher der doppelte Vorgang). Wenn das EGLSyncKHR
-Objekt zerstört wird, wird das interne Fence-Attribut geschlossen.
Integration von Hardware Composer
Der Hardware Composer verarbeitet drei Arten von Synchronisations-Fences:
- Acquire-Fences werden zusammen mit Eingabepuffern an die Aufrufe
setLayerBuffer
undsetClientTarget
übergeben. Diese stellen einen ausstehenden Schreibvorgang in den Puffer dar und müssen signalisiert werden, bevor SurfaceFlinger oder HWC versucht, aus dem zugehörigen Puffer zu lesen, um die Komposition durchzuführen. - Release-Fences werden nach dem Aufruf von
presentDisplay
mit dem Aufruf vongetReleaseFences
abgerufen. Sie stellen einen ausstehenden Lesevorgang aus dem vorherigen Puffer auf derselben Ebene dar. Ein Release-Fence signalisiert, wann der HWC den vorherigen Puffer nicht mehr verwendet, weil 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 ein Release-Fence-Signal eingeht, bevor sie neue Inhalte in den Puffer schreibt, der ihr zurückgegeben wurde. - Präsentations-Fences werden als Teil des Aufrufs von
presentDisplay
zurückgegeben, eines pro Frame. 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 gibtpresentDisplay
vorhandene Fences zurück, wenn der aktuelle Frame auf dem Bildschirm angezeigt wird. Nachdem die Present-Fences zurückgegeben wurden, kann wieder in den SurfaceFlinger-Zielpuffer geschrieben werden, sofern dies zutrifft. Bei virtuellen Displays werden Present-Fences zurückgegeben, wenn es sicher ist, aus dem Ausgabepuffer zu lesen.