【问题标题】:How to halt concurrent OpenGL drawing during app backgrounding?如何在应用程序后台停止并发 OpenGL 绘图?
【发布时间】:2013-10-13 11:05:46
【问题描述】:

一旦应用进入后台,它就必须停止调用 OpenGL 函数。但不知何故,阻止 OpenGL 的所有努力都失败了,当用户按下主页按钮时,我的应用程序经常(但不总是)崩溃。

它崩溃了

异常类型:EXC_BAD_ACCESS 代码:0x1 处的 KERN_INVALID_ADDRESS libGPUSupportMercury.dylib gpus_ReturnNotPermittedKillClient

据我了解,如果您在应用不再处于前台后调用 OpenGL 函数,应用将会崩溃,因为 GPU 对应用不可用。

使用 OpenGL 渲染的视图有一个调度队列

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

它必须与 UIScrollView 并行渲染。所以有一个GCDsemaphore让队列等待UIScrollView。

self.renderSemaphore = dispatch_semaphore_create(1);

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

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

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);
  });
}

update 方法检查主线程是否可以渲染。检查如下所示:

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

我最怀疑的一件事是暂停队列的并发问题。因为它是异步的,即使在我停止显示链接后也会触发该块。

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

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

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

因此,即使我在每个 gl 函数调用之前检查主线程上的 -canRender ,也可能是一个分纳秒后的应用程序处于后台并崩溃。

有没有办法从内到外关闭 OpenGL,使其忽略对其函数的调用。我听说过一种整理方法。我将不得不关闭它。但是怎么做呢?

【问题讨论】:

    标签: ios opengl-es concurrency caeagllayer


    【解决方案1】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-03-25
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-03
      • 1970-01-01
      相关资源
      最近更新 更多