Skip to content

Commit 7634a68

Browse files
authored
Merge pull request #851 from zeux/clflow
clusterizer: Implement global meshlet flow
2 parents a938600 + 21763d7 commit 7634a68

File tree

2 files changed

+175
-25
lines changed

2 files changed

+175
-25
lines changed

demo/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,6 +1484,7 @@ void processDev(const char* path)
14841484

14851485
bool dump = getenv("DUMP") && atoi(getenv("DUMP"));
14861486

1487+
meshlets(mesh, /* scan= */ false, /* uniform= */ false, /* flex= */ false);
14871488
meshlets(mesh, /* scan= */ false, /* uniform= */ true, /* flex= */ false);
14881489
meshlets(mesh, /* scan= */ false, /* uniform= */ true, /* flex= */ true, dump);
14891490
}

src/clusterizer.cpp

Lines changed: 174 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ const size_t kMeshletMaxVertices = 255;
1919
// A reasonable limit is around 2*max_vertices or less
2020
const size_t kMeshletMaxTriangles = 512;
2121

22+
// We keep a limited number of seed triangles and add a few triangles per finished meshlet
23+
const size_t kMeshletMaxSeeds = 256;
24+
const size_t kMeshletAddSeeds = 4;
25+
2226
struct TriangleAdjacency2
2327
{
2428
unsigned int* counts;
@@ -362,7 +366,7 @@ static bool appendMeshlet(meshopt_Meshlet& meshlet, unsigned int a, unsigned int
362366
return result;
363367
}
364368

365-
static unsigned int getNeighborTriangle(const meshopt_Meshlet& meshlet, const Cone* meshlet_cone, unsigned int* meshlet_vertices, const unsigned int* indices, const TriangleAdjacency2& adjacency, const Cone* triangles, const unsigned int* live_triangles, const unsigned char* used, float meshlet_expected_radius, float cone_weight)
369+
static unsigned int getNeighborTriangle(const meshopt_Meshlet& meshlet, const Cone& meshlet_cone, const unsigned int* meshlet_vertices, const unsigned int* indices, const TriangleAdjacency2& adjacency, const Cone* triangles, const unsigned int* live_triangles, const unsigned char* used, float meshlet_expected_radius, float cone_weight)
366370
{
367371
unsigned int best_triangle = ~0u;
368372
int best_priority = 5;
@@ -402,24 +406,13 @@ static unsigned int getNeighborTriangle(const meshopt_Meshlet& meshlet, const Co
402406
if (priority > best_priority)
403407
continue;
404408

405-
float score = 0;
409+
const Cone& tri_cone = triangles[triangle];
406410

407-
// caller selects one of two scoring functions: geometrical (based on meshlet cone) or topological (based on remaining triangles)
408-
if (meshlet_cone)
409-
{
410-
const Cone& tri_cone = triangles[triangle];
411+
float dx = tri_cone.px - meshlet_cone.px, dy = tri_cone.py - meshlet_cone.py, dz = tri_cone.pz - meshlet_cone.pz;
412+
float distance = getDistance(dx, dy, dz, cone_weight < 0);
413+
float spread = tri_cone.nx * meshlet_cone.nx + tri_cone.ny * meshlet_cone.ny + tri_cone.nz * meshlet_cone.nz;
411414

412-
float dx = tri_cone.px - meshlet_cone->px, dy = tri_cone.py - meshlet_cone->py, dz = tri_cone.pz - meshlet_cone->pz;
413-
float distance = getDistance(dx, dy, dz, cone_weight < 0);
414-
float spread = tri_cone.nx * meshlet_cone->nx + tri_cone.ny * meshlet_cone->ny + tri_cone.nz * meshlet_cone->nz;
415-
416-
score = getMeshletScore(distance, spread, cone_weight, meshlet_expected_radius);
417-
}
418-
else
419-
{
420-
// each live_triangles entry is >= 1 since it includes the current triangle we're processing
421-
score = float(live_triangles[a] + live_triangles[b] + live_triangles[c] - 3);
422-
}
415+
float score = getMeshletScore(distance, spread, cone_weight, meshlet_expected_radius);
423416

424417
// note that topology-based priority is always more important than the score
425418
// this helps maintain reasonable effectiveness of meshlet data and reduces scoring cost
@@ -435,6 +428,113 @@ static unsigned int getNeighborTriangle(const meshopt_Meshlet& meshlet, const Co
435428
return best_triangle;
436429
}
437430

431+
static size_t appendSeedTriangles(unsigned int* seeds, const meshopt_Meshlet& meshlet, const unsigned int* meshlet_vertices, const unsigned int* indices, const TriangleAdjacency2& adjacency, const Cone* triangles, const unsigned int* live_triangles, float cornerx, float cornery, float cornerz)
432+
{
433+
unsigned int best_seeds[kMeshletAddSeeds];
434+
unsigned int best_live[kMeshletAddSeeds];
435+
float best_score[kMeshletAddSeeds];
436+
437+
for (size_t i = 0; i < kMeshletAddSeeds; ++i)
438+
{
439+
best_seeds[i] = ~0u;
440+
best_live[i] = ~0u;
441+
best_score[i] = FLT_MAX;
442+
}
443+
444+
for (size_t i = 0; i < meshlet.vertex_count; ++i)
445+
{
446+
unsigned int index = meshlet_vertices[meshlet.vertex_offset + i];
447+
448+
unsigned int best_neighbor = ~0u;
449+
unsigned int best_neighbor_live = ~0u;
450+
451+
// find the neighbor with the smallest live metric
452+
unsigned int* neighbors = &adjacency.data[0] + adjacency.offsets[index];
453+
size_t neighbors_size = adjacency.counts[index];
454+
455+
for (size_t j = 0; j < neighbors_size; ++j)
456+
{
457+
unsigned int triangle = neighbors[j];
458+
unsigned int a = indices[triangle * 3 + 0], b = indices[triangle * 3 + 1], c = indices[triangle * 3 + 2];
459+
460+
unsigned int live = live_triangles[a] + live_triangles[b] + live_triangles[c];
461+
462+
if (live < best_neighbor_live)
463+
{
464+
best_neighbor = triangle;
465+
best_neighbor_live = live;
466+
}
467+
}
468+
469+
// add the neighbor to the list of seeds; the list is unsorted and the replacement criteria is approximate
470+
if (best_neighbor == ~0u)
471+
continue;
472+
473+
float best_neighbor_score = getDistance(triangles[best_neighbor].px - cornerx, triangles[best_neighbor].py - cornery, triangles[best_neighbor].pz - cornerz, false);
474+
475+
for (size_t j = 0; j < kMeshletAddSeeds; ++j)
476+
{
477+
// non-strict comparison reduces the number of duplicate seeds (triangles adjacent to multiple vertices)
478+
if (best_neighbor_live < best_live[j] || (best_neighbor_live == best_live[j] && best_neighbor_score <= best_score[j]))
479+
{
480+
best_seeds[j] = best_neighbor;
481+
best_live[j] = best_neighbor_live;
482+
best_score[j] = best_neighbor_score;
483+
break;
484+
}
485+
}
486+
}
487+
488+
// add surviving seeds to the meshlet
489+
size_t seed_count = 0;
490+
491+
for (size_t i = 0; i < kMeshletAddSeeds; ++i)
492+
if (best_seeds[i] != ~0u)
493+
seeds[seed_count++] = best_seeds[i];
494+
495+
return seed_count;
496+
}
497+
498+
static size_t pruneSeedTriangles(unsigned int* seeds, size_t seed_count, const unsigned char* emitted_flags)
499+
{
500+
size_t result = 0;
501+
502+
for (size_t i = 0; i < seed_count; ++i)
503+
{
504+
unsigned int index = seeds[i];
505+
506+
seeds[result] = index;
507+
result += emitted_flags[index] == 0;
508+
}
509+
510+
return result;
511+
}
512+
513+
static unsigned int selectSeedTriangle(const unsigned int* seeds, size_t seed_count, const unsigned int* indices, const Cone* triangles, const unsigned int* live_triangles, float cornerx, float cornery, float cornerz)
514+
{
515+
unsigned int best_seed = ~0u;
516+
unsigned int best_live = ~0u;
517+
float best_score = FLT_MAX;
518+
519+
for (size_t i = 0; i < seed_count; ++i)
520+
{
521+
unsigned int index = seeds[i];
522+
unsigned int a = indices[index * 3 + 0], b = indices[index * 3 + 1], c = indices[index * 3 + 2];
523+
524+
unsigned int live = live_triangles[a] + live_triangles[b] + live_triangles[c];
525+
float score = getDistance(triangles[index].px - cornerx, triangles[index].py - cornery, triangles[index].pz - cornerz, false);
526+
527+
if (live < best_live || (live == best_live && score < best_score))
528+
{
529+
best_seed = index;
530+
best_live = live;
531+
best_score = score;
532+
}
533+
}
534+
535+
return best_seed;
536+
}
537+
438538
struct KDNode
439539
{
440540
union
@@ -658,10 +758,43 @@ size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshle
658758
KDNode* nodes = allocator.allocate<KDNode>(face_count * 2);
659759
kdtreeBuild(0, nodes, face_count * 2, &triangles[0].px, sizeof(Cone) / sizeof(float), kdindices, face_count, /* leaf_size= */ 8);
660760

761+
// find a specific corner of the mesh to use as a starting point for meshlet flow
762+
float cornerx = FLT_MAX, cornery = FLT_MAX, cornerz = FLT_MAX;
763+
764+
for (size_t i = 0; i < face_count; ++i)
765+
{
766+
const Cone& tri = triangles[i];
767+
768+
cornerx = cornerx > tri.px ? tri.px : cornerx;
769+
cornery = cornery > tri.py ? tri.py : cornery;
770+
cornerz = cornerz > tri.pz ? tri.pz : cornerz;
771+
}
772+
661773
// index of the vertex in the meshlet, 0xff if the vertex isn't used
662774
unsigned char* used = allocator.allocate<unsigned char>(vertex_count);
663775
memset(used, -1, vertex_count);
664776

777+
// initial seed triangle is the one closest to the corner
778+
unsigned int initial_seed = ~0u;
779+
float initial_score = FLT_MAX;
780+
781+
for (size_t i = 0; i < face_count; ++i)
782+
{
783+
const Cone& tri = triangles[i];
784+
785+
float score = getDistance(tri.px - cornerx, tri.py - cornery, tri.pz - cornerz, false);
786+
787+
if (initial_seed == ~0u || score < initial_score)
788+
{
789+
initial_seed = unsigned(i);
790+
initial_score = score;
791+
}
792+
}
793+
794+
// seed triangles to continue meshlet flow
795+
unsigned int seeds[kMeshletMaxSeeds] = {};
796+
size_t seed_count = 0;
797+
665798
meshopt_Meshlet meshlet = {};
666799
size_t meshlet_offset = 0;
667800

@@ -671,18 +804,18 @@ size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshle
671804
{
672805
Cone meshlet_cone = getMeshletCone(meshlet_cone_acc, meshlet.triangle_count);
673806

674-
unsigned int best_triangle = getNeighborTriangle(meshlet, &meshlet_cone, meshlet_vertices, indices, adjacency, triangles, live_triangles, used, meshlet_expected_radius, cone_weight);
675-
int best_extra = best_triangle == ~0u ? -1 : (used[indices[best_triangle * 3 + 0]] == 0xff) + (used[indices[best_triangle * 3 + 1]] == 0xff) + (used[indices[best_triangle * 3 + 2]] == 0xff);
807+
unsigned int best_triangle = ~0u;
676808

677-
// if the best triangle doesn't fit into current meshlet, the spatial scoring we've used is not very meaningful, so we re-select using topological scoring
678-
if (best_triangle != ~0u && (meshlet.vertex_count + best_extra > max_vertices || meshlet.triangle_count >= max_triangles))
679-
{
680-
best_triangle = getNeighborTriangle(meshlet, NULL, meshlet_vertices, indices, adjacency, triangles, live_triangles, used, meshlet_expected_radius, 0.f);
681-
}
809+
// for the first triangle, we don't have a meshlet cone yet, so we use the initial seed
810+
// to continue the meshlet, we select an adjacent triangle based on connectivity and spatial scoring
811+
if (meshlet_offset == 0 && meshlet.triangle_count == 0)
812+
best_triangle = initial_seed;
813+
else
814+
best_triangle = getNeighborTriangle(meshlet, meshlet_cone, meshlet_vertices, indices, adjacency, triangles, live_triangles, used, meshlet_expected_radius, cone_weight);
682815

683816
bool split = false;
684817

685-
// when we run out of neighboring triangles we need to switch to spatial search; we currently just pick the closest triangle irrespective of connectivity
818+
// when we run out of adjacent triangles we need to switch to spatial search; we currently just pick the closest triangle irrespective of connectivity
686819
if (best_triangle == ~0u)
687820
{
688821
float position[3] = {meshlet_cone.px, meshlet_cone.py, meshlet_cone.pz};
@@ -698,6 +831,21 @@ size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshle
698831
if (best_triangle == ~0u)
699832
break;
700833

834+
int best_extra = (used[indices[best_triangle * 3 + 0]] == 0xff) + (used[indices[best_triangle * 3 + 1]] == 0xff) + (used[indices[best_triangle * 3 + 2]] == 0xff);
835+
836+
// if the best triangle doesn't fit into current meshlet, we re-select using seeds to maintain global flow
837+
if (split || (meshlet.vertex_count + best_extra > max_vertices || meshlet.triangle_count >= max_triangles))
838+
{
839+
seed_count = pruneSeedTriangles(seeds, seed_count, emitted_flags);
840+
seed_count = (seed_count + kMeshletAddSeeds <= kMeshletMaxSeeds) ? seed_count : kMeshletMaxSeeds - kMeshletAddSeeds;
841+
seed_count += appendSeedTriangles(seeds + seed_count, meshlet, meshlet_vertices, indices, adjacency, triangles, live_triangles, cornerx, cornery, cornerz);
842+
843+
unsigned int best_seed = selectSeedTriangle(seeds, seed_count, indices, triangles, live_triangles, cornerx, cornery, cornerz);
844+
845+
// we may not find a valid seed triangle if the mesh is disconnected as seeds are based on adjacency
846+
best_triangle = best_seed != ~0u ? best_seed : best_triangle;
847+
}
848+
701849
unsigned int a = indices[best_triangle * 3 + 0], b = indices[best_triangle * 3 + 1], c = indices[best_triangle * 3 + 2];
702850
assert(a < vertex_count && b < vertex_count && c < vertex_count);
703851

@@ -739,6 +887,7 @@ size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshle
739887
meshlet_cone_acc.ny += triangles[best_triangle].ny;
740888
meshlet_cone_acc.nz += triangles[best_triangle].nz;
741889

890+
assert(!emitted_flags[best_triangle]);
742891
emitted_flags[best_triangle] = 1;
743892
}
744893

0 commit comments

Comments
 (0)