如何在应用程序后台处理期间暂停并发OpenGL绘图?

ope*_*rog 6 concurrency opengl-es ios caeagllayer

应用程序转到后台后,必须立即停止调用OpenGL函数.但不知何故,当用户按下主页按钮时,所有停止OpenGL的努力都会失败并且我的应用程序经常(但并不总是)崩溃.

它崩溃了

异常类型:EXC_BAD_ACCESS代码:KERN_INVALID_ADDRESS位于0x1 libGPUSupportMercury.dylib gpus_ReturnNotPermittedKillClient

据我所知,如果你在应用程序不在前台后调用OpenGL函数,应用程序将崩溃,因为GPU不适用于应用程序.

使用OpenGL呈现的视图具有调度队列

self.drawingQueue = dispatch_queue_create("openglRenderQueue", DISPATCH_QUEUE_SERIAL);
Run Code Online (Sandbox Code Playgroud)

它必须与UIScrollView并行渲染.所以有一个GCD 信号量让队列等待UIScrollView.

self.renderSemaphore = dispatch_semaphore_create(1);
Run Code Online (Sandbox Code Playgroud)

我创建了一个CADisplayLink来更新runloop:

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

update方法在串行渲染队列上执行绘图计算异步,并使用信号量等待UIScrollView.它最终在主线程上提交同步.

- (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);
  });
}
Run Code Online (Sandbox Code Playgroud)

更新方法检查主线程是否可以呈现.支票看起来像这样:

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

我怀疑最重要的一件事是停止队列的并发问题.因为它是异步的,所以即使在我停止显示链接之后块也会触发.

所以我认为缺少的是我必须在调用此异步块中的任何OpenGL函数之前检查-canRender:

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

好的,但现在想象一下,我在渲染运行循环的开头做了这个检查.-canRender是的.然后我打电话[sprite drawState:state];.这个方法调用了很多gl函数.我认为这是根本问题.因为即使我的异步块检查主线程上的应用程序是否还在前台,当我继续异步执行复杂绘图3纳秒后,应用程序可能处于后台状态和CRASH.

因此,即使我在每个单独的gl函数调用之前检查主线程上的-canRender,它可能是一个分裂纳秒以后的应用程序在后台并崩溃.

有没有办法从内到外关闭OpenGL,所以它忽略了对其函数的调用.我听说过整理方法.我不得不把它关掉.但是怎么样?

And*_*man 1

glFinish (...)在允许您的应用程序进入后台之前,您可以考虑一下。它将阻塞,直到管道中的每个命令完成为止。但这可能需要一段时间,可能需要几毫秒。

出于同样的原因,如果您真正想做的只是让 OpenGL 忽略 API 调用,直到满足某些条件(在本例中,直到您的应用程序不再处于后台),最简单的方法是设置活动上下文对于给定线程为 NULL。这将使每个调用都成为无操作,以至于在这种状态下进行任何 GL API 调用时都不会生成错误。当应用程序返回前台时,您可以恢复活动上下文。

从您的问题描述来看,听起来您可能需要将这两件事结合起来。然而,glFlush (...)可能就足够了 - 这将告诉 OpenGL 刷新缓冲区中的所有命令(基本上开始执行正在排队的任何命令)。它不会等待命令完成后再返回,但它保证它们会在 GL 执行其他操作之前完成。