Rendering Terrain Dynamically with Argument Buffers : 理解为什么粒子缓冲区没有被 GPU 飞行覆盖

Rendering Terrain Dynamically with Argument Buffers : Understanding why the particle buffer is not overwritten by the GPU inflight

我正在浏览与 2017 年 WWDC 视频“Introducing Metal 2”相关的 Apple 演示项目,开发人员在其中演示了参数缓冲区的使用。该项目链接在 Apple 开发者网站上标题为“使用参数缓冲区动态渲染地形”的页面上 here。在这里,它们通过 CPU 同步资源写入以防止与 dispatch_semaphore_t 的竞争条件,当命令缓冲区在 GPU 上完成执行时向其发出信号并在 CPU 正在写入数据时等待它比 GPU 提前几帧。这与之前 2014 年 WWDC“Working With Metal: Fundamentals”中展示的内容一致。

我注意到 APPLParticleRenderer 似乎正在发送要由 GPU 在计算通道中写入的数据,然后它完成从前一个渲染通道的片段着色器的同一缓冲区读取。 buffer的资源存储方式为MTLResourceStorageModePrivate。我的问题:Metal 是否会自动同步对只能由 GPU 访问的私有 id<MTLBuffer>s 的访问?从 new id<MTLCommandEncoder> 调用的渲染、计算和 blit 通道是否只有在其他通道写入和读取缓冲区后才能访问缓冲区(独占访问)?我已经看到在图块着色器中有保证的障碍,在后续片段着色器访问内存之前,内核专门访问图块内存。

最后,在 2016 年 WWDC“Metal 的新功能,第 2 部分”中,第一位主持人 Charles Brissart 在 16:44 提到必须放置从同一缓冲区读取和写入的片段和顶点函数分为两个渲染命令编码器,但对于计算内核,一个计算命令编码器就足够了。这与在粒子渲染器中看到的一致。

请参阅我对原始问题的评论以获取此答案的简短版本。

事实证明,对于 MTLResource 类型,Metal 跟踪默认调度到 GPU 的命令之间的依赖关系。根据 Metal 文档,MTLResourcehazardTrackingMode 属性 默认为 MTLHazardTrackingModeTracked(Swift 中的 MTLHazardTrackingMode.tracked)。这意味着 Metal 跟踪修改资源的命令之间的依赖关系,就像粒子内核的情况一样,并延迟执行,直到访问资源的先前命令完成。 因此,由于_particleDataPool缓冲区的存储模式为MTLResourceStorageModePrivate(Swift中的storageModePrivate),因此只能由GPU写入;因此,不需要 CPU/GPU 与此缓冲区的信号量同步,因此资源不需要多缓冲区系统。 只有当 GPU 仍在读取资源时 CPU 可以写入资源时,我们才需要多个缓冲区,这样 CPU 就不会空闲。

请注意,MTLHeap 的默认危险跟踪模式是 MTLHazardTrackingModeUntracked(Swift 中的 MTLHazardTrackingMode.untracked),在这种情况下 you负责GPU同步资源写入

编辑

在阅读了 Metal 中的资源同步之后,我想进一步说明一些额外的要点,以进一步阐明正在发生的事情。请注意,剩余部分在 Swift 中。要详细了解更多信息,我建议阅读 Metal 文档中的“同步”部分 here

MTLFence

首先,MTLFence用于在单个命令缓冲区的执行中同步对未跟踪资源的访问。栅栏可让您明确控制 GPU 访问资源的时间,并且在您使用未跟踪的资源时是必需的。否则,Metal 将为您处理此同步

重要的是要注意,我在答案中提到的自动管理仅发生在编码通道之间的单个命令缓冲区中。但这并不意味着我们需要在 same 命令队列中调度的命令缓冲区之间进行同步,因为不会立即安排执行命令缓冲区。事实上,根据MTLCommandBuffer协议的addScheduledHandler(_:)方法的文档发现here

The device object schedules the command buffer after it identifies any dependencies with work tasks submitted by other command buffers or other APIs in the system.

此时可以安全地访问这些相同的缓冲区。请注意,在 渲染编码通道中,重要的是要提到,如果顶点着色器写入缓冲区,则同一通道中的片段着色器会从中读取,这是未定义的。我在最初的问题中提到了这一点,解决方案是使用两个渲染通道编码器。我还没有确定为什么这对于计算编码器来说不是必需的,但我想这与与顶点和片段着色器相比内核的执行方式有关

MTLEvent

然而,在某些情况下,由相同 MTLDevice创建的不同队列中的命令缓冲区需要访问相同的资源或以某种方式相互依赖。在这种情况下,同步是必要的,因为单独的队列在不知道对方的情况下安排自己的命令缓冲区,这意味着两个命令缓冲区有可能同时执行。

要解决此问题,您可以使用设备使用 makeEvent() 创建的 MTLEvent 实例,并在每个缓冲区的特定点对事件信号进行编码。

MTLSharedEvent

如果您有多个处理器(不同的 CPU 内核、CPU 和 GPU,或多 GPU),则需要资源同步。在这里,您创建一个 MTLSharedEvent 代替 MTLEvent,可用于跨设备和进程同步。它与 MTLEvent 的 API 本质上相同,但涉及 不同 设备上的命令队列。