Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/meshoptimizer/opacitymap.cpp
59209 views
1
// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details
2
#include "meshoptimizer.h"
3
4
#include <assert.h>
5
#include <math.h>
6
#include <string.h>
7
8
namespace meshopt
9
{
10
11
// opacity micromaps use a "bird" traversal order which recursively subdivides the triangles:
12
// https://docs.vulkan.org/spec/latest/_images/micromap-subd.svg
13
// note that triangles 0 and 2 have the same winding as the source triangle, however triangles 1 (flipped)
14
// and 3 (upright) have flipped winding; this is obvious from the level 2 subdivision in the diagram above
15
inline size_t getLevelSize(int level, int states)
16
{
17
// 1-bit 2-state or 2-bit 4-state per micro triangle, rounded up to whole bytes
18
return ((1 << (level * 2)) * (states >> 1) + 7) >> 3;
19
}
20
21
struct Texture
22
{
23
const unsigned char* data;
24
size_t stride, pitch;
25
unsigned int width, height;
26
float widthf, heightf; // width * 256.f, height * 256.f
27
};
28
29
static float sampleTexture(const Texture& texture, float u, float v)
30
{
31
// wrap texture coordinates; floor is expensive so only call it if we're outside of [0, 1] range (+eps)
32
u = fabsf(u - 0.5f) > 0.5f ? u - floorf(u) : u;
33
v = fabsf(v - 0.5f) > 0.5f ? v - floorf(v) : v;
34
35
// convert from [0, 1] to 16.8 fixed point coordinates (rounded to nearest subpixel) with texel centers on integer grid
36
int uf = int(u * texture.widthf - 127.5f);
37
int vf = int(v * texture.heightf - 127.5f);
38
39
// clamp to avoid extrapolation past left/top edge since we don't wrap across the edge
40
uf = uf < 0 ? 0 : uf;
41
vf = vf < 0 ? 0 : vf;
42
43
// x/y are texel coordinates, rx/ry are subpixel offsets
44
int x = uf >> 8;
45
int y = vf >> 8;
46
int rx = uf & 255;
47
int ry = vf & 255;
48
49
// safeguard: this should not happen but if it ever does, ensure the accesses are inbounds
50
if (unsigned(x) >= texture.width || unsigned(y) >= texture.height)
51
return 0.f;
52
53
// clamp the offsets instead of wrapping for simplicity and performance
54
size_t offset = size_t(y) * texture.pitch + x * texture.stride;
55
size_t offsetx = (x + 1 < int(texture.width)) ? texture.stride : 0;
56
size_t offsety = (y + 1 < int(texture.height)) ? texture.pitch : 0;
57
58
unsigned char a00 = texture.data[offset];
59
unsigned char a10 = texture.data[offset + offsetx];
60
unsigned char a01 = texture.data[offset + offsety];
61
unsigned char a11 = texture.data[offset + offsetx + offsety];
62
63
// bilinear interpolation in integer space: result is 8.16 fixed point
64
int ax0 = a00 * 256 + (a10 - a00) * rx;
65
int ax1 = a01 * 256 + (a11 - a01) * rx;
66
int axy = ax0 * 256 + (ax1 - ax0) * ry;
67
68
return float(axy) * (1.f / (255.f * 65536.f));
69
}
70
71
static unsigned int hashUpdate4u(unsigned int h, const unsigned char* key, size_t len)
72
{
73
// MurmurHash2
74
const unsigned int m = 0x5bd1e995;
75
const int r = 24;
76
77
while (len >= 4)
78
{
79
unsigned int k;
80
memcpy(&k, key, sizeof(k));
81
82
k *= m;
83
k ^= k >> r;
84
k *= m;
85
86
h *= m;
87
h ^= k;
88
89
key += 4;
90
len -= 4;
91
}
92
93
return h;
94
}
95
96
struct TriangleOMM
97
{
98
int uvs[6];
99
int level;
100
};
101
102
struct TriangleOMMHasher
103
{
104
const TriangleOMM* data;
105
106
size_t hash(unsigned int index) const
107
{
108
const TriangleOMM& tri = data[index];
109
110
return hashUpdate4u(tri.level, reinterpret_cast<const unsigned char*>(tri.uvs), sizeof(tri.uvs));
111
}
112
113
bool equal(unsigned int lhs, unsigned int rhs) const
114
{
115
const TriangleOMM& lt = data[lhs];
116
const TriangleOMM& rt = data[rhs];
117
118
return lt.level == rt.level && memcmp(lt.uvs, rt.uvs, sizeof(lt.uvs)) == 0;
119
}
120
};
121
122
struct OMMHasher
123
{
124
const unsigned char* data;
125
const unsigned int* offsets;
126
const unsigned char* levels;
127
int states;
128
129
size_t hash(unsigned int index) const
130
{
131
const unsigned char* key = data + offsets[index];
132
size_t size = getLevelSize(levels[index], states);
133
134
unsigned int h = levels[index];
135
136
// MurmurHash2 for large keys, simple fold for small; note that size is a power of two
137
if (size < 4)
138
h ^= key[0] | (key[size - 1] << 8);
139
else
140
h = hashUpdate4u(h, key, size);
141
142
// MurmurHash2 finalizer
143
h ^= h >> 13;
144
h *= 0x5bd1e995;
145
h ^= h >> 15;
146
return h;
147
}
148
149
bool equal(unsigned int lhs, unsigned int rhs) const
150
{
151
size_t size = getLevelSize(levels[lhs], states);
152
153
return levels[lhs] == levels[rhs] && memcmp(data + offsets[lhs], data + offsets[rhs], size) == 0;
154
}
155
};
156
157
static size_t hashBuckets3(size_t count)
158
{
159
size_t buckets = 1;
160
while (buckets < count + count / 4)
161
buckets *= 2;
162
163
return buckets;
164
}
165
166
template <typename T, typename Hash>
167
static T* hashLookup3(T* table, size_t buckets, const Hash& hash, const T& key, const T& empty)
168
{
169
assert(buckets > 0);
170
assert((buckets & (buckets - 1)) == 0);
171
172
size_t hashmod = buckets - 1;
173
size_t bucket = hash.hash(key) & hashmod;
174
175
for (size_t probe = 0; probe <= hashmod; ++probe)
176
{
177
T& item = table[bucket];
178
179
if (item == empty)
180
return &item;
181
182
if (hash.equal(item, key))
183
return &item;
184
185
// hash collision, quadratic probing
186
bucket = (bucket + probe + 1) & hashmod;
187
}
188
189
assert(false && "Hash table is full"); // unreachable
190
return NULL;
191
}
192
193
inline int quantizeSubpixel(float v, unsigned int size)
194
{
195
return int(v * float(int(size) * 4) + (v >= 0 ? 0.5f : -0.5f));
196
}
197
198
static int rasterizeEdge(float u0, float v0, float u1, float v1, int edgeres, const Texture& texture)
199
{
200
float edgestep = 1.f / float(edgeres + 1);
201
202
float ud = (u1 - u0) * edgestep, vd = (v1 - v0) * edgestep;
203
float u = u0, v = v0;
204
205
int mask = 0;
206
int count = 0;
207
208
for (int i = 0; i < edgeres; ++i)
209
{
210
u += ud;
211
v += vd;
212
213
float a = sampleTexture(texture, u, v);
214
mask |= (a >= 0.5f) << i;
215
count += a >= 0.5f;
216
}
217
218
return mask | (count << 16);
219
}
220
221
template <int States>
222
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)
223
{
224
int states = States;
225
226
// basic coverage estimator from center and corner values; trained to minimize error
227
float coverage = (a0 + a1 + a2) * 0.12f + ac * 0.64f;
228
229
if (edgeres)
230
{
231
float edgescale = 1.f / edgeres;
232
233
// if we have edge samples, we can get a better coverage estimate by including them; trained to minimize error
234
coverage = ac * 0.22f + float((e0 >> 16) + (e1 >> 16) + (e2 >> 16)) * edgescale * 0.23f + (a0 + a1 + a2) * 0.03f;
235
}
236
237
if (states == 2)
238
{
239
result[index / 8] |= (coverage >= 0.5f) << (index % 8);
240
return;
241
}
242
243
int transp = (a0 < 0.5f) & (a1 < 0.5f) & (a2 < 0.5f) & (ac < 0.5f);
244
int opaque = (a0 > 0.5f) & (a1 > 0.5f) & (a2 > 0.5f) & (ac > 0.5f);
245
246
// treat state as known if thresholding of corners & centers against wider bounds is consistent
247
// for unknown states, we currently use the same formula as the 2-state opacity for better consistency with forced 2-state
248
int unknown = 2 + (coverage >= 0.5f);
249
int state = (transp | opaque) ? opaque : unknown;
250
251
if (edgeres && (transp | opaque))
252
{
253
// if we have edge samples, ensure they are consistent too, falling back to unknown if not
254
int exp = opaque ? (1 << edgeres) - 1 : 0;
255
int eok = ((e0 & 0xffff) == exp) & ((e1 & 0xffff) == exp) & ((e2 & 0xffff) == exp);
256
257
state = eok ? state : unknown;
258
}
259
260
result[index / 4] |= state << ((index % 4) * 2);
261
}
262
263
template <int States>
264
static void rasterizeOpacity1(unsigned char* result, size_t index, int edgeres, const float* c0, const float* c1, const float* c2, const Texture& texture)
265
{
266
// compute each edge midpoint & sample
267
float c01[3] = {(c0[0] + c1[0]) / 2, (c0[1] + c1[1]) / 2, 0.f};
268
float c12[3] = {(c1[0] + c2[0]) / 2, (c1[1] + c2[1]) / 2, 0.f};
269
float c20[3] = {(c2[0] + c0[0]) / 2, (c2[1] + c0[1]) / 2, 0.f};
270
271
c01[2] = sampleTexture(texture, c01[0], c01[1]);
272
c12[2] = sampleTexture(texture, c12[0], c12[1]);
273
c20[2] = sampleTexture(texture, c20[0], c20[1]);
274
275
// corner tables for each edge, and corner + edge tables for each triangle
276
// edges are numbered counter clockwise, 6 outer first, 3 inner last; triangle vertex and edge references are in triangle winding order
277
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}};
278
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}};
279
280
const float* points[] = {c0, c01, c1, c12, c2, c20};
281
282
int em[9] = {};
283
284
// sample additional points on the edges to improve state estimation
285
if (edgeres > 0)
286
for (size_t i = 0; i < 9; ++i)
287
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);
288
289
for (size_t i = 0; i < 4; ++i)
290
{
291
const unsigned char* tri = triangles[i];
292
const float* p0 = points[tri[0]];
293
const float* p1 = points[tri[1]];
294
const float* p2 = points[tri[2]];
295
296
// compute triangle center & sample
297
float uc = (p0[0] + p1[0] + p2[0]) * (1.f / 3.f);
298
float vc = (p0[1] + p1[1] + p2[1]) * (1.f / 3.f);
299
float ac = sampleTexture(texture, uc, vc);
300
301
// rasterize opacity state based on alpha values in corners and center (and optionally edges)
302
rasterizeOpacity0<States>(result, index * 4 + i, p0[2], p1[2], p2[2], ac, em[tri[3]], em[tri[4]], em[tri[5]], edgeres);
303
}
304
}
305
306
template <int States>
307
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)
308
{
309
if (level == 0)
310
{
311
// compute triangle center & sample
312
float uc = (c0[0] + c1[0] + c2[0]) * (1.f / 3.f);
313
float vc = (c0[1] + c1[1] + c2[1]) * (1.f / 3.f);
314
float ac = sampleTexture(texture, uc, vc);
315
316
int e0 = 0, e1 = 0, e2 = 0;
317
318
if (edgeres > 0)
319
{
320
// sample additional points on the edges to improve state estimation
321
e0 = rasterizeEdge(c0[0], c0[1], c1[0], c1[1], edgeres, texture);
322
e1 = rasterizeEdge(c1[0], c1[1], c2[0], c2[1], edgeres, texture);
323
e2 = rasterizeEdge(c2[0], c2[1], c0[0], c0[1], edgeres, texture);
324
}
325
326
// rasterize opacity state based on alpha values in corners and center (and optionally edges)
327
return rasterizeOpacity0<States>(result, index, c0[2], c1[2], c2[2], ac, e0, e1, e2, edgeres);
328
}
329
330
// fast path: equivalent to recursive rasterization, but reuses edge data to reduce sample count
331
if (level == 1 && edgeres > 0)
332
return rasterizeOpacity1<States>(result, index, edgeres, c0, c1, c2, texture);
333
334
// compute each edge midpoint & sample
335
float c01[3] = {(c0[0] + c1[0]) / 2, (c0[1] + c1[1]) / 2, 0.f};
336
float c12[3] = {(c1[0] + c2[0]) / 2, (c1[1] + c2[1]) / 2, 0.f};
337
float c20[3] = {(c2[0] + c0[0]) / 2, (c2[1] + c0[1]) / 2, 0.f};
338
339
c01[2] = sampleTexture(texture, c01[0], c01[1]);
340
c12[2] = sampleTexture(texture, c12[0], c12[1]);
341
c20[2] = sampleTexture(texture, c20[0], c20[1]);
342
343
// recursively rasterize each triangle
344
// note: triangles 1 and 3 have flipped winding, and 1 is flipped upside down
345
rasterizeOpacityRec<States>(result, index * 4 + 0, level - 1, edgeres, c0, c01, c20, texture);
346
rasterizeOpacityRec<States>(result, index * 4 + 1, level - 1, edgeres, c20, c12, c01, texture);
347
rasterizeOpacityRec<States>(result, index * 4 + 2, level - 1, edgeres, c01, c1, c12, texture);
348
rasterizeOpacityRec<States>(result, index * 4 + 3, level - 1, edgeres, c12, c20, c2, texture);
349
}
350
351
static int getSpecialIndex(const unsigned char* data, int level, int states)
352
{
353
int first = data[0] & (states == 2 ? 1 : 3);
354
int special = -(1 + first);
355
356
// at level 0, every micromap can be converted to a special index
357
if (level == 0)
358
return special;
359
360
// at level 1 with 2 states, the byte is partially filled so we need a separate check
361
if (level == 1 && states == 2)
362
return (data[0] & 15) == ((-first) & 15) ? special : 0;
363
364
// otherwise we need to check that all bytes are consistent with the first value and we can do this byte-wise
365
int expected = first * (states == 2 ? 0xff : 0x55);
366
size_t size = getLevelSize(level, states);
367
368
for (size_t i = 0; i < size; ++i)
369
if (data[i] != expected)
370
return 0;
371
372
return special;
373
}
374
375
} // namespace meshopt
376
377
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)
378
{
379
using namespace meshopt;
380
381
assert(index_count % 3 == 0);
382
assert(vertex_uvs_stride >= 8 && vertex_uvs_stride <= 256);
383
assert(vertex_uvs_stride % sizeof(float) == 0);
384
assert(unsigned(texture_width - 1) < 16384 && unsigned(texture_height - 1) < 16384);
385
assert(max_level >= 0 && max_level <= 12);
386
assert(target_edge >= 0);
387
388
(void)vertex_count;
389
390
meshopt_Allocator allocator;
391
392
size_t vertex_stride_float = vertex_uvs_stride / sizeof(float);
393
float texture_area = float(texture_width) * float(texture_height);
394
395
// hash map used to deduplicate triangle rasterization requests based on UV
396
size_t table_size = hashBuckets3(index_count / 3);
397
unsigned int* table = allocator.allocate<unsigned int>(table_size);
398
memset(table, -1, table_size * sizeof(unsigned int));
399
400
TriangleOMM* triangles = allocator.allocate<TriangleOMM>(index_count / 3);
401
TriangleOMMHasher hasher = {triangles};
402
403
size_t result = 0;
404
405
for (size_t i = 0; i < index_count; i += 3)
406
{
407
unsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2];
408
assert(a < vertex_count && b < vertex_count && c < vertex_count);
409
410
float u0 = vertex_uvs[a * vertex_stride_float + 0], v0 = vertex_uvs[a * vertex_stride_float + 1];
411
float u1 = vertex_uvs[b * vertex_stride_float + 0], v1 = vertex_uvs[b * vertex_stride_float + 1];
412
float u2 = vertex_uvs[c * vertex_stride_float + 0], v2 = vertex_uvs[c * vertex_stride_float + 1];
413
414
int level = max_level;
415
416
if (target_edge > 0)
417
{
418
// compute ratio of edge length (in texels) to target and determine subdivision level
419
float uvarea = fabsf((u1 - u0) * (v2 - v0) - (u2 - u0) * (v1 - v0)) * 0.5f * texture_area;
420
float ratio = sqrtf(uvarea) / target_edge;
421
float levelf = log2f(ratio > 1 ? ratio : 1);
422
423
// round to nearest and clamp
424
level = int(levelf + 0.5f);
425
level = level < 0 ? 0 : level;
426
level = level < max_level ? level : max_level;
427
}
428
429
// deduplicate rasterization requests based on UV
430
int su0 = quantizeSubpixel(u0, texture_width), sv0 = quantizeSubpixel(v0, texture_height);
431
int su1 = quantizeSubpixel(u1, texture_width), sv1 = quantizeSubpixel(v1, texture_height);
432
int su2 = quantizeSubpixel(u2, texture_width), sv2 = quantizeSubpixel(v2, texture_height);
433
434
TriangleOMM tri = {{su0, sv0, su1, sv1, su2, sv2}, level};
435
triangles[result] = tri; // speculatively write triangle data to give hasher a way to compare it
436
437
unsigned int* entry = hashLookup3(table, table_size, hasher, unsigned(result), ~0u);
438
439
if (*entry == ~0u)
440
{
441
*entry = unsigned(result);
442
levels[result] = (unsigned char)level;
443
sources[result] = unsigned(i / 3);
444
result++;
445
}
446
447
omm_indices[i / 3] = int(*entry);
448
}
449
450
return result;
451
}
452
453
size_t meshopt_opacityMapEntrySize(int level, int states)
454
{
455
assert(level >= 0 && level <= 12);
456
assert(states == 2 || states == 4);
457
458
return meshopt::getLevelSize(level, states);
459
}
460
461
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)
462
{
463
using namespace meshopt;
464
465
assert(level >= 0 && level <= 12);
466
assert(states == 2 || states == 4);
467
assert(unsigned(texture_width - 1) < 16384 && unsigned(texture_height - 1) < 16384);
468
assert(texture_stride >= 1 && texture_stride <= 4);
469
assert(texture_pitch >= texture_stride * texture_width);
470
471
memset(result, 0, getLevelSize(level, states));
472
473
Texture texture = {texture_data, texture_stride, texture_pitch, texture_width, texture_height, float(int(texture_width)) * 256.f, float(int(texture_height)) * 256.f};
474
475
// determine number of edge samples for conservative state estimation
476
float texture_area = float(int(texture_width)) * float(int(texture_height));
477
float uvarea = fabsf((uv1[0] - uv0[0]) * (uv2[1] - uv0[1]) - (uv2[0] - uv0[0]) * (uv1[1] - uv0[1])) * 0.5f * texture_area;
478
float uvedge = sqrtf(uvarea) / float(1 << level);
479
480
// target ~2px distance between edge samples (assuming equilateral microtriangles)
481
int edgeres = int(uvedge * 0.75f);
482
edgeres = edgeres < 0 ? 0 : edgeres;
483
edgeres = edgeres > 7 ? 7 : edgeres;
484
485
// rasterize all micro triangles recursively, passing corner data down to reduce redundant sampling
486
float c0[3] = {uv0[0], uv0[1], sampleTexture(texture, uv0[0], uv0[1])};
487
float c1[3] = {uv1[0], uv1[1], sampleTexture(texture, uv1[0], uv1[1])};
488
float c2[3] = {uv2[0], uv2[1], sampleTexture(texture, uv2[0], uv2[1])};
489
490
(states == 2 ? rasterizeOpacityRec<2> : rasterizeOpacityRec<4>)(result, 0, level, edgeres, c0, c1, c2, texture);
491
}
492
493
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)
494
{
495
using namespace meshopt;
496
497
assert(states == 2 || states == 4);
498
499
meshopt_Allocator allocator;
500
501
unsigned char* data_old = allocator.allocate<unsigned char>(data_size);
502
memcpy(data_old, data, data_size);
503
504
size_t table_size = hashBuckets3(omm_count);
505
unsigned int* table = allocator.allocate<unsigned int>(table_size);
506
memset(table, -1, table_size * sizeof(unsigned int));
507
508
OMMHasher hasher = {data, offsets, levels, states};
509
510
int* remap = allocator.allocate<int>(omm_count);
511
512
size_t next = 0;
513
size_t offset = 0;
514
515
for (size_t i = 0; i < omm_count; ++i)
516
{
517
int level = levels[i];
518
assert(level >= 0 && level <= 12);
519
520
const unsigned char* old = data_old + offsets[i];
521
size_t size = getLevelSize(level, states);
522
assert(offsets[i] + size <= data_size);
523
524
// try to convert to a special index if all micro-triangle states are the same
525
int special = getSpecialIndex(old, level, states);
526
if (special < 0)
527
{
528
remap[i] = special;
529
continue;
530
}
531
532
// speculatively write data to give hasher a way to compare it
533
memcpy(data + offset, old, size);
534
offsets[next] = unsigned(offset);
535
levels[next] = (unsigned char)level;
536
537
unsigned int* entry = hashLookup3(table, table_size, hasher, unsigned(next), ~0u);
538
539
if (*entry == ~0u)
540
{
541
*entry = unsigned(next);
542
next++;
543
offset += size;
544
}
545
546
remap[i] = int(*entry);
547
}
548
549
// remap triangle indices to new indices or special indices
550
for (size_t i = 0; i < triangle_count; ++i)
551
{
552
assert(omm_indices[i] < 0 || unsigned(omm_indices[i]) < omm_count);
553
omm_indices[i] = omm_indices[i] < 0 ? omm_indices[i] : remap[omm_indices[i]];
554
}
555
556
return next;
557
}
558
559