Поворот экрана iOS OpenGL ES при видимой панели фоновых приложений

Мое приложение использует GLKit для рендеринга 3D-сцены с помощью OpenGL ES.

Все работает отлично, кроме одного. Когда я запускаю свое приложение на iPad и отображаю панель фоновых приложений (с двойным нажатием кнопки «Домой»), а затем меняю ориентацию устройства, сцена обновляется неправильно (последнее отображаемое изображение просто растягивается, чтобы заполнить новый прямоугольник).

Я нашел причину. Когда появляется панель фоновых приложений, GLKViewController's paused автоматически устанавливается на YES (делегат приложения получает -applicationWillResignActive:), и рендеринг не происходит, пока эта панель не будет закрыта.

Я нашел в руководствах Apple (Руководство по программированию OpenGL ES для iOS / Реализация многозадачного приложения OpenGL ES), которое после получения -applicationWillResignActive: приложения должно остановить рендеринг GL или будет прекращено. Так что вроде все ок, кроме плохого рендеринга после поворота :)

Я проверил несколько игр OpenGL. Они также становились «приостановленными», когда отображалась эта полоса, но каким-то образом правильно обновляли приостановленную сцену при вращении устройства. Как они этого добиваются?


person kpower    schedule 19.12.2012    source источник


Ответы (2)


tl; dr: возможно, вам нужно только установить GLKView contentMode на UIViewContentModeRedraw

Во-первых, я не думаю, что ваше приложение действительно уходит в фон, я думаю, что оно просто становится неактивным. Различие между методами делегата applicationWillResignActive и applicationDidEnterBackground. Предполагая, что приложение неактивно, используйте следующее, если оно действительно переводится в фоновый режим, см. ниже.

В документации Apple говорится, что вы должны «дросселировать частоту кадров OpenGL ES», когда вызывается applicationWillResignActive, а не то, что вызовы OpenGL ES не разрешены, это происходит только после того, как приложение переходит в фоновый режим.

Это означает, что GLKView/GLKViewController из GLKit могут быть немного переусердствовали в этом отношении. Чтобы исправить это, вам нужно убедиться, что:

  1. contentMode GLKView установлен на UIViewContentModeRedraw
  2. Метод drawRect класса GLKView отрисовывает кадр, даже когда приложение неактивно, но кадр изменен, но не рисует кадр (то есть использует вызовы OpenGL ES), когда приложение находится в фоновом режиме.

Однако я предполагаю, что метод drawRect даже не вызывается, когда приложение находится в фоновом режиме, поэтому вам, вероятно, не нужно беспокоиться о вызовах OpenGL ES в методе делегата glkView:drawInRect. Однако причина того, что эта функция не вызывается в вашей ситуации, заключается в том, что представление не становится недействительным. Причина не признания недействительной двояка:

  1. Цикл основного кадра в GLKViewController, который периодически делает представление недействительным, приостанавливается свойством paused.
  2. GLKView contentMode, вероятно, является значением по умолчанию «UIViewContetModeScaleToFill».

Поскольку метод GLKView drawRect, вероятно, даже не смотрит на свойство paused, простого изменения contentMode может быть уже достаточно.


Я бы предложил следующее решение, если приложение действительно переходит в фоновый режим. Поскольку вам не разрешено использовать вызовы OpenGL ES во время работы в фоновом режиме, решение довольно простое:

Выполните все вызовы OpenGL ES, необходимые для поддержки того, что вы хотите, прежде чем войти в фоновый режим.

То есть в applicationWillResignActive делаем следующее:

  1. Приостановить игровой цикл (сделано путем установки paused GLKViewController)
  2. Приостановите цикл рендеринга (сделано путем установки paused GLKViewController)
  3. Захватите текущий буфер кадра для текущего состояния ориентации
  4. Визуализируйте текущее состояние игры еще раз с буфером кадра и областью просмотра, которые соответствуют повернутому состоянию ориентации, и захватите этот буфер кадра.

Кроме того, вам нужно, чтобы contentMode в GLKView было установлено на UIViewContentModeRedraw, чтобы метод drawRect фактически вызывался после того, как рамка представления была изменена изменением ориентации.

Наконец, в методе drawRect GLKView вам нужно проверить, является ли paused YES или NO в случае NO сделать рендеринг как обычно, в случае YES взять один из кадровых буферов, сохраненных в applicationWillResignActive, и нарисовать его в представлении с помощью обычных вызовов UIKit.

Я не уверен, насколько хорошо это хакерское решение будет интегрироваться с GLKit, вам может понадобиться подкласс.

person wich    schedule 13.03.2013
comment
Я только что протестировал свой проект в симуляторе, анимация приостанавливается, когда панель приложения открывается, однако, когда я поворачиваю виртуальный iPad, кадр перерисовывается с новым соотношением сторон. Похоже, это происходит независимо от режима контента, тестирование с iPad iOS 6.1. - person wich; 13.03.2013
comment
Основная проблема заключается в том, что сцена перерисовывается только до нового (результативного) соотношения сторон. Объекты на моей сцене зависят от рамки просмотра — я пересчитываю их размеры и положение, чтобы заполнить как можно больше места на экране. Поэтому во время анимации мне нужны плавные пересчеты, а не начальный размер и положение -> конечный размер и положение — какой-то анимированный переход от одного к другому. И в случае повторного использования одного и того же фреймбуфера мы видим растяжение вместо изменения положения. - person kpower; 14.03.2013
comment
Если вы не начнете взламывать GLKit, вы не получите анимацию из одной формы в другую, поскольку цикл кадров действительно приостанавливается, и вы не хотите (не должны) продолжать цикл кадров, когда ваше приложение неактивно, т.е. это также происходит, когда вы получить входящий звонок или что-то подобное на iphone. Единственное, что вы можете сделать, это просто перерисовать сцену, основываясь на новых границах вида, которые корректно работают в GLKit. Лично я не думаю, что вращение устройства будет переключать презентации, а не анимировать их. - person wich; 14.03.2013
comment
Спасибо за ваш ответ, комментарии и время. Это не 100% ответ, который я хотел, но ответ, которого я ожидал. :) Теперь (по крайней мере) я знаю, что я ничего не пропустил. - person kpower; 14.03.2013

Реализуйте метод делегата (если вы еще этого не сделали)

-(void)applicationWillEnterForeground и возобновите паузу GLKViewController оттуда.

Из того, что я понял из вашего вопроса, вы можете поставить игру на паузу, но также изменить размер glView в этом методе, но, не видя никакого кода, также трудно понять, что происходит.

person Andres Bucci    schedule 13.03.2013
comment
GLKViewController автоматически возобновляет паузу при переходе на передний план. Когда панель фоновых приложений видна, приложение находится в фоновом состоянии, и на этот раз мне нужно GLKView обновление. - person kpower; 13.03.2013
comment
@kpower Думаю, теперь я понимаю ваш вопрос. Дело в том, что приложение запущено, но вы дважды нажали кнопку «Домой». Если у вас есть таймер, который контролирует ваш цикл рендеринга, тогда да, сделайте его недействительным во время работы didenterbackground и не отменяйте его активность. Он должен продолжать анимироваться, как это было. - person Andres Bucci; 13.03.2013