Merge pull request #118778 from Chubercik/meshoptimizer-1.1
meshoptimizer: Update to 1.1
This commit is contained in:
+1
-1
@@ -426,7 +426,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/*
|
||||
|
||||
Vendored
+1
-1
@@ -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:
|
||||
|
||||
Vendored
+1
-1
@@ -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
|
||||
|
||||
+10
-425
@@ -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 <emmintrin.h>
|
||||
#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 <arm_neon.h>
|
||||
#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
|
||||
|
||||
-1
@@ -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;
|
||||
|
||||
+4
-8
@@ -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]];
|
||||
|
||||
|
||||
-5
@@ -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);
|
||||
|
||||
+1051
File diff suppressed because it is too large
Load Diff
+613
@@ -0,0 +1,613 @@
|
||||
// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details
|
||||
#include "meshoptimizer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
// 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;
|
||||
}
|
||||
+116
-7
@@ -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 <stddef.h>
|
||||
|
||||
/* 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 <typename T>
|
||||
inline size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count);
|
||||
template <typename T>
|
||||
inline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size);
|
||||
template <typename V, typename T>
|
||||
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 <typename T>
|
||||
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 <typename V, typename T>
|
||||
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
|
||||
|
||||
+557
@@ -0,0 +1,557 @@
|
||||
// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details
|
||||
#include "meshoptimizer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
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<const unsigned char*>(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 <typename T, typename Hash>
|
||||
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 <int States>
|
||||
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 <int States>
|
||||
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<States>(result, index * 4 + i, p0[2], p1[2], p2[2], ac, em[tri[3]], em[tri[4]], em[tri[5]], edgeres);
|
||||
}
|
||||
}
|
||||
|
||||
template <int States>
|
||||
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<States>(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<States>(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<States>(result, index * 4 + 0, level - 1, edgeres, c0, c01, c20, texture);
|
||||
rasterizeOpacityRec<States>(result, index * 4 + 1, level - 1, edgeres, c20, c12, c01, texture);
|
||||
rasterizeOpacityRec<States>(result, index * 4 + 2, level - 1, edgeres, c01, c1, c12, texture);
|
||||
rasterizeOpacityRec<States>(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<unsigned int>(table_size);
|
||||
memset(table, -1, table_size * sizeof(unsigned int));
|
||||
|
||||
TriangleOMM* triangles = allocator.allocate<TriangleOMM>(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<unsigned char>(data_size);
|
||||
memcpy(data_old, data, data_size);
|
||||
|
||||
size_t table_size = hashBuckets3(omm_count);
|
||||
unsigned int* table = allocator.allocate<unsigned int>(table_size);
|
||||
memset(table, -1, table_size * sizeof(unsigned int));
|
||||
|
||||
OMMHasher hasher = {data, offsets, levels, states};
|
||||
|
||||
int* remap = allocator.allocate<int>(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;
|
||||
}
|
||||
+8
-5
@@ -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)
|
||||
|
||||
+11
-11
@@ -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);
|
||||
|
||||
+14
-15
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user