viscosity/engine/thirdparty/meshoptimizer/meshletcodec.cpp

1051 lines
33 KiB
C++

// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details
#include "meshoptimizer.h"
#include <assert.h>
#include <string.h>
// The block below auto-detects SIMD ISA that can be used on the target platform
#ifndef MESHOPTIMIZER_NO_SIMD
// The SIMD implementation requires SSE4.1, which can be enabled unconditionally through compiler settings
#if defined(__AVX__) || defined(__SSE4_1__)
#define SIMD_SSE
#endif
// MSVC supports compiling SSE4.1 code regardless of compile options; we use a cpuid-based scalar fallback
#if !defined(SIMD_SSE) && defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC)))
#define SIMD_SSE
#define SIMD_FALLBACK
#endif
// GCC 4.9+ and clang 3.8+ support targeting SIMD ISA from individual functions; we use a cpuid-based scalar fallback
#if !defined(SIMD_SSE) && ((defined(__clang__) && __clang_major__ * 100 + __clang_minor__ >= 308) || (defined(__GNUC__) && __GNUC__ * 100 + __GNUC_MINOR__ >= 409)) && (defined(__i386__) || defined(__x86_64__))
#define SIMD_SSE
#define SIMD_FALLBACK
#define SIMD_TARGET __attribute__((target("sse4.1")))
#endif
// When targeting AArch64, enable NEON SIMD unconditionally; we do not support SIMD decoding for 32-bit ARM
#if defined(__aarch64__) || (defined(_MSC_VER) && (defined(_M_ARM64) || defined(_M_ARM64EC)) && _MSC_VER >= 1922)
#define SIMD_NEON
#endif
#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER > 1930
#define SIMD_FLATTEN [[msvc::flatten]]
#elif defined(__GNUC__) || defined(__clang__)
#define SIMD_FLATTEN __attribute__((flatten))
#else
#define SIMD_FLATTEN
#endif
#ifndef SIMD_TARGET
#define SIMD_TARGET
#endif
#endif // !MESHOPTIMIZER_NO_SIMD
#ifdef SIMD_SSE
#include <smmintrin.h>
#endif
#ifdef SIMD_NEON
#include <arm_neon.h>
#endif
#if defined(SIMD_SSE) && defined(SIMD_FALLBACK)
#ifdef _MSC_VER
#include <intrin.h> // __cpuid
#else
#include <cpuid.h> // __cpuid
#endif
#endif
#ifndef TRACE
#define TRACE 0
#endif
#if TRACE
#include <stdio.h>
#endif
namespace meshopt
{
typedef unsigned int EdgeFifo8[8][2];
static int rotateTriangle(unsigned int a, unsigned int b, unsigned int c)
{
return (a > b && a > c) ? 1 : (b > c ? 2 : 0);
}
static int getEdgeFifo8(EdgeFifo8 fifo, unsigned int a, unsigned int b, unsigned int c, size_t offset)
{
for (int i = 0; i < 8; ++i)
{
size_t index = (offset - 1 - i) & 7;
unsigned int e0 = fifo[index][0];
unsigned int e1 = fifo[index][1];
if (e0 == a && e1 == b)
return (i << 2) | 0;
if (e0 == b && e1 == c)
return (i << 2) | 1;
if (e0 == c && e1 == a)
return (i << 2) | 2;
}
return -1;
}
static void pushEdgeFifo8(EdgeFifo8 fifo, unsigned int a, unsigned int b, size_t& offset)
{
fifo[offset][0] = a;
fifo[offset][1] = b;
offset = (offset + 1) & 7;
}
static size_t encodeTriangles(unsigned char* codes, unsigned char* extra, const unsigned char* triangles, size_t triangle_count)
{
EdgeFifo8 edgefifo;
memset(edgefifo, -1, sizeof(edgefifo));
size_t edgefifooffset = 0;
unsigned int next = 0;
// 4-bit triangle codes give us 16 options that we use as follows:
// 3*2 edge reuse (2 edges * 3 last triangles) * 2 next/explicit = 12 options
// 4 remaining options = next bits; 000, 001, 011, 111.
// triangles are rotated to make next bits line up.
memset(codes, 0, (triangle_count + 1) / 2);
static const int rotations[] = {0, 1, 2, 0, 1};
unsigned char* start = extra;
for (size_t i = 0; i < triangle_count; ++i)
{
#if TRACE > 1
unsigned int last = next;
#endif
int fer = getEdgeFifo8(edgefifo, triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2], edgefifooffset);
if (fer >= 0 && (fer >> 2) < 6)
{
// note: getEdgeFifo8 implicitly rotates triangles by matching a/b to existing edge
const int* order = rotations + (fer & 3);
unsigned int a = triangles[i * 3 + order[0]], b = triangles[i * 3 + order[1]], c = triangles[i * 3 + order[2]];
int fec = (c == next) ? (next++, 0) : 1;
#if TRACE > 1
printf("%3d+ | %3d %3d %3d | edge: e%d c%d\n", last, a, b, c, fer >> 2, fec);
#endif
unsigned int code = (fer >> 2) * 2 + fec;
codes[i / 2] |= (unsigned char)(code << ((i & 1) * 4));
if (fec)
*extra++ = (unsigned char)c;
pushEdgeFifo8(edgefifo, c, b, edgefifooffset);
pushEdgeFifo8(edgefifo, a, c, edgefifooffset);
}
else
{
// rotate triangles to minimize the need for extra vertices
int rotation = rotateTriangle(triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2]);
const int* order = rotations + rotation;
unsigned int a = triangles[i * 3 + order[0]], b = triangles[i * 3 + order[1]], c = triangles[i * 3 + order[2]];
// fe must be continuous: once a vertex is encoded with next, further vertices must also be encoded with next
int fea = (a == next && b == next + 1 && c == next + 2) ? (next++, 0) : 1;
int feb = (b == next && c == next + 1) ? (next++, 0) : 1;
int fec = (c == next) ? (next++, 0) : 1;
assert(fea == 1 || feb == 0);
assert(feb == 1 || fec == 0);
#if TRACE > 1
printf("%3d+ | %3d %3d %3d | restart: %d%d%d\n", last, a, b, c, fea, feb, fec);
#endif
unsigned int code = 12 + (fea + feb + fec);
codes[i / 2] |= (unsigned char)(code << ((i & 1) * 4));
if (fea)
*extra++ = (unsigned char)a;
if (feb)
*extra++ = (unsigned char)b;
if (fec)
*extra++ = (unsigned char)c;
pushEdgeFifo8(edgefifo, c, b, edgefifooffset);
pushEdgeFifo8(edgefifo, a, c, edgefifooffset);
}
}
return extra - start;
}
static size_t encodeVertices(unsigned char* ctrl, unsigned char* data, const unsigned int* vertices, size_t vertex_count)
{
// grouped varint, 2 bit per value to indicate 0/1/2/3 byte deltas, with per-group 4-byte fallback
memset(ctrl, 0, (vertex_count + 3) / 4);
unsigned char* start = data;
unsigned int last = ~0u;
for (size_t i = 0; i < vertex_count; i += 4)
{
unsigned int gv[4] = {};
for (int k = 0; k < 4 && i + k < vertex_count; ++k)
{
unsigned int d = vertices[i + k] - last - 1;
unsigned int v = (d << 1) ^ (int(d) >> 31);
gv[k] = v;
last = vertices[i + k];
}
// if any value needs 4 bytes, or if *all* values need 3 bytes, we use 4 bytes for all values
// this allows us to encode most 3-byte deltas with 3 bytes which saves space overall
bool use4 = (gv[0] | gv[1] | gv[2] | gv[3]) > 0xffffff || (gv[0] > 0xffff && gv[1] > 0xffff && gv[2] > 0xffff && gv[3] > 0xffff);
for (int k = 0; k < 4; ++k)
{
unsigned int v = gv[k];
// 0/1/2/3 bytes per value, or all 4 values use 4 bytes
int code = use4 ? 3 : (v == 0 ? 0 : (v < 256 ? 1 : (v < 65536 ? 2 : 3)));
if (code > 0)
*data++ = (unsigned char)(v & 0xff);
if (code > 1)
*data++ = (unsigned char)((v >> 8) & 0xff);
if (code > 2)
*data++ = (unsigned char)((v >> 16) & 0xff);
if (use4)
*data++ = (unsigned char)((v >> 24) & 0xff);
// split low and high bits into two nibbles for better packing
ctrl[i / 4] |= ((code & 1) << k) | ((code >> 1) << (k + 4));
}
}
return data - start;
}
#if defined(SIMD_FALLBACK) || (!defined(SIMD_SSE) && !defined(SIMD_NEON))
inline void writeTriangle(unsigned int* triangles, size_t i, unsigned int fifo)
{
// output triangle is stored without extra edge vertex (0xcbac => 0xcba)
triangles[i] = fifo >> 8;
}
inline void writeTriangle(unsigned char* triangles, size_t i, unsigned int fifo)
{
triangles[i * 3 + 0] = (unsigned char)(fifo >> 8);
triangles[i * 3 + 1] = (unsigned char)(fifo >> 16);
triangles[i * 3 + 2] = (unsigned char)(fifo >> 24);
}
template <typename T>
static const unsigned char* decodeTriangles(T* triangles, const unsigned char* codes, const unsigned char* extra, const unsigned char* bound, size_t triangle_count)
{
// branchlessly read next or extra vertex and advance pointers
#define NEXT(var, ec) \
e = *extra; \
unsigned int var = (ec) ? e : next; \
extra += (ec), next += 1 - (ec)
unsigned int next = 0;
unsigned int fifo[3] = {}; // two edge fifo entries in one uint: 0xcbac
for (size_t i = 0; i < triangle_count; ++i)
{
if (extra > bound)
return NULL;
unsigned int code = (codes[i / 2] >> ((i & 1) * 4)) & 0xF;
unsigned int tri;
if (code < 12)
{
// reuse
unsigned int edge = fifo[code / 4];
edge >>= (code << 3) & 16; // shift by 16 if bit 1 is set (odd edge for each triangle)
// 0-1 extra vertices
unsigned int e;
NEXT(c, code & 1);
// repack triangle into edge format (0xcbac)
tri = ((edge & 0xff) << 16) | (edge & 0xff00) | c | (c << 24);
}
else
{
// restart
int fea = code > 12;
int feb = code > 13;
int fec = code > 14;
// 0-3 extra vertices
unsigned int e;
NEXT(a, fea);
NEXT(b, feb);
NEXT(c, fec);
// repack triangle into edge format (0xcbac)
tri = c | (a << 8) | (b << 16) | (c << 24);
}
writeTriangle(triangles, i, tri);
fifo[2] = fifo[1];
fifo[1] = fifo[0];
fifo[0] = tri;
}
return extra;
#undef NEXT
}
template <typename V>
static const unsigned char* decodeVertices(V* vertices, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count)
{
unsigned int last = ~0u;
for (size_t i = 0; i < vertex_count; i += 4)
{
if (data > bound)
return NULL;
unsigned char code4 = ctrl[i / 4];
for (int k = 0; k < 4; ++k)
{
int code = ((code4 >> k) & 1) | ((code4 >> (k + 3)) & 2);
int length = code4 == 0xff ? 4 : code;
// branchlessly read up to 4 bytes
unsigned int mask = (length == 4) ? ~0u : (1 << (8 * length)) - 1;
unsigned int v = (data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)) & mask;
// unzigzag + 1
unsigned int d = (v >> 1) ^ -int(v & 1);
unsigned int r = last + d + 1;
if (i + k < vertex_count)
vertices[i + k] = V(r);
data += length;
last = r;
}
}
return data;
}
static int decodeMeshlet(void* vertices, void* triangles, const unsigned char* codes, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count, size_t triangle_count, size_t vertex_size, size_t triangle_size)
{
if (vertex_size == 4)
data = decodeVertices(static_cast<unsigned int*>(vertices), ctrl, data, bound, vertex_count);
else
data = decodeVertices(static_cast<unsigned short*>(vertices), ctrl, data, bound, vertex_count);
if (!data)
return -2;
if (triangle_size == 4)
data = decodeTriangles(static_cast<unsigned int*>(triangles), codes, data, bound, triangle_count);
else
data = decodeTriangles(static_cast<unsigned char*>(triangles), codes, data, bound, triangle_count);
if (!data)
return -2;
return (data == bound) ? 0 : -3;
}
#endif
#if defined(SIMD_SSE) || defined(SIMD_NEON)
// SIMD state is stored in a single 16b register as follows:
// 0..5: 6 next extra bytes
// 6..14: 9 bytes = 3 triangles worth of index data
// 15: 'next' byte
// upon reading each triangle pair we need to transform this state such that the 9 bytes with triangle data contain the newly decoded triangles,
// which is a permutation of original state modulo per-element additions
// this transform can be chained to decode second triangle from original state; we create tables for 256 combinations of two 4-bit triangle codes
// the actual decoding becomes shuffle+add per triangle pair, plus management of extra bytes
static unsigned char kDecodeTableMasks[256][16];
static unsigned char kDecodeTableExtra[256];
// for SIMD vertex decoding we need to unpack 4 values with 0-4 bytes in each
// this can be done with a single control-dependent shuffle per group
static unsigned char kDecodeTableVerts[256][16];
static unsigned char kDecodeTableLength[256];
static bool decodeBuildTables()
{
#define NEXT(var, ec) \
shuf[var] = (ec) ? (unsigned char)extra : 15; \
next[var] = (ec) ? 0 : (unsigned char)nextoff; \
extra += (ec), nextoff += 1 - (ec)
// check for SSE4.1 support if we have a fallback path
#if defined(SIMD_SSE) && defined(SIMD_FALLBACK)
int cpuinfo[4] = {};
#ifdef _MSC_VER
__cpuid(cpuinfo, 1);
#else
__cpuid(1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]);
#endif
// bit 19 = SSE4.1
if ((cpuinfo[2] & (1 << 19)) == 0)
return false;
#endif
// fill triangle decoding tables for each combination of two triangle codes
for (int code = 0; code < 256; ++code)
{
unsigned char shuf[16] = {};
unsigned char next[16] = {};
int extra = 0;
int nextoff = 0;
// state 0..5 will be refilled every iteration, so we ignore that
// state 6..8 will always contain the last decoded triangle because every triangle shifts fifo equally, so we can decode it independently
shuf[6] = 12;
shuf[7] = 13;
shuf[8] = 14;
// state 15 will contain next (potentially incremented a few times)
shuf[15] = 15;
// state 9..11 will contain the first decoded triangle (tri0), which can refer to extra/next and the original triangle history
// state 12..14 will contain the second decoded triangle (tri1); when decoding edge reuse, we need to handle edge 0/1 specially as it was just decoded earlier
for (int k = 0; k < 2; ++k)
{
int tri = (code >> (k * 4)) & 0xf;
if (tri < 12)
{
if (k == 1 && tri / 4 == 0)
{
// we need to decode one of two edges from the triangle we just decoded earlier
// for that we simply need to copy shuf/next values for the two decoded indices
shuf[9 + k * 3] = shuf[9 + ((tri & 2) ? 2 : 0)];
next[9 + k * 3] = next[9 + ((tri & 2) ? 2 : 0)];
shuf[10 + k * 3] = shuf[9 + ((tri & 2) ? 1 : 2)];
next[10 + k * 3] = next[9 + ((tri & 2) ? 1 : 2)];
}
else
{
// reuse: edge comes from the history based on edge index
// note: we reuse with an offset because last triangle in the original history was consumed by tri0
int trioff = 6 + k * 3 + (2 - tri / 4) * 3;
// edge cb or ac
shuf[9 + k * 3] = (unsigned char)(trioff + ((tri & 2) ? 2 : 0));
shuf[10 + k * 3] = (unsigned char)(trioff + ((tri & 2) ? 1 : 2));
}
// third vertex is either next or comes from extra
NEXT(11 + k * 3, tri & 1);
}
else
{
// restart: three vertices, each comes from next or extra
int fea = tri > 12;
int feb = tri > 13;
int fec = tri > 14;
NEXT(9 + k * 3, fea);
NEXT(10 + k * 3, feb);
NEXT(11 + k * 3, fec);
}
}
// next needs to advance
next[15] = (unsigned char)nextoff;
// next[0..8] = 0 trivially (never written to); next[9] must also be 0 because nextoff is 0 initially
// shuf[0..5] is not used, which allows us to pack next[10..15] + shuf[6..15] into a single 16-byte entry
assert(next[9] == 0);
memcpy(&kDecodeTableMasks[code][0], &next[10], 6);
memcpy(&kDecodeTableMasks[code][6], &shuf[6], 10);
kDecodeTableExtra[code] = (unsigned char)extra;
}
// fill vertex decoding tables for each combination of four vertex references
for (unsigned int i = 0; i < 256; ++i)
{
unsigned char shuf[16] = {};
int offset = 0;
for (int k = 0; k < 4; ++k)
{
int code = ((i >> k) & 1) | ((i >> (k + 3)) & 2);
int length = i == 0xff ? 4 : code; // 0/1/2/3 bytes, or all 4 bytes if code==0xff
shuf[k * 4 + 0] = (length > 0) ? (unsigned char)(offset + 0) : 0x80;
shuf[k * 4 + 1] = (length > 1) ? (unsigned char)(offset + 1) : 0x80;
shuf[k * 4 + 2] = (length > 2) ? (unsigned char)(offset + 2) : 0x80;
shuf[k * 4 + 3] = (length > 3) ? (unsigned char)(offset + 3) : 0x80;
offset += length;
}
memcpy(kDecodeTableVerts[i], shuf, sizeof(shuf));
kDecodeTableLength[i] = (unsigned char)offset;
}
return true;
#undef NEXT
}
static bool gDecodeTablesInitialized = decodeBuildTables();
#endif
#if defined(SIMD_SSE)
SIMD_TARGET
inline __m128i decodeTriangleGroup(__m128i state, unsigned char code, const unsigned char*& extra)
{
__m128i shuf = _mm_loadu_si128(reinterpret_cast<const __m128i*>(kDecodeTableMasks[code]));
__m128i next = _mm_slli_si128(shuf, 10);
// patch first 6 bytes with current extra and roll state forward
__m128i ext = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(extra));
state = _mm_blend_epi16(state, ext, 7);
state = _mm_add_epi8(_mm_shuffle_epi8(state, shuf), next);
extra += kDecodeTableExtra[code];
return state;
}
SIMD_TARGET
inline __m128i decodeVertexGroup(__m128i last, unsigned char code, const unsigned char*& data)
{
__m128i word = _mm_loadu_si128(reinterpret_cast<const __m128i*>(data));
__m128i shuf = _mm_loadu_si128(reinterpret_cast<const __m128i*>(kDecodeTableVerts[code]));
__m128i v = _mm_shuffle_epi8(word, shuf);
// unzigzag+1
__m128i xl = _mm_sub_epi32(_mm_setzero_si128(), _mm_and_si128(v, _mm_set1_epi32(1)));
__m128i xr = _mm_srli_epi32(v, 1);
__m128i x = _mm_add_epi32(_mm_xor_si128(xl, xr), _mm_set1_epi32(1));
// prefix sum
x = _mm_add_epi32(x, _mm_slli_si128(x, 8));
x = _mm_add_epi32(x, _mm_slli_si128(x, 4));
x = _mm_add_epi32(x, _mm_shuffle_epi32(last, 0xff));
data += kDecodeTableLength[code];
return x;
}
#endif
#if defined(SIMD_NEON)
SIMD_TARGET
inline uint8x16_t decodeTriangleGroup(uint8x16_t state, unsigned char code, const unsigned char*& extra)
{
uint8x16_t shuf = vld1q_u8(kDecodeTableMasks[code]);
uint8x16_t next = vextq_u8(vdupq_n_u8(0), shuf, 6);
// patch first 6 bytes with current extra and roll state forward
uint8x8_t extl = vld1_u8(extra);
uint8x16_t ext = vcombine_u8(extl, vdup_n_u8(0));
state = vbslq_u8(vcombine_u8(vcreate_u8(0xffffffffffffull), vdup_n_u8(0)), ext, state);
state = vaddq_u8(vqtbl1q_u8(state, shuf), next);
extra += kDecodeTableExtra[code];
return state;
}
SIMD_TARGET
inline uint32x4_t decodeVertexGroup(uint32x4_t last, unsigned char code, const unsigned char*& data)
{
uint8x16_t word = vld1q_u8(data);
uint8x16_t shuf = vld1q_u8(kDecodeTableVerts[code]);
uint32x4_t v = vreinterpretq_u32_u8(vqtbl1q_u8(word, shuf));
// unzigzag+1
uint32x4_t xl = vsubq_u32(vdupq_n_u32(0), vandq_u32(v, vdupq_n_u32(1)));
uint32x4_t xr = vshrq_n_u32(v, 1);
uint32x4_t x = vaddq_u32(veorq_u32(xl, xr), vdupq_n_u32(1));
// prefix sum
x = vaddq_u32(x, vextq_u32(vdupq_n_u32(0), x, 2));
x = vaddq_u32(x, vextq_u32(vdupq_n_u32(0), x, 3));
x = vaddq_u32(x, vdupq_n_u32(vgetq_lane_u32(last, 3)));
data += kDecodeTableLength[code];
return x;
}
#endif
#if defined(SIMD_SSE)
#ifdef __GNUC__
typedef int __attribute__((aligned(1))) unaligned_int;
#else
typedef int unaligned_int;
#endif
#endif
#if defined(SIMD_SSE) || defined(SIMD_NEON)
SIMD_TARGET
static const unsigned char* decodeTrianglesSimd(unsigned int* triangles, const unsigned char* codes, const unsigned char* extra, const unsigned char* bound, size_t triangle_count)
{
#if defined(SIMD_SSE)
__m128i repack = _mm_setr_epi8(9, 10, 11, -1, 12, 13, 14, -1, 0, 0, 0, 0, 0, 0, 0, 0);
__m128i state = _mm_setzero_si128();
#elif defined(SIMD_NEON)
uint8x8_t repack = vcreate_u8(0xff0e0d0cff0b0a09ull);
uint8x16_t state = vdupq_n_u8(0);
#endif
size_t groups = triangle_count / 2;
// process all complete groups
for (size_t i = 0; i < groups; ++i)
{
unsigned char code = *codes++;
if (extra > bound)
return NULL;
state = decodeTriangleGroup(state, code, extra);
// write 6 bytes of new triangle data into output, formatted as 8 bytes with 0 padding
#if defined(SIMD_SSE)
__m128i r = _mm_shuffle_epi8(state, repack);
_mm_storel_epi64(reinterpret_cast<__m128i*>(&triangles[i * 2]), r);
#elif defined(SIMD_NEON)
uint32x2_t r = vreinterpret_u32_u8(vqtbl1_u8(state, repack));
vst1_u32(&triangles[i * 2], r);
#endif
}
// process a 1 triangle tail; to maintain the memory safety guarantee we have to write a 32-bit element
if (triangle_count & 1)
{
unsigned char code = *codes++;
if (extra > bound)
return NULL;
state = decodeTriangleGroup(state, code, extra);
unsigned int* tail = &triangles[triangle_count & ~1u];
#if defined(SIMD_SSE)
__m128i r = _mm_shuffle_epi8(state, repack);
*tail = unsigned(_mm_cvtsi128_si32(r));
#elif defined(SIMD_NEON)
uint32x2_t r = vreinterpret_u32_u8(vqtbl1_u8(state, repack));
vst1_lane_u32(tail, r, 0);
#endif
}
return extra;
}
SIMD_TARGET
static const unsigned char* decodeTrianglesSimd(unsigned char* triangles, const unsigned char* codes, const unsigned char* extra, const unsigned char* bound, size_t triangle_count)
{
#if defined(SIMD_SSE)
__m128i state = _mm_setzero_si128();
#elif defined(SIMD_NEON)
uint8x16_t state = vdupq_n_u8(0);
#endif
// because the output buffer is guaranteed to have 32-bit aligned size available, we can optimize writes and tail processing
// instead of processing triangles 2 at a time, we process 2 *pairs* at a time (12-byte write) followed by a tail pair, if present
// if the number of triangles mod 4 is 3, we'd normally need to write 12k+9 bytes, but we can instead overwrite up to 3 bytes in the main loop
size_t groups = (triangle_count + 1) / 4;
// process all complete groups
for (size_t i = 0; i < groups; ++i)
{
unsigned char code0 = *codes++;
unsigned char code1 = *codes++;
// each triangle pair reads <=6 bytes from extra, so two pairs need <=12 bytes and gap guarantees 16 byte of overread
if (extra > bound)
return NULL;
state = decodeTriangleGroup(state, code0, extra);
// write first decoded triangle and first index of second decoded triangle
#if defined(SIMD_SSE)
__m128i r0 = _mm_srli_si128(state, 9);
*reinterpret_cast<unaligned_int*>(&triangles[i * 12]) = _mm_cvtsi128_si32(r0);
#elif defined(SIMD_NEON)
uint8x16_t r0 = vextq_u8(state, vdupq_n_u8(0), 9);
vst1q_lane_u32(reinterpret_cast<unsigned int*>(&triangles[i * 12]), vreinterpretq_u32_u8(r0), 0);
#endif
state = decodeTriangleGroup(state, code1, extra);
// write last two indices of second decoded triangle that we didn't write above plus two new ones
// note that the second decoded triangle has shifted down to 6-8 bytes, hence shift by 7
#if defined(SIMD_SSE)
__m128i r1 = _mm_srli_si128(state, 7);
_mm_storel_epi64(reinterpret_cast<__m128i*>(&triangles[i * 12 + 4]), r1);
#elif defined(SIMD_NEON)
uint8x16_t r1 = vextq_u8(state, vdupq_n_u8(0), 7);
vst1_u8(&triangles[i * 12 + 4], vget_low_u8(r1));
#endif
}
// process a 1-2 triangle tail; to maintain the memory safety guarantee we have to write 1-2 32-bit elements
if (groups * 4 < triangle_count)
{
unsigned char code = *codes++;
if (extra > bound)
return NULL;
state = decodeTriangleGroup(state, code, extra);
unsigned char* tail = &triangles[(triangle_count & ~3u) * 3];
#if defined(SIMD_SSE)
__m128i r = _mm_srli_si128(state, 9);
*reinterpret_cast<unaligned_int*>(tail) = _mm_cvtsi128_si32(r);
if ((triangle_count & 3) > 1)
*reinterpret_cast<unaligned_int*>(tail + 4) = _mm_extract_epi32(r, 1);
#elif defined(SIMD_NEON)
uint8x16_t r = vextq_u8(state, vdupq_n_u8(0), 9);
vst1q_lane_u32(reinterpret_cast<unsigned int*>(tail), vreinterpretq_u32_u8(r), 0);
if ((triangle_count & 3) > 1)
vst1q_lane_u32(reinterpret_cast<unsigned int*>(tail + 4), vreinterpretq_u32_u8(r), 1);
#endif
}
return extra;
}
SIMD_TARGET
static const unsigned char* decodeVerticesSimd(unsigned int* vertices, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count)
{
#if defined(SIMD_SSE)
__m128i last = _mm_set1_epi32(-1);
#elif defined(SIMD_NEON)
uint32x4_t last = vdupq_n_u32(~0u);
#endif
size_t groups = vertex_count / 4;
// process all complete groups
for (size_t i = 0; i < groups; ++i)
{
unsigned char code = *ctrl++;
if (data > bound)
return NULL;
last = decodeVertexGroup(last, code, data);
#if defined(SIMD_SSE)
_mm_storeu_si128(reinterpret_cast<__m128i*>(&vertices[i * 4]), last);
#elif defined(SIMD_NEON)
vst1q_u32(&vertices[i * 4], last);
#endif
}
// process a 1-3 vertex tail; to maintain the memory safety guarantee we have to write individual elements
if (vertex_count & 3)
{
unsigned char code = *ctrl++;
if (data > bound)
return NULL;
last = decodeVertexGroup(last, code, data);
unsigned int* tail = &vertices[vertex_count & ~3u];
#if defined(SIMD_SSE)
tail[0] = _mm_cvtsi128_si32(last);
if ((vertex_count & 3) > 1)
tail[1] = _mm_extract_epi32(last, 1);
if ((vertex_count & 3) > 2)
tail[2] = _mm_extract_epi32(last, 2);
#elif defined(SIMD_NEON)
vst1q_lane_u32(&tail[0], last, 0);
if ((vertex_count & 3) > 1)
vst1q_lane_u32(&tail[1], last, 1);
if ((vertex_count & 3) > 2)
vst1q_lane_u32(&tail[2], last, 2);
#endif
}
return data;
}
SIMD_TARGET
static const unsigned char* decodeVerticesSimd(unsigned short* vertices, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count)
{
#if defined(SIMD_SSE)
__m128i repack = _mm_setr_epi8(0, 1, 4, 5, 8, 9, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0);
__m128i last = _mm_set1_epi32(-1);
#elif defined(SIMD_NEON)
uint32x4_t last = vdupq_n_u32(~0u);
#endif
// because the output buffer is guaranteed to have 32-bit aligned size available, we can simplify tail processing
// if the number of vertices mod 4 is 3, we'd normally need to write 8+6 bytes, but we can instead overwrite up to 2 bytes in the main loop
size_t groups = (vertex_count + 1) / 4;
// process all complete groups
for (size_t i = 0; i < groups; ++i)
{
unsigned char code = *ctrl++;
if (data > bound)
return NULL;
last = decodeVertexGroup(last, code, data);
#if defined(SIMD_SSE)
__m128i r = _mm_shuffle_epi8(last, repack);
_mm_storel_epi64(reinterpret_cast<__m128i*>(&vertices[i * 4]), r);
#elif defined(SIMD_NEON)
uint16x4_t r = vmovn_u32(last);
vst1_u16(&vertices[i * 4], r);
#endif
}
// process a 1-2 vertex tail; to maintain the memory safety guarantee we have to write a 32-bit element
if (groups * 4 < vertex_count)
{
unsigned char code = *ctrl++;
if (data > bound)
return NULL;
last = decodeVertexGroup(last, code, data);
unsigned short* tail = &vertices[vertex_count & ~3u];
#if defined(SIMD_SSE)
__m128i r = _mm_shufflelo_epi16(last, 8);
*reinterpret_cast<unaligned_int*>(tail) = _mm_cvtsi128_si32(r);
#elif defined(SIMD_NEON)
uint16x4_t r = vmovn_u32(last);
vst1_lane_u32(reinterpret_cast<unsigned int*>(tail), vreinterpret_u32_u16(r), 0);
#endif
}
return data;
}
template <int Raw>
SIMD_TARGET SIMD_FLATTEN static int
decodeMeshletSimd(void* vertices, void* triangles, const unsigned char* codes, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count, size_t triangle_count, size_t vertex_size, size_t triangle_size)
{
assert(gDecodeTablesInitialized);
(void)gDecodeTablesInitialized;
#ifdef __clang__
// data is guaranteed to be non-null initially; if decode loops never hit bounds errors, it remains non-null
__builtin_assume(data);
#endif
// decodes 4 vertices at a time with tail processing; writes up to align(vertex_size * vertex_count, 4)
// raw decoding skips tail processing by rounding up vertex count; it's safe because output buffer is guaranteed to have extra space, and tail control data is 0
if (vertex_size == 4 || Raw)
data = decodeVerticesSimd(static_cast<unsigned int*>(vertices), ctrl, data, bound, Raw ? (vertex_count + 3) & ~3 : vertex_count);
else
data = decodeVerticesSimd(static_cast<unsigned short*>(vertices), ctrl, data, bound, vertex_count);
if (!data)
return -2;
// decodes 2/4 triangles at a time with tail processing; writes up to align(triangle_size * triangle_count, 4)
// raw decoding skips tail processing by rounding up triangle count; it's safe because output buffer is guaranteed to have extra space, and tail code data is 0
if (triangle_size == 4 || Raw)
data = decodeTrianglesSimd(static_cast<unsigned int*>(triangles), codes, data, bound, Raw ? (triangle_count + 1) & ~1 : triangle_count);
else
data = decodeTrianglesSimd(static_cast<unsigned char*>(triangles), codes, data, bound, triangle_count);
if (!data)
return -2;
return (data == bound) ? 0 : -3;
}
#endif
} // namespace meshopt
size_t meshopt_encodeMeshletBound(size_t max_vertices, size_t max_triangles)
{
size_t codes_size = (max_triangles + 1) / 2;
size_t extra_size = max_triangles * 3;
size_t ctrl_size = (max_vertices + 3) / 4;
size_t data_size = (max_vertices + 3) / 4 * 16; // worst case: 16 bytes per vertex group
size_t gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0;
return codes_size + extra_size + ctrl_size + data_size + gap_size;
}
size_t meshopt_encodeMeshlet(unsigned char* buffer, size_t buffer_size, const unsigned int* vertices, size_t vertex_count, const unsigned char* triangles, size_t triangle_count)
{
using namespace meshopt;
assert(triangle_count <= 256 && vertex_count <= 256);
// 4 bits per triangle + up to three bytes of extra data
unsigned char codes[256 / 2];
unsigned char extra[256 * 3];
size_t codes_size = (triangle_count + 1) / 2;
size_t extra_size = encodeTriangles(codes, extra, triangles, triangle_count);
assert(extra_size <= sizeof(extra));
// 2 bits per vertex + up to 4 bytes of actual data
unsigned char ctrl[256 / 4];
unsigned char data[256 * 4];
size_t ctrl_size = (vertex_count + 3) / 4;
size_t data_size = encodeVertices(ctrl, data, vertices, vertex_count);
assert(data_size <= sizeof(data));
// we need to ensure that up to 16 bytes after extra+data are available for SIMD decoding
// to minimize overhead, we place fixed-size codes+control at the end of the buffer
size_t gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0;
size_t result = codes_size + extra_size + ctrl_size + data_size + gap_size;
if (result > buffer_size)
return 0;
// variable-size data first
memcpy(buffer, data, data_size);
buffer += data_size;
memcpy(buffer, extra, extra_size);
buffer += extra_size;
// gap (for accelerated decoding) separates variable-size and fixed-size data
memset(buffer, 0, gap_size);
buffer += gap_size;
// fixed-size data last; it can be located from buffer end during decoding
memcpy(buffer, ctrl, ctrl_size);
buffer += ctrl_size;
memcpy(buffer, codes, codes_size);
buffer += codes_size;
#if TRACE > 1
printf("extra:");
for (size_t i = 0; i < extra_size; ++i)
printf(" %d", extra[i]);
printf("\n");
unsigned int minv = ~0u;
for (size_t i = 0; i < vertex_count; ++i)
minv = minv < vertices[i] ? minv : vertices[i];
printf("vertices: [%d+]", minv);
for (size_t i = 0; i < vertex_count; ++i)
printf(" %d", vertices[i] - minv);
printf("\n");
#endif
#if TRACE
printf("stats: %d vertices, %d triangles => %d bytes (triangles: %d codes, %d extra; vertices: %d control, %d data; %d gap)\n",
int(vertex_count), int(triangle_count), int(result),
int(codes_size), int(extra_size), int(ctrl_size), int(data_size), int(gap_size));
#endif
return result;
}
int meshopt_decodeMeshlet(void* vertices, size_t vertex_count, size_t vertex_size, void* triangles, size_t triangle_count, size_t triangle_size, const unsigned char* buffer, size_t buffer_size)
{
using namespace meshopt;
assert(triangle_count <= 256 && vertex_count <= 256);
assert(vertex_size == 4 || vertex_size == 2);
assert(triangle_size == 4 || triangle_size == 3);
// layout must match encoding
size_t codes_size = (triangle_count + 1) / 2;
size_t ctrl_size = (vertex_count + 3) / 4;
size_t gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0;
if (buffer_size < codes_size + ctrl_size + gap_size)
return -2;
const unsigned char* end = buffer + buffer_size;
const unsigned char* codes = end - codes_size;
const unsigned char* ctrl = codes - ctrl_size;
const unsigned char* data = buffer;
// gap ensures we have at least 16 bytes available after bound; this allows SIMD decoders to over-read safely
const unsigned char* bound = ctrl - gap_size;
assert(bound >= buffer && bound + 16 <= buffer + buffer_size);
#if defined(SIMD_FALLBACK)
return (gDecodeTablesInitialized ? decodeMeshletSimd<0> : decodeMeshlet)(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, vertex_size, triangle_size);
#elif defined(SIMD_SSE) || defined(SIMD_NEON)
return decodeMeshletSimd<0>(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, vertex_size, triangle_size);
#else
return decodeMeshlet(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, vertex_size, triangle_size);
#endif
}
int meshopt_decodeMeshletRaw(unsigned int* vertices, size_t vertex_count, unsigned int* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size)
{
using namespace meshopt;
assert(triangle_count <= 256 && vertex_count <= 256);
// layout must match encoding
size_t codes_size = (triangle_count + 1) / 2;
size_t ctrl_size = (vertex_count + 3) / 4;
size_t gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0;
if (buffer_size < codes_size + ctrl_size + gap_size)
return -2;
const unsigned char* end = buffer + buffer_size;
const unsigned char* codes = end - codes_size;
const unsigned char* ctrl = codes - ctrl_size;
const unsigned char* data = buffer;
// gap ensures we have at least 16 bytes available after bound; this allows SIMD decoders to over-read safely
const unsigned char* bound = ctrl - gap_size;
assert(bound >= buffer && bound + 16 <= buffer + buffer_size);
#if defined(SIMD_FALLBACK)
return (gDecodeTablesInitialized ? decodeMeshletSimd<1> : decodeMeshlet)(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, 4, 4);
#elif defined(SIMD_SSE) || defined(SIMD_NEON)
return decodeMeshletSimd<1>(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, 4, 4);
#else
return decodeMeshlet(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, 4, 4);
#endif
}
#undef SIMD_SSE
#undef SIMD_NEON
#undef SIMD_FALLBACK
#undef SIMD_FLATTEN
#undef SIMD_TARGET