benchmarks.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. #include <fstream>
  2. #include <catch2/benchmark/catch_benchmark.hpp>
  3. #include <catch2/catch_test_macros.hpp>
  4. #include "simdjson.h"
  5. #include <fastgltf/parser.hpp>
  6. #include "gltf_path.hpp"
  7. constexpr auto benchmarkOptions = fastgltf::Options::DontRequireValidAssetMember;
  8. #ifdef HAS_RAPIDJSON
  9. #include "rapidjson/document.h"
  10. #include "rapidjson/prettywriter.h"
  11. #include "rapidjson/rapidjson.h"
  12. #include "rapidjson/stringbuffer.h"
  13. #include "rapidjson/writer.h"
  14. #endif
  15. #ifdef HAS_TINYGLTF
  16. // We don't want tinygltf to load/write images.
  17. #define TINYGLTF_NO_STB_IMAGE_WRITE
  18. #define TINYGLTF_NO_STB_IMAGE
  19. #define TINYGLTF_NO_FS
  20. #define TINYGLTF_IMPLEMENTATION
  21. #include <tiny_gltf.h>
  22. bool tinygltf_FileExistsFunction([[maybe_unused]] const std::string& filename, [[maybe_unused]] void* user) {
  23. return true;
  24. }
  25. std::string tinygltf_ExpandFilePathFunction(const std::string& path, [[maybe_unused]] void* user) {
  26. return path;
  27. }
  28. bool tinygltf_ReadWholeFileFunction(std::vector<unsigned char>* data, std::string*, const std::string&, void*) {
  29. // tinygltf checks if size == 1. It also checks if the size is correct for glb files, but
  30. // well ignore that for now.
  31. data->resize(1);
  32. return true;
  33. }
  34. bool tinygltf_LoadImageData(tinygltf::Image *image, const int image_idx, std::string *err,
  35. std::string *warn, int req_width, int req_height,
  36. const unsigned char *bytes, int size, void *user_data) {
  37. return true;
  38. }
  39. void setTinyGLTFCallbacks(tinygltf::TinyGLTF& gltf) {
  40. gltf.SetFsCallbacks({
  41. tinygltf_FileExistsFunction,
  42. tinygltf_ExpandFilePathFunction,
  43. tinygltf_ReadWholeFileFunction,
  44. nullptr, nullptr,
  45. });
  46. gltf.SetImageLoader(tinygltf_LoadImageData, nullptr);
  47. }
  48. #endif
  49. #ifdef HAS_CGLTF
  50. #define CGLTF_IMPLEMENTATION
  51. #include <cgltf.h>
  52. #endif
  53. #ifdef HAS_GLTFRS
  54. #include "rust/cxx.h"
  55. #include "gltf-rs-bridge/lib.h"
  56. #endif
  57. std::vector<uint8_t> readFileAsBytes(std::filesystem::path path) {
  58. std::ifstream file(path, std::ios::ate | std::ios::binary);
  59. if (!file.is_open())
  60. throw std::runtime_error(std::string { "Failed to open file: " } + path.string());
  61. auto fileSize = file.tellg();
  62. std::vector<uint8_t> bytes(static_cast<size_t>(fileSize) + fastgltf::getGltfBufferPadding());
  63. file.seekg(0, std::ifstream::beg);
  64. file.read(reinterpret_cast<char*>(bytes.data()), fileSize);
  65. file.close();
  66. return bytes;
  67. }
  68. TEST_CASE("Benchmark loading of NewSponza", "[gltf-benchmark]") {
  69. if (!std::filesystem::exists(intelSponza / "NewSponza_Main_glTF_002.gltf")) {
  70. // NewSponza is not part of gltf-Sample-Models, and therefore not always available.
  71. SKIP("Intel's NewSponza (GLTF) is required for this benchmark.");
  72. }
  73. fastgltf::Parser parser;
  74. #ifdef HAS_TINYGLTF
  75. tinygltf::TinyGLTF tinygltf;
  76. tinygltf::Model model;
  77. std::string warn, err;
  78. #endif
  79. auto bytes = readFileAsBytes(intelSponza / "NewSponza_Main_glTF_002.gltf");
  80. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  81. REQUIRE(jsonData->fromByteView(bytes.data(), bytes.size() - fastgltf::getGltfBufferPadding(), bytes.size()));
  82. BENCHMARK("Parse NewSponza") {
  83. return parser.loadGLTF(jsonData.get(), intelSponza, benchmarkOptions);
  84. };
  85. #ifdef HAS_TINYGLTF
  86. setTinyGLTFCallbacks(tinygltf);
  87. BENCHMARK("Parse NewSponza with tinygltf") {
  88. return tinygltf.LoadASCIIFromString(&model, &err, &warn, reinterpret_cast<char*>(bytes.data()), bytes.size(), intelSponza.string());
  89. };
  90. #endif
  91. #ifdef HAS_CGLTF
  92. BENCHMARK("Parse NewSponza with cgltf") {
  93. cgltf_options options = {};
  94. cgltf_data* data = nullptr;
  95. cgltf_result result = cgltf_parse(&options, bytes.data(), bytes.size(), &data);
  96. REQUIRE(result == cgltf_result_success);
  97. cgltf_free(data);
  98. return result;
  99. };
  100. #endif
  101. #ifdef HAS_GLTFRS
  102. auto padding = fastgltf::getGltfBufferPadding();
  103. BENCHMARK("Parse NewSponza with gltf-rs") {
  104. auto slice = rust::Slice<const std::uint8_t>(reinterpret_cast<std::uint8_t*>(bytes.data()), bytes.size() - padding);
  105. return rust::gltf::run(slice);
  106. };
  107. #endif
  108. }
  109. TEST_CASE("Benchmark base64 decoding from glTF file", "[gltf-benchmark]") {
  110. fastgltf::Parser parser;
  111. #ifdef HAS_TINYGLTF
  112. tinygltf::TinyGLTF tinygltf;
  113. tinygltf::Model model;
  114. std::string warn, err;
  115. #endif
  116. auto cylinderEngine = sampleModels / "2.0" / "2CylinderEngine" / "glTF-Embedded";
  117. auto bytes = readFileAsBytes(cylinderEngine / "2CylinderEngine.gltf");
  118. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  119. REQUIRE(jsonData->fromByteView(bytes.data(), bytes.size() - fastgltf::getGltfBufferPadding(), bytes.size()));
  120. BENCHMARK("Parse 2CylinderEngine and decode base64") {
  121. return parser.loadGLTF(jsonData.get(), cylinderEngine, benchmarkOptions);
  122. };
  123. #ifdef HAS_TINYGLTF
  124. setTinyGLTFCallbacks(tinygltf);
  125. BENCHMARK("2CylinderEngine decode with tinygltf") {
  126. return tinygltf.LoadASCIIFromString(&model, &err, &warn, reinterpret_cast<char*>(bytes.data()), bytes.size(), cylinderEngine.string());
  127. };
  128. #endif
  129. #ifdef HAS_CGLTF
  130. BENCHMARK("2CylinderEngine decode with cgltf") {
  131. cgltf_options options = {};
  132. cgltf_data* data = nullptr;
  133. auto filePath = cylinderEngine.string();
  134. cgltf_result result = cgltf_parse(&options, bytes.data(), bytes.size(), &data);
  135. REQUIRE(result == cgltf_result_success);
  136. result = cgltf_load_buffers(&options, data, filePath.c_str());
  137. cgltf_free(data);
  138. return result;
  139. };
  140. #endif
  141. #ifdef HAS_GLTFRS
  142. auto padding = fastgltf::getGltfBufferPadding();
  143. BENCHMARK("2CylinderEngine with gltf-rs") {
  144. auto slice = rust::Slice<const std::uint8_t>(reinterpret_cast<std::uint8_t*>(bytes.data()), bytes.size() - padding);
  145. return rust::gltf::run(slice);
  146. };
  147. #endif
  148. }
  149. TEST_CASE("Benchmark raw JSON parsing", "[gltf-benchmark]") {
  150. fastgltf::Parser parser;
  151. #ifdef HAS_TINYGLTF
  152. tinygltf::TinyGLTF tinygltf;
  153. tinygltf::Model model;
  154. std::string warn, err;
  155. #endif
  156. auto buggyPath = sampleModels / "2.0" / "Buggy" / "glTF";
  157. auto bytes = readFileAsBytes(buggyPath / "Buggy.gltf");
  158. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  159. REQUIRE(jsonData->fromByteView(bytes.data(), bytes.size() - fastgltf::getGltfBufferPadding(), bytes.size()));
  160. BENCHMARK("Parse Buggy.gltf") {
  161. return parser.loadGLTF(jsonData.get(), buggyPath, benchmarkOptions);
  162. };
  163. #ifdef HAS_TINYGLTF
  164. setTinyGLTFCallbacks(tinygltf);
  165. BENCHMARK("Parse Buggy.gltf with tinygltf") {
  166. return tinygltf.LoadASCIIFromString(&model, &err, &warn, reinterpret_cast<char*>(bytes.data()), bytes.size(), buggyPath.string());
  167. };
  168. #endif
  169. #ifdef HAS_CGLTF
  170. BENCHMARK("Parse Buggy.gltf with cgltf") {
  171. cgltf_options options = {};
  172. cgltf_data* data = nullptr;
  173. auto filePath = buggyPath.string();
  174. cgltf_result result = cgltf_parse(&options, bytes.data(), bytes.size(), &data);
  175. REQUIRE(result == cgltf_result_success);
  176. cgltf_free(data);
  177. return result;
  178. };
  179. #endif
  180. #ifdef HAS_GLTFRS
  181. auto padding = fastgltf::getGltfBufferPadding();
  182. BENCHMARK("Parse Buggy.gltf with gltf-rs") {
  183. auto slice = rust::Slice<const std::uint8_t>(reinterpret_cast<std::uint8_t*>(bytes.data()), bytes.size() - padding);
  184. return rust::gltf::run(slice);
  185. };
  186. #endif
  187. }
  188. TEST_CASE("Benchmark massive gltf file", "[gltf-benchmark]") {
  189. if (!std::filesystem::exists(bistroPath / "bistro.gltf")) {
  190. // Bistro is not part of gltf-Sample-Models, and therefore not always available.
  191. SKIP("Amazon's Bistro (GLTF) is required for this benchmark.");
  192. }
  193. fastgltf::Parser parser(fastgltf::Extensions::KHR_mesh_quantization);
  194. #ifdef HAS_TINYGLTF
  195. tinygltf::TinyGLTF tinygltf;
  196. tinygltf::Model model;
  197. std::string warn, err;
  198. #endif
  199. auto bytes = readFileAsBytes(bistroPath / "bistro.gltf");
  200. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  201. REQUIRE(jsonData->fromByteView(bytes.data(), bytes.size() - fastgltf::getGltfBufferPadding(), bytes.size()));
  202. BENCHMARK("Parse Bistro") {
  203. return parser.loadGLTF(jsonData.get(), bistroPath, benchmarkOptions);
  204. };
  205. #ifdef HAS_TINYGLTF
  206. setTinyGLTFCallbacks(tinygltf);
  207. BENCHMARK("Parse Bistro with tinygltf") {
  208. return tinygltf.LoadASCIIFromString(&model, &err, &warn, reinterpret_cast<char*>(bytes.data()), bytes.size(), bistroPath.string());
  209. };
  210. #endif
  211. #ifdef HAS_CGLTF
  212. BENCHMARK("Parse Bistro with cgltf") {
  213. cgltf_options options = {};
  214. cgltf_data* data = nullptr;
  215. auto filePath = bistroPath.string();
  216. cgltf_result result = cgltf_parse(&options, bytes.data(), bytes.size(), &data);
  217. REQUIRE(result == cgltf_result_success);
  218. cgltf_free(data);
  219. return result;
  220. };
  221. #endif
  222. #ifdef HAS_GLTFRS
  223. auto padding = fastgltf::getGltfBufferPadding();
  224. BENCHMARK("Parse Bistro with gltf-rs") {
  225. auto slice = rust::Slice<const std::uint8_t>(reinterpret_cast<std::uint8_t*>(bytes.data()), bytes.size() - padding);
  226. return rust::gltf::run(slice);
  227. };
  228. #endif
  229. }
  230. TEST_CASE("Compare parsing performance with minified documents", "[gltf-benchmark]") {
  231. auto buggyPath = sampleModels / "2.0" / "Buggy" / "glTF";
  232. auto bytes = readFileAsBytes(buggyPath / "Buggy.gltf");
  233. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  234. REQUIRE(jsonData->fromByteView(bytes.data(), bytes.size() - fastgltf::getGltfBufferPadding(), bytes.size()));
  235. // Create a minified JSON string
  236. std::vector<uint8_t> minified(bytes.size());
  237. size_t dstLen = 0;
  238. auto result = simdjson::minify(reinterpret_cast<const char*>(bytes.data()), bytes.size(),
  239. reinterpret_cast<char*>(minified.data()), dstLen);
  240. REQUIRE(result == simdjson::SUCCESS);
  241. minified.resize(dstLen);
  242. // For completeness, benchmark minifying the JSON
  243. BENCHMARK("Minify Buggy.gltf") {
  244. auto result = simdjson::minify(reinterpret_cast<const char*>(bytes.data()), bytes.size(),
  245. reinterpret_cast<char*>(minified.data()), dstLen);
  246. REQUIRE(result == simdjson::SUCCESS);
  247. return result;
  248. };
  249. auto minifiedJsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  250. REQUIRE(minifiedJsonData->fromByteView(minified.data(), minified.size() - fastgltf::getGltfBufferPadding(), minified.size()));
  251. fastgltf::Parser parser;
  252. BENCHMARK("Parse Buggy.gltf with normal JSON") {
  253. return parser.loadGLTF(jsonData.get(), buggyPath, benchmarkOptions);
  254. };
  255. BENCHMARK("Parse Buggy.gltf with minified JSON") {
  256. return parser.loadGLTF(minifiedJsonData.get(), buggyPath, benchmarkOptions);
  257. };
  258. }
  259. #if defined(FASTGLTF_IS_X86)
  260. TEST_CASE("Small CRC32-C benchmark", "[gltf-benchmark]") {
  261. static constexpr std::string_view test = "abcdefghijklmnopqrstuvwxyz";
  262. BENCHMARK("Default 1-byte tabular algorithm") {
  263. return fastgltf::crc32c(reinterpret_cast<const std::uint8_t*>(test.data()), test.size());
  264. };
  265. BENCHMARK("SSE4 hardware algorithm") {
  266. return fastgltf::hwcrc32c(reinterpret_cast<const std::uint8_t*>(test.data()), test.size());
  267. };
  268. }
  269. #endif