diff options
| author | Bryan Galdrikian <[email protected]> | 2017-02-21 12:07:59 -0800 |
|---|---|---|
| committer | Bryan Galdrikian <[email protected]> | 2017-02-21 12:07:59 -0800 |
| commit | 446ce137c6823ba9eff273bdafdaf266287c7c98 (patch) | |
| tree | d20aab3e2ed08d7b3ca71c2f40db6a93ea00c459 /NvBlast/test/src/perf | |
| download | blast-1.0.0-beta.tar.xz blast-1.0.0-beta.zip | |
first commitv1.0.0-beta
Diffstat (limited to 'NvBlast/test/src/perf')
| -rw-r--r-- | NvBlast/test/src/perf/BlastBasePerfTest.h | 374 | ||||
| -rw-r--r-- | NvBlast/test/src/perf/SolverPerfTests.cpp | 211 |
2 files changed, 585 insertions, 0 deletions
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; +} |