Il framework di sincronizzazione descrive esplicitamente le dipendenze tra le diverse operazioni asincrone nel sistema grafico Android. Il framework fornisce un'API che consente ai componenti di indicare quando i buffer vengono rilasciati. Il framework consente inoltre di passare le primitive di sincronizzazione tra i driver dal kernel allo spazio utente e tra i processi dello spazio utente stessi.
Ad esempio, un'applicazione può mettere in coda il lavoro da eseguire nella GPU. La GPU inizia a disegnare l'immagine. Sebbene l'immagine non sia ancora stata disegnata nella memoria, il puntatore del buffer viene passato al compositore della finestra insieme a una barriera che indica quando il lavoro della GPU sarà terminato. Il compositore della finestra inizia l'elaborazione in anticipo e passa il lavoro al controller del display. Allo stesso modo, il lavoro della CPU viene eseguito in anticipo. Una volta terminata la GPU, il controller del display visualizza immediatamente l'immagine.
Il framework di sincronizzazione consente inoltre agli implementatori di utilizzare 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 viene implementata nello spazio kernel.
I vantaggi della sincronizzazione esplicita includono:
- Minore variazione del comportamento tra i dispositivi
- Migliore supporto per il debug
- Metriche di test migliorate
Il framework di sincronizzazione ha tre tipi di oggetti:
sync_timelinesync_ptsync_fence
sync_timeline
sync_timeline è una sequenza temporale in aumento monotono che i fornitori devono implementare per ogni istanza del driver, ad esempio un contesto GL, un controller del display o un blitter 2D. sync_timeline conta i job inviati al kernel per un particolare componente hardware.
sync_timeline garantisce l'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_strept_value_strnelle sequenze temporali per rendere più leggibile l'output di debug. - Se necessario, implementa il riempimento
driver_dataper consentire alle librerie dello spazio utente, come la libreria GL, di accedere ai dati privati della sequenza temporale.data_driverconsente ai fornitori di passare informazioni sugli elementi immutabilisync_fenceesync_ptsper creare righe di comando in base a questi elementi. - Non consentire allo spazio utente di creare o segnalare esplicitamente una barriera. La creazione esplicita di segnali/barriere comporta 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 agli stati 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 può scrivere di nuovo nel buffer.
sync_fence
sync_fence è una raccolta di valori sync_pt che spesso hanno genitori sync_timeline diversi (ad esempio per il controller del display 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 proprie dipendenze. Quando una barriera viene segnalata, tutti i comandi emessi prima della barriera vengono completati 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 finito di utilizzare un buffer, comunicando le informazioni sulle dipendenze con un parametro di funzione. Le barriere sono supportate da un descrittore del file e vengono passate dallo spazio kernel allo spazio utente. Ad esempio, una barriera può contenere due valori sync_pt che indicano quando due consumatori di immagini separati hanno finito di leggere un buffer. Quando la barriera viene segnalata, i produttori di immagini sanno che entrambi i consumatori hanno finito di consumare.
Le barriere, come i valori sync_pt, iniziano attive e cambiano stato in base allo stato dei punti. Se tutti i valori sync_pt vengono segnalati, viene segnalato anche sync_fence. Se un sync_pt passa a uno stato di errore, l'intero sync_fence ha uno stato di errore.
L'appartenenza a un sync_fence è immutabile dopo la creazione della barriera. Per ottenere più di un punto in una barriera, viene eseguita un'unione in cui i punti di due barriere distinte vengono aggiunti a una terza barriera.
Se uno di questi punti è stato segnalato nella barriera di origine e l'altro no, anche la terza barriera non sarà in uno stato segnalato.
Per implementare la sincronizzazione esplicita, fornisci quanto segue:
- Un sottosistema dello spazio kernel che implementa il framework di sincronizzazione per un particolare driver hardware. I driver che devono essere a conoscenza delle barriere sono in genere tutti quelli che accedono o comunicano con Hardware Composer (HWC).
I file chiave includono:
- Implementazione principale:
kernel/common/include/linux/sync.hkernel/common/drivers/base/sync.c
- Documentazione in
kernel/common/Documentation/sync.txt - Libreria per comunicare con lo spazio kernel in
platform/system/core/libsync
- Implementazione principale:
- Il fornitore deve fornire le barriere di sincronizzazione appropriate come parametri delle funzioni
validateDisplay()epresentDisplay()nel livello di astrazione hardware (HAL). - Due estensioni GL correlate alle barriere (
EGL_ANDROID_native_fence_synceEGL_ANDROID_wait_sync) e supporto delle barriere nel driver grafico.
Case study: implementare un driver del display
Per utilizzare l'API che supporta la funzione di sincronizzazione, sviluppa un driver del display con una funzione di buffer del display. Prima dell'esistenza del framework di sincronizzazione, questa funzione riceveva oggetti dma-buf, inseriva questi buffer sul display e bloccava mentre il buffer era visibile. Ad 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 inserisci un buffer sul display, il buffer viene associato a una barriera che indica quando il buffer sarà pronto. Puoi mettere in coda e avviare il lavoro dopo che la barriera è stata eliminata.
La messa in coda e l'avvio del lavoro dopo l'eliminazione della barriera non bloccano nulla. Restituisci immediatamente la tua barriera, che segnala quando il buffer non sarà più visualizzato. Quando 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);
Integrazione della 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 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 un
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 del file contenente
un
sync_pta una funzione API, il driver del fornitore o l'HAL non deve chiudere il descrittore del file. - Per continuare a utilizzare il descrittore di file della barriera, il driver del fornitore o il HAL deve duplicare il descrittore.
Un oggetto barriera viene rinominato ogni volta che passa attraverso BufferQueue.
Il supporto delle barriere del kernel consente alle barriere di avere stringhe per i nomi, quindi il framework di sincronizzazione utilizza il nome della finestra e l'indice del buffer in coda per denominare la barriera, ad esempio SurfaceView:0. Questo è utile per il debug per identificare l'origine di un deadlock, poiché i nomi vengono visualizzati nell'output di /d/sync e nei report sui bug.
Integrazione di ANativeWindow
ANativeWindow è a conoscenza delle barriere. dequeueBuffer,
queueBuffer e cancelBuffer hanno parametri di barriera.
Integrazione di OpenGL ES
L'integrazione della sincronizzazione di OpenGL ES si basa su due estensioni EGL:
EGL_ANDROID_native_fence_syncfornisce un modo per eseguire il wrapping o creare descrittori di file di barriera Android nativi in oggettiEGLSyncKHR.EGL_ANDROID_wait_syncconsente di bloccare la GPU anziché la CPU, facendo in modo che la GPU attendaEGLSyncKHR. L'EGL_ANDROID_wait_syncestensione è uguale all'EGL_KHR_wait_syncestensione.
Per utilizzare queste estensioni in modo indipendente, implementa l'estensione EGL_ANDROID_native_fence_sync insieme al supporto del kernel associato. Poi, abilita l'estensione EGL_ANDROID_wait_sync nel driver. L'estensione EGL_ANDROID_native_fence_sync è costituita da un tipo di oggetto EGLSyncKHR di barriera nativa distinta. 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 di descrittore del file di barriera nativa corrispondente che può essere impostato solo al momento della creazione e non può essere sottoposto a query direttamente da un oggetto di sincronizzazione esistente. Questo attributo può essere impostato su una delle due modalità:
- Un descrittore del file di barriera valido esegue il wrapping di un descrittore del file di barriera Android nativo esistente in un oggetto
EGLSyncKHR. - -1 crea un descrittore del file di barriera Android nativo da un
EGLSyncKHRoggetto.
Utilizza la chiamata di funzione DupNativeFenceFD() per estrarre l'oggetto EGLSyncKHR dal descrittore del file di barriera Android nativo.
Il risultato è lo stesso della query dell'attributo impostato, ma rispetta la convenzione secondo cui il destinatario chiude la barriera (da cui l'operazione di duplicazione). Infine, l'eliminazione dell'oggetto EGLSyncKHR chiude l'attributo della barriera interna.
Integrazione di Hardware Composer
HWC gestisce tre tipi di barriere di sincronizzazione:
- Le **barriere di acquisizione** vengono passate insieme ai buffer di input alle
chiamate
setLayerBufferesetClientTarget. Queste rappresentano una scrittura in sospeso nel buffer e devono segnalare prima che il SurfaceFlinger o l'HWC tentino di leggere dal buffer associato per eseguire la composizione. - Le barriere di rilascio vengono recuperate dopo la chiamata a
presentDisplayutilizzando lagetReleaseFenceschiamata. Queste rappresentano una lettura in sospeso dal buffer precedente sullo stesso livello. Una barriera di rilascio segnala quando 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 che una barriera di rilascio segnali prima di scrivere nuovi contenuti nel buffer che le è stato restituito. - Le barriere di presentazione vengono restituite, una per frame, nell'ambito della chiamata a
presentDisplay. Le barriere di presentazione rappresentano il momento in cui la composizione di questo frame è stata completata o, in alternativa, quando il risultato della composizione del frame precedente non è più necessario. Per i display fisici,presentDisplayrestituisce le barriere di presentazione quando il frame corrente viene visualizzato sullo schermo. Dopo che le barriere di presentazione sono state restituite, è possibile scrivere di nuovo nel buffer di destinazione di SurfaceFlinger, se applicabile. Per i display virtuali, le barriere di presentazione vengono restituite quando è possibile leggere dal buffer di output.