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. Inoltre, il framework consente il passaggio delle primitive di sincronizzazione tra i driver dal kernel allo spazio utente e tra i vari 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 di finestre avvia l'elaborazione in anticipo passa 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 e 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 è implementato in kernel-space.

I vantaggi della sincronizzazione esplicita includono:

  • Minore variazione del comportamento tra i dispositivi
  • Miglior supporto al debug
  • Metriche di test migliorate

Il framework di sincronizzazione prevede tre tipi di oggetti:

  • sync_timeline
  • sync_pt
  • sync_fence

cronologica_sincronizzazione

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 conteggi inviati al kernel per un particolare 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 timeline_value_str e pt_value_str operatori 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 ai dati privati della sequenza temporale, se necessario. data_driver consente ai fornitori di trasmettere informazioni sull'immutabile sync_fence e sync_pts per creare le righe di comando sulla base di questi ultimi.
  • Non consentire allo spazio utente di creare o segnalare esplicitamente una recinzione. Esplicitamente la creazione di indicatori/recinzioni porta a un attacco denial of service 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.

sincronizzazione_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'immagine il consumatore non ha più bisogno di un buffer, viene segnalato un sync_pt in modo che il produttore di immagini sappia che è consentito 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 segnalata una recinzione, inviati prima del limite, poiché il driver 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 il completamento di due consumatori di immagini separati per leggere un buffer. Quando viene segnalata la recinzione, i produttori di immagini sanno che sia che i consumatori hanno finito di consumare.

Le barriere, come i valori sync_pt, iniziano come attive e cambiano stato in base a lo stato dei loro punti. Se tutti i valori di sync_pt vengono segnalati, il valore Viene segnalato il valore sync_fence. Se un valore di sync_pt cade in uno stato di errore, l'intero sync_fence ha uno stato di errore.

L'appartenenza a un sync_fence è immutabile una volta che il recinto è è stato creato. Per ottenere più di un punto in una recinzione, un'unione condotte in cui i punti di due recinzioni distinte vengono aggiunti a una terza recinzione. 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 dello spazio del kernel che implementa il framework di sincronizzazione per un determinato driver hardware. I conducenti che devono essere consapevoli del recinto in genere qualsiasi elemento che acceda o comunichi con Hardware Composer. I file chiave 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 l'appropriata le recinzioni come parametri per validateDisplay() e presentDisplay() nell'HAL.
  • Due estensioni GL correlate a recinzioni (EGL_ANDROID_native_fence_sync e EGL_ANDROID_wait_sync) e il supporto del recinto nelle immagini conducente.

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 del framework di sincronizzazione esistente, questa funzione riceverà dma-buf oggetti, mettere i buffer sul display e bloccarli 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 mettere in coda e iniziare i lavori dopo che il recinto si è risolto.

Mettere in coda e iniziare il lavoro dopo che il recinto è stato cancellato non blocca nulla. Restituisci immediatamente la tua recinzione, il che garantisce che quando il buffer viene disattivata dal 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 dello spazio del kernel sono rappresentati come descrittori dei file nel tuo spazio utente.

Convenzioni di integrazione

Segui le convenzioni dell'interfaccia di Android HAL:

  • Se l'API fornisce un descrittore del file che fa riferimento a un sync_pt, il driver del fornitore o l'HAL che utilizza l'API deve chiudere il descrittore del file.
  • Se il driver del fornitore o l'HAL trasmette un descrittore del file che contiene un sync_pt a una funzione API, il driver del fornitore o l'HAL non deve chiudi il descrittore del file.
  • Per continuare a utilizzare il descrittore del file di recinto, 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

A NativeWindow è sensibile al recinto. dequeueBuffer, queueBuffer e cancelBuffer hanno parametri di recinto.

Integrazione 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. La L'estensione EGL_ANDROID_wait_sync è uguale all'estensione EGL_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 modello nativo corrispondente fence file descriptor, che può essere impostato solo al momento della creazione e non può essere eseguita direttamente su 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 Oggetto EGLSyncKHR dal descrittore nativo del file di recinto di Android. Questo risultato corrisponde all'esecuzione di una query sull'attributo set, ma è conforme alle la convenzione con cui il destinatario chiude il recinto (da qui il duplicato ). 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. R rilascia indicatori del recinto quando l'HWC non utilizza più il buffer precedente perché il buffer attuale 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 fino a quando di rilasciare indicatori del recinto prima di scrivere nel buffer nuovi contenuti che che le è 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 aver restituito i recinti attuali, puoi 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.