Synchronisierungs-Framework

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 und pt_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. Mit data_driver können Anbieter Informationen zum unveränderlichen sync_fence und sync_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 oder sync_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
  • Der Anbieter muss die entsprechenden Synchronisations-Fences als Parameter für die Funktionen validateDisplay() und presentDisplay() im HAL bereitstellen.
  • Zwei GL-Erweiterungen im Zusammenhang mit Fences (EGL_ANDROID_native_fence_sync und EGL_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 in EGLSyncKHR-Objekten zu umschließen oder zu erstellen.
  • Mit EGL_ANDROID_wait_sync werden GPU-seitige anstelle von CPU-seitigen Stalls zugelassen, 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, 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 und setClientTarget ü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 von getReleaseFences 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 gibt presentDisplay 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.