tests.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. #include "../src/meshoptimizer.h"
  2. #include <assert.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <vector>
  6. // This file uses assert() to verify algorithm correctness
  7. #undef NDEBUG
  8. #include <assert.h>
  9. struct PV
  10. {
  11. unsigned short px, py, pz;
  12. unsigned char nu, nv; // octahedron encoded normal, aliases .pw
  13. unsigned short tx, ty;
  14. };
  15. // note: 4 6 5 triangle here is a combo-breaker:
  16. // we encode it without rotating, a=next, c=next - this means we do *not* bump next to 6
  17. // which means that the next triangle can't be encoded via next sequencing!
  18. static const unsigned int kIndexBuffer[] = {0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9};
  19. static const unsigned char kIndexDataV0[] = {
  20. 0xe0, 0xf0, 0x10, 0xfe, 0xff, 0xf0, 0x0c, 0xff, 0x02, 0x02, 0x02, 0x00, 0x76, 0x87, 0x56, 0x67,
  21. 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00, // clang-format :-/
  22. };
  23. static const PV kVertexBuffer[] = {
  24. {0, 0, 0, 0, 0, 0, 0},
  25. {300, 0, 0, 0, 0, 500, 0},
  26. {0, 300, 0, 0, 0, 0, 500},
  27. {300, 300, 0, 0, 0, 500, 500},
  28. };
  29. static const unsigned char kVertexDataV0[] = {
  30. 0xa0, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x58, 0x57, 0x58, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01,
  31. 0x0c, 0x00, 0x00, 0x00, 0x58, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  32. 0x3f, 0x00, 0x00, 0x00, 0x17, 0x18, 0x17, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00,
  33. 0x00, 0x00, 0x17, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  34. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  35. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // clang-format :-/
  36. };
  37. static void decodeIndexV0()
  38. {
  39. const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);
  40. std::vector<unsigned char> buffer(kIndexDataV0, kIndexDataV0 + sizeof(kIndexDataV0));
  41. unsigned int decoded[index_count];
  42. assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0);
  43. assert(memcmp(decoded, kIndexBuffer, sizeof(kIndexBuffer)) == 0);
  44. }
  45. static void decodeIndex16()
  46. {
  47. const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);
  48. const size_t vertex_count = 10;
  49. std::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count));
  50. buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count));
  51. unsigned short decoded[index_count];
  52. assert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0);
  53. for (size_t i = 0; i < index_count; ++i)
  54. assert(decoded[i] == kIndexBuffer[i]);
  55. }
  56. static void encodeIndexMemorySafe()
  57. {
  58. const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);
  59. const size_t vertex_count = 10;
  60. std::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count));
  61. buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count));
  62. // check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access
  63. for (size_t i = 0; i <= buffer.size(); ++i)
  64. {
  65. std::vector<unsigned char> shortbuffer(i);
  66. size_t result = meshopt_encodeIndexBuffer(i == 0 ? 0 : &shortbuffer[0], i, kIndexBuffer, index_count);
  67. if (i == buffer.size())
  68. assert(result == buffer.size());
  69. else
  70. assert(result == 0);
  71. }
  72. }
  73. static void decodeIndexMemorySafe()
  74. {
  75. const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);
  76. const size_t vertex_count = 10;
  77. std::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count));
  78. buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count));
  79. // check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access
  80. unsigned int decoded[index_count];
  81. for (size_t i = 0; i <= buffer.size(); ++i)
  82. {
  83. std::vector<unsigned char> shortbuffer(buffer.begin(), buffer.begin() + i);
  84. int result = meshopt_decodeIndexBuffer(decoded, index_count, i == 0 ? 0 : &shortbuffer[0], i);
  85. if (i == buffer.size())
  86. assert(result == 0);
  87. else
  88. assert(result < 0);
  89. }
  90. }
  91. static void decodeIndexRejectExtraBytes()
  92. {
  93. const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);
  94. const size_t vertex_count = 10;
  95. std::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count));
  96. buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count));
  97. // check that decoder doesn't accept extra bytes after a valid stream
  98. std::vector<unsigned char> largebuffer(buffer);
  99. largebuffer.push_back(0);
  100. unsigned int decoded[index_count];
  101. assert(meshopt_decodeIndexBuffer(decoded, index_count, &largebuffer[0], largebuffer.size()) < 0);
  102. }
  103. static void decodeIndexRejectMalformedHeaders()
  104. {
  105. const size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);
  106. const size_t vertex_count = 10;
  107. std::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count));
  108. buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count));
  109. // check that decoder doesn't accept malformed headers
  110. std::vector<unsigned char> brokenbuffer(buffer);
  111. brokenbuffer[0] = 0;
  112. unsigned int decoded[index_count];
  113. assert(meshopt_decodeIndexBuffer(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0);
  114. }
  115. static void decodeVertexV0()
  116. {
  117. const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]);
  118. std::vector<unsigned char> buffer(kVertexDataV0, kVertexDataV0 + sizeof(kVertexDataV0));
  119. PV decoded[vertex_count];
  120. assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &buffer[0], buffer.size()) == 0);
  121. assert(memcmp(decoded, kVertexBuffer, sizeof(kVertexBuffer)) == 0);
  122. }
  123. static void encodeVertexMemorySafe()
  124. {
  125. const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]);
  126. std::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV)));
  127. buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV)));
  128. // check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access
  129. for (size_t i = 0; i <= buffer.size(); ++i)
  130. {
  131. std::vector<unsigned char> shortbuffer(i);
  132. size_t result = meshopt_encodeVertexBuffer(i == 0 ? 0 : &shortbuffer[0], i, kVertexBuffer, vertex_count, sizeof(PV));
  133. if (i == buffer.size())
  134. assert(result == buffer.size());
  135. else
  136. assert(result == 0);
  137. }
  138. }
  139. static void decodeVertexMemorySafe()
  140. {
  141. const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]);
  142. std::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV)));
  143. buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV)));
  144. // check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access
  145. PV decoded[vertex_count];
  146. for (size_t i = 0; i <= buffer.size(); ++i)
  147. {
  148. std::vector<unsigned char> shortbuffer(buffer.begin(), buffer.begin() + i);
  149. int result = meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), i == 0 ? 0 : &shortbuffer[0], i);
  150. (void)result;
  151. if (i == buffer.size())
  152. assert(result == 0);
  153. else
  154. assert(result < 0);
  155. }
  156. }
  157. static void decodeVertexRejectExtraBytes()
  158. {
  159. const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]);
  160. std::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV)));
  161. buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV)));
  162. // check that decoder doesn't accept extra bytes after a valid stream
  163. std::vector<unsigned char> largebuffer(buffer);
  164. largebuffer.push_back(0);
  165. PV decoded[vertex_count];
  166. assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &largebuffer[0], largebuffer.size()) < 0);
  167. }
  168. static void decodeVertexRejectMalformedHeaders()
  169. {
  170. const size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]);
  171. std::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV)));
  172. buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV)));
  173. // check that decoder doesn't accept malformed headers
  174. std::vector<unsigned char> brokenbuffer(buffer);
  175. brokenbuffer[0] = 0;
  176. PV decoded[vertex_count];
  177. assert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &brokenbuffer[0], brokenbuffer.size()) < 0);
  178. }
  179. static void clusterBoundsDegenerate()
  180. {
  181. const float vbd[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
  182. const unsigned int ibd[] = {0, 0, 0};
  183. const unsigned int ib1[] = {0, 1, 2};
  184. // all of the bounds below are degenerate as they use 0 triangles, one topology-degenerate triangle and one position-degenerate triangle respectively
  185. meshopt_Bounds bounds0 = meshopt_computeClusterBounds(0, 0, 0, 0, 12);
  186. meshopt_Bounds boundsd = meshopt_computeClusterBounds(ibd, 3, vbd, 3, 12);
  187. meshopt_Bounds bounds1 = meshopt_computeClusterBounds(ib1, 3, vbd, 3, 12);
  188. assert(bounds0.center[0] == 0 && bounds0.center[1] == 0 && bounds0.center[2] == 0 && bounds0.radius == 0);
  189. assert(boundsd.center[0] == 0 && boundsd.center[1] == 0 && boundsd.center[2] == 0 && boundsd.radius == 0);
  190. assert(bounds1.center[0] == 0 && bounds1.center[1] == 0 && bounds1.center[2] == 0 && bounds1.radius == 0);
  191. const float vb1[] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
  192. const unsigned int ib2[] = {0, 1, 2, 0, 2, 1};
  193. // these bounds have a degenerate cone since the cluster has two triangles with opposite normals
  194. meshopt_Bounds bounds2 = meshopt_computeClusterBounds(ib2, 6, vb1, 3, 12);
  195. assert(bounds2.cone_apex[0] == 0 && bounds2.cone_apex[1] == 0 && bounds2.cone_apex[2] == 0);
  196. assert(bounds2.cone_axis[0] == 0 && bounds2.cone_axis[1] == 0 && bounds2.cone_axis[2] == 0);
  197. assert(bounds2.cone_cutoff == 1);
  198. assert(bounds2.cone_axis_s8[0] == 0 && bounds2.cone_axis_s8[1] == 0 && bounds2.cone_axis_s8[2] == 0);
  199. assert(bounds2.cone_cutoff_s8 == 127);
  200. // however, the bounding sphere needs to be in tact (here we only check bbox for simplicity)
  201. assert(bounds2.center[0] - bounds2.radius <= 0 && bounds2.center[0] + bounds2.radius >= 1);
  202. assert(bounds2.center[1] - bounds2.radius <= 0 && bounds2.center[1] + bounds2.radius >= 1);
  203. assert(bounds2.center[2] - bounds2.radius <= 0 && bounds2.center[2] + bounds2.radius >= 1);
  204. }
  205. static size_t allocCount;
  206. static size_t freeCount;
  207. static void* customAlloc(size_t size)
  208. {
  209. allocCount++;
  210. return malloc(size);
  211. }
  212. static void customFree(void* ptr)
  213. {
  214. freeCount++;
  215. free(ptr);
  216. }
  217. static void customAllocator()
  218. {
  219. meshopt_setAllocator(customAlloc, customFree);
  220. assert(allocCount == 0 && freeCount == 0);
  221. float vb[] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
  222. unsigned int ib[] = {0, 1, 2};
  223. unsigned short ibs[] = {0, 1, 2};
  224. // meshopt_computeClusterBounds doesn't allocate
  225. meshopt_computeClusterBounds(ib, 3, vb, 3, 12);
  226. assert(allocCount == 0 && freeCount == 0);
  227. // ... unless IndexAdapter is used
  228. meshopt_computeClusterBounds(ibs, 3, vb, 3, 12);
  229. assert(allocCount == 1 && freeCount == 1);
  230. // meshopt_optimizeVertexFetch allocates internal remap table and temporary storage for in-place remaps
  231. meshopt_optimizeVertexFetch(vb, ib, 3, vb, 3, 12);
  232. assert(allocCount == 3 && freeCount == 3);
  233. // ... plus one for IndexAdapter
  234. meshopt_optimizeVertexFetch(vb, ibs, 3, vb, 3, 12);
  235. assert(allocCount == 6 && freeCount == 6);
  236. meshopt_setAllocator(operator new, operator delete);
  237. // customAlloc & customFree should not get called anymore
  238. meshopt_optimizeVertexFetch(vb, ib, 3, vb, 3, 12);
  239. assert(allocCount == 6 && freeCount == 6);
  240. }
  241. static void emptyMesh()
  242. {
  243. meshopt_optimizeVertexCache(0, 0, 0, 0);
  244. meshopt_optimizeVertexCacheFifo(0, 0, 0, 0, 16);
  245. meshopt_optimizeOverdraw(0, 0, 0, 0, 0, 12, 1.f);
  246. }
  247. static void simplifyStuck()
  248. {
  249. // tetrahedron can't be simplified due to collapse error restrictions
  250. float vb1[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1};
  251. unsigned int ib1[] = {0, 1, 2, 0, 2, 3, 0, 3, 1, 2, 1, 3};
  252. assert(meshopt_simplify(ib1, ib1, 12, vb1, 4, 12, 6, 1e-3f) == 12);
  253. // 5-vertex strip can't be simplified due to topology restriction since middle triangle has flipped winding
  254. float vb2[] = {0, 0, 0, 1, 0, 0, 2, 0, 0, 0.5f, 1, 0, 1.5f, 1, 0};
  255. unsigned int ib2[] = {0, 1, 3, 3, 1, 4, 1, 2, 4}; // ok
  256. unsigned int ib3[] = {0, 1, 3, 1, 3, 4, 1, 2, 4}; // flipped
  257. assert(meshopt_simplify(ib2, ib2, 9, vb2, 5, 12, 6, 1e-3f) == 6);
  258. assert(meshopt_simplify(ib3, ib3, 9, vb2, 5, 12, 6, 1e-3f) == 9);
  259. }
  260. static void simplifySloppyStuck()
  261. {
  262. const float vb[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
  263. const unsigned int ib[] = {0, 1, 2, 0, 1, 2};
  264. // simplifying down to 0 triangles results in 0 immediately
  265. assert(meshopt_simplifySloppy(0, ib, 3, vb, 3, 12, 0) == 0);
  266. // simplifying down to 2 triangles given that all triangles are degenerate results in 0 as well
  267. assert(meshopt_simplifySloppy(0, ib, 6, vb, 3, 12, 6) == 0);
  268. }
  269. static void simplifyPointsStuck()
  270. {
  271. const float vb[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
  272. // simplifying down to 0 points results in 0 immediately
  273. assert(meshopt_simplifyPoints(0, vb, 3, 12, 0) == 0);
  274. }
  275. void runTests()
  276. {
  277. decodeIndexV0();
  278. decodeIndex16();
  279. encodeIndexMemorySafe();
  280. decodeIndexMemorySafe();
  281. decodeIndexRejectExtraBytes();
  282. decodeIndexRejectMalformedHeaders();
  283. decodeVertexV0();
  284. encodeVertexMemorySafe();
  285. decodeVertexMemorySafe();
  286. decodeVertexRejectExtraBytes();
  287. decodeVertexRejectMalformedHeaders();
  288. clusterBoundsDegenerate();
  289. customAllocator();
  290. emptyMesh();
  291. simplifyStuck();
  292. simplifySloppyStuck();
  293. simplifyPointsStuck();
  294. }