Das Synchronisierungsframework beschreibt explizit Abhängigkeiten zwischen verschiedenen asynchronen Vorgängen im Android-Grafiksystem. Das Framework stellt eine API bereit, mit der Komponenten angeben können, wann Puffer freigegeben werden. Außerdem ermöglicht das Framework die Weitergabe von Synchronisierungsprimitiven zwischen Treibern vom Kernel an den Userspace und zwischen Userspace-Prozessen selbst.
Eine Anwendung kann beispielsweise die Arbeit in der GPU in die Warteschlange stellen. 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 an den Fenster-Compositor übergeben, der angibt, wann die GPU-Arbeit abgeschlossen ist. Die Fenstererstellung beginnt im Voraus mit der Verarbeitung und übergibt die Arbeit an den Display-Controller. Ähnlich wird die CPU-Arbeit im Voraus ausgeführt. Sobald die GPU fertig ist, zeigt der Display-Controller das Bild sofort an.
Mit dem Synchronisierungsframework können Implementierer auch Synchronisierungsressourcen in ihren eigenen Hardwarekomponenten nutzen. Schließlich bietet das Framework Einblicke in die Grafikpipeline, um das Debuggen zu erleichtern.
Explizite Synchronisierung
Durch die explizite Synchronisierung können Ersteller und Nutzer von Grafikpuffern signalisieren, wenn sie mit der Verwendung eines Puffers fertig sind. Die explizite Synchronisierung wird im Kernelbereich implementiert.
Zu den Vorteilen der expliziten Synchronisierung gehören:
- Weniger Verhaltensabweichungen zwischen Geräten
- Bessere Unterstützung bei der Fehlerbehebung
- Verbesserte Testmesswerte
Das Synchronisierungs-Framework umfasst 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. für einen GL-Kontext, einen Displaycontroller oder einen 2D-Blitter. sync_timeline
zählt die Jobs, die für eine bestimmte Hardware an den Kernel gesendet wurden.
sync_timeline
bietet Garantien für die Reihenfolge der Vorgänge und ermöglicht hardwarespezifische Implementierungen.
Beachten Sie die folgenden Richtlinien, wenn Sie sync_timeline
implementieren:
- Geben Sie nützliche Namen für alle Treiber, Zeitleisten und Begrenzungen an, um das Debuggen zu vereinfachen.
- Implementieren Sie die Operatoren
timeline_value_str
undpt_value_str
in Zeitleisten, um die Ausgabe der Fehlerbehebung übersichtlicher zu gestalten. - Implementieren Sie die Funktion „fill“
driver_data
, um Userspace-Bibliotheken wie die GL-Bibliothek bei Bedarf Zugriff auf private Zeitachsendaten zu gewähren. Mitdata_driver
können Anbieter Informationen zu den unveränderlichensync_fence
undsync_pts
übergeben, um darauf basierende Befehlszeilen zu erstellen. - Es darf nicht möglich sein, im Userspace explizit einen Zaun zu erstellen oder zu signalisieren. Das explizite Erstellen von Signalen/Grenzen führt zu einem Denial-of-Service-Angriff, der die Pipelinefunktionalität stoppt.
- Greifen Sie nicht explizit auf
sync_timeline
-,sync_pt
- odersync_fence
-Elemente 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 Status: aktiv, signalisiert und fehlerhaft. Punkte beginnen im aktiven Status und wechseln in den Status „Signalisiert“ oder „Fehler“. Wenn ein Image-Nutzer beispielsweise keinen Zwischenspeicher mehr benötigt, wird ein sync_pt
-Signal gesendet, damit der Image-Ersteller weiß, dass wieder in den Zwischenspeicher geschrieben werden kann.
sync_fence
sync_fence
ist eine Sammlung von sync_pt
-Werten, die oft unterschiedliche sync_timeline
-Eltern 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 Kernel-Treiber oder Hardwareblock Befehle in der richtigen Reihenfolge ausführt.
Mit dem Synchronisierungsframework können mehrere Abnehmer oder Erzeuger mithilfe eines Buffers signalisieren, dass sie fertig sind, und die Abhängigkeitsinformationen mit einem Funktionsparameter kommunizieren. Fences werden von einem Dateideskriptor unterstützt und vom Kernelbereich an den Userbereich übergeben. Ein Zaun kann beispielsweise zwei sync_pt
-Werte enthalten, die angeben, wann zwei separate Bildnutzer einen Zwischenspeicher gelesen haben. Wenn der Zaun signalisiert wird, wissen die Ersteller des Bildes, dass beide Nutzer die Daten nicht mehr benötigen.
Einschlüsse sind wie sync_pt
-Werte standardmäßig aktiv und ändern ihren Status je nach Status ihrer Punkte. Wenn alle sync_pt
-Werte signalisiert werden, wird auch 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 dem Erstellen des Zauns nicht mehr geändert werden. Wenn Sie mehr als einen Punkt in einem Zaun haben möchten, wird ein Zusammenführen durchgeführt, bei dem Punkte aus zwei verschiedenen Zäunen zu einem dritten Zaun hinzugefügt werden.
Wenn einer dieser Punkte im ursprünglichen Zaun signalisiert wurde und der andere nicht, ist auch der dritte Zaun nicht signalisiert.
Wenn Sie eine explizite Synchronisierung implementieren möchten, geben Sie Folgendes an:
- Ein Kernel-Subsystem, das das Synchronisierungsframework für einen bestimmten Hardwaretreiber implementiert. Treiber, die standortbewusst sein 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 zur Kommunikation mit dem Kernel-Bereich in
platform/system/core/libsync
- Kernimplementierung:
- Der Anbieter muss die entsprechenden Synchronisationsschranken als Parameter für die Funktionen
validateDisplay()
undpresentDisplay()
in der HAL bereitstellen. - Zwei grenzwertbezogene GL-Erweiterungen (
EGL_ANDROID_native_fence_sync
undEGL_ANDROID_wait_sync
) sowie Unterstützung für Grenzwerte im Grafiktreiber.
Fallstudie: Treiber für Display-Werbung implementieren
Entwickeln Sie einen Anzeigetreiber mit einer Anzeigepufferfunktion, um die API zu verwenden, die die Synchronisierungsfunktion unterstützt. Vor der Einführung des Synchronization Framework empfing diese Funktion dma-buf
-Objekte, legte diese Buffers auf dem Display ab und blockierte, während der Buffer 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 Synchronization Framework ist die display_buffer
-Funktion komplexer. Wenn ein Puffer angezeigt wird, wird er mit einem Zaun verknüpft, der angibt, wann der Puffer bereit ist. Sie können die Arbeit in die Warteschlange stellen und starten, sobald der Begrenzungsbereich aufgehoben wurde.
Wenn Sie die Warteschlange stellen und mit der Arbeit beginnen, nachdem der Zaun geräumt wurde, wird nichts blockiert. Sie stellen sofort Ihren eigenen Zaun wieder her. Dadurch wird sichergestellt, dass der Puffer nicht auf dem Display zu sehen ist. 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 Sie das Synchronisierungs-Framework im Kernelbereich mit den Userspace-Teilen des Android-Frameworks und den Treibern integrieren, die miteinander kommunizieren müssen. Kernel-Objekte werden im Userspace als Dateideskriptoren dargestellt.
Integrationskonventionen
Halten Sie sich an die Android-HAL-Schnittstellenkonventionen:
- Wenn die API einen Dateideskriptor bereitstellt, der sich auf eine
sync_pt
bezieht, 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 einen
sync_pt
enthält, an eine API-Funktion weitergibt, darf der Anbietertreiber oder die HAL den Dateideskriptor nicht schließen. - Damit der Deskriptor der Sperrdatei weiterhin verwendet werden kann, muss der Anbietertreiber oder die HAL den Deskriptor duplizieren.
Ein Zaunobjekt wird jedes Mal umbenannt, wenn es die BufferQueue durchläuft.
Dank der Unterstützung von Kernel-Sperren können Sperren Strings als Namen haben. Das synchrone Framework verwendet daher den Fensternamen und den Pufferindex, der in die Warteschlange gestellt wird, um die Sperre zu benennen, z. B. SurfaceView:0
. Dies ist hilfreich bei der Fehlerbehebung, um die Ursache eines Deadlocks zu ermitteln, da die Namen in der Ausgabe von /d/sync
und in Fehlerberichten erscheinen.
ANativeWindow-Integration
ANativeWindow ist zaunfähig. dequeueBuffer
, queueBuffer
und cancelBuffer
haben Fencing-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-Dateibeschreibungen inEGLSyncKHR
-Objekten zu verpacken oder zu erstellen.EGL_ANDROID_wait_sync
ermöglicht GPU-seitige Aussetzer anstelle von CPU-seitigen Aussetzern, 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, implementieren Sie die EGL_ANDROID_native_fence_sync
-Erweiterung zusammen mit der zugehörigen Kernelunterstü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 speziellen 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. So werden unerwünschte Interaktionen vermieden.
Die EGL_ANDROID_native_fence_sync
-Erweiterung verwendet ein entsprechendes natives Fence-Dateiendpunktattribut, das nur zum Zeitpunkt der Erstellung festgelegt werden kann und nicht direkt von einem vorhandenen Synchronisierungsobjekt abgefragt werden kann. Für dieses Attribut kann einer der beiden Modi festgelegt werden:
- Ein gültiger Fence-Dateideskriptor umschließt einen vorhandenen nativen Android-Fence-Dateideskriptor in einem
EGLSyncKHR
-Objekt. - Mit -1 wird ein nativer Android-Fence-Datei-Descriptor aus einem
EGLSyncKHR
-Objekt erstellt.
Verwenden Sie den Funktionsaufruf DupNativeFenceFD()
, um das EGLSyncKHR
-Objekt aus dem nativen Android-Begrenzungsdateideskriptor zu extrahieren.
Das hat das gleiche Ergebnis wie die Abfrage des Attributs „set“, entspricht aber der Konvention, dass der Empfänger den Zaun schließt (daher der Duplikatvorgang). Durch das Löschen des EGLSyncKHR
-Objekts wird das Attribut „internal_fence“ geschlossen.
Hardware Composer-Integration
Der Hardware Composer verarbeitet drei Arten von Synchronisationssperren:
- Acquire-Grenzen werden zusammen mit Eingabepuffern an die
setLayerBuffer
- undsetClientTarget
-Aufrufe übergeben. Diese stehen für eine ausstehende Schreiboperation in den Puffer und müssen signalisiert werden, bevor SurfaceFlinger oder die HWC versucht, aus dem zugehörigen Puffer zu lesen, um die Komposition auszuführen. - Release-Zäune werden nach dem Aufruf von
presentDisplay
mit demgetReleaseFences
-Aufruf abgerufen. Diese stehen für einen ausstehenden Lesevorgang aus dem vorherigen Zwischenspeicher auf derselben Ebene. Ein Release-Grenzwert signalisiert, dass der HWC den vorherigen Puffer nicht mehr verwendet, weil der aktuelle Puffer den vorherigen auf dem Display ersetzt hat. Release-Fences werden zusammen mit den vorherigen Zwischenspeichern, die während der aktuellen Komposition ersetzt werden, an die App zurückgegeben. Die App muss warten, bis ein Release-Fence-Signal vorliegt, bevor neue Inhalte in den Zwischenspeicher geschrieben werden, der an sie zurückgegeben wurde. - Present-Absperrungen werden als Teil des Aufrufs von
presentDisplay
zurückgegeben, jeweils eine pro Frame. Present-Grenzen geben an, wann die Komposition dieses Frames abgeschlossen ist oder wann das Kompositionierungsergebnis des vorherigen Frames nicht mehr benötigt wird. Bei physischen Bildschirmen gibtpresentDisplay
vorhandene Zäune zurück, wenn der aktuelle Frame auf dem Bildschirm angezeigt wird. Nachdem die Present-Grenzen zurückgegeben wurden, kann bei Bedarf wieder in den SurfaceFlinger-Zielpuffer geschrieben werden. Bei virtuellen Displays werden Present-Grenzen zurückgegeben, wenn es sicher ist, aus dem Ausgabebuffer zu lesen.