有关此答案的简要版本,请参阅我对原始问题的评论。
事实证明,Metal 跟踪默认情况下调度到 GPU 的命令之间的依赖关系的MTLResource
类型。根据 Metal 文档, a的hazardTrackingMode
属性MTLResource
默认为MTLHazardTrackingModeTracked
(MTLHazardTrackingMode.tracked
在 Swift 中)。这意味着 Metal 跟踪修改资源的命令之间的依赖关系,就像粒子内核的情况一样,并延迟执行,直到访问资源的先前命令完成。因此,由于_particleDataPool
缓冲区有一个存储模式MTLResourceStorageModePrivate
(storageModePrivate
在 Swift 中),它只能由 GPU 写入;因此,此缓冲区的信号量不需要 CPU/GPU 同步,因此资源不需要多缓冲区系统。只有当 GPU 仍在读取资源时 CPU 可以写入资源时,我们才需要多个缓冲区,这样 CPU 就不会空闲。
请注意,a 的默认危险跟踪模式MTLHeap
是MTLHazardTrackingModeUntracked
(MTLHazardTrackingMode.untracked
在 Swift 中),在这种情况下,您负责同步 GPU 的资源写入
编辑
在阅读了 Metal 中的资源同步之后,我想提出一些额外的观点,我想进一步澄清正在发生的事情。请注意,其余部分在 Swift 中。要了解更多详细信息,我建议在此处阅读 Metal 文档中的“同步”部分。
MTLF围栏
首先,aMTLFence
用于在单个命令缓冲区的执行中同步对未跟踪资源的访问。栅栏可让您明确控制 GPU 何时访问资源,并且在您使用未跟踪的资源时是必需的。否则,Metal 将为您处理此同步
重要的是要注意,我在答案中提到的自动管理仅发生在编码传递之间的单个命令缓冲区内。但这并不意味着我们需要在同一命令队列中调度的命令缓冲区之间进行同步,因为命令缓冲区不会立即被调度执行。事实上,根据这里找到的协议addScheduledHandler(_:)
方法的文档MTLCommandBuffer
设备对象在识别出与系统中其他命令缓冲区或其他 API 提交的工作任务的任何依赖关系后调度命令缓冲区。
此时访问这些相同的缓冲区是安全的。请注意,在单个渲染编码过程中,重要的是要提到,如果顶点着色器将同一通道中的片段着色器写入缓冲区,则这是未定义的。我在原始问题中提到了这一点,解决方案是使用两个渲染通道编码器。我还没有确定为什么这对于计算编码器来说不是必需的,但我想它与内核与顶点和片段着色器相比的执行方式有关
MTL事件
然而,在某些情况下,由同一队列创建的不同队列中的命令缓冲区需要访问相同的资源或以某种方式相互依赖。在这种情况下,同步是必要的,因为单独的队列在不知道另一个的情况下调度它们自己的命令缓冲区,这意味着两个命令缓冲区有可能同时执行。 MTLDevice
要解决此问题,您可以使用MTLEvent
设备创建的实例,makeEvent()
并在每个缓冲区的特定点对事件信号进行编码。
MTLSharedEvent
如果您有多个处理器(不同的 CPU 内核、CPU 和 GPU 或多 GPU)(没有双关语),则需要资源同步。在这里,您创建一个MTLSharedEvent
代替 aMTLEvent
可用于跨设备和进程同步。它本质上与 的 API 相同MTLEvent
,但涉及不同设备上的命令队列。