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:
Mike Balfour
2022-01-31 12:54:16 -06:00
committed by GitHub
parent 25878f9487
commit b975111a93
14 changed files with 551 additions and 112 deletions
+104 -44
View File
@@ -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);