Obejście problemu utraty kontekstu OpenGL po wstrzymaniu Androida?

40

Dokumentacja Androida mówi:

Istnieją sytuacje, w których kontekst renderowania EGL zostanie utracony. Zwykle dzieje się tak, gdy urządzenie budzi się po zaśnięciu. Gdy kontekst EGL zostanie utracony, wszystkie zasoby OpenGL (takie jak tekstury), które są powiązane z tym kontekstem, zostaną automatycznie usunięte. Aby zapewnić prawidłowe renderowanie, renderer musi odtworzyć utracone zasoby, których nadal potrzebuje. Metoda onSurfaceCreated (GL10, EGLConfig) jest wygodnym miejscem do tego.

Jednak ponowne ładowanie wszystkich tekstur w kontekście OpenGL jest zarówno uciążliwe, jak i utrudnia użytkownikowi grę po ponownym uruchomieniu aplikacji po przerwie. Wiem, że „Angry Birds” jakoś tego unika, szukam sugestii, jak to zrobić?

Pracuję z Androidem NDK r5 (wersja CrystaX.) Znalazłem ten możliwy hack do problemu, ale staram się uniknąć budowania całej niestandardowej wersji SDK.

Nick Gotch
źródło
tryb uśpienia i przebudzenia jest przypisany do urządzenia, więc gdy użytkownik po prostu wstrzyma grę lub przełączy proces, powinien utracić dowolny kontekst EGL.
Ali1S232

Odpowiedzi:

22

Wyspa repliki ma zmodyfikowaną wersję GLSurfaceView, która rozwiązuje ten problem (i działa z wcześniejszymi wersjami Androida). Według Chrisa Pruetta :

Zasadniczo zhakowałem oryginalny GLSurfaceView, aby rozwiązać bardzo konkretny problem: chciałem przejść do różnych działań w mojej aplikacji bez wyrzucania całego mojego stanu OpenGL. Główną zmianą było oddzielenie EGLSurface od EGLContext i wyrzucenie pierwszego z nich przyPause (), ale zachowanie drugiego aż do jawnego zagubienia kontekstu. Domyślna implementacja GLSurfaceView (której nie pisałem, nawiasem mówiąc), odrzuca cały stan GL, gdy działanie jest wstrzymane i wywołuje funkcjęSurfaceCreated (), gdy jest ona wznawiana. Oznaczało to, że gdy w mojej grze pojawiło się okno dialogowe, zamknięcie go spowodowało opóźnienie, ponieważ wszystkie tekstury musiały zostać ponownie załadowane.

Powinieneś użyć domyślnego GLSurfaceView. Jeśli musisz mieć taką samą funkcjonalność jak moja, możesz na nią spojrzeć. Ale robienie tego, co zrobiłem, ujawniło różnego rodzaju okropne błędy sterowników w niektórych telefonach (patrz bardzo długie komentarze na końcu tego pliku), a można uniknąć całego tego bałaganu, używając tylko domyślnego.

Edycja: Właśnie zdałem sobie sprawę, że opublikowałeś już link do podobnego hacka. Nie sądzę, aby istniało jakieś wbudowane rozwiązanie przed budową plastra miodu. Replica Island to popularna gra działająca na wielu urządzeniach i może okazać się pomocna w implementacji Chrisa i komentarzach.

Firas Assaad
źródło
1
Po wypróbowaniu różnych podejść do obejścia tego problemu, zdecydowałem, że najlepszym rozwiązaniem jest po prostu przekodowanie aplikacji w celu odtworzenia kontekstu. Jest to tak marnotrawne podejście, ale nie wygląda na to, aby istniało jakieś dobre rozwiązanie code.google.com/p/android/issues/detail?id=12774
Nick Gotch
9

Chciałbym dodać kolejną odpowiedź na to pytanie, którą przekazał mi rok lub dwa lata temu Chris Pruett (wyspa repliki, Wind-Up Knight itp.). Jest to szczególnie przydatne tutaj w 2013 r., Ponieważ wydaje się, że setPreserveEglContextOnPause (true) nie działa na 4.3. (Mogę się mylić w tej sprawie, ale tak to wygląda teraz, kiedy aktualizuję kod gry ostatnio dotknięty w 2011 roku).

Zasadniczo sztuczka polega na odłączeniu GLSurfaceView od hierarchii widoku od działania onPause () Twojej aktywności. Ponieważ nie ma go w hierarchii widoków w momencie uruchomienia onPause (), kontekst nigdy nie zostaje zniszczony.

Więc twoja aktywność onPause () powinna wyglądać następująco:

@Override
public void onPause() {
    view.setVisibility(View.GONE);
    super.onPause();
    ...
}

I przywracasz GLSurfaceView do hierarchii nie z onResume (), ale z onWindowFocusChanged ():

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus && view.getVisibility() == View.GONE) {
         view.setVisibility(View.VISIBLE);
    }
    ...
}

Zauważ, że nigdy nie wywołujesz GLSurfaceView onPause () i onResume () i że jest to oficjalny SDK GLSurfaceView, nie jest wymagana żadna zhackowana wersja alternatywna.

Reuben Scratton
źródło
W rzeczywistości to podejście nie działa. Chris Pruett użył go w połączeniu z Unity, czy to możliwe, że to obejście nie działa w rodzimych projektach? Ale tylko nie wywoływanie GLView.onPause () wydaje się działać !? Czy są inni, którzy mogą to potwierdzić?
sjkm
Działa dla mnie na Androida 2.2 OpenGl ES 2. Nie jestem pewien, jak działa na innych urządzeniach. Mam telefon LG G2 D802. Czy ktoś wie, czy jest to ogólne rozwiązanie?
Pixel
7

<rant> Spędziłem dużo czasu na tym problemie, wypróbowałem wiele różnych rozwiązań i żadne nie działało do dziś, myślę, że jest to jedna z najstraszniejszych decyzji projektowych, jakie kiedykolwiek widziałem, ale pochodzący z zespołu Androida nie jestem naprawdę zaskoczony. </ rant>

Tak więc rozwiązaniem jest przesunięcie elementu eglContext w górę i ustawienie go w sposób statyczny (globalny), aby nie został zniszczony, a następnie po prostu musisz sprawdzić, czy jest on zerowy, zanim utworzysz go ponownie.

Do tej pory wydaje się, że to rozwiązanie działa dla nas i nie obchodzi nas, czy zepsuje się na urządzeniach z 2005 roku.

Stephane Cocquereaumont
źródło
4

Użyj interfejsu API plastra miodu. Istnieje opcja zachowania twojego kontekstu OGL. W przeciwnym razie musisz ponownie załadować kontekst. To nie jest trudne ani bolesne.

Musisz zrozumieć, że istnieją dwa przypadki (Android 2.1):

  • Wstrzymanie ekranu: twoja aplikacja jest zawsze pierwsza => hack dostępny
  • Pauza aplikacji: istnieje inna aplikacja z przodu => Brak rozwiązania

Uwaga: stary Android Gpus nie obsługuje wielu kontekstów. Tak więc kontekst opengl zostaje utracony po przełączeniu na inną aplikację => brak dostępnego rozwiązania (możesz zhakować, aby zachować swój kontekst na ekranie pauzy).

Uwaga 2: Ustawiona jest funkcja HoneyCombPreserveEGLContextOnPause

Ellis
źródło
1
Przenoszę grę C ++ na Androida NDK, która nie została zaprojektowana tak, by łatwo ponownie ładować kontekst, więc przynajmniej dla mnie jest to trochę trudne. Pomijając trudności, przeładowanie tekstur wprowadzi opóźnienie, które nie jest obecne na naszych innych urządzeniach (iPhone, PSP, DS itp.). Cieszę się, że to plaster miodu naprawia to, ale niestety musimy wesprzeć 2.1+
Nick Gotch
1
To wcale nie odpowiada na jego pytanie.
notlesh
1
Jeśli nie rozumiesz, nie możesz tego zrobić.
Ellis
2

Niektóre odpowiedzi tutaj są uzasadnione dla wczesnego użycia OpenGL ES na Androidzie. Pierwsze urządzenia GLES obsługiwały tylko jeden kontekst, więc GLSurfaceView został zaprojektowany do agresywnego odrzucania stanu. Przekonanie GLSurfaceView do zrobienia czegoś innego nie jest łatwe.

W przypadku nowszych wersji Androida (prawdopodobnie wszystko korzystających z GLES 2.x) najlepszą odpowiedzią jest użycie zwykłego SurfaceView i samodzielne zarządzanie EGL i zarządzanie wątkami. Możesz znaleźć wiele przykładów GLES używanych z prostym SurfaceView w Grafice , w tym bibliotekę prostych klas do tworzenia i niszczenia kontekstów EGL.

Nadal dobrą praktyką jest zwalnianie stanu, gdy aplikacja przechodzi w tło, ale na przykład od Chrisa Pruetta - gdzie aplikacja była nadal na pierwszym planie, ale działanie Hosting GLSurfaceView zostało wyłączone - nie ma żadnej wartości w rozdzieraniu w dół kontekstu.

dziwne
źródło