Synchronisierungs-Framework

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 und pt_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. Mit data_driver können Anbieter Informationen zu den unveränderlichen sync_fence und sync_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- oder sync_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
  • Der Anbieter muss die entsprechenden Synchronisationsschranken als Parameter für die Funktionen validateDisplay() und presentDisplay() in der HAL bereitstellen.
  • Zwei grenzwertbezogene GL-Erweiterungen (EGL_ANDROID_native_fence_sync und EGL_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 in EGLSyncKHR-Objekten zu verpacken oder zu erstellen.
  • EGL_ANDROID_wait_sync ermöglicht GPU-seitige Aussetzer anstelle von CPU-seitigen Aussetzern, sodass die GPU auf EGLSyncKHR wartet. Die Erweiterung EGL_ANDROID_wait_sync ist mit der Erweiterung EGL_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- und setClientTarget-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 dem getReleaseFences-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 gibt presentDisplay 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.