clusterizer: Implement experimental meshlet optimizer

So far we were mostly concerned with meshlet clustering from the
perspective of treating meshlets as an unordered set of triangles; while
this matches the computational and documented model, this may not be
optimal for a given GPU.

Notably, NVidia GPUs are much more sensitive to the order of triangles
in the meshlet than to the number and fill percentage; so much so that
from pure rasterization performance, scan may win over proper clustering
because it implicitly generates a better order.

We do not know the precise criteria / mechanism that NV GPUs use here
but it helps to do locality optimization; most importantly, triangle
order, but also reordering meshlet-local vertices helps a little bit.

This change implements a simple meshlet optimizer; while this can also
be achieved by running existing optimization algorithms (vcache /
vfetch) on meshlet data, a custom optimizer is faster even when using
unoptimized quadratic implementation, and may allow us to implement
better locality reodering algorithms in the future assuming a small
input patch.

For now we just select the next triangle to maximize the number of
shared vertices with the previous triangle; this results in a
pseudo-strip order which seems reasonably optimal for NV; note that
neither the hardware nor this algorithm is concerned with the specifics
of edge matching as it doesn't seem to matter for performance.
This commit is contained in:
Arseny Kapoulkine
2024-03-29 20:46:23 -07:00
parent 630a6a0553
commit 0970785d4a
2 changed files with 88 additions and 0 deletions
+78
View File
@@ -882,3 +882,81 @@ meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices
return meshopt_computeClusterBounds(indices, triangle_count * 3, vertex_positions, vertex_count, vertex_positions_stride);
}
void meshopt_optimizeMeshlet(unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, size_t triangle_count, size_t vertex_count)
{
using namespace meshopt;
assert(triangle_count <= kMeshletMaxTriangles);
assert(vertex_count <= kMeshletMaxVertices);
unsigned char* indices = meshlet_triangles;
unsigned int* vertices = meshlet_vertices;
unsigned char result[kMeshletMaxTriangles * 3];
unsigned char visited[kMeshletMaxTriangles];
memset(visited, 0, triangle_count);
unsigned char lasta = 0, lastb = 0, lastc = 0;
for (size_t i = 0; i < triangle_count; ++i)
{
int next = -1;
for (int pass = 2; pass >= 0; pass--)
{
for (size_t j = 0; j < triangle_count; ++j)
{
if (visited[j])
continue;
unsigned char a = indices[j * 3 + 0], b = indices[j * 3 + 1], c = indices[j * 3 + 2];
int aok = (a == lasta || a == lastb || a == lastc);
int bok = (b == lasta || b == lastb || b == lastc);
int cok = (c == lasta || c == lastb || c == lastc);
if (aok + bok + cok >= pass)
{
next = (int)j;
break;
}
}
if (next >= 0)
break;
}
assert(next >= 0);
visited[next] = 1;
result[i * 3 + 0] = lasta = indices[next * 3 + 0];
result[i * 3 + 1] = lastb = indices[next * 3 + 1];
result[i * 3 + 2] = lastc = indices[next * 3 + 2];
}
memcpy(indices, result, triangle_count * 3);
unsigned int newv[kMeshletMaxVertices];
unsigned char remap[kMeshletMaxVertices];
memset(remap, -1, vertex_count);
size_t vertex_offset = 0;
for (size_t i = 0; i < triangle_count * 3; ++i)
{
unsigned char& r = remap[indices[i]];
if (r == 0xff)
{
r = unsigned(vertex_offset);
newv[vertex_offset] = vertices[indices[i]];
vertex_offset++;
}
indices[i] = r;
}
memcpy(vertices, newv, vertex_offset * sizeof(unsigned int));
}
+10
View File
@@ -493,6 +493,16 @@ MESHOPTIMIZER_API size_t meshopt_buildMeshlets(struct meshopt_Meshlet* meshlets,
MESHOPTIMIZER_API size_t meshopt_buildMeshletsScan(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles);
MESHOPTIMIZER_API size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_t max_triangles);
/**
* Experimental: Meshlet optimizer
* Reorders meshlet vertices and triangles to maximize locality to improve rasterizer throughput
*
* meshlet_triangles and meshlet_vertices must refer to meshlet triangle and vertex index data; when buildMeshlets* is used, these
* need to be computed from meshlet's vertex_offset and triangle_offset
* triangle_count and vertex_count must not exceed implementation limits (vertex_count <= 255 - not 256!, triangle_count <= 512)
*/
MESHOPTIMIZER_EXPERIMENTAL void meshopt_optimizeMeshlet(unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, size_t triangle_count, size_t vertex_count);
struct meshopt_Bounds
{
/* bounding sphere, useful for frustum and occlusion culling */