Solução alternativa para perder o contexto do OpenGL quando o Android faz uma pausa?

40

A documentação do Android diz:

Há situações em que o contexto de renderização do EGL será perdido. Isso normalmente acontece quando o dispositivo acorda depois de dormir. Quando o contexto EGL é perdido, todos os recursos OpenGL (como texturas) associados a esse contexto serão excluídos automaticamente. Para manter a renderização correta, um renderizador deve recriar todos os recursos perdidos que ainda precisa. O método onSurfaceCreated (GL10, EGLConfig) é um local conveniente para fazer isso.

Mas ter que recarregar todas as texturas no contexto OpenGL é uma dor e prejudica a experiência de jogo para o usuário ao entrar novamente no aplicativo após uma pausa. Eu sei que "Angry Birds" de alguma forma evita isso, estou procurando sugestões sobre como fazer o mesmo?

Estou trabalhando com o Android NDK r5 (versão CrystaX.) Encontrei esse possível hack para o problema, mas estou tentando evitar a criação de uma versão completa do SDK personalizado.

Nick Gotch
fonte
dormir e acordar é referido ao dispositivo, portanto, enquanto o usuário apenas pausa o jogo ou troca de processo, ele deve perder qualquer contexto do EGL.
Ali1S232

Respostas:

22

O Replica Island possui uma versão modificada do GLSurfaceView que lida com esse problema (e funciona com versões anteriores do Android). De acordo com Chris Pruett :

Basicamente, hackeei o GLSurfaceView original para resolver um problema muito específico: queria ir para diferentes atividades no meu aplicativo sem jogar fora todo o meu estado OpenGL. A principal mudança foi separar o EGLSurface do EGLContext e jogar o primeiro fora na Pausa (), mas preservar o último até que o contexto seja explicitamente perdido. A implementação padrão do GLSurfaceView (que eu não escrevi, por sinal) joga fora todo o estado do GL quando a atividade é pausada e chama onSurfaceCreated () quando é retomada. Isso significava que, quando uma caixa de diálogo aparecia no meu jogo, o fechamento ocorria um atraso, porque todas as texturas tinham que ser recarregadas.

Você deve usar o GLSurfaceView padrão. Se você deve ter a mesma funcionalidade que a minha, você pode olhar para a minha. Mas fazer o que eu fiz expôs todos os tipos de erros de driver em alguns aparelhos (veja os comentários muito longos no final do arquivo) e você pode evitar toda essa bagunça usando o padrão.

Edit: Acabei de perceber que você já postou o link em um hack semelhante. Eu não acho que exista uma solução interna antes do favo de mel. Replica Island é um jogo popular que funciona em muitos dispositivos e você pode achar úteis os comentários e implementação de Chris.

Firas Assaad
fonte
11
Depois de tentar várias abordagens para contornar isso, decidi que a melhor solução é apenas recodificar o aplicativo para recriar o contexto. Essa é uma abordagem tão inútil, mas não parece haver um bom código de
Nick Gotch
9

Gostaria de acrescentar outra resposta que me foi passada um ano ou dois por Chris Pruett (Ilha da Réplica, Wind-Up Knight, etc.). É especialmente útil aqui em 2013, pois setPreserveEglContextOnPause (true) não parece funcionar no 4.3. (Eu posso estar errado sobre isso, mas é assim que me parece no momento em que atualizo o código do jogo pela última vez em 2011).

Basicamente, o truque é desanexar seu GLSurfaceView da hierarquia de exibição da onPause () da sua atividade. Como não está na hierarquia de exibição no momento em que a Pausa () é executada, o contexto nunca é destruído.

Portanto, a onPause () da sua atividade deve ficar assim:

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

E você restaura seu GLSurfaceView para a hierarquia não de onResume (), mas de onWindowFocusChanged ():

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

Observe que você nunca chama onPause () e onResume () do GLSurfaceView e que este é o SDK GLSurfaceView oficial, nenhuma versão alternativa hackeada é necessária.

Reuben Scratton
fonte
De fato, essa abordagem não parece funcionar. Chris Pruett o usou em combinação com o Unity, é possível que essa solução alternativa não esteja funcionando em projetos nativos? Mas apenas não chamar GLView.onPause () parece funcionar !? Existem outros que podem confirmar isso?
sjkm
Funcionou para mim voltado para o Android 2.2 OpenGl ES 2. Não tenho certeza do desempenho em outros dispositivos. Eu tenho um telefone LG G2 D802. Alguém sabe se esta é uma solução geral?
Pixel
7

<Rant> passei uma enorme quantidade de tempo sobre este problema, eu tentei muitas soluções diferentes e nenhum funcionou até hoje, eu acho que este é um dos a decisão de design terrível mais eu mesmo vi, mas vindo da equipe do Android Eu não sou realmente surpreso. </ rant>

Portanto, a solução é mover o membro eglContext para cima e torná-lo estático (global) para que não seja destruído; basta verificar se é nulo ou não antes de criá-lo novamente.

Até agora, essa solução parece funcionar para nós, e não nos importamos se ela quebra nos dispositivos de 2005.

Stephane Cocquereaumont
fonte
4

Use a API do favo de mel. Existe uma opção para preservar seu contexto OGL. Caso contrário, você precisará recarregar seu contexto. Não é difícil nem doloroso.

Você precisa entender que existem dois casos (Android 2.1):

  • Pausa na tela: seu aplicativo é sempre o primeiro => disponível
  • Pausa no aplicativo: existe outro aplicativo frontal => Sem solução

Nota: os antigos gpus android não suportam multi-contexto. Portanto, o contexto opengl é perdido quando você alterna para outro aplicativo => nenhuma solução disponível (você pode invadir para preservar o contexto na pausa na tela).

Nota 2: A função HoneyComb está configuradaPreserveEGLContextOnPause

Ellis
fonte
11
Estou portando um jogo C ++ no NDK do Android que não foi projetado para recarregar o contexto facilmente, então pelo menos para mim é um pouco difícil. Dificuldades à parte, recarregando texturas irá introduzir um atraso que não está presente em nossos outros dispositivos (iPhone, PSP, DS, etc ..) Fico feliz em ouvir correções de favo de mel este mas infelizmente precisamos apoiar 2.1+
Nick Gotch
11
Isso não responde à sua pergunta.
Notlesh #
11
Se você não entende, não pode fazê-lo.
Ellis
2

Algumas das respostas aqui são razoáveis ​​para os primeiros usos do OpenGL ES no Android. Os primeiros dispositivos GLES eram compatíveis apenas com um único contexto; portanto, o GLSurfaceView foi projetado para descartar agressivamente o estado. Convencer o GLSurfaceView a fazer o contrário não é fácil.

Para versões mais recentes do Android (provavelmente qualquer coisa usando o GLES 2.x), a melhor resposta é usar um SurfaceView simples e fazer seu próprio EGL e gerenciamento de threads. Você pode encontrar vários exemplos de GLES usado com um SurfaceView simples no Grafika , incluindo uma biblioteca de classes simples para criar e destruir contextos EGL.

Ainda é uma boa prática descarregar o estado quando um aplicativo entra em segundo plano, mas, por exemplo, de Chris Pruett - onde o aplicativo ainda estava em primeiro plano, mas a Atividade que hospeda o GLSurfaceView foi desativada - não há valor em rasgar abaixo o contexto.

fadden
fonte