【问题标题】:How to synchronize OpenGL drawing with UIKit updates如何使 OpenGL 绘图与 UIKit 更新同步
【发布时间】:2012-10-19 04:20:03
【问题描述】:

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

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

但我们无法将 CAEAGLLayer 的更新与 UIScrollView 中视图的移动同步。 我们有某种时间错误——红色矩形落后于白色矩形。

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

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

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 中绘制一组白色方块并为其设置动画,并为一组红色 UIView 执行相同的操作。在红色和白色方块之间很容易看到滞后。

【问题讨论】:

  • +1 表示优秀(精简)的示例项目。
  • 使用 Xcode 6+,要让示例项目正常工作,必须:#import <OpenGLES/ES1/glext.h> in ViewController.mm

标签: opengl-es uikit core-animation


【解决方案1】:

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

【讨论】:

  • 这很难找到。谢谢。
【解决方案2】:

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

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

【讨论】:

  • 我知道这不是 iOS,但这有关系吗? link 我已经提到不使用 displaylink 可以提高刷新率分配!为此我使用的是 Openframeworks v0.74,0.80 有 displaylink,差别很大。
【解决方案3】:

经过一番挖掘,我想扩展我之前的答案:

您的 OpenGL 渲染是在 scrollViewDidScroll: 方法中立即完成的,而 UIKit 绘制是稍后执行的,在运行循环的正常 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];
}

【讨论】:

  • 感谢您的帮助!我们找到了这个解决方案,并且绘图是同步的。但是性能很差(我说的是我们的应用程序,而不是示例代码)。不幸的是,这不是我们的方式。
  • @NikolaiRuhe 这太好了,感谢您发布它。我们需要将 OpenGL 视图与 CA 视图同步,这非常有效。我无法评论性能 - 在我们的案例中,性能不是问题,因为不需要经常同步抽签。
  • 也有性能问题。通过刷新除 scrollViewDidScroll 之外的所有内容来修复它,所以它是 viewForZoomingInScrollView、gestureRecognizer、gestureRecognizerShouldBegin、gestureRecognizer
【解决方案4】:

在没有正确答案的情况下,我想分享一下我的想法:

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

为了使这项工作发挥作用,您的应用进程会序列化层层次结构并更改其属性,并将数据传递到 backboardd,然后由 backboardd 渲染和组合层并使​​结果可见。

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

要解决您的问题,有必要找到一种将 OpenGL 渲染与 Core Animation 层序列化同步并传输到 backboardd 的方法。很抱歉无法提出解决方案,但我希望这些想法和猜测能帮助您理解问题的原因。

【讨论】:

  • "...将数据传递给 backboardd,然后由 backboardd 渲染和组合图层并使结果可见。" - 棒极了!您是怎么知道的?我在哪里可以了解更多相关信息?
  • @BartoszCiechanowski 恐怕我没有参考我在两个答案中所做的陈述。大多数信息来自调试或分析应用程序时的观察结果,或者只是纯粹的猜测。对 Core Animation 进行一些逆向工程也有助于理解层树的私有序列化及其变化。
  • 在 WWDC 上,Apple 的消息称 Core Animation 渲染在 iOS 6 中的 backboardd 中进行。在 iOS 5 和更早的版本中,在 springboardd 中进行渲染。
【解决方案5】:

我正在尝试解决完全相同的问题。我尝试了上述所有方法,但没有任何方法可以接受。

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

另一种方法是将 GL 结果传输到 CA。我通过读取帧缓冲区像素来分派到太慢的 CALayer 来尝试这个。应该传输大约超过 200MB/秒的数据。现在我正在尝试优化它。因为这是我可以尝试的最后一种方法。

更新

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

更新2

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

【讨论】:

  • Eonil,如果您知道任何允许存档结果的 private API,请告诉我。
  • @beefon 不幸的是,我也不知道。这只是我的猜测。很抱歉没有将其澄清为猜测。我为此向 Apple 请求了技术支持,我会在收到他们的回复后更新此答案。
  • 我也申请了Apple DTS,他们告诉我现在没有办法解决我们的问题。他们建议填写一个请求 API 的错误报告。
  • @beefon 哇。谢谢你让我知道。我也会提交错误。
【解决方案6】:

为了同步 UIKit 和 OpenGL 的绘制,你应该尝试渲染 Core Animation 期望你绘制的地方,例如:

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

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

【讨论】:

  • CAEGLLayer 不支持调用 setNeedsDisplay。
  • 目前(iOS 6.0)支持调用setNeedsdisplay并正确发送-displayLayer:消息,但是GL渲染仍然比CA渲染早。
猜你喜欢
  • 2018-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多