// 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-2020 NVIDIA Corporation. All rights reserved. #include "TkBaseTest.h" #include #include #include #include #include "PsMemoryBuffer.h" #include "NvBlastTime.h" #include "NvBlastExtPxTask.h" struct ExpectedVisibleChunks { ExpectedVisibleChunks() :numActors(0), numChunks(0) {} ExpectedVisibleChunks(size_t a, size_t c) :numActors(a), numChunks(c) {} size_t numActors; size_t numChunks; }; void testResults(std::vector& families, std::map& expectedVisibleChunks) { size_t numActors = 0; for (TkFamily* fam : families) { auto ex = expectedVisibleChunks[fam]; EXPECT_EQ(ex.numActors, fam->getActorCount()); numActors += ex.numActors; std::vector actors(fam->getActorCount()); fam->getActors(actors.data(), static_cast(actors.size())); for (TkActor* actor : actors) { EXPECT_EQ(ex.numChunks, actor->getVisibleChunkCount()); } } size_t numActorsExpected = 0; for (auto expected : expectedVisibleChunks) { numActorsExpected += expected.second.numActors; } EXPECT_EQ(numActorsExpected, numActors); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Tests /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// TEST_F(TkTestStrict, CreateFramework) { createFramework(); releaseFramework(); } TEST_F(TkTestStrict, CreateAsset) { createFramework(); createTestAssets(); releaseTestAssets(); releaseFramework(); } #if USE_PHYSX_DISPATCHER TEST_F(TkTestStrict, DISABLED_MemLeak) { PxFoundation* pxFoundation = PxCreateFoundation(PX_FOUNDATION_VERSION, NvBlastGetPxAllocatorCallback(), NvBlastGetPxErrorCallback()); PxU32 affinity[] = { 1, 2, 4, 8 }; PxDefaultCpuDispatcher* cpuDispatcher = PxDefaultCpuDispatcherCreate(4, affinity); cpuDispatcher->setRunProfiled(false); PxTaskManager* taskman = PxTaskManager::createTaskManager(NvBlastGetPxErrorCallback(), cpuDispatcher, nullptr); cpuDispatcher->release(); taskman->release(); pxFoundation->release(); } #endif TEST_F(TkTestStrict, ActorDamageNoGroup) { createFramework(); createTestAssets(); TkFramework* fwk = NvBlastTkFrameworkGet(); TkActorDesc actorDesc; actorDesc.asset = testAssets[0]; TkActor* actor = fwk->createActor(actorDesc); const size_t bondFractureCount = 4; NvBlastFractureBuffers commands; NvBlastBondFractureData bdata[bondFractureCount]; for (uint32_t i = 0; i < bondFractureCount; i++) { bdata[i].nodeIndex0 = 2 * i + 0; bdata[i].nodeIndex1 = 2 * i + 1; bdata[i].health = 1.0f; } commands.bondFractureCount = bondFractureCount; commands.bondFractures = bdata; commands.chunkFractureCount = 0; commands.chunkFractures = nullptr; actor->applyFracture(&commands, &commands); TkFamily& family = actor->getFamily(); EXPECT_TRUE(commands.bondFractureCount == 4); EXPECT_TRUE(actor->isPending()); TkGroupDesc gdesc; gdesc.workerCount = m_taskman->getCpuDispatcher()->getWorkerCount(); TkGroup* group = fwk->createGroup(gdesc); EXPECT_TRUE(group != nullptr); m_groupTM->setGroup(group); group->addActor(*actor); m_groupTM->process(); m_groupTM->wait(); EXPECT_FALSE(actor->isPending()); EXPECT_EQ(2, family.getActorCount()); releaseFramework(); } TEST_F(TkTestStrict, ActorDamageGroup) { TEST_ZONE_BEGIN("ActorDamageGroup"); createFramework(); createTestAssets(); TkFramework* fwk = NvBlastTkFrameworkGet(); TestFamilyTracker ftrack1, ftrack2; TkGroupDesc gdesc; gdesc.workerCount = m_taskman->getCpuDispatcher()->getWorkerCount(); TkGroup* group = fwk->createGroup(gdesc); EXPECT_TRUE(group != nullptr); m_groupTM->setGroup(group); NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); NvBlastExtProgramParams radialDamageParams = { &radialDamage, nullptr }; NvBlastExtShearDamageDesc shearDamage = getShearDamageDesc(0, 0, 0); NvBlastExtProgramParams shearDamageParams = { &shearDamage, nullptr }; CSParams csDamage0(0, 0.0f); NvBlastExtProgramParams csDamageParams0 = { &csDamage0, nullptr }; CSParams csDamage1(1, 0.0f); NvBlastExtProgramParams csDamageParams1 = { &csDamage1, nullptr }; CSParams csDamage2(2, 0.0f); NvBlastExtProgramParams csDamageParams2 = { &csDamage2, nullptr }; std::vector families; TkFamily* trackedFamily; std::map expectedVisibleChunks; { TkActorDesc adesc(testAssets[0]); TkActor* actor1 = fwk->createActor(adesc); EXPECT_TRUE(actor1 != nullptr); TkActor* actor2 = fwk->createActor(adesc); EXPECT_TRUE(actor2 != nullptr); expectedVisibleChunks[&actor1->getFamily()] = ExpectedVisibleChunks(8, 1); // full damage expectedVisibleChunks[&actor2->getFamily()] = ExpectedVisibleChunks(1, 1); // not split GeneratorAsset cube; TkAssetDesc assetDesc; generateCube(cube, assetDesc, 5, 2); assetDesc.bondFlags = nullptr; TkAsset* cubeAsset = fwk->createAsset(assetDesc); testAssets.push_back(cubeAsset); TkActorDesc cubeAD(cubeAsset); TkActor* cubeActor1 = fwk->createActor(cubeAD); EXPECT_TRUE(cubeActor1 != nullptr); trackedFamily = &cubeActor1->getFamily(); cubeActor1->getFamily().addListener(ftrack1); TkActor* cubeActor2 = fwk->createActor(cubeAD); EXPECT_TRUE(cubeActor2 != nullptr); expectedVisibleChunks[&cubeActor1->getFamily()] = ExpectedVisibleChunks(2, 4); // split in 2, 4 chunks each expectedVisibleChunks[&cubeActor2->getFamily()] = ExpectedVisibleChunks(1, 1); // not split ftrack1.insertActor(cubeActor1); ftrack2.insertActor(actor1); actor1->getFamily().addListener(ftrack2); TEST_ZONE_BEGIN("add to groups"); group->addActor(*cubeActor1); group->addActor(*cubeActor2); group->addActor(*actor1); group->addActor(*actor2); TEST_ZONE_END("add to groups"); families.push_back(&cubeActor1->getFamily()); families.push_back(&cubeActor2->getFamily()); families.push_back(&actor1->getFamily()); families.push_back(&actor2->getFamily()); cubeActor1->damage(getCubeSlicerProgram(), &csDamageParams0); actor1->damage(getFalloffProgram(), &radialDamageParams); } EXPECT_FALSE(group->endProcess()); m_groupTM->process(); m_groupTM->wait(); testResults(families, expectedVisibleChunks); { std::vector actors(trackedFamily->getActorCount()); trackedFamily->getActors(actors.data(), static_cast(actors.size())); for (TkActor* actor : actors) { actor->damage(getCubeSlicerProgram(), &csDamageParams1); } } expectedVisibleChunks[trackedFamily] = ExpectedVisibleChunks(4, 2); m_groupTM->process(); m_groupTM->wait(); testResults(families, expectedVisibleChunks); { std::vector actors(trackedFamily->getActorCount()); trackedFamily->getActors(actors.data(), static_cast(actors.size())); for (TkActor* actor : actors) { actor->damage(getCubeSlicerProgram(), &csDamageParams2); } } expectedVisibleChunks[trackedFamily] = ExpectedVisibleChunks(8, 1); m_groupTM->process(); m_groupTM->wait(); testResults(families, expectedVisibleChunks); { std::vector actors(trackedFamily->getActorCount()); trackedFamily->getActors(actors.data(), static_cast(actors.size())); TEST_ZONE_BEGIN("damage"); for (TkActor* actor : actors) { actor->damage(getFalloffProgram(), &radialDamageParams); } TEST_ZONE_END("damage"); } expectedVisibleChunks[trackedFamily] = ExpectedVisibleChunks(4096, 1); m_groupTM->process(); m_groupTM->wait(); testResults(families, expectedVisibleChunks); { std::vector actors(trackedFamily->getActorCount()); trackedFamily->getActors(actors.data(), static_cast(actors.size())); TEST_ZONE_BEGIN("damage"); for (TkActor* actor : actors) { actor->damage(getShearProgram(), &shearDamageParams); } TEST_ZONE_END("damage"); } m_groupTM->process(); m_groupTM->wait(); { std::vector actors(trackedFamily->getActorCount()); trackedFamily->getActors(actors.data(), static_cast(actors.size())); TEST_ZONE_BEGIN("damage"); for (TkActor* actor : actors) { actor->damage(getShearProgram(), &shearDamageParams); } TEST_ZONE_END("damage"); } m_groupTM->process(); m_groupTM->wait(); group->release(); TEST_ZONE_BEGIN("family release"); trackedFamily->release(); TEST_ZONE_END("family release"); releaseTestAssets(); releaseFramework(); TEST_ZONE_END("ActorDamageGroup"); } TEST_F(TkTestStrict, ActorDamageMultiGroup) { createFramework(); createTestAssets(); TkFramework* fwk = NvBlastTkFrameworkGet(); TestFamilyTracker ftrack1, ftrack2; TkGroupDesc gdesc; gdesc.workerCount = m_taskman->getCpuDispatcher()->getWorkerCount(); TkGroup* group0 = fwk->createGroup(gdesc); EXPECT_TRUE(group0 != nullptr); TkGroup* group1 = fwk->createGroup(gdesc); EXPECT_TRUE(group1 != nullptr); ExtGroupTaskManager& gtm1 = *ExtGroupTaskManager::create(*m_taskman, group1); ExtGroupTaskManager& gtm0 = *ExtGroupTaskManager::create(*m_taskman, group0); std::vector families(2); std::map expectedVisibleChunks; CSParams csDamage0(0, 0.0f); NvBlastExtProgramParams csDamageParams0 = { &csDamage0, nullptr }; CSParams csDamage1(1, 0.0f); NvBlastExtProgramParams csDamageParams1 = { &csDamage1, nullptr }; CSParams csDamage2(2, 0.0f); NvBlastExtProgramParams csDamageParams2 = { &csDamage2, nullptr }; // prepare 2 equal actors/families and damage { GeneratorAsset cube; TkAssetDesc assetDesc; generateCube(cube, assetDesc, 6, 2, 5); assetDesc.bondFlags = nullptr; TkAsset* cubeAsset = fwk->createAsset(assetDesc); testAssets.push_back(cubeAsset); TkActorDesc cubeAD(cubeAsset); TkActor* cubeActor0 = fwk->createActor(cubeAD); EXPECT_TRUE(cubeActor0 != nullptr); cubeActor0->getFamily().addListener(ftrack1); TkActor* cubeActor1 = fwk->createActor(cubeAD); EXPECT_TRUE(cubeActor1 != nullptr); cubeActor1->getFamily().addListener(ftrack2); ftrack1.insertActor(cubeActor0); ftrack2.insertActor(cubeActor1); group0->addActor(*cubeActor0); group1->addActor(*cubeActor1); families[0] = (&cubeActor0->getFamily()); families[1] = (&cubeActor1->getFamily()); { cubeActor0->damage(getCubeSlicerProgram(), &csDamageParams0); cubeActor0->damage(getCubeSlicerProgram(), &csDamageParams1); cubeActor1->damage(getCubeSlicerProgram(), &csDamageParams0); } expectedVisibleChunks[families[0]] = ExpectedVisibleChunks(4, 2); // split in 4, 2 chunks each expectedVisibleChunks[families[1]] = ExpectedVisibleChunks(2, 4); // split in 2, 4 chunks each } // async process 2 groups { EXPECT_GT(gtm0.process(2), (uint32_t)0); EXPECT_GT(gtm1.process(2), (uint32_t)0); uint32_t completed = 0; while (completed < 2) { if (gtm0.wait(false)) completed++; if (gtm1.wait(false)) completed++; } } // checks testResults(families, expectedVisibleChunks); EXPECT_EQ(families[0]->getActorCount(), 4); EXPECT_EQ(group0->getActorCount(), 4); EXPECT_EQ(families[1]->getActorCount(), 2); EXPECT_EQ(group1->getActorCount(), 2); // we have group0 with 4 actors 2 chunks: // group0: [2]' [2]' [2]' [2]' (family0') // group1: [4]'' [4]'' (family1'') // rearrange: // group0: [2]' [2]' [4]'' // group1: [4]'' [2]' [2]' { TkActor* group0Actors[2]; group0->getActors(group0Actors, 2, 1); // start index: 1, because..why not? TkActor* group1Actors[2]; group1->getActors(group1Actors, 2, 0); group0Actors[0]->removeFromGroup(); group1->addActor(*group0Actors[0]); group0Actors[1]->removeFromGroup(); group1->addActor(*group0Actors[1]); group1Actors[0]->removeFromGroup(); group0->addActor(*group1Actors[0]); } // checks EXPECT_EQ(families[0]->getActorCount(), 4); EXPECT_EQ(group0->getActorCount(), 3); EXPECT_EQ(families[1]->getActorCount(), 2); EXPECT_EQ(group1->getActorCount(), 3); // damage all { TkActor* allActors[6]; families[0]->getActors(allActors, 4, 0); families[1]->getActors(allActors + 4, 2, 0); typedef std::pair pair; std::set combinations; for (auto actor : allActors) { combinations.emplace(pair(actor->getGroup(), &actor->getFamily())); if (actor->getVisibleChunkCount() == 4) { actor->damage(getCubeSlicerProgram(), &csDamageParams1); } actor->damage(getCubeSlicerProgram(), &csDamageParams2); } EXPECT_EQ(combinations.size(), 4); expectedVisibleChunks[families[0]] = ExpectedVisibleChunks(8, 1); // split in 8, 1 chunks each expectedVisibleChunks[families[1]] = ExpectedVisibleChunks(8, 1); // split in 8, 1 chunks each } // async process 2 groups { EXPECT_GT(gtm1.process(2), (uint32_t)0); EXPECT_GT(gtm0.process(2), (uint32_t)0); uint32_t completed = 0; while (completed < 2) { if (gtm1.wait(false)) completed++; if (gtm0.wait(false)) completed++; } } // checks testResults(families, expectedVisibleChunks); EXPECT_EQ(families[0]->getActorCount(), 8); EXPECT_EQ(ftrack1.actors.size(), 8); EXPECT_EQ(group0->getActorCount(), 8); EXPECT_EQ(families[1]->getActorCount(), 8); EXPECT_EQ(ftrack2.actors.size(), 8); EXPECT_EQ(group1->getActorCount(), 8); // damage till the end, aggressively std::default_random_engine re; { NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); NvBlastExtProgramParams radialDamageParams = { &radialDamage, nullptr }; NvBlastExtShearDamageDesc shearDamage = getShearDamageDesc(0, 0, 0); NvBlastExtProgramParams shearDamageParams = { &shearDamage, nullptr }; std::vector actors; while (1) { TEST_ZONE_BEGIN("damage loop"); uint32_t n0 = families[0]->getActorCount(); uint32_t n1 = families[1]->getActorCount(); actors.resize(n0 + n1); families[0]->getActors(actors.data(), n0, 0); families[1]->getActors(actors.data() + n0, n1, 0); bool workTBD = false; for (TkActor* actor : actors) { if (!NvBlastActorCanFracture(actor->getActorLL(), nullptr)) { continue; } workTBD = true; if (actor->getGraphNodeCount() > 1) { actor->damage(getFalloffProgram(), &radialDamageParams); } else { actor->damage(getShearProgram(), &shearDamageParams); } if (re() % 1000 < 500) { // switch group TkGroup* newGroup = actor->getGroup() == group0 ? group1 : group0; actor->removeFromGroup(); newGroup->addActor(*actor); } } TEST_ZONE_END("damage loop"); if (!workTBD) break; // async process 2 groups { EXPECT_GT(gtm1.process(2), (uint32_t)0); EXPECT_GT(gtm0.process(2), (uint32_t)0); uint32_t completed = 0; while (completed < 2) { if (gtm1.wait(false)) completed++; if (gtm0.wait(false)) completed++; } } } } // checks EXPECT_EQ(families[0]->getActorCount(), ftrack1.actors.size()); EXPECT_EQ(families[1]->getActorCount(), ftrack2.actors.size()); EXPECT_EQ(65536, families[0]->getActorCount() + families[1]->getActorCount()); EXPECT_EQ(65536, group0->getActorCount() + group1->getActorCount()); gtm0.release(); gtm1.release(); group0->release(); group1->release(); for (auto f : families) f->release(); releaseTestAssets(); releaseFramework(); } TEST_F(TkTestStrict, ActorDamageBufferedDamage) { createFramework(); TkFramework* fwk = NvBlastTkFrameworkGet(); // group TkGroupDesc gdesc; gdesc.workerCount = m_taskman->getCpuDispatcher()->getWorkerCount(); TkGroup* group = fwk->createGroup(gdesc); EXPECT_TRUE(group != nullptr); m_groupTM->setGroup(group); // random engine std::default_random_engine re; // cube asset GeneratorAsset cube; TkAssetDesc assetDesc; generateCube(cube, assetDesc, 4, 2, 3); assetDesc.bondFlags = nullptr; TkAsset* cubeAsset = fwk->createAsset(assetDesc); testAssets.push_back(cubeAsset); // actor desc TkActorDesc cubeAD(cubeAsset); // test will be repated 'trials' times. Because of random shuffle inside. const uint32_t trials = 100; for (uint32_t i = 0; i < trials; i++) { // create actor TkActor* actor = fwk->createActor(cubeAD); EXPECT_TRUE(actor != nullptr); TkFamily* family = (&actor->getFamily()); group->addActor(*actor); // damage 3 times with CubeSlicer 2 * 2 * 2 = 8 actors // damage 4 corners with falloff radial 4 * 2 = 8 actors // total 16 actors uint32_t expectedActorCount = 16; // fallof params const float P = 0.5f; const float R = 0.35f; // 2 of damage types would be through user's NvBlastDamageProgram, this pointer must live till group->sync() NvBlastExtRadialDamageDesc userR0 = getRadialDamageDesc(P, P, 0, R, R); NvBlastExtProgramParams userProgramParams0 = { &userR0, nullptr }; NvBlastExtRadialDamageDesc userR1 = getRadialDamageDesc(-P, P, 0, R, R); NvBlastExtProgramParams userProgramParams1 = { &userR1, nullptr }; CSParams csDamage0(0, 0.0f); NvBlastExtProgramParams csDamageParams0 = { &csDamage0, nullptr }; CSParams csDamage1(1, 0.0f); NvBlastExtProgramParams csDamageParams1 = { &csDamage1, nullptr }; CSParams csDamage2(2, 0.0f); NvBlastExtProgramParams csDamageParams2 = { &csDamage2, nullptr }; NvBlastExtRadialDamageDesc r0 = getRadialDamageDesc(P, -P, 0, R, R); NvBlastExtProgramParams rDamageParams0 = { &r0, nullptr }; NvBlastExtRadialDamageDesc r1 = getRadialDamageDesc(-P, -P, 0, R, R); NvBlastExtProgramParams rDamageParams1 = { &r1, nullptr }; // fill damage functions, shuffle and apply { const uint32_t damageCount = 7; std::vector> damageFns(damageCount); damageFns[0] = [&]() { actor->damage(getCubeSlicerProgram(), &csDamageParams0); }; damageFns[1] = [&]() { actor->damage(getCubeSlicerProgram(), &csDamageParams1); }; damageFns[2] = [&]() { actor->damage(getCubeSlicerProgram(), &csDamageParams2); }; damageFns[3] = [&]() { actor->damage(getFalloffProgram(), &rDamageParams0); }; damageFns[4] = [&]() { actor->damage(getFalloffProgram(), &rDamageParams1); }; damageFns[5] = [&]() { actor->damage(getFalloffProgram(), &userProgramParams0); }; damageFns[6] = [&]() { actor->damage(getFalloffProgram(), &userProgramParams1); }; // shuffle order! std::shuffle(std::begin(damageFns), std::end(damageFns), re); for (uint32_t i = 0; i < damageCount; i++) { damageFns[i](); } } // sync EXPECT_GT(m_groupTM->process(), (uint32_t)0); m_groupTM->wait(); const auto ac = family->getActorCount(); // check EXPECT_EQ(family->getActorCount(), expectedActorCount); EXPECT_EQ(group->getActorCount(), expectedActorCount); // release std::vector actors(family->getActorCount()); family->getActors(actors.data(), static_cast(actors.size())); for (auto a : actors) a->removeFromGroup(); family->release(); } group->release(); releaseFramework(); } TEST_F(TkTestStrict, CreateActor) { createFramework(); TkFramework* framework = NvBlastTkFrameworkGet(); const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); std::vector assets(assetDescCount); // assets for (uint32_t i = 0; i < assetDescCount; ++i) { TkAssetDesc desc; reinterpret_cast(desc) = g_assetDescs[i]; desc.bondFlags = nullptr; assets[i] = framework->createAsset(desc); EXPECT_TRUE(assets[i] != nullptr); } // actors std::vector actors;; std::vector actorFamilies;; for (const TkAsset* asset : assets) { for (int i = 0; i < 2; i++) { TkActorDesc desc(asset); TkActor* actor = framework->createActor(desc); EXPECT_TRUE(actor != nullptr); EXPECT_TRUE(actor->getActorLL() != nullptr); //EXPECT_TRUE(&actor->getFamily() != nullptr); EXPECT_TRUE(actor->getFamily().getActorCount() == 1); actors.push_back(actor); EXPECT_TRUE(std::find(actorFamilies.begin(), actorFamilies.end(), &actor->getFamily()) == actorFamilies.end()); actorFamilies.push_back(&actor->getFamily()); } } // framework checks { std::vector objects; // assets { const TkType* assetType = framework->getType(TkTypeIndex::Asset); objects.resize(framework->getObjectCount(*assetType)); EXPECT_TRUE(framework->getObjects(reinterpret_cast(objects.data()), static_cast(objects.size()), *assetType) == static_cast(objects.size())); ExpectArrayMatch(objects.data(), objects.size(), (TkObject**)assets.data(), assets.size()); } // actors # if(0) // framework does not track actors explicitly anymore { const TkType* actorType = framework->getType(TkTypeIndex::Actor); objects.resize(framework->getObjectCount(*actorType)); EXPECT_TRUE(framework->getObjects(reinterpret_cast(objects.data()), objects.size(), *actorType) == objects.size()); ExpectArrayMatch(objects.data(), objects.size(), (TkObject**)actors.data(), actors.size()); } # endif // families { const TkType* familyType = framework->getType(TkTypeIndex::Family); objects.resize(framework->getObjectCount(*familyType)); EXPECT_TRUE(framework->getObjects(reinterpret_cast(objects.data()), static_cast(objects.size()), *familyType) == static_cast(objects.size())); ExpectArrayMatch(objects.data(), objects.size(), (TkObject**)actorFamilies.data(), actorFamilies.size()); } } // release for (TkActor* actor : actors) { actor->release(); } for (TkAsset* asset : assets) { asset->release(); } releaseFramework(); } template TkFamily* TkBaseTest::familySerialization(TkFamily* family) { #if 0 TkFramework* fw = NvBlastTkFrameworkGet(); const TkType* familyType = fw->getType(TkTypeIndex::Family); EXPECT_TRUE(familyType != nullptr); PsMemoryBuffer* membuf = PX_NEW(PsMemoryBuffer); EXPECT_TRUE(membuf != nullptr); if (membuf != nullptr) { const bool result = family->serialize(*membuf); EXPECT_EQ(true, result); if (!result) { return family; } const size_t familyActorCount = family->getActorCount(); const TkAsset* familyAsset = family->getAsset(); family->release(); family = reinterpret_cast(fw->deserialize(*membuf)); EXPECT_TRUE(family != nullptr); if (family != nullptr) { EXPECT_EQ(familyActorCount, family->getActorCount()); EXPECT_EQ(familyAsset, family->getAsset()); } membuf->release(); } return family; #endif return nullptr; } TEST_F(TkTestAllowWarnings, DISABLED_FamilySerialization) { createFramework(); TkFramework* fwk = NvBlastTkFrameworkGet(); // group TkGroupDesc gdesc; gdesc.workerCount = m_taskman->getCpuDispatcher()->getWorkerCount(); TkGroup* group = fwk->createGroup(gdesc); EXPECT_TRUE(group != nullptr); m_groupTM->setGroup(group); // random engine std::default_random_engine re; // cube asset TkAsset* cubeAsset = createCubeAsset(4, 2, 3, false); // actor desc TkActorDesc cubeAD(cubeAsset); // create actor TkActor* actor = fwk->createActor(cubeAD); EXPECT_TRUE(actor != nullptr); TkFamily* family = (&actor->getFamily()); // set an ID NvBlastID id; memcpy(id.data, "Observer-expectancy effect", sizeof(NvBlastID)); // Stuffing an arbitrary 16 bytes (The prefix of the given string) cubeAsset->setID(id); // serialize/deserialize family = familySerialization(family); // fill damage functions, apply one by one and serialize family in between { // damage 3 times with CubeSlicer 2 * 2 * 2 = 8 actors // damage 4 corners with falloff radial 4 * 2 = 8 actors // total 16 actors uint32_t expectedActorCount = 16; // cube slicer params CSParams csDamage0(0, 0.0f); NvBlastExtProgramParams csDamageParams0 = { &csDamage0, nullptr }; CSParams csDamage1(1, 0.0f); NvBlastExtProgramParams csDamageParams1 = { &csDamage1, nullptr }; CSParams csDamage2(2, 0.0f); NvBlastExtProgramParams csDamageParams2 = { &csDamage2, nullptr }; // fallof params const float P = 0.5f; const float R = 0.35f; NvBlastExtRadialDamageDesc r0 = getRadialDamageDesc(P, P, 0, R, R); NvBlastExtRadialDamageDesc r1 = getRadialDamageDesc(-P, P, 0, R, R); NvBlastExtRadialDamageDesc r2 = getRadialDamageDesc(P, -P, 0, R, R); NvBlastExtRadialDamageDesc r3 = getRadialDamageDesc(-P, -P, 0, R, R); NvBlastExtProgramParams r0p = { &r0, nullptr }; NvBlastExtProgramParams r1p = { &r1, nullptr }; NvBlastExtProgramParams r2p = { &r2, nullptr }; NvBlastExtProgramParams r3p = { &r3, nullptr }; const uint32_t damageCount = 7; std::vector> damageFns(damageCount); damageFns[0] = [&](TkActor* a) { a->damage(getCubeSlicerProgram(), &csDamageParams0); }; damageFns[1] = [&](TkActor* a) { a->damage(getCubeSlicerProgram(), &csDamageParams1); }; damageFns[2] = [&](TkActor* a) { a->damage(getCubeSlicerProgram(), &csDamageParams2); }; damageFns[3] = [&](TkActor* a) { a->damage(getFalloffProgram(), &r0p); }; damageFns[4] = [&](TkActor* a) { a->damage(getFalloffProgram(), &r1p); }; damageFns[5] = [&](TkActor* a) { a->damage(getFalloffProgram(), &r2p); }; damageFns[6] = [&](TkActor* a) { a->damage(getFalloffProgram(), &r3p); }; std::vector actors(64); for (uint32_t i = 0; i < damageCount; i++) { actors.resize(family->getActorCount()); family->getActors(actors.data(), static_cast(actors.size())); // damage for (auto actor : actors) { group->addActor(*actor); damageFns[i](actor); } // sync EXPECT_GT(m_groupTM->process(), (uint32_t)0); m_groupTM->wait(); family = familySerialization(family); } // check EXPECT_EQ(family->getActorCount(), expectedActorCount); } // release family->release(); group->release(); releaseFramework(); } TEST_F(TkTestStrict, GroupStats) { createFramework(); TkFramework* fwk = NvBlastTkFrameworkGet(); // group TkGroupDesc gdesc; gdesc.workerCount = m_taskman->getCpuDispatcher()->getWorkerCount(); TkGroup* group = fwk->createGroup(gdesc); EXPECT_TRUE(group != nullptr); m_groupTM->setGroup(group); TkAsset* cubeAsset = createCubeAsset(4, 2); TkActorDesc cubeDesc(cubeAsset); TkActor* cubeActor1 = fwk->createActor(cubeDesc); TkActor* cubeActor2 = fwk->createActor(cubeDesc); TkActor* cubeActor3 = fwk->createActor(cubeDesc); TkActor* cubeActor4 = fwk->createActor(cubeDesc); group->addActor(*cubeActor1); group->addActor(*cubeActor2); group->addActor(*cubeActor3); group->addActor(*cubeActor4); NvBlastExtRadialDamageDesc r0 = getRadialDamageDesc(0.0f, 0.0f, 0.0f); NvBlastExtProgramParams radialDamageParams = { &r0, nullptr }; cubeActor1->damage(getFalloffProgram(), &radialDamageParams); cubeActor2->damage(getFalloffProgram(), &radialDamageParams); cubeActor3->damage(getFalloffProgram(), &radialDamageParams); cubeActor4->damage(getFalloffProgram(), &radialDamageParams); Nv::Blast::Time time; m_groupTM->process(); m_groupTM->wait(); int64_t groupTime = time.getElapsedTicks(); TkGroupStats gstats; group->getStats(gstats); int64_t total = gstats.timers.fracture + gstats.timers.island + gstats.timers.material + gstats.timers.partition + gstats.timers.visibility; #if NV_PROFILE EXPECT_GT(total, 0); // some values are reported EXPECT_LT(groupTime, total); // total LL time is higher than group time EXPECT_GT((double)gstats.workerTime / groupTime, 2.0); // expect some minimal speedup (including overhead) EXPECT_EQ(4, gstats.processedActorsCount); // actors processed #endif releaseFramework(); } TEST_F(TkTestStrict, FractureReportSupport) { createFramework(); TkFramework* fwk = NvBlastTkFrameworkGet(); NvBlastChunkDesc chunkDescs[] = { { { 0,0,0 }, 2, UINT32_MAX, NvBlastChunkDesc::SupportFlag, 'prnt' }, { { -1,0,0 }, 1, 0, NvBlastChunkDesc::NoFlags, 'left' }, { { +1,0,0 }, 1, 0, NvBlastChunkDesc::NoFlags, 'rght' }, }; TkAssetDesc assetDesc; assetDesc.chunkCount = sizeof(chunkDescs) / sizeof(NvBlastChunkDesc); assetDesc.chunkDescs = chunkDescs; assetDesc.bondCount = 0; assetDesc.bondDescs = nullptr; assetDesc.bondFlags = nullptr; const TkAsset* asset = fwk->createAsset(assetDesc); TkActorDesc actorDesc; actorDesc.asset = asset; TkActor* actor = fwk->createActor(actorDesc); actor->userData = (void*)'root'; class Listener : public TkEventListener { void receive(const TkEvent* events, uint32_t eventCount) override { for (uint32_t i = 0; i < eventCount; i++) { const TkEvent& event = events[i]; switch (event.type) { case TkJointUpdateEvent::EVENT_TYPE: FAIL() << "not expecting joints here"; break; case TkFractureCommands::EVENT_TYPE: { const TkActorData& actor = event.getPayload()->tkActorData; // Group::sync still needed the family for SharedMemory management. EXPECT_TRUE(nullptr != actor.family); EXPECT_EQ((void*)'root', actor.userData); EXPECT_EQ(0, actor.index); } break; case TkFractureEvents::EVENT_TYPE: { const TkActorData& actor = event.getPayload()->tkActorData; EXPECT_EQ((void*)'root', actor.userData); EXPECT_EQ(0, actor.index); } break; case TkSplitEvent::EVENT_TYPE: { const TkSplitEvent* split = event.getPayload(); EXPECT_TRUE(nullptr != split->parentData.family); EXPECT_EQ((void*)'root', split->parentData.userData); EXPECT_EQ(0, split->parentData.index); EXPECT_EQ(2, split->numChildren); EXPECT_EQ(1, split->children[0]->getVisibleChunkCount()); uint32_t visibleChunkIndex; // child order is not mandatory { TkActor* a = split->children[0]; a->getVisibleChunkIndices(&visibleChunkIndex, 1); uint32_t li = a->getIndex(); EXPECT_EQ(1, li); EXPECT_EQ(split->parentData.family, &a->getFamily()); EXPECT_EQ('left', a->getAsset()->getChunks()[visibleChunkIndex].userData); } { TkActor*a = split->children[1]; a->getVisibleChunkIndices(&visibleChunkIndex, 1); uint32_t ri = a->getIndex(); EXPECT_EQ(2, ri); EXPECT_EQ(split->parentData.family, &a->getFamily()); EXPECT_EQ('rght', a->getAsset()->getChunks()[visibleChunkIndex].userData); } } break; default: FAIL() << "should not get here"; } } } } listener; actor->getFamily().addListener(listener); // expected state for the original actor, see Listener EXPECT_EQ((void*)'root', actor->userData); EXPECT_EQ(0, actor->getIndex()); TkGroupDesc groupDesc = { m_taskman->getCpuDispatcher()->getWorkerCount() }; TkGroup* group = fwk->createGroup(groupDesc); m_groupTM->setGroup(group); group->addActor(*actor); // this will trigger hierarchical chunk fracture NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); NvBlastExtProgramParams radialDamageParams = { &radialDamage, nullptr }; actor->damage(getFalloffProgram(), &radialDamageParams); m_groupTM->process(); m_groupTM->wait(); releaseFramework(); } TEST_F(TkTestStrict, FractureReportGraph) { createFramework(); TkFramework* fwk = NvBlastTkFrameworkGet(); NvBlastBond bondToBreak = { { 1, 0, 0 }, 1, { 0, 0, 0 }, 0 }; NvBlastBond bondToKeep = { { 1, 0, 0 }, 1, { 10, 10, 10 }, 0 }; NvBlastBondDesc bondDescs[] = { { bondToKeep, { 1, 2 } }, { bondToBreak, { 2, 3 } }, }; NvBlastChunkDesc chunkDescs[] = { { { 0, 0, 0 }, 2, UINT32_MAX, NvBlastChunkDesc::NoFlags, 'root' }, { { -1, 0, 0 }, 1, 0, NvBlastChunkDesc::SupportFlag, 'A' }, { { +1, 0, 0 }, 1, 0, NvBlastChunkDesc::SupportFlag, 'B' }, { { +1, 0, 0 }, 1, 0, NvBlastChunkDesc::SupportFlag, 'C' }, }; TkAssetDesc assetDesc; assetDesc.chunkCount = sizeof(chunkDescs) / sizeof(NvBlastChunkDesc); assetDesc.chunkDescs = chunkDescs; assetDesc.bondCount = 2; assetDesc.bondDescs = bondDescs; assetDesc.bondFlags = nullptr; const TkAsset* asset = fwk->createAsset(assetDesc); TkActorDesc actorDesc; actorDesc.asset = asset; TkActor* rootActor = fwk->createActor(actorDesc); rootActor->userData = (void*)'root'; class Listener : public TkEventListener { void receive(const TkEvent* events, uint32_t eventCount) override { for (uint32_t i = 0; i < eventCount; i++) { const TkEvent& event = events[i]; switch (event.type) { case TkJointUpdateEvent::EVENT_TYPE: FAIL() << "not expecting joints here"; break; case TkFractureCommands::EVENT_TYPE: { const TkActorData& actor = event.getPayload()->tkActorData; // Group::sync still needed the family for SharedMemory management. EXPECT_TRUE(nullptr != actor.family); // original actor state is not preserved, the last test will fail EXPECT_EQ((void*)'root', actor.userData); EXPECT_EQ(0, actor.index); // this information was invalid anyway //EXPECT_EQ(1, actor->getVisibleChunkCount()) << "state not preserved"; } break; case TkFractureEvents::EVENT_TYPE: { const TkActorData& actor = event.getPayload()->tkActorData; // Group::sync still needed the family for SharedMemory management. EXPECT_TRUE(nullptr != actor.family); // original actor state is not preserved, the last test will fail EXPECT_EQ((void*)'root', actor.userData); EXPECT_EQ(0, actor.index); // this information was invalid anyway //EXPECT_EQ(1, actor->getVisibleChunkCount()) << "state not preserved"; } break; case TkSplitEvent::EVENT_TYPE: { const TkSplitEvent* split = event.getPayload(); EXPECT_EQ((void*)'root', split->parentData.userData); EXPECT_EQ(0, split->parentData.index); EXPECT_EQ(2, split->numChildren); uint32_t visibleChunkIndex[2]; // child order is not mandatory { TkActor* a = split->children[1]; EXPECT_EQ(2, a->getVisibleChunkCount()); // chunks A and B a->getVisibleChunkIndices(visibleChunkIndex, 2); uint32_t actorIndex = a->getIndex(); EXPECT_EQ(0, actorIndex); // same index as the original actor // visible chunk order is not mandatory EXPECT_EQ('B', a->getAsset()->getChunks()[visibleChunkIndex[0]].userData); EXPECT_EQ('A', a->getAsset()->getChunks()[visibleChunkIndex[1]].userData); } { TkActor* a = split->children[0]; EXPECT_EQ(1, a->getVisibleChunkCount()); a->getVisibleChunkIndices(visibleChunkIndex, 1); uint32_t actorIndex = a->getIndex(); EXPECT_EQ(2, actorIndex); EXPECT_EQ('C', a->getAsset()->getChunks()[visibleChunkIndex[0]].userData); } } break; default: FAIL() << "should not get here"; } } } } listener; rootActor->getFamily().addListener(listener); // expected state for the original actor, see Listener EXPECT_EQ((void*)'root', rootActor->userData); EXPECT_EQ(0, rootActor->getIndex()); EXPECT_EQ(1, rootActor->getVisibleChunkCount()); TkGroupDesc groupDesc = { m_taskman->getCpuDispatcher()->getWorkerCount() }; TkGroup* group = fwk->createGroup(groupDesc); m_groupTM->setGroup(group); group->addActor(*rootActor); // this will trigger one bond to break NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0, 0.5f, 0.5f); NvBlastExtProgramParams radialDamageParams = { &radialDamage, nullptr }; rootActor->damage(getFalloffProgram(), &radialDamageParams); m_groupTM->process(); m_groupTM->wait(); releaseFramework(); } TEST_F(TkTestStrict, SplitWarning) // GWD-167 { createFramework(); TkFramework* fwk = NvBlastTkFrameworkGet(); NvBlastChunkDesc chunkDescs[] = { { { 0,0,0 }, 2, UINT32_MAX, NvBlastChunkDesc::SupportFlag, 'root' }, { { -1,0,0 }, 1, 0, NvBlastChunkDesc::NoFlags, 'A' }, { { +1,0,0 }, 1, 0, NvBlastChunkDesc::NoFlags, 'B' }, { { -1,0,0 }, 1, 0, NvBlastChunkDesc::NoFlags, 'C' }, { { +1,0,0 }, 1, 0, NvBlastChunkDesc::NoFlags, 'D' }, { { -1,0,0 }, 1, 1, NvBlastChunkDesc::NoFlags, 'AAAA' }, { { +1,0,0 }, 1, 2, NvBlastChunkDesc::NoFlags, 'BBBB' }, { { -1,0,0 }, 1, 3, NvBlastChunkDesc::NoFlags, 'CCCC' }, { { +1,0,0 }, 1, 4, NvBlastChunkDesc::NoFlags, 'DDDD' }, }; TkAssetDesc assetDesc; assetDesc.chunkCount = sizeof(chunkDescs) / sizeof(NvBlastChunkDesc); assetDesc.chunkDescs = chunkDescs; assetDesc.bondCount = 0; assetDesc.bondDescs = nullptr; assetDesc.bondFlags = nullptr; const TkAsset* asset = fwk->createAsset(assetDesc); TkActorDesc actorDesc; actorDesc.asset = asset; TkActor* actor = fwk->createActor(actorDesc); TkGroupDesc groupDesc = { m_taskman->getCpuDispatcher()->getWorkerCount() }; TkGroup* group = fwk->createGroup(groupDesc); m_groupTM->setGroup(group); group->addActor(*actor); NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); NvBlastExtProgramParams radialDamageParams = { &radialDamage, nullptr }; actor->damage(getFalloffProgram(), &radialDamageParams); m_groupTM->process(); m_groupTM->wait(); releaseFramework(); } TEST_F(TkTestAllowWarnings, ChangeThreadCountToZero) { // tests that group still allocates memory for one worker // by replacing to a 0 threads cpu dispatcher (warns) // mainly relies on internal asserts class EventCounter : public TkEventListener { public: EventCounter() :fracCommands(0), fracEvents(0) {} void receive(const TkEvent* events, uint32_t eventCount) { for (uint32_t i = 0; i < eventCount; i++) { const TkEvent& event = events[i]; switch (event.type) { case TkFractureCommands::EVENT_TYPE: fracCommands++; break; case TkFractureEvents::EVENT_TYPE: fracEvents++; break; default: FAIL(); // no split due to single chunk // no joints } } } uint32_t fracCommands, fracEvents; } listener; createFramework(); TkFramework* fwk = NvBlastTkFrameworkGet(); NvBlastChunkDesc chunkDescs[] = { { { 0,0,0 }, 2, UINT32_MAX, NvBlastChunkDesc::SupportFlag, 'root' } }; TkAssetDesc assetDesc; assetDesc.chunkCount = sizeof(chunkDescs) / sizeof(NvBlastChunkDesc); assetDesc.chunkDescs = chunkDescs; assetDesc.bondCount = 0; assetDesc.bondDescs = nullptr; assetDesc.bondFlags = nullptr; const TkAsset* asset = fwk->createAsset(assetDesc); TkActorDesc actorDesc; actorDesc.asset = asset; TkActor* actor1 = fwk->createActor(actorDesc); TkActor* actor2 = fwk->createActor(actorDesc); TkActor* actor3 = fwk->createActor(actorDesc); TkActor* actor4 = fwk->createActor(actorDesc); actor1->getFamily().addListener(listener); actor2->getFamily().addListener(listener); actor3->getFamily().addListener(listener); actor4->getFamily().addListener(listener); #if USE_PHYSX_DISPATCHER PxU32 affinity[] = { 1, 2, 4, 8 }; PxDefaultCpuDispatcher* disp0 = PxDefaultCpuDispatcherCreate(0, affinity); disp0->setRunProfiled(false); PxDefaultCpuDispatcher* disp4 = PxDefaultCpuDispatcherCreate(4, affinity); disp4->setRunProfiled(false); #else TestCpuDispatcher* disp0 = new TestCpuDispatcher(0); TestCpuDispatcher* disp4 = new TestCpuDispatcher(4); #endif m_taskman->setCpuDispatcher(*disp4); TkGroupDesc groupDesc = { m_taskman->getCpuDispatcher()->getWorkerCount() }; TkGroup* group = fwk->createGroup(groupDesc); m_groupTM->setGroup(group); group->addActor(*actor1); group->addActor(*actor2); m_taskman->setCpuDispatcher(*disp0); //group->setWorkerCount(m_taskman->getCpuDispatcher()->getWorkerCount()); group->addActor(*actor3); group->addActor(*actor4); NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); NvBlastExtProgramParams radialDamageParams = { &radialDamage, nullptr }; actor1->damage(getFalloffProgram(), &radialDamageParams); actor2->damage(getFalloffProgram(), &radialDamageParams); actor3->damage(getFalloffProgram(), &radialDamageParams); actor4->damage(getFalloffProgram(), &radialDamageParams); m_groupTM->process(); m_groupTM->wait(); EXPECT_EQ(4, listener.fracCommands); EXPECT_EQ(4, listener.fracEvents); releaseFramework(); disp0->release(); disp4->release(); } TEST_F(TkTestStrict, ChangeThreadCountUp) { // tests that group allocates more memory for additional workers // by replacing to a higher thread count cpu dispatcher (warns) // mainly relies on internal asserts class EventCounter : public TkEventListener { public: EventCounter() :fracCommands(0), fracEvents(0) {} void receive(const TkEvent* events, uint32_t eventCount) { for (uint32_t i = 0; i < eventCount; i++) { const TkEvent& event = events[i]; switch (event.type) { case TkFractureCommands::EVENT_TYPE: fracCommands++; break; case TkFractureEvents::EVENT_TYPE: fracEvents++; break; default: FAIL(); // no split due to single chunk // no joints } } } uint32_t fracCommands, fracEvents; } listener; createFramework(); TkFramework* fwk = NvBlastTkFrameworkGet(); NvBlastChunkDesc chunkDescs[] = { { { 0,0,0 }, 2, UINT32_MAX, NvBlastChunkDesc::SupportFlag, 'root' } }; TkAssetDesc assetDesc; assetDesc.chunkCount = sizeof(chunkDescs) / sizeof(NvBlastChunkDesc); assetDesc.chunkDescs = chunkDescs; assetDesc.bondCount = 0; assetDesc.bondDescs = nullptr; assetDesc.bondFlags = nullptr; const TkAsset* asset = fwk->createAsset(assetDesc); TkActorDesc actorDesc; actorDesc.asset = asset; TkActor* actor1 = fwk->createActor(actorDesc); TkActor* actor2 = fwk->createActor(actorDesc); TkActor* actor3 = fwk->createActor(actorDesc); TkActor* actor4 = fwk->createActor(actorDesc); actor1->getFamily().addListener(listener); actor2->getFamily().addListener(listener); actor3->getFamily().addListener(listener); actor4->getFamily().addListener(listener); #if USE_PHYSX_DISPATCHER PxU32 affinity[] = { 1, 2, 4, 8 }; PxDefaultCpuDispatcher* disp2 = PxDefaultCpuDispatcherCreate(2, affinity); disp2->setRunProfiled(false); PxDefaultCpuDispatcher* disp4 = PxDefaultCpuDispatcherCreate(4, affinity); disp4->setRunProfiled(false); #else TestCpuDispatcher* disp2 = new TestCpuDispatcher(2); TestCpuDispatcher* disp4 = new TestCpuDispatcher(4); #endif m_taskman->setCpuDispatcher(*disp2); TkGroupDesc groupDesc = { m_taskman->getCpuDispatcher()->getWorkerCount() }; TkGroup* group = fwk->createGroup(groupDesc); m_groupTM->setGroup(group); group->addActor(*actor1); group->addActor(*actor2); group->addActor(*actor3); group->addActor(*actor4); NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); NvBlastExtProgramParams radialDamageParams = { &radialDamage, nullptr }; actor1->damage(getFalloffProgram(), &radialDamageParams); actor2->damage(getFalloffProgram(), &radialDamageParams); actor3->damage(getFalloffProgram(), &radialDamageParams); actor4->damage(getFalloffProgram(), &radialDamageParams); m_taskman->setCpuDispatcher(*disp4); //group->setWorkerCount(m_taskman->getCpuDispatcher()->getWorkerCount()); m_groupTM->process(); m_groupTM->wait(); EXPECT_EQ(4, listener.fracCommands); EXPECT_EQ(4, listener.fracEvents); releaseFramework(); disp2->release(); disp4->release(); } TEST_F(TkTestAllowWarnings, GroupNoWorkers) { // tests that group still works without a taskmanager // a warnings is expected // mainly relies on internal asserts class EventCounter : public TkEventListener { public: EventCounter() :fracCommands(0), fracEvents(0) {} void receive(const TkEvent* events, uint32_t eventCount) { for (uint32_t i = 0; i < eventCount; i++) { const TkEvent& event = events[i]; switch (event.type) { case TkFractureCommands::EVENT_TYPE: fracCommands++; break; case TkFractureEvents::EVENT_TYPE: fracEvents++; break; default: FAIL(); // no split due to single chunk // no joints } } } uint32_t fracCommands, fracEvents; } listener; createFramework(); TkFramework* fwk = NvBlastTkFrameworkGet(); NvBlastChunkDesc chunkDescs[] = { { { 0,0,0 }, 2, UINT32_MAX, NvBlastChunkDesc::SupportFlag, 'root' } }; TkAssetDesc assetDesc; assetDesc.chunkCount = sizeof(chunkDescs) / sizeof(NvBlastChunkDesc); assetDesc.chunkDescs = chunkDescs; assetDesc.bondCount = 0; assetDesc.bondDescs = nullptr; assetDesc.bondFlags = nullptr; const TkAsset* asset = fwk->createAsset(assetDesc); TkActorDesc actorDesc; actorDesc.asset = asset; TkActor* actor1 = fwk->createActor(actorDesc); TkActor* actor2 = fwk->createActor(actorDesc); TkActor* actor3 = fwk->createActor(actorDesc); TkActor* actor4 = fwk->createActor(actorDesc); actor1->getFamily().addListener(listener); actor2->getFamily().addListener(listener); actor3->getFamily().addListener(listener); actor4->getFamily().addListener(listener); #if USE_PHYSX_DISPATCHER PxDefaultCpuDispatcher* disp = PxDefaultCpuDispatcherCreate(0); #else TestCpuDispatcher* disp = new TestCpuDispatcher(0); #endif m_taskman->setCpuDispatcher(*disp); TkGroupDesc groupDesc = { m_taskman->getCpuDispatcher()->getWorkerCount() }; TkGroup* group = fwk->createGroup(groupDesc); m_groupTM->setGroup(group); group->addActor(*actor1); group->addActor(*actor2); group->addActor(*actor3); group->addActor(*actor4); NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); NvBlastExtProgramParams programParams = { &radialDamage, getDefaultMaterial() }; actor1->damage(getFalloffProgram(), &programParams); actor2->damage(getFalloffProgram(), &programParams); actor3->damage(getFalloffProgram(), &programParams); actor4->damage(getFalloffProgram(), &programParams); m_groupTM->process(); m_groupTM->wait(); EXPECT_EQ(4, listener.fracCommands); EXPECT_EQ(4, listener.fracEvents); disp->release(); releaseFramework(); }