Metal / UIKit与.presentsWithTransaction同步

Tri*_*cky 6 uiscrollview uikit ios metal metalkit

苹果文档CAMetalLayer状态属性presentsWithTransaction是:

一个布尔值,该值确定图层是否使用Core Animation事务显示其内容

当我使用UIKit驱动一些金属动画(类似于Apple在本WWDC 2012会话中为OpenGL建议的方法)时,我假设这是启用它的正确时机。我有一个Metal“背景”视图,上面覆盖了一些UIKit组件(也可以进行动画处理),因此这听起来很像适用的用例:

默认情况下,[ .presentsWithTransaction]为false:CAMetalLayer尽可能快地将渲染过程的输出显示到显示器上,并且与任何Core Animation事务异步显示。但是,如果您的游戏或应用结合了Metal和Core Animation内容,则不能保证Metal内容与Core Animation内容位于同一帧中。例如,如果您的应用在您的顶部绘制UIKit内容(例如带有目标位置和时间的标签),CAMetalLayer并且两个域需要同步,则可能会出现问题。

当然,在未启用该设置的情况下,滚动会显得生涩。随着presentsWithTransaction启用我有一些有限的成功,但既不是两条路线我已经启用该设置尝试是完美的。

我已经试过第一种方法如下中的指令的文档presentsWithTransaction。因此,我内部MTKViewDelegate有以下方法:

func draw(in view: MTKView) {
    guard
        let commandBuffer = commandQueue.makeCommandBuffer(),
        let drawable = view.currentDrawable
    else { return }

    updateState(device: device, library: library) // update positions, etc.
    render(with: commandBuffer, in: view) // drawing code

    commandBuffer.commit()
    commandBuffer.waitUntilScheduled()
    drawable.present()
}
Run Code Online (Sandbox Code Playgroud)

这在大多数情况下都可以正常工作,但是完全可以避免这种情况。它具有在某些点失同步的趋势,从而导致典型的颤抖通过UIScrollView例如a驱动滚动动画。整个想法presentsWithTransaction是完全避免这种情况,所以也许我在这里做错了。

第二种方法addScheduledHandler在命令缓冲区上使用:

func draw(in view: MTKView) {
    guard
        let commandBuffer = commandQueue.makeCommandBuffer(),
        let drawable = view.currentDrawable
    else { return }

    updateState(device: device, library: library) // update positions, etc.
    render(with: commandBuffer, in: view) // drawing code

    commandBuffer.addScheduledHandler { _ in
        DispatchQueue.main.async { drawable.present() }
    }
    commandBuffer.commit()
}
Run Code Online (Sandbox Code Playgroud)

这种方法似乎保持同步,但是会导致一些可怕的CPU挂起(2秒或更长时间),尤其是在后台运行应用程序后。

有什么办法可以做到两全其美吗?

编辑:2018年12月9日: 虽然上述第一种方法似乎是优先选择的,但如果主线程上的CPU使用率达到峰值,它仍然会导致频繁的不同步-在大多数情况下这是不可避免的。

当绘制循环变得缺少可绘制对象时,您可以判断何时发生这种情况。这会产生连锁效应,这意味着下一帧的可绘制对象也会延迟。在“金属仪器”面板中,这将导致一系列“线程阻塞,等待下一个可绘制的”警告。

采用上述设计时,由于金属被阻塞,无法等待可拉伸对象-主线程也是如此。现在,触摸事件变得延迟了,从而产生了独特的摇动手势-即使该应用程序理论上仍以60 fps的全速运行,该遮挡似乎也会影响报告触摸事件的节奏-从而产生颤动效果。

随后的CPU高峰可能会使事情重归序,该应用将开始正常运行。

编辑:2018年12月10日: 这是一个演示问题的小示例项目。创建一个新的Xcode项目副本并粘贴两个swift文件的内容(为metal shader文件添加一个新文件)并在设备上运行:

https://gist.github.com/tcldr/ee7640ccd97e5d8810af4c34cf960284

Tri*_*cky 2

编辑 2018 年 12 月 9 日: 我正在从此答案中删除已接受的答案指定,因为此错误似乎仍然存在。虽然以下内容使该错误不太可能发生,但它仍然发生。原始问题中的更多信息。

原始答案:这似乎对我有用:

func draw(in view: MTKView) {

    updateState()  // update positions, etc.

    guard let commandBuffer = commandQueue.makeCommandBuffer() else { return }

    autoreleasepool { render(with: commandBuffer, in: view) } // drawing code

    commandBuffer.commit()
    commandBuffer.waitUntilScheduled()
    view.currentDrawable?.present()
}
Run Code Online (Sandbox Code Playgroud)

.presentsWithTransactiononMTKView也设置为true

我们的想法是等到最后可能的时刻来调用,currentDrawable这是文档中某处建议的,但我现在不记得在哪里了。至少,我相信它应该在.waitUntilScheduled().