Как остановить одновременное рисование OpenGL во время фона приложения?

Как только приложение переходит в фоновый режим, оно должно перестать вызывать функции OpenGL. Но почему-то все попытки остановить OpenGL терпят неудачу, и мое приложение часто (но не всегда) вылетает, когда пользователь нажимает кнопку "Домой".

Сбой с

Тип исключения: EXC_BAD_ACCESS Код: KERN_INVALID_ADDRESS в 0x1 libGPUSupportMercury.dylib gpus_ReturnNotPermittedKillClient

Насколько я понимаю, если вы вызываете функции OpenGL после того, как приложение перестает быть на переднем плане, приложение перестает работать, поскольку приложение недоступно для графического процессора.

Представление, которое визуализируется с помощью OpenGL, имеет очередь отправки

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

Он должен отображаться параллельно с UIScrollView. Таким образом, существует семафор GCD, позволяющий очереди ждать 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);
}

Одна вещь, которую я подозреваю больше всего, это проблема параллелизма, чтобы остановить очередь. Поскольку он асинхронный, блок срабатывает даже после того, как я остановил ссылку на дисплей.

Поэтому я думаю, что не хватает только того, что я должен проверить -canRender ДО ТОГО, как я вызову любую функцию OpenGL в этом асинхронном блоке:

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

Хорошо, но теперь представьте, что я делаю эту проверку в начале цикла рендеринга. -canRender говорит ДА. Тогда я звоню [sprite drawState:state];, Этот метод вызывает много функций gl. Я думаю, что это коренная проблема. Потому что, даже если мои асинхронные блоки проверяют основной поток, если приложение все еще находится на переднем плане, при продолжении асинхронного выполнения сложного рисования через 3 наносекунды приложение может находиться в фоновом состоянии и CRASH.

Поэтому, даже если я проверяю -canRender в главном потоке перед каждым вызовом функции gl, может случиться так, что приложение, разделенное на доли секунды позже, будет в фоновом режиме и аварийно завершится.

Есть ли способ отключить OpenGL изнутри, чтобы он игнорировал вызовы своих функций. Я слышал о методе отделки. Я должен был бы закрыть это. Но как?

1 ответ

Вы могли бы рассмотреть glFinish (...) прежде чем позволить вашему приложению перейти в фоновый режим. Он будет блокироваться до тех пор, пока не завершится каждая команда в конвейере. Это может занять некоторое время, потенциально несколько мс.

Точно так же, если все, что вы действительно хотите сделать, это заставить OpenGL игнорировать вызовы API до тех пор, пока не будет выполнено какое-то условие (в этом случае, пока ваше приложение больше не будет работать в фоновом режиме), то самый простой способ сделать это - установить активный контекст для данного потока в NULL. Это сделает каждый вызов бездействующим, настолько, что при выполнении любого вызова GL API в этом состоянии не будет возникать никаких ошибок. Вы можете восстановить активный контекст, когда ваше приложение вернется на передний план.

Из описания вашей проблемы может показаться, что вам может понадобиться сочетание обеих этих вещей. Тем не мение, glFlush (...) может быть достаточно - это скажет OpenGL сбросить все команды в буфере (в основном, начать выполнять все, что было поставлено в очередь). Он не будет ждать окончания команд перед возвратом, но гарантирует, что они закончат, прежде чем GL сделает что-либо еще.

Другие вопросы по тегам