为什么在Metal中禁止从片段着色器向缓冲区写入数据?

31
Metal Shading Language Guide所述:

在片段函数中,禁止向缓冲区或纹理写入。

我知道这是事实,但我很好奇为什么会这样。从片段着色器中写入缓冲区非常有用;我知道直接写入缓冲区时硬件端可能更复杂,因为不总是能预知特定线程的内存写入的最终位置,而原始缓冲区写入就不总是知道,但这是Metal计算着色器中公开的功能,那为什么不在片段着色器中也使用呢?

附加说明

我应该澄清一下为什么我认为从片段函数中写入缓冲区很有用。在典型的栅格化管线使用情况下,三角形正在被栅格化和着色(根据片段着色器),并写入到预定义的内存位置中,在每个片段着色器调用之前都已知,并由规范化设备坐标与帧缓冲区的预定义映射确定。这适用于大多数使用情况,因为大多数情况下您只想直接将三角形渲染到缓冲区或屏幕。
还有其他情况,您可能希望在片段着色器内进行延迟写入,其最终位置基于片段属性而不是片段的确切位置;实际上是具有副作用的栅格化。例如,大多数基于GPU的体素化都是通过从一些理想角度使用正交投影渲染场景,然后写入3D纹理来完成的,将片段的XY坐标和其相关深度值映射到3D纹理中的位置。这在此处有所描述。

其他用途包括一些形式的无序透明度 (透明度顺序无关紧要,允许重叠透明对象)。一种解决方法是使用多层帧缓冲区,然后根据它们的深度值在单独的处理中进行排序和混合片段。由于大多数 GPU 上没有硬件支持这样做 (我相信 Intel 的 GPU 有硬件加速),因此您必须维护原子计数器并从每个像素手动纹理/缓冲区写入以协调对分层帧缓冲区的写入。

另一个例子可能是通过光柱化提取 GI 的虚拟点光源(即,当您进行光柱化时,会为相关片段编写出点光源)。在所有这些用法情况下,都需要从片段着色器进行缓冲区写入,因为 ROPs 只存储每个像素的一个结果片段。在没有此功能的情况下获得等效结果的唯一方法是进行某种深度剥离,但对于深度复杂度高的场景来说非常慢。

现在我意识到我所举的例子并不是特别关于缓冲区写入的,而更多地是关于片段着色器动态内存写入的概念,最好还要支持原子性。缓冲区写入似乎只是一个简单的问题,它们的包含将在很大程度上改善情况。

由于我在这里没有得到任何答案,最终我在 苹果开发者论坛上发布了这个问题。我在那里得到了更多的反馈,但仍然没有真正的答案。除非我漏掉了什么,否则几乎每台正式支持 Metal 的 OS X 设备都具有此功能的硬件支持。据我所知,这个功能首次出现在 2009 年左右的 GPU 上。它是当前 DirectX 和 OpenGL (甚至不考虑 DX12 或 Vulkan) 中的常见功能,因此 Metal 将是唯一缺少该功能的“尖端” API。

我知道这个特性可能在PowerVR硬件上不被支持,但是苹果已经成功地通过功能集区分了Metal Shading Language。例如,在iOS上使用的Metal允许在片元着色器中进行“自由”帧缓冲提取,这直接受到高速缓存密集型PowerVR架构的硬件支持。这个特性会直接体现在Metal Shading语言中,因为它允许你使用[[color(m)]]属性限定符来声明iOS着色器中的片元函数输入。可以说,允许将device存储空间限定符用于缓冲区声明,或将access::write用于纹理声明,作为片元着色器的输入,与苹果为优化iOS所做的工作相比,并没有更大的语义变化。因此,在我看来,PowerVR的不支持并不能解释我在OS X上寻找的特性缺失。

2个回答

9

现在支持从片段着色器向缓冲区写入数据,如iOS 10、tvOS 10 和 macOS 10.12的新变化中所述。

函数缓冲区读写的可用性:iOS_GPUFamily3_v2、OSX_GPUFamily1_v2

现在片段函数可以写入缓冲区。可写入的缓冲区必须在设备地址空间中声明,并且不能为const。使用动态索引来写入缓冲区。

另外,原问题中指定的限制条款在Metal Shading Language Specification 2.0中不存在。


0

我认为在OpenGL或DirectX的片段函数中,你都不能随意写入像素或纹素。渲染API和片段或顶点函数是两回事。

片段函数旨在产生一个像素/纹素输出作为结果,每次运行一个,即使每个像素/纹素有多个通道。通常,如果您想要写入缓冲区或纹理,则需要渲染一些东西(四边形、三角形或使用片段函数在表面(缓冲区或纹理)上绘制的其他东西)。结果,每个像素/纹素将使用您的片段函数进行渲染。例如,光线投射或光线跟踪片段函数通常使用此方法。

不允许您编写任意像素/纹素的一个很好的原因是并行化。片段函数通常以非常高的并行化模式同时执行大量不同的像素/纹素,每个GPU都有自己的并行化方式(SMP、矢量...),但所有GPU都具有非常高的并行化。因此,您只能通过返回一个像素或纹素通道输出作为片段函数的返回来进行编写,以避免常见的并行化问题,如竞争。我所知道的每个图形库都适用于此。


有道理。因为片段着色器的许多实例是并行运行的,它们可以写入的任何外部资源都必须进行串行化以避免冲突。输出片段是不同的,因为根据定义,每次片段着色器的运行都会写入渲染目标上的不同、独占像素。 - Nicolas Miari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接