为什么 Xcode 8.1 + 金属。使我用来操作 3D 模型的计时器崩溃?

Why does Xcode 8.1 + Metal. crash the Timer I use to manipulate my 3D model?

我有一个轨迹球操纵器,它使用 Timer 来创建当前旋转角度的平滑减速衰减。它在我的 GL 应用程序中一直运行良好。在 Metal 中,它使应用程序崩溃。

下面是我如何实例化定时器:

rotationTimer = Timer.scheduledTimer(
timeInterval: TimeInterval(kRotationRate), 
target: self,
selector: Selector(("rotationTimerHandler")),
userInfo: [ "radiansBegin":radians, "radians":radians, "radiansEnd":0, "counter":0 ],
repeats: true)

函数 rotationTimerHandler 减小角度 - radians - 然后从中构建一个变换矩阵,该矩阵包含在我的渲染绘制循环的更新函数中。

我怀疑我需要与 Metal 绘制循环的时间同步,但我看不出有什么办法。如何让 TimerMetal 一起玩得开心?

更新 0

根据 Warren 的问题风暴 :-) 这里有更多的背景信息。

我的应用程序的视图是具有 2 个属性的 MTKView

符合MTKViewDelegate协议并实现的渲染器:

func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize)
func draw(in view: MTKView)

绘图由 MTKView 的内部显示 link 计时器驱动。一切都在主线程上运行。没有异步调用。

还有一个轨迹球。将屏幕手势映射到四元数到 3D 旋转矩阵的对象。 arcball 是视图的平移手势处理程序:

addGestureRecognizer(UIPanGestureRecognizer.init(
    target: arcBall,
    action: #selector(EIArcball.arcBallPanHandler)))

}

当 arcball 的平移手势处理程序进入结束状态时,我实例化一个计时器 N 秒以继续生成 quaternions/matrices 基于平移结束时旋转的 angle/axis 以获得令人愉悦的滑动效果。

好的,渲染器(符合MTKViewDelegate)。绘制循环:

public func draw(in view: MTKView) {

    update(view: view as! EIView, drawableSize: view.bounds.size)

    // final pass
    if let passDescriptor = view.currentRenderPassDescriptor, let drawable = view.currentDrawable {

        passDescriptor.colorAttachments[ 0 ].clearColor = MTLClearColorMake(0.5, 0.5, 0.5, 1.0)

        let commandBuffer = commandQueue.makeCommandBuffer()

        let renderCommandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor)

        renderCommandEncoder.setFrontFacing(.counterClockwise)
        renderCommandEncoder.setTriangleFillMode(.fill)
        renderCommandEncoder.setCullMode(.none)

        renderCommandEncoder.setRenderPipelineState(heroModelPipelineState)
        renderCommandEncoder.setVertexBuffer(heroModel.vertexMetalBuffer, offset: 0, at: 0)
        renderCommandEncoder.setVertexBuffer(heroModel.metallicTransform.metalBuffer, offset: 0, at: 1)
        renderCommandEncoder.setFragmentTexture(heroModelTexture, at: 0)
        renderCommandEncoder.drawIndexedPrimitives(
                type: .triangle,
                indexCount: heroModel.vertexIndexMetalBuffer.length / MemoryLayout<UInt16>.size,
                indexType: MTLIndexType.uint16,
                indexBuffer: heroModel.vertexIndexMetalBuffer,
                indexBufferOffset: 0)

        renderCommandEncoder.endEncoding()

        commandBuffer.present(drawable)
        commandBuffer.commit()
    }

}

这里没有什么特别有趣的。注意调用:

update(view: view as! EIView, drawableSize: view.bounds.size)

这里是update

func update(view: EIView, drawableSize:CGSize) {

    heroModel.metallicTransform.update(camera: camera, transformer: {
        return view.arcBall.rotationMatrix * GLKMatrix4MakeScale(150, 150, 1)
    })

}

function heroModel.metallicTransform.update(...) 构建了一个在我的顶点着色器中使用的模型-视图-投影矩阵。这是一个玩具应用程序,它绘制一个在 x-y 方向缩放 150 倍的四边形,通过向 arcball 提供平移手势来操纵(旋转)该四边形。

update 中,我没有尝试安排 arcball 变换矩阵的馈送进行更新。这似乎是代码的味道。

这是崩溃的屏幕转储:

我想得越多,似乎我应该明确地进行绘制调用,因为我没有真正需要不断重绘。 1) 平移时只需调用 renderer.draw()。 2) 平移结束后从我的计时器处理程序调用 render.draw()。

更新 1

这里有更多堆栈跟踪。只是一大堆汇编程序:

更新 2

bt(回溯)的输出:

我想我会尝试 运行 Metal 没有 内部计时器并显式调用 MTKView.draw()。在如此简单的应用程序中不需要基于计时器的方法,并且我基于 NSTimer 的 Arcball 存在问题。

已解决。我决定放弃使用 MTKView 内部计时器,并在 NSTimer 回调中平移 期间在 Arcball 中显式调用 MTKView.draw()。十分简单。工作方式相同。

代码:https://github.com/turner/HelloMetal