Libreria Frame Pacing   Parte dell'Android Game Development Kit.

La libreria Android Frame Pacing, nota anche come Swappy, fa parte delle librerie AGDK. Consente ai giochi OpenGL e Vulkan di ottenere un rendering fluido e una corretta spaziatura dei frame su Android. Questo documento definisce il frame pacing, descrive le situazioni in cui è necessario e mostra come la libreria gestisce queste situazioni. Se vuoi passare direttamente all'implementazione del frame pacing nel tuo gioco, consulta la sezione Passaggio successivo.

Sfondo

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

  • Memorizza internamente i frame precedenti
  • Rileva l'invio di frame in ritardo
  • Ripete la visualizzazione dei fotogrammi precedenti quando vengono rilevati fotogrammi in ritardo

Un gioco comunica a SurfaceFlinger, il compositore all'interno del sottosistema di visualizzazione, di aver inviato tutte le chiamate di disegno necessarie per un frame (chiamando eglSwapBuffers o vkQueuePresentKHR). SurfaceFlinger segnala la disponibilità di un frame all'hardware di visualizzazione utilizzando un latch. L'hardware del display mostra quindi il frame specificato. L'hardware del display funziona a una velocità costante, ad esempio 60 Hz, e se non è presente un nuovo frame quando l'hardware ne ha bisogno, l'hardware visualizza nuovamente il frame precedente.

Tempi di frame incoerenti si verificano spesso quando un ciclo di rendering del gioco viene eseguito a una velocità diversa rispetto all'hardware di visualizzazione nativo. Se un gioco in esecuzione a 30 FPS tenta di essere visualizzato su un dispositivo che supporta nativamente 60 FPS, il ciclo di rendering del gioco non si rende conto che un frame ripetuto rimane sullo schermo per altri 16 millisecondi. Questo disallineamento di solito crea un'incoerenza sostanziale nei tempi dei fotogrammi, ad esempio: 49 millisecondi, 16 millisecondi, 33 millisecondi. Le scene eccessivamente complesse aggravano ulteriormente il problema, in quanto causano la perdita di fotogrammi.

Soluzioni non ottimali

Le seguenti soluzioni per la spaziatura dei fotogrammi sono state utilizzate in passato dai giochi e in genere comportano tempi di frame incoerenti e una maggiore latenza di input.

Inviare i frame il più rapidamente possibile in base alle API di rendering

Questo approccio lega un gioco all'attività variabile di SurfaceFlinger e introduce un frame di latenza aggiuntivo. La pipeline di visualizzazione contiene una coda di frame, in genere di dimensione 2, che si riempie se il gioco tenta di presentare i frame troppo velocemente. Non essendoci più spazio nella coda, il ciclo di gioco (o almeno il thread di rendering) è bloccato da una chiamata OpenGL o Vulkan. Il gioco è 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 queue-stuffing. Il processo di rendering non si rende conto di cosa sta succedendo, quindi l'incoerenza del framerate peggiora. Se il gioco campiona l'input prima del frame, la latenza di input peggiora.

Utilizzare Android Choreographer da solo

Anche i giochi utilizzano Android Choreographer per la sincronizzazione. Questo componente, disponibile in Java dall'API 16 e in C++ dall'API 24, fornisce tick regolari alla stessa frequenza del sottosistema di visualizzazione. Esistono ancora sottigliezze su quando viene inviato questo tick rispetto alla VSYNC hardware effettiva e questi offset variano in base al dispositivo. Il buffer-stuffing potrebbe comunque verificarsi per i frame lunghi.

Vantaggi della libreria Frame Pacing

La libreria Frame Pacing utilizza Android Choreographer per la sincronizzazione e gestisce la variabilità nella distribuzione dei tick. Utilizza i timestamp di presentazione per assicurarsi che i frame vengano presentati al momento giusto e le barriere di sincronizzazione per evitare il riempimento del buffer. La libreria utilizza NDK Choreographer se disponibile e torna a Java Choreographer se non lo è.

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

Come funziona

Le sezioni seguenti mostrano come la libreria Frame Pacing gestisce i frame di gioco lunghi e brevi per ottenere un frame pacing corretto.

Pacing dei frame corretto a 30 Hz

Il rendering a 30 Hz su un dispositivo a 60 Hz è la situazione ideale su Android, come mostrato nella figura 1. SurfaceFlinger acquisisce nuovi buffer grafici, se presenti (NB nel diagramma indica "nessun buffer" presente e quello precedente viene ripetuto).

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

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

Frame di gioco brevi causano interruzioni

Sulla maggior parte dei dispositivi moderni, i motori di gioco si basano sul coreografo della piattaforma che fornisce i tick per guidare l'invio dei frame. Tuttavia, esiste ancora la possibilità di una scarsa spaziatura dei fotogrammi a causa di fotogrammi brevi, come mostrato nella figura 2. I frame brevi seguiti da frame lunghi vengono percepiti dal giocatore come balbuzie.

Frame di gioco brevi

Figura 2. Il frame C della partita breve fa sì che il frame B presenti un solo frame, seguito da più frame C.

La libreria Frame Pacing risolve questo problema utilizzando i timestamp di presentazione. La libreria utilizza le estensioni del timestamp di presentazione EGL_ANDROID_presentation_time e VK_GOOGLE_display_timing in modo che i frame non vengano presentati in anticipo, come mostrato nella Figura 3.

Timestamp della presentazione

Figura 3. Il fotogramma B del gioco viene presentato due volte per una visualizzazione più fluida.

I frame lunghi causano stuttering e latenza

Quando il workload di visualizzazione richiede più tempo del workload dell'applicazione, i frame aggiuntivi vengono aggiunti a una coda. Ciò comporta, ancora una volta, un effetto stuttering e potrebbe anche causare un frame di latenza aggiuntivo a causa del riempimento del buffer (vedi figura 4). La libreria rimuove sia lo stuttering che il frame di latenza aggiuntivo.

Frame di gioco lunghi

Figura 4. Il frame B lungo dà un pacing errato per 2 frame: A e B

La libreria risolve questo problema utilizzando barriere di sincronizzazione (EGL_KHR_fence_sync e VkFence) per inserire attese nell'applicazione che consentono alla pipeline di visualizzazione di recuperare invece di consentire l'accumulo di contropressione. Il frame A presenta ancora un frame aggiuntivo, ma il frame B ora viene visualizzato correttamente, come mostrato nella Figura 5.

Attese aggiunte al livello di applicazione

Figura 5. I frame C e D sono in attesa di essere presentati.

Modalità operative supportate

Puoi configurare la libreria Frame Pacing in una delle tre modalità seguenti:

  • Modalità automatica disattivata + Pipeline
  • Modalità automatica + Pipeline
  • Modalità automatica attiva + Modalità pipeline automatica (pipeline/non pipeline)

Puoi sperimentare le modalità automatica e pipeline, ma devi 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 utilizza in genere un modello di pipeline che separa i carichi di lavoro della CPU e della GPU in base ai limiti VSYNC.

Modalità pipeline

Figura 6. Modalità pipeline.

Modalità non pipeline

In generale, questo approccio comporta una latenza dello schermo di input inferiore e più prevedibile. Nei casi in cui un gioco ha una durata frame molto bassa, i carichi di lavoro della CPU e della GPU potrebbero rientrare in un singolo intervallo di scambio. In questo caso, un approccio non in pipeline offrirebbe una latenza dello schermo di input inferiore.

Modalità non pipeline

Figura 7. Modalità non pipeline.

Modalità Automatica

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

  • Seleziona automaticamente gli intervalli di scambio: i giochi che offrono 30 Hz in alcune scene e 60 Hz in altre possono consentire alla libreria di regolare questo intervallo in modo dinamico.
  • Disattiva il pipelining per frame ultraveloci: offre una latenza ottimale tra input e schermo in tutti i casi.

Frequenze di aggiornamento multiple

I dispositivi che supportano più frequenze di aggiornamento offrono una maggiore flessibilità nella scelta di un intervallo di scambio che appaia fluido:

  • Su dispositivi a 60 Hz: 60 FPS / 30 FPS / 20 FPS
  • Su dispositivi a 60 Hz + 90 Hz: 90 FPS / 60 FPS / 45 FPS / 30 FPS
  • Su dispositivi a 60 Hz + 90 Hz + 120 Hz: 120 FPS / 90 FPS / 60 FPS / 45 FPS/ 40 FPS / 30 FPS

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

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

Statistiche dei frame

La libreria Frame Pacing offre le seguenti statistiche per il debug e la profilazione:

  • Un istogramma del numero di aggiornamenti dello schermo in attesa di un frame nella coda del compositore dopo il completamento del rendering.
  • Un istogramma del numero di aggiornamenti dello schermo trascorsi tra l'ora di presentazione richiesta e l'ora di presentazione effettiva.
  • Un istogramma del numero di aggiornamenti dello schermo passati tra due frame consecutivi.
  • Un istogramma del numero di aggiornamenti dello schermo trascorsi tra l'inizio del lavoro della CPU per questo frame e l'ora di presentazione effettiva.

Passaggio successivo

Consulta una delle seguenti guide per integrare la libreria Android Frame Pacing nel tuo gioco:

Risorse aggiuntive