initial commit-moved from vulkan_guide
This commit is contained in:
63
third_party/fastgltf/tests/CMakeLists.txt
vendored
Normal file
63
third_party/fastgltf/tests/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL TRUE)
|
||||
|
||||
# We want these tests to be a optional executable.
|
||||
add_executable(fastgltf_tests EXCLUDE_FROM_ALL
|
||||
"base64_tests.cpp" "basic_test.cpp" "benchmarks.cpp" "glb_tests.cpp" "gltf_path.hpp"
|
||||
"vector_tests.cpp" "uri_tests.cpp" "extension_tests.cpp" "accessor_tests.cpp")
|
||||
target_compile_features(fastgltf_tests PRIVATE cxx_std_17)
|
||||
target_link_libraries(fastgltf_tests PRIVATE fastgltf fastgltf_simdjson)
|
||||
target_link_libraries(fastgltf_tests PRIVATE glm::glm Catch2::Catch2WithMain)
|
||||
fastgltf_compiler_flags(fastgltf_tests)
|
||||
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/deps/catch2")
|
||||
add_subdirectory(deps/catch2)
|
||||
target_link_libraries(fastgltf_tests PRIVATE Catch2::Catch2)
|
||||
endif()
|
||||
|
||||
# We only use tinygltf to compare against.
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/gltf_loaders/tinygltf/tiny_gltf.h")
|
||||
message(STATUS "fastgltf: Found tinygltf")
|
||||
set(TINYGLTF_INSTALL OFF CACHE BOOL "")
|
||||
set(TINYGLTF_BUILD_LOADER_EXAMPLE OFF CACHE BOOL "")
|
||||
set(TINYGLTF_HEADER_ONLY ON CACHE BOOL "")
|
||||
|
||||
add_subdirectory(gltf_loaders/tinygltf)
|
||||
target_link_libraries(fastgltf_tests PRIVATE tinygltf)
|
||||
target_compile_definitions(fastgltf_tests PRIVATE HAS_TINYGLTF=1)
|
||||
|
||||
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/gltf_loaders/RapidJSON")
|
||||
# RapidJSON's CMake is weird
|
||||
message(STATUS "fastgltf: Found RapidJSON")
|
||||
target_include_directories(fastgltf_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/gltf_loaders/RapidJSON/include")
|
||||
target_compile_definitions(fastgltf_tests PRIVATE HAS_RAPIDJSON=1 TINYGLTF_USE_RAPIDJSON=1 TINYGLTF_NO_INCLUDE_RAPIDJSON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/gltf_loaders/cgltf/cgltf.h")
|
||||
message(STATUS "fastgltf: Found cgltf")
|
||||
target_include_directories(fastgltf_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/gltf_loaders/cgltf")
|
||||
target_compile_definitions(fastgltf_tests PRIVATE HAS_CGLTF=1)
|
||||
endif()
|
||||
|
||||
if (FASTGLTF_ENABLE_GLTF_RS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/gltf-rs/src/lib.rs")
|
||||
message(STATUS "fastgltf: Found gltf-rs")
|
||||
corrosion_import_crate(MANIFEST_PATH gltf-rs/Cargo.toml)
|
||||
corrosion_add_cxxbridge(gltf-rs-bridge CRATE gltf_rs MANIFEST_PATH gltf-rs FILES lib.rs)
|
||||
|
||||
target_link_libraries(fastgltf_tests PUBLIC gltf-rs-bridge)
|
||||
target_compile_definitions(fastgltf_tests PRIVATE HAS_GLTFRS=1)
|
||||
endif()
|
||||
|
||||
if(FASTGLTF_ENABLE_ASSIMP AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/gltf_loaders/assimp")
|
||||
message(STATUS "fastgltf: Found assimp")
|
||||
# Only enable glTF importer
|
||||
set(ASSIMP_NO_EXPORT ON CACHE BOOL "")
|
||||
set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "")
|
||||
set(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT OFF CACHE BOOL "")
|
||||
set(ASSIMP_BUILD_GLTF_IMPORTER ON CACHE BOOL "")
|
||||
add_subdirectory(gltf_loaders/assimp)
|
||||
target_link_libraries(fastgltf_tests PRIVATE assimp::assimp)
|
||||
target_compile_definitions(fastgltf_tests PRIVATE HAS_ASSIMP=1)
|
||||
endif()
|
||||
|
||||
fastgltf_add_source_directory(TARGET fastgltf_tests FOLDER ".")
|
||||
4
third_party/fastgltf/tests/README.md
vendored
Normal file
4
third_party/fastgltf/tests/README.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# tests
|
||||
|
||||
The tests are written with C++20 and [Catch2](https://github.com/catchorg/Catch2). To run, one also
|
||||
needs to init the submodules and therefore download the [glTF-Sample-Models](https://github.com/KhronosGroup/glTF-Sample-Models/).
|
||||
218
third_party/fastgltf/tests/accessor_tests.cpp
vendored
Normal file
218
third_party/fastgltf/tests/accessor_tests.cpp
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/gtc/epsilon.hpp>
|
||||
#include <glm/ext/scalar_constants.hpp>
|
||||
|
||||
#include <fastgltf/parser.hpp>
|
||||
#include <fastgltf/tools.hpp>
|
||||
#include "gltf_path.hpp"
|
||||
|
||||
template<>
|
||||
struct fastgltf::ElementTraits<glm::vec3> : fastgltf::ElementTraitsBase<glm::vec3, AccessorType::Vec3, float> {};
|
||||
|
||||
static const std::byte* getBufferData(const fastgltf::Buffer& buffer) {
|
||||
const std::byte* result = nullptr;
|
||||
|
||||
std::visit(fastgltf::visitor {
|
||||
[](auto&) {},
|
||||
[&](const fastgltf::sources::Vector& vec) {
|
||||
result = reinterpret_cast<const std::byte*>(vec.bytes.data());
|
||||
},
|
||||
[&](const fastgltf::sources::ByteView& bv) {
|
||||
result = bv.bytes.data();
|
||||
},
|
||||
}, buffer.data);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST_CASE("Test data type conversion", "[gltf-tools]") {
|
||||
// normalized int-to-float and normalized float-to-int
|
||||
for (auto i = std::numeric_limits<std::int8_t>::min(); i < std::numeric_limits<std::int8_t>::max(); ++i) {
|
||||
auto converted = fastgltf::internal::convertComponent<float>(i, true);
|
||||
REQUIRE(glm::epsilonEqual<float>(converted, fastgltf::max<float>(i / 127.0f, -1), glm::epsilon<float>()));
|
||||
REQUIRE(fastgltf::internal::convertComponent<std::int8_t>(converted, true) == std::round(converted * 127.0f));
|
||||
}
|
||||
for (auto i = std::numeric_limits<std::uint8_t>::min(); i < std::numeric_limits<std::uint8_t>::max(); ++i) {
|
||||
auto converted = fastgltf::internal::convertComponent<float>(i, true);
|
||||
REQUIRE(glm::epsilonEqual<float>(converted, i / 255.0f, glm::epsilon<float>()));
|
||||
REQUIRE(fastgltf::internal::convertComponent<std::uint8_t>(converted, true) == std::round(converted * 255.0f));
|
||||
}
|
||||
for (auto i = std::numeric_limits<std::int16_t>::min(); i < std::numeric_limits<std::int16_t>::max(); ++i) {
|
||||
auto converted = fastgltf::internal::convertComponent<float>(i, true);
|
||||
REQUIRE(glm::epsilonEqual<float>(converted, fastgltf::max<float>(i / 32767.0f, -1), glm::epsilon<float>()));
|
||||
REQUIRE(fastgltf::internal::convertComponent<std::int16_t>(converted, true) == std::round(converted * 32767.0f));
|
||||
}
|
||||
for (auto i = std::numeric_limits<std::uint16_t>::min(); i < std::numeric_limits<std::uint16_t>::max(); ++i) {
|
||||
auto converted = fastgltf::internal::convertComponent<float>(i, true);
|
||||
REQUIRE(glm::epsilonEqual<float>(converted, i / 65535.0f, glm::epsilon<float>()));
|
||||
REQUIRE(fastgltf::internal::convertComponent<std::uint16_t>(converted, true) == std::round(converted * 65535.0f));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test accessor", "[gltf-tools]") {
|
||||
auto lightsLamp = sampleModels / "2.0" / "LightsPunctualLamp" / "glTF";
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(lightsLamp / "LightsPunctualLamp.gltf"));
|
||||
|
||||
fastgltf::Parser parser(fastgltf::Extensions::KHR_lights_punctual);
|
||||
auto asset = parser.loadGLTF(&jsonData, lightsLamp, fastgltf::Options::LoadExternalBuffers,
|
||||
fastgltf::Category::Buffers | fastgltf::Category::BufferViews | fastgltf::Category::Accessors);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->accessors.size() == 15);
|
||||
auto& accessors = asset->accessors;
|
||||
|
||||
SECTION("getAccessorElement<std::uint16_t>") {
|
||||
auto& firstAccessor = accessors[0];
|
||||
REQUIRE(firstAccessor.type == fastgltf::AccessorType::Scalar);
|
||||
REQUIRE(firstAccessor.componentType == fastgltf::ComponentType::UnsignedShort);
|
||||
|
||||
REQUIRE(firstAccessor.bufferViewIndex.has_value());
|
||||
auto& view = asset->bufferViews[*firstAccessor.bufferViewIndex];
|
||||
|
||||
auto* bufferData = getBufferData(asset->buffers[view.bufferIndex]);
|
||||
REQUIRE(bufferData != nullptr);
|
||||
|
||||
auto* checkData = reinterpret_cast<const std::uint16_t*>(bufferData + view.byteOffset
|
||||
+ firstAccessor.byteOffset);
|
||||
|
||||
REQUIRE(*checkData == fastgltf::getAccessorElement<std::uint16_t>(asset.get(), firstAccessor, 0));
|
||||
}
|
||||
|
||||
{
|
||||
auto& secondAccessor = accessors[1];
|
||||
REQUIRE(secondAccessor.type == fastgltf::AccessorType::Vec3);
|
||||
REQUIRE(secondAccessor.componentType == fastgltf::ComponentType::Float);
|
||||
|
||||
REQUIRE(secondAccessor.bufferViewIndex.has_value());
|
||||
auto& view = asset->bufferViews[*secondAccessor.bufferViewIndex];
|
||||
|
||||
auto* bufferData = getBufferData(asset->buffers[view.bufferIndex]);
|
||||
REQUIRE(bufferData != nullptr);
|
||||
|
||||
auto* checkData = reinterpret_cast<const glm::vec3*>(bufferData + view.byteOffset
|
||||
+ secondAccessor.byteOffset);
|
||||
|
||||
SECTION("getAccessorElement<glm::vec3>") {
|
||||
REQUIRE(*checkData == fastgltf::getAccessorElement<glm::vec3>(asset.get(), secondAccessor, 0));
|
||||
}
|
||||
|
||||
SECTION("iterateAccessor") {
|
||||
auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
|
||||
std::size_t i = 0;
|
||||
|
||||
fastgltf::iterateAccessor<glm::vec3>(asset.get(), secondAccessor, [&](auto&& v3) {
|
||||
dstCopy[i++] = std::forward<glm::vec3>(v3);
|
||||
});
|
||||
|
||||
REQUIRE(std::memcmp(dstCopy.get(), checkData, secondAccessor.count * sizeof(glm::vec3)) == 0);
|
||||
}
|
||||
|
||||
SECTION("copyFromAccessor") {
|
||||
auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
|
||||
fastgltf::copyFromAccessor<glm::vec3>(asset.get(), secondAccessor, dstCopy.get());
|
||||
REQUIRE(std::memcmp(dstCopy.get(), checkData, secondAccessor.count * sizeof(glm::vec3)) == 0);
|
||||
}
|
||||
|
||||
SECTION("Iterator test") {
|
||||
auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
|
||||
auto accessor = fastgltf::iterateAccessor<glm::vec3>(asset.get(), secondAccessor);
|
||||
for (auto it = accessor.begin(); it != accessor.end(); ++it) {
|
||||
dstCopy[std::distance(accessor.begin(), it)] = *it;
|
||||
}
|
||||
REQUIRE(std::memcmp(dstCopy.get(), checkData, secondAccessor.count * sizeof(glm::vec3)) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test sparse accessor", "[gltf-tools]") {
|
||||
auto simpleSparseAccessor = sampleModels / "2.0" / "SimpleSparseAccessor" / "glTF";
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(simpleSparseAccessor / "SimpleSparseAccessor.gltf"));
|
||||
|
||||
fastgltf::Parser parser;
|
||||
auto asset = parser.loadGLTF(jsonData.get(), simpleSparseAccessor, fastgltf::Options::LoadExternalBuffers,
|
||||
fastgltf::Category::Buffers | fastgltf::Category::BufferViews | fastgltf::Category::Accessors);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->accessors.size() == 2);
|
||||
REQUIRE(!asset->accessors[0].sparse.has_value());
|
||||
REQUIRE(asset->accessors[1].sparse.has_value());
|
||||
auto& sparse = asset->accessors[1].sparse.value();
|
||||
REQUIRE(sparse.count == 3);
|
||||
REQUIRE(sparse.indicesBufferView == 2);
|
||||
REQUIRE(sparse.indicesByteOffset == 0);
|
||||
REQUIRE(sparse.valuesBufferView == 3);
|
||||
REQUIRE(sparse.valuesByteOffset == 0);
|
||||
REQUIRE(sparse.indexComponentType == fastgltf::ComponentType::UnsignedShort);
|
||||
|
||||
auto& secondAccessor = asset->accessors[1];
|
||||
auto& viewIndices = asset->bufferViews[secondAccessor.sparse->indicesBufferView];
|
||||
auto& viewValues = asset->bufferViews[secondAccessor.sparse->valuesBufferView];
|
||||
|
||||
auto& viewData = asset->bufferViews[*secondAccessor.bufferViewIndex];
|
||||
auto* bufferData = getBufferData(asset->buffers[viewData.bufferIndex]) + viewData.byteOffset
|
||||
+ secondAccessor.byteOffset;
|
||||
auto dataStride = viewData.byteStride ? *viewData.byteStride
|
||||
: fastgltf::getElementByteSize(secondAccessor.type, secondAccessor.componentType);
|
||||
|
||||
auto* dataIndices = reinterpret_cast<const std::uint16_t*>(getBufferData(asset->buffers[viewIndices.bufferIndex])
|
||||
+ viewIndices.byteOffset + secondAccessor.sparse->indicesByteOffset);
|
||||
auto* dataValues = reinterpret_cast<const glm::vec3*>(getBufferData(asset->buffers[viewValues.bufferIndex])
|
||||
+ viewValues.byteOffset + secondAccessor.sparse->valuesByteOffset);
|
||||
|
||||
auto checkValues = std::make_unique<glm::vec3[]>(secondAccessor.count);
|
||||
|
||||
for (std::size_t i = 0, sparseIndex = 0; i < secondAccessor.count; ++i) {
|
||||
if (sparseIndex < secondAccessor.sparse->count && dataIndices[sparseIndex] == i) {
|
||||
checkValues[i] = dataValues[sparseIndex];
|
||||
++sparseIndex;
|
||||
} else {
|
||||
checkValues[i] = *reinterpret_cast<const glm::vec3*>(bufferData + dataStride * i);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("getAccessorElement") {
|
||||
for (std::size_t i = 0; i < secondAccessor.count; ++i) {
|
||||
REQUIRE(checkValues[i] == fastgltf::getAccessorElement<glm::vec3>(asset.get(), secondAccessor, i));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("iterateAccessor") {
|
||||
auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
|
||||
std::size_t i = 0;
|
||||
|
||||
fastgltf::iterateAccessor<glm::vec3>(asset.get(), secondAccessor, [&](auto&& v3) {
|
||||
dstCopy[i++] = std::forward<glm::vec3>(v3);
|
||||
});
|
||||
|
||||
REQUIRE(std::memcmp(dstCopy.get(), checkValues.get(), secondAccessor.count * sizeof(glm::vec3)) == 0);
|
||||
}
|
||||
|
||||
SECTION("iterateAccessor with idx") {
|
||||
auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
|
||||
|
||||
fastgltf::iterateAccessorWithIndex<glm::vec3>(asset.get(), secondAccessor, [&](auto&& v3, std::size_t i) {
|
||||
dstCopy[i] = std::forward<glm::vec3>(v3);
|
||||
});
|
||||
|
||||
REQUIRE(std::memcmp(dstCopy.get(), checkValues.get(), secondAccessor.count * sizeof(glm::vec3)) == 0);
|
||||
}
|
||||
|
||||
SECTION("copyFromAccessor") {
|
||||
auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
|
||||
fastgltf::copyFromAccessor<glm::vec3>(asset.get(), secondAccessor, dstCopy.get());
|
||||
REQUIRE(std::memcmp(dstCopy.get(), checkValues.get(), secondAccessor.count * sizeof(glm::vec3)) == 0);
|
||||
}
|
||||
|
||||
SECTION("Iterator test") {
|
||||
auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
|
||||
auto accessor = fastgltf::iterateAccessor<glm::vec3>(asset.get(), secondAccessor);
|
||||
for (auto it = accessor.begin(); it != accessor.end(); ++it) {
|
||||
dstCopy[std::distance(accessor.begin(), it)] = *it;
|
||||
}
|
||||
REQUIRE(std::memcmp(dstCopy.get(), checkValues.get(), secondAccessor.count * sizeof(glm::vec3)) == 0);
|
||||
}
|
||||
}
|
||||
115
third_party/fastgltf/tests/base64_tests.cpp
vendored
Normal file
115
third_party/fastgltf/tests/base64_tests.cpp
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
|
||||
#include <fastgltf/base64.hpp>
|
||||
#include <fastgltf/types.hpp>
|
||||
#include <fastgltf/parser.hpp>
|
||||
#include "gltf_path.hpp"
|
||||
|
||||
constexpr std::string_view testBase64 = "SGVsbG8gV29ybGQuIEhlbGxvIFdvcmxkLiBIZWxsbyBXb3JsZC4=";
|
||||
|
||||
TEST_CASE("Check base64 utility functions", "[base64]") {
|
||||
REQUIRE(fastgltf::base64::getPadding("Li==") == 2);
|
||||
REQUIRE(fastgltf::base64::getPadding("Li4=") == 1);
|
||||
REQUIRE(fastgltf::base64::getPadding("Li4u") == 0);
|
||||
|
||||
REQUIRE(fastgltf::base64::getOutputSize(4, 0) == 3); // Li4u
|
||||
REQUIRE(fastgltf::base64::getOutputSize(4, 1) == 2); // Li4=
|
||||
REQUIRE(fastgltf::base64::getOutputSize(4, 2) == 1); // Li==
|
||||
}
|
||||
|
||||
TEST_CASE("Check base64 decoding", "[base64]") {
|
||||
// This is "Hello World. Hello World.". The decode function
|
||||
// uses the best possible SIMD version of the algorithm.
|
||||
auto bytes = fastgltf::base64::decode(testBase64);
|
||||
std::string strings(bytes.begin(), bytes.end());
|
||||
REQUIRE(strings == "Hello World. Hello World. Hello World.");
|
||||
}
|
||||
|
||||
TEST_CASE("Check all base64 decoders", "[base64]") {
|
||||
// Checks that the base64 decoders return the same.
|
||||
auto bytes = fastgltf::base64::fallback_decode(testBase64);
|
||||
std::string strings(bytes.begin(), bytes.end());
|
||||
REQUIRE(strings == "Hello World. Hello World. Hello World.");
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_AMD64) || defined(_M_IX86)
|
||||
REQUIRE(bytes == fastgltf::base64::avx2_decode(testBase64));
|
||||
REQUIRE(bytes == fastgltf::base64::sse4_decode(testBase64));
|
||||
#endif
|
||||
#if defined(__aarch64__)
|
||||
REQUIRE(bytes == fastgltf::base64::neon_decode(testBase64));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("Check big base64 data decoding", "[base64]") {
|
||||
std::ifstream file(path / "base64.txt");
|
||||
REQUIRE(file.is_open());
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
|
||||
auto encodedBytes = buffer.str();
|
||||
auto bytes = fastgltf::base64::decode(encodedBytes);
|
||||
REQUIRE(!bytes.empty());
|
||||
|
||||
std::ifstream output(path / "base64.txt.out", std::ios::binary | std::ios::ate);
|
||||
REQUIRE(output.is_open());
|
||||
std::vector<uint8_t> decodedBytes(output.tellg());
|
||||
output.seekg(0);
|
||||
output.read(reinterpret_cast<char*>(decodedBytes.data()), static_cast<std::streamsize>(decodedBytes.size()));
|
||||
|
||||
REQUIRE(bytes == decodedBytes);
|
||||
}
|
||||
|
||||
TEST_CASE("Test base64 buffer decoding", "[base64]") {
|
||||
fastgltf::Parser parser;
|
||||
fastgltf::Image texture;
|
||||
std::string bufferData;
|
||||
|
||||
auto cylinderEngine = sampleModels / "2.0" / "2CylinderEngine" / "glTF-Embedded";
|
||||
auto boxTextured = sampleModels / "2.0" / "BoxTextured" / "glTF-Embedded";
|
||||
|
||||
auto tceJsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(tceJsonData->loadFromFile(cylinderEngine / "2CylinderEngine.gltf"));
|
||||
auto btJsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(btJsonData->loadFromFile(boxTextured / "BoxTextured.gltf"));
|
||||
|
||||
SECTION("Validate large buffer load from glTF") {
|
||||
auto asset = parser.loadGLTF(tceJsonData.get(), cylinderEngine, fastgltf::Options::None, fastgltf::Category::Buffers);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->buffers.size() == 1);
|
||||
|
||||
// Load the buffer from the parsed glTF file.
|
||||
auto& buffer = asset->buffers.front();
|
||||
REQUIRE(buffer.byteLength == 1794612);
|
||||
auto bufferVector = std::get_if<fastgltf::sources::Vector>(&buffer.data);
|
||||
REQUIRE(bufferVector != nullptr);
|
||||
REQUIRE(bufferVector->mimeType == fastgltf::MimeType::OctetStream);
|
||||
REQUIRE(!bufferVector->bytes.empty());
|
||||
}
|
||||
|
||||
SECTION("Validate base64 buffer and image load from glTF") {
|
||||
auto asset = parser.loadGLTF(btJsonData.get(), boxTextured, fastgltf::Options::None, fastgltf::Category::Images | fastgltf::Category::Buffers);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->buffers.size() == 1);
|
||||
REQUIRE(asset->images.size() == 1);
|
||||
|
||||
auto& buffer = asset->buffers.front();
|
||||
REQUIRE(buffer.byteLength == 840);
|
||||
auto bufferVector = std::get_if<fastgltf::sources::Vector>(&buffer.data);
|
||||
REQUIRE(bufferVector != nullptr);
|
||||
REQUIRE(bufferVector->mimeType == fastgltf::MimeType::OctetStream);
|
||||
REQUIRE(!bufferVector->bytes.empty());
|
||||
|
||||
auto& image = asset->images.front();
|
||||
auto imageVector = std::get_if<fastgltf::sources::Vector>(&image.data);
|
||||
REQUIRE(imageVector != nullptr);
|
||||
REQUIRE(imageVector->mimeType == fastgltf::MimeType::PNG);
|
||||
REQUIRE(!imageVector->bytes.empty());
|
||||
}
|
||||
}
|
||||
583
third_party/fastgltf/tests/basic_test.cpp
vendored
Normal file
583
third_party/fastgltf/tests/basic_test.cpp
vendored
Normal file
@@ -0,0 +1,583 @@
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <random>
|
||||
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
#include <glm/gtx/matrix_decompose.hpp>
|
||||
|
||||
#include <fastgltf/base64.hpp>
|
||||
#include <fastgltf/parser.hpp>
|
||||
#include <fastgltf/types.hpp>
|
||||
#include "gltf_path.hpp"
|
||||
|
||||
constexpr auto noOptions = fastgltf::Options::None;
|
||||
|
||||
TEST_CASE("Component type tests", "[gltf-loader]") {
|
||||
using namespace fastgltf;
|
||||
|
||||
// clang-format off
|
||||
REQUIRE(fastgltf::getNumComponents(AccessorType::Scalar) == 1);
|
||||
REQUIRE(fastgltf::getNumComponents(AccessorType::Vec2) == 2);
|
||||
REQUIRE(fastgltf::getNumComponents(AccessorType::Vec3) == 3);
|
||||
REQUIRE(fastgltf::getNumComponents(AccessorType::Vec4) == 4);
|
||||
REQUIRE(fastgltf::getNumComponents(AccessorType::Mat2) == 4);
|
||||
REQUIRE(fastgltf::getNumComponents(AccessorType::Mat3) == 9);
|
||||
REQUIRE(fastgltf::getNumComponents(AccessorType::Mat4) == 16);
|
||||
|
||||
REQUIRE(fastgltf::getComponentBitSize(ComponentType::Byte) == 8);
|
||||
REQUIRE(fastgltf::getComponentBitSize(ComponentType::UnsignedByte) == 8);
|
||||
REQUIRE(fastgltf::getComponentBitSize(ComponentType::Short) == 16);
|
||||
REQUIRE(fastgltf::getComponentBitSize(ComponentType::UnsignedShort) == 16);
|
||||
REQUIRE(fastgltf::getComponentBitSize(ComponentType::UnsignedInt) == 32);
|
||||
REQUIRE(fastgltf::getComponentBitSize(ComponentType::Float) == 32);
|
||||
REQUIRE(fastgltf::getComponentBitSize(ComponentType::Double) == 64);
|
||||
REQUIRE(fastgltf::getComponentBitSize(ComponentType::Invalid) == 0);
|
||||
|
||||
REQUIRE(fastgltf::getElementByteSize(AccessorType::Scalar, ComponentType::Byte) == 1);
|
||||
REQUIRE(fastgltf::getElementByteSize(AccessorType::Vec4, ComponentType::Byte) == 4);
|
||||
REQUIRE(fastgltf::getElementByteSize(AccessorType::Vec4, ComponentType::Short) == 8);
|
||||
|
||||
REQUIRE(fastgltf::getComponentType(5120) == ComponentType::Byte);
|
||||
REQUIRE(fastgltf::getComponentType(5121) == ComponentType::UnsignedByte);
|
||||
REQUIRE(fastgltf::getComponentType(5122) == ComponentType::Short);
|
||||
REQUIRE(fastgltf::getComponentType(5123) == ComponentType::UnsignedShort);
|
||||
REQUIRE(fastgltf::getComponentType(5125) == ComponentType::UnsignedInt);
|
||||
REQUIRE(fastgltf::getComponentType(5126) == ComponentType::Float);
|
||||
REQUIRE(fastgltf::getComponentType(5130) == ComponentType::Double);
|
||||
REQUIRE(fastgltf::getComponentType(5131) == ComponentType::Invalid);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
TEST_CASE("Test all variants of CRC32-C hashing", "[gltf-loader]") {
|
||||
// TODO: Determine SSE4.2 support here.
|
||||
for (std::size_t i = 0; i < 256; ++i) {
|
||||
// Generate a random string up to 256 chars long.
|
||||
static constexpr std::string_view chars =
|
||||
"0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
static std::mt19937 rng(std::random_device{}());
|
||||
static std::uniform_int_distribution<std::string::size_type> pick(0, chars.size() - 1);
|
||||
std::string str(i, '\0');
|
||||
for (std::size_t j = 0; j < i; ++j)
|
||||
str[j] = chars[pick(rng)];
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_AMD64) || defined(_M_IX86)
|
||||
// We'll try and test if the hardware accelerated version generates the same, correct results.
|
||||
REQUIRE(fastgltf::crc32c(str) == fastgltf::hwcrc32c(str));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test extension stringification", "[gltf-loader]") {
|
||||
auto stringified = stringifyExtension(fastgltf::Extensions::EXT_meshopt_compression);
|
||||
REQUIRE(stringified == fastgltf::extensions::EXT_meshopt_compression);
|
||||
|
||||
stringified = stringifyExtension(fastgltf::Extensions::EXT_meshopt_compression | fastgltf::Extensions::EXT_texture_webp);
|
||||
REQUIRE(stringified == fastgltf::extensions::EXT_meshopt_compression);
|
||||
}
|
||||
|
||||
TEST_CASE("Test if glTF type detection works", "[gltf-loader]") {
|
||||
fastgltf::Parser parser;
|
||||
|
||||
SECTION("glTF") {
|
||||
auto gltfPath = sampleModels / "2.0" / "ABeautifulGame" / "glTF";
|
||||
REQUIRE(std::filesystem::exists(gltfPath));
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(gltfPath / "ABeautifulGame.gltf"));
|
||||
REQUIRE(fastgltf::determineGltfFileType(&jsonData) == fastgltf::GltfType::glTF);
|
||||
|
||||
auto model = parser.loadGLTF(&jsonData, gltfPath);
|
||||
REQUIRE(model.error() == fastgltf::Error::None);
|
||||
REQUIRE(model.get_if() != nullptr);
|
||||
REQUIRE(fastgltf::validate(model.get()) == fastgltf::Error::None);
|
||||
}
|
||||
|
||||
SECTION("GLB") {
|
||||
auto glbPath = sampleModels / "2.0" / "BoomBox" / "glTF-Binary";
|
||||
REQUIRE(std::filesystem::exists(glbPath));
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(glbPath / "BoomBox.glb"));
|
||||
REQUIRE(fastgltf::determineGltfFileType(&jsonData) == fastgltf::GltfType::GLB);
|
||||
|
||||
auto model = parser.loadBinaryGLTF(&jsonData, glbPath);
|
||||
REQUIRE(model.error() == fastgltf::Error::None);
|
||||
REQUIRE(model.get_if() != nullptr);
|
||||
}
|
||||
|
||||
SECTION("Invalid") {
|
||||
auto gltfPath = path / "base64.txt"; // Random file in the test directory that's not a glTF file.
|
||||
REQUIRE(std::filesystem::exists(gltfPath));
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(gltfPath));
|
||||
REQUIRE(fastgltf::determineGltfFileType(&jsonData) == fastgltf::GltfType::Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Loading some basic glTF", "[gltf-loader]") {
|
||||
fastgltf::Parser parser;
|
||||
SECTION("Loading basic invalid glTF files") {
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(path / "empty_json.gltf"));
|
||||
auto emptyGltf = parser.loadGLTF(jsonData.get(), path);
|
||||
REQUIRE(emptyGltf.error() == fastgltf::Error::InvalidOrMissingAssetField);
|
||||
}
|
||||
|
||||
SECTION("Load basic glTF file") {
|
||||
auto basicJsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(basicJsonData->loadFromFile(path / "basic_gltf.gltf"));
|
||||
|
||||
auto basicGltf = parser.loadGLTF(basicJsonData.get(), path);
|
||||
REQUIRE(basicGltf.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(basicGltf.get()) == fastgltf::Error::None);
|
||||
}
|
||||
|
||||
SECTION("Loading basic Cube.gltf") {
|
||||
auto cubePath = sampleModels / "2.0" / "Cube" / "glTF";
|
||||
auto cubeJsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(cubeJsonData->loadFromFile(cubePath / "Cube.gltf"));
|
||||
|
||||
auto cube = parser.loadGLTF(cubeJsonData.get(), cubePath, noOptions, fastgltf::Category::OnlyRenderable);
|
||||
REQUIRE(cube.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(cube.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(cube->scenes.size() == 1);
|
||||
REQUIRE(cube->scenes.front().nodeIndices.size() == 1);
|
||||
REQUIRE(cube->scenes.front().nodeIndices.front() == 0);
|
||||
|
||||
REQUIRE(cube->nodes.size() == 1);
|
||||
REQUIRE(cube->nodes.front().name == "Cube");
|
||||
REQUIRE(std::holds_alternative<fastgltf::Node::TRS>(cube->nodes.front().transform));
|
||||
|
||||
REQUIRE(cube->accessors.size() == 5);
|
||||
REQUIRE(cube->accessors[0].type == fastgltf::AccessorType::Scalar);
|
||||
REQUIRE(cube->accessors[0].componentType == fastgltf::ComponentType::UnsignedShort);
|
||||
REQUIRE(cube->accessors[1].type == fastgltf::AccessorType::Vec3);
|
||||
REQUIRE(cube->accessors[1].componentType == fastgltf::ComponentType::Float);
|
||||
|
||||
REQUIRE(cube->bufferViews.size() == 5);
|
||||
REQUIRE(cube->buffers.size() == 1);
|
||||
|
||||
REQUIRE(cube->materials.size() == 1);
|
||||
auto& material = cube->materials.front();
|
||||
REQUIRE(material.name == "Cube");
|
||||
REQUIRE(material.pbrData.baseColorTexture.has_value());
|
||||
REQUIRE(material.pbrData.baseColorTexture->textureIndex == 0);
|
||||
REQUIRE(material.pbrData.metallicRoughnessTexture.has_value());
|
||||
REQUIRE(material.pbrData.metallicRoughnessTexture->textureIndex == 1);
|
||||
REQUIRE(!material.normalTexture.has_value());
|
||||
REQUIRE(!material.emissiveTexture.has_value());
|
||||
REQUIRE(!material.occlusionTexture.has_value());
|
||||
}
|
||||
|
||||
SECTION("Loading basic Box.gltf") {
|
||||
auto boxPath = sampleModels / "2.0" / "Box" / "glTF";
|
||||
auto boxJsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(boxJsonData->loadFromFile(boxPath / "Box.gltf"));
|
||||
|
||||
auto box = parser.loadGLTF(boxJsonData.get(), boxPath, noOptions, fastgltf::Category::OnlyRenderable);
|
||||
REQUIRE(box.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(box.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(box->defaultScene.has_value());
|
||||
REQUIRE(box->defaultScene.value() == 0);
|
||||
|
||||
REQUIRE(box->nodes.size() == 2);
|
||||
REQUIRE(box->nodes[0].children.size() == 1);
|
||||
REQUIRE(box->nodes[0].children[0] == 1);
|
||||
REQUIRE(box->nodes[1].children.empty());
|
||||
REQUIRE(box->nodes[1].meshIndex.has_value());
|
||||
REQUIRE(box->nodes[1].meshIndex.value() == 0);
|
||||
|
||||
REQUIRE(box->materials.size() == 1);
|
||||
REQUIRE(box->materials[0].name == "Red");
|
||||
REQUIRE(box->materials[0].pbrData.baseColorFactor[3] == 1.0f);
|
||||
REQUIRE(box->materials[0].pbrData.metallicFactor == 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Loading glTF animation", "[gltf-loader]") {
|
||||
auto animatedCube = sampleModels / "2.0" / "AnimatedCube" / "glTF";
|
||||
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(animatedCube / "AnimatedCube.gltf"));
|
||||
|
||||
fastgltf::Parser parser;
|
||||
auto asset = parser.loadGLTF(jsonData.get(), animatedCube, noOptions, fastgltf::Category::OnlyAnimations);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(!asset->animations.empty());
|
||||
|
||||
auto& animation = asset->animations.front();
|
||||
REQUIRE(animation.name == "animation_AnimatedCube");
|
||||
|
||||
REQUIRE(!animation.channels.empty());
|
||||
REQUIRE(animation.channels.front().nodeIndex == 0);
|
||||
REQUIRE(animation.channels.front().samplerIndex == 0);
|
||||
REQUIRE(animation.channels.front().path == fastgltf::AnimationPath::Rotation);
|
||||
|
||||
REQUIRE(!animation.samplers.empty());
|
||||
REQUIRE(animation.samplers.front().interpolation == fastgltf::AnimationInterpolation::Linear);
|
||||
REQUIRE(animation.samplers.front().inputAccessor == 0);
|
||||
REQUIRE(animation.samplers.front().outputAccessor == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Loading glTF skins", "[gltf-loader]") {
|
||||
auto simpleSkin = sampleModels / "2.0" / "SimpleSkin" / "glTF";
|
||||
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(simpleSkin / "SimpleSkin.gltf"));
|
||||
|
||||
fastgltf::Parser parser;
|
||||
auto asset = parser.loadGLTF(jsonData.get(), simpleSkin, noOptions, fastgltf::Category::Skins | fastgltf::Category::Nodes);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(!asset->skins.empty());
|
||||
|
||||
auto& skin = asset->skins.front();
|
||||
REQUIRE(skin.joints.size() == 2);
|
||||
REQUIRE(skin.joints[0] == 1);
|
||||
REQUIRE(skin.joints[1] == 2);
|
||||
REQUIRE(skin.inverseBindMatrices.has_value());
|
||||
REQUIRE(skin.inverseBindMatrices.value() == 4);
|
||||
|
||||
REQUIRE(!asset->nodes.empty());
|
||||
|
||||
auto& node = asset->nodes.front();
|
||||
REQUIRE(node.skinIndex.has_value());
|
||||
REQUIRE(node.skinIndex == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Loading glTF cameras", "[gltf-loader]") {
|
||||
auto cameras = sampleModels / "2.0" / "Cameras" / "glTF";
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(cameras / "Cameras.gltf"));
|
||||
|
||||
fastgltf::Parser parser;
|
||||
auto asset = parser.loadGLTF(jsonData.get(), cameras, noOptions, fastgltf::Category::Cameras);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->cameras.size() == 2);
|
||||
|
||||
REQUIRE(std::holds_alternative<fastgltf::Camera::Perspective>(asset->cameras[0].camera));
|
||||
REQUIRE(std::holds_alternative<fastgltf::Camera::Orthographic>(asset->cameras[1].camera));
|
||||
|
||||
const auto* pPerspective = std::get_if<fastgltf::Camera::Perspective>(&asset->cameras[0].camera);
|
||||
REQUIRE(pPerspective != nullptr);
|
||||
REQUIRE(pPerspective->aspectRatio == 1.0f);
|
||||
REQUIRE(pPerspective->yfov == 0.7f);
|
||||
REQUIRE(pPerspective->zfar == 100);
|
||||
REQUIRE(pPerspective->znear == 0.01f);
|
||||
|
||||
const auto* pOrthographic = std::get_if<fastgltf::Camera::Orthographic>(&asset->cameras[1].camera);
|
||||
REQUIRE(pOrthographic != nullptr);
|
||||
REQUIRE(pOrthographic->xmag == 1.0f);
|
||||
REQUIRE(pOrthographic->ymag == 1.0f);
|
||||
REQUIRE(pOrthographic->zfar == 100);
|
||||
REQUIRE(pOrthographic->znear == 0.01f);
|
||||
}
|
||||
|
||||
TEST_CASE("Validate whole glTF", "[gltf-loader]") {
|
||||
auto sponza = sampleModels / "2.0" / "Sponza" / "glTF";
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(sponza / "Sponza.gltf"));
|
||||
|
||||
fastgltf::Parser parser;
|
||||
auto model = parser.loadGLTF(jsonData.get(), sponza);
|
||||
REQUIRE(model.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(model.get()) == fastgltf::Error::None);
|
||||
|
||||
auto brainStem = sampleModels / "2.0" / "BrainStem" / "glTF";
|
||||
jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(brainStem / "BrainStem.gltf"));
|
||||
|
||||
auto model2 = parser.loadGLTF(jsonData.get(), brainStem);
|
||||
REQUIRE(model2.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(model2.get()) == fastgltf::Error::None);
|
||||
}
|
||||
|
||||
TEST_CASE("Test allocation callbacks for embedded buffers", "[gltf-loader]") {
|
||||
auto boxPath = sampleModels / "2.0" / "Box" / "glTF-Embedded";
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(boxPath / "Box.gltf"));
|
||||
|
||||
std::vector<void*> allocations;
|
||||
|
||||
auto mapCallback = [](uint64_t bufferSize, void* userPointer) -> fastgltf::BufferInfo {
|
||||
REQUIRE(userPointer != nullptr);
|
||||
auto* allocations = static_cast<std::vector<void*>*>(userPointer);
|
||||
allocations->emplace_back(std::malloc(bufferSize));
|
||||
return fastgltf::BufferInfo {
|
||||
allocations->back(),
|
||||
allocations->size() - 1,
|
||||
};
|
||||
};
|
||||
|
||||
fastgltf::Parser parser;
|
||||
parser.setUserPointer(&allocations);
|
||||
parser.setBufferAllocationCallback(mapCallback, nullptr);
|
||||
auto asset = parser.loadGLTF(jsonData.get(), boxPath, noOptions, fastgltf::Category::Buffers);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(allocations.size() == 1);
|
||||
REQUIRE(asset->buffers.size() == 1);
|
||||
|
||||
auto& buffer = asset->buffers.front();
|
||||
const auto* customBuffer = std::get_if<fastgltf::sources::CustomBuffer>(&buffer.data);
|
||||
REQUIRE(customBuffer != nullptr);
|
||||
REQUIRE(customBuffer->id == 0);
|
||||
|
||||
for (auto& allocation : allocations) {
|
||||
REQUIRE(allocation != nullptr);
|
||||
std::free(allocation);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test base64 decoding callbacks", "[gltf-loader]") {
|
||||
auto boxPath = sampleModels / "2.0" / "Box" / "glTF-Embedded";
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(boxPath / "Box.gltf"));
|
||||
|
||||
size_t decodeCounter = 0;
|
||||
auto decodeCallback = [](std::string_view encodedData, uint8_t* outputData, size_t padding, size_t outputSize, void* userPointer) {
|
||||
(*static_cast<size_t*>(userPointer))++;
|
||||
fastgltf::base64::decode_inplace(encodedData, outputData, padding);
|
||||
};
|
||||
|
||||
fastgltf::Parser parser;
|
||||
parser.setUserPointer(&decodeCounter);
|
||||
parser.setBase64DecodeCallback(decodeCallback);
|
||||
auto model = parser.loadGLTF(jsonData.get(), boxPath, noOptions, fastgltf::Category::Buffers);
|
||||
REQUIRE(model.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(model.get()) == fastgltf::Error::None);
|
||||
REQUIRE(decodeCounter != 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Test TRS parsing and optional decomposition", "[gltf-loader]") {
|
||||
SECTION("Test decomposition on glTF asset") {
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(path / "transform_matrices.gltf"));
|
||||
|
||||
// Parse once without decomposing, once with decomposing the matrix.
|
||||
fastgltf::Parser parser;
|
||||
auto assetWithMatrix = parser.loadGLTF(jsonData.get(), path, noOptions, fastgltf::Category::Nodes | fastgltf::Category::Cameras);
|
||||
REQUIRE(assetWithMatrix.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(assetWithMatrix.get()) == fastgltf::Error::None);
|
||||
|
||||
auto assetDecomposed = parser.loadGLTF(jsonData.get(), path, fastgltf::Options::DecomposeNodeMatrices, fastgltf::Category::Nodes | fastgltf::Category::Cameras);
|
||||
REQUIRE(assetDecomposed.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(assetDecomposed.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(assetWithMatrix->cameras.size() == 1);
|
||||
REQUIRE(assetDecomposed->cameras.size() == 1);
|
||||
REQUIRE(assetWithMatrix->nodes.size() == 2);
|
||||
REQUIRE(assetDecomposed->nodes.size() == 2);
|
||||
REQUIRE(std::holds_alternative<fastgltf::Node::TransformMatrix>(assetWithMatrix->nodes.back().transform));
|
||||
REQUIRE(std::holds_alternative<fastgltf::Node::TRS>(assetDecomposed->nodes.back().transform));
|
||||
|
||||
// Get the TRS components from the first node and use them as the test data for decomposing.
|
||||
const auto* pDefaultTRS = std::get_if<fastgltf::Node::TRS>(&assetWithMatrix->nodes.front().transform);
|
||||
REQUIRE(pDefaultTRS != nullptr);
|
||||
auto translation = glm::make_vec3(pDefaultTRS->translation.data());
|
||||
auto rotation = glm::make_quat(pDefaultTRS->rotation.data());
|
||||
auto scale = glm::make_vec3(pDefaultTRS->scale.data());
|
||||
auto rotationMatrix = glm::toMat4(rotation);
|
||||
auto transform = glm::translate(glm::mat4(1.0f), translation) * rotationMatrix * glm::scale(glm::mat4(1.0f), scale);
|
||||
|
||||
// Check if the parsed matrix is correct.
|
||||
const auto* pMatrix = std::get_if<fastgltf::Node::TransformMatrix>(&assetWithMatrix->nodes.back().transform);
|
||||
REQUIRE(pMatrix != nullptr);
|
||||
REQUIRE(glm::make_mat4x4(pMatrix->data()) == transform);
|
||||
|
||||
// Check if the decomposed components equal the original components.
|
||||
const auto* pDecomposedTRS = std::get_if<fastgltf::Node::TRS>(&assetDecomposed->nodes.back().transform);
|
||||
REQUIRE(glm::make_vec3(pDecomposedTRS->translation.data()) == translation);
|
||||
REQUIRE(glm::make_quat(pDecomposedTRS->rotation.data()) == rotation);
|
||||
REQUIRE(glm::make_vec3(pDecomposedTRS->scale.data()) == scale);
|
||||
}
|
||||
|
||||
SECTION("Test decomposition against glm decomposition") {
|
||||
// Some random complex transform matrix from one of the glTF sample models.
|
||||
std::array<float, 16> matrix = {
|
||||
-0.4234085381031037F,
|
||||
-0.9059388637542724F,
|
||||
-7.575183536001616e-11F,
|
||||
0.0F,
|
||||
-0.9059388637542724F,
|
||||
0.4234085381031037F,
|
||||
-4.821281221478735e-11F,
|
||||
0.0F,
|
||||
7.575183536001616e-11F,
|
||||
4.821281221478735e-11F,
|
||||
-1.0F,
|
||||
0.0F,
|
||||
-90.59386444091796F,
|
||||
-24.379817962646489F,
|
||||
-40.05522918701172F,
|
||||
1.0F
|
||||
};
|
||||
|
||||
std::array<float, 3> translation = {}, scale = {};
|
||||
std::array<float, 4> rotation = {};
|
||||
fastgltf::decomposeTransformMatrix(matrix, scale, rotation, translation);
|
||||
|
||||
auto glmMatrix = glm::make_mat4x4(matrix.data());
|
||||
glm::vec3 glmScale, glmTranslation, glmSkew;
|
||||
glm::quat glmRotation;
|
||||
glm::vec4 glmPerspective;
|
||||
glm::decompose(glmMatrix, glmScale, glmRotation, glmTranslation, glmSkew, glmPerspective);
|
||||
|
||||
// I use glm::epsilon<float>() * 10 here because some matrices I tested this with resulted
|
||||
// in an error margin greater than the normal epsilon value. I will investigate this in the
|
||||
// future, but I suspect using double in the decompose functions should help mitigate most
|
||||
// of it.
|
||||
REQUIRE(glm::make_vec3(translation.data()) == glmTranslation);
|
||||
REQUIRE(glm::all(glm::epsilonEqual(glm::make_quat(rotation.data()), glmRotation, glm::epsilon<float>() * 10)));
|
||||
REQUIRE(glm::all(glm::epsilonEqual(glm::make_vec3(scale.data()), glmScale, glm::epsilon<float>())));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Validate sparse accessor parsing", "[gltf-loader]") {
|
||||
auto simpleSparseAccessor = sampleModels / "2.0" / "SimpleSparseAccessor" / "glTF";
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(simpleSparseAccessor / "SimpleSparseAccessor.gltf"));
|
||||
|
||||
fastgltf::Parser parser;
|
||||
auto asset = parser.loadGLTF(jsonData.get(), simpleSparseAccessor, noOptions, fastgltf::Category::Accessors);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->accessors.size() == 2);
|
||||
REQUIRE(!asset->accessors[0].sparse.has_value());
|
||||
REQUIRE(asset->accessors[1].sparse.has_value());
|
||||
auto& sparse = asset->accessors[1].sparse.value();
|
||||
REQUIRE(sparse.count == 3);
|
||||
REQUIRE(sparse.indicesBufferView == 2);
|
||||
REQUIRE(sparse.indicesByteOffset == 0);
|
||||
REQUIRE(sparse.valuesBufferView == 3);
|
||||
REQUIRE(sparse.valuesByteOffset == 0);
|
||||
REQUIRE(sparse.indexComponentType == fastgltf::ComponentType::UnsignedShort);
|
||||
}
|
||||
|
||||
TEST_CASE("Validate morph target parsing", "[gltf-loader]") {
|
||||
auto simpleMorph = sampleModels / "2.0" / "SimpleMorph" / "glTF";
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(simpleMorph / "SimpleMorph.gltf"));
|
||||
|
||||
fastgltf::Parser parser;
|
||||
auto asset = parser.loadGLTF(jsonData.get(), simpleMorph, noOptions, fastgltf::Category::Meshes);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->meshes.size() == 1);
|
||||
REQUIRE(asset->meshes.front().weights.size() == 2);
|
||||
REQUIRE(asset->meshes.front().primitives.size() == 1);
|
||||
|
||||
auto& primitive = asset->meshes.front().primitives.front();
|
||||
|
||||
auto position = primitive.findAttribute("POSITION");
|
||||
REQUIRE(position != primitive.attributes.end());
|
||||
REQUIRE((*position).second == 1);
|
||||
|
||||
REQUIRE(primitive.targets.size() == 2);
|
||||
|
||||
auto positionTarget0 = primitive.findTargetAttribute(0, "POSITION");
|
||||
REQUIRE(positionTarget0 != primitive.targets[0].end());
|
||||
REQUIRE((*positionTarget0).second == 2);
|
||||
|
||||
auto positionTarget1 = primitive.findTargetAttribute(1, "POSITION");
|
||||
REQUIRE(positionTarget0 != primitive.targets[1].end());
|
||||
REQUIRE((*positionTarget1).second == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("Test accessors min/max", "[gltf-loader]") {
|
||||
auto lightsLamp = sampleModels / "2.0" / "LightsPunctualLamp" / "glTF";
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(lightsLamp / "LightsPunctualLamp.gltf"));
|
||||
|
||||
fastgltf::Parser parser(fastgltf::Extensions::KHR_lights_punctual);
|
||||
auto asset = parser.loadGLTF(&jsonData, lightsLamp, noOptions, fastgltf::Category::Accessors);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(std::find_if(asset->extensionsUsed.begin(), asset->extensionsUsed.end(), [](auto& string) {
|
||||
return string == fastgltf::extensions::KHR_lights_punctual;
|
||||
}) != asset->extensionsUsed.end());
|
||||
|
||||
REQUIRE(asset->accessors.size() == 15);
|
||||
auto& accessors = asset->accessors;
|
||||
|
||||
{
|
||||
auto& firstAccessor = accessors[0];
|
||||
const auto* max = std::get_if<FASTGLTF_STD_PMR_NS::vector<std::int64_t>>(&firstAccessor.max);
|
||||
const auto* min = std::get_if<FASTGLTF_STD_PMR_NS::vector<std::int64_t>>(&firstAccessor.min);
|
||||
REQUIRE(max != nullptr);
|
||||
REQUIRE(min != nullptr);
|
||||
REQUIRE(max->size() == fastgltf::getNumComponents(firstAccessor.type));
|
||||
REQUIRE(max->size() == 1);
|
||||
REQUIRE(min->size() == 1);
|
||||
REQUIRE(max->front() == 3211);
|
||||
REQUIRE(min->front() == 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto& secondAccessor = accessors[1];
|
||||
const auto* max = std::get_if<FASTGLTF_STD_PMR_NS::vector<double>>(&secondAccessor.max);
|
||||
const auto* min = std::get_if<FASTGLTF_STD_PMR_NS::vector<double>>(&secondAccessor.min);
|
||||
REQUIRE(max != nullptr);
|
||||
REQUIRE(min != nullptr);
|
||||
REQUIRE(max->size() == fastgltf::getNumComponents(secondAccessor.type));
|
||||
REQUIRE(max->size() == 3);
|
||||
REQUIRE(min->size() == 3);
|
||||
|
||||
REQUIRE(glm::epsilonEqual(max->at(0), 0.81497824192047119, glm::epsilon<double>()));
|
||||
REQUIRE(glm::epsilonEqual(max->at(1), 1.8746249675750732, glm::epsilon<double>()));
|
||||
REQUIRE(glm::epsilonEqual(max->at(2), 0.32295516133308411, glm::epsilon<double>()));
|
||||
|
||||
REQUIRE(glm::epsilonEqual(min->at(0), -0.12269512563943863, glm::epsilon<double>()));
|
||||
REQUIRE(glm::epsilonEqual(min->at(1), 0.013025385327637196, glm::epsilon<double>()));
|
||||
REQUIRE(glm::epsilonEqual(min->at(2), -0.32393229007720947, glm::epsilon<double>()));
|
||||
}
|
||||
|
||||
{
|
||||
auto& fifthAccessor = accessors[4];
|
||||
const auto* max = std::get_if<FASTGLTF_STD_PMR_NS::vector<double>>(&fifthAccessor.max);
|
||||
const auto* min = std::get_if<FASTGLTF_STD_PMR_NS::vector<double>>(&fifthAccessor.min);
|
||||
REQUIRE(max != nullptr);
|
||||
REQUIRE(min != nullptr);
|
||||
REQUIRE(max->size() == fastgltf::getNumComponents(fifthAccessor.type));
|
||||
REQUIRE(max->size() == 4);
|
||||
REQUIRE(min->size() == 4);
|
||||
|
||||
REQUIRE(max->back() == 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test unicode characters", "[gltf-loader]") {
|
||||
auto lightsLamp = sampleModels / "2.0" / std::filesystem::u8path(u8"Unicode❤♻Test") / "glTF";
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(lightsLamp / std::filesystem::u8path(u8"Unicode❤♻Test.gltf")));
|
||||
|
||||
fastgltf::Parser parser;
|
||||
auto asset = parser.loadGLTF(&jsonData, lightsLamp);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(!asset->materials.empty());
|
||||
REQUIRE(asset->materials[0].name == u8"Unicode❤♻Material");
|
||||
|
||||
REQUIRE(!asset->buffers.empty());
|
||||
auto bufferUri = std::get<fastgltf::sources::URI>(asset->buffers[0].data);
|
||||
REQUIRE(bufferUri.uri.path() == u8"Unicode❤♻Binary.bin");
|
||||
}
|
||||
423
third_party/fastgltf/tests/benchmarks.cpp
vendored
Normal file
423
third_party/fastgltf/tests/benchmarks.cpp
vendored
Normal file
@@ -0,0 +1,423 @@
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "simdjson.h"
|
||||
|
||||
#include <fastgltf/parser.hpp>
|
||||
#include <fastgltf/base64.hpp>
|
||||
#include "gltf_path.hpp"
|
||||
|
||||
constexpr auto benchmarkOptions = fastgltf::Options::DontRequireValidAssetMember;
|
||||
|
||||
#ifdef HAS_RAPIDJSON
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/prettywriter.h"
|
||||
#include "rapidjson/rapidjson.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
#include "rapidjson/writer.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAS_TINYGLTF
|
||||
// We don't want tinygltf to load/write images.
|
||||
#define TINYGLTF_NO_STB_IMAGE_WRITE
|
||||
#define TINYGLTF_NO_STB_IMAGE
|
||||
#define TINYGLTF_NO_FS
|
||||
#define TINYGLTF_IMPLEMENTATION
|
||||
#include <tiny_gltf.h>
|
||||
|
||||
bool tinygltf_FileExistsFunction([[maybe_unused]] const std::string& filename, [[maybe_unused]] void* user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string tinygltf_ExpandFilePathFunction(const std::string& path, [[maybe_unused]] void* user) {
|
||||
return path;
|
||||
}
|
||||
|
||||
bool tinygltf_ReadWholeFileFunction(std::vector<unsigned char>* data, std::string*, const std::string&, void*) {
|
||||
// tinygltf checks if size == 1. It also checks if the size is correct for glb files, but
|
||||
// well ignore that for now.
|
||||
data->resize(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tinygltf_LoadImageData(tinygltf::Image *image, const int image_idx, std::string *err,
|
||||
std::string *warn, int req_width, int req_height,
|
||||
const unsigned char *bytes, int size, void *user_data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void setTinyGLTFCallbacks(tinygltf::TinyGLTF& gltf) {
|
||||
gltf.SetFsCallbacks({
|
||||
tinygltf_FileExistsFunction,
|
||||
tinygltf_ExpandFilePathFunction,
|
||||
tinygltf_ReadWholeFileFunction,
|
||||
nullptr, nullptr,
|
||||
});
|
||||
gltf.SetImageLoader(tinygltf_LoadImageData, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAS_CGLTF
|
||||
#define CGLTF_IMPLEMENTATION
|
||||
#include <cgltf.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAS_GLTFRS
|
||||
#include "rust/cxx.h"
|
||||
#include "gltf-rs-bridge/lib.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAS_ASSIMP
|
||||
#include <assimp/cimport.h>
|
||||
#include <assimp/scene.h>
|
||||
#include <assimp/Base64.hpp>
|
||||
#endif
|
||||
|
||||
std::vector<uint8_t> readFileAsBytes(std::filesystem::path path) {
|
||||
std::ifstream file(path, std::ios::ate | std::ios::binary);
|
||||
if (!file.is_open())
|
||||
throw std::runtime_error(std::string { "Failed to open file: " } + path.string());
|
||||
|
||||
auto fileSize = file.tellg();
|
||||
std::vector<uint8_t> bytes(static_cast<size_t>(fileSize) + fastgltf::getGltfBufferPadding());
|
||||
file.seekg(0, std::ifstream::beg);
|
||||
file.read(reinterpret_cast<char*>(bytes.data()), fileSize);
|
||||
file.close();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
TEST_CASE("Benchmark loading of NewSponza", "[gltf-benchmark]") {
|
||||
if (!std::filesystem::exists(intelSponza / "NewSponza_Main_glTF_002.gltf")) {
|
||||
// NewSponza is not part of gltf-Sample-Models, and therefore not always available.
|
||||
SKIP("Intel's NewSponza (GLTF) is required for this benchmark.");
|
||||
}
|
||||
|
||||
fastgltf::Parser parser;
|
||||
#ifdef HAS_TINYGLTF
|
||||
tinygltf::TinyGLTF tinygltf;
|
||||
tinygltf::Model model;
|
||||
std::string warn, err;
|
||||
#endif
|
||||
|
||||
auto bytes = readFileAsBytes(intelSponza / "NewSponza_Main_glTF_002.gltf");
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->fromByteView(bytes.data(), bytes.size() - fastgltf::getGltfBufferPadding(), bytes.size()));
|
||||
|
||||
BENCHMARK("Parse NewSponza") {
|
||||
return parser.loadGLTF(jsonData.get(), intelSponza, benchmarkOptions);
|
||||
};
|
||||
|
||||
#ifdef HAS_TINYGLTF
|
||||
setTinyGLTFCallbacks(tinygltf);
|
||||
BENCHMARK("Parse NewSponza with tinygltf") {
|
||||
return tinygltf.LoadASCIIFromString(&model, &err, &warn, reinterpret_cast<char*>(bytes.data()), bytes.size(), intelSponza.string());
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_CGLTF
|
||||
BENCHMARK("Parse NewSponza with cgltf") {
|
||||
cgltf_options options = {};
|
||||
cgltf_data* data = nullptr;
|
||||
cgltf_result result = cgltf_parse(&options, bytes.data(), bytes.size(), &data);
|
||||
REQUIRE(result == cgltf_result_success);
|
||||
cgltf_free(data);
|
||||
return result;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_GLTFRS
|
||||
auto padding = fastgltf::getGltfBufferPadding();
|
||||
BENCHMARK("Parse NewSponza with gltf-rs") {
|
||||
auto slice = rust::Slice<const std::uint8_t>(reinterpret_cast<std::uint8_t*>(bytes.data()), bytes.size() - padding);
|
||||
return rust::gltf::run(slice);
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_ASSIMP
|
||||
BENCHMARK("Parse NewSponza with assimp") {
|
||||
return aiImportFileFromMemory(reinterpret_cast<const char*>(bytes.data()), jsonData->getBufferSize(), 0, nullptr);
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("Benchmark base64 decoding from glTF file", "[gltf-benchmark]") {
|
||||
fastgltf::Parser parser;
|
||||
#ifdef HAS_TINYGLTF
|
||||
tinygltf::TinyGLTF tinygltf;
|
||||
tinygltf::Model model;
|
||||
std::string warn, err;
|
||||
#endif
|
||||
|
||||
auto cylinderEngine = sampleModels / "2.0" / "2CylinderEngine" / "glTF-Embedded";
|
||||
auto bytes = readFileAsBytes(cylinderEngine / "2CylinderEngine.gltf");
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->fromByteView(bytes.data(), bytes.size() - fastgltf::getGltfBufferPadding(), bytes.size()));
|
||||
|
||||
BENCHMARK("Parse 2CylinderEngine and decode base64") {
|
||||
return parser.loadGLTF(jsonData.get(), cylinderEngine, benchmarkOptions);
|
||||
};
|
||||
|
||||
#ifdef HAS_TINYGLTF
|
||||
setTinyGLTFCallbacks(tinygltf);
|
||||
BENCHMARK("2CylinderEngine decode with tinygltf") {
|
||||
return tinygltf.LoadASCIIFromString(&model, &err, &warn, reinterpret_cast<char*>(bytes.data()), bytes.size(), cylinderEngine.string());
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_CGLTF
|
||||
BENCHMARK("2CylinderEngine decode with cgltf") {
|
||||
cgltf_options options = {};
|
||||
cgltf_data* data = nullptr;
|
||||
auto filePath = cylinderEngine.string();
|
||||
cgltf_result result = cgltf_parse(&options, bytes.data(), bytes.size(), &data);
|
||||
REQUIRE(result == cgltf_result_success);
|
||||
result = cgltf_load_buffers(&options, data, filePath.c_str());
|
||||
cgltf_free(data);
|
||||
return result;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_GLTFRS
|
||||
auto padding = fastgltf::getGltfBufferPadding();
|
||||
BENCHMARK("2CylinderEngine with gltf-rs") {
|
||||
auto slice = rust::Slice<const std::uint8_t>(reinterpret_cast<std::uint8_t*>(bytes.data()), bytes.size() - padding);
|
||||
return rust::gltf::run(slice);
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_ASSIMP
|
||||
BENCHMARK("2CylinderEngine with assimp") {
|
||||
const auto* scene = aiImportFileFromMemory(reinterpret_cast<const char*>(bytes.data()), jsonData->getBufferSize(), 0, nullptr);
|
||||
REQUIRE(scene != nullptr);
|
||||
return scene;
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("Benchmark raw JSON parsing", "[gltf-benchmark]") {
|
||||
fastgltf::Parser parser;
|
||||
#ifdef HAS_TINYGLTF
|
||||
tinygltf::TinyGLTF tinygltf;
|
||||
tinygltf::Model model;
|
||||
std::string warn, err;
|
||||
#endif
|
||||
|
||||
auto buggyPath = sampleModels / "2.0" / "Buggy" / "glTF";
|
||||
auto bytes = readFileAsBytes(buggyPath / "Buggy.gltf");
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->fromByteView(bytes.data(), bytes.size() - fastgltf::getGltfBufferPadding(), bytes.size()));
|
||||
|
||||
BENCHMARK("Parse Buggy.gltf") {
|
||||
return parser.loadGLTF(jsonData.get(), buggyPath, benchmarkOptions);
|
||||
};
|
||||
|
||||
#ifdef HAS_TINYGLTF
|
||||
setTinyGLTFCallbacks(tinygltf);
|
||||
BENCHMARK("Parse Buggy.gltf with tinygltf") {
|
||||
return tinygltf.LoadASCIIFromString(&model, &err, &warn, reinterpret_cast<char*>(bytes.data()), bytes.size(), buggyPath.string());
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_CGLTF
|
||||
BENCHMARK("Parse Buggy.gltf with cgltf") {
|
||||
cgltf_options options = {};
|
||||
cgltf_data* data = nullptr;
|
||||
auto filePath = buggyPath.string();
|
||||
cgltf_result result = cgltf_parse(&options, bytes.data(), bytes.size(), &data);
|
||||
REQUIRE(result == cgltf_result_success);
|
||||
cgltf_free(data);
|
||||
return result;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_GLTFRS
|
||||
auto padding = fastgltf::getGltfBufferPadding();
|
||||
BENCHMARK("Parse Buggy.gltf with gltf-rs") {
|
||||
auto slice = rust::Slice<const std::uint8_t>(reinterpret_cast<std::uint8_t*>(bytes.data()), bytes.size() - padding);
|
||||
return rust::gltf::run(slice);
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_ASSIMP
|
||||
BENCHMARK("Parse Buggy.gltf with assimp") {
|
||||
return aiImportFileFromMemory(reinterpret_cast<const char*>(bytes.data()), jsonData->getBufferSize(), 0, nullptr);
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("Benchmark massive gltf file", "[gltf-benchmark]") {
|
||||
if (!std::filesystem::exists(bistroPath / "bistro.gltf")) {
|
||||
// Bistro is not part of gltf-Sample-Models, and therefore not always available.
|
||||
SKIP("Amazon's Bistro (GLTF) is required for this benchmark.");
|
||||
}
|
||||
|
||||
fastgltf::Parser parser(fastgltf::Extensions::KHR_mesh_quantization);
|
||||
#ifdef HAS_TINYGLTF
|
||||
tinygltf::TinyGLTF tinygltf;
|
||||
tinygltf::Model model;
|
||||
std::string warn, err;
|
||||
#endif
|
||||
|
||||
auto bytes = readFileAsBytes(bistroPath / "bistro.gltf");
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->fromByteView(bytes.data(), bytes.size() - fastgltf::getGltfBufferPadding(), bytes.size()));
|
||||
|
||||
BENCHMARK("Parse Bistro") {
|
||||
return parser.loadGLTF(jsonData.get(), bistroPath, benchmarkOptions);
|
||||
};
|
||||
|
||||
#ifdef HAS_TINYGLTF
|
||||
setTinyGLTFCallbacks(tinygltf);
|
||||
BENCHMARK("Parse Bistro with tinygltf") {
|
||||
return tinygltf.LoadASCIIFromString(&model, &err, &warn, reinterpret_cast<char*>(bytes.data()), bytes.size(), bistroPath.string());
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_CGLTF
|
||||
BENCHMARK("Parse Bistro with cgltf") {
|
||||
cgltf_options options = {};
|
||||
cgltf_data* data = nullptr;
|
||||
auto filePath = bistroPath.string();
|
||||
cgltf_result result = cgltf_parse(&options, bytes.data(), bytes.size(), &data);
|
||||
REQUIRE(result == cgltf_result_success);
|
||||
cgltf_free(data);
|
||||
return result;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_GLTFRS
|
||||
auto padding = fastgltf::getGltfBufferPadding();
|
||||
BENCHMARK("Parse Bistro with gltf-rs") {
|
||||
auto slice = rust::Slice<const std::uint8_t>(reinterpret_cast<std::uint8_t*>(bytes.data()), bytes.size() - padding);
|
||||
return rust::gltf::run(slice);
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_ASSIMP
|
||||
BENCHMARK("Parse Bistro with assimp") {
|
||||
return aiImportFileFromMemory(reinterpret_cast<const char*>(bytes.data()), jsonData->getBufferSize(), 0, nullptr);
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("Compare parsing performance with minified documents", "[gltf-benchmark]") {
|
||||
auto buggyPath = sampleModels / "2.0" / "Buggy" / "glTF";
|
||||
auto bytes = readFileAsBytes(buggyPath / "Buggy.gltf");
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->fromByteView(bytes.data(), bytes.size() - fastgltf::getGltfBufferPadding(), bytes.size()));
|
||||
|
||||
// Create a minified JSON string
|
||||
std::vector<uint8_t> minified(bytes.size());
|
||||
size_t dstLen = 0;
|
||||
auto result = simdjson::minify(reinterpret_cast<const char*>(bytes.data()), bytes.size(),
|
||||
reinterpret_cast<char*>(minified.data()), dstLen);
|
||||
REQUIRE(result == simdjson::SUCCESS);
|
||||
minified.resize(dstLen);
|
||||
|
||||
// For completeness, benchmark minifying the JSON
|
||||
BENCHMARK("Minify Buggy.gltf") {
|
||||
auto result = simdjson::minify(reinterpret_cast<const char*>(bytes.data()), bytes.size(),
|
||||
reinterpret_cast<char*>(minified.data()), dstLen);
|
||||
REQUIRE(result == simdjson::SUCCESS);
|
||||
return result;
|
||||
};
|
||||
|
||||
auto minifiedJsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(minifiedJsonData->fromByteView(minified.data(), minified.size() - fastgltf::getGltfBufferPadding(), minified.size()));
|
||||
|
||||
fastgltf::Parser parser;
|
||||
BENCHMARK("Parse Buggy.gltf with normal JSON") {
|
||||
return parser.loadGLTF(jsonData.get(), buggyPath, benchmarkOptions);
|
||||
};
|
||||
|
||||
BENCHMARK("Parse Buggy.gltf with minified JSON") {
|
||||
return parser.loadGLTF(minifiedJsonData.get(), buggyPath, benchmarkOptions);
|
||||
};
|
||||
}
|
||||
|
||||
#if defined(FASTGLTF_IS_X86)
|
||||
TEST_CASE("Small CRC32-C benchmark", "[gltf-benchmark]") {
|
||||
static constexpr std::string_view test = "abcdefghijklmnopqrstuvwxyz";
|
||||
BENCHMARK("Default 1-byte tabular algorithm") {
|
||||
return fastgltf::crc32c(reinterpret_cast<const std::uint8_t*>(test.data()), test.size());
|
||||
};
|
||||
BENCHMARK("SSE4 hardware algorithm") {
|
||||
return fastgltf::hwcrc32c(reinterpret_cast<const std::uint8_t*>(test.data()), test.size());
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("Compare base64 decoding performance", "[gltf-benchmark]") {
|
||||
std::string base64Characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
constexpr std::size_t bufferSize = 2 * 1024 * 1024;
|
||||
|
||||
// We'll generate a random base64 buffer
|
||||
std::random_device device;
|
||||
std::mt19937 gen(device());
|
||||
std::uniform_int_distribution<> distribution(0, base64Characters.size() - 1);
|
||||
std::string generatedData;
|
||||
generatedData.reserve(bufferSize);
|
||||
for (std::size_t i = 0; i < bufferSize; ++i) {
|
||||
generatedData.push_back(base64Characters[distribution(gen)]);
|
||||
}
|
||||
|
||||
#ifdef HAS_TINYGLTF
|
||||
BENCHMARK("Run tinygltf's base64 decoder") {
|
||||
return tinygltf::base64_decode(generatedData);
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_CGLTF
|
||||
cgltf_options options {};
|
||||
BENCHMARK("Run cgltf's base64 decoder") {
|
||||
auto padding = fastgltf::base64::getPadding(generatedData);
|
||||
auto outputSize = fastgltf::base64::getOutputSize(generatedData.size(), padding);
|
||||
std::string output;
|
||||
output.resize(outputSize);
|
||||
auto* outputData = output.data();
|
||||
return cgltf_load_buffer_base64(&options, generatedData.size(), generatedData.data(), reinterpret_cast<void**>(&outputData));
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_GLTFRS
|
||||
BENCHMARK("Run base64 Rust library decoder") {
|
||||
auto slice = rust::Slice<const std::uint8_t>(reinterpret_cast<std::uint8_t*>(generatedData.data()), generatedData.size());
|
||||
return rust::gltf::run_base64(slice);
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef HAS_ASSIMP
|
||||
BENCHMARK("Run Assimp's base64 decoder") {
|
||||
return Assimp::Base64::Decode(generatedData);
|
||||
};
|
||||
#endif
|
||||
|
||||
BENCHMARK("Run fastgltf's fallback base64 decoder") {
|
||||
return fastgltf::base64::fallback_decode(generatedData);
|
||||
};
|
||||
|
||||
#if defined(FASTGLTF_IS_X86)
|
||||
const auto& impls = simdjson::get_available_implementations();
|
||||
if (const auto* sse4 = impls["westmere"]; sse4 != nullptr && sse4->supported_by_runtime_system()) {
|
||||
BENCHMARK("Run fastgltf's SSE4 base64 decoder") {
|
||||
return fastgltf::base64::sse4_decode(generatedData);
|
||||
};
|
||||
}
|
||||
|
||||
if (const auto* avx2 = impls["haswell"]; avx2 != nullptr && avx2->supported_by_runtime_system()) {
|
||||
BENCHMARK("Run fastgltf's AVX2 base64 decoder") {
|
||||
return fastgltf::base64::avx2_decode(generatedData);
|
||||
};
|
||||
}
|
||||
#elif defined(FASTGLTF_IS_A64)
|
||||
const auto& impls = simdjson::get_available_implementations();
|
||||
if (const auto* neon = impls["arm64"]; avx2 != nullptr && neon->supported_by_runtime_system()) {
|
||||
BENCHMARK("Run fastgltf's Neon base64 decoder") {
|
||||
return fastgltf::base64::neon_decode(generatedData);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
262
third_party/fastgltf/tests/extension_tests.cpp
vendored
Normal file
262
third_party/fastgltf/tests/extension_tests.cpp
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
|
||||
#include <glm/gtc/epsilon.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include <fastgltf/parser.hpp>
|
||||
#include "gltf_path.hpp"
|
||||
|
||||
TEST_CASE("Loading KHR_texture_basisu glTF files", "[gltf-loader]") {
|
||||
auto stainedLamp = sampleModels / "2.0" / "StainedGlassLamp" / "glTF-KTX-BasisU";
|
||||
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(stainedLamp / "StainedGlassLamp.gltf"));
|
||||
|
||||
SECTION("Loading KHR_texture_basisu") {
|
||||
fastgltf::Parser parser(fastgltf::Extensions::KHR_texture_basisu);
|
||||
auto asset = parser.loadGLTF(jsonData.get(), path, fastgltf::Options::DontRequireValidAssetMember,
|
||||
fastgltf::Category::Textures | fastgltf::Category::Images);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->textures.size() == 19);
|
||||
REQUIRE(!asset->images.empty());
|
||||
|
||||
auto& texture = asset->textures[1];
|
||||
REQUIRE(!texture.imageIndex.has_value());
|
||||
REQUIRE(texture.samplerIndex == 0);
|
||||
REQUIRE(texture.basisuImageIndex.has_value());
|
||||
REQUIRE(texture.basisuImageIndex.value() == 1);
|
||||
|
||||
auto& image = asset->images.front();
|
||||
auto* filePath = std::get_if<fastgltf::sources::URI>(&image.data);
|
||||
REQUIRE(filePath != nullptr);
|
||||
REQUIRE(filePath->uri.valid());
|
||||
REQUIRE(filePath->uri.isLocalPath());
|
||||
REQUIRE(filePath->mimeType == fastgltf::MimeType::KTX2);
|
||||
}
|
||||
|
||||
SECTION("Testing requiredExtensions") {
|
||||
// We specify no extensions, yet the StainedGlassLamp requires KHR_texture_basisu.
|
||||
fastgltf::Parser parser(fastgltf::Extensions::None);
|
||||
auto stainedGlassLamp = parser.loadGLTF(jsonData.get(), path, fastgltf::Options::DontRequireValidAssetMember);
|
||||
REQUIRE(stainedGlassLamp.error() == fastgltf::Error::MissingExtensions);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Loading KHR_texture_transform glTF files", "[gltf-loader]") {
|
||||
auto transformTest = sampleModels / "2.0" / "TextureTransformMultiTest" / "glTF";
|
||||
|
||||
auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
|
||||
REQUIRE(jsonData->loadFromFile(transformTest / "TextureTransformMultiTest.gltf"));
|
||||
|
||||
fastgltf::Parser parser(fastgltf::Extensions::KHR_texture_transform);
|
||||
auto asset = parser.loadGLTF(jsonData.get(), transformTest, fastgltf::Options::DontRequireValidAssetMember, fastgltf::Category::Materials);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(!asset->materials.empty());
|
||||
|
||||
auto& material = asset->materials.front();
|
||||
REQUIRE(material.pbrData.baseColorTexture.has_value());
|
||||
REQUIRE(material.pbrData.baseColorTexture->transform != nullptr);
|
||||
REQUIRE(material.pbrData.baseColorTexture->transform->uvOffset[0] == 0.705f);
|
||||
REQUIRE(material.pbrData.baseColorTexture->transform->rotation == Catch::Approx(1.5707963705062866f));
|
||||
}
|
||||
|
||||
TEST_CASE("Test KHR_lights_punctual", "[gltf-loader]") {
|
||||
auto lightsLamp = sampleModels / "2.0" / "LightsPunctualLamp" / "glTF";
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(lightsLamp / "LightsPunctualLamp.gltf"));
|
||||
|
||||
fastgltf::Parser parser(fastgltf::Extensions::KHR_lights_punctual);
|
||||
auto asset = parser.loadGLTF(&jsonData, lightsLamp, fastgltf::Options::None, fastgltf::Category::Nodes);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->lights.size() == 5);
|
||||
REQUIRE(asset->nodes.size() > 4);
|
||||
|
||||
auto& nodes = asset->nodes;
|
||||
REQUIRE(nodes[3].lightIndex.has_value());
|
||||
REQUIRE(nodes[3].lightIndex.value() == 0);
|
||||
|
||||
auto& lights = asset->lights;
|
||||
REQUIRE(lights[0].name == "Point");
|
||||
REQUIRE(lights[0].type == fastgltf::LightType::Point);
|
||||
REQUIRE(lights[0].intensity == 15.0f);
|
||||
REQUIRE(glm::epsilonEqual(lights[0].color[0], 1.0f, glm::epsilon<float>()));
|
||||
REQUIRE(glm::epsilonEqual(lights[0].color[1], 0.63187497854232788f, glm::epsilon<float>()));
|
||||
REQUIRE(glm::epsilonEqual(lights[0].color[2], 0.23909975588321689f, glm::epsilon<float>()));
|
||||
}
|
||||
|
||||
TEST_CASE("Test KHR_materials_specular", "[gltf-loader]") {
|
||||
auto specularTest = sampleModels / "2.0" / "SpecularTest" / "glTF";
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(specularTest / "SpecularTest.gltf"));
|
||||
|
||||
fastgltf::Parser parser(fastgltf::Extensions::KHR_materials_specular);
|
||||
auto asset = parser.loadGLTF(&jsonData, specularTest, fastgltf::Options::None, fastgltf::Category::Materials);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->materials.size() >= 12);
|
||||
|
||||
auto& materials = asset->materials;
|
||||
REQUIRE(materials[1].specular != nullptr);
|
||||
REQUIRE(materials[1].specular->specularFactor == 0.0f);
|
||||
|
||||
REQUIRE(materials[2].specular != nullptr);
|
||||
REQUIRE(glm::epsilonEqual(materials[2].specular->specularFactor, 0.051269f, glm::epsilon<float>()));
|
||||
|
||||
REQUIRE(materials[8].specular != nullptr);
|
||||
REQUIRE(glm::epsilonEqual(materials[8].specular->specularColorFactor[0], 0.051269f, glm::epsilon<float>()));
|
||||
REQUIRE(glm::epsilonEqual(materials[8].specular->specularColorFactor[1], 0.051269f, glm::epsilon<float>()));
|
||||
REQUIRE(glm::epsilonEqual(materials[8].specular->specularColorFactor[2], 0.051269f, glm::epsilon<float>()));
|
||||
|
||||
REQUIRE(materials[12].specular != nullptr);
|
||||
REQUIRE(materials[12].specular->specularColorTexture.has_value());
|
||||
REQUIRE(materials[12].specular->specularColorTexture.value().textureIndex == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Test KHR_materials_ior and KHR_materials_iridescence", "[gltf-loader]") {
|
||||
auto specularTest = sampleModels / "2.0" / "IridescenceDielectricSpheres" / "glTF";
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(specularTest / "IridescenceDielectricSpheres.gltf"));
|
||||
|
||||
fastgltf::Parser parser(fastgltf::Extensions::KHR_materials_iridescence | fastgltf::Extensions::KHR_materials_ior);
|
||||
auto asset = parser.loadGLTF(&jsonData, specularTest, fastgltf::Options::None, fastgltf::Category::Materials);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->materials.size() >= 50);
|
||||
|
||||
auto& materials = asset->materials;
|
||||
REQUIRE(materials[0].iridescence != nullptr);
|
||||
REQUIRE(materials[0].iridescence->iridescenceFactor == 1.0f);
|
||||
REQUIRE(materials[0].iridescence->iridescenceIor == 1.0f);
|
||||
REQUIRE(materials[0].iridescence->iridescenceThicknessMaximum == 100.0f);
|
||||
|
||||
REQUIRE(materials[0].ior.has_value());
|
||||
REQUIRE(materials[0].ior.value() == 1.0f);
|
||||
|
||||
REQUIRE(materials[7].ior.has_value());
|
||||
REQUIRE(materials[7].ior.value() == 1.17f);
|
||||
|
||||
REQUIRE(materials[50].iridescence != nullptr);
|
||||
REQUIRE(materials[50].iridescence->iridescenceFactor == 1.0f);
|
||||
REQUIRE(materials[50].iridescence->iridescenceIor == 1.17f);
|
||||
REQUIRE(materials[50].iridescence->iridescenceThicknessMaximum == 200.0f);
|
||||
}
|
||||
|
||||
TEST_CASE("Test KHR_materials_volume and KHR_materials_transmission", "[gltf-loader]") {
|
||||
auto beautifulGame = sampleModels / "2.0" / "ABeautifulGame" / "glTF";
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(beautifulGame / "ABeautifulGame.gltf"));
|
||||
|
||||
fastgltf::Parser parser(fastgltf::Extensions::KHR_materials_volume | fastgltf::Extensions::KHR_materials_transmission);
|
||||
auto asset = parser.loadGLTF(&jsonData, beautifulGame, fastgltf::Options::None, fastgltf::Category::Materials);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->materials.size() >= 5);
|
||||
|
||||
auto& materials = asset->materials;
|
||||
REQUIRE(materials[5].volume != nullptr);
|
||||
REQUIRE(glm::epsilonEqual(materials[5].volume->thicknessFactor, 0.2199999988079071f, glm::epsilon<float>()));
|
||||
REQUIRE(glm::epsilonEqual(materials[5].volume->attenuationColor[0], 0.800000011920929f, glm::epsilon<float>()));
|
||||
REQUIRE(glm::epsilonEqual(materials[5].volume->attenuationColor[1], 0.800000011920929f, glm::epsilon<float>()));
|
||||
REQUIRE(glm::epsilonEqual(materials[5].volume->attenuationColor[2], 0.800000011920929f, glm::epsilon<float>()));
|
||||
|
||||
REQUIRE(materials[5].transmission != nullptr);
|
||||
REQUIRE(materials[5].transmission->transmissionFactor == 1.0f);
|
||||
}
|
||||
|
||||
TEST_CASE("Test KHR_materials_clearcoat", "[gltf-loader]") {
|
||||
auto clearcoatTest = sampleModels / "2.0" / "ClearCoatTest" / "glTF";
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(clearcoatTest / "ClearCoatTest.gltf"));
|
||||
|
||||
fastgltf::Parser parser(fastgltf::Extensions::KHR_materials_clearcoat);
|
||||
auto asset = parser.loadGLTF(&jsonData, clearcoatTest, fastgltf::Options::None, fastgltf::Category::Materials);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->materials.size() >= 7);
|
||||
|
||||
auto& materials = asset->materials;
|
||||
REQUIRE(materials[1].clearcoat != nullptr);
|
||||
REQUIRE(materials[1].clearcoat->clearcoatFactor == 1.0f);
|
||||
REQUIRE(materials[1].clearcoat->clearcoatRoughnessFactor == 0.03f);
|
||||
|
||||
REQUIRE(materials[7].clearcoat != nullptr);
|
||||
REQUIRE(materials[7].clearcoat->clearcoatFactor == 1.0f);
|
||||
REQUIRE(materials[7].clearcoat->clearcoatRoughnessFactor == 1.0f);
|
||||
REQUIRE(materials[7].clearcoat->clearcoatRoughnessTexture.has_value());
|
||||
REQUIRE(materials[7].clearcoat->clearcoatRoughnessTexture->textureIndex == 2);
|
||||
REQUIRE(materials[7].clearcoat->clearcoatRoughnessTexture->texCoordIndex == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Test EXT_mesh_gpu_instancing", "[gltf-loader]") {
|
||||
auto simpleInstancingTest = sampleModels / "2.0" / "SimpleInstancing" / "glTF";
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(simpleInstancingTest / "SimpleInstancing.gltf"));
|
||||
|
||||
fastgltf::Parser parser(fastgltf::Extensions::EXT_mesh_gpu_instancing);
|
||||
auto asset = parser.loadGLTF(&jsonData, simpleInstancingTest, fastgltf::Options::None, fastgltf::Category::Accessors | fastgltf::Category::Nodes);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->accessors.size() >= 6);
|
||||
REQUIRE(asset->nodes.size() >= 1);
|
||||
|
||||
auto& nodes = asset->nodes;
|
||||
REQUIRE(nodes[0].instancingAttributes.size() == 3u);
|
||||
REQUIRE(nodes[0].findInstancingAttribute("TRANSLATION") != nodes[0].instancingAttributes.cend());
|
||||
REQUIRE(nodes[0].findInstancingAttribute("SCALE") != nodes[0].instancingAttributes.cend());
|
||||
REQUIRE(nodes[0].findInstancingAttribute("ROTATION") != nodes[0].instancingAttributes.cend());
|
||||
}
|
||||
|
||||
#if FASTGLTF_ENABLE_DEPRECATED_EXT
|
||||
TEST_CASE("Test KHR_materials_pbrSpecularGlossiness", "[gltf-loader]") {
|
||||
auto specularGlossinessTest = sampleModels / "2.0" / "SpecGlossVsMetalRough" / "glTF";
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(specularGlossinessTest / "SpecGlossVsMetalRough.gltf"));
|
||||
|
||||
fastgltf::Parser parser(fastgltf::Extensions::KHR_materials_pbrSpecularGlossiness | fastgltf::Extensions::KHR_materials_specular);
|
||||
auto asset = parser.loadGLTF(&jsonData, specularGlossinessTest);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->materials.size() == 4);
|
||||
|
||||
auto& materials = asset->materials;
|
||||
REQUIRE(materials[0].specularGlossiness != nullptr);
|
||||
REQUIRE(materials[0].specularGlossiness->diffuseFactor[0] == 1.0f);
|
||||
REQUIRE(materials[0].specularGlossiness->diffuseFactor[1] == 1.0f);
|
||||
REQUIRE(materials[0].specularGlossiness->diffuseFactor[2] == 1.0f);
|
||||
REQUIRE(materials[0].specularGlossiness->diffuseFactor[3] == 1.0f);
|
||||
REQUIRE(materials[0].specularGlossiness->specularFactor[0] == 1.0f);
|
||||
REQUIRE(materials[0].specularGlossiness->specularFactor[1] == 1.0f);
|
||||
REQUIRE(materials[0].specularGlossiness->specularFactor[2] == 1.0f);
|
||||
REQUIRE(materials[0].specularGlossiness->glossinessFactor == 1.0f);
|
||||
REQUIRE(materials[0].specularGlossiness->diffuseTexture.has_value());
|
||||
REQUIRE(materials[0].specularGlossiness->diffuseTexture.value().textureIndex == 5);
|
||||
REQUIRE(materials[0].specularGlossiness->specularGlossinessTexture.has_value());
|
||||
REQUIRE(materials[0].specularGlossiness->specularGlossinessTexture.value().textureIndex == 6);
|
||||
|
||||
REQUIRE(materials[3].specularGlossiness != nullptr);
|
||||
REQUIRE(materials[3].specularGlossiness->diffuseFactor[0] == 1.0f);
|
||||
REQUIRE(materials[3].specularGlossiness->diffuseFactor[1] == 1.0f);
|
||||
REQUIRE(materials[3].specularGlossiness->diffuseFactor[2] == 1.0f);
|
||||
REQUIRE(materials[3].specularGlossiness->diffuseFactor[3] == 1.0f);
|
||||
REQUIRE(materials[3].specularGlossiness->specularFactor[0] == 0.0f);
|
||||
REQUIRE(materials[3].specularGlossiness->specularFactor[1] == 0.0f);
|
||||
REQUIRE(materials[3].specularGlossiness->specularFactor[2] == 0.0f);
|
||||
REQUIRE(materials[3].specularGlossiness->glossinessFactor == 0.0f);
|
||||
REQUIRE(materials[3].specularGlossiness->diffuseTexture.has_value());
|
||||
REQUIRE(materials[3].specularGlossiness->diffuseTexture.value().textureIndex == 7);
|
||||
}
|
||||
#endif
|
||||
57
third_party/fastgltf/tests/glb_tests.cpp
vendored
Normal file
57
third_party/fastgltf/tests/glb_tests.cpp
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
#include <fstream>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <fastgltf/parser.hpp>
|
||||
#include <fastgltf/types.hpp>
|
||||
#include "gltf_path.hpp"
|
||||
|
||||
TEST_CASE("Load basic GLB file", "[gltf-loader]") {
|
||||
fastgltf::Parser parser;
|
||||
auto folder = sampleModels / "2.0" / "Box" / "glTF-Binary";
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(folder / "Box.glb"));
|
||||
|
||||
SECTION("Load basic Box.glb") {
|
||||
auto asset = parser.loadBinaryGLTF(&jsonData, folder, fastgltf::Options::None, fastgltf::Category::Buffers);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->buffers.size() == 1);
|
||||
|
||||
auto& buffer = asset->buffers.front();
|
||||
auto* bufferView = std::get_if<fastgltf::sources::ByteView>(&buffer.data);
|
||||
REQUIRE(bufferView != nullptr);
|
||||
auto jsonSpan = fastgltf::span<std::byte>(jsonData);
|
||||
REQUIRE(bufferView->bytes.data() - jsonSpan.data() == 1016);
|
||||
REQUIRE(jsonSpan.size() == 1664);
|
||||
}
|
||||
|
||||
SECTION("Load basic Box.glb and load buffers") {
|
||||
auto asset = parser.loadBinaryGLTF(&jsonData, folder, fastgltf::Options::LoadGLBBuffers, fastgltf::Category::Buffers);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->buffers.size() == 1);
|
||||
|
||||
auto& buffer = asset->buffers.front();
|
||||
auto* bufferVector = std::get_if<fastgltf::sources::Vector>(&buffer.data);
|
||||
REQUIRE(bufferVector != nullptr);
|
||||
REQUIRE(!bufferVector->bytes.empty());
|
||||
REQUIRE(static_cast<uint64_t>(bufferVector->bytes.size() - buffer.byteLength) < 3);
|
||||
}
|
||||
|
||||
SECTION("Load GLB by bytes") {
|
||||
std::ifstream file(folder / "Box.glb", std::ios::binary | std::ios::ate);
|
||||
auto length = static_cast<size_t>(file.tellg());
|
||||
file.seekg(0, std::ifstream::beg);
|
||||
std::vector<uint8_t> bytes(length + fastgltf::getGltfBufferPadding());
|
||||
file.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(length));
|
||||
|
||||
fastgltf::GltfDataBuffer byteBuffer;
|
||||
REQUIRE(byteBuffer.fromByteView(bytes.data(), length, length + fastgltf::getGltfBufferPadding()));
|
||||
|
||||
auto asset = parser.loadBinaryGLTF(&byteBuffer, folder, fastgltf::Options::LoadGLBBuffers, fastgltf::Category::Buffers);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
}
|
||||
}
|
||||
16
third_party/fastgltf/tests/gltf-rs/Cargo.toml
vendored
Normal file
16
third_party/fastgltf/tests/gltf-rs/Cargo.toml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "gltf_rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cxx = "1.0"
|
||||
gltf = "1.1.0"
|
||||
base64 = "0.21.2"
|
||||
|
||||
[build-dependencies]
|
||||
cxx-build = "1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
path = "src/lib.rs"
|
||||
54
third_party/fastgltf/tests/gltf-rs/src/lib.rs
vendored
Normal file
54
third_party/fastgltf/tests/gltf-rs/src/lib.rs
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
use base64::Engine;
|
||||
use base64::{alphabet, engine};
|
||||
|
||||
#[cxx::bridge(namespace = "rust::gltf")]
|
||||
mod ffi {
|
||||
extern "Rust" {
|
||||
fn run(data: &[u8]) -> i32;
|
||||
fn run_base64(data: &[u8]) -> Vec<u8>;
|
||||
}
|
||||
}
|
||||
|
||||
fn run(data: &[u8]) -> i32 {
|
||||
// TODO: Decode URIs and data URIs?
|
||||
let gltf = gltf::Gltf::from_slice(data)
|
||||
.unwrap();
|
||||
|
||||
// Decode URIs
|
||||
let json = gltf.document.into_json();
|
||||
|
||||
let mut uri_count = 0;
|
||||
for x in json.buffers {
|
||||
// gltf-rs doesn't automatically decode base64. Using its "import" feature won't work,
|
||||
// because we're not interested in file-loaded buffer/image data.
|
||||
if x.uri.is_some() {
|
||||
let uri = x.uri.unwrap();
|
||||
if let Some(rest) = uri.strip_prefix("data:") {
|
||||
let mut it = rest.split(";base64,");
|
||||
let data = match (it.next(), it.next()) {
|
||||
(_, Some(data)) => Some(data),
|
||||
(Some(data), _) => Some(data),
|
||||
_ => None
|
||||
};
|
||||
if data.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
base64::engine::GeneralPurpose::new(
|
||||
&base64::alphabet::STANDARD,
|
||||
base64::engine::general_purpose::PAD)
|
||||
.decode(data.unwrap()).expect("Decoded bytes");
|
||||
uri_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uri_count
|
||||
}
|
||||
|
||||
fn run_base64(data: &[u8]) -> Vec<u8> {
|
||||
return engine::GeneralPurpose::new(
|
||||
&alphabet::STANDARD,
|
||||
engine::general_purpose::PAD)
|
||||
.decode(data).expect("Decoded bytes");
|
||||
}
|
||||
1
third_party/fastgltf/tests/gltf/base64.txt
vendored
Normal file
1
third_party/fastgltf/tests/gltf/base64.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA
|
||||
BIN
third_party/fastgltf/tests/gltf/base64.txt.out
vendored
Normal file
BIN
third_party/fastgltf/tests/gltf/base64.txt.out
vendored
Normal file
Binary file not shown.
5
third_party/fastgltf/tests/gltf/basic_gltf.gltf
vendored
Normal file
5
third_party/fastgltf/tests/gltf/basic_gltf.gltf
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"asset": {
|
||||
"version": "2.0"
|
||||
}
|
||||
}
|
||||
1
third_party/fastgltf/tests/gltf/empty_json.gltf
vendored
Normal file
1
third_party/fastgltf/tests/gltf/empty_json.gltf
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
40
third_party/fastgltf/tests/gltf/transform_matrices.gltf
vendored
Normal file
40
third_party/fastgltf/tests/gltf/transform_matrices.gltf
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"asset": {
|
||||
"version": "2.0"
|
||||
},
|
||||
"cameras": [
|
||||
{
|
||||
"perspective": {
|
||||
"yfov": 1.0,
|
||||
"zfar": 1.0,
|
||||
"znear": 0.001
|
||||
},
|
||||
"type": "perspective"
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"name": "TRS components",
|
||||
"camera": 0,
|
||||
"translation": [
|
||||
1.0, 1.0, 1.0
|
||||
],
|
||||
"rotation": [
|
||||
0.0, 1.0, 0.0, 0.0
|
||||
],
|
||||
"scale": [
|
||||
2.0, 0.5, 1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Matrix",
|
||||
"camera": 0,
|
||||
"matrix": [
|
||||
-2.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.5, 0.0, 0.0,
|
||||
0.0, 0.0, -1.0, 0.0,
|
||||
1.0, 1.0, 1.0, 1.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
9
third_party/fastgltf/tests/gltf_path.hpp
vendored
Normal file
9
third_party/fastgltf/tests/gltf_path.hpp
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <filesystem>
|
||||
|
||||
// We need to use the __FILE__ macro so that we have access to test glTF files in this
|
||||
// directory. As Clang does not yet fully support std::source_location, we cannot use that.
|
||||
inline auto path = std::filesystem::path { __FILE__ }.parent_path() / "gltf";
|
||||
inline auto sampleModels = std::filesystem::path { __FILE__ }.parent_path() / "gltf" / "glTF-Sample-Models";
|
||||
inline auto intelSponza = std::filesystem::path { __FILE__ }.parent_path() / "gltf" / "intel_sponza";
|
||||
inline auto bistroPath = std::filesystem::path {};
|
||||
153
third_party/fastgltf/tests/uri_tests.cpp
vendored
Normal file
153
third_party/fastgltf/tests/uri_tests.cpp
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <fastgltf/types.hpp>
|
||||
#include <fastgltf/parser.hpp>
|
||||
|
||||
#include "gltf_path.hpp"
|
||||
|
||||
TEST_CASE("Test basic URIs", "[uri-tests]") {
|
||||
const fastgltf::URI uri1(std::string_view(""));
|
||||
REQUIRE(uri1.scheme().empty());
|
||||
REQUIRE(uri1.path().empty());
|
||||
|
||||
std::string_view path = "path/somewhere.xyz";
|
||||
SECTION("Basic local path") {
|
||||
const fastgltf::URI uri2(path);
|
||||
REQUIRE(uri2.scheme().empty());
|
||||
REQUIRE(uri2.path() == path);
|
||||
REQUIRE(uri2.isLocalPath());
|
||||
REQUIRE(uri2.fspath() == path);
|
||||
}
|
||||
|
||||
std::string_view abspath = "/path/somewhere.xyz";
|
||||
SECTION("File scheme path") {
|
||||
const std::string_view filePath = "file:/path/somewhere.xyz";
|
||||
const fastgltf::URI uri3(filePath);
|
||||
REQUIRE(uri3.scheme() == "file");
|
||||
REQUIRE(uri3.isLocalPath());
|
||||
REQUIRE(uri3.path() == abspath);
|
||||
}
|
||||
|
||||
SECTION("File scheme localhost path") {
|
||||
const std::string_view localhostPath = "file://localhost/path/somewhere.xyz";
|
||||
const fastgltf::URI uri4(localhostPath);
|
||||
REQUIRE(uri4.scheme() == "file");
|
||||
REQUIRE(uri4.path() == abspath);
|
||||
REQUIRE(!uri4.isLocalPath());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test generic URIs", "[uri-tests]") {
|
||||
// These are a bunch of example URIs from https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Example_URIs
|
||||
const fastgltf::URI uri(std::string_view("https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top"));
|
||||
REQUIRE(uri.scheme() == "https");
|
||||
REQUIRE(uri.userinfo() == "john.doe");
|
||||
REQUIRE(uri.host() == "www.example.com");
|
||||
REQUIRE(uri.port() == "123");
|
||||
REQUIRE(uri.path() == "/forum/questions/");
|
||||
REQUIRE(uri.query() == "tag=networking&order=newest");
|
||||
REQUIRE(uri.fragment() == "top");
|
||||
|
||||
const fastgltf::URI uri1(std::string_view("ldap://[2001:db8::7]/c=GB?objectClass?one"));
|
||||
REQUIRE(uri1.scheme() == "ldap");
|
||||
REQUIRE(uri1.host() == "2001:db8::7");
|
||||
REQUIRE(uri1.path() == "/c=GB");
|
||||
REQUIRE(uri1.query() == "objectClass?one");
|
||||
|
||||
const fastgltf::URI uri2(std::string_view("mailto:John.Doe@example.com"));
|
||||
REQUIRE(uri2.scheme() == "mailto");
|
||||
REQUIRE(uri2.path() == "John.Doe@example.com");
|
||||
|
||||
const fastgltf::URI uri3(std::string_view("telnet://192.0.2.16:80/"));
|
||||
REQUIRE(uri3.scheme() == "telnet");
|
||||
REQUIRE(uri3.host() == "192.0.2.16");
|
||||
REQUIRE(uri3.port() == "80");
|
||||
REQUIRE(uri3.path() == "/");
|
||||
}
|
||||
|
||||
TEST_CASE("Test percent decoding", "[uri-tests]") {
|
||||
std::string test = "%22 %25";
|
||||
fastgltf::URI::decodePercents(test);
|
||||
REQUIRE(test == "\" %");
|
||||
}
|
||||
|
||||
TEST_CASE("Test data URI parsing", "[uri-tests]") {
|
||||
// This example base64 data is from an example on https://en.wikipedia.org/wiki/Data_URI_scheme.
|
||||
const std::string_view data = "data:image/png;base64,iVBORw0KGgoAAA"
|
||||
"ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4"
|
||||
"//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU"
|
||||
"5ErkJggg==";
|
||||
const fastgltf::URI uri(data);
|
||||
REQUIRE(uri.scheme() == "data");
|
||||
REQUIRE(uri.path() == data.substr(5));
|
||||
}
|
||||
|
||||
TEST_CASE("Validate URI copying/moving", "[uri-tests]") {
|
||||
const std::string_view data = "test.bin";
|
||||
SECTION("Copy semantics") {
|
||||
fastgltf::URI uri(data);
|
||||
REQUIRE(uri.path() == data);
|
||||
fastgltf::URI uri2(uri);
|
||||
REQUIRE(uri2.string().data() != uri.string().data());
|
||||
REQUIRE(uri2.path() == data);
|
||||
}
|
||||
|
||||
SECTION("Move semantics") {
|
||||
fastgltf::URI uri;
|
||||
{
|
||||
fastgltf::URI uri2(data);
|
||||
uri = std::move(uri2);
|
||||
REQUIRE(uri2.string().empty());
|
||||
}
|
||||
// Test that the values were copied over and that the string views are still valid.
|
||||
REQUIRE(uri.string() == data);
|
||||
REQUIRE(uri.path() == uri.string());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Validate escaped/percent-encoded URI", "[uri-tests]") {
|
||||
const std::string_view gltfString = R"({"images": [{"uri": "grande_sph\u00E8re.png"}]})";
|
||||
fastgltf::GltfDataBuffer dataBuffer;
|
||||
dataBuffer.copyBytes((uint8_t*) gltfString.data(), gltfString.size());
|
||||
|
||||
fastgltf::Parser parser;
|
||||
auto asset = parser.loadGLTF(&dataBuffer, "", fastgltf::Options::DontRequireValidAssetMember);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
|
||||
auto escaped = std::get<fastgltf::sources::URI>(asset->images.front().data);
|
||||
|
||||
// This only tests wether the default ctor of fastgltf::URI can handle percent-encoding correctly.
|
||||
const fastgltf::URI original(std::string_view("grande_sphère.png"));
|
||||
const fastgltf::URI encoded(std::string_view("grande_sph%C3%A8re.png"));
|
||||
REQUIRE(original.string() == escaped.uri.string());
|
||||
REQUIRE(original.string() == encoded.string());
|
||||
}
|
||||
|
||||
TEST_CASE("Test percent-encoded URIs in glTF", "[uri-tests]") {
|
||||
auto boxWithSpaces = sampleModels / "2.0" / "Box With Spaces" / "glTF";
|
||||
fastgltf::GltfDataBuffer jsonData;
|
||||
REQUIRE(jsonData.loadFromFile(boxWithSpaces / "Box With Spaces.gltf"));
|
||||
|
||||
fastgltf::Parser parser;
|
||||
auto asset = parser.loadGLTF(&jsonData, boxWithSpaces);
|
||||
REQUIRE(asset.error() == fastgltf::Error::None);
|
||||
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
|
||||
|
||||
REQUIRE(asset->images.size() == 3);
|
||||
|
||||
auto* image0 = std::get_if<fastgltf::sources::URI>(&asset->images[0].data);
|
||||
REQUIRE(image0 != nullptr);
|
||||
REQUIRE(image0->uri.path() == "Normal Map.png");
|
||||
|
||||
auto* image1 = std::get_if<fastgltf::sources::URI>(&asset->images[1].data);
|
||||
REQUIRE(image1 != nullptr);
|
||||
REQUIRE(image1->uri.path() == "glTF Logo With Spaces.png");
|
||||
|
||||
auto* image2 = std::get_if<fastgltf::sources::URI>(&asset->images[2].data);
|
||||
REQUIRE(image2 != nullptr);
|
||||
REQUIRE(image2->uri.path() == "Roughness Metallic.png");
|
||||
|
||||
auto* buffer0 = std::get_if<fastgltf::sources::URI>(&asset->buffers[0].data);
|
||||
REQUIRE(buffer0 != nullptr);
|
||||
REQUIRE(buffer0->uri.path() == "Box With Spaces.bin");
|
||||
}
|
||||
115
third_party/fastgltf/tests/vector_tests.cpp
vendored
Normal file
115
third_party/fastgltf/tests/vector_tests.cpp
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <fastgltf/types.hpp>
|
||||
|
||||
TEST_CASE("Verify clz", "[vector-tests]") {
|
||||
REQUIRE(fastgltf::clz<std::uint8_t>(0b00000001) == 7);
|
||||
REQUIRE(fastgltf::clz<std::uint8_t>(0b00000010) == 6);
|
||||
REQUIRE(fastgltf::clz<std::uint8_t>(0b00000100) == 5);
|
||||
REQUIRE(fastgltf::clz<std::uint8_t>(0b00001000) == 4);
|
||||
REQUIRE(fastgltf::clz<std::uint8_t>(0b00010000) == 3);
|
||||
REQUIRE(fastgltf::clz<std::uint8_t>(0b00100000) == 2);
|
||||
REQUIRE(fastgltf::clz<std::uint8_t>(0b01000000) == 1);
|
||||
REQUIRE(fastgltf::clz<std::uint8_t>(0b10000000) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Test resize/reserve", "[vector-tests]") {
|
||||
fastgltf::SmallVector<uint32_t, 4> vec = {1, 2, 3};
|
||||
REQUIRE(vec[0] == 1);
|
||||
REQUIRE(vec[1] == 2);
|
||||
REQUIRE(vec[2] == 3);
|
||||
|
||||
vec.resize(5);
|
||||
REQUIRE(vec.size() == 5);
|
||||
REQUIRE(vec[3] == 0);
|
||||
REQUIRE(vec[4] == 0);
|
||||
|
||||
vec.resize(2);
|
||||
REQUIRE(vec.size() == 2);
|
||||
REQUIRE(vec[0] == 1);
|
||||
REQUIRE(vec[1] == 2);
|
||||
|
||||
vec.resize(6, 4);
|
||||
REQUIRE(vec.size() == 6);
|
||||
for (std::size_t i = 2; i < vec.size(); ++i) {
|
||||
REQUIRE(vec[i] == 4);
|
||||
}
|
||||
|
||||
vec.reserve(8);
|
||||
REQUIRE(vec.size() == 6);
|
||||
REQUIRE(vec.capacity() == 8);
|
||||
|
||||
vec.shrink_to_fit();
|
||||
REQUIRE(vec.capacity() == 6);
|
||||
}
|
||||
|
||||
TEST_CASE("Test constructors", "[vector-tests]") {
|
||||
fastgltf::SmallVector<uint32_t, 4> vec = {0, 1, 2, 3};
|
||||
for (std::size_t i = 0; i < vec.size(); ++i) {
|
||||
REQUIRE(vec[i] == i);
|
||||
}
|
||||
|
||||
fastgltf::SmallVector<uint32_t, 4> vec2(vec);
|
||||
for (std::size_t i = 0; i < vec2.size(); ++i) {
|
||||
REQUIRE(vec2[i] == i);
|
||||
}
|
||||
|
||||
fastgltf::SmallVector<uint32_t, 4> vec3 = std::move(vec2);
|
||||
REQUIRE(vec2.empty());
|
||||
vec3.resize(6);
|
||||
for (std::size_t i = 0; i < 4; ++i) {
|
||||
REQUIRE(vec3[i] == i);
|
||||
}
|
||||
REQUIRE(vec3[4] == 0);
|
||||
REQUIRE(vec3[5] == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Nested SmallVector", "[vector-tests]") {
|
||||
fastgltf::SmallVector<fastgltf::SmallVector<uint32_t, 2>, 4> vectors(6, {4}); // This should heap allocate straight away.
|
||||
REQUIRE(vectors.size() == 6);
|
||||
for (auto& vector : vectors) {
|
||||
REQUIRE(vector.size() == 1);
|
||||
REQUIRE(vector.front() == 4);
|
||||
vector.reserve(6);
|
||||
}
|
||||
}
|
||||
|
||||
struct RefCountedObject {
|
||||
static inline std::size_t aliveObjects = 0;
|
||||
|
||||
RefCountedObject() {
|
||||
++aliveObjects;
|
||||
}
|
||||
|
||||
RefCountedObject(const RefCountedObject& other) {
|
||||
++aliveObjects;
|
||||
}
|
||||
|
||||
RefCountedObject(RefCountedObject&& other) = delete;
|
||||
|
||||
~RefCountedObject() {
|
||||
--aliveObjects;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("Test shrinking vectors", "[vector-tests]") {
|
||||
fastgltf::SmallVector<RefCountedObject, 4> objects;
|
||||
for (std::size_t i = 0; i < 4; ++i) {
|
||||
objects.emplace_back();
|
||||
}
|
||||
REQUIRE(RefCountedObject::aliveObjects == 4);
|
||||
objects.emplace_back();
|
||||
REQUIRE(RefCountedObject::aliveObjects == 5);
|
||||
objects.resize(4);
|
||||
REQUIRE(RefCountedObject::aliveObjects == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("Test vectors with polymorphic allocators", "[vector-tests]") {
|
||||
fastgltf::pmr::SmallVector<std::uint32_t, 4> ints;
|
||||
ints.assign(10, 5);
|
||||
REQUIRE(ints.size() == 10);
|
||||
REQUIRE(ints.data() != nullptr);
|
||||
for (auto& i : ints) {
|
||||
REQUIRE(i == 5);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user