Implementare l'HAL Hardware Composer

L'HAL Hardware Composer (HWC) compone i livelli ricevuti da SurfaceFlinger, riducendo la quantità di composizione OpenGL ES (GLES) e le prestazioni della GPU.

L'HWC astrae gli oggetti, come le sovrapposizioni e i blitter 2D, per comporre le superfici e comunica con l'hardware di composizione delle finestre specializzato per comporre le finestre. Utilizza HWC per comporre le finestre anziché SurfaceFlinger con la GPU. La maggior parte delle GPU non è ottimizzata per la composizione e, quando la GPU compone i livelli da SurfaceFlinger, le app non possono utilizzare la GPU per il proprio rendering.

Le implementazioni HWC devono supportare:

  • Almeno quattro overlay:
    • Barra di stato
    • Barra di sistema
    • App
    • Sfondo
  • Livelli più grandi del display (ad esempio, uno sfondo)
  • Fusione alfa per pixel e per piano premoltiplicata simultanea
  • Percorso hardware per la riproduzione di video protetti
  • Ordine di pacchettizzazione RGBA, formati YUV e proprietà di tiling, swizzling e stride

Per implementare l'HWC:

  1. Implementa un HWC non operativo e invia tutto il lavoro di composizione a GLES.
  2. Implementa un algoritmo per delegare la composizione all'HWC in modo incrementale. Ad esempio, delega solo le prime tre o quattro superfici all'hardware di sovrapposizione dell'HWC.
  3. Ottimizza l'HWC. Potrebbe includere:
    • Selezionando le superfici che massimizzano il carico della GPU e inviandole all'HWC.
    • Rilevamento dell'aggiornamento dello schermo. In caso contrario, delega la composizione a GLES anziché a HWC per risparmiare energia. Quando lo schermo si aggiorna di nuovo, continua a scaricare la composizione sull'HWC.
    • Prepararsi per casi d'uso comuni, ad esempio:
      • La schermata Home, che include la barra di stato, la barra di sistema, la finestra dell'app e gli sfondi animati
      • Giochi a schermo intero in modalità verticale e orizzontale
      • Video a schermo intero con sottotitoli codificati e controllo della riproduzione
      • Riproduzione di video protetti
      • Multifinestra con schermo diviso

Primitive HWC

L'HWC fornisce due primitive, layers e displays, per rappresentare il lavoro di composizione e la sua interazione con l'hardware del display. L'HWC fornisce anche il controllo su VSync e un callback a SurfaceFlinger per comunicare quando si verifica un evento VSync.

Interfaccia HIDL

Android 8.0 e versioni successive utilizzano un'interfaccia HIDL chiamata Composer HAL per IPC binderizzato tra HWC e SurfaceFlinger. L'HAL di Composer sostituisce l'interfaccia hwcomposer2.h legacy. Se i fornitori forniscono un'implementazione di Composer HAL dell'HWC, Composer HAL accetta direttamente le chiamate HIDL da SurfaceFlinger. Se i fornitori forniscono un'implementazione legacy dell'HWC, Composer HAL carica i puntatori di funzione da hwcomposer2.h, inoltrando le chiamate HIDL alle chiamate dei puntatori di funzione.

L'HWC fornisce funzioni per determinare le proprietà di un determinato display, per passare da una configurazione del display all'altra (ad esempio risoluzione 4K o 1080p) e da una modalità colore all'altra (ad esempio colore nativo o sRGB reale) e per accendere, spegnere o impostare la modalità a basso consumo del display, se supportata.

Puntatori a funzione

Se i fornitori implementano direttamente Composer HAL, SurfaceFlinger chiama le sue funzioni tramite HIDL IPC. Ad esempio, per creare un livello, SurfaceFlinger chiama createLayer() su Composer HAL.

Se i fornitori implementano l'interfaccia hwcomposer2.h, Composer HAL chiama i puntatori di funzione hwcomposer2.h. Nei commenti hwcomposer2.h, le funzioni dell'interfaccia HWC sono indicate con nomi in formato lowerCamelCase che non esistono nell'interfaccia come campi denominati. Quasi tutte le funzioni vengono caricate richiedendo un puntatore di funzione utilizzando getFunction fornito da hwc2_device_t. Ad esempio, la funzione createLayer è un puntatore a funzione di tipo HWC2_PFN_CREATE_LAYER, che viene restituito quando il valore enumerato HWC2_FUNCTION_CREATE_LAYER viene trasmesso a getFunction.

Per la documentazione dettagliata sulle funzioni HAL di Composer e sulle funzioni di passthrough di HWC, consulta composer. Per la documentazione dettagliata sui puntatori di funzione HWC, consulta hwcomposer2.h.

Maniglie dei livelli e del display

I livelli e i display vengono manipolati tramite i punti di manipolazione generati dall'HWC. Gli handle sono opachi per SurfaceFlinger.

Quando SurfaceFlinger crea un nuovo livello, chiama createLayer, che restituisce il tipo Layer per le implementazioni dirette o hwc2_layer_t per le implementazioni passthrough. Quando SurfaceFlinger modifica una proprietà di questo livello, SurfaceFlinger passa il valore hwc2_layer_t alla funzione di modifica appropriata insieme a qualsiasi altra informazione necessaria per apportare la modifica. Il tipo hwc2_layer_t è abbastanza grande da contenere un puntatore o un indice.

I display fisici vengono creati tramite hotplug. Quando un display fisico viene inserito a caldo, HWC crea un handle e lo passa a SurfaceFlinger tramite il callback hotplug. I display virtuali vengono creati da SurfaceFlinger che chiama createVirtualDisplay() per richiedere un display. Se HWC supporta la composizione del display virtuale, restituisce un handle. Poi, SurfaceFlinger delega la composizione dei display all'HWC. Se HWC non supporta la composizione dello schermo virtuale, SurfaceFlinger crea l'handle e compone lo schermo.

Visualizza le operazioni di composizione

Una volta per VSync, SurfaceFlinger si riattiva se ha nuovi contenuti da comporre. Questi nuovi contenuti possono essere nuovi buffer di immagini delle app o una modifica delle proprietà di uno o più livelli. Quando SurfaceFlinger lo riattiva:

  1. Gestisce le transazioni, se presenti.
  2. Blocca i nuovi buffer grafici, se presenti.
  3. Esegue una nuova composizione, se il passaggio 1 o 2 ha comportato una modifica ai contenuti visualizzati.

Per eseguire una nuova composizione, SurfaceFlinger crea ed elimina i livelli o modifica gli stati dei livelli, a seconda dei casi. Aggiorna anche i livelli con i loro contenuti attuali, utilizzando chiamate come setLayerBuffer o setLayerColor. Dopo l'aggiornamento di tutti i livelli, SurfaceFlinger chiama validateDisplay, che indica all'HWC di esaminare lo stato dei livelli e determinare come procedere con la composizione. Per impostazione predefinita, SurfaceFlinger tenta di configurare ogni livello in modo che venga composto dall'HWC; tuttavia, in alcune circostanze, SurfaceFlinger compone i livelli tramite il fallback della GPU.

Dopo la chiamata a validateDisplay, SurfaceFlinger chiama getChangedCompositionTypes per verificare se l'HWC vuole che venga modificato uno dei tipi di composizione dei livelli prima di eseguire la composizione. Per accettare le modifiche, SurfaceFlinger chiama acceptDisplayChanges.

Se alcuni livelli sono contrassegnati per la composizione di SurfaceFlinger, SurfaceFlinger li compone nel buffer di destinazione. SurfaceFlinger chiama quindi setClientTarget per dare il buffer al display in modo che possa essere visualizzato sullo schermo o ulteriormente composto con livelli che non sono stati contrassegnati per la composizione di SurfaceFlinger. Se non sono contrassegnati livelli per la composizione di SurfaceFlinger, SurfaceFlinger ignora il passaggio di composizione.

Infine, SurfaceFlinger chiama presentDisplay per comunicare all'HWC di completare la procedura di composizione e visualizzare il risultato finale.

Più annunci display

Android 10 supporta più display fisici. Quando progetti un'implementazione HWC destinata all'uso su Android 7.0 e versioni successive, esistono alcune limitazioni non presenti nella definizione HWC:

  • Si presume che esista esattamente un display interno. Il display interno è quello che viene segnalato dall'hotplug iniziale durante l'avvio. Una volta collegato a caldo, il display interno non può essere disconnesso.
  • Oltre al display interno, è possibile collegare a caldo un numero qualsiasi di display esterni durante il normale funzionamento del dispositivo. Il framework presuppone che tutti gli hotplug dopo il primo display interno siano display esterni, quindi se vengono aggiunti altri display interni, vengono classificati in modo errato come Display.TYPE_HDMI anziché Display.TYPE_BUILT_IN.

Sebbene le operazioni di SurfaceFlinger descritte sopra vengano eseguite per display, vengono eseguite in sequenza per tutti i display attivi, anche se vengono aggiornati solo i contenuti di un display.

Ad esempio, se il display esterno viene aggiornato, la sequenza è:

// In Android 9 and lower:

// Update state for internal display
// Update state for external display
validateDisplay(<internal display>)
validateDisplay(<external display>)
presentDisplay(<internal display>)
presentDisplay(<external display>)

// In Android 10 and higher:

// Update state for internal display
// Update state for external display
validateInternal(<internal display>)
presentInternal(<internal display>)
validateExternal(<external display>)
presentExternal(<external display>)

Composizione del display virtuale

La composizione del display virtuale è simile a quella del display esterno. La differenza tra la composizione dello schermo virtuale e quella dello schermo fisico è che gli schermi virtuali inviano l'output a un buffer Gralloc anziché allo schermo. Hardware Composer (HWC) scrive l'output in un buffer, fornisce la barriera di completamento e invia il buffer a un consumer (ad esempio il codificatore video, la GPU, la CPU e così via). I display virtuali possono utilizzare 2D/blitter o overlay se la pipeline di visualizzazione scrive nella memoria.

Modalità

Ogni frame si trova in una delle tre modalità dopo che SurfaceFlinger chiama il metodo HWC validateDisplay():

  • GLES: la GPU compone tutti i livelli, scrivendo direttamente nel buffer di output. L'HWC non è coinvolta nella composizione.
  • MISTO: la GPU compone alcuni livelli nel frame buffer e HWC compone il frame buffer e i livelli rimanenti, scrivendo direttamente nel buffer di output.
  • HWC: HWC compone tutti i livelli e scrive direttamente nel buffer di output.

Formato di output

I formati di output del buffer del display virtuale dipendono dalla modalità:

  • Modalità GLES: il driver EGL imposta il formato del buffer di output in dequeueBuffer(), in genere RGBA_8888. Il consumatore deve essere in grado di accettare il formato di output impostato dal driver, altrimenti il buffer non può essere letto.
  • Modalità MISTA e HWC: se il consumatore ha bisogno dell'accesso alla CPU, imposta il formato. In caso contrario, il formato è IMPLEMENTATION_DEFINED e Gralloc imposta il formato migliore in base ai flag di utilizzo. Ad esempio, Gralloc imposta un formato YCbCr se il consumer è un codificatore video e HWC può scrivere il formato in modo efficiente.

Barriere di sincronizzazione

Le recinzioni di sincronizzazione sono un aspetto fondamentale del sistema grafico di Android. Le barriere consentono alla CPU di funzionare indipendentemente dal lavoro simultaneo della GPU, bloccando solo in caso di una vera dipendenza.

Ad esempio, quando un'app invia un buffer in fase di produzione sulla GPU, invia anche un oggetto barriera di sincronizzazione. Questa barriera segnala quando la GPU ha finito di scrivere nel buffer.

L'HWC richiede che la GPU finisca di scrivere i buffer prima che vengano visualizzati. Le barriere di sincronizzazione vengono trasmesse tramite la pipeline grafica con buffer e segnalano quando i buffer vengono scritti. Prima che venga visualizzato un buffer, HWC controlla se la barriera di sincronizzazione ha segnalato e, in caso affermativo, visualizza il buffer.

Per saperne di più sulle barriere di sincronizzazione, consulta Integrazione di Hardware Composer.