Raccolta del pacing dei frame Componente del Kit di sviluppo dei giochi Android.

La libreria di pacing dei frame Android, nota anche come Swappy, fa parte delle Librerie AGDK. Consente ai giochi OpenGL e Vulkan di ottenere un rendering fluido e un corretto pacing del frame su Android. Questo documento definisce il pacing dei frame, descrive le situazioni in cui è necessario il pacing dei frame e mostra in che modo la libreria risponde a queste situazioni. Se vuoi passare direttamente all'implementazione del pacing del frame nel gioco, consulta il Passaggio successivo.

Premessa

Il pacing di frame è la sincronizzazione della logica e del ciclo di rendering di un gioco con il sottosistema di visualizzazione di un sistema operativo e l'hardware di visualizzazione sottostante. Il sottosistema display Android è stato progettato per evitare artefatti visivi (noti come lacrime) che possono verificarsi quando l'hardware del display passa a un nuovo frame durante un aggiornamento. Per evitare questi artefatti, il sottosistema display:

  • Esegue il buffer interno dei frame
  • Rileva gli invii di frame in ritardo
  • Consente di ripetere la visualizzazione dei frame precedenti quando vengono rilevati frame tardivi

Un gioco informa SurfaceFlinger, il compositore all'interno del sottosistema display, che ha inviato tutte le richieste di estrazione necessarie per un frame (chiamando eglSwapBuffers o vkQueuePresentKHR). SurfaceFlinger segnala la disponibilità di un frame all'hardware del display utilizzando un blocco. L'hardware del display mostra quindi il frame specificato. L'hardware del display riscontra a una frequenza costante, ad esempio a 60 Hz, e se non è presente un nuovo frame quando l'hardware ne ha bisogno, l'hardware visualizza di nuovo il frame precedente.

Spesso si verificano tempi di frame incoerenti quando il rendering di un loop di rendering del gioco viene eseguito a una frequenza diversa rispetto all'hardware display nativo. Se un gioco a 30 f/s tenta di eseguire il rendering su un dispositivo che supporta nativamente la frequenza di 60 f/s, il loop di rendering del gioco non si rende conto che sullo schermo rimane un frame ripetuto per altri 16 millisecondi in più. Questa disconnessione di solito crea incoerenze significative nella durata frame, ad esempio: 49 millisecondi, 16 millisecondi, 33 millisecondi. Le scene eccessivamente complesse aggravano ulteriormente il problema, poiché causano la perdita di frame.

Soluzioni non ottimali

Le seguenti soluzioni per il pacing dei frame sono state utilizzate dai giochi in passato e solitamente comportano tempi di frame incoerenti e una maggiore latenza di input.

Invia frame con la velocità consentita dall'API di rendering

Questo approccio collega un gioco all'attività variabile SurfaceFlinger e introduce un frame di latenza aggiuntivo. La pipeline di visualizzazione contiene una coda di frame, di solito della dimensione 2, che si riempie se il gioco cerca di presentare frame troppo velocemente. Se la coda non contiene più spazio, il ciclo di gioco (o almeno il thread di rendering) viene bloccato da una chiamata OpenGL o Vulkan. Il gioco viene quindi costretto ad attendere che l'hardware del display mostri un frame e questa contropressione sincronizza i due componenti. Questa situazione è nota come buffer stuffing o coda. Il processo di rendering non si rende conto di cosa sta succedendo, pertanto l'incoerenza della frequenza fotogrammi può peggiorare. Se gli esempi di gioco vengono inseriti prima del frame, la latenza di input peggiora.

Utilizzare Android Choreographer da solo

I giochi usano anche Android Choreographer per la sincronizzazione. Questo componente, disponibile in Java dall'API 16 e in C++ dall'API 24, pubblica tick regolari con la stessa frequenza del sottosistema display. Sussistono ancora delle sottigliezze riguardo al momento in cui questo segno di spunta viene pubblicato rispetto all'effettivo VSYNC dell'hardware, i quali variano a seconda del dispositivo. Il riempimento del buffer potrebbe comunque verificarsi per i frame lunghi.

Vantaggi della libreria di pacing dei frame

La libreria di pacing del frame utilizza Android Choreographer per la sincronizzazione e si occupa della variabilità dell'erogazione dei segni di graduazione. Utilizza i timestamp di presentazione per assicurarsi che i frame vengano presentati al momento giusto e sincronizzare i recinzioni per evitare il buffer stuffing. La libreria utilizza l'NDK Choreographer se è disponibile e, in caso contrario, si affida al Coreografo Java.

La libreria gestisce più frequenze di aggiornamento se sono supportate dal dispositivo, il che offre al gioco maggiore flessibilità nel presentare un frame. Ad esempio, per un dispositivo che supporta sia una frequenza di aggiornamento di 60 Hz sia una frequenza di 90 Hz, un gioco che non è in grado di produrre 60 frame al secondo può scendere a 45 FPS anziché 30 FPS per rimanere stabile. La libreria rileva la frequenza fotogrammi prevista del gioco e regola automaticamente i tempi di presentazione dei frame di conseguenza. La libreria di pacing dei frame migliora anche la durata della batteria perché evita inutili aggiornamenti del display. Ad esempio, se un gioco esegue il rendering a 60 f/s, ma il display si aggiorna a 120 Hz, lo schermo viene aggiornato due volte per ogni frame. La libreria di pacing dei frame evita questo problema impostando la frequenza di aggiornamento sul valore supportato dal dispositivo più vicino alla frequenza frame target.

Come funziona

Le seguenti sezioni mostrano in che modo la libreria del pacing frame gestisce i frame dei giochi lunghi e brevi per ottenere un pacing corretto dei frame.

Correggi pacing del frame a 30 Hz

Con un rendering a 30 Hz su un dispositivo a 60 Hz, la situazione ideale su Android è mostrata nella Figura 1. SurfaceFlinger blocca nuovi buffer grafici, se presenti (NB nel diagramma indica "nessun buffer" presente e il precedente è ripetuto).

Pacing ideale dei fotogrammi a 30 Hz su un dispositivo a 60 Hz

Figura 1. Pacing ideale dei fotogrammi a 30 Hz su un dispositivo a 60 Hz

I frame brevi dei giochi causano stuttering

Sulla maggior parte dei dispositivi moderni, i motori di gioco si basano sul coreografo della piattaforma che assegna i segni di graduazione per incentivare l'invio dei frame. Tuttavia, esiste ancora la possibilità di un pacing dei frame scadente a causa di frame corti, come si vede nella Figura 2. I frame brevi seguiti da quelli lunghi vengono percepiti dal player come stuttering.

Fotogrammi brevi per i giochi

Figura 2. Il frame breve C del gioco fa sì che per il frame B venga mostrato un solo frame, seguito da più frame C

La libreria di pacing dei frame risolve questo problema utilizzando i timestamp della presentazione. La libreria utilizza le estensioni del timestamp della presentazione EGL_ANDROID_presentation_time e VK_GOOGLE_display_timing per fare in modo che i frame non vengano presentati in anticipo, come si vede nella figura 3.

Timestamp della presentazione

Figura 3. Frame di gioco B presentato due volte per una visualizzazione più fluida

I frame lunghi causano stuttering e latenza

Quando il carico di lavoro di visualizzazione richiede più tempo di quello dell'applicazione, vengono aggiunti frame aggiuntivi alla coda. Ciò porta, ancora una volta, a stuttering e può portare a un frame aggiuntivo di latenza dovuto al buffer stuffing (vedi Figura 4). La libreria rimuove lo stuttering e il frame aggiuntivo di latenza.

Frame da gioco lunghi

Figura 4. Il frame lungo B restituisce un pacing errato per due frame: A e B

La libreria risolve questo problema utilizzando blocchi di sincronizzazione (EGL_KHR_fence_sync e VkFence) per inserire le attese nell'applicazione che consentano alla pipeline di visualizzazione di recuperare i dati, anziché consentire l'aumento della pressione. Il frame A presenta ancora un frame aggiuntivo, ma ora il frame B viene presentato correttamente, come si vede nella figura 5.

Attesa aggiunta al livello dell'applicazione

Figura 5. I frame C e D attendono la presentazione

Modalità operative supportate

Puoi configurare la libreria di pacing del frame in modo che operi in una delle tre modalità seguenti:

  • Modalità automatica disattivata + pipeline
  • Modalità automatica attivata + Pipeline
  • Modalità automatica attivata + Modalità pipeline automatica (pipeline/senza pipeline)

Puoi fare esperimenti con le modalità in modalità automatica e pipeline, ma puoi iniziare disattivandole e includendo quanto segue dopo aver inizializzato Swappy:

  swappyAutoSwapInterval(false);
  swappyAutoPipelineMode(false);
  swappyEnableStats(false);
  swappySwapIntervalNS(1000000000L/yourPreferredFrameRateInHz);

Modalità pipeline

Per coordinare i carichi di lavoro del motore, la libreria in genere utilizza un modello di pipeline che separa i carichi di lavoro di CPU e GPU oltre i confini di VSYNC.

Modalità pipeline

Figura 6. Modalità pipeline

Modalità senza pipeline

In generale, questo approccio comporta una latenza della schermata di input inferiore e più prevedibile. Nei casi in cui un gioco ha un tempo di frame molto ridotto, sia i carichi di lavoro di CPU e GPU possono rientrare in un singolo intervallo di scambio. In questo caso, un approccio senza pipeline garantirebbe in realtà una latenza della schermata di input inferiore.

Modalità senza pipeline

Figura 7. Modalità senza pipeline

Modalità Automatica

La maggior parte dei giochi non sa come scegliere l'intervallo di scambio, ovvero la durata di presentazione di ogni frame (ad esempio, 33,3 ms per 30 Hz). Su alcuni dispositivi, il rendering di un gioco può essere eseguito a 60 FPS, mentre su un altro potrebbe essere necessario scendere a un valore inferiore. La modalità automatica misura i tempi di CPU e GPU per:

  • Seleziona automaticamente intervalli di scambio. I giochi con frequenza di 30 Hz in alcune scene e 60 Hz in altre possono consentire alla libreria di regolare dinamicamente questo intervallo.
  • Disattiva pipeline per frame ultraveloci: offre una latenza della schermata di input ottimale in tutti i casi.

Più frequenze di aggiornamento

I dispositivi che supportano diverse frequenze di aggiornamento offrono una maggiore flessibilità nella scelta di un intervallo di scambio uniforme:

  • Su dispositivi a 60 Hz: 60 f/s/30 f/s/20 f/s
  • Su dispositivi da 60 Hz + 90 Hz: 90 f/s/60 f/s/45 f/s/30 f/s
  • Su dispositivi 60 Hz + 90 Hz + 120 Hz: 120 f/s/90 f/s/60 f/s/45 f/s/40 f/s/30 f/s

La libreria sceglie la frequenza di aggiornamento più adatta alla durata effettiva di rendering dei frame di un gioco, offrendo un'esperienza visiva migliore.

Per ulteriori informazioni sul pacing di più frame con frequenza di aggiornamento, consulta il post del blog Rendering ad alta frequenza di aggiornamento su Android.

Statistiche frame

La libreria di pacing frame offre le seguenti statistiche per scopi di debug e profilazione:

  • Un istogramma del numero di schermate aggiorna un frame atteso nella coda del compositore al termine del rendering.
  • Un istogramma del numero di aggiornamenti dello schermo trascorsi tra l'ora di presentazione richiesta e l'ora attuale effettiva.
  • Un istogramma del numero di aggiornamenti della schermata trascorsi tra due frame consecutivi.
  • Un istogramma del numero di aggiornamenti dello schermo passati tra l'inizio del lavoro della CPU per questo frame e l'ora attuale effettiva.

Passaggio successivo

Consulta una delle seguenti guide per integrare la libreria di pacing del frame Android nel tuo gioco: