Home > database >  How can one avoid losing OpenGL ES context when switching between game states after a key event?
How can one avoid losing OpenGL ES context when switching between game states after a key event?

Time:06-08

I am trying to make a 3D app for Android using OpenGL ES 3.0 but I have an issue when going from one state to another (in a state machine). When switching state, the OpenGL context seems to be lost in computer memory. This only happens when I push a button that will trigger state switching.

I think it is due to thread creation when switching state in Android (even though in a desktop java application, I don't recall seeing such an issue). Correct me if I'm wrong.

The exact message that appears (in red) in the debugger is :

E/libEGL: call to OpenGL ES API with no current context (logged once per thread)

I made it so that if I don't press any key, the state will be switched after 20 sec. In that case the message does not appear (i.e. no red message if the user is not the one triggering the switch). But after the automatic switch, if I press a button I get a (normally colored) message in the debugger that states the following :

D/CompatibilityChangeReporter: Compat change id reported: 147798919; UID 10060; state: ENABLED

Not sure if it's an issue. All I know is that if a use a workaround to track which keys are pressed (array of boolean describing each key state, with each index of the element of the table being used to represent the keycode), the keys being pressed after the switch of state get stuck on pressed (values in the table for the keys that are pressed are true).

Apologies in advance for the (probably) long code that I have to post. I tried my best to strip away and test so that the issue remains as much as I could. That's also why I wrote the whole explaination in the beginning

code-listing 1: The GLSurfaceView

public class OpenGLView extends GLSurfaceView
{

    private OpenGLRenderer renderer;

    public OpenGLView(Context context)
    {
        super(context);
        init(context);
    }

    public OpenGLView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        init(context);
    }

    private void init(Context context)
    {
        setEGLContextClientVersion(3);
        setPreserveEGLContextOnPause(true);
        renderer = new OpenGLRenderer();
        setRenderer(renderer);

        Display.getInstance().setContext(context);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event)
    {
        renderer.onKeyDown(keyCode, event);
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event)
    {
        renderer.onKeyUp(keyCode, event);
        return super.onKeyUp(keyCode, event);
    }
}

code-listing 2: the GLSurfaceView.Renderer

public class OpenGLRenderer implements GLSurfaceView.Renderer
{
    private final StateMachine gameStateMachine = StateMachine.getInstance();

    public void onSurfaceCreated (GL10 glUnused, EGLConfig config ) {/* empty */}

    public void onSurfaceChanged ( GL10 glUnused, int width, int height )
    {
        gameStateMachine.pushState(new HomeState(gameStateMachine));
    }

    public void onDrawFrame ( GL10 glUnused )
    {
        gameStateMachine.update();
        gameStateMachine.render();
    }

    public void onKeyDown(int keyCode, KeyEvent event)
    {
        gameStateMachine.handleEvent(EventType.KEY_PRESSED, keyCode);
    }


    public void onKeyUp(int keyCode, KeyEvent event)
    {
        gameStateMachine.handleEvent(EventType.KEY_PRESSED, keyCode);
    }

}

code-listing 3: HomeState.java (first state in the game state machine)

public class HomeState extends State {

    public HomeState(StateMachine gsm) { super(gsm); }

    @Override
    public void onEnter() {/* empty */}

    @Override
    public void onExit() {/* empty */}

    @Override
    public void handleEvent(EventType type, int keyCode)
    {
        if(type == EventType.KEY_PRESSED)
        {
            switch (keyCode)
            {
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    gameStateMachine.pushState(new InGameState(gameStateMachine));
            }
        }
    }

    long sometime = SystemClock.uptimeMillis();

    @Override
    public void update()
    {
        Log.d("DEBUG", "" (SystemClock.uptimeMillis() - sometime));

        if(SystemClock.uptimeMillis() - sometime > 20000)
        {
            gameStateMachine.pushState(new InGameState(gameStateMachine));
        }

    }

    @Override
    public void render() {/* empty */}
}

code-listing 4: inGameState.java (state one is switching to)

public class InGameState extends State {

    public InGameState(StateMachine gsm) { super(gsm); }

    @Override
    public void onEnter()
    {
        glCreateShader(GL_VERTEX_SHADER);
    }

    @Override
    public void onExit() {/* empty */}

    @Override
    public void handleEvent(EventType type, int keyCode)
    {
        if(type == EventType.KEY_PRESSED)
        {
            Toast.makeText(Display.getInstance().getContext(), "key pressed in InGameState!", Toast.LENGTH_SHORT).show();
        }

    }

    @Override
    public void update() {/* empty */}

    @Override
    public void render() {/* empty */}
}

Note that when there is no OpenGL call in the second game state, the red message doesn't appear. But as soon as I make any OpenGL call in the second game state, the red message appears when the user triggers the switch.

Also note that, like I mentioned previously, the red message only appears when I directly check the keyCode/KeyEvent given by Android-Java. If I do something like the following code, the red message will not appear but key pressed will be stuck (through software).

code-listing 5: workaround GLSurfaceView.Renderer

public class OpenGLRenderer implements GLSurfaceView.Renderer
{
    private final StateMachine gameStateMachine = StateMachine.getInstance();

    private final boolean[] keyStates = new boolean[1024];

    public void onSurfaceCreated (GL10 glUnused, EGLConfig config )
    {
        for(boolean b : keyStates)
        {
            b = false;
        }
    }

    /* see code-listing 2 */

    public void onDrawFrame ( GL10 glUnused )
    {
        gameStateMachine.handleEvent(EventType.KEY_PRESSED, keyStates);
        gameStateMachine.update();
        gameStateMachine.render();

    }

    public void onKeyDown(int keyCode, KeyEvent event)
    {
        keyStates[keyCode] = true;
    }


    public void onKeyUp(int keyCode, KeyEvent event)
    {
        keyStates[keyCode] = false;
    }

}

CodePudding user response:

When you're using GLSurfaceView, all of your interactions with OpenGLES should be downstream from OpenGLRenderer's onDrawFrame, as that's called on the thread with the OpenGLES context bound.

onKeyDown is called from some other Android thread with no OpenGLES context. If I'm following your code correctly, then it eventually invokes InGameState's onEnter function which calls glCreateShader which fails because there's no OpenGLES context.

Your workaround seems like the right sort of idea. I would recommend that you have onKeyDown and onKeyUp append to a queue of input events, which can be processed in your onDrawFrame. I think that's a more robust approach than your array of boolean keyStates as you can process input in order, and easily extend the system to have touch events when you need to.

  • Related