iOS OpenGL ES screen rotation while background app

2019-03-20 16:00发布

问题:

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?

回答1:

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:

  1. The GLKView's contentMode is set to UIViewContentModeRedraw
  2. 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:

  1. The main frame loop in GLKViewController that invalidates the view periodically is paused by the paused property.
  2. 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:

  1. Pause your game loop (done by setting GLKViewController's paused)
  2. Pause your render loop (done by setting GLKViewController's paused)
  3. Grab the current framebuffer for the current orientation state
  4. 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.



回答2:

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.