Biblioteka Frame Pacing Część Android Game Development Kit.
Biblioteka Android Frame Pacing, znana też jako Swappy, jest częścią bibliotek AGDK. Umożliwia płynne renderowanie i prawidłowe tempo klatek w grach na Androida korzystających z OpenGL i Vulkan. W tym dokumencie znajdziesz definicję synchronizacji klatek, opis sytuacji, w których jest ona potrzebna, oraz informacje o tym, jak biblioteka radzi sobie w takich przypadkach. Jeśli chcesz od razu przejść do implementacji synchronizacji klatek w swojej grze, zobacz Dalszy krok.
Tło
Synchronizacja klatek to synchronizacja logiki gry i pętli renderowania z podsystemem wyświetlania systemu operacyjnego i sprzętem wyświetlającym. Podsystem wyświetlania Androida został zaprojektowany tak, aby unikać artefaktów wizualnych (znanych jako rozrywanie), które mogą wystąpić, gdy sprzęt wyświetlający przełącza się na nową klatkę w trakcie aktualizacji. Aby uniknąć tych artefaktów, podsystem wyświetlania wykonuje te czynności:
- buforuje poprzednie klatki wewnętrznie,
- Wykrywanie spóźnionych przesłań klatek
- Powtarza wyświetlanie poprzednich klatek, gdy wykryje opóźnione klatki.
Gra informuje SurfaceFlingera, kompozytora w podsystemie wyświetlania, że przesłała wszystkie wywołania rysowania potrzebne do utworzenia klatki (wywołując eglSwapBuffers
lub vkQueuePresentKHR
). SurfaceFlinger sygnalizuje dostępność klatki dla sprzętu wyświetlającego za pomocą zatrzasku. Sprzęt wyświetlający pokazuje wtedy daną klatkę. Wyświetlacz odświeża się ze stałą częstotliwością, np. 60 Hz. Jeśli w momencie, gdy jest potrzebna nowa klatka, nie jest ona dostępna, wyświetlacz pokazuje poprzednią klatkę.
Niespójne czasy klatek często występują, gdy pętla renderowania gry renderuje z inną częstotliwością niż natywne sprzętowe wyświetlanie. Jeśli gra działająca z częstotliwością 30 klatek na sekundę próbuje renderować obraz na urządzeniu, które natywnie obsługuje 60 klatek na sekundę, pętla renderowania gry nie zdaje sobie sprawy, że powtórzona klatka pozostaje na ekranie przez dodatkowe 16 milisekund. Takie rozłączenie zwykle powoduje znaczną niespójność w czasach klatek, np. 49 ms, 16 ms, 33 ms. Zbyt złożone sceny dodatkowo pogłębiają ten problem, ponieważ powodują pomijanie klatek.
Nieoptymalne rozwiązania
W przeszłości w grach stosowano te rozwiązania dotyczące synchronizacji klatek, które zwykle powodują nieregularne czasy klatek i zwiększone opóźnienie wejściowe.
Przesyłanie klatek tak szybko, jak pozwala na to interfejs API renderowania
Takie podejście wiąże grę ze zmienną aktywnością usługi SurfaceFlinger i wprowadza dodatkową klatkę opóźnienia. Potok wyświetlania zawiera kolejkę klatek, zwykle o rozmiarze 2, która wypełnia się, jeśli gra próbuje wyświetlać klatki zbyt szybko. Gdy w kolejce nie ma już miejsca, pętla gry (lub przynajmniej wątek renderowania) jest blokowana przez wywołanie OpenGL lub Vulkan. Gra musi wtedy czekać, aż sprzęt wyświetli klatkę, a to „ciśnienie zwrotne” synchronizuje oba komponenty. Ta sytuacja jest znana jako wypełnianie bufora lub wypełnianie kolejki. Proces renderowania nie wie, co się dzieje, więc niespójność liczby klatek na sekundę się pogarsza. Jeśli gra pobiera próbki wejściowe przed klatką, opóźnienie wejściowe się pogarsza.
Używanie Androida Choreographer samodzielnie
Gry również używają Androida Choreographer do synchronizacji. Ten komponent, dostępny w języku Java od API 16 i w języku C++ od API 24, generuje regularne sygnały z częstotliwością taką samą jak podsystem wyświetlania. Istnieją jednak subtelne różnice w tym, kiedy ten sygnał jest dostarczany w stosunku do rzeczywistego sygnału VSYNC sprzętu, a te przesunięcia różnią się w zależności od urządzenia. W przypadku długich klatek może nadal występować wypełnianie bufora.
Zalety biblioteki Frame Pacing
Biblioteka Frame Pacing używa do synchronizacji narzędzia Android Choreographer i radzi sobie z różnicami w dostarczaniu sygnałów. Używa sygnatur czasowych prezentacji, aby zapewnić wyświetlanie klatek we właściwym czasie, oraz barier synchronizacji, aby uniknąć przepełnienia bufora. Biblioteka używa NDK Choreographer, jeśli jest dostępny, a jeśli nie, wraca do Java Choreographer.
Biblioteka obsługuje wiele częstotliwości odświeżania, jeśli są one obsługiwane przez urządzenie, co daje grze większą elastyczność w wyświetlaniu klatki. Na przykład w przypadku urządzenia, które obsługuje częstotliwość odświeżania 60 Hz i 90 Hz, gra, która nie może generować 60 klatek na sekundę, może zamiast 30 klatek na sekundę generować 45 klatek na sekundę, aby zachować płynność. Biblioteka wykrywa oczekiwaną liczbę klatek na sekundę w grze i odpowiednio dostosowuje czasy wyświetlania klatek. Biblioteka Frame Pacing wydłuża też czas pracy baterii, ponieważ zapobiega niepotrzebnym aktualizacjom wyświetlacza. Jeśli na przykład gra renderuje obraz z częstotliwością 60 klatek na sekundę, ale wyświetlacz odświeża się z częstotliwością 120 Hz, ekran jest odświeżany 2 razy na każdą klatkę. Biblioteka Frame Pacing zapobiega temu, ustawiając częstotliwość odświeżania na wartość obsługiwaną przez urządzenie, która jest najbliższa docelowej liczbie klatek na sekundę.
Jak to działa
W sekcjach poniżej dowiesz się, jak biblioteka Frame Pacing radzi sobie z długimi i krótkimi klatkami gry, aby uzyskać prawidłowe tempo klatek.
Prawidłowe tempo klatek przy 30 Hz
Idealna sytuacja w Androidzie podczas renderowania z częstotliwością 30 Hz na urządzeniu o częstotliwości 60 Hz jest przedstawiona na rysunku 1. SurfaceFlinger zatrzaskuje nowe bufory graficzne, jeśli są dostępne (NB na diagramie oznacza „brak bufora”, a poprzedni jest powtarzany).
Rysunek 1. Idealne tempo wyświetlania klatek przy 30 Hz na urządzeniu 60 Hz.
Krótkie klatki gry powodują zacinanie się
Na większości nowoczesnych urządzeń silniki gier korzystają z platformy choreograficznej, która dostarcza sygnały do przesyłania klatek. Jednak ze względu na krótkie klatki nadal istnieje możliwość słabego tempa klatek, jak widać na rysunku 2. Krótkie klatki, po których następują długie, są odbierane przez gracza jako zacinanie się.
Rysunek 2. Krótka klatka gry C powoduje, że klatka B wyświetla tylko jedną klatkę, a następnie wyświetla wiele klatek C.
Biblioteka Frame Pacing rozwiązuje ten problem, używając sygnatur czasowych prezentacji. Biblioteka korzysta z rozszerzeń znacznika czasu prezentacji EGL_ANDROID_presentation_time
i VK_GOOGLE_display_timing
, aby klatki nie były prezentowane przedwcześnie, jak widać na rysunku 3.
Rysunek 3. Klatka B gry wyświetlana 2 razy, aby zapewnić płynniejsze wyświetlanie.
Długie klatki powodują zacinanie się obrazu i opóźnienia
Gdy zadanie wyświetlania trwa dłużej niż zadanie aplikacji, dodatkowe klatki są dodawane do kolejki. Prowadzi to ponownie do zacinania się obrazu i może też powodować dodatkową klatkę opóźnienia z powodu wypełniania bufora (patrz rysunek 4). Biblioteka usuwa zarówno zacinanie się, jak i dodatkową klatkę opóźnienia.
Rysunek 4. Długa klatka B daje nieprawidłowe tempo dla 2 klatek – A i B
Biblioteka rozwiązuje ten problem, używając barier synchronizacji (EGL_KHR_fence_sync
i VkFence
), aby wstawiać do aplikacji oczekiwania, które pozwalają potokowi wyświetlania nadrobić zaległości, zamiast dopuszczać do wzrostu ciśnienia zwrotnego. Klatka A nadal zawiera dodatkową klatkę, ale klatka B jest już wyświetlana prawidłowo, co widać na rysunku 5.
Rysunek 5. Ramki C i D czekają na wyświetlenie.
Obsługiwane tryby działania
Bibliotekę Frame Pacing możesz skonfigurować tak, aby działała w jednym z tych 3 trybów:
- Tryb automatyczny wyłączony + potok
- Tryb automatyczny włączony + potok
- Tryb automatyczny + automatyczny tryb potoku (potokowy/niepotokowy)
Zalecany tryb
Możesz eksperymentować z trybem automatycznym i trybami potokowymi, ale zacznij od wyłączenia ich i po zainicjowaniu Swappy dodaj te elementy:
swappyAutoSwapInterval(false);
swappyAutoPipelineMode(false);
swappyEnableStats(false);
swappySwapIntervalNS(1000000000L/yourPreferredFrameRateInHz);
Tryb potoku
Aby koordynować obciążenia silnika, biblioteka zwykle używa modelu potokowego, który rozdziela obciążenia procesora i GPU na granice VSYNC.
Rysunek 6. Tryb potoku.
Tryb bez potoku
Ogólnie rzecz biorąc, takie podejście skutkuje mniejszym i bardziej przewidywalnym opóźnieniem ekranu wejściowego. W przypadku gier o bardzo krótkim czasie renderowania klatki obciążenie procesora i GPU może zmieścić się w jednym przedziale zamiany. W takim przypadku podejście bez potoku zapewni mniejsze opóźnienie ekranu wejściowego.
Rysunek 7. Tryb bez potoku.
Tryb automatyczny
Większość gier nie wie, jak wybrać interwał zamiany, czyli czas, przez który wyświetlana jest każda klatka (np.33,3 ms przy 30 Hz). Na niektórych urządzeniach gra może być renderowana z szybkością 60 FPS, a na innych może być konieczne zmniejszenie tej wartości. Tryb automatyczny mierzy czas procesora i GPU, aby:
- Automatycznie wybieraj interwały zamiany: w przypadku gier, które w niektórych scenach działają z częstotliwością 30 Hz, a w innych z częstotliwością 60 Hz, biblioteka może dynamicznie dostosowywać ten interwał.
- Dezaktywacja potokowego przetwarzania danych w przypadku ultraszybkich klatek: zapewnia optymalne opóźnienie sygnału wejściowego w każdym przypadku.
Wiele częstotliwości odświeżania
Urządzenia obsługujące wiele częstotliwości odświeżania zapewniają większą elastyczność w wyborze interwału zamiany, który wygląda płynnie:
- Na urządzeniach 60 Hz: 60 kl./s / 30 kl./s / 20 kl./s
- Na urządzeniach 60 Hz i 90 Hz: 90 FPS / 60 FPS / 45 FPS / 30 FPS
- Na urządzeniach 60 Hz + 90 Hz + 120 Hz: 120 kl./s / 90 kl./s / 60 kl./s / 45 kl./s / 40 kl./s / 30 kl./s
Biblioteka wybiera częstotliwość odświeżania, która najlepiej pasuje do rzeczywistego czasu renderowania klatek gry, co zapewnia lepsze wrażenia wizualne.
Więcej informacji o synchronizacji klatek przy różnych częstotliwościach odświeżania znajdziesz w poście na blogu o renderowaniu przy wysokiej częstotliwości odświeżania na Androidzie.
Statystyki klatek
Biblioteka Frame Pacing udostępnia te statystyki na potrzeby debugowania i profilowania:
- Histogram liczby odświeżeń ekranu, na które czekała klatka w kolejce kompozytora po zakończeniu renderowania.
- Histogram liczby odświeżeń ekranu, które upłynęły między żądanym czasem wyświetlania a rzeczywistym czasem wyświetlania.
- Histogram liczby odświeżeń ekranu, które upłynęły między dwoma kolejnymi klatkami.
- Histogram liczby odświeżeń ekranu, które upłynęły od rozpoczęcia pracy procesora w przypadku tej klatki do rzeczywistego czasu wyświetlania.
Następny krok
Aby zintegrować bibliotekę Android Frame Pacing z grą, skorzystaj z jednego z tych przewodników:
- Integrowanie funkcji Android Frame Pacing z renderowaniem OpenGL
- Integracja funkcji Android Frame Pacing z renderowaniem Vulkan
Dodatkowe materiały
- Mir 2 poprawia wydajność renderowania dzięki użyciu Swappy i zmniejsza odsetek wolnych sesji z 40% do 10%.