You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h

214 lines
12 KiB
C++

/*
* 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
*
*/
#pragma once
#include <benchmark/benchmark.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/chrono/types.h>
#include <AzCore/std/numeric.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
#include <AzFramework/Physics/SystemBus.h>
#include <AzFramework/Physics/Common/PhysicsEvents.h>
namespace AzPhysics
{
class Scene;
struct RigidBody;
}
namespace PhysX::Benchmarks
{
namespace Types
{
//! Alias for the lists of frame and sub tick timing data.
using TimeList = AZStd::vector<double>;
//! Chrono duration to represent milliseconds as a double.
using double_milliseconds = AZStd::chrono::duration<double, AZStd::chrono::milliseconds::period>;
} // namespace Types
namespace Utils
{
//! Function pointer to allow Shape configuration customization rigid bodies created with Utils::CreateRigidBodies. int param is the id of the rigid body being created (values 0-N, where N=number requested to be created)
using GenerateColliderFuncPtr = AZStd::function<AZStd::shared_ptr<Physics::ShapeConfiguration>(int)>;
//! Function pointer to allow spawn position customization rigid bodies created with Utils::CreateRigidBodies. int param is the id of the rigid body being created (values 0-N, where N=number requested to be created)
using GenerateSpawnPositionFuncPtr = AZStd::function<const AZ::Vector3(int)>;
//! Function pointer to allow spawn orientation customization rigid bodies created with Utils::CreateRigidBodies. int param is the id of the rigid body being created (values 0-N, where N=number requested to be created)
using GenerateSpawnOrientationFuncPtr = AZStd::function<const AZ::Quaternion(int)>;
//! Function pointer to allow setting the mass of the rigid bodies created with Utils::CreateRigidBodies. int param is the id of the rigid body being created (values 0-N, where N=number requested to be created)
using GenerateMassFuncPtr = AZStd::function<float(int)>;
//! Function pointer to allow setting an Entity Id on the rigid bodies created with Utils::CreateRigidBodies. int param is the id of the rigid body being created (values 0-N, where N=number requested to be created)
using GenerateEntityIdFuncPtr = AZStd::function<AZ::EntityId(int)>;
//! Helper function to create the required number of rigid bodies and spawn them in the provided world.
//! @param numRigidBodies The number of bodies to spawn.
//! @param world World where the rigid bodies will be spawned into.
//! @param enableCCD Flag to enable|disable Continuous Collision Detection (CCD).
//! @param genColliderFuncPtr [optional] Function pointer to allow caller to pick the collider object Default is a box sized at 1m.
//! @param genSpawnPosFuncPtr [optional] Function pointer to allow caller to pick the spawn position.
//! @param genSpawnOriFuncPtr [optional] Function pointer to allow caller to pick the spawn orientation.
//! @param genMassFuncPtr [optional] Function pointer to allow caller to pick the mass of the object.
//! @param genEntityIdFuncPtr [optional] Function pointer to allow caller to define the entity id of the object.
AzPhysics::SimulatedBodyHandleList CreateRigidBodies(int numRigidBodies,
AzPhysics::Scene* scene, bool enableCCD,
GenerateColliderFuncPtr* genColliderFuncPtr = nullptr, GenerateSpawnPositionFuncPtr* genSpawnPosFuncPtr = nullptr,
GenerateSpawnOrientationFuncPtr* genSpawnOriFuncPtr = nullptr, GenerateMassFuncPtr* genMassFuncPtr = nullptr,
GenerateEntityIdFuncPtr* genEntityIdFuncPtr = nullptr
);
//! Helper that takes a list of SimulatedBodyHandles to Rigid Bodies and return RigidBody pointers
AZStd::vector<AzPhysics::RigidBody*> GetRigidBodiesFromHandles(AzPhysics::Scene* scene, const AzPhysics::SimulatedBodyHandleList& handlesList);
//! Object that when given a World will listen to the Pre / Post physics updates.
//! Will time the duration between Pre and Post events in milliseconds. Used for running Benchmarks
struct PrePostSimulationEventHandler
{
PrePostSimulationEventHandler();
//! Begin tracking the physics tick times.
//! This will clear any previous recorded times
//! @param world The physics world to track tick times
void Start(AzPhysics::Scene* scene);
//! Stop tracking the physics tick times.
void Stop();
Types::TimeList& GetSubTickTimes() { return m_subTickTimes; }
private:
void PreTick();
void PostTick();
//! list of each sub tick execution time in milliseconds
Types::TimeList m_subTickTimes;
AZStd::chrono::system_clock::time_point m_tickStart;
AzPhysics::SceneEvents::OnSceneSimulationStartHandler m_sceneStartSimHandler;
AzPhysics::SceneEvents::OnSceneSimulationFinishHandler m_sceneFinishSimHandler;
};
//! This will calculate and return each requested percentiles of the data set provided.
//! @param percentiles List of percentiles to return. values must be between 0.0 - 1.0.
//! @param values Data set to find the percentile in. This will be modified by being partially sorted by the nth_element algorithm.
//! @return Each percentile requested, ordered to match percentiles vector.
template<class T>
AZStd::vector<T> GetPercentiles(const AZStd::vector<double>& percentiles, AZStd::vector<T>& values)
{
AZStd::vector<T> results;
if (values.empty() || percentiles.empty())
{
return results;
}
results.reserve(percentiles.size());
for (double percentile : percentiles)
{
//ensure the percentile is between 0.0 and 1.0
const double testEpsilon = 0.001;
AZ::ClampIfCloseMag(percentile, 0.0, testEpsilon);
AZ::ClampIfCloseMag(percentile, 1.0, testEpsilon);
size_t idx = aznumeric_cast<size_t>(std::round(percentile * (values.size() - 1)));
std::nth_element(values.begin(), values.begin() + idx, values.end());
results.emplace_back(values[idx]);
}
return results;
}
//! Returned from GetStandardDeviationAndMean.
//! Contains the calculated mean and standard deviation of the given data set.
struct StandardDeviationAndMeanResults
{
double m_mean = 0.0;
double m_standardDeviation = 0.0;
};
//! This will return standard deviation and mean of the data set provided.
//! @param values Data set to use.
//! @return A structure that contains the standard deviation and mean of the data set.
template<class T>
StandardDeviationAndMeanResults GetStandardDeviationAndMean(const AZStd::vector<T>& values)
{
StandardDeviationAndMeanResults res;
if (values.empty())
{
return res;
}
//sum all values
T sum = AZStd::accumulate(values.begin(), values.end(), T());
//calculate mean
res.m_mean = aznumeric_cast<double>(sum) / values.size();
//calculate standard deviation
auto calcStdev = [=](T sum, T calcStdev) -> T {
const T deviation = calcStdev - aznumeric_cast<T>(res.m_mean);
return sum + (deviation * deviation);
};
res.m_standardDeviation = aznumeric_cast<double>(AZStd::accumulate(values.begin(), values.end(), T(), calcStdev));
res.m_standardDeviation = std::sqrt(res.m_standardDeviation / values.size());
return res;
}
//! Helper to add frame and sub tick timing percentile stats to the benchmark.
//! Adds the counters under the labels
//! 'Frame-P{x}', 'Frame-Fastest', 'Frame-Slowest', 'SubTick-P{x}', 'SubTick-Fastest', 'SubTick-Slowest',
//! where {x} is each requested percentile.
//! @param state The benchmark::State object to add the counters.
//! @param frameTimes List of execution times (milliseconds) of each game frame ran in the benchmark.
//! @param subTickTimes List of execution times (milliseconds) of each physX sub tick ran in the benchmark.
//! @param requestedPercentiles List of percentiles to calculate. Percentile values are 0.0 - 1.0. Defaults P50 (0.5), P90 (0.9), P99 (0.99).
void ReportFramePercentileCounters(benchmark::State& state, Types::TimeList& frameTimes, Types::TimeList& subTickTimes, const AZStd::vector<double>& requestedPercentiles = { {0.5, 0.9, 0.99} });
//! Helper function to add P50, P90, P99, fastest and slowest execution times from the provided list.
//! Adds the counters under the labels 'P{x}', 'Fastest', 'Slowest',
//! where {x} is each requested percentile.
//! @param state The benchmark state to add the counters.
//! @param executionTimes List of execution times to use to calculate the percentiles.
//! @param requestedPercentiles List of percentiles to calculate. Percentile values are 0.0 - 1.0. Defaults P50 (0.5), P90 (0.9), P99 (0.99).
template<typename T>
void ReportPercentiles(benchmark::State& state, AZStd::vector<T>& executionTimes, const AZStd::vector<double>& requestedPercentiles = { {0.5, 0.9, 0.99} })
{
AZStd::vector<T> percentiles = GetPercentiles(requestedPercentiles, executionTimes);
int minRange = static_cast<int>(AZStd::min(requestedPercentiles.size(), executionTimes.size()));
for (int i = 0; i < minRange; i++)
{
AZStd::string label = AZStd::string::format("P%d", static_cast<int>(requestedPercentiles[i] * 100.0));
state.counters[label.c_str()] = aznumeric_cast<double>(percentiles[i]);
}
std::nth_element(percentiles.begin(), percentiles.begin(), percentiles.end());
state.counters["Fastest"] = !percentiles.empty() ? aznumeric_cast<double>(percentiles.front()) : -1.0;
std::nth_element(percentiles.begin(), percentiles.begin() + (percentiles.size() - 1), percentiles.end());
state.counters["Slowest"] = !percentiles.empty() ? aznumeric_cast<double>(percentiles.back()) : -1.0;
}
//! Helper to add frame and sub tick timing standard deviation and mean stats to the benchmark.
//! Adds the counters under the labels 'Frame-StDev', 'Frame-Mean', 'SubTick-StDev', and 'SubTick-Mean'.
//! @param state The benchmark::State object to add the counters.
//! @param frameTimes List of execution times (milliseconds) of each game frame ran in the benchmark.
//! @param subTickTimes List of execution times (milliseconds) of each physX sub tick ran in the benchmark.
void ReportFrameStandardDeviationAndMeanCounters(benchmark::State& state, const Types::TimeList& frameTimes, const Types::TimeList& subTickTimes);
//! Helper to add frame and sub tick timing standard deviation and mean stats to the benchmark
//! Adds the counters under the labels 'StDev' and 'Mean'.
//! @param state The benchmark::State object to add the counters.
//! @param executionTimes List of execution times to use in the calculation.
template<typename T>
void ReportStandardDeviationAndMeanCounters(benchmark::State& state, const AZStd::vector<T>& executionTimes)
{
StandardDeviationAndMeanResults stdivMeanFrameTimes = GetStandardDeviationAndMean(executionTimes);
state.counters["Mean"] = std::ceil(stdivMeanFrameTimes.m_mean * 1000.0) / 1000.0; //truncate to 3 decimal places
state.counters["StDev"] = std::ceil(stdivMeanFrameTimes.m_standardDeviation * 1000.0) / 1000.0;
}
} // namespace Utils
} // namespace PhysX::Benchmarks