Framework di sincronizzazione

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 e pt_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 su sync_fence e sync_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 o sync_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
  • Il fornitore deve fornire le barriere di sincronizzazione appropriate come parametri alle funzioni validateDisplay() e presentDisplay() nell'HAL.
  • Due estensioni GL relative ai recinti (EGL_ANDROID_native_fence_sync e EGL_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 in EGLSyncKHR oggetti.
  • EGL_ANDROID_wait_sync consente i blocchi lato GPU anziché sul lato CPU, facendo in modo che la GPU attenda EGLSyncKHR. 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 e setClientTarget. 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 chiamata getReleaseFences. 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.