Ottima domanda.
po 'veloce di sfondo per chiunque altro leggendo questo:
L'obiettivo è quello di ridurre al minimo la latenza del display, vale a dire il tempo che intercorre tra quando l'applicazione esegue il rendering di un fotogramma e quando il pannello del display si illumina i pixel. Se stai solo lanciando contenuti sullo schermo, non importa, perché l'utente non può dire la differenza. Se stai rispondendo all'input tattile, però, ogni fotogramma di latenza rende la tua app un po 'meno reattiva.
Il problema è simile alla sincronizzazione A/V, in cui è necessario l'audio associato a una cornice per far uscire l'altoparlante mentre il frame video viene visualizzato sullo schermo. In tal caso, la latenza complessiva non è importante a patto che sia costantemente uguale su entrambe le uscite audio e video. Tuttavia, ci sono problemi molto simili, perché perdi la sincronizzazione se SurfaceFlinger si blocca e il tuo video viene visualizzato in modo coerente un fotogramma successivo.
SurfaceFlinger viene eseguito con priorità elevata e svolge relativamente poco lavoro, quindi è improbabile che possa perdere un colpo da solo ... ma può succedere. Inoltre, compositing frame da più fonti, alcuni dei quali usano recinzioni per segnalare il completamento asincrono. Se un frame video in tempo reale è composto con l'output OpenGL e il rendering GLES non è completato quando arriva la deadline, l'intera composizione verrà posticipata al successivo VSYNC.
Il desiderio di ridurre al minimo la latenza è stato abbastanza forte che la versione Android KitKat (4.4) ha introdotto la funzione "DispSync" in SurfaceFlinger, che radono mezzo fotogramma di latenza rispetto al solito ritardo a due fotogrammi. (Questo è brevemente menzionato nel documento dell'architettura grafica, ma non è molto diffuso.)
Quindi questa è la situazione. In passato questo era meno di un problema per il video, perché il video a 30 fps aggiorna l'altro fotogramma. I singhiozzi si risolvono naturalmente perché non stiamo cercando di mantenere la coda piena. Stiamo iniziando a vedere video a 48Hz e 60Hz, quindi questo è più importante.
La domanda è: come possiamo rilevare se i frame che inviamo a SurfaceFlinger vengono visualizzati al più presto possibile, o stiamo spendendo un frame extra in attesa dietro un buffer che abbiamo inviato in precedenza?
La prima parte della risposta è: non è possibile. Non ci sono query di stato o richiamate su SurfaceFlinger che ti diranno qual è il suo stato. In teoria è possibile interrogare lo stesso BufferQueue, ma questo non ti dirà necessariamente quello che devi sapere.
Il problema con le query e callback è che non si può dire ciò che lo stato è, solo ciò che lo Stato era . Nel momento in cui l'app riceve le informazioni e agisce su di esse, la situazione potrebbe essere completamente diversa. L'app verrà eseguita con priorità normale, pertanto è soggetta a ritardi.
Per la sincronizzazione A/V è leggermente più complicato, perché l'app non può conoscere le caratteristiche del display. Ad esempio, alcuni display hanno "pannelli intelligenti" con memoria incorporata. (Se ciò che appare sullo schermo non si aggiorna spesso, è possibile risparmiare molta energia non avendo il pannello scansionato i pixel attraverso il bus di memoria 60x al secondo.) Questi possono aggiungere un ulteriore frame di latenza che deve essere tenuto in considerazione.
La soluzione in cui Android si sta muovendo verso la sincronizzazione A/V deve fare in modo che l'app comunichi SurfaceFlinger quando desidera che venga visualizzata la cornice. Se SurfaceFlinger salta la scadenza, abbassa la cornice. Questo è stato aggiunto sperimentalmente in 4.4, anche se non è destinato a essere usato fino alla prossima release (dovrebbe funzionare abbastanza bene in "L preview", anche se non so se questo include tutti i pezzi necessari per usarlo completamente).
Il modo in cui un'applicazione utilizza questo è chiamare l'estensione eglPresentationTimeANDROID()
prima di eglSwapBuffers()
. L'argomento della funzione è il tempo di presentazione desiderato, in nanosecondi, utilizzando la stessa base dei tempi di Choreographer (in particolare, Linux CLOCK_MONOTONIC
). Quindi, per ogni fotogramma, si prende il timestamp ottenuto dal coreografo, si aggiunge il numero desiderato di fotogrammi moltiplicato per la frequenza di aggiornamento approssimativa (che è possibile ottenere interrogando l'oggetto Visualizza - vedere MiscUtils#getDisplayRefreshNsec()) e passarlo a EGL. Quando si scambiano i buffer, il tempo di presentazione desiderato viene passato insieme al buffer.
Ricordare che SurfaceFlinger si riattiva una volta per VSYNC, esamina la raccolta di buffer in sospeso e consegna un set all'hardware di visualizzazione tramite Hardware Composer. Se si richiede la visualizzazione all'ora T e SurfaceFlinger crede che un frame passato all'hardware del display verrà visualizzato al tempo T-1 o precedente, il fotogramma verrà tenuto premuto (e il fotogramma precedente verrà nuovamente visualizzato). Se la cornice apparirà al tempo T, verrà inviata al display. Se il frame apparirà al tempo T + 1 o successivo (cioè mancherà alla sua scadenza), e c'è un altro frame dietro di esso nella coda che è pianificata per un secondo momento (es. Il frame inteso per il tempo T + 1) , quindi il frame inteso per il tempo T verrà abbandonato.
La soluzione non si adatta perfettamente al tuo problema. Per la sincronizzazione A/V, è necessaria una latenza costante, non una latenza minima. Se osservi l'attività "scheduled swap" di Grafika puoi trovare del codice che utilizza eglPresentationTimeANDROID()
in un modo simile a quello che farebbe un video player. (Nel suo stato attuale è poco più di un "generatore di suoni" per la creazione dell'output di systrace, ma i pezzi di base sono lì.) La strategia è di renderizzare alcuni fotogrammi in anticipo, quindi SurfaceFlinger non si esaurisce mai, ma è esattamente sbagliato app.
Il meccanismo di presentazione, tuttavia, fornisce un modo per rilasciare i frame anziché consentire loro di eseguire il backup. Se ti capita di sapere che ci sono due frame di latenza tra il tempo riportato da Choreographer e il momento in cui il tuo frame può essere visualizzato, puoi usare questa funzione per assicurarti che i frame vengano rilasciati piuttosto che in coda se sono troppo lontani passato. L'attività Grafika consente di impostare la frequenza dei fotogrammi e la latenza richiesta, quindi visualizzare i risultati in systrace.
Sarebbe utile per un'applicazione sapere quanti frame di latenza SurfaceFlinger ha effettivamente, ma non c'è una query per quello. (Questo è piuttosto difficile da gestire in ogni caso, poiché i "pannelli intelligenti" possono cambiare modalità, cambiando la latenza del display, ma a meno che non si stia lavorando alla sincronizzazione A/V, tutto ciò che interessa davvero è ridurre al minimo la latenza di SurfaceFlinger.) ragionevolmente sicuro di assumere due frame su 4.3+. Se non sono due fotogrammi, potresti avere prestazioni non ottimali, ma l'effetto netto non sarà peggiore di quello che otterresti se non impostassi affatto il tempo di presentazione.
Si potrebbe provare a impostare il tempo di presentazione desiderato uguale al timestamp di Choreographer; un timestamp nel passato recente significa "mostra ASAP". Questo garantisce una latenza minima, ma può ritorcersi contro la scorrevolezza. SurfaceFlinger ha il ritardo di due fotogrammi perché dà a tutto il sistema tempo sufficiente per completare il lavoro. Se il carico di lavoro è irregolare, oscillerai tra la latenza a fotogramma singolo e doppio fotogramma e l'uscita apparirà janky alle transizioni. (Questo era un problema per DispSync, che riduce il tempo totale a 1,5 frame.)
Non ricordo quando è stata aggiunta la funzione eglPresentationTimeANDROID()
, ma nelle versioni precedenti dovrebbe essere un no-op.
Riga inferiore: per "L" e in parte 4.4, dovresti essere in grado di ottenere il comportamento desiderato usando l'estensione EGL con due frame di latenza. Nelle versioni precedenti non c'è aiuto dal sistema. Se vuoi assicurarti che non ci sia un buffer sulla tua strada, puoi lasciar cadere deliberatamente un frame ogni tanto per lasciare che la coda del buffer si esaurisca.
Aggiornamento: un modo per evitare l'accodamento dei frame è chiamare eglSwapInterval(0)
. Se si inviava l'output direttamente a un display, la chiamata disabilitava la sincronizzazione con VSYNC, annullando la limitazione della frequenza fotogrammi dell'applicazione. Quando si esegue il rendering tramite SurfaceFlinger, questo pone il BufferQueue in "modalità asincrona", che provoca la caduta dei fotogrammi se inviati più velocemente di quanto il sistema possa visualizzarli.
Nota: il buffer è ancora triplo: viene visualizzato un buffer, uno è trattenuto da SurfaceFlinger per essere visualizzato sul successivo capovolgimento e uno viene prelevato dall'applicazione.
Questa è una grande domanda! –
Buona domanda! –