From adab80fb07c4f8f411a904aa662681c6e7ec5f1e Mon Sep 17 00:00:00 2001 From: Jakub Marcowski Date: Mon, 20 Apr 2026 15:06:27 +0200 Subject: [PATCH] meshoptimizer: Update to 1.1 --- COPYRIGHT.txt | 2 +- thirdparty/README.md | 2 +- thirdparty/meshoptimizer/LICENSE.md | 2 +- thirdparty/meshoptimizer/clusterizer.cpp | 435 +------- thirdparty/meshoptimizer/indexanalyzer.cpp | 1 - thirdparty/meshoptimizer/indexcodec.cpp | 12 +- thirdparty/meshoptimizer/indexgenerator.cpp | 5 - thirdparty/meshoptimizer/meshletcodec.cpp | 1051 ++++++++++++++++++ thirdparty/meshoptimizer/meshletutils.cpp | 613 ++++++++++ thirdparty/meshoptimizer/meshoptimizer.h | 123 +- thirdparty/meshoptimizer/opacitymap.cpp | 557 ++++++++++ thirdparty/meshoptimizer/simplifier.cpp | 13 +- thirdparty/meshoptimizer/vertexcodec.cpp | 22 +- thirdparty/meshoptimizer/vertexfilter.cpp | 29 +- thirdparty/meshoptimizer/vfetchoptimizer.cpp | 3 - 15 files changed, 2387 insertions(+), 483 deletions(-) create mode 100644 thirdparty/meshoptimizer/meshletcodec.cpp create mode 100644 thirdparty/meshoptimizer/meshletutils.cpp create mode 100644 thirdparty/meshoptimizer/opacitymap.cpp diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 2d65970b0a..165ee1c7c0 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -429,7 +429,7 @@ License: Apache-2.0 Files: thirdparty/meshoptimizer/* Comment: meshoptimizer -Copyright: 2016-2024, Arseny Kapoulkine +Copyright: 2016-2026, Arseny Kapoulkine License: Expat Files: thirdparty/metal-cpp/* diff --git a/thirdparty/README.md b/thirdparty/README.md index cd3968eef3..8b9f9e0a40 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -720,7 +720,7 @@ Update instructions: ## meshoptimizer - Upstream: https://github.com/zeux/meshoptimizer -- Version: 1.0 (73583c335e541c139821d0de2bf5f12960a04941, 2025) +- Version: 1.1 (dc9d09ed83e1004aef47a1c3c597e0ec64848a37, 2026) - License: MIT Files extracted from upstream repository: diff --git a/thirdparty/meshoptimizer/LICENSE.md b/thirdparty/meshoptimizer/LICENSE.md index a5c3b1ccca..468827f4b8 100644 --- a/thirdparty/meshoptimizer/LICENSE.md +++ b/thirdparty/meshoptimizer/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016-2025 Arseny Kapoulkine +Copyright (c) 2016-2026 Arseny Kapoulkine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/thirdparty/meshoptimizer/clusterizer.cpp b/thirdparty/meshoptimizer/clusterizer.cpp index 73cc0ab535..b5a3cd9216 100644 --- a/thirdparty/meshoptimizer/clusterizer.cpp +++ b/thirdparty/meshoptimizer/clusterizer.cpp @@ -8,10 +8,10 @@ // 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)) +#if defined(__SSE2__) || (defined(_MSC_VER) && defined(_M_X64) && !defined(_M_ARM64EC)) #define SIMD_SSE #include -#elif defined(__aarch64__) || (defined(_MSC_VER) && defined(_M_ARM64) && _MSC_VER >= 1922) +#elif defined(__aarch64__) || (defined(_MSC_VER) && (defined(_M_ARM64) || defined(_M_ARM64EC)) && _MSC_VER >= 1922) #define SIMD_NEON #include #endif @@ -19,19 +19,10 @@ // 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 { -// This must be <= 256 since meshlet indices are stored as bytes -const size_t kMeshletMaxVertices = 256; - -// A reasonable limit is around 2*max_vertices or less -const size_t kMeshletMaxTriangles = 512; - // We keep a limited number of seed triangles and add a few triangles per finished meshlet const size_t kMeshletMaxSeeds = 256; const size_t kMeshletAddSeeds = 4; @@ -173,116 +164,6 @@ static void clearUsed(short* used, size_t vertex_count, const unsigned int* indi } } -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] = { - // X, Y, Z - {1, 0, 0}, - {0, 1, 0}, - {0, 0, 1}, - - // XYZ, -XYZ, X-YZ, XY-Z; normalized to unit length - {0.57735026f, 0.57735026f, 0.57735026f}, - {-0.57735026f, 0.57735026f, 0.57735026f}, - {0.57735026f, -0.57735026f, 0.57735026f}, - {0.57735026f, 0.57735026f, -0.57735026f}, - }; - - assert(count > 0); - assert(axis_count <= sizeof(kAxes) / sizeof(kAxes[0])); - - size_t points_stride_float = points_stride / sizeof(float); - size_t radii_stride_float = radii_stride / sizeof(float); - - // find extremum points along all axes; for each axis we get a pair of points with min/max coordinates - size_t pmin[7], pmax[7]; - float tmin[7], tmax[7]; - - for (size_t axis = 0; axis < axis_count; ++axis) - { - pmin[axis] = pmax[axis] = 0; - tmin[axis] = FLT_MAX; - tmax[axis] = -FLT_MAX; - } - - for (size_t i = 0; i < count; ++i) - { - const float* p = points + i * points_stride_float; - float r = radii[i * radii_stride_float]; - - for (size_t axis = 0; axis < axis_count; ++axis) - { - const float* ax = kAxes[axis]; - - float tp = ax[0] * p[0] + ax[1] * p[1] + ax[2] * p[2]; - float tpmin = tp - r, tpmax = tp + r; - - pmin[axis] = (tpmin < tmin[axis]) ? i : pmin[axis]; - pmax[axis] = (tpmax > tmax[axis]) ? i : pmax[axis]; - tmin[axis] = (tpmin < tmin[axis]) ? tpmin : tmin[axis]; - tmax[axis] = (tpmax > tmax[axis]) ? tpmax : tmax[axis]; - } - } - - // find the pair of points with largest distance - size_t paxis = 0; - float paxisdr = 0; - - for (size_t axis = 0; axis < axis_count; ++axis) - { - const float* p1 = points + pmin[axis] * points_stride_float; - const float* p2 = points + pmax[axis] * points_stride_float; - float r1 = radii[pmin[axis] * radii_stride_float]; - float r2 = radii[pmax[axis] * radii_stride_float]; - - float d2 = (p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1]) + (p2[2] - p1[2]) * (p2[2] - p1[2]); - float dr = sqrtf(d2) + r1 + r2; - - if (dr > paxisdr) - { - paxisdr = dr; - paxis = axis; - } - } - - // use the longest segment as the initial sphere diameter - const float* p1 = points + pmin[paxis] * points_stride_float; - const float* p2 = points + pmax[paxis] * points_stride_float; - float r1 = radii[pmin[paxis] * radii_stride_float]; - float r2 = radii[pmax[paxis] * radii_stride_float]; - - float paxisd = sqrtf((p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1]) + (p2[2] - p1[2]) * (p2[2] - p1[2])); - float paxisk = paxisd > 0 ? (paxisd + r2 - r1) / (2 * paxisd) : 0.f; - - float center[3] = {p1[0] + (p2[0] - p1[0]) * paxisk, p1[1] + (p2[1] - p1[1]) * paxisk, p1[2] + (p2[2] - p1[2]) * paxisk}; - float radius = paxisdr / 2; - - // iteratively adjust the sphere up until all points fit - for (size_t i = 0; i < count; ++i) - { - const float* p = points + i * points_stride_float; - float r = radii[i * radii_stride_float]; - - float d2 = (p[0] - center[0]) * (p[0] - center[0]) + (p[1] - center[1]) * (p[1] - center[1]) + (p[2] - center[2]) * (p[2] - center[2]); - float d = sqrtf(d2); - - if (d + r > radius) - { - float k = d > 0 ? (d + r - radius) / (2 * d) : 0.f; - - center[0] += k * (p[0] - center[0]); - center[1] += k * (p[1] - center[1]); - center[2] += k * (p[2] - center[2]); - radius = (radius + d + r) / 2; - } - } - - result[0] = center[0]; - result[1] = center[1]; - result[2] = center[2]; - result[3] = radius; -} - struct Cone { float px, py, pz; @@ -1130,11 +1011,8 @@ size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_ using namespace meshopt; assert(index_count % 3 == 0); - assert(max_vertices >= 3 && max_vertices <= kMeshletMaxVertices); - assert(max_triangles >= 1 && max_triangles <= kMeshletMaxTriangles); - - (void)kMeshletMaxVertices; - (void)kMeshletMaxTriangles; + assert(max_vertices >= 3 && max_vertices <= 256); + assert(max_triangles >= 1 && max_triangles <= 512); // meshlet construction is limited by max vertices and max triangles per meshlet // the worst case is that the input is an unindexed stream since this equally stresses both limits @@ -1154,8 +1032,8 @@ size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshle assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256); assert(vertex_positions_stride % sizeof(float) == 0); - assert(max_vertices >= 3 && max_vertices <= kMeshletMaxVertices); - assert(min_triangles >= 1 && min_triangles <= max_triangles && max_triangles <= kMeshletMaxTriangles); + assert(max_vertices >= 3 && max_vertices <= 256); + assert(min_triangles >= 1 && min_triangles <= max_triangles && max_triangles <= 512); assert(cone_weight >= 0 && cone_weight <= 1); assert(split_factor >= 0); @@ -1348,8 +1226,8 @@ size_t meshopt_buildMeshletsScan(meshopt_Meshlet* meshlets, unsigned int* meshle assert(index_count % 3 == 0); - assert(max_vertices >= 3 && max_vertices <= kMeshletMaxVertices); - assert(max_triangles >= 1 && max_triangles <= kMeshletMaxTriangles); + assert(max_vertices >= 3 && max_vertices <= 256); + assert(max_triangles >= 1 && max_triangles <= 512); meshopt_Allocator allocator; @@ -1385,8 +1263,8 @@ size_t meshopt_buildMeshletsSpatial(struct meshopt_Meshlet* meshlets, unsigned i assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256); assert(vertex_positions_stride % sizeof(float) == 0); - assert(max_vertices >= 3 && max_vertices <= kMeshletMaxVertices); - assert(min_triangles >= 1 && min_triangles <= max_triangles && max_triangles <= kMeshletMaxTriangles); + assert(max_vertices >= 3 && max_vertices <= 256); + assert(min_triangles >= 1 && min_triangles <= max_triangles && max_triangles <= 512); if (index_count == 0) return 0; @@ -1476,298 +1354,5 @@ size_t meshopt_buildMeshletsSpatial(struct meshopt_Meshlet* meshlets, unsigned i return meshlet_offset; } -meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) -{ - using namespace meshopt; - - assert(index_count % 3 == 0); - assert(index_count / 3 <= kMeshletMaxTriangles); - assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256); - assert(vertex_positions_stride % sizeof(float) == 0); - - (void)vertex_count; - - size_t vertex_stride_float = vertex_positions_stride / sizeof(float); - - // compute triangle normals and gather triangle corners - float normals[kMeshletMaxTriangles][3]; - float corners[kMeshletMaxTriangles][3][3]; - size_t triangles = 0; - - for (size_t i = 0; i < index_count; i += 3) - { - unsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2]; - assert(a < vertex_count && b < vertex_count && c < vertex_count); - - const float* p0 = vertex_positions + vertex_stride_float * a; - const float* p1 = vertex_positions + vertex_stride_float * b; - const float* p2 = vertex_positions + vertex_stride_float * c; - - float p10[3] = {p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]}; - float p20[3] = {p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]}; - - float normalx = p10[1] * p20[2] - p10[2] * p20[1]; - float normaly = p10[2] * p20[0] - p10[0] * p20[2]; - float normalz = p10[0] * p20[1] - p10[1] * p20[0]; - - float area = sqrtf(normalx * normalx + normaly * normaly + normalz * normalz); - - // no need to include degenerate triangles - they will be invisible anyway - if (area == 0.f) - continue; - - // record triangle normals & corners for future use; normal and corner 0 define a plane equation - normals[triangles][0] = normalx / area; - normals[triangles][1] = normaly / area; - normals[triangles][2] = normalz / area; - memcpy(corners[triangles][0], p0, 3 * sizeof(float)); - memcpy(corners[triangles][1], p1, 3 * sizeof(float)); - memcpy(corners[triangles][2], p2, 3 * sizeof(float)); - triangles++; - } - - meshopt_Bounds bounds = {}; - - // degenerate cluster, no valid triangles => trivial reject (cone data is 0) - if (triangles == 0) - return bounds; - - const float rzero = 0.f; - - // compute cluster bounding sphere; we'll use the center to determine normal cone apex as well - float psphere[4] = {}; - computeBoundingSphere(psphere, corners[0][0], triangles * 3, sizeof(float) * 3, &rzero, 0, 7); - - float center[3] = {psphere[0], psphere[1], psphere[2]}; - - // treating triangle normals as points, find the bounding sphere - the sphere center determines the optimal cone axis - float nsphere[4] = {}; - computeBoundingSphere(nsphere, normals[0], triangles, sizeof(float) * 3, &rzero, 0, 3); - - float axis[3] = {nsphere[0], nsphere[1], nsphere[2]}; - float axislength = sqrtf(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]); - float invaxislength = axislength == 0.f ? 0.f : 1.f / axislength; - - axis[0] *= invaxislength; - axis[1] *= invaxislength; - axis[2] *= invaxislength; - - // compute a tight cone around all normals, mindp = cos(angle/2) - float mindp = 1.f; - - for (size_t i = 0; i < triangles; ++i) - { - float dp = normals[i][0] * axis[0] + normals[i][1] * axis[1] + normals[i][2] * axis[2]; - - mindp = (dp < mindp) ? dp : mindp; - } - - // fill bounding sphere info; note that below we can return bounds without cone information for degenerate cones - bounds.center[0] = center[0]; - bounds.center[1] = center[1]; - bounds.center[2] = center[2]; - bounds.radius = psphere[3]; - - // degenerate cluster, normal cone is larger than a hemisphere => trivial accept - // note that if mindp is positive but close to 0, the triangle intersection code below gets less stable - // we arbitrarily decide that if a normal cone is ~168 degrees wide or more, the cone isn't useful - if (mindp <= 0.1f) - { - bounds.cone_cutoff = 1; - bounds.cone_cutoff_s8 = 127; - return bounds; - } - - float maxt = 0; - - // we need to find the point on center-t*axis ray that lies in negative half-space of all triangles - for (size_t i = 0; i < triangles; ++i) - { - // dot(center-t*axis-corner, trinormal) = 0 - // dot(center-corner, trinormal) - t * dot(axis, trinormal) = 0 - float cx = center[0] - corners[i][0][0]; - float cy = center[1] - corners[i][0][1]; - float cz = center[2] - corners[i][0][2]; - - float dc = cx * normals[i][0] + cy * normals[i][1] + cz * normals[i][2]; - float dn = axis[0] * normals[i][0] + axis[1] * normals[i][1] + axis[2] * normals[i][2]; - - // dn should be larger than mindp cutoff above - assert(dn > 0.f); - float t = dc / dn; - - maxt = (t > maxt) ? t : maxt; - } - - // cone apex should be in the negative half-space of all cluster triangles by construction - bounds.cone_apex[0] = center[0] - axis[0] * maxt; - bounds.cone_apex[1] = center[1] - axis[1] * maxt; - bounds.cone_apex[2] = center[2] - axis[2] * maxt; - - // note: this axis is the axis of the normal cone, but our test for perspective camera effectively negates the axis - bounds.cone_axis[0] = axis[0]; - bounds.cone_axis[1] = axis[1]; - bounds.cone_axis[2] = axis[2]; - - // cos(a) for normal cone is mindp; we need to add 90 degrees on both sides and invert the cone - // which gives us -cos(a+90) = -(-sin(a)) = sin(a) = sqrt(1 - cos^2(a)) - bounds.cone_cutoff = sqrtf(1 - mindp * mindp); - - // quantize axis & cutoff to 8-bit SNORM format - bounds.cone_axis_s8[0] = (signed char)(meshopt_quantizeSnorm(bounds.cone_axis[0], 8)); - bounds.cone_axis_s8[1] = (signed char)(meshopt_quantizeSnorm(bounds.cone_axis[1], 8)); - bounds.cone_axis_s8[2] = (signed char)(meshopt_quantizeSnorm(bounds.cone_axis[2], 8)); - - // for the 8-bit test to be conservative, we need to adjust the cutoff by measuring the max. error - float cone_axis_s8_e0 = fabsf(bounds.cone_axis_s8[0] / 127.f - bounds.cone_axis[0]); - float cone_axis_s8_e1 = fabsf(bounds.cone_axis_s8[1] / 127.f - bounds.cone_axis[1]); - float cone_axis_s8_e2 = fabsf(bounds.cone_axis_s8[2] / 127.f - bounds.cone_axis[2]); - - // note that we need to round this up instead of rounding to nearest, hence +1 - int cone_cutoff_s8 = int(127 * (bounds.cone_cutoff + cone_axis_s8_e0 + cone_axis_s8_e1 + cone_axis_s8_e2) + 1); - - bounds.cone_cutoff_s8 = (cone_cutoff_s8 > 127) ? 127 : (signed char)(cone_cutoff_s8); - - return bounds; -} - -meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices, const unsigned char* meshlet_triangles, size_t triangle_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) -{ - using namespace meshopt; - - assert(triangle_count <= kMeshletMaxTriangles); - assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256); - assert(vertex_positions_stride % sizeof(float) == 0); - - unsigned int indices[kMeshletMaxTriangles * 3]; - - for (size_t i = 0; i < triangle_count * 3; ++i) - { - unsigned int index = meshlet_vertices[meshlet_triangles[i]]; - assert(index < vertex_count); - - indices[i] = index; - } - - return meshopt_computeClusterBounds(indices, triangle_count * 3, vertex_positions, vertex_count, vertex_positions_stride); -} - -meshopt_Bounds meshopt_computeSphereBounds(const float* positions, size_t count, size_t positions_stride, const float* radii, size_t radii_stride) -{ - using namespace meshopt; - - assert(positions_stride >= 12 && positions_stride <= 256); - assert(positions_stride % sizeof(float) == 0); - assert((radii_stride >= 4 && radii_stride <= 256) || radii == NULL); - assert(radii_stride % sizeof(float) == 0); - - meshopt_Bounds bounds = {}; - - if (count == 0) - return bounds; - - const float rzero = 0.f; - - float psphere[4] = {}; - computeBoundingSphere(psphere, positions, count, positions_stride, radii ? radii : &rzero, radii ? radii_stride : 0, 7); - - bounds.center[0] = psphere[0]; - bounds.center[1] = psphere[1]; - bounds.center[2] = psphere[2]; - bounds.radius = psphere[3]; - - return bounds; -} - -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; - - // cache tracks vertex timestamps (corresponding to triangle index! all 3 vertices are added at the same time and never removed) - unsigned char cache[kMeshletMaxVertices]; - memset(cache, 0, vertex_count); - - // note that we start from a value that means all vertices aren't in cache - unsigned char cache_last = 128; - const unsigned char cache_cutoff = 3; // 3 triangles = ~5..9 vertices depending on reuse - - for (size_t i = 0; i < triangle_count; ++i) - { - int next = -1; - int next_match = -1; - - for (size_t j = i; j < triangle_count; ++j) - { - unsigned char a = indices[j * 3 + 0], b = indices[j * 3 + 1], c = indices[j * 3 + 2]; - assert(a < vertex_count && b < vertex_count && c < vertex_count); - - // score each triangle by how many vertices are in cache - // note: the distance is computed using unsigned 8-bit values, so cache timestamp overflow is handled gracefully - int aok = (unsigned char)(cache_last - cache[a]) < cache_cutoff; - int bok = (unsigned char)(cache_last - cache[b]) < cache_cutoff; - int cok = (unsigned char)(cache_last - cache[c]) < cache_cutoff; - - if (aok + bok + cok > next_match) - { - next = (int)j; - next_match = aok + bok + cok; - - // note that we could end up with all 3 vertices in the cache, but 2 is enough for ~strip traversal - if (next_match >= 2) - break; - } - } - - assert(next >= 0); - - unsigned char a = indices[next * 3 + 0], b = indices[next * 3 + 1], c = indices[next * 3 + 2]; - - // shift triangles before the next one forward so that we always keep an ordered partition - // note: this could have swapped triangles [i] and [next] but that distorts the order and may skew the output sequence - memmove(indices + (i + 1) * 3, indices + i * 3, (next - i) * 3 * sizeof(unsigned char)); - - indices[i * 3 + 0] = a; - indices[i * 3 + 1] = b; - indices[i * 3 + 2] = c; - - // cache timestamp is the same between all vertices of each triangle to reduce overflow - cache_last++; - cache[a] = cache_last; - cache[b] = cache_last; - cache[c] = cache_last; - } - - // reorder meshlet vertices for access locality assuming index buffer is scanned sequentially - unsigned int order[kMeshletMaxVertices]; - - short remap[kMeshletMaxVertices]; - memset(remap, -1, vertex_count * sizeof(short)); - - size_t vertex_offset = 0; - - for (size_t i = 0; i < triangle_count * 3; ++i) - { - short& r = remap[indices[i]]; - - if (r < 0) - { - r = short(vertex_offset); - order[vertex_offset] = vertices[indices[i]]; - vertex_offset++; - } - - indices[i] = (unsigned char)r; - } - - assert(vertex_offset <= vertex_count); - memcpy(vertices, order, vertex_offset * sizeof(unsigned int)); -} - #undef SIMD_SSE #undef SIMD_NEON diff --git a/thirdparty/meshoptimizer/indexanalyzer.cpp b/thirdparty/meshoptimizer/indexanalyzer.cpp index 87ceeae661..e8e975c274 100644 --- a/thirdparty/meshoptimizer/indexanalyzer.cpp +++ b/thirdparty/meshoptimizer/indexanalyzer.cpp @@ -74,7 +74,6 @@ meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const unsigned int* ind meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const unsigned int* indices, size_t index_count, size_t vertex_count, size_t vertex_size) { - assert(index_count % 3 == 0); assert(vertex_size > 0 && vertex_size <= 256); meshopt_Allocator allocator; diff --git a/thirdparty/meshoptimizer/indexcodec.cpp b/thirdparty/meshoptimizer/indexcodec.cpp index 7a8fd68675..8e6b3a4ae7 100644 --- a/thirdparty/meshoptimizer/indexcodec.cpp +++ b/thirdparty/meshoptimizer/indexcodec.cpp @@ -19,12 +19,6 @@ const int kDecodeIndexVersion = 1; typedef unsigned int VertexFifo[16]; typedef unsigned int EdgeFifo[16][2]; -static const unsigned int kTriangleIndexOrder[3][3] = { - {0, 1, 2}, - {1, 2, 0}, - {2, 0, 1}, -}; - static const unsigned char kCodeAuxEncodingTable[16] = { 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0, 0, // last two entries aren't used for encoding @@ -194,6 +188,8 @@ size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, cons int fecmax = version >= 1 ? 13 : 15; + static const int rotations[] = {0, 1, 2, 0, 1}; + // use static encoding table; it's possible to pack the result and then build an optimal table and repack // for now we keep it simple and use the table that has been generated based on symbol frequency on a training mesh set const unsigned char* codeaux_table = kCodeAuxEncodingTable; @@ -211,7 +207,7 @@ size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, cons if (fer >= 0 && (fer >> 2) < 15) { // note: getEdgeFifo implicitly rotates triangles by matching a/b to existing edge - const unsigned int* order = kTriangleIndexOrder[fer & 3]; + const int* order = rotations + (fer & 3); unsigned int a = indices[i + order[0]], b = indices[i + order[1]], c = indices[i + order[2]]; @@ -247,7 +243,7 @@ size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, cons else { int rotation = rotateTriangle(indices[i + 0], indices[i + 1], indices[i + 2], next); - const unsigned int* order = kTriangleIndexOrder[rotation]; + const int* order = rotations + rotation; unsigned int a = indices[i + order[0]], b = indices[i + order[1]], c = indices[i + order[2]]; diff --git a/thirdparty/meshoptimizer/indexgenerator.cpp b/thirdparty/meshoptimizer/indexgenerator.cpp index 4bf9fccad5..5bde1b48dd 100644 --- a/thirdparty/meshoptimizer/indexgenerator.cpp +++ b/thirdparty/meshoptimizer/indexgenerator.cpp @@ -311,7 +311,6 @@ size_t meshopt_generateVertexRemap(unsigned int* destination, const unsigned int using namespace meshopt; assert(indices || index_count == vertex_count); - assert(!indices || index_count % 3 == 0); assert(vertex_size > 0 && vertex_size <= 256); meshopt_Allocator allocator; @@ -325,7 +324,6 @@ size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const unsigne using namespace meshopt; assert(indices || index_count == vertex_count); - assert(index_count % 3 == 0); assert(stream_count > 0 && stream_count <= 16); for (size_t i = 0; i < stream_count; ++i) @@ -345,7 +343,6 @@ size_t meshopt_generateVertexRemapCustom(unsigned int* destination, const unsign using namespace meshopt; assert(indices || index_count == vertex_count); - assert(!indices || index_count % 3 == 0); assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256); assert(vertex_positions_stride % sizeof(float) == 0); @@ -393,8 +390,6 @@ void meshopt_remapVertexBuffer(void* destination, const void* vertices, size_t v void meshopt_remapIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const unsigned int* remap) { - assert(index_count % 3 == 0); - for (size_t i = 0; i < index_count; ++i) { unsigned int index = indices ? indices[i] : unsigned(i); diff --git a/thirdparty/meshoptimizer/meshletcodec.cpp b/thirdparty/meshoptimizer/meshletcodec.cpp new file mode 100644 index 0000000000..f6984a77b9 --- /dev/null +++ b/thirdparty/meshoptimizer/meshletcodec.cpp @@ -0,0 +1,1051 @@ +// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +#include "meshoptimizer.h" + +#include +#include + +// The block below auto-detects SIMD ISA that can be used on the target platform +#ifndef MESHOPTIMIZER_NO_SIMD + +// The SIMD implementation requires SSE4.1, which can be enabled unconditionally through compiler settings +#if defined(__AVX__) || defined(__SSE4_1__) +#define SIMD_SSE +#endif + +// MSVC supports compiling SSE4.1 code regardless of compile options; we use a cpuid-based scalar fallback +#if !defined(SIMD_SSE) && defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC))) +#define SIMD_SSE +#define SIMD_FALLBACK +#endif + +// GCC 4.9+ and clang 3.8+ support targeting SIMD ISA from individual functions; we use a cpuid-based scalar fallback +#if !defined(SIMD_SSE) && ((defined(__clang__) && __clang_major__ * 100 + __clang_minor__ >= 308) || (defined(__GNUC__) && __GNUC__ * 100 + __GNUC_MINOR__ >= 409)) && (defined(__i386__) || defined(__x86_64__)) +#define SIMD_SSE +#define SIMD_FALLBACK +#define SIMD_TARGET __attribute__((target("sse4.1"))) +#endif + +// When targeting AArch64, enable NEON SIMD unconditionally; we do not support SIMD decoding for 32-bit ARM +#if defined(__aarch64__) || (defined(_MSC_VER) && (defined(_M_ARM64) || defined(_M_ARM64EC)) && _MSC_VER >= 1922) +#define SIMD_NEON +#endif + +#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER > 1930 +#define SIMD_FLATTEN [[msvc::flatten]] +#elif defined(__GNUC__) || defined(__clang__) +#define SIMD_FLATTEN __attribute__((flatten)) +#else +#define SIMD_FLATTEN +#endif + +#ifndef SIMD_TARGET +#define SIMD_TARGET +#endif + +#endif // !MESHOPTIMIZER_NO_SIMD + +#ifdef SIMD_SSE +#include +#endif + +#ifdef SIMD_NEON +#include +#endif + +#if defined(SIMD_SSE) && defined(SIMD_FALLBACK) +#ifdef _MSC_VER +#include // __cpuid +#else +#include // __cpuid +#endif +#endif + +#ifndef TRACE +#define TRACE 0 +#endif + +#if TRACE +#include +#endif + +namespace meshopt +{ + +typedef unsigned int EdgeFifo8[8][2]; + +static int rotateTriangle(unsigned int a, unsigned int b, unsigned int c) +{ + return (a > b && a > c) ? 1 : (b > c ? 2 : 0); +} + +static int getEdgeFifo8(EdgeFifo8 fifo, unsigned int a, unsigned int b, unsigned int c, size_t offset) +{ + for (int i = 0; i < 8; ++i) + { + size_t index = (offset - 1 - i) & 7; + + unsigned int e0 = fifo[index][0]; + unsigned int e1 = fifo[index][1]; + + if (e0 == a && e1 == b) + return (i << 2) | 0; + if (e0 == b && e1 == c) + return (i << 2) | 1; + if (e0 == c && e1 == a) + return (i << 2) | 2; + } + + return -1; +} + +static void pushEdgeFifo8(EdgeFifo8 fifo, unsigned int a, unsigned int b, size_t& offset) +{ + fifo[offset][0] = a; + fifo[offset][1] = b; + offset = (offset + 1) & 7; +} + +static size_t encodeTriangles(unsigned char* codes, unsigned char* extra, const unsigned char* triangles, size_t triangle_count) +{ + EdgeFifo8 edgefifo; + memset(edgefifo, -1, sizeof(edgefifo)); + + size_t edgefifooffset = 0; + + unsigned int next = 0; + + // 4-bit triangle codes give us 16 options that we use as follows: + // 3*2 edge reuse (2 edges * 3 last triangles) * 2 next/explicit = 12 options + // 4 remaining options = next bits; 000, 001, 011, 111. + // triangles are rotated to make next bits line up. + memset(codes, 0, (triangle_count + 1) / 2); + + static const int rotations[] = {0, 1, 2, 0, 1}; + + unsigned char* start = extra; + + for (size_t i = 0; i < triangle_count; ++i) + { +#if TRACE > 1 + unsigned int last = next; +#endif + + int fer = getEdgeFifo8(edgefifo, triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2], edgefifooffset); + + if (fer >= 0 && (fer >> 2) < 6) + { + // note: getEdgeFifo8 implicitly rotates triangles by matching a/b to existing edge + const int* order = rotations + (fer & 3); + + unsigned int a = triangles[i * 3 + order[0]], b = triangles[i * 3 + order[1]], c = triangles[i * 3 + order[2]]; + + int fec = (c == next) ? (next++, 0) : 1; + +#if TRACE > 1 + printf("%3d+ | %3d %3d %3d | edge: e%d c%d\n", last, a, b, c, fer >> 2, fec); +#endif + + unsigned int code = (fer >> 2) * 2 + fec; + + codes[i / 2] |= (unsigned char)(code << ((i & 1) * 4)); + + if (fec) + *extra++ = (unsigned char)c; + + pushEdgeFifo8(edgefifo, c, b, edgefifooffset); + pushEdgeFifo8(edgefifo, a, c, edgefifooffset); + } + else + { + // rotate triangles to minimize the need for extra vertices + int rotation = rotateTriangle(triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2]); + const int* order = rotations + rotation; + + unsigned int a = triangles[i * 3 + order[0]], b = triangles[i * 3 + order[1]], c = triangles[i * 3 + order[2]]; + + // fe must be continuous: once a vertex is encoded with next, further vertices must also be encoded with next + int fea = (a == next && b == next + 1 && c == next + 2) ? (next++, 0) : 1; + int feb = (b == next && c == next + 1) ? (next++, 0) : 1; + int fec = (c == next) ? (next++, 0) : 1; + + assert(fea == 1 || feb == 0); + assert(feb == 1 || fec == 0); + +#if TRACE > 1 + printf("%3d+ | %3d %3d %3d | restart: %d%d%d\n", last, a, b, c, fea, feb, fec); +#endif + + unsigned int code = 12 + (fea + feb + fec); + + codes[i / 2] |= (unsigned char)(code << ((i & 1) * 4)); + + if (fea) + *extra++ = (unsigned char)a; + if (feb) + *extra++ = (unsigned char)b; + if (fec) + *extra++ = (unsigned char)c; + + pushEdgeFifo8(edgefifo, c, b, edgefifooffset); + pushEdgeFifo8(edgefifo, a, c, edgefifooffset); + } + } + + return extra - start; +} + +static size_t encodeVertices(unsigned char* ctrl, unsigned char* data, const unsigned int* vertices, size_t vertex_count) +{ + // grouped varint, 2 bit per value to indicate 0/1/2/3 byte deltas, with per-group 4-byte fallback + memset(ctrl, 0, (vertex_count + 3) / 4); + + unsigned char* start = data; + + unsigned int last = ~0u; + + for (size_t i = 0; i < vertex_count; i += 4) + { + unsigned int gv[4] = {}; + + for (int k = 0; k < 4 && i + k < vertex_count; ++k) + { + unsigned int d = vertices[i + k] - last - 1; + unsigned int v = (d << 1) ^ (int(d) >> 31); + + gv[k] = v; + last = vertices[i + k]; + } + + // if any value needs 4 bytes, or if *all* values need 3 bytes, we use 4 bytes for all values + // this allows us to encode most 3-byte deltas with 3 bytes which saves space overall + bool use4 = (gv[0] | gv[1] | gv[2] | gv[3]) > 0xffffff || (gv[0] > 0xffff && gv[1] > 0xffff && gv[2] > 0xffff && gv[3] > 0xffff); + + for (int k = 0; k < 4; ++k) + { + unsigned int v = gv[k]; + + // 0/1/2/3 bytes per value, or all 4 values use 4 bytes + int code = use4 ? 3 : (v == 0 ? 0 : (v < 256 ? 1 : (v < 65536 ? 2 : 3))); + + if (code > 0) + *data++ = (unsigned char)(v & 0xff); + if (code > 1) + *data++ = (unsigned char)((v >> 8) & 0xff); + if (code > 2) + *data++ = (unsigned char)((v >> 16) & 0xff); + if (use4) + *data++ = (unsigned char)((v >> 24) & 0xff); + + // split low and high bits into two nibbles for better packing + ctrl[i / 4] |= ((code & 1) << k) | ((code >> 1) << (k + 4)); + } + } + + return data - start; +} + +#if defined(SIMD_FALLBACK) || (!defined(SIMD_SSE) && !defined(SIMD_NEON)) +inline void writeTriangle(unsigned int* triangles, size_t i, unsigned int fifo) +{ + // output triangle is stored without extra edge vertex (0xcbac => 0xcba) + triangles[i] = fifo >> 8; +} + +inline void writeTriangle(unsigned char* triangles, size_t i, unsigned int fifo) +{ + triangles[i * 3 + 0] = (unsigned char)(fifo >> 8); + triangles[i * 3 + 1] = (unsigned char)(fifo >> 16); + triangles[i * 3 + 2] = (unsigned char)(fifo >> 24); +} + +template +static const unsigned char* decodeTriangles(T* triangles, const unsigned char* codes, const unsigned char* extra, const unsigned char* bound, size_t triangle_count) +{ + // branchlessly read next or extra vertex and advance pointers +#define NEXT(var, ec) \ + e = *extra; \ + unsigned int var = (ec) ? e : next; \ + extra += (ec), next += 1 - (ec) + + unsigned int next = 0; + unsigned int fifo[3] = {}; // two edge fifo entries in one uint: 0xcbac + + for (size_t i = 0; i < triangle_count; ++i) + { + if (extra > bound) + return NULL; + + unsigned int code = (codes[i / 2] >> ((i & 1) * 4)) & 0xF; + unsigned int tri; + + if (code < 12) + { + // reuse + unsigned int edge = fifo[code / 4]; + edge >>= (code << 3) & 16; // shift by 16 if bit 1 is set (odd edge for each triangle) + + // 0-1 extra vertices + unsigned int e; + NEXT(c, code & 1); + + // repack triangle into edge format (0xcbac) + tri = ((edge & 0xff) << 16) | (edge & 0xff00) | c | (c << 24); + } + else + { + // restart + int fea = code > 12; + int feb = code > 13; + int fec = code > 14; + + // 0-3 extra vertices + unsigned int e; + NEXT(a, fea); + NEXT(b, feb); + NEXT(c, fec); + + // repack triangle into edge format (0xcbac) + tri = c | (a << 8) | (b << 16) | (c << 24); + } + + writeTriangle(triangles, i, tri); + + fifo[2] = fifo[1]; + fifo[1] = fifo[0]; + fifo[0] = tri; + } + + return extra; + +#undef NEXT +} + +template +static const unsigned char* decodeVertices(V* vertices, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count) +{ + unsigned int last = ~0u; + + for (size_t i = 0; i < vertex_count; i += 4) + { + if (data > bound) + return NULL; + + unsigned char code4 = ctrl[i / 4]; + + for (int k = 0; k < 4; ++k) + { + int code = ((code4 >> k) & 1) | ((code4 >> (k + 3)) & 2); + int length = code4 == 0xff ? 4 : code; + + // branchlessly read up to 4 bytes + unsigned int mask = (length == 4) ? ~0u : (1 << (8 * length)) - 1; + unsigned int v = (data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)) & mask; + + // unzigzag + 1 + unsigned int d = (v >> 1) ^ -int(v & 1); + unsigned int r = last + d + 1; + + if (i + k < vertex_count) + vertices[i + k] = V(r); + + data += length; + last = r; + } + } + + return data; +} + +static int decodeMeshlet(void* vertices, void* triangles, const unsigned char* codes, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count, size_t triangle_count, size_t vertex_size, size_t triangle_size) +{ + if (vertex_size == 4) + data = decodeVertices(static_cast(vertices), ctrl, data, bound, vertex_count); + else + data = decodeVertices(static_cast(vertices), ctrl, data, bound, vertex_count); + if (!data) + return -2; + + if (triangle_size == 4) + data = decodeTriangles(static_cast(triangles), codes, data, bound, triangle_count); + else + data = decodeTriangles(static_cast(triangles), codes, data, bound, triangle_count); + if (!data) + return -2; + + return (data == bound) ? 0 : -3; +} +#endif + +#if defined(SIMD_SSE) || defined(SIMD_NEON) +// SIMD state is stored in a single 16b register as follows: +// 0..5: 6 next extra bytes +// 6..14: 9 bytes = 3 triangles worth of index data +// 15: 'next' byte + +// upon reading each triangle pair we need to transform this state such that the 9 bytes with triangle data contain the newly decoded triangles, +// which is a permutation of original state modulo per-element additions +// this transform can be chained to decode second triangle from original state; we create tables for 256 combinations of two 4-bit triangle codes +// the actual decoding becomes shuffle+add per triangle pair, plus management of extra bytes +static unsigned char kDecodeTableMasks[256][16]; +static unsigned char kDecodeTableExtra[256]; + +// for SIMD vertex decoding we need to unpack 4 values with 0-4 bytes in each +// this can be done with a single control-dependent shuffle per group +static unsigned char kDecodeTableVerts[256][16]; +static unsigned char kDecodeTableLength[256]; + +static bool decodeBuildTables() +{ +#define NEXT(var, ec) \ + shuf[var] = (ec) ? (unsigned char)extra : 15; \ + next[var] = (ec) ? 0 : (unsigned char)nextoff; \ + extra += (ec), nextoff += 1 - (ec) + + // check for SSE4.1 support if we have a fallback path +#if defined(SIMD_SSE) && defined(SIMD_FALLBACK) + int cpuinfo[4] = {}; +#ifdef _MSC_VER + __cpuid(cpuinfo, 1); +#else + __cpuid(1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]); +#endif + // bit 19 = SSE4.1 + if ((cpuinfo[2] & (1 << 19)) == 0) + return false; +#endif + + // fill triangle decoding tables for each combination of two triangle codes + for (int code = 0; code < 256; ++code) + { + unsigned char shuf[16] = {}; + unsigned char next[16] = {}; + int extra = 0; + int nextoff = 0; + + // state 0..5 will be refilled every iteration, so we ignore that + // state 6..8 will always contain the last decoded triangle because every triangle shifts fifo equally, so we can decode it independently + shuf[6] = 12; + shuf[7] = 13; + shuf[8] = 14; + + // state 15 will contain next (potentially incremented a few times) + shuf[15] = 15; + + // state 9..11 will contain the first decoded triangle (tri0), which can refer to extra/next and the original triangle history + // state 12..14 will contain the second decoded triangle (tri1); when decoding edge reuse, we need to handle edge 0/1 specially as it was just decoded earlier + for (int k = 0; k < 2; ++k) + { + int tri = (code >> (k * 4)) & 0xf; + + if (tri < 12) + { + if (k == 1 && tri / 4 == 0) + { + // we need to decode one of two edges from the triangle we just decoded earlier + // for that we simply need to copy shuf/next values for the two decoded indices + shuf[9 + k * 3] = shuf[9 + ((tri & 2) ? 2 : 0)]; + next[9 + k * 3] = next[9 + ((tri & 2) ? 2 : 0)]; + + shuf[10 + k * 3] = shuf[9 + ((tri & 2) ? 1 : 2)]; + next[10 + k * 3] = next[9 + ((tri & 2) ? 1 : 2)]; + } + else + { + // reuse: edge comes from the history based on edge index + // note: we reuse with an offset because last triangle in the original history was consumed by tri0 + int trioff = 6 + k * 3 + (2 - tri / 4) * 3; + + // edge cb or ac + shuf[9 + k * 3] = (unsigned char)(trioff + ((tri & 2) ? 2 : 0)); + shuf[10 + k * 3] = (unsigned char)(trioff + ((tri & 2) ? 1 : 2)); + } + + // third vertex is either next or comes from extra + NEXT(11 + k * 3, tri & 1); + } + else + { + // restart: three vertices, each comes from next or extra + int fea = tri > 12; + int feb = tri > 13; + int fec = tri > 14; + + NEXT(9 + k * 3, fea); + NEXT(10 + k * 3, feb); + NEXT(11 + k * 3, fec); + } + } + + // next needs to advance + next[15] = (unsigned char)nextoff; + + // next[0..8] = 0 trivially (never written to); next[9] must also be 0 because nextoff is 0 initially + // shuf[0..5] is not used, which allows us to pack next[10..15] + shuf[6..15] into a single 16-byte entry + assert(next[9] == 0); + memcpy(&kDecodeTableMasks[code][0], &next[10], 6); + memcpy(&kDecodeTableMasks[code][6], &shuf[6], 10); + kDecodeTableExtra[code] = (unsigned char)extra; + } + + // fill vertex decoding tables for each combination of four vertex references + for (unsigned int i = 0; i < 256; ++i) + { + unsigned char shuf[16] = {}; + int offset = 0; + + for (int k = 0; k < 4; ++k) + { + int code = ((i >> k) & 1) | ((i >> (k + 3)) & 2); + int length = i == 0xff ? 4 : code; // 0/1/2/3 bytes, or all 4 bytes if code==0xff + + shuf[k * 4 + 0] = (length > 0) ? (unsigned char)(offset + 0) : 0x80; + shuf[k * 4 + 1] = (length > 1) ? (unsigned char)(offset + 1) : 0x80; + shuf[k * 4 + 2] = (length > 2) ? (unsigned char)(offset + 2) : 0x80; + shuf[k * 4 + 3] = (length > 3) ? (unsigned char)(offset + 3) : 0x80; + + offset += length; + } + + memcpy(kDecodeTableVerts[i], shuf, sizeof(shuf)); + kDecodeTableLength[i] = (unsigned char)offset; + } + + return true; + +#undef NEXT +} + +static bool gDecodeTablesInitialized = decodeBuildTables(); +#endif + +#if defined(SIMD_SSE) +SIMD_TARGET +inline __m128i decodeTriangleGroup(__m128i state, unsigned char code, const unsigned char*& extra) +{ + __m128i shuf = _mm_loadu_si128(reinterpret_cast(kDecodeTableMasks[code])); + __m128i next = _mm_slli_si128(shuf, 10); + + // patch first 6 bytes with current extra and roll state forward + __m128i ext = _mm_loadl_epi64(reinterpret_cast(extra)); + state = _mm_blend_epi16(state, ext, 7); + state = _mm_add_epi8(_mm_shuffle_epi8(state, shuf), next); + + extra += kDecodeTableExtra[code]; + + return state; +} + +SIMD_TARGET +inline __m128i decodeVertexGroup(__m128i last, unsigned char code, const unsigned char*& data) +{ + __m128i word = _mm_loadu_si128(reinterpret_cast(data)); + __m128i shuf = _mm_loadu_si128(reinterpret_cast(kDecodeTableVerts[code])); + + __m128i v = _mm_shuffle_epi8(word, shuf); + + // unzigzag+1 + __m128i xl = _mm_sub_epi32(_mm_setzero_si128(), _mm_and_si128(v, _mm_set1_epi32(1))); + __m128i xr = _mm_srli_epi32(v, 1); + __m128i x = _mm_add_epi32(_mm_xor_si128(xl, xr), _mm_set1_epi32(1)); + + // prefix sum + x = _mm_add_epi32(x, _mm_slli_si128(x, 8)); + x = _mm_add_epi32(x, _mm_slli_si128(x, 4)); + x = _mm_add_epi32(x, _mm_shuffle_epi32(last, 0xff)); + + data += kDecodeTableLength[code]; + + return x; +} +#endif + +#if defined(SIMD_NEON) +SIMD_TARGET +inline uint8x16_t decodeTriangleGroup(uint8x16_t state, unsigned char code, const unsigned char*& extra) +{ + uint8x16_t shuf = vld1q_u8(kDecodeTableMasks[code]); + uint8x16_t next = vextq_u8(vdupq_n_u8(0), shuf, 6); + + // patch first 6 bytes with current extra and roll state forward + uint8x8_t extl = vld1_u8(extra); + uint8x16_t ext = vcombine_u8(extl, vdup_n_u8(0)); + state = vbslq_u8(vcombine_u8(vcreate_u8(0xffffffffffffull), vdup_n_u8(0)), ext, state); + state = vaddq_u8(vqtbl1q_u8(state, shuf), next); + + extra += kDecodeTableExtra[code]; + + return state; +} + +SIMD_TARGET +inline uint32x4_t decodeVertexGroup(uint32x4_t last, unsigned char code, const unsigned char*& data) +{ + uint8x16_t word = vld1q_u8(data); + uint8x16_t shuf = vld1q_u8(kDecodeTableVerts[code]); + + uint32x4_t v = vreinterpretq_u32_u8(vqtbl1q_u8(word, shuf)); + + // unzigzag+1 + uint32x4_t xl = vsubq_u32(vdupq_n_u32(0), vandq_u32(v, vdupq_n_u32(1))); + uint32x4_t xr = vshrq_n_u32(v, 1); + uint32x4_t x = vaddq_u32(veorq_u32(xl, xr), vdupq_n_u32(1)); + + // prefix sum + x = vaddq_u32(x, vextq_u32(vdupq_n_u32(0), x, 2)); + x = vaddq_u32(x, vextq_u32(vdupq_n_u32(0), x, 3)); + x = vaddq_u32(x, vdupq_n_u32(vgetq_lane_u32(last, 3))); + + data += kDecodeTableLength[code]; + + return x; +} +#endif + +#if defined(SIMD_SSE) +#ifdef __GNUC__ +typedef int __attribute__((aligned(1))) unaligned_int; +#else +typedef int unaligned_int; +#endif +#endif + +#if defined(SIMD_SSE) || defined(SIMD_NEON) +SIMD_TARGET +static const unsigned char* decodeTrianglesSimd(unsigned int* triangles, const unsigned char* codes, const unsigned char* extra, const unsigned char* bound, size_t triangle_count) +{ +#if defined(SIMD_SSE) + __m128i repack = _mm_setr_epi8(9, 10, 11, -1, 12, 13, 14, -1, 0, 0, 0, 0, 0, 0, 0, 0); + __m128i state = _mm_setzero_si128(); +#elif defined(SIMD_NEON) + uint8x8_t repack = vcreate_u8(0xff0e0d0cff0b0a09ull); + uint8x16_t state = vdupq_n_u8(0); +#endif + + size_t groups = triangle_count / 2; + + // process all complete groups + for (size_t i = 0; i < groups; ++i) + { + unsigned char code = *codes++; + + if (extra > bound) + return NULL; + + state = decodeTriangleGroup(state, code, extra); + + // write 6 bytes of new triangle data into output, formatted as 8 bytes with 0 padding +#if defined(SIMD_SSE) + __m128i r = _mm_shuffle_epi8(state, repack); + _mm_storel_epi64(reinterpret_cast<__m128i*>(&triangles[i * 2]), r); +#elif defined(SIMD_NEON) + uint32x2_t r = vreinterpret_u32_u8(vqtbl1_u8(state, repack)); + vst1_u32(&triangles[i * 2], r); +#endif + } + + // process a 1 triangle tail; to maintain the memory safety guarantee we have to write a 32-bit element + if (triangle_count & 1) + { + unsigned char code = *codes++; + + if (extra > bound) + return NULL; + + state = decodeTriangleGroup(state, code, extra); + + unsigned int* tail = &triangles[triangle_count & ~1u]; + +#if defined(SIMD_SSE) + __m128i r = _mm_shuffle_epi8(state, repack); + *tail = unsigned(_mm_cvtsi128_si32(r)); +#elif defined(SIMD_NEON) + uint32x2_t r = vreinterpret_u32_u8(vqtbl1_u8(state, repack)); + vst1_lane_u32(tail, r, 0); +#endif + } + + return extra; +} + +SIMD_TARGET +static const unsigned char* decodeTrianglesSimd(unsigned char* triangles, const unsigned char* codes, const unsigned char* extra, const unsigned char* bound, size_t triangle_count) +{ +#if defined(SIMD_SSE) + __m128i state = _mm_setzero_si128(); +#elif defined(SIMD_NEON) + uint8x16_t state = vdupq_n_u8(0); +#endif + + // because the output buffer is guaranteed to have 32-bit aligned size available, we can optimize writes and tail processing + // instead of processing triangles 2 at a time, we process 2 *pairs* at a time (12-byte write) followed by a tail pair, if present + // if the number of triangles mod 4 is 3, we'd normally need to write 12k+9 bytes, but we can instead overwrite up to 3 bytes in the main loop + size_t groups = (triangle_count + 1) / 4; + + // process all complete groups + for (size_t i = 0; i < groups; ++i) + { + unsigned char code0 = *codes++; + unsigned char code1 = *codes++; + + // each triangle pair reads <=6 bytes from extra, so two pairs need <=12 bytes and gap guarantees 16 byte of overread + if (extra > bound) + return NULL; + + state = decodeTriangleGroup(state, code0, extra); + + // write first decoded triangle and first index of second decoded triangle +#if defined(SIMD_SSE) + __m128i r0 = _mm_srli_si128(state, 9); + *reinterpret_cast(&triangles[i * 12]) = _mm_cvtsi128_si32(r0); +#elif defined(SIMD_NEON) + uint8x16_t r0 = vextq_u8(state, vdupq_n_u8(0), 9); + vst1q_lane_u32(reinterpret_cast(&triangles[i * 12]), vreinterpretq_u32_u8(r0), 0); +#endif + + state = decodeTriangleGroup(state, code1, extra); + + // write last two indices of second decoded triangle that we didn't write above plus two new ones + // note that the second decoded triangle has shifted down to 6-8 bytes, hence shift by 7 +#if defined(SIMD_SSE) + __m128i r1 = _mm_srli_si128(state, 7); + _mm_storel_epi64(reinterpret_cast<__m128i*>(&triangles[i * 12 + 4]), r1); +#elif defined(SIMD_NEON) + uint8x16_t r1 = vextq_u8(state, vdupq_n_u8(0), 7); + vst1_u8(&triangles[i * 12 + 4], vget_low_u8(r1)); +#endif + } + + // process a 1-2 triangle tail; to maintain the memory safety guarantee we have to write 1-2 32-bit elements + if (groups * 4 < triangle_count) + { + unsigned char code = *codes++; + + if (extra > bound) + return NULL; + + state = decodeTriangleGroup(state, code, extra); + + unsigned char* tail = &triangles[(triangle_count & ~3u) * 3]; + +#if defined(SIMD_SSE) + __m128i r = _mm_srli_si128(state, 9); + + *reinterpret_cast(tail) = _mm_cvtsi128_si32(r); + if ((triangle_count & 3) > 1) + *reinterpret_cast(tail + 4) = _mm_extract_epi32(r, 1); +#elif defined(SIMD_NEON) + uint8x16_t r = vextq_u8(state, vdupq_n_u8(0), 9); + + vst1q_lane_u32(reinterpret_cast(tail), vreinterpretq_u32_u8(r), 0); + if ((triangle_count & 3) > 1) + vst1q_lane_u32(reinterpret_cast(tail + 4), vreinterpretq_u32_u8(r), 1); +#endif + } + + return extra; +} + +SIMD_TARGET +static const unsigned char* decodeVerticesSimd(unsigned int* vertices, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count) +{ +#if defined(SIMD_SSE) + __m128i last = _mm_set1_epi32(-1); +#elif defined(SIMD_NEON) + uint32x4_t last = vdupq_n_u32(~0u); +#endif + + size_t groups = vertex_count / 4; + + // process all complete groups + for (size_t i = 0; i < groups; ++i) + { + unsigned char code = *ctrl++; + if (data > bound) + return NULL; + + last = decodeVertexGroup(last, code, data); + +#if defined(SIMD_SSE) + _mm_storeu_si128(reinterpret_cast<__m128i*>(&vertices[i * 4]), last); +#elif defined(SIMD_NEON) + vst1q_u32(&vertices[i * 4], last); +#endif + } + + // process a 1-3 vertex tail; to maintain the memory safety guarantee we have to write individual elements + if (vertex_count & 3) + { + unsigned char code = *ctrl++; + + if (data > bound) + return NULL; + + last = decodeVertexGroup(last, code, data); + + unsigned int* tail = &vertices[vertex_count & ~3u]; + +#if defined(SIMD_SSE) + tail[0] = _mm_cvtsi128_si32(last); + if ((vertex_count & 3) > 1) + tail[1] = _mm_extract_epi32(last, 1); + if ((vertex_count & 3) > 2) + tail[2] = _mm_extract_epi32(last, 2); +#elif defined(SIMD_NEON) + vst1q_lane_u32(&tail[0], last, 0); + if ((vertex_count & 3) > 1) + vst1q_lane_u32(&tail[1], last, 1); + if ((vertex_count & 3) > 2) + vst1q_lane_u32(&tail[2], last, 2); +#endif + } + + return data; +} + +SIMD_TARGET +static const unsigned char* decodeVerticesSimd(unsigned short* vertices, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count) +{ +#if defined(SIMD_SSE) + __m128i repack = _mm_setr_epi8(0, 1, 4, 5, 8, 9, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0); + __m128i last = _mm_set1_epi32(-1); +#elif defined(SIMD_NEON) + uint32x4_t last = vdupq_n_u32(~0u); +#endif + + // because the output buffer is guaranteed to have 32-bit aligned size available, we can simplify tail processing + // if the number of vertices mod 4 is 3, we'd normally need to write 8+6 bytes, but we can instead overwrite up to 2 bytes in the main loop + size_t groups = (vertex_count + 1) / 4; + + // process all complete groups + for (size_t i = 0; i < groups; ++i) + { + unsigned char code = *ctrl++; + + if (data > bound) + return NULL; + + last = decodeVertexGroup(last, code, data); + +#if defined(SIMD_SSE) + __m128i r = _mm_shuffle_epi8(last, repack); + _mm_storel_epi64(reinterpret_cast<__m128i*>(&vertices[i * 4]), r); +#elif defined(SIMD_NEON) + uint16x4_t r = vmovn_u32(last); + vst1_u16(&vertices[i * 4], r); +#endif + } + + // process a 1-2 vertex tail; to maintain the memory safety guarantee we have to write a 32-bit element + if (groups * 4 < vertex_count) + { + unsigned char code = *ctrl++; + + if (data > bound) + return NULL; + + last = decodeVertexGroup(last, code, data); + + unsigned short* tail = &vertices[vertex_count & ~3u]; + +#if defined(SIMD_SSE) + __m128i r = _mm_shufflelo_epi16(last, 8); + *reinterpret_cast(tail) = _mm_cvtsi128_si32(r); +#elif defined(SIMD_NEON) + uint16x4_t r = vmovn_u32(last); + vst1_lane_u32(reinterpret_cast(tail), vreinterpret_u32_u16(r), 0); +#endif + } + + return data; +} + +template +SIMD_TARGET SIMD_FLATTEN static int +decodeMeshletSimd(void* vertices, void* triangles, const unsigned char* codes, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count, size_t triangle_count, size_t vertex_size, size_t triangle_size) +{ + assert(gDecodeTablesInitialized); + (void)gDecodeTablesInitialized; + +#ifdef __clang__ + // data is guaranteed to be non-null initially; if decode loops never hit bounds errors, it remains non-null + __builtin_assume(data); +#endif + + // decodes 4 vertices at a time with tail processing; writes up to align(vertex_size * vertex_count, 4) + // raw decoding skips tail processing by rounding up vertex count; it's safe because output buffer is guaranteed to have extra space, and tail control data is 0 + if (vertex_size == 4 || Raw) + data = decodeVerticesSimd(static_cast(vertices), ctrl, data, bound, Raw ? (vertex_count + 3) & ~3 : vertex_count); + else + data = decodeVerticesSimd(static_cast(vertices), ctrl, data, bound, vertex_count); + if (!data) + return -2; + + // decodes 2/4 triangles at a time with tail processing; writes up to align(triangle_size * triangle_count, 4) + // raw decoding skips tail processing by rounding up triangle count; it's safe because output buffer is guaranteed to have extra space, and tail code data is 0 + if (triangle_size == 4 || Raw) + data = decodeTrianglesSimd(static_cast(triangles), codes, data, bound, Raw ? (triangle_count + 1) & ~1 : triangle_count); + else + data = decodeTrianglesSimd(static_cast(triangles), codes, data, bound, triangle_count); + if (!data) + return -2; + + return (data == bound) ? 0 : -3; +} +#endif + +} // namespace meshopt + +size_t meshopt_encodeMeshletBound(size_t max_vertices, size_t max_triangles) +{ + size_t codes_size = (max_triangles + 1) / 2; + size_t extra_size = max_triangles * 3; + + size_t ctrl_size = (max_vertices + 3) / 4; + size_t data_size = (max_vertices + 3) / 4 * 16; // worst case: 16 bytes per vertex group + + size_t gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0; + + return codes_size + extra_size + ctrl_size + data_size + gap_size; +} + +size_t meshopt_encodeMeshlet(unsigned char* buffer, size_t buffer_size, const unsigned int* vertices, size_t vertex_count, const unsigned char* triangles, size_t triangle_count) +{ + using namespace meshopt; + + assert(triangle_count <= 256 && vertex_count <= 256); + + // 4 bits per triangle + up to three bytes of extra data + unsigned char codes[256 / 2]; + unsigned char extra[256 * 3]; + size_t codes_size = (triangle_count + 1) / 2; + size_t extra_size = encodeTriangles(codes, extra, triangles, triangle_count); + assert(extra_size <= sizeof(extra)); + + // 2 bits per vertex + up to 4 bytes of actual data + unsigned char ctrl[256 / 4]; + unsigned char data[256 * 4]; + size_t ctrl_size = (vertex_count + 3) / 4; + size_t data_size = encodeVertices(ctrl, data, vertices, vertex_count); + assert(data_size <= sizeof(data)); + + // we need to ensure that up to 16 bytes after extra+data are available for SIMD decoding + // to minimize overhead, we place fixed-size codes+control at the end of the buffer + size_t gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0; + + size_t result = codes_size + extra_size + ctrl_size + data_size + gap_size; + + if (result > buffer_size) + return 0; + + // variable-size data first + memcpy(buffer, data, data_size); + buffer += data_size; + memcpy(buffer, extra, extra_size); + buffer += extra_size; + + // gap (for accelerated decoding) separates variable-size and fixed-size data + memset(buffer, 0, gap_size); + buffer += gap_size; + + // fixed-size data last; it can be located from buffer end during decoding + memcpy(buffer, ctrl, ctrl_size); + buffer += ctrl_size; + memcpy(buffer, codes, codes_size); + buffer += codes_size; + +#if TRACE > 1 + printf("extra:"); + for (size_t i = 0; i < extra_size; ++i) + printf(" %d", extra[i]); + printf("\n"); + + unsigned int minv = ~0u; + for (size_t i = 0; i < vertex_count; ++i) + minv = minv < vertices[i] ? minv : vertices[i]; + + printf("vertices: [%d+]", minv); + for (size_t i = 0; i < vertex_count; ++i) + printf(" %d", vertices[i] - minv); + printf("\n"); +#endif + +#if TRACE + printf("stats: %d vertices, %d triangles => %d bytes (triangles: %d codes, %d extra; vertices: %d control, %d data; %d gap)\n", + int(vertex_count), int(triangle_count), int(result), + int(codes_size), int(extra_size), int(ctrl_size), int(data_size), int(gap_size)); +#endif + + return result; +} + +int meshopt_decodeMeshlet(void* vertices, size_t vertex_count, size_t vertex_size, void* triangles, size_t triangle_count, size_t triangle_size, const unsigned char* buffer, size_t buffer_size) +{ + using namespace meshopt; + + assert(triangle_count <= 256 && vertex_count <= 256); + assert(vertex_size == 4 || vertex_size == 2); + assert(triangle_size == 4 || triangle_size == 3); + + // layout must match encoding + size_t codes_size = (triangle_count + 1) / 2; + size_t ctrl_size = (vertex_count + 3) / 4; + size_t gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0; + + if (buffer_size < codes_size + ctrl_size + gap_size) + return -2; + + const unsigned char* end = buffer + buffer_size; + const unsigned char* codes = end - codes_size; + const unsigned char* ctrl = codes - ctrl_size; + const unsigned char* data = buffer; + + // gap ensures we have at least 16 bytes available after bound; this allows SIMD decoders to over-read safely + const unsigned char* bound = ctrl - gap_size; + assert(bound >= buffer && bound + 16 <= buffer + buffer_size); + +#if defined(SIMD_FALLBACK) + return (gDecodeTablesInitialized ? decodeMeshletSimd<0> : decodeMeshlet)(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, vertex_size, triangle_size); +#elif defined(SIMD_SSE) || defined(SIMD_NEON) + return decodeMeshletSimd<0>(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, vertex_size, triangle_size); +#else + return decodeMeshlet(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, vertex_size, triangle_size); +#endif +} + +int meshopt_decodeMeshletRaw(unsigned int* vertices, size_t vertex_count, unsigned int* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size) +{ + using namespace meshopt; + + assert(triangle_count <= 256 && vertex_count <= 256); + + // layout must match encoding + size_t codes_size = (triangle_count + 1) / 2; + size_t ctrl_size = (vertex_count + 3) / 4; + size_t gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0; + + if (buffer_size < codes_size + ctrl_size + gap_size) + return -2; + + const unsigned char* end = buffer + buffer_size; + const unsigned char* codes = end - codes_size; + const unsigned char* ctrl = codes - ctrl_size; + const unsigned char* data = buffer; + + // gap ensures we have at least 16 bytes available after bound; this allows SIMD decoders to over-read safely + const unsigned char* bound = ctrl - gap_size; + assert(bound >= buffer && bound + 16 <= buffer + buffer_size); + +#if defined(SIMD_FALLBACK) + return (gDecodeTablesInitialized ? decodeMeshletSimd<1> : decodeMeshlet)(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, 4, 4); +#elif defined(SIMD_SSE) || defined(SIMD_NEON) + return decodeMeshletSimd<1>(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, 4, 4); +#else + return decodeMeshlet(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, 4, 4); +#endif +} + +#undef SIMD_SSE +#undef SIMD_NEON +#undef SIMD_FALLBACK +#undef SIMD_FLATTEN +#undef SIMD_TARGET diff --git a/thirdparty/meshoptimizer/meshletutils.cpp b/thirdparty/meshoptimizer/meshletutils.cpp new file mode 100644 index 0000000000..e91969e58c --- /dev/null +++ b/thirdparty/meshoptimizer/meshletutils.cpp @@ -0,0 +1,613 @@ +// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +#include "meshoptimizer.h" + +#include +#include +#include +#include + +// This work is based on: +// Matthaeus Chajdas. GeometryFX 1.2 - Cluster Culling. 2016 +// Jack Ritter. An Efficient Bounding Sphere. 1990 +// Thomas Larsson. Fast and Tight Fitting Bounding Spheres. 2008 +namespace meshopt +{ + +// This must be <= 256 since meshlet indices are stored as bytes +const size_t kMeshletMaxVertices = 256; + +// A reasonable limit is around 2*max_vertices or less +const size_t kMeshletMaxTriangles = 512; + +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, const unsigned int* indices = NULL) +{ + static const float axes[7][3] = { + // X, Y, Z + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + + // XYZ, -XYZ, X-YZ, XY-Z; normalized to unit length + {0.57735026f, 0.57735026f, 0.57735026f}, + {-0.57735026f, 0.57735026f, 0.57735026f}, + {0.57735026f, -0.57735026f, 0.57735026f}, + {0.57735026f, 0.57735026f, -0.57735026f}, + }; + + assert(count > 0); + assert(axis_count <= sizeof(axes) / sizeof(axes[0])); + + size_t points_stride_float = points_stride / sizeof(float); + size_t radii_stride_float = radii_stride / sizeof(float); + + // find extremum points along all axes; for each axis we get a pair of points with min/max coordinates + unsigned int pmin[7], pmax[7]; + float tmin[7], tmax[7]; + + for (size_t axis = 0; axis < axis_count; ++axis) + { + pmin[axis] = pmax[axis] = 0; + tmin[axis] = FLT_MAX; + tmax[axis] = -FLT_MAX; + } + + for (size_t i = 0; i < count; ++i) + { + unsigned int v = indices ? indices[i] : unsigned(i); + const float* p = points + v * points_stride_float; + float r = radii[v * radii_stride_float]; + + for (size_t axis = 0; axis < axis_count; ++axis) + { + const float* ax = axes[axis]; + + float tp = ax[0] * p[0] + ax[1] * p[1] + ax[2] * p[2]; + float tpmin = tp - r, tpmax = tp + r; + + pmin[axis] = (tpmin < tmin[axis]) ? v : pmin[axis]; + pmax[axis] = (tpmax > tmax[axis]) ? v : pmax[axis]; + tmin[axis] = (tpmin < tmin[axis]) ? tpmin : tmin[axis]; + tmax[axis] = (tpmax > tmax[axis]) ? tpmax : tmax[axis]; + } + } + + // find the pair of points with largest distance + size_t paxis = 0; + float paxisdr = 0; + + for (size_t axis = 0; axis < axis_count; ++axis) + { + const float* p1 = points + pmin[axis] * points_stride_float; + const float* p2 = points + pmax[axis] * points_stride_float; + float r1 = radii[pmin[axis] * radii_stride_float]; + float r2 = radii[pmax[axis] * radii_stride_float]; + + float d2 = (p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1]) + (p2[2] - p1[2]) * (p2[2] - p1[2]); + float dr = sqrtf(d2) + r1 + r2; + + if (dr > paxisdr) + { + paxisdr = dr; + paxis = axis; + } + } + + // use the longest segment as the initial sphere diameter + const float* p1 = points + pmin[paxis] * points_stride_float; + const float* p2 = points + pmax[paxis] * points_stride_float; + float r1 = radii[pmin[paxis] * radii_stride_float]; + float r2 = radii[pmax[paxis] * radii_stride_float]; + + float paxisd = sqrtf((p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1]) + (p2[2] - p1[2]) * (p2[2] - p1[2])); + float paxisk = paxisd > 0 ? (paxisd + r2 - r1) / (2 * paxisd) : 0.f; + + float center[3] = {p1[0] + (p2[0] - p1[0]) * paxisk, p1[1] + (p2[1] - p1[1]) * paxisk, p1[2] + (p2[2] - p1[2]) * paxisk}; + float radius = paxisdr / 2; + + // iteratively adjust the sphere up until all points fit + for (size_t i = 0; i < count; ++i) + { + unsigned int v = indices ? indices[i] : unsigned(i); + const float* p = points + v * points_stride_float; + float r = radii[v * radii_stride_float]; + + float d2 = (p[0] - center[0]) * (p[0] - center[0]) + (p[1] - center[1]) * (p[1] - center[1]) + (p[2] - center[2]) * (p[2] - center[2]); + float d = sqrtf(d2); + + if (d + r > radius) + { + float k = d > 0 ? (d + r - radius) / (2 * d) : 0.f; + + center[0] += k * (p[0] - center[0]); + center[1] += k * (p[1] - center[1]); + center[2] += k * (p[2] - center[2]); + radius = (radius + d + r) / 2; + } + } + + result[0] = center[0]; + result[1] = center[1]; + result[2] = center[2]; + result[3] = radius; +} + +static meshopt_Bounds computeClusterBounds(const unsigned int* indices, size_t index_count, const unsigned int* corners, size_t corner_count, const float* vertex_positions, size_t vertex_positions_stride) +{ + size_t vertex_stride_float = vertex_positions_stride / sizeof(float); + + // compute triangle normals (.w completes plane equation) + float normals[kMeshletMaxTriangles][4]; + size_t triangles = 0; + + for (size_t i = 0; i < index_count; i += 3) + { + unsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2]; + + const float* p0 = vertex_positions + vertex_stride_float * a; + const float* p1 = vertex_positions + vertex_stride_float * b; + const float* p2 = vertex_positions + vertex_stride_float * c; + + float p10[3] = {p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]}; + float p20[3] = {p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]}; + + float normalx = p10[1] * p20[2] - p10[2] * p20[1]; + float normaly = p10[2] * p20[0] - p10[0] * p20[2]; + float normalz = p10[0] * p20[1] - p10[1] * p20[0]; + + float area = sqrtf(normalx * normalx + normaly * normaly + normalz * normalz); + + // no need to include degenerate triangles - they will be invisible anyway + if (area == 0.f) + continue; + + normalx /= area; + normaly /= area; + normalz /= area; + + // record triangle normals; normal and corner 0 define a plane equation + normals[triangles][0] = normalx; + normals[triangles][1] = normaly; + normals[triangles][2] = normalz; + normals[triangles][3] = -(normalx * p0[0] + normaly * p0[1] + normalz * p0[2]); + triangles++; + } + + meshopt_Bounds bounds = {}; + + // degenerate cluster, no valid triangles => trivial reject (cone data is 0) + if (triangles == 0) + return bounds; + + const float rzero = 0.f; + + // compute cluster bounding sphere; we'll use the center to determine normal cone apex as well + float psphere[4] = {}; + computeBoundingSphere(psphere, vertex_positions, corner_count, vertex_positions_stride, &rzero, 0, 7, corners); + + float center[3] = {psphere[0], psphere[1], psphere[2]}; + + // treating triangle normals as points, find the bounding sphere - the sphere center determines the optimal cone axis + float nsphere[4] = {}; + computeBoundingSphere(nsphere, normals[0], triangles, sizeof(float) * 4, &rzero, 0, 3); + + float axis[3] = {nsphere[0], nsphere[1], nsphere[2]}; + float axislength = sqrtf(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]); + float invaxislength = axislength == 0.f ? 0.f : 1.f / axislength; + + axis[0] *= invaxislength; + axis[1] *= invaxislength; + axis[2] *= invaxislength; + + // compute a tight cone around all normals, mindp = cos(angle/2) + float mindp = 1.f; + + for (size_t i = 0; i < triangles; ++i) + { + float dp = normals[i][0] * axis[0] + normals[i][1] * axis[1] + normals[i][2] * axis[2]; + + mindp = (dp < mindp) ? dp : mindp; + } + + // fill bounding sphere info; note that below we can return bounds without cone information for degenerate cones + bounds.center[0] = center[0]; + bounds.center[1] = center[1]; + bounds.center[2] = center[2]; + bounds.radius = psphere[3]; + + // degenerate cluster, normal cone is larger than a hemisphere => trivial accept + // note that if mindp is positive but close to 0, the triangle intersection code below gets less stable + // we arbitrarily decide that if a normal cone is ~168 degrees wide or more, the cone isn't useful + if (mindp <= 0.1f) + { + bounds.cone_cutoff = 1; + bounds.cone_cutoff_s8 = 127; + return bounds; + } + + float maxt = 0; + + // we need to find the point on center-t*axis ray that lies in negative half-space of all triangles + for (size_t i = 0; i < triangles; ++i) + { + // dot(center-t*axis-corner, trinormal) = 0 + // dot(center-corner, trinormal) - t * dot(axis, trinormal) = 0 + float dc = center[0] * normals[i][0] + center[1] * normals[i][1] + center[2] * normals[i][2] + normals[i][3]; + float dn = axis[0] * normals[i][0] + axis[1] * normals[i][1] + axis[2] * normals[i][2]; + + // dn should be larger than mindp cutoff above + assert(dn > 0.f); + float t = dc / dn; + + maxt = (t > maxt) ? t : maxt; + } + + // cone apex should be in the negative half-space of all cluster triangles by construction + bounds.cone_apex[0] = center[0] - axis[0] * maxt; + bounds.cone_apex[1] = center[1] - axis[1] * maxt; + bounds.cone_apex[2] = center[2] - axis[2] * maxt; + + // note: this axis is the axis of the normal cone, but our test for perspective camera effectively negates the axis + bounds.cone_axis[0] = axis[0]; + bounds.cone_axis[1] = axis[1]; + bounds.cone_axis[2] = axis[2]; + + // cos(a) for normal cone is mindp; we need to add 90 degrees on both sides and invert the cone + // which gives us -cos(a+90) = -(-sin(a)) = sin(a) = sqrt(1 - cos^2(a)) + bounds.cone_cutoff = sqrtf(1 - mindp * mindp); + + // quantize axis & cutoff to 8-bit SNORM format + bounds.cone_axis_s8[0] = (signed char)(meshopt_quantizeSnorm(bounds.cone_axis[0], 8)); + bounds.cone_axis_s8[1] = (signed char)(meshopt_quantizeSnorm(bounds.cone_axis[1], 8)); + bounds.cone_axis_s8[2] = (signed char)(meshopt_quantizeSnorm(bounds.cone_axis[2], 8)); + + // for the 8-bit test to be conservative, we need to adjust the cutoff by measuring the max. error + float cone_axis_s8_e0 = fabsf(bounds.cone_axis_s8[0] / 127.f - bounds.cone_axis[0]); + float cone_axis_s8_e1 = fabsf(bounds.cone_axis_s8[1] / 127.f - bounds.cone_axis[1]); + float cone_axis_s8_e2 = fabsf(bounds.cone_axis_s8[2] / 127.f - bounds.cone_axis[2]); + + // note that we need to round this up instead of rounding to nearest, hence +1 + int cone_cutoff_s8 = int(127 * (bounds.cone_cutoff + cone_axis_s8_e0 + cone_axis_s8_e1 + cone_axis_s8_e2) + 1); + + bounds.cone_cutoff_s8 = (cone_cutoff_s8 > 127) ? 127 : (signed char)(cone_cutoff_s8); + + return bounds; +} + +} // namespace meshopt + +meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) +{ + using namespace meshopt; + + assert(index_count % 3 == 0); + assert(index_count / 3 <= kMeshletMaxTriangles); + assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256); + assert(vertex_positions_stride % sizeof(float) == 0); + + (void)vertex_count; + + unsigned int cache[512]; + memset(cache, -1, sizeof(cache)); + + unsigned int corners[kMeshletMaxTriangles * 3 + 1]; // +1 for branchless slot + size_t corner_count = 0; + + for (size_t i = 0; i < index_count; ++i) + { + unsigned int v = indices[i]; + assert(v < vertex_count); + + unsigned int& c = cache[v & (sizeof(cache) / sizeof(cache[0]) - 1)]; + + // branchless append if vertex isn't in cache + corners[corner_count] = v; + corner_count += (c != v); + c = v; + } + + return computeClusterBounds(indices, index_count, corners, corner_count, vertex_positions, vertex_positions_stride); +} + +meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices, const unsigned char* meshlet_triangles, size_t triangle_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) +{ + using namespace meshopt; + + assert(triangle_count <= kMeshletMaxTriangles); + assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256); + assert(vertex_positions_stride % sizeof(float) == 0); + + (void)vertex_count; + + unsigned int indices[kMeshletMaxTriangles * 3]; + size_t corner_count = 0; + + for (size_t i = 0; i < triangle_count * 3; ++i) + { + unsigned char t = meshlet_triangles[i]; + unsigned int index = meshlet_vertices[t]; + assert(index < vertex_count); + + indices[i] = index; + + // meshlet_vertices[] slice should only contain vertices used by triangle indices, which is the case for any well formed meshlet + corner_count = t >= corner_count ? t + 1 : corner_count; + } + + return computeClusterBounds(indices, triangle_count * 3, meshlet_vertices, corner_count, vertex_positions, vertex_positions_stride); +} + +meshopt_Bounds meshopt_computeSphereBounds(const float* positions, size_t count, size_t positions_stride, const float* radii, size_t radii_stride) +{ + using namespace meshopt; + + assert(positions_stride >= 12 && positions_stride <= 256); + assert(positions_stride % sizeof(float) == 0); + assert((radii_stride >= 4 && radii_stride <= 256) || radii == NULL); + assert(radii_stride % sizeof(float) == 0); + + meshopt_Bounds bounds = {}; + + if (count == 0) + return bounds; + + const float rzero = 0.f; + + float psphere[4] = {}; + computeBoundingSphere(psphere, positions, count, positions_stride, radii ? radii : &rzero, radii ? radii_stride : 0, 7); + + bounds.center[0] = psphere[0]; + bounds.center[1] = psphere[1]; + bounds.center[2] = psphere[2]; + bounds.radius = psphere[3]; + + return bounds; +} + +void meshopt_optimizeMeshletLevel(unsigned int* meshlet_vertices, size_t vertex_count, unsigned char* meshlet_triangles, size_t triangle_count, int level) +{ + using namespace meshopt; + + assert(triangle_count <= kMeshletMaxTriangles); + assert(vertex_count <= kMeshletMaxVertices); + assert(level >= 0 && level <= 9); + + unsigned char* indices = meshlet_triangles; + unsigned int* vertices = meshlet_vertices; + + // cache tracks vertex timestamps (corresponding to triangle index! all 3 vertices are added at the same time and never removed) + unsigned char cache[kMeshletMaxVertices]; + memset(cache, 0, vertex_count); + + // note that we start from a value that means all vertices aren't in cache + unsigned char cache_last = 128; + const unsigned char cache_cutoff = 3; // 3 triangles = ~5..9 vertices depending on reuse + + // vertex valence is used to prioritize triangles for level>0 + // note: we use 8-bit counters for performance; for outlier vertices the valence is incorrect but that just affects the heuristic + unsigned char valence[kMeshletMaxVertices]; + memset(valence, 0, vertex_count); + + for (size_t i = 0; i < triangle_count; ++i) + { + unsigned char a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2]; + assert(a < vertex_count && b < vertex_count && c < vertex_count); + + valence[a]++; + valence[b]++; + valence[c]++; + } + + for (size_t i = 0; i < triangle_count; ++i) + { + int next = -1; + int next_score = -1; + int edges = 0; + + for (size_t j = i; j < triangle_count; ++j) + { + unsigned char a = indices[j * 3 + 0], b = indices[j * 3 + 1], c = indices[j * 3 + 2]; + assert(a < vertex_count && b < vertex_count && c < vertex_count); + + // compute cache distance using unsigned 8-bit subtraction, so cache timestamp overflow is handled gracefully + unsigned char ad = (unsigned char)(cache_last - cache[a]); + unsigned char bd = (unsigned char)(cache_last - cache[b]); + unsigned char cd = (unsigned char)(cache_last - cache[c]); + + int match = (ad < cache_cutoff) + (bd < cache_cutoff) + (cd < cache_cutoff); + + if (level) + { + // prefer low minimum valence + int vmin = valence[a] < valence[b] ? valence[a] : valence[b]; + vmin = valence[c] < vmin ? valence[c] : vmin; + + // prefer vertices with smaller cache distance and valence to improve traversal locality + int score = match * 1024 + (1023 - ad - bd - cd); + score = score * 256 + (255 - vmin); + + next = (score > next_score) ? int(j) : next; + next_score = (score > next_score) ? score : next_score; + + // terminate after finding enough edge matches + if (match >= 2 && ++edges >= level) + break; + } + else + { + int score = match; + + next = (score > next_score) ? int(j) : next; + next_score = (score > next_score) ? score : next_score; + + // settle for a first edge match, which makes the function ~linear in practice + if (match >= 2) + break; + } + } + + assert(next >= 0); + + unsigned char a = indices[next * 3 + 0], b = indices[next * 3 + 1], c = indices[next * 3 + 2]; + + // shift triangles before the next one forward so that we always keep an ordered partition + // note: this could have swapped triangles [i] and [next] but that distorts the order and may skew the output sequence + memmove(indices + (i + 1) * 3, indices + i * 3, (next - i) * 3 * sizeof(unsigned char)); + + indices[i * 3 + 0] = a; + indices[i * 3 + 1] = b; + indices[i * 3 + 2] = c; + + // cache timestamp is the same between all vertices of each triangle to reduce overflow + cache_last++; + cache[a] = cache_last; + cache[b] = cache_last; + cache[c] = cache_last; + + // update vertex valences for scoring heuristic + valence[a]--; + valence[b]--; + valence[c]--; + } + + // rotate triangles to maximize compressibility; only done at level >= 1 for compatibility + if (level >= 1) + { + memset(cache, 0, vertex_count); + + for (size_t i = 0; i < triangle_count; ++i) + { + unsigned char a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2]; + + // if only the middle vertex has been used, rotate triangle to ensure new vertices are always sequential + if (!cache[a] && cache[b] && !cache[c]) + { + // abc -> bca + unsigned char t = a; + a = b, b = c, c = t; + } + else if (!cache[a] && !cache[b] && !cache[c]) + { + // out of three edges, the edge ab can not be reused by subsequent triangles in some encodings + // if subsequent triangles don't share edges ca or bc, we can rotate the triangle to fix this + bool needab = false, needbc = false, needca = false; + + for (size_t j = i + 1; j < triangle_count && j <= i + 3; ++j) + { + unsigned char oa = indices[j * 3 + 0], ob = indices[j * 3 + 1], oc = indices[j * 3 + 2]; + + // note: edge comparisons are reversed as reused edges are flipped + needab |= (oa == b && ob == a) || (ob == b && oc == a) || (oc == b && oa == a); + needbc |= (oa == c && ob == b) || (ob == c && oc == b) || (oc == c && oa == b); + needca |= (oa == a && ob == c) || (ob == a && oc == c) || (oc == a && oa == c); + } + + if (needab && !needbc) + { + // abc -> bca + unsigned char t = a; + a = b, b = c, c = t; + } + else if (needab && !needca) + { + // abc -> cab + unsigned char t = c; + c = b, b = a, a = t; + } + } + + indices[i * 3 + 0] = a, indices[i * 3 + 1] = b, indices[i * 3 + 2] = c; + + cache[a] = cache[b] = cache[c] = 1; + } + } + + // reorder meshlet vertices for access locality assuming index buffer is scanned sequentially + unsigned int order[kMeshletMaxVertices]; + + short remap[kMeshletMaxVertices]; + memset(remap, -1, vertex_count * sizeof(short)); + + size_t vertex_offset = 0; + + for (size_t i = 0; i < triangle_count * 3; ++i) + { + short& r = remap[indices[i]]; + + if (r < 0) + { + r = short(vertex_offset); + order[vertex_offset] = vertices[indices[i]]; + vertex_offset++; + } + + indices[i] = (unsigned char)r; + } + + assert(vertex_offset <= vertex_count); + memcpy(vertices, order, vertex_offset * sizeof(unsigned int)); +} + +void meshopt_optimizeMeshlet(unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, size_t triangle_count, size_t vertex_count) +{ + meshopt_optimizeMeshletLevel(meshlet_vertices, vertex_count, meshlet_triangles, triangle_count, 0); +} + +size_t meshopt_extractMeshletIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count) +{ + using namespace meshopt; + + assert(index_count % 3 == 0); + assert(index_count / 3 <= kMeshletMaxTriangles); + + size_t unique = 0; + + // direct mapped cache for fast lookups based on low index bits; inspired by vk_lod_clusters from NVIDIA + short cache[1024]; + memset(cache, -1, sizeof(cache)); + + for (size_t i = 0; i < index_count; ++i) + { + unsigned int v = indices[i]; + unsigned int key = v & (sizeof(cache) / sizeof(cache[0]) - 1); + short c = cache[key]; + + // fast path: vertex has been seen before + if (c >= 0 && vertices[c] == v) + { + triangles[i] = (unsigned char)c; + continue; + } + + // fast path: vertex has never been seen before + if (c < 0) + { + assert(unique < kMeshletMaxVertices); + cache[key] = short(unique); + triangles[i] = (unsigned char)unique; + vertices[unique++] = v; + continue; + } + + // slow path: collision with a different vertex, so we need to look through all vertices + int pos = -1; + for (size_t j = 0; j < unique; ++j) + if (vertices[j] == v) + { + pos = int(j); + break; + } + + if (pos < 0) + { + assert(unique < kMeshletMaxVertices); + pos = int(unique); + vertices[unique++] = v; + } + + cache[key] = short(pos); + triangles[i] = (unsigned char)pos; + } + + assert(unique <= kMeshletMaxVertices); + return unique; +} diff --git a/thirdparty/meshoptimizer/meshoptimizer.h b/thirdparty/meshoptimizer/meshoptimizer.h index c9239bc301..7d951192b8 100644 --- a/thirdparty/meshoptimizer/meshoptimizer.h +++ b/thirdparty/meshoptimizer/meshoptimizer.h @@ -1,7 +1,7 @@ /** - * meshoptimizer - version 1.0 + * meshoptimizer - version 1.1 * - * Copyright (C) 2016-2025, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://github.com/zeux/meshoptimizer * * This library is distributed under the MIT License. See notice at the end of this file. @@ -12,7 +12,7 @@ #include /* Version macro; major * 1000 + minor * 10 + patch */ -#define MESHOPTIMIZER_VERSION 1000 /* 1.0 */ +#define MESHOPTIMIZER_VERSION 1010 /* 1.1 */ /* If no API is defined, assume default */ #ifndef MESHOPTIMIZER_API @@ -295,6 +295,38 @@ MESHOPTIMIZER_API size_t meshopt_encodeIndexSequenceBound(size_t index_count, si */ MESHOPTIMIZER_API int meshopt_decodeIndexSequence(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size); +/** + * Experimental: Meshlet encoder + * Encodes meshlet data into an array of bytes that is generally smaller and compresses better compared to original. + * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space + * This function encodes a single meshlet; when encoding multiple meshlets, additional headers may be necessary to store vertex/triangle count and encoded size. + * For maximum efficiency the meshlet being encoded should be optimized using meshopt_optimizeMeshletLevel with level 1+ (3 recommended); additionally, vertex reference data should be optimized for locality (fetch). + * + * buffer must contain enough space for the encoded meshlet (use meshopt_encodeMeshletBound to compute worst case size) + * vertices may be NULL, in which case vertex_count must be 0 and only triangle data is encoded + * vertex_count and triangle_count must be <= 256. + */ +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeMeshlet(unsigned char* buffer, size_t buffer_size, const unsigned int* vertices, size_t vertex_count, const unsigned char* triangles, size_t triangle_count); +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeMeshletBound(size_t max_vertices, size_t max_triangles); + +/** + * Experimental: Meshlet decoder + * Decodes meshlet data from an array of bytes generated by meshopt_encodeMeshlet + * Returns 0 if decoding was successful, and an error code otherwise + * The decoder is safe to use for untrusted input, but it may produce garbage data. + * + * vertices must contain enough space for the resulting vertex data, aligned to 4 bytes (align(vertex_count * vertex_size, 4) bytes) + * vertex_size must be 2 (16-bit vertex references) or 4 (32-bit vertex references) + * triangles must contain enough space for the resulting triangle data, aligned to 4 bytes (align(triangle_count * triangle_size, 4) bytes) + * triangle_size must be 3 (8-bit triangle indices) or 4 (32-bit packed triangles, stored as (a) | (b << 8) | (c << 16)) + * vertex_count, triangle_count match those used during encoding exactly; buffer_size must be equal to the encoded size returned by meshopt_encodeMeshlet. + * vertices may be NULL, in which case vertex_count must be 0 and the meshlet must contain just triangle data + * + * When using "raw" decoding (meshopt_decodeMeshletRaw), both vertices and triangles should have available space further aligned to 16 bytes for efficient SIMD decoding. + */ +MESHOPTIMIZER_EXPERIMENTAL int meshopt_decodeMeshlet(void* vertices, size_t vertex_count, size_t vertex_size, void* triangles, size_t triangle_count, size_t triangle_size, const unsigned char* buffer, size_t buffer_size); +MESHOPTIMIZER_EXPERIMENTAL int meshopt_decodeMeshletRaw(unsigned int* vertices, size_t vertex_count, unsigned int* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size); + /** * Vertex buffer encoder * Encodes vertex data into an array of bytes that is generally smaller and compresses better compared to original. @@ -422,10 +454,12 @@ enum meshopt_SimplifyRegularize = 1 << 4, /* Experimental: Allow collapses across attribute discontinuities, except for vertices that are tagged with meshopt_SimplifyVertex_Protect in vertex_lock. */ meshopt_SimplifyPermissive = 1 << 5, + /* Experimental: Produce more regular triangle sizes and shapes during simplification, at a small cost to geometric and attribute quality. */ + meshopt_SimplifyRegularizeLight = 1 << 6, }; /** - * Experimental: Simplification vertex flags/locks, for use in `vertex_lock` arrays in simplification APIs + * Simplification vertex flags/locks, for use in `vertex_lock` arrays in simplification APIs */ enum { @@ -433,6 +467,8 @@ enum meshopt_SimplifyVertex_Lock = 1 << 0, /* Protect attribute discontinuity at this vertex; must be used together with meshopt_SimplifyPermissive option. */ meshopt_SimplifyVertex_Protect = 1 << 1, + /* Experimental: Increase priority for this vertex, making it more likely that it's preserved during simplification. */ + meshopt_SimplifyVertex_Priority = 1 << 2, }; /** @@ -470,7 +506,7 @@ MESHOPTIMIZER_API size_t meshopt_simplify(unsigned int* destination, const unsig * vertex_attributes should have attribute_count floats for each vertex * attribute_weights should have attribute_count floats in total; the weights determine relative priority of attributes between each other and wrt position * attribute_count must be <= 32 - * vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex; 1 denotes vertices that can't be moved + * vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex composed of meshopt_SimplifyVertex_* flags * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1] * options must be a bitmask composed of meshopt_SimplifyX options; 0 is a safe default * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification @@ -493,7 +529,7 @@ MESHOPTIMIZER_API size_t meshopt_simplifyWithAttributes(unsigned int* destinatio * vertex_attributes should have attribute_count floats for each vertex * attribute_weights should have attribute_count floats in total; the weights determine relative priority of attributes between each other and wrt position * attribute_count must be <= 32 - * vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex; 1 denotes vertices that can't be moved + * vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex composed of meshopt_SimplifyVertex_* flags * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1] * options must be a bitmask composed of meshopt_SimplifyX options; 0 is a safe default * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification @@ -706,6 +742,15 @@ MESHOPTIMIZER_API size_t meshopt_buildMeshletsSpatial(struct meshopt_Meshlet* me */ MESHOPTIMIZER_API void meshopt_optimizeMeshlet(unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, size_t triangle_count, size_t vertex_count); +/** + * Experimental: Meshlet optimizer + * Reorders meshlet vertices and triangles to maximize locality, with higher levels resulting in smaller compressed size at the cost of optimization time. + * At level 0 the result is equivalent to meshopt_optimizeMeshlet; levels >= 1 may rotate triangle corners to improve compression (which can change provoking vertex and affect OMM data). + * + * level should be in the range [0, 9] with 0 equivalent to meshopt_optimizeMeshlet and 9 being the slowest; the sweet spot for compression ratio is around 3 + */ +MESHOPTIMIZER_EXPERIMENTAL void meshopt_optimizeMeshletLevel(unsigned int* meshlet_vertices, size_t vertex_count, unsigned char* meshlet_triangles, size_t triangle_count, int level); + struct meshopt_Bounds { /* bounding sphere, useful for frustum and occlusion culling */ @@ -743,6 +788,7 @@ struct meshopt_Bounds * * vertex_positions should have float3 position in the first 12 bytes of each vertex * vertex_count should specify the number of vertices in the entire mesh, not cluster or meshlet + * indices should have at most 256 unique vertex indices * index_count/3 and triangle_count must not exceed implementation limits (<= 512) */ MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); @@ -757,6 +803,15 @@ MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeMeshletBounds(const unsig */ MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeSphereBounds(const float* positions, size_t count, size_t positions_stride, const float* radii, size_t radii_stride); +/** + * Experimental: Extract meshlet-local vertex and triangle indices from absolute cluster indices. + * Fills triangles[] and vertices[] such that vertices[triangles[i]] == indices[i], and returns the number of unique vertices. + * + * indices should have at most 256 unique vertex indices + * index_count/3 must not exceed implementation limits (<= 512) + */ +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_extractMeshletIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count); + /** * Cluster partitioner * Partitions clusters into groups of similar size, prioritizing grouping clusters that share vertices or are close to each other. @@ -800,6 +855,49 @@ MESHOPTIMIZER_API void meshopt_spatialSortTriangles(unsigned int* destination, c */ MESHOPTIMIZER_API void meshopt_spatialClusterPoints(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t cluster_size); +/** + * Experimental: Opacity micromap generator (measure) + * Computes a subdivision level for each input triangle, as well as deduplicating the triangles that reference the same UVs to reduce rasterization requests. + * Returns the number of OMM entries. + * + * levels and sources must contain enough space for the worst case output (index_count/3 elements, one per resulting OMM entry) + * levels[i] will contain the subdivision level for entry i, with the total number of entries returned by the function; each entry should be rasterized from triangle index sources[i] + * omm_indices must contain enough space for the resulting OMM indices (index_count/3 elements, one per triangle) + * vertex_uvs should have float2 texture coordinate in the first 8 bytes of each vertex + * max_level specifies the maximum subdivision level (0..12) + * target_edge can be 0; when >0, triangle subdivision is adaptive and targets target_edge^2 texel area + */ +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_opacityMapMeasure(unsigned char* levels, unsigned int* sources, int* omm_indices, const unsigned int* indices, size_t index_count, const float* vertex_uvs, size_t vertex_count, size_t vertex_uvs_stride, unsigned int texture_width, unsigned int texture_height, int max_level, float target_edge); + +/** + * Experimental: Opacity micromap generator (rasterize) + * Rasterizes opacity state for a single triangle entry by sampling the alpha texture, using bilinear filtering and 0.5 alpha cutoff. + * + * result should contain enough space for the output opacity data (which can be computed using meshopt_opacityMapEntrySize) + * level specifies the subdivision level (0..12) + * states should be 2 for 2-state format (opaque/transparent) and 4 for 4-state format (opaque/transparent/unknown) + * uv0/uv1/uv2 should refer to a float2 texture coordinate for each triangle corner; note that micromap data is sensitive to the corner order + * texture_data should point to the alpha channel of the first pixel, encoded as UNORM8 + * texture_stride specifies the distance in bytes between consecutive pixels, e.g. 4 for RGBA input + * texture_pitch specifies the distance in bytes between consecutive rows, e.g. 4*texture_width for tightly packed RGBA input + */ +MESHOPTIMIZER_EXPERIMENTAL void meshopt_opacityMapRasterize(unsigned char* result, int level, int states, const float* uv0, const float* uv1, const float* uv2, const unsigned char* texture_data, size_t texture_stride, size_t texture_pitch, unsigned int texture_width, unsigned int texture_height); +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_opacityMapEntrySize(int level, int states); + +/** + * Experimental: Opacity micromap generator (compact) + * Compacts and deduplicates opacity data, merging identical micromap entries and replacing micromap states with special indices (-4..-1) when possible. + * Returns the number of OMM entries after compaction; the data array should be trimmed using the last offset/size. + * + * data should contain opacity data for all input/output entries + * levels should contain subdivision levels for all input/output entries + * offsets should contain offset into data[] for each entry + * levels[i] and offsets[i] will be updated with post-compaction level/offset for entry i, with the total number of entries returned by the function + * omm_indices should contain indices into the original OMM data, and will be updated with a new index or a special index (-4..-1) when possible + * states should be 2 for 2-state format (opaque/transparent) and 4 for 4-state format (opaque/transparent/unknown) + */ +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_opacityMapCompact(unsigned char* data, size_t data_size, unsigned char* levels, unsigned int* offsets, size_t omm_count, int* omm_indices, size_t triangle_count, int states); + /** * Quantize a float into half-precision (as defined by IEEE-754 fp16) floating point value * Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest @@ -899,6 +997,8 @@ template inline size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count); template inline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size); +template +inline int meshopt_decodeMeshlet(V* vertices, size_t vertex_count, T* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size); inline size_t meshopt_encodeVertexBufferLevel(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size, int level); template inline size_t meshopt_simplify(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options = 0, float* result_error = NULL); @@ -1255,6 +1355,15 @@ inline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const return meshopt_decodeIndexSequence(destination, index_count, sizeof(T), buffer, buffer_size); } +template +inline int meshopt_decodeMeshlet(V* vertices, size_t vertex_count, T* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size) +{ + char types_valid[(sizeof(V) == 2 || sizeof(V) == 4) && (sizeof(T) == 1 || sizeof(T) == 4) ? 1 : -1]; + (void)types_valid; + + return meshopt_decodeMeshlet(vertices, vertex_count, sizeof(V), triangles, triangle_count, sizeof(T) == 1 ? 3 : 4, buffer, buffer_size); +} + inline size_t meshopt_encodeVertexBufferLevel(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size, int level) { return meshopt_encodeVertexBufferLevel(buffer, buffer_size, vertices, vertex_count, vertex_size, level, -1); @@ -1422,7 +1531,7 @@ inline void meshopt_spatialSortTriangles(T* destination, const T* indices, size_ #endif /** - * Copyright (c) 2016-2025 Arseny Kapoulkine + * Copyright (c) 2016-2026 Arseny Kapoulkine * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation diff --git a/thirdparty/meshoptimizer/opacitymap.cpp b/thirdparty/meshoptimizer/opacitymap.cpp new file mode 100644 index 0000000000..0ec5eaab4a --- /dev/null +++ b/thirdparty/meshoptimizer/opacitymap.cpp @@ -0,0 +1,557 @@ +// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details +#include "meshoptimizer.h" + +#include +#include +#include + +namespace meshopt +{ + +// opacity micromaps use a "bird" traversal order which recursively subdivides the triangles: +// https://docs.vulkan.org/spec/latest/_images/micromap-subd.svg +// note that triangles 0 and 2 have the same winding as the source triangle, however triangles 1 (flipped) +// and 3 (upright) have flipped winding; this is obvious from the level 2 subdivision in the diagram above +inline size_t getLevelSize(int level, int states) +{ + // 1-bit 2-state or 2-bit 4-state per micro triangle, rounded up to whole bytes + return ((1 << (level * 2)) * (states >> 1) + 7) >> 3; +} + +struct Texture +{ + const unsigned char* data; + size_t stride, pitch; + unsigned int width, height; + float widthf, heightf; // width * 256.f, height * 256.f +}; + +static float sampleTexture(const Texture& texture, float u, float v) +{ + // wrap texture coordinates; floor is expensive so only call it if we're outside of [0, 1] range (+eps) + u = fabsf(u - 0.5f) > 0.5f ? u - floorf(u) : u; + v = fabsf(v - 0.5f) > 0.5f ? v - floorf(v) : v; + + // convert from [0, 1] to 16.8 fixed point coordinates (rounded to nearest subpixel) with texel centers on integer grid + int uf = int(u * texture.widthf - 127.5f); + int vf = int(v * texture.heightf - 127.5f); + + // clamp to avoid extrapolation past left/top edge since we don't wrap across the edge + uf = uf < 0 ? 0 : uf; + vf = vf < 0 ? 0 : vf; + + // x/y are texel coordinates, rx/ry are subpixel offsets + int x = uf >> 8; + int y = vf >> 8; + int rx = uf & 255; + int ry = vf & 255; + + // safeguard: this should not happen but if it ever does, ensure the accesses are inbounds + if (unsigned(x) >= texture.width || unsigned(y) >= texture.height) + return 0.f; + + // clamp the offsets instead of wrapping for simplicity and performance + size_t offset = size_t(y) * texture.pitch + x * texture.stride; + size_t offsetx = (x + 1 < int(texture.width)) ? texture.stride : 0; + size_t offsety = (y + 1 < int(texture.height)) ? texture.pitch : 0; + + unsigned char a00 = texture.data[offset]; + unsigned char a10 = texture.data[offset + offsetx]; + unsigned char a01 = texture.data[offset + offsety]; + unsigned char a11 = texture.data[offset + offsetx + offsety]; + + // bilinear interpolation in integer space: result is 8.16 fixed point + int ax0 = a00 * 256 + (a10 - a00) * rx; + int ax1 = a01 * 256 + (a11 - a01) * rx; + int axy = ax0 * 256 + (ax1 - ax0) * ry; + + return float(axy) * (1.f / (255.f * 65536.f)); +} + +static unsigned int hashUpdate4u(unsigned int h, const unsigned char* key, size_t len) +{ + // MurmurHash2 + const unsigned int m = 0x5bd1e995; + const int r = 24; + + while (len >= 4) + { + unsigned int k; + memcpy(&k, key, sizeof(k)); + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + key += 4; + len -= 4; + } + + return h; +} + +struct TriangleOMM +{ + int uvs[6]; + int level; +}; + +struct TriangleOMMHasher +{ + const TriangleOMM* data; + + size_t hash(unsigned int index) const + { + const TriangleOMM& tri = data[index]; + + return hashUpdate4u(tri.level, reinterpret_cast(tri.uvs), sizeof(tri.uvs)); + } + + bool equal(unsigned int lhs, unsigned int rhs) const + { + const TriangleOMM& lt = data[lhs]; + const TriangleOMM& rt = data[rhs]; + + return lt.level == rt.level && memcmp(lt.uvs, rt.uvs, sizeof(lt.uvs)) == 0; + } +}; + +struct OMMHasher +{ + const unsigned char* data; + const unsigned int* offsets; + const unsigned char* levels; + int states; + + size_t hash(unsigned int index) const + { + const unsigned char* key = data + offsets[index]; + size_t size = getLevelSize(levels[index], states); + + unsigned int h = levels[index]; + + // MurmurHash2 for large keys, simple fold for small; note that size is a power of two + if (size < 4) + h ^= key[0] | (key[size - 1] << 8); + else + h = hashUpdate4u(h, key, size); + + // MurmurHash2 finalizer + h ^= h >> 13; + h *= 0x5bd1e995; + h ^= h >> 15; + return h; + } + + bool equal(unsigned int lhs, unsigned int rhs) const + { + size_t size = getLevelSize(levels[lhs], states); + + return levels[lhs] == levels[rhs] && memcmp(data + offsets[lhs], data + offsets[rhs], size) == 0; + } +}; + +static size_t hashBuckets3(size_t count) +{ + size_t buckets = 1; + while (buckets < count + count / 4) + buckets *= 2; + + return buckets; +} + +template +static T* hashLookup3(T* table, size_t buckets, const Hash& hash, const T& key, const T& empty) +{ + assert(buckets > 0); + assert((buckets & (buckets - 1)) == 0); + + size_t hashmod = buckets - 1; + size_t bucket = hash.hash(key) & hashmod; + + for (size_t probe = 0; probe <= hashmod; ++probe) + { + T& item = table[bucket]; + + if (item == empty) + return &item; + + if (hash.equal(item, key)) + return &item; + + // hash collision, quadratic probing + bucket = (bucket + probe + 1) & hashmod; + } + + assert(false && "Hash table is full"); // unreachable + return NULL; +} + +inline int quantizeSubpixel(float v, unsigned int size) +{ + return int(v * float(int(size) * 4) + (v >= 0 ? 0.5f : -0.5f)); +} + +static int rasterizeEdge(float u0, float v0, float u1, float v1, int edgeres, const Texture& texture) +{ + float edgestep = 1.f / float(edgeres + 1); + + float ud = (u1 - u0) * edgestep, vd = (v1 - v0) * edgestep; + float u = u0, v = v0; + + int mask = 0; + int count = 0; + + for (int i = 0; i < edgeres; ++i) + { + u += ud; + v += vd; + + float a = sampleTexture(texture, u, v); + mask |= (a >= 0.5f) << i; + count += a >= 0.5f; + } + + return mask | (count << 16); +} + +template +static void rasterizeOpacity0(unsigned char* result, size_t index, float a0, float a1, float a2, float ac, int e0, int e1, int e2, int edgeres) +{ + int states = States; + + // basic coverage estimator from center and corner values; trained to minimize error + float coverage = (a0 + a1 + a2) * 0.12f + ac * 0.64f; + + if (edgeres) + { + float edgescale = 1.f / edgeres; + + // if we have edge samples, we can get a better coverage estimate by including them; trained to minimize error + coverage = ac * 0.22f + float((e0 >> 16) + (e1 >> 16) + (e2 >> 16)) * edgescale * 0.23f + (a0 + a1 + a2) * 0.03f; + } + + if (states == 2) + { + result[index / 8] |= (coverage >= 0.5f) << (index % 8); + return; + } + + int transp = (a0 < 0.5f) & (a1 < 0.5f) & (a2 < 0.5f) & (ac < 0.5f); + int opaque = (a0 > 0.5f) & (a1 > 0.5f) & (a2 > 0.5f) & (ac > 0.5f); + + // treat state as known if thresholding of corners & centers against wider bounds is consistent + // for unknown states, we currently use the same formula as the 2-state opacity for better consistency with forced 2-state + int unknown = 2 + (coverage >= 0.5f); + int state = (transp | opaque) ? opaque : unknown; + + if (edgeres && (transp | opaque)) + { + // if we have edge samples, ensure they are consistent too, falling back to unknown if not + int exp = opaque ? (1 << edgeres) - 1 : 0; + int eok = ((e0 & 0xffff) == exp) & ((e1 & 0xffff) == exp) & ((e2 & 0xffff) == exp); + + state = eok ? state : unknown; + } + + result[index / 4] |= state << ((index % 4) * 2); +} + +template +static void rasterizeOpacity1(unsigned char* result, size_t index, int edgeres, const float* c0, const float* c1, const float* c2, const Texture& texture) +{ + // compute each edge midpoint & sample + float c01[3] = {(c0[0] + c1[0]) / 2, (c0[1] + c1[1]) / 2, 0.f}; + float c12[3] = {(c1[0] + c2[0]) / 2, (c1[1] + c2[1]) / 2, 0.f}; + float c20[3] = {(c2[0] + c0[0]) / 2, (c2[1] + c0[1]) / 2, 0.f}; + + c01[2] = sampleTexture(texture, c01[0], c01[1]); + c12[2] = sampleTexture(texture, c12[0], c12[1]); + c20[2] = sampleTexture(texture, c20[0], c20[1]); + + // corner tables for each edge, and corner + edge tables for each triangle + // edges are numbered counter clockwise, 6 outer first, 3 inner last; triangle vertex and edge references are in triangle winding order + static const unsigned char edges[9][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 0}, {5, 1}, {1, 3}, {3, 5}}; + static const unsigned char triangles[4][6] = {{0, 1, 5, 0, 6, 5}, {5, 3, 1, 8, 7, 6}, {1, 2, 3, 1, 2, 7}, {3, 5, 4, 8, 4, 3}}; + + const float* points[] = {c0, c01, c1, c12, c2, c20}; + + int em[9] = {}; + + // sample additional points on the edges to improve state estimation + if (edgeres > 0) + for (size_t i = 0; i < 9; ++i) + em[i] = rasterizeEdge(points[edges[i][0]][0], points[edges[i][0]][1], points[edges[i][1]][0], points[edges[i][1]][1], edgeres, texture); + + for (size_t i = 0; i < 4; ++i) + { + const unsigned char* tri = triangles[i]; + const float* p0 = points[tri[0]]; + const float* p1 = points[tri[1]]; + const float* p2 = points[tri[2]]; + + // compute triangle center & sample + float uc = (p0[0] + p1[0] + p2[0]) * (1.f / 3.f); + float vc = (p0[1] + p1[1] + p2[1]) * (1.f / 3.f); + float ac = sampleTexture(texture, uc, vc); + + // rasterize opacity state based on alpha values in corners and center (and optionally edges) + rasterizeOpacity0(result, index * 4 + i, p0[2], p1[2], p2[2], ac, em[tri[3]], em[tri[4]], em[tri[5]], edgeres); + } +} + +template +static void rasterizeOpacityRec(unsigned char* result, size_t index, int level, int edgeres, const float* c0, const float* c1, const float* c2, const Texture& texture) +{ + if (level == 0) + { + // compute triangle center & sample + float uc = (c0[0] + c1[0] + c2[0]) * (1.f / 3.f); + float vc = (c0[1] + c1[1] + c2[1]) * (1.f / 3.f); + float ac = sampleTexture(texture, uc, vc); + + int e0 = 0, e1 = 0, e2 = 0; + + if (edgeres > 0) + { + // sample additional points on the edges to improve state estimation + e0 = rasterizeEdge(c0[0], c0[1], c1[0], c1[1], edgeres, texture); + e1 = rasterizeEdge(c1[0], c1[1], c2[0], c2[1], edgeres, texture); + e2 = rasterizeEdge(c2[0], c2[1], c0[0], c0[1], edgeres, texture); + } + + // rasterize opacity state based on alpha values in corners and center (and optionally edges) + return rasterizeOpacity0(result, index, c0[2], c1[2], c2[2], ac, e0, e1, e2, edgeres); + } + + // fast path: equivalent to recursive rasterization, but reuses edge data to reduce sample count + if (level == 1 && edgeres > 0) + return rasterizeOpacity1(result, index, edgeres, c0, c1, c2, texture); + + // compute each edge midpoint & sample + float c01[3] = {(c0[0] + c1[0]) / 2, (c0[1] + c1[1]) / 2, 0.f}; + float c12[3] = {(c1[0] + c2[0]) / 2, (c1[1] + c2[1]) / 2, 0.f}; + float c20[3] = {(c2[0] + c0[0]) / 2, (c2[1] + c0[1]) / 2, 0.f}; + + c01[2] = sampleTexture(texture, c01[0], c01[1]); + c12[2] = sampleTexture(texture, c12[0], c12[1]); + c20[2] = sampleTexture(texture, c20[0], c20[1]); + + // recursively rasterize each triangle + // note: triangles 1 and 3 have flipped winding, and 1 is flipped upside down + rasterizeOpacityRec(result, index * 4 + 0, level - 1, edgeres, c0, c01, c20, texture); + rasterizeOpacityRec(result, index * 4 + 1, level - 1, edgeres, c20, c12, c01, texture); + rasterizeOpacityRec(result, index * 4 + 2, level - 1, edgeres, c01, c1, c12, texture); + rasterizeOpacityRec(result, index * 4 + 3, level - 1, edgeres, c12, c20, c2, texture); +} + +static int getSpecialIndex(const unsigned char* data, int level, int states) +{ + int first = data[0] & (states == 2 ? 1 : 3); + int special = -(1 + first); + + // at level 0, every micromap can be converted to a special index + if (level == 0) + return special; + + // at level 1 with 2 states, the byte is partially filled so we need a separate check + if (level == 1 && states == 2) + return (data[0] & 15) == ((-first) & 15) ? special : 0; + + // otherwise we need to check that all bytes are consistent with the first value and we can do this byte-wise + int expected = first * (states == 2 ? 0xff : 0x55); + size_t size = getLevelSize(level, states); + + for (size_t i = 0; i < size; ++i) + if (data[i] != expected) + return 0; + + return special; +} + +} // namespace meshopt + +size_t meshopt_opacityMapMeasure(unsigned char* levels, unsigned int* sources, int* omm_indices, const unsigned int* indices, size_t index_count, const float* vertex_uvs, size_t vertex_count, size_t vertex_uvs_stride, unsigned int texture_width, unsigned int texture_height, int max_level, float target_edge) +{ + using namespace meshopt; + + assert(index_count % 3 == 0); + assert(vertex_uvs_stride >= 8 && vertex_uvs_stride <= 256); + assert(vertex_uvs_stride % sizeof(float) == 0); + assert(unsigned(texture_width - 1) < 16384 && unsigned(texture_height - 1) < 16384); + assert(max_level >= 0 && max_level <= 12); + assert(target_edge >= 0); + + (void)vertex_count; + + meshopt_Allocator allocator; + + size_t vertex_stride_float = vertex_uvs_stride / sizeof(float); + float texture_area = float(texture_width) * float(texture_height); + + // hash map used to deduplicate triangle rasterization requests based on UV + size_t table_size = hashBuckets3(index_count / 3); + unsigned int* table = allocator.allocate(table_size); + memset(table, -1, table_size * sizeof(unsigned int)); + + TriangleOMM* triangles = allocator.allocate(index_count / 3); + TriangleOMMHasher hasher = {triangles}; + + size_t result = 0; + + for (size_t i = 0; i < index_count; i += 3) + { + unsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2]; + assert(a < vertex_count && b < vertex_count && c < vertex_count); + + float u0 = vertex_uvs[a * vertex_stride_float + 0], v0 = vertex_uvs[a * vertex_stride_float + 1]; + float u1 = vertex_uvs[b * vertex_stride_float + 0], v1 = vertex_uvs[b * vertex_stride_float + 1]; + float u2 = vertex_uvs[c * vertex_stride_float + 0], v2 = vertex_uvs[c * vertex_stride_float + 1]; + + int level = max_level; + + if (target_edge > 0) + { + // compute ratio of edge length (in texels) to target and determine subdivision level + float uvarea = fabsf((u1 - u0) * (v2 - v0) - (u2 - u0) * (v1 - v0)) * 0.5f * texture_area; + float ratio = sqrtf(uvarea) / target_edge; + float levelf = log2f(ratio > 1 ? ratio : 1); + + // round to nearest and clamp + level = int(levelf + 0.5f); + level = level < 0 ? 0 : level; + level = level < max_level ? level : max_level; + } + + // deduplicate rasterization requests based on UV + int su0 = quantizeSubpixel(u0, texture_width), sv0 = quantizeSubpixel(v0, texture_height); + int su1 = quantizeSubpixel(u1, texture_width), sv1 = quantizeSubpixel(v1, texture_height); + int su2 = quantizeSubpixel(u2, texture_width), sv2 = quantizeSubpixel(v2, texture_height); + + TriangleOMM tri = {{su0, sv0, su1, sv1, su2, sv2}, level}; + triangles[result] = tri; // speculatively write triangle data to give hasher a way to compare it + + unsigned int* entry = hashLookup3(table, table_size, hasher, unsigned(result), ~0u); + + if (*entry == ~0u) + { + *entry = unsigned(result); + levels[result] = (unsigned char)level; + sources[result] = unsigned(i / 3); + result++; + } + + omm_indices[i / 3] = int(*entry); + } + + return result; +} + +size_t meshopt_opacityMapEntrySize(int level, int states) +{ + assert(level >= 0 && level <= 12); + assert(states == 2 || states == 4); + + return meshopt::getLevelSize(level, states); +} + +void meshopt_opacityMapRasterize(unsigned char* result, int level, int states, const float* uv0, const float* uv1, const float* uv2, const unsigned char* texture_data, size_t texture_stride, size_t texture_pitch, unsigned int texture_width, unsigned int texture_height) +{ + using namespace meshopt; + + assert(level >= 0 && level <= 12); + assert(states == 2 || states == 4); + assert(unsigned(texture_width - 1) < 16384 && unsigned(texture_height - 1) < 16384); + assert(texture_stride >= 1 && texture_stride <= 4); + assert(texture_pitch >= texture_stride * texture_width); + + memset(result, 0, getLevelSize(level, states)); + + Texture texture = {texture_data, texture_stride, texture_pitch, texture_width, texture_height, float(int(texture_width)) * 256.f, float(int(texture_height)) * 256.f}; + + // determine number of edge samples for conservative state estimation + float texture_area = float(int(texture_width)) * float(int(texture_height)); + float uvarea = fabsf((uv1[0] - uv0[0]) * (uv2[1] - uv0[1]) - (uv2[0] - uv0[0]) * (uv1[1] - uv0[1])) * 0.5f * texture_area; + float uvedge = sqrtf(uvarea) / float(1 << level); + + // target ~2px distance between edge samples (assuming equilateral microtriangles) + int edgeres = int(uvedge * 0.75f); + edgeres = edgeres < 0 ? 0 : edgeres; + edgeres = edgeres > 7 ? 7 : edgeres; + + // rasterize all micro triangles recursively, passing corner data down to reduce redundant sampling + float c0[3] = {uv0[0], uv0[1], sampleTexture(texture, uv0[0], uv0[1])}; + float c1[3] = {uv1[0], uv1[1], sampleTexture(texture, uv1[0], uv1[1])}; + float c2[3] = {uv2[0], uv2[1], sampleTexture(texture, uv2[0], uv2[1])}; + + (states == 2 ? rasterizeOpacityRec<2> : rasterizeOpacityRec<4>)(result, 0, level, edgeres, c0, c1, c2, texture); +} + +size_t meshopt_opacityMapCompact(unsigned char* data, size_t data_size, unsigned char* levels, unsigned int* offsets, size_t omm_count, int* omm_indices, size_t triangle_count, int states) +{ + using namespace meshopt; + + assert(states == 2 || states == 4); + + meshopt_Allocator allocator; + + unsigned char* data_old = allocator.allocate(data_size); + memcpy(data_old, data, data_size); + + size_t table_size = hashBuckets3(omm_count); + unsigned int* table = allocator.allocate(table_size); + memset(table, -1, table_size * sizeof(unsigned int)); + + OMMHasher hasher = {data, offsets, levels, states}; + + int* remap = allocator.allocate(omm_count); + + size_t next = 0; + size_t offset = 0; + + for (size_t i = 0; i < omm_count; ++i) + { + int level = levels[i]; + assert(level >= 0 && level <= 12); + + const unsigned char* old = data_old + offsets[i]; + size_t size = getLevelSize(level, states); + assert(offsets[i] + size <= data_size); + + // try to convert to a special index if all micro-triangle states are the same + int special = getSpecialIndex(old, level, states); + if (special < 0) + { + remap[i] = special; + continue; + } + + // speculatively write data to give hasher a way to compare it + memcpy(data + offset, old, size); + offsets[next] = unsigned(offset); + levels[next] = (unsigned char)level; + + unsigned int* entry = hashLookup3(table, table_size, hasher, unsigned(next), ~0u); + + if (*entry == ~0u) + { + *entry = unsigned(next); + next++; + offset += size; + } + + remap[i] = int(*entry); + } + + // remap triangle indices to new indices or special indices + for (size_t i = 0; i < triangle_count; ++i) + { + assert(omm_indices[i] < 0 || unsigned(omm_indices[i]) < omm_count); + omm_indices[i] = omm_indices[i] < 0 ? omm_indices[i] : remap[omm_indices[i]]; + } + + return next; +} diff --git a/thirdparty/meshoptimizer/simplifier.cpp b/thirdparty/meshoptimizer/simplifier.cpp index 14d4d42fed..88d4b15b38 100644 --- a/thirdparty/meshoptimizer/simplifier.cpp +++ b/thirdparty/meshoptimizer/simplifier.cpp @@ -1081,18 +1081,22 @@ static void fillFaceQuadrics(Quadric* vertex_quadrics, QuadricGrad* volume_gradi } } -static void fillVertexQuadrics(Quadric* vertex_quadrics, const Vector3* vertex_positions, size_t vertex_count, const unsigned int* remap, unsigned int options) +static void fillVertexQuadrics(Quadric* vertex_quadrics, const Vector3* vertex_positions, size_t vertex_count, const unsigned int* remap, const unsigned char* vertex_lock, const unsigned int* sparse_remap, unsigned int options) { // by default, we use a very small weight to improve triangulation and numerical stability without affecting the shape or error - float factor = (options & meshopt_SimplifyRegularize) ? 1e-1f : 1e-7f; + float factor = (options & meshopt_SimplifyRegularizeLight) ? 1e-2f : ((options & meshopt_SimplifyRegularize) ? 1e-1f : 1e-7f); for (size_t i = 0; i < vertex_count; ++i) { if (remap[i] != i) continue; + // increase regularization weight for vertices marked as priority; for now we only examine the primary vertex + unsigned int ri = sparse_remap ? sparse_remap[i] : unsigned(i); + bool priority = vertex_lock && (vertex_lock[ri] & meshopt_SimplifyVertex_Priority) != 0; + const Vector3& p = vertex_positions[i]; - float w = vertex_quadrics[i].w * factor; + float w = vertex_quadrics[i].w * (priority ? 1.0f : factor); Quadric Q; quadricFromPoint(Q, p.x, p.y, p.z, w); @@ -2346,7 +2350,6 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic assert(vertex_positions_stride % sizeof(float) == 0); assert(target_index_count <= index_count); assert(target_error >= 0); - assert((options & ~(meshopt_SimplifyLockBorder | meshopt_SimplifySparse | meshopt_SimplifyErrorAbsolute | meshopt_SimplifyPrune | meshopt_SimplifyRegularize | meshopt_SimplifyPermissive | meshopt_SimplifyInternalSolve | meshopt_SimplifyInternalDebug)) == 0); assert(vertex_attributes_stride >= attribute_count * sizeof(float) && vertex_attributes_stride <= 256); assert(vertex_attributes_stride % sizeof(float) == 0); assert(attribute_count <= kMaxAttributes); @@ -2440,7 +2443,7 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic } fillFaceQuadrics(vertex_quadrics, volume_gradients, result, index_count, vertex_positions, remap); - fillVertexQuadrics(vertex_quadrics, vertex_positions, vertex_count, remap, options); + fillVertexQuadrics(vertex_quadrics, vertex_positions, vertex_count, remap, vertex_lock, sparse_remap, options); fillEdgeQuadrics(vertex_quadrics, result, index_count, vertex_positions, remap, vertex_kind, loop, loopback); if (attribute_count) diff --git a/thirdparty/meshoptimizer/vertexcodec.cpp b/thirdparty/meshoptimizer/vertexcodec.cpp index 7085cce323..545ca90708 100644 --- a/thirdparty/meshoptimizer/vertexcodec.cpp +++ b/thirdparty/meshoptimizer/vertexcodec.cpp @@ -19,7 +19,7 @@ #endif // MSVC supports compiling SSSE3 code regardless of compile options; we use a cpuid-based scalar fallback -#if !defined(SIMD_SSE) && !defined(SIMD_AVX) && defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_X64)) +#if !defined(SIMD_SSE) && !defined(SIMD_AVX) && defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC))) #define SIMD_SSE #define SIMD_FALLBACK #endif @@ -37,7 +37,7 @@ #endif // On MSVC, we assume that ARM builds always target NEON-capable devices -#if !defined(SIMD_NEON) && defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) +#if !defined(SIMD_NEON) && defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC)) #define SIMD_NEON #endif @@ -56,7 +56,7 @@ // When targeting AArch64/x64, optimize for latency to allow decoding of individual 16-byte groups to overlap // We don't do this for 32-bit systems because we need 64-bit math for this and this will hurt in-order CPUs -#if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) +#if (defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) && !defined(MESHOPTIMIZER_VERTEXCODEC_SIMDNOLOPT) #define SIMD_LATENCYOPT #endif @@ -816,6 +816,12 @@ inline __m128i decodeShuffleMask(unsigned char mask0, unsigned char mask1) return _mm_unpacklo_epi64(sm0, sm1r); } +#ifdef __GNUC__ +typedef int __attribute__((aligned(1))) unaligned_int; +#else +typedef int unaligned_int; +#endif + SIMD_TARGET inline const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsigned char* buffer, int hbits) { @@ -834,19 +840,13 @@ inline const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsi case 1: case 6: { -#ifdef __GNUC__ - typedef int __attribute__((aligned(1))) unaligned_int; -#else - typedef int unaligned_int; -#endif - #ifdef SIMD_LATENCYOPT unsigned int data32; memcpy(&data32, data, 4); data32 &= data32 >> 1; // arrange bits such that low bits of nibbles of data64 contain all 2-bit elements of data32 - unsigned long long data64 = ((unsigned long long)data32 << 30) | (data32 & 0x3fffffff); + unsigned long long data64 = ((unsigned long long)data32 << 30) | data32; // adds all 1-bit nibbles together; the sum fits in 4 bits because datacnt=16 would have used mode 3 int datacnt = int(((data64 & 0x1111111111111111ull) * 0x1111111111111111ull) >> 60); @@ -1060,7 +1060,7 @@ inline const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsi data32 &= data32 >> 1; // arrange bits such that low bits of nibbles of data64 contain all 2-bit elements of data32 - unsigned long long data64 = ((unsigned long long)data32 << 30) | (data32 & 0x3fffffff); + unsigned long long data64 = ((unsigned long long)data32 << 30) | data32; // adds all 1-bit nibbles together; the sum fits in 4 bits because datacnt=16 would have used mode 3 int datacnt = int(((data64 & 0x1111111111111111ull) * 0x1111111111111111ull) >> 60); diff --git a/thirdparty/meshoptimizer/vertexfilter.cpp b/thirdparty/meshoptimizer/vertexfilter.cpp index 3fd8360836..57eecb80c7 100644 --- a/thirdparty/meshoptimizer/vertexfilter.cpp +++ b/thirdparty/meshoptimizer/vertexfilter.cpp @@ -13,7 +13,7 @@ #endif // MSVC supports compiling SSE2 code regardless of compile options; we assume all 32-bit CPUs support SSE2 -#if !defined(SIMD_SSE) && defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_X64)) +#if !defined(SIMD_SSE) && defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC))) #define SIMD_SSE #endif @@ -23,7 +23,7 @@ #endif // On MSVC, we assume that ARM builds always target NEON-capable devices -#if !defined(SIMD_NEON) && defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) +#if !defined(SIMD_NEON) && defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC)) #define SIMD_NEON #endif @@ -222,6 +222,11 @@ static void dispatchSimd(void (*process)(T*, size_t), T* data, size_t count, siz size_t count4 = count & ~size_t(3); process(data, count4); +#ifdef MESHOPTIMIZER_VERTEXFILTER_SIMDNOTAIL + // optionally omit tail processing to improve code size, expecting the caller to pass aligned counts + assert(count4 == count); + (void)stride; +#else if (count4 < count) { T tail[4 * 4] = {}; // max stride 4, max count 4 @@ -232,6 +237,7 @@ static void dispatchSimd(void (*process)(T*, size_t), T* data, size_t count, siz process(tail, count - count4); memcpy(data + count4 * stride, tail, tail_size); } +#endif } inline uint64_t rotateleft64(uint64_t v, int x) @@ -536,7 +542,7 @@ static void decodeFilterColorSimd16(unsigned short* data, size_t count) } #endif -#if defined(SIMD_NEON) && !defined(__aarch64__) && !defined(_M_ARM64) +#if defined(SIMD_NEON) && !defined(__aarch64__) && !(defined(_M_ARM64) || defined(_M_ARM64EC)) inline float32x4_t vsqrtq_f32(float32x4_t x) { float32x4_t r = vrsqrteq_f32(x); @@ -640,7 +646,7 @@ static void decodeFilterOctSimd16(short* data, size_t count) // compute normal length & scale float32x4_t ll = vfmaq_f32(vfmaq_f32(vmulq_f32(x, x), y, y), z, z); -#if !defined(__aarch64__) && !defined(_M_ARM64) +#if !defined(__aarch64__) && !(defined(_M_ARM64) || defined(_M_ARM64EC)) float32x4_t rl = vrsqrteq_f32(ll); rl = vmulq_f32(rl, vrsqrtsq_f32(vmulq_f32(rl, ll), rl)); // refine rsqrt estimate float32x4_t s = vmulq_f32(vdupq_n_f32(32767.f), rl); @@ -919,8 +925,7 @@ static void decodeFilterOctSimd8(signed char* data, size_t count) static void decodeFilterOctSimd16(short* data, size_t count) { const v128_t sign = wasm_f32x4_splat(-0.f); - // TODO: volatile here works around LLVM mis-optimizing code; https://github.com/llvm/llvm-project/issues/149457 - volatile v128_t zmask = wasm_i32x4_splat(0x7fff); + const v128_t zmask = wasm_i32x4_splat(0x7fff); for (size_t i = 0; i < count; i += 4) { @@ -1069,9 +1074,6 @@ static void decodeFilterExpSimd(unsigned int* data, size_t count) static void decodeFilterColorSimd8(unsigned char* data, size_t count) { - // TODO: volatile here works around LLVM mis-optimizing code; https://github.com/llvm/llvm-project/issues/149457 - volatile v128_t zero = wasm_i32x4_splat(0); - for (size_t i = 0; i < count; i += 4) { v128_t c4 = wasm_v128_load(&data[i * 4]); @@ -1080,7 +1082,7 @@ static void decodeFilterColorSimd8(unsigned char* data, size_t count) v128_t yf = wasm_v128_and(c4, wasm_i32x4_splat(0xff)); v128_t cof = wasm_i32x4_shr(wasm_i32x4_shl(c4, 16), 24); v128_t cgf = wasm_i32x4_shr(wasm_i32x4_shl(c4, 8), 24); - v128_t af = wasm_v128_or(zero, wasm_u32x4_shr(c4, 24)); + v128_t af = wasm_u32x4_shr(c4, 24); // recover scale from alpha high bit v128_t as = af; @@ -1120,9 +1122,6 @@ static void decodeFilterColorSimd8(unsigned char* data, size_t count) static void decodeFilterColorSimd16(unsigned short* data, size_t count) { - // TODO: volatile here works around LLVM mis-optimizing code; https://github.com/llvm/llvm-project/issues/149457 - volatile v128_t zero = wasm_i32x4_splat(0); - for (size_t i = 0; i < count; i += 4) { v128_t c4_0 = wasm_v128_load(&data[(i + 0) * 4]); @@ -1136,7 +1135,7 @@ static void decodeFilterColorSimd16(unsigned short* data, size_t count) v128_t yf = wasm_v128_and(c4_yco, wasm_i32x4_splat(0xffff)); v128_t cof = wasm_i32x4_shr(c4_yco, 16); v128_t cgf = wasm_i32x4_shr(wasm_i32x4_shl(c4_cga, 16), 16); - v128_t af = wasm_v128_or(zero, wasm_u32x4_shr(c4_cga, 16)); + v128_t af = wasm_u32x4_shr(c4_cga, 16); // recover scale from alpha high bit v128_t as = af; @@ -1469,7 +1468,7 @@ void meshopt_encodeFilterColor(void* destination, size_t count, size_t stride, i assert(unsigned((fy + fco - fcg) | (fy + fcg) | (fy - fco - fcg)) < (1u << bits)); // alpha: K-1-bit encoding with high bit set to 1 - int fa = meshopt_quantizeUnorm(c[3], bits - 1) | (1 << (bits - 1)); + int fa = (meshopt_quantizeUnorm(c[3], bits) >> 1) | (1 << (bits - 1)); if (stride == 4) { diff --git a/thirdparty/meshoptimizer/vfetchoptimizer.cpp b/thirdparty/meshoptimizer/vfetchoptimizer.cpp index 465d6df5ca..4847465b6a 100644 --- a/thirdparty/meshoptimizer/vfetchoptimizer.cpp +++ b/thirdparty/meshoptimizer/vfetchoptimizer.cpp @@ -6,8 +6,6 @@ size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count) { - assert(index_count % 3 == 0); - memset(destination, -1, vertex_count * sizeof(unsigned int)); unsigned int next_vertex = 0; @@ -30,7 +28,6 @@ size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const unsigne size_t meshopt_optimizeVertexFetch(void* destination, unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size) { - assert(index_count % 3 == 0); assert(vertex_size > 0 && vertex_size <= 256); meshopt_Allocator allocator;