Libreria di pacing dei frame Componente dell'Android Game Development Kit.
La libreria Android Frame Pacing, nota anche come Swappy, fa parte delle librerie AGDK. Aiuta i giochi OpenGL e Vulkan a ottenere un rendering fluido e un corretto pacing dei frame su Android. Questo documento definisce il pacing dei frame, descrive le situazioni in cui è necessario e mostra come la libreria gestisce queste situazioni. Se vuoi passare direttamente all'implementazione del pacing dei frame nel tuo gioco, consulta la sezione Passaggio successivo.
Sfondo
La sincronizzazione dei frame è la sincronizzazione del loop 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 di Android è stato progettato per evitare artefatti visivi (noti come tearing) che possono verificarsi quando l'hardware del display passa a un nuovo frame durante un aggiornamento. Per evitare questi artefatti, il sottosistema di visualizzazione esegue quanto segue:
- Memorizza internamente i frame precedenti
- Rileva l'invio in ritardo dei fotogrammi
- Ripete la visualizzazione dei frame precedenti quando vengono rilevati frame in ritardo
Un gioco informa 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 del display utilizzando un latch. L'hardware del display mostra quindi il frame specificato. L'hardware del display
si aggiorna a una frequenza costante, ad esempio 60 Hz, e se non è presente un nuovo frame quando
l'hardware ne ha bisogno, l'hardware mostra di nuovo il frame precedente.
I tempi di frame incoerenti si verificano spesso quando un loop di rendering di un gioco viene eseguito a una frequenza diversa rispetto all'hardware di visualizzazione nativo. Se un gioco a 30 FPS tenta di eseguire il rendering su un dispositivo che supporta nativamente 60 FPS, il loop di rendering del gioco non si accorge che un frame ripetuto rimane sullo schermo per altri 16 millisecondi. Questa disconnessione in genere crea una sostanziale incoerenza nei tempi di frame, ad esempio: 49 millisecondi, 16 millisecondi, 33 millisecondi. Le scene eccessivamente complesse aggravano ulteriormente il problema, poiché causano la perdita di fotogrammi.
Soluzioni non ottimali
In passato, i giochi hanno utilizzato le seguenti soluzioni per il pacing dei frame, che in genere hanno comportato tempi di frame incoerenti e un aumento della latenza di input.
Invia i frame il più rapidamente possibile in base alle limitazioni dell'API di rendering
Questo approccio lega un gioco all'attività variabile di SurfaceFlinger e introduce un altro frame di latenza. La pipeline di visualizzazione contiene una coda di frame, tipicamente di dimensione 2, che si riempie se il gioco tenta di presentare i frame troppo rapidamente. Non essendoci più spazio nella coda, il ciclo di gioco (o almeno il thread di rendering) viene 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'incongruenza del frame rate 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, genera tick regolari alla stessa frequenza del sottosistema di visualizzazione. Esistono ancora sfumature su quando viene inviato questo tick rispetto al VSYNC hardware effettivo e questi offset variano in base al dispositivo. Il buffering potrebbe comunque verificarsi per i frame lunghi.
Vantaggi della libreria Frame Pacing
La libreria Frame Pacing utilizza Android Choreographer per la sincronizzazione e si occupa della variabilità nell'invio 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 raccolta utilizza NDK Choreographer se disponibile e, in caso contrario, passa a Java Choreographer.
La libreria gestisce più frequenze di aggiornamento, se supportate dal dispositivo, in modo da offrire a un gioco una 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 scorrevole. La libreria rileva la frequenza fotogrammi prevista del gioco e regola automaticamente i tempi di presentazione dei fotogrammi di conseguenza. La libreria Frame Pacing migliora anche l'autonomia della batteria perché evita aggiornamenti del display non necessari. Ad esempio, se un gioco viene visualizzato 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 riportate di seguito mostrano come la libreria Frame Pacing gestisce i frame di gioco lunghi e brevi per ottenere un frame pacing corretto.
Temporizzazione dei fotogrammi corretta a 30 Hz
Quando esegui il rendering a 30 Hz su un dispositivo a 60 Hz, la situazione ideale su Android è illustrata nella figura 1. SurfaceFlinger aggancia i nuovi buffer grafici, se presenti (NB nel diagramma viene indicato "nessun buffer" presente e quello precedente viene ripetuto).
Figura 1. Tempo di esecuzione dei fotogrammi ideale a 30 Hz su un dispositivo a 60 Hz.
I frame dei giochi brevi causano balbuzie
Sulla maggior parte dei dispositivi moderni, i motori di gioco si basano sul coreographer della piattaforma che fornisce i tick per gestire l'invio dei frame. Tuttavia, esiste ancora la possibilità di un cattivo pacing dei frame a causa di frame brevi, come mostrato nella figura 2. I frame brevi seguiti da frame lunghi vengono percepiti dal player come scatti.
Figura 2. Il frame C del gioco breve fa sì che il frame B presenti un solo frame, seguito da più frame C.
La libreria Frame Pacing risolve il problema utilizzando i timestamp di presentazione. La biblioteca 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.
Figura 3. Frame di gioco B mostrato due volte per una visualizzazione più fluida.
I frame lunghi causano balbuzie e latenza
Quando il carico di lavoro di visualizzazione richiede più tempo del carico di lavoro dell'applicazione, vengono aggiunti frame aggiuntivi a una coda. Ciò porta, ancora una volta, a balbuzie e potrebbe anche portare a un frame di latenza aggiuntivo a causa del buffering (vedi figura 4). La biblioteca rimuove sia lo stuttering sia il frame extra di latenza.
Figura 4. Il frame B lungo genera un pacing errato per 2 frame: A e B
La libreria risolve il problema utilizzando i recinti di sincronizzazione
(EGL_KHR_fence_sync
e
VkFence
)
per iniettare nell'applicazione delle interruzioni che consentono alla pipeline di visualizzazione di recuperare, anziché consentire l'accumulo di pressione. Il frame A presenta ancora un frame aggiuntivo, ma il frame B ora viene visualizzato correttamente, come mostrato nella figura 5.
Figura 5. I frame C e D sono in attesa di essere presentati.
Modalità di funzionamento supportate
Puoi configurare la libreria Frame Pacing in una delle tre seguenti modalità:
- Modalità automatica disattivata + Pipeline
- Modalità automatica attiva + Pipeline
- Modalità automatica attiva + modalità pipeline automatica (pipeline/non pipeline)
Modalità consigliata
Puoi fare esperimenti con la modalità automatica e le modalità pipeline, ma inizia 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 dell'engine, la libreria utilizza in genere un modello di pipeline che separa i carichi di lavoro della CPU e della GPU in base ai confini VSYNC.
Figura 6. Modalità Pipeline.
Modalità non pipeline
In generale, questo approccio comporta una latenza della schermata di input inferiore e più prevedibile. Se un gioco ha una durata frame molto bassa, i carichi di lavoro della CPU e della GPU possono rientrare in un singolo intervallo di scambio. In questo caso, un approccio non in pipeline avrebbe effettivamente una latenza dello schermo di input inferiore.
Figura 7. Modalità non pipeline.
Modalità Automatica
La maggior parte dei giochi non sa come scegliere l'intervallo di scambio, ovvero la durata per la quale viene presentato ogni frame (ad esempio 33, 3 ms per 30 Hz). Su alcuni dispositivi, un gioco può essere visualizzato a 60 FPS, mentre su altri potrebbe essere necessario scendere a un valore inferiore. La modalità automatica misura i tempi della CPU e della 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 raccolta di regolare questo intervallo dinamicamente.
- Disattiva la pipeline per i frame ultraveloci: offre una latenza ottimale tra input e schermata in tutti i casi.
Più frequenze di aggiornamento
I dispositivi che supportano più frequenze di aggiornamento offrono una maggiore flessibilità per scegliere un intervallo di scambio fluido:
- Su dispositivi a 60 Hz: 60 FPS / 30 FPS / 20 FPS
- Su dispositivi a 60 Hz + 90 Hz: 90 f/s/60 f/s/45 f/s/30 f/s
- 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 effettiva del rendering delle immagini di un gioco, offrendo un'esperienza visiva migliore.
Per ulteriori informazioni sulla frequenza dei fotogrammi 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 scopi di debug e profiling:
- Un istogramma del numero di aggiornamenti dello schermo che un fotogramma ha aspettato nella fila del compositore al termine del rendering.
- Un istogramma del numero di aggiornamenti dello schermo passati tra l'ora di presentazione richiesta e l'ora corrente effettiva.
- Un istogramma del numero di aggiornamenti dello schermo passati 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 corrente effettiva.
Passaggio successivo
Per integrare la libreria Android Frame Pacing nel tuo gioco, consulta una delle seguenti guide:
- Integrare la funzionalità Frame Pacing di Android nel renderer OpenGL
- Integrare la funzionalità di pacing dei frame di Android nel renderer Vulkan
Risorse aggiuntive
- Mir 2 migliora le prestazioni di rendering utilizzando Swappy, riduce il tasso di sessioni lente dal 40% al 10%.