7

Apple 文档CAMetalLayer声明该属性为presentsWithTransaction

一个布尔值,用于确定图层是否使用核心动画事务呈现其内容

由于我正在使用 UIKit 来驱动一些金属动画(类似于 Apple 在此WWDC 2012 会话中为 OpenGL 建议的方法),我假设这是启用它的正确时间。我有一个金属“背景”视图,上面覆盖着一些 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()
}

这大部分工作正常 - 但完全离开设置也是如此。它有在某些点不同步的趋势,导致通过UIScrollView例如驱动滚动动画的特征性颤抖。的整个想法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()
}

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

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

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

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

在上面的设计中,由于 Metal 在等待可绘制对象时被阻塞 - 主线程也是如此。现在,触摸事件变得延迟,导致平移手势出现明显的断断续续模式——即使应用程序理论上仍以 60 fps 的速度运行,阻塞似乎会影响报告触摸事件的节奏——从而导致抖动效果。

随后的 CPU 峰值可能会使事情恢复正常,应用程序将开始正常运行。

编辑:2018 年 12 月 10 日: 这是一个演示该问题的小示例项目。创建一个新的 Xcode 项目,复制并粘贴两个 swift 文件的内容(为金属着色器文件添加一个新文件)并在设备上运行:

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

4

1 回答 1

3

编辑 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()
}

.presentsWithTransactiononMTKView也设置为true

这个想法是等到最后一刻才打电话currentDrawable,这是文档中某处建议的,但我不记得现在在哪里。至少,我相信它应该在.waitUntilScheduled().

于 2018-07-27T01:15:23.150 回答