Визуализация CADisplayLink OpenGL нарушает поведение UIScrollView
Есть несколько похожих вопросов по SO (ссылки в конце), но ни один из них не позволил мне решить мою проблему, так что вот так:
Я использую рендеринг OpenGL для создания библиотеки листов и кэширования изображений для использования в игровом проекте, и я хочу перехватить физику UIScrollView, чтобы позволить пользователю перемещаться по изображениям (так как он имеет хорошее поведение отскока, может как хорошо им пользоваться). Таким образом, у меня есть UIScrollView, который я использую, чтобы получить представление рендеринга для моих текстур, но есть проблема - перемещение по представлению прокрутки предотвращает запуск CADisplayLink до тех пор, пока пользователь не закончит прокрутку (что выглядит ужасно). Одним из временных исправлений было использование NSRunLoopCommonModes вместо режима запуска по умолчанию, но, к сожалению, это нарушает некоторые аспекты поведения просмотра прокрутки на некоторых телефонах, на которых я тестирую (3GS и симулятор, кажется, работают нормально, в то время как iPhone4 и 3G не работают "т).
Кто-нибудь знает, как я мог обойти это столкновение между CADisplayLink и UIScrollView, или знает, как исправить UIScrollView, работающий в других режимах выполнения? Заранее спасибо:)
Обещанные ссылки на похожие вопросы: UIScrollView не работает и останавливает прокрутку с помощью рендеринга OpenGL (связанный CADisplayLink, NSRunLoop)
Анимация в представлении OpenGL ES зависает при перетаскивании UIScrollView на iPhone
4 ответа
Возможно, что медленные обновления в главном потоке, запускаемые CADisplayLink, являются причиной того, что нарушает поведение прокрутки в UIScrollView. Ваш рендеринг ES OpenGL может занять достаточно много времени для каждого кадра, чтобы отбросить время UIScrollView при использовании NSRunLoopCommonModes
для CADisplayLink.
Одним из способов решения этой проблемы является выполнение действий рендеринга OpenGL ES в фоновом потоке с использованием последовательной очереди Grand Central Dispatch. Я сделал это в своем недавнем обновлении Molecules (исходный код которого можно найти по этой ссылке), а также в тестировании с использованием NSRunLoopCommonModes
на моем CADisplayLink я не вижу прерывания собственного режима прокрутки табличного представления, которое отображается на экране одновременно с рендерингом.
Для этого вы можете создать очередь последовательной отправки GCD и использовать ее для всех ваших обновлений рендеринга в конкретный контекст OpenGL ES, чтобы избежать двух действий, записывающих в контекст одновременно. Затем в вашем обратном вызове CADisplayLink вы можете использовать код, подобный следующему:
if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0)
{
return;
}
dispatch_async(openGLESContextQueue, ^{
[EAGLContext setCurrentContext:context];
// Render here
dispatch_semaphore_signal(frameRenderingSemaphore);
});
где frameRenderingSemaphore
создан ранее следующим образом:
frameRenderingSemaphore = dispatch_semaphore_create(1);
Этот код только добавит новое действие рендеринга фрейма в очередь, если он не находится в середине выполнения. Таким образом, CADisplayLink может запускаться непрерывно, но он не будет перегружать очередь ожидающими действиями рендеринга, если для обработки кадра требуется больше 1/60 секунды.
Опять же, я попробовал это на своем iPad здесь и не обнаружил нарушения прокрутки в представлении таблицы, только небольшое замедление, поскольку рендеринг OpenGL ES потреблял циклы графического процессора.
Мое простое решение состоит в том, чтобы вдвое уменьшить скорость рендеринга, когда цикл выполнения находится в режиме отслеживания. Все мои UIScrollViews теперь работают без сбоев.
Вот фрагмент кода:
- (void) drawView: (CADisplayLink*) displayLink
{
if (displayLink != nil)
{
self.tickCounter++;
if(( [[ NSRunLoop currentRunLoop ] currentMode ] == UITrackingRunLoopMode ) && ( self.tickCounter & 1 ))
{
return;
}
/*** Rendering code goes here ***/
}
}
Ответ в следующем посте очень хорошо работает для меня (похоже, он очень похож на ответ Тилля):
UIScrollView приостанавливает NSTimer до завершения прокрутки
Подводя итог: отключите цикл рендеринга CADisplayLink или GLKViewController, когда появится UIScrollView, и запустите NSTimer для выполнения цикла обновления / рендеринга с желаемой частотой кадров. Когда UIScrollView отклонен / удален из иерархии представления, повторно включите цикл displayLink/GLKViewController.
В подклассе GLKViewController я использую следующий код
при появлении UIScrollView:
// disable GLKViewController update/render loop, it will be interrupted
// by the UIScrollView of the MPMediaPicker
self.paused = YES;
updateAndRenderTimer = [NSTimer timerWithTimeInterval:1.0f/60.0f target:self selector:@selector(updateAndRender) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:updateAndRenderTimer forMode:NSRunLoopCommonModes];
при закрытии UIScrollView:
// enable the GLKViewController update/render loop and cancel our own.
// UIScrollView wont interrupt us anymore
self.paused = NO;
[updateAndRenderTimer invalidate];
updateAndRenderTimer = nil;
Просто и эффективно. Я не уверен, может ли это вызвать какие-либо артефакты / разрывы, поскольку рендеринг отделен от обновлений экрана, но использование CADisplayLink с NSRunLoopCommonModes полностью нарушает UIScrollView в нашем случае. Использование NSTimer прекрасно выглядит для нашего приложения и определенно намного лучше, чем отсутствие рендеринга.
Несмотря на то, что это не идеальное решение, оно все еще может работать в качестве обходного пути; Вы можете игнорировать доступность отображаемой ссылки и использовать NSTimer для обновления вашего GL-слоя.