Dowiedz się więcej o renderowaniu w pętlach gry

Bardzo popularny sposób implementacji pętli gry wygląda tak:

while (playing) {
    advance state by one frame
    render the new frame
    sleep until it’s time to do the next frame
}

Jest z tym kilka problemów, a najważniejszą z nich jest przekonanie, gra może zdefiniować, czym jest ramka Różne wyświetlacze będą odświeżać się w różnym stopniu i może się zmieniać z czasem. Jeśli generujesz klatki szybciej niż tylko pojawia się na ekranie. Jeśli wygenerujesz zbyt wolno, SurfaceFlinger okresowo nie znajdzie nowego bufora, i ponownie wyświetli poprzednią klatkę. Obie te sytuacje mogą powodować widoczne usterki.

Musisz dopasować liczbę klatek na ekranie i zaawansowany stan gry. zgodnie z czasem, który upłynął od ostatniej klatki. Dostępnych jest kilka jak to zrobić:

  • Użyj biblioteki Android Frame Pacing (zalecane)
  • Wypełnij BufferQueue i korzystaj z „buforów zamiany” ciśnienie wsteczne
  • Korzystanie z Choreografa (API w wersji 16 lub nowszej)

Biblioteka Android Frame Pacing

Więcej informacji znajdziesz w artykule Prawidłowe tempo klatek. na temat korzystania z tej biblioteki.

Upychanie kolejki

To bardzo proste: wystarczy jak najszybciej wymienić bufory. Przed czasem wersji Androida może skutkować karami, Urządzenie SurfaceView#lockCanvas() spowoduje usypianie na 100 ms. Teraz w której znajduje się BufferQueue, a w chwili, gdy jest pełna, SurfaceFlinger ma taką możliwość.

Przykład takiego podejścia znajdziesz na stronie Android Breakout. it korzysta z GLSurfaceView, który działa w pętli, która wywołuje metodę onDrawFrame(), a następnie zamienia bufor. Jeśli kolejka BufferQueue jest pełna, wywołanie eglSwapBuffers() zaczeka na bufor. Bufory stają się dostępne po opublikowaniu ich przez usługę SurfaceFlinger. Robi to po pozyskania nowego do wyświetlania. Ponieważ dzieje się tak w systemie VSYNC, pętla rysowania będzie pasować do częstotliwości odświeżania. Najczęściej.

Z takim podejściem wiążą się pewne problemy. Po pierwsze, aplikacja jest powiązana z aktywność w SurfaceFlinger, która zajmuje różne czasy. w zależności od ilości pracy do wykonania i od tego, czy ogranicza ona czas pracy procesora wraz z innymi procesami. Stan gry zmienia się zgodnie z czasem między zamianami bufora, animacja nie będzie aktualizowana ze stałą szybkością. Kiedy działa z prędkością 60 kl./s przy uśrednianiu niespójności w czasie. prawdopodobnie nie zauważy żadnych skoków.

Po drugie: pierwsze kilka buforów będzie odbywać się bardzo szybko, bo BufferQueue nie jest jeszcze pełna. Obliczony czas między klatkami będzie bliska zera, więc gra wygeneruje kilka klatek, w których nic się nie stanie. W grze takiej jak Breakout, która aktualizuje ekran przy każdym odświeżeniu, kolejka jest zawsze zapełniona, chyba że gra uruchamia się (lub nie jest wstrzymana), więc efekt jest niezauważalne. gra, która od czasu do czasu zatrzymuje animację, a potem powraca w trybie „jak najszybciej” mogą pojawiać się dziwne problemy.

Choreographer

Choreograf umożliwia ustawienie wywołania zwrotnego, które zostanie wykonane przy kolejnym VSYNC. rzeczywisty czas VSYNC jest przekazywany jako argument. Nawet jeśli aplikacja się nie wybudza, nadal masz dokładny obraz tego, kiedy wyświetlacz jest odświeżany rozpoczął się okres medyczny. Użycie tej wartości zamiast bieżącego czasu daje jednolite źródło czasu logiki aktualizacji stanu gry.

Niestety, fakt, że telefon do Ciebie oddzwoni po każdej weryfikacji VSYNC, gwarantujemy terminowe wykonanie prośby o wywołanie lub będziemy mogli podjąć odpowiednie działania. Aplikacja musi wykryć: w sytuacjach, gdy jest on w tyle i ręcznie usuwa klatki.

Aplikacja „Record GL” działania w Grafikach stanowi tego przykład. Niektóre (np. Nexus 4 i Nexus 5), ramka zacznie znikać, jeśli siedzisz i patrzysz. Renderowanie GL jest proste, ale czasami elementy są ponownie rysowane, więc przesłanie pomiaru/układu może zająć bardzo dużo czasu, urządzenie przejdzie w tryb zmniejszonej mocy. (Według systrace w Androidzie 4.4 trwa 28 ms zamiast 6 ms. Jeśli przeciągniesz palcem po ekranie, uznaje, że wchodzisz w interakcję z ćwiczeniem, dzięki czemu zegar jest wysoki i nigdy nie stracisz klatki).

Prostym rozwiązaniem jest usunięcie klatki w wywołaniu Choreografa, jeśli bieżący czas jest dłuższy niż N milisekund po czasie VSYNC. W idealnej sytuacji wartość N jest określany na podstawie zaobserwowanych wcześniej interwałów VSYNC. Na przykład, jeśli plik okres odświeżania wynosi 16,7 ms (60 kl./s). Przy większej liczbie ćwiczeń może to nastąpić ponad 15 ms opóźnienia.

Jeśli oglądasz aplikację „Record GL” zobaczysz licznik utraconych klatek, a nawet rozbłysk czerwieni na obramowaniu przy opadaniu klatek. O ile masz jednak dobre oczy, ale nie zauważysz zacinania się animacji. Przy 60 kl./s aplikacja może od czasu do czasu usunąć klatkę bez zauważenia, pod warunkiem że animacja rozwija się ze stałą szybkością. Ile możesz uciec zależy od tego, co rysujesz, od charakterystyki wyświetlacza i widoczność użytkownika w wykrywaniu zacięć.

Zarządzanie wątkami

Ogólnie rzecz biorąc, jeśli renderujesz dane w widoku SurfaceView, GLSurfaceView lub TextureView, chcemy je renderować w osobnym wątku. Nigdy nie rób żadnych "ciężki trening" lub czegokolwiek, co zajmuje nieokreślony czas Wątek interfejsu. Zamiast tego utwórz 2 wątki dla gry: wątek w grze. i wątek renderowany. Zobacz Zwiększanie wydajności gry .

Breakout i „Record GL app” wykorzystują dedykowane wątki mechanizmu renderowania, aby zaktualizować stan animacji w danym wątku. Jest to rozsądne podejście, o ile możesz szybko zaktualizować stan gry.

Inne gry całkowicie odseparują logikę gry od renderowania. Jeśli masz w grze, która nie robi nic poza przesuwaniem bloków co 100 ms, w specjalnym wątku:

run() {
    Thread.sleep(100);
    synchronized (mLock) {
        moveBlock();
    }
}

(Aby uniknąć dryfu, warto wyznaczyć czas snu na podstawie ustalonego zegara – Służąca do tego funkcja sen() nie jest całkowicie spójna, a MoveBlock() pobiera niezerową ilość ale wiesz, o co chodzi).

Po wybudzeniu kod renderowania tylko chwyta blokadę i ustawia bieżącą pozycję. bryłę, zwolni się blokadę i wyciągnąć bryłę. Zamiast działań ułamkowych na podstawie czasów delta między klatkami, mamy tylko jeden wątek, który porusza się i kolejny wątek, który przyciąga wszystko, co się dzieje. po rozpoczęciu rysowania.

W przypadku sceny o dowolnej złożoności możesz utworzyć listę nadchodzących wydarzeń posortowane według godziny pobudki i snu do terminu następnego wydarzenia, ale jest on taki sam. i pomysłu.