From 2b43ad8029f53acd9edee4b49adfafdf9b2e0a01 Mon Sep 17 00:00:00 2001 From: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> Date: Wed, 19 Jan 2022 17:10:37 -0600 Subject: [PATCH] FastNoise GetValues() specialization (#7009) * Add comparison operator for use from unit tests. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * First version of FastNoise benchmarks. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Simplified unit tests and added initial benchmarks. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Add GetValue vs GetValues unit test. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Moved Gradient test code into helper files for use from FastNoise. Also added benchmarks for each type of FastNoise so that we can have some comparative values handy. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Specialization for GetValues(). Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> --- Gems/FastNoise/Code/CMakeLists.txt | 46 ++-- .../Source/FastNoiseGradientComponent.cpp | 46 +++- .../Code/Source/FastNoiseGradientComponent.h | 3 + .../Code/Tests/FastNoiseBenchmarks.cpp | 116 ++++++++ .../Code/Tests/FastNoiseEditorTest.cpp | 44 +++ Gems/FastNoise/Code/Tests/FastNoiseTest.cpp | 254 ++++-------------- Gems/FastNoise/Code/Tests/FastNoiseTest.h | 60 +++++ .../Code/fastnoise_editor_tests_files.cmake | 13 + .../Code/fastnoise_tests_files.cmake | 1 + .../Code/Tests/GradientSignalBenchmarks.cpp | 211 ++------------- .../Tests/GradientSignalGetValuesTests.cpp | 76 ++---- .../Code/Tests/GradientSignalTestHelpers.cpp | 203 ++++++++++++++ .../Code/Tests/GradientSignalTestHelpers.h | 76 ++++++ .../gradientsignal_shared_tests_files.cmake | 2 + 14 files changed, 672 insertions(+), 479 deletions(-) create mode 100644 Gems/FastNoise/Code/Tests/FastNoiseBenchmarks.cpp create mode 100644 Gems/FastNoise/Code/Tests/FastNoiseEditorTest.cpp create mode 100644 Gems/FastNoise/Code/Tests/FastNoiseTest.h create mode 100644 Gems/FastNoise/Code/fastnoise_editor_tests_files.cmake create mode 100644 Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.cpp create mode 100644 Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.h diff --git a/Gems/FastNoise/Code/CMakeLists.txt b/Gems/FastNoise/Code/CMakeLists.txt index 819592e018..d945db1665 100644 --- a/Gems/FastNoise/Code/CMakeLists.txt +++ b/Gems/FastNoise/Code/CMakeLists.txt @@ -104,7 +104,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) NAME FastNoise.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} NAMESPACE Gem FILES_CMAKE - fastnoise_tests_files.cmake + fastnoise_editor_tests_files.cmake COMPILE_DEFINITIONS PUBLIC FASTNOISE_EDITOR @@ -120,23 +120,31 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_googletest( NAME Gem::FastNoise.Editor.Tests ) - else() - ly_add_target( - NAME FastNoise.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} - NAMESPACE Gem - FILES_CMAKE - fastnoise_tests_files.cmake - INCLUDE_DIRECTORIES - PRIVATE - Tests - BUILD_DEPENDENCIES - PRIVATE - AZ::AzTest - Gem::FastNoise.Static - Gem::LmbrCentral - ) - ly_add_googletest( - NAME Gem::FastNoise.Tests - ) endif() + + ly_add_target( + NAME FastNoise.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Gem + FILES_CMAKE + fastnoise_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + BUILD_DEPENDENCIES + PRIVATE + AZ::AzTest + Gem::FastNoise.Static + Gem::GradientSignal + Gem::GradientSignal.Tests.Static + Gem::LmbrCentral + ) + ly_add_googletest( + NAME Gem::FastNoise.Tests + ) + + ly_add_googlebenchmark( + NAME Gem::FastNoise.Benchmarks + TARGET Gem::FastNoise.Tests + ) + endif() diff --git a/Gems/FastNoise/Code/Source/FastNoiseGradientComponent.cpp b/Gems/FastNoise/Code/Source/FastNoiseGradientComponent.cpp index 4cea2efcc8..ceeafc3af6 100644 --- a/Gems/FastNoise/Code/Source/FastNoiseGradientComponent.cpp +++ b/Gems/FastNoise/Code/Source/FastNoiseGradientComponent.cpp @@ -54,6 +54,21 @@ namespace FastNoiseGem return AZ::Edit::PropertyVisibility::Hide; } + bool FastNoiseGradientConfig::operator==(const FastNoiseGradientConfig& rhs) const + { + return (m_cellularDistanceFunction == rhs.m_cellularDistanceFunction) + && (m_cellularJitter == rhs.m_cellularJitter) + && (m_cellularReturnType == rhs.m_cellularReturnType) + && (m_fractalType == rhs.m_fractalType) + && (m_frequency == rhs.m_frequency) + && (m_gain == rhs.m_gain) + && (m_interp == rhs.m_interp) + && (m_lacunarity == rhs.m_lacunarity) + && (m_noiseType == rhs.m_noiseType) + && (m_octaves == rhs.m_octaves) + && (m_seed == rhs.m_seed); + } + void FastNoiseGradientConfig::Reflect(AZ::ReflectContext* context) { if (auto serializeContext = azrtti_cast(context)) @@ -306,7 +321,7 @@ namespace FastNoiseGem float FastNoiseGradientComponent::GetValue(const GradientSignal::GradientSampleParams& sampleParams) const { - AZ::Vector3 uvw = sampleParams.m_position; + AZ::Vector3 uvw; bool wasPointRejected = false; { @@ -314,13 +329,34 @@ namespace FastNoiseGem m_gradientTransform.TransformPositionToUVW(sampleParams.m_position, uvw, wasPointRejected); } - if (!wasPointRejected) + // Generator returns a range between [-1, 1], map that to [0, 1] + return wasPointRejected ? + 0.0f : + AZ::GetClamp((m_generator.GetNoise(uvw.GetX(), uvw.GetY(), uvw.GetZ()) + 1.0f) / 2.0f, 0.0f, 1.0f); + } + + void FastNoiseGradientComponent::GetValues(AZStd::span positions, AZStd::span outValues) const + { + if (positions.size() != outValues.size()) { - // Generator returns a range between [-1, 1], map that to [0, 1] - return AZ::GetClamp((m_generator.GetNoise(uvw.GetX(), uvw.GetY(), uvw.GetZ()) + 1.0f) / 2.0f, 0.0f, 1.0f); + AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size()); + return; } - return 0.0f; + AZStd::shared_lock lock(m_transformMutex); + AZ::Vector3 uvw; + + for (size_t index = 0; index < positions.size(); index++) + { + bool wasPointRejected = false; + + m_gradientTransform.TransformPositionToUVW(positions[index], uvw, wasPointRejected); + + // Generator returns a range between [-1, 1], map that to [0, 1] + outValues[index] = wasPointRejected ? + 0.0f : + AZ::GetClamp((m_generator.GetNoise(uvw.GetX(), uvw.GetY(), uvw.GetZ()) + 1.0f) / 2.0f, 0.0f, 1.0f); + } } template diff --git a/Gems/FastNoise/Code/Source/FastNoiseGradientComponent.h b/Gems/FastNoise/Code/Source/FastNoiseGradientComponent.h index dd19049ee6..29c42bfe1b 100644 --- a/Gems/FastNoise/Code/Source/FastNoiseGradientComponent.h +++ b/Gems/FastNoise/Code/Source/FastNoiseGradientComponent.h @@ -47,6 +47,8 @@ namespace FastNoiseGem AZ::u32 GetFrequencyParameterVisbility() const; AZ::u32 GetInterpParameterVisibility() const; + bool operator==(const FastNoiseGradientConfig& rhs) const; + int m_seed = 1; float m_frequency = 1.f; FastNoise::Interp m_interp = FastNoise::Interp::Quintic; @@ -90,6 +92,7 @@ namespace FastNoiseGem // GradientRequestBus overrides... float GetValue(const GradientSignal::GradientSampleParams& sampleParams) const override; + void GetValues(AZStd::span positions, AZStd::span outValues) const override; protected: FastNoiseGradientConfig m_configuration; diff --git a/Gems/FastNoise/Code/Tests/FastNoiseBenchmarks.cpp b/Gems/FastNoise/Code/Tests/FastNoiseBenchmarks.cpp new file mode 100644 index 0000000000..1e438db112 --- /dev/null +++ b/Gems/FastNoise/Code/Tests/FastNoiseBenchmarks.cpp @@ -0,0 +1,116 @@ +/* + * 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 +#include + +namespace UnitTest +{ + class FastNoiseGetValues + : public ::benchmark::Fixture + { + public: + void RunGetValueOrGetValuesBenchmark(benchmark::State& state, FastNoise::NoiseType noiseType) + { + AZ::Entity* noiseEntity = aznew AZ::Entity("noise_entity"); + ASSERT_TRUE(noiseEntity != nullptr); + noiseEntity->CreateComponent(); + noiseEntity->CreateComponent(LmbrCentral::BoxShapeComponentTypeId); + noiseEntity->CreateComponent(); + + // Set up a FastNoise component with the requested noise type + FastNoiseGem::FastNoiseGradientConfig cfg; + cfg.m_frequency = 0.01f; + cfg.m_noiseType = noiseType; + noiseEntity->CreateComponent(cfg); + + noiseEntity->Init(); + noiseEntity->Activate(); + + UnitTest::GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, noiseEntity->GetId()); + } + + }; + + BENCHMARK_DEFINE_F(FastNoiseGetValues, BM_FastNoiseGradient_Value)(benchmark::State& state) + { + RunGetValueOrGetValuesBenchmark(state, FastNoise::NoiseType::Value); + } + + BENCHMARK_DEFINE_F(FastNoiseGetValues, BM_FastNoiseGradient_ValueFractal)(benchmark::State& state) + { + RunGetValueOrGetValuesBenchmark(state, FastNoise::NoiseType::ValueFractal); + } + + BENCHMARK_DEFINE_F(FastNoiseGetValues, BM_FastNoiseGradient_Perlin)(benchmark::State& state) + { + RunGetValueOrGetValuesBenchmark(state, FastNoise::NoiseType::Perlin); + } + + BENCHMARK_DEFINE_F(FastNoiseGetValues, BM_FastNoiseGradient_PerlinFractal)(benchmark::State& state) + { + RunGetValueOrGetValuesBenchmark(state, FastNoise::NoiseType::PerlinFractal); + } + + BENCHMARK_DEFINE_F(FastNoiseGetValues, BM_FastNoiseGradient_Simplex)(benchmark::State& state) + { + RunGetValueOrGetValuesBenchmark(state, FastNoise::NoiseType::Simplex); + } + + BENCHMARK_DEFINE_F(FastNoiseGetValues, BM_FastNoiseGradient_SimplexFractal)(benchmark::State& state) + { + RunGetValueOrGetValuesBenchmark(state, FastNoise::NoiseType::SimplexFractal); + } + + BENCHMARK_DEFINE_F(FastNoiseGetValues, BM_FastNoiseGradient_Cellular)(benchmark::State& state) + { + RunGetValueOrGetValuesBenchmark(state, FastNoise::NoiseType::Cellular); + } + + BENCHMARK_DEFINE_F(FastNoiseGetValues, BM_FastNoiseGradient_WhiteNoise)(benchmark::State& state) + { + RunGetValueOrGetValuesBenchmark(state, FastNoise::NoiseType::WhiteNoise); + } + + BENCHMARK_DEFINE_F(FastNoiseGetValues, BM_FastNoiseGradient_Cubic)(benchmark::State& state) + { + RunGetValueOrGetValuesBenchmark(state, FastNoise::NoiseType::Cubic); + } + + BENCHMARK_DEFINE_F(FastNoiseGetValues, BM_FastNoiseGradient_CubicFractal)(benchmark::State& state) + { + RunGetValueOrGetValuesBenchmark(state, FastNoise::NoiseType::CubicFractal); + } + + GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(FastNoiseGetValues, BM_FastNoiseGradient_Value); + GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(FastNoiseGetValues, BM_FastNoiseGradient_ValueFractal); + GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(FastNoiseGetValues, BM_FastNoiseGradient_Perlin); + GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(FastNoiseGetValues, BM_FastNoiseGradient_PerlinFractal); + GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(FastNoiseGetValues, BM_FastNoiseGradient_Simplex); + GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(FastNoiseGetValues, BM_FastNoiseGradient_SimplexFractal); + GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(FastNoiseGetValues, BM_FastNoiseGradient_Cellular); + GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(FastNoiseGetValues, BM_FastNoiseGradient_WhiteNoise); + GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(FastNoiseGetValues, BM_FastNoiseGradient_Cubic); + GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(FastNoiseGetValues, BM_FastNoiseGradient_CubicFractal); + +#endif +} + + + diff --git a/Gems/FastNoise/Code/Tests/FastNoiseEditorTest.cpp b/Gems/FastNoise/Code/Tests/FastNoiseEditorTest.cpp new file mode 100644 index 0000000000..e30c6c01e0 --- /dev/null +++ b/Gems/FastNoise/Code/Tests/FastNoiseEditorTest.cpp @@ -0,0 +1,44 @@ +/* + * 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 + +#include +#include +#include +#include + + +class FastNoiseEditorTestApp : public ::testing::Test +{ +}; + +TEST_F(FastNoiseEditorTestApp, FastNoise_EditorCreateGameEntity) +{ + AZStd::unique_ptr noiseEntity(aznew AZ::Entity("editor_noise_entity")); + ASSERT_TRUE(noiseEntity != nullptr); + + FastNoiseGem::EditorFastNoiseGradientComponent editor; + auto* editorBase = static_cast(&editor); + editorBase->BuildGameEntity(noiseEntity.get()); + + // the new game entity's FastNoise component should look like the default one + FastNoiseGem::FastNoiseGradientConfig defaultConfig; + FastNoiseGem::FastNoiseGradientConfig gameComponentConfig; + + FastNoiseGem::FastNoiseGradientComponent* noiseComp = noiseEntity->FindComponent(); + ASSERT_TRUE(noiseComp != nullptr); + + // Change a value in the gameComponentConfig just to verify that it got overwritten instead of simply matching the default. + gameComponentConfig.m_seed++; + noiseComp->WriteOutConfig(&gameComponentConfig); + ASSERT_EQ(defaultConfig, gameComponentConfig); +} + +// This uses custom test / benchmark hooks so that we can load LmbrCentral and GradientSignal Gems. +AZ_UNIT_TEST_HOOK(new UnitTest::FastNoiseTestEnvironment, UnitTest::FastNoiseBenchmarkEnvironment); diff --git a/Gems/FastNoise/Code/Tests/FastNoiseTest.cpp b/Gems/FastNoise/Code/Tests/FastNoiseTest.cpp index f0d5a9d1bd..1ac5e8ddca 100644 --- a/Gems/FastNoise/Code/Tests/FastNoiseTest.cpp +++ b/Gems/FastNoise/Code/Tests/FastNoiseTest.cpp @@ -10,199 +10,61 @@ #include #include -#include -#include -#include #include +#include #include -#include -#include +#include #include -#include +#include +#include +#include #include #include #include +#include +#include -class MockGradientTransformComponent - : public AZ::Component - , private GradientSignal::GradientTransformRequestBus::Handler - , private GradientSignal::GradientTransformModifierRequestBus::Handler +class FastNoiseTest : public ::testing::Test { -public: - AZ_COMPONENT(MockGradientTransformComponent, "{464CF47B-7E10-4E1B-BD06-79BD2AC91399}"); - - static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) - { - services.push_back(AZ_CRC("GradientTransformService", 0x8c8c5ecc)); - } - static void Reflect([[maybe_unused]] AZ::ReflectContext* context) {} - - MockGradientTransformComponent() = default; - ~MockGradientTransformComponent() = default; - - // AZ::Component interface - void Activate() override {} - void Deactivate() override {} - - //////////////////////////////////////////////////////////////////////////// - //// GradientTransformRequestBus - const GradientSignal::GradientTransform& GetGradientTransform() const override - { - return m_gradientTransform; - } - - ////////////////////////////////////////////////////////////////////////// - // GradientTransformModifierRequestBus - bool GetAllowReference() const override { return false; } - void SetAllowReference([[maybe_unused]] bool value) override {} - - AZ::EntityId GetShapeReference() const override { return AZ::EntityId(); } - void SetShapeReference([[maybe_unused]] AZ::EntityId shapeReference) override {} - - bool GetOverrideBounds() const override { return false; } - void SetOverrideBounds([[maybe_unused]] bool value) override {} - - AZ::Vector3 GetBounds() const override { return AZ::Vector3(); } - void SetBounds([[maybe_unused]] AZ::Vector3 bounds) override {} - - GradientSignal::TransformType GetTransformType() const override { return static_cast(0); } - void SetTransformType([[maybe_unused]] GradientSignal::TransformType type) override {} - - bool GetOverrideTranslate() const override { return false; } - void SetOverrideTranslate([[maybe_unused]] bool value) override {} - - AZ::Vector3 GetTranslate() const override { return AZ::Vector3(); } - void SetTranslate([[maybe_unused]] AZ::Vector3 translate) override {} - - bool GetOverrideRotate() const override { return false; } - void SetOverrideRotate([[maybe_unused]] bool value) override {} - - AZ::Vector3 GetRotate() const override { return AZ::Vector3(); } - void SetRotate([[maybe_unused]] AZ::Vector3 rotate) override {} - - bool GetOverrideScale() const override { return false; } - void SetOverrideScale([[maybe_unused]] bool value) override {} - - AZ::Vector3 GetScale() const override { return AZ::Vector3(); } - void SetScale([[maybe_unused]] AZ::Vector3 scale) override {} - - float GetFrequencyZoom() const override { return false; } - void SetFrequencyZoom([[maybe_unused]] float frequencyZoom) override {} - - GradientSignal::WrappingType GetWrappingType() const override { return static_cast(0); } - void SetWrappingType([[maybe_unused]] GradientSignal::WrappingType type) override {} - - bool GetIs3D() const override { return false; } - void SetIs3D([[maybe_unused]] bool value) override {} - - bool GetAdvancedMode() const override { return false; } - void SetAdvancedMode([[maybe_unused]] bool value) override {} - - GradientSignal::GradientTransform m_gradientTransform; }; -TEST(FastNoiseTest, ComponentsWithComponentApplication) -{ - AZ::ComponentApplication::Descriptor appDesc; - appDesc.m_memoryBlocksByteSize = 10 * 1024 * 1024; - appDesc.m_recordingMode = AZ::Debug::AllocationRecords::RECORD_FULL; - appDesc.m_stackRecordLevels = 20; - - AZ::ComponentApplication app; - AZ::Entity* systemEntity = app.Create(appDesc); - ASSERT_TRUE(systemEntity != nullptr); - app.RegisterComponentDescriptor(FastNoiseGem::FastNoiseSystemComponent::CreateDescriptor()); - systemEntity->CreateComponent(); - - systemEntity->Init(); - systemEntity->Activate(); - - AZ::Entity* noiseEntity = aznew AZ::Entity("fastnoise_entity"); - noiseEntity->CreateComponent(); - app.AddEntity(noiseEntity); - - app.Destroy(); - ASSERT_TRUE(true); -} - -class FastNoiseTestApp - : public ::testing::Test -{ -public: - FastNoiseTestApp() - : m_application() - , m_systemEntity(nullptr) - { - } - - void SetUp() override - { - AZ::ComponentApplication::Descriptor appDesc; - appDesc.m_memoryBlocksByteSize = 10 * 1024 * 1024; - appDesc.m_recordingMode = AZ::Debug::AllocationRecords::RECORD_FULL; - appDesc.m_stackRecordLevels = 20; - - AZ::ComponentApplication::StartupParameters appStartup; - appStartup.m_createStaticModulesCallback = - [](AZStd::vector& modules) - { - modules.emplace_back(new FastNoiseGem::FastNoiseModule); - }; - - m_systemEntity = m_application.Create(appDesc, appStartup); - m_application.RegisterComponentDescriptor(MockGradientTransformComponent::CreateDescriptor()); - m_systemEntity->Init(); - m_systemEntity->Activate(); - } - - void TearDown() override - { - m_application.Destroy(); - } - - AZ::ComponentApplication m_application; - AZ::Entity* m_systemEntity; -}; - -////////////////////////////////////////////////////////////////////////// -// testing class to inspect protected data members in the FastNoiseGradientComponent -struct FastNoiseGradientComponentTester : public FastNoiseGem::FastNoiseGradientComponent -{ - const FastNoiseGem::FastNoiseGradientConfig& GetConfig() const { return m_configuration; } - - void AssertTrue(const FastNoiseGem::FastNoiseGradientConfig& cfg) - { - ASSERT_TRUE(m_configuration.m_cellularDistanceFunction == cfg.m_cellularDistanceFunction); - ASSERT_TRUE(m_configuration.m_cellularJitter == cfg.m_cellularJitter); - ASSERT_TRUE(m_configuration.m_cellularReturnType == cfg.m_cellularReturnType); - ASSERT_TRUE(m_configuration.m_fractalType == cfg.m_fractalType); - ASSERT_TRUE(m_configuration.m_frequency == cfg.m_frequency); - ASSERT_TRUE(m_configuration.m_gain == cfg.m_gain); - ASSERT_TRUE(m_configuration.m_interp == cfg.m_interp); - ASSERT_TRUE(m_configuration.m_lacunarity == cfg.m_lacunarity); - ASSERT_TRUE(m_configuration.m_noiseType == cfg.m_noiseType); - ASSERT_TRUE(m_configuration.m_octaves == cfg.m_octaves); - ASSERT_TRUE(m_configuration.m_seed == cfg.m_seed); - } -}; - -TEST_F(FastNoiseTestApp, FastNoise_Component) +TEST_F(FastNoiseTest, FastNoise_ComponentCreatesSuccessfully) { AZ::Entity* noiseEntity = aznew AZ::Entity("noise_entity"); ASSERT_TRUE(noiseEntity != nullptr); noiseEntity->CreateComponent(); - m_application.AddEntity(noiseEntity); FastNoiseGem::FastNoiseGradientComponent* noiseComp = noiseEntity->FindComponent(); ASSERT_TRUE(noiseComp != nullptr); } -TEST_F(FastNoiseTestApp, FastNoise_ComponentEbus) +TEST_F(FastNoiseTest, FastNoise_ComponentMatchesConfiguration) { AZ::Entity* noiseEntity = aznew AZ::Entity("noise_entity"); ASSERT_TRUE(noiseEntity != nullptr); + + FastNoiseGem::FastNoiseGradientConfig cfg; + FastNoiseGem::FastNoiseGradientConfig componentConfig; + + noiseEntity->CreateComponent(); + noiseEntity->CreateComponent(LmbrCentral::BoxShapeComponentTypeId); + noiseEntity->CreateComponent(); + noiseEntity->CreateComponent(cfg); + + FastNoiseGem::FastNoiseGradientComponent* noiseComp = noiseEntity->FindComponent(); + ASSERT_TRUE(noiseComp != nullptr); + noiseComp->WriteOutConfig(&componentConfig); + ASSERT_EQ(cfg, componentConfig); +} + +TEST_F(FastNoiseTest, FastNoise_ComponentEbusWorksSuccessfully) +{ + AZ::Entity* noiseEntity = aznew AZ::Entity("noise_entity"); + ASSERT_TRUE(noiseEntity != nullptr); + noiseEntity->CreateComponent(); + noiseEntity->CreateComponent(LmbrCentral::BoxShapeComponentTypeId); + noiseEntity->CreateComponent(); noiseEntity->CreateComponent(); - noiseEntity->CreateComponent(); noiseEntity->Init(); noiseEntity->Activate(); @@ -210,51 +72,39 @@ TEST_F(FastNoiseTestApp, FastNoise_ComponentEbus) GradientSignal::GradientSampleParams params; float sample = -1.0f; - GradientSignal::GradientRequestBus::EventResult(sample, noiseEntity->GetId(), &GradientSignal::GradientRequestBus::Events::GetValue, params); + GradientSignal::GradientRequestBus::EventResult(sample, noiseEntity->GetId(), + &GradientSignal::GradientRequestBus::Events::GetValue, params); ASSERT_TRUE(sample >= 0.0f); ASSERT_TRUE(sample <= 1.0f); } -TEST_F(FastNoiseTestApp, FastNoise_ComponentMatchesConfiguration) +TEST_F(FastNoiseTest, FastNoise_VerifyGetValueAndGetValuesMatch) { + const float shapeHalfBounds = 128.0f; + AZ::Entity* noiseEntity = aznew AZ::Entity("noise_entity"); ASSERT_TRUE(noiseEntity != nullptr); + noiseEntity->CreateComponent(); + noiseEntity->CreateComponent(); - AZ::SimpleLcgRandom rand(AZStd::GetTimeNowMicroSecond()); + // Create a Box Shape to map our gradient into + LmbrCentral::BoxShapeConfig boxConfig(AZ::Vector3(shapeHalfBounds * 2.0f)); + auto boxComponent = noiseEntity->CreateComponent(LmbrCentral::BoxShapeComponentTypeId); + boxComponent->SetConfiguration(boxConfig); + // Create a Fast Noise component with an adjusted frequency. (The defaults of Perlin noise with frequency=1.0 would cause us + // to always get back the same noise value) FastNoiseGem::FastNoiseGradientConfig cfg; - + cfg.m_frequency = 0.01f; noiseEntity->CreateComponent(cfg); - noiseEntity->CreateComponent(); - m_application.AddEntity(noiseEntity); + noiseEntity->Init(); + noiseEntity->Activate(); - FastNoiseGem::FastNoiseGradientComponent* noiseComp = noiseEntity->FindComponent(); - ASSERT_TRUE(noiseComp != nullptr); - reinterpret_cast(noiseComp)->AssertTrue(cfg); + // Create a gradient sampler and run through a series of points to see if they match expectations. + UnitTest::GradientSignalTestHelpers::CompareGetValueAndGetValues(noiseEntity->GetId(), shapeHalfBounds); } -#if FASTNOISE_EDITOR -#include - -TEST_F(FastNoiseTestApp, FastNoise_EditorCreateGameEntity) -{ - AZStd::unique_ptr noiseEntity(aznew AZ::Entity("editor_noise_entity")); - ASSERT_TRUE(noiseEntity != nullptr); - - FastNoiseGem::EditorFastNoiseGradientComponent editor; - auto* editorBase = static_cast(&editor); - editorBase->BuildGameEntity(noiseEntity.get()); - - // the new game entity's ocean component should look like the default one - FastNoiseGem::FastNoiseGradientConfig cfg; - - FastNoiseGem::FastNoiseGradientComponent* noiseComp = noiseEntity->FindComponent(); - ASSERT_TRUE(noiseComp != nullptr); - reinterpret_cast(noiseComp)->AssertTrue(cfg); -} - -#endif - -AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); +// This uses custom test / benchmark hooks so that we can load LmbrCentral and GradientSignal Gems. +AZ_UNIT_TEST_HOOK(new UnitTest::FastNoiseTestEnvironment, UnitTest::FastNoiseBenchmarkEnvironment); diff --git a/Gems/FastNoise/Code/Tests/FastNoiseTest.h b/Gems/FastNoise/Code/Tests/FastNoiseTest.h new file mode 100644 index 0000000000..71b10cfc68 --- /dev/null +++ b/Gems/FastNoise/Code/Tests/FastNoiseTest.h @@ -0,0 +1,60 @@ +/* + * 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 +#include +#include +#include + +namespace UnitTest +{ + // The FastNoise unit tests need to use the GemTestEnvironment to load the GradientSignal and LmbrCentral Gems so that + // GradientTransform components can be used in the unit tests and benchmarks. + class FastNoiseTestEnvironment + : public AZ::Test::GemTestEnvironment + { + public: + void AddGemsAndComponents() override + { + AddDynamicModulePaths({ "GradientSignal" }); + AddDynamicModulePaths({ "LmbrCentral" }); + + AddComponentDescriptors({ + AzFramework::TransformComponent::CreateDescriptor(), + FastNoiseGem::FastNoiseSystemComponent::CreateDescriptor(), + FastNoiseGem::FastNoiseGradientComponent::CreateDescriptor() + }); + + AddRequiredComponents({ FastNoiseGem::FastNoiseSystemComponent::TYPEINFO_Uuid() }); + } + }; + +#ifdef HAVE_BENCHMARK + //! The Benchmark environment is used for one time setup and tear down of shared resources + class FastNoiseBenchmarkEnvironment + : public AZ::Test::BenchmarkEnvironmentBase + , public FastNoiseTestEnvironment + + { + protected: + void SetUpBenchmark() override + { + SetupEnvironment(); + } + + void TearDownBenchmark() override + { + TeardownEnvironment(); + } + }; +#endif + + +} // namespace UnitTest + diff --git a/Gems/FastNoise/Code/fastnoise_editor_tests_files.cmake b/Gems/FastNoise/Code/fastnoise_editor_tests_files.cmake new file mode 100644 index 0000000000..685e2fb647 --- /dev/null +++ b/Gems/FastNoise/Code/fastnoise_editor_tests_files.cmake @@ -0,0 +1,13 @@ +# +# 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 +# +# + +set(FILES + Tests/FastNoiseEditorTest.cpp + Source/FastNoiseModule.h + Source/FastNoiseModule.cpp +) diff --git a/Gems/FastNoise/Code/fastnoise_tests_files.cmake b/Gems/FastNoise/Code/fastnoise_tests_files.cmake index 08386940f2..4669b0d446 100644 --- a/Gems/FastNoise/Code/fastnoise_tests_files.cmake +++ b/Gems/FastNoise/Code/fastnoise_tests_files.cmake @@ -7,6 +7,7 @@ # set(FILES + Tests/FastNoiseBenchmarks.cpp Tests/FastNoiseTest.cpp Source/FastNoiseModule.h Source/FastNoiseModule.cpp diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalBenchmarks.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalBenchmarks.cpp index bd4ccf5205..6ffb4a31c4 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalBenchmarks.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalBenchmarks.cpp @@ -9,6 +9,7 @@ #ifdef HAVE_BENCHMARK #include +#include #include #include @@ -21,220 +22,42 @@ namespace UnitTest class GradientGetValues : public GradientSignalBenchmarkFixture { public: - // We use an enum to list out the different types of GetValue() benchmarks to run so that way we can condense our test cases - // to just take the value in as a benchmark argument and switch on it. Otherwise, we would need to write a different benchmark - // function for each test case for each gradient. - enum GetValuePermutation : int64_t - { - EBUS_GET_VALUE, - EBUS_GET_VALUES, - SAMPLER_GET_VALUE, - SAMPLER_GET_VALUES, - }; - // Create an arbitrary size shape for creating our gradients for benchmark runs. const float TestShapeHalfBounds = 128.0f; - - void FillQueryPositions(AZStd::vector& positions, float height, float width) - { - size_t index = 0; - for (float y = 0.0f; y < height; y += 1.0f) - { - for (float x = 0.0f; x < width; x += 1.0f) - { - positions[index++] = AZ::Vector3(x, y, 0.0f); - } - } - } - - void RunEBusGetValueBenchmark(benchmark::State& state, const AZ::EntityId& gradientId, int64_t queryRange) - { - AZ_PROFILE_FUNCTION(Entity); - - GradientSignal::GradientSampleParams params; - - // Get the height and width ranges for querying from our benchmark parameters - const float height = aznumeric_cast(queryRange); - const float width = aznumeric_cast(queryRange); - - // Call GetValue() on the EBus for every height and width in our ranges. - for (auto _ : state) - { - for (float y = 0.0f; y < height; y += 1.0f) - { - for (float x = 0.0f; x < width; x += 1.0f) - { - float value = 0.0f; - params.m_position = AZ::Vector3(x, y, 0.0f); - GradientSignal::GradientRequestBus::EventResult( - value, gradientId, &GradientSignal::GradientRequestBus::Events::GetValue, params); - benchmark::DoNotOptimize(value); - } - } - } - } - - void RunEBusGetValuesBenchmark(benchmark::State& state, const AZ::EntityId& gradientId, int64_t queryRange) - { - AZ_PROFILE_FUNCTION(Entity); - - // Get the height and width ranges for querying from our benchmark parameters - float height = aznumeric_cast(queryRange); - float width = aznumeric_cast(queryRange); - int64_t totalQueryPoints = queryRange * queryRange; - - // Call GetValues() for every height and width in our ranges. - for (auto _ : state) - { - // Set up our vector of query positions. This is done inside the benchmark timing since we're counting the work to create - // each query position in the single GetValue() call benchmarks, and will make the timing more directly comparable. - AZStd::vector positions(totalQueryPoints); - FillQueryPositions(positions, height, width); - - // Query and get the results. - AZStd::vector results(totalQueryPoints); - GradientSignal::GradientRequestBus::Event( - gradientId, &GradientSignal::GradientRequestBus::Events::GetValues, positions, results); - } - } - - void RunSamplerGetValueBenchmark(benchmark::State& state, const AZ::EntityId& gradientId, int64_t queryRange) - { - AZ_PROFILE_FUNCTION(Entity); - - // Create a gradient sampler to use for querying our gradient. - GradientSignal::GradientSampler gradientSampler; - gradientSampler.m_gradientId = gradientId; - - // Get the height and width ranges for querying from our benchmark parameters - const float height = aznumeric_cast(queryRange); - const float width = aznumeric_cast(queryRange); - - // Call GetValue() through the GradientSampler for every height and width in our ranges. - for (auto _ : state) - { - for (float y = 0.0f; y < height; y += 1.0f) - { - for (float x = 0.0f; x < width; x += 1.0f) - { - GradientSignal::GradientSampleParams params; - params.m_position = AZ::Vector3(x, y, 0.0f); - float value = gradientSampler.GetValue(params); - benchmark::DoNotOptimize(value); - } - } - } - } - - void RunSamplerGetValuesBenchmark(benchmark::State& state, const AZ::EntityId& gradientId, int64_t queryRange) - { - AZ_PROFILE_FUNCTION(Entity); - - // Create a gradient sampler to use for querying our gradient. - GradientSignal::GradientSampler gradientSampler; - gradientSampler.m_gradientId = gradientId; - - // Get the height and width ranges for querying from our benchmark parameters - const float height = aznumeric_cast(queryRange); - const float width = aznumeric_cast(queryRange); - const int64_t totalQueryPoints = queryRange * queryRange; - - // Call GetValues() through the GradientSampler for every height and width in our ranges. - for (auto _ : state) - { - // Set up our vector of query positions. This is done inside the benchmark timing since we're counting the work to create - // each query position in the single GetValue() call benchmarks, and will make the timing more directly comparable. - AZStd::vector positions(totalQueryPoints); - FillQueryPositions(positions, height, width); - - // Query and get the results. - AZStd::vector results(totalQueryPoints); - gradientSampler.GetValues(positions, results); - } - } - - void RunGetValueOrGetValuesBenchmark(benchmark::State& state, const AZ::EntityId& gradientId) - { - switch (state.range(0)) - { - case GetValuePermutation::EBUS_GET_VALUE: - RunEBusGetValueBenchmark(state, gradientId, state.range(1)); - break; - case GetValuePermutation::EBUS_GET_VALUES: - RunEBusGetValuesBenchmark(state, gradientId, state.range(1)); - break; - case GetValuePermutation::SAMPLER_GET_VALUE: - RunSamplerGetValueBenchmark(state, gradientId, state.range(1)); - break; - case GetValuePermutation::SAMPLER_GET_VALUES: - RunSamplerGetValuesBenchmark(state, gradientId, state.range(1)); - break; - default: - AZ_Assert(false, "Benchmark permutation type not supported."); - } - } }; -// Because there's no good way to label different enums in the output results (they just appear as integer values), we work around it by -// registering one set of benchmark runs for each enum value and use ArgNames() to give it a friendly name in the results. -#define GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(Fixture, Func) \ - BENCHMARK_REGISTER_F(Fixture, Func) \ - ->Args({ GradientGetValues::GetValuePermutation::EBUS_GET_VALUE, 1024 }) \ - ->Args({ GradientGetValues::GetValuePermutation::EBUS_GET_VALUE, 2048 }) \ - ->Args({ GradientGetValues::GetValuePermutation::EBUS_GET_VALUE, 4096 }) \ - ->ArgNames({ "EbusGetValue", "size" }) \ - ->Unit(::benchmark::kMillisecond); \ - BENCHMARK_REGISTER_F(Fixture, Func) \ - ->Args({ GradientGetValues::GetValuePermutation::EBUS_GET_VALUES, 1024 }) \ - ->Args({ GradientGetValues::GetValuePermutation::EBUS_GET_VALUES, 2048 }) \ - ->Args({ GradientGetValues::GetValuePermutation::EBUS_GET_VALUES, 4096 }) \ - ->ArgNames({ "EbusGetValues", "size" }) \ - ->Unit(::benchmark::kMillisecond); \ - BENCHMARK_REGISTER_F(Fixture, Func) \ - ->Args({ GradientGetValues::GetValuePermutation::SAMPLER_GET_VALUE, 1024 }) \ - ->Args({ GradientGetValues::GetValuePermutation::SAMPLER_GET_VALUE, 2048 }) \ - ->Args({ GradientGetValues::GetValuePermutation::SAMPLER_GET_VALUE, 4096 }) \ - ->ArgNames({ "SamplerGetValue", "size" }) \ - ->Unit(::benchmark::kMillisecond); \ - BENCHMARK_REGISTER_F(Fixture, Func) \ - ->Args({ GradientGetValues::GetValuePermutation::SAMPLER_GET_VALUES, 1024 }) \ - ->Args({ GradientGetValues::GetValuePermutation::SAMPLER_GET_VALUES, 2048 }) \ - ->Args({ GradientGetValues::GetValuePermutation::SAMPLER_GET_VALUES, 4096 }) \ - ->ArgNames({ "SamplerGetValues", "size" }) \ - ->Unit(::benchmark::kMillisecond); - // -------------------------------------------------------------------------------------- // Base Gradients BENCHMARK_DEFINE_F(GradientGetValues, BM_ConstantGradient)(benchmark::State& state) { auto entity = BuildTestConstantGradient(TestShapeHalfBounds); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_ImageGradient)(benchmark::State& state) { auto entity = BuildTestImageGradient(TestShapeHalfBounds); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_PerlinGradient)(benchmark::State& state) { auto entity = BuildTestPerlinGradient(TestShapeHalfBounds); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_RandomGradient)(benchmark::State& state) { auto entity = BuildTestRandomGradient(TestShapeHalfBounds); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_ShapeAreaFalloffGradient)(benchmark::State& state) { auto entity = BuildTestShapeAreaFalloffGradient(TestShapeHalfBounds); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(GradientGetValues, BM_ConstantGradient); @@ -250,21 +73,21 @@ namespace UnitTest { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestDitherGradient(TestShapeHalfBounds, baseEntity->GetId()); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_InvertGradient)(benchmark::State& state) { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestDitherGradient(TestShapeHalfBounds, baseEntity->GetId()); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_LevelsGradient)(benchmark::State& state) { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestLevelsGradient(TestShapeHalfBounds, baseEntity->GetId()); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_MixedGradient)(benchmark::State& state) @@ -272,35 +95,35 @@ namespace UnitTest auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto mixedEntity = BuildTestConstantGradient(TestShapeHalfBounds); auto entity = BuildTestMixedGradient(TestShapeHalfBounds, baseEntity->GetId(), mixedEntity->GetId()); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_PosterizeGradient)(benchmark::State& state) { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestPosterizeGradient(TestShapeHalfBounds, baseEntity->GetId()); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_ReferenceGradient)(benchmark::State& state) { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestReferenceGradient(TestShapeHalfBounds, baseEntity->GetId()); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_SmoothStepGradient)(benchmark::State& state) { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestSmoothStepGradient(TestShapeHalfBounds, baseEntity->GetId()); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_ThresholdGradient)(benchmark::State& state) { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestThresholdGradient(TestShapeHalfBounds, baseEntity->GetId()); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(GradientGetValues, BM_DitherGradient); @@ -321,7 +144,7 @@ namespace UnitTest CreateMockSurfaceDataSystem(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds))); auto entity = BuildTestSurfaceAltitudeGradient(TestShapeHalfBounds); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_SurfaceMaskGradient)(benchmark::State& state) @@ -330,7 +153,7 @@ namespace UnitTest CreateMockSurfaceDataSystem(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds))); auto entity = BuildTestSurfaceMaskGradient(TestShapeHalfBounds); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } BENCHMARK_DEFINE_F(GradientGetValues, BM_SurfaceSlopeGradient)(benchmark::State& state) @@ -339,7 +162,7 @@ namespace UnitTest CreateMockSurfaceDataSystem(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds))); auto entity = BuildTestSurfaceSlopeGradient(TestShapeHalfBounds); - RunGetValueOrGetValuesBenchmark(state, entity->GetId()); + GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(state, entity->GetId()); } GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(GradientGetValues, BM_SurfaceAltitudeGradient); diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalGetValuesTests.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalGetValuesTests.cpp index 6c4ebcc4c0..f0ad53ee64 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalGetValuesTests.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalGetValuesTests.cpp @@ -8,6 +8,7 @@ #include +#include #include namespace UnitTest @@ -18,79 +19,36 @@ namespace UnitTest // Create an arbitrary size shape for comparing values within. It should be large enough that we detect any value anomalies // but small enough that the tests run quickly. const float TestShapeHalfBounds = 128.0f; - - void CompareGetValueAndGetValues(AZ::EntityId gradientEntityId) - { - // Create a gradient sampler and run through a series of points to see if they match expectations. - - const AZ::Aabb queryRegion = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds)); - const AZ::Vector2 stepSize(1.0f, 1.0f); - - GradientSignal::GradientSampler gradientSampler; - gradientSampler.m_gradientId = gradientEntityId; - - const size_t numSamplesX = aznumeric_cast(ceil(queryRegion.GetExtents().GetX() / stepSize.GetX())); - const size_t numSamplesY = aznumeric_cast(ceil(queryRegion.GetExtents().GetY() / stepSize.GetY())); - - // Build up the list of positions to query. - AZStd::vector positions(numSamplesX * numSamplesY); - size_t index = 0; - for (size_t yIndex = 0; yIndex < numSamplesY; yIndex++) - { - float y = queryRegion.GetMin().GetY() + (stepSize.GetY() * yIndex); - for (size_t xIndex = 0; xIndex < numSamplesX; xIndex++) - { - float x = queryRegion.GetMin().GetX() + (stepSize.GetX() * xIndex); - positions[index++] = AZ::Vector3(x, y, 0.0f); - } - } - - // Get the results from GetValues - AZStd::vector results(numSamplesX * numSamplesY); - gradientSampler.GetValues(positions, results); - - // For each position, call GetValue and verify that the values match. - for (size_t positionIndex = 0; positionIndex < positions.size(); positionIndex++) - { - GradientSignal::GradientSampleParams params; - params.m_position = positions[positionIndex]; - float value = gradientSampler.GetValue(params); - - // We use ASSERT_NEAR instead of EXPECT_NEAR because if one value doesn't match, they probably all won't, so there's no - // reason to keep running and printing failures for every value. - ASSERT_NEAR(value, results[positionIndex], 0.000001f); - } - } }; TEST_F(GradientSignalGetValuesTestsFixture, ImageGradientComponent_VerifyGetValueAndGetValuesMatch) { auto entity = BuildTestImageGradient(TestShapeHalfBounds); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, PerlinGradientComponent_VerifyGetValueAndGetValuesMatch) { auto entity = BuildTestPerlinGradient(TestShapeHalfBounds); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, RandomGradientComponent_VerifyGetValueAndGetValuesMatch) { auto entity = BuildTestRandomGradient(TestShapeHalfBounds); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, ConstantGradientComponent_VerifyGetValueAndGetValuesMatch) { auto entity = BuildTestConstantGradient(TestShapeHalfBounds); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, ShapeAreaFalloffGradientComponent_VerifyGetValueAndGetValuesMatch) { auto entity = BuildTestShapeAreaFalloffGradient(TestShapeHalfBounds); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, DitherGradientComponent_VerifyGetValueAndGetValuesMatch) @@ -98,21 +56,21 @@ namespace UnitTest auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestDitherGradient(TestShapeHalfBounds, baseEntity->GetId()); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, InvertGradientComponent_VerifyGetValueAndGetValuesMatch) { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestInvertGradient(TestShapeHalfBounds, baseEntity->GetId()); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, LevelsGradientComponent_VerifyGetValueAndGetValuesMatch) { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestLevelsGradient(TestShapeHalfBounds, baseEntity->GetId()); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, MixedGradientComponent_VerifyGetValueAndGetValuesMatch) @@ -120,35 +78,35 @@ namespace UnitTest auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto mixedEntity = BuildTestConstantGradient(TestShapeHalfBounds); auto entity = BuildTestMixedGradient(TestShapeHalfBounds, baseEntity->GetId(), mixedEntity->GetId()); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, PosterizeGradientComponent_VerifyGetValueAndGetValuesMatch) { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestPosterizeGradient(TestShapeHalfBounds, baseEntity->GetId()); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, ReferenceGradientComponent_VerifyGetValueAndGetValuesMatch) { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestReferenceGradient(TestShapeHalfBounds, baseEntity->GetId()); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, SmoothStepGradientComponent_VerifyGetValueAndGetValuesMatch) { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestSmoothStepGradient(TestShapeHalfBounds, baseEntity->GetId()); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, ThresholdGradientComponent_VerifyGetValueAndGetValuesMatch) { auto baseEntity = BuildTestRandomGradient(TestShapeHalfBounds); auto entity = BuildTestThresholdGradient(TestShapeHalfBounds, baseEntity->GetId()); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, SurfaceAltitudeGradientComponent_VerifyGetValueAndGetValuesMatch) @@ -157,7 +115,7 @@ namespace UnitTest CreateMockSurfaceDataSystem(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds))); auto entity = BuildTestSurfaceAltitudeGradient(TestShapeHalfBounds); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, SurfaceMaskGradientComponent_VerifyGetValueAndGetValuesMatch) @@ -166,7 +124,7 @@ namespace UnitTest CreateMockSurfaceDataSystem(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds))); auto entity = BuildTestSurfaceMaskGradient(TestShapeHalfBounds); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } TEST_F(GradientSignalGetValuesTestsFixture, SurfaceSlopeGradientComponent_VerifyGetValueAndGetValuesMatch) @@ -175,7 +133,7 @@ namespace UnitTest CreateMockSurfaceDataSystem(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-TestShapeHalfBounds), AZ::Vector3(TestShapeHalfBounds))); auto entity = BuildTestSurfaceSlopeGradient(TestShapeHalfBounds); - CompareGetValueAndGetValues(entity->GetId()); + GradientSignalTestHelpers::CompareGetValueAndGetValues(entity->GetId(), TestShapeHalfBounds); } } diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.cpp new file mode 100644 index 0000000000..46cc3475e8 --- /dev/null +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.cpp @@ -0,0 +1,203 @@ +/* + * 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 +#include +#include + +namespace UnitTest +{ + void GradientSignalTestHelpers::CompareGetValueAndGetValues(AZ::EntityId gradientEntityId, float shapeHalfBounds) + { + // Create a gradient sampler and run through a series of points to see if they match expectations. + + const AZ::Aabb queryRegion = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-shapeHalfBounds), AZ::Vector3(shapeHalfBounds)); + const AZ::Vector2 stepSize(1.0f, 1.0f); + + GradientSignal::GradientSampler gradientSampler; + gradientSampler.m_gradientId = gradientEntityId; + + const size_t numSamplesX = aznumeric_cast(ceil(queryRegion.GetExtents().GetX() / stepSize.GetX())); + const size_t numSamplesY = aznumeric_cast(ceil(queryRegion.GetExtents().GetY() / stepSize.GetY())); + + // Build up the list of positions to query. + AZStd::vector positions(numSamplesX * numSamplesY); + size_t index = 0; + for (size_t yIndex = 0; yIndex < numSamplesY; yIndex++) + { + float y = queryRegion.GetMin().GetY() + (stepSize.GetY() * yIndex); + for (size_t xIndex = 0; xIndex < numSamplesX; xIndex++) + { + float x = queryRegion.GetMin().GetX() + (stepSize.GetX() * xIndex); + positions[index++] = AZ::Vector3(x, y, 0.0f); + } + } + + // Get the results from GetValues + AZStd::vector results(numSamplesX * numSamplesY); + gradientSampler.GetValues(positions, results); + + // For each position, call GetValue and verify that the values match. + for (size_t positionIndex = 0; positionIndex < positions.size(); positionIndex++) + { + GradientSignal::GradientSampleParams params; + params.m_position = positions[positionIndex]; + float value = gradientSampler.GetValue(params); + + // We use ASSERT_NEAR instead of EXPECT_NEAR because if one value doesn't match, they probably all won't, so there's no + // reason to keep running and printing failures for every value. + ASSERT_NEAR(value, results[positionIndex], 0.000001f); + } + } + +#ifdef HAVE_BENCHMARK + + void GradientSignalTestHelpers::FillQueryPositions(AZStd::vector& positions, float height, float width) + { + size_t index = 0; + for (float y = 0.0f; y < height; y += 1.0f) + { + for (float x = 0.0f; x < width; x += 1.0f) + { + positions[index++] = AZ::Vector3(x, y, 0.0f); + } + } + } + + void GradientSignalTestHelpers::RunEBusGetValueBenchmark(benchmark::State& state, const AZ::EntityId& gradientId, int64_t queryRange) + { + AZ_PROFILE_FUNCTION(Entity); + + GradientSignal::GradientSampleParams params; + + // Get the height and width ranges for querying from our benchmark parameters + const float height = aznumeric_cast(queryRange); + const float width = aznumeric_cast(queryRange); + + // Call GetValue() on the EBus for every height and width in our ranges. + for (auto _ : state) + { + for (float y = 0.0f; y < height; y += 1.0f) + { + for (float x = 0.0f; x < width; x += 1.0f) + { + float value = 0.0f; + params.m_position = AZ::Vector3(x, y, 0.0f); + GradientSignal::GradientRequestBus::EventResult( + value, gradientId, &GradientSignal::GradientRequestBus::Events::GetValue, params); + benchmark::DoNotOptimize(value); + } + } + } + } + + void GradientSignalTestHelpers::RunEBusGetValuesBenchmark(benchmark::State& state, const AZ::EntityId& gradientId, int64_t queryRange) + { + AZ_PROFILE_FUNCTION(Entity); + + // Get the height and width ranges for querying from our benchmark parameters + float height = aznumeric_cast(queryRange); + float width = aznumeric_cast(queryRange); + int64_t totalQueryPoints = queryRange * queryRange; + + // Call GetValues() for every height and width in our ranges. + for (auto _ : state) + { + // Set up our vector of query positions. This is done inside the benchmark timing since we're counting the work to create + // each query position in the single GetValue() call benchmarks, and will make the timing more directly comparable. + AZStd::vector positions(totalQueryPoints); + FillQueryPositions(positions, height, width); + + // Query and get the results. + AZStd::vector results(totalQueryPoints); + GradientSignal::GradientRequestBus::Event( + gradientId, &GradientSignal::GradientRequestBus::Events::GetValues, positions, results); + } + } + + void GradientSignalTestHelpers::RunSamplerGetValueBenchmark(benchmark::State& state, const AZ::EntityId& gradientId, int64_t queryRange) + { + AZ_PROFILE_FUNCTION(Entity); + + // Create a gradient sampler to use for querying our gradient. + GradientSignal::GradientSampler gradientSampler; + gradientSampler.m_gradientId = gradientId; + + // Get the height and width ranges for querying from our benchmark parameters + const float height = aznumeric_cast(queryRange); + const float width = aznumeric_cast(queryRange); + + // Call GetValue() through the GradientSampler for every height and width in our ranges. + for (auto _ : state) + { + for (float y = 0.0f; y < height; y += 1.0f) + { + for (float x = 0.0f; x < width; x += 1.0f) + { + GradientSignal::GradientSampleParams params; + params.m_position = AZ::Vector3(x, y, 0.0f); + float value = gradientSampler.GetValue(params); + benchmark::DoNotOptimize(value); + } + } + } + } + + void GradientSignalTestHelpers::RunSamplerGetValuesBenchmark( + benchmark::State& state, const AZ::EntityId& gradientId, int64_t queryRange) + { + AZ_PROFILE_FUNCTION(Entity); + + // Create a gradient sampler to use for querying our gradient. + GradientSignal::GradientSampler gradientSampler; + gradientSampler.m_gradientId = gradientId; + + // Get the height and width ranges for querying from our benchmark parameters + const float height = aznumeric_cast(queryRange); + const float width = aznumeric_cast(queryRange); + const int64_t totalQueryPoints = queryRange * queryRange; + + // Call GetValues() through the GradientSampler for every height and width in our ranges. + for (auto _ : state) + { + // Set up our vector of query positions. This is done inside the benchmark timing since we're counting the work to create + // each query position in the single GetValue() call benchmarks, and will make the timing more directly comparable. + AZStd::vector positions(totalQueryPoints); + FillQueryPositions(positions, height, width); + + // Query and get the results. + AZStd::vector results(totalQueryPoints); + gradientSampler.GetValues(positions, results); + } + } + + void GradientSignalTestHelpers::RunGetValueOrGetValuesBenchmark(benchmark::State& state, const AZ::EntityId& gradientId) + { + switch (state.range(0)) + { + case GetValuePermutation::EBUS_GET_VALUE: + RunEBusGetValueBenchmark(state, gradientId, state.range(1)); + break; + case GetValuePermutation::EBUS_GET_VALUES: + RunEBusGetValuesBenchmark(state, gradientId, state.range(1)); + break; + case GetValuePermutation::SAMPLER_GET_VALUE: + RunSamplerGetValueBenchmark(state, gradientId, state.range(1)); + break; + case GetValuePermutation::SAMPLER_GET_VALUES: + RunSamplerGetValuesBenchmark(state, gradientId, state.range(1)); + break; + default: + AZ_Assert(false, "Benchmark permutation type not supported."); + } + } +#endif +} + + diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.h b/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.h new file mode 100644 index 0000000000..8a175939ee --- /dev/null +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestHelpers.h @@ -0,0 +1,76 @@ +/* + * 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 +#include +#include +#include + +namespace UnitTest +{ + class GradientSignalTestHelpers + { + public: + static void CompareGetValueAndGetValues(AZ::EntityId gradientEntityId, float shapeHalfBounds); + +#ifdef HAVE_BENCHMARK + // We use an enum to list out the different types of GetValue() benchmarks to run so that way we can condense our test cases + // to just take the value in as a benchmark argument and switch on it. Otherwise, we would need to write a different benchmark + // function for each test case for each gradient. + enum GetValuePermutation : int64_t + { + EBUS_GET_VALUE, + EBUS_GET_VALUES, + SAMPLER_GET_VALUE, + SAMPLER_GET_VALUES, + }; + + static void FillQueryPositions(AZStd::vector& positions, float height, float width); + + static void RunEBusGetValueBenchmark(benchmark::State& state, const AZ::EntityId& gradientId, int64_t queryRange); + static void RunEBusGetValuesBenchmark(benchmark::State& state, const AZ::EntityId& gradientId, int64_t queryRange); + static void RunSamplerGetValueBenchmark(benchmark::State& state, const AZ::EntityId& gradientId, int64_t queryRange); + static void RunSamplerGetValuesBenchmark(benchmark::State& state, const AZ::EntityId& gradientId, int64_t queryRange); + static void RunGetValueOrGetValuesBenchmark(benchmark::State& state, const AZ::EntityId& gradientId); + +// Because there's no good way to label different enums in the output results (they just appear as integer values), we work around it by +// registering one set of benchmark runs for each enum value and use ArgNames() to give it a friendly name in the results. +#ifndef GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F +#define GRADIENT_SIGNAL_GET_VALUES_BENCHMARK_REGISTER_F(Fixture, Func) \ + BENCHMARK_REGISTER_F(Fixture, Func) \ + ->Args({ GradientSignalTestHelpers::GetValuePermutation::EBUS_GET_VALUE, 1024 }) \ + ->Args({ GradientSignalTestHelpers::GetValuePermutation::EBUS_GET_VALUE, 2048 }) \ + ->Args({ GradientSignalTestHelpers::GetValuePermutation::EBUS_GET_VALUE, 4096 }) \ + ->ArgNames({ "EbusGetValue", "size" }) \ + ->Unit(::benchmark::kMillisecond); \ + BENCHMARK_REGISTER_F(Fixture, Func) \ + ->Args({ GradientSignalTestHelpers::GetValuePermutation::EBUS_GET_VALUES, 1024 }) \ + ->Args({ GradientSignalTestHelpers::GetValuePermutation::EBUS_GET_VALUES, 2048 }) \ + ->Args({ GradientSignalTestHelpers::GetValuePermutation::EBUS_GET_VALUES, 4096 }) \ + ->ArgNames({ "EbusGetValues", "size" }) \ + ->Unit(::benchmark::kMillisecond); \ + BENCHMARK_REGISTER_F(Fixture, Func) \ + ->Args({ GradientSignalTestHelpers::GetValuePermutation::SAMPLER_GET_VALUE, 1024 }) \ + ->Args({ GradientSignalTestHelpers::GetValuePermutation::SAMPLER_GET_VALUE, 2048 }) \ + ->Args({ GradientSignalTestHelpers::GetValuePermutation::SAMPLER_GET_VALUE, 4096 }) \ + ->ArgNames({ "SamplerGetValue", "size" }) \ + ->Unit(::benchmark::kMillisecond); \ + BENCHMARK_REGISTER_F(Fixture, Func) \ + ->Args({ GradientSignalTestHelpers::GetValuePermutation::SAMPLER_GET_VALUES, 1024 }) \ + ->Args({ GradientSignalTestHelpers::GetValuePermutation::SAMPLER_GET_VALUES, 2048 }) \ + ->Args({ GradientSignalTestHelpers::GetValuePermutation::SAMPLER_GET_VALUES, 4096 }) \ + ->ArgNames({ "SamplerGetValues", "size" }) \ + ->Unit(::benchmark::kMillisecond); +#endif + +#endif + }; + + +} diff --git a/Gems/GradientSignal/Code/gradientsignal_shared_tests_files.cmake b/Gems/GradientSignal/Code/gradientsignal_shared_tests_files.cmake index 7d867b0a33..98ab57b7b0 100644 --- a/Gems/GradientSignal/Code/gradientsignal_shared_tests_files.cmake +++ b/Gems/GradientSignal/Code/gradientsignal_shared_tests_files.cmake @@ -7,6 +7,8 @@ # set(FILES + Tests/GradientSignalTestHelpers.cpp + Tests/GradientSignalTestHelpers.h Tests/GradientSignalTestFixtures.cpp Tests/GradientSignalTestFixtures.h Tests/GradientSignalTestMocks.cpp