I have a GLSurfaceView renderer and method onSurfaceCreated is called only once (basically only when activity is created or re-created).
I need to test behavior when EGL context is lost and surface is recreated during normal activity life cycle, however I am unable to put app into such situation no matter what I do.
Is there anything that I can do to simulate situation in which is onSurfaceCreated called again? How to make my app lose EGL context?
Edit: After a few days I noticed that the problem was gone and the app started to behave properly as said in specs:
public abstract void onSurfaceCreated (GL10 gl, EGLConfig config)
Called when the surface is created or recreated.
Called when the rendering thread starts and whenever the EGL context is lost.
The EGL context will typically be lost when the Android device awakes
after going to sleep
Before that, every time the device went to sleep, the activity was destroyed and recreated on awake which was preventing me to test if the code in OnSurfaceCreated worked well.
Now it behaves much better when sleep/awake occurs - it usually does not destroy the activity and just calls OnSurfaceCreated method which is the situation I was aiming for.
I'm not sure but the only thing that i can think of is to switch the GLSUrfaceView with a new one using the same GLSurfaceView.Renderer.
That should create a new EGL context and call the onSurfaceCreated() a second time.
According to the documentation, GLSurfaceView.Renderer.onSurfaceCreated is called when the surface is created or recreated.
Called when the rendering thread starts and whenever the EGL context is lost. The EGL context will typically be lost when the Android device awakes after going to sleep.
Have you tried locking your device's screen and unlocking it?
Maybe you can look at onScreenStateChanged method but it's available for API 16 or higher. If you mean "pausing and resuming the activity" by "normal activity life ciycle" maybe you can use onPause or onResume to track this.
What exactly do you want to do with onSurfaceCreated() ? You can force the rendering by requestRender() method of SurfaceView and do the necessary calculations inside onDrawFrame() of GLSurfaceView.Renderer
If your view is in a fragment, you can detach and re-attach the fragment.
Related
I'm successfully drawing simple shapes on my GLSurfaceView using OpenGL ES 2.0. So far i'm not using any textures. The problem is that when i re-run for the second time the activity with GLSurfaceView the screen is empty (black), shapes are not being drawn again. I have to reinstall the whole app to make it work again.
I don't have any special methods onPause & onResume so i assume the new instance of GLSurfaceView should redraw the screen each time i re-run the application.
Does anyone have any clue what am i missing here?
I don't have any special methods onPause & onResume
That's likely your problem. You need to follow the documentation of GLSurfaceView, specifically what it says under "Activity Life-cycle":
A GLSurfaceView must be notified when the activity is paused and resumed. GLSurfaceView clients are required to call onPause() when the activity pauses and onResume() when the activity resumes. These calls allow GLSurfaceView to pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate the OpenGL display.
When creating a SurfaceView it's normal to also create a separate thread to draw onto the surface. Is it better programming practice to have the thread be created and destroyed at the same time the activity is, or at the same time the surface is?
What are some of the advantages/pitfalls of either way?
The Activity and the View are created at essentially the same time. The Surface is created later, and that's what the SufaceHolder callbacks are for.
You can't render on the Surface before it exists or after it's destroyed, so there's no point in starting your rendering thread before then or leaving it running after. The tricky part is that the callbacks happen on the main UI thread (since that's where you set it up), so the surfaceDestroyed() callback could be called while your render thread is doing work.
EDIT:
Some notes about the SurfaceView / Activity lifecycle are included below. These are now part of the official Android documentation; see Appendix B in the System-Level Graphics doc. The original post is available below for historical purposes.
You can see examples of both approaches in Grafika. Approach #1 (create/destroy thread in onResume/onPause) can be seen in TextureFromCameraActivity, approach #2 (create/destroy thread in surfaceCreated/surfaceDestroyed) can be seen in HardwareScalerActivity and RecordFBOActivity.
A few thoughts about app life cycle and SurfaceView.
There are two somewhat independent things going on:
Application onCreate / onResume / onPause
Surface created / changed / destroyed
When the Activity starts, you get callbacks in this order:
onCreate
onResume
surfaceCreated
surfaceChanged
If you hit "back", you get:
onPause
surfaceDestroyed (called just before the Surface goes away)
If you rotate the screen, the Activity is torn down and recreated, so you get
the full cycle. (You can tell it's a "quick" restart by checking isFinishing().) It might be possible to start / stop an activity so quickly that surfaceCreated() might happen after onPause(), but I'm not sure about that.
If you tap the power button to blank the screen, however, you only get onPause() --
no surfaceDestroyed(). The Surface remains alive, and rendering can continue (you
even keep getting Choreographer events if you continue to request them). If you have
a lock screen that forces a specific orientation your Activity can get kicked, but
if not you can come out of screen-blank with the same Surface you had before.
This raises a fundamental question when using a separate renderer thread with
SurfaceView: should the lifespan of the thread be tied to the Surface or to the
Activity? The answer is: it depends on what you want to have happen when the screen
goes blank. There are two basic approaches: (1) start/stop the thread on Activity
start/stop; (2) start/stop the thread on Surface create/destroy.
#1 interacts well with the app lifecycle. We start the renderer thread in onResume() and
stop it in onPause(). It gets a bit awkward when creating and configuring the thread
because sometimes the Surface will already exist and sometimes it won't. We can't simply
forward the Surface callbacks to the thread, because they won't fire again if the
Surface already exists. So we need to query or cache the Surface state, and forward it
to the renderer thread. Note we have to be a little careful here passing objects between
threads -- best to pass the Surface or SurfaceHolder through a Handler message, rather
than just stuffing it into the thread, to avoid issues on multi-core systems (cf.
Android SMP Primer).
#2 has a certain appeal because the Surface and the renderer are logically intertwined.
We start the thread after the Surface has been created, which avoids the inter-thread
communication concerns. Surface created / changed messages are simply forwarded. We
need to make sure rendering stops when the screen goes blank, and resumes when it
un-blanks; this could be a simple matter of telling Choreographer to stop invoking the
frame draw callback. Our onResume() will need to resume the callbacks if and only if
the renderer thread is running. It may not be so trivial though -- if we animate based
on elapsed time between frames, we could have a very large gap when the next event
arrives, so an explicit pause/resume message may be desirable.
The above is primarily concerned with how the renderer thread is configured and whether
it's executing. A related concern is extracting state from the thread when the
Activity is killed (in onPause() or onSaveInstanceState()). Approach #1 will work
best for that, because once the renderer thread has been joined its state can be
accessed without synchronization primitives.
Could you describe me when I should recreate vbos, shaders, textures? I know that OpenGL functions are called in separated thread. I call GLSurfaceView's method onResume in Activity's onResume (the same with onPause method).
When the GLContext is lost?
Let's assume that the user touch home button. Activity's onPause method is called. Should I delete vbo, shaders, textures? Or should I delete those resources when onDestroy is called (resume GL Thread to delete them?)? What if user touch back button?
The another question. Should I keep bitmaps, vertices attributes in Java to reinit VBOs, Textures?
Now I delete all shaders, vbos & textures each time the Activity's onPaused method is called. I send a special event to event queue. In onSurfaceCreated I reinit all resources. It's working but it's not fast & it consumes a lot of memory. Is there a better solution?
As long as you are using GLSurfaceView , dont worry about deletion of OpenGL resources. Everytime user clicks home button / back button the EGL context and all the resources associated with that context (textures,VBOs,shaders) will be deleted. So it is your responsibility to create them in onSurfaceCreated() method.
Moreover I dont think deleting resources in onDestroy() will work because there wont be a valid EGL context available there.
On android, the GLSurfaceView documentation says this:
A GLSurfaceView must be notified when the activity is paused and
resumed. GLSurfaceView clients are required to call onPause() when the
activity pauses and onResume() when the activity resumes. These calls
allow GLSurfaceView to pause and resume the rendering thread, and also
allow GLSurfaceView to release and recreate the OpenGL display.
So I'm supposed to do something like this in my activity:
public void onPause() {
myGlSurfaceView.onPause();
}
public void onResume() {
myGlSurfaceView.onResume();
}
I'm observing in my code that if I don't call onPause() and onResume() then the context is not lost when I press the home button, so I can switch between applications and then go back to my game and everything is working. What I see is that if I close the game using the back button then the screen is black when I open it again, but I can change the back button behaviour to totally close the game and avoid this problem.
So my question is: when is the OpenGL context destroyed? If I don't call onPause() and onResume() can I assume that it will never be destroyed?
EDIT:
I'm targeting Android 2.2, so setPreserveEGLContextOnPause() is not an option to me.
The OpenGL might be lost only after Actvity::onPause() is called, and only in this case. See the setPreserveEGLContextOnPause documentation :
Whether the EGL context is actually preserved or not depends upon whether the Android device that the program is running on can support an arbitrary number of EGL contexts or not. Devices that can only support a limited number of EGL contexts must release the EGL context in order to allow multiple applications to share the GPU.
[...] the EGL context [can be] released when the GLSurfaceView is paused, and recreated when the GLSurfaceView is resumed.
EDIT : The situation described in the documentation is valid on all Android version. Not matter you have access to setPreserveEGLContextOnPause
In my opinion, this is one major drawback is Android OGLES implementation : you can't be certain.
The documentation itself is vague (EGL Context Lost note) :
There are situations where the EGL rendering context will be lost. This typically happens when device wakes up after going to sleep
I noticed the same behavior as you about the Home and Back button. Calls are not exactly the sames (but can't remember them precisely).
The only way to be sure that the OpenGL context is available is to create all OpenGL resources in onSurfaceCreated
Note about setPreserveEGLContextOnPause. Once again, this documentation comment demonstrates the "random" behavior of context destruction :
If set to true, then the EGL context may be preserved when the GLSurfaceView is paused. [...]
setPreserveEGLContextOnPause is an option for you, you just have to implement the GlSurfaceView yourself.
See my answer here to a similar question:
Prevent onPause from trashing OpenGL Context
My game is working correctly except in the case where I press the HOME button then resume. What needs to be done to use the textures again? I have tried calling onPause and onResume on the GLSurfaceView (when the activity's onPause and onResume are called).
Any ideas what I could be doing wrong?
If all else fails, reload the textures:
Pseudocode
for tex in textures:
if glIsTexture(tex.opengl_name) == false:
glGenTextures(1, &tex.opengl_name)
glBindTexture(tex.texture_target);
glTexImage(..., texture.image);
Even if you fixed your problem, just to give a bit of explanation that might help others.
Android does not guaranty to keep the OpenGL context alive when the activity is paused.
You have to recreate every OpenGL-resources on resume (texture in you case, but also VBOs etc etc).
Since API 11, you can ask kindly Android to keep the context, but there is no guaranty it would.
After trying:
do not call GLSurfaceView#onPause/onResume in Activity's onPause/onResume
call GLSurfaceView#onPause/onResume, but also set GLSurfaceView#setPreserveEGLContextOnPause(true)
Both of cases fix the HOME-resume-black-texture issue.
Guess Android implementation failed to re-create the EGL context when resume. Since onPause/onResume are required to call, should always set setPreserveEGLContextOnPause to true.