Il framework di sincronizzazione descrive esplicitamente le dipendenze tra diverse operazioni asincrone nel sistema grafico di 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 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 disegnare l'immagine. Anche se l'immagine non è stata ancora disegnata nella memoria, il puntatore del buffer viene passato al compositore della finestra insieme a una recinzione che indica quando verrà completata l'attività della GPU. Il compositore della finestra avvia l'elaborazione in anticipo e gira il lavoro al controller del display. In modo simile, il lavoro della CPU viene eseguito in anticipo. Al termine della GPU, il controller 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à sulla pipeline grafica per facilitare il debugging.
Sincronizzazione esplicita
La sincronizzazione esplicita consente a producer e consumer 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:
- Meno variazioni 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 in aumento monotonico 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
conteggia
i job inviati al kernel per un determinato hardware.
sync_timeline
fornisce garanzie sull'ordine delle operazioni e consente implementazioni specifiche per hardware.
Segui queste linee guida durante l'implementazione di sync_timeline
:
- Fornisci nomi utili per tutti i driver, le tempistiche e le recinti per semplificare il debugging.
- Implementa gli operatori
timeline_value_str
ept_value_str
nelle sequenze temporali per rendere più leggibile l'output di debug. - Implementa il riempimento
driver_data
per concedere alle librerie dello spazio utente, come la libreria GL, l'accesso ai dati della cronologia privata, se vuoi.data_driver
consente ai fornitori di passare informazioni sugli immutabilisync_fence
esync_pts
per creare righe di comando basate su queste. - Non consentire allo spazio utente di creare o segnalare esplicitamente un recinto. La creazione esplicita di indicatori/recinzioni determina 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.
sincronizzazione_pt
sync_pt
è un singolo valore o punto su un
sync_timeline
. Un punto
ha tre stati: attivo, segnalato ed errore. I punti iniziano nello stato attivo
e passano 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.
sync_fence
sync_fence
è una raccolta di valori sync_pt
che spesso
hanno sync_timeline
principali diversi (ad esempio per il controllore
dell'unità di visualizzazione e la GPU). sync_fence
, sync_pt
e
sync_timeline
sono le primitive principali 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 esegue i comandi in ordine.
Il framework di sincronizzazione consente a più consumatori o produttori di segnalare quando hanno terminato di utilizzare un buffer, comunicando le informazioni sulle dipendenze con un parametro di funzione. Le recinti sono supportati da un descrittore file e vengono passati dallo spazio kernel allo spazio utente. Ad esempio, una recinto può contenere due valori sync_pt
che indicano quando due consumer di immagini separati hanno finito 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 nel recinto di origine e l'altro no, anche il terzo recinto non sarà in uno 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 di base:
kernel/common/include/linux/sync.h
kernel/common/drivers/base/sync.c
- Documentazione all'indirizzo
kernel/common/Documentation/sync.txt
- Libreria per comunicare con lo spazio del kernel in
platform/system/core/libsync
- Implementazione di base:
- 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: implementazione di un driver di visualizzazione
Per utilizzare l'API che supporta la funzione di sincronizzazione,
sviluppa un driver di visualizzazione con una funzione di buffer di visualizzazione. 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 visualizzato un buffer, questo viene associato a un recinto che indica quando sarà pronto. Puoi mettere in coda
e iniziare il lavoro dopo che il recinto si è risolto.
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 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 nello 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 dei 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 di file contenente un
sync_pt
a una funzione API, il driver del fornitore o l'HAL non deve chiudere il descrittore del 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 delle barriere del kernel consente alle barriere di avere stringhe per i nomi, pertanto il framework di sincronizzazione utilizza il nome della finestra e l'indice del buffer in coda per assegnare un nome alla barriera, ad esempio SurfaceView:0
. Ciò è utile per il debug e l'identificazione dell'origine di un deadlock come i nomi visualizzati nell'output di /d/sync
e nelle segnalazioni di bug.
Integrazione di ANativeWindow
ANativeWindow è consapevole della recinzione. dequeueBuffer
,
queueBuffer
e cancelBuffer
hanno parametri di recinzione.
Integrazione OpenGL ES
L'integrazione della sincronizzazione OpenGL ES si basa su due estensioni EGL:
EGL_ANDROID_native_fence_sync
fornisce un modo per eseguire il wrapping o creare descrittori file di recinti Android nativi negli oggettiEGLSyncKHR
.EGL_ANDROID_wait_sync
consente interruzioni sul lato GPU piuttosto che sul lato CPU, facendo attendere la GPU perEGLSyncKHR
. L'estensioneEGL_ANDROID_wait_sync
è uguale all'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. Poi, attiva l'EGL_ANDROID_wait_sync
estensione nel driver. L'estensione EGL_ANDROID_native_fence_sync
è costituita da un distinto tipo di oggetto recinto nativo EGLSyncKHR
. 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 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 di due modalità:
- Un descrittore di file di recinto valido aggrega un descrittore di file di recinto nativo esistente
di Android in un oggetto
EGLSyncKHR
. - -1 crea un descrittore file di recinzione Android nativo da un oggetto
EGLSyncKHR
.
Utilizza la chiamata di funzione DupNativeFenceFD()
per estrarre l'oggetto EGLSyncKHR
dal descrittore file di recinzione Android nativo.
Questa operazione ha lo stesso risultato dell'esecuzione di una query sull'attributo impostato, ma è conforme alla convenzione secondo cui il destinatario chiude il recinto (da cui l'operazione di duplicazione). Infine, l'eliminazione dell'oggetto EGLSyncKHR
chiude
l'attributo recinto interno.
Integrazione di Hardware Composer
Il compositore hardware gestisce tre tipi di barriere di sincronizzazione:
- I limiti di acquisizione vengono passati insieme ai buffer di input alle chiamate
setLayerBuffer
esetClientTarget
. Questi rappresentano una scrittura in attesa nel buffer e devono essere segnalati prima che SurfaceFlinger o l'HWC tenti di leggere dal buffer associato per eseguire la composizione. - I sistemi di rilascio vengono recuperati dopo la chiamata a
presentDisplay
tramite 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 barriere di rilascio vengono ritrasmesse 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. - I limiti di presentazione vengono restituiti, uno per frame, nell'ambito della chiamata a
presentDisplay
. I recinti attuali indicano quando la composizione di questo frame è stata completata oppure quando il risultato della composizione del frame precedente non è più necessario. Per i display fisici,presentDisplay
restituisce le recinti presenti quando il frame corrente viene visualizzato sullo schermo. Dopo che vengono restituiti i recinti attuali, è possibile scrivere di nuovo nel buffer di destinazione di SurfaceFlinger, se applicabile. Per i display virtuali, i recinti attuali vengono restituiti quando è possibile leggere dal buffer di output.