// This code contains NVIDIA Confidential Information and is disclosed to you // under a form of NVIDIA software license agreement provided separately to you. // // Notice // NVIDIA Corporation and its licensors retain all intellectual property and // proprietary rights in and to this software and related documentation and // any modifications thereto. Any use, reproduction, disclosure, or // distribution of this software and related documentation without an express // license agreement from NVIDIA Corporation is strictly prohibited. // // ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES // NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO // THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, // MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. // // Information and code furnished is believed to be accurate and reliable. // However, NVIDIA Corporation assumes no responsibility for the consequences of use of such // information or for any infringement of patents or other rights of third parties that may // result from its use. No license is granted by implication or otherwise under any patent // or patent rights of NVIDIA Corporation. Details are subject to change without notice. // This code supersedes and replaces all information previously supplied. // NVIDIA Corporation products are not authorized for use as critical // components in life support devices or systems without express written approval of // NVIDIA Corporation. // // Copyright (c) 2016-2017 NVIDIA Corporation. All rights reserved. #include "NvBlastAsset.h" #include "NvBlastMath.h" #include "BlastBaseTest.h" #include "NvBlastTkFramework.h" #include // all supported platform now provide serialization // keep the define for future platforms that won't #define ENABLE_SERIALIZATION_TESTS 1 #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 "NvBlastExtSerialization.h" #include "NvBlastExtLlSerialization.h" #include "NvBlastExtSerializationInternal.h" #endif #pragma warning( pop ) #include #include #ifdef WIN32 #include #endif template class AssetTest : public BlastBaseTest { public: AssetTest() { NvBlastTkFrameworkCreate(); } ~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::alignedZeroedAlloc(size); } static void free(void* mem) { BlastBaseTest::alignedFree(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.data(), 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); } // expects that the bond normal points from the lower indexed chunk to higher index chunk // uses chunk.centroid // convention, requirement from findClosestNode void checkNormalDir(NvBlastChunkDesc* chunkDescs, size_t chunkDescCount, NvBlastBondDesc* bondDescs, size_t bondDescCount) { for (size_t bondIndex = 0; bondIndex < bondDescCount; ++bondIndex) { NvBlastBondDesc& bond = bondDescs[bondIndex]; uint32_t chunkIndex0 = bond.chunkIndices[0]; uint32_t chunkIndex1 = bond.chunkIndices[1]; bool swap = chunkIndex0 > chunkIndex1; uint32_t testIndex0 = swap ? chunkIndex1 : chunkIndex0; uint32_t testIndex1 = swap ? chunkIndex0 : chunkIndex1; EXPECT_TRUE(testIndex0 < testIndex1); // no convention for world chunks if (!Nv::Blast::isInvalidIndex(testIndex0) && !Nv::Blast::isInvalidIndex(testIndex1)) { NvBlastChunkDesc& chunk0 = chunkDescs[testIndex0]; NvBlastChunkDesc& chunk1 = chunkDescs[testIndex1]; float dir[3]; Nv::Blast::VecMath::sub(chunk1.centroid, chunk0.centroid, dir); bool meetsConvention = Nv::Blast::VecMath::dot(bond.bond.normal, dir) > 0; EXPECT_TRUE(meetsConvention); if (!meetsConvention) { printf("bond %zd chunks(%d,%d): %.2f %.2f %.2f %.2f %.2f %.2f %d\n", bondIndex, chunkIndex0, chunkIndex1, bond.bond.normal[0], bond.bond.normal[1], bond.bond.normal[2], dir[0], dir[1], dir[2], Nv::Blast::VecMath::dot(bond.bond.normal, dir) > 0); } } } } // expects that the bond normal points from the lower indexed node to higher index node // uses chunk.centroid // convention, requirement from findClosestNode void checkNormalDir(const NvBlastSupportGraph graph, const NvBlastChunk* assetChunks, const NvBlastBond* assetBonds) { for (uint32_t nodeIndex = 0; nodeIndex < graph.nodeCount; nodeIndex++) { uint32_t adjStart = graph.adjacencyPartition[nodeIndex]; uint32_t adjStop = graph.adjacencyPartition[nodeIndex + 1]; for (uint32_t adj = adjStart; adj < adjStop; ++adj) { uint32_t adjNodeIndex = graph.adjacentNodeIndices[adj]; bool swap = nodeIndex > adjNodeIndex; uint32_t testIndex0 = swap ? adjNodeIndex : nodeIndex; uint32_t testIndex1 = swap ? nodeIndex : adjNodeIndex; // no convention for world chunks if (!Nv::Blast::isInvalidIndex(graph.chunkIndices[testIndex0]) && !Nv::Blast::isInvalidIndex(graph.chunkIndices[testIndex1])) { const NvBlastChunk& chunk0 = assetChunks[graph.chunkIndices[testIndex0]]; const NvBlastChunk& chunk1 = assetChunks[graph.chunkIndices[testIndex1]]; uint32_t bondIndex = graph.adjacentBondIndices[adj]; const NvBlastBond& bond = assetBonds[bondIndex]; float dir[3]; Nv::Blast::VecMath::sub(chunk1.centroid, chunk0.centroid, dir); bool meetsConvention = Nv::Blast::VecMath::dot(bond.normal, dir) > 0; EXPECT_TRUE(meetsConvention); if (!meetsConvention) { printf("bond %d nodes(%d,%d): %.2f %.2f %.2f %.2f %.2f %.2f %d\n", bondIndex, nodeIndex, adjNodeIndex, bond.normal[0], bond.normal[1], bond.normal[2], dir[0], dir[1], dir[2], Nv::Blast::VecMath::dot(bond.normal, dir) > 0); } } } } } void checkNormalDir(const NvBlastAsset* asset) { const NvBlastChunk* assetChunks = NvBlastAssetGetChunks(asset, nullptr); const NvBlastBond* assetBonds = NvBlastAssetGetBonds(asset, nullptr); const NvBlastSupportGraph graph = NvBlastAssetGetSupportGraph(asset, nullptr); checkNormalDir(graph, assetChunks, assetBonds); } 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.data(); std::vector bondDescs(desc->bondDescs, desc->bondDescs + desc->bondCount); shuffledDesc.bondDescs = bondDescs.data(); 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) { checkNormalDir(chunkDescs.data(), chunkDescs.size(), bondDescs.data(), bondDescs.size()); shuffleAndFixChunkDescs(chunkDescs.data(), desc->chunkCount, bondDescs.data(), desc->bondCount, useTk); checkNormalDir(chunkDescs.data(), chunkDescs.size(), bondDescs.data(), bondDescs.size()); NvBlastAsset* asset = buildAsset(expected, &shuffledDesc); EXPECT_TRUE(asset != nullptr); checkNormalDir(asset); 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(), true, nullptr); // All the normals are pointing in the expected direction (they have been swapped) checkNormalDir(shuffledChunkDescs.data(), chunkDescCount, bondDescs, bondDescCount); checkNormalDir(chunkDescs, chunkDescCount, savedBondDescs.data(), bondDescCount); // 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) { if (!Nv::Blast::isInvalidIndex(savedBondDescs[i].chunkIndices[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(), true, messageLog); } else { memcpy(chunkDescs, shuffledChunkDescs.data(), chunkDescCount * sizeof(NvBlastChunkDesc)); const bool isIdentity = NvBlastTkFrameworkGet()->reorderAssetDescChunks(chunkDescs, chunkDescCount, bondDescs, bondDescCount, nullptr, true); 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 TEST_F(AssetTestStrict, SerializeAssets) { Nv::Blast::ExtSerialization* ser = NvBlastExtSerializationCreate(); EXPECT_TRUE(ser != nullptr); 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) { void* buffer; const uint64_t size = NvBlastExtSerializationSerializeAssetIntoBuffer(buffer, *ser, asset); EXPECT_TRUE(size != 0); uint32_t objectTypeID; uint32_t encodingID; uint64_t dataSize = 0; EXPECT_TRUE(ser->peekHeader(&objectTypeID, &encodingID, &dataSize, buffer, size)); EXPECT_EQ(objectTypeID, Nv::Blast::LlObjectTypeID::Asset); EXPECT_EQ(encodingID, ser->getSerializationEncoding()); EXPECT_EQ(dataSize + Nv::Blast::ExtSerializationInternal::HeaderSize, size); } // Destroy for (uint32_t i = 0; i < assetDescCount; ++i) { if (assets[i]) { free(assets[i]); } } ser->release(); } TEST_F(AssetTestStrict, SerializeAssetsRoundTrip) { Nv::Blast::ExtSerialization* ser = NvBlastExtSerializationCreate(); EXPECT_TRUE(ser != nullptr); 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])); } const uint32_t encodings[] = { Nv::Blast::ExtSerialization::EncodingID::CapnProtoBinary, Nv::Blast::ExtSerialization::EncodingID::RawBinary }; for (auto encoding : encodings) { ser->setSerializationEncoding(encoding); // Serialize them for (uint32_t i = 0; i < assetDescCount; ++i) { Nv::Blast::Asset* asset = assets[i]; void* buffer; const uint64_t size = NvBlastExtSerializationSerializeAssetIntoBuffer(buffer, *ser, asset); EXPECT_TRUE(size != 0); Nv::Blast::Asset* rtAsset = reinterpret_cast(ser->deserializeFromBuffer(buffer, size)); //TODO: Compare assets checkAssetsExpected(*rtAsset, g_assetExpectedValues[i]); free(static_cast(rtAsset)); } } // Destroy for (uint32_t i = 0; i < assetDescCount; ++i) { if (assets[i]) { free(assets[i]); } } ser->release(); } TEST_F(AssetTestStrict, SerializeAssetsRoundTripWithSkipping) { Nv::Blast::ExtSerialization* ser = NvBlastExtSerializationCreate(); EXPECT_TRUE(ser != nullptr); std::vector stream; class StreamBufferProvider : public Nv::Blast::ExtSerialization::BufferProvider { public: StreamBufferProvider(std::vector& stream) : m_stream(stream), m_cursor(0) {} virtual void* requestBuffer(size_t size) override { m_stream.resize(m_cursor + size); void* data = m_stream.data() + m_cursor; m_cursor += size; return data; } private: std::vector& m_stream; size_t m_cursor; } myStreamProvider(stream); ser->setBufferProvider(&myStreamProvider); 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])); } const uint32_t encodings[] = { Nv::Blast::ExtSerialization::EncodingID::CapnProtoBinary, Nv::Blast::ExtSerialization::EncodingID::RawBinary }; for (auto encoding : encodings) { ser->setSerializationEncoding(encoding); // Serialize them for (uint32_t i = 0; i < assetDescCount; ++i) { void* buffer; const uint64_t size = NvBlastExtSerializationSerializeAssetIntoBuffer(buffer, *ser, assets[i]); EXPECT_TRUE(size != 0); } } // Deserialize from stream const void* buffer = stream.data(); uint64_t bufferSize = stream.size(); for (uint32_t assetCount = 0; bufferSize; ++assetCount) { uint32_t objectTypeID; uint32_t encodingID; const bool peekSuccess = ser->peekHeader(&objectTypeID, &encodingID, nullptr, buffer, bufferSize); EXPECT_TRUE(peekSuccess); if (!peekSuccess) { break; } EXPECT_EQ(Nv::Blast::LlObjectTypeID::Asset, objectTypeID); if (assetCount < assetDescCount) { EXPECT_EQ(Nv::Blast::ExtSerialization::EncodingID::CapnProtoBinary, encodingID); } else { EXPECT_EQ(Nv::Blast::ExtSerialization::EncodingID::RawBinary, encodingID); } const bool skip = (assetCount & 1) != 0; if (!skip) { const uint32_t assetnum = assetCount % assetDescCount; Nv::Blast::Asset* rtAsset = reinterpret_cast(ser->deserializeFromBuffer(buffer, bufferSize)); EXPECT_TRUE(rtAsset != nullptr); if (rtAsset == nullptr) { break; } //TODO: Compare assets checkAssetsExpected(*rtAsset, g_assetExpectedValues[assetnum]); free(static_cast(rtAsset)); } buffer = ser->skipObject(bufferSize, buffer); } // Destroy for (uint32_t i = 0; i < assetDescCount; ++i) { if (assets[i]) { free(assets[i]); } } ser->release(); } #endif // ENABLE_SERIALIZATION_TESTS 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(), true, 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); } }