I'm trying to execute some OpenGL commands for my GLSurfaceView from my main activity. As the OpenGL renderer works in its own thread, I have to use queueEvent, as far as I understand.
I'm calling queueEvent with the following code in my GLSurfaceView:
queueEvent(new Runnable(){
#Override
public void run() {
renderer.doSomething(data); //executes some OpenGL commands
requestRender();
}});
The doSomething() method binds a texture and compiles shaders.
This does not work. glCreateProgram returns 0, which happens for example when a GL command is executed outside of the GL thread. Exactly the same code also works fine if I execute it from within my renderer. So it seems that the commands I execute using queueEvent are not executed within the GL context, but are executed in the wrong thread.
Is my understanding that calling queueEvent is sufficient to execute code inside the GL thread wrong? Is there anything else I have to do, or any mistake in how I call it now?
It did some experimenting and it seems that in some cases queueEvent will execute the Runnable before onSurfaceCreated is actually called, though still on the GL thread.
This can happen if you are using queueEvent immediately after onResume in the Activity.
I did the experiment with glClearColor and even though it called the command without any exceptions, the background had not changed. Maybe the GLContext is still not properly available and the commands do nothing.
Hope this helps!
Related
Situation: Android
class GLRenderer implements GLSurfaceView.Renderer
..
void onDrawFrame(GL10 gl) {..}
class MainGLSurfaceView extends GLSurfaceView
..
setRenderer(new GLRenderer());
class MainActivity ..
..
boolean onTouchEvent(MotionEvent event) {..}
MainActivity.onTouchEvent receives and processes events, some of which change state used by onDrawFrame(gl).
Q 1: Is there a way to "put the message queue on hold" until onDrawFrame returns?
NOTE: my onDrawFrame might take up to ~1/3 second on a slow phone. If necessary, I can probably change it so that if it doesn't have information it needs, it can start fetching it in the background, return promptly, and then draw the new frame on a later draw request (triggered by a timer).
Q 1B: Perhaps the events are only interrupting the draw, because of something I'm doing to fetch data. Can events interrupt at any moment (in the middle of onDrawFrame), or is it only when my custom onDrawFrame logic makes certain (system?) calls?
A 1B: Unfortunately, with a breakpoint I caught the event interrupting in the middle of a computation (a VM "init" of a new instance of a small class used to hold a temporary value was all that was required, to be "interruptible"; something almost any java code might do). So I will need to cope with interrupting events, can't side-step them.
Q 2: Or would it be better to examine incoming messages, and somehow decide which ones should be handled immediately, and then ?do what? with other messages, to process them after onDrawFrame returns?
Q 3: I've made an attempt at Q 2, putting messages on to an internal queue. Then I tried processing them at end of the onDrawFrame method. This worked okay until a message which tried to open a confirmation dialog. Result: RuntimeException: Can't create handler inside thread that has not called Looper.prepare(). Yeah, I didn't think I should be doing it that way. Can i somehow shove those messages back on to the main message queue?
(I didn't want to create yet another thread, so at end of onDrawFrame I tried "new Handler().postDelayed(new Runnable() .." inside of which I was going to do something with those events. Oops - that has the same problem - can't create handler on the thread that onDrawFrame is running on.)
So the basic idea is that I seek a way to not pull the rug out from under the current draw frame. I'd rather not have to make all the event-triggered logic work on one set of data, then "freeze" that data (copy it), so that draw frame can work on a frozen set.
Q&A's that I looked at before asking this:
Message queue in android
This talks about creating a looper and a handler, including a link to another article. I might adapt this to create a handler attached to the main thread's looper. So instead of having to inject back into the MessageQueue, I just need to pass my secondary queue to this handler, and start it running. But I'm on the wrong thread at the time I want to start the handler, so not sure how to proceed. Hmm, maybe make a custom event, that I somehow trigger on the main thread?
How to pause the activity?
shows how to use a flag to pause a worker thread. Since I have multiple types of events I wish to defer, instead of that approach, it is easier to hold (copies of) those events on a separate queue. Then I just need to know how to "inject" them back into the main MessageQueue. I'm trying to avoid creating another thread, to minimize system resources.
How to pause a Thread's Message Queue in Android?
Alternate approaches (not using looper) when creating one's own thread, e.g. a worker thread. Doesn't help for my situation, which is UI events coming in to existing looper.
And in case there is some completely different way to tackle this:
isn't this a situation that everyone who uses GLSurfaceView rendering would encounter eventually?
Is there any example of a robust way to deal with gl drawing and asynchronous GUI events?
The final piece to my solution to "Q 3":
public class MainActivity ...
// Call this, if not already on UI thread.
public static void processQueuedEventsOnUIThread() {
try {
Runnable runnable = new Runnable() {
#Override
public void run() {
... process the deferred UI events, which I have stored on a private queue ...
}
};
MainActivity.mMainActivity.runOnUiThread(runnable);
} catch (Exception e) {
Log.e("MainActivity", "processQueuedEventsOnUIThread", e);
}
}
The last statement in my GLRenderer.onDrawFrame() is now
MainActivity.processQueuedEventsOnUIThread();
The exceptions no longer occur, even if the processed events cause a dialog window (with its own handler) to open. activity.runOnUiThread(runnable) is the essential step.
What is different between this:
queueEvent(new Runnable(){
#Override
public void run() {
mRenderer.method();
}});
And this:
mRenderer.method();
And what is better for OpenGL FPS?
GLSurfaceView creates a separate rendering thread. In OpenGL, you need a current context for making any OpenGL calls. The "current context" state is per thread. GLSurfaceView creates an OpenGL context for you, and makes it current for any of the GLSurfaceView.Renderer overrides you implement. So as long as you make OpenGL calls in those methods, you don't have to worry about any of that, it just works like pure magic (well, it's not really magic, but hides a lot of complexity).
Based on this, you can't make OpenGL calls from the UI thread without jumping through hoops. So simply calling a method on the Renderer in something that is e.g. triggered by user input, and then making OpenGL calls in that method, will fail. Beyond that, even if you don't make OpenGL calls in the method, you have to worry about thread safety if the method accesses/modifies member variables of the Renderer that are also used by the rendering thread.
Using queueEvent() provides a convenient way of executing a method in the rendering thread. So you don't have to worry about thread safety of Renderer member variables, because all access will happen in the rendering thread.
I believe you might also be able to make OpenGL calls in that method if you submit it through queueEvent(). But I'm not totally sure if the OpenGL context is always current in the rendering thread, or if that's only guaranteed while the Renderer method overrides are called. It's much more typically to just change state in the Renderer in response to user input, and then use that new state in your override of Renderer.onDrawFrame().
I have a LinkedQueue that stores the calls to anything from the GLES20 class. After loading it up I send a requestRender() call to GLSurfaceView and in onDrawFrame() I process the queue and call the actual GLES20 method.
Yet when I call GLES20.glLinkProgram() the function never returns. I don't have anything in the logcat, no exception, no killed thread nothing. It just remains there.
Any ideas in which conditions glLinkProgram() doesn't return and what can I do to debug this situation? It's frustrating that I can't get to the inner part since it's native and I'm trying to avoid downloading the whole android source code and study it...
I am creating a game loop and I need to be able to call onDrawFrame (from inside the renderer manually) in order to "skip frames" if I find that I am falling behind on processes.
Currently I have a GLSurfaceView class that calls
setRenderer(glSurfaceRenderer);
With this set up I understand that onDrawFrame is called every tick.
I tried putting the above call inside a method so that I could call it from inside my game loop but on the second run of the game loop I crash with the message saying
setRenderer has already been called for this instance
Is there a way to call the renderer manually every frame
Will just calling the
onDrawFrame
method work properly. Or is it not good practice to control the renderer in such a way when using openGL
I don't see how calling setRenderer() nor manually calling onDrawFrame() in the game loop will solve anything. You should refactor your rendering calls to take into account the time it takes draw so in other words time based animation. In any case manually calling onDrawFrame() without a call to eglMakeCurrent() before any drawing calls won't work as you have both the renderer and game loop thread drawing.
Edit: Since you are using GLSurfaceView, you can call requestRender() from your game loop to trigger a render if you feel RENDERMODE_CONTINUOUSLY is too slow for you.
I have a game using OpenGL. I've built off of the examples, for the most part, having the Main Thread, Renderer Thread (GLSurfaceView.Renderer) and added a GameLogic thread as well. When the game is executed, everything seems to run through perfectly. When back is pressed and onPause() is fired, I'm also firing the GLSurfaceView()'s onPause, but I'm having a "crash" at that point. Here's the MainActivity's onPause:
#Override
protected void onPause() {
Log.d("Main", "Pre- Super onPause");
super.onPause();
Log.d("Main", "Post- Super onPause");
mGSGLView.onPause();
Log.d("Main", "Post- GL onPause");
}
Each log point is reached except the last. In logcat, immediately following the "Post Super onPause" line, I get an Activity pause timeout.
I am not overriding onPause in the GLSurfaceView class... and as far as I know this had been working for me for some time, but recently started occurring when I started getting a completely black screen on the second time I tried to run my game which sits until finally getting an ANR. 95% of my game runs natively. Similar to the San Angeles example, the Renderer calls to onDraw, for instance call a NativeDraw function instead of java. The same is true for the onSurfaceCreated, onSurfaceChanged, and I also call a native GameLogic method in the logic thread (basically all that is called there is a thread sleep and that logic method.)
I hope I've given enough information, please let me know if there is anything else I should be providing.
EDIT - Well... I've actually narrowed down the issue to a native function call where I'm freeing certain pointers that had previously had memory allocated for them with malloc(). My code there looks okay, but if I omit the call, everything works okay, so my free() calls must be corrupting something...
Okay, I'm only answering this myself because the solution was so specific to my own code that no one would have been able to.
If you have memory allocated on the native side, take care not to free it until after you are sure no other code will be trying to access it.