金属片段着色器 A-Buffer 产生闪烁的毛刺
Metal fragment shader A-Buffer produces shimmering glitch
我正在为 Mac 在 Metal 中实现一个 A-Buffer,它几乎可以正常工作——除了我在三角形重叠的地方看到闪烁的故障。似乎涉及的缓冲区可能没有在正确的时间更新。但我不知道是什么原因造成的。这是一张图片 -- 'corrupted' 区域每一帧都在变化,但始终是两种颜色重叠的地方。
我不会解释整个 A-Buffer 操作,但它涉及将三个缓冲区绑定到着色器:一个非常大(172MB,尽管本示例只写入了一小部分)。还有一个 "texture" 整数和一个整数原子计数器。
渲染分两步完成——第一步为每个可见的渲染像素位置创建一个像素片段链表:
// the uint return goes into the start index buffer, our 'image'. The FragLinkBuffer stores the data
fragment uint stroke_abuffer_fragment(VertexIn interpolated [[stage_in]],
const device uint& color [[ buffer(0) ]],
device FragLink* LinkBuffer [[ buffer(1) ]],
device atomic_uint &counter[[buffer(2)]],
texture2d<uint> StartTexture [[ texture(0) ]]) {
constexpr sampler Sampler(coord::pixel,filter::nearest);
// get old start position for this pixel from from start buffer
uint value = atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed);
// store pointer to this position in the start buffer
int oldStart = StartTexture.sample(Sampler, interpolated.position.xy).x;
// store fragment information in link buffer
FragLink F;
F.color = color;
F.depth = interpolated.position.z;
F.next = oldStart;
LinkBuffer[value] = F;
// return pointer to new start for this fragment, which will be stored back to the StartTexture
return value;
}
第二遍在每个像素处对片段进行排序和混合。
#define MAX_PIXELS 16
fragment float4 stroke_abuffer_fragment_composite(CompositeVertexOut interpolated [[stage_in]],
device FragLink* LinkBuffer [[ buffer(0) ]],
texture2d<uint> StartTexture [[ texture(0) ]]) {
pixel SortedPixels[MAX_PIXELS];
int numPixels = 0;
constexpr sampler Sampler(coord::pixel,filter::nearest);
FragLink F;
pixel P;
uint index = StartTexture.sample(Sampler, interpolated.position.xy).x;
if (index == 0)
discard_fragment();
float4 finalColor = float4(0.0);
// grab all the linked fragments for this pixel
while (index != 0) {
F = LinkBuffer[index];
P.color = F.color;
P.depth = F.depth;
SortedPixels[numPixels++] = P;
index = (numPixels >= MAX_PIXELS) ? 0 : F.next;
}
// now sort them by depth
for (int j = 1; j < numPixels; ++j) {
pixel key = SortedPixels[j];
int i = j - 1;
while (i >= 0 && SortedPixels[i].depth <= key.depth)
{
SortedPixels[i+1] = SortedPixels[i];
--i;
}
SortedPixels[i+1] = key;
}
// blend them in order
for (int k = 0; k < numPixels; k++) {
uint color = SortedPixels[k].color;
float red = ((color>>24)&255)/255.0;
float green = ((color>>16)&255)/255.0;
float blue = ((color>>8)&255)/255.0;
float alpha = ((color)&255)/255.0;
//red = 1.0; green = 0.0; blue = 0.0; alpha = 0.25;
finalColor.xyz = mix(finalColor.xyz, float3(red,green,blue), alpha);
finalColor.w = alpha;
}
return finalColor;
}
我只是想知道这种行为的原因可能是什么。如果我在每一帧检查缓冲区的值,通过将它们的内容 blit 回 CPU 内存和打印值,它们在每一帧都在改变,而它们应该是相同的。
无论我是否在每帧调用 commandBuffer.commit() 之后调用 commandBuffer.waitUntilCompleted(),结果都是一样的。通过调用 waitUntilCompleted,我是否应该消除与一帧使用缓冲区相关的任何问题,而下一帧也在尝试访问它? (因为我想也许我需要将 172MB 的缓冲区三重缓冲,这太可怕了。)
我正在做整个渲染——重置计数器的初始 blit、第一个渲染通道,然后是第二个渲染通道,全部作为一个 commandBuffer 调用。那会有问题吗?换句话说,我是否需要实际提交第一个渲染通道,等待它完成,然后启动第二个渲染通道? (编辑:我试过了,它没有改变任何东西)
我正在移植的原始技术 (https://www.slideshare.net/hgruen/oit-and-indirect-illumination-using-dx11-linked-lists) 在第二阶段不使用 OpenGL 混合——它们将背景绑定为纹理缓冲区并将其与像素片段一起手动混合,然后 return 完整的结果。我只是决定跳过这个并使用正常 'over' 混合将我最终组合的片段颜色与背景混合。但我不明白为什么这会导致我遇到的问题。我会按照他们的方式尝试以防万一...
我非常感谢任何关于导致这种情况的想法!
谢谢
。
.
.
更新:根据评论中的对话,我更新了着色器以使用原子缓冲区而不是纹理,但现在得到 "Execution of the command buffer was aborted due to an error during execution. Internal Error (IOAF code 1)":
fragment void stroke_abuffer_fragment(VertexIn interpolated [[stage_in]],
const device uint& color [[ buffer(0) ]],
constant Uniforms& uniforms [[ buffer(1) ]],
device FragLink* LinkBuffer [[ buffer(3) ]],
device atomic_uint &counter[[buffer(2)]],
device atomic_uint *StartBuffer[[buffer(4)]]
) {
uint pos = int(interpolated.position.x)+int(interpolated.position.y)*uniforms.displaySize[0];
// get counter value -- the index to next spot in link buffer
uint value = atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed);
value += 1;
// store fragment information in link buffer
FragLink F;
F.color = color;
F.depth = interpolated.position.z;
F.next = atomic_exchange_explicit(&StartBuffer[pos], value, memory_order_relaxed);
LinkBuffer[value] = F;
}
对于 stroke_abuffer_fragment
通道,您是否对渲染目标和 StartTexture
参数使用相同的纹理?我不认为那是犹太洁食。我希望验证层会抱怨这一点,但也许不会。
可能 StartTexture
应该使用 access::read_write
并且函数应该将结果写入它和 return void
。在这种情况下,渲染命令编码器应该没有渲染目标。
您还需要使用 raster_order_group(0)
限定符对其进行声明,以确保一次只为该像素调用一次片段函数 运行。
写入后可能需要调用StartTexture.fence()
。我不确定这一点,因为下一次读取同一个纹素将在片段函数的后续调用中进行(感谢 raster_order_group()
)。换句话说,raster_order_group()
本身似乎暗示着栅栏。
您还需要在该通道的绘制调用之后在命令编码器上调用 textureBarrier
。这对于确保下一遍看到第一遍写入的结果是必要的。不过,除此之外,在一个命令缓冲区中完成所有这些应该没问题。
更新:
如果您不能使用 raster_order_group()
因为您的目标是 High Sierra 之前的 OS 版本,还有一个替代方案。事实上,即使可以,它也可能更优越,因为它不需要 raster_order_group()
.
隐含的同步
基本思想是使用原子交换来操作 linked 列表。
因此,必须将 StartTexture
更改为缓冲区而不是纹理(正如您在第一条评论中提到的尝试)。是的,您需要将宽度作为 "uniform" 传递并按照您指示的方式计算元素索引 (x + y * width)。您不会尝试继续使用 read()
。缓冲区没有那样的成员函数。它们只是引用,或者在本例中是指针。您只需像 StartTexture[index]
.
一样对其进行索引
不过,您需要将元素类型设为 atomic_uint
而不是 uint
。您将使用原子交换而不是正常的读取或写入 StartTexture
将新节点集成到 link 列表中:
F.next = atomic_exchange_explicit(&StartTexture[index], value, memory_order_relaxed);
这保持了 linked 列表的完整性,即使 stroke_abuffer_fragment()
的两次调用同时针对给定位置 运行ning。
另一件事:您要将 counter
缓冲区初始化为什么? StartBuffer
清除了什么?似乎您正在使用 0 值作为列表结尾的标记,所以我猜您将两者都重置为全零。这是有道理的,但请记住 atomic_fetch_add_explicit()
return 是计数器的值,因为它是 在 递增之前。所以第一次调用stroke_abuffer_fragment()
会得到0,如果你想要递增后的值,当然要加1。如果你不想浪费LinkBuffer
中的一个元素,你可以在对其进行索引时减去 1。或者您可以选择不同的哨兵值并适当地清除事物。不管怎样,您需要修复不匹配问题。
哦对了,stroke_abuffer_fragment()
的color
参数大概应该声明在constant
地址space,而不是device
地址space.
我正在为 Mac 在 Metal 中实现一个 A-Buffer,它几乎可以正常工作——除了我在三角形重叠的地方看到闪烁的故障。似乎涉及的缓冲区可能没有在正确的时间更新。但我不知道是什么原因造成的。这是一张图片 -- 'corrupted' 区域每一帧都在变化,但始终是两种颜色重叠的地方。
我不会解释整个 A-Buffer 操作,但它涉及将三个缓冲区绑定到着色器:一个非常大(172MB,尽管本示例只写入了一小部分)。还有一个 "texture" 整数和一个整数原子计数器。
渲染分两步完成——第一步为每个可见的渲染像素位置创建一个像素片段链表:
// the uint return goes into the start index buffer, our 'image'. The FragLinkBuffer stores the data
fragment uint stroke_abuffer_fragment(VertexIn interpolated [[stage_in]],
const device uint& color [[ buffer(0) ]],
device FragLink* LinkBuffer [[ buffer(1) ]],
device atomic_uint &counter[[buffer(2)]],
texture2d<uint> StartTexture [[ texture(0) ]]) {
constexpr sampler Sampler(coord::pixel,filter::nearest);
// get old start position for this pixel from from start buffer
uint value = atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed);
// store pointer to this position in the start buffer
int oldStart = StartTexture.sample(Sampler, interpolated.position.xy).x;
// store fragment information in link buffer
FragLink F;
F.color = color;
F.depth = interpolated.position.z;
F.next = oldStart;
LinkBuffer[value] = F;
// return pointer to new start for this fragment, which will be stored back to the StartTexture
return value;
}
第二遍在每个像素处对片段进行排序和混合。
#define MAX_PIXELS 16
fragment float4 stroke_abuffer_fragment_composite(CompositeVertexOut interpolated [[stage_in]],
device FragLink* LinkBuffer [[ buffer(0) ]],
texture2d<uint> StartTexture [[ texture(0) ]]) {
pixel SortedPixels[MAX_PIXELS];
int numPixels = 0;
constexpr sampler Sampler(coord::pixel,filter::nearest);
FragLink F;
pixel P;
uint index = StartTexture.sample(Sampler, interpolated.position.xy).x;
if (index == 0)
discard_fragment();
float4 finalColor = float4(0.0);
// grab all the linked fragments for this pixel
while (index != 0) {
F = LinkBuffer[index];
P.color = F.color;
P.depth = F.depth;
SortedPixels[numPixels++] = P;
index = (numPixels >= MAX_PIXELS) ? 0 : F.next;
}
// now sort them by depth
for (int j = 1; j < numPixels; ++j) {
pixel key = SortedPixels[j];
int i = j - 1;
while (i >= 0 && SortedPixels[i].depth <= key.depth)
{
SortedPixels[i+1] = SortedPixels[i];
--i;
}
SortedPixels[i+1] = key;
}
// blend them in order
for (int k = 0; k < numPixels; k++) {
uint color = SortedPixels[k].color;
float red = ((color>>24)&255)/255.0;
float green = ((color>>16)&255)/255.0;
float blue = ((color>>8)&255)/255.0;
float alpha = ((color)&255)/255.0;
//red = 1.0; green = 0.0; blue = 0.0; alpha = 0.25;
finalColor.xyz = mix(finalColor.xyz, float3(red,green,blue), alpha);
finalColor.w = alpha;
}
return finalColor;
}
我只是想知道这种行为的原因可能是什么。如果我在每一帧检查缓冲区的值,通过将它们的内容 blit 回 CPU 内存和打印值,它们在每一帧都在改变,而它们应该是相同的。
无论我是否在每帧调用 commandBuffer.commit() 之后调用 commandBuffer.waitUntilCompleted(),结果都是一样的。通过调用 waitUntilCompleted,我是否应该消除与一帧使用缓冲区相关的任何问题,而下一帧也在尝试访问它? (因为我想也许我需要将 172MB 的缓冲区三重缓冲,这太可怕了。)
我正在做整个渲染——重置计数器的初始 blit、第一个渲染通道,然后是第二个渲染通道,全部作为一个 commandBuffer 调用。那会有问题吗?换句话说,我是否需要实际提交第一个渲染通道,等待它完成,然后启动第二个渲染通道? (编辑:我试过了,它没有改变任何东西)
我正在移植的原始技术 (https://www.slideshare.net/hgruen/oit-and-indirect-illumination-using-dx11-linked-lists) 在第二阶段不使用 OpenGL 混合——它们将背景绑定为纹理缓冲区并将其与像素片段一起手动混合,然后 return 完整的结果。我只是决定跳过这个并使用正常 'over' 混合将我最终组合的片段颜色与背景混合。但我不明白为什么这会导致我遇到的问题。我会按照他们的方式尝试以防万一...
我非常感谢任何关于导致这种情况的想法! 谢谢
。 . .
更新:根据评论中的对话,我更新了着色器以使用原子缓冲区而不是纹理,但现在得到 "Execution of the command buffer was aborted due to an error during execution. Internal Error (IOAF code 1)":
fragment void stroke_abuffer_fragment(VertexIn interpolated [[stage_in]],
const device uint& color [[ buffer(0) ]],
constant Uniforms& uniforms [[ buffer(1) ]],
device FragLink* LinkBuffer [[ buffer(3) ]],
device atomic_uint &counter[[buffer(2)]],
device atomic_uint *StartBuffer[[buffer(4)]]
) {
uint pos = int(interpolated.position.x)+int(interpolated.position.y)*uniforms.displaySize[0];
// get counter value -- the index to next spot in link buffer
uint value = atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed);
value += 1;
// store fragment information in link buffer
FragLink F;
F.color = color;
F.depth = interpolated.position.z;
F.next = atomic_exchange_explicit(&StartBuffer[pos], value, memory_order_relaxed);
LinkBuffer[value] = F;
}
对于 stroke_abuffer_fragment
通道,您是否对渲染目标和 StartTexture
参数使用相同的纹理?我不认为那是犹太洁食。我希望验证层会抱怨这一点,但也许不会。
可能 StartTexture
应该使用 access::read_write
并且函数应该将结果写入它和 return void
。在这种情况下,渲染命令编码器应该没有渲染目标。
您还需要使用 raster_order_group(0)
限定符对其进行声明,以确保一次只为该像素调用一次片段函数 运行。
写入后可能需要调用StartTexture.fence()
。我不确定这一点,因为下一次读取同一个纹素将在片段函数的后续调用中进行(感谢 raster_order_group()
)。换句话说,raster_order_group()
本身似乎暗示着栅栏。
您还需要在该通道的绘制调用之后在命令编码器上调用 textureBarrier
。这对于确保下一遍看到第一遍写入的结果是必要的。不过,除此之外,在一个命令缓冲区中完成所有这些应该没问题。
更新:
如果您不能使用 raster_order_group()
因为您的目标是 High Sierra 之前的 OS 版本,还有一个替代方案。事实上,即使可以,它也可能更优越,因为它不需要 raster_order_group()
.
基本思想是使用原子交换来操作 linked 列表。
因此,必须将 StartTexture
更改为缓冲区而不是纹理(正如您在第一条评论中提到的尝试)。是的,您需要将宽度作为 "uniform" 传递并按照您指示的方式计算元素索引 (x + y * width)。您不会尝试继续使用 read()
。缓冲区没有那样的成员函数。它们只是引用,或者在本例中是指针。您只需像 StartTexture[index]
.
不过,您需要将元素类型设为 atomic_uint
而不是 uint
。您将使用原子交换而不是正常的读取或写入 StartTexture
将新节点集成到 link 列表中:
F.next = atomic_exchange_explicit(&StartTexture[index], value, memory_order_relaxed);
这保持了 linked 列表的完整性,即使 stroke_abuffer_fragment()
的两次调用同时针对给定位置 运行ning。
另一件事:您要将 counter
缓冲区初始化为什么? StartBuffer
清除了什么?似乎您正在使用 0 值作为列表结尾的标记,所以我猜您将两者都重置为全零。这是有道理的,但请记住 atomic_fetch_add_explicit()
return 是计数器的值,因为它是 在 递增之前。所以第一次调用stroke_abuffer_fragment()
会得到0,如果你想要递增后的值,当然要加1。如果你不想浪费LinkBuffer
中的一个元素,你可以在对其进行索引时减去 1。或者您可以选择不同的哨兵值并适当地清除事物。不管怎样,您需要修复不匹配问题。
哦对了,stroke_abuffer_fragment()
的color
参数大概应该声明在constant
地址space,而不是device
地址space.