Quadro di sincronizzazione

Il framework di sincronizzazione descrive esplicitamente le dipendenze tra diverse operazioni asincrone nel sistema grafico Android. Il framework fornisce un'API che consente ai componenti di indicare quando vengono rilasciati i buffer. Il framework consente inoltre di passare le primitive di sincronizzazione tra i driver dal kernel allo userspace e tra gli stessi processi dello userspace.

Ad esempio, un'applicazione potrebbe mettere in coda il lavoro da eseguire nella GPU. La GPU inizia a disegnare quell'immagine. Anche se l'immagine non è stata ancora disegnata in memoria, il puntatore del buffer viene passato al compositore della finestra insieme a un recinto che indica quando finirà il lavoro della GPU. Il compositore di finestre avvia l'elaborazione in anticipo e passa il lavoro al controller del display. In modo simile, il lavoro della CPU viene svolto in anticipo. Una volta terminata la GPU, il controller del display visualizza immediatamente l'immagine.

Il framework di sincronizzazione consente inoltre agli implementatori di sfruttare le risorse di sincronizzazione nei propri componenti hardware. Infine, il framework fornisce visibilità nella pipeline grafica per facilitare il debug.

Sincronizzazione esplicita

La sincronizzazione esplicita consente ai produttori e ai consumatori di buffer grafici di segnalare quando hanno finito di utilizzare un buffer. La sincronizzazione esplicita è implementata nello spazio del kernel.

I vantaggi della sincronizzazione esplicita includono:

  • Minore variazione di comportamento tra i dispositivi
  • Migliore supporto per il debug
  • Metriche di test migliorate

Il framework di sincronizzazione ha tre tipi di oggetti:

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

sync_timeline è una sequenza temporale ad aumento monotono che i fornitori dovrebbero implementare per ogni istanza del driver, ad esempio un contesto GL, un controller di visualizzazione o un blitter 2D. sync_timeline conta i lavori inviati al kernel per un particolare componente hardware. sync_timeline fornisce garanzie sull'ordine delle operazioni e consente implementazioni specifiche dell'hardware.

Segui queste linee guida durante l'implementazione sync_timeline :

  • Fornisci nomi utili per tutti i driver, le sequenze temporali e i recinti per semplificare il debug.
  • Implementare gli operatori timeline_value_str e pt_value_str nelle sequenze temporali per rendere più leggibile l'output del debug.
  • Implementa il riempimento driver_data per fornire alle librerie dello spazio utente, come la libreria GL, l'accesso ai dati della sequenza temporale privata, se lo si desidera. data_driver consente ai fornitori di trasmettere informazioni sugli immutabili sync_fence e sync_pts per creare righe di comando basate su di essi.
  • Non consentire allo spazio utente di creare o segnalare esplicitamente un recinto. La creazione esplicita di segnali/recinzioni provoca un attacco di negazione del servizio che interrompe la funzionalità della pipeline.
  • Non accedere esplicitamente agli elementi sync_timeline , sync_pt o sync_fence . L'API fornisce tutte le funzioni richieste.

sincronizzazione_pt

sync_pt è un singolo valore o punto su una sync_timeline . Un punto ha tre stati: attivo, segnalato ed errore. I punti iniziano nello stato attivo e passano allo stato segnalato o di errore. Ad esempio, quando un consumatore di immagini non ha più bisogno di un buffer, viene segnalato un sync_pt in modo che un produttore di immagini sappia che è possibile scrivere nuovamente nel buffer.

sync_fence

sync_fence è una raccolta di valori sync_pt che spesso hanno elementi padre sync_timeline diversi (ad esempio per il controller del display e la GPU). sync_fence , sync_pt e sync_timeline sono le principali primitive utilizzate dai driver e dallo spazio utente per comunicare le loro dipendenze. Quando viene segnalato un recinto, tutti i comandi emessi prima del recinto sono garantiti come completi perché il driver del kernel o il blocco hardware eseguono i comandi in ordine.

Il framework di sincronizzazione consente a più consumatori o produttori di segnalare quando hanno finito di utilizzare un buffer, comunicando le informazioni sulla dipendenza con un parametro di funzione. Le recinzioni sono supportate da un descrittore di file e vengono passate dallo spazio del kernel allo spazio utente. Ad esempio, un recinto può contenere due valori sync_pt che indicano quando due consumatori di immagini separati hanno finito di leggere un buffer. Quando viene segnalato il limite, i produttori di immagine sanno che entrambi i consumatori hanno finito di consumare.

Le recinzioni, come i valori sync_pt , iniziano attive e cambiano stato in base allo stato dei relativi punti. Se tutti i valori sync_pt vengono segnalati, viene segnalato il sync_fence . Se un sync_pt cade in uno stato di errore, l'intero sync_fence avrà uno stato di errore.

L'appartenenza a un sync_fence è immutabile dopo la creazione del recinto. Per ottenere più di un punto in una recinzione, viene eseguita un'unione in cui i punti di due recinzioni distinte vengono aggiunti a una terza recinzione. Se uno di questi punti è stato segnalato nella recinzione originaria e l'altro no, anche la terza recinzione non sarà in uno stato segnalato.

Per implementare la sincronizzazione esplicita, fornire quanto segue:

  • Un sottosistema dello spazio kernel che implementa il framework di sincronizzazione per un particolare driver hardware. I driver che devono essere sensibilizzati sono generalmente tutto ciò che accede o comunica con l'Hardware Composer. I file chiave includono:
    • Implementazione principale:
      • kernel/common/include/linux/sync.h
      • kernel/common/drivers/base/sync.c
    • Documentazione su kernel/common/Documentation/sync.txt
    • Libreria per comunicare con lo spazio del kernel in platform/system/core/libsync
  • Il fornitore deve fornire i recinti di sincronizzazione appropriati come parametri per le funzioni validateDisplay() e presentDisplay() nell'HAL.
  • Due estensioni GL relative al recinto ( EGL_ANDROID_native_fence_sync e EGL_ANDROID_wait_sync ) e supporto per il recinto nel driver grafico.

Case study: implementare un driver video

Per utilizzare l'API che supporta la funzione di sincronizzazione, sviluppare un driver video dotato di funzione buffer di visualizzazione. Prima che esistesse il framework di sincronizzazione, questa funzione riceveva oggetti dma-buf , metteva quei buffer sul display e si bloccava mentre il buffer era visibile. Per esempio:

/*
 * assumes buffer is ready to be displayed.  returns when buffer is no longer on
 * screen.
 */
void display_buffer(struct dma_buf *buffer);

Con il framework di sincronizzazione, la funzione display_buffer è più complessa. Quando si mette in mostra un buffer, al buffer è associata una recinzione che indica quando il buffer sarà pronto. Puoi fare la fila e iniziare il lavoro dopo che la recinzione è stata ripulita.

Fare la fila e iniziare il lavoro dopo che la recinzione è stata liberata non blocca nulla. Restituisci immediatamente il tuo recinto, che garantisce quando il buffer sarà fuori dal display. Mentre metti in coda i buffer, il kernel elenca le dipendenze con il framework di sincronizzazione:

/*
 * 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);

Sincronizza l'integrazione

Questa sezione spiega come integrare il framework di sincronizzazione dello spazio kernel con le parti dello spazio utente del framework Android e i driver che devono comunicare tra loro. Gli oggetti dello spazio kernel sono rappresentati come descrittori di file nello spazio utente.

Convenzioni di integrazione

Seguire le convenzioni dell'interfaccia HAL Android:

  • Se l'API fornisce un descrittore di file che fa riferimento a sync_pt , il driver del fornitore o l'HAL che utilizza l'API deve chiudere il descrittore di file.
  • Se il driver del fornitore o l'HAL passa un descrittore di file che contiene un sync_pt a una funzione API, il driver del fornitore o l'HAL non deve chiudere il descrittore di file.
  • Per continuare a utilizzare il descrittore del file fencing, il driver del fornitore o l'HAL devono duplicare il descrittore.

Un oggetto fencing viene rinominato ogni volta che passa attraverso BufferQueue. Il supporto del recinto del kernel consente ai recinti di avere stringhe per i nomi, quindi il framework di sincronizzazione utilizza il nome della finestra e l'indice del buffer che viene accodato per denominare il recinto, ad esempio SurfaceView:0 . Ciò è utile nel debug per identificare l'origine di un deadlock poiché i nomi appaiono nell'output di /d/sync e nelle segnalazioni di bug.

Integrazione ANativeWindow

ANativeWindow riconosce la recinzione. dequeueBuffer , queueBuffer e cancelBuffer hanno parametri di fencing.

Integrazione OpenGL ES

L'integrazione della sincronizzazione OpenGL ES si basa su due estensioni EGL:

  • EGL_ANDROID_native_fence_sync fornisce un modo per racchiudere o creare descrittori di file di fencing nativi Android negli oggetti EGLSyncKHR .
  • EGL_ANDROID_wait_sync consente stalli lato GPU anziché lato CPU, facendo attendere la GPU per EGLSyncKHR . L'estensione EGL_ANDROID_wait_sync è uguale all'estensione EGL_KHR_wait_sync .

Per utilizzare queste estensioni in modo indipendente, implementa l'estensione EGL_ANDROID_native_fence_sync insieme al supporto del kernel associato. Successivamente, abilita l'estensione EGL_ANDROID_wait_sync nel tuo driver. L'estensione EGL_ANDROID_native_fence_sync è costituita da un tipo di oggetto EGLSyncKHR di recinzione nativa distinta. Di conseguenza, le estensioni che si applicano ai tipi di oggetto EGLSyncKHR esistenti non si applicano necessariamente agli oggetti EGL_ANDROID_native_fence , evitando interazioni indesiderate.

L'estensione EGL_ANDROID_native_fence_sync utilizza un attributo descrittore del file di fencing nativo corrispondente che può essere impostato solo al momento della creazione e non può essere interrogato direttamente in seguito da un oggetto di sincronizzazione esistente. Questo attributo può essere impostato su una delle due modalità:

  • Un descrittore di file di fencing valido racchiude un descrittore di file di fencing nativo Android esistente in un oggetto EGLSyncKHR .
  • -1 crea un descrittore di file di recinzione Android nativo da un oggetto EGLSyncKHR .

Utilizza la chiamata alla funzione DupNativeFenceFD() per estrarre l'oggetto EGLSyncKHR dal descrittore del file di fencing nativo di Android. Ciò ha lo stesso risultato dell'interrogazione dell'attributo set, ma aderisce alla convenzione secondo cui il destinatario chiude il recinto (da qui l'operazione di duplicazione). Infine, la distruzione dell'oggetto EGLSyncKHR chiude l'attributo del recinto interno.

Integrazione del compositore hardware

Hardware Composer gestisce tre tipi di barriere di sincronizzazione:

  • Le recinzioni di acquisizione vengono passate insieme ai buffer di input alle chiamate setLayerBuffer e setClientTarget . Questi rappresentano una scrittura in sospeso nel buffer e devono segnalare prima che SurfaceFlinger o l'HWC tentino di leggere dal buffer associato per eseguire la composizione.
  • Le recinzioni di rilascio vengono recuperate dopo la chiamata a presentDisplay utilizzando la chiamata getReleaseFences . Questi rappresentano una lettura in sospeso dal buffer precedente sullo stesso livello. Un limite di rilascio segnala quando l'HWC non utilizza più il buffer precedente perché il buffer corrente ha sostituito il buffer precedente sul display. Le recinzioni di rilascio vengono restituite all'app insieme ai buffer precedenti che verranno sostituiti durante la composizione corrente. L'app deve attendere fino al segnale di un limite di rilascio prima di scrivere nuovi contenuti nel buffer che le è stato restituito.
  • Vengono restituite le recinzioni presenti , una per fotogramma, come parte della chiamata a presentDisplay . Le recinzioni presenti rappresentano quando la composizione di questa cornice è stata completata o, in alternativa, quando il risultato della composizione della cornice precedente non è più necessario. Per i display fisici, presentDisplay restituisce i recinti presenti quando il fotogramma corrente viene visualizzato sullo schermo. Dopo che le recinzioni attuali sono state restituite, è sicuro scrivere nuovamente nel buffer di destinazione di SurfaceFlinger, se applicabile. Per i display virtuali, i recinti attuali vengono restituiti quando è sicuro leggere dal buffer di output.