diff options
Diffstat (limited to 'NvBlast/test/src')
| -rw-r--r-- | NvBlast/test/src/AlignedAllocator.h | 69 | ||||
| -rw-r--r-- | NvBlast/test/src/BlastBaseTest.h | 159 | ||||
| -rw-r--r-- | NvBlast/test/src/TkBaseTest.h | 467 | ||||
| -rw-r--r-- | NvBlast/test/src/perf/BlastBasePerfTest.h | 374 | ||||
| -rw-r--r-- | NvBlast/test/src/perf/SolverPerfTests.cpp | 211 | ||||
| -rw-r--r-- | NvBlast/test/src/unit/APITests.cpp | 1354 | ||||
| -rw-r--r-- | NvBlast/test/src/unit/ActorTests.cpp | 1059 | ||||
| -rw-r--r-- | NvBlast/test/src/unit/AssetTests.cpp | 529 | ||||
| -rw-r--r-- | NvBlast/test/src/unit/CoreTests.cpp | 293 | ||||
| -rw-r--r-- | NvBlast/test/src/unit/FamilyGraphTests.cpp | 377 | ||||
| -rw-r--r-- | NvBlast/test/src/unit/MultithreadingTests.cpp | 395 | ||||
| -rw-r--r-- | NvBlast/test/src/unit/SyncTests.cpp | 309 | ||||
| -rw-r--r-- | NvBlast/test/src/unit/TkCompositeTests.cpp | 739 | ||||
| -rw-r--r-- | NvBlast/test/src/unit/TkTests.cpp | 1528 | ||||
| -rw-r--r-- | NvBlast/test/src/utils/TaskDispatcher.h | 191 | ||||
| -rw-r--r-- | NvBlast/test/src/utils/TestAssets.cpp | 288 | ||||
| -rw-r--r-- | NvBlast/test/src/utils/TestAssets.h | 40 | ||||
| -rw-r--r-- | NvBlast/test/src/utils/TestProfiler.h | 27 |
18 files changed, 8409 insertions, 0 deletions
diff --git a/NvBlast/test/src/AlignedAllocator.h b/NvBlast/test/src/AlignedAllocator.h new file mode 100644 index 0000000..f8bf3a5 --- /dev/null +++ b/NvBlast/test/src/AlignedAllocator.h @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved. +* +* NVIDIA CORPORATION and its licensors retain all intellectual property +* and proprietary rights in and to this software, 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. +*/ + +#ifndef ALIGNEDALLOCATOR_H +#define ALIGNEDALLOCATOR_H + +#include "NvPreprocessor.h" + + +/** +Aligned allocation. First template argument has the signature of stdlib malloc. + +Example using malloc and 16-byte alignment: + +// b will lie on a 16-byte boundary and point to 50 bytes of usable memory +void* b = alignedAlloc<malloc,16>(50); +*/ +template<void*(*allocFn)(size_t), int A> +void* alignedAlloc(size_t size) +{ + NV_COMPILE_TIME_ASSERT(A > 0 && A <= 256); + unsigned char* mem = (unsigned char*)allocFn(size + A); + const unsigned char offset = (unsigned char)((uintptr_t)A - (uintptr_t)mem % A - 1); + mem += offset; + *mem++ = offset; + return mem; +}; + + +/** +Version of alignedAlloc specialized 16-byte aligned allocation. +*/ +template<void*(*allocFn)(size_t)> +void* alignedAlloc(size_t size) +{ + return alignedAlloc<allocFn, 16>(size); +} + + +/** +Aligned deallocation. First template argument has the signature of stdlib free. + +Memory freed using this function MUST have been allocated using alignedAlloc. + +Example using free: + +// Using the memory pointer b from the example above (for alignedAlloc) +alignedFree<free>(b); +*/ +template<void(*freeFn)(void*)> +void alignedFree(void* block) +{ + if (block != nullptr) + { + unsigned char* mem = (unsigned char*)block; + const unsigned char offset = *--mem; + freeFn(mem - offset); + } +}; + + +#endif // ALIGNEDALLOCATOR_H diff --git a/NvBlast/test/src/BlastBaseTest.h b/NvBlast/test/src/BlastBaseTest.h new file mode 100644 index 0000000..2881645 --- /dev/null +++ b/NvBlast/test/src/BlastBaseTest.h @@ -0,0 +1,159 @@ +/* +* Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved. +* +* NVIDIA CORPORATION and its licensors retain all intellectual property +* and proprietary rights in and to this software, 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. +*/ + +#ifndef BLASTBASETEST_H +#define BLASTBASETEST_H + + +#include "NvBlastTkFramework.h" + +#include "gtest/gtest.h" + +#include "NvBlast.h" + +#include "AlignedAllocator.h" + +#include "TestAssets.h" + +#include "PxErrorCallback.h" +#include "PxAllocatorCallback.h" + + +#include <ostream> + + +template<int FailLevel, int Verbosity> +class BlastBaseTest : public testing::Test, public physx::PxAllocatorCallback, public physx::PxErrorCallback +{ +public: + static void* tkAlloc(size_t size) + { + Nv::Blast::TkFramework* fw = NvBlastTkFrameworkGet(); + if (fw != nullptr) + { + return fw->getAllocatorCallback().allocate(size, nullptr, __FILE__, __LINE__); + } + else + { + return std::malloc(size); + } + } + + static void tkFree(void* mem) + { + Nv::Blast::TkFramework* fw = NvBlastTkFrameworkGet(); + if (fw != nullptr) + { + fw->getAllocatorCallback().deallocate(mem); + } + else + { + std::free(mem); + } + } + + // A zeroing alloc with the same signature as malloc + static void* alloc(size_t size) + { + return memset(alignedAlloc<tkAlloc>(size), 0, size); + } + + static void free(void* mem) + { + alignedFree<tkFree>(mem); + } + + // Message log for blast functions + static void messageLog(int type, const char* msg, const char* file, int line) + { + if (FailLevel >= type) + { + switch (type) + { + case NvBlastMessage::Error: EXPECT_TRUE(false) << "NvBlast Error message in " << file << "(" << line << "): " << msg << "\n"; break; + case NvBlastMessage::Warning: EXPECT_TRUE(false) << "NvBlast Warning message in " << file << "(" << line << "): " << msg << "\n"; break; + case NvBlastMessage::Info: EXPECT_TRUE(false) << "NvBlast Info message in " << file << "(" << line << "): " << msg << "\n"; break; + case NvBlastMessage::Debug: EXPECT_TRUE(false) << "NvBlast Debug message in " << file << "(" << line << "): " << msg << "\n"; break; + } + } + else + if (Verbosity > 0) + { + switch (type) + { + case NvBlastMessage::Error: std::cout << "NvBlast Error message in " << file << "(" << line << "): " << msg << "\n"; break; + case NvBlastMessage::Warning: std::cout << "NvBlast Warning message in " << file << "(" << line << "): " << msg << "\n"; break; + case NvBlastMessage::Info: std::cout << "NvBlast Info message in " << file << "(" << line << "): " << msg << "\n"; break; + case NvBlastMessage::Debug: std::cout << "NvBlast Debug message in " << file << "(" << line << "): " << msg << "\n"; break; + } + } + } + + // PxAllocatorCallback interface + virtual void* allocate(size_t size, const char* typeName, const char* filename, int line) override + { + NV_UNUSED(typeName); + NV_UNUSED(filename); + NV_UNUSED(line); + return alignedAlloc<std::malloc>(size); + } + + // PxAllocatorCallback interface + virtual void deallocate(void* ptr) override + { + alignedFree<std::free>(ptr); + } + + // PxErrorCallback interface + virtual void reportError(physx::PxErrorCode::Enum code, const char* message, const char* file, int line) override + { + uint32_t failMask = 0; + switch (FailLevel) + { + case NvBlastMessage::Debug: + case NvBlastMessage::Info: failMask |= physx::PxErrorCode::eDEBUG_INFO; + case NvBlastMessage::Warning: failMask |= physx::PxErrorCode::eDEBUG_WARNING; + case NvBlastMessage::Error: failMask |= physx::PxErrorCode::eABORT | physx::PxErrorCode::eABORT | physx::PxErrorCode::eINTERNAL_ERROR | physx::PxErrorCode::eOUT_OF_MEMORY | physx::PxErrorCode::eINVALID_OPERATION | physx::PxErrorCode::eINVALID_PARAMETER; + } + + if (!(failMask & code) && Verbosity <= 0) + { + return; + } + + std::string output = "NvBlast Test "; + switch (code) + { + case physx::PxErrorCode::eNO_ERROR: break; + case physx::PxErrorCode::eDEBUG_INFO: output += "Debug Info"; break; + case physx::PxErrorCode::eDEBUG_WARNING: output += "Debug Warning"; break; + case physx::PxErrorCode::eINVALID_PARAMETER: output += "Invalid Parameter"; break; + case physx::PxErrorCode::eINVALID_OPERATION: output += "Invalid Operation"; break; + case physx::PxErrorCode::eOUT_OF_MEMORY: output += "Out of Memory"; break; + case physx::PxErrorCode::eINTERNAL_ERROR: output += "Internal Error"; break; + case physx::PxErrorCode::eABORT: output += "Abort"; break; + case physx::PxErrorCode::ePERF_WARNING: output += "Perf Warning"; break; + default: FAIL(); + } + output += std::string(" message in ") + file + "(" + std::to_string(line) + "): " + message + "\n"; + + if (failMask & code) + { + EXPECT_TRUE(false) << output; + } + else + { + std::cout << output; + } + } +}; + + +#endif // #ifndef BLASTBASETEST_H diff --git a/NvBlast/test/src/TkBaseTest.h b/NvBlast/test/src/TkBaseTest.h new file mode 100644 index 0000000..9ea632b --- /dev/null +++ b/NvBlast/test/src/TkBaseTest.h @@ -0,0 +1,467 @@ +/* +* Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved. +* +* NVIDIA CORPORATION and its licensors retain all intellectual property +* and proprietary rights in and to this software, 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. +*/ + +#ifndef TKBASETEST_H +#define TKBASETEST_H + + +#include "NvBlastTk.h" +#include "NvBlastTkActor.h" + +#include "BlastBaseTest.h" + +#include "NvBlastExtDamageShaders.h" + +#include "NvBlastIndexFns.h" +#include "NvBlastProfiler.h" +#include "TestProfiler.h" + +#include "PxAllocatorCallback.h" +#include "PxErrorCallback.h" +#include "PxCpuDispatcher.h" +#include "PxTask.h" +#include "PxFoundation.h" +#include "PxFoundationVersion.h" + +#include <thread> +#include <algorithm> +#include <queue> +#include <mutex> +#include <condition_variable> +#include <atomic> + + +#define USE_PHYSX_DISPATCHER 0 + +#if USE_PHYSX_DISPATCHER +#include "PxDefaultCpuDispatcher.h" +#endif + + +using namespace Nv::Blast; +using namespace physx; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Helpers +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +NV_INLINE void ExpectArrayMatch(TkObject** arr0, size_t size0, TkObject** arr1, size_t size1) +{ + EXPECT_TRUE(size0 == size1); + std::set<TkObject*> set0(arr0, arr0 + size0); + std::set<TkObject*> set1(arr1, arr1 + size1); + EXPECT_TRUE(set0 == set1); +} + +class TestCpuDispatcher : public physx::PxCpuDispatcher +{ + struct SharedContext + { + std::queue<PxBaseTask*> workQueue; + std::condition_variable cv; + std::mutex mutex; + std::atomic<bool> quit; + }; + + void submitTask(PxBaseTask& task) override + { + if (m_threads.size() > 0) + { + std::unique_lock<std::mutex> lk(m_context.mutex); + m_context.workQueue.push(&task); + lk.unlock(); + m_context.cv.notify_one(); + } + else + { + TEST_ZONE_BEGIN(task.getName()); + task.run(); + TEST_ZONE_END(task.getName()); + task.release(); + } + } + + uint32_t getWorkerCount() const override { return (uint32_t)m_threads.size(); } + + static void execute(SharedContext& context) + { + while (!context.quit) + { + std::unique_lock<std::mutex> lk(context.mutex); + if (!context.workQueue.empty()) + { + PxBaseTask& task = *context.workQueue.front(); + context.workQueue.pop(); + lk.unlock(); + TEST_ZONE_BEGIN(task.getName()); + task.run(); + TEST_ZONE_END(task.getName()); + task.release(); + } + else + { + // shared variables must be modified under the mutex in order + // to correctly publish the modification to the waiting thread + context.cv.wait(lk, [&]{ return !context.workQueue.empty() || context.quit; }); + } + } + } + + SharedContext m_context; + std::vector<std::thread> m_threads; + +public: + TestCpuDispatcher(uint32_t numWorkers) + { + m_context.quit = false; + for (uint32_t i = 0; i < numWorkers; ++i) + { + m_threads.push_back(std::thread(execute, std::ref(m_context))); + } + } + + void release() + { + std::unique_lock<std::mutex> lk(m_context.mutex); + m_context.quit = true; + lk.unlock(); + m_context.cv.notify_all(); + for (std::thread& t : m_threads) + { + t.join(); + } + delete this; + } +}; + + +struct CSParams +{ + CSParams(uint32_t axis_, float coord_) : axis(axis_), coord(coord_) {} + uint32_t axis; + float coord; +}; + +static void CubeSlicer(NvBlastFractureBuffers* outbuf, const NvBlastGraphShaderActor* actor, const NvBlastProgramParams* params) +{ + uint32_t bondFractureCount = 0; + uint32_t bondFractureCountMax = outbuf->bondFractureCount; + + for (size_t i = 0; i < params->damageDescCount; ++i) + { + const CSParams& p = (reinterpret_cast<const CSParams*> (params->damageDescBuffer))[i]; + + uint32_t currentNodeIndex = actor->firstGraphNodeIndex; + while (!Nv::Blast::isInvalidIndex(currentNodeIndex)) + { + for (uint32_t adj = actor->adjacencyPartition[currentNodeIndex]; adj < actor->adjacencyPartition[currentNodeIndex + 1]; ++adj) + { + if (currentNodeIndex < actor->adjacentNodeIndices[adj]) + { + if (actor->assetBonds[actor->adjacentBondIndices[adj]].centroid[p.axis] == p.coord && bondFractureCount < bondFractureCountMax) + { + NvBlastBondFractureData& data = outbuf->bondFractures[bondFractureCount++]; + data.userdata = 0; + data.nodeIndex0 = currentNodeIndex; + data.nodeIndex1 = actor->adjacentNodeIndices[adj]; + data.health = 1.0f; + } + } + } + currentNodeIndex = actor->graphNodeIndexLinks[currentNodeIndex]; + } + } + + outbuf->bondFractureCount = bondFractureCount; + outbuf->chunkFractureCount = 0; + + //printf("slicer outcount %d\n", bondFractureCount); +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TkBaseTest Class +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +template<int FailLevel, int Verbosity> +class TkBaseTest : public BlastBaseTest<FailLevel, Verbosity> +{ +public: + TkBaseTest() : m_cpuDispatcher(), m_taskman(nullptr), m_foundation(nullptr) + { + } + + virtual void SetUp() override + { + m_foundation = PxCreateFoundation(PX_FOUNDATION_VERSION, *this, *this); + + NvBlastProfilerEnablePlatform(true); + +#if USE_PHYSX_DISPATCHER + PxU32 affinity[] = { 1, 2, 4, 8 }; + m_cpuDispatcher = PxDefaultCpuDispatcherCreate(4, affinity); + m_cpuDispatcher->setRunProfiled(false); +#else + m_cpuDispatcher = new TestCpuDispatcher(4); +#endif + + m_taskman = PxTaskManager::createTaskManager(*this, m_cpuDispatcher, nullptr); + } + + virtual void TearDown() override + { + m_cpuDispatcher->release(); + if (m_taskman) m_taskman->release(); + if (m_foundation) m_foundation->release(); + } + + void createFramework() + { + TkFrameworkDesc desc; + desc.allocatorCallback = this; + desc.errorCallback = this; + TkFramework* framework = NvBlastTkFrameworkCreate(desc); + EXPECT_TRUE(framework != nullptr); + EXPECT_EQ(framework, NvBlastTkFrameworkGet()); + } + + void releaseFramework() + { + TkFramework* framework = NvBlastTkFrameworkGet(); + framework->release(); + EXPECT_TRUE(NvBlastTkFrameworkGet() == nullptr); + } + + void createTestAssets(bool addInternalJoints = false) + { + const uint8_t cube1BondDescFlags_internalJoints[12] = + { + TkAssetDesc::NoFlags, + TkAssetDesc::NoFlags, + TkAssetDesc::NoFlags, + TkAssetDesc::NoFlags, + + TkAssetDesc::NoFlags, + TkAssetDesc::NoFlags, + TkAssetDesc::NoFlags, + TkAssetDesc::NoFlags, + + TkAssetDesc::BondJointed, + TkAssetDesc::BondJointed, + TkAssetDesc::BondJointed, + TkAssetDesc::BondJointed + }; + + const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); + TkFramework* framework = NvBlastTkFrameworkGet(); + for (uint32_t i = 0; i < assetDescCount; ++i) + { + TkAssetDesc desc; + reinterpret_cast<NvBlastAssetDesc&>(desc) = g_assetDescs[i]; + desc.bondFlags = addInternalJoints ? cube1BondDescFlags_internalJoints : nullptr; + testAssets.push_back(framework->createAsset(desc)); + EXPECT_TRUE(testAssets[i] != nullptr); + } + } + + TkAsset* createCubeAsset(size_t maxDepth, size_t width, int32_t supportDepth = -1, bool addInternalJoints = false) + { + TkFramework* framework = NvBlastTkFrameworkGet(); + GeneratorAsset cube; + TkAssetDesc assetDesc; + generateCube(cube, assetDesc, maxDepth, width, supportDepth); + std::vector<uint8_t> bondFlags(assetDesc.bondCount); + std::fill(bondFlags.begin(), bondFlags.end(), addInternalJoints ? 1 : 0); + assetDesc.bondFlags = bondFlags.data(); + TkAsset* cubeAsset = framework->createAsset(assetDesc); + testAssets.push_back(cubeAsset); + return cubeAsset; + } + + void releaseTestAssets() + { + for (uint32_t i = 0; i < testAssets.size(); ++i) + { + testAssets[i]->release(); + } + testAssets.clear(); + } + + NvBlastExtRadialDamageDesc getRadialDamageDesc(float x, float y, float z, float minRadius = 10.0f, float maxRadius = 10.0f, float compressive = 10.0f) + { + NvBlastExtRadialDamageDesc desc; + desc.position[0] = x; + desc.position[1] = y; + desc.position[2] = z; + + desc.minRadius = minRadius; + desc.maxRadius = maxRadius; + desc.compressive = compressive; + return desc; + } + + NvBlastExtShearDamageDesc getShearDamageDesc(float x, float y, float z, float shearX = 1.0f, float shearY = 0.0f, float shearZ = 0.0f) + { + NvBlastExtShearDamageDesc desc; + desc.position[0] = x; + desc.position[1] = y; + desc.position[2] = z; + + desc.shear[0] = shearX; + desc.shear[1] = shearY; + desc.shear[2] = shearZ; + return desc; + } + + static const NvBlastDamageProgram& getCubeSlicerProgram() + { + static NvBlastDamageProgram program = { CubeSlicer, nullptr }; + return program; + } + + static const NvBlastDamageProgram& getFalloffProgram() + { + static NvBlastDamageProgram program = { NvBlastExtFalloffGraphShader, NvBlastExtFalloffSubgraphShader }; + return program; + } + + static const NvBlastDamageProgram& getShearProgram() + { + static NvBlastDamageProgram program = { NvBlastExtShearGraphShader, NvBlastExtShearSubgraphShader }; + return program; + } + + static const NvBlastExtMaterial* getDefaultMaterial() + { + static NvBlastExtMaterial material = { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f + }; + return &material; + }; + + TkFamily* familySerialization(TkFamily* family); + + + std::vector<TkAsset*> testAssets; + +#if USE_PHYSX_DISPATCHER + PxDefaultCpuDispatcher* m_cpuDispatcher; +#else + TestCpuDispatcher* m_cpuDispatcher; +#endif + + PxTaskManager* m_taskman; + PxFoundation* m_foundation; +}; + + +#define TkPxErrorMask (PxErrorCode::eINVALID_PARAMETER | PxErrorCode::eINVALID_OPERATION | PxErrorCode::eOUT_OF_MEMORY | PxErrorCode::eINTERNAL_ERROR | PxErrorCode::eABORT) +#define TkPxWarningMask (PxErrorCode::eDEBUG_WARNING | PxErrorCode::ePERF_WARNING) + +typedef TkBaseTest<NvBlastMessage::Error, 1> TkTestAllowWarnings; +typedef TkBaseTest<NvBlastMessage::Warning, 1> TkTestStrict; + + +class TestFamilyTracker : public TkEventListener +{ +public: + TestFamilyTracker() {} + + typedef std::pair<TkFamily*, uint32_t> Actor; + + virtual void receive(const TkEvent* events, uint32_t eventCount) override + { + TEST_ZONE_BEGIN("TestFamilyTracker"); + for (size_t i = 0; i < eventCount; ++i) + { + const TkEvent& e = events[i]; + switch (e.type) + { + case (TkEvent::Split): + { + const TkSplitEvent* splitEvent = e.getPayload<TkSplitEvent>(); + EXPECT_EQ((size_t)1, actors.erase(Actor(splitEvent->parentData.family, splitEvent->parentData.index))); + for (size_t i = 0; i < splitEvent->numChildren; ++i) + { + TkActor* a = splitEvent->children[i]; + EXPECT_TRUE(actors.insert(Actor(&a->getFamily(), a->getIndex())).second); + } + break; + } + case (TkEvent::FractureCommand): + { + const TkFractureCommands* fracEvent = e.getPayload<TkFractureCommands>(); + EXPECT_TRUE(!isInvalidIndex(fracEvent->tkActorData.index)); +#if 0 + printf("chunks broken: %d\n", fracEvent->buffers.chunkFractureCount); + printf("bonds broken: %d\n", fracEvent->buffers.bondFractureCount); + for (uint32_t t = 0; t < fracEvent->buffers.bondFractureCount; t++) + { + //printf("%x ", fracEvent->buffers.bondFractures[t].userdata); + } + //printf("\n"); +#endif + break; + } + case (TkEvent::FractureEvent): + { + const TkFractureEvents* fracEvent = e.getPayload<TkFractureEvents>(); + EXPECT_TRUE(!isInvalidIndex(fracEvent->tkActorData.index)); + break; + } + case (TkEvent::JointUpdate): + { + const TkJointUpdateEvent* jointEvent = e.getPayload<TkJointUpdateEvent>(); + TkJoint* joint = jointEvent->joint; + EXPECT_TRUE(joint != nullptr); + + switch (jointEvent->subtype) + { + case TkJointUpdateEvent::External: + EXPECT_TRUE(joints.end() == joints.find(joint)); // We should not have this joint yet + joints.insert(joint); + break; + case TkJointUpdateEvent::Changed: + break; + case TkJointUpdateEvent::Unreferenced: + EXPECT_EQ(1, joints.erase(joint)); + joint->release(); + break; + } + break; + } + default: + break; + } + } + TEST_ZONE_END("TestFamilyTracker"); + } + + void insertActor(const TkActor* actor) + { + actors.insert(TestFamilyTracker::Actor(&actor->getFamily(), actor->getIndex())); + } + + void eraseActor(const TkActor* actor) + { + actors.erase(TestFamilyTracker::Actor(&actor->getFamily(), actor->getIndex())); + } + + std::set<Actor> actors; + std::set<TkJoint*> joints; +}; + + +#endif // #ifndef TKBASETEST_H diff --git a/NvBlast/test/src/perf/BlastBasePerfTest.h b/NvBlast/test/src/perf/BlastBasePerfTest.h new file mode 100644 index 0000000..2a4701d --- /dev/null +++ b/NvBlast/test/src/perf/BlastBasePerfTest.h @@ -0,0 +1,374 @@ +/* +* Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved. +* +* NVIDIA CORPORATION and its licensors retain all intellectual property +* and proprietary rights in and to this software, 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. +*/ + +#ifndef BLASTBASEPERFTEST_H +#define BLASTBASEPERFTEST_H + + +#include "BlastBaseTest.h" +#include <fstream> + +#include <algorithm> +#include <map> + + +template<typename T> +class DataCollection +{ +public: + struct Stats + { + double m_mean; + double m_sdev; + double m_min; + double m_max; + + Stats() + { + reset(); + } + + void reset() + { + m_mean = 0.0; + m_sdev = 0.0; + m_min = std::numeric_limits<double>().max(); + m_max = -std::numeric_limits<double>().max(); + } + }; + + struct DataSet + { + std::vector<T> m_data; + Stats m_stats; + + void calculateStats() + { + m_stats.reset(); + if (m_data.size() > 0) + { + if (m_data.size() > 1) // Remove top half of values to eliminate outliers + { + std::sort(m_data.begin(), m_data.end()); + m_data.resize(m_data.size() / 2); + } + for (size_t i = 0; i < m_data.size(); ++i) + { + m_stats.m_mean += m_data[i]; + m_stats.m_min = std::min(m_stats.m_min, (double)m_data[i]); + m_stats.m_max = std::max(m_stats.m_max, (double)m_data[i]); + } + m_stats.m_mean /= m_data.size(); + if (m_data.size() > 1) + { + for (size_t i = 0; i < m_data.size(); ++i) + { + m_stats.m_sdev += pow(m_data[i] - m_stats.m_mean, 2); + } + m_stats.m_sdev = sqrt(m_stats.m_sdev / (m_data.size() - 1)); + } + } + } + }; + + DataSet& getDataSet(const std::string& name) + { + auto entry = m_lookup.find(name); + if (entry != m_lookup.end()) + { + return m_dataSets[entry->second]; + } + m_lookup[name] = m_dataSets.size(); + m_dataSets.push_back(DataSet()); + return m_dataSets.back(); + } + + bool dataSetExists(const std::string& name) const + { + return m_lookup.find(name) != m_lookup.end(); + } + + void calculateStats() + { + for (size_t i = 0; i < m_dataSets.size(); ++i) + { + m_dataSets[i].calculateStats(); + } + } + + void test(DataCollection<int64_t>& calibration, double relativeThreshold = 0.10, double tickThreshold = 100.0) + { + for (auto entry = m_lookup.begin(); entry != m_lookup.end(); ++entry) + { + const std::string& name = entry->first; + DataCollection<int64_t>::DataSet& data = m_dataSets[entry->second]; + data.calculateStats(); + + if (!calibration.dataSetExists(name)) + { + FAIL() << "PerfTest is not calibrated!" << std::endl << "Missing DataSet: " << name << std::endl; + } + const DataCollection<int64_t>::DataSet& cal = calibration.getDataSet(name); + const double calMin = cal.m_stats.m_min; + + if (data.m_stats.m_min > (1.0 + relativeThreshold) * calMin && data.m_stats.m_min - calMin > tickThreshold) + { + std::cout << name << ":" << std::endl; + std::cout << "PERF - : Timing (" << data.m_stats.m_min << ") exceeds recorded min (" << calMin << ") by more than allowed relative threshold (" << relativeThreshold*100 << "%) and absolute threshold (" << tickThreshold << " ticks)." << std::endl; + EXPECT_FALSE(data.m_stats.m_min > (1.0 + relativeThreshold) * calMin && data.m_stats.m_min - calMin > tickThreshold) + << name << ":" << std::endl + << "PERF - : Timing (" << data.m_stats.m_min << ") exceeds recorded min (" << calMin << ") by more than allowed relative threshold (" << relativeThreshold * 100 << "%) and absolute threshold (" << tickThreshold << " ticks)." << std::endl; + } + else + if (data.m_stats.m_min < (1.0 - relativeThreshold) * calMin && data.m_stats.m_min - calMin < -tickThreshold) + { + std::cout << name << ":" << std::endl; + std::cout << "PERF + : Timing (" << data.m_stats.m_min << ") is less than the recorded min (" << calMin << ") by more than the relative threshold (" << relativeThreshold * 100 << "%) and absolute threshold (" << tickThreshold << " ticks)." << std::endl; + } + } + } + + size_t size() const + { + return m_dataSets.size(); + } + + void clear() + { + m_lookup.clear(); + m_dataSets.clear(); + } + + template<class S> + friend std::istream& operator >> (std::istream& stream, DataCollection<S>& c); + + template<class S> + friend std::ostream& operator << (std::ostream& stream, const DataCollection<S>& c); + +private: + std::map<std::string, size_t> m_lookup; + std::vector< DataSet > m_dataSets; +}; + +template<typename T> +std::istream& operator >> (std::istream& stream, DataCollection<T>& c) +{ + std::string name; + while (!stream.eof()) + { + std::getline(stream >> std::ws, name); + typename DataCollection<T>::DataSet& dataSet = c.getDataSet(name); + stream >> dataSet.m_stats.m_mean >> dataSet.m_stats.m_sdev >> dataSet.m_stats.m_min >> dataSet.m_stats.m_max >> std::ws; + } + return stream; +} + +template<typename T> +std::ostream& operator << (std::ostream& stream, const DataCollection<T>& c) +{ + for (auto entry = c.m_lookup.begin(); entry != c.m_lookup.end(); ++entry) + { + const std::string& name = entry->first; + stream << name.c_str() << std::endl; + const typename DataCollection<T>::DataSet& data = c.m_dataSets[entry->second]; + stream << data.m_stats.m_mean << " " << data.m_stats.m_sdev << " " << data.m_stats.m_min << " " << data.m_stats.m_max << std::endl; + } + return stream; +} + + +static const char* getPlatformSuffix() +{ +#if NV_WIN32 + return "win32"; +#elif NV_WIN64 + return "win64"; +#elif NV_XBOXONE + return "xb1"; +#elif NV_PS4 + return "ps4"; +#elif NV_LINUX + #if NV_X64 + return "linux64"; + #else + return "linux32"; + #endif +#else + return "gen"; +#endif +} + +static const char* getPlatformRoot() +{ +#if NV_PS4 + return "/app0/"; +#elif NV_XBOXONE + return "G:/"; +#else + return "../../"; +#endif +} + +static std::string defaultRelativeDataPath() +{ + const char* dataDir = "test/data/"; + + std::string rootDir = getPlatformRoot(); + return rootDir + dataDir + getPlatformSuffix() + "/"; +} + +class PerfTestEngine +{ +public: + PerfTestEngine(const char* collectionName) : m_calibrate(false) + { + m_filename = defaultRelativeDataPath() + std::string(collectionName) + "_" + getPlatformSuffix() + ".cal"; + + auto argvs = testing::internal::GetArgvs(); + size_t argCount = argvs.size(); + + for (size_t argNum = 0; argNum < argCount; ++argNum) + { + if (argvs[argNum] == "-calibrate") + { + m_calibrate = true; + } + else + if (argvs[argNum] == "-calPath") + { + if (++argNum < argCount) + { + m_filename = argvs[argNum]; + } + } + } + + if (!m_calibrate) + { + std::ifstream in; + in.open(m_filename); + if (in.is_open()) + { + std::string name; + std::getline(in, name); // Eat header + std::getline(in, name); // Eat header (2 lines) + in >> m_dataCalibration; + in.close(); + } + m_calibrate = m_dataCalibration.size() == 0; + } + + if (m_calibrate) + { + std::ofstream out; + out.open(m_filename); + if (out.is_open()) + { + out << "Format: timing name (whole line)" << std::endl << "timing mean s.d. min max" << std::endl; // Header (2 lines) + out.close(); + } + } + + if (m_calibrate) + { + std::cout << "******** Calibration Mode ********\n"; + } + else + { + std::cout << "******** Test Mode ********\n"; + std::cout << "Read calibration data from " << m_filename << std::endl; + } + } + + void endTest() + { + if (m_calibrate) + { + m_dataTempCollection.calculateStats(); + std::ofstream out; + out.open(m_filename, std::ofstream::app); + if (out.is_open()) + { + out << m_dataTempCollection; + out.close(); + std::cout << "Calibration stats written to " << m_filename << std::endl; + } + else + { + std::cout << "Failed to open calibration file " << m_filename << ". Stats not written." << std::endl; + FAIL() << "Failed to open calibration file " << m_filename << ". Stats not written." << std::endl; + } + } + else + { + m_dataTempCollection.test(m_dataCalibration); + } + m_dataTempCollection.clear(); + } + + void reportData(const std::string& name, int64_t data) + { + m_dataTempCollection.getDataSet(name).m_data.push_back(data); + } + +private: + std::string m_filename; + bool m_calibrate; + DataCollection<int64_t> m_dataTempCollection; + DataCollection<int64_t> m_dataCalibration; +}; + + +template<int FailLevel, int Verbosity> +class BlastBasePerfTest : public BlastBaseTest<FailLevel, Verbosity> +{ +public: + /** + This function allows to create/destroy and get PerfTestEngine in local static variable (works header only). + It allows to have PeftTestEngine alive through life span of gtest TestCase. + */ + static PerfTestEngine* getEngineDeadOrAlive(bool alive = true) + { + static PerfTestEngine* engine = nullptr; + if (alive && !engine) + { + engine = new PerfTestEngine(::testing::UnitTest::GetInstance()->current_test_case()->name()); + } + else if (!alive && engine) + { + delete engine; + engine = nullptr; + } + return engine; + } + + static void SetUpTestCase() + { + getEngineDeadOrAlive(); + } + + static void TearDownTestCase() + { + getEngineDeadOrAlive(false); + } + + void TearDown() override + { + getEngineDeadOrAlive()->endTest(); + } + + void reportData(const std::string& name, int64_t data) + { + getEngineDeadOrAlive()->reportData(name, data); + } +}; + + +#endif // #ifndef BLASTBASEPERFTEST_H diff --git a/NvBlast/test/src/perf/SolverPerfTests.cpp b/NvBlast/test/src/perf/SolverPerfTests.cpp new file mode 100644 index 0000000..8a53c97 --- /dev/null +++ b/NvBlast/test/src/perf/SolverPerfTests.cpp @@ -0,0 +1,211 @@ +#include "BlastBasePerfTest.h" +#include "TestAssets.h" +#include "NvBlastExtDamageShaders.h" +#include <memory> + + +static void blast +( + std::set<NvBlastActor*>& actorsToDamage, + GeneratorAsset* testAsset, + GeneratorAsset::Vec3 localPos, + float minRadius, float maxRadius, + float compressiveDamage, + NvBlastTimers& timers +) +{ + std::vector<NvBlastChunkFractureData> chunkEvents; /* num lower-support chunks + bonds */ + std::vector<NvBlastBondFractureData> bondEvents; /* num lower-support chunks + bonds */ + chunkEvents.resize(testAsset->solverChunks.size()); + bondEvents.resize(testAsset->solverBonds.size()); + + NvBlastExtRadialDamageDesc damage[] = { + { + compressiveDamage, + { localPos.x, localPos.y, localPos.z }, + minRadius, + maxRadius + } + }; + + NvBlastProgramParams programParams = + { + &damage, + 1, + nullptr + }; + + NvBlastDamageProgram program = { + NvBlastExtFalloffGraphShader, + nullptr + }; + + std::vector<char> splitScratch; + std::vector<NvBlastActor*> newActors(testAsset->solverChunks.size()); + + size_t totalNewActorsCount = 0; + for (std::set<NvBlastActor*>::iterator k = actorsToDamage.begin(); k != actorsToDamage.end();) + { + NvBlastActor* actor = *k; + + NvBlastFractureBuffers events = { (uint32_t)bondEvents.size(), (uint32_t)chunkEvents.size(), bondEvents.data(), chunkEvents.data() }; + + NvBlastActorGenerateFracture(&events, actor, program, &programParams, nullptr, &timers); + NvBlastActorApplyFracture(&events, actor, &events, nullptr, &timers); + + bool removeActor = false; + + if (events.bondFractureCount + events.chunkFractureCount > 0) + { + splitScratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, nullptr)); + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = &newActors[totalNewActorsCount]; + const size_t bufferSize = newActors.size() - totalNewActorsCount; + const size_t newActorsCount = NvBlastActorSplit(&result, actor, (uint32_t)bufferSize, splitScratch.data(), nullptr, &timers); + totalNewActorsCount += newActorsCount; + removeActor = newActorsCount > 0; + } + + if (removeActor) + { + k = actorsToDamage.erase(k); + } + else + { + ++k; + } + } + + for (size_t i = 0; i < totalNewActorsCount; ++i) + { + actorsToDamage.insert(newActors[i]); + } +} + +typedef BlastBasePerfTest<NvBlastMessage::Warning, 1> BlastBasePerfTestStrict; + +class PerfTest : public BlastBasePerfTestStrict +{ +public: + void damageLeafSupportActors(const char* testName, uint32_t assetCount, uint32_t familyCount, uint32_t damageCount) + { + const float relativeDamageRadius = 0.2f; + const float compressiveDamage = 1.0f; + const uint32_t minChunkCount = 100; + const uint32_t maxChunkCount = 10000; + + srand(0); + + for (uint32_t assetNum = 0; assetNum < assetCount; ++assetNum) + { + CubeAssetGenerator::Settings settings; + settings.extents = GeneratorAsset::Vec3(1, 1, 1); + CubeAssetGenerator::DepthInfo depthInfo; + depthInfo.slicesPerAxis = GeneratorAsset::Vec3(1, 1, 1); + depthInfo.flag = NvBlastChunkDesc::Flags::NoFlags; + settings.depths.push_back(depthInfo); + uint32_t chunkCount = 1; + while (chunkCount < minChunkCount) + { + uint32_t chunkMul; + do + { + depthInfo.slicesPerAxis = GeneratorAsset::Vec3((float)(1 + rand() % 4), (float)(1 + rand() % 4), (float)(1 + rand() % 4)); + chunkMul = (uint32_t)(depthInfo.slicesPerAxis.x * depthInfo.slicesPerAxis.y * depthInfo.slicesPerAxis.z); + } while (chunkMul == 1); + if (chunkCount*chunkMul > maxChunkCount) + { + break; + } + chunkCount *= chunkMul; + settings.depths.push_back(depthInfo); + settings.extents = settings.extents * depthInfo.slicesPerAxis; + } + settings.depths.back().flag = NvBlastChunkDesc::SupportFlag; // Leaves are support + + // Make largest direction unit size + settings.extents = settings.extents * (1.0f / std::max(settings.extents.x, std::max(settings.extents.y, settings.extents.z))); + + // Create asset + GeneratorAsset testAsset; + CubeAssetGenerator::generate(testAsset, settings); + + NvBlastAssetDesc desc; + desc.chunkDescs = &testAsset.solverChunks[0]; + desc.chunkCount = (uint32_t)testAsset.solverChunks.size(); + desc.bondDescs = &testAsset.solverBonds[0]; + desc.bondCount = (uint32_t)testAsset.solverBonds.size(); + + { + std::vector<char> 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); + + // Generate familes + for (uint32_t familyNum = 0; familyNum < familyCount; ++familyNum) + { + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = nullptr; + actorDesc.uniformInitialBondHealth = 1.0f; + actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* mem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(mem, asset, messageLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog)); + EXPECT_TRUE(family != nullptr); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], messageLog); + EXPECT_TRUE(actor != nullptr); + + // Generate damage + std::set<NvBlastActor*> actors; + actors.insert(actor); + for (uint32_t damageNum = 0; damageNum < damageCount; ++damageNum) + { + GeneratorAsset::Vec3 localPos = settings.extents*GeneratorAsset::Vec3((float)rand() / RAND_MAX - 0.5f, (float)rand() / RAND_MAX - 0.5f, (float)rand() / RAND_MAX - 0.5f); + + NvBlastTimers timers; + NvBlastTimersReset(&timers); + blast(actors, &testAsset, localPos, relativeDamageRadius, relativeDamageRadius*1.2f, compressiveDamage, timers); + const std::string timingName = std::string(testName) + " asset " + std::to_string(assetNum) + " family " + std::to_string(familyNum) + " damage " + std::to_string(damageNum); + BlastBasePerfTestStrict::reportData(timingName + " material", timers.material); + BlastBasePerfTestStrict::reportData(timingName + " fracture", timers.fracture); + BlastBasePerfTestStrict::reportData(timingName + " island", timers.island); + BlastBasePerfTestStrict::reportData(timingName + " partition", timers.partition); + BlastBasePerfTestStrict::reportData(timingName + " visibility", timers.visibility); + } + + // Release remaining actors + std::for_each(actors.begin(), actors.end(), [](NvBlastActor* a){ NvBlastActorDeactivate(a, messageLog); }); + actors.clear(); + + free(family); + } + + // Release asset data + free(asset); + } + } + } +}; + + +// Tests +TEST_F(PerfTest, DamageLeafSupportActorsTestVisibility) +{ + const int trialCount = 1000; + std::cout << "Trial (of " << trialCount << "): "; + for (int trial = 1; trial <= trialCount; ++trial) + { + if (trial % 100 == 0) + { + std::cout << trial << ".. "; + std::cout.flush(); + } + damageLeafSupportActors(test_info_->name(), 4, 4, 5); + } + std::cout << "done." << std::endl; +} diff --git a/NvBlast/test/src/unit/APITests.cpp b/NvBlast/test/src/unit/APITests.cpp new file mode 100644 index 0000000..c2a4a04 --- /dev/null +++ b/NvBlast/test/src/unit/APITests.cpp @@ -0,0 +1,1354 @@ +#include "BlastBaseTest.h" +#include "NvBlastIndexFns.h" +#include "NvBlastExtDamageShaders.h" + +#include <algorithm> + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Utils / Tests Common +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using namespace Nv::Blast; + +class APITest : public BlastBaseTest < NvBlastMessage::Error, 1 > +{ +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Tests +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +TEST_F(APITest, Basic) +{ + // create asset + const NvBlastAssetDesc& assetDesc = g_assetDescs[0]; + + std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, messageLog)); + void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, messageLog)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), messageLog); + EXPECT_TRUE(asset != nullptr); + + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), messageLog); + EXPECT_TRUE(actor != nullptr); + + NvBlastExtRadialDamageDesc damage[] = { + { + 10.0f, // compressive + { 0.0f, 0.0f, 0.0f }, // position + 4.0f, // min radius - maximum damage + 6.0f // max radius - zero damage + } // linear falloff + }; + + NvBlastBondFractureData outFracture[12]; /*num lower-support chunks + bonds?*/ + + NvBlastFractureBuffers events; + events.bondFractureCount = 12; + events.bondFractures = outFracture; + events.chunkFractureCount = 0; + events.chunkFractures = nullptr; + + NvBlastProgramParams programParams; + programParams.damageDescCount = 1; + programParams.damageDescBuffer = &damage; + + NvBlastDamageProgram program = { + NvBlastExtFalloffGraphShader, + nullptr + }; + + NvBlastActorGenerateFracture(&events, actor, program, &programParams, messageLog, nullptr); + NvBlastActorApplyFracture(&events, actor, &events, messageLog, nullptr); + EXPECT_EQ(12, events.bondFractureCount); + + NvBlastActor* newActors[8]; /* num lower-support chunks? plus space for deletedActor */ + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = newActors; + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, messageLog)); + size_t newActorsCount = NvBlastActorSplit(&result, actor, 8, scratch.data(), messageLog, nullptr); + EXPECT_EQ(8, newActorsCount); + EXPECT_EQ(true, result.deletedActor == actor); + + for (uint32_t i = 0; i < newActorsCount; ++i) + { + const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], messageLog); + EXPECT_TRUE(actorReleaseResult); + } + free(family); + free(asset); +} + +TEST_F(APITest, DamageBondsCompressive) +{ + const size_t bondsCount = 6; + + const NvBlastChunkDesc c_chunks[8] = + { + // centroid volume parent idx flags ID + { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 5 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 6 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 7 } + }; + + const NvBlastBondDesc c_bonds[bondsCount] = + { + { { 1, 2 }, { { -1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 2.0f, 0.0f }, 0 } }, + { { 2, 3 }, { { -1.0f, 0.0f, 0.0f }, 1.0f, { -1.0f, 2.0f, 0.0f }, 0 } }, + { { 3, 4 }, { { 0.0f, -1.0f, 0.0f }, 1.0f, { -2.0f, 1.0f, 0.0f }, 0 } }, + { { 4, 5 }, { { 0.0f, -1.0f, 0.0f }, 1.0f, { -2.0f, -1.0f, 0.0f }, 0 } }, + { { 5, 6 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { -1.0f, -2.0f, 0.0f }, 0 } }, + { { 6, 7 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, -2.0f, 0.0f }, 0 } } + }; + + // create asset + const NvBlastAssetDesc assetDesc = { 8, c_chunks, bondsCount, c_bonds }; + + std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, messageLog)); + void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, messageLog)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), messageLog); + EXPECT_TRUE(asset != nullptr); + + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), messageLog); + EXPECT_TRUE(actor != nullptr); + + // get graph nodes check + std::vector<uint32_t> graphNodeIndices; + graphNodeIndices.resize(NvBlastActorGetGraphNodeCount(actor, nullptr)); + uint32_t graphNodesCount = NvBlastActorGetGraphNodeIndices(graphNodeIndices.data(), (uint32_t)graphNodeIndices.size(), actor, nullptr); + EXPECT_EQ(graphNodesCount, 7); + + NvBlastExtRadialDamageDesc damage = { + 1.0f, // compressive + { 4.0f, 2.0f, 0.0f }, // position + 4.0f, // min radius - maximum damage + 6.0f // max radius - zero damage + }; // linear falloff + + NvBlastBondFractureData outCommands[bondsCount] = { + { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0 }, + { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0 }, + { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0 }, + { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0 }, + { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0 }, + { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0 }, + }; + + NvBlastFractureBuffers commands = { + 6, 0, outCommands, nullptr + }; + + NvBlastProgramParams programParams; + programParams.damageDescCount = 1; + programParams.damageDescBuffer = &damage; + + NvBlastDamageProgram program = { + NvBlastExtFalloffGraphShader, + nullptr + }; + + NvBlastActorGenerateFracture(&commands, actor, program, &programParams, messageLog, nullptr); + + ASSERT_EQ(3, commands.bondFractureCount); + ASSERT_EQ(0, commands.chunkFractureCount); + + // node indices in _graph_ chunks + NvBlastBondFractureData expectedCommand[] = { + { 0, 0, 1, 1.0f }, + { 0, 1, 2, 0.5f }, + { 0, 5, 6, 0.5f } + }; + + for (int i = 0; i < 3; i++) + { + EXPECT_EQ(expectedCommand[i].nodeIndex0, outCommands[i].nodeIndex0); + EXPECT_EQ(expectedCommand[i].nodeIndex1, outCommands[i].nodeIndex1); + EXPECT_EQ(expectedCommand[i].health, outCommands[i].health); + } + + const bool actorReleaseResult = NvBlastActorDeactivate(actor, messageLog); + EXPECT_TRUE(actorReleaseResult); + free(family); + free(asset); +} + +TEST_F(APITest, DirectFractureKillsChunk) +{ + // 1--2 + // | | + // 3--4 <-- kill 4 + + const NvBlastChunkDesc c_chunks[9] = + { + // centroid volume parent idx flags ID + { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 4, NvBlastChunkDesc::NoFlags, 5 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 4, NvBlastChunkDesc::NoFlags, 6 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 4, NvBlastChunkDesc::NoFlags, 7 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 4, NvBlastChunkDesc::NoFlags, 8 }, + }; + + const NvBlastBondDesc c_bonds[4] = + { + { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f, +1.0f, 0.0f }, 0 } }, + { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f, -1.0f, 0.0f }, 0 } }, + { { 1, 3 }, { { 0.0f, -1.0f, 0.0f }, 1.0f, { -1.0f, 0.0f, 0.0f }, 0 } }, + { { 2, 4 }, { { 0.0f, -1.0f, 0.0f }, 1.0f, { +1.0f, 0.0f, 0.0f }, 0 } }, + }; + + NvBlastAssetDesc assetDesc; + assetDesc.chunkCount = 9; + assetDesc.chunkDescs = c_chunks; + assetDesc.bondCount = 4; + assetDesc.bondDescs = c_bonds; + + // create asset + std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, messageLog)); + void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, messageLog)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), messageLog); + EXPECT_TRUE(asset != nullptr); + + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), messageLog); + EXPECT_TRUE(actor != nullptr); + + NvBlastChunkFractureData fractureCmd; + fractureCmd.chunkIndex = 4; + fractureCmd.health = 1.0f; + + NvBlastFractureBuffers commands = { 0, 1, nullptr, &fractureCmd }; + + NvBlastChunkFractureData fractureEvt; + NvBlastFractureBuffers events = { 0, 1, nullptr, &fractureEvt }; + + NvBlastActorApplyFracture(&events, actor, &commands, messageLog, nullptr); + EXPECT_EQ(1, events.chunkFractureCount); + + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, messageLog)); + std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, messageLog)); + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = newActors.data(); + size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), messageLog, nullptr); + newActors.resize(newActorsCount); + + EXPECT_EQ(5, newActorsCount); + EXPECT_EQ(actor, result.deletedActor); + // check newActors contain original actor + EXPECT_TRUE(std::any_of(newActors.begin(), newActors.end(), [&](const NvBlastActor* a) { return actor == a; })); + + for (uint32_t i = 0; i < newActorsCount; ++i) + { + const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], messageLog); + EXPECT_TRUE(actorReleaseResult); + } + free(family); + free(asset); +} + +TEST_F(APITest, DirectFractureKillsIslandRootChunk) +{ + // 1--2 <-- kill 1 + // | | + // 3--4 + + const NvBlastChunkDesc c_chunks[9] = + { + // centroid volume parent idx flags ID + { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 5 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 6 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 7 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 8 }, + }; + + const NvBlastBondDesc c_bonds[4] = + { + { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f, +1.0f, 0.0f }, 0 } }, + { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f, -1.0f, 0.0f }, 0 } }, + { { 1, 3 }, { { 0.0f, -1.0f, 0.0f }, 1.0f, { -1.0f, 0.0f, 0.0f }, 0 } }, + { { 2, 4 }, { { 0.0f, -1.0f, 0.0f }, 1.0f, { +1.0f, 0.0f, 0.0f }, 0 } }, + }; + + NvBlastAssetDesc assetDesc; + assetDesc.chunkCount = 9; + assetDesc.chunkDescs = c_chunks; + assetDesc.bondCount = 4; + assetDesc.bondDescs = c_bonds; + + // create asset + std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, messageLog)); + void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, messageLog)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), messageLog); + EXPECT_TRUE(asset != nullptr); + + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), messageLog); + EXPECT_TRUE(actor != nullptr); + + NvBlastChunkFractureData fractureCmd; + fractureCmd.chunkIndex = 1; + fractureCmd.health = 1.0f; + + NvBlastFractureBuffers commands = { 0, 1, nullptr, &fractureCmd }; + + NvBlastChunkFractureData fractureEvt; + NvBlastFractureBuffers events = { 0, 1, nullptr, &fractureEvt }; + + NvBlastActorApplyFracture(&events, actor, &commands, messageLog, nullptr); + EXPECT_EQ(1, events.chunkFractureCount); + + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, messageLog)); + std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, messageLog)); + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = newActors.data(); + size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), messageLog, nullptr); + newActors.resize(newActorsCount); + + EXPECT_EQ(5, newActorsCount); + EXPECT_EQ(actor, result.deletedActor); + // check if newActors don't contain original actor + EXPECT_TRUE(!std::any_of(newActors.begin(), newActors.end(), [&](const NvBlastActor* a) { return actor == a; })); + + for (uint32_t i = 0; i < newActorsCount; ++i) + { + const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], messageLog); + EXPECT_TRUE(actorReleaseResult); + } + free(family); + free(asset); +} + +TEST_F(APITest, SubsupportFracture) +{ + const NvBlastAssetDesc& assetDesc = g_assetDescs[1]; // cube with subsupport + + // create asset with chunk map + std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, messageLog)); + void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, messageLog)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), messageLog); + EXPECT_TRUE(asset != nullptr); + + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), messageLog); + EXPECT_TRUE(actor != nullptr); + + // first set of fracture commands + NvBlastChunkFractureData f1 = { 0, 1, 2.0f }; + NvBlastChunkFractureData f3 = { 0, 3, 0.5f }; + NvBlastChunkFractureData f5 = { 0, 5, 1.0f }; + NvBlastChunkFractureData f7 = { 0, 7, 1.0f }; + + std::vector<NvBlastChunkFractureData> chunkFractureData; + chunkFractureData.reserve(assetDesc.chunkCount); + chunkFractureData.push_back(f1); + chunkFractureData.push_back(f3); + chunkFractureData.push_back(f5); + chunkFractureData.push_back(f7); + ASSERT_EQ(assetDesc.chunkCount, chunkFractureData.capacity()); + ASSERT_EQ(4, chunkFractureData.size()); + + NvBlastFractureBuffers target = { 0, static_cast<uint32_t>(chunkFractureData.capacity()), nullptr, chunkFractureData.data() }; + { + NvBlastFractureBuffers events = target; + NvBlastFractureBuffers commands = { 0, static_cast<uint32_t>(chunkFractureData.size()), nullptr, chunkFractureData.data() }; + NvBlastActorApplyFracture(&events, actor, &commands, messageLog, nullptr); + ASSERT_EQ(4 + 8, events.chunkFractureCount); // all requested chunks take damage, and the children of one of them + } + + // re-apply same set of commands + chunkFractureData.clear(); + chunkFractureData.reserve(assetDesc.chunkCount); + chunkFractureData.push_back(f1); + chunkFractureData.push_back(f3); + chunkFractureData.push_back(f5); + chunkFractureData.push_back(f7); + ASSERT_EQ(assetDesc.chunkCount, chunkFractureData.capacity()); + ASSERT_EQ(4, chunkFractureData.size()); + + { + NvBlastFractureBuffers events = target; + NvBlastFractureBuffers commands = { 0, static_cast<uint32_t>(chunkFractureData.size()), nullptr, chunkFractureData.data() }; + NvBlastActorApplyFracture(&events, actor, &commands, messageLog, nullptr); + ASSERT_EQ(1, events.chunkFractureCount); // f3 has broken the chunk + } + + // fracture all support chunks + // the chunks from the previous fractures must not be reported again (since they are all broken already) + NvBlastChunkFractureData f2 = { 0, 2, 2.0f }; // will damage chunk and children + NvBlastChunkFractureData f4 = { 0, 4, 0.5f }; // will damage chunk without creating children on split + NvBlastChunkFractureData f6 = { 0, 6, 2.0f }; // will damage chunk and children + NvBlastChunkFractureData f8 = { 0, 8, 1.0f }; // will damage chunk + + chunkFractureData.clear(); + chunkFractureData.reserve(assetDesc.chunkCount); + chunkFractureData.push_back(f1); + chunkFractureData.push_back(f2); + chunkFractureData.push_back(f3); + chunkFractureData.push_back(f4); + chunkFractureData.push_back(f5); + chunkFractureData.push_back(f6); + chunkFractureData.push_back(f7); + chunkFractureData.push_back(f8); + ASSERT_EQ(assetDesc.chunkCount, chunkFractureData.capacity()); + ASSERT_EQ(8, chunkFractureData.size()); + + NvBlastFractureBuffers events = target; + { + NvBlastFractureBuffers commands = { 0, static_cast<uint32_t>(chunkFractureData.size()), nullptr, chunkFractureData.data() }; + NvBlastActorApplyFracture(&events, actor, &commands, messageLog, nullptr); + ASSERT_EQ(4 + 8 + 8, events.chunkFractureCount); // the new fracture commands all apply, plus two of them damage their children too + } + + for (size_t i = 0; i < events.chunkFractureCount; i++) + { + const uint32_t chunkIndex = events.chunkFractures[i].chunkIndex; + + ASSERT_TRUE(chunkIndex != 1); + ASSERT_TRUE(chunkIndex != 3); + ASSERT_TRUE(chunkIndex != 5); + ASSERT_TRUE(chunkIndex != 7); + + // literal values come from g_cube2ChunkDescs + bool isInSupportRange = chunkIndex <= 8 && chunkIndex >= 1; + bool isChildOfTwo = chunkIndex <= 24 && chunkIndex >= 17; + bool isChildOfSix = chunkIndex <= 56 && chunkIndex >= 49; + + ASSERT_TRUE(isInSupportRange || isChildOfTwo || isChildOfSix); + } + + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, messageLog)); + std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, messageLog)); + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = newActors.data(); + size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), messageLog, nullptr); + newActors.resize(newActorsCount); + + EXPECT_EQ(64 - 8 + 1, newActorsCount); + for (uint32_t i = 0; i < newActorsCount; ++i) + { + const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], messageLog); + EXPECT_TRUE(actorReleaseResult); + } + free(family); + free(asset); +} + +static bool hasWarned = false; +static void myLog(int type, const char* msg, const char* file, int line) +{ + BlastBaseTest<-1, 0>::messageLog(type, msg, file, line); + hasWarned = true; +} +#define EXPECT_WARNING EXPECT_TRUE(hasWarned); hasWarned=false; +#define EXPECT_NO_WARNING EXPECT_FALSE(hasWarned); hasWarned=false; + +TEST_F(APITest, FractureNoEvents) +{ + static const uint32_t GUARD = 0xb1a57; + + const uint32_t chunksCount = 17; + const NvBlastChunkDesc c_chunks[chunksCount] = + { + // centroid volume parent idx flags ID + { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + + { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 5 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 6 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 2, NvBlastChunkDesc::NoFlags, 7 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 2, NvBlastChunkDesc::NoFlags, 8 }, + + { {0.0f, 0.0f, 0.0f}, 0.0f, 5, NvBlastChunkDesc::NoFlags, 9 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 5, NvBlastChunkDesc::NoFlags, 10 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 6, NvBlastChunkDesc::NoFlags, 11 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 6, NvBlastChunkDesc::NoFlags, 12 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 7, NvBlastChunkDesc::NoFlags, 13 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 7, NvBlastChunkDesc::NoFlags, 14 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 8, NvBlastChunkDesc::NoFlags, 15 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 8, NvBlastChunkDesc::NoFlags, 16 }, + }; + + const NvBlastBondDesc c_bonds[3] = + { + // chunks, normal, area, centroid, userdata + { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } }, + { { 2, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 0.0f, 0.0f }, 0 } }, + { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 3.0f, 0.0f, 0.0f }, 0 } }, + }; + + NvBlastAssetDesc assetDesc = { chunksCount, c_chunks, 3, c_bonds }; + + // create asset with chunk map + std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, myLog)); + void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, myLog)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), myLog); + EXPECT_TRUE(asset != nullptr); + + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, myLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, myLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, myLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), myLog); + EXPECT_TRUE(actor != nullptr); + + std::vector<NvBlastChunkFractureData> cfData; + cfData.resize(0 + 1); + cfData[cfData.size() - 1].userdata = GUARD; + std::vector<NvBlastBondFractureData> bfData; + + NvBlastChunkFractureData command[] = + { + { 0, 1, 10.0f }, + { 0, 2, 10.0f }, + }; + + NvBlastFractureBuffers commands = { 0, 2, nullptr, command }; + NvBlastActorApplyFracture(nullptr, actor, &commands, myLog, nullptr); + + EXPECT_NO_WARNING; // events can be null + EXPECT_EQ(GUARD, cfData[cfData.size() - 1].userdata); + + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog)); + std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog)); + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = newActors.data(); + size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr); + newActors.resize(newActorsCount); + + EXPECT_EQ(9, newActorsCount); + for (uint32_t i = 0; i < newActorsCount; ++i) + { + const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], myLog); + EXPECT_TRUE(actorReleaseResult); + } + + free(family); + free(asset); + + EXPECT_NO_WARNING; +} + +TEST_F(APITest, FractureBufferLimits) +{ + static const uint32_t GUARD = 0xb1a57; + + const uint32_t chunksCount = 17; + const NvBlastChunkDesc c_chunks[chunksCount] = + { + // centroid volume parent idx flags ID + { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + + { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 5 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 6 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 2, NvBlastChunkDesc::NoFlags, 7 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 2, NvBlastChunkDesc::NoFlags, 8 }, + + { {0.0f, 0.0f, 0.0f}, 0.0f, 5, NvBlastChunkDesc::NoFlags, 9 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 5, NvBlastChunkDesc::NoFlags, 10 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 6, NvBlastChunkDesc::NoFlags, 11 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 6, NvBlastChunkDesc::NoFlags, 12 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 7, NvBlastChunkDesc::NoFlags, 13 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 7, NvBlastChunkDesc::NoFlags, 14 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 8, NvBlastChunkDesc::NoFlags, 15 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 8, NvBlastChunkDesc::NoFlags, 16 }, + }; + + const NvBlastBondDesc c_bonds[3] = + { + // chunks, normal, area, centroid, userdata + { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } }, + { { 2, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 0.0f, 0.0f }, 0 } }, + { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 3.0f, 0.0f, 0.0f }, 0 } }, + }; + + NvBlastAssetDesc assetDesc = { chunksCount, c_chunks, 3, c_bonds }; + { + // create asset with chunk map + std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, myLog)); + void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, myLog)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), myLog); + EXPECT_TRUE(asset != nullptr); + + for (uint32_t i = 0; i < 14; i++) + { + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, myLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, myLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, myLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), myLog); + EXPECT_TRUE(actor != nullptr); + + std::vector<NvBlastChunkFractureData> cfData; + cfData.resize(i + 1); + cfData[cfData.size() - 1].userdata = GUARD; + std::vector<NvBlastBondFractureData> bfData; + + NvBlastChunkFractureData command[] = + { + { 0, 1, 10.0f }, + { 0, 2, 10.0f }, + }; + + NvBlastFractureBuffers commands = { 0, 2, nullptr, command }; + NvBlastFractureBuffers events = { static_cast<uint32_t>(bfData.size()), static_cast<uint32_t>(cfData.size()) - 1, bfData.data(), cfData.data() }; + + NvBlastActorApplyFracture(&events, actor, &commands, myLog, nullptr); + + EXPECT_WARNING; + EXPECT_EQ(GUARD, cfData[cfData.size() - 1].userdata); + EXPECT_EQ(i, events.chunkFractureCount); + for (uint32_t i = 0; i < events.chunkFractureCount; i++) + { + EXPECT_EQ(events.chunkFractures[i].chunkIndex, events.chunkFractures[i].userdata); + } + + EXPECT_TRUE(NvBlastActorDeactivate(actor, myLog)); + free(family); + } + + { + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, myLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, myLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, myLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), myLog); + EXPECT_TRUE(actor != nullptr); + + std::vector<NvBlastChunkFractureData> cfData; + cfData.resize(14 + 1); + cfData[cfData.size() - 1].userdata = GUARD; + std::vector<NvBlastBondFractureData> bfData; + + NvBlastChunkFractureData command[] = + { + { 0, 1, 10.0f }, + { 0, 2, 10.0f }, + }; + + NvBlastFractureBuffers commands = { 0, 2, nullptr, command }; + NvBlastFractureBuffers events = { static_cast<uint32_t>(bfData.size()), static_cast<uint32_t>(cfData.size()) - 1, bfData.data(), cfData.data() }; + + NvBlastActorApplyFracture(&events, actor, &commands, myLog, nullptr); + + EXPECT_NO_WARNING; + EXPECT_EQ(14, events.chunkFractureCount); + for (uint32_t i = 0; i < events.chunkFractureCount; i++) + { + EXPECT_EQ(events.chunkFractures[i].chunkIndex, events.chunkFractures[i].userdata); + } + ASSERT_EQ(GUARD, cfData[cfData.size() - 1].userdata); + + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog)); + std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog)); + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = newActors.data(); + size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr); + newActors.resize(newActorsCount); + + EXPECT_EQ(9, newActorsCount); + for (uint32_t i = 0; i < newActorsCount; ++i) + { + const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], myLog); + EXPECT_TRUE(actorReleaseResult); + } + + free(family); + } + + free(asset); + } + EXPECT_NO_WARNING; +} + +TEST_F(APITest, FractureBufferLimitsInSitu) +{ + static const uint32_t GUARD = 0xb1a57; + + const uint32_t chunksCount = 17; + const NvBlastChunkDesc c_chunks[chunksCount] = + { + // cenroid volume parent idx flags ID + { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + + { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 5 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 6 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 2, NvBlastChunkDesc::NoFlags, 7 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 2, NvBlastChunkDesc::NoFlags, 8 }, + + { {0.0f, 0.0f, 0.0f}, 0.0f, 5, NvBlastChunkDesc::NoFlags, 9 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 5, NvBlastChunkDesc::NoFlags, 10 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 6, NvBlastChunkDesc::NoFlags, 11 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 6, NvBlastChunkDesc::NoFlags, 12 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 7, NvBlastChunkDesc::NoFlags, 13 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 7, NvBlastChunkDesc::NoFlags, 14 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 8, NvBlastChunkDesc::NoFlags, 15 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 8, NvBlastChunkDesc::NoFlags, 16 }, + }; + + const NvBlastBondDesc c_bonds[3] = + { + // chunks, normal, area, centroid, userdata + { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } }, + { { 2, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 0.0f, 0.0f }, 0 } }, + { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 3.0f, 0.0f, 0.0f }, 0 } }, + }; + + NvBlastAssetDesc assetDesc = { chunksCount, c_chunks, 3, c_bonds }; + { + // create asset with chunk map + std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, myLog)); + void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, myLog)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), myLog); + EXPECT_TRUE(asset != nullptr); + + for (uint32_t i = 0; i < 14 - 2; i++) + { + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, myLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, myLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, myLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), myLog); + EXPECT_TRUE(actor != nullptr); + + std::vector<NvBlastChunkFractureData> cfData; + cfData.resize(2 + i + 1); + + std::vector<NvBlastBondFractureData> bfData; + + cfData[0].userdata = 0; + cfData[0].chunkIndex = 1; + cfData[0].health = 10.0f; + + cfData[1].userdata = 0; + cfData[1].chunkIndex = 2; + cfData[1].health = 10.0f; + + cfData[2 + i].userdata = GUARD; + + NvBlastFractureBuffers commands = { 0, 2, nullptr, cfData.data() }; + NvBlastFractureBuffers events = { static_cast<uint32_t>(bfData.size()), static_cast<uint32_t>(cfData.size()) - 1, bfData.data(), cfData.data() }; + + NvBlastActorApplyFracture(&events, actor, &commands, myLog, nullptr); + + EXPECT_WARNING; + EXPECT_EQ(GUARD, cfData[cfData.size() - 1].userdata); + EXPECT_EQ(2 + i, events.chunkFractureCount); + for (uint32_t i = 0; i < events.chunkFractureCount; i++) + { + EXPECT_EQ(events.chunkFractures[i].chunkIndex, events.chunkFractures[i].userdata); + } + + EXPECT_TRUE(NvBlastActorDeactivate(actor, myLog)); + + free(family); + } + + { + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, myLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, myLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, myLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), myLog); + EXPECT_TRUE(actor != nullptr); + + std::vector<NvBlastChunkFractureData> cfData; + cfData.resize(14 + 1); + cfData[cfData.size() - 1].userdata = GUARD; + std::vector<NvBlastBondFractureData> bfData; + + cfData[0].userdata = 0; + cfData[0].chunkIndex = 1; + cfData[0].health = 10.0f; + + cfData[1].userdata = 0; + cfData[1].chunkIndex = 2; + cfData[1].health = 10.0f; + + cfData[14].userdata = GUARD; + + NvBlastFractureBuffers commands = { 0, 2, nullptr, cfData.data() }; + NvBlastFractureBuffers events = { static_cast<uint32_t>(bfData.size()), static_cast<uint32_t>(cfData.size()) - 1, bfData.data(), cfData.data() }; + + NvBlastActorApplyFracture(&events, actor, &commands, myLog, nullptr); + + EXPECT_NO_WARNING; + EXPECT_EQ(14, events.chunkFractureCount); + for (uint32_t i = 0; i < events.chunkFractureCount; i++) + { + EXPECT_EQ(events.chunkFractures[i].chunkIndex, events.chunkFractures[i].userdata); + } + ASSERT_EQ(GUARD, cfData[cfData.size() - 1].userdata); + + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog)); + std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog)); + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = newActors.data(); + size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr); + newActors.resize(newActorsCount); + + EXPECT_EQ(9, newActorsCount); + for (uint32_t i = 0; i < newActorsCount; ++i) + { + const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], myLog); + EXPECT_TRUE(actorReleaseResult); + } + free(family); + } + + free(asset); + } + EXPECT_NO_WARNING; +} + +#if 0 +TEST(APITest, UserChunkMap) +{ + for (int i = 0; i < 2; ++i) + { + // Choose descriptor list + const NvBlastAssetDesc* descs = nullptr; + size_t size = 0; + switch (i) + { + case 0: + descs = g_assetDescs; + size = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); + break; + case 1: + descs = g_assetDescsMissingCoverage; + size = sizeof(g_assetDescsMissingCoverage) / sizeof(g_assetDescsMissingCoverage[0]); + break; + default: + continue; + } + + // Iterate over list + for (size_t j = 0; j < size; ++j) + { + // Create asset + const NvBlastAssetDesc* desc = descs + j; + std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(desc)); + std::vector<uint32_t> chunkMap(desc->chunkCount); + NvBlastAsset* asset = NvBlastCreateAsset(&chunkMap[0], desc, alignedAlloc<malloc>, scratch.data(), nullptr); + EXPECT_TRUE(asset); + + // Test map + Nv::Blast::Asset& a = static_cast<Nv::Blast::Asset&>(asset); + uint32_t supportChunkCount = 0; + uint32_t subsupportChunkCount = 0; + for (uint32_t i = 0; i < desc->chunkCount; ++i) + { + const uint32_t map = chunkMap[i]; + if (Nv::Blast::isInvalidIndex(map)) + { + continue; + } + else if (map < a.m_firstSubsupportChunkIndex) + { + EXPECT_LT(map, asset.m_graph.m_nodeCount); + ++supportChunkCount; + } + else + { + EXPECT_LT(map, asset.m_chunkCount); + EXPECT_GE(map, asset.m_graph.m_nodeCount); + ++subsupportChunkCount; + } + } + EXPECT_EQ(supportChunkCount, asset.m_graph.m_nodeCount); + EXPECT_EQ(subsupportChunkCount, a.getLowerSupportChunkCount() - asset.m_graph.m_nodeCount); + + // Release asset + NvBlastAssetRelease(asset, free, nullptr); + } + } +} +#endif + +TEST_F(APITest, NoBondsSausage) +{ + // create asset + const NvBlastChunkDesc c_chunks[4] = + { + // centroid volume parent idx flags ID + { { 0.0f, 0.0f, 0.0f }, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 1, NvBlastChunkDesc::NoFlags, 2 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 2, NvBlastChunkDesc::NoFlags, 3 } + }; + + NvBlastAssetDesc assetDesc; + assetDesc.chunkCount = 4; + assetDesc.chunkDescs = c_chunks; + assetDesc.bondCount = 0; + assetDesc.bondDescs = nullptr; + + std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, messageLog)); + void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, messageLog)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), messageLog); + const NvBlastChunk* chunks = NvBlastAssetGetChunks(asset, messageLog); + EXPECT_TRUE(asset != nullptr); + + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), messageLog); + EXPECT_TRUE(actor != nullptr); + + // check visible chunk + { + EXPECT_EQ(NvBlastActorGetVisibleChunkCount(actor, messageLog), 1); + uint32_t chunkIndex; + NvBlastActorGetVisibleChunkIndices(&chunkIndex, 1, actor, messageLog); + EXPECT_EQ(chunks[chunkIndex].userData, 0); + } + + // damage + NvBlastExtRadialDamageDesc damage[] = { + { + 10.0f, // compressive + { 0.0f, 0.0f, 0.0f }, // position + 4.0f, // min radius - maximum damage + 6.0f // max radius - zero damage + } // linear falloff + }; + + NvBlastBondFractureData outBondFracture[2]; + NvBlastChunkFractureData outChunkFracture[2]; + + NvBlastFractureBuffers events; + events.bondFractureCount = 2; + events.bondFractures = outBondFracture; + events.chunkFractureCount = 2; + events.chunkFractures = outChunkFracture; + + NvBlastProgramParams programParams; + programParams.damageDescCount = 1; + programParams.damageDescBuffer = &damage; + + NvBlastDamageProgram program = { + NvBlastExtFalloffGraphShader, + NvBlastExtFalloffSubgraphShader + }; + + NvBlastActorGenerateFracture(&events, actor, program, &programParams, messageLog, nullptr); + NvBlastActorApplyFracture(&events, actor, &events, messageLog, nullptr); + EXPECT_EQ(0, events.bondFractureCount); + EXPECT_EQ(1, events.chunkFractureCount); + + // split + NvBlastActor* newActors[8]; /* num lower-support chunks? plus space for deletedActor */ + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = newActors; + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, messageLog)); + size_t newActorsCount = NvBlastActorSplit(&result, actor, 8, scratch.data(), messageLog, nullptr); + EXPECT_EQ(1, newActorsCount); + EXPECT_EQ(true, result.deletedActor == actor); + + // check visible chunk + { + EXPECT_EQ(NvBlastActorGetVisibleChunkCount(result.newActors[0], messageLog), 1); + uint32_t chunkIndex; + NvBlastActorGetVisibleChunkIndices(&chunkIndex, 1, result.newActors[0], messageLog); + EXPECT_EQ(chunks[chunkIndex].userData, 3); + } + + // release all + for (uint32_t i = 0; i < newActorsCount; ++i) + { + const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], messageLog); + EXPECT_TRUE(actorReleaseResult); + } + + free(family); + free(asset); +} + +TEST_F(APITest, SplitOnlyWhenNecessary) +{ + static const uint32_t GUARD = 0xb1a57; + + const uint32_t chunksCount = 17; + const NvBlastChunkDesc c_chunks[chunksCount] = + { + // centroid volume parent idx flags ID + { { 0.0f, 0.0f, 0.0f }, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + + { { 0.0f, 0.0f, 0.0f }, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + + { { 0.0f, 0.0f, 0.0f }, 0.0f, 1, NvBlastChunkDesc::NoFlags, 5 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 1, NvBlastChunkDesc::NoFlags, 6 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 2, NvBlastChunkDesc::NoFlags, 7 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 2, NvBlastChunkDesc::NoFlags, 8 }, + + { { 0.0f, 0.0f, 0.0f }, 0.0f, 5, NvBlastChunkDesc::NoFlags, 9 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 5, NvBlastChunkDesc::NoFlags, 10 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 6, NvBlastChunkDesc::NoFlags, 11 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 6, NvBlastChunkDesc::NoFlags, 12 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 7, NvBlastChunkDesc::NoFlags, 13 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 7, NvBlastChunkDesc::NoFlags, 14 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 8, NvBlastChunkDesc::NoFlags, 15 }, + { { 0.0f, 0.0f, 0.0f }, 0.0f, 8, NvBlastChunkDesc::NoFlags, 16 }, + }; + + const NvBlastBondDesc c_bonds[4] = + { + // chunks, normal, area, centroid, userdata + { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } }, + { { 2, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } }, + { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } }, + { { 1, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } } + }; + + NvBlastAssetDesc assetDesc = { chunksCount, c_chunks, 4, c_bonds }; + + // create asset with chunk map + std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, myLog)); + void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, myLog)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), myLog); + EXPECT_TRUE(asset != nullptr); + + // create actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, myLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, myLog); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, myLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), myLog); + EXPECT_TRUE(actor != nullptr); + + + // damage health only (expect no split) + { + NvBlastBondFractureData command[] = + { + { 0, 0, 1, 0.99f }, + { 0, 1, 2, 0.50f }, + { 0, 2, 3, 0.01f } + }; + + NvBlastFractureBuffers commands = { 3, 0, command, nullptr }; + NvBlastActorApplyFracture(nullptr, actor, &commands, myLog, nullptr); + + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog)); + std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog)); + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = newActors.data(); + size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr); + + EXPECT_EQ(0, newActorsCount); + EXPECT_EQ(nullptr, result.deletedActor); + + EXPECT_EQ(1, NvBlastActorGetVisibleChunkCount(actor, myLog)); + uint32_t chunkIndex; + NvBlastActorGetVisibleChunkIndices(&chunkIndex, 1, actor, myLog); + EXPECT_EQ(0, chunkIndex); + } + + // break 1 bond (expect no split) + { + NvBlastBondFractureData command[] = + { + { 0, 0, 2, 10.0f }, + }; + + NvBlastFractureBuffers commands = { 1, 0, command, nullptr }; + NvBlastActorApplyFracture(nullptr, actor, &commands, myLog, nullptr); + + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog)); + std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog)); + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = newActors.data(); + size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr); + + EXPECT_EQ(0, newActorsCount); + EXPECT_EQ(nullptr, result.deletedActor); + + EXPECT_EQ(1, NvBlastActorGetVisibleChunkCount(actor, myLog)); + uint32_t chunkIndex; + NvBlastActorGetVisibleChunkIndices(&chunkIndex, 1, actor, myLog); + EXPECT_EQ(0, chunkIndex); + } + + // split in 4 + std::vector<NvBlastActor*> actors; + { + NvBlastBondFractureData command[] = + { + { 0, 0, 1, 10.0f }, + { 0, 1, 2, 10.0f }, + { 0, 2, 3, 10.0f } + }; + + NvBlastFractureBuffers commands = { 3, 0, command, nullptr }; + NvBlastActorApplyFracture(nullptr, actor, &commands, myLog, nullptr); + + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog)); + std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog)); + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = newActors.data(); + size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr); + + EXPECT_EQ(4, newActorsCount); + EXPECT_EQ(actor, result.deletedActor); + + actors.insert(actors.begin(), result.newActors, result.newActors + newActorsCount); + } + + // damage chunk's health only (expect no split) + { + for (NvBlastActor* actor : actors) + { + uint32_t chunkToDamage; + NvBlastActorGetVisibleChunkIndices(&chunkToDamage, 1, actor, myLog); + + NvBlastChunkFractureData command[] = + { + { 0, chunkToDamage, 0.9f }, + }; + + NvBlastFractureBuffers commands = { 0, 1, nullptr, command }; + NvBlastActorApplyFracture(nullptr, actor, &commands, myLog, nullptr); + + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog)); + std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog)); + NvBlastActorSplitEvent result; + result.deletedActor = nullptr; + result.newActors = newActors.data(); + size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr); + + EXPECT_EQ(0, newActorsCount); + EXPECT_EQ(nullptr, result.deletedActor); + + EXPECT_EQ(1, NvBlastActorGetVisibleChunkCount(actor, myLog)); + uint32_t chunkIndex; + NvBlastActorGetVisibleChunkIndices(&chunkIndex, 1, actor, myLog); + EXPECT_EQ(chunkToDamage, chunkIndex); + } + } + + for (NvBlastActor* actor : actors) + { + NvBlastActorDeactivate(actor, myLog); + } + + free(family); + free(asset); + + EXPECT_NO_WARNING; +} + +#if NV_WINDOWS_FAMILY +#include <windows.h> +TEST_F(APITest,CExportsNoNameMangling) +{ + + // + // tests the lib-link-free approach using unmangled names (extern "C") + // + +#if NV_WIN32 +#if NV_DEBUG + const char* dllName = "NvBlastDebug_x86.dll"; +#elif NV_CHECKED + const char* dllName = "NvBlastChecked_x86.dll"; +#elif NV_PROFILE + const char* dllName = "NvBlastProfile_x86.dll"; +#else + const char* dllName = "NvBlast_x86.dll"; +#endif +#elif NV_WIN64 +#if NV_DEBUG + const char* dllName = "NvBlastDebug_x64.dll"; +#elif NV_CHECKED + const char* dllName = "NvBlastChecked_x64.dll"; +#elif NV_PROFILE + const char* dllName = "NvBlastProfile_x64.dll"; +#else + const char* dllName = "NvBlast_x64.dll"; +#endif +#endif + + HMODULE dllHandle = LoadLibrary(TEXT(dllName)); + DWORD error = GetLastError(); + ASSERT_TRUE(dllHandle != nullptr); + + + // Asset functions + typedef size_t(*NvBlastGetRequiredScratchForCreateAsset)(const NvBlastAssetDesc* desc); + typedef size_t(*NvBlastGetAssetMemorySize)(const NvBlastAssetDesc* desc); + typedef NvBlastAsset*(*NvBlastCreateAsset)(void* mem, const NvBlastAssetDesc* desc, void* scratch, NvBlastLog logFn); + + NvBlastGetRequiredScratchForCreateAsset assetCreateRequiredScratch = (NvBlastGetRequiredScratchForCreateAsset)GetProcAddress(dllHandle, TEXT("NvBlastGetRequiredScratchForCreateAsset")); + ASSERT_TRUE(assetCreateRequiredScratch != nullptr); + + NvBlastGetAssetMemorySize assetGetMemorySize = (NvBlastGetAssetMemorySize)GetProcAddress(dllHandle, TEXT("NvBlastGetAssetMemorySize")); + ASSERT_TRUE(assetGetMemorySize != nullptr); + + NvBlastCreateAsset assetCreate = (NvBlastCreateAsset)GetProcAddress(dllHandle, TEXT("NvBlastCreateAsset")); + ASSERT_TRUE(assetCreate != nullptr); + + // Family functions + typedef NvBlastFamily* (*NvBlastAssetCreateFamily)(void* mem, const NvBlastAsset* asset, NvBlastLog logFn); + typedef size_t(*NVBLASTASSETGETFAMILYMEMORYSIZE)(const NvBlastAsset* asset); + + NVBLASTASSETGETFAMILYMEMORYSIZE familyGetMemorySize = (NVBLASTASSETGETFAMILYMEMORYSIZE)GetProcAddress(dllHandle, TEXT("NvBlastAssetGetFamilyMemorySize")); + ASSERT_TRUE(familyGetMemorySize != nullptr); + + NvBlastAssetCreateFamily familyCreate = (NvBlastAssetCreateFamily)GetProcAddress(dllHandle, TEXT("NvBlastAssetCreateFamily")); + ASSERT_TRUE(familyCreate != nullptr); + + + // Actor functions + typedef size_t(*NvBlastFamilyGetRequiredScratchForCreateFirstActor)(const NvBlastFamily* family); + typedef NvBlastActor* (*NvBlastFamilyCreateFirstActor)(NvBlastFamily* family, const NvBlastActorDesc* desc, void* scratch, NvBlastLog logFn); + typedef bool(*NVBLASTACTORDEACTIVATE)(NvBlastActor* actor); + + NvBlastFamilyGetRequiredScratchForCreateFirstActor actorcreaterequiredscratch = (NvBlastFamilyGetRequiredScratchForCreateFirstActor)GetProcAddress(dllHandle, TEXT("NvBlastFamilyGetRequiredScratchForCreateFirstActor")); + ASSERT_TRUE(actorcreaterequiredscratch != nullptr); + + NvBlastFamilyCreateFirstActor actorCreate = (NvBlastFamilyCreateFirstActor)GetProcAddress(dllHandle, TEXT("NvBlastFamilyCreateFirstActor")); + ASSERT_TRUE(actorCreate != nullptr); + + NVBLASTACTORDEACTIVATE actorRelease = (NVBLASTACTORDEACTIVATE)GetProcAddress(dllHandle, TEXT("NvBlastActorDeactivate")); + ASSERT_TRUE(actorRelease != nullptr); + + + const NvBlastChunkDesc c_chunks[] = + { + // centroid volume parent idx flags ID + { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 0 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 0 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 0 }, + }; + + NvBlastAssetDesc assetDesc; + assetDesc.bondCount = 0; + assetDesc.bondDescs = nullptr; + assetDesc.chunkCount = 4; + assetDesc.chunkDescs = c_chunks; + + NvBlastAsset* asset; + { + size_t requiredsize = assetCreateRequiredScratch(&assetDesc); + std::vector<char>scratch(requiredsize); + void* mem = alloc(assetGetMemorySize(&assetDesc)); + asset = assetCreate(mem, &assetDesc, scratch.data(), myLog); + ASSERT_TRUE(asset != nullptr); + } + + void* fmem = alloc(familyGetMemorySize(asset)); + NvBlastFamily* family = familyCreate(fmem, asset, myLog); + + { + NvBlastActorDesc actorD; + actorD.initialBondHealths = actorD.initialSupportChunkHealths = nullptr; + actorD.uniformInitialBondHealth = actorD.uniformInitialLowerSupportChunkHealth = 1.0f; + + size_t requiredsize = actorcreaterequiredscratch(family); + std::vector<char>scratch(requiredsize); + NvBlastActor* actor = actorCreate(family, &actorD, scratch.data(), myLog); + ASSERT_TRUE(actor != nullptr); + + ASSERT_TRUE(actorRelease(actor)); + } + + free(family); + free(asset); + + EXPECT_NO_WARNING; +} +#endif diff --git a/NvBlast/test/src/unit/ActorTests.cpp b/NvBlast/test/src/unit/ActorTests.cpp new file mode 100644 index 0000000..12dc19c --- /dev/null +++ b/NvBlast/test/src/unit/ActorTests.cpp @@ -0,0 +1,1059 @@ +#include "BlastBaseTest.h" +#include "AssetGenerator.h" + +#include <map> +#include <algorithm> + +#include "NvBlastActor.h" +#include "NvBlastExtDamageShaders.h" + + +static bool chooseRandomGraphNodes(uint32_t* g, uint32_t count, const Nv::Blast::Actor& actor) +{ + const uint32_t graphNodeCount = actor.getGraphNodeCount(); + + if (graphNodeCount < count) + { + return false; + } + + std::vector<uint32_t> graphNodeIndices(graphNodeCount); + uint32_t* index = graphNodeIndices.data(); + for (Nv::Blast::Actor::GraphNodeIt i = actor; (bool)i ; ++i) + { + *index++ = (uint32_t)i; + } + struct UserDataSorter + { + UserDataSorter(const Nv::Blast::Actor& actor) : m_asset(*actor.getAsset()) {} + + bool operator () (uint32_t i0, uint32_t i1) const + { + const uint32_t c0 = m_asset.m_graph.getChunkIndices()[i0]; + const uint32_t c1 = m_asset.m_graph.getChunkIndices()[i1]; + return m_asset.getChunks()[c0].userData < m_asset.getChunks()[c1].userData; + } + + const Nv::Blast::Asset& m_asset; + } userDataSorter(actor); + std::sort(graphNodeIndices.data(), graphNodeIndices.data() + graphNodeCount, userDataSorter); + +#if 0 + std::vector<uint32_t> descUserData(graphNodeCount); + for (uint32_t i = 0; i < graphNodeCount; ++i) + { + descUserData[i] = actor.getAsset()->m_chunks[actor.getAsset()->m_graph.m_chunkIndices[graphNodeIndices[i]]].userData; + } +#endif + + uint32_t t = 0; + uint32_t m = 0; + for (uint32_t i = 0; i < graphNodeCount && m < count; ++i, ++t) + { + NVBLAST_ASSERT(t < graphNodeCount); + if (t >= graphNodeCount) + { + break; + } + const float U = (float)rand()/RAND_MAX; // U is uniform random number in [0,1) + if ((graphNodeCount - t)*U < count - m) + { + g[m++] = graphNodeIndices[i]; + } + } + + return m == count; +} + + +static void blast(std::set<NvBlastActor*>& actorsToDamage, GeneratorAsset* testAsset, GeneratorAsset::Vec3 localPos, float minRadius, float maxRadius, float compressiveDamage) +{ + std::vector<NvBlastChunkFractureData> chunkEvents; /* num lower-support chunks + bonds */ + std::vector<NvBlastBondFractureData> bondEvents; /* num lower-support chunks + bonds */ + chunkEvents.resize(testAsset->solverChunks.size()); + bondEvents.resize(testAsset->solverBonds.size()); + + + std::vector<char> splitScratch; + std::vector<NvBlastActor*> newActorsBuffer(testAsset->solverChunks.size()); + + NvBlastExtRadialDamageDesc damage[] = { + { + compressiveDamage, + { localPos.x, localPos.y, localPos.z }, + minRadius, + maxRadius + } + }; + + NvBlastProgramParams programParams = + { + &damage, + 1, + nullptr + }; + + NvBlastDamageProgram program = { + NvBlastExtFalloffGraphShader, + nullptr + }; + + size_t totalNewActorsCount = 0; + for (std::set<NvBlastActor*>::iterator k = actorsToDamage.begin(); k != actorsToDamage.end();) + { + NvBlastActor* actor = *k; + NvBlastFractureBuffers events = { static_cast<uint32_t>(bondEvents.size()), static_cast<uint32_t>(chunkEvents.size()), bondEvents.data(), chunkEvents.data() }; + + NvBlastActorGenerateFracture(&events, actor, program, &programParams, nullptr, nullptr); + NvBlastActorApplyFracture(&events, actor, &events, nullptr, nullptr); + bool removeActor = false; + + if (events.bondFractureCount + events.chunkFractureCount > 0) + { + NvBlastActorSplitEvent splitEvent; + splitEvent.newActors = &newActorsBuffer.data()[totalNewActorsCount]; + uint32_t newActorSize = (uint32_t)(newActorsBuffer.size() - totalNewActorsCount); + + splitScratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, nullptr)); + const size_t newActorsCount = NvBlastActorSplit(&splitEvent, actor, newActorSize, splitScratch.data(), nullptr, nullptr); + totalNewActorsCount += newActorsCount; + removeActor = splitEvent.deletedActor != NULL; + } + + if (removeActor) + { + k = actorsToDamage.erase(k); + } + else + { + ++k; + } + } + + for (size_t i = 0; i < totalNewActorsCount; ++i) + { + actorsToDamage.insert(newActorsBuffer[i]); + } +} + + +template<int FailLevel, int Verbosity> +class ActorTest : public BlastBaseTest<FailLevel, Verbosity> +{ +public: + ActorTest() + { + + } + + static void messageLog(int type, const char* msg, const char* file, int line) + { + BlastBaseTest<FailLevel, Verbosity>::messageLog(type, msg, file, line); + } + + static void* alloc(size_t size) + { + return BlastBaseTest<FailLevel, Verbosity>::alloc(size); + } + + static void free(void* mem) + { + BlastBaseTest<FailLevel, Verbosity>::free(mem); + } + + NvBlastAsset* buildAsset(const NvBlastAssetDesc& desc) + { + // fix desc if wrong order or missing coverage first + NvBlastAssetDesc fixedDesc = desc; + std::vector<NvBlastChunkDesc> chunkDescs(desc.chunkDescs, desc.chunkDescs + desc.chunkCount); + std::vector<NvBlastBondDesc> bondDescs(desc.bondDescs, desc.bondDescs + desc.bondCount); + std::vector<uint32_t> chunkReorderMap(desc.chunkCount); + std::vector<char> scratch(desc.chunkCount * sizeof(NvBlastChunkDesc)); + NvBlastEnsureAssetExactSupportCoverage(chunkDescs.data(), fixedDesc.chunkCount, scratch.data(), messageLog); + NvBlastReorderAssetDescChunks(chunkDescs.data(), fixedDesc.chunkCount, bondDescs.data(), fixedDesc.bondCount, chunkReorderMap.data(), scratch.data(), messageLog); + fixedDesc.chunkDescs = chunkDescs.data(); + fixedDesc.bondDescs = bondDescs.empty() ? nullptr : bondDescs.data(); + + // create asset + m_scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(&fixedDesc, messageLog)); + void* mem = alloc(NvBlastGetAssetMemorySize(&fixedDesc, messageLog)); + NvBlastAsset* asset = NvBlastCreateAsset(mem, &fixedDesc, &m_scratch[0], messageLog); + EXPECT_TRUE(asset != nullptr); + return asset; + } + + void buildAssets() + { + m_assets.resize(getAssetDescCount()); + for (uint32_t i = 0; i < m_assets.size(); ++i) + { + m_assets[i] = buildAsset(g_assetDescs[i]); + } + } + + NvBlastActor* instanceActor(const NvBlastAsset& asset) + { + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(&asset, nullptr)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, &asset, nullptr); + std::vector<char> scratch((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], messageLog); + EXPECT_TRUE(actor != nullptr); + return actor; + } + + void instanceActors() + { + m_actors.resize(m_assets.size()); + for (uint32_t i = 0; i < m_actors.size(); ++i) + { + m_actors[i] = instanceActor(*m_assets[i]); + } + } + + void releaseActors() + { + for (uint32_t i = 0; i < m_actors.size(); ++i) + { + NvBlastFamily* family = NvBlastActorGetFamily(m_actors[i], messageLog); + + const bool actorReleaseResult = NvBlastActorDeactivate(m_actors[i], messageLog); + EXPECT_TRUE(actorReleaseResult); + + free(family); + } + } + + void destroyAssets() + { + for (uint32_t i = 0; i < m_assets.size(); ++i) + { + free(m_assets[i]); + } + } + + void instanceAndPartitionRecursively + ( + const NvBlastAsset& asset, + bool partitionToSubsupport, + void (*preSplitTest)(const Nv::Blast::Actor&, NvBlastLog), + void (*postSplitTest)(const std::vector<Nv::Blast::Actor*>&, uint32_t, uint32_t, bool) + ) + { + const Nv::Blast::Asset& solverAsset = *static_cast<const Nv::Blast::Asset*>(&asset); + + std::vector<Nv::Blast::Actor*> actors; + std::vector<Nv::Blast::Actor*> buffer(NvBlastAssetGetChunkCount(&asset, messageLog)); + + // Instance the first actor from the asset + actors.push_back(static_cast<Nv::Blast::Actor*>(instanceActor(asset))); + + NvBlastFamily* family = NvBlastActorGetFamily(actors[0], messageLog); + + const uint32_t supportChunkCount = actors[0]->getAsset()->m_graph.m_nodeCount; + const uint32_t leafChunkCount = actors[0]->getAsset()->m_leafChunkCount; + + // Now randomly partition the actors in the array, and keep going until we're down to single support chunks + bool canFracture = true; + + while (canFracture) + { + canFracture = false; + + for (uint32_t actorToPartition = 0; actorToPartition < actors.size(); ++actorToPartition) + { + Nv::Blast::Actor* a = (Nv::Blast::Actor*)actors[actorToPartition]; + if (a == nullptr) + { + continue; + } + + m_scratch.reserve((size_t)NvBlastActorGetRequiredScratchForSplit(a, messageLog)); + + if (preSplitTest) + { + preSplitTest(*a, nullptr); + } + + const bool singleLowerSupportChunk = a->getGraphNodeCount() <= 1; + uint32_t newActorCount = 0; + + for (int damageNum = 0; newActorCount < 2 && damageNum < 100; ++damageNum) // Avoid infinite loops + { + if (!singleLowerSupportChunk) + { + uint32_t g[2]; + chooseRandomGraphNodes(g, 2, *a); + const uint32_t bondIndex = solverAsset.m_graph.findBond(g[0], g[1]); + if (bondIndex != Nv::Blast::invalidIndex<uint32_t>()) + { + a->damageBond(g[0], g[1], bondIndex, 100.0f); + a->findIslands(&m_scratch[0]); + } + } + else + if (!partitionToSubsupport) + { + continue; + } + + // Split actor + newActorCount = a->partition((Nv::Blast::Actor**)&buffer[0], (uint32_t)buffer.size(), messageLog); + + if (newActorCount >= 2) + { + actors[actorToPartition] = nullptr; + } + } + + if (newActorCount > 1) + { + canFracture = true; + } + + for (uint32_t i = 0; i < newActorCount; ++i) + { + actors.push_back(buffer[i]); + buffer[i]->updateVisibleChunksFromGraphNodes(); + } + } + } + + if (postSplitTest) + { + postSplitTest(actors, leafChunkCount, supportChunkCount, partitionToSubsupport); + } + + for (auto actor : actors) + { + if (actor) + actor->release(); + } + + free(family); + } + + static void recursivePartitionPostSplitTestCounts(const std::vector<Nv::Blast::Actor*>& actors, uint32_t leafChunkCount, uint32_t supportChunkCount, bool partitionToSubsupport) + { + // Test to see that all actors are split down to single support chunks + uint32_t remainingActorCount = 0; + for (uint32_t i = 0; i < actors.size(); ++i) + { + Nv::Blast::Actor* a = (Nv::Blast::Actor*)actors[i]; + if (a == nullptr) + { + continue; + } + + ++remainingActorCount; + + NVBLAST_ASSERT(1 == a->getVisibleChunkCount()); + EXPECT_EQ(1, a->getVisibleChunkCount()); + if (!partitionToSubsupport) + { + EXPECT_EQ(1, a->getGraphNodeCount()); + } + + const bool actorReleaseResult = NvBlastActorDeactivate(actors[i], nullptr); + EXPECT_TRUE(actorReleaseResult); + } + + if (partitionToSubsupport) + { + EXPECT_EQ(leafChunkCount, remainingActorCount); + } + else + { + EXPECT_EQ(supportChunkCount, remainingActorCount); + } + } + + static void testActorVisibleChunks(const Nv::Blast::Actor& actor, NvBlastLog) + { + const Nv::Blast::Asset& asset = *actor.getAsset(); + const NvBlastChunk* chunks = asset.getChunks(); + + if (actor.isSubSupportChunk()) + { + EXPECT_EQ(1, actor.getVisibleChunkCount()); + + const uint32_t firstVisibleChunkIndex = (uint32_t)Nv::Blast::Actor::VisibleChunkIt(actor); + + EXPECT_EQ(actor.getIndex() - asset.m_graph.m_nodeCount, firstVisibleChunkIndex - asset.m_firstSubsupportChunkIndex); + + // Make sure the visible chunk is subsupport + // Array of support flags + std::vector<bool> isSupport(asset.m_chunkCount, false); + for (uint32_t i = 0; i < asset.m_graph.m_nodeCount; ++i) + { + isSupport[asset.m_graph.getChunkIndices()[i]] = true; + } + + // Climb hierarchy to find support chunk + uint32_t chunkIndex = firstVisibleChunkIndex; + while (chunkIndex != Nv::Blast::invalidIndex<uint32_t>()) + { + if (isSupport[chunkIndex]) + { + break; + } + chunkIndex = chunks[chunkIndex].parentChunkIndex; + } + + EXPECT_FALSE(Nv::Blast::isInvalidIndex(chunkIndex)); + } + else + { + // Array of visibility flags + std::vector<bool> isVisible(asset.m_chunkCount, false); + for (Nv::Blast::Actor::VisibleChunkIt i = actor; (bool)i; ++i) + { + isVisible[(uint32_t)i] = true; + } + + // Mark visible nodes representing graph chunks + std::vector<bool> visibleChunkFound(asset.m_chunkCount, false); + + // Make sure every graph chunk is represented by a visible chunk + for (Nv::Blast::Actor::GraphNodeIt i = actor; (bool)i; ++i) + { + const uint32_t graphNodeIndex = (uint32_t)i; + uint32_t chunkIndex = asset.m_graph.getChunkIndices()[graphNodeIndex]; + // Climb hierarchy to find visible chunk + while (chunkIndex != Nv::Blast::invalidIndex<uint32_t>()) + { + // Check that chunk owners are accurate + EXPECT_EQ(actor.getIndex(), actor.getFamilyHeader()->getChunkActorIndices()[chunkIndex]); + if (isVisible[chunkIndex]) + { + visibleChunkFound[chunkIndex] = true; + break; + } + chunkIndex = chunks[chunkIndex].parentChunkIndex; + } + EXPECT_FALSE(Nv::Blast::isInvalidIndex(chunkIndex)); + } + + // Check that all visible chunks are accounted for + for (uint32_t i = 0; i < asset.m_chunkCount; ++i) + { + EXPECT_EQ(visibleChunkFound[i], isVisible[i]); + } + + // Make sure that, if all siblings are intact, they are invisible + for (uint32_t i = 0; i < asset.m_chunkCount; ++i) + { + bool allIntact = true; + bool noneVisible = true; + if (chunks[i].firstChildIndex < asset.getUpperSupportChunkCount()) // Do not check subsupport + { + for (uint32_t j = chunks[i].firstChildIndex; j < chunks[i].childIndexStop; ++j) + { + allIntact = allIntact && actor.getFamilyHeader()->getChunkActorIndices()[j] == actor.getIndex(); + noneVisible = noneVisible && !isVisible[j]; + } + EXPECT_TRUE(!allIntact || noneVisible); + } + } + } + } + + static void recursivePartitionPostSplitTestVisibleChunks(const std::vector<Nv::Blast::Actor*>& actors, uint32_t leafChunkCount, uint32_t supportChunkCount, bool partitionToSubsupport) + { + for (uint32_t i = 0; i < actors.size(); ++i) + { + Nv::Blast::Actor* a = (Nv::Blast::Actor*)actors[i]; + if (a == nullptr) + { + continue; + } + + testActorVisibleChunks(*a, nullptr); + } + } + + void partitionActorsToSupportChunks + ( + uint32_t assetDescCount, + const NvBlastAssetDesc* assetDescs, + void(*preSplitTest)(const Nv::Blast::Actor&, NvBlastLog), + void(*postSplitTest)(const std::vector<Nv::Blast::Actor*>&, uint32_t, uint32_t, bool), + bool partitionToSubsupport + ) + { + srand(0); + + for (uint32_t i = 0; i < assetDescCount; ++i) + { + // Create an asset + NvBlastAsset* asset = buildAsset(assetDescs[i]); + + // Perform repeated partitioning + instanceAndPartitionRecursively(*asset, partitionToSubsupport, preSplitTest, postSplitTest); + + // Free the asset + free(asset); + } + } + + static void compareFamilies(const NvBlastFamily* family1, const NvBlastFamily* family2, size_t size, NvBlastLog logFn) + { + const char* block1 = reinterpret_cast<const char*>(family1); + const char* block2 = reinterpret_cast<const char*>(family2); +#if 0 + EXPECT_EQ(0, memcmp(block1, block2, size)); +#else + bool diffFound = false; + size_t startDiff = 0; + for (size_t i = 0; i < size; ++i) + { + if (block1[i] != block2[i]) + { + diffFound = true; + startDiff = i; + break; + } + } + if (!diffFound) + { + return; + } + size_t endDiff = startDiff; + for (size_t i = size; i--;) + { + if (block1[i] != block2[i]) + { + endDiff = i; + break; + } + } + std::ostringstream msg; + msg << "Block deserialization does not match current block in position range [" << startDiff << ", " << endDiff << "]."; + logFn(NvBlastMessage::Error, msg.str().c_str(), __FILE__, __LINE__); +#endif + } + + static void testActorBlockSerialize(std::vector<NvBlastActor*>& actors, NvBlastLog logFn) + { + if (actors.size()) + { + const NvBlastFamily* family = NvBlastActorGetFamily(actors[0], logFn); + const uint32_t size = NvBlastFamilyGetSize(family, logFn); + s_storage.insert(s_storage.end(), (char*)family, (char*)family + size); + } + } + + static void testActorBlockDeserialize(std::vector<NvBlastActor*>& actors, NvBlastLog logFn) + { + if (actors.size()) + { + EXPECT_LT(s_curr, s_storage.size()); + const NvBlastFamily* family = reinterpret_cast<NvBlastFamily*>(&s_storage[s_curr]); + const uint32_t size = NvBlastFamilyGetSize(family, logFn); + EXPECT_LE(s_curr + size, s_storage.size()); + s_curr += size; + const NvBlastFamily* actorFamily = NvBlastActorGetFamily(actors[0], logFn); + // Family may contain different assets pointers, copy into new family block and set the same asset before comparing + Nv::Blast::Actor& a = *static_cast<Nv::Blast::Actor*>(actors[0]); + const Nv::Blast::Asset* solverAsset = a.getAsset(); + std::vector<char> storageFamilyCopy((char*)family, (char*)family + size); + NvBlastFamily* storageFamily = reinterpret_cast<NvBlastFamily*>(storageFamilyCopy.data()); + NvBlastFamilySetAsset(storageFamily, solverAsset, logFn); + { + const uint32_t actorCountExpected = NvBlastFamilyGetActorCount(storageFamily, logFn); + std::vector<NvBlastActor*> blockActors(actorCountExpected); + const uint32_t actorCountReturned = NvBlastFamilyGetActors(&blockActors[0], actorCountExpected, storageFamily, logFn); + EXPECT_EQ(actorCountExpected, actorCountReturned); + } + compareFamilies(storageFamily, actorFamily, size, logFn); + } + } + + // Serialize all actors and then deserialize back into a new family in a random order, and compare with the original family + static void testActorSerializationNewFamily(std::vector<NvBlastActor*>& actors, NvBlastLog logFn) + { + if (actors.size() == 0) + { + return; + } + + Nv::Blast::Actor& a = *static_cast<Nv::Blast::Actor*>(actors[0]); + const Nv::Blast::Asset* solverAsset = a.getAsset(); + + const uint32_t serSizeBound = NvBlastAssetGetActorSerializationSizeUpperBound(solverAsset, logFn); + + std::vector< std::vector<char> > streams(actors.size()); + for (size_t i = 0; i < actors.size(); ++i) + { + const uint32_t serSize = NvBlastActorGetSerializationSize(actors[i], logFn); + EXPECT_GE(serSizeBound, serSize); + std::vector<char>& stream = streams[i]; + stream.resize(serSize); + const uint32_t bytesWritten = NvBlastActorSerialize(&stream[0], serSize, actors[i], logFn); + EXPECT_EQ(serSize, bytesWritten); + } + + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(solverAsset, logFn)); + NvBlastFamily* newFamily = NvBlastAssetCreateFamily(fmem, solverAsset, logFn); + + std::vector<size_t> order(actors.size()); + for (size_t i = 0; i < order.size(); ++i) + { + order[i] = i; + } + std::random_shuffle(order.begin(), order.end()); + + for (size_t i = 0; i < actors.size(); ++i) + { + NvBlastActor* newActor = NvBlastFamilyDeserializeActor(newFamily, &streams[order[i]][0], logFn); + EXPECT_TRUE(newActor != nullptr); + } + + const NvBlastFamily* oldFamily = NvBlastActorGetFamily(&a, logFn); + compareFamilies(oldFamily, newFamily, NvBlastFamilyGetSize(oldFamily, logFn), logFn); + + free(newFamily); + } + + // Copy the family and then serialize some subset of actors, deleting them afterwards. + // Then, deserialize back into the block and compare the original and new families. + static void testActorSerializationPartialBlock(std::vector<NvBlastActor*>& actors, NvBlastLog logFn) + { + if (actors.size() <= 1) + { + return; + } + + Nv::Blast::Actor& a = *static_cast<Nv::Blast::Actor*>(actors[0]); + const Nv::Blast::Asset* solverAsset = a.getAsset(); + + const NvBlastFamily* oldFamily = NvBlastActorGetFamily(&a, logFn); + const uint32_t size = NvBlastFamilyGetSize(oldFamily, logFn); + std::vector<char> buffer((char*)oldFamily, (char*)oldFamily + size); + NvBlastFamily* familyCopy = reinterpret_cast<NvBlastFamily*>(&buffer[0]); + + const uint32_t serCount = 1 + (rand() % actors.size() - 1); + + const uint32_t actorCount = NvBlastFamilyGetActorCount(familyCopy, logFn); + std::vector<NvBlastActor*> actorsRemaining(actorCount); + const uint32_t actorsInFamily = NvBlastFamilyGetActors(&actorsRemaining[0], actorCount, familyCopy, logFn); + EXPECT_EQ(actorCount, actorsInFamily); + + const uint32_t serSizeBound = NvBlastAssetGetActorSerializationSizeUpperBound(solverAsset, logFn); + + std::vector< std::vector<char> > streams(serCount); + for (uint32_t i = 0; i < serCount; ++i) + { + std::vector<char>& stream = streams[i]; + const uint32_t indexToStream = rand() % actorsRemaining.size(); + NvBlastActor* actorToStream = actorsRemaining[indexToStream]; + std::swap(actorsRemaining[indexToStream], actorsRemaining[actorsRemaining.size() - 1]); + actorsRemaining.pop_back(); + const uint32_t serSize = NvBlastActorGetSerializationSize(actorToStream, logFn); + EXPECT_GE(serSizeBound, serSize); + stream.resize(serSize); + const uint32_t bytesWritten = NvBlastActorSerialize(&stream[0], serSize, actorToStream, logFn); + EXPECT_EQ(serSize, bytesWritten); + NvBlastActorDeactivate(actorToStream, logFn); + } + + for (uint32_t i = 0; i < serCount; ++i) + { + NvBlastActor* newActor = NvBlastFamilyDeserializeActor(familyCopy, &streams[i][0], logFn); + EXPECT_TRUE(newActor != nullptr); + } + + compareFamilies(oldFamily, familyCopy, size, logFn); + } + + void damageLeafSupportActors + ( + uint32_t assetCount, + uint32_t familyCount, + uint32_t damageCount, + bool simple, + void (*actorTest)(const Nv::Blast::Actor&, NvBlastLog), + void (*postDamageTest)(std::vector<NvBlastActor*>&, NvBlastLog), + CubeAssetGenerator::BondFlags bondFlags = CubeAssetGenerator::BondFlags::ALL_BONDS + ) + { + const float relativeDamageRadius = simple ? 0.75f : 0.2f; + const float compressiveDamage = 1.0f; + const uint32_t minChunkCount = simple ? 9 : 100; + const uint32_t maxChunkCount = simple ? 9 : 10000; + const bool printActorCount = false; + + srand(0); + + std::cout << "Asset # (out of " << assetCount << "): "; + for (uint32_t assetNum = 0; assetNum < assetCount; ++assetNum) + { + std::cout << assetNum + 1 << ".. "; + CubeAssetGenerator::Settings settings; + settings.extents = GeneratorAsset::Vec3(1, 1, 1); + settings.bondFlags = bondFlags; + CubeAssetGenerator::DepthInfo depthInfo; + depthInfo.slicesPerAxis = GeneratorAsset::Vec3(1, 1, 1); + depthInfo.flag = NvBlastChunkDesc::Flags::NoFlags; + settings.depths.push_back(depthInfo); + uint32_t chunkCount = 1; + while (chunkCount < minChunkCount) + { + uint32_t chunkMul; + do + { + depthInfo.slicesPerAxis = simple ? GeneratorAsset::Vec3(2, 2, 2) : GeneratorAsset::Vec3((float)(1 + rand() % 4), (float)(1 + rand() % 4), (float)(1 + rand() % 4)); + chunkMul = (uint32_t)(depthInfo.slicesPerAxis.x * depthInfo.slicesPerAxis.y * depthInfo.slicesPerAxis.z); + } while (chunkMul == 1); + if (chunkCount*chunkMul > maxChunkCount) + { + break; + } + chunkCount *= chunkMul; + settings.depths.push_back(depthInfo); + settings.extents = settings.extents * depthInfo.slicesPerAxis; + } + settings.depths.back().flag = NvBlastChunkDesc::SupportFlag; // Leaves are support + + // Make largest direction unit size + settings.extents = settings.extents * (1.0f / std::max(settings.extents.x, std::max(settings.extents.y, settings.extents.z))); + + // Create asset + GeneratorAsset testAsset; + CubeAssetGenerator::generate(testAsset, settings); + + NvBlastAssetDesc desc; + desc.chunkDescs = testAsset.solverChunks.data(); + desc.chunkCount = (uint32_t)testAsset.solverChunks.size(); + desc.bondDescs = testAsset.solverBonds.data(); + desc.bondCount = (uint32_t)testAsset.solverBonds.size(); + + NvBlastAsset* asset = buildAsset(desc); + NvBlastID assetID = NvBlastAssetGetID(asset, messageLog); + + // copy asset (for setAsset testing) + const char* data = (const char*)asset; + const uint32_t dataSize = NvBlastAssetGetSize(asset, messageLog); + char* duplicateData = (char*)alloc(dataSize); + memcpy(duplicateData, data, dataSize); + NvBlastAsset* assetDuplicate = (NvBlastAsset*)duplicateData; + + // Generate families + for (uint32_t familyNum = 0; familyNum < familyCount; ++familyNum) + { + // family + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog); // Using zeroingAlloc in case actorTest compares memory blocks + NvBlastID id = NvBlastFamilyGetAssetID(family, messageLog); + EXPECT_TRUE(!memcmp(&assetID, &id, sizeof(NvBlastID))); + if (rand() % 2 == 0) + { + // replace asset with duplicate in half of cases to test setAsset + NvBlastFamilySetAsset(family, assetDuplicate, messageLog); + NvBlastID id2 = NvBlastFamilyGetAssetID(family, messageLog); + EXPECT_TRUE(!memcmp(&assetID, &id2, sizeof(NvBlastID))); + } + + // actor + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + m_scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, m_scratch.data(), messageLog); + EXPECT_TRUE(actor != nullptr); + + // Generate damage + std::set<NvBlastActor*> actors; + actors.insert(actor); + if (printActorCount) std::cout << "Actors: 1.. "; + for (uint32_t damageNum = 0; damageNum < damageCount; ++damageNum) + { + GeneratorAsset::Vec3 localPos = settings.extents*GeneratorAsset::Vec3((float)rand() / RAND_MAX - 0.5f, (float)rand() / RAND_MAX - 0.5f, (float)rand() / RAND_MAX - 0.5f); + blast(actors, &testAsset, localPos, relativeDamageRadius, relativeDamageRadius*1.2f, compressiveDamage); + if (printActorCount) std::cout << actors.size() << ".. "; + if (actors.size() > 0) + { + const NvBlastFamily* family = NvBlastActorGetFamily(*actors.begin(), messageLog); + const uint32_t actorCount = NvBlastFamilyGetActorCount(family, messageLog); + EXPECT_EQ((uint32_t)actors.size(), actorCount); + if ((uint32_t)actors.size() == actorCount) + { + std::vector<NvBlastActor*> buffer1(actorCount); + const uint32_t actorsWritten = NvBlastFamilyGetActors(&buffer1[0], actorCount, family, messageLog); + EXPECT_EQ(actorsWritten, actorCount); + std::vector<NvBlastActor*> buffer2(actors.begin(), actors.end()); + EXPECT_EQ(0, memcmp(&buffer1[0], &buffer2[0], actorCount*sizeof(NvBlastActor*))); + } + } + // Test individual actors + if (actorTest != nullptr) + { + for (std::set<NvBlastActor*>::iterator k = actors.begin(); k != actors.end(); ++k) + { + actorTest(*static_cast<Nv::Blast::Actor*>(*k), messageLog); + } + } + } + if (printActorCount) std::cout << "\n"; + + // Test fractured actor set + if (postDamageTest) + { + std::vector<NvBlastActor*> actorArray(actors.begin(), actors.end()); + postDamageTest(actorArray, messageLog); + } + + // Release remaining actors + for (std::set<NvBlastActor*>::iterator k = actors.begin(); k != actors.end(); ++k) + { + NvBlastActorDeactivate(*k, messageLog); + } + actors.clear(); + + free(family); + } + + // Release asset data + free(asset); + free(assetDuplicate); + } + std::cout << "done.\n"; + } + + std::vector<NvBlastAsset*> m_assets; + std::vector<NvBlastActor*> m_actors; + std::vector<char> m_scratch; + static std::vector<char> s_storage; + + static size_t s_curr; +}; + +// Static values +template<int FailLevel, int Verbosity> +std::vector<char> ActorTest<FailLevel, Verbosity>::s_storage; + +template<int FailLevel, int Verbosity> +size_t ActorTest<FailLevel, Verbosity>::s_curr; + +// Specializations +typedef ActorTest<NvBlastMessage::Error, 1> ActorTestAllowWarnings; +typedef ActorTest<NvBlastMessage::Warning, 1> ActorTestStrict; + +// Tests +TEST_F(ActorTestStrict, InstanceActors) +{ + // Build assets and instance actors + buildAssets(); + instanceActors(); + + // Release actors and destroy assets + releaseActors(); + destroyAssets(); +} + +TEST_F(ActorTestAllowWarnings, ActorHealthInitialization) +{ + // Test all assets + std::vector<NvBlastAssetDesc> assetDescs; + assetDescs.insert(assetDescs.end(), g_assetDescs, g_assetDescs + getAssetDescCount()); + assetDescs.insert(assetDescs.end(), g_assetDescsMissingCoverage, g_assetDescsMissingCoverage + getAssetDescMissingCoverageCount()); + + struct TestMode + { + enum Enum + { + Uniform, + Nonuniform, + + Count + }; + }; + + for (auto assetDesc : assetDescs) + { + NvBlastAsset* asset = buildAsset(assetDesc); + EXPECT_TRUE(asset != nullptr); + + Nv::Blast::Asset& assetInt = static_cast<Nv::Blast::Asset&>(*asset); + + NvBlastSupportGraph graph = NvBlastAssetGetSupportGraph(asset, nullptr); + + std::vector<float> supportChunkHealths(graph.nodeCount); + for (size_t i = 0; i < supportChunkHealths.size(); ++i) + { + supportChunkHealths[i] = 1.0f + (float)i; + } + + std::vector<float> bondHealths(assetInt.getBondCount()); + for (size_t i = 0; i < bondHealths.size(); ++i) + { + bondHealths[i] = 1.5f + (float)i; + } + + for (int chunkTestMode = 0; chunkTestMode < TestMode::Count; ++chunkTestMode) + { + for (int bondTestMode = 0; bondTestMode < TestMode::Count; ++bondTestMode) + { + NvBlastActorDesc actorDesc; + + switch (chunkTestMode) + { + default: + case TestMode::Uniform: + actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + break; + case TestMode::Nonuniform: + actorDesc.initialSupportChunkHealths = supportChunkHealths.data(); + break; + } + + switch (bondTestMode) + { + default: + case TestMode::Uniform: + actorDesc.initialBondHealths = nullptr; + actorDesc.uniformInitialBondHealth = 2.0f; + break; + case TestMode::Nonuniform: + actorDesc.initialBondHealths = bondHealths.data(); + break; + } + + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, nullptr); + std::vector<char> scratch((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], messageLog); + EXPECT_TRUE(actor != nullptr); + + Nv::Blast::Actor& actorInt = static_cast<Nv::Blast::Actor&>(*actor); + Nv::Blast::FamilyHeader* header = actorInt.getFamilyHeader(); + + + for (uint32_t i = 0; i < graph.nodeCount; ++i) + { + const uint32_t supportChunkIndex = graph.chunkIndices[i]; + for (Nv::Blast::Asset::DepthFirstIt it(assetInt, supportChunkIndex); (bool)it; ++it) + { + const uint32_t chunkIndex = (uint32_t)it; + const uint32_t lowerSupportIndex = assetInt.getContiguousLowerSupportIndex(chunkIndex); + NVBLAST_ASSERT(lowerSupportIndex < assetInt.getLowerSupportChunkCount()); + const float health = header->getLowerSupportChunkHealths()[lowerSupportIndex]; + switch (chunkTestMode) + { + default: + case TestMode::Uniform: + EXPECT_EQ(1.0f, health); + break; + case TestMode::Nonuniform: + EXPECT_EQ(supportChunkHealths[i], health); + break; + } + } + } + + for (uint32_t i = 0; i < assetInt.getBondCount(); ++i) + { + switch (bondTestMode) + { + default: + case TestMode::Uniform: + EXPECT_EQ(2.0f, header->getBondHealths()[i]); + break; + case TestMode::Nonuniform: + EXPECT_EQ(bondHealths[i], header->getBondHealths()[i]); + break; + } + } + + NvBlastActorDeactivate(actor, messageLog); + free(family); + } + } + + free(asset); + } +} + +TEST_F(ActorTestStrict, PartitionActorsToSupportChunksTestCounts) +{ + partitionActorsToSupportChunks(getAssetDescCount(), g_assetDescs, nullptr, recursivePartitionPostSplitTestCounts, false); +} + +TEST_F(ActorTestAllowWarnings, PartitionActorsFromBadDescriptorsToSupportChunksTestCounts) +{ + partitionActorsToSupportChunks(getAssetDescMissingCoverageCount(), g_assetDescsMissingCoverage, nullptr, recursivePartitionPostSplitTestCounts, false); +} + +TEST_F(ActorTestStrict, PartitionActorsToLeafChunksTestCounts) +{ + partitionActorsToSupportChunks(getAssetDescCount(), g_assetDescs, nullptr, recursivePartitionPostSplitTestCounts, true); +} + +TEST_F(ActorTestAllowWarnings, PartitionActorsFromBadDescriptorsToLeafChunksTestCounts) +{ + partitionActorsToSupportChunks(getAssetDescMissingCoverageCount(), g_assetDescsMissingCoverage, nullptr, recursivePartitionPostSplitTestCounts, true); +} + +TEST_F(ActorTestStrict, PartitionActorsToSupportChunksTestVisibility) +{ + partitionActorsToSupportChunks(getAssetDescCount(), g_assetDescs, testActorVisibleChunks, recursivePartitionPostSplitTestVisibleChunks, false); +} + +TEST_F(ActorTestAllowWarnings, PartitionActorsFromBadDescriptorsToSupportChunksTestVisibility) +{ + partitionActorsToSupportChunks(getAssetDescMissingCoverageCount(), g_assetDescsMissingCoverage, testActorVisibleChunks, recursivePartitionPostSplitTestVisibleChunks, false); +} + +TEST_F(ActorTestStrict, PartitionActorsToLeafChunksTestVisibility) +{ + partitionActorsToSupportChunks(getAssetDescCount(), g_assetDescs, testActorVisibleChunks, recursivePartitionPostSplitTestVisibleChunks, true); +} + +TEST_F(ActorTestAllowWarnings, PartitionActorsFromBadDescriptorsToLeafChunksTestVisibility) +{ + partitionActorsToSupportChunks(getAssetDescMissingCoverageCount(), g_assetDescsMissingCoverage, testActorVisibleChunks, recursivePartitionPostSplitTestVisibleChunks, true); +} + +TEST_F(ActorTestStrict, DamageLeafSupportActorsTestVisibility) +{ + damageLeafSupportActors(4, 4, 5, false, testActorVisibleChunks, nullptr); +} + +TEST_F(ActorTestStrict, DamageLeafSupportActorTestBlockSerialization) +{ + s_storage.resize(0); + damageLeafSupportActors(4, 4, 5, false, nullptr, testActorBlockSerialize); + s_curr = 0; + damageLeafSupportActors(4, 4, 5, false, nullptr, testActorBlockDeserialize); + s_storage.resize(0); +} + +TEST_F(ActorTestStrict, DamageSimpleLeafSupportActorTestActorSerializationNewFamily) +{ + damageLeafSupportActors(1, 1, 4, true, nullptr, testActorSerializationNewFamily); +} + +TEST_F(ActorTestStrict, DamageSimpleLeafSupportActorTestActorSerializationPartialBlock) +{ + damageLeafSupportActors(1, 1, 4, true, nullptr, testActorSerializationPartialBlock); +} + +TEST_F(ActorTestStrict, DamageLeafSupportActorTestActorSerializationNewFamily) +{ + damageLeafSupportActors(4, 4, 4, false, nullptr, testActorSerializationNewFamily); +} + +TEST_F(ActorTestStrict, DamageLeafSupportActorTestActorSerializationPartialBlock) +{ + damageLeafSupportActors(4, 4, 4, false, nullptr, testActorSerializationPartialBlock); +} + +TEST_F(ActorTestStrict, DamageMultipleIslandLeafSupportActorsTestVisibility) +{ + damageLeafSupportActors(4, 4, 5, false, testActorVisibleChunks, nullptr, CubeAssetGenerator::BondFlags::Y_BONDS | CubeAssetGenerator::BondFlags::Z_BONDS); // Only connect y-z plane islands + damageLeafSupportActors(4, 4, 5, false, testActorVisibleChunks, nullptr, CubeAssetGenerator::BondFlags::Z_BONDS); // Only connect z-direction islands + damageLeafSupportActors(4, 4, 5, false, testActorVisibleChunks, nullptr, CubeAssetGenerator::BondFlags::NO_BONDS); // All support chunks disconnected (single-chunk islands) +} diff --git a/NvBlast/test/src/unit/AssetTests.cpp b/NvBlast/test/src/unit/AssetTests.cpp new file mode 100644 index 0000000..24e5f77 --- /dev/null +++ b/NvBlast/test/src/unit/AssetTests.cpp @@ -0,0 +1,529 @@ +#include "NvBlastAsset.h" + +#include "BlastBaseTest.h" + +#include "NvBlastTkFramework.h" + +#include <algorithm> + + +#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 <fstream> +#include <iosfwd> + +#ifdef WIN32 +#include <windows.h> +#endif + +template<int FailLevel, int Verbosity> +class AssetTest : public BlastBaseTest<FailLevel, Verbosity> +{ +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<FailLevel, Verbosity>::messageLog(type, msg, file, line); + } + + static void* alloc(size_t size) + { + return BlastBaseTest<FailLevel, Verbosity>::alloc(size); + } + + static void free(void* mem) + { + BlastBaseTest<FailLevel, Verbosity>::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<const uint32_t*, uint32_t>(a.m_graph.getChunkIndices(), chunkIndexStop, chunkIndex); + EXPECT_EQ(chunkIndexStop, it); + } + } + } + + NvBlastAsset* buildAsset(const ExpectedAssetValues& expected, const NvBlastAssetDesc* desc) + { + std::vector<char> 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<NvBlastChunkDesc> chunkDescs(desc->chunkDescs, desc->chunkDescs + desc->chunkCount); + shuffledDesc.chunkDescs = &chunkDescs[0]; + std::vector<NvBlastBondDesc> bondDescs(desc->bondDescs, desc->bondDescs + desc->bondCount); + shuffledDesc.bondDescs = &bondDescs[0]; + if (!useTk) + { + std::vector<char> 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<uint32_t> shuffledOrder(chunkDescCount); + for (uint32_t i = 0; i < chunkDescCount; ++i) + { + shuffledOrder[i] = i; + } + + // An array into which to copy the reordered descs + std::vector<NvBlastChunkDesc> shuffledChunkDescs(chunkDescCount); + + std::vector<char> 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<NvBlastBondDesc> 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<uint32_t> chunkReorderMap(chunkDescCount); + std::vector<char> 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<NvBlastMessage::Error, 0> AssetTestAllowWarningsSilently; +typedef AssetTest<NvBlastMessage::Error, 1> AssetTestAllowWarnings; +typedef AssetTest<NvBlastMessage::Warning, 1> AssetTestStrict; + + +TEST_F(AssetTestStrict, BuildAssets) +{ + const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); + + std::vector<NvBlastAsset*> 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<Nv::Blast::Asset *> assets(assetDescCount); + + // Build + for (uint32_t i = 0; i < assetDescCount; ++i) + { + assets[i] = reinterpret_cast<Nv::Blast::Asset*>(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<Nv::Blast::Asset *>(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<Nv::Blast::Asset *> assets(assetDescCount); + + // Build + for (uint32_t i = 0; i < assetDescCount; ++i) + { + assets[i] = reinterpret_cast<Nv::Blast::Asset*>(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<Nv::Blast::Asset, Nv::Blast::Serialization::Asset::Reader, Nv::Blast::Serialization::Asset::Builder>::serializeIntoNewBuffer(asset, &buffer, size); + EXPECT_TRUE(serializeAssetIntoNewBuffer(asset, &buffer, size)); + + free(static_cast<void*>(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<Nv::Blast::Asset *> assets(assetDescCount); + + // Build + for (uint32_t i = 0; i < assetDescCount; ++i) + { + assets[i] = reinterpret_cast<Nv::Blast::Asset*>(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<void*>(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<Nv::Blast::Asset *> assets(assetDescCount); + + // Build + for (uint32_t i = 0; i < assetDescCount; ++i) + { + assets[i] = reinterpret_cast<Nv::Blast::Asset*>(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<Nv::Blast::Asset*>(deserializeAsset(buffer, size)); + + //TODO: Compare assets + checkAssetsExpected(*rtAsset, g_assetExpectedValues[i]); + + free(static_cast<void*>(buffer)); + free(static_cast<void*>(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<NvBlastAsset*> assets(assetDescCount); + + // Build + for (uint32_t i = 0; i < assetDescCount; ++i) + { + const NvBlastAssetDesc* desc = &g_assetDescsMissingCoverage[i]; + NvBlastAssetDesc fixedDesc = *desc; + std::vector<NvBlastChunkDesc> chunkDescs(desc->chunkDescs, desc->chunkDescs + desc->chunkCount); + std::vector<NvBlastBondDesc> bondDescs(desc->bondDescs, desc->bondDescs + desc->bondCount); + std::vector<uint32_t> chunkReorderMap(desc->chunkCount); + std::vector<char> 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); + } +} diff --git a/NvBlast/test/src/unit/CoreTests.cpp b/NvBlast/test/src/unit/CoreTests.cpp new file mode 100644 index 0000000..4aff4ae --- /dev/null +++ b/NvBlast/test/src/unit/CoreTests.cpp @@ -0,0 +1,293 @@ +#include <algorithm> +#include "gtest/gtest.h" + +//#include "NvBlast.h" +#include "NvBlastActor.h" +#include "NvBlastIndexFns.h" + +#include "AlignedAllocator.h" + +#include "TestAssets.h" +#include "NvBlastActor.h" + +static void messageLog(int type, const char* msg, const char* file, int line) +{ + { + switch (type) + { + case NvBlastMessage::Error: std::cout << "NvBlast Error message in " << file << "(" << line << "): " << msg << "\n"; break; + case NvBlastMessage::Warning: std::cout << "NvBlast Warning message in " << file << "(" << line << "): " << msg << "\n"; break; + case NvBlastMessage::Info: std::cout << "NvBlast Info message in " << file << "(" << line << "): " << msg << "\n"; break; + case NvBlastMessage::Debug: std::cout << "NvBlast Debug message in " << file << "(" << line << "): " << msg << "\n"; break; + } + } +} + +TEST(CoreTests, IndexStartLookup) +{ + uint32_t lookup[32]; + uint32_t indices[] = {1,1,2,2,4,4,4}; + + Nv::Blast::createIndexStartLookup<uint32_t>(lookup, 0, 30, indices, 7, 4); + + EXPECT_EQ(lookup[0], 0); + EXPECT_EQ(lookup[1], 0); + EXPECT_EQ(lookup[2], 2); + EXPECT_EQ(lookup[3], 4); + EXPECT_EQ(lookup[4], 4); + EXPECT_EQ(lookup[5], 7); + EXPECT_EQ(lookup[31], 7); +} + +#include "NvBlastGeometry.h" + +TEST(CoreTests, FindChunkByPosition) +{ + std::vector<char> scratch; + const NvBlastAssetDesc& desc = g_assetDescs[0]; // 1-cube + scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(&desc, nullptr)); + void* amem = alignedAlloc<malloc>(NvBlastGetAssetMemorySize(&desc, nullptr)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &desc, &scratch[0], nullptr); + ASSERT_TRUE(asset != nullptr); + + uint32_t expectedNode[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; + const float positions[] = { + -2.0f, -2.0f, -2.0f, + +2.0f, -2.0f, -2.0f, + -2.0f, +2.0f, -2.0f, + +2.0f, +2.0f, -2.0f, + -2.0f, -2.0f, +2.0f, + +2.0f, -2.0f, +2.0f, + -2.0f, +2.0f, +2.0f, + +2.0f, +2.0f, +2.0f, + }; + const float* pos = &positions[0]; + + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alignedAlloc<malloc>(NvBlastAssetGetFamilyMemorySize(asset, nullptr)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, nullptr); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, nullptr)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], nullptr); + ASSERT_TRUE(actor != nullptr); + + std::vector<uint32_t> graphNodeIndices; + graphNodeIndices.resize(NvBlastActorGetGraphNodeCount(actor, nullptr)); + const float* bondHealths = NvBlastActorGetBondHealths(actor, messageLog); + uint32_t graphNodesCount = NvBlastActorGetGraphNodeIndices(graphNodeIndices.data(), (uint32_t)graphNodeIndices.size(), actor, nullptr); + + const NvBlastBond* bonds = NvBlastAssetGetBonds(asset, nullptr); + NvBlastSupportGraph graph = NvBlastAssetGetSupportGraph(asset, nullptr); + for (int i = 0; i < 8; ++i, pos += 3) + { + EXPECT_EQ(expectedNode[i], Nv::Blast::findNodeByPosition(pos, graphNodesCount, graphNodeIndices.data(), graph, bonds, bondHealths)); + EXPECT_EQ(expectedNode[i] + 1, NvBlastActorClosestChunk(pos, actor, nullptr)); // Works because (chunk index) = (node index) + 1 in these cases + } + + EXPECT_TRUE(NvBlastActorDeactivate(actor, nullptr)); + alignedFree<free>(family); + alignedFree<free>(asset); +} + +TEST(CoreTests, FindChunkByPositionUShape) +{ + /* + considering this graph + + 4->5->6 + ^ + | + 1->2->3 + + and trying to find chunks by some position + */ + const NvBlastChunkDesc uchunks[7] = + { + // centroid volume parent idx flags ID + { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 5 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 6 } + }; + + const NvBlastBondDesc ubonds[5] = + { + // chunks normal area centroid userData + { { 2, 1 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 1.0f, 0.0f }, 0 } }, // index swap should not matter + { { 2, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 4.0f, 1.0f, 0.0f }, 0 } }, + { { 1, 4 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 1.0f, 2.0f, 0.0f }, 0 } }, + { { 4, 5 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 3.0f, 0.0f }, 0 } }, + { { 5, 6 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 4.0f, 3.0f, 0.0f }, 0 } }, + }; + + const NvBlastAssetDesc desc = { 7, uchunks, 5, ubonds }; + std::vector<char> scratch; + scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(&desc, messageLog)); + void* amem = alignedAlloc<malloc>(NvBlastGetAssetMemorySize(&desc, messageLog)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &desc, &scratch[0], messageLog); + ASSERT_TRUE(asset != nullptr); + + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alignedAlloc<malloc>(NvBlastAssetGetFamilyMemorySize(asset, messageLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, nullptr); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], nullptr); + ASSERT_TRUE(actor != nullptr); + + std::vector<uint32_t> graphNodeIndices; + graphNodeIndices.resize(NvBlastActorGetGraphNodeCount(actor, nullptr)); + uint32_t graphNodesCount = NvBlastActorGetGraphNodeIndices(graphNodeIndices.data(), (uint32_t)graphNodeIndices.size(), actor, nullptr); + + const NvBlastBond* bonds = NvBlastAssetGetBonds(asset, nullptr); + NvBlastSupportGraph graph = NvBlastAssetGetSupportGraph(asset, nullptr); + + srand(100); + for (uint32_t i = 0; i < 100000; i++) + { + float rx = 20 * (float)(rand() - 1) / RAND_MAX - 10; + float ry = 20 * (float)(rand() - 1) / RAND_MAX - 10; + float rz = 0.0f; + float rpos[] = { rx, ry, rz }; + + // open boundaries + uint32_t col = std::max(0, std::min(2, int(rx / 2))); + uint32_t row = std::max(0, std::min(1, int(ry / 2))); + uint32_t expectedNode = col + row * 3; + + //printf("iteration %i: %.1f %.1f %.1f expected: %d\n", i, rpos[0], rpos[1], rpos[2], expectedNode); + { + uint32_t returnedNode = Nv::Blast::findNodeByPosition(rpos, graphNodesCount, graphNodeIndices.data(), graph, bonds, NvBlastActorGetBondHealths(actor, messageLog)); + if (expectedNode != returnedNode) + Nv::Blast::findNodeByPosition(rpos, graphNodesCount, graphNodeIndices.data(), graph, bonds, NvBlastActorGetBondHealths(actor, messageLog)); + EXPECT_EQ(expectedNode, returnedNode); + } + { + // +1 to account for graph vs. asset indices + uint32_t expectedChunk = expectedNode + 1; + uint32_t returnedChunk = NvBlastActorClosestChunk(rpos, actor, nullptr); + if (expectedChunk != returnedChunk) + NvBlastActorClosestChunk(rpos, actor, nullptr); + EXPECT_EQ(expectedChunk, returnedChunk); + } + + } + + EXPECT_TRUE(NvBlastActorDeactivate(actor, messageLog)); + + alignedFree<free>(family); + alignedFree<free>(asset); +} + +TEST(CoreTests, FindChunkByPositionLandlocked) +{ + const NvBlastChunkDesc chunks[10] = + { + // centroid volume parent idx flags ID + { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 5 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 6 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 7 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 8 }, + { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 9 }, + }; + + const NvBlastBondDesc bonds[12] = + { + // chunks normal area centroid userData + { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 1.0f, 0.0f }, 0 } }, + { { 2, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 4.0f, 1.0f, 0.0f }, 0 } }, + { { 4, 5 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 3.0f, 0.0f }, 0 } }, + { { 5, 6 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 4.0f, 3.0f, 0.0f }, 0 } }, + { { 7, 8 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 5.0f, 0.0f }, 0 } }, + { { 8, 9 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 4.0f, 5.0f, 0.0f }, 0 } }, + { { 1, 4 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 1.0f, 2.0f, 0.0f }, 0 } }, + { { 2, 5 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 3.0f, 2.0f, 0.0f }, 0 } }, + { { 3, 6 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 5.0f, 2.0f, 0.0f }, 0 } }, + { { 4, 7 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 1.0f, 4.0f, 0.0f }, 0 } }, + { { 5, 8 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 3.0f, 4.0f, 0.0f }, 0 } }, + { { 6, 9 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 5.0f, 4.0f, 0.0f }, 0 } }, + }; + + const NvBlastAssetDesc desc = { 10, chunks, 12, bonds }; + std::vector<char> scratch; + scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(&desc, messageLog)); + void* amem = alignedAlloc<malloc>(NvBlastGetAssetMemorySize(&desc, messageLog)); + NvBlastAsset* asset = NvBlastCreateAsset(amem, &desc, &scratch[0], messageLog); + ASSERT_TRUE(asset != nullptr); + + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alignedAlloc<malloc>(NvBlastAssetGetFamilyMemorySize(asset, nullptr)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, nullptr); + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, nullptr)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], nullptr); + ASSERT_TRUE(actor != nullptr); + + const NvBlastBond* assetBonds = NvBlastAssetGetBonds(asset, nullptr); + NvBlastSupportGraph graph = NvBlastAssetGetSupportGraph(asset, nullptr); + + float point[4] = { 3.0f, 3.0f, 0.0f }; + EXPECT_EQ(5, NvBlastActorClosestChunk(point, actor, nullptr)); + { + std::vector<uint32_t> graphNodeIndices; + graphNodeIndices.resize(NvBlastActorGetGraphNodeCount(actor, nullptr)); + uint32_t graphNodesCount = NvBlastActorGetGraphNodeIndices(graphNodeIndices.data(), (uint32_t)graphNodeIndices.size(), actor, nullptr); + + EXPECT_EQ(4, Nv::Blast::findNodeByPosition(point, graphNodesCount, graphNodeIndices.data(), graph, assetBonds, NvBlastActorGetBondHealths(actor, messageLog))); + } + + NvBlastChunkFractureData chunkBuffer[1]; + NvBlastFractureBuffers events = { 0, 1, nullptr, chunkBuffer }; + + NvBlastChunkFractureData chunkFracture = { 0, 5, 1.0f }; + NvBlastFractureBuffers commands = { 0, 1, nullptr, &chunkFracture }; + + NvBlastActorApplyFracture(&events, actor, &commands, messageLog, nullptr); + EXPECT_EQ(1, events.chunkFractureCount); + + NvBlastActor* newActors[5]; + NvBlastActorSplitEvent splitEvent = { nullptr, newActors }; + scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, messageLog)); + size_t newActorsCount = NvBlastActorSplit(&splitEvent, actor, 5, scratch.data(), messageLog, nullptr); + + ASSERT_EQ(actor, newActors[1]); + + EXPECT_NE(5, NvBlastActorClosestChunk(point, actor, nullptr)); + + float point2[4] = { 80.0f, 80.0f, 80.0f }; + EXPECT_EQ(5, NvBlastActorClosestChunk(point2, newActors[0], nullptr)); + + { + const float* bondHealths = NvBlastActorGetBondHealths(actor, messageLog); + std::vector<uint32_t> graphNodeIndices; + graphNodeIndices.resize(NvBlastActorGetGraphNodeCount(actor, nullptr)); + uint32_t graphNodesCount = NvBlastActorGetGraphNodeIndices(graphNodeIndices.data(), (uint32_t)graphNodeIndices.size(), actor, nullptr); + + EXPECT_NE(4, Nv::Blast::findNodeByPosition(point, graphNodesCount, graphNodeIndices.data(), graph, assetBonds, bondHealths)); + + graphNodeIndices.resize(NvBlastActorGetGraphNodeCount(newActors[0], nullptr)); + graphNodesCount = NvBlastActorGetGraphNodeIndices(graphNodeIndices.data(), (uint32_t)graphNodeIndices.size(), newActors[0], nullptr); + + EXPECT_EQ(4, Nv::Blast::findNodeByPosition(point, graphNodesCount, graphNodeIndices.data(), graph, assetBonds, bondHealths)); + } + + + for (uint32_t i = 0; i < newActorsCount; ++i) + { + EXPECT_TRUE(NvBlastActorDeactivate(newActors[i], nullptr)); + } + + alignedFree<free>(family); + alignedFree<free>(asset); +} diff --git a/NvBlast/test/src/unit/FamilyGraphTests.cpp b/NvBlast/test/src/unit/FamilyGraphTests.cpp new file mode 100644 index 0000000..fdb9738 --- /dev/null +++ b/NvBlast/test/src/unit/FamilyGraphTests.cpp @@ -0,0 +1,377 @@ +#include "BlastBaseTest.h" + +#include "NvBlastSupportGraph.h" +#include "NvBlastFamilyGraph.h" +#include "NvBlastAssert.h" +#include "NvBlastIndexFns.h" + +#include <stdlib.h> +#include <ostream> +#include <stdint.h> +#include <map> +#include <algorithm> + + +// ==================================================================================================================== +// HELPERS +// ==================================================================================================================== + +::testing::AssertionResult VectorMatch(const std::vector<uint32_t>& actual, const uint32_t* expected, uint32_t size) +{ + for (size_t i(0); i < size; ++i) + { + if (expected[i] != actual[i]) + { + testing::Message msg; + msg << "array[" << i + << "] (" << actual[i] << ") != expected[" << i + << "] (" << expected[i] << ")"; + return (::testing::AssertionFailure(msg));; + } + } + + return ::testing::AssertionSuccess(); +} + +#define VECTOR_MATCH(actual, ...) \ +{ \ + const uint32_t arr[] = { __VA_ARGS__ }; \ + const uint32_t size = (sizeof(arr) / sizeof(arr[0])); \ + EXPECT_EQ(size, actual.size()); \ + EXPECT_TRUE(VectorMatch(actual, arr, size)); \ +} + + +// ==================================================================================================================== +// TEST CLASS +// ==================================================================================================================== + +using namespace Nv::Blast; + +template<int FailLevel, int Verbosity> +class FamilyGraphTest : public BlastBaseTest < FailLevel, Verbosity > +{ +public: + FamilyGraphTest() + { + } + +protected: + FamilyGraph* buildFamilyGraph(uint32_t chunkCount, const uint32_t* adjacentChunkPartition, const uint32_t* adjacentChunkIndices) + { + NVBLAST_ASSERT(m_memoryBlock.size() == 0); // can't build twice per test + + // Fill SupportGraph with data: + NvBlastCreateOffsetStart(sizeof(SupportGraph)); + const size_t NvBlastCreateOffsetAlign16(chunkIndicesOffset, chunkCount*sizeof(uint32_t)); + const size_t NvBlastCreateOffsetAlign16(adjacencyPartitionOffset, (chunkCount + 1)*sizeof(uint32_t)); + const size_t NvBlastCreateOffsetAlign16(adjacentNodeIndicesOffset, adjacentChunkPartition[chunkCount] * sizeof(uint32_t)); + const size_t NvBlastCreateOffsetAlign16(adjacentBondIndicesOffset, adjacentChunkPartition[chunkCount] * sizeof(uint32_t)); + const size_t graphDataSize = NvBlastCreateOffsetEndAlign16(); + m_graphMemory.resize(graphDataSize); + m_graph = reinterpret_cast<SupportGraph*>(m_graphMemory.data()); + + m_graph->m_nodeCount = chunkCount; + m_graph->m_chunkIndicesOffset = static_cast<uint32_t>(chunkIndicesOffset); + m_graph->m_adjacencyPartitionOffset = static_cast<uint32_t>(adjacencyPartitionOffset); + m_graph->m_adjacentNodeIndicesOffset = static_cast<uint32_t>(adjacentNodeIndicesOffset); + m_graph->m_adjacentBondIndicesOffset = static_cast<uint32_t>(adjacentBondIndicesOffset); + + memcpy(m_graph->getAdjacencyPartition(), adjacentChunkPartition, (chunkCount + 1) * sizeof(uint32_t)); + memcpy(m_graph->getAdjacentNodeIndices(), adjacentChunkIndices, adjacentChunkPartition[chunkCount] * sizeof(uint32_t)); + + // fill bondIndices by incrementing bondIndex and putting same bondIndex in mirror bond index for (n0, n1) == (n1, n0) + memset(m_graph->getAdjacentBondIndices(), (uint32_t)-1, adjacentChunkPartition[chunkCount] * sizeof(uint32_t)); + uint32_t bondIndex = 0; + for (uint32_t chunk0 = 0; chunk0 < m_graph->m_nodeCount; chunk0++) + { + for (uint32_t i = m_graph->getAdjacencyPartition()[chunk0]; i < m_graph->getAdjacencyPartition()[chunk0 + 1]; i++) + { + if (m_graph->getAdjacentBondIndices()[i] == (uint32_t)-1) + { + m_graph->getAdjacentBondIndices()[i] = bondIndex; + + uint32_t chunk1 = m_graph->getAdjacentNodeIndices()[i]; + for (uint32_t j = m_graph->getAdjacencyPartition()[chunk1]; j < m_graph->getAdjacencyPartition()[chunk1 + 1]; j++) + { + if (m_graph->getAdjacentNodeIndices()[j] == chunk0) + { + m_graph->getAdjacentBondIndices()[j] = bondIndex; + } + } + bondIndex++; + } + } + } + + // reserve memory for family graph and asset pointer + uint32_t familyGraphMemorySize = (uint32_t)FamilyGraph::requiredMemorySize(m_graph->m_nodeCount, bondIndex); + m_memoryBlock.resize(familyGraphMemorySize); + // placement new family graph + FamilyGraph* familyGraph = new(&m_memoryBlock[0]) FamilyGraph(m_graph); + + return familyGraph; + } + + struct IslandInfo + { + std::vector<NodeIndex> nodes; + }; + + /** + Function to gather islands info for tests and debug purposes + Returned islands sorted by nodes counts. Island nodes also sorted by NodeIndex. + */ + void getIslandsInfo(const FamilyGraph& graph, std::vector<IslandInfo>& info) + { + IslandId* islandIds = graph.getIslandIds(); + + std::map<IslandId, IslandInfo> islandMap; + + for (NodeIndex n = 0; n < m_graph->m_nodeCount; n++) + { + EXPECT_TRUE(islandIds[n] != invalidIndex<uint32_t>()); + IslandId islandId = islandIds[n]; + if (islandMap.find(islandId) == islandMap.end()) + { + IslandInfo info; + info.nodes.push_back(n); + islandMap[islandId] = info; + } + else + { + islandMap[islandId].nodes.push_back(n); + } + } + + for (auto it = islandMap.begin(); it != islandMap.end(); ++it) + { + std::sort(it->second.nodes.begin(), it->second.nodes.end()); + info.push_back(it->second); + } + + // sort islands by size ascending + std::sort(info.begin(), info.end(), [](const IslandInfo& i0, const IslandInfo& i1) -> bool + { + size_t s0 = i0.nodes.size(); + size_t s1 = i1.nodes.size(); + if (s0 == s1 && s0 > 0) + { + s0 = i0.nodes[0]; + s1 = i1.nodes[0]; + } + return s0 < s1; + }); + } + + static const uint32_t DEFAULT_ACTOR_INDEX = 0; + + SupportGraph* m_graph; + std::vector<char> m_graphMemory; + std::vector<char> m_memoryBlock; +}; + +typedef FamilyGraphTest<NvBlastMessage::Error, 1> FamilyGraphTestAllowWarnings; +typedef FamilyGraphTest<NvBlastMessage::Warning, 1> FamilyGraphTestStrict; + + +// ==================================================================================================================== +// GRAPH DATA +// ==================================================================================================================== + +// Graph 0: +// +// 0 -- 1 -- 2 -- 3 +// | | | | +// | | | | +// 4 -- 5 6 -- 7 +// +const uint32_t chunkCount0 = 8; +const uint32_t adjacentChunkPartition0[] = { 0, 2, 5, 8, 10, 12, 14, 16, 18 }; +const uint32_t adjacentChunkIndices0[] = { /*0*/ 1, 4, /*1*/ 0, 2, 5, /*2*/ 1, 3, 6, /*3*/ 2, 7, /*4*/ 0, 5, /*5*/ 1, 4, /*6*/ 2, 7, /*7*/ 3, 6 }; + + +// Graph 1: +// +// 0 -- 1 -- 2 -- 3 +// | | | | +// 4 -- 5 -- 6 -- 7 +// | | | | +// 8 -- 9 -- 10-- 11 +// +const uint32_t chunkCount1 = 12; +const uint32_t adjacentChunkPartition1[] = { 0, 2, 5, 8, 10, 13, 17, 21, 24, 26, 29, 32, 34 }; +const uint32_t adjacentChunkIndices1[] = { /*0*/ 1, 4, /*1*/ 0, 2, 5, /*2*/ 1, 3, 6, /*3*/ 2, 7, /*4*/ 0, 5, 8, /*5*/ 1, 4, 6, 9, /*6*/ 2, 5, 7, 10, + /*7*/ 3, 6, 11, /*8*/ 4, 9, /*9*/ 5, 8, 10, /*10*/ 6, 9, 11, /*11*/ 7, 10 }; + + +// ==================================================================================================================== +// TESTS +// ==================================================================================================================== + +TEST_F(FamilyGraphTestStrict, Graph0FindIslands0) +{ + FamilyGraph* graph = buildFamilyGraph(chunkCount0, adjacentChunkPartition0, adjacentChunkIndices0); + graph->initialize(DEFAULT_ACTOR_INDEX, m_graph); + + std::vector<char> scratch; + scratch.resize((size_t)FamilyGraph::findIslandsRequiredScratch(chunkCount0)); + + EXPECT_EQ(9, graph->getEdgesCount(m_graph)); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 0, 4, m_graph); + EXPECT_EQ(8, graph->getEdgesCount(m_graph)); + EXPECT_EQ(1, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph)); + + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 1, 2, m_graph); + EXPECT_EQ(1, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph)); + + std::vector<IslandInfo> info; + getIslandsInfo(*graph, info); + EXPECT_EQ(2, info.size()); + VECTOR_MATCH(info[0].nodes, 0, 1, 4, 5); + VECTOR_MATCH(info[1].nodes, 2, 3, 6, 7); +} + +TEST_F(FamilyGraphTestStrict, Graph0FindIslands1) +{ + FamilyGraph* graph = buildFamilyGraph(chunkCount0, adjacentChunkPartition0, adjacentChunkIndices0); + graph->initialize(DEFAULT_ACTOR_INDEX, m_graph); + + std::vector<char> scratch; + scratch.resize((size_t)FamilyGraph::findIslandsRequiredScratch(chunkCount0)); + + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 0, 4, m_graph); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 4, 5, m_graph); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 1, 2, m_graph); + EXPECT_EQ(6, graph->getEdgesCount(m_graph)); + EXPECT_EQ(3, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph)); + + std::vector<IslandInfo> info; + getIslandsInfo(*graph, info); + EXPECT_EQ(3, info.size()); + VECTOR_MATCH(info[0].nodes, 4); + VECTOR_MATCH(info[1].nodes, 0, 1, 5); + VECTOR_MATCH(info[2].nodes, 2, 3, 6, 7); +} + +TEST_F(FamilyGraphTestStrict, Graph0FindIslandsDifferentActors) +{ + const uint32_t ACTOR_0_INDEX = 5; + const uint32_t ACTOR_1_INDEX = 2; + + FamilyGraph* graph = buildFamilyGraph(chunkCount0, adjacentChunkPartition0, adjacentChunkIndices0); + graph->initialize(ACTOR_0_INDEX, m_graph); + + std::vector<char> scratch; + scratch.resize((size_t)FamilyGraph::findIslandsRequiredScratch(chunkCount0)); + + EXPECT_EQ(0, graph->findIslands(ACTOR_1_INDEX, &scratch[0], m_graph)); + EXPECT_EQ(1, graph->findIslands(ACTOR_0_INDEX, &scratch[0], m_graph)); + + graph->notifyEdgeRemoved(ACTOR_0_INDEX, 2, 1, m_graph); + EXPECT_EQ(8, graph->getEdgesCount(m_graph)); + + EXPECT_EQ(1, graph->findIslands(ACTOR_0_INDEX, &scratch[0], m_graph)); + + graph->notifyEdgeRemoved(ACTOR_1_INDEX, 2, 6, m_graph); + graph->notifyEdgeRemoved(ACTOR_1_INDEX, 7, 3, m_graph); + EXPECT_EQ(1, graph->findIslands(ACTOR_1_INDEX, &scratch[0], m_graph)); + + graph->notifyEdgeRemoved(ACTOR_0_INDEX, 0, 1, m_graph); + graph->notifyEdgeRemoved(ACTOR_0_INDEX, 4, 5, m_graph); + EXPECT_EQ(1, graph->findIslands(ACTOR_0_INDEX, &scratch[0], m_graph)); + + + std::vector<IslandInfo> info; + getIslandsInfo(*graph, info); + EXPECT_EQ(4, info.size()); + VECTOR_MATCH(info[0].nodes, 0, 4); + VECTOR_MATCH(info[1].nodes, 1, 5); + VECTOR_MATCH(info[2].nodes, 2, 3); + VECTOR_MATCH(info[3].nodes, 6, 7); +} + +TEST_F(FamilyGraphTestStrict, Graph1FindIslands0) +{ + FamilyGraph* graph = buildFamilyGraph(chunkCount1, adjacentChunkPartition1, adjacentChunkIndices1); + graph->initialize(DEFAULT_ACTOR_INDEX, m_graph); + + std::vector<char> scratch; + scratch.resize((size_t)FamilyGraph::findIslandsRequiredScratch(chunkCount1)); + + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 0, 4, m_graph); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 1, 5, m_graph); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 2, 6, m_graph); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 3, 7, m_graph); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 5, 6, m_graph); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 9, 10, m_graph); + EXPECT_EQ(11, graph->getEdgesCount(m_graph)); + EXPECT_EQ(3, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph)); + + std::vector<IslandInfo> info; + getIslandsInfo(*graph, info); + EXPECT_EQ(3, info.size()); + + VECTOR_MATCH(info[0].nodes, 0, 1, 2, 3); + VECTOR_MATCH(info[1].nodes, 4, 5, 8, 9); + VECTOR_MATCH(info[2].nodes, 6, 7, 10, 11); +} + +TEST_F(FamilyGraphTestStrict, Graph1FindIslands1) +{ + FamilyGraph* graph = buildFamilyGraph(chunkCount1, adjacentChunkPartition1, adjacentChunkIndices1); + graph->initialize(DEFAULT_ACTOR_INDEX, m_graph); + + std::vector<char> scratch; + scratch.resize((size_t)FamilyGraph::findIslandsRequiredScratch(chunkCount1)); + + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 0, 4, m_graph); + EXPECT_EQ(1, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph)); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 1, 5, m_graph); + EXPECT_EQ(0, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph)); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 2, 6, m_graph); + EXPECT_EQ(0, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph)); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 3, 7, m_graph); + EXPECT_EQ(1, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph)); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 5, 6, m_graph); + EXPECT_EQ(0, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph)); + graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 9, 10, m_graph); + EXPECT_EQ(1, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph)); + + std::vector<IslandInfo> info; + getIslandsInfo(*graph, info); + EXPECT_EQ(3, info.size()); + VECTOR_MATCH(info[0].nodes, 0, 1, 2, 3); + VECTOR_MATCH(info[1].nodes, 4, 5, 8, 9); + VECTOR_MATCH(info[2].nodes, 6, 7, 10, 11); +} + +TEST_F(FamilyGraphTestStrict, Graph1FindIslandsRemoveAllEdges) +{ + FamilyGraph* graph = buildFamilyGraph(chunkCount1, adjacentChunkPartition1, adjacentChunkIndices1); + graph->initialize(DEFAULT_ACTOR_INDEX, m_graph); + + std::vector<char> scratch; + scratch.resize((size_t)FamilyGraph::findIslandsRequiredScratch(chunkCount1)); + + uint32_t edges = graph->getEdgesCount(m_graph); + for (uint32_t node0 = 0; node0 < chunkCount1; node0++) + { + for (uint32_t i = adjacentChunkPartition1[node0]; i < adjacentChunkPartition1[node0 + 1]; i++) + { + if (graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, node0, adjacentChunkIndices1[i], m_graph)) + { + edges--; + EXPECT_EQ(edges, graph->getEdgesCount(m_graph)); + } + } + } + EXPECT_EQ(0, graph->getEdgesCount(m_graph)); + + EXPECT_EQ(12, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph)); + + for (uint32_t node0 = 0; node0 < chunkCount1; node0++) + { + EXPECT_EQ(node0, graph->getIslandIds()[node0]); + } +} diff --git a/NvBlast/test/src/unit/MultithreadingTests.cpp b/NvBlast/test/src/unit/MultithreadingTests.cpp new file mode 100644 index 0000000..f1ae176 --- /dev/null +++ b/NvBlast/test/src/unit/MultithreadingTests.cpp @@ -0,0 +1,395 @@ +#include "BlastBaseTest.h" +#include "AssetGenerator.h" + +#include <iostream> +#include <memory> +#include "TaskDispatcher.h" + +#include "NvBlastActor.h" +#include "NvBlastExtDamageShaders.h" + +#if NV_XBOXONE +#undef min +#undef max +#endif + +typedef std::function<void(const Nv::Blast::Actor&, NvBlastLog)> ActorTestFunction; +typedef std::function<void(std::vector<NvBlastActor*>&, NvBlastLog)> PostDamageTestFunction; + + +static void blast(std::set<NvBlastActor*>& actorsToDamage, GeneratorAsset* testAsset, GeneratorAsset::Vec3 localPos, float minRadius, float maxRadius, float compressiveDamage) +{ + std::vector<NvBlastChunkFractureData> chunkEvents; /* num lower-support chunks + bonds */ + std::vector<NvBlastBondFractureData> bondEvents; /* num lower-support chunks + bonds */ + chunkEvents.resize(testAsset->solverChunks.size()); + bondEvents.resize(testAsset->solverBonds.size()); + + NvBlastFractureBuffers events = { static_cast<uint32_t>(bondEvents.size()), static_cast<uint32_t>(chunkEvents.size()), bondEvents.data(), chunkEvents.data() }; + + std::vector<float> scratch(chunkEvents.size() + bondEvents.size(), 0.0f); + + std::vector<char> splitScratch; + std::vector<NvBlastActor*> newActorsBuffer(testAsset->solverChunks.size()); + + NvBlastExtRadialDamageDesc damage[] = { + { + compressiveDamage, + { localPos.x, localPos.y, localPos.z }, + minRadius, + maxRadius + } + }; + + NvBlastProgramParams programParams = + { + &damage, + 1, + nullptr + }; + + NvBlastDamageProgram program = { + NvBlastExtFalloffGraphShader, + nullptr + }; + + size_t totalNewActorsCount = 0; + for (std::set<NvBlastActor*>::iterator k = actorsToDamage.begin(); k != actorsToDamage.end();) + { + NvBlastActor* actor = *k; + + NvBlastActorGenerateFracture(&events, actor, program, &programParams, nullptr, nullptr); + NvBlastActorApplyFracture(&events, actor, &events, nullptr, nullptr); + + bool removeActor = false; + + if (events.bondFractureCount + events.chunkFractureCount > 0) + { + NvBlastActorSplitEvent splitEvent; + splitEvent.newActors = &newActorsBuffer.data()[totalNewActorsCount]; + uint32_t newActorSize = (uint32_t)(newActorsBuffer.size() - totalNewActorsCount); + + splitScratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, nullptr)); + const size_t newActorsCount = NvBlastActorSplit(&splitEvent, actor, newActorSize, splitScratch.data(), nullptr, nullptr); + totalNewActorsCount += newActorsCount; + removeActor = splitEvent.deletedActor != NULL; + } + + if (removeActor) + { + k = actorsToDamage.erase(k); + } + else + { + ++k; + } + } + + for (size_t i = 0; i < totalNewActorsCount; ++i) + { + actorsToDamage.insert(newActorsBuffer[i]); + } +} + + +template<int FailLevel, int Verbosity> +class MultithreadingTest : public BlastBaseTest<FailLevel, Verbosity> +{ +public: + MultithreadingTest() + { + + } + + static void messageLog(int type, const char* msg, const char* file, int line) + { + BlastBaseTest<FailLevel, Verbosity>::messageLog(type, msg, file, line); + } + + static void* alloc(size_t size) + { + return BlastBaseTest<FailLevel, Verbosity>::alloc(size); + } + + static void free(void* mem) + { + BlastBaseTest<FailLevel, Verbosity>::free(mem); + } + + static void testActorVisibleChunks(const Nv::Blast::Actor& actor, NvBlastLog) + { + const Nv::Blast::Asset& asset = *actor.getAsset(); + const NvBlastChunk* chunks = asset.getChunks(); + + if (actor.isSubSupportChunk()) + { + EXPECT_EQ(1, actor.getVisibleChunkCount()); + + const uint32_t firstVisibleChunkIndex = (uint32_t)Nv::Blast::Actor::VisibleChunkIt(actor); + + EXPECT_EQ(actor.getIndex() - asset.m_graph.m_nodeCount, firstVisibleChunkIndex - asset.m_firstSubsupportChunkIndex); + + // Make sure the visible chunk is subsupport + // Array of support flags + std::vector<bool> isSupport(asset.m_chunkCount, false); + for (uint32_t i = 0; i < asset.m_graph.m_nodeCount; ++i) + { + isSupport[asset.m_graph.getChunkIndices()[i]] = true; + } + + // Climb hierarchy to find support chunk + uint32_t chunkIndex = firstVisibleChunkIndex; + while (chunkIndex != Nv::Blast::invalidIndex<uint32_t>()) + { + if (isSupport[chunkIndex]) + { + break; + } + chunkIndex = chunks[chunkIndex].parentChunkIndex; + } + + EXPECT_FALSE(Nv::Blast::isInvalidIndex(chunkIndex)); + } + else + { + // Array of visibility flags + std::vector<bool> isVisible(asset.m_chunkCount, false); + for (Nv::Blast::Actor::VisibleChunkIt i = actor; (bool)i; ++i) + { + isVisible[(uint32_t)i] = true; + } + + // Mark visible nodes representing graph chunks + std::vector<bool> visibleChunkFound(asset.m_chunkCount, false); + + // Make sure every graph chunk is represented by a visible chunk + for (Nv::Blast::Actor::GraphNodeIt i = actor; (bool)i; ++i) + { + const uint32_t graphNodeIndex = (uint32_t)i; + uint32_t chunkIndex = asset.m_graph.getChunkIndices()[graphNodeIndex]; + // Climb hierarchy to find visible chunk + while (chunkIndex != Nv::Blast::invalidIndex<uint32_t>()) + { + // Check that chunk owners are accurate + EXPECT_EQ(actor.getIndex(), actor.getFamilyHeader()->getChunkActorIndices()[chunkIndex]); + if (isVisible[chunkIndex]) + { + visibleChunkFound[chunkIndex] = true; + break; + } + chunkIndex = chunks[chunkIndex].parentChunkIndex; + } + EXPECT_FALSE(Nv::Blast::isInvalidIndex(chunkIndex)); + } + + // Check that all visible chunks are accounted for + for (uint32_t i = 0; i < asset.m_chunkCount; ++i) + { + EXPECT_EQ(visibleChunkFound[i], isVisible[i]); + } + + // Make sure that, if all siblings are intact, they are invisible + for (uint32_t i = 0; i < asset.m_chunkCount; ++i) + { + bool allIntact = true; + bool noneVisible = true; + if (chunks[i].firstChildIndex < asset.getUpperSupportChunkCount()) // Do not check subsupport + { + for (uint32_t j = chunks[i].firstChildIndex; j < chunks[i].childIndexStop; ++j) + { + allIntact = allIntact && actor.getFamilyHeader()->getChunkActorIndices()[j] == actor.getIndex(); + noneVisible = noneVisible && !isVisible[j]; + } + EXPECT_TRUE(!allIntact || noneVisible); + } + } + } + } + + class DamageActorTask : public TaskDispatcher::Task + { + public: + DamageActorTask(NvBlastActor* actor, GeneratorAsset* asset, GeneratorAsset::Vec3 localPos, float minRadius, float maxRadius, float compressiveDamage, ActorTestFunction testFunction) + : m_asset(asset) + , m_localPos(localPos) + , m_minRadius(minRadius) + , m_maxRadius(maxRadius) + , m_compressiveDamage(compressiveDamage) + , m_testFunction(testFunction) + { + m_actors.insert(actor); + } + + virtual void process() + { + blast(m_actors, m_asset, m_localPos, m_minRadius, m_maxRadius, m_compressiveDamage); + + // Test individual actors + if (m_testFunction != nullptr) + { + for (std::set<NvBlastActor*>::iterator k = m_actors.begin(); k != m_actors.end(); ++k) + { + m_testFunction(*static_cast<Nv::Blast::Actor*>(*k), messageLog); + } + } + } + + const std::set<NvBlastActor*>& getResult() const { return m_actors; } + + private: + std::set<NvBlastActor*> m_actors; + GeneratorAsset* m_asset; + GeneratorAsset::Vec3 m_localPos; + float m_minRadius; + float m_maxRadius; + float m_compressiveDamage; + ActorTestFunction m_testFunction; + + std::vector<NvBlastActor*> m_resultActors; + }; + + void damageLeafSupportActorsParallelized + ( + uint32_t assetCount, + uint32_t minChunkCount, + uint32_t damageCount, + uint32_t threadCount, + ActorTestFunction actorTestFunction, + PostDamageTestFunction postDamageTestFunction + ) + { + const float relativeDamageRadius = 0.05f; + const float compressiveDamage = 1.0f; + + srand(0); + + std::cout << "Asset # (out of " << assetCount << "): "; + for (uint32_t assetNum = 0; assetNum < assetCount; ++assetNum) + { + std::cout << assetNum + 1 << ".. "; + CubeAssetGenerator::Settings settings; + settings.extents = GeneratorAsset::Vec3(1, 1, 1); + CubeAssetGenerator::DepthInfo depthInfo; + depthInfo.slicesPerAxis = GeneratorAsset::Vec3(1, 1, 1); + depthInfo.flag = NvBlastChunkDesc::Flags::NoFlags; + settings.depths.push_back(depthInfo); + uint32_t chunkCount = 1; + while (chunkCount < minChunkCount) + { + uint32_t chunkMul; + do + { + depthInfo.slicesPerAxis = GeneratorAsset::Vec3((float)(1 + rand() % 4), (float)(1 + rand() % 4), (float)(1 + rand() % 4)); + chunkMul = (uint32_t)(depthInfo.slicesPerAxis.x * depthInfo.slicesPerAxis.y * depthInfo.slicesPerAxis.z); + } while (chunkMul == 1); + chunkCount *= chunkMul; + settings.depths.push_back(depthInfo); + settings.extents = settings.extents * depthInfo.slicesPerAxis; + } + settings.depths.back().flag = NvBlastChunkDesc::SupportFlag; // Leaves are support + + // Make largest direction unit size + settings.extents = settings.extents * (1.0f / std::max(settings.extents.x, std::max(settings.extents.y, settings.extents.z))); + + // Create asset + GeneratorAsset testAsset; + CubeAssetGenerator::generate(testAsset, settings); + + NvBlastAssetDesc desc; + desc.chunkDescs = &testAsset.solverChunks[0]; + desc.chunkCount = (uint32_t)testAsset.solverChunks.size(); + desc.bondDescs = &testAsset.solverBonds[0]; + desc.bondCount = (uint32_t)testAsset.solverBonds.size(); + + std::vector<char> 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); + + NvBlastActorDesc actorDesc; + actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr; + actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f; + void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog)); + NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, nullptr); // Using zeroingAlloc in case actorTest compares memory blocks + scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog)); + NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], messageLog); + EXPECT_TRUE(actor != nullptr); + + // Run parallelized damage through TaskDispatcher + std::set<NvBlastActor*> resultActors; + { + uint32_t damageNum = 0; + + // create DamageActorTask and it to dispatcher helper function + auto addDamageTaskFunction = [&](TaskDispatcher& dispatcher, NvBlastActor* actor) + { + GeneratorAsset::Vec3 localPos = settings.extents*GeneratorAsset::Vec3((float)rand() / RAND_MAX - 0.5f, (float)rand() / RAND_MAX - 0.5f, (float)rand() / RAND_MAX - 0.5f); + auto newTask = std::unique_ptr<DamageActorTask>(new DamageActorTask(actor, &testAsset, localPos, relativeDamageRadius, relativeDamageRadius*1.2f, compressiveDamage, actorTestFunction)); + dispatcher.addTask(std::move(newTask)); + }; + + // on task finished function for dispatcher (main thread) + TaskDispatcher::OnTaskFinishedFunction onTaskFinishedFunction = [&](TaskDispatcher& dispatcher, std::unique_ptr<TaskDispatcher::Task> task) { + const DamageActorTask* damageTask = static_cast<const DamageActorTask*>(task.get()); + const std::set<NvBlastActor*>& actors = damageTask->getResult(); + for (NvBlastActor* actor : actors) + { + if (damageNum >= damageCount) + { + resultActors.insert(actor); + } + else + { + damageNum++; + addDamageTaskFunction(dispatcher, actor); + } + } + }; + + // create dispatcher, add first task and run + TaskDispatcher dispatcher(threadCount, onTaskFinishedFunction); + addDamageTaskFunction(dispatcher, actor); + dispatcher.process(); + } + + // Test fractured actor set + if (postDamageTestFunction) + { + std::vector<NvBlastActor*> actorArray(resultActors.begin(), resultActors.end()); + postDamageTestFunction(actorArray, messageLog); + } + + // Release remaining actors + for (std::set<NvBlastActor*>::iterator k = resultActors.begin(); k != resultActors.end(); ++k) + { + NvBlastActorDeactivate(*k, messageLog); + } + resultActors.clear(); + + const uint32_t actorCount = NvBlastFamilyGetActorCount(family, messageLog); + EXPECT_TRUE(actorCount == 0); + + free(family); + + // Release asset data + free(asset); + } + std::cout << "done.\n"; + } +}; + + +// Specializations +typedef MultithreadingTest<NvBlastMessage::Error, 1> MultithreadingTestAllowWarnings; +typedef MultithreadingTest<NvBlastMessage::Error, 1> MultithreadingTestStrict; + + +TEST_F(MultithreadingTestStrict, MultithreadingTestDamageLeafSupportActorsTestVisibility) +{ + damageLeafSupportActorsParallelized(1, 1000, 50, 4, testActorVisibleChunks, nullptr); +} + +TEST_F(MultithreadingTestStrict, MultithreadingTestDamageLeafSupportActors) +{ + damageLeafSupportActorsParallelized(1, 3000, 1000, 4, nullptr, nullptr); +} diff --git a/NvBlast/test/src/unit/SyncTests.cpp b/NvBlast/test/src/unit/SyncTests.cpp new file mode 100644 index 0000000..425210d --- /dev/null +++ b/NvBlast/test/src/unit/SyncTests.cpp @@ -0,0 +1,309 @@ +#include "TkBaseTest.h" + +#include "NvBlastExtSync.h" +#include "NvBlastTkEvent.h" + +#include <map> + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ExtSync Tests +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +class Base +{ +public: + Base(TkTestStrict* test) : m_test(test) + { + + } + + void run(std::stringstream& finalState) + { + //////// initial setup //////// + + m_test->createTestAssets(); + + TkFramework* fwk = NvBlastTkFrameworkGet(); + + TkGroupDesc gdesc; + gdesc.pxTaskManager = m_test->m_taskman; + m_group = fwk->createGroup(gdesc); + EXPECT_TRUE(m_group != nullptr); + + TkActorDesc adesc(m_test->testAssets[0]); + + NvBlastID id; + + TkActor* actor0 = fwk->createActor(adesc); + EXPECT_TRUE(actor0 != nullptr); + families[0] = &actor0->getFamily(); + memcpy(id.data, "Mumble Jumble Bumble", sizeof(NvBlastID)); // Stuffing an arbitrary 16 bytes (The prefix of the given string) + families[0]->setID(id); + m_group->addActor(*actor0); + + TkActor* actor1 = fwk->createActor(adesc); + EXPECT_TRUE(actor1 != nullptr); + families[1] = &actor1->getFamily(); + memcpy(id.data, "buzzkillerdiller", sizeof(NvBlastID)); // Stuffing an arbitrary 16 bytes (The prefix of the given string) + families[1]->setID(id); + m_group->addActor(*actor1); + + + //////// server/client specific impl //////// + + impl(); + + + //////// write out framework final state //////// + + finalState.clear(); + for (auto family : families) + { + std::vector<TkActor*> actors(family->getActorCount()); + family->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + for (auto actor : actors) + { + finalState << actor->getVisibleChunkCount(); + finalState << actor->getGraphNodeCount(); + std::vector<uint32_t> chunkIndices(actor->getGraphNodeCount()); + actor->getVisibleChunkIndices(chunkIndices.data(), (uint32_t)chunkIndices.size()); + + for (uint32_t chunkIndex : chunkIndices) + finalState << chunkIndex; + const float* bondHealths = actor->getBondHealths(); + for (uint32_t i = 0; i < actor->getAsset()->getBondCount(); ++i) + finalState << bondHealths[i]; + } + } + + + //////// release //////// + + m_group->release(); + + for (auto family : families) + { + family->release(); + } + + m_test->releaseTestAssets(); + } + +protected: + virtual void impl() = 0; + + TkTestStrict* m_test; + TkGroup* m_group; + TkFamily* families[2]; +}; + + +class Server : public Base +{ +public: + Server(TkTestStrict* test, std::vector<ExtSyncEvent*>& syncBuffer) : Base(test), m_syncBuffer(syncBuffer) {} + +protected: + virtual void impl() override + { + // create sync ext + ExtSync* sync = ExtSync::create(); + + // add sync as listener to family #1 + families[1]->addListener(*sync); + + // damage family #0 (make it split) + { + TkActor* actor; + families[0]->getActors(&actor, 1); + CSParams p(1, 0.0f); + actor->damage(m_test->getCubeSlicerProgram(), &p, sizeof(p), m_test->getDefaultMaterial()); + } + + // process + m_group->process(); + m_group->sync(); + EXPECT_EQ(families[0]->getActorCount(), 2); + + // sync family #0 + sync->syncFamily(*families[0]); + + // add sync as listener to family #0 + families[0]->addListener(*sync); + + // damage family #0 (make it split fully) + { + TkActor* actor; + families[0]->getActors(&actor, 1, 1); + NvBlastExtRadialDamageDesc radialDamage = m_test->getRadialDamageDesc(0, 0, 0); + actor->damage(m_test->getFalloffProgram(), &radialDamage, sizeof(radialDamage), m_test->getDefaultMaterial()); + } + + + // damage family 1 (just damage bonds health) + { + TkActor* actor; + families[1]->getActors(&actor, 1); + NvBlastExtRadialDamageDesc radialDamage = m_test->getRadialDamageDesc(0, 0, 0, 10.0f, 10.0f, 0.1f); + actor->damage(m_test->getFalloffProgram(), &radialDamage, sizeof(radialDamage), m_test->getDefaultMaterial()); + } + + // process + m_group->process(); + m_group->sync(); + EXPECT_EQ(families[0]->getActorCount(), 5); + EXPECT_EQ(families[1]->getActorCount(), 1); + + // take sync buffer from sync + { + const ExtSyncEvent*const* buffer; + uint32_t size; + sync->acquireSyncBuffer(buffer, size); + + m_syncBuffer.resize(size); + for (size_t i = 0; i < size; ++i) + { + m_syncBuffer[i] = buffer[i]->clone(); + } + + sync->releaseSyncBuffer(); + } + + // + families[0]->removeListener(*sync); + families[1]->removeListener(*sync); + + // + sync->release(); + } + +private: + std::vector<ExtSyncEvent*>& m_syncBuffer; +}; + + +class Client : public Base, public TkEventListener +{ +public: + Client(TkTestStrict* test, std::vector<ExtSyncEvent*>& syncBuffer) : Base(test), m_syncBuffer(syncBuffer) {} + +protected: + + virtual void impl() override + { + ExtSync* sync = ExtSync::create(); + + // fill map + for (auto& family : families) + { + std::vector<TkActor*> actors(family->getActorCount()); + family->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + auto& actorsSet = m_actorsPerFamily[family]; + for (auto actor : actors) + EXPECT_TRUE(actorsSet.insert(actor->getIndex()).second); + } + + // subscribe + for (auto& family : families) + { + family->addListener(*this); + } + + // apply sync buffer + sync->applySyncBuffer(*NvBlastTkFrameworkGet(), (const Nv::Blast::ExtSyncEvent**)m_syncBuffer.data(), static_cast<uint32_t>(m_syncBuffer.size()), m_group); + + // check map + for (auto& family : families) + { + std::vector<TkActor*> actors(family->getActorCount()); + family->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + std::set<uint32_t> actorsSet; + for (auto actor : actors) + EXPECT_TRUE(actorsSet.insert(actor->getIndex()).second); + EXPECT_TRUE(m_actorsPerFamily[family] == actorsSet); + } + + // unsubscribe + for (auto& family : families) + { + family->removeListener(*this); + } + + m_group->process(); + m_group->sync(); + + sync->release(); + } + + // listen for Split event and update actors map + virtual void receive(const TkEvent* events, uint32_t eventCount) override + { + for (size_t i = 0; i < eventCount; ++i) + { + const TkEvent& e = events[i]; + switch (e.type) + { + case (TkEvent::Split) : + { + const TkSplitEvent* splitEvent = e.getPayload<TkSplitEvent>(); + auto& actorsSet = m_actorsPerFamily[splitEvent->parentData.family]; + if (!isInvalidIndex(splitEvent->parentData.index)) + { + EXPECT_EQ((size_t)1, actorsSet.erase(splitEvent->parentData.index)); + } + for (size_t i = 0; i < splitEvent->numChildren; ++i) + { + TkActor* a = splitEvent->children[i]; + EXPECT_TRUE(actorsSet.insert(a->getIndex()).second); + } + break; + } + case (TkEvent::FractureCommand) : + { + break; + } + case (TkEvent::JointUpdate) : + { + FAIL(); + break; + } + default: + break; + } + } + } + +private: + std::map<TkFamily*, std::set<uint32_t>> m_actorsPerFamily; + std::vector<ExtSyncEvent*>& m_syncBuffer; +}; + +TEST_F(TkTestStrict, SyncTest1) +{ + this->createFramework(); + + std::vector<ExtSyncEvent*> syncBuffer; + + std::stringstream serverFinalState; + { + Server s(this, syncBuffer); + s.run(serverFinalState); + } + EXPECT_TRUE(syncBuffer.size() > 0); + + std::stringstream clientFinalState; + { + Client c(this, syncBuffer); + c.run(clientFinalState); + } + + for (auto e : syncBuffer) + { + e->release(); + } + syncBuffer.clear(); + + EXPECT_EQ(serverFinalState.str(), clientFinalState.str()); + + this->releaseFramework(); +} diff --git a/NvBlast/test/src/unit/TkCompositeTests.cpp b/NvBlast/test/src/unit/TkCompositeTests.cpp new file mode 100644 index 0000000..60fbe49 --- /dev/null +++ b/NvBlast/test/src/unit/TkCompositeTests.cpp @@ -0,0 +1,739 @@ +#include "TkBaseTest.h" + +#include <map> +#include <random> +#include <algorithm> + +#include "PsMemoryBuffer.h" + +#include "NvBlastTkSerializable.h" + +#include "NvBlastTime.h" + + +/* +Composite and joint tests: + +0) Test serialization of composites and assemblies + +1) Create assembly, actors and joints should be created automatically + +2) Create an actor with internal joints. Splitting the actor should cause joint create events to be dispatched + +3) Joint update events should be fired when attached actors change + +4) Joint delete events should be fired when at least one attached actor is deleted + +5) Creating a composite from assets with internal joints should have expected behaviors (1-4) above +*/ + + +struct Composite +{ + std::vector<TkActorDesc> m_actorDescs; + std::vector<physx::PxTransform> m_relTMs; + std::vector<TkJointDesc> m_jointDescs; +}; + + +template<int FailLevel, int Verbosity> +class TkCompositeTest : public TkBaseTest<FailLevel, Verbosity> +{ +public: + virtual void* allocate(size_t size, const char* typeName, const char* filename, int line) override + { + void* ptr = TkBaseTest<FailLevel, Verbosity>::allocate(size, typeName, filename, line); + return ptr; + } + + virtual void deallocate(void* ptr) override + { + return TkBaseTest<FailLevel, Verbosity>::deallocate(ptr); + } + + + // Composite/joint tests + void createAssembly(std::vector<TkActor*>& actors, std::vector<TkJoint*>& joints, bool createNRFJoints) + { + TkFramework* fw = NvBlastTkFrameworkGet(); + + actors.resize(4, nullptr); + actors[0] = fw->createActor(TkActorDesc(testAssets[0])); + actors[1] = fw->createActor(TkActorDesc(testAssets[0])); + actors[2] = fw->createActor(TkActorDesc(testAssets[1])); + actors[3] = fw->createActor(TkActorDesc(testAssets[1])); + + std::vector<TkFamily*> families(4); + families[0] = &actors[0]->getFamily(); + families[1] = &actors[1]->getFamily(); + families[2] = &actors[2]->getFamily(); + families[3] = &actors[3]->getFamily(); + + EXPECT_FALSE(actors[0] == nullptr); + EXPECT_FALSE(actors[1] == nullptr); + EXPECT_FALSE(actors[2] == nullptr); + EXPECT_FALSE(actors[3] == nullptr); + + const TkJointDesc jointDescsNoNRF[8] = + { + // Actor indices, chunk indices, attach position in the composite frame + { { families[0], families[1] }, { 6, 5 }, { PxVec3(0.0f, -1.5f, 0.5f), PxVec3(0.0f, -1.5f, 0.5f) } }, + { { families[0], families[1] }, { 4, 3 }, { PxVec3(0.0f, -0.5f, -0.5f), PxVec3(0.0f, -0.5f, -0.5f) } }, + + { { families[0], families[2] }, { 8, 6 }, { PxVec3(-0.5f, 0.0f, 0.5f), PxVec3(-0.5f, 0.0f, 0.5f) } }, + { { families[0], families[2] }, { 3, 1 }, { PxVec3(-1.5f, 0.0f, -0.5f), PxVec3(-1.5f, 0.0f, -0.5f) } }, + + { { families[1], families[3] }, { 7, 5 }, { PxVec3(0.5f, 0.0f, 0.5f), PxVec3(0.5f, 0.0f, 0.5f) } }, + { { families[1], families[3] }, { 4, 2 }, { PxVec3(1.0f, 0.0f, -0.5f), PxVec3(1.0f, 0.0f, -0.5f) } }, + + { { families[2], families[3] }, { 8, 7 }, { PxVec3(0.0f, 1.5f, 0.5f), PxVec3(0.0f, 1.5f, 0.5f) } }, + { { families[2], families[3] }, { 2, 1 }, { PxVec3(0.0f, 0.5f, -0.5f), PxVec3(0.0f, 0.5f, -0.5f) } } + }; + + const TkJointDesc jointDescsWithNRF[12] = + { + // Actor indices, chunk indices, attach position in the composite frame + { { families[0], families[1] }, { 6, 5 }, { PxVec3(0.0f, -1.5f, 0.5f), PxVec3(0.0f, -1.5f, 0.5f) } }, + { { families[0], families[1] }, { 4, 3 }, { PxVec3(0.0f, -0.5f, -0.5f), PxVec3(0.0f, -0.5f, -0.5f) } }, + + { { families[0], nullptr }, { 8, 0xFFFFFFFF }, { PxVec3(-0.5f, 0.0f, 0.5f), PxVec3(-0.5f, 0.0f, 0.5f) } }, + { { families[0], nullptr }, { 3, 0xFFFFFFFF }, { PxVec3(-1.5f, 0.0f, -0.5f), PxVec3(-1.5f, 0.0f, -0.5f) } }, + + { { nullptr, families[2] }, { 0xFFFFFFFF, 6 }, { PxVec3(-0.5f, 0.0f, 0.5f), PxVec3(-0.5f, 0.0f, 0.5f) } }, + { { nullptr, families[2] }, { 0xFFFFFFFF, 1 }, { PxVec3(-1.5f, 0.0f, -0.5f), PxVec3(-1.5f, 0.0f, -0.5f) } }, + + { { families[1], nullptr }, { 7, 0xFFFFFFFF }, { PxVec3(0.5f, 0.0f, 0.5f), PxVec3(0.5f, 0.0f, 0.5f) } }, + { { families[1], nullptr }, { 4, 0xFFFFFFFF }, { PxVec3(1.0f, 0.0f, -0.5f), PxVec3(1.0f, 0.0f, -0.5f) } }, + + { { nullptr, families[3] }, { 0xFFFFFFFF, 5 }, { PxVec3(0.5f, 0.0f, 0.5f), PxVec3(0.5f, 0.0f, 0.5f) } }, + { { nullptr, families[3] }, { 0xFFFFFFFF, 2 }, { PxVec3(1.0f, 0.0f, -0.5f), PxVec3(1.0f, 0.0f, -0.5f) } }, + + { { families[2], families[3] }, { 8, 7 }, { PxVec3(0.0f, 1.5f, 0.5f), PxVec3(0.0f, 1.5f, 0.5f) } }, + { { families[2], families[3] }, { 2, 1 }, { PxVec3(0.0f, 0.5f, -0.5f), PxVec3(0.0f, 0.5f, -0.5f), } } + }; + + const TkJointDesc* jointDescs = createNRFJoints ? jointDescsWithNRF : jointDescsNoNRF; + const int jointCount = createNRFJoints ? 12 : 8; + + joints.resize(jointCount, nullptr); + for (int i = 0; i < jointCount; ++i) + { + joints[i] = fw->createJoint(jointDescs[i]); + EXPECT_FALSE(joints[i] == nullptr); + } + } + + void familySerialization(std::vector<TkFamily*>& families, TestFamilyTracker& tracker) + { + TkFramework* fw = NvBlastTkFrameworkGet(); + + PsMemoryBuffer* membuf = PX_NEW(PsMemoryBuffer); + EXPECT_TRUE(membuf != nullptr); + if (membuf == nullptr) + { + return; + } + + std::vector<TkFamily*> oldFamilies = families; + + for (size_t familyNum = 0; familyNum < families.size(); ++familyNum) + { + families[familyNum]->serialize(*membuf); + } + + for (size_t familyNum = 0; familyNum < families.size(); ++familyNum) + { + TkFamily* f = families[familyNum]; + + std::vector<TkActor*> actors(f->getActorCount()); + f->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + for (auto a : actors) + { + tracker.eraseActor(a); + } + + f->release(); + families[familyNum] = nullptr; + } + + for (size_t familyNum = 0; familyNum < families.size(); ++familyNum) + { + TkFamily* f = reinterpret_cast<TkFamily*>(fw->deserialize(*membuf)); + f->addListener(tracker); + families[familyNum] = f; + } + + for (size_t familyNum = 0; familyNum < families.size(); ++familyNum) + { + TkFamily* f = families[familyNum]; + + std::vector<TkActor*> actors(f->getActorCount()); + f->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + for (auto a : actors) + { + tracker.insertActor(a); + + std::vector<TkJoint*> joints(a->getJointCount()); + a->getJoints(joints.data(), (uint32_t)joints.size()); + + for (auto j : joints) + { + const TkJointData jd = j->getData(); + if (jd.actors[0] != jd.actors[1]) + { + tracker.joints.insert(j); + } + } + } + } + + membuf->release(); + } + + void recollectActors(std::vector<TkFamily*>& families, std::vector<TkActor*>& actors) + { + uint32_t totalActorCount = 0; + for (auto family : families) + { + EXPECT_LE(family->getActorCount() + totalActorCount, actors.size()); + totalActorCount += family->getActors(actors.data() + totalActorCount, static_cast<uint32_t>(actors.size()) - totalActorCount); + } + } + + void assemblyCreateAndRelease(bool createNRFJoints, bool serializationTest) + { + createFramework(); + createTestAssets(); + + TkFramework* fw = NvBlastTkFrameworkGet(); + + const TkType* familyType = fw->getType(TkTypeIndex::Family); + EXPECT_TRUE(familyType != nullptr); + + TestFamilyTracker tracker; + + std::vector<TkFamily*> families1; + std::vector<TkFamily*> families2; + + // Create one assembly + std::vector<TkActor*> actors1; + std::vector<TkJoint*> joints1; + createAssembly(actors1, joints1, createNRFJoints); + tracker.joints.insert(joints1.begin(), joints1.end()); + + // Create another assembly + std::vector<TkActor*> actors2; + std::vector<TkJoint*> joints2; + createAssembly(actors2, joints2, createNRFJoints); + tracker.joints.insert(joints2.begin(), joints2.end()); + + // Store families and fill group + for (size_t actorNum = 0; actorNum < actors1.size(); ++actorNum) + { + TkFamily& family = actors1[actorNum]->getFamily(); + families1.push_back(&family); + family.addListener(tracker); + } + for (size_t actorNum = 0; actorNum < actors2.size(); ++actorNum) + { + TkFamily& family = actors2[actorNum]->getFamily(); + families2.push_back(&family); + family.addListener(tracker); + } + + if (serializationTest) + { + familySerialization(families1, tracker); + recollectActors(families1, actors1); + familySerialization(families2, tracker); + recollectActors(families2, actors2); + } + + EXPECT_EQ(joints1.size() + joints2.size(), tracker.joints.size()); + + // Release 1st assembly's actors + for (size_t actorNum = 0; actorNum < actors1.size(); ++actorNum) + { + actors1[actorNum]->release(); + } + + if (serializationTest) + { + familySerialization(families2, tracker); + recollectActors(families2, actors2); + } + + EXPECT_EQ(joints2.size(), tracker.joints.size()); + + // Release 2nd assembly's actors + for (size_t actorNum = 0; actorNum < actors1.size(); ++actorNum) + { + actors2[actorNum]->release(); + } + + EXPECT_EQ(0, tracker.joints.size()); + + releaseTestAssets(); + releaseFramework(); + } + + void assemblyInternalJoints(bool testAssemblySerialization) + { + createFramework(); + createTestAssets(true); // Create assets with internal joints + + TkFramework* fw = NvBlastTkFrameworkGet(); + + TestFamilyTracker tracker; + + TkGroupDesc gdesc; + gdesc.pxTaskManager = m_taskman; + TkGroup* group = fw->createGroup(gdesc); + EXPECT_TRUE(group != nullptr); + + TkActorDesc adesc(testAssets[0]); + + TkActor* actor1 = fw->createActor(adesc); + EXPECT_TRUE(actor1 != nullptr); + tracker.insertActor(actor1); + + actor1->getFamily().addListener(tracker); + + TkFamily* family = &actor1->getFamily(); + + group->addActor(*actor1); + + CSParams p(2, 0.0f); + actor1->damage(getCubeSlicerProgram(), &p, sizeof(p), getDefaultMaterial()); + + EXPECT_EQ((size_t)0, tracker.joints.size()); + + group->process(); + group->sync(); + + if (testAssemblySerialization) + { + std::vector<TkFamily*> families; + families.push_back(family); + familySerialization(families, tracker); + family = families[0]; + std::vector<TkActor*> actors(family->getActorCount()); + family->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + for (TkActor* actor : actors) + { + group->addActor(*actor); + } + } + + EXPECT_EQ((size_t)2, family->getActorCount()); + EXPECT_EQ((size_t)4, tracker.joints.size()); // 2) Create an actor with internal joints. Splitting the actor should cause joint create events to be dispatched + + std::vector<TkActor*> actors(family->getActorCount()); + family->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + + for (TkJoint* joint : tracker.joints) + { + TkJointData jd = joint->getData(); + EXPECT_FALSE(actors.end() == std::find(actors.begin(), actors.end(), jd.actors[0])); + EXPECT_FALSE(actors.end() == std::find(actors.begin(), actors.end(), jd.actors[1])); + } + + NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); + for (TkActor* actor : actors) + { + actor->damage(getFalloffProgram(), &radialDamage, sizeof(radialDamage), getDefaultMaterial()); + } + + group->process(); + group->sync(); + + if (testAssemblySerialization) + { + std::vector<TkFamily*> families; + families.push_back(family); + familySerialization(families, tracker); + family = families[0]; + } + + EXPECT_EQ((size_t)8, family->getActorCount()); + EXPECT_EQ((size_t)4, tracker.joints.size()); + + // 3) Joint update events should be fired when attached actors change + + actors.resize(family->getActorCount()); + family->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + + for (TkJoint* joint : tracker.joints) + { + TkJointData jd = joint->getData(); + EXPECT_FALSE(actors.end() == std::find(actors.begin(), actors.end(), jd.actors[0])); + EXPECT_FALSE(actors.end() == std::find(actors.begin(), actors.end(), jd.actors[1])); + } + + for (TkActor* actor : actors) + { + actor->release(); + } + + EXPECT_EQ((size_t)0, tracker.joints.size()); // 4) Joint delete events should be fired when at least one attached actor is deleted + + group->release(); + + releaseTestAssets(); + releaseFramework(); + } + + void assemblyCompositeWithInternalJoints(bool createNRFJoints, bool serializationTest) + { + createFramework(); + createTestAssets(true); // Create assets with internal joints + + TkFramework* fw = NvBlastTkFrameworkGet(); + + const TkType* familyType = fw->getType(TkTypeIndex::Family); + EXPECT_TRUE(familyType != nullptr); + + if (familyType == nullptr) + { + return; + } + + TestFamilyTracker tracker; + + std::vector<TkFamily*> families; + + // Create assembly + std::vector<TkActor*> actors; + std::vector<TkJoint*> joints; + createAssembly(actors, joints, createNRFJoints); + tracker.joints.insert(joints.begin(), joints.end()); + + TkGroupDesc gdesc; + gdesc.pxTaskManager = m_taskman; + TkGroup* group = fw->createGroup(gdesc); + EXPECT_TRUE(group != nullptr); + + for (size_t i = 0; i < actors.size(); ++i) + { + TkFamily& family = actors[i]->getFamily(); + families.push_back(&family); + family.addListener(tracker); + tracker.insertActor(actors[i]); + group->addActor(*actors[i]); + } + + if (serializationTest) + { + familySerialization(families, tracker); + recollectActors(families, actors); + for (auto actor : actors) + { + group->addActor(*actor); + } + } + + EXPECT_EQ((size_t)4, actors.size()); + + const size_t compJointCount = createNRFJoints ? (size_t)12 : (size_t)8; + + EXPECT_EQ(compJointCount, tracker.joints.size()); + + size_t totalActorCount = 0; + for (uint32_t i = 0; i < 4; ++i) + { + CSParams p(2, 0.0f); + actors[i]->damage(getCubeSlicerProgram(), &p, sizeof(p), getDefaultMaterial()); + + group->process(); + group->sync(); + + if (serializationTest) + { + familySerialization(families, tracker); + for (size_t j = 0; j < families.size(); ++j) + { + TkFamily* family = families[j]; + std::vector<TkActor*> a(family->getActorCount()); + family->getActors(a.data(), static_cast<uint32_t>(a.size())); + for (auto actor : a) + { + group->addActor(*actor); + } + EXPECT_TRUE(j <= i || a.size() == 1); + if (j > i && a.size() == 1) + { + actors[j] = a[0]; + } + } + } + + EXPECT_EQ((size_t)2, families[i]->getActorCount()); + EXPECT_EQ((size_t)(compJointCount + 4 * (i + 1)), tracker.joints.size()); // Four joints created per actor + + totalActorCount += families[i]->getActorCount(); + } + + actors.resize(totalActorCount); + totalActorCount = 0; + for (int i = 0; i < 4; ++i) + { + families[i]->getActors(actors.data() + totalActorCount, families[i]->getActorCount()); + totalActorCount += families[i]->getActorCount(); + } + + for (TkJoint* joint : tracker.joints) + { + TkJointData jd = joint->getData(); + EXPECT_TRUE(jd.actors[0] == nullptr || actors.end() != std::find(actors.begin(), actors.end(), jd.actors[0])); + EXPECT_TRUE(jd.actors[1] == nullptr || actors.end() != std::find(actors.begin(), actors.end(), jd.actors[1])); + } + + NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); + for (TkActor* actor : actors) + { + actor->damage(getFalloffProgram(), &radialDamage, sizeof(radialDamage), getDefaultMaterial()); + } + + group->process(); + group->sync(); + + totalActorCount = 0; + for (int i = 0; i < 4; ++i) + { + totalActorCount += families[i]->getActorCount(); + } + + if (serializationTest) + { + familySerialization(families, tracker); + } + + EXPECT_EQ((size_t)32, totalActorCount); + EXPECT_EQ(compJointCount + (size_t)16, tracker.joints.size()); + + actors.resize(totalActorCount); + totalActorCount = 0; + for (int i = 0; i < 4; ++i) + { + families[i]->getActors(actors.data() + totalActorCount, families[i]->getActorCount()); + totalActorCount += families[i]->getActorCount(); + } + + // 3) Joint update events should be fired when attached actors change + + for (TkActor* actor : actors) + { + actor->release(); + } + + EXPECT_EQ((size_t)0, tracker.joints.size()); // 4) Joint delete events should be fired when at least one attached actor is deleted + + group->release(); + + releaseTestAssets(); + releaseFramework(); + } + + void assemblyExternalJoints_MultiFamilyDamage(bool explicitJointRelease = true) + { + createFramework(); + + const NvBlastChunkDesc chunkDescs[3] = + { +// centroid volume parent idx flags ID + { { 0.0f, 0.0f, 0.0f }, 4.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { { 0.0f,-1.0f, 0.0f }, 2.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { { 0.0f, 1.0f, 0.0f }, 2.0f, 0, NvBlastChunkDesc::SupportFlag, 2 } + }; + + const NvBlastBondDesc bondDesc = +// chunks normal area centroid userData + { { 1, 2 },{ { 0.0f, 1.0f, 0.0f }, 1.0f,{ 0.0f, 0.0f, 0.0f }, 0 } }; + + TkFramework* framework = NvBlastTkFrameworkGet(); + + TestFamilyTracker tracker; + + TkAssetDesc desc; + desc.chunkCount = 3; + desc.chunkDescs = chunkDescs; + desc.bondCount = 1; + desc.bondDescs = &bondDesc; + desc.bondFlags = nullptr; + TkAsset* asset = framework->createAsset(desc); + EXPECT_TRUE(asset != nullptr); + + TkGroupDesc gdesc; + gdesc.pxTaskManager = m_taskman; + TkGroup* group = framework->createGroup(gdesc); + EXPECT_TRUE(group != nullptr); + + TkActorDesc adesc(asset); + TkActor* actor1 = framework->createActor(adesc); + EXPECT_TRUE(actor1 != nullptr); + TkActor* actor2 = framework->createActor(adesc); + EXPECT_TRUE(actor2 != nullptr); + + group->addActor(*actor1); + group->addActor(*actor2); + + TkFamily* family1 = &actor1->getFamily(); + TkFamily* family2 = &actor2->getFamily(); + + family1->addListener(tracker); + family2->addListener(tracker); + tracker.insertActor(actor1); + tracker.insertActor(actor2); + + TkJointDesc jdesc; + jdesc.families[0] = family1; + jdesc.families[1] = family2; + jdesc.chunkIndices[0] = 2; + jdesc.chunkIndices[1] = 1; + jdesc.attachPositions[0] = PxVec3(0.0f, 1.0f, 0.0f); + jdesc.attachPositions[1] = PxVec3(0.0f, -1.0f, 0.0f); + TkJoint* joint = framework->createJoint(jdesc); + EXPECT_TRUE(joint != nullptr); + tracker.joints.insert(joint); + + NvBlastExtRadialDamageDesc radialDamage1 = getRadialDamageDesc(0, 1, 0, 2, 2); + actor1->damage(getFalloffProgram(), &radialDamage1, sizeof(radialDamage1), getDefaultMaterial()); + NvBlastExtRadialDamageDesc radialDamage2 = getRadialDamageDesc(0, -1, 0, 2, 2); + actor2->damage(getFalloffProgram(), &radialDamage2, sizeof(radialDamage2), getDefaultMaterial()); + + group->process(); + group->sync(); + + TkActor* actors1[2]; + TkActor* actors2[2]; + EXPECT_EQ(2, family1->getActors(actors1, 2)); + EXPECT_EQ(2, family2->getActors(actors2, 2)); + + const TkJointData jdata = joint->getData(); + EXPECT_TRUE(jdata.actors[0] != nullptr); + EXPECT_TRUE(jdata.actors[1] != nullptr); + EXPECT_TRUE(&jdata.actors[0]->getFamily() == family1); + EXPECT_TRUE(&jdata.actors[1]->getFamily() == family2); + + // Clean up + if (explicitJointRelease) + { + joint->release(); + family2->release(); + family1->release(); + asset->release(); + releaseFramework(); + } + else + { + EXPECT_EQ(1, tracker.joints.size()); + releaseFramework(); + // Commenting these out - but shouldn't we be sending delete events when we release the framework? +// EXPECT_EQ(0, tracker.joints.size()); +// EXPECT_EQ(0, tracker.actors.size()); + } + } + +protected: + // http://clang.llvm.org/compatibility.html#dep_lookup_bases + // http://stackoverflow.com/questions/6592512/templates-parent-class-member-variables-not-visible-in-inherited-class + + using TkBaseTest<FailLevel, Verbosity>::testAssets; + using TkBaseTest<FailLevel, Verbosity>::m_taskman; + using TkBaseTest<FailLevel, Verbosity>::createFramework; + using TkBaseTest<FailLevel, Verbosity>::releaseFramework; + using TkBaseTest<FailLevel, Verbosity>::createTestAssets; + using TkBaseTest<FailLevel, Verbosity>::releaseTestAssets; + using TkBaseTest<FailLevel, Verbosity>::getCubeSlicerProgram; + using TkBaseTest<FailLevel, Verbosity>::getDefaultMaterial; + using TkBaseTest<FailLevel, Verbosity>::getRadialDamageDesc; + using TkBaseTest<FailLevel, Verbosity>::getFalloffProgram; +}; + + +typedef TkCompositeTest<NvBlastMessage::Error, 1> TkCompositeTestAllowWarnings; +typedef TkCompositeTest<NvBlastMessage::Error, 1> TkCompositeTestStrict; + + +/* +1) Create assembly, actors and joints should be created automatically +*/ + +TEST_F(TkCompositeTestStrict, AssemblyCreateAndRelease_NoNRFJoints_NoSerialization) +{ + assemblyCreateAndRelease(false, false); +} + +TEST_F(TkCompositeTestStrict, AssemblyCreateAndRelease_NoNRFJoints_AssemblySerialization) +{ + assemblyCreateAndRelease(false, true); +} + +TEST_F(TkCompositeTestStrict, AssemblyCreateAndRelease_WithNRFJoints_NoSerialization) +{ + assemblyCreateAndRelease(true, false); +} + +TEST_F(TkCompositeTestStrict, AssemblyCreateAndRelease_WithNRFJoints_AssemblySerialization) +{ + assemblyCreateAndRelease(true, true); +} + + +/** +2) Create an actor with internal joints. Splitting the actor should cause joint create events to be dispatched + +3) Joint update events should be fired when attached actors change + +4) Joint delete events should be fired when at least one attached actor is deleted +*/ + +TEST_F(TkCompositeTestStrict, AssemblyInternalJoints_NoSerialization) +{ + assemblyInternalJoints(false); +} + +TEST_F(TkCompositeTestStrict, AssemblyInternalJoints_AssemblySerialization) +{ + assemblyInternalJoints(true); +} + + +/** +5) Creating a composite from assets with internal joints should have expected behaviors (1-4) above +*/ + +TEST_F(TkCompositeTestStrict, AssemblyCompositeWithInternalJoints_NoNRFJoints_NoSerialization) +{ + assemblyCompositeWithInternalJoints(false, false); +} + +TEST_F(TkCompositeTestStrict, AssemblyCompositeWithInternalJoints_NoNRFJoints_AssemblySerialization) +{ + assemblyCompositeWithInternalJoints(false, true); +} + +TEST_F(TkCompositeTestStrict, AssemblyCompositeWithInternalJoints_WithNRFJoints_NoSerialization) +{ + assemblyCompositeWithInternalJoints(true, false); +} + +TEST_F(TkCompositeTestStrict, AssemblyCompositeWithInternalJoints_WithNRFJoints_AssemblySerialization) +{ + assemblyCompositeWithInternalJoints(true, true); +} + + +/* +More tests +*/ + +TEST_F(TkCompositeTestStrict, AssemblyExternalJoints_MultiFamilyDamage) +{ + assemblyExternalJoints_MultiFamilyDamage(true); +} + +TEST_F(TkCompositeTestStrict, AssemblyExternalJoints_MultiFamilyDamage_AutoJointRelease) +{ + assemblyExternalJoints_MultiFamilyDamage(false); +} diff --git a/NvBlast/test/src/unit/TkTests.cpp b/NvBlast/test/src/unit/TkTests.cpp new file mode 100644 index 0000000..045b0c9 --- /dev/null +++ b/NvBlast/test/src/unit/TkTests.cpp @@ -0,0 +1,1528 @@ +#include "TkBaseTest.h" + +#include <map> +#include <random> +#include <algorithm> + +#include "PsMemoryBuffer.h" + +#include "NvBlastTkSerializable.h" + +#include "NvBlastTime.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<TkFamily*>& families, std::map<TkFamily*, ExpectedVisibleChunks>& expectedVisibleChunks) +{ + size_t numActors = 0; + for (TkFamily* fam : families) + { + auto ex = expectedVisibleChunks[fam]; + EXPECT_EQ(ex.numActors, fam->getActorCount()); + numActors += ex.numActors; + std::vector<TkActor*> actors(fam->getActorCount()); + fam->getActors(actors.data(), static_cast<uint32_t>(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, *this, *this); + PxU32 affinity[] = { 1, 2, 4, 8 }; + PxDefaultCpuDispatcher* cpuDispatcher = PxDefaultCpuDispatcherCreate(4, affinity); + cpuDispatcher->setRunProfiled(false); + PxTaskManager* taskman = PxTaskManager::createTaskManager(*this, cpuDispatcher, nullptr); + + cpuDispatcher->release(); + taskman->release(); + pxFoundation->release(); +} +#endif + +TEST_F(TkTestAllowWarnings, 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.pxTaskManager = m_taskman; + TkGroup* group = fwk->createGroup(gdesc); + EXPECT_TRUE(group != nullptr); + + group->addActor(*actor); + + group->process(); + group->sync(true); + + EXPECT_FALSE(actor->isPending()); + EXPECT_EQ(2, family.getActorCount()); + + releaseFramework(); +} + +TEST_F(TkTestAllowWarnings, ActorDamageGroup) +{ + TEST_ZONE_BEGIN("ActorDamageGroup"); + + createFramework(); + createTestAssets(); + + TkFramework* fwk = NvBlastTkFrameworkGet(); + + TestFamilyTracker ftrack1, ftrack2; + + TkGroupDesc gdesc; + gdesc.pxTaskManager = m_taskman; + TkGroup* group = fwk->createGroup(gdesc); + EXPECT_TRUE(group != nullptr); + + NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); + NvBlastExtShearDamageDesc shearDamage = getShearDamageDesc(0, 0, 0); + + std::vector<TkFamily*> families; + TkFamily* trackedFamily; + std::map<TkFamily*, ExpectedVisibleChunks> 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; + generateCube(cube, 5, 2); + TkAssetDesc assetDesc; + assetDesc.bondCount = (uint32_t)cube.solverBonds.size(); + assetDesc.bondDescs = cube.solverBonds.data(); + assetDesc.chunkCount = (uint32_t)cube.chunks.size(); + assetDesc.chunkDescs = cube.solverChunks.data(); + 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); + + CSParams p(0, 0.0f); + + 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(), &p, sizeof(p), getDefaultMaterial()); + actor1->damage(getFalloffProgram(), &radialDamage, sizeof(radialDamage), getDefaultMaterial()); + } + + EXPECT_FALSE(group->sync(true)); + EXPECT_FALSE(group->sync(false)); + + group->process(); + group->sync(); + + testResults(families, expectedVisibleChunks); + + + { + std::vector<TkActor*> actors(trackedFamily->getActorCount()); + trackedFamily->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + for (TkActor* actor : actors) + { + CSParams p(1, 0.0f); + actor->damage(getCubeSlicerProgram(), &p, sizeof(p), getDefaultMaterial()); + } + } + expectedVisibleChunks[trackedFamily] = ExpectedVisibleChunks(4, 2); + + group->process(); + group->sync(); + + testResults(families, expectedVisibleChunks); + + + { + std::vector<TkActor*> actors(trackedFamily->getActorCount()); + trackedFamily->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + for (TkActor* actor : actors) + { + CSParams p(2, 0.0f); + actor->damage(getCubeSlicerProgram(), &p, sizeof(p), getDefaultMaterial()); + } + } + + expectedVisibleChunks[trackedFamily] = ExpectedVisibleChunks(8, 1); + + group->process(); + group->sync(); + + testResults(families, expectedVisibleChunks); + + + { + std::vector<TkActor*> actors(trackedFamily->getActorCount()); + trackedFamily->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + TEST_ZONE_BEGIN("damage"); + for (TkActor* actor : actors) + { + actor->damage(getFalloffProgram(), &radialDamage, sizeof(radialDamage), getDefaultMaterial()); + } + TEST_ZONE_END("damage"); + } + expectedVisibleChunks[trackedFamily] = ExpectedVisibleChunks(4096, 1); + + group->process(); + while (!group->sync(true)); + + testResults(families, expectedVisibleChunks); + + + + { + std::vector<TkActor*> actors(trackedFamily->getActorCount()); + trackedFamily->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + TEST_ZONE_BEGIN("damage"); + for (TkActor* actor : actors) + { + actor->damage(getShearProgram(), &shearDamage, sizeof(shearDamage), getDefaultMaterial()); + } + TEST_ZONE_END("damage"); + } + + group->process(); + while (!group->sync(true)) + ; + + + + { + std::vector<TkActor*> actors(trackedFamily->getActorCount()); + trackedFamily->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + TEST_ZONE_BEGIN("damage"); + for (TkActor* actor : actors) + { + actor->damage(getShearProgram(), &shearDamage, sizeof(shearDamage), getDefaultMaterial()); + } + TEST_ZONE_END("damage"); + } + + group->process(); + while (!group->sync(true)); + + group->release(); + + TEST_ZONE_BEGIN("family release"); + trackedFamily->release(); + TEST_ZONE_END("family release"); + + releaseTestAssets(); + releaseFramework(); + + TEST_ZONE_END("ActorDamageGroup"); +} + + +TEST_F(TkTestAllowWarnings, ActorDamageMultiGroup) +{ + createFramework(); + createTestAssets(); + + TkFramework* fwk = NvBlastTkFrameworkGet(); + + TestFamilyTracker ftrack1, ftrack2; + + TkGroupDesc gdesc; + gdesc.pxTaskManager = m_taskman; + TkGroup* group0 = fwk->createGroup(gdesc); + EXPECT_TRUE(group0 != nullptr); + TkGroup* group1 = fwk->createGroup(gdesc); + EXPECT_TRUE(group1 != nullptr); + + std::vector<TkFamily*> families(2); + std::map<TkFamily*, ExpectedVisibleChunks> expectedVisibleChunks; + + // prepare 2 equal actors/families and damage + { + GeneratorAsset cube; + generateCube(cube, 6, 2, 5); + TkAssetDesc assetDesc; + assetDesc.bondCount = (uint32_t)cube.solverBonds.size(); + assetDesc.bondDescs = cube.solverBonds.data(); + assetDesc.chunkCount = (uint32_t)cube.chunks.size(); + assetDesc.chunkDescs = cube.solverChunks.data(); + 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()); + + { + CSParams p0(0, 0.0f); + CSParams p1(1, 0.0f); + cubeActor0->damage(getCubeSlicerProgram(), &p0, sizeof(p0), getDefaultMaterial()); + cubeActor0->damage(getCubeSlicerProgram(), &p1, sizeof(p1), getDefaultMaterial()); + + cubeActor1->damage(getCubeSlicerProgram(), &p0, sizeof(p0), getDefaultMaterial()); + } + + 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_TRUE(group0->process()); + EXPECT_TRUE(group1->process()); + uint32_t completed = 0; + while (completed < 2) + { + if (group0->sync(false)) + completed++; + if (group1->sync(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<TkGroup*, TkFamily*> pair; + std::set<pair> combinations; + for (auto actor : allActors) + { + combinations.emplace(pair(actor->getGroup(), &actor->getFamily())); + if (actor->getVisibleChunkCount() == 4) + { + CSParams p1(1, 0.0f); + actor->damage(getCubeSlicerProgram(), &p1, sizeof(p1), getDefaultMaterial()); + } + CSParams p2(2, 0.0f); + actor->damage(getCubeSlicerProgram(), &p2, sizeof(p2), getDefaultMaterial()); + } + 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_TRUE(group1->process()); + EXPECT_TRUE(group0->process()); + uint32_t completed = 0; + while (completed < 2) + { + if (group0->sync(false)) + completed++; + if (group1->sync(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); + NvBlastExtShearDamageDesc shearDamage = getShearDamageDesc(0, 0, 0); + + std::vector<TkActor*> 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(), &radialDamage, sizeof(radialDamage), getDefaultMaterial()); + } + else + { + actor->damage(getShearProgram(), &shearDamage, sizeof(shearDamage), getDefaultMaterial()); + } + + if (re() % 1000 < 500) + { + // switch group + TkGroup* newGroup = actor->getGroup() == group0 ? group1 : group0; + actor->removeFromGroup(); + newGroup->addActor(*actor); + } + } + + if (!workTBD) + break; + + // async process 2 groups + { + EXPECT_TRUE(group1->process()); + EXPECT_TRUE(group0->process()); + uint32_t completed = 0; + while (completed < 2) + { + if (group0->sync(false)) + completed++; + if (group1->sync(false)) + completed++; + } + } + TEST_ZONE_END("damage loop"); + } + } + + // 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()); + + group0->release(); + group1->release(); + + for (auto f : families) + f->release(); + + releaseTestAssets(); + releaseFramework(); +} + +TEST_F(TkTestAllowWarnings, ActorDamageBufferedDamage) +{ + createFramework(); + TkFramework* fwk = NvBlastTkFrameworkGet(); + + // group + TkGroupDesc gdesc; + gdesc.pxTaskManager = m_taskman; + TkGroup* group = fwk->createGroup(gdesc); + EXPECT_TRUE(group != nullptr); + + // random engine + std::default_random_engine re; + + // cube asset + GeneratorAsset cube; + generateCube(cube, 4, 2, 3); + TkAssetDesc assetDesc; + assetDesc.bondCount = (uint32_t)cube.solverBonds.size(); + assetDesc.bondDescs = cube.solverBonds.data(); + assetDesc.chunkCount = (uint32_t)cube.chunks.size(); + assetDesc.chunkDescs = cube.solverChunks.data(); + 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); + NvBlastProgramParams userProgramParams0 = + { + &userR0, // damageDescBuffer + 1, // damageDescCount + nullptr, // material + }; + + NvBlastExtRadialDamageDesc userR1 = getRadialDamageDesc(-P, P, 0, R, R); + NvBlastProgramParams userProgramParams1 = + { + &userR1, // damageDescBuffer + 1, // damageDescCount + nullptr, // material + }; + + // fill damage functions, shuffle and apply + { + CSParams p0(0, 0.0f); + CSParams p1(1, 0.0f); + CSParams p2(2, 0.0f); + NvBlastExtRadialDamageDesc r0 = getRadialDamageDesc(P, -P, 0, R, R); + NvBlastExtRadialDamageDesc r1 = getRadialDamageDesc(-P, -P, 0, R, R); + + const uint32_t damageCount = 7; + std::vector<std::function<void(void)>> damageFns(damageCount); + damageFns[0] = [&]() { actor->damage(getCubeSlicerProgram(), &p0, sizeof(p0), getDefaultMaterial()); }; + damageFns[1] = [&]() { actor->damage(getCubeSlicerProgram(), &p1, sizeof(p1), getDefaultMaterial()); }; + damageFns[2] = [&]() { actor->damage(getCubeSlicerProgram(), &p2, sizeof(p2), getDefaultMaterial()); }; + damageFns[3] = [&]() { actor->damage(getFalloffProgram(), &r0, sizeof(r0), getDefaultMaterial()); }; + damageFns[4] = [&]() { actor->damage(getFalloffProgram(), &r1, sizeof(r1), getDefaultMaterial()); }; + 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_TRUE(group->process()); + group->sync(true); + + const auto ac = family->getActorCount(); + + // check + EXPECT_EQ(family->getActorCount(), expectedActorCount); + EXPECT_EQ(group->getActorCount(), expectedActorCount); + + // release + std::vector<TkActor*> actors(family->getActorCount()); + family->getActors(actors.data(), static_cast<uint32_t>(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<TkAsset*> assets(assetDescCount); + + // assets + for (uint32_t i = 0; i < assetDescCount; ++i) + { + TkAssetDesc desc; + reinterpret_cast<NvBlastAssetDesc&>(desc) = g_assetDescs[i]; + desc.bondFlags = nullptr; + assets[i] = framework->createAsset(desc); + EXPECT_TRUE(assets[i] != nullptr); + } + + // actors + std::vector<TkActor*> actors;; + std::vector<TkFamily*> 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<TkObject*> objects; + + // assets + { + const TkType* assetType = framework->getType(TkTypeIndex::Asset); + objects.resize(framework->getObjectCount(*assetType)); + EXPECT_TRUE(framework->getObjects(reinterpret_cast<TkIdentifiable**>(objects.data()), static_cast<uint32_t>(objects.size()), *assetType) == static_cast<uint32_t>(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<TkIdentifiable**>(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<TkIdentifiable**>(objects.data()), static_cast<uint32_t>(objects.size()), *familyType) == static_cast<uint32_t>(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<int FailMask, int Verbosity> +TkFamily* TkBaseTest<FailMask, Verbosity>::familySerialization(TkFamily* family) +{ + 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<TkFamily*>(fw->deserialize(*membuf)); + EXPECT_TRUE(family != nullptr); + if (family != nullptr) + { + EXPECT_EQ(familyActorCount, family->getActorCount()); + EXPECT_EQ(familyAsset, family->getAsset()); + } + membuf->release(); + } + + return family; +} + +TEST_F(TkTestAllowWarnings, FamilySerialization) +{ + createFramework(); + TkFramework* fwk = NvBlastTkFrameworkGet(); + + // group + TkGroupDesc gdesc; + gdesc.pxTaskManager = m_taskman; + TkGroup* group = fwk->createGroup(gdesc); + EXPECT_TRUE(group != nullptr); + + // 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 p0(0, 0.0f); + CSParams p1(1, 0.0f); + CSParams p2(2, 0.0f); + + // 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); + + const uint32_t damageCount = 7; + std::vector<std::function<void(TkActor* a)>> damageFns(damageCount); + damageFns[0] = [&](TkActor* a) { a->damage(getCubeSlicerProgram(), &p0, sizeof(p0), getDefaultMaterial()); }; + damageFns[1] = [&](TkActor* a) { a->damage(getCubeSlicerProgram(), &p1, sizeof(p1), getDefaultMaterial()); }; + damageFns[2] = [&](TkActor* a) { a->damage(getCubeSlicerProgram(), &p2, sizeof(p2), getDefaultMaterial()); }; + damageFns[3] = [&](TkActor* a) { a->damage(getFalloffProgram(), &r0, sizeof(r0), getDefaultMaterial()); }; + damageFns[4] = [&](TkActor* a) { a->damage(getFalloffProgram(), &r1, sizeof(r1), getDefaultMaterial()); }; + damageFns[5] = [&](TkActor* a) { a->damage(getFalloffProgram(), &r2, sizeof(r2), getDefaultMaterial()); }; + damageFns[6] = [&](TkActor* a) { a->damage(getFalloffProgram(), &r3, sizeof(r3), getDefaultMaterial()); }; + + std::vector<TkActor*> actors(64); + + for (uint32_t i = 0; i < damageCount; i++) + { + actors.resize(family->getActorCount()); + family->getActors(actors.data(), static_cast<uint32_t>(actors.size())); + + // damage + for (auto actor : actors) + { + group->addActor(*actor); + damageFns[i](actor); + } + + // sync + EXPECT_TRUE(group->process()); + group->sync(true); + + 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.pxTaskManager = m_taskman; + TkGroup* group = fwk->createGroup(gdesc); + EXPECT_TRUE(group != nullptr); + + 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); + cubeActor1->damage(getFalloffProgram(), &r0, sizeof(r0)); + cubeActor2->damage(getFalloffProgram(), &r0, sizeof(r0)); + cubeActor3->damage(getFalloffProgram(), &r0, sizeof(r0)); + cubeActor4->damage(getFalloffProgram(), &r0, sizeof(r0)); + + Nv::Blast::Time time; + group->process(); + group->sync(true); + 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<TkFractureCommands>()->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<TkFractureEvents>()->tkActorData; + EXPECT_EQ((void*)'root', actor.userData); + EXPECT_EQ(0, actor.index); + } + break; + + case TkSplitEvent::EVENT_TYPE: + { + const TkSplitEvent* split = event.getPayload<TkSplitEvent>(); + + 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 }; + TkGroup* group = fwk->createGroup(groupDesc); + + group->addActor(*actor); + + // this will trigger hierarchical chunk fracture + NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); + actor->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + + group->process(); + group->sync(); + + 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[] = + { + { { 1,2 }, bondToKeep }, + { { 2,3 }, bondToBreak }, + }; + + 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<TkFractureCommands>()->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<TkFractureEvents>()->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<TkSplitEvent>(); + 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 }; + TkGroup* group = fwk->createGroup(groupDesc); + + group->addActor(*rootActor); + + // this will trigger one bond to break + NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0, 0.5f, 0.5f); + rootActor->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + + group->process(); + group->sync(); + + 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 }; + TkGroup* group = fwk->createGroup(groupDesc); + + group->addActor(*actor); + + NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); + actor->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + + group->process(); + group->sync(); + + 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 + + PxTaskManager* taskman = PxTaskManager::createTaskManager(*this, disp4); + + TkGroupDesc groupDesc = { taskman }; + TkGroup* group = fwk->createGroup(groupDesc); + + group->addActor(*actor1); + group->addActor(*actor2); + taskman->setCpuDispatcher(*disp0); + group->addActor(*actor3); + group->addActor(*actor4); + + NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); + actor1->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + actor2->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + actor3->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + actor4->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + + group->process(); + group->sync(); + + EXPECT_EQ(4, listener.fracCommands); + EXPECT_EQ(4, listener.fracEvents); + + releaseFramework(); + + disp0->release(); + disp4->release(); + taskman->release(); +} + +TEST_F(TkTestAllowWarnings, 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 + + PxTaskManager* taskman = PxTaskManager::createTaskManager(*this, disp2); + + TkGroupDesc groupDesc = { taskman }; + TkGroup* group = fwk->createGroup(groupDesc); + + group->addActor(*actor1); + group->addActor(*actor2); + group->addActor(*actor3); + group->addActor(*actor4); + + NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); + actor1->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + actor2->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + actor3->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + actor4->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + + taskman->setCpuDispatcher(*disp4); + + group->process(); + group->sync(); + + EXPECT_EQ(4, listener.fracCommands); + EXPECT_EQ(4, listener.fracEvents); + + releaseFramework(); + + disp2->release(); + disp4->release(); + taskman->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); + + TkGroupDesc groupDesc = { nullptr }; + TkGroup* group = fwk->createGroup(groupDesc); + + group->addActor(*actor1); + group->addActor(*actor2); + group->addActor(*actor3); + group->addActor(*actor4); + + NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0); + actor1->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + actor2->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + actor3->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + actor4->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial()); + + group->process(); + group->sync(); + + EXPECT_EQ(4, listener.fracCommands); + EXPECT_EQ(4, listener.fracEvents); + + releaseFramework(); +} + diff --git a/NvBlast/test/src/utils/TaskDispatcher.h b/NvBlast/test/src/utils/TaskDispatcher.h new file mode 100644 index 0000000..bd8bfc8 --- /dev/null +++ b/NvBlast/test/src/utils/TaskDispatcher.h @@ -0,0 +1,191 @@ +#pragma once + +#include <thread> +#include <mutex> +#include <queue> +#include <list> +#include <future> +#include <condition_variable> +#include <memory> +#include <atomic> + +class TaskDispatcher +{ +public: + class Task + { + public: + virtual void process() = 0; + virtual ~Task() {}; + }; + + typedef std::function<void(TaskDispatcher& dispatcher, std::unique_ptr<Task>)> OnTaskFinishedFunction; + + TaskDispatcher(uint32_t threadCount, OnTaskFinishedFunction onTaskFinished) : + m_workingThreadsCount(0), m_onTaskFinished(onTaskFinished) + { + m_threads.resize(threadCount); + for (uint32_t i = 0; i < threadCount; i++) + { + m_threads[i] = std::unique_ptr<Thread>(new Thread(i, m_completionSemaphore)); + m_threads[i]->start(); + m_freeThreads.push(m_threads[i].get()); + } + } + + void addTask(std::unique_ptr<Task> task) + { + m_tasks.push(std::move(task)); + } + + void process() + { + // main loop + while (m_tasks.size() > 0 || m_workingThreadsCount > 0) + { + // assign tasks + while (!(m_tasks.empty() || m_freeThreads.empty())) + { + auto task = std::move(m_tasks.front()); + m_tasks.pop(); + + Thread* freeThread = m_freeThreads.front(); + m_freeThreads.pop(); + + freeThread->processTask(std::move(task)); + m_workingThreadsCount++; + } + + m_completionSemaphore.wait(); + + // check for completion + for (std::unique_ptr<Thread>& thread : m_threads) + { + if (thread->isTaskFinished()) + { + std::unique_ptr<Task> task; + thread->collectTask(task); + m_onTaskFinished(*this, std::move(task)); + + m_freeThreads.push(thread.get()); + m_workingThreadsCount--; + break; + } + } + } + } + +private: + class Semaphore + { + public: + Semaphore(int count_ = 0) + : m_count(count_) {} + + inline void notify() + { + std::unique_lock<std::mutex> lock(m_mutex); + m_count++; + m_cv.notify_one(); + } + + inline void wait() + { + std::unique_lock<std::mutex> lock(m_mutex); + + while (m_count == 0){ + m_cv.wait(lock); + } + m_count--; + } + + + private: + std::mutex m_mutex; + std::condition_variable m_cv; + int m_count; + }; + + class Thread + { + public: + Thread(uint32_t id_, Semaphore& completionSemaphore) : m_id(id_), m_completionSemaphore(completionSemaphore), m_running(false), m_taskFinished(false) {} + virtual ~Thread() { stop(); } + + void start() + { + if (!m_running) + { + m_running = true; + m_thread = std::thread(&Thread::body, this); + } + } + + void stop() + { + if (m_running) + { + m_running = false; + m_newTaskSemaphore.notify(); + m_thread.join(); + } + } + + void processTask(std::unique_ptr<Task> task) + { + m_task = std::move(task); + m_taskFinished = false; + m_newTaskSemaphore.notify(); + } + + void collectTask(std::unique_ptr<Task>& task) + { + task = std::move(m_task); + m_task = nullptr; + m_taskFinished = false; + } + + bool hasTask() const { return m_task != nullptr; } + + bool isTaskFinished() const { return m_taskFinished; } + + private: + void body() + { + while (1) + { + m_newTaskSemaphore.wait(); + + if (!m_running) + return; + + m_task->process(); + m_taskFinished = true; + + m_completionSemaphore.notify(); + } + } + + uint32_t m_id; + Semaphore& m_completionSemaphore; + std::thread m_thread; + bool m_running; + + std::unique_ptr<Task> m_task; + std::atomic<bool> m_taskFinished; + + Semaphore m_newTaskSemaphore; + }; + +private: + uint32_t m_workingThreadsCount; + + std::queue<std::unique_ptr<Task>> m_tasks; + OnTaskFinishedFunction m_onTaskFinished; + + std::vector<std::unique_ptr<Thread>> m_threads; + std::queue<Thread*> m_freeThreads; + + Semaphore m_completionSemaphore; +}; + diff --git a/NvBlast/test/src/utils/TestAssets.cpp b/NvBlast/test/src/utils/TestAssets.cpp new file mode 100644 index 0000000..f2a1396 --- /dev/null +++ b/NvBlast/test/src/utils/TestAssets.cpp @@ -0,0 +1,288 @@ +#include "TestAssets.h" +#include "AssetGenerator.h" + +const NvBlastChunkDesc g_cube1ChunkDescs[9] = +{ +// centroid volume parent idx flags ID + { { 0.0f, 0.0f, 0.0f }, 8.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { {-0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { { 0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {-0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { { 0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + { {-0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 5 }, + { { 0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 6 }, + { {-0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 7 }, + { { 0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 8 }, +}; + +const NvBlastBondDesc g_cube1BondDescs[12] = +{ +// chunks normal area centroid userData + { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f,-0.5f,-0.5f }, 0 } }, + { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f, 0.5f,-0.5f }, 0 } }, + { { 5, 6 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f,-0.5f, 0.5f }, 0 } }, + { { 7, 8 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f, 0.5f, 0.5f }, 0 } }, + + { { 1, 3 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, {-0.5f, 0.0f,-0.5f }, 0 } }, + { { 2, 4 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 0.5f, 0.0f,-0.5f }, 0 } }, + { { 5, 7 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, {-0.5f, 0.0f, 0.5f }, 0 } }, + { { 6, 8 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 0.5f, 0.0f, 0.5f }, 0 } }, + + { { 1, 5 }, { { 0.0f, 0.0f, 1.0f }, 1.0f, {-0.5f,-0.5f, 0.0f }, 0 } }, + { { 2, 6 }, { { 0.0f, 0.0f, 1.0f }, 1.0f, { 0.5f,-0.5f, 0.0f }, 0 } }, + { { 3, 7 }, { { 0.0f, 0.0f, 1.0f }, 1.0f, {-0.5f, 0.5f, 0.0f }, 0 } }, + { { 4, 8 }, { { 0.0f, 0.0f, 1.0f }, 1.0f, { 0.5f, 0.5f, 0.0f }, 0 } }, +}; + +const NvBlastChunkDesc g_cube2ChunkDescs[73] = +{ +// centroid volume parent idx flags ID + { { 0.0f, 0.0f, 0.0f }, 8.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { {-0.5f, -0.5f, -0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { { 0.5f, -0.5f, -0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {-0.5f, 0.5f, -0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { { 0.5f, 0.5f, -0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + { {-0.5f, -0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 5 }, + { { 0.5f, -0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 6 }, + { {-0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 7 }, + { { 0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 8 }, + { {-0.25f-0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 9 }, + { { 0.25f-0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 10 }, + { {-0.25f-0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 11 }, + { { 0.25f-0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 12 }, + { {-0.25f-0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 13 }, + { { 0.25f-0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 14 }, + { {-0.25f-0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 15 }, + { { 0.25f-0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 16 }, + { {-0.25f+0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 17 }, + { { 0.25f+0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 18 }, + { {-0.25f+0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 19 }, + { { 0.25f+0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 20 }, + { {-0.25f+0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 21 }, + { { 0.25f+0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 22 }, + { {-0.25f+0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 23 }, + { { 0.25f+0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 24 }, + { {-0.25f-0.5f,-0.25f+0.5f,-0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 25 }, + { { 0.25f-0.5f,-0.25f+0.5f,-0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 26 }, + { {-0.25f-0.5f, 0.25f+0.5f,-0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 27 }, + { { 0.25f-0.5f, 0.25f+0.5f,-0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 28 }, + { {-0.25f-0.5f,-0.25f+0.5f, 0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 29 }, + { { 0.25f-0.5f,-0.25f+0.5f, 0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 30 }, + { {-0.25f-0.5f, 0.25f+0.5f, 0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 31 }, + { { 0.25f-0.5f, 0.25f+0.5f, 0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 32 }, + { {-0.25f+0.5f,-0.25f+0.5f,-0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 33 }, + { { 0.25f+0.5f,-0.25f+0.5f,-0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 34 }, + { {-0.25f+0.5f, 0.25f+0.5f,-0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 35 }, + { { 0.25f+0.5f, 0.25f+0.5f,-0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 36 }, + { {-0.25f+0.5f,-0.25f+0.5f, 0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 37 }, + { { 0.25f+0.5f,-0.25f+0.5f, 0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 38 }, + { {-0.25f+0.5f, 0.25f+0.5f, 0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 39 }, + { { 0.25f+0.5f, 0.25f+0.5f, 0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 40 }, + { {-0.25f-0.5f,-0.25f-0.5f,-0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 41 }, + { { 0.25f-0.5f,-0.25f-0.5f,-0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 42 }, + { {-0.25f-0.5f, 0.25f-0.5f,-0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 43 }, + { { 0.25f-0.5f, 0.25f-0.5f,-0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 44 }, + { {-0.25f-0.5f,-0.25f-0.5f, 0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 45 }, + { { 0.25f-0.5f,-0.25f-0.5f, 0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 46 }, + { {-0.25f-0.5f, 0.25f-0.5f, 0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 47 }, + { { 0.25f-0.5f, 0.25f-0.5f, 0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 48 }, + { {-0.25f+0.5f,-0.25f-0.5f,-0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 49 }, + { { 0.25f+0.5f,-0.25f-0.5f,-0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 50 }, + { {-0.25f+0.5f, 0.25f-0.5f,-0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 51 }, + { { 0.25f+0.5f, 0.25f-0.5f,-0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 52 }, + { {-0.25f+0.5f,-0.25f-0.5f, 0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 53 }, + { { 0.25f+0.5f,-0.25f-0.5f, 0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 54 }, + { {-0.25f+0.5f, 0.25f-0.5f, 0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 55 }, + { { 0.25f+0.5f, 0.25f-0.5f, 0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 56 }, + { {-0.25f-0.5f,-0.25f+0.5f,-0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 57 }, + { { 0.25f-0.5f,-0.25f+0.5f,-0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 58 }, + { {-0.25f-0.5f, 0.25f+0.5f,-0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 59 }, + { { 0.25f-0.5f, 0.25f+0.5f,-0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 60 }, + { {-0.25f-0.5f,-0.25f+0.5f, 0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 61 }, + { { 0.25f-0.5f,-0.25f+0.5f, 0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 62 }, + { {-0.25f-0.5f, 0.25f+0.5f, 0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 63 }, + { { 0.25f-0.5f, 0.25f+0.5f, 0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 64 }, + { {-0.25f+0.5f,-0.25f+0.5f,-0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 65 }, + { { 0.25f+0.5f,-0.25f+0.5f,-0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 66 }, + { {-0.25f+0.5f, 0.25f+0.5f,-0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 67 }, + { { 0.25f+0.5f, 0.25f+0.5f,-0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 68 }, + { {-0.25f+0.5f,-0.25f+0.5f, 0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 69 }, + { { 0.25f+0.5f,-0.25f+0.5f, 0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 70 }, + { {-0.25f+0.5f, 0.25f+0.5f, 0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 71 }, + { { 0.25f+0.5f, 0.25f+0.5f, 0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 72 }, +}; + +const NvBlastChunkDesc g_cube3ChunkDescs[11] = +{ +// centroid volume parent idx flags ID + { { 0.0f, 0.0f, 0.0f }, 4.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { { 0.0f, 0.0f, 0.0f }, 3.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 1 }, + { { 0.0f, 0.0f, 0.0f }, 1.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 2 }, + { {-0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { { 0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + { {-0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 5 }, + { { 0.5f, 0.5f,-0.5f }, 1.0f, 1, NvBlastChunkDesc::SupportFlag, 6 }, + { {-0.5f,-0.5f, 0.5f }, 1.0f, 1, NvBlastChunkDesc::SupportFlag, 7 }, + { { 0.5f,-0.5f, 0.5f }, 1.0f, 1, NvBlastChunkDesc::SupportFlag, 8 }, + { {-0.5f, 0.5f, 0.5f }, 1.0f, 2, NvBlastChunkDesc::SupportFlag, 9 }, + { { 0.5f, 0.5f, 0.5f }, 1.0f, 2, NvBlastChunkDesc::SupportFlag, 10 }, +}; + +const NvBlastBondDesc g_cube3BondDescs[12] = +{ + // chunks normal area centroid userData + { { 3, 4 },{ { 1.0f, 0.0f, 0.0f }, 1.0f,{ 0.0f,-0.5f,-0.5f }, 0 } }, + { { 5, 6 },{ { 1.0f, 0.0f, 0.0f }, 1.0f,{ 0.0f, 0.5f,-0.5f }, 0 } }, + { { 7, 8 },{ { 1.0f, 0.0f, 0.0f }, 1.0f,{ 0.0f,-0.5f, 0.5f }, 0 } }, + { { 9, 10},{ { 1.0f, 0.0f, 0.0f }, 1.0f,{ 0.0f, 0.5f, 0.5f }, 0 } }, + + { { 3, 5 },{ { 0.0f, 1.0f, 0.0f }, 1.0f,{-0.5f, 0.0f,-0.5f }, 0 } }, + { { 4, 6 },{ { 0.0f, 1.0f, 0.0f }, 1.0f,{ 0.5f, 0.0f,-0.5f }, 0 } }, + { { 7, 9 },{ { 0.0f, 1.0f, 0.0f }, 1.0f,{-0.5f, 0.0f, 0.5f }, 0 } }, + { { 8, 10},{ { 0.0f, 1.0f, 0.0f }, 1.0f,{ 0.5f, 0.0f, 0.5f }, 0 } }, + + { { 3, 7 },{ { 0.0f, 0.0f, 1.0f }, 1.0f,{-0.5f,-0.5f, 0.0f }, 0 } }, + { { 4, 8 },{ { 0.0f, 0.0f, 1.0f }, 1.0f,{ 0.5f,-0.5f, 0.0f }, 0 } }, + { { 5, 9 },{ { 0.0f, 0.0f, 1.0f }, 1.0f,{-0.5f, 0.5f, 0.0f }, 0 } }, + { { 6, 10},{ { 0.0f, 0.0f, 1.0f }, 1.0f,{ 0.5f, 0.5f, 0.0f }, 0 } }, +}; + +const NvBlastAssetDesc g_assetDescs[3] = +{ + // 2x2x2 axis-aligned cube centered at the origin, split into 8 depth-1 1x1x1 child chunks + { sizeof(g_cube1ChunkDescs) / sizeof(g_cube1ChunkDescs[0]), g_cube1ChunkDescs, sizeof(g_cube1BondDescs) / sizeof(g_cube1BondDescs[0]), g_cube1BondDescs }, + + // 2x2x2 axis-aligned cube centered at the origin, split into 8 depth-1 1x1x1 child chunks which are then split into 8 depth-2 (1/2)x(1/2)x(1/2) child chunks each + // Support is at depth-1, so the g_cube1BondDescs are used + { sizeof(g_cube2ChunkDescs) / sizeof(g_cube2ChunkDescs[0]), g_cube2ChunkDescs, sizeof(g_cube1BondDescs) / sizeof(g_cube1BondDescs[0]), g_cube1BondDescs }, + + // 2x2x2 axis-aligned cube centered at the origin, split into 8 depth-1 1x1x1 child chunks with multiple roots + { sizeof(g_cube3ChunkDescs) / sizeof(g_cube3ChunkDescs[0]), g_cube3ChunkDescs, sizeof(g_cube3BondDescs) / sizeof(g_cube3BondDescs[0]), g_cube3BondDescs }, +}; + + + +struct ExpectedValues +{ + uint32_t totalChunkCount; + uint32_t graphNodeCount; + uint32_t leafChunkCount; + uint32_t bondCount; + uint32_t subsupportChunkCount; +}; + +const ExpectedAssetValues g_assetExpectedValues[3] = +{ +// total graph leaves bonds sub + { 9, 8, 8, 12, 0 }, + { 73, 8, 64, 12, 64 }, + { 11, 8, 8, 12, 0 } +}; + + +///////////// Badly-formed asset descs below ////////////// + +const NvBlastChunkDesc g_cube1ChunkDescsMissingCoverage[9] = +{ +// centroid volume parent idx flags ID + { { 0.0f, 0.0f, 0.0f }, 8.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { {-0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 1 }, + { { 0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {-0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { { 0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + { {-0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 5 }, + { { 0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 6 }, + { {-0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 7 }, + { { 0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::NoFlags, 8 }, +}; + + +const NvBlastChunkDesc g_cube2ChunkDescsMissingCoverage1[17] = +{ +// centroid volume parent idx flags ID + { { 0.0f, 0.0f, 0.0f }, 8.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { {-0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::NoFlags, 1 }, + { { 0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {-0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { { 0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + { {-0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 5 }, + { { 0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 6 }, + { {-0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 7 }, + { { 0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::NoFlags, 8 }, + { {-0.25f-0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 9 }, + { { 0.25f-0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 10 }, + { {-0.25f-0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 11 }, + { { 0.25f-0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 12 }, + { {-0.25f-0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 13 }, + { { 0.25f-0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 14 }, + { {-0.25f-0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 15 }, + { { 0.25f-0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 16 }, +}; + + +const NvBlastChunkDesc g_cube2ChunkDescsMissingCoverage2[17] = +{ +// centroid volume parent idx flags ID + { { 0.0f, 0.0f, 0.0f }, 8.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 }, + { {-0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::NoFlags, 1 }, + { { 0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }, + { {-0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 3 }, + { { 0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 4 }, + { {-0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 5 }, + { { 0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 6 }, + { {-0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 7 }, + { { 0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::NoFlags, 8 }, + { {-0.25f-0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 9 }, + { { 0.25f-0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 10 }, + { {-0.25f-0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 11 }, + { { 0.25f-0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 12 }, + { {-0.25f-0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 13 }, + { { 0.25f-0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 14 }, + { {-0.25f-0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 15 }, + { { 0.25f-0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::SupportFlag, 16 }, +}; + + +const NvBlastAssetDesc g_assetDescsMissingCoverage[3] = +{ + { sizeof(g_cube1ChunkDescsMissingCoverage) / sizeof(g_cube1ChunkDescsMissingCoverage[0]), g_cube1ChunkDescsMissingCoverage, sizeof(g_cube1BondDescs) / sizeof(g_cube1BondDescs[0]), g_cube1BondDescs }, + { sizeof(g_cube2ChunkDescsMissingCoverage1) / sizeof(g_cube2ChunkDescsMissingCoverage1[0]), g_cube2ChunkDescsMissingCoverage1, sizeof(g_cube1BondDescs) / sizeof(g_cube1BondDescs[0]), g_cube1BondDescs }, + { sizeof(g_cube2ChunkDescsMissingCoverage2) / sizeof(g_cube2ChunkDescsMissingCoverage2[0]), g_cube2ChunkDescsMissingCoverage2, sizeof(g_cube1BondDescs) / sizeof(g_cube1BondDescs[0]), g_cube1BondDescs }, +}; + +extern const ExpectedAssetValues g_assetsFromMissingCoverageExpectedValues[3] = +{ +// total graph leaves bonds sub + { 9, 8, 8, 12, 0 }, + { 17, 8, 15, 12, 8 }, + { 17, 15, 15, 9, 0 }, +}; + + +void generateCube(GeneratorAsset& cubeAsset, size_t maxDepth, size_t width, int32_t supportDepth) +{ + CubeAssetGenerator::Settings settings; + settings.extents = GeneratorAsset::Vec3(1, 1, 1); + CubeAssetGenerator::DepthInfo depthInfo; + depthInfo.slicesPerAxis = GeneratorAsset::Vec3(1, 1, 1); + depthInfo.flag = NvBlastChunkDesc::Flags::NoFlags; + settings.depths.push_back(depthInfo); + + for (uint32_t depth = 1; depth < maxDepth; ++depth) + { + depthInfo.slicesPerAxis = GeneratorAsset::Vec3((float)width, (float)width, (float)width); + settings.depths.push_back(depthInfo); + } + settings.depths[(supportDepth > 0 ? supportDepth : maxDepth) - 1].flag = NvBlastChunkDesc::SupportFlag; // Leaves are support + + CubeAssetGenerator::generate(cubeAsset, settings); +} + +void generateCube(GeneratorAsset& cubeAsset, NvBlastAssetDesc& assetDesc, size_t maxDepth, size_t width, int32_t supportDepth) +{ + generateCube(cubeAsset, maxDepth, width, supportDepth); + assetDesc.bondCount = (uint32_t)cubeAsset.solverBonds.size(); + assetDesc.bondDescs = cubeAsset.solverBonds.data(); + assetDesc.chunkCount = (uint32_t)cubeAsset.chunks.size(); + assetDesc.chunkDescs = cubeAsset.solverChunks.data(); +}
\ No newline at end of file diff --git a/NvBlast/test/src/utils/TestAssets.h b/NvBlast/test/src/utils/TestAssets.h new file mode 100644 index 0000000..e4a9c77 --- /dev/null +++ b/NvBlast/test/src/utils/TestAssets.h @@ -0,0 +1,40 @@ +#ifndef TESTASSETS_H +#define TESTASSETS_H + +#include "NvBlast.h" +#include "AssetGenerator.h" + +struct ExpectedAssetValues +{ + uint32_t totalChunkCount; + uint32_t graphNodeCount; + uint32_t leafChunkCount; + uint32_t bondCount; + uint32_t subsupportChunkCount; +}; + + +// Indexable asset descriptors and expected values +extern const NvBlastAssetDesc g_assetDescs[3]; +extern const ExpectedAssetValues g_assetExpectedValues[3]; + +// Indexable asset descriptors for assets missing coverage and expected values +extern const NvBlastAssetDesc g_assetDescsMissingCoverage[3]; +extern const ExpectedAssetValues g_assetsFromMissingCoverageExpectedValues[3]; + + +inline uint32_t getAssetDescCount() +{ + return sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); +} + +inline uint32_t getAssetDescMissingCoverageCount() +{ + return sizeof(g_assetDescsMissingCoverage) / sizeof(g_assetDescsMissingCoverage[0]); +} + + +void generateCube(GeneratorAsset& cubeAsset, size_t maxDepth, size_t width, int32_t supportDepth = -1); +void generateCube(GeneratorAsset& cubeAsset, NvBlastAssetDesc& assetDesc, size_t maxDepth, size_t width, int32_t supportDepth = -1); + +#endif // #ifdef TESTASSETS_H diff --git a/NvBlast/test/src/utils/TestProfiler.h b/NvBlast/test/src/utils/TestProfiler.h new file mode 100644 index 0000000..318e6e6 --- /dev/null +++ b/NvBlast/test/src/utils/TestProfiler.h @@ -0,0 +1,27 @@ +#ifndef TESTPROFILER_H +#define TESTPROFILER_H + +#include "NvBlastPreprocessor.h" + +#if NV_NVTX +#include "nvToolsExt.h" +NV_INLINE void platformZoneStart(const char* name) { nvtxRangePush(name); } +NV_INLINE void platformZoneEnd(const char*) { nvtxRangePop(); } + +#elif NV_XBOXONE +#define NOMINMAX +#include "xboxone/NvBlastProfilerXB1.h" + +#elif NV_PS4 +#include "ps4/NvBlastProfilerPS4.h" + +#else +NV_INLINE void platformZoneStart(const char*) { } +NV_INLINE void platformZoneEnd(const char*) { } + +#endif + +#define TEST_ZONE_BEGIN(name) platformZoneStart(name) +#define TEST_ZONE_END(name) platformZoneEnd(name) + +#endif // TESTPROFILER_H |