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.