CADisplayLink OpenGL-рендеринг нарушает поведение UIScrollView
Есть несколько похожих вопросов на SO (ссылки в конце), но ни один из них не позволил мне исправить мою проблему, так вот:
Я использую рендеринг OpenGL, чтобы сделать библиотеку черепицы изображений и кеширования для использования в игровом проекте, и я хочу захватить физику UIScrollView, чтобы позволить пользователю перемещаться по изображениям (так как это имеет хорошее поведение отскока, может также использовать его). Таким образом, у меня есть UIScrollView, который я использую, чтобы получить представление рендеринга для моих текстур, но есть проблема - перемещение по просмотру прокрутки препятствует запуску CADisplayLink, пока пользователь не завершит прокрутку (что выглядит ужасно). Одним из временных исправлений было использование NSRunLoopCommonModes вместо режима запуска по умолчанию, но, к сожалению, это нарушает некоторые аспекты поведения прокрутки на некоторых телефонах, на которых я тестирую (3GS и симулятор, похоже, работают нормально, в то время как iPhone4 и 3G надеются "т).
Кто-нибудь знает, как я могу обойти это столкновение между CADisplayLink и UIScrollView или знать, как исправить UIScrollView, работающий в других режимах? Спасибо заранее:)
Обещанные ссылки на похожие вопросы:
UIScrollView сломал и остановил прокрутку с помощью рендеринга OpenGL (связанный CADisplayLink, NSRunLoop)
Анимация в OpenGL ES view зависает, когда UIScrollView перетаскивается на iPhone
Ответы
Ответ 1
Возможно, что медленные обновления в основном потоке, инициируемые CADisplayLink, - вот что нарушает поведение прокрутки UIScrollView. Ваш рендеринг OpenGL ES может занять достаточно много времени, чтобы каждый кадр отбрасывал время 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 потреблял циклы GPU.
Ответ 2
Мое простое решение - вдвое уменьшить скорость рендеринга, когда цикл выполнения находится в режиме отслеживания. Все мои UIScrollViews теперь работают плавно.
Вот фрагмент кода:
- (void) drawView: (CADisplayLink*) displayLink
{
if (displayLink != nil)
{
self.tickCounter++;
if(( [[ NSRunLoop currentRunLoop ] currentMode ] == UITrackingRunLoopMode ) && ( self.tickCounter & 1 ))
{
return;
}
/*** Rendering code goes here ***/
}
}
Ответ 3
Ответ на следующее сообщение очень хорошо работает для меня (похоже, он очень похож на ответ Till):
UIScrollView приостанавливает NSTimer до завершения прокрутки
Подводя итог: отключите цикл рендеринга CADisplayLink или GLKViewController, когда появится UIScrollView, и запустите NSTimer для выполнения цикла update/render с нужной частотой кадров. Когда 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 отлично подходит для нашего приложения и, безусловно, намного лучше, чем рендеринг.
Ответ 4
Несмотря на то, что это не идеальное решение, оно все равно может работать как обходное решение;
Вы можете игнорировать доступность отображения и использовать NSTimer для обновления своего GL-слоя.