金属片段着色器A-Buffer产生闪烁的毛刺

时间:2017-11-11 19:42:11

标签: fragment-shader metal visual-glitch

我正在Mac for Metal中实现A-Buffer,它几乎正常工作 - 除了我看到三角形重叠的地方出现闪烁的毛刺。似乎所涉及的缓冲区可能无法在正确的时间更新。但我不知道是什么原因造成的。这是一张图片 - “已损坏的图片”#39;区域每帧都会改变,但总是在两种颜色重叠的地方。

fragment shader interference

我不会解释整个A-Buffer操作,但它涉及将三个缓冲区绑定到着色器:一个非常大(172MB,尽管本例中只写了一小部分)。还有一个"纹理"整数和单个整数原子计数器。

渲染完成两次 - 第一遍为每个可见的渲染像素位置创建一个像素片段的链接列表:

// 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;

}

我只是想知道这种行为可能是什么原因。如果我检查每帧的缓冲区值,通过将它们的内容重新打印回CPU内存和打印值,它们会在每一帧都改变时,它们应该是相同的。

无论我是否在每次调用commandBuffer.commit()之后调用commandBuffer.waitUntilCompleted(),结果都是一样的。通过调用waitUntilCompleted,我不应该消除与一帧使用缓冲区有关的问题,而下一帧也试图访问它吗? (因为我想也许我需要将172MB缓冲区的三倍缓冲区变得非常糟糕。)

我正在进行整个渲染 - 重置计数器的初始blit,第一个渲染过程,然后是第二个渲染过程,所有这些都作为一个commandBuffer调用。那会是个问题吗?换句话说,我是否需要实际提交第一个渲染过程,等待它完成,然后启动第二个渲染过程? (编辑:我尝试了这个并没有改变任何东西)

我移植的原始技术(https://www.slideshare.net/hgruen/oit-and-indirect-illumination-using-dx11-linked-lists)在第二阶段不使用OpenGL混合 - 它们将背景绑定为纹理缓冲区并将其与像素片段手动混合,然后返回完整结果。我刚刚决定跳过这个并使用普通的&#39; over&#39;混合。但我不明白为什么这会导致我遇到的问题。我会以他们的方式尝试以防万一...

我非常感谢有关导致此问题的任何想法! 感谢

。 。

更新:在评论中的对话之后我已经更新着色器以使用原子缓冲区而不是纹理,但现在正在获得&#34;命令缓冲区的执行因执行期间的错误而中止。内部错误(IOAF代码1)&#34;:

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;
}

1 个答案:

答案 0 :(得分:0)

对于stroke_abuffer_fragment传递,您是否对渲染目标和StartTexture参数使用相同的纹理?我不认为这是犹太人。我希望验证层会抱怨,但也许不会。

可能StartTexture应该使用access::read_write,函数应该将结果写入它并返回void。在这种情况下,渲染命令编码器应该没有渲染目标。

您还需要使用raster_order_group(0)限定符声明它,以确保一次只运行该像素的片段函数的一次调用。

写完后,您可能需要致电StartTexture.fence()。我不确定这一点,因为下一次读取相同的纹素将在片段函数的后续调用中(感谢raster_order_group())。换句话说,raster_order_group()似乎意味着围栏本身。

在该传递的绘制调用之后,您还需要在命令编码器上调用textureBarrier。这是确保下一遍看到第一遍写的结果所必需的。除此之外,在单个命令缓冲区中完成所有这些操作应该没问题。

更新

如果由于您在High Sierra之前定位操作系统版本而无法使用raster_order_group(),则可以选择其他方式。实际上,即使可以,它也可能更优越,因为它不需要raster_order_group()暗示的同步。

基本思想是使用原子交换来操纵链表。

因此,必须将StartTexture更改为缓冲区而不是纹理(正如您在第一条评论中提到的那样)。是的,您需要将宽度作为“统一”传递并计算元素索引(x + y * width)。您不会尝试继续使用read()。缓冲区没有类似的成员函数。它们只是引用,或者在本例中是指针。您只需将其编入索引,如StartTexture[index]

但事实是,您要使元素类型atomic_uint而不是uint。您将使用原子交换而不是正常读取或编写StartTexture来将新节点集成到链接列表中:

F.next = atomic_exchange_explicit(&StartTexture[index], value, memory_order_relaxed);

即使两个stroke_abuffer_fragment()的调用同时针对给定位置运行,也可以保持链表的完整性。

另一件事:你正在将counter缓冲区初始化为什么?什么是StartBuffer清除?看起来你正在使用0值作为列表末尾的标记,所以我猜你将两个都重置为全零。这是有道理的,但请记住atomic_fetch_add_explicit()返回计数器的值,因为它是递增之前。所以stroke_abuffer_fragment()的第一次调用将得到0.如果你希望递增后的值,你当然会加1.如果你不想在LinkBuffer中浪费一个元素,你可以在索引时将1减去。或者您可以选择不同的标记值并适当地清除事物。无论如何,你需要修复不匹配。

哦,顺便说一句,color的{​​{1}}参数应该在stroke_abuffer_fragment()地址空间而不是constant地址空间中声明。