2014-04-30 18 views
9

Sprawdzam skuteczność mojej aplikacji, ponieważ zauważyłem, że podczas przewijania upuszcza klatki. Uruchomiłem systrace (na Nexusie 4 z 4.3) i zauważyłem na wyjściu interesting section.Android potrójne buforowanie - oczekiwane zachowanie?

Wszystko jest w porządku na początku. Zooming in on the left section, widzimy, że rysunek zaczyna się od każdej vsync, kończy się z czasem do stracenia i czeka aż do następnej vsync. Ponieważ jest potrójnie buforowany, powinien być wciągany do bufora, który zostanie opublikowany w następującej wersji vsync po zakończeniu.

Przy czwartej wariancie na powiększonym zrzucie ekranu aplikacja wykonuje pewną pracę, a operacja losowania nie kończy się w czasie dla następnej wersji vsync. Jednak nie upuszczamy żadnych klatek, ponieważ poprzednie remisy pracowały z wyprzedzeniem.

Po tym zdarzeniu operacje losowania nie nadrabiają pominiętej vsync. Zamiast tego, tylko jedna operacja losowania zaczyna się od jednej z funkcji vsync, a teraz nie rysują one już o jedną klatkę.

Zooming in on the right section, aplikacja wykonuje więcej pracy i pomija kolejną wersję vsync. Ponieważ nie rysowaliśmy ramy do przodu, ramka w rzeczywistości zostaje tu upuszczona. Potem wraca do rysowania jednej klatki przed sobą.

Czy to oczekiwane zachowanie? Rozumiem, że potrójne buforowanie pozwoliło ci na powrót do zdrowia, jeśli przegapiłeś vsync, ale to zachowanie wygląda tak, jakby opuściło klatkę raz na dwa razy, które przegapiłeś.


Kontynuacja pytania

  1. Po prawej stronie this screenshot, aplikacja jest faktycznie renderowania buforów szybciej niż wyświetlacz jest ich spożywania. Podczas performTraversals # 1 (oznaczonego na zrzucie ekranu) powiedzmy, że bufor A jest wyświetlany, a bufor B jest renderowany. # 1 kończy się na długo przed wersją vsync i umieszcza bufor B w kolejce. W tym momencie, czy aplikacja nie powinna od razu zacząć renderować bufora C? Zamiast tego performTraversals # 2 nie rozpoczyna się do następnej vsync, marnując cenny czas pomiędzy.

  2. W podobnym tonie, jestem nieco zdezorientowany potrzebą waitForever on the left side here. Powiedzmy, że bufor A jest wyświetlany, bufor B jest w kolejce, a bufor C jest renderowany. Kiedy bufor C kończy renderowanie, dlaczego nie jest on natychmiast dodawany do kolejki? Zamiast tego robi to waitForever, aż bufor B zostanie usunięty z kolejki, w którym to momencie dodaje bufor C, dlatego wydaje się, że kolejka zawsze pozostaje w rozmiarze 1, bez względu na to, jak szybko aplikacja renderuje bufory.

Odpowiedz

8

Ilość buforowania podana ma znaczenie tylko wtedy, gdy bufor jest pełny. Oznacza to, że renderowanie jest szybsze niż zużywa je wyświetlacz.

Etykiety nie pojawiają się na twoich obrazach, ale domyślam się, że fioletowy rząd powyżej zielonego wiersza vsync to stan BufferQueue. Możesz zauważyć, że zazwyczaj ma 0 lub 1 pełne bufory w dowolnym momencie. Po lewej stronie obrazu "powiększonego po lewej" widać, że ma dwa bufory, ale potem ma tylko jeden, a na 3/4 na ekranie widać bardzo krótki, purpurowy pasek wskazuje, że po prostu ledwo zdążyła uzyskać ramkę w czasie.

Zobacz na tle: this post i this post.

Aktualizacja dla dodanych pytań ...

Szczegóły w the other post ledwie powierzchownie. Musimy iść głębiej.

Licznik bufora wyświetlany na systrace to liczba buforów w kolejce, tj. Liczba buforów, które zawierają w nich zawartość. Kiedy SurfaceFlinger przechwytuje bufor do wyświetlenia, natychmiast zwalnia bufor, zmieniając jego stan na "wolny". Jest to szczególnie ekscytujące, gdy bufor jest wyświetlany na nakładce, ponieważ wyświetlacz renderuje się bezpośrednio z bufora (w przeciwieństwie do tworzenia w buforze scratch i wyświetlania tego).

Pozwolę sobie powtórzyć: bufor, z którego wyświetlacz aktywnie odczytuje dane do wyświetlenia na ekranie, jest oznaczony jako "wolny" w buforze buforowym. Bufor ma powiązane ogrodzenie, które początkowo jest "aktywne". Gdy jest aktywny, nikt nie może modyfikować zawartości bufora. Gdy wyświetlacz nie potrzebuje już bufora, sygnalizuje to ogrodzeniem.

Powodem, dla którego kod po lewej stronie twojego śladu znajduje się w waitForever(), jest to, że oczekuje na sygnał ogrodzenia. Kiedy VSYNC uderza, wyświetlacz przełącza się do innego bufora, sygnalizuje ogrodzenie, a twoja aplikacja może natychmiast rozpocząć korzystanie z bufora. Eliminuje to opóźnienie, które mogłoby wystąpić, gdybyś musiał poczekać, aż funkcja SurfaceFlinger się obudzi, zobaczy, że bufor nie był już używany, wysłać IPC poprzez BufferQueue, aby zwolnić bufor itp.

Należy pamiętać, że połączenia waitForever() pojawia się tylko wtedy, gdy nie jesteś w tyle (po lewej i prawej stronie śladu). Nie jestem pewien, dlaczego tak się dzieje, gdy kolejka ma tylko 1 pełny bufor - powinien to być ostatni bufor, który powinien już sygnalizować.

Najważniejsze jest to, że nigdy nie zobaczysz, że BufferQueue przekroczył dwa dla potrójnego buforowania.

Nie wszystkie urządzenia działają w sposób opisany powyżej. Nexus 7 (2012) nie używa mechanizmu "jawnej synchronizacji", a urządzenia poprzedzające ICS w ogóle nie mają buforów.

Wracając do zrzutu ekranu z numeracją, tak, jest dużo czasu między "1" i "2", w których aplikacja może uruchomić funkcję WykonajTraversals(). Trudno powiedzieć na pewno, nie wiedząc, co robi twoja aplikacja, ale przypuszczam, że masz zaplanowany cykl animacji Choreographer, który budzi każdy VSYNC i działa. Nie działa częściej.

Jeśli jesteś systrace Android Breakout możesz zobaczyć, jak to wygląda, gdy renderujesz tak szybko, jak to możliwe ("nadziewanie w kolejce") i polegać na Back-Pressure BufferQueue, aby regulować szybkość gry.

Szczególnie interesujące jest porównanie N4 z wersją 4.3 z N4 w wersji 4.4. W 4.3, ślad jest podobny do twojego, z kolejką w dużej mierze zawisającą na 1, z regularnymi spadkami do 0 i sporadycznymi skokami do 2. W dniu 4.4 kolejka prawie zawsze wynosi 2, a od czasu do czasu spada do 1. W obu przypadkach jest to spanie w eglSwapBuffers(); w 4.3 ślad zwykle pokazuje waitForever() poniżej, podczas gdy w 4.4 pokazuje dequeueBuffer(). (Nie znam powodu tego offhandu).

Aktualizacja 2: Przyczyną różnicy między 4.3 a 4.4 wydaje się być zmiana sterownika Nexusa 4. Sterownik 4.3 używał starego wywołania dequeueBuffer, które zmienia się w dequeueBuffer_DEPRECATED() (Surface.cpp line 112). Stary interfejs nie przyjmuje ogrodzenia jako parametru "out", więc połączenie musi zadzwonić pod numer waitForever().Nowszy interfejs po prostu odsyła ogrodzenie do sterownika GL, który czeka, kiedy zajdzie taka potrzeba (co może nie być od razu).

Aktualizacja 3: Jeszcze dłuższe wyjaśnienie jest już dostępne here.

+0

Dzięki za odpowiedź! Połączone posty są bardzo użyteczne, ponieważ wiedzą, że systrace pokazuje kolejkę bufora. Nadal mam kilka pytań (dodanych powyżej). –

+0

Dodano więcej odpowiedzi. – fadden

+0

Zaktualizowano trochę więcej. – fadden

Powiązane problemy