金属使用计算着色器模拟几何着色器

the*_*cks 5 macos shader metal

我正在尝试在Metal中实现体素锥描图。该算法的步骤之一是使用几何着色器对几何进行体素化。Metal没有几何着色器,因此我一直在考虑使用计算着色器对其进行仿真。我将顶点缓冲区传递到计算着色器中,执行几何着色器通常会执行的操作,然后将结果写入输出缓冲区。我还将绘制命令添加到间接缓冲区。我将输出缓冲区用作我的顶点着色器的顶点缓冲区。这可以正常工作,但是我需要为顶点存储两倍的内存,为顶点缓冲区存储一个,为输出缓冲区存储一个。有什么方法可以将计算着色器的输出直接传递到顶点着色器,而无需将其存储在中间缓冲区中?我不需要保存计算着色器的输出缓冲区的内容。

这可能吗?谢谢

编辑

本质上,我正在尝试从glsl模拟以下着色器:

#version 450

layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;

layout(location = 0) in vec3 in_position[];
layout(location = 1) in vec3 in_normal[];
layout(location = 2) in vec2 in_uv[];

layout(location = 0) out vec3 out_position;
layout(location = 1) out vec3 out_normal;
layout(location = 2) out vec2 out_uv;

void main()
{
    vec3 p = abs(cross(in_position[1] - in_position[0], in_position[2] - in_position[0]));

    for (uint i = 0; i < 3; ++i)
    {
        out_position = in_position[i];
        out_normal = in_normal[i];
        out_uv = in_uv[i];

        if (p.z > p.x && p.z > p.y)
        {
            gl_Position = vec4(out_position.x, out_position.y, 0, 1);
        }
        else if (p.x > p.y && p.x > p.z)
        {
            gl_Position = vec4(out_position.y, out_position.z, 0, 1);
        }
        else
        {
            gl_Position = vec4(out_position.x, out_position.z, 0, 1);
        }

        EmitVertex();
    }

    EndPrimitive();
}
Run Code Online (Sandbox Code Playgroud)

对于每个三角形,我需要在这些新位置输出一个带有顶点的三角形。三角形顶点来自顶点缓冲区,并使用索引缓冲区绘制。我还计划添加将执行保守光栅化的代码(只是将三角形的大小稍微增加一点),但此处未显示。目前,我在Metal计算着色器中正在使用索引缓冲区获取顶点,在上方的几何着色器中执行相同的代码,然后在另一个缓冲区中输出新的顶点,然后使用该缓冲区进行绘制。

Ken*_*ses 5

这是一种非常推测的可能性,具体取决于您的几何着色器需要做什么。

我认为你可以只用一个顶点着色器而不需要单独的计算着色器来“向后”完成它,但代价是 GPU 上的冗余工作。您将进行绘制,就好像您有一个包含几何着色器输出图元的所有输出顶点的缓冲区一样。不过,你实际上手头上并没有这个。您将构建一个顶点着色器来在飞行中计算它们。

因此,在应用程序代码中,计算输出图元的数量,从而计算为给定数量的输入图元生成的输出顶点的数量。使用那么多顶点绘制输出基元类型。

您不会提供包含输出顶点数据的缓冲区作为此绘制的输入。

您将提供原始索引缓冲区和原始顶点缓冲区作为该绘制的顶点着色器的输入。着色器将根据顶点 ID 计算它所针对的输出图元,以及该图元的哪个顶点(例如,分别针对三角形vid / 3vid % 3)。根据输出图元 ID,它将计算哪个输入图元将在原始几何着色器中生成它。

着色器将从索引缓冲区查找该输入图元的索引,然后从顶点缓冲区查找顶点数据。(例如,这对三角形列表与三角形带之间的区别很敏感。)它将向该数据应用任何预几何着色器顶点着色。然后,它将执行有助于识别输出图元的识别顶点的几何计算部分。一旦计算出输出顶点数据,您就可以应用任何您想要的后几何着色器顶点着色(?)。结果就是它将返回的结果。

如果几何着色器可以为每个输入图元生成可变数量的输出图元,那么至少您有一个最大数量。因此,您可以为输出图元的最大潜在数量绘制顶点的最大潜在数量。顶点着色器可以进行必要的计算,以确定几何着色器实际上是否会生成该图元。[[clip_distance]]如果不是,顶点着色器可以通过将其放置在截锥体之外或使用输出顶点数据的属性来安排将整个基元剪掉。

这避免了将生成的原语存储在缓冲区中。然而,它会导致GPU重复执行一些几何着色器前的顶点着色器和几何着色器计算。当然,它会并行化,但可能仍然比您现在所做的慢。此外,它可能会失败一些围绕获取索引和顶点数据的优化,而这些优化可能通过更普通的顶点着色器实现。


以下是几何着色器的转换示例:

#include <metal_stdlib>
using namespace metal;

struct VertexIn {
    // maybe need packed types here depending on your vertex buffer layout
    // can't use [[attribute(n)]] for these because Metal isn't doing the vertex lookup for us
    float3 position;
    float3 normal;
    float2 uv;
};

struct VertexOut {
    float3 position;
    float3 normal;
    float2 uv;
    float4 new_position [[position]];
};


vertex VertexOut foo(uint vid [[vertex_id]],
                     device const uint *indexes [[buffer(0)]],
                     device const VertexIn *vertexes [[buffer(1)]])
{
    VertexOut out;

    const uint triangle_id = vid / 3;
    const uint vertex_of_triangle = vid % 3;

    // indexes is for a triangle strip even though this shader is invoked for a triangle list.
    const uint index[3] = { indexes[triangle_id], index[triangle_id + 1], index[triangle_id + 2] };
    const VertexIn v[3] = { vertexes[index[0]], vertexes[index[1]], vertexes[index[2]] };

    float3 p = abs(cross(v[1].position - v[0].position, v[2].position - v[0].position));

    out.position = v[vertex_of_triangle].position;
    out.normal = v[vertex_of_triangle].normal;
    out.uv = v[vertex_of_triangle].uv;

    if (p.z > p.x && p.z > p.y)
    {
        out.new_position = float4(out.position.x, out.position.y, 0, 1);
    }
    else if (p.x > p.y && p.x > p.z)
    {
        out.new_position = float4(out.position.y, out.position.z, 0, 1);
    }
    else
    {
        out.new_position = float4(out.position.x, out.position.z, 0, 1);
    }

    return out;
}
Run Code Online (Sandbox Code Playgroud)