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
ept_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 susync_fence
esync_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
osync_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
- Implementazione principale:
- Il fornitore deve fornire le barriere di sincronizzazione appropriate come parametri per le funzioni
validateDisplay()
epresentDisplay()
. - Due estensioni GL relative alla recinzione (
EGL_ANDROID_native_fence_sync
eEGL_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 oggettiEGLSyncKHR
. -
EGL_ANDROID_wait_sync
consente stalli lato GPU anziché lato CPU, facendo attendere la GPU perEGLSyncKHR
. L'estensioneEGL_ANDROID_wait_sync
è la stessa dell'estensioneEGL_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
esetClientTarget
. 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 chiamatagetReleaseFences
. 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.