网格着色器性能并不是 AMD GPU 的亮点。MDI 和 Mesh 着色器之间的 FPS 也没有太大差异。但有一个技巧可以在所有具有计算着色器支持的 GPU 上模拟网格着色器功能。而且该技术在几乎所有 AMD GPU 上的运行速度都比 MDI 和网格着色器更快。它还为 Qualcomm GPU 带来了令人难以置信的 (7 倍) 性能提升。
为网格着色器渲染准备的几何体包含顶点和网格数据缓冲区。每个网格体使用uint8_t索引作为顶点,这允许在一些内存锁定中加载更多基元(每 4 个三角形uint32_t[3] )。
这是每个网格的数据布局:
uint32_t Number of primitives
uint32_t Number of vertices
uint32_t Base index (index buffer offset)
uint32_t Base vertex (vertex buffer offset)
网格着色器很简单:加载网格信息,获取顶点和索引,提交顶点和索引。
但是,如果没有网格着色器支持,我们将如何渲染网格物体呢?我们将在简单的计算着色器的帮助下使用单个绘制调用。该着色器会将网格体索引转换为包含网格体索引和顶点索引的打包uint32_t三角形索引。着色器很简单:
layout(std430, binding = 0) readonly buffer meshlets_buffer { uint meshlets_data[]; };
layout(std430, binding = 1) writeonly buffer indexing_buffer { uvec4 indexing_data[]; };
/*
*/
void main() {
uint group_id = base_group + gl_WorkGroupID.x;
uint local_id = gl_LocalInvocationIndex;
[[branch]] if(local_id == 0u) {
uint meshlet_index = (group_id % num_meshlets) * 4u;
num_primitives = meshlets_data[meshlet_index + 0u];
base_index = meshlets_data[meshlet_index + 2u];
}
memoryBarrierShared(); barrier();
uint indices_0 = 0u;
uint indices_1 = 0u;
uint indices_2 = 0u;
[[branch]] if(local_id * 4u < num_primitives) {
uint address = base_index + local_id * 3u;
indices_0 = meshlets_data[address + 0u];
indices_1 = meshlets_data[address + 1u];
indices_2 = meshlets_data[address + 2u];
}
uint group_index = group_id << 8u;
uint index = (GROUP_SIZE * group_id + local_id) * 3u;
indexing_data[index + 0u] = (uvec4(indices_0, indices_0 >> 8u, indices_0 >> 16u, indices_0 >> 24u) & 0xffu) | group_index;
indexing_data[index + 1u] = (uvec4(indices_1, indices_1 >> 8u, indices_1 >> 16u, indices_1 >> 24u) & 0xffu) | group_index;
indexing_data[index + 2u] = (uvec4(indices_2, indices_2 >> 8u, indices_2 >> 16u, indices_2 >> 24u) & 0xffu) | group_index;
}
在该计算过程之后,只需要一个drawElements()来渲染所有meshlet。每 1M 三角形的写入和读取内存量为 11 MB。不可能使用 16 位索引,因为在这种情况下,每次绘制调用只能绘制 1024 个网格物体。TriangleIndex内置着色器变量将使这成为可能,但我们没有它。
顶点着色器可以通过对VertexIndex内置变量使用简单的数学运算来获取网格索引和网格顶点:
uint meshlet = gl_VertexIndex >> 8u;
uint 顶点 = gl_VertexIndex & 0xffu;
// 从SSBO加载顶点数据
向该着色器添加每个三角形的可见性剔除测试是没有问题的。而且它会提高性能,因为背面和不可见三角形不会使用带宽。
让我们检查仿真速度更快的不同配置和平台中的结果:
单拨码 | 仿真 | 网格着色器 | MDI/ICB/回路 | |
---|---|---|---|---|
Radeon 6900 XT 64/126 | 17.2乙 | 9.6乙 | 9.2乙 | 4.1乙 |
Radeon 6900 XT 128/212 | 17.2乙 | 10.1乙 | 9.2乙 | 8.5乙 |
Radeon 6700 XT 64/126 | 14.1乙 | 7.7乙 | 4.6乙 | 4.1乙 |
Radeon 6700 XT 128/212 | 14.1乙 | 7.9乙 | 4.6乙 | 8.3乙 |
Radeon 5600 M 64/126 | 5.0乙 | 2.9乙 | 1.4乙 | |
Radeon 5600 M 128/212 | 5.0乙 | 2.9乙 | 2.8乙 | |
Radeon Vega 56 64/126 (macOS) | 4.1乙 | 2.7乙 | 860米 | |
Radeon Vega 56 128/212 (macOS) | 4.1乙 | 2.9乙 | 1.8乙 | |
肾上腺素 660 64/126 | 583米 | 265米 | 36.6M | |
肾上腺素 660 128/212 | 596米 | 309米 | 74.7M | |
马里-G78 MP20 64/126 | 176米 | 153米 | 83米 | |
马里-G78 MP20 128/212 | 187米 | 123米 | 134米 |
结果以每秒十亿和百万处理三角形为单位。MDI – 多重绘制间接,ICB – 间接命令缓冲区,循环 – 多个drawIndirect()命令的CPU循环。
如果您需要绘制大量小型 DIP,最好将所有内容打包到 AMD 和 Qualcomm 上的单个绘制调用中。即使有额外的索引缓冲区生成开销,单个 DIP 也比网格着色器或 MDI 工作得更好。