人们很容易在所有支持的平台上将计算着色器光栅化与网格着色器和 MDI 进行比较。Unreal 5 Nanite 已经在小三角形渲染方面展示了出色的性能。但是基于图块的架构的数字又如何呢?专用光栅器有多好?
让我们使用旧的测试网格着色器与具有 498990 64×128 Meshlet 的 MDI 进行比较,除了背面之外没有任何剔除。为 Meshlet 准备的数据非常适合基于计算的光栅化。所有顶点和索引都是独立且紧密堆积的。可以为每个 Meshlet 生成一个组,并为每个线程光栅化一个三角形。我们将软件光栅化限制为仅深度模式,因为 64 位原子在移动设备和 Metal 上不可用。它不会对性能产生很大的影响。Metal 也不支持纹理的原子操作,但有一种方法可以从缓冲区数据创建纹理并使用缓冲区原子操作。
我们比较了 Nvidia、AMD、Intel、Qualcomm 和 Apple GPU 上的单个 DIP 32 位索引、Mesh Shader、MultiDrawIndirect 和 Compute Shader 光栅化性能。
结果如下:
单拨码 | 网格着色器 | MDI/ICB/回路 | 计算着色器 | |
---|---|---|---|---|
GeForce 2080 钛 | 12.05 乙 | 12.57乙 | 12.63乙 | 17.26 乙 |
英伟达精视1060M | 3.86乙 | 3.90乙 | 4.55乙 | |
Radeon 6700 XT | 14.73乙 | 4.38乙 | 3.63乙 | 16.74乙 |
Radeon RX 5600M | 4.87乙 | 1.11乙 | 7.57乙 | |
Radeon Vega 56 (macOS) | 2.40乙 | 796.0 米 | 3.17乙 | |
苹果 M1 (macOS) | 1.37乙 | 739.0 米 | 2.30乙 | |
苹果 A14(iOS) | 666.1M | 475.0 米 | 1.02乙 | |
英特尔超高清显卡 | 680.0M | 396.5M | 556.1M | |
肾上腺素 660(安卓) | 565.2M | 31.17米 | 497.3M |
该表显示了每秒处理的三角形数量(数百万和数十亿)。
出色地…
- GPU 固定功能单元和大量着色器类型的时代即将结束。
- 由于基于图块的渲染,MultiDrawIndirect 在移动设备上效果不佳。
- 最佳网格着色器/MDI 比基于计算的光栅化慢。
- 单一着色器类型优于 14 个专用着色器类型。
我们需要的是计算着色器以及有效地从着色器生成线程的能力。其他一切(包括光线追踪)都可以在计算着色器级别轻松实现。十年前,英特尔 Larrabee 的性能不足以满足纯计算模式,但现在可以放弃所有其他着色器。
允许自动将有效负载写入图像的计算着色器扩展将改变一切。如果没有它,我们就只能使用 32 位有效负载数据,并且必须执行冗余三角形相交。
uint imageAtomicPayloadMax(gimage2Datomic_image, gimage2Dpayload_image,
ivec2 P,
uintatomic_data,gvec4payload_data);
以下是计算光栅化着色器的未优化代码,由于我们的Clay 着色器编译器,该代码已在所有平台和 API 上的所有测试中使用,无需任何修改:
layout(local_size_x = GROUP_SIZE) in;
layout(row_major, binding = 0) uniform common_parameters {
mat4 projection;
mat4 modelview;
vec4 camera;
};
layout(std140, binding = 1) uniform compute_parameters {
uint num_meshlets;
uint group_offset;
vec2 surface_size;
float surface_stride;
};
layout(std140, binding = 2) uniform transform_parameters {
vec4 transforms[NUM_INSTANCES * 3u];
};
layout(std430, binding = 3) readonly buffer vertices_buffer { vec4 vertices_data[]; };
layout(std430, binding = 4) readonly buffer meshlets_buffer { uint meshlets_data[]; };
#if CLAY_MTL
layout(std430, binding = 5) buffer surface_buffer { uint out_surface[]; };
#else
layout(binding = 0, set = 1, r32ui) uniform uimage2D out_surface;
#endif
shared uint num_primitives;
shared uint num_vertices;
shared uint base_index;
shared uint base_vertex;
shared vec4 row_0;
shared vec4 row_1;
shared vec4 row_2;
shared vec3 positions[NUM_VERTICES];
shared uint indices[NUM_PRIMITIVES * 3u];
/*
*/
void rasterize(vec3 p0, vec3 p1, vec3 p2) {
[[branch]] if(p0.z < 0.0f || p1.z < 0.0f || p2.z < 0.0f) return;
vec3 p10 = p1 – p0;
vec3 p20 = p2 – p0;
float det = p20.x * p10.y – p20.y * p10.x;
[[branch]] if(det >= 0.0f) return;
vec2 min_p = floor(min(min(p0.xy, p1.xy), p2.xy));
vec2 max_p = ceil(max(max(p0.xy, p1.xy), p2.xy));
[[branch]] if(max_p.x < 0.0f || max_p.y < 0.0f || min_p.x >= surface_size.x || min_p.y >= surface_size.x) return;
min_p = clamp(min_p, vec2(0.0f), surface_size – 1.0f);
max_p = clamp(max_p, vec2(0.0f), surface_size – 1.0f);
vec2 texcoord_dx = vec2(-p20.y, p10.y) / det;
vec2 texcoord_dy = vec2(p20.x, -p10.x) / det;
vec2 texcoord_x = texcoord_dx * (min_p.x – p0.x);
vec2 texcoord_y = texcoord_dy * (min_p.y – p0.y);
for(float y = min_p.y; y <= max_p.y; y += 1.0f) {
vec2 texcoord = texcoord_x + texcoord_y;
for(float x = min_p.x; x <= max_p.x; x += 1.0f) {
if(texcoord.x >= 0.0f && texcoord.y >= 0.0f && texcoord.x + texcoord.y <= 1.0f) {
float z = p10.z * texcoord.x + p20.z * texcoord.y + p0.z;
#if CLAY_MTL
uint index = uint(surface_stride * y + x);
atomicMax(out_surface[index], floatBitsToUint(z));
#else
imageAtomicMax(out_surface, ivec2(vec2(x, y)), floatBitsToUint(z));
#endif
}
texcoord += texcoord_dx;
}
texcoord_y += texcoord_dy;
}
}
/*
*/
void main() {
uint group_id = gl_WorkGroupID.x + group_offset;
uint local_id = gl_LocalInvocationIndex;
// meshlet parameters
[[branch]] if(local_id == 0u) {
uint transform_index = (group_id / num_meshlets) * 3u;
row_0 = transforms[transform_index + 0u];
row_1 = transforms[transform_index + 1u];
row_2 = transforms[transform_index + 2u];
uint meshlet_index = (group_id % num_meshlets) * 4u;
num_primitives = meshlets_data[meshlet_index + 0u];
num_vertices = meshlets_data[meshlet_index + 1u];
base_index = meshlets_data[meshlet_index + 2u];
base_vertex = meshlets_data[meshlet_index + 3u];
}
memoryBarrierShared(); barrier();
// load vertices
[[unroll]] for(uint i = 0; i < NUM_VERTICES; i += GROUP_SIZE) {
uint index = local_id + i;
[[branch]] if(index < num_vertices) {
uint address = (base_vertex + index) * 2u;
vec4 position = vec4(vertices_data[address].xyz, 1.0f);
position = vec4(dot(row_0, position), dot(row_1, position), dot(row_2, position), 1.0f);
position = projection * (modelview * position);
positions[index] = vec3(round((position.xy * (0.5f / position.w) + 0.5f) * surface_size * 256.0f) / 256.0f – 0.5f, position.z / position.w);
}
}
// load indices
[[loop]] for(uint i = local_id; (i << 2u) < num_primitives; i += GROUP_SIZE) {
uint index = i * 12u;
uint address = base_index + i * 3u;
uint indices_0 = meshlets_data[address + 0u];
uint indices_1 = meshlets_data[address + 1u];
uint indices_2 = meshlets_data[address + 2u];
indices[index + 0u] = (indices_0 >> 0u) & 0xffu;
indices[index + 1u] = (indices_0 >> 8u) & 0xffu;
indices[index + 2u] = (indices_0 >> 16u) & 0xffu;
indices[index + 3u] = (indices_0 >> 24u) & 0xffu;
indices[index + 4u] = (indices_1 >> 0u) & 0xffu;
indices[index + 5u] = (indices_1 >> 8u) & 0xffu;
indices[index + 6u] = (indices_1 >> 16u) & 0xffu;
indices[index + 7u] = (indices_1 >> 24u) & 0xffu;
indices[index + 8u] = (indices_2 >> 0u) & 0xffu;
indices[index + 9u] = (indices_2 >> 8u) & 0xffu;
indices[index + 10u] = (indices_2 >> 16u) & 0xffu;
indices[index + 11u] = (indices_2 >> 24u) & 0xffu;
}
memoryBarrierShared(); barrier();
// rasterize triangles
[[branch]] if(local_id < num_primitives) {
uint index = local_id * 3u;
uint index_0 = indices[index + 0u];
uint index_1 = indices[index + 1u];
uint index_2 = indices[index + 2u];
rasterize(positions[index_0], positions[index_1], positions[index_2]);
}
}
更多信息请访问https://www.51garena.com