Quadro di sincronizzazione

Il framework di sincronizzazione descrive in modo esplicito 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 anche di passare le primitive di sincronizzazione tra i driver dal kernel allo spazio utente e tra gli stessi processi dello spazio utente.

Ad esempio, un'applicazione può mettere in coda il lavoro da eseguire nella GPU. La GPU inizia a disegnare quell'immagine. Sebbene l'immagine non sia stata ancora disegnata in memoria, il puntatore del buffer viene passato al compositore di finestre insieme a un recinto che indica quando il lavoro della GPU sarà terminato. Il compositore di finestre inizia l'elaborazione in anticipo e passa il lavoro al controller del display. In modo simile, il lavoro della CPU viene svolto in anticipo. Al termine della 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 terminato l'utilizzo di un buffer. La sincronizzazione esplicita è implementata nello spazio 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 monotonicamente crescente che i fornitori dovrebbero implementare per ogni istanza del driver, come 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 quando implementi sync_timeline :

  • Fornisci nomi utili per tutti i driver, le sequenze temporali e le barriere per semplificare il debug.
  • Implementa gli operatori timeline_value_str e pt_value_str nelle timeline per rendere più leggibile l'output di debug.
  • Implementare 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 passare informazioni su sync_fence e sync_pts immutabili per creare righe di comando basate su di esse.
  • Non consentire allo spazio utente di creare o segnalare esplicitamente una recinzione. La creazione esplicita di segnali/recinzioni si traduce in un attacco denial-of-service 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.

sync_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 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 di nuovo nel buffer.

sync_fence

sync_fence è una raccolta di valori sync_pt che spesso hanno diversi sync_timeline (come per il controller video e la GPU). sync_fence , sync_pt e sync_timeline sono le primitive principali che i driver e lo spazio utente utilizzano per comunicare le loro dipendenze. Quando viene segnalato un fence, tutti i comandi emessi prima del fence sono garantiti per essere completi perché il driver del kernel o il blocco hardware esegue i comandi in ordine.

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

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

L'appartenenza a un sync_fence è immutabile dopo la creazione del fence. Per ottenere più di un punto in una recinzione, viene eseguita un'unione in cui i punti di due barriere distinte vengono aggiunti a una terza recinzione. Se uno di questi punti è stato segnalato nella recinzione di origine 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 in grado di riconoscere la recinzione sono generalmente tutto ciò che accede o comunica con 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 le barriere di sincronizzazione appropriate come parametri per le funzioni validateDisplay() e presentDisplay() .
  • Due estensioni GL relative alla recinzione ( EGL_ANDROID_native_fence_sync e EGL_ANDROID_wait_sync ) e supporto per la recinzione nel driver grafico.

Caso di studio: implementazione di un driver video

Per utilizzare l'API che supporta la funzione di sincronizzazione, sviluppare un driver di visualizzazione che disponga di una funzione di 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. Durante la visualizzazione di un buffer, il buffer è associato a una barriera che indica quando il buffer sarà pronto. Puoi fare la coda e iniziare il lavoro dopo che la recinzione è stata liberata.

Accodare e avviare 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. Quando si accodano 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);

Integrazione di sincronizzazione

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 del kernel sono rappresentati come descrittori di file nello spazio utente.

Convenzioni di integrazione

Segui le convenzioni dell'interfaccia HAL di 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 devono chiudere il descrittore di file.
  • Per continuare a utilizzare il descrittore di file fence, il driver del fornitore o l'HAL devono duplicare il descrittore.

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

Integrazione con ANativeWindow

ANativeWindow è a conoscenza del recinto. dequeueBuffer , queueBuffer e cancelBuffer hanno parametri di fencing.

Integrazione OpenGL ES

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

  • EGL_ANDROID_native_fence_sync fornisce un modo per avvolgere o creare descrittori di file di recinzione Android nativi 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 è la stessa dell'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. Quindi, 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 nativo distinto. Di conseguenza, le estensioni che si applicano ai tipi di oggetti 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 del descrittore di file di fencing nativo corrispondente che può essere impostato solo al momento della creazione e non può essere interrogato direttamente da un oggetto di sincronizzazione esistente. Questo attributo può essere impostato su una delle due modalità:

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

Utilizzare la chiamata alla funzione DupNativeFenceFD() per estrarre l'oggetto EGLSyncKHR dal descrittore di file di recinzione Android nativo. Questo ha lo stesso risultato della query sull'attributo set, ma aderisce alla convenzione che il destinatario chiude il recinto (da cui l'operazione duplicata). Infine, la distruzione dell'oggetto EGLSyncKHR chiude l'attributo di recinzione interna.

Integrazione del compositore hardware

Hardware Composer gestisce tre tipi di sync fence:

  • Le barriere 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 tenti di leggere dal buffer associato per eseguire la composizione.
  • I blocchi di rilascio vengono recuperati dopo la chiamata a presentDisplay utilizzando la chiamata getReleaseFences . Questi rappresentano una lettura in sospeso dal buffer precedente sullo stesso livello. Una barriera di rilascio segnala quando l'HWC non utilizza più il buffer precedente perché il buffer corrente ha sostituito il buffer precedente sul display. Le barriere di rilascio vengono restituite all'app insieme ai buffer precedenti che verranno sostituiti durante la composizione corrente. L'app deve attendere fino a quando non viene segnalato un limite di rilascio prima di scrivere nuovi contenuti nel buffer che è stato restituito loro.
  • Le barriere presenti vengono restituite, una per frame, come parte della chiamata a presentDisplay . Le recinzioni presenti rappresentano quando la composizione di questo telaio è completata, o in alternativa, quando il risultato compositivo del telaio precedente non è più necessario. Per le visualizzazioni fisiche, presentDisplay restituisce le barriere presenti quando il fotogramma corrente viene visualizzato sullo schermo. Dopo che le barriere presenti sono state restituite, è possibile scrivere nuovamente nel buffer di destinazione SurfaceFlinger, se applicabile. Per i display virtuali, le barriere presenti vengono restituite quando è sicuro leggere dal buffer di output.