MTLBuffer 分配 + CPU/GPU 同步

MTLBuffer allocation + CPU/GPU synchronisation

我正在使用金属性能着色器 (MPSImageHistogram) 计算我抓取的 MTLBuffer 中的内容,执行计算,然后通过 MTKView 显示。着色器的 MTLBuffer 输出很小(~4K 字节)。所以我为每个渲染通道分配一个新的 MTLBuffer 对象,每个视频帧每秒至少渲染 30 次。

calculation = MPSImageHistogram(device: device, histogramInfo: &histogramInfo)
let bufferLength = calculation.histogramSize(forSourceFormat: MTLPixelFormat.bgra8Unorm)
let buffer = device.makeBuffer(length: bufferLength, options: .storageModeShared)
let commandBuffer = commandQueue?.makeCommandBuffer()

calculation.encode(to: commandBuffer!, sourceTexture: metalTexture!, histogram: buffer!, histogramOffset: 0)
commandBuffer?.commit()

commandBuffer?.addCompletedHandler({ (cmdBuffer) in
    let dataPtr = buffer!.contents().assumingMemoryBound(to: UInt32.self)
    ...
    ...

}

我的问题 -

  1. 每次使用device.makeBuffer(..)都可以创建一个新缓冲区吗,还是静态分配更好 很少的缓冲区并实现重用这些缓冲区?如果重用更好,我们如何在这些缓冲区上同步 CPU/GPU 数据 write/read?

  2. 又一个不相关的问题,在非主线程上绘制MTKView结果可以吗?或者 MTKView 绘制只能在主线程中进行(即使我读到 Metal is truly multithreaded)?

  1. 如果是少量数据(4K以下)可以使用setBytes(): https://developer.apple.com/documentation/metal/mtlcomputecommandencoder/1443159-setbytes

与每帧分配一个新缓冲区相比,这可能 faster/better。您还可以使用三重缓冲方法,这样连续帧对缓冲区的访问就不会受到干扰。 https://developer.apple.com/library/content/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/TripleBuffering.html

本教程展示了如何为渲染设置三重缓冲:https://www.raywenderlich.com/146418/metal-tutorial-swift-3-part-3-adding-texture

这实际上类似于教程的第三部分,但它是显示三重缓冲设置的部分,在 "Reusing Uniform Buffers" 下。

  1. 分配有点昂贵,所以我推荐一个可重用的缓冲区方案。我的首选方法是保留缓冲区的可变数组(队列),当使用它的命令缓冲区完成时(或者在您的情况下,在您读回 CPU 上的结果后)将缓冲区排队),并在队列为空且需要编码更多工作时分配新缓冲区。在稳定状态下,您会发现该方案分配的缓冲区总数很少会超过 2-3 个,前提是您的帧会及时完成。如果您需要此方案是线程安全的,则可以使用互斥锁(使用 dispatch_semaphore 实现)来保护对队列的访问。

  2. 只要遵循标准的多线程预防措施,您就可以使用另一个线程对绘制到 MTKView 出售的可绘制对象中的渲染工作进行编码。请记住,虽然命令队列是线程安全的(从某种意义上说,您可以同时从同一队列创建和编码多个命令缓冲区),但命令缓冲区本身和编码器不是。我建议你分析单线程的情况,只介绍多线程的复杂性 if/when 绝对必要。