Il framework di sincronizzazione descrive esplicitamente le dipendenze tra operazioni asincrone diverse nel sistema grafico Android. Il framework fornisce un'API che consente ai componenti di indicare il rilascio dei buffer. 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 potrebbe mettere in coda il lavoro da eseguire nella GPU. La GPU inizia a tracciare l'immagine. Anche se l'immagine non è stata disegnata in memoria, il puntatore del buffer viene passato alla finestra con una recinto che indica quando la GPU l'operazione. Il compositore della finestra avvia l'elaborazione in anticipo e gira il lavoro al controller del display. In modo simile, la CPU funziona avviene in anticipo. Al termine della GPU, il controller display per visualizzare immediatamente l'immagine.
Il framework di sincronizzazione consente inoltre agli implementatori di sfruttare le risorse di sincronizzazione nei propri componenti hardware. Infine, offre visibilità sulla pipeline grafica per aiutarti il debug del machine learning.
Sincronizzazione esplicita
La sincronizzazione esplicita consente a produttori e consumer di buffer grafici per segnalare quando ha finito di usare un buffer. La sincronizzazione esplicita è implementata nello spazio del 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 prevede tre tipi di oggetti:
sync_timeline
sync_pt
sync_fence
sync_timeline
sync_timeline
è una sequenza temporale in aumento monotonico che
che i fornitori dovrebbero implementare per ogni istanza del driver, ad esempio un contesto GL,
un controller per il display o un blitter 2D. sync_timeline
conteggia
i job inviati al kernel per un determinato hardware.
sync_timeline
fornisce garanzie sull'ordine delle operazioni
e consente implementazioni specifiche per l'hardware.
Segui queste linee guida durante l'implementazione di sync_timeline
:
- Fornisci nomi utili per tutti i conducenti, le tempistiche e i recinti per semplificare il debug del machine learning.
- Implementa gli operatori
timeline_value_str
ept_value_str
nelle sequenze temporali per rendere più leggibile l'output di debug. - Implementa l'
driver_data
di riempimento per fornire librerie dello spazio utente, come la libreria GL, l'accesso a dati privati della sequenza temporale, se necessario.data_driver
consente ai fornitori di trasmettere informazioni susync_fence
esync_pts
immutabili per creare righe di comando in base a queste. - Non consentire allo spazio utente di creare o segnalare esplicitamente una recinzione. La creazione esplicita di indicatori/reti virtuali comporta un attacco di negazione del servizio che interrompe la funzionalità della pipeline.
- Non accedere a
sync_timeline
,sync_pt
osync_fence
in modo esplicito. L'API fornisce tutte le risorse necessarie funzioni.
sync_pt
sync_pt
è un singolo valore o punto su una
sync_timeline
. Un punto
ha tre stati: attivo, segnalato e errore. I punti iniziano nello stato attivo
e passare agli stati segnalati 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.
recinto_sincronizzazione
sync_fence
è una raccolta di valori sync_pt
che spesso
avere sync_timeline
diversi elementi principali (ad esempio, per il display
il controller e la GPU). sync_fence
, sync_pt
e
sync_timeline
sono le primitive principali che driver e 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 esegue i comandi in ordine.
Il framework di sincronizzazione consente a più consumatori o produttori di segnalare
ha finito di utilizzare un buffer, comunicando le informazioni sulle dipendenze con una funzione
. Le barriere sono supportate da un descrittore di file e vengono passate da
dallo spazio kernel allo spazio utente. Ad esempio, una recinzione può contenere due valori sync_pt
che indicano quando due consumatori di immagini separati hanno terminato di leggere un buffer. Quando viene segnalato il recinto, i produttori di immagini sanno che entrambi i consumatori hanno terminato di consumare.
I recinti, come i valori sync_pt
, iniziano attivi e cambiano stato in base allo stato dei relativi punti. Se tutti i valori sync_pt
vengono segnalati, viene segnalato anche sync_fence
. Se un sync_pt
assume
uno stato di errore, l'intero sync_fence
ha uno stato di errore.
L'appartenenza a un sync_fence
è immutabile dopo la creazione del recinto. Per ottenere più di un punto in un recinto, viene eseguita un'unione in cui i punti di due recinti distinti vengono aggiunti a un terzo recinto.
Se uno di questi punti è stato segnalato nella recinzione di origine e l'altro no,
né la terza recinto sarà nello stato segnalato.
Per implementare la sincronizzazione esplicita, fornisci quanto segue:
- Un sottosistema nello spazio del kernel che implementa il framework di sincronizzazione per un determinato driver hardware. I driver che devono essere compatibili con la recinzione sono generalmente tutti i componenti che accedono o comunicano con il compositore hardware.
I file principali includono:
- Implementazione principale:
kernel/common/include/linux/sync.h
kernel/common/drivers/base/sync.c
- Documentazione all'indirizzo
kernel/common/Documentation/sync.txt
- una libreria per comunicare con lo spazio del kernel
platform/system/core/libsync
- Implementazione principale:
- Il fornitore deve fornire le barriere di sincronizzazione appropriate come parametri alle funzioni
validateDisplay()
epresentDisplay()
nell'HAL. - Due estensioni GL relative ai recinti (
EGL_ANDROID_native_fence_sync
eEGL_ANDROID_wait_sync
) e il supporto dei recinti nel driver grafico.
Case study: implementare un driver display
Per utilizzare l'API che supporta la funzione di sincronizzazione,
sviluppare 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 i 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 viene messo sul display un buffer, quest'ultimo viene associato
con una recinzione che indica quando il buffer sarà pronto. Puoi metterli in coda
e avviare il lavoro dopo che il recinto è stato rimosso.
L'inserimento in coda e l'avvio del lavoro dopo il completamento del recinto non bloccano nulla. Restituirai immediatamente la tua recinzione, che garantisce quando il buffer sarà spento sul display. Quando metti in coda i buffer, il kernel elenca 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 l'uno con l'altro. Gli oggetti nello spazio del kernel sono rappresentati come descrittori file nello spazio utente.
Convenzioni di integrazione
Segui le convenzioni dell'interfaccia HAL di Android:
- Se l'API fornisce un descrittore file che fa riferimento a un
sync_pt
, il driver del fornitore o l'HAL che utilizza l'API deve chiudere il descrittore file. - Se il driver del fornitore o l'HAL passa un descrittore file contenente un
sync_pt
a una funzione API, il driver del fornitore o l'HAL non deve chiudere il descrittore file. - Per continuare a utilizzare il descrittore file della recinzione, il driver del fornitore o l'HAL deve duplicare il descrittore.
Un oggetto recinto viene rinominato ogni volta che passa attraverso BufferQueue.
Il supporto della recinzione del kernel consente alle recinzioni di avere stringhe per i nomi, quindi
utilizza il nome della finestra e l'indice del buffer che vengono accodati per
del recinto, ad esempio SurfaceView:0
. Questo
è utile per il debug, al fine di identificare l'origine di un deadlock visualizzato dai nomi.
nell'output di /d/sync
e nelle segnalazioni di bug.
Integrazione di A NativeWindow
ANativeWindow è consapevole della recinzione. dequeueBuffer
,
queueBuffer
e cancelBuffer
hanno parametri di recinto.
Integrazione di OpenGL ES
L'integrazione della sincronizzazione OpenGL ES si basa su due estensioni EGL:
EGL_ANDROID_native_fence_sync
offre un modo per racchiudere o creare descrittori del file di recinto nativo di Android inEGLSyncKHR
oggetti.EGL_ANDROID_wait_sync
consente i blocchi lato GPU anziché sul lato CPU, facendo in modo che la GPU attendaEGLSyncKHR
. L'estensioneEGL_ANDROID_wait_sync
è la stessa dell'estensioneEGL_KHR_wait_sync
.
Per utilizzare queste estensioni in modo indipendente, implementa il metodo
EGL_ANDROID_native_fence_sync
insieme all'estensione associata
il supporto dei kernel. A questo punto, abilita EGL_ANDROID_wait_sync
nel driver. EGL_ANDROID_native_fence_sync
è costituita da un oggetto EGLSyncKHR
di recinto nativo distinto
di testo. Di conseguenza, le estensioni che si applicano ai EGLSyncKHR
esistenti
i tipi di oggetti non si applicano necessariamente a EGL_ANDROID_native_fence
ed evitare interazioni indesiderate.
L'estensione EGL_ANDROID_native_fence_sync
utilizza un attributo descrittore file di recinzione nativo corrispondente che può essere impostato solo al momento della creazione e su cui non è possibile eseguire query direttamente da un oggetto di sincronizzazione esistente. Questo attributo
può essere impostato su una delle due modalità:
- Un descrittore del file di recinto valido aggrega un descrittore nativo esistente
Descrittore di file di recinto Android in un oggetto
EGLSyncKHR
. - -1 crea un descrittore di file di recinto nativo di Android da un
EGLSyncKHR
oggetto.
Utilizza la chiamata di funzione DupNativeFenceFD()
per estrarre l'oggetto EGLSyncKHR
dal descrittore file di recinzione Android nativo.
Il risultato è lo stesso della query sull'attributo set, ma rispetta la convenzione che il destinatario chiude la recinzione (da qui l'operazione di duplicazione). Infine, l'eliminazione dell'oggetto EGLSyncKHR
l'attributo recinto interno.
Integrazione di Hardware Composer
Hardware Composer gestisce tre tipi di limiti di sincronizzazione:
- L'opzione Acquisisci le barriere viene passata insieme ai buffer di input
le chiamate
setLayerBuffer
esetClientTarget
. Questi rappresentano una scrittura in sospeso nel buffer e devono essere segnalati prima SurfaceFlinger o HWC tenta di leggere dal buffer associato eseguire composizioni. - Il sblocco di restrizioni viene recuperato dopo la chiamata a
presentDisplay
sta usando la chiamatagetReleaseFences
. Questi rappresentano una lettura in attesa dal buffer precedente sullo stesso livello. Un confine di rilascio indica quando l'HWC non utilizza più il buffer precedente perché il buffer corrente ha sostituito quello 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 che un interruttore di rilascio indichi che è possibile scrivere nuovi contenuti nel buffer che è stato restituito. - Le recinzioni presenti vengono restituite, una per frame, nell'ambito di
la chiamata a
presentDisplay
. Gli recinti attuali rappresentano quando composizione di questo frame o, in alternativa, quando il risultato della composizione del frame precedente non è più necessario. Per uso fisico viene visualizzato,presentDisplay
restituisce le barriere presenti quando il frame corrente viene visualizzato sullo schermo. Dopo che sono stati restituiti i recinti attuali, è possibile scrivere di nuovo nel buffer di destinazione di SurfaceFlinger, se applicabile. Per gli schermi virtuali, le recinzioni presenti vengono restituite quando poter leggere in sicurezza dal buffer di output.