Path: blob/master/thirdparty/meshoptimizer/opacitymap.cpp
59209 views
// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details1#include "meshoptimizer.h"23#include <assert.h>4#include <math.h>5#include <string.h>67namespace meshopt8{910// opacity micromaps use a "bird" traversal order which recursively subdivides the triangles:11// https://docs.vulkan.org/spec/latest/_images/micromap-subd.svg12// note that triangles 0 and 2 have the same winding as the source triangle, however triangles 1 (flipped)13// and 3 (upright) have flipped winding; this is obvious from the level 2 subdivision in the diagram above14inline size_t getLevelSize(int level, int states)15{16// 1-bit 2-state or 2-bit 4-state per micro triangle, rounded up to whole bytes17return ((1 << (level * 2)) * (states >> 1) + 7) >> 3;18}1920struct Texture21{22const unsigned char* data;23size_t stride, pitch;24unsigned int width, height;25float widthf, heightf; // width * 256.f, height * 256.f26};2728static float sampleTexture(const Texture& texture, float u, float v)29{30// wrap texture coordinates; floor is expensive so only call it if we're outside of [0, 1] range (+eps)31u = fabsf(u - 0.5f) > 0.5f ? u - floorf(u) : u;32v = fabsf(v - 0.5f) > 0.5f ? v - floorf(v) : v;3334// convert from [0, 1] to 16.8 fixed point coordinates (rounded to nearest subpixel) with texel centers on integer grid35int uf = int(u * texture.widthf - 127.5f);36int vf = int(v * texture.heightf - 127.5f);3738// clamp to avoid extrapolation past left/top edge since we don't wrap across the edge39uf = uf < 0 ? 0 : uf;40vf = vf < 0 ? 0 : vf;4142// x/y are texel coordinates, rx/ry are subpixel offsets43int x = uf >> 8;44int y = vf >> 8;45int rx = uf & 255;46int ry = vf & 255;4748// safeguard: this should not happen but if it ever does, ensure the accesses are inbounds49if (unsigned(x) >= texture.width || unsigned(y) >= texture.height)50return 0.f;5152// clamp the offsets instead of wrapping for simplicity and performance53size_t offset = size_t(y) * texture.pitch + x * texture.stride;54size_t offsetx = (x + 1 < int(texture.width)) ? texture.stride : 0;55size_t offsety = (y + 1 < int(texture.height)) ? texture.pitch : 0;5657unsigned char a00 = texture.data[offset];58unsigned char a10 = texture.data[offset + offsetx];59unsigned char a01 = texture.data[offset + offsety];60unsigned char a11 = texture.data[offset + offsetx + offsety];6162// bilinear interpolation in integer space: result is 8.16 fixed point63int ax0 = a00 * 256 + (a10 - a00) * rx;64int ax1 = a01 * 256 + (a11 - a01) * rx;65int axy = ax0 * 256 + (ax1 - ax0) * ry;6667return float(axy) * (1.f / (255.f * 65536.f));68}6970static unsigned int hashUpdate4u(unsigned int h, const unsigned char* key, size_t len)71{72// MurmurHash273const unsigned int m = 0x5bd1e995;74const int r = 24;7576while (len >= 4)77{78unsigned int k;79memcpy(&k, key, sizeof(k));8081k *= m;82k ^= k >> r;83k *= m;8485h *= m;86h ^= k;8788key += 4;89len -= 4;90}9192return h;93}9495struct TriangleOMM96{97int uvs[6];98int level;99};100101struct TriangleOMMHasher102{103const TriangleOMM* data;104105size_t hash(unsigned int index) const106{107const TriangleOMM& tri = data[index];108109return hashUpdate4u(tri.level, reinterpret_cast<const unsigned char*>(tri.uvs), sizeof(tri.uvs));110}111112bool equal(unsigned int lhs, unsigned int rhs) const113{114const TriangleOMM& lt = data[lhs];115const TriangleOMM& rt = data[rhs];116117return lt.level == rt.level && memcmp(lt.uvs, rt.uvs, sizeof(lt.uvs)) == 0;118}119};120121struct OMMHasher122{123const unsigned char* data;124const unsigned int* offsets;125const unsigned char* levels;126int states;127128size_t hash(unsigned int index) const129{130const unsigned char* key = data + offsets[index];131size_t size = getLevelSize(levels[index], states);132133unsigned int h = levels[index];134135// MurmurHash2 for large keys, simple fold for small; note that size is a power of two136if (size < 4)137h ^= key[0] | (key[size - 1] << 8);138else139h = hashUpdate4u(h, key, size);140141// MurmurHash2 finalizer142h ^= h >> 13;143h *= 0x5bd1e995;144h ^= h >> 15;145return h;146}147148bool equal(unsigned int lhs, unsigned int rhs) const149{150size_t size = getLevelSize(levels[lhs], states);151152return levels[lhs] == levels[rhs] && memcmp(data + offsets[lhs], data + offsets[rhs], size) == 0;153}154};155156static size_t hashBuckets3(size_t count)157{158size_t buckets = 1;159while (buckets < count + count / 4)160buckets *= 2;161162return buckets;163}164165template <typename T, typename Hash>166static T* hashLookup3(T* table, size_t buckets, const Hash& hash, const T& key, const T& empty)167{168assert(buckets > 0);169assert((buckets & (buckets - 1)) == 0);170171size_t hashmod = buckets - 1;172size_t bucket = hash.hash(key) & hashmod;173174for (size_t probe = 0; probe <= hashmod; ++probe)175{176T& item = table[bucket];177178if (item == empty)179return &item;180181if (hash.equal(item, key))182return &item;183184// hash collision, quadratic probing185bucket = (bucket + probe + 1) & hashmod;186}187188assert(false && "Hash table is full"); // unreachable189return NULL;190}191192inline int quantizeSubpixel(float v, unsigned int size)193{194return int(v * float(int(size) * 4) + (v >= 0 ? 0.5f : -0.5f));195}196197static int rasterizeEdge(float u0, float v0, float u1, float v1, int edgeres, const Texture& texture)198{199float edgestep = 1.f / float(edgeres + 1);200201float ud = (u1 - u0) * edgestep, vd = (v1 - v0) * edgestep;202float u = u0, v = v0;203204int mask = 0;205int count = 0;206207for (int i = 0; i < edgeres; ++i)208{209u += ud;210v += vd;211212float a = sampleTexture(texture, u, v);213mask |= (a >= 0.5f) << i;214count += a >= 0.5f;215}216217return mask | (count << 16);218}219220template <int States>221static void rasterizeOpacity0(unsigned char* result, size_t index, float a0, float a1, float a2, float ac, int e0, int e1, int e2, int edgeres)222{223int states = States;224225// basic coverage estimator from center and corner values; trained to minimize error226float coverage = (a0 + a1 + a2) * 0.12f + ac * 0.64f;227228if (edgeres)229{230float edgescale = 1.f / edgeres;231232// if we have edge samples, we can get a better coverage estimate by including them; trained to minimize error233coverage = ac * 0.22f + float((e0 >> 16) + (e1 >> 16) + (e2 >> 16)) * edgescale * 0.23f + (a0 + a1 + a2) * 0.03f;234}235236if (states == 2)237{238result[index / 8] |= (coverage >= 0.5f) << (index % 8);239return;240}241242int transp = (a0 < 0.5f) & (a1 < 0.5f) & (a2 < 0.5f) & (ac < 0.5f);243int opaque = (a0 > 0.5f) & (a1 > 0.5f) & (a2 > 0.5f) & (ac > 0.5f);244245// treat state as known if thresholding of corners & centers against wider bounds is consistent246// for unknown states, we currently use the same formula as the 2-state opacity for better consistency with forced 2-state247int unknown = 2 + (coverage >= 0.5f);248int state = (transp | opaque) ? opaque : unknown;249250if (edgeres && (transp | opaque))251{252// if we have edge samples, ensure they are consistent too, falling back to unknown if not253int exp = opaque ? (1 << edgeres) - 1 : 0;254int eok = ((e0 & 0xffff) == exp) & ((e1 & 0xffff) == exp) & ((e2 & 0xffff) == exp);255256state = eok ? state : unknown;257}258259result[index / 4] |= state << ((index % 4) * 2);260}261262template <int States>263static void rasterizeOpacity1(unsigned char* result, size_t index, int edgeres, const float* c0, const float* c1, const float* c2, const Texture& texture)264{265// compute each edge midpoint & sample266float c01[3] = {(c0[0] + c1[0]) / 2, (c0[1] + c1[1]) / 2, 0.f};267float c12[3] = {(c1[0] + c2[0]) / 2, (c1[1] + c2[1]) / 2, 0.f};268float c20[3] = {(c2[0] + c0[0]) / 2, (c2[1] + c0[1]) / 2, 0.f};269270c01[2] = sampleTexture(texture, c01[0], c01[1]);271c12[2] = sampleTexture(texture, c12[0], c12[1]);272c20[2] = sampleTexture(texture, c20[0], c20[1]);273274// corner tables for each edge, and corner + edge tables for each triangle275// edges are numbered counter clockwise, 6 outer first, 3 inner last; triangle vertex and edge references are in triangle winding order276static const unsigned char edges[9][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 0}, {5, 1}, {1, 3}, {3, 5}};277static 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}};278279const float* points[] = {c0, c01, c1, c12, c2, c20};280281int em[9] = {};282283// sample additional points on the edges to improve state estimation284if (edgeres > 0)285for (size_t i = 0; i < 9; ++i)286em[i] = rasterizeEdge(points[edges[i][0]][0], points[edges[i][0]][1], points[edges[i][1]][0], points[edges[i][1]][1], edgeres, texture);287288for (size_t i = 0; i < 4; ++i)289{290const unsigned char* tri = triangles[i];291const float* p0 = points[tri[0]];292const float* p1 = points[tri[1]];293const float* p2 = points[tri[2]];294295// compute triangle center & sample296float uc = (p0[0] + p1[0] + p2[0]) * (1.f / 3.f);297float vc = (p0[1] + p1[1] + p2[1]) * (1.f / 3.f);298float ac = sampleTexture(texture, uc, vc);299300// rasterize opacity state based on alpha values in corners and center (and optionally edges)301rasterizeOpacity0<States>(result, index * 4 + i, p0[2], p1[2], p2[2], ac, em[tri[3]], em[tri[4]], em[tri[5]], edgeres);302}303}304305template <int States>306static void rasterizeOpacityRec(unsigned char* result, size_t index, int level, int edgeres, const float* c0, const float* c1, const float* c2, const Texture& texture)307{308if (level == 0)309{310// compute triangle center & sample311float uc = (c0[0] + c1[0] + c2[0]) * (1.f / 3.f);312float vc = (c0[1] + c1[1] + c2[1]) * (1.f / 3.f);313float ac = sampleTexture(texture, uc, vc);314315int e0 = 0, e1 = 0, e2 = 0;316317if (edgeres > 0)318{319// sample additional points on the edges to improve state estimation320e0 = rasterizeEdge(c0[0], c0[1], c1[0], c1[1], edgeres, texture);321e1 = rasterizeEdge(c1[0], c1[1], c2[0], c2[1], edgeres, texture);322e2 = rasterizeEdge(c2[0], c2[1], c0[0], c0[1], edgeres, texture);323}324325// rasterize opacity state based on alpha values in corners and center (and optionally edges)326return rasterizeOpacity0<States>(result, index, c0[2], c1[2], c2[2], ac, e0, e1, e2, edgeres);327}328329// fast path: equivalent to recursive rasterization, but reuses edge data to reduce sample count330if (level == 1 && edgeres > 0)331return rasterizeOpacity1<States>(result, index, edgeres, c0, c1, c2, texture);332333// compute each edge midpoint & sample334float c01[3] = {(c0[0] + c1[0]) / 2, (c0[1] + c1[1]) / 2, 0.f};335float c12[3] = {(c1[0] + c2[0]) / 2, (c1[1] + c2[1]) / 2, 0.f};336float c20[3] = {(c2[0] + c0[0]) / 2, (c2[1] + c0[1]) / 2, 0.f};337338c01[2] = sampleTexture(texture, c01[0], c01[1]);339c12[2] = sampleTexture(texture, c12[0], c12[1]);340c20[2] = sampleTexture(texture, c20[0], c20[1]);341342// recursively rasterize each triangle343// note: triangles 1 and 3 have flipped winding, and 1 is flipped upside down344rasterizeOpacityRec<States>(result, index * 4 + 0, level - 1, edgeres, c0, c01, c20, texture);345rasterizeOpacityRec<States>(result, index * 4 + 1, level - 1, edgeres, c20, c12, c01, texture);346rasterizeOpacityRec<States>(result, index * 4 + 2, level - 1, edgeres, c01, c1, c12, texture);347rasterizeOpacityRec<States>(result, index * 4 + 3, level - 1, edgeres, c12, c20, c2, texture);348}349350static int getSpecialIndex(const unsigned char* data, int level, int states)351{352int first = data[0] & (states == 2 ? 1 : 3);353int special = -(1 + first);354355// at level 0, every micromap can be converted to a special index356if (level == 0)357return special;358359// at level 1 with 2 states, the byte is partially filled so we need a separate check360if (level == 1 && states == 2)361return (data[0] & 15) == ((-first) & 15) ? special : 0;362363// otherwise we need to check that all bytes are consistent with the first value and we can do this byte-wise364int expected = first * (states == 2 ? 0xff : 0x55);365size_t size = getLevelSize(level, states);366367for (size_t i = 0; i < size; ++i)368if (data[i] != expected)369return 0;370371return special;372}373374} // namespace meshopt375376size_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)377{378using namespace meshopt;379380assert(index_count % 3 == 0);381assert(vertex_uvs_stride >= 8 && vertex_uvs_stride <= 256);382assert(vertex_uvs_stride % sizeof(float) == 0);383assert(unsigned(texture_width - 1) < 16384 && unsigned(texture_height - 1) < 16384);384assert(max_level >= 0 && max_level <= 12);385assert(target_edge >= 0);386387(void)vertex_count;388389meshopt_Allocator allocator;390391size_t vertex_stride_float = vertex_uvs_stride / sizeof(float);392float texture_area = float(texture_width) * float(texture_height);393394// hash map used to deduplicate triangle rasterization requests based on UV395size_t table_size = hashBuckets3(index_count / 3);396unsigned int* table = allocator.allocate<unsigned int>(table_size);397memset(table, -1, table_size * sizeof(unsigned int));398399TriangleOMM* triangles = allocator.allocate<TriangleOMM>(index_count / 3);400TriangleOMMHasher hasher = {triangles};401402size_t result = 0;403404for (size_t i = 0; i < index_count; i += 3)405{406unsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2];407assert(a < vertex_count && b < vertex_count && c < vertex_count);408409float u0 = vertex_uvs[a * vertex_stride_float + 0], v0 = vertex_uvs[a * vertex_stride_float + 1];410float u1 = vertex_uvs[b * vertex_stride_float + 0], v1 = vertex_uvs[b * vertex_stride_float + 1];411float u2 = vertex_uvs[c * vertex_stride_float + 0], v2 = vertex_uvs[c * vertex_stride_float + 1];412413int level = max_level;414415if (target_edge > 0)416{417// compute ratio of edge length (in texels) to target and determine subdivision level418float uvarea = fabsf((u1 - u0) * (v2 - v0) - (u2 - u0) * (v1 - v0)) * 0.5f * texture_area;419float ratio = sqrtf(uvarea) / target_edge;420float levelf = log2f(ratio > 1 ? ratio : 1);421422// round to nearest and clamp423level = int(levelf + 0.5f);424level = level < 0 ? 0 : level;425level = level < max_level ? level : max_level;426}427428// deduplicate rasterization requests based on UV429int su0 = quantizeSubpixel(u0, texture_width), sv0 = quantizeSubpixel(v0, texture_height);430int su1 = quantizeSubpixel(u1, texture_width), sv1 = quantizeSubpixel(v1, texture_height);431int su2 = quantizeSubpixel(u2, texture_width), sv2 = quantizeSubpixel(v2, texture_height);432433TriangleOMM tri = {{su0, sv0, su1, sv1, su2, sv2}, level};434triangles[result] = tri; // speculatively write triangle data to give hasher a way to compare it435436unsigned int* entry = hashLookup3(table, table_size, hasher, unsigned(result), ~0u);437438if (*entry == ~0u)439{440*entry = unsigned(result);441levels[result] = (unsigned char)level;442sources[result] = unsigned(i / 3);443result++;444}445446omm_indices[i / 3] = int(*entry);447}448449return result;450}451452size_t meshopt_opacityMapEntrySize(int level, int states)453{454assert(level >= 0 && level <= 12);455assert(states == 2 || states == 4);456457return meshopt::getLevelSize(level, states);458}459460void 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)461{462using namespace meshopt;463464assert(level >= 0 && level <= 12);465assert(states == 2 || states == 4);466assert(unsigned(texture_width - 1) < 16384 && unsigned(texture_height - 1) < 16384);467assert(texture_stride >= 1 && texture_stride <= 4);468assert(texture_pitch >= texture_stride * texture_width);469470memset(result, 0, getLevelSize(level, states));471472Texture texture = {texture_data, texture_stride, texture_pitch, texture_width, texture_height, float(int(texture_width)) * 256.f, float(int(texture_height)) * 256.f};473474// determine number of edge samples for conservative state estimation475float texture_area = float(int(texture_width)) * float(int(texture_height));476float uvarea = fabsf((uv1[0] - uv0[0]) * (uv2[1] - uv0[1]) - (uv2[0] - uv0[0]) * (uv1[1] - uv0[1])) * 0.5f * texture_area;477float uvedge = sqrtf(uvarea) / float(1 << level);478479// target ~2px distance between edge samples (assuming equilateral microtriangles)480int edgeres = int(uvedge * 0.75f);481edgeres = edgeres < 0 ? 0 : edgeres;482edgeres = edgeres > 7 ? 7 : edgeres;483484// rasterize all micro triangles recursively, passing corner data down to reduce redundant sampling485float c0[3] = {uv0[0], uv0[1], sampleTexture(texture, uv0[0], uv0[1])};486float c1[3] = {uv1[0], uv1[1], sampleTexture(texture, uv1[0], uv1[1])};487float c2[3] = {uv2[0], uv2[1], sampleTexture(texture, uv2[0], uv2[1])};488489(states == 2 ? rasterizeOpacityRec<2> : rasterizeOpacityRec<4>)(result, 0, level, edgeres, c0, c1, c2, texture);490}491492size_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)493{494using namespace meshopt;495496assert(states == 2 || states == 4);497498meshopt_Allocator allocator;499500unsigned char* data_old = allocator.allocate<unsigned char>(data_size);501memcpy(data_old, data, data_size);502503size_t table_size = hashBuckets3(omm_count);504unsigned int* table = allocator.allocate<unsigned int>(table_size);505memset(table, -1, table_size * sizeof(unsigned int));506507OMMHasher hasher = {data, offsets, levels, states};508509int* remap = allocator.allocate<int>(omm_count);510511size_t next = 0;512size_t offset = 0;513514for (size_t i = 0; i < omm_count; ++i)515{516int level = levels[i];517assert(level >= 0 && level <= 12);518519const unsigned char* old = data_old + offsets[i];520size_t size = getLevelSize(level, states);521assert(offsets[i] + size <= data_size);522523// try to convert to a special index if all micro-triangle states are the same524int special = getSpecialIndex(old, level, states);525if (special < 0)526{527remap[i] = special;528continue;529}530531// speculatively write data to give hasher a way to compare it532memcpy(data + offset, old, size);533offsets[next] = unsigned(offset);534levels[next] = (unsigned char)level;535536unsigned int* entry = hashLookup3(table, table_size, hasher, unsigned(next), ~0u);537538if (*entry == ~0u)539{540*entry = unsigned(next);541next++;542offset += size;543}544545remap[i] = int(*entry);546}547548// remap triangle indices to new indices or special indices549for (size_t i = 0; i < triangle_count; ++i)550{551assert(omm_indices[i] < 0 || unsigned(omm_indices[i]) < omm_count);552omm_indices[i] = omm_indices[i] < 0 ? omm_indices[i] : remap[omm_indices[i]];553}554555return next;556}557558559