如何将OpenGL绘图与UIKit更新同步

时间:2012-10-19 04:20:04

标签: opengl-es uikit core-animation

在我们的应用程序中,我们在CAEAGLLayer上面有UIScrollView。 UIScrollView包含一些UIViews(红色矩形)。在CAEAGLLayer中,我们绘制白色矩形。白色矩形的中心与红色矩形的中心相同。当UIScrollView滚动时,我们更新CAEAGLLayer中白色矩形的位置并渲染它们。

我们得到了预期的结果:白色矩形的中心总是与红色矩形的中心相同。

但是我们无法将CAEAGLLayer的更新与UIScrollView中的视图移动同步。 我们有一些错误 - 红色矩形落后于白色矩形。

粗略地说,我们真的想让CAEAGLLayer与UIScollView一起滞后。

我们准备了示例代码。在设备上运行并滚动,您将看到白色矩形(由OpenGL绘制)比红色矩形(常规UIViews)移动得更快。在scrollViewDidScroll:委托调用中正在更新OpenGL。

https://www.dropbox.com/s/kzybsyu10825ikw/ios-opengl-scrollview-test.zip

即使在iOS模拟器中也是如此,只需看一下视频:http://www.youtube.com/watch?v=1T9hsAVrEXw

红色= UIKit,白色= OpenGL

代码是:

- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
    // reuses red squares that are gone outside thw bounds
    [overlayView updateVisibleRect:CGRectMake(...)];
    // draws white squares using OpenGL under the red squares
    [openGlView updateVisibleRect:CGRectMake(...)];
}

编辑:

在简化的样本中可以很容易地证明同样的问题。可以在以下位置找到工作的xcodeproj:

https://www.dropbox.com/s/vznzccibhlf8bon/simple_sample.zip

示例项目基本上在OpenGL中绘制并动画一组WHITE正方形,并对一组RED UIViews执行相同操作。红色和白色方块之间很容易看到滞后。

6 个答案:

答案 0 :(得分:11)

在iOS 9中,CAEAGLLayer具有presentsWithTransaction属性,可同步两者。

答案 1 :(得分:8)

实际上,您无法使用当前API同步它们。 MobileMaps.app和Apple Map Kit使用CAEAGLLayer asynchronous上的私有属性来解决此问题。

以下是相关雷达:http://openradar.appspot.com/radar?id=3118401

答案 2 :(得分:5)

稍微挖了一下后,我想扩展我以前的答案:

您的OpenGL渲染会立即从scrollViewDidScroll:方法中完成,而UIKit绘图将在稍后执行,在来自runloop的正常CATransaction更新期间执行。

要将UIKit更新与OpenGL渲染同步,请将两者都包含在一个显式事务中,并将其刷新以强制UIKit立即将更改提交到backbaordd

- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
    [CATransaction begin];
    [overlayView updateVisibleRect:CGRectMake(...)];
    [openGlView updateVisibleRect:CGRectMake(...)];
    [CATransaction flush]; // trigger a UIKit and Core Animation graphics update
    [CATransaction commit];
}

答案 3 :(得分:2)

由于缺乏正确答案,我想分享一下我的想法:

有两种绘图方式:核心动画(UIKit)和OpenGL。最后,所有绘图都是由OpenGL完成的,但Core Animation部分是在backboardd(或者是iOS 6之前的Springboard.app)中渲染的,它是一种窗口服务器。

为了使这项工作正常,您的应用程序的流程会序列化层次结构并更改其属性,并将数据传递给backboardd,后者又会渲染和组合图层并使结果可见。

将OpenGL与UIKit混合时,CAEAGLLayer的渲染(在您的应用程序中完成)必须与剩余的Core Animation图层合成。我猜测CAEAGLLayer使用的渲染缓冲区以某种方式与backboardd共享,以便快速传输应用程序的渲染。此机制不一定必须与Core Animation的更新同步。

要解决您的问题,有必要找到一种方法将OpenGL渲染与Core Animation层序列化同步并转移到Backboardd。很抱歉无法提供解决方案,但我希望这些想法和猜测可以帮助您了解问题的原因。

答案 4 :(得分:0)

我正在努力解决完全相同的问题。我尝试了上面描述的所有方法,但没有什么是可以接受的。

理想情况下,这可以通过访问Core Animation的最终合成GL上下文来解决。但我们无法访问私有API。

另一种方法是将GL结果传输到CA.我尝试通过读取帧缓冲区像素来调度到CALayer,这太慢了。应传输大约200MB /秒以上的数据。现在我正在努力优化这一点。因为这是我可以尝试的最后一种方式。

更新

仍然很重,但读取帧缓冲区并将其设置为CALayer.content运行良好,并在我的iPhone 4S上显示可接受的性能。无论如何,超过70%的CPU负载仅用于此读取和设置操作。特别是glReadPixels我其中大多数只是等待从GPU内存中读取数据的时间。

UPDATE2

我放弃了最后一种方法。它的成本几乎完全是像素传输,但仍然太慢。我决定在GL中绘制每个动态对象。只会使用CA绘制一些静态弹出视图。

答案 5 :(得分:-1)

为了同步UIKit和OpenGL绘图,您应该尝试渲染Core Animation期望您绘制的位置,例如:

- (void)displayLayer:(CALayer *)layer
{
    [self drawFrame];
}

- (void)updateVisibleRect:(CGRect)rect {
    _visibleRect = rect;
    [self.layer setNeedsDisplay];
}