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-слоя.