/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #ifdef HAVE_BENCHMARK #include #include #include #include #include #include #include #include #include namespace PhysX::Benchmarks { namespace RigidBodyConstants { //! Controls the simulation length of the test. 30secs at 60fps static const int GameFramesToSimulate = 1800; //! The size of safe the test region, used for spawning Rigid bodies static const float TestRadius = 250.0f; //! The size of the test terrain static const float TerrainSize = 1000.0f; //! Decide if CCD should be on/off for the following tests static const bool CCDEnabled = true; //! Constant seed to use with random number generation static const long long RandGenSeed = 8010412111588; //(Number generated by concatenating 'PhysX' ascii character codes (80 104 121 115 88). //! Constants for setting up the washing machine namespace WashingMachine { static const float CylinderHeight = 500.0f; static const float BladeRPM = 10.0f; } // namespace WashingMachine //! Constants for setting up the Rigid Bodies namespace RigidBodys { static const float BoxSize = 5.0f; //! Default starting value of Entity Ids for rigid bodies static const AZ::u64 RigidBodyEntityIdStart = 2000u; } //! Settings used to setup each benchmark namespace BenchmarkSettings { //! Values passed to benchmark to select the number of rigid bodies to spawn during each test //! Current values will run tests between StartRange to EndRange (inclusive), multiplying by RangeMultiplier each step. static const int StartRange = 128; static const int EndRange = 8192; static const int RangeMultipler = 2; //!Flags to adjust how the collision handlers benchmark runs static const int AllCollisionHanders = 0; // create the same number of handlers as rigid bodies static const int HalfCollisionHandlers = 1; // create half the number of handlers as rigid bodies static const int NoCollisionHandlers = 2; // create the no handlers //! Number of iterations for each test static const int NumIterations = 3; } // namespace BenchmarkRange } // namespace RigidBodyConstants namespace Utils { //! Object that will connect to the collision notification bus of the provided entity id struct SimulatedBodyCollisionHandler { SimulatedBodyCollisionHandler(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle) : m_onCollisionBeginHandler( [this]([[maybe_unused]] AzPhysics::SimulatedBodyHandle bodyHandle, [[maybe_unused]] const AzPhysics::CollisionEvent& event) { m_beginCount++; }) , m_onCollisionPersistHandler( [this]([[maybe_unused]] AzPhysics::SimulatedBodyHandle bodyHandle, [[maybe_unused]] const AzPhysics::CollisionEvent& event) { m_persistCount++; }) , m_onCollisionEndHandler( [this]([[maybe_unused]] AzPhysics::SimulatedBodyHandle bodyHandle, [[maybe_unused]] const AzPhysics::CollisionEvent& event) { m_endCount++; }) { AzPhysics::SimulatedBodyEvents::RegisterOnCollisionBeginHandler(sceneHandle, bodyHandle, m_onCollisionBeginHandler); AzPhysics::SimulatedBodyEvents::RegisterOnCollisionBeginHandler(sceneHandle, bodyHandle, m_onCollisionPersistHandler); AzPhysics::SimulatedBodyEvents::RegisterOnCollisionBeginHandler(sceneHandle, bodyHandle, m_onCollisionEndHandler); } SimulatedBodyCollisionHandler(SimulatedBodyCollisionHandler&& other) { m_onCollisionBeginHandler = AZStd::move(other.m_onCollisionBeginHandler); m_onCollisionPersistHandler = AZStd::move(other.m_onCollisionPersistHandler); m_onCollisionEndHandler = AZStd::move(other.m_onCollisionEndHandler); m_beginCount = other.m_beginCount; m_persistCount = other.m_persistCount; m_endCount = other.m_endCount; } ~SimulatedBodyCollisionHandler() { m_onCollisionBeginHandler.Disconnect(); m_onCollisionPersistHandler.Disconnect(); m_onCollisionEndHandler.Disconnect(); } int m_beginCount = 0; int m_persistCount = 0; int m_endCount = 0; private: AzPhysics::SimulatedBodyEvents::OnCollisionBegin::Handler m_onCollisionBeginHandler; AzPhysics::SimulatedBodyEvents::OnCollisionPersist::Handler m_onCollisionPersistHandler; AzPhysics::SimulatedBodyEvents::OnCollisionEnd::Handler m_onCollisionEndHandler; }; } // namespace Utils //! Rigid body performance fixture. //! Will create a world, and terrain. class PhysXRigidbodyBenchmarkFixture : public PhysXBaseBenchmarkFixture { protected: virtual void internalSetUp() { PhysXBaseBenchmarkFixture::SetUpInternal(); //need to get the Physics::System to be able to spawn the rigid bodies m_system = AZ::Interface::Get(); m_terrainEntity = PhysX::TestUtils::CreateFlatTestTerrain(m_testSceneHandle, RigidBodyConstants::TerrainSize, RigidBodyConstants::TerrainSize); } virtual void internalTearDown() { m_terrainEntity = nullptr; PhysXBaseBenchmarkFixture::TearDownInternal(); } public: void SetUp(const benchmark::State&) override { internalSetUp(); } void SetUp(benchmark::State&) override { internalSetUp(); } void TearDown(const benchmark::State&) override { internalTearDown(); } void TearDown(benchmark::State&) override { internalTearDown(); } protected: // PhysXBaseBenchmarkFixture Interface --------- AzPhysics::SceneConfiguration GetDefaultSceneConfiguration() override { AzPhysics::SceneConfiguration sceneConfig = AzPhysics::SceneConfiguration::CreateDefault(); sceneConfig.m_enableCcd = RigidBodyConstants::CCDEnabled; return sceneConfig; } // PhysXBaseBenchmarkFixture Interface --------- Physics::System *m_system; EntityPtr m_terrainEntity; }; //! BM_RigidBody_AtRest - This test will spawn the requested number of rigid bodies and place them near the ground //! and the rigid bodies will go 'asleep' //! The test will run the simulation for ~1800 game frames at 60fps. BENCHMARK_DEFINE_F(PhysXRigidbodyBenchmarkFixture, BM_RigidBody_AtRest)(benchmark::State& state) { //get the request number of rigid bodies and prepare to spawn them const int numRigidBodies = static_cast(state.range(0)); //common settings for each rigid body const float boxSizeWithSpacing = RigidBodyConstants::RigidBodys::BoxSize + 2.0f; const int boxesPerCol = static_cast(RigidBodyConstants::TerrainSize / boxSizeWithSpacing) - 1; int spawnColIdx = 0; int spawnRowIdx = 0; //function to generate the rigid bodies position / orientation / mass Utils::GenerateSpawnPositionFuncPtr posGenerator = [boxSizeWithSpacing, boxesPerCol, &spawnColIdx, &spawnRowIdx]([[maybe_unused]] int idx) -> const AZ::Vector3 { const float x = boxSizeWithSpacing + (boxSizeWithSpacing * spawnColIdx); const float y = boxSizeWithSpacing + (boxSizeWithSpacing * spawnRowIdx); const float z = RigidBodyConstants::RigidBodys::BoxSize / 2.0f; //advance to the next position to spawn the next rigid body spawnColIdx++; if (spawnColIdx >= boxesPerCol) { spawnColIdx = 0; spawnRowIdx++; } return AZ::Vector3(x, y, z); }; auto boxShapeConfiguration = AZStd::make_shared(AZ::Vector3(RigidBodyConstants::RigidBodys::BoxSize)); Utils::GenerateColliderFuncPtr colliderGenerator = [&boxShapeConfiguration]([[maybe_unused]] int idx) { return boxShapeConfiguration; }; //spawn the rigid bodies AzPhysics::SimulatedBodyHandleList rigidBodies = Utils::CreateRigidBodies(numRigidBodies, m_defaultScene, RigidBodyConstants::CCDEnabled, &colliderGenerator, &posGenerator); //setup the sub tick tracker Utils::PrePostSimulationEventHandler subTickTracker; subTickTracker.Start(m_defaultScene); //setup the frame timer tracker Types::TimeList tickTimes; for ([[maybe_unused]] auto _ : state) { for (AZ::u32 i = 0; i < RigidBodyConstants::GameFramesToSimulate; i++) { auto start = AZStd::chrono::system_clock::now(); StepScene1Tick(DefaultTimeStep); //time each physics tick and store it to analyze auto tickElapsedMilliseconds = Types::double_milliseconds(AZStd::chrono::system_clock::now() - start); tickTimes.emplace_back(tickElapsedMilliseconds.count()); } } subTickTracker.Stop(); //object clean up m_defaultScene->RemoveSimulatedBodies(rigidBodies); rigidBodies.clear(); //sort the frame times and get the P50, P90, P99 percentiles Utils::ReportFramePercentileCounters(state, tickTimes, subTickTracker.GetSubTickTimes()); Utils::ReportFrameStandardDeviationAndMeanCounters(state, tickTimes, subTickTracker.GetSubTickTimes()); } //! BM_RigidBody_MovingAndColliding - This test will create the physics washing machine, a cylinder with a spinning blade where //! it will spawn the requested number of rigid bodies above the machine and let them fall into a spinning blade. //! The test will run the simulation for ~1800 game frames at 60fps. BENCHMARK_DEFINE_F(PhysXRigidbodyBenchmarkFixture, BM_RigidBody_MovingAndColliding)(benchmark::State &state) { //setup some pieces for the test AZ::SimpleLcgRandom rand; rand.SetSeed(RigidBodyConstants::RandGenSeed); //Create a washing machine of physx objects. This is a cylinder with a spinning blade that rigid bodies are placed inside const AZ::Vector3 washingMachineCentre(500.0f, 500.0f, 1.0f); WashingMachine washingMachine; washingMachine.SetupWashingMachine( m_testSceneHandle, RigidBodyConstants::TestRadius, RigidBodyConstants::WashingMachine::CylinderHeight, washingMachineCentre, RigidBodyConstants::WashingMachine::BladeRPM); //get the request number of rigid bodies and prepare to spawn them const int numRigidBodies = static_cast(state.range(0)); //add the rigid bodies //function to generate the rigid bodies position / orientation / mass Utils::GenerateSpawnPositionFuncPtr posGenerator = [washingMachineCentre, &rand](int idx) -> const AZ::Vector3 { const float spawnArea = (RigidBodyConstants::TestRadius * 1.5f); const float x = washingMachineCentre.GetX() + (rand.GetRandomFloat() - 0.5f) * spawnArea; const float y = washingMachineCentre.GetY() + (rand.GetRandomFloat() - 0.5f) * spawnArea; const float z = washingMachineCentre.GetZ() + RigidBodyConstants::WashingMachine::CylinderHeight + ((RigidBodyConstants::RigidBodys::BoxSize / 2.0f) * idx); return AZ::Vector3(x, y, z); }; Utils::GenerateSpawnOrientationFuncPtr oriGenerator = [&rand]([[maybe_unused]] int idx) -> AZ::Quaternion { return AZ::CreateRandomQuaternion(rand); }; Utils::GenerateMassFuncPtr massGenerator = [&rand]([[maybe_unused]] int idx) -> float { return rand.GetRandomFloat() * 25.0f + 5.0f; }; auto boxShapeConfiguration = AZStd::make_shared(AZ::Vector3(RigidBodyConstants::RigidBodys::BoxSize)); Utils::GenerateColliderFuncPtr colliderGenerator = [&boxShapeConfiguration]([[maybe_unused]] int idx) { return boxShapeConfiguration; }; //spawn the rigid bodies AzPhysics::SimulatedBodyHandleList rigidBodies = Utils::CreateRigidBodies(numRigidBodies, m_defaultScene, RigidBodyConstants::CCDEnabled, &colliderGenerator, &posGenerator, &oriGenerator, &massGenerator); //setup the sub tick tracker Utils::PrePostSimulationEventHandler subTickTracker; subTickTracker.Start(m_defaultScene); //setup the frame timer tracker AZStd::vector tickTimes; tickTimes.reserve(RigidBodyConstants::GameFramesToSimulate); for ([[maybe_unused]] auto _ : state) { for (AZ::u32 i = 0; i < RigidBodyConstants::GameFramesToSimulate; i++) { auto start = AZStd::chrono::system_clock::now(); StepScene1Tick(DefaultTimeStep); //time each physics tick and store it to analyze auto tickElapsedMilliseconds = Types::double_milliseconds(AZStd::chrono::system_clock::now() - start); tickTimes.emplace_back(tickElapsedMilliseconds.count()); } } subTickTracker.Stop(); //object clean up washingMachine.TearDownWashingMachine(); m_defaultScene->RemoveSimulatedBodies(rigidBodies); rigidBodies.clear(); //sort the frame times and get the P50, P90, P99 percentiles Utils::ReportFramePercentileCounters(state, tickTimes, subTickTracker.GetSubTickTimes()); Utils::ReportFrameStandardDeviationAndMeanCounters(state, tickTimes, subTickTracker.GetSubTickTimes()); } //! Same as the PhysXRigidbodyBenchmarkFixture, adds a world event handler to receive collision events class PhysXRigidbodyCollisionsBenchmarkFixture : public PhysXRigidbodyBenchmarkFixture { void internalSetUp() override { PhysXRigidbodyBenchmarkFixture::internalSetUp(); m_collisionBeginCount = 0; m_collisionPersistCount = 0; m_collisionEndCount = 0; m_onSceneCollisionHandler = AzPhysics::SceneEvents::OnSceneCollisionsEvent::Handler( [this]([[maybe_unused]] AzPhysics::SceneHandle sceneHandle, const AzPhysics::CollisionEventList& collisionData) { //count the events for (const auto& collisionEvent : collisionData) { switch (collisionEvent.m_type) { case AzPhysics::CollisionEvent::Type::Begin: m_collisionBeginCount++; break; case AzPhysics::CollisionEvent::Type::Persist: m_collisionPersistCount++; break; case AzPhysics::CollisionEvent::Type::End: m_collisionEndCount++; break; } } } ); m_defaultScene->RegisterSceneCollisionEventHandler(m_onSceneCollisionHandler); } void internalTearDown() override { m_onSceneCollisionHandler.Disconnect(); PhysXRigidbodyBenchmarkFixture::internalTearDown(); } public: void SetUp(const benchmark::State&) override { internalSetUp(); } void SetUp(benchmark::State&) override { internalSetUp(); } void TearDown(const benchmark::State&) override { internalTearDown(); } void TearDown(benchmark::State&) override { internalTearDown(); } protected: // Collision counters to track and report the number of events int m_collisionBeginCount; int m_collisionPersistCount; int m_collisionEndCount; AzPhysics::SceneEvents::OnSceneCollisionsEvent::Handler m_onSceneCollisionHandler; }; //! BM_RigidBody_MovingAndColliding_CollisionHandlers - Runs that same benchmark as BM_RigidBody_MovingAndColliding, under a different fixture that adds world event handler for collisions //! Will have an equal number of Collision Handlers and rigid bodies. Not all rigid bodies will get a handler, some will have multiple. //! The test will run the simulation for ~1800 game frames at 60fps. BENCHMARK_DEFINE_F(PhysXRigidbodyCollisionsBenchmarkFixture, BM_RigidBody_MovingAndColliding_CollisionHandlers)(benchmark::State& state) { //setup some pieces for the test AZ::SimpleLcgRandom rand; rand.SetSeed(RigidBodyConstants::RandGenSeed); //Create a washing machine of physx objects. This is a cylinder with a spinning blade that rigid bodies are placed inside const AZ::Vector3 washingMachineCentre(500.0f, 500.0f, 1.0f); WashingMachine washingMachine; washingMachine.SetupWashingMachine( m_testSceneHandle, RigidBodyConstants::TestRadius, RigidBodyConstants::WashingMachine::CylinderHeight, washingMachineCentre, RigidBodyConstants::WashingMachine::BladeRPM); //get the request number of rigid bodies and prepare to spawn them const int numRigidBodies = static_cast(state.range(0)); //add the rigid bodies //function to generate the rigid bodies position / orientation / mass Utils::GenerateSpawnPositionFuncPtr posGenerator = [washingMachineCentre, &rand](int idx) -> const AZ::Vector3 { const float u = std::sqrt(rand.GetRandomFloat()); const float theta = AZ::Constants::TwoPi * rand.GetRandomFloat(); const float x = washingMachineCentre.GetX() + RigidBodyConstants::TestRadius * u * std::sin(theta); const float y = washingMachineCentre.GetY() + RigidBodyConstants::TestRadius * u * std::cos(theta); const float z = washingMachineCentre.GetZ() + RigidBodyConstants::WashingMachine::CylinderHeight + ((RigidBodyConstants::RigidBodys::BoxSize / 2.0f) * idx); return AZ::Vector3(x, y, z); }; Utils::GenerateSpawnOrientationFuncPtr oriGenerator = [&rand]([[maybe_unused]] int idx) -> AZ::Quaternion { return AZ::CreateRandomQuaternion(rand); }; Utils::GenerateMassFuncPtr massGenerator = [&rand]([[maybe_unused]] int idx) -> float { return rand.GetRandomFloat() * 25.0f + 5.0f; }; Utils::GenerateEntityIdFuncPtr entityIdGenerator = [](int idx) -> AZ::EntityId { return AZ::EntityId(static_cast(idx) + RigidBodyConstants::RigidBodys::RigidBodyEntityIdStart); }; auto boxShapeConfiguration = AZStd::make_shared(AZ::Vector3(RigidBodyConstants::RigidBodys::BoxSize)); Utils::GenerateColliderFuncPtr colliderGenerator = [&boxShapeConfiguration]([[maybe_unused]] int idx) { return boxShapeConfiguration; }; //spawn the rigid bodies AzPhysics::SimulatedBodyHandleList rigidBodies = Utils::CreateRigidBodies(numRigidBodies, m_defaultScene, RigidBodyConstants::CCDEnabled, &colliderGenerator, &posGenerator, &oriGenerator, &massGenerator); //create the collision handlers AZStd::vector collisionHandlers; int numberCollisionHandlers = 0; if (static_cast(state.range(1)) == RigidBodyConstants::BenchmarkSettings::AllCollisionHanders) { numberCollisionHandlers = numRigidBodies; } else if (static_cast(state.range(1)) == RigidBodyConstants::BenchmarkSettings::HalfCollisionHandlers) { numberCollisionHandlers = numRigidBodies / 2; } collisionHandlers.reserve(numberCollisionHandlers); for (int i = 0; i < numberCollisionHandlers; i++) { const AZ::u64 randIndex = rand.Getu64Random() % numRigidBodies; //randomly select the offset. collisionHandlers.emplace_back(m_testSceneHandle, rigidBodies[randIndex]); } //setup the sub tick tracker Utils::PrePostSimulationEventHandler subTickTracker; subTickTracker.Start(m_defaultScene); //setup the frame timer tracker AZStd::vector tickTimes; tickTimes.reserve(RigidBodyConstants::GameFramesToSimulate); for ([[maybe_unused]] auto _ : state) { for (AZ::u32 i = 0; i < RigidBodyConstants::GameFramesToSimulate; i++) { auto start = AZStd::chrono::system_clock::now(); StepScene1Tick(DefaultTimeStep); //time each physics tick and store it to analyze auto tickElapsedMilliseconds = Types::double_milliseconds(AZStd::chrono::system_clock::now() - start); tickTimes.emplace_back(tickElapsedMilliseconds.count()); } } subTickTracker.Stop(); //object clean up collisionHandlers.clear(); washingMachine.TearDownWashingMachine(); m_defaultScene->RemoveSimulatedBodies(rigidBodies); rigidBodies.clear(); //sort the frame times and get the P50, P90, P99 percentiles Utils::ReportFramePercentileCounters(state, tickTimes, subTickTracker.GetSubTickTimes()); Utils::ReportFrameStandardDeviationAndMeanCounters(state, tickTimes, subTickTracker.GetSubTickTimes()); //add the collision counts state.counters["Collisions-Begin"] = static_cast(m_collisionBeginCount); state.counters["Collisions-Persist"] = static_cast(m_collisionPersistCount); state.counters["Collisions-End"] = static_cast(m_collisionEndCount); } BENCHMARK_REGISTER_F(PhysXRigidbodyBenchmarkFixture, BM_RigidBody_AtRest) ->RangeMultiplier(RigidBodyConstants::BenchmarkSettings::RangeMultipler) ->Range(RigidBodyConstants::BenchmarkSettings::StartRange, RigidBodyConstants::BenchmarkSettings::EndRange) ->Unit(benchmark::kMillisecond) ->Iterations(RigidBodyConstants::BenchmarkSettings::NumIterations) ; BENCHMARK_REGISTER_F(PhysXRigidbodyBenchmarkFixture, BM_RigidBody_MovingAndColliding) ->RangeMultiplier(RigidBodyConstants::BenchmarkSettings::RangeMultipler) ->Range(RigidBodyConstants::BenchmarkSettings::StartRange, RigidBodyConstants::BenchmarkSettings::EndRange) ->Unit(benchmark::kMillisecond) ->Iterations(RigidBodyConstants::BenchmarkSettings::NumIterations) ; BENCHMARK_REGISTER_F(PhysXRigidbodyCollisionsBenchmarkFixture, BM_RigidBody_MovingAndColliding_CollisionHandlers) ->RangeMultiplier(RigidBodyConstants::BenchmarkSettings::RangeMultipler) ->Ranges({ {RigidBodyConstants::BenchmarkSettings::StartRange, RigidBodyConstants::BenchmarkSettings::EndRange}, {RigidBodyConstants::BenchmarkSettings::AllCollisionHanders, RigidBodyConstants::BenchmarkSettings::AllCollisionHanders} }) ->Unit(benchmark::kMillisecond) ->Iterations(RigidBodyConstants::BenchmarkSettings::NumIterations) ; BENCHMARK_REGISTER_F(PhysXRigidbodyCollisionsBenchmarkFixture, BM_RigidBody_MovingAndColliding_CollisionHandlers) ->RangeMultiplier(RigidBodyConstants::BenchmarkSettings::RangeMultipler) ->Ranges({ {RigidBodyConstants::BenchmarkSettings::StartRange, RigidBodyConstants::BenchmarkSettings::EndRange}, {RigidBodyConstants::BenchmarkSettings::HalfCollisionHandlers, RigidBodyConstants::BenchmarkSettings::HalfCollisionHandlers} }) ->Unit(benchmark::kMillisecond) ->Iterations(RigidBodyConstants::BenchmarkSettings::NumIterations) ; BENCHMARK_REGISTER_F(PhysXRigidbodyCollisionsBenchmarkFixture, BM_RigidBody_MovingAndColliding_CollisionHandlers) ->RangeMultiplier(RigidBodyConstants::BenchmarkSettings::RangeMultipler) ->Ranges({ {RigidBodyConstants::BenchmarkSettings::StartRange, RigidBodyConstants::BenchmarkSettings::EndRange}, {RigidBodyConstants::BenchmarkSettings::NoCollisionHandlers, RigidBodyConstants::BenchmarkSettings::NoCollisionHandlers} }) ->Unit(benchmark::kMillisecond) ->Iterations(RigidBodyConstants::BenchmarkSettings::NumIterations) ; } // namespace PhysX::Benchmarks #endif