Updated meshoptimizer.

This commit is contained in:
Бранимир Караџић
2025-09-14 09:01:40 -07:00
parent 62177540ce
commit c24efd27c9
10 changed files with 1556 additions and 295 deletions
+171 -95
View File
@@ -6,11 +6,23 @@
#include <math.h>
#include <string.h>
// The block below auto-detects SIMD ISA that can be used on the target platform
#ifndef MESHOPTIMIZER_NO_SIMD
#if defined(__SSE2__) || (defined(_MSC_VER) && defined(_M_X64))
#define SIMD_SSE
#include <emmintrin.h>
#elif defined(__aarch64__) || (defined(_MSC_VER) && defined(_M_ARM64) && _MSC_VER >= 1922)
#define SIMD_NEON
#include <arm_neon.h>
#endif
#endif // !MESHOPTIMIZER_NO_SIMD
// This work is based on:
// Graham Wihlidal. Optimizing the Graphics Pipeline with Compute. 2016
// Matthaeus Chajdas. GeometryFX 1.2 - Cluster Culling. 2016
// Jack Ritter. An Efficient Bounding Sphere. 1990
// Thomas Larsson. Fast and Tight Fitting Bounding Spheres. 2008
// Ingo Wald, Vlastimil Havran. On building fast kd-Trees for Ray Tracing, and on doing that in O(N log N). 2006
namespace meshopt
{
@@ -148,6 +160,19 @@ static void buildTriangleAdjacencySparse(TriangleAdjacency2& adjacency, const un
}
}
static void clearUsed(short* used, size_t vertex_count, const unsigned int* indices, size_t index_count)
{
// for sparse inputs, it's faster to only clear vertices referenced by the index buffer
if (vertex_count <= index_count)
memset(used, -1, vertex_count * sizeof(short));
else
for (size_t i = 0; i < index_count; ++i)
{
assert(indices[i] < vertex_count);
used[indices[i]] = -1;
}
}
static void computeBoundingSphere(float result[4], const float* points, size_t count, size_t points_stride, const float* radii, size_t radii_stride, size_t axis_count)
{
static const float kAxes[7][3] = {
@@ -264,21 +289,8 @@ struct Cone
float nx, ny, nz;
};
static float getDistance(float dx, float dy, float dz, bool aa)
{
if (!aa)
return sqrtf(dx * dx + dy * dy + dz * dz);
float rx = fabsf(dx), ry = fabsf(dy), rz = fabsf(dz);
float rxy = rx > ry ? rx : ry;
return rxy > rz ? rxy : rz;
}
static float getMeshletScore(float distance, float spread, float cone_weight, float expected_radius)
{
if (cone_weight < 0)
return 1 + distance / expected_radius;
float cone = 1.f - spread * cone_weight;
float cone_clamped = cone < 1e-3f ? 1e-3f : cone;
@@ -347,15 +359,6 @@ static float computeTriangleCones(Cone* triangles, const unsigned int* indices,
return mesh_area;
}
static void finishMeshlet(meshopt_Meshlet& meshlet, unsigned char* meshlet_triangles)
{
size_t offset = meshlet.triangle_offset + meshlet.triangle_count * 3;
// fill 4b padding with 0
while (offset & 3)
meshlet_triangles[offset++] = 0;
}
static bool appendMeshlet(meshopt_Meshlet& meshlet, unsigned int a, unsigned int b, unsigned int c, short* used, meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, size_t meshlet_offset, size_t max_vertices, size_t max_triangles, bool split = false)
{
short& av = used[a];
@@ -373,10 +376,8 @@ static bool appendMeshlet(meshopt_Meshlet& meshlet, unsigned int a, unsigned int
for (size_t j = 0; j < meshlet.vertex_count; ++j)
used[meshlet_vertices[meshlet.vertex_offset + j]] = -1;
finishMeshlet(meshlet, meshlet_triangles);
meshlet.vertex_offset += meshlet.vertex_count;
meshlet.triangle_offset += (meshlet.triangle_count * 3 + 3) & ~3; // 4b padding
meshlet.triangle_offset += meshlet.triangle_count * 3;
meshlet.vertex_count = 0;
meshlet.triangle_count = 0;
@@ -452,7 +453,7 @@ static unsigned int getNeighborTriangle(const meshopt_Meshlet& meshlet, const Co
const Cone& tri_cone = triangles[triangle];
float dx = tri_cone.px - meshlet_cone.px, dy = tri_cone.py - meshlet_cone.py, dz = tri_cone.pz - meshlet_cone.pz;
float distance = getDistance(dx, dy, dz, cone_weight < 0);
float distance = sqrtf(dx * dx + dy * dy + dz * dz);
float spread = tri_cone.nx * meshlet_cone.nx + tri_cone.ny * meshlet_cone.ny + tri_cone.nz * meshlet_cone.nz;
float score = getMeshletScore(distance, spread, cone_weight, meshlet_expected_radius);
@@ -513,7 +514,8 @@ static size_t appendSeedTriangles(unsigned int* seeds, const meshopt_Meshlet& me
if (best_neighbor == ~0u)
continue;
float best_neighbor_score = getDistance(triangles[best_neighbor].px - cornerx, triangles[best_neighbor].py - cornery, triangles[best_neighbor].pz - cornerz, false);
float dx = triangles[best_neighbor].px - cornerx, dy = triangles[best_neighbor].py - cornery, dz = triangles[best_neighbor].pz - cornerz;
float best_neighbor_score = sqrtf(dx * dx + dy * dy + dz * dz);
for (size_t j = 0; j < kMeshletAddSeeds; ++j)
{
@@ -565,7 +567,8 @@ static unsigned int selectSeedTriangle(const unsigned int* seeds, size_t seed_co
unsigned int a = indices[index * 3 + 0], b = indices[index * 3 + 1], c = indices[index * 3 + 2];
unsigned int live = live_triangles[a] + live_triangles[b] + live_triangles[c];
float score = getDistance(triangles[index].px - cornerx, triangles[index].py - cornery, triangles[index].pz - cornerz, false);
float dx = triangles[index].px - cornerx, dy = triangles[index].py - cornery, dz = triangles[index].pz - cornerz;
float score = sqrtf(dx * dx + dy * dy + dz * dz);
if (live < best_live || (live == best_live && score < best_score))
{
@@ -586,7 +589,7 @@ struct KDNode
unsigned int index;
};
// leaves: axis = 3, children = number of extra points after this one (0 if 'index' is the only point)
// leaves: axis = 3, children = number of points including this one
// branches: axis != 3, left subtree = skip 1, right subtree = skip 1+children
unsigned int axis : 2;
unsigned int children : 30;
@@ -622,7 +625,7 @@ static size_t kdtreeBuildLeaf(size_t offset, KDNode* nodes, size_t node_count, u
result.index = indices[0];
result.axis = 3;
result.children = unsigned(count - 1);
result.children = unsigned(count);
// all remaining points are stored in nodes immediately following the leaf
for (size_t i = 1; i < count; ++i)
@@ -681,29 +684,37 @@ static size_t kdtreeBuild(size_t offset, KDNode* nodes, size_t node_count, const
size_t next_offset = kdtreeBuild(offset + 1, nodes, node_count, points, stride, indices, middle, leaf_size);
// distance to the right subtree is represented explicitly
assert(next_offset - offset > 1);
result.children = unsigned(next_offset - offset - 1);
return kdtreeBuild(next_offset, nodes, node_count, points, stride, indices + middle, count - middle, leaf_size);
}
static void kdtreeNearest(KDNode* nodes, unsigned int root, const float* points, size_t stride, const unsigned char* emitted_flags, const float* position, bool aa, unsigned int& result, float& limit)
static void kdtreeNearest(KDNode* nodes, unsigned int root, const float* points, size_t stride, const unsigned char* emitted_flags, const float* position, unsigned int& result, float& limit)
{
const KDNode& node = nodes[root];
if (node.children == 0)
return;
if (node.axis == 3)
{
// leaf
for (unsigned int i = 0; i <= node.children; ++i)
bool inactive = true;
for (unsigned int i = 0; i < node.children; ++i)
{
unsigned int index = nodes[root + i].index;
if (emitted_flags[index])
continue;
inactive = false;
const float* point = points + index * stride;
float dx = point[0] - position[0], dy = point[1] - position[1], dz = point[2] - position[2];
float distance = getDistance(dx, dy, dz, aa);
float distance = sqrtf(dx * dx + dy * dy + dz * dz);
if (distance < limit)
{
@@ -711,6 +722,10 @@ static void kdtreeNearest(KDNode* nodes, unsigned int root, const float* points,
limit = distance;
}
}
// deactivate leaves that no longer have items to emit
if (inactive)
nodes[root].children = 0;
}
else
{
@@ -719,34 +734,84 @@ static void kdtreeNearest(KDNode* nodes, unsigned int root, const float* points,
unsigned int first = (delta <= 0) ? 0 : node.children;
unsigned int second = first ^ node.children;
kdtreeNearest(nodes, root + 1 + first, points, stride, emitted_flags, position, aa, result, limit);
// deactivate branches that no longer have items to emit to accelerate traversal
// note that we do this *before* recursing which delays deactivation but keeps tail calls
if ((nodes[root + 1 + first].children | nodes[root + 1 + second].children) == 0)
nodes[root].children = 0;
kdtreeNearest(nodes, root + 1 + first, points, stride, emitted_flags, position, result, limit);
// only process the other node if it can have a match based on closest distance so far
if (fabsf(delta) <= limit)
kdtreeNearest(nodes, root + 1 + second, points, stride, emitted_flags, position, aa, result, limit);
kdtreeNearest(nodes, root + 1 + second, points, stride, emitted_flags, position, result, limit);
}
}
struct BVHBoxT
{
float min[4];
float max[4];
};
struct BVHBox
{
float min[3];
float max[3];
};
static void boxMerge(BVHBox& box, const BVHBox& other)
#if defined(SIMD_SSE)
static float boxMerge(BVHBoxT& box, const BVHBox& other)
{
__m128 min = _mm_loadu_ps(box.min);
__m128 max = _mm_loadu_ps(box.max);
min = _mm_min_ps(min, _mm_loadu_ps(other.min));
max = _mm_max_ps(max, _mm_loadu_ps(other.max));
_mm_store_ps(box.min, min);
_mm_store_ps(box.max, max);
__m128 size = _mm_sub_ps(max, min);
__m128 size_yzx = _mm_shuffle_ps(size, size, _MM_SHUFFLE(0, 0, 2, 1));
__m128 mul = _mm_mul_ps(size, size_yzx);
__m128 sum_xy = _mm_add_ss(mul, _mm_shuffle_ps(mul, mul, _MM_SHUFFLE(1, 1, 1, 1)));
__m128 sum_xyz = _mm_add_ss(sum_xy, _mm_shuffle_ps(mul, mul, _MM_SHUFFLE(2, 2, 2, 2)));
return _mm_cvtss_f32(sum_xyz);
}
#elif defined(SIMD_NEON)
static float boxMerge(BVHBoxT& box, const BVHBox& other)
{
float32x4_t min = vld1q_f32(box.min);
float32x4_t max = vld1q_f32(box.max);
min = vminq_f32(min, vld1q_f32(other.min));
max = vmaxq_f32(max, vld1q_f32(other.max));
vst1q_f32(box.min, min);
vst1q_f32(box.max, max);
float32x4_t size = vsubq_f32(max, min);
float32x4_t size_yzx = vextq_f32(vextq_f32(size, size, 3), size, 2);
float32x4_t mul = vmulq_f32(size, size_yzx);
float sum_xy = vgetq_lane_f32(mul, 0) + vgetq_lane_f32(mul, 1);
float sum_xyz = sum_xy + vgetq_lane_f32(mul, 2);
return sum_xyz;
}
#else
static float boxMerge(BVHBoxT& box, const BVHBox& other)
{
for (int k = 0; k < 3; ++k)
{
box.min[k] = other.min[k] < box.min[k] ? other.min[k] : box.min[k];
box.max[k] = other.max[k] > box.max[k] ? other.max[k] : box.max[k];
}
}
inline float boxSurface(const BVHBox& box)
{
float sx = box.max[0] - box.min[0], sy = box.max[1] - box.min[1], sz = box.max[2] - box.min[2];
return sx * sy + sx * sz + sy * sz;
}
#endif
inline unsigned int radixFloat(unsigned int v)
{
@@ -832,7 +897,7 @@ static void bvhPrepare(BVHBox* boxes, float* centroids, const unsigned int* indi
}
}
static bool bvhPackLeaf(unsigned char* boundary, const unsigned int* order, size_t count, short* used, const unsigned int* indices, size_t max_vertices)
static size_t bvhCountVertices(const unsigned int* order, size_t count, short* used, const unsigned int* indices, unsigned int* out = NULL)
{
// count number of unique vertices
size_t used_vertices = 0;
@@ -843,6 +908,9 @@ static bool bvhPackLeaf(unsigned char* boundary, const unsigned int* order, size
used_vertices += (used[a] < 0) + (used[b] < 0) + (used[c] < 0);
used[a] = used[b] = used[c] = 1;
if (out)
out[i] = unsigned(used_vertices);
}
// reset used[] for future invocations
@@ -854,16 +922,16 @@ static bool bvhPackLeaf(unsigned char* boundary, const unsigned int* order, size
used[a] = used[b] = used[c] = -1;
}
if (used_vertices > max_vertices)
return false;
return used_vertices;
}
static void bvhPackLeaf(unsigned char* boundary, size_t count)
{
// mark meshlet boundary for future reassembly
assert(count > 0);
boundary[0] = 1;
memset(boundary + 1, 0, count - 1);
return true;
}
static void bvhPackTail(unsigned char* boundary, const unsigned int* order, size_t count, short* used, const unsigned int* indices, size_t max_vertices, size_t max_triangles)
@@ -872,8 +940,9 @@ static void bvhPackTail(unsigned char* boundary, const unsigned int* order, size
{
size_t chunk = i + max_triangles <= count ? max_triangles : count - i;
if (bvhPackLeaf(boundary + i, order + i, chunk, used, indices, max_vertices))
if (bvhCountVertices(order + i, chunk, used, indices) <= max_vertices)
{
bvhPackLeaf(boundary + i, chunk);
i += chunk;
continue;
}
@@ -881,7 +950,7 @@ static void bvhPackTail(unsigned char* boundary, const unsigned int* order, size
// chunk is vertex bound, split it into smaller meshlets
assert(chunk > max_vertices / 3);
bvhPackLeaf(boundary + i, order + i, max_vertices / 3, used, indices, max_vertices);
bvhPackLeaf(boundary + i, max_vertices / 3);
i += max_vertices / 3;
}
}
@@ -889,30 +958,32 @@ static void bvhPackTail(unsigned char* boundary, const unsigned int* order, size
static bool bvhDivisible(size_t count, size_t min, size_t max)
{
// count is representable as a sum of values in [min..max] if if it in range of [k*min..k*min+k*(max-min)]
// equivalent to ceil(count / max) <= floor(count / min), but the form below allows using idiv
// equivalent to ceil(count / max) <= floor(count / min), but the form below allows using idiv (see nv_cluster_builder)
// we avoid expensive integer divisions in the common case where min is <= max/2
return min * 2 <= max ? count >= min : count % min <= (count / min) * (max - min);
}
static size_t bvhPivot(const BVHBox* boxes, const unsigned int* order, size_t count, void* scratch, size_t step, size_t min, size_t max, float fill, float* out_cost)
static void bvhComputeArea(float* areas, const BVHBox* boxes, const unsigned int* order, size_t count)
{
BVHBox accuml = boxes[order[0]], accumr = boxes[order[count - 1]];
float* costs = static_cast<float*>(scratch);
BVHBoxT accuml = {{FLT_MAX, FLT_MAX, FLT_MAX, 0}, {-FLT_MAX, -FLT_MAX, -FLT_MAX, 0}};
BVHBoxT accumr = accuml;
// accumulate SAH cost in forward and backward directions
for (size_t i = 0; i < count; ++i)
{
boxMerge(accuml, boxes[order[i]]);
boxMerge(accumr, boxes[order[count - 1 - i]]);
float larea = boxMerge(accuml, boxes[order[i]]);
float rarea = boxMerge(accumr, boxes[order[count - 1 - i]]);
costs[i] = boxSurface(accuml);
costs[i + count] = boxSurface(accumr);
areas[i] = larea;
areas[i + count] = rarea;
}
}
static size_t bvhPivot(const float* areas, const unsigned int* vertices, size_t count, size_t step, size_t min, size_t max, float fill, size_t maxfill, float* out_cost)
{
bool aligned = count >= min * 2 && bvhDivisible(count, min, max);
size_t end = aligned ? count - min : count - 1;
float rmaxf = 1.f / float(int(max));
float rmaxfill = 1.f / float(int(maxfill));
// find best split that minimizes SAH
size_t bestsplit = 0;
@@ -927,17 +998,22 @@ static size_t bvhPivot(const BVHBox* boxes, const unsigned int* order, size_t co
if (aligned && !bvhDivisible(rsplit, min, max))
continue;
// costs[x] = inclusive surface area of boxes[0..x]
// costs[count-1-x] = inclusive surface area of boxes[x..count-1]
float larea = costs[i], rarea = costs[(count - 1 - (i + 1)) + count];
// areas[x] = inclusive surface area of boxes[0..x]
// areas[count-1-x] = inclusive surface area of boxes[x..count-1]
float larea = areas[i], rarea = areas[(count - 1 - (i + 1)) + count];
float cost = larea * float(int(lsplit)) + rarea * float(int(rsplit));
if (cost > bestcost)
continue;
// fill cost; use floating point math to avoid expensive integer modulo
int lrest = int(float(int(lsplit + max - 1)) * rmaxf) * int(max) - int(lsplit);
int rrest = int(float(int(rsplit + max - 1)) * rmaxf) * int(max) - int(rsplit);
// use vertex fill when splitting vertex limited clusters; note that we use the same (left->right) vertex count
// using bidirectional vertex counts is a little more expensive to compute and produces slightly worse results in practice
size_t lfill = vertices ? vertices[i] : lsplit;
size_t rfill = vertices ? vertices[i] : rsplit;
// fill cost; use floating point math to round up to maxfill to avoid expensive integer modulo
int lrest = int(float(int(lfill + maxfill - 1)) * rmaxfill) * int(maxfill) - int(lfill);
int rrest = int(float(int(rfill + maxfill - 1)) * rmaxfill) * int(maxfill) - int(rfill);
cost += fill * (float(lrest) * larea + float(rrest) * rarea);
@@ -973,8 +1049,8 @@ static void bvhSplit(const BVHBox* boxes, unsigned int* orderx, unsigned int* or
if (depth >= kMeshletMaxTreeDepth)
return bvhPackTail(boundary, orderx, count, used, indices, max_vertices, max_triangles);
if (count <= max_triangles && bvhPackLeaf(boundary, orderx, count, used, indices, max_vertices))
return;
if (count <= max_triangles && bvhCountVertices(orderx, count, used, indices) <= max_vertices)
return bvhPackLeaf(boundary, count);
unsigned int* axes[3] = {orderx, ordery, orderz};
@@ -983,9 +1059,7 @@ static void bvhSplit(const BVHBox* boxes, unsigned int* orderx, unsigned int* or
// if we could not pack the meshlet, we must be vertex bound
size_t mint = count <= max_triangles && max_vertices / 3 < min_triangles ? max_vertices / 3 : min_triangles;
// only use fill weight if we are optimizing for triangle count
float fill = count <= max_triangles ? 0.f : fill_weight;
size_t maxfill = count <= max_triangles ? max_vertices : max_triangles;
// find best split that minimizes SAH
int bestk = -1;
@@ -994,8 +1068,20 @@ static void bvhSplit(const BVHBox* boxes, unsigned int* orderx, unsigned int* or
for (int k = 0; k < 3; ++k)
{
float* areas = static_cast<float*>(scratch);
unsigned int* vertices = NULL;
bvhComputeArea(areas, boxes, axes[k], count);
if (count <= max_triangles)
{
// for vertex bound clusters, count number of unique vertices for each split
vertices = reinterpret_cast<unsigned int*>(areas + 2 * count);
bvhCountVertices(axes[k], count, used, indices, vertices);
}
float axiscost = FLT_MAX;
size_t axissplit = bvhPivot(boxes, axes[k], count, scratch, step, mint, max_triangles, fill, &axiscost);
size_t axissplit = bvhPivot(areas, vertices, count, step, mint, max_triangles, fill_weight, maxfill, &axiscost);
if (axissplit && axiscost < bestcost)
{
@@ -1044,7 +1130,6 @@ size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_
assert(index_count % 3 == 0);
assert(max_vertices >= 3 && max_vertices <= kMeshletMaxVertices);
assert(max_triangles >= 1 && max_triangles <= kMeshletMaxTriangles);
assert(max_triangles % 4 == 0); // ensures the caller will compute output space properly as index data is 4b aligned
(void)kMeshletMaxVertices;
(void)kMeshletMaxTriangles;
@@ -1069,9 +1154,8 @@ size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshle
assert(max_vertices >= 3 && max_vertices <= kMeshletMaxVertices);
assert(min_triangles >= 1 && min_triangles <= max_triangles && max_triangles <= kMeshletMaxTriangles);
assert(min_triangles % 4 == 0 && max_triangles % 4 == 0); // ensures the caller will compute output space properly as index data is 4b aligned
assert(cone_weight <= 1); // negative cone weight switches metric to optimize for axis-aligned meshlets
assert(cone_weight >= 0 && cone_weight <= 1);
assert(split_factor >= 0);
if (index_count == 0)
@@ -1123,7 +1207,7 @@ size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshle
// index of the vertex in the meshlet, -1 if the vertex isn't used
short* used = allocator.allocate<short>(vertex_count);
memset(used, -1, vertex_count * sizeof(short));
clearUsed(used, vertex_count, indices, index_count);
// initial seed triangle is the one closest to the corner
unsigned int initial_seed = ~0u;
@@ -1133,7 +1217,8 @@ size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshle
{
const Cone& tri = triangles[i];
float score = getDistance(tri.px - cornerx, tri.py - cornery, tri.pz - cornerz, false);
float dx = tri.px - cornerx, dy = tri.py - cornery, dz = tri.pz - cornerz;
float score = sqrtf(dx * dx + dy * dy + dz * dz);
if (initial_seed == ~0u || score < initial_score)
{
@@ -1173,7 +1258,7 @@ size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshle
unsigned int index = ~0u;
float distance = FLT_MAX;
kdtreeNearest(nodes, 0, &triangles[0].px, sizeof(Cone) / sizeof(float), emitted_flags, position, cone_weight < 0.f, index, distance);
kdtreeNearest(nodes, 0, &triangles[0].px, sizeof(Cone) / sizeof(float), emitted_flags, position, index, distance);
best_triangle = index;
split = meshlet.triangle_count >= min_triangles && split_factor > 0 && distance > meshlet_expected_radius * split_factor;
@@ -1243,20 +1328,15 @@ size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshle
}
if (meshlet.triangle_count)
{
finishMeshlet(meshlet, meshlet_triangles);
meshlets[meshlet_offset++] = meshlet;
}
assert(meshlet_offset <= meshopt_buildMeshletsBound(index_count, max_vertices, min_triangles));
assert(meshlet.triangle_offset + meshlet.triangle_count * 3 <= index_count && meshlet.vertex_offset + meshlet.vertex_count <= index_count);
return meshlet_offset;
}
size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight)
{
assert(cone_weight >= 0); // to use negative cone weight, use meshopt_buildMeshletsFlex
return meshopt_buildMeshletsFlex(meshlets, meshlet_vertices, meshlet_triangles, indices, index_count, vertex_positions, vertex_count, vertex_positions_stride, max_vertices, max_triangles, max_triangles, cone_weight, 0.0f);
}
@@ -1268,13 +1348,12 @@ size_t meshopt_buildMeshletsScan(meshopt_Meshlet* meshlets, unsigned int* meshle
assert(max_vertices >= 3 && max_vertices <= kMeshletMaxVertices);
assert(max_triangles >= 1 && max_triangles <= kMeshletMaxTriangles);
assert(max_triangles % 4 == 0); // ensures the caller will compute output space properly as index data is 4b aligned
meshopt_Allocator allocator;
// index of the vertex in the meshlet, -1 if the vertex isn't used
short* used = allocator.allocate<short>(vertex_count);
memset(used, -1, vertex_count * sizeof(short));
clearUsed(used, vertex_count, indices, index_count);
meshopt_Meshlet meshlet = {};
size_t meshlet_offset = 0;
@@ -1289,17 +1368,14 @@ size_t meshopt_buildMeshletsScan(meshopt_Meshlet* meshlets, unsigned int* meshle
}
if (meshlet.triangle_count)
{
finishMeshlet(meshlet, meshlet_triangles);
meshlets[meshlet_offset++] = meshlet;
}
assert(meshlet_offset <= meshopt_buildMeshletsBound(index_count, max_vertices, max_triangles));
assert(meshlet.triangle_offset + meshlet.triangle_count * 3 <= index_count && meshlet.vertex_offset + meshlet.vertex_count <= index_count);
return meshlet_offset;
}
size_t meshopt_buildMeshletsSplit(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float fill_weight)
size_t meshopt_buildMeshletsSpatial(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float fill_weight)
{
using namespace meshopt;
@@ -1309,7 +1385,6 @@ size_t meshopt_buildMeshletsSplit(struct meshopt_Meshlet* meshlets, unsigned int
assert(max_vertices >= 3 && max_vertices <= kMeshletMaxVertices);
assert(min_triangles >= 1 && min_triangles <= max_triangles && max_triangles <= kMeshletMaxTriangles);
assert(min_triangles % 4 == 0 && max_triangles % 4 == 0); // ensures the caller will compute output space properly as index data is 4b aligned
if (index_count == 0)
return 0;
@@ -1320,13 +1395,14 @@ size_t meshopt_buildMeshletsSplit(struct meshopt_Meshlet* meshlets, unsigned int
meshopt_Allocator allocator;
// 3 floats plus 1 uint for sorting, or
// 2 floats for SAH costs, or
// 2 floats plus 1 uint for pivoting, or
// 1 uint plus 1 byte for partitioning
float* scratch = allocator.allocate<float>(face_count * 4);
// compute bounding boxes and centroids for sorting
BVHBox* boxes = allocator.allocate<BVHBox>(face_count);
BVHBox* boxes = allocator.allocate<BVHBox>(face_count + 1); // padding for SIMD
bvhPrepare(boxes, scratch, indices, face_count, vertex_positions, vertex_count, vertex_stride_float);
memset(boxes + face_count, 0, sizeof(BVHBox));
unsigned int* axes = allocator.allocate<unsigned int>(face_count * 3);
unsigned int* temp = reinterpret_cast<unsigned int*>(scratch) + face_count * 3;
@@ -1350,7 +1426,7 @@ size_t meshopt_buildMeshletsSplit(struct meshopt_Meshlet* meshlets, unsigned int
// index of the vertex in the meshlet, -1 if the vertex isn't used
short* used = allocator.allocate<short>(vertex_count);
memset(used, -1, vertex_count * sizeof(short));
clearUsed(used, vertex_count, indices, index_count);
unsigned char* boundary = allocator.allocate<unsigned char>(face_count);
@@ -1391,13 +1467,10 @@ size_t meshopt_buildMeshletsSplit(struct meshopt_Meshlet* meshlets, unsigned int
}
if (meshlet.triangle_count)
{
finishMeshlet(meshlet, meshlet_triangles);
meshlets[meshlet_offset++] = meshlet;
}
assert(meshlet_offset <= meshlet_bound);
assert(meshlet.triangle_offset + meshlet.triangle_count * 3 <= index_count && meshlet.vertex_offset + meshlet.vertex_count <= index_count);
return meshlet_offset;
}
@@ -1693,3 +1766,6 @@ void meshopt_optimizeMeshlet(unsigned int* meshlet_vertices, unsigned char* mesh
assert(vertex_offset <= vertex_count);
memcpy(vertices, order, vertex_offset * sizeof(unsigned int));
}
#undef SIMD_SSE
#undef SIMD_NEON