Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to halt concurrent OpenGL drawing during app backgrounding?

As soon as app goes to background it must stop calling OpenGL functions. But somehow all efforts to stop OpenGL fail and my app often (but not always) crashes when user presses the home button.

It crashes with

Exception Type: EXC_BAD_ACCESS Code: KERN_INVALID_ADDRESS at 0x1 libGPUSupportMercury.dylib gpus_ReturnNotPermittedKillClient

To my understanding if you call OpenGL functions after app is not in foreground anymore app will crash because GPU is not available to app.

The view that renders with OpenGL has a dispatch queue

self.drawingQueue = dispatch_queue_create("openglRenderQueue", DISPATCH_QUEUE_SERIAL);

It has to render in parallel with UIScrollView. So there is a GCD semaphore to let the queue wait for UIScrollView.

self.renderSemaphore = dispatch_semaphore_create(1);

I create a CADisplayLink to update runloop:

CADisplayLink *dl = [[UIScreen mainScreen] displayLinkWithTarget:self selector:@selector(update)];
[dl addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
self.displayLink = displayLink; 

The update method performs drawing calculations async on serial render queue and uses the semaphore to wait for UIScrollView. It finally commits sync on main thread.

- (void)update {
  dispatch_async(drawingQueue, ^{
    if (dispatch_semaphore_wait(renderSemaphore, DISPATCH_TIME_NOW) != 0) {
        return;
    }

    @autoreleasepool {
        [EAGLContext setCurrentContext:context];

        glBindFramebufferOES(GL_FRAMEBUFFER_OES, framebuffer);
        glViewport(0, 0, width, height);
        glMatrixMode(GL_MODELVIEW);
        glClear(GL_COLOR_BUFFER_BIT);
        glLoadIdentity();

        // Quickly grab the target game state for rendering
        GameState state = targetState;

        [sprite drawState:state];

        dispatch_sync(dispatch_get_main_queue(), ^{
            if ([self canRender]) {

                BOOL isAnimating = [self isAnimating];
                if (isAnimating && displayLink) {
                    [EAGLContext setCurrentContext:context];

                    glBindRenderbufferOES(GL_RENDERBUFFER_OES, renderbuffer);

                    if (displayLink != nil) {
                        [context presentRenderbuffer:GL_RENDERBUFFER_OES];
                    }
                }

            }
        });
    }

    dispatch_semaphore_signal(renderSemaphore);
  });
}

The update method checks on the main thread if it can render. The check looks like this:

- (BOOL)canRender {
    UIApplicationState appState = [[UIApplication sharedApplication] applicationState];
    return (appState != UIApplicationStateBackground && appState != UIApplicationStateInactive);
}

The one thing I suspect the most is a concurrency problem to halt the queue. Because it is async the block fires even after I stopped the display link.

So I think what is missing is that I must also check -canRender BEFORE I call any OpenGL function in this async block:

__block BOOL canRender = YES;
dispatch_sync(dispatch_get_main_queue(), ^{
    canRender = [self canRender];
});
if (!canRender) {
    return;
}

OK but now imagine I make this check at the beginning of render run loop. -canRender says YES. Then I call [sprite drawState:state];. This method calls a lot of gl functions. I think that this is the root problem. Because even if my async blocks checks on main thread if app is still in foreground, when I continue async to do complex drawing 3 nanoseconds later app could be in background state and CRASH.

So even if I check -canRender on main thread before every single gl function call it could be that a split nanosecond later app is in background and crash.

Is there a way to shut down OpenGL from inside out so it ignores calls to its functions. I had heard about a finishing method. I would have to shut it down. But how?

like image 277
openfrog Avatar asked Oct 06 '13 23:10

openfrog


1 Answers

You could consider glFinish (...) before allowing your application to go into the background. It will block until every command in the pipeline finishes. This could take a while though, potentially several ms.

By the same token, if all you really want to do is make OpenGL ignore API calls until some condition is met (in this case, until your application is no longer in the background) the easiest way to do this is to set the active context for a given thread to NULL. This will make each and every call a no-op, so much so that no error will be generated when you make any GL API call in this state. You can restore the active context when your application is brought back into the foreground.

From your problem description, it sounds like you may need a combination of both of these things. However, glFlush (...) may be sufficient - this will tell OpenGL to flush all of the commands in the buffer (basically start executing anything that was being queued up). It will not wait for the commands to finish before returning, but it guarantees that they will finish before GL does anything else.

like image 76
Andon M. Coleman Avatar answered Oct 11 '22 15:10

Andon M. Coleman