My app uses GLKit
to render 3D scene with OpenGL ES
.
All works fine, except one thing. When I launch my app in iPad and display background apps bar (with double "Home" button click) and then change device's orientation, scene is updated wrongly (last rendered image is simply stretched to fill new rectangle).
I found the reason. When background apps bar appears, GLKViewController's
paused
is set to YES
automatically (application delegate receives -applicationWillResignActive:
) and no rendering happens until this bar is closed.
I've found in Apple guides (OpenGL ES Programming Guide for iOS / Implementing a Multitasking-aware OpenGL ES Application) that after receiving -applicationWillResignActive:
application should stop GL rendering or will be terminated. So it seems that all is ok, except bad rendering after rotation :)
I checked some OpenGL games. They also became "paused" when this bar displayed, but somehow correctly update paused scene on device rotation. How do they achieve this?
tl;dr: possibly you only need to set the GLKView
's contentMode
to UIViewContentModeRedraw
Firstly I don't think that your application actually enters into the background, I think it only becomes inactive. The distinction between the applicationWillResignActive
and applicationDidEnterBackground
delegate methods. Assuming that the application is only inactive use the following, in case it actually gets put in the background then see below.
The apple documentation says that you should "throttle down OpenGL ES framerates" when applicationWillResignActive
gets called, not that OpenGL ES calls are not allowed, that only happens after the application goes into the background.
This means that GLKit
's GLKView
/GLKViewController
may be a bit overzealous in this regard. To fix it you need to make sure that:
- The
GLKView
's contentMode
is set to UIViewContentModeRedraw
- The
GLKView
's drawRect
method does draw the frame even when the application is inactive but the frame is changed, but does not draw the frame (that is use OpenGL ES calls) when the application is in the background.
However, my presumption is that the drawRect
method does not even get called when the application is in the background, so you probably don't really have to worry about the OpenGL ES calls in the glkView:drawInRect
delegate method. The reason however that this function does not get called in your situation is that the view does not get invalidated. The reason for not being invalidated is twofold:
- The main frame loop in
GLKViewController
that invalidates the view periodically is paused by the paused
property.
- The
GLKView
contentMode
is probably the default 'UIViewContetModeScaleToFill'
As the GLKView
drawRect
method is probably not even looking at the paused
property, just changing the contentMode
may already be enough.
I would propose the following solution if the application actually does go into the background. As you're not allowed to use OpenGL ES calls while running in the background the solution is pretty straightforward:
Do all OpenGL ES calls you need to do to support what you want before you enter the background.
That is, in applicationWillResignActive
do the following:
- Pause your game loop (done by setting
GLKViewController
's paused
)
- Pause your render loop (done by setting
GLKViewController
's paused
)
- Grab the current framebuffer for the current orientation state
- Render the current game state one more time with a framebuffer and viewport that correspond to the rotated orientation state and grab that framebuffer
Furthermore you need GLKView
's contentMode
to be set to UIViewContentModeRedraw
so that the drawRect
method is actually called after the view's frame has been changed by the orientation change.
Finally in GLKView
's drawRect
method you need to check whether paused
is YES
or NO
in the NO
case do the rendering as normal, in the YES
case take one of the framebuffers saved in applicationWillResignActive
and draw it to the view with regular UIKit
calls.
I'm not sure how well this hackish solution would integrate with GLKit
, you might need some subclassing.
Implement the delegate method (if you haven't done it already)
-(void)applicationWillEnterForeground
and unpause the GLKViewController
from there.
From what I understood from your question you can keep the game paused but resize the glView in this method as well, but without seeing any code is also difficult to really see what's going on.