CADisplayLink OpenGL 渲染破坏了 UIScrollView 行为

发布于 2024-11-06 19:10:26 字数 852 浏览 2 评论 0原文

SO 上有一些类似的问题(链接在最后),但它们都不允许我解决我的问题,所以这里是:

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

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

承诺类似问题的链接: UIScrollView 损坏并停止使用 OpenGL 渲染滚动(相关 CADisplayLink、 NSRunLoop)

拖动 UIScrollView 时 OpenGL ES 视图中的动画冻结在 iPhone 上

There are a few similar questions out there on SO (links at end), but none of them has allowed me to fix my problem, so here goes:

I'm using OpenGL rendering to make an image tiling and caching library for use in a game project, and I want to hijack the physics of the UIScrollView to allow the user to navigate around the images (since it has nice bounce behaviour, might as well use it). So I have a UIScrollView which I'm using to get the rendering view for my textures, but there's a problem - moving around on the scroll view prevents the CADisplayLink from firing until the user has finished scrolling (which looks horrible). One temporary fix has been to use NSRunLoopCommonModes instead of the default run mode, but unfortunately this breaks some aspects of scroll view behaviour on certain phones I'm testing on (the 3GS and simulator seem to work fine, while the iPhone4 and the 3G don't).

Does anyone know how I could get around this clash between the CADisplayLink and the UIScrollView, or know how to fix the UIScrollView working in other run modes? Thanks in advance :)

Promised links to similar questions:
UIScrollView broken and halts scrolling with OpenGL rendering (related CADisplayLink, NSRunLoop)

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

泪痕残 2024-11-13 19:10:26

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 周期而速度稍有减慢。

It's possible that slow updates on the main thread triggered by the CADisplayLink are what's breaking UIScrollView's scrolling behavior here. Your OpenGL ES rendering might be taking long enough for each frame to throw off the timing of a UIScrollView when using NSRunLoopCommonModes for the CADisplayLink.

One way around this is to perform your OpenGL ES rendering actions on a background thread by using a Grand Central Dispatch serial queue. I did this in my recent update to Molecules (source code for which can be found at that link), and in testing with using NSRunLoopCommonModes on my CADisplayLink, I don't see any interruption of the native scrolling behavior of a table view that's onscreen at the same time as the rendering.

For this, you can create a GCD serial dispatch queue and use it for all of your rendering updates to a particular OpenGL ES context to avoid two actions writing to the context at the same time. Then, within your CADisplayLink callback you can use code like the following:

if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0)
{
    return;
}

dispatch_async(openGLESContextQueue, ^{

    [EAGLContext setCurrentContext:context];

    // Render here

    dispatch_semaphore_signal(frameRenderingSemaphore);
});

where frameRenderingSemaphore is created earlier as follows:

frameRenderingSemaphore = dispatch_semaphore_create(1);

This code will only add a new frame rendering action onto the queue if one isn't in the middle of executing. That way, the CADisplayLink can fire continuously, but it won't overload the queue with pending rendering actions if a frame takes longer than 1/60th of a second to process.

Again, I tried this on my iPad here and found no disruption to the scrolling action of a table view, just a little slowdown as the OpenGL ES rendering consumed GPU cycles.

各自安好 2024-11-13 19:10:26

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

这是代码片段:

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

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

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

My simple solution is to halve the rendering rate when the run loop is in tracking mode. All my UIScrollViews now work smoothly.

Here is the code fragment:

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

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

        /*** Rendering code goes here ***/
     }
}
遗忘曾经 2024-11-13 19:10:26

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

UIScrollView 暂停 NSTimer 直到滚动完成< /a>

总结一下:当 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 看起来对我们的应用程序来说很好,并且绝对比不渲染好得多。

The answer at the following post works very well for me (it appears to be quite similar to Till's answer):

UIScrollView pauses NSTimer until scrolling finishes

To summarize: disable the CADisplayLink or GLKViewController render loop when the UIScrollView appears and start a NSTimer to perform the update/render loop at the desired framerate. When the UIScrollView is dismissed/removed from the view hierarchy, re-enable the displayLink/GLKViewController loop.

In the GLKViewController subclass I use the following code

on appear of 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];

on dismiss of UIScrollView:

// enable the GLKViewController update/render loop and cancel our own.
// UIScrollView wont interrupt us anymore
self.paused = NO;
[updateAndRenderTimer invalidate];
updateAndRenderTimer = nil;

Simple and effective. I'm not sure if this could cause artifacts/tearing of some sort since the rendering is decoupled from screen refreshes, but using CADisplayLink with NSRunLoopCommonModes totally breaks the UIScrollView in our case. Using NSTimer looks just fine for our app and definitely a whole lot better than no rendering.

新人笑 2024-11-13 19:10:26

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

Even though this is not the perfect solution, it still might work as a workaround;
You could ignore the display link availability and use NSTimer for updating your GL-layer instead.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文