#include "NvBlastAsset.h" #include "BlastBaseTest.h" #include "NvBlastTkFramework.h" #include #if defined(_MSC_VER) && _MSC_VER < 1900 || defined(_XBOX_ONE) || defined(PS4) || PX_LINUX #define ENABLE_SERIALIZATION_TESTS 0 #else #define ENABLE_SERIALIZATION_TESTS 1 #endif #pragma warning( push ) #pragma warning( disable : 4267 ) // NOTE: Instead of excluding serialization and the tests when on VC12, should break the tests out into a separate C++ file. #if ENABLE_SERIALIZATION_TESTS #include "NvBlastExtSerializationInterface.h" #include "generated/NvBlastExtSerialization.capn.h" #endif #pragma warning( pop ) #include #include #ifdef WIN32 #include #endif template class AssetTest : public BlastBaseTest { public: AssetTest() { Nv::Blast::TkFrameworkDesc desc; desc.allocatorCallback = this; desc.errorCallback = this; NvBlastTkFrameworkCreate(desc); } ~AssetTest() { NvBlastTkFrameworkGet()->release(); } static void messageLog(int type, const char* msg, const char* file, int line) { BlastBaseTest::messageLog(type, msg, file, line); } static void* alloc(size_t size) { return BlastBaseTest::alloc(size); } static void free(void* mem) { BlastBaseTest::free(mem); } void testSubtreeLeafChunkCounts(const Nv::Blast::Asset& a) { const NvBlastChunk* chunks = a.getChunks(); const uint32_t* subtreeLeafChunkCounts = a.getSubtreeLeafChunkCounts(); uint32_t totalLeafChunkCount = 0; for (uint32_t chunkIndex = 0; chunkIndex < a.m_chunkCount; ++chunkIndex) { const NvBlastChunk& chunk = chunks[chunkIndex]; if (Nv::Blast::isInvalidIndex(chunk.parentChunkIndex)) { totalLeafChunkCount += subtreeLeafChunkCounts[chunkIndex]; } const bool isLeafChunk = chunk.firstChildIndex >= chunk.childIndexStop; uint32_t subtreeLeafChunkCount = isLeafChunk ? 1 : 0; for (uint32_t childIndex = chunk.firstChildIndex; childIndex < chunk.childIndexStop; ++childIndex) { subtreeLeafChunkCount += subtreeLeafChunkCounts[childIndex]; } EXPECT_EQ(subtreeLeafChunkCount, subtreeLeafChunkCounts[chunkIndex]); } EXPECT_EQ(totalLeafChunkCount, a.m_leafChunkCount); } void testChunkToNodeMap(const Nv::Blast::Asset& a) { for (uint32_t chunkIndex = 0; chunkIndex < a.m_chunkCount; ++chunkIndex) { const uint32_t nodeIndex = a.getChunkToGraphNodeMap()[chunkIndex]; if (!Nv::Blast::isInvalidIndex(nodeIndex)) { EXPECT_LT(nodeIndex, a.m_graph.m_nodeCount); EXPECT_EQ(chunkIndex, a.m_graph.getChunkIndices()[nodeIndex]); } else { const uint32_t* chunkIndexStop = a.m_graph.getChunkIndices() + a.m_graph.m_nodeCount; const uint32_t* it = std::find(a.m_graph.getChunkIndices(), chunkIndexStop, chunkIndex); EXPECT_EQ(chunkIndexStop, it); } } } NvBlastAsset* buildAsset(const ExpectedAssetValues& expected, const NvBlastAssetDesc* desc) { std::vector scratch; scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(desc, messageLog)); void* mem = alloc(NvBlastGetAssetMemorySize(desc, messageLog)); NvBlastAsset* asset = NvBlastCreateAsset(mem, desc, &scratch[0], messageLog); EXPECT_TRUE(asset != nullptr); if (asset == nullptr) { free(mem); return nullptr; } Nv::Blast::Asset& a = *(Nv::Blast::Asset*)asset; EXPECT_EQ(expected.totalChunkCount, a.m_chunkCount); EXPECT_EQ(expected.graphNodeCount, a.m_graph.m_nodeCount); EXPECT_EQ(expected.bondCount, a.m_graph.getAdjacencyPartition()[a.m_graph.m_nodeCount] / 2); EXPECT_EQ(expected.leafChunkCount, a.m_leafChunkCount); EXPECT_EQ(expected.subsupportChunkCount, a.m_chunkCount - a.m_firstSubsupportChunkIndex); testSubtreeLeafChunkCounts(a); testChunkToNodeMap(a); return asset; } void checkAssetsExpected(Nv::Blast::Asset& asset, const ExpectedAssetValues& expected) { EXPECT_EQ(expected.totalChunkCount, asset.m_chunkCount); EXPECT_EQ(expected.graphNodeCount, asset.m_graph.m_nodeCount); EXPECT_EQ(expected.bondCount, asset.m_graph.getAdjacencyPartition()[asset.m_graph.m_nodeCount] / 2); EXPECT_EQ(expected.leafChunkCount, asset.m_leafChunkCount); EXPECT_EQ(expected.subsupportChunkCount, asset.m_chunkCount - asset.m_firstSubsupportChunkIndex); testSubtreeLeafChunkCounts(asset); testChunkToNodeMap(asset); } void buildAssetShufflingDescriptors(const NvBlastAssetDesc* desc, const ExpectedAssetValues& expected, uint32_t shuffleCount, bool useTk) { NvBlastAssetDesc shuffledDesc = *desc; std::vector chunkDescs(desc->chunkDescs, desc->chunkDescs + desc->chunkCount); shuffledDesc.chunkDescs = &chunkDescs[0]; std::vector bondDescs(desc->bondDescs, desc->bondDescs + desc->bondCount); shuffledDesc.bondDescs = &bondDescs[0]; if (!useTk) { std::vector scratch(desc->chunkCount); NvBlastEnsureAssetExactSupportCoverage(chunkDescs.data(), desc->chunkCount, scratch.data(), messageLog); } else { NvBlastTkFrameworkGet()->ensureAssetExactSupportCoverage(chunkDescs.data(), desc->chunkCount); } for (uint32_t i = 0; i < shuffleCount; ++i) { shuffleAndFixChunkDescs(&chunkDescs[0], desc->chunkCount, &bondDescs[0], desc->bondCount, useTk); NvBlastAsset* asset = buildAsset(expected, &shuffledDesc); EXPECT_TRUE(asset != nullptr); if (asset) { free(asset); } } } void shuffleAndFixChunkDescs(NvBlastChunkDesc* chunkDescs, uint32_t chunkDescCount, NvBlastBondDesc* bondDescs, uint32_t bondDescCount, bool useTk) { // Create reorder array and fill with identity map std::vector shuffledOrder(chunkDescCount); for (uint32_t i = 0; i < chunkDescCount; ++i) { shuffledOrder[i] = i; } // An array into which to copy the reordered descs std::vector shuffledChunkDescs(chunkDescCount); std::vector scratch; const uint32_t trials = 30; uint32_t attempt = 0; while(1) { // Shuffle the reorder array std::random_shuffle(shuffledOrder.begin(), shuffledOrder.end()); // Save initial bonds std::vector savedBondDescs(bondDescs, bondDescs + bondDescCount); // Shuffle chunks and bonds NvBlastApplyAssetDescChunkReorderMap(shuffledChunkDescs.data(), chunkDescs, chunkDescCount, bondDescs, bondDescCount, shuffledOrder.data(), nullptr); // Check the results for (uint32_t i = 0; i < chunkDescCount; ++i) { EXPECT_EQ(chunkDescs[i].userData, shuffledChunkDescs[shuffledOrder[i]].userData); EXPECT_TRUE(chunkDescs[i].parentChunkIndex > chunkDescCount || shuffledChunkDescs[shuffledOrder[i]].parentChunkIndex == shuffledOrder[chunkDescs[i].parentChunkIndex]); } for (uint32_t i = 0; i < bondDescCount; ++i) { for (uint32_t k = 0; k < 2; ++k) { EXPECT_EQ(shuffledOrder[savedBondDescs[i].chunkIndices[k]], bondDescs[i].chunkIndices[k]); } } // Try creating asset, usually it should fail (otherwise make another attempt) NvBlastAssetDesc desc = { chunkDescCount, shuffledChunkDescs.data(), bondDescCount, bondDescs }; scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(&desc, nullptr)); void* mem = alloc(NvBlastGetAssetMemorySize(&desc, nullptr)); NvBlastAsset* asset = NvBlastCreateAsset(mem, &desc, scratch.data(), nullptr); if (asset == nullptr) { free(mem); break; } else { free(asset); memcpy(bondDescs, savedBondDescs.data(), sizeof(NvBlastBondDesc) * bondDescCount); attempt++; if (attempt >= trials) { GTEST_NONFATAL_FAILURE_("Shuffled chunk descs should fail asset creation (most of the time)."); break; } } } // Now we want to fix that order if (!useTk) { std::vector chunkReorderMap(chunkDescCount); std::vector scratch2(2 * chunkDescCount * sizeof(uint32_t)); const bool isIdentity = NvBlastBuildAssetDescChunkReorderMap(chunkReorderMap.data(), shuffledChunkDescs.data(), chunkDescCount, scratch2.data(), messageLog); EXPECT_FALSE(isIdentity); NvBlastApplyAssetDescChunkReorderMap(chunkDescs, shuffledChunkDescs.data(), chunkDescCount, bondDescs, bondDescCount, chunkReorderMap.data(), messageLog); } else { memcpy(chunkDescs, shuffledChunkDescs.data(), chunkDescCount * sizeof(NvBlastChunkDesc)); const bool isIdentity = NvBlastTkFrameworkGet()->reorderAssetDescChunks(chunkDescs, chunkDescCount, bondDescs, bondDescCount); EXPECT_FALSE(isIdentity); } } }; typedef AssetTest AssetTestAllowWarningsSilently; typedef AssetTest AssetTestAllowWarnings; typedef AssetTest AssetTestStrict; TEST_F(AssetTestStrict, BuildAssets) { const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); std::vector assets(assetDescCount); // Build for (uint32_t i = 0; i < assetDescCount; ++i) { assets[i] = buildAsset(g_assetExpectedValues[i], &g_assetDescs[i]); } // Destroy for (uint32_t i = 0; i < assetDescCount; ++i) { if (assets[i]) { free(assets[i]); } } } #if ENABLE_SERIALIZATION_TESTS // Restricting this test to windows since we don't have a handy cross platform temp file. #if defined(WIN32) || defined(WIN64) TEST_F(AssetTestStrict, SerializeAssetIntoFile) { const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); std::vector assets(assetDescCount); // Build for (uint32_t i = 0; i < assetDescCount; ++i) { assets[i] = reinterpret_cast(buildAsset(g_assetExpectedValues[i], &g_assetDescs[i])); } char tempPath[1024]; GetTempPathA(1024, tempPath); char tempFilename[1024]; GetTempFileNameA(tempPath, nullptr, 0, tempFilename); std::ofstream myFile(tempFilename, std::ios::out | std::ios::binary); EXPECT_TRUE(serializeAssetIntoStream(assets[0], myFile)); myFile.flush(); // Load it back std::ifstream myFileReader(tempFilename, std::ios::binary); Nv::Blast::Asset* rtAsset = reinterpret_cast(deserializeAssetFromStream(myFileReader)); EXPECT_TRUE(rtAsset != nullptr); checkAssetsExpected(*rtAsset, g_assetExpectedValues[0]); for (uint32_t i = 0; i < assetDescCount; ++i) { free(assets[i]); } free(rtAsset); } #endif TEST_F(AssetTestStrict, SerializeAssetsNewBuffer) { const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); std::vector assets(assetDescCount); // Build for (uint32_t i = 0; i < assetDescCount; ++i) { assets[i] = reinterpret_cast(buildAsset(g_assetExpectedValues[i], &g_assetDescs[i])); } // Serialize them for (Nv::Blast::Asset* asset : assets) { uint32_t size = 0; unsigned char* buffer = nullptr; // auto result = Nv::Blast::BlastSerialization::serializeIntoNewBuffer(asset, &buffer, size); EXPECT_TRUE(serializeAssetIntoNewBuffer(asset, &buffer, size)); free(static_cast(buffer)); } // Destroy for (uint32_t i = 0; i < assetDescCount; ++i) { if (assets[i]) { free(assets[i]); } } } TEST_F(AssetTestStrict, SerializeAssetsExistingBuffer) { const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); std::vector assets(assetDescCount); // Build for (uint32_t i = 0; i < assetDescCount; ++i) { assets[i] = reinterpret_cast(buildAsset(g_assetExpectedValues[i], &g_assetDescs[i])); } // How big does our buffer need to be? Guess. uint32_t maxSize = 1024 * 1024; void* buffer = alloc(maxSize); // Serialize them for (Nv::Blast::Asset* asset : assets) { uint32_t usedSize = 0; EXPECT_TRUE(serializeAssetIntoExistingBuffer(asset, (unsigned char *)buffer, maxSize, usedSize)); } free(static_cast(buffer)); // Destroy for (uint32_t i = 0; i < assetDescCount; ++i) { if (assets[i]) { free(assets[i]); } } } TEST_F(AssetTestStrict, SerializeAssetsRoundTrip) { const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); std::vector assets(assetDescCount); // Build for (uint32_t i = 0; i < assetDescCount; ++i) { assets[i] = reinterpret_cast(buildAsset(g_assetExpectedValues[i], &g_assetDescs[i])); } // Serialize them for (uint32_t i = 0; i < assetDescCount; ++i) { Nv::Blast::Asset* asset = assets[i]; uint32_t size = 0; unsigned char* buffer = nullptr; EXPECT_TRUE(serializeAssetIntoNewBuffer(asset, &buffer, size)); // No release needed for this asset since it's never put into that system Nv::Blast::Asset* rtAsset = reinterpret_cast(deserializeAsset(buffer, size)); //TODO: Compare assets checkAssetsExpected(*rtAsset, g_assetExpectedValues[i]); free(static_cast(buffer)); free(static_cast(rtAsset)); } // Destroy for (uint32_t i = 0; i < assetDescCount; ++i) { if (assets[i]) { free(assets[i]); } } } #endif #if 0 TEST_F(AssetTestStrict, AssociateAsset) { const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); for (uint32_t i = 0; i < assetDescCount; ++i) { // Build NvBlastAsset asset; if (!buildAsset(&asset, g_assetExpectedValues[i], &g_assetDescs[i])) { continue; } // Copy const char* data = (const char*)NvBlastAssetGetData(&asset, messageLog); const size_t dataSize = NvBlastAssetDataGetSize(data, messageLog); NvBlastAsset duplicate; char* duplicateData = (char*)alloc(dataSize); memcpy(duplicateData, data, dataSize); const bool assetAssociateResult = NvBlastAssetAssociateData(&duplicate, duplicateData, messageLog); EXPECT_TRUE(assetAssociateResult); // Destroy NvBlastAssetFreeData(&asset, free, messageLog); NvBlastAssetFreeData(&duplicate, free, messageLog); } } #endif TEST_F(AssetTestAllowWarnings, BuildAssetsMissingCoverage) { const uint32_t assetDescCount = sizeof(g_assetDescsMissingCoverage) / sizeof(g_assetDescsMissingCoverage[0]); std::vector assets(assetDescCount); // Build for (uint32_t i = 0; i < assetDescCount; ++i) { const NvBlastAssetDesc* desc = &g_assetDescsMissingCoverage[i]; NvBlastAssetDesc fixedDesc = *desc; std::vector chunkDescs(desc->chunkDescs, desc->chunkDescs + desc->chunkCount); std::vector bondDescs(desc->bondDescs, desc->bondDescs + desc->bondCount); std::vector chunkReorderMap(desc->chunkCount); std::vector scratch(desc->chunkCount * sizeof(NvBlastChunkDesc)); const bool changedCoverage = !NvBlastEnsureAssetExactSupportCoverage(chunkDescs.data(), fixedDesc.chunkCount, scratch.data(), messageLog); EXPECT_TRUE(changedCoverage); NvBlastReorderAssetDescChunks(chunkDescs.data(), fixedDesc.chunkCount, bondDescs.data(), fixedDesc.bondCount, chunkReorderMap.data(), scratch.data(), messageLog); fixedDesc.chunkDescs = chunkDescs.data(); fixedDesc.bondDescs = bondDescs.data(); assets[i] = buildAsset(g_assetsFromMissingCoverageExpectedValues[i], &fixedDesc); } // Destroy for (uint32_t i = 0; i < assetDescCount; ++i) { if (assets[i]) { free(assets[i]); } } } TEST_F(AssetTestAllowWarningsSilently, BuildAssetsShufflingChunkDescriptors) { for (uint32_t i = 0; i < sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); ++i) { buildAssetShufflingDescriptors(&g_assetDescs[i], g_assetExpectedValues[i], 10, false); } for (uint32_t i = 0; i < sizeof(g_assetDescsMissingCoverage) / sizeof(g_assetDescsMissingCoverage[0]); ++i) { buildAssetShufflingDescriptors(&g_assetDescsMissingCoverage[i], g_assetsFromMissingCoverageExpectedValues[i], 10, false); } } TEST_F(AssetTestAllowWarningsSilently, BuildAssetsShufflingChunkDescriptorsUsingTk) { for (uint32_t i = 0; i < sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); ++i) { buildAssetShufflingDescriptors(&g_assetDescs[i], g_assetExpectedValues[i], 10, true); } for (uint32_t i = 0; i < sizeof(g_assetDescsMissingCoverage) / sizeof(g_assetDescsMissingCoverage[0]); ++i) { buildAssetShufflingDescriptors(&g_assetDescsMissingCoverage[i], g_assetsFromMissingCoverageExpectedValues[i], 10, true); } }