【问题标题】:CADisplayLink OpenGL rendering breaks UIScrollView behaviourCADisplayLink OpenGL 渲染中断 UIScrollView 行为
【发布时间】:2011-08-22 02:11:03
【问题描述】:

在 SO 上有一些类似的问题(链接在最后),但没有一个可以让我解决我的问题,所以这里是:

我正在使用 OpenGL 渲染来制作用于游戏项目的图像平铺和缓存库,并且我想劫持 UIScrollView 的物理特性以允许用户在图像中导航(因为它具有良好的反弹行为,不妨使用它)。所以我有一个 UIScrollView,我用它来获取我的纹理的渲染视图,但是有一个问题 - 在滚动视图上四处移动会阻止 CADisplayLink 触发,直到用户完成滚动(这看起来很糟糕)。一个临时解决方法是使用 NSRunLoopCommonModes 而不是默认运行模式,但不幸的是,这会破坏我正在测试的某些手机上滚动视图行为的某些方面(3GS 和模拟器似乎工作正常,而 iPhone4 和 3G 不't)。

有谁知道我如何解决 CADisplayLink 和 UIScrollView 之间的冲突,或者知道如何修复 UIScrollView 在其他运行模式下的工作?在此先感谢:)

承诺的类似问题的链接: UIScrollView broken and halts scrolling with OpenGL rendering (related CADisplayLink, NSRunLoop)

Animation in OpenGL ES view freezes when UIScrollView is dragged on iPhone

【问题讨论】:

标签: iphone opengl-es


【解决方案1】:

以下帖子中的答案对我来说非常有效(它似乎与 Till 的答案非常相似):

UIScrollView pauses NSTimer until scrolling finishes

总结:当 UIScrollView 出现时禁用 CADisplayLink 或 GLKViewController 渲染循环,并启动 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 看起来还不错,而且绝对比不渲染要好很多。

【讨论】:

  • 这确实是最好的解决方案。恒定帧率,不允许 UI 阻止它...应该选择答案。
【解决方案2】:

我的简单解决方案是在运行循环处于跟踪模式时将渲染速率减半。我所有的 UIScrollViews 现在都可以正常工作了。

代码片段如下:

- (void) drawView: (CADisplayLink*) displayLink
{
    if (displayLink != nil) 
    {
        self.tickCounter++;

        if(( [[ NSRunLoop currentRunLoop ] currentMode ] == UITrackingRunLoopMode ) && ( self.tickCounter & 1 ))
        {
            return;
        }

        /*** Rendering code goes here ***/
     }
}

【讨论】:

  • 这不是只有在有触摸事件时才有用吗?假设您滚动,但仍有动力。我以为跟踪模式只在有触摸事件时才会限制?
  • 恰恰相反。当有任何跟踪或动量时,它会有所帮助。但是,我已经弃用了这种方法,转而支持上面详述的 Brad Larson 的 GCD 解决方案,它非常有效。
【解决方案3】:

CADisplayLink 触发的主线程上的缓慢更新可能会破坏 UIScrollView 的滚动行为。为 CADisplayLink 使用 NSRunLoopCommonModes 时,您的 OpenGL ES 渲染可能需要足够长的时间,以致每一帧都无法触发 UIScrollView 的计时。

解决此问题的一种方法是使用 Grand Central Dispatch 串行队列在后台线程上执行 OpenGL ES 渲染操作。我在最近对Molecules 的更新中做到了这一点(可以在该链接中找到其源代码),并且在我的 CADisplayLink 上使用NSRunLoopCommonModes 进行测试时,我没有看到任何本地滚动行为的中断与渲染同时出现在屏幕上的表格视图。

为此,您可以创建一个 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 周期而稍微变慢了。

【讨论】:

  • 我们计划针对 3.1+ 的设备,尽管您可能是对的,我们应该完全放弃 3.x...
  • @ranReloaded - 通过让你的渲染发生在非主线程上,你可以看到一些不错的性能改进,因为并行化 GPU 和 CPU 绑定处理(从 CPU 上传数据,而 GPU仍在处理最后一组等)。这对多核系统特别有帮助,我已经看到仅通过背景帧渲染就可以将渲染性能提高 40%。即使在单核机器上,我也看到了 10-20% 的改进。
  • @ranReloaded - 如果您使用串行调度队列,这还不错。对涉及特定 OpenGL ES 上下文的所有操作使用单个串行调度队列将保证您不会从多个线程同时访问该上下文。就我而言,从在主线程上运行所有内容很容易迁移到该模式。只需将所有涉及此上下文的操作包装在调度中的块中,然后根据需要同步或异步到串行队列。
  • 我刚刚使用 GLKViewController 拥有的 GLKView 实现了这个解决方案,发现我必须调用 '[(GLKView*)self.view display];'在 displayLink 回调结束时的主线程上,以便更新所有内容。非常感谢这个帖子的贡献者;我的 opengl 世界的视角现在由 UIScrollView 控制,让我免费获得所有漂亮的流体运动。
  • @Xavier - 这是一个很好的问题,也是我自己尝试过的一个问题。根据我的经验,CADisplayLink 似乎在屏幕刷新之前触发了一点,所以在你进入下一次刷新之前你有几毫秒的时间来渲染。尽管如此,同步调度仍然有严重的缺点,它会阻塞主队列并中断渲染,就像这个问题中的问题一样。 OpenGL ES 渲染的异步调度还可以很好地并行化数据上传和渲染,并且可以在多核设备上带来巨大的性能优势。我已经测试了这两种方法,在这里异步获胜。
【解决方案4】:

即使这不是完美的解决方案,它仍然可以作为一种解决方法; 您可以忽略显示链接的可用性并使用 NSTimer 来更新您的 GL 层。

【讨论】:

  • 我们目前的解决方案也不完美(完全忽略显示循环并在 didScroll 委托方法上调用渲染)。真正的问题是我们希望我正在编写的这个类适合我们现有的 iPhone 上的 OpenGL 游戏框架。最终,如果显示链接不能很好地与滚动视图配合使用,我们可能只需要重新实现我们喜欢的滚动视图行为,但这需要一段时间:(
  • 到过那里,得到了 T 恤 ;).... 带有反弹的摩擦滚动并非易事。
猜你喜欢
  • 2011-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多