与.presentsWithTransaction进行Metal / UIKit同步

时间:2017-11-23 09:21:44

标签: ios uiscrollview uikit metal metalkit

CAMetalLayer的{​​{3}}表示属性presentsWithTransaction为:

  

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

由于我正在使用UIKit来驱动一些金属动画(类似于Apple在此The Apple docs中建议的OpenGL方法),我假设这是启用它的正确时间。我有一个金属“背景”视图,覆盖了一些UIKit组件(也有动画),所以这听起来非常像适用的用例:

  

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

当然,如果未启用该设置,滚动会显得不稳定。启用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() }

public class PIArrays {

    public boolean[] pairwiseContainsDividableBy(final int[] a, final int[] b, 
    final int divisor) {

    boolean[] result = new boolean [a.length];

    for(int i = 0; i < a.length; i++){
            if(a[i]%divisor == 0 && b[i]%divisor == 0){
                result[i] = true;
            }else{
                result[i] = false;
            }
        }
   return result; }
}

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

有没有办法让两全其美?

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

当绘制循环变得缺乏drawable时,你可以知道这种情况何时发生。这会导致连锁效应,这意味着下一帧的可绘制也会延迟。在“金属工具”面板中,这会导致一系列“线程被阻塞等待下一个可绘制”警告。

通过上面的设计,当Metal被阻塞等待drawable时 - 主线程也是如此。现在触摸事件被延迟,导致平移手势的独特口吃模式 - 即使应用程序仍然在理论上以完整的60fps运行,阻塞似乎会影响报告触摸事件的节奏 - 导致抖动效果。

随后的CPU峰值可以将事情重新排列,应​​用程序将开始正常运行。

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

WWDC 2012 session

1 个答案:

答案 0 :(得分: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()
}
.presentsWithTransaction上的

MTKView也设置为true

这个想法是等到最后一次调用currentDrawable时才出现,这是在文档中建议的某个地方,但是我不记得现在在哪里。我认为至少应该在.waitUntilScheduled()之后调用它。