From 0970785d4a8c20cbd577a3cd0d99489d95cb269d Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Fri, 29 Mar 2024 20:46:23 -0700 Subject: [PATCH] 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. --- src/clusterizer.cpp | 78 +++++++++++++++++++++++++++++++++++++++++++++ src/meshoptimizer.h | 10 ++++++ 2 files changed, 88 insertions(+) diff --git a/src/clusterizer.cpp b/src/clusterizer.cpp index c4672ad6..555cd108 100644 --- a/src/clusterizer.cpp +++ b/src/clusterizer.cpp @@ -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)); +} diff --git a/src/meshoptimizer.h b/src/meshoptimizer.h index 0619cec8..6ab48c19 100644 --- a/src/meshoptimizer.h +++ b/src/meshoptimizer.h @@ -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 */