Add benchmarks and unit tests for GetSurfacePoints*. (#7216)
* Add benchmarks and unit tests for GetSurfacePoints*. The benchmarks are very enlightening - the existing implementation of GetSurfacePointsFromRegion (and GetSurfacePointsFromList) is currently measurably *slower* than just calling GetSurfacePoints() many times in a loop. This is due to all of the extra allocation overhead that's currently happening with the way these data structures are built. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Small syntax improvement Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Small update to the benchmark to use filtered results. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Removed accidental extra include. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>
This commit is contained in:
@@ -102,4 +102,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
ly_add_googletest(
|
||||
NAME Gem::SurfaceData.Tests
|
||||
)
|
||||
ly_add_googlebenchmark(
|
||||
NAME Gem::SurfaceData.Benchmarks
|
||||
TARGET Gem::SurfaceData.Tests
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
#include <AzCore/Math/Vector2.h>
|
||||
#include <AzCore/std/containers/span.h>
|
||||
#include <SurfaceData/SurfaceDataTypes.h>
|
||||
|
||||
namespace SurfaceData
|
||||
@@ -41,6 +42,12 @@ namespace SurfaceData
|
||||
virtual void GetSurfacePointsFromRegion(const AZ::Aabb& inRegion, const AZ::Vector2 stepSize, const SurfaceTagVector& desiredTags,
|
||||
SurfacePointLists& surfacePointLists) const = 0;
|
||||
|
||||
// Get all surface points for every passed-in input position. Only the XY dimensions of each position are used.
|
||||
virtual void GetSurfacePointsFromList(
|
||||
AZStd::span<const AZ::Vector3> inPositions,
|
||||
const SurfaceTagVector& desiredTags,
|
||||
SurfacePointLists& surfacePointLists) const = 0;
|
||||
|
||||
virtual SurfaceDataRegistryHandle RegisterSurfaceDataProvider(const SurfaceDataRegistryEntry& entry) = 0;
|
||||
virtual void UnregisterSurfaceDataProvider(const SurfaceDataRegistryHandle& handle) = 0;
|
||||
virtual void UpdateSurfaceDataProvider(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry) = 0;
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzCore/std/hash.h>
|
||||
|
||||
#include <AzCore/Casting/lossy_cast.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Component/ComponentApplication.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/std/hash.h>
|
||||
|
||||
#include <LmbrCentral/Shape/ShapeComponentBus.h>
|
||||
#include <SurfaceData/SurfaceDataSystemRequestBus.h>
|
||||
#include <AzCore/Casting/lossy_cast.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
@@ -23,23 +24,6 @@ namespace UnitTest
|
||||
: public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
AZ::ComponentApplication m_app;
|
||||
AZ::Entity* m_systemEntity = nullptr;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
AZ::ComponentApplication::Descriptor appDesc;
|
||||
appDesc.m_memoryBlocksByteSize = 128 * 1024 * 1024;
|
||||
m_systemEntity = m_app.Create(appDesc);
|
||||
m_app.AddEntity(m_systemEntity);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_app.Destroy();
|
||||
m_systemEntity = nullptr;
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> CreateEntity()
|
||||
{
|
||||
return AZStd::make_unique<AZ::Entity>();
|
||||
@@ -57,14 +41,12 @@ namespace UnitTest
|
||||
template <typename Component, typename Configuration>
|
||||
AZ::Component* CreateComponent(AZ::Entity* entity, const Configuration& config)
|
||||
{
|
||||
m_app.RegisterComponentDescriptor(Component::CreateDescriptor());
|
||||
return entity->CreateComponent<Component>(config);
|
||||
}
|
||||
|
||||
template <typename Component>
|
||||
AZ::Component* CreateComponent(AZ::Entity* entity)
|
||||
{
|
||||
m_app.RegisterComponentDescriptor(Component::CreateDescriptor());
|
||||
return entity->CreateComponent<Component>();
|
||||
}
|
||||
};
|
||||
@@ -129,6 +111,29 @@ namespace UnitTest
|
||||
}
|
||||
};
|
||||
|
||||
// Mock out a generic Physics Collider Component, which is a required dependency for adding a SurfaceDataColliderComponent.
|
||||
struct MockPhysicsColliderComponent : public AZ::Component
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(MockPhysicsColliderComponent, "{4F7C36DE-6475-4E0A-96A7-BFAF21C07C95}", AZ::Component);
|
||||
|
||||
void Activate() override
|
||||
{
|
||||
}
|
||||
void Deactivate() override
|
||||
{
|
||||
}
|
||||
|
||||
static void Reflect(AZ::ReflectContext* reflect)
|
||||
{
|
||||
AZ_UNUSED(reflect);
|
||||
}
|
||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
|
||||
{
|
||||
provided.push_back(AZ_CRC("PhysXColliderService", 0x4ff43f7c));
|
||||
}
|
||||
};
|
||||
|
||||
struct MockTransformHandler
|
||||
: public AZ::TransformBus::Handler
|
||||
{
|
||||
@@ -204,6 +209,13 @@ namespace UnitTest
|
||||
{
|
||||
}
|
||||
|
||||
void GetSurfacePointsFromList(
|
||||
[[maybe_unused]] AZStd::span<const AZ::Vector3> inPositions,
|
||||
[[maybe_unused]] const SurfaceData::SurfaceTagVector& desiredTags,
|
||||
[[maybe_unused]] SurfaceData::SurfacePointLists& surfacePointLists) const override
|
||||
{
|
||||
}
|
||||
|
||||
SurfaceData::SurfaceDataRegistryHandle RegisterSurfaceDataProvider(const SurfaceData::SurfaceDataRegistryEntry& entry) override
|
||||
{
|
||||
return RegisterEntry(entry, m_providers);
|
||||
|
||||
@@ -240,15 +240,16 @@ namespace SurfaceData
|
||||
point.m_entityId = GetEntityId();
|
||||
point.m_position = hitPosition;
|
||||
point.m_normal = hitNormal;
|
||||
AddMaxValueForMasks(point.m_masks, m_configuration.m_providerTags, 1.0f);
|
||||
for (auto& tag : m_configuration.m_providerTags)
|
||||
{
|
||||
point.m_masks[tag] = 1.0f;
|
||||
}
|
||||
surfacePointList.push_back(point);
|
||||
}
|
||||
}
|
||||
|
||||
void SurfaceDataColliderComponent::ModifySurfacePoints(SurfacePointList& surfacePointList) const
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Entity);
|
||||
|
||||
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
|
||||
|
||||
if (m_colliderBounds.IsValid() && !m_configuration.m_modifierTags.empty())
|
||||
|
||||
@@ -143,8 +143,6 @@ namespace SurfaceData
|
||||
|
||||
void SurfaceDataShapeComponent::GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Entity);
|
||||
|
||||
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
|
||||
|
||||
if (m_shapeBoundsIsValid)
|
||||
@@ -160,7 +158,10 @@ namespace SurfaceData
|
||||
point.m_entityId = GetEntityId();
|
||||
point.m_position = rayOrigin + intersectionDistance * rayDirection;
|
||||
point.m_normal = AZ::Vector3::CreateAxisZ();
|
||||
AddMaxValueForMasks(point.m_masks, m_configuration.m_providerTags, 1.0f);
|
||||
for (auto& tag : m_configuration.m_providerTags)
|
||||
{
|
||||
point.m_masks[tag] = 1.0f;
|
||||
}
|
||||
surfacePointList.push_back(point);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,8 +181,6 @@ namespace SurfaceData
|
||||
|
||||
void SurfaceDataSystemComponent::GetSurfacePoints(const AZ::Vector3& inPosition, const SurfaceTagVector& desiredTags, SurfacePointList& surfacePointList) const
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Entity);
|
||||
|
||||
const bool hasDesiredTags = HasValidTags(desiredTags);
|
||||
const bool hasModifierTags = hasDesiredTags && HasMatchingTags(desiredTags, m_registeredModifierTags);
|
||||
|
||||
@@ -228,42 +226,47 @@ namespace SurfaceData
|
||||
void SurfaceDataSystemComponent::GetSurfacePointsFromRegion(const AZ::Aabb& inRegion, const AZ::Vector2 stepSize,
|
||||
const SurfaceTagVector& desiredTags, SurfacePointLists& surfacePointLists) const
|
||||
{
|
||||
AZStd::lock_guard<decltype(m_registrationMutex)> registrationLock(m_registrationMutex);
|
||||
|
||||
const size_t totalQueryPositions = aznumeric_cast<size_t>(ceil(inRegion.GetXExtent() / stepSize.GetX())) *
|
||||
aznumeric_cast<size_t>(ceil(inRegion.GetYExtent() / stepSize.GetY()));
|
||||
|
||||
AZStd::vector<AZ::Vector3> inPositions;
|
||||
inPositions.reserve(totalQueryPositions);
|
||||
|
||||
surfacePointLists.clear();
|
||||
surfacePointLists.reserve(totalQueryPositions);
|
||||
|
||||
// Initialize our list-per-position list with every input position to query from the region.
|
||||
// This is inclusive on the min sides of inRegion, and exclusive on the max sides.
|
||||
for (float y = inRegion.GetMin().GetY(); y < inRegion.GetMax().GetY(); y += stepSize.GetY())
|
||||
{
|
||||
for (float x = inRegion.GetMin().GetX(); x < inRegion.GetMax().GetX(); x += stepSize.GetX())
|
||||
{
|
||||
inPositions.emplace_back(AZ::Vector3(x, y, AZ::Constants::FloatMax));
|
||||
surfacePointLists.emplace_back(SurfaceData::SurfacePointList{});
|
||||
inPositions.emplace_back(x, y, AZ::Constants::FloatMax);
|
||||
}
|
||||
}
|
||||
|
||||
GetSurfacePointsFromList(inPositions, desiredTags, surfacePointLists);
|
||||
}
|
||||
|
||||
void SurfaceDataSystemComponent::GetSurfacePointsFromList(
|
||||
AZStd::span<const AZ::Vector3> inPositions, const SurfaceTagVector& desiredTags, SurfacePointLists& surfacePointLists) const
|
||||
{
|
||||
AZStd::lock_guard<decltype(m_registrationMutex)> registrationLock(m_registrationMutex);
|
||||
|
||||
const size_t totalQueryPositions = inPositions.size();
|
||||
|
||||
surfacePointLists.clear();
|
||||
surfacePointLists.resize(totalQueryPositions);
|
||||
|
||||
const bool hasDesiredTags = HasValidTags(desiredTags);
|
||||
const bool hasModifierTags = hasDesiredTags && HasMatchingTags(desiredTags, m_registeredModifierTags);
|
||||
|
||||
// Loop through each data provider, and query all the points for each one. This allows us to check the tags and the overall
|
||||
// AABB bounds just once per provider, instead of once per point. It also allows for an eventual optimization in which we could send
|
||||
// the list of points directly into each SurfaceDataProvider.
|
||||
// AABB bounds just once per provider, instead of once per point. It also allows for an eventual optimization in which we could
|
||||
// send the list of points directly into each SurfaceDataProvider.
|
||||
for (const auto& entryPair : m_registeredSurfaceDataProviders)
|
||||
{
|
||||
const SurfaceDataRegistryEntry& entry = entryPair.second;
|
||||
bool alwaysApplies = !entry.m_bounds.IsValid();
|
||||
|
||||
if ((!hasDesiredTags || hasModifierTags || HasMatchingTags(desiredTags, entry.m_tags)) &&
|
||||
( alwaysApplies || AabbOverlaps2D(entry.m_bounds, inRegion) )
|
||||
)
|
||||
if (!hasDesiredTags || hasModifierTags || HasMatchingTags(desiredTags, entry.m_tags))
|
||||
{
|
||||
for (size_t index = 0; index < totalQueryPositions; index++)
|
||||
{
|
||||
@@ -288,18 +291,16 @@ namespace SurfaceData
|
||||
const SurfaceDataRegistryEntry& entry = entryPair.second;
|
||||
bool alwaysApplies = !entry.m_bounds.IsValid();
|
||||
|
||||
if (alwaysApplies || AabbOverlaps2D(entry.m_bounds, inRegion))
|
||||
for (size_t index = 0; index < totalQueryPositions; index++)
|
||||
{
|
||||
for (size_t index = 0; index < totalQueryPositions; index++)
|
||||
const auto& inPosition = inPositions[index];
|
||||
SurfacePointList& surfacePointList = surfacePointLists[index];
|
||||
if (!surfacePointList.empty())
|
||||
{
|
||||
const auto& inPosition = inPositions[index];
|
||||
SurfacePointList& surfacePointList = surfacePointLists[index];
|
||||
if (!surfacePointList.empty())
|
||||
if (alwaysApplies || AabbContains2D(entry.m_bounds, inPosition))
|
||||
{
|
||||
if (alwaysApplies || AabbContains2D(entry.m_bounds, inPosition))
|
||||
{
|
||||
SurfaceDataModifierRequestBus::Event(entryPair.first, &SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, surfacePointList);
|
||||
}
|
||||
SurfaceDataModifierRequestBus::Event(
|
||||
entryPair.first, &SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, surfacePointList);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,6 +319,8 @@ namespace SurfaceData
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SurfaceDataSystemComponent::CombineSortAndFilterNeighboringPoints(SurfacePointList& sourcePointList, bool hasDesiredTags, const SurfaceTagVector& desiredTags) const
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Entity);
|
||||
|
||||
@@ -42,6 +42,10 @@ namespace SurfaceData
|
||||
void GetSurfacePointsFromRegion(
|
||||
const AZ::Aabb& inRegion, const AZ::Vector2 stepSize, const SurfaceTagVector& desiredTags,
|
||||
SurfacePointLists& surfacePointListPerPosition) const override;
|
||||
void GetSurfacePointsFromList(
|
||||
AZStd::span<const AZ::Vector3> inPositions,
|
||||
const SurfaceTagVector& desiredTags,
|
||||
SurfacePointLists& surfacePointLists) const override;
|
||||
|
||||
SurfaceDataRegistryHandle RegisterSurfaceDataProvider(const SurfaceDataRegistryEntry& entry) override;
|
||||
void UnregisterSurfaceDataProvider(const SurfaceDataRegistryHandle& handle) override;
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* 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 <AzTest/AzTest.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Debug/Profiler.h>
|
||||
#include <AzCore/Memory/PoolAllocator.h>
|
||||
#include <AzCore/Math/Vector2.h>
|
||||
#include <AzCore/std/containers/array.h>
|
||||
#include <AzCore/std/containers/span.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
#include <LmbrCentral/Shape/BoxShapeComponentBus.h>
|
||||
#include <LmbrCentral/Shape/CylinderShapeComponentBus.h>
|
||||
#include <SurfaceDataSystemComponent.h>
|
||||
#include <Components/SurfaceDataShapeComponent.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class SurfaceDataBenchmark : public ::benchmark::Fixture
|
||||
{
|
||||
public:
|
||||
void internalSetUp()
|
||||
{
|
||||
m_surfaceDataSystemEntity = AZStd::make_unique<AZ::Entity>();
|
||||
m_surfaceDataSystemEntity->CreateComponent<SurfaceData::SurfaceDataSystemComponent>();
|
||||
m_surfaceDataSystemEntity->Init();
|
||||
m_surfaceDataSystemEntity->Activate();
|
||||
}
|
||||
|
||||
void internalTearDown()
|
||||
{
|
||||
m_surfaceDataSystemEntity.reset();
|
||||
}
|
||||
|
||||
// Create an entity with a Transform component and a SurfaceDataShape component at the given position with the given tags.
|
||||
AZStd::unique_ptr<AZ::Entity> CreateBenchmarkEntity(
|
||||
AZ::Vector3 worldPos, AZStd::span<const char* const> providerTags, AZStd::span<const char* const> modifierTags)
|
||||
{
|
||||
AZStd::unique_ptr<AZ::Entity> entity = AZStd::make_unique<AZ::Entity>();
|
||||
|
||||
auto transform = entity->CreateComponent<AzFramework::TransformComponent>();
|
||||
transform->SetLocalTM(AZ::Transform::CreateTranslation(worldPos));
|
||||
transform->SetWorldTM(AZ::Transform::CreateTranslation(worldPos));
|
||||
|
||||
SurfaceData::SurfaceDataShapeConfig surfaceConfig;
|
||||
for (auto& providerTag : providerTags)
|
||||
{
|
||||
surfaceConfig.m_providerTags.push_back(SurfaceData::SurfaceTag(providerTag));
|
||||
}
|
||||
for (auto& modifierTag : modifierTags)
|
||||
{
|
||||
surfaceConfig.m_modifierTags.push_back(SurfaceData::SurfaceTag(modifierTag));
|
||||
}
|
||||
entity->CreateComponent<SurfaceData::SurfaceDataShapeComponent>(surfaceConfig);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/* Create a set of shape surfaces in the world that we can use for benchmarking.
|
||||
Each shape is centered in XY and is the XY size of the world, but with different Z heights and placements.
|
||||
There are two boxes and one cylinder, layered like this:
|
||||
|
||||
Top:
|
||||
---
|
||||
|O| <- two boxes of equal XY size with a cylinder face-up in the center
|
||||
---
|
||||
|
||||
Side:
|
||||
|-----------|
|
||||
| |<- entity 3, box that contains the other shapes
|
||||
| |-------| | <- entity 2, cylinder inside entity 3 and intersecting entity 1
|
||||
| | | |
|
||||
|-----------|<- entity 1, thin box
|
||||
| |-------| |
|
||||
| |
|
||||
|-----------|
|
||||
|
||||
This will give us either 2 or 3 generated surface points at every query point. The entity 1 surface will get the entity 2 and 3
|
||||
modifier tags added to it. The entity 2 surface will get the entity 3 modifier tag added to it. The entity 3 surface won't get
|
||||
modified.
|
||||
*/
|
||||
AZStd::vector<AZStd::unique_ptr<AZ::Entity>> CreateBenchmarkEntities(float worldSize)
|
||||
{
|
||||
AZStd::vector<AZStd::unique_ptr<AZ::Entity>> testEntities;
|
||||
float halfWorldSize = worldSize / 2.0f;
|
||||
|
||||
// Create a large flat box with 1 provider tag.
|
||||
AZStd::unique_ptr<AZ::Entity> surface1 = CreateBenchmarkEntity(
|
||||
AZ::Vector3(halfWorldSize, halfWorldSize, 10.0f), AZStd::array{ "surface1" }, {});
|
||||
{
|
||||
LmbrCentral::BoxShapeConfig boxConfig(AZ::Vector3(worldSize, worldSize, 1.0f));
|
||||
auto shapeComponent = surface1->CreateComponent(LmbrCentral::BoxShapeComponentTypeId);
|
||||
shapeComponent->SetConfiguration(boxConfig);
|
||||
|
||||
surface1->Init();
|
||||
surface1->Activate();
|
||||
}
|
||||
testEntities.push_back(AZStd::move(surface1));
|
||||
|
||||
// Create a large cylinder with 1 provider tag and 1 modifier tag.
|
||||
AZStd::unique_ptr<AZ::Entity> surface2 = CreateBenchmarkEntity(
|
||||
AZ::Vector3(halfWorldSize, halfWorldSize, 20.0f), AZStd::array{ "surface2" }, AZStd::array{ "modifier2" });
|
||||
{
|
||||
LmbrCentral::CylinderShapeConfig cylinderConfig;
|
||||
cylinderConfig.m_height = 30.0f;
|
||||
cylinderConfig.m_radius = halfWorldSize;
|
||||
auto shapeComponent = surface2->CreateComponent(LmbrCentral::CylinderShapeComponentTypeId);
|
||||
shapeComponent->SetConfiguration(cylinderConfig);
|
||||
|
||||
surface2->Init();
|
||||
surface2->Activate();
|
||||
}
|
||||
testEntities.push_back(AZStd::move(surface2));
|
||||
|
||||
// Create a large box with 1 provider tag and 1 modifier tag.
|
||||
AZStd::unique_ptr<AZ::Entity> surface3 = CreateBenchmarkEntity(
|
||||
AZ::Vector3(halfWorldSize, halfWorldSize, 30.0f), AZStd::array{ "surface3" }, AZStd::array{ "modifier3" });
|
||||
{
|
||||
LmbrCentral::BoxShapeConfig boxConfig(AZ::Vector3(worldSize, worldSize, 100.0f));
|
||||
auto shapeComponent = surface3->CreateComponent(LmbrCentral::BoxShapeComponentTypeId);
|
||||
shapeComponent->SetConfiguration(boxConfig);
|
||||
|
||||
surface3->Init();
|
||||
surface3->Activate();
|
||||
}
|
||||
testEntities.push_back(AZStd::move(surface3));
|
||||
|
||||
return testEntities;
|
||||
}
|
||||
|
||||
SurfaceData::SurfaceTagVector CreateBenchmarkTagFilterList()
|
||||
{
|
||||
SurfaceData::SurfaceTagVector tagFilterList;
|
||||
tagFilterList.emplace_back("surface1");
|
||||
tagFilterList.emplace_back("surface2");
|
||||
tagFilterList.emplace_back("surface3");
|
||||
tagFilterList.emplace_back("modifier2");
|
||||
tagFilterList.emplace_back("modifier3");
|
||||
return tagFilterList;
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetUp([[maybe_unused]] const benchmark::State& state) override
|
||||
{
|
||||
internalSetUp();
|
||||
}
|
||||
void SetUp([[maybe_unused]] benchmark::State& state) override
|
||||
{
|
||||
internalSetUp();
|
||||
}
|
||||
|
||||
void TearDown([[maybe_unused]] const benchmark::State& state) override
|
||||
{
|
||||
internalTearDown();
|
||||
}
|
||||
void TearDown([[maybe_unused]] benchmark::State& state) override
|
||||
{
|
||||
internalTearDown();
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> m_surfaceDataSystemEntity;
|
||||
};
|
||||
|
||||
BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_GetSurfacePoints)(benchmark::State& state)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Entity);
|
||||
|
||||
// Create our benchmark world
|
||||
const float worldSize = aznumeric_cast<float>(state.range(0));
|
||||
AZStd::vector<AZStd::unique_ptr<AZ::Entity>> benchmarkEntities = CreateBenchmarkEntities(worldSize);
|
||||
SurfaceData::SurfaceTagVector filterTags = CreateBenchmarkTagFilterList();
|
||||
|
||||
// Query every point in our world at 1 meter intervals.
|
||||
for (auto _ : state)
|
||||
{
|
||||
// This is declared outside the loop so that the list of points doesn't fully reallocate on every query.
|
||||
SurfaceData::SurfacePointList points;
|
||||
|
||||
for (float y = 0.0f; y < worldSize; y += 1.0f)
|
||||
{
|
||||
for (float x = 0.0f; x < worldSize; x += 1.0f)
|
||||
{
|
||||
AZ::Vector3 queryPosition(x, y, 0.0f);
|
||||
points.clear();
|
||||
|
||||
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
|
||||
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, filterTags, points);
|
||||
benchmark::DoNotOptimize(points);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_GetSurfacePointsFromRegion)(benchmark::State& state)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Entity);
|
||||
|
||||
// Create our benchmark world
|
||||
float worldSize = aznumeric_cast<float>(state.range(0));
|
||||
AZStd::vector<AZStd::unique_ptr<AZ::Entity>> benchmarkEntities = CreateBenchmarkEntities(worldSize);
|
||||
SurfaceData::SurfaceTagVector filterTags = CreateBenchmarkTagFilterList();
|
||||
|
||||
// Query every point in our world at 1 meter intervals.
|
||||
for (auto _ : state)
|
||||
{
|
||||
SurfaceData::SurfacePointLists points;
|
||||
|
||||
AZ::Aabb inRegion = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(worldSize));
|
||||
AZ::Vector2 stepSize(1.0f);
|
||||
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
|
||||
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion, inRegion, stepSize, filterTags,
|
||||
points);
|
||||
benchmark::DoNotOptimize(points);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_GetSurfacePointsFromList)(benchmark::State& state)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Entity);
|
||||
|
||||
// Create our benchmark world
|
||||
const float worldSize = aznumeric_cast<float>(state.range(0));
|
||||
const int64_t worldSizeInt = state.range(0);
|
||||
AZStd::vector<AZStd::unique_ptr<AZ::Entity>> benchmarkEntities = CreateBenchmarkEntities(worldSize);
|
||||
SurfaceData::SurfaceTagVector filterTags = CreateBenchmarkTagFilterList();
|
||||
|
||||
// Query every point in our world at 1 meter intervals.
|
||||
for (auto _ : state)
|
||||
{
|
||||
AZStd::vector<AZ::Vector3> queryPositions;
|
||||
queryPositions.reserve(worldSizeInt * worldSizeInt);
|
||||
|
||||
for (float y = 0.0f; y < worldSize; y += 1.0f)
|
||||
{
|
||||
for (float x = 0.0f; x < worldSize; x += 1.0f)
|
||||
{
|
||||
queryPositions.emplace_back(x, y, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
SurfaceData::SurfacePointLists points;
|
||||
|
||||
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
|
||||
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromList, queryPositions, filterTags, points);
|
||||
benchmark::DoNotOptimize(points);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_GetSurfacePoints)
|
||||
->Arg( 1024 )
|
||||
->Arg( 2048 )
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_GetSurfacePointsFromRegion)
|
||||
->Arg( 1024 )
|
||||
->Arg( 2048 )
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_GetSurfacePointsFromList)
|
||||
->Arg( 1024 )
|
||||
->Arg( 2048 )
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzCore/Math/MathUtils.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Component/ComponentApplication.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
|
||||
#include <Source/Components/SurfaceDataColliderComponent.h>
|
||||
@@ -23,23 +22,6 @@
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
// Mock out a generic Physics Collider Component, which is a required dependency for adding a SurfaceDataColliderComponent.
|
||||
struct MockPhysicsColliderComponent
|
||||
: public AZ::Component
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(MockPhysicsColliderComponent, "{4F7C36DE-6475-4E0A-96A7-BFAF21C07C95}", AZ::Component);
|
||||
|
||||
void Activate() override {}
|
||||
void Deactivate() override {}
|
||||
|
||||
static void Reflect(AZ::ReflectContext* reflect) { AZ_UNUSED(reflect); }
|
||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
|
||||
{
|
||||
provided.push_back(AZ_CRC("PhysXColliderService", 0x4ff43f7c));
|
||||
}
|
||||
};
|
||||
|
||||
class MockPhysicsWorldBusProvider
|
||||
: public AzPhysics::SimulatedBodyComponentRequestsBus::Handler
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
#include <AzCore/Component/ComponentApplication.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Math/Random.h>
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
@@ -22,6 +21,7 @@
|
||||
#include <SurfaceData/SurfaceDataModifierRequestBus.h>
|
||||
#include <SurfaceData/SurfaceTag.h>
|
||||
#include <SurfaceData/Utility/SurfaceDataUtility.h>
|
||||
#include <Tests/SurfaceDataTestFixtures.h>
|
||||
|
||||
// Simple class for mocking out a surface provider, so that we can control exactly what points we expect to query in our tests.
|
||||
// This can be used to either provide a surface or modify a surface.
|
||||
@@ -175,61 +175,33 @@ class MockSurfaceProvider
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
TEST(SurfaceDataTest, ComponentsWithComponentApplication)
|
||||
{
|
||||
AZ::ComponentApplication::Descriptor appDesc;
|
||||
appDesc.m_memoryBlocksByteSize = 10 * 1024 * 1024;
|
||||
appDesc.m_recordingMode = AZ::Debug::AllocationRecords::RECORD_FULL;
|
||||
appDesc.m_stackRecordLevels = 20;
|
||||
AZ::Entity* testSystemEntity = new AZ::Entity();
|
||||
testSystemEntity->CreateComponent<SurfaceData::SurfaceDataSystemComponent>();
|
||||
|
||||
AZ::ComponentApplication app;
|
||||
AZ::Entity* systemEntity = app.Create(appDesc);
|
||||
ASSERT_TRUE(systemEntity != nullptr);
|
||||
app.RegisterComponentDescriptor(SurfaceData::SurfaceDataSystemComponent::CreateDescriptor());
|
||||
systemEntity->CreateComponent<SurfaceData::SurfaceDataSystemComponent>();
|
||||
|
||||
systemEntity->Init();
|
||||
systemEntity->Activate();
|
||||
|
||||
app.Destroy();
|
||||
ASSERT_TRUE(true);
|
||||
testSystemEntity->Init();
|
||||
testSystemEntity->Activate();
|
||||
EXPECT_EQ(testSystemEntity->GetState(), AZ::Entity::State::Active);
|
||||
testSystemEntity->Deactivate();
|
||||
delete testSystemEntity;
|
||||
}
|
||||
|
||||
class SurfaceDataTestApp
|
||||
: public ::testing::Test
|
||||
{
|
||||
public:
|
||||
SurfaceDataTestApp()
|
||||
: m_application()
|
||||
, m_systemEntity(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
AZ::ComponentApplication::Descriptor appDesc;
|
||||
appDesc.m_memoryBlocksByteSize = 50 * 1024 * 1024;
|
||||
appDesc.m_recordingMode = AZ::Debug::AllocationRecords::RECORD_FULL;
|
||||
appDesc.m_stackRecordLevels = 20;
|
||||
|
||||
AZ::ComponentApplication::StartupParameters appStartup;
|
||||
appStartup.m_createStaticModulesCallback =
|
||||
[](AZStd::vector<AZ::Module*>& modules)
|
||||
{
|
||||
modules.emplace_back(new SurfaceData::SurfaceDataModule);
|
||||
};
|
||||
|
||||
m_systemEntity = m_application.Create(appDesc, appStartup);
|
||||
m_systemEntity->Init();
|
||||
m_systemEntity->Activate();
|
||||
m_surfaceDataSystemEntity = AZStd::make_unique<AZ::Entity>();
|
||||
m_surfaceDataSystemEntity->CreateComponent<SurfaceData::SurfaceDataSystemComponent>();
|
||||
m_surfaceDataSystemEntity->Init();
|
||||
m_surfaceDataSystemEntity->Activate();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_application.Destroy();
|
||||
m_surfaceDataSystemEntity.reset();
|
||||
}
|
||||
|
||||
bool ValidateRegionListSize(AZ::Aabb bounds, AZ::Vector2 stepSize, const SurfaceData::SurfacePointLists& outputLists)
|
||||
@@ -240,15 +212,43 @@ public:
|
||||
return (outputLists.size() == aznumeric_cast<size_t>(ceil(bounds.GetXExtent() * stepSize.GetX()) * ceil(bounds.GetYExtent() * stepSize.GetY())));
|
||||
}
|
||||
|
||||
void CompareSurfacePointListWithGetSurfacePoints(
|
||||
SurfaceData::SurfacePointLists surfacePointLists, const SurfaceData::SurfaceTagVector& testTags)
|
||||
{
|
||||
for (auto& pointList : surfacePointLists)
|
||||
{
|
||||
AZ::Vector3 queryPosition(pointList[0].m_position.GetX(), pointList[0].m_position.GetY(), 16.0f);
|
||||
SurfaceData::SurfacePointList singleQueryPointList;
|
||||
|
||||
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
|
||||
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, testTags, singleQueryPointList);
|
||||
|
||||
// Verify the two point lists are the same size, then verify that each point in each list is equal.
|
||||
ASSERT_EQ(pointList.size(), singleQueryPointList.size());
|
||||
for (size_t index = 0; index < pointList.size(); index++)
|
||||
{
|
||||
SurfaceData::SurfacePoint& point1 = pointList[index];
|
||||
SurfaceData::SurfacePoint& point2 = singleQueryPointList[index];
|
||||
|
||||
EXPECT_EQ(point1.m_entityId, point2.m_entityId);
|
||||
EXPECT_EQ(point1.m_position, point2.m_position);
|
||||
EXPECT_EQ(point1.m_normal, point2.m_normal);
|
||||
ASSERT_EQ(point1.m_masks.size(), point2.m_masks.size());
|
||||
for (auto& mask : point1.m_masks)
|
||||
{
|
||||
EXPECT_EQ(mask.second, point2.m_masks[mask.first]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AZ::ComponentApplication m_application;
|
||||
AZ::Entity* m_systemEntity;
|
||||
|
||||
// Test Surface Data tags that we can use for testing query functionality
|
||||
const AZ::Crc32 m_testSurface1Crc = AZ::Crc32("test_surface1");
|
||||
const AZ::Crc32 m_testSurface2Crc = AZ::Crc32("test_surface2");
|
||||
const AZ::Crc32 m_testSurfaceNoMatchCrc = AZ::Crc32("test_surface_no_match");
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> m_surfaceDataSystemEntity;
|
||||
};
|
||||
|
||||
TEST_F(SurfaceDataTestApp, SurfaceData_TestRegisteredTags)
|
||||
@@ -706,4 +706,64 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_DissimilarPoi
|
||||
}
|
||||
}
|
||||
|
||||
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
|
||||
TEST_F(SurfaceDataTestApp, SurfaceData_VerifyGetSurfacePointsFromRegionAndGetSurfacePointsMatch)
|
||||
{
|
||||
// This ensures that both GetSurfacePointsFromRegion and GetSurfacePoints produce the same results.
|
||||
|
||||
// Create a mock Surface Provider that covers from (0, 0) - (8, 8) in space.
|
||||
// It defines points spaced 0.25 apart, with heights of 0 and 4, and with the tags "test_surface1" and "test_surface2".
|
||||
// (We're creating points spaced more densely than we'll query just to verify that we only get back the queried points)
|
||||
SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
|
||||
MockSurfaceProvider mockProvider(
|
||||
MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags, AZ::Vector3(0.0f), AZ::Vector3(8.0f),
|
||||
AZ::Vector3(0.25f, 0.25f, 4.0f));
|
||||
|
||||
// Query for all the surface points from (0, 0, 16) - (4, 4, 16) with a step size of 1.
|
||||
SurfaceData::SurfacePointLists availablePointsPerPosition;
|
||||
AZ::Vector2 stepSize(1.0f, 1.0f);
|
||||
AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f, 0.0f, 16.0f), AZ::Vector3(4.0f, 4.0f, 16.0f));
|
||||
|
||||
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
|
||||
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion, regionBounds, stepSize, providerTags,
|
||||
availablePointsPerPosition);
|
||||
|
||||
EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition));
|
||||
|
||||
// For each point entry returned from GetSurfacePointsFromRegion, call GetSurfacePoints and verify the results match.
|
||||
CompareSurfacePointListWithGetSurfacePoints(availablePointsPerPosition, providerTags);
|
||||
}
|
||||
|
||||
TEST_F(SurfaceDataTestApp, SurfaceData_VerifyGetSurfacePointsFromListAndGetSurfacePointsMatch)
|
||||
{
|
||||
// This ensures that both GetSurfacePointsFromList and GetSurfacePoints produce the same results.
|
||||
|
||||
// Create a mock Surface Provider that covers from (0, 0) - (8, 8) in space.
|
||||
// It defines points spaced 0.25 apart, with heights of 0 and 4, and with the tags "test_surface1" and "test_surface2".
|
||||
// (We're creating points spaced more densely than we'll query just to verify that we only get back the queried points)
|
||||
SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
|
||||
MockSurfaceProvider mockProvider(
|
||||
MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags, AZ::Vector3(0.0f), AZ::Vector3(8.0f),
|
||||
AZ::Vector3(0.25f, 0.25f, 4.0f));
|
||||
|
||||
// Query for all the surface points from (0, 0, 16) - (4, 4, 16) with a step size of 1.
|
||||
SurfaceData::SurfacePointLists availablePointsPerPosition;
|
||||
AZStd::vector<AZ::Vector3> queryPositions;
|
||||
for (float y = 0.0f; y < 4.0f; y += 1.0f)
|
||||
{
|
||||
for (float x = 0.0f; x < 4.0f; x += 1.0f)
|
||||
{
|
||||
queryPositions.push_back(AZ::Vector3(x, y, 16.0f));
|
||||
}
|
||||
}
|
||||
|
||||
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
|
||||
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromList,
|
||||
queryPositions, providerTags, availablePointsPerPosition);
|
||||
|
||||
EXPECT_EQ(availablePointsPerPosition.size(), 16);
|
||||
|
||||
// For each point entry returned from GetSurfacePointsFromList, call GetSurfacePoints and verify the results match.
|
||||
CompareSurfacePointListWithGetSurfacePoints(availablePointsPerPosition, providerTags);
|
||||
}
|
||||
// This uses custom test / benchmark hooks so that we can load LmbrCentral and use Shape components in our unit tests and benchmarks.
|
||||
AZ_UNIT_TEST_HOOK(new UnitTest::SurfaceDataTestEnvironment, UnitTest::SurfaceDataBenchmarkEnvironment);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <Tests/SurfaceDataTestFixtures.h>
|
||||
#include <SurfaceData/Tests/SurfaceDataTestMocks.h>
|
||||
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
#include <SurfaceDataSystemComponent.h>
|
||||
#include <Components/SurfaceDataColliderComponent.h>
|
||||
#include <Components/SurfaceDataShapeComponent.h>
|
||||
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
void SurfaceDataTestEnvironment::AddGemsAndComponents()
|
||||
{
|
||||
AddDynamicModulePaths({ "LmbrCentral" });
|
||||
|
||||
AddComponentDescriptors({
|
||||
AzFramework::TransformComponent::CreateDescriptor(),
|
||||
|
||||
SurfaceData::SurfaceDataSystemComponent::CreateDescriptor(),
|
||||
SurfaceData::SurfaceDataColliderComponent::CreateDescriptor(),
|
||||
SurfaceData::SurfaceDataShapeComponent::CreateDescriptor(),
|
||||
|
||||
MockPhysicsColliderComponent::CreateDescriptor()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 <AzTest/GemTestEnvironment.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
// SurfaceData needs to use the GemTestEnvironment to load the LmbrCentral Gem so that Shape components can be used
|
||||
// in the unit tests and benchmarks.
|
||||
class SurfaceDataTestEnvironment
|
||||
: public AZ::Test::GemTestEnvironment
|
||||
{
|
||||
public:
|
||||
void AddGemsAndComponents() override;
|
||||
};
|
||||
|
||||
#ifdef HAVE_BENCHMARK
|
||||
//! The Benchmark environment is used for one time setup and tear down of shared resources
|
||||
class SurfaceDataBenchmarkEnvironment
|
||||
: public AZ::Test::BenchmarkEnvironmentBase
|
||||
, public SurfaceDataTestEnvironment
|
||||
|
||||
{
|
||||
protected:
|
||||
void SetUpBenchmark() override
|
||||
{
|
||||
SetupEnvironment();
|
||||
}
|
||||
|
||||
void TearDownBenchmark() override
|
||||
{
|
||||
TeardownEnvironment();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -8,8 +8,11 @@
|
||||
|
||||
set(FILES
|
||||
Include/SurfaceData/Tests/SurfaceDataTestMocks.h
|
||||
Tests/SurfaceDataBenchmarks.cpp
|
||||
Tests/SurfaceDataColliderComponentTest.cpp
|
||||
Tests/SurfaceDataTest.cpp
|
||||
Tests/SurfaceDataTestFixtures.cpp
|
||||
Tests/SurfaceDataTestFixtures.h
|
||||
Source/SurfaceDataModule.cpp
|
||||
Source/SurfaceDataModule.h
|
||||
)
|
||||
|
||||
@@ -345,6 +345,13 @@ namespace UnitTest
|
||||
{
|
||||
}
|
||||
|
||||
void GetSurfacePointsFromList(
|
||||
[[maybe_unused]] AZStd::span<const AZ::Vector3> inPositions,
|
||||
[[maybe_unused]] const SurfaceData::SurfaceTagVector& desiredTags,
|
||||
[[maybe_unused]] SurfaceData::SurfacePointLists& surfacePointLists) const override
|
||||
{
|
||||
}
|
||||
|
||||
SurfaceData::SurfaceDataRegistryHandle RegisterSurfaceDataProvider([[maybe_unused]] const SurfaceData::SurfaceDataRegistryEntry& entry) override
|
||||
{
|
||||
++m_count;
|
||||
|
||||
Reference in New Issue
Block a user