godot/thirdparty/meshoptimizer/vertexfilter.cpp
reduz 77a045e902 Rework Mesh handling on scene importing.
-Reworked how meshes are treated by importer by using EditorSceneImporterMesh and EditorSceneImporterMeshNode. Instead of Mesh and MeshInstance, this allows more efficient processing of meshes before they are actually registered in the RenderingServer.
-Integrated MeshOptimizer
-Reworked internals of SurfaceTool to use arrays, making it more performant and easy to run optimizatons on.
2020-12-13 21:29:51 -03:00

826 lines
31 KiB
C++

// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details
#include "meshoptimizer.h"
#include <math.h>
// The block below auto-detects SIMD ISA that can be used on the target platform
#ifndef MESHOPTIMIZER_NO_SIMD
// The SIMD implementation requires SSE2, which can be enabled unconditionally through compiler settings
#if defined(__SSE2__)
#define SIMD_SSE
#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))
#define SIMD_SSE
#endif
// GCC/clang define these when NEON support is available
#if defined(__ARM_NEON__) || defined(__ARM_NEON)
#define SIMD_NEON
#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))
#define SIMD_NEON
#endif
// When targeting Wasm SIMD we can't use runtime cpuid checks so we unconditionally enable SIMD
#if defined(__wasm_simd128__)
#define SIMD_WASM
#endif
#endif // !MESHOPTIMIZER_NO_SIMD
#ifdef SIMD_SSE
#include <emmintrin.h>
#include <stdint.h>
#endif
#ifdef _MSC_VER
#include <intrin.h>
#endif
#ifdef SIMD_NEON
#if defined(_MSC_VER) && defined(_M_ARM64)
#include <arm64_neon.h>
#else
#include <arm_neon.h>
#endif
#endif
#ifdef SIMD_WASM
#include <wasm_simd128.h>
#endif
#ifdef SIMD_WASM
#define wasmx_unpacklo_v16x8(a, b) wasm_v16x8_shuffle(a, b, 0, 8, 1, 9, 2, 10, 3, 11)
#define wasmx_unpackhi_v16x8(a, b) wasm_v16x8_shuffle(a, b, 4, 12, 5, 13, 6, 14, 7, 15)
#define wasmx_unziplo_v32x4(a, b) wasm_v32x4_shuffle(a, b, 0, 2, 4, 6)
#define wasmx_unziphi_v32x4(a, b) wasm_v32x4_shuffle(a, b, 1, 3, 5, 7)
#endif
namespace meshopt
{
#if !defined(SIMD_SSE) && !defined(SIMD_NEON) && !defined(SIMD_WASM)
template <typename T>
static void decodeFilterOct(T* data, size_t count)
{
const float max = float((1 << (sizeof(T) * 8 - 1)) - 1);
for (size_t i = 0; i < count; ++i)
{
// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count
float x = float(data[i * 4 + 0]);
float y = float(data[i * 4 + 1]);
float z = float(data[i * 4 + 2]) - fabsf(x) - fabsf(y);
// fixup octahedral coordinates for z<0
float t = (z >= 0.f) ? 0.f : z;
x += (x >= 0.f) ? t : -t;
y += (y >= 0.f) ? t : -t;
// compute normal length & scale
float l = sqrtf(x * x + y * y + z * z);
float s = max / l;
// rounded signed float->int
int xf = int(x * s + (x >= 0.f ? 0.5f : -0.5f));
int yf = int(y * s + (y >= 0.f ? 0.5f : -0.5f));
int zf = int(z * s + (z >= 0.f ? 0.5f : -0.5f));
data[i * 4 + 0] = T(xf);
data[i * 4 + 1] = T(yf);
data[i * 4 + 2] = T(zf);
}
}
static void decodeFilterQuat(short* data, size_t count)
{
const float scale = 1.f / sqrtf(2.f);
for (size_t i = 0; i < count; ++i)
{
// recover scale from the high byte of the component
int sf = data[i * 4 + 3] | 3;
float ss = scale / float(sf);
// convert x/y/z to [-1..1] (scaled...)
float x = float(data[i * 4 + 0]) * ss;
float y = float(data[i * 4 + 1]) * ss;
float z = float(data[i * 4 + 2]) * ss;
// reconstruct w as a square root; we clamp to 0.f to avoid NaN due to precision errors
float ww = 1.f - x * x - y * y - z * z;
float w = sqrtf(ww >= 0.f ? ww : 0.f);
// rounded signed float->int
int xf = int(x * 32767.f + (x >= 0.f ? 0.5f : -0.5f));
int yf = int(y * 32767.f + (y >= 0.f ? 0.5f : -0.5f));
int zf = int(z * 32767.f + (z >= 0.f ? 0.5f : -0.5f));
int wf = int(w * 32767.f + 0.5f);
int qc = data[i * 4 + 3] & 3;
// output order is dictated by input index
data[i * 4 + ((qc + 1) & 3)] = short(xf);
data[i * 4 + ((qc + 2) & 3)] = short(yf);
data[i * 4 + ((qc + 3) & 3)] = short(zf);
data[i * 4 + ((qc + 0) & 3)] = short(wf);
}
}
static void decodeFilterExp(unsigned int* data, size_t count)
{
for (size_t i = 0; i < count; ++i)
{
unsigned int v = data[i];
// decode mantissa and exponent
int m = int(v << 8) >> 8;
int e = int(v) >> 24;
union
{
float f;
unsigned int ui;
} u;
// optimized version of ldexp(float(m), e)
u.ui = unsigned(e + 127) << 23;
u.f = u.f * float(m);
data[i] = u.ui;
}
}
#endif
#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)
inline uint64_t rotateleft64(uint64_t v, int x)
{
#if defined(_MSC_VER) && !defined(__clang__)
return _rotl64(v, x);
// Apple's Clang 8 is actually vanilla Clang 3.9, there we need to look for
// version 11 instead: https://en.wikipedia.org/wiki/Xcode#Toolchain_versions
#elif defined(__clang__) && ((!defined(__apple_build_version__) && __clang_major__ >= 8) || __clang_major__ >= 11)
return __builtin_rotateleft64(v, x);
#else
return (v << (x & 63)) | (v >> ((64 - x) & 63));
#endif
}
#endif
#ifdef SIMD_SSE
static void decodeFilterOctSimd(signed char* data, size_t count)
{
const __m128 sign = _mm_set1_ps(-0.f);
for (size_t i = 0; i < count; i += 4)
{
__m128i n4 = _mm_loadu_si128(reinterpret_cast<__m128i*>(&data[i * 4]));
// sign-extends each of x,y in [x y ? ?] with arithmetic shifts
__m128i xf = _mm_srai_epi32(_mm_slli_epi32(n4, 24), 24);
__m128i yf = _mm_srai_epi32(_mm_slli_epi32(n4, 16), 24);
// unpack z; note that z is unsigned so we technically don't need to sign extend it
__m128i zf = _mm_srai_epi32(_mm_slli_epi32(n4, 8), 24);
// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count
__m128 x = _mm_cvtepi32_ps(xf);
__m128 y = _mm_cvtepi32_ps(yf);
__m128 z = _mm_sub_ps(_mm_cvtepi32_ps(zf), _mm_add_ps(_mm_andnot_ps(sign, x), _mm_andnot_ps(sign, y)));
// fixup octahedral coordinates for z<0
__m128 t = _mm_min_ps(z, _mm_setzero_ps());
x = _mm_add_ps(x, _mm_xor_ps(t, _mm_and_ps(x, sign)));
y = _mm_add_ps(y, _mm_xor_ps(t, _mm_and_ps(y, sign)));
// compute normal length & scale
__m128 ll = _mm_add_ps(_mm_mul_ps(x, x), _mm_add_ps(_mm_mul_ps(y, y), _mm_mul_ps(z, z)));
__m128 s = _mm_mul_ps(_mm_set1_ps(127.f), _mm_rsqrt_ps(ll));
// rounded signed float->int
__m128i xr = _mm_cvtps_epi32(_mm_mul_ps(x, s));
__m128i yr = _mm_cvtps_epi32(_mm_mul_ps(y, s));
__m128i zr = _mm_cvtps_epi32(_mm_mul_ps(z, s));
// combine xr/yr/zr into final value
__m128i res = _mm_and_si128(n4, _mm_set1_epi32(0xff000000));
res = _mm_or_si128(res, _mm_and_si128(xr, _mm_set1_epi32(0xff)));
res = _mm_or_si128(res, _mm_slli_epi32(_mm_and_si128(yr, _mm_set1_epi32(0xff)), 8));
res = _mm_or_si128(res, _mm_slli_epi32(_mm_and_si128(zr, _mm_set1_epi32(0xff)), 16));
_mm_storeu_si128(reinterpret_cast<__m128i*>(&data[i * 4]), res);
}
}
static void decodeFilterOctSimd(short* data, size_t count)
{
const __m128 sign = _mm_set1_ps(-0.f);
for (size_t i = 0; i < count; i += 4)
{
__m128 n4_0 = _mm_loadu_ps(reinterpret_cast<float*>(&data[(i + 0) * 4]));
__m128 n4_1 = _mm_loadu_ps(reinterpret_cast<float*>(&data[(i + 2) * 4]));
// gather both x/y 16-bit pairs in each 32-bit lane
__m128i n4 = _mm_castps_si128(_mm_shuffle_ps(n4_0, n4_1, _MM_SHUFFLE(2, 0, 2, 0)));
// sign-extends each of x,y in [x y] with arithmetic shifts
__m128i xf = _mm_srai_epi32(_mm_slli_epi32(n4, 16), 16);
__m128i yf = _mm_srai_epi32(n4, 16);
// unpack z; note that z is unsigned so we don't need to sign extend it
__m128i z4 = _mm_castps_si128(_mm_shuffle_ps(n4_0, n4_1, _MM_SHUFFLE(3, 1, 3, 1)));
__m128i zf = _mm_and_si128(z4, _mm_set1_epi32(0x7fff));
// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count
__m128 x = _mm_cvtepi32_ps(xf);
__m128 y = _mm_cvtepi32_ps(yf);
__m128 z = _mm_sub_ps(_mm_cvtepi32_ps(zf), _mm_add_ps(_mm_andnot_ps(sign, x), _mm_andnot_ps(sign, y)));
// fixup octahedral coordinates for z<0
__m128 t = _mm_min_ps(z, _mm_setzero_ps());
x = _mm_add_ps(x, _mm_xor_ps(t, _mm_and_ps(x, sign)));
y = _mm_add_ps(y, _mm_xor_ps(t, _mm_and_ps(y, sign)));
// compute normal length & scale
__m128 ll = _mm_add_ps(_mm_mul_ps(x, x), _mm_add_ps(_mm_mul_ps(y, y), _mm_mul_ps(z, z)));
__m128 s = _mm_div_ps(_mm_set1_ps(32767.f), _mm_sqrt_ps(ll));
// rounded signed float->int
__m128i xr = _mm_cvtps_epi32(_mm_mul_ps(x, s));
__m128i yr = _mm_cvtps_epi32(_mm_mul_ps(y, s));
__m128i zr = _mm_cvtps_epi32(_mm_mul_ps(z, s));
// mix x/z and y/0 to make 16-bit unpack easier
__m128i xzr = _mm_or_si128(_mm_and_si128(xr, _mm_set1_epi32(0xffff)), _mm_slli_epi32(zr, 16));
__m128i y0r = _mm_and_si128(yr, _mm_set1_epi32(0xffff));
// pack x/y/z using 16-bit unpacks; note that this has 0 where we should have .w
__m128i res_0 = _mm_unpacklo_epi16(xzr, y0r);
__m128i res_1 = _mm_unpackhi_epi16(xzr, y0r);
// patch in .w
res_0 = _mm_or_si128(res_0, _mm_and_si128(_mm_castps_si128(n4_0), _mm_set1_epi64x(0xffff000000000000)));
res_1 = _mm_or_si128(res_1, _mm_and_si128(_mm_castps_si128(n4_1), _mm_set1_epi64x(0xffff000000000000)));
_mm_storeu_si128(reinterpret_cast<__m128i*>(&data[(i + 0) * 4]), res_0);
_mm_storeu_si128(reinterpret_cast<__m128i*>(&data[(i + 2) * 4]), res_1);
}
}
static void decodeFilterQuatSimd(short* data, size_t count)
{
const float scale = 1.f / sqrtf(2.f);
for (size_t i = 0; i < count; i += 4)
{
__m128 q4_0 = _mm_loadu_ps(reinterpret_cast<float*>(&data[(i + 0) * 4]));
__m128 q4_1 = _mm_loadu_ps(reinterpret_cast<float*>(&data[(i + 2) * 4]));
// gather both x/y 16-bit pairs in each 32-bit lane
__m128i q4_xy = _mm_castps_si128(_mm_shuffle_ps(q4_0, q4_1, _MM_SHUFFLE(2, 0, 2, 0)));
__m128i q4_zc = _mm_castps_si128(_mm_shuffle_ps(q4_0, q4_1, _MM_SHUFFLE(3, 1, 3, 1)));
// sign-extends each of x,y in [x y] with arithmetic shifts
__m128i xf = _mm_srai_epi32(_mm_slli_epi32(q4_xy, 16), 16);
__m128i yf = _mm_srai_epi32(q4_xy, 16);
__m128i zf = _mm_srai_epi32(_mm_slli_epi32(q4_zc, 16), 16);
__m128i cf = _mm_srai_epi32(q4_zc, 16);
// get a floating-point scaler using zc with bottom 2 bits set to 1 (which represents 1.f)
__m128i sf = _mm_or_si128(cf, _mm_set1_epi32(3));
__m128 ss = _mm_div_ps(_mm_set1_ps(scale), _mm_cvtepi32_ps(sf));
// convert x/y/z to [-1..1] (scaled...)
__m128 x = _mm_mul_ps(_mm_cvtepi32_ps(xf), ss);
__m128 y = _mm_mul_ps(_mm_cvtepi32_ps(yf), ss);
__m128 z = _mm_mul_ps(_mm_cvtepi32_ps(zf), ss);
// reconstruct w as a square root; we clamp to 0.f to avoid NaN due to precision errors
__m128 ww = _mm_sub_ps(_mm_set1_ps(1.f), _mm_add_ps(_mm_mul_ps(x, x), _mm_add_ps(_mm_mul_ps(y, y), _mm_mul_ps(z, z))));
__m128 w = _mm_sqrt_ps(_mm_max_ps(ww, _mm_setzero_ps()));
__m128 s = _mm_set1_ps(32767.f);
// rounded signed float->int
__m128i xr = _mm_cvtps_epi32(_mm_mul_ps(x, s));
__m128i yr = _mm_cvtps_epi32(_mm_mul_ps(y, s));
__m128i zr = _mm_cvtps_epi32(_mm_mul_ps(z, s));
__m128i wr = _mm_cvtps_epi32(_mm_mul_ps(w, s));
// mix x/z and w/y to make 16-bit unpack easier
__m128i xzr = _mm_or_si128(_mm_and_si128(xr, _mm_set1_epi32(0xffff)), _mm_slli_epi32(zr, 16));
__m128i wyr = _mm_or_si128(_mm_and_si128(wr, _mm_set1_epi32(0xffff)), _mm_slli_epi32(yr, 16));
// pack x/y/z/w using 16-bit unpacks; we pack wxyz by default (for qc=0)
__m128i res_0 = _mm_unpacklo_epi16(wyr, xzr);
__m128i res_1 = _mm_unpackhi_epi16(wyr, xzr);
// store results to stack so that we can rotate using scalar instructions
uint64_t res[4];
_mm_storeu_si128(reinterpret_cast<__m128i*>(&res[0]), res_0);
_mm_storeu_si128(reinterpret_cast<__m128i*>(&res[2]), res_1);
// rotate and store
uint64_t* out = reinterpret_cast<uint64_t*>(&data[i * 4]);
out[0] = rotateleft64(res[0], data[(i + 0) * 4 + 3] << 4);
out[1] = rotateleft64(res[1], data[(i + 1) * 4 + 3] << 4);
out[2] = rotateleft64(res[2], data[(i + 2) * 4 + 3] << 4);
out[3] = rotateleft64(res[3], data[(i + 3) * 4 + 3] << 4);
}
}
static void decodeFilterExpSimd(unsigned int* data, size_t count)
{
for (size_t i = 0; i < count; i += 4)
{
__m128i v = _mm_loadu_si128(reinterpret_cast<__m128i*>(&data[i]));
// decode exponent into 2^x directly
__m128i ef = _mm_srai_epi32(v, 24);
__m128i es = _mm_slli_epi32(_mm_add_epi32(ef, _mm_set1_epi32(127)), 23);
// decode 24-bit mantissa into floating-point value
__m128i mf = _mm_srai_epi32(_mm_slli_epi32(v, 8), 8);
__m128 m = _mm_cvtepi32_ps(mf);
__m128 r = _mm_mul_ps(_mm_castsi128_ps(es), m);
_mm_storeu_ps(reinterpret_cast<float*>(&data[i]), r);
}
}
#endif
#if defined(SIMD_NEON) && !defined(__aarch64__) && !defined(_M_ARM64)
inline float32x4_t vsqrtq_f32(float32x4_t x)
{
float32x4_t r = vrsqrteq_f32(x);
r = vmulq_f32(r, vrsqrtsq_f32(vmulq_f32(r, x), r)); // refine rsqrt estimate
return vmulq_f32(r, x);
}
inline float32x4_t vdivq_f32(float32x4_t x, float32x4_t y)
{
float32x4_t r = vrecpeq_f32(y);
r = vmulq_f32(r, vrecpsq_f32(y, r)); // refine rcp estimate
return vmulq_f32(x, r);
}
#endif
#ifdef SIMD_NEON
static void decodeFilterOctSimd(signed char* data, size_t count)
{
const int32x4_t sign = vdupq_n_s32(0x80000000);
for (size_t i = 0; i < count; i += 4)
{
int32x4_t n4 = vld1q_s32(reinterpret_cast<int32_t*>(&data[i * 4]));
// sign-extends each of x,y in [x y ? ?] with arithmetic shifts
int32x4_t xf = vshrq_n_s32(vshlq_n_s32(n4, 24), 24);
int32x4_t yf = vshrq_n_s32(vshlq_n_s32(n4, 16), 24);
// unpack z; note that z is unsigned so we technically don't need to sign extend it
int32x4_t zf = vshrq_n_s32(vshlq_n_s32(n4, 8), 24);
// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count
float32x4_t x = vcvtq_f32_s32(xf);
float32x4_t y = vcvtq_f32_s32(yf);
float32x4_t z = vsubq_f32(vcvtq_f32_s32(zf), vaddq_f32(vabsq_f32(x), vabsq_f32(y)));
// fixup octahedral coordinates for z<0
float32x4_t t = vminq_f32(z, vdupq_n_f32(0.f));
x = vaddq_f32(x, vreinterpretq_f32_s32(veorq_s32(vreinterpretq_s32_f32(t), vandq_s32(vreinterpretq_s32_f32(x), sign))));
y = vaddq_f32(y, vreinterpretq_f32_s32(veorq_s32(vreinterpretq_s32_f32(t), vandq_s32(vreinterpretq_s32_f32(y), sign))));
// compute normal length & scale
float32x4_t ll = vaddq_f32(vmulq_f32(x, x), vaddq_f32(vmulq_f32(y, y), vmulq_f32(z, z)));
float32x4_t rl = vrsqrteq_f32(ll);
float32x4_t s = vmulq_f32(vdupq_n_f32(127.f), rl);
// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value
// note: the result is offset by 0x4B40_0000, but we only need the low 16 bits so we can omit the subtraction
const float32x4_t fsnap = vdupq_n_f32(3 << 22);
int32x4_t xr = vreinterpretq_s32_f32(vaddq_f32(vmulq_f32(x, s), fsnap));
int32x4_t yr = vreinterpretq_s32_f32(vaddq_f32(vmulq_f32(y, s), fsnap));
int32x4_t zr = vreinterpretq_s32_f32(vaddq_f32(vmulq_f32(z, s), fsnap));
// combine xr/yr/zr into final value
int32x4_t res = vandq_s32(n4, vdupq_n_s32(0xff000000));
res = vorrq_s32(res, vandq_s32(xr, vdupq_n_s32(0xff)));
res = vorrq_s32(res, vshlq_n_s32(vandq_s32(yr, vdupq_n_s32(0xff)), 8));
res = vorrq_s32(res, vshlq_n_s32(vandq_s32(zr, vdupq_n_s32(0xff)), 16));
vst1q_s32(reinterpret_cast<int32_t*>(&data[i * 4]), res);
}
}
static void decodeFilterOctSimd(short* data, size_t count)
{
const int32x4_t sign = vdupq_n_s32(0x80000000);
for (size_t i = 0; i < count; i += 4)
{
int32x4_t n4_0 = vld1q_s32(reinterpret_cast<int32_t*>(&data[(i + 0) * 4]));
int32x4_t n4_1 = vld1q_s32(reinterpret_cast<int32_t*>(&data[(i + 2) * 4]));
// gather both x/y 16-bit pairs in each 32-bit lane
int32x4_t n4 = vuzpq_s32(n4_0, n4_1).val[0];
// sign-extends each of x,y in [x y] with arithmetic shifts
int32x4_t xf = vshrq_n_s32(vshlq_n_s32(n4, 16), 16);
int32x4_t yf = vshrq_n_s32(n4, 16);
// unpack z; note that z is unsigned so we don't need to sign extend it
int32x4_t z4 = vuzpq_s32(n4_0, n4_1).val[1];
int32x4_t zf = vandq_s32(z4, vdupq_n_s32(0x7fff));
// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count
float32x4_t x = vcvtq_f32_s32(xf);
float32x4_t y = vcvtq_f32_s32(yf);
float32x4_t z = vsubq_f32(vcvtq_f32_s32(zf), vaddq_f32(vabsq_f32(x), vabsq_f32(y)));
// fixup octahedral coordinates for z<0
float32x4_t t = vminq_f32(z, vdupq_n_f32(0.f));
x = vaddq_f32(x, vreinterpretq_f32_s32(veorq_s32(vreinterpretq_s32_f32(t), vandq_s32(vreinterpretq_s32_f32(x), sign))));
y = vaddq_f32(y, vreinterpretq_f32_s32(veorq_s32(vreinterpretq_s32_f32(t), vandq_s32(vreinterpretq_s32_f32(y), sign))));
// compute normal length & scale
float32x4_t ll = vaddq_f32(vmulq_f32(x, x), vaddq_f32(vmulq_f32(y, y), vmulq_f32(z, z)));
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);
// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value
// note: the result is offset by 0x4B40_0000, but we only need the low 16 bits so we can omit the subtraction
const float32x4_t fsnap = vdupq_n_f32(3 << 22);
int32x4_t xr = vreinterpretq_s32_f32(vaddq_f32(vmulq_f32(x, s), fsnap));
int32x4_t yr = vreinterpretq_s32_f32(vaddq_f32(vmulq_f32(y, s), fsnap));
int32x4_t zr = vreinterpretq_s32_f32(vaddq_f32(vmulq_f32(z, s), fsnap));
// mix x/z and y/0 to make 16-bit unpack easier
int32x4_t xzr = vorrq_s32(vandq_s32(xr, vdupq_n_s32(0xffff)), vshlq_n_s32(zr, 16));
int32x4_t y0r = vandq_s32(yr, vdupq_n_s32(0xffff));
// pack x/y/z using 16-bit unpacks; note that this has 0 where we should have .w
int32x4_t res_0 = vreinterpretq_s32_s16(vzipq_s16(vreinterpretq_s16_s32(xzr), vreinterpretq_s16_s32(y0r)).val[0]);
int32x4_t res_1 = vreinterpretq_s32_s16(vzipq_s16(vreinterpretq_s16_s32(xzr), vreinterpretq_s16_s32(y0r)).val[1]);
// patch in .w
res_0 = vbslq_s32(vreinterpretq_u32_u64(vdupq_n_u64(0xffff000000000000)), n4_0, res_0);
res_1 = vbslq_s32(vreinterpretq_u32_u64(vdupq_n_u64(0xffff000000000000)), n4_1, res_1);
vst1q_s32(reinterpret_cast<int32_t*>(&data[(i + 0) * 4]), res_0);
vst1q_s32(reinterpret_cast<int32_t*>(&data[(i + 2) * 4]), res_1);
}
}
static void decodeFilterQuatSimd(short* data, size_t count)
{
const float scale = 1.f / sqrtf(2.f);
for (size_t i = 0; i < count; i += 4)
{
int32x4_t q4_0 = vld1q_s32(reinterpret_cast<int32_t*>(&data[(i + 0) * 4]));
int32x4_t q4_1 = vld1q_s32(reinterpret_cast<int32_t*>(&data[(i + 2) * 4]));
// gather both x/y 16-bit pairs in each 32-bit lane
int32x4_t q4_xy = vuzpq_s32(q4_0, q4_1).val[0];
int32x4_t q4_zc = vuzpq_s32(q4_0, q4_1).val[1];
// sign-extends each of x,y in [x y] with arithmetic shifts
int32x4_t xf = vshrq_n_s32(vshlq_n_s32(q4_xy, 16), 16);
int32x4_t yf = vshrq_n_s32(q4_xy, 16);
int32x4_t zf = vshrq_n_s32(vshlq_n_s32(q4_zc, 16), 16);
int32x4_t cf = vshrq_n_s32(q4_zc, 16);
// get a floating-point scaler using zc with bottom 2 bits set to 1 (which represents 1.f)
int32x4_t sf = vorrq_s32(cf, vdupq_n_s32(3));
float32x4_t ss = vdivq_f32(vdupq_n_f32(scale), vcvtq_f32_s32(sf));
// convert x/y/z to [-1..1] (scaled...)
float32x4_t x = vmulq_f32(vcvtq_f32_s32(xf), ss);
float32x4_t y = vmulq_f32(vcvtq_f32_s32(yf), ss);
float32x4_t z = vmulq_f32(vcvtq_f32_s32(zf), ss);
// reconstruct w as a square root; we clamp to 0.f to avoid NaN due to precision errors
float32x4_t ww = vsubq_f32(vdupq_n_f32(1.f), vaddq_f32(vmulq_f32(x, x), vaddq_f32(vmulq_f32(y, y), vmulq_f32(z, z))));
float32x4_t w = vsqrtq_f32(vmaxq_f32(ww, vdupq_n_f32(0.f)));
float32x4_t s = vdupq_n_f32(32767.f);
// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value
// note: the result is offset by 0x4B40_0000, but we only need the low 16 bits so we can omit the subtraction
const float32x4_t fsnap = vdupq_n_f32(3 << 22);
int32x4_t xr = vreinterpretq_s32_f32(vaddq_f32(vmulq_f32(x, s), fsnap));
int32x4_t yr = vreinterpretq_s32_f32(vaddq_f32(vmulq_f32(y, s), fsnap));
int32x4_t zr = vreinterpretq_s32_f32(vaddq_f32(vmulq_f32(z, s), fsnap));
int32x4_t wr = vreinterpretq_s32_f32(vaddq_f32(vmulq_f32(w, s), fsnap));
// mix x/z and w/y to make 16-bit unpack easier
int32x4_t xzr = vorrq_s32(vandq_s32(xr, vdupq_n_s32(0xffff)), vshlq_n_s32(zr, 16));
int32x4_t wyr = vorrq_s32(vandq_s32(wr, vdupq_n_s32(0xffff)), vshlq_n_s32(yr, 16));
// pack x/y/z/w using 16-bit unpacks; we pack wxyz by default (for qc=0)
int32x4_t res_0 = vreinterpretq_s32_s16(vzipq_s16(vreinterpretq_s16_s32(wyr), vreinterpretq_s16_s32(xzr)).val[0]);
int32x4_t res_1 = vreinterpretq_s32_s16(vzipq_s16(vreinterpretq_s16_s32(wyr), vreinterpretq_s16_s32(xzr)).val[1]);
// rotate and store
uint64_t* out = (uint64_t*)&data[i * 4];
out[0] = rotateleft64(vgetq_lane_u64(vreinterpretq_u64_s32(res_0), 0), vgetq_lane_s32(cf, 0) << 4);
out[1] = rotateleft64(vgetq_lane_u64(vreinterpretq_u64_s32(res_0), 1), vgetq_lane_s32(cf, 1) << 4);
out[2] = rotateleft64(vgetq_lane_u64(vreinterpretq_u64_s32(res_1), 0), vgetq_lane_s32(cf, 2) << 4);
out[3] = rotateleft64(vgetq_lane_u64(vreinterpretq_u64_s32(res_1), 1), vgetq_lane_s32(cf, 3) << 4);
}
}
static void decodeFilterExpSimd(unsigned int* data, size_t count)
{
for (size_t i = 0; i < count; i += 4)
{
int32x4_t v = vld1q_s32(reinterpret_cast<int32_t*>(&data[i]));
// decode exponent into 2^x directly
int32x4_t ef = vshrq_n_s32(v, 24);
int32x4_t es = vshlq_n_s32(vaddq_s32(ef, vdupq_n_s32(127)), 23);
// decode 24-bit mantissa into floating-point value
int32x4_t mf = vshrq_n_s32(vshlq_n_s32(v, 8), 8);
float32x4_t m = vcvtq_f32_s32(mf);
float32x4_t r = vmulq_f32(vreinterpretq_f32_s32(es), m);
vst1q_f32(reinterpret_cast<float*>(&data[i]), r);
}
}
#endif
#ifdef SIMD_WASM
static void decodeFilterOctSimd(signed char* data, size_t count)
{
const v128_t sign = wasm_f32x4_splat(-0.f);
for (size_t i = 0; i < count; i += 4)
{
v128_t n4 = wasm_v128_load(&data[i * 4]);
// sign-extends each of x,y in [x y ? ?] with arithmetic shifts
v128_t xf = wasm_i32x4_shr(wasm_i32x4_shl(n4, 24), 24);
v128_t yf = wasm_i32x4_shr(wasm_i32x4_shl(n4, 16), 24);
// unpack z; note that z is unsigned so we technically don't need to sign extend it
v128_t zf = wasm_i32x4_shr(wasm_i32x4_shl(n4, 8), 24);
// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count
v128_t x = wasm_f32x4_convert_i32x4(xf);
v128_t y = wasm_f32x4_convert_i32x4(yf);
v128_t z = wasm_f32x4_sub(wasm_f32x4_convert_i32x4(zf), wasm_f32x4_add(wasm_f32x4_abs(x), wasm_f32x4_abs(y)));
// fixup octahedral coordinates for z<0
// note: i32x4_min with 0 is equvalent to f32x4_min
v128_t t = wasm_i32x4_min(z, wasm_i32x4_splat(0));
x = wasm_f32x4_add(x, wasm_v128_xor(t, wasm_v128_and(x, sign)));
y = wasm_f32x4_add(y, wasm_v128_xor(t, wasm_v128_and(y, sign)));
// compute normal length & scale
v128_t ll = wasm_f32x4_add(wasm_f32x4_mul(x, x), wasm_f32x4_add(wasm_f32x4_mul(y, y), wasm_f32x4_mul(z, z)));
v128_t s = wasm_f32x4_div(wasm_f32x4_splat(127.f), wasm_f32x4_sqrt(ll));
// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value
// note: the result is offset by 0x4B40_0000, but we only need the low 8 bits so we can omit the subtraction
const v128_t fsnap = wasm_f32x4_splat(3 << 22);
v128_t xr = wasm_f32x4_add(wasm_f32x4_mul(x, s), fsnap);
v128_t yr = wasm_f32x4_add(wasm_f32x4_mul(y, s), fsnap);
v128_t zr = wasm_f32x4_add(wasm_f32x4_mul(z, s), fsnap);
// combine xr/yr/zr into final value
v128_t res = wasm_v128_and(n4, wasm_i32x4_splat(0xff000000));
res = wasm_v128_or(res, wasm_v128_and(xr, wasm_i32x4_splat(0xff)));
res = wasm_v128_or(res, wasm_i32x4_shl(wasm_v128_and(yr, wasm_i32x4_splat(0xff)), 8));
res = wasm_v128_or(res, wasm_i32x4_shl(wasm_v128_and(zr, wasm_i32x4_splat(0xff)), 16));
wasm_v128_store(&data[i * 4], res);
}
}
static void decodeFilterOctSimd(short* data, size_t count)
{
const v128_t sign = wasm_f32x4_splat(-0.f);
const v128_t zmask = wasm_i32x4_splat(0x7fff);
for (size_t i = 0; i < count; i += 4)
{
v128_t n4_0 = wasm_v128_load(&data[(i + 0) * 4]);
v128_t n4_1 = wasm_v128_load(&data[(i + 2) * 4]);
// gather both x/y 16-bit pairs in each 32-bit lane
v128_t n4 = wasmx_unziplo_v32x4(n4_0, n4_1);
// sign-extends each of x,y in [x y] with arithmetic shifts
v128_t xf = wasm_i32x4_shr(wasm_i32x4_shl(n4, 16), 16);
v128_t yf = wasm_i32x4_shr(n4, 16);
// unpack z; note that z is unsigned so we don't need to sign extend it
v128_t z4 = wasmx_unziphi_v32x4(n4_0, n4_1);
v128_t zf = wasm_v128_and(z4, zmask);
// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count
v128_t x = wasm_f32x4_convert_i32x4(xf);
v128_t y = wasm_f32x4_convert_i32x4(yf);
v128_t z = wasm_f32x4_sub(wasm_f32x4_convert_i32x4(zf), wasm_f32x4_add(wasm_f32x4_abs(x), wasm_f32x4_abs(y)));
// fixup octahedral coordinates for z<0
// note: i32x4_min with 0 is equvalent to f32x4_min
v128_t t = wasm_i32x4_min(z, wasm_i32x4_splat(0));
x = wasm_f32x4_add(x, wasm_v128_xor(t, wasm_v128_and(x, sign)));
y = wasm_f32x4_add(y, wasm_v128_xor(t, wasm_v128_and(y, sign)));
// compute normal length & scale
v128_t ll = wasm_f32x4_add(wasm_f32x4_mul(x, x), wasm_f32x4_add(wasm_f32x4_mul(y, y), wasm_f32x4_mul(z, z)));
v128_t s = wasm_f32x4_div(wasm_f32x4_splat(32767.f), wasm_f32x4_sqrt(ll));
// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value
// note: the result is offset by 0x4B40_0000, but we only need the low 16 bits so we can omit the subtraction
const v128_t fsnap = wasm_f32x4_splat(3 << 22);
v128_t xr = wasm_f32x4_add(wasm_f32x4_mul(x, s), fsnap);
v128_t yr = wasm_f32x4_add(wasm_f32x4_mul(y, s), fsnap);
v128_t zr = wasm_f32x4_add(wasm_f32x4_mul(z, s), fsnap);
// mix x/z and y/0 to make 16-bit unpack easier
v128_t xzr = wasm_v128_or(wasm_v128_and(xr, wasm_i32x4_splat(0xffff)), wasm_i32x4_shl(zr, 16));
v128_t y0r = wasm_v128_and(yr, wasm_i32x4_splat(0xffff));
// pack x/y/z using 16-bit unpacks; note that this has 0 where we should have .w
v128_t res_0 = wasmx_unpacklo_v16x8(xzr, y0r);
v128_t res_1 = wasmx_unpackhi_v16x8(xzr, y0r);
// patch in .w
res_0 = wasm_v128_or(res_0, wasm_v128_and(n4_0, wasm_i64x2_splat(0xffff000000000000)));
res_1 = wasm_v128_or(res_1, wasm_v128_and(n4_1, wasm_i64x2_splat(0xffff000000000000)));
wasm_v128_store(&data[(i + 0) * 4], res_0);
wasm_v128_store(&data[(i + 2) * 4], res_1);
}
}
static void decodeFilterQuatSimd(short* data, size_t count)
{
const float scale = 1.f / sqrtf(2.f);
for (size_t i = 0; i < count; i += 4)
{
v128_t q4_0 = wasm_v128_load(&data[(i + 0) * 4]);
v128_t q4_1 = wasm_v128_load(&data[(i + 2) * 4]);
// gather both x/y 16-bit pairs in each 32-bit lane
v128_t q4_xy = wasmx_unziplo_v32x4(q4_0, q4_1);
v128_t q4_zc = wasmx_unziphi_v32x4(q4_0, q4_1);
// sign-extends each of x,y in [x y] with arithmetic shifts
v128_t xf = wasm_i32x4_shr(wasm_i32x4_shl(q4_xy, 16), 16);
v128_t yf = wasm_i32x4_shr(q4_xy, 16);
v128_t zf = wasm_i32x4_shr(wasm_i32x4_shl(q4_zc, 16), 16);
v128_t cf = wasm_i32x4_shr(q4_zc, 16);
// get a floating-point scaler using zc with bottom 2 bits set to 1 (which represents 1.f)
v128_t sf = wasm_v128_or(cf, wasm_i32x4_splat(3));
v128_t ss = wasm_f32x4_div(wasm_f32x4_splat(scale), wasm_f32x4_convert_i32x4(sf));
// convert x/y/z to [-1..1] (scaled...)
v128_t x = wasm_f32x4_mul(wasm_f32x4_convert_i32x4(xf), ss);
v128_t y = wasm_f32x4_mul(wasm_f32x4_convert_i32x4(yf), ss);
v128_t z = wasm_f32x4_mul(wasm_f32x4_convert_i32x4(zf), ss);
// reconstruct w as a square root; we clamp to 0.f to avoid NaN due to precision errors
// note: i32x4_max with 0 is equivalent to f32x4_max
v128_t ww = wasm_f32x4_sub(wasm_f32x4_splat(1.f), wasm_f32x4_add(wasm_f32x4_mul(x, x), wasm_f32x4_add(wasm_f32x4_mul(y, y), wasm_f32x4_mul(z, z))));
v128_t w = wasm_f32x4_sqrt(wasm_i32x4_max(ww, wasm_i32x4_splat(0)));
v128_t s = wasm_f32x4_splat(32767.f);
// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value
// note: the result is offset by 0x4B40_0000, but we only need the low 16 bits so we can omit the subtraction
const v128_t fsnap = wasm_f32x4_splat(3 << 22);
v128_t xr = wasm_f32x4_add(wasm_f32x4_mul(x, s), fsnap);
v128_t yr = wasm_f32x4_add(wasm_f32x4_mul(y, s), fsnap);
v128_t zr = wasm_f32x4_add(wasm_f32x4_mul(z, s), fsnap);
v128_t wr = wasm_f32x4_add(wasm_f32x4_mul(w, s), fsnap);
// mix x/z and w/y to make 16-bit unpack easier
v128_t xzr = wasm_v128_or(wasm_v128_and(xr, wasm_i32x4_splat(0xffff)), wasm_i32x4_shl(zr, 16));
v128_t wyr = wasm_v128_or(wasm_v128_and(wr, wasm_i32x4_splat(0xffff)), wasm_i32x4_shl(yr, 16));
// pack x/y/z/w using 16-bit unpacks; we pack wxyz by default (for qc=0)
v128_t res_0 = wasmx_unpacklo_v16x8(wyr, xzr);
v128_t res_1 = wasmx_unpackhi_v16x8(wyr, xzr);
// compute component index shifted left by 4 (and moved into i32x4 slot)
// TODO: volatile here works around LLVM mis-optimizing code; https://github.com/emscripten-core/emscripten/issues/11449
volatile v128_t cm = wasm_i32x4_shl(cf, 4);
// rotate and store
uint64_t* out = reinterpret_cast<uint64_t*>(&data[i * 4]);
out[0] = rotateleft64(wasm_i64x2_extract_lane(res_0, 0), wasm_i32x4_extract_lane(cm, 0));
out[1] = rotateleft64(wasm_i64x2_extract_lane(res_0, 1), wasm_i32x4_extract_lane(cm, 1));
out[2] = rotateleft64(wasm_i64x2_extract_lane(res_1, 0), wasm_i32x4_extract_lane(cm, 2));
out[3] = rotateleft64(wasm_i64x2_extract_lane(res_1, 1), wasm_i32x4_extract_lane(cm, 3));
}
}
static void decodeFilterExpSimd(unsigned int* data, size_t count)
{
for (size_t i = 0; i < count; i += 4)
{
v128_t v = wasm_v128_load(&data[i]);
// decode exponent into 2^x directly
v128_t ef = wasm_i32x4_shr(v, 24);
v128_t es = wasm_i32x4_shl(wasm_i32x4_add(ef, wasm_i32x4_splat(127)), 23);
// decode 24-bit mantissa into floating-point value
v128_t mf = wasm_i32x4_shr(wasm_i32x4_shl(v, 8), 8);
v128_t m = wasm_f32x4_convert_i32x4(mf);
v128_t r = wasm_f32x4_mul(es, m);
wasm_v128_store(&data[i], r);
}
}
#endif
} // namespace meshopt
void meshopt_decodeFilterOct(void* buffer, size_t vertex_count, size_t vertex_size)
{
using namespace meshopt;
assert(vertex_count % 4 == 0);
assert(vertex_size == 4 || vertex_size == 8);
#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)
if (vertex_size == 4)
decodeFilterOctSimd(static_cast<signed char*>(buffer), vertex_count);
else
decodeFilterOctSimd(static_cast<short*>(buffer), vertex_count);
#else
if (vertex_size == 4)
decodeFilterOct(static_cast<signed char*>(buffer), vertex_count);
else
decodeFilterOct(static_cast<short*>(buffer), vertex_count);
#endif
}
void meshopt_decodeFilterQuat(void* buffer, size_t vertex_count, size_t vertex_size)
{
using namespace meshopt;
assert(vertex_count % 4 == 0);
assert(vertex_size == 8);
(void)vertex_size;
#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)
decodeFilterQuatSimd(static_cast<short*>(buffer), vertex_count);
#else
decodeFilterQuat(static_cast<short*>(buffer), vertex_count);
#endif
}
void meshopt_decodeFilterExp(void* buffer, size_t vertex_count, size_t vertex_size)
{
using namespace meshopt;
assert(vertex_count % 4 == 0);
assert(vertex_size % 4 == 0);
#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)
decodeFilterExpSimd(static_cast<unsigned int*>(buffer), vertex_count * (vertex_size / 4));
#else
decodeFilterExp(static_cast<unsigned int*>(buffer), vertex_count * (vertex_size / 4));
#endif
}
#undef SIMD_SSE
#undef SIMD_NEON
#undef SIMD_WASM