Iphone CADisplayLink OpenGL渲染破坏了UIScrollView行为

Iphone CADisplayLink OpenGL渲染破坏了UIScrollView行为,iphone,opengl-es,Iphone,Opengl Es,在SO上有一些类似的问题(最后的链接),但没有一个问题允许我解决我的问题,所以下面是: 我正在使用OpenGL渲染制作一个用于游戏项目的图像平铺和缓存库,我想利用UIScrollView的物理特性,允许用户在图像周围导航(因为它有很好的反弹行为,也可以使用它)。因此,我有一个UIScrollView,我正在使用它来获取纹理的渲染视图,但有一个问题-在滚动视图上移动会阻止CADisplayLink在用户完成滚动之前启动(这看起来很可怕)。一个临时修复方法是使用nsrunlopcomonmodes而

在SO上有一些类似的问题(最后的链接),但没有一个问题允许我解决我的问题,所以下面是:

我正在使用OpenGL渲染制作一个用于游戏项目的图像平铺和缓存库,我想利用UIScrollView的物理特性,允许用户在图像周围导航(因为它有很好的反弹行为,也可以使用它)。因此,我有一个UIScrollView,我正在使用它来获取纹理的渲染视图,但有一个问题-在滚动视图上移动会阻止CADisplayLink在用户完成滚动之前启动(这看起来很可怕)。一个临时修复方法是使用nsrunlopcomonmodes而不是默认的运行模式,但不幸的是,这破坏了我正在测试的某些手机上滚动视图行为的某些方面(3GS和模拟器似乎工作正常,而iPhone4和3G没有)

有人知道我如何避免CADisplayLink和UIScrollView之间的冲突,或者知道如何修复在其他运行模式下工作的UIScrollView吗?提前感谢:)

类似问题的承诺链接:


尽管这不是一个完美的解决方案,但它仍然可以作为一种变通方法;
您可以忽略显示链接的可用性,而使用NSTimer来更新GL图层。

可能是CADisplayLink触发的主线程上的缓慢更新破坏了UIScrollView在此的滚动行为。当对CADisplayLink使用
NSRunLoopCommonModes
时,OpenGL ES渲染可能需要足够长的时间,以使每个帧都偏离UIScrollView的计时

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

为此,您可以创建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周期而有点慢。

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

以下是代码片段:

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

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

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

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

总结:当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];
// enable the GLKViewController update/render loop and cancel our own.
// UIScrollView wont interrupt us anymore
self.paused = NO;
[updateAndRenderTimer invalidate];
updateAndRenderTimer = nil;
关闭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];
// enable the GLKViewController update/render loop and cancel our own.
// UIScrollView wont interrupt us anymore
self.paused = NO;
[updateAndRenderTimer invalidate];
updateAndRenderTimer = nil;

简单有效。我不确定这是否会导致某种伪影/撕裂,因为渲染与屏幕刷新是解耦的,但在我们的例子中,将CADisplayLink与nsrunlopCommonModes一起使用会完全破坏UIScrollView。对于我们的应用程序来说,使用NSTimer看起来很好,而且肯定比不渲染要好得多。

我们目前的解决方案也不完全完美(完全忽略显示循环,并在didScroll委托方法上调用render)。真正的问题是,我们希望我正在编写的这个类能够适合我们已经拥有的iPhone上已有的OpenGL游戏框架。最终,如果显示链接不能很好地与滚动视图配合使用,我们可能不得不重新实现我们喜欢的滚动视图行为,但这需要一段时间:(在那里,得到了t恤;)。。。。摩擦滚动和反弹并不是很容易实现的。我们计划针对3.1+设备,尽管也许你是对的,我们应该删除3.x…@ranreloated-通过使渲染发生在非主线程上,你可以看到由于并行化GPU和CPU绑定的处理而带来的一些不错的性能改进(当GPU仍在处理最后一组数据时,从CPU上传数据,等等)。这对多核系统特别有帮助,我看到仅从帧渲染的后台处理中,渲染性能就提高了40%。即使在单核计算机上,我也看到了10-20%的改进。@RanReloated-如果使用串行调度队列,情况也不太糟。对所有需要执行的操作使用单个串行调度队列h一个特定的OpenGL ES上下文将保证您不会从多个线程同时访问该上下文。在我的情况下,通过在主线程上运行所有内容很容易迁移到该上下文。只需将所有与此上下文相关的操作打包到分派中的块中,然后根据需要同步或异步到串行队列。I刚刚用GLKViewController拥有的GLKView实现了这个解决方案,我发现我必须在displayLink回调结束时在主线程上调用“[(GLKView*)self.view display];”,以便更新所有内容。非常感谢这个线程上的贡献者;我的ope