PhysXCharactersRagdollBenchmarks.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #ifdef HAVE_BENCHMARK
  9. #include <benchmark/benchmark.h>
  10. #include <AzTest/AzTest.h>
  11. #include <AzTest/Utils.h>
  12. #include <AzCore/Math/MathUtils.h>
  13. #include <Benchmarks/PhysXBenchmarksCommon.h>
  14. #include <Benchmarks/PhysXBenchmarksUtilities.h>
  15. #include <Benchmarks/PhysXBenchmarkWashingMachine.h>
  16. #include <AzFramework/Physics/SystemBus.h>
  17. #include <RagdollTestData.h>
  18. #include <RagdollConfiguration.h>
  19. #include <AzFramework/Physics/RagdollPhysicsBus.h>
  20. #include <PhysXCharacters/API/Ragdoll.h>
  21. #include <PhysXCharacters/API/CharacterUtils.h>
  22. #include <PhysXTestCommon.h>
  23. namespace PhysX::Benchmarks
  24. {
  25. namespace RagdollConstants
  26. {
  27. //! Controls the simulation length of the test. 30secs at 60fps
  28. static const int GameFramesToSimulate = 1800;
  29. //! The size of the test terrain
  30. static const float TerrainSize = 1000.0f;
  31. //! Decide if CCD should be on/off for the following tests
  32. static const bool CCDEnabled = true;
  33. //! Constant seed to use with random number generation
  34. static const long long RandGenSeed = 909; //(Number generated by adding 'Character' ascii character codes together (67+104+97+114+97+99+116+101+114).
  35. //! Settings used to setup each benchmark
  36. namespace BenchmarkSettings
  37. {
  38. //! Values passed to benchmark to select the number of rigid bodies to spawn during each test
  39. //! Current values will run tests between StartRange to EndRange (inclusive), multiplying by RangeMultiplier each step.
  40. static const int StartRange = 1;
  41. static const int EndRange = 64;
  42. static const int RangeMultipler = 2;
  43. //! Number of iterations for each test
  44. static const int NumIterations = 3;
  45. } // namespace BenchmarkSettings
  46. //! Constants for setting up the washing machine
  47. namespace WashingMachine
  48. {
  49. static const float CylinderRadius = 5.0f;
  50. static const float CylinderHeight = 10.0f;
  51. static const float BladeRPM = 7.5f;
  52. } // namespace WashingMachine
  53. } // namespace RagdollConstants
  54. //! Ragdoll performance fixture.
  55. //! Will create a world, and terrain used within the test.
  56. class PhysXCharactersRagdollBenchmarkFixture
  57. : public PhysX::Benchmarks::PhysXBaseBenchmarkFixture
  58. {
  59. void internalSetUp()
  60. {
  61. PhysX::Benchmarks::PhysXBaseBenchmarkFixture::SetUpInternal();
  62. //need to get the Physics::System to be able to spawn the rigid bodies
  63. m_system = AZ::Interface<Physics::System>::Get();
  64. m_terrainEntity = PhysX::TestUtils::CreateFlatTestTerrain(m_testSceneHandle, RagdollConstants::TerrainSize, RagdollConstants::TerrainSize);
  65. }
  66. void internalTearDown()
  67. {
  68. m_terrainEntity = nullptr;
  69. PhysX::Benchmarks::PhysXBaseBenchmarkFixture::TearDownInternal();
  70. }
  71. public:
  72. void SetUp(const benchmark::State&) override
  73. {
  74. internalSetUp();
  75. }
  76. void SetUp(benchmark::State&) override
  77. {
  78. internalSetUp();
  79. }
  80. void TearDown(const benchmark::State&) override
  81. {
  82. internalTearDown();
  83. }
  84. void TearDown(benchmark::State&) override
  85. {
  86. internalTearDown();
  87. }
  88. protected:
  89. // PhysXBaseBenchmarkFixture Overrides ...
  90. AzPhysics::SceneConfiguration GetDefaultSceneConfiguration() override
  91. {
  92. AzPhysics::SceneConfiguration sceneConfig = AzPhysics::SceneConfiguration::CreateDefault();
  93. sceneConfig.m_enableCcd = RagdollConstants::CCDEnabled;
  94. return sceneConfig;
  95. }
  96. Physics::System* m_system;
  97. PhysX::EntityPtr m_terrainEntity;
  98. };
  99. Physics::RagdollState GetTPose(const AZ::Vector3& position, Physics::SimulationType simulationType = Physics::SimulationType::Simulated)
  100. {
  101. Physics::RagdollState ragdollState;
  102. for (int nodeIndex = 0; nodeIndex < RagdollTestData::NumNodes; nodeIndex++)
  103. {
  104. Physics::RagdollNodeState nodeState;
  105. nodeState.m_position = RagdollTestData::NodePositions[nodeIndex] + position;
  106. nodeState.m_orientation = RagdollTestData::NodeOrientations[nodeIndex];
  107. nodeState.m_simulationType = simulationType;
  108. ragdollState.push_back(nodeState);
  109. }
  110. return ragdollState;
  111. }
  112. Physics::RagdollState GetTPose(Physics::SimulationType simulationType = Physics::SimulationType::Simulated)
  113. {
  114. return GetTPose(AZ::Vector3::CreateZero(), simulationType);
  115. }
  116. PhysX::Ragdoll* CreateRagdoll(AzPhysics::SceneHandle sceneHandle)
  117. {
  118. Physics::RagdollConfiguration* configuration = AZ::Utils::LoadObjectFromBuffer<Physics::RagdollConfiguration>(
  119. RagdollTestData::RagdollConfiguration.data(), RagdollTestData::RagdollConfiguration.size());
  120. if (!configuration)
  121. {
  122. return nullptr;
  123. }
  124. configuration->m_initialState = GetTPose();
  125. configuration->m_parentIndices.reserve(configuration->m_nodes.size());
  126. for (int i = 0; i < configuration->m_nodes.size(); i++)
  127. {
  128. configuration->m_parentIndices.push_back(RagdollTestData::ParentIndices[i]);
  129. }
  130. if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
  131. {
  132. AzPhysics::SimulatedBodyHandle bodyHandle = sceneInterface->AddSimulatedBody(sceneHandle, configuration);
  133. return azdynamic_cast<Ragdoll*>(sceneInterface->GetSimulatedBodyFromHandle(sceneHandle, bodyHandle));
  134. }
  135. return nullptr;
  136. }
  137. //! BM_Ragdoll_AtRest - This test just spawns the requested number of ragdolls and places them near the terrain
  138. //! The test will run the simulation for ~1800 game frames at 60fps.
  139. BENCHMARK_DEFINE_F(PhysXCharactersRagdollBenchmarkFixture, BM_Ragdoll_AtRest)(benchmark::State& state)
  140. {
  141. //setup some pieces for the test
  142. const int numRagdolls = static_cast<const int>(state.range(0));
  143. //create ragdolls
  144. AZStd::vector<PhysX::Ragdoll*> ragdolls;
  145. ragdolls.reserve(numRagdolls);
  146. for (int i = 0; i < numRagdolls; i++)
  147. {
  148. ragdolls.emplace_back(CreateRagdoll(m_testSceneHandle));
  149. }
  150. //enable and position the ragdolls
  151. int spawnColIdx = 0;
  152. int spawnRowIdx = 0;
  153. const float spacing = 5.0f;
  154. const int ragdollsPerCol = static_cast<const int>(RagdollConstants::TerrainSize / spacing) - 1;
  155. for (auto& ragdoll : ragdolls)
  156. {
  157. spawnColIdx++;
  158. if (spawnColIdx >= ragdollsPerCol)
  159. {
  160. spawnColIdx = 0;
  161. spawnRowIdx++;
  162. }
  163. const AZ::Vector3 rootSpawnPosition(spacing + (spacing * spawnColIdx), spacing + (spacing * spawnRowIdx), 0.0f);
  164. auto kinematicTPose = GetTPose(rootSpawnPosition, static_cast<Physics::SimulationType>(state.range(1)));
  165. ragdoll->EnableSimulation(kinematicTPose);
  166. ragdoll->SetState(kinematicTPose);
  167. }
  168. //setup the sub tick tracker
  169. PhysX::Benchmarks::Utils::PrePostSimulationEventHandler subTickTracker;
  170. subTickTracker.Start(m_defaultScene);
  171. //setup the frame timer tracker
  172. AZStd::vector<double> tickTimes;
  173. tickTimes.reserve(RagdollConstants::GameFramesToSimulate);
  174. for ([[maybe_unused]] auto _ : state)
  175. {
  176. for (AZ::u32 i = 0; i < RagdollConstants::GameFramesToSimulate; i++)
  177. {
  178. auto start = AZStd::chrono::steady_clock::now();
  179. StepScene1Tick(DefaultTimeStep);
  180. //time each physics tick and store it to analyze
  181. auto tickElapsedMilliseconds = PhysX::Benchmarks::Types::double_milliseconds(AZStd::chrono::steady_clock::now() - start);
  182. tickTimes.emplace_back(tickElapsedMilliseconds.count());
  183. }
  184. }
  185. subTickTracker.Stop();
  186. //get the P50, P90, P99 percentiles
  187. PhysX::Benchmarks::Utils::ReportFramePercentileCounters(state, tickTimes, subTickTracker.GetSubTickTimes());
  188. PhysX::Benchmarks::Utils::ReportFrameStandardDeviationAndMeanCounters(state, tickTimes, subTickTracker.GetSubTickTimes());
  189. }
  190. //! BM_Ragdoll_MovingAndColliding - This test will create the physics washing machine, a cylinder with a spinning blade where
  191. //! it will spawn the requested number of ragdolls and places them above the machine to fall into the spinning blade.
  192. //! The test will run the simulation for ~1800 game frames at 60fps.
  193. BENCHMARK_DEFINE_F(PhysXCharactersRagdollBenchmarkFixture, BM_Ragdoll_MovingAndColliding)(benchmark::State& state)
  194. {
  195. //setup some pieces for the test
  196. AZ::SimpleLcgRandom rand;
  197. rand.SetSeed(RagdollConstants::RandGenSeed);
  198. const int numRagdolls = static_cast<const int>(state.range(0));
  199. //Create a washing machine of physx objects. This is a cylinder with a spinning blade that rigid bodies are placed inside
  200. const AZ::Vector3 washingMachineCentre(500.0f, 500.0f, 0.0f);
  201. PhysX::Benchmarks::WashingMachine washingMachine;
  202. washingMachine.SetupWashingMachine(
  203. m_testSceneHandle, RagdollConstants::WashingMachine::CylinderRadius, RagdollConstants::WashingMachine::CylinderHeight,
  204. washingMachineCentre, RagdollConstants::WashingMachine::BladeRPM);
  205. //create ragdolls
  206. AZStd::vector<PhysX::Ragdoll*> ragdolls;
  207. ragdolls.reserve(numRagdolls);
  208. for (int i = 0; i < numRagdolls; i++)
  209. {
  210. ragdolls.emplace_back(CreateRagdoll(m_testSceneHandle));
  211. }
  212. //enable and position the ragdolls
  213. int idx = 0;
  214. for (auto& ragdoll : ragdolls)
  215. {
  216. const float u = std::sqrt(rand.GetRandomFloat());
  217. const float theta = AZ::Constants::TwoPi * rand.GetRandomFloat();
  218. const float x = washingMachineCentre.GetX() + RagdollConstants::WashingMachine::CylinderRadius * u * std::sin(theta);
  219. const float y = washingMachineCentre.GetY() + RagdollConstants::WashingMachine::CylinderRadius * u * std::cos(theta);
  220. const float z = washingMachineCentre.GetZ() + 1.0f + (0.3f * idx);
  221. auto kinematicTPose = GetTPose(AZ::Vector3(x, y, z), Physics::SimulationType::Simulated);
  222. ragdoll->EnableSimulation(kinematicTPose);
  223. ragdoll->SetState(kinematicTPose);
  224. idx++;
  225. }
  226. //setup the sub tick tracker
  227. PhysX::Benchmarks::Utils::PrePostSimulationEventHandler subTickTracker;
  228. subTickTracker.Start(m_defaultScene);
  229. //setup the frame timer tracker
  230. AZStd::vector<double> tickTimes;
  231. tickTimes.reserve(RagdollConstants::GameFramesToSimulate);
  232. for ([[maybe_unused]] auto _ : state)
  233. {
  234. for (AZ::u32 i = 0; i < RagdollConstants::GameFramesToSimulate; i++)
  235. {
  236. auto start = AZStd::chrono::steady_clock::now();
  237. StepScene1Tick(DefaultTimeStep);
  238. //time each physics tick and store it to analyze
  239. auto tickElapsedMilliseconds = PhysX::Benchmarks::Types::double_milliseconds(AZStd::chrono::steady_clock::now() - start);
  240. tickTimes.emplace_back(tickElapsedMilliseconds.count());
  241. }
  242. }
  243. subTickTracker.Stop();
  244. //get the P50, P90, P99 percentiles
  245. PhysX::Benchmarks::Utils::ReportFramePercentileCounters(state, tickTimes, subTickTracker.GetSubTickTimes());
  246. PhysX::Benchmarks::Utils::ReportFrameStandardDeviationAndMeanCounters(state, tickTimes, subTickTracker.GetSubTickTimes());
  247. }
  248. BENCHMARK_REGISTER_F(PhysXCharactersRagdollBenchmarkFixture, BM_Ragdoll_AtRest)
  249. ->RangeMultiplier(RagdollConstants::BenchmarkSettings::RangeMultipler)
  250. ->Ranges({
  251. {RagdollConstants::BenchmarkSettings::StartRange, RagdollConstants::BenchmarkSettings::EndRange},
  252. {static_cast<int>(Physics::SimulationType::Kinematic), static_cast<int>(Physics::SimulationType::Simulated)}
  253. })
  254. ->Unit(benchmark::kMillisecond)
  255. ->Iterations(RagdollConstants::BenchmarkSettings::NumIterations)
  256. ;
  257. BENCHMARK_REGISTER_F(PhysXCharactersRagdollBenchmarkFixture, BM_Ragdoll_MovingAndColliding)
  258. ->RangeMultiplier(RagdollConstants::BenchmarkSettings::RangeMultipler)
  259. ->Range(RagdollConstants::BenchmarkSettings::StartRange, RagdollConstants::BenchmarkSettings::EndRange)
  260. ->Unit(benchmark::kMillisecond)
  261. ->Iterations(RagdollConstants::BenchmarkSettings::NumIterations)
  262. ;
  263. } // namespace PhysX::Benchmarks
  264. #endif // #ifdef HAVE_BENCHMARK