Files
o3de/Gems/PhysX/Code/Tests/Benchmarks/PhysXRigidBodyBenchmarks.cpp
T
2021-03-08 14:30:57 -08:00

478 lines
24 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <PhysX_precompiled.h>
#ifdef HAVE_BENCHMARK
#include <benchmark/benchmark.h>
#include <AzTest/AzTest.h>
#include <Benchmarks/PhysXBenchmarksUtilities.h>
#include <Benchmarks/PhysXBenchmarksCommon.h>
#include <Benchmarks/PhysXBenchmarkWashingMachine.h>
#include <PhysXTestCommon.h>
#include <PhysXTestUtil.h>
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
: public Physics::CollisionNotificationBus::Handler
{
SimulatedBodyCollisionHandler(AZ::EntityId entityId)
{
Physics::CollisionNotificationBus::Handler::BusConnect(entityId);
}
~SimulatedBodyCollisionHandler()
{
Physics::CollisionNotificationBus::Handler::BusDisconnect();
}
// Physics::CollisionNotificationBus::Handler ---------
// just having the bus connected is enough to cause a change in perf, no need to add anything to the function callbacks
void OnCollisionBegin([[maybe_unused]] const Physics::CollisionEvent& collisionEvent) override {}
void OnCollisionPersist([[maybe_unused]] const Physics::CollisionEvent& collisionEvent) override {}
void OnCollisionEnd([[maybe_unused]] const Physics::CollisionEvent& collisionEvent) override {}
// Physics::CollisionNotificationBus::Handler ---------
};
} // namespace Utils
//! Rigid body performance fixture.
//! Will create a world, and terrain.
class PhysXRigidbodyBenchmarkFixture
: public PhysXBaseBenchmarkFixture
{
public:
virtual void SetUp([[maybe_unused]] const ::benchmark::State &state) override
{
PhysXBaseBenchmarkFixture::SetUpInternal();
//need to get the Physics::System to be able to spawn the rigid bodies
m_system = AZ::Interface<Physics::System>::Get();
m_terrainEntity = PhysX::TestUtils::CreateFlatTestTerrain(RigidBodyConstants::TerrainSize, RigidBodyConstants::TerrainSize);
}
virtual void TearDown([[maybe_unused]] const ::benchmark::State &state) override
{
m_terrainEntity = nullptr;
PhysXBaseBenchmarkFixture::TearDownInternal();
}
protected:
// PhysXBaseBenchmarkFixture Interface ---------
Physics::WorldConfiguration GetDefaultWorldConfiguration() override
{
Physics::WorldConfiguration worldConfig;
worldConfig.m_gravity = AZ::Vector3(0.0f, 0.0f, -9.81f);
worldConfig.m_enableCcd = RigidBodyConstants::CCDEnabled;
return worldConfig;
}
// 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<int>(state.range(0));
//common settings for each rigid body
const float boxSize = 5.0f;
const float boxSizeWithSpacing = RigidBodyConstants::RigidBodys::BoxSize + 2.0f;
const int boxesPerCol = static_cast<const int>(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);
};
Physics::BoxShapeConfiguration boxShapeConfiguration = Physics::BoxShapeConfiguration(AZ::Vector3(RigidBodyConstants::RigidBodys::BoxSize));
Utils::GenerateColliderFuncPtr colliderGenerator = [&boxShapeConfiguration]([[maybe_unused]] int idx) -> Physics::ShapeConfiguration*
{
return &boxShapeConfiguration;
};
//spawn the rigid bodies
AZStd::vector<AZStd::unique_ptr<Physics::RigidBody>> rigidBodies = Utils::CreateRigidBodies(numRigidBodies, m_system, 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 (auto _ : state)
{
for (AZ::u32 i = 0; i < RigidBodyConstants::GameFramesToSimulate; i++)
{
auto start = AZStd::chrono::system_clock::now();
m_defaultScene->StartSimulation(DefaultTimeStep);
m_defaultScene->FinishSimulation();
//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
for (int i = 0; i < rigidBodies.size(); i++)
{
rigidBodies[i] = nullptr;
}
//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_defaultScene, 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<int>(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;
};
Physics::BoxShapeConfiguration boxShapeConfiguration = Physics::BoxShapeConfiguration(AZ::Vector3(RigidBodyConstants::RigidBodys::BoxSize));
Utils::GenerateColliderFuncPtr colliderGenerator = [&boxShapeConfiguration]([[maybe_unused]] int idx) -> Physics::ShapeConfiguration*
{
return &boxShapeConfiguration;
};
//spawn the rigid bodies
AZStd::vector<AZStd::unique_ptr<Physics::RigidBody>> rigidBodies = Utils::CreateRigidBodies(numRigidBodies, m_system, 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<double> tickTimes;
tickTimes.reserve(RigidBodyConstants::GameFramesToSimulate);
for (auto _ : state)
{
for (AZ::u32 i = 0; i < RigidBodyConstants::GameFramesToSimulate; i++)
{
auto start = AZStd::chrono::system_clock::now();
m_defaultScene->StartSimulation(DefaultTimeStep);
m_defaultScene->FinishSimulation();
//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();
for (int i = 0; i < rigidBodies.size(); i++)
{
rigidBodies[i] = nullptr;
}
//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
, public Physics::WorldEventHandler
{
public:
void SetUp(const ::benchmark::State& state) override
{
PhysXRigidbodyBenchmarkFixture::SetUp(state);
m_collisionBeginCount = 0;
m_collisionPersistCount = 0;
m_collisionEndCount = 0;
}
// Physics::WorldEventHandler Interface ---------
void OnTriggerEnter([[maybe_unused]] const Physics::TriggerEvent& triggerEvent) override {}
void OnTriggerExit([[maybe_unused]] const Physics::TriggerEvent& triggerEvent) override {}
//Recreate collision handling to be similar to what was in 1.x Action game
//queue an event to each party involved in the collision
void OnCollisionBegin(const Physics::CollisionEvent& collisionEvent) override
{
Physics::CollisionNotificationBus::QueueEvent(collisionEvent.m_body1->GetEntityId(), &Physics::CollisionNotifications::OnCollisionBegin, collisionEvent);
m_collisionBeginCount++;
}
void OnCollisionPersist(const Physics::CollisionEvent& collisionEvent) override
{
Physics::CollisionNotificationBus::QueueEvent(collisionEvent.m_body1->GetEntityId(), &Physics::CollisionNotifications::OnCollisionPersist, collisionEvent);
m_collisionPersistCount++;
}
void OnCollisionEnd(const Physics::CollisionEvent& collisionEvent) override
{
Physics::CollisionNotificationBus::QueueEvent(collisionEvent.m_body1->GetEntityId(), &Physics::CollisionNotifications::OnCollisionEnd, collisionEvent);
m_collisionEndCount++;
}
// Physics::WorldEventHandler Interface ---------
protected:
//attach this fixture as an event handler
Physics::WorldEventHandler* GetWorldEventHandler() override { return this; }
// Collision counters to track and report the number of events
int m_collisionBeginCount;
int m_collisionPersistCount;
int m_collisionEndCount;
};
//! 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_defaultScene, 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<int>(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 = [&rand](int idx) -> AZ::EntityId {
return AZ::EntityId(static_cast<AZ::u64>(idx) + RigidBodyConstants::RigidBodys::RigidBodyEntityIdStart);
};
Physics::BoxShapeConfiguration boxShapeConfiguration = Physics::BoxShapeConfiguration(AZ::Vector3(RigidBodyConstants::RigidBodys::BoxSize));
Utils::GenerateColliderFuncPtr colliderGenerator = [&boxShapeConfiguration]([[maybe_unused]] int idx) -> Physics::ShapeConfiguration*
{
return &boxShapeConfiguration;
};
//spawn the rigid bodies
AZStd::vector<AZStd::unique_ptr<Physics::RigidBody>> rigidBodies = Utils::CreateRigidBodies(numRigidBodies, m_system, m_defaultScene,
RigidBodyConstants::CCDEnabled, &colliderGenerator, &posGenerator, &oriGenerator, &massGenerator);
//create the collision handlers
AZStd::vector<Utils::SimulatedBodyCollisionHandler> collisionHandlers;
int numberCollisionHandlers = 0;
if (static_cast<int>(state.range(1)) == RigidBodyConstants::BenchmarkSettings::AllCollisionHanders)
{
numberCollisionHandlers = numRigidBodies;
}
else if (static_cast<int>(state.range(1)) == RigidBodyConstants::BenchmarkSettings::HalfCollisionHandlers)
{
numberCollisionHandlers = numRigidBodies / 2;
}
collisionHandlers.reserve(numberCollisionHandlers);
for (int i = 0; i < numberCollisionHandlers; i++)
{
AZ::u64 idOffset = rand.Getu64Random() % numRigidBodies; //randomly select the offset.
collisionHandlers.emplace_back(Utils::SimulatedBodyCollisionHandler(AZ::EntityId(RigidBodyConstants::RigidBodys::RigidBodyEntityIdStart + idOffset)));
}
//setup the sub tick tracker
Utils::PrePostSimulationEventHandler subTickTracker;
subTickTracker.Start(m_defaultScene);
//setup the frame timer tracker
AZStd::vector<double> tickTimes;
tickTimes.reserve(RigidBodyConstants::GameFramesToSimulate);
for (auto _ : state)
{
for (AZ::u32 i = 0; i < RigidBodyConstants::GameFramesToSimulate; i++)
{
auto start = AZStd::chrono::system_clock::now();
m_defaultScene->StartSimulation(DefaultTimeStep);
m_defaultScene->FinishSimulation();
//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();
for (int i = 0; i < rigidBodies.size(); i++)
{
rigidBodies[i] = nullptr;
}
//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<double>(m_collisionBeginCount);
state.counters["Collisions-Persist"] = static_cast<double>(m_collisionPersistCount);
state.counters["Collisions-End"] = static_cast<double>(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