瀏覽代碼

Naive GetValues() implementation. (#6741)

* Naive GetValues() implementation.
Added the method itself, and the benchmarks which show that even the naive version is currently 10-50% faster than calling GetValue() for multiple values.

Signed-off-by: Mike Balfour <[email protected]>

* Added comments documenting why the const_cast is there.

Signed-off-by: Mike Balfour <[email protected]>

* Fixed link errors by creating new Shared.Tests lib.

Signed-off-by: Mike Balfour <[email protected]>

* Addressed PR feedback.

Signed-off-by: Mike Balfour <[email protected]>

* Fixed incorrect comparison.

Signed-off-by: Mike Balfour <[email protected]>
Mike Balfour 3 年之前
父節點
當前提交
7123ed18be

+ 22 - 0
Gems/GradientSignal/Code/CMakeLists.txt

@@ -124,6 +124,26 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
                 Mocks
     )
     
+    ly_add_target(
+        NAME GradientSignal.Tests.Static STATIC
+        NAMESPACE Gem
+        FILES_CMAKE
+            gradientsignal_shared_tests_files.cmake
+        INCLUDE_DIRECTORIES
+            PUBLIC
+                Tests
+            PRIVATE
+                .
+                Source
+        BUILD_DEPENDENCIES
+            PRIVATE
+                AZ::AzTest
+                AZ::AzTestShared
+                Gem::GradientSignal.Static
+                Gem::LmbrCentral
+                Gem::GradientSignal.Mocks
+    )
+    
     ly_add_target(
         NAME GradientSignal.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
         NAMESPACE Gem
@@ -137,6 +157,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
             PRIVATE
                 AZ::AzTest
                 AZ::AzTestShared
+                Gem::GradientSignal.Tests.Static
                 Gem::GradientSignal.Static
                 Gem::LmbrCentral
                 Gem::GradientSignal.Mocks
@@ -165,6 +186,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
                 PRIVATE
                     AZ::AzTest
                     AZ::AzTestShared
+                    Gem::GradientSignal.Tests.Static
                     Gem::GradientSignal.Static
                     Gem::GradientSignal.Editor.Static
                     Gem::LmbrCentral.Editor

+ 32 - 0
Gems/GradientSignal/Code/Include/GradientSignal/Ebuses/GradientRequestBus.h

@@ -11,6 +11,8 @@
 #include <AzCore/Component/EntityId.h>
 #include <AzCore/Math/Vector3.h>
 
+#include <AtomCore/std/containers/array_view.h>
+
 namespace GradientSignal
 {
     struct GradientSampleParams final
@@ -49,6 +51,36 @@ namespace GradientSignal
         */
         virtual float GetValue(const GradientSampleParams& sampleParams) const = 0;
 
+        /**
+         * Given a list of positions, generate values. Implementations of this need to be thread-safe without using locks,
+         * as it can get called from multiple threads simultaneously and has the potential to cause lock inversion deadlocks.
+         * \param positions The input list of positions to query.
+         * \param outValues The output list of values. This list is expected to be the same size as the positions list.
+         */
+        virtual void GetValues(AZStd::array_view<AZ::Vector3> positions, AZStd::array_view<float> outValues) const
+        {
+            // Reference implementation of GetValues for any gradients that don't have their own optimized implementations.
+            // This is 10%-60% faster than calling GetValue via EBus many times due to the per-call EBus overhead.
+
+            AZ_Assert(
+                positions.size() == outValues.size(), "input and output lists are different sizes (%zu vs %zu).",
+                positions.size(), outValues.size());
+
+            if (positions.size() == outValues.size())
+            {
+                GradientSampleParams sampleParams;
+                for (size_t index = 0; index < positions.size(); index++)
+                {
+                    sampleParams.m_position = positions[index];
+
+                    // The const_cast is necessary for now since array_view currently only supports const entries.
+                    // If/when array_view is fixed to support non-const, or AZStd::span gets created, the const_cast can get removed.
+                    auto& outValue = const_cast<float&>(outValues[index]);
+                    outValue = GetValue(sampleParams);
+                }
+            }
+        }
+
         /**
         * Call to check the hierarchy to see if a given entityId exists in the gradient signal chain
         */

+ 90 - 0
Gems/GradientSignal/Code/Include/GradientSignal/GradientSampler.h

@@ -33,6 +33,7 @@ namespace GradientSignal
         static void Reflect(AZ::ReflectContext* context);
 
         inline float GetValue(const GradientSampleParams& sampleParams) const;
+        inline void GetValues(AZStd::array_view<AZ::Vector3> positions, AZStd::array_view<float> outValues) const;
 
         bool IsEntityInHierarchy(const AZ::EntityId& entityId) const;
 
@@ -145,4 +146,93 @@ namespace GradientSignal
 
         return output * m_opacity;
     }
+
+    inline void GradientSampler::GetValues(AZStd::array_view<AZ::Vector3> positions, AZStd::array_view<float> outValues) const
+    {
+        auto ClearOutputValues = [](AZStd::array_view<float> outValues)
+        {
+            // If we don't have a valid gradient (or it is fully transparent), clear out all the output values.
+            for (size_t index = 0; index < outValues.size(); index++)
+            {
+                // The const_cast is necessary for now since array_view currently only supports const entries.
+                // If/when array_view is fixed to support non-const, or AZStd::span gets created, the const_cast can get removed.
+                auto& outValue = const_cast<float&>(outValues[index]);
+                outValue = 0.0f;
+            }
+        };
+
+        if (m_opacity <= 0.0f || !m_gradientId.IsValid())
+        {
+            ClearOutputValues(outValues);
+            return;
+        }
+
+        AZStd::vector<AZ::Vector3> transformedPositions;
+        bool useTransformedPositions = false;
+
+        // apply transform if set
+        if (m_enableTransform && GradientSamplerUtil::AreTransformParamsSet(*this))
+        {
+            AZ::Matrix3x4 matrix3x4;
+            matrix3x4.SetFromEulerDegrees(m_rotate);
+            matrix3x4.MultiplyByScale(m_scale);
+            matrix3x4.SetTranslation(m_translate);
+
+            useTransformedPositions = true;
+            transformedPositions.resize(positions.size());
+            for (size_t index = 0; index < positions.size(); index++)
+            {
+                transformedPositions[index] = matrix3x4 * positions[index];
+            }
+        }
+
+        {
+            // Block other threads from accessing the surface data bus while we are in GetValue (which may call into the SurfaceData bus).
+            // We lock our surface data mutex *before* checking / setting "isRequestInProgress" so that we prevent race conditions
+            // that create false detection of cyclic dependencies when multiple requests occur on different threads simultaneously.
+            // (One case where this was previously able to occur was in rapid updating of the Preview widget on the
+            // GradientSurfaceDataComponent in the Editor when moving the threshold sliders back and forth rapidly)
+            auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false);
+            typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex);
+
+            if (m_isRequestInProgress)
+            {
+                AZ_ErrorOnce("GradientSignal", !m_isRequestInProgress, "Detected cyclic dependences with gradient entity references");
+                ClearOutputValues(outValues);
+                return;
+            }
+            else
+            {
+                m_isRequestInProgress = true;
+
+                GradientRequestBus::Event(
+                    m_gradientId, &GradientRequestBus::Events::GetValues, useTransformedPositions ? transformedPositions : positions,
+                    outValues);
+
+                m_isRequestInProgress = false;
+            }
+        }
+
+        // Perform any post-fetch transformations on the gradient values (invert, levels, opacity).
+        for (size_t index = 0; index < outValues.size(); index++)
+        {
+            // The const_cast is necessary for now since array_view currently only supports const entries.
+            // If/when array_view is fixed to support non-const, or AZStd::span gets created, the const_cast can get removed.
+            auto& outValue = const_cast<float&>(outValues[index]);
+
+            if (m_invertInput)
+            {
+                outValue = 1.0f - outValue;
+            }
+
+            // apply levels if set
+            if (m_enableLevels && GradientSamplerUtil::AreLevelParamsSet(*this))
+            {
+                outValue = GetLevels(outValue, m_inputMid, m_inputMin, m_inputMax, m_outputMin, m_outputMax);
+            }
+
+            outValue = outValue * m_opacity;
+        }
+    }
+
 }

+ 120 - 49
Gems/GradientSignal/Code/Tests/GradientSignalBenchmarks.cpp

@@ -23,74 +23,145 @@
 
 namespace UnitTest
 {
-    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_ImageGradientGetValue)(benchmark::State& state)
+    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_ImageGradientEBusGetValue)(benchmark::State& state)
     {
-        // Create the Image Gradient Component with some default sizes and parameters.
-        GradientSignal::ImageGradientConfig config;
-        const uint32_t imageSize = 4096;
-        const int32_t imageSeed = 12345;
-        config.m_imageAsset = ImageAssetMockAssetHandler::CreateImageAsset(imageSize, imageSize, imageSeed);
-        config.m_tilingX = 1.0f;
-        config.m_tilingY = 1.0f;
-        CreateComponent<GradientSignal::ImageGradientComponent>(m_testEntity.get(), config);
-
-        // Create the Gradient Transform Component with some default parameters.
-        GradientSignal::GradientTransformConfig gradientTransformConfig;
-        gradientTransformConfig.m_wrappingType = GradientSignal::WrappingType::None;
-        CreateComponent<GradientSignal::GradientTransformComponent>(m_testEntity.get(), gradientTransformConfig);
-
-        // Run the benchmark
-        RunGetValueBenchmark(state);
+        CreateTestImageGradient(m_testEntity.get());
+        RunEBusGetValueBenchmark(state);
     }
 
-    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_ImageGradientGetValue)
+    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_ImageGradientEBusGetValue)
         ->Args({ 1024, 1024 })
         ->Args({ 2048, 2048 })
         ->Args({ 4096, 4096 })
         ->Unit(::benchmark::kMillisecond);
 
-    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_PerlinGradientGetValue)(benchmark::State& state)
+    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_ImageGradientEBusGetValues)(benchmark::State& state)
     {
-        // Create the Perlin Gradient Component with some default sizes and parameters.
-        GradientSignal::PerlinGradientConfig config;
-        config.m_amplitude = 1.0f;
-        config.m_frequency = 1.1f;
-        config.m_octave = 4;
-        config.m_randomSeed = 12345;
-        CreateComponent<GradientSignal::PerlinGradientComponent>(m_testEntity.get(), config);
-
-        // Create the Gradient Transform Component with some default parameters.
-        GradientSignal::GradientTransformConfig gradientTransformConfig;
-        gradientTransformConfig.m_wrappingType = GradientSignal::WrappingType::None;
-        CreateComponent<GradientSignal::GradientTransformComponent>(m_testEntity.get(), gradientTransformConfig);
-
-        // Run the benchmark
-        RunGetValueBenchmark(state);
+        CreateTestImageGradient(m_testEntity.get());
+        RunEBusGetValuesBenchmark(state);
     }
 
-    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_PerlinGradientGetValue)
+    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_ImageGradientEBusGetValues)
         ->Args({ 1024, 1024 })
         ->Args({ 2048, 2048 })
         ->Args({ 4096, 4096 })
         ->Unit(::benchmark::kMillisecond);
 
-    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_RandomGradientGetValue)(benchmark::State& state)
+    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_ImageGradientSamplerGetValue)(benchmark::State& state)
     {
-        // Create the Random Gradient Component with some default parameters.
-        GradientSignal::RandomGradientConfig config;
-        config.m_randomSeed = 12345;
-        CreateComponent<GradientSignal::RandomGradientComponent>(m_testEntity.get(), config);
-
-        // Create the Gradient Transform Component with some default parameters.
-        GradientSignal::GradientTransformConfig gradientTransformConfig;
-        gradientTransformConfig.m_wrappingType = GradientSignal::WrappingType::None;
-        CreateComponent<GradientSignal::GradientTransformComponent>(m_testEntity.get(), gradientTransformConfig);
-
-        // Run the benchmark
-        RunGetValueBenchmark(state);
+        CreateTestImageGradient(m_testEntity.get());
+        RunSamplerGetValueBenchmark(state);
     }
 
-    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_RandomGradientGetValue)
+    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_ImageGradientSamplerGetValue)
+        ->Args({ 1024, 1024 })
+        ->Args({ 2048, 2048 })
+        ->Args({ 4096, 4096 })
+        ->Unit(::benchmark::kMillisecond);
+
+    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_ImageGradientSamplerGetValues)(benchmark::State& state)
+    {
+        CreateTestImageGradient(m_testEntity.get());
+        RunSamplerGetValuesBenchmark(state);
+    }
+
+    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_ImageGradientSamplerGetValues)
+        ->Args({ 1024, 1024 })
+        ->Args({ 2048, 2048 })
+        ->Args({ 4096, 4096 })
+        ->Unit(::benchmark::kMillisecond);
+
+    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_PerlinGradientEBusGetValue)(benchmark::State& state)
+    {
+        CreateTestPerlinGradient(m_testEntity.get());
+        RunEBusGetValueBenchmark(state);
+    }
+
+    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_PerlinGradientEBusGetValue)
+        ->Args({ 1024, 1024 })
+        ->Args({ 2048, 2048 })
+        ->Args({ 4096, 4096 })
+        ->Unit(::benchmark::kMillisecond);
+
+    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_PerlinGradientEBusGetValues)(benchmark::State& state)
+    {
+        CreateTestPerlinGradient(m_testEntity.get());
+        RunEBusGetValuesBenchmark(state);
+    }
+
+    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_PerlinGradientEBusGetValues)
+        ->Args({ 1024, 1024 })
+        ->Args({ 2048, 2048 })
+        ->Args({ 4096, 4096 })
+        ->Unit(::benchmark::kMillisecond);
+
+    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_PerlinGradientSamplerGetValue)(benchmark::State& state)
+    {
+        CreateTestPerlinGradient(m_testEntity.get());
+        RunSamplerGetValueBenchmark(state);
+    }
+
+    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_PerlinGradientSamplerGetValue)
+        ->Args({ 1024, 1024 })
+        ->Args({ 2048, 2048 })
+        ->Args({ 4096, 4096 })
+        ->Unit(::benchmark::kMillisecond);
+
+    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_PerlinGradientSamplerGetValues)(benchmark::State& state)
+    {
+        CreateTestPerlinGradient(m_testEntity.get());
+        RunSamplerGetValuesBenchmark(state);
+    }
+
+    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_PerlinGradientSamplerGetValues)
+        ->Args({ 1024, 1024 })
+        ->Args({ 2048, 2048 })
+        ->Args({ 4096, 4096 })
+        ->Unit(::benchmark::kMillisecond);
+
+    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_RandomGradientEBusGetValue)(benchmark::State& state)
+    {
+        CreateTestRandomGradient(m_testEntity.get());
+        RunEBusGetValueBenchmark(state);
+    }
+
+    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_RandomGradientEBusGetValue)
+        ->Args({ 1024, 1024 })
+        ->Args({ 2048, 2048 })
+        ->Args({ 4096, 4096 })
+        ->Unit(::benchmark::kMillisecond);
+
+    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_RandomGradientEBusGetValues)(benchmark::State& state)
+    {
+        CreateTestRandomGradient(m_testEntity.get());
+        RunEBusGetValuesBenchmark(state);
+    }
+
+    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_RandomGradientEBusGetValues)
+        ->Args({ 1024, 1024 })
+        ->Args({ 2048, 2048 })
+        ->Args({ 4096, 4096 })
+        ->Unit(::benchmark::kMillisecond);
+
+    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_RandomGradientSamplerGetValue)(benchmark::State& state)
+    {
+        CreateTestRandomGradient(m_testEntity.get());
+        RunSamplerGetValueBenchmark(state);
+    }
+
+    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_RandomGradientSamplerGetValue)
+        ->Args({ 1024, 1024 })
+        ->Args({ 2048, 2048 })
+        ->Args({ 4096, 4096 })
+        ->Unit(::benchmark::kMillisecond);
+
+    BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_RandomGradientSamplerGetValues)(benchmark::State& state)
+    {
+        CreateTestRandomGradient(m_testEntity.get());
+        RunSamplerGetValuesBenchmark(state);
+    }
+
+    BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_RandomGradientSamplerGetValues)
         ->Args({ 1024, 1024 })
         ->Args({ 2048, 2048 })
         ->Args({ 4096, 4096 })

+ 155 - 1
Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp

@@ -9,6 +9,11 @@
 
 #include <Tests/GradientSignalTestFixtures.h>
 
+#include <GradientSignal/Components/GradientTransformComponent.h>
+#include <GradientSignal/Components/ImageGradientComponent.h>
+#include <GradientSignal/Components/PerlinGradientComponent.h>
+#include <GradientSignal/Components/RandomGradientComponent.h>
+
 namespace UnitTest
 {
     void GradientSignalBaseFixture::SetupCoreSystems()
@@ -86,8 +91,56 @@ namespace UnitTest
         m_testEntity.reset();
     }
 
-    void GradientSignalBenchmarkFixture::RunGetValueBenchmark(benchmark::State& state)
+    void GradientSignalBenchmarkFixture::CreateTestImageGradient(AZ::Entity* entity)
+    {
+        // Create the Image Gradient Component with some default sizes and parameters.
+        GradientSignal::ImageGradientConfig config;
+        const uint32_t imageSize = 4096;
+        const int32_t imageSeed = 12345;
+        config.m_imageAsset = ImageAssetMockAssetHandler::CreateImageAsset(imageSize, imageSize, imageSeed);
+        config.m_tilingX = 1.0f;
+        config.m_tilingY = 1.0f;
+        CreateComponent<GradientSignal::ImageGradientComponent>(entity, config);
+
+        // Create the Gradient Transform Component with some default parameters.
+        GradientSignal::GradientTransformConfig gradientTransformConfig;
+        gradientTransformConfig.m_wrappingType = GradientSignal::WrappingType::None;
+        CreateComponent<GradientSignal::GradientTransformComponent>(entity, gradientTransformConfig);
+    }
+
+    void GradientSignalBenchmarkFixture::CreateTestPerlinGradient(AZ::Entity* entity)
+    {
+        // Create the Perlin Gradient Component with some default sizes and parameters.
+        GradientSignal::PerlinGradientConfig config;
+        config.m_amplitude = 1.0f;
+        config.m_frequency = 1.1f;
+        config.m_octave = 4;
+        config.m_randomSeed = 12345;
+        CreateComponent<GradientSignal::PerlinGradientComponent>(entity, config);
+
+        // Create the Gradient Transform Component with some default parameters.
+        GradientSignal::GradientTransformConfig gradientTransformConfig;
+        gradientTransformConfig.m_wrappingType = GradientSignal::WrappingType::None;
+        CreateComponent<GradientSignal::GradientTransformComponent>(entity, gradientTransformConfig);
+    }
+
+    void GradientSignalBenchmarkFixture::CreateTestRandomGradient(AZ::Entity* entity)
+    {
+        // Create the Random Gradient Component with some default parameters.
+        GradientSignal::RandomGradientConfig config;
+        config.m_randomSeed = 12345;
+        CreateComponent<GradientSignal::RandomGradientComponent>(entity, config);
+
+        // Create the Gradient Transform Component with some default parameters.
+        GradientSignal::GradientTransformConfig gradientTransformConfig;
+        gradientTransformConfig.m_wrappingType = GradientSignal::WrappingType::None;
+        CreateComponent<GradientSignal::GradientTransformComponent>(entity, gradientTransformConfig);
+    }
+
+    void GradientSignalBenchmarkFixture::RunSamplerGetValueBenchmark(benchmark::State& state)
     {
+        AZ_PROFILE_FUNCTION(Entity);
+
         // All components are created, so activate the entity
         ActivateEntity(m_testEntity.get());
 
@@ -115,6 +168,107 @@ namespace UnitTest
         }
     }
 
+    void GradientSignalBenchmarkFixture::RunSamplerGetValuesBenchmark(benchmark::State& state)
+    {
+        AZ_PROFILE_FUNCTION(Entity);
+
+        // All components are created, so activate the entity
+        ActivateEntity(m_testEntity.get());
+
+        // Create a gradient sampler and run through a series of points to see if they match expectations.
+        GradientSignal::GradientSampler gradientSampler;
+        gradientSampler.m_gradientId = m_testEntity->GetId();
+
+        // Get the height and width ranges for querying from our benchmark parameters
+        float height = aznumeric_cast<float>(state.range(0));
+        float width = aznumeric_cast<float>(state.range(1));
+        int64_t totalQueryPoints = state.range(0) * state.range(1);
+
+        // Call GetValues() for every height and width in our ranges.
+        for (auto _ : state)
+        {
+            // Set up our vector of query positions.
+            AZStd::vector<AZ::Vector3> positions(totalQueryPoints);
+            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);
+                }
+            }
+
+            // Query and get the results.
+            AZStd::vector<float> results(totalQueryPoints);
+            gradientSampler.GetValues(positions, results);
+        }
+    }
+
+    void GradientSignalBenchmarkFixture::RunEBusGetValueBenchmark(benchmark::State& state)
+    {
+        AZ_PROFILE_FUNCTION(Entity);
+
+        // All components are created, so activate the entity
+        ActivateEntity(m_testEntity.get());
+
+        GradientSignal::GradientSampleParams params;
+
+        // Get the height and width ranges for querying from our benchmark parameters
+        float height = aznumeric_cast<float>(state.range(0));
+        float width = aznumeric_cast<float>(state.range(1));
+
+        // Call GetValue() 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, m_testEntity->GetId(), &GradientSignal::GradientRequestBus::Events::GetValue, params);
+                    benchmark::DoNotOptimize(value);
+                }
+            }
+        }
+    }
+
+    void GradientSignalBenchmarkFixture::RunEBusGetValuesBenchmark(benchmark::State& state)
+    {
+        AZ_PROFILE_FUNCTION(Entity);
+
+        // All components are created, so activate the entity
+        ActivateEntity(m_testEntity.get());
+
+        GradientSignal::GradientSampler gradientSampler;
+        gradientSampler.m_gradientId = m_testEntity->GetId();
+
+        // Get the height and width ranges for querying from our benchmark parameters
+        float height = aznumeric_cast<float>(state.range(0));
+        float width = aznumeric_cast<float>(state.range(1));
+        int64_t totalQueryPoints = state.range(0) * state.range(1);
+
+        // Call GetValues() for every height and width in our ranges.
+        for (auto _ : state)
+        {
+            // Set up our vector of query positions.
+            AZStd::vector<AZ::Vector3> positions(totalQueryPoints);
+            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);
+                }
+            }
+
+            // Query and get the results.
+            AZStd::vector<float> results(totalQueryPoints);
+            GradientSignal::GradientRequestBus::Event(
+                m_testEntity->GetId(), &GradientSignal::GradientRequestBus::Events::GetValues, positions, results);
+        }
+    }
 #endif
 
 }

+ 9 - 1
Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.h

@@ -97,7 +97,15 @@ namespace UnitTest
         void CreateTestEntity(float shapeHalfBounds);
         void DestroyTestEntity();
 
-        void RunGetValueBenchmark(benchmark::State& state);
+        void CreateTestImageGradient(AZ::Entity* entity);
+        void CreateTestPerlinGradient(AZ::Entity* entity);
+        void CreateTestRandomGradient(AZ::Entity* entity);
+
+        void RunSamplerGetValueBenchmark(benchmark::State& state);
+        void RunSamplerGetValuesBenchmark(benchmark::State& state);
+
+        void RunEBusGetValueBenchmark(benchmark::State& state);
+        void RunEBusGetValuesBenchmark(benchmark::State& state);
 
     protected:
         void SetUp(const benchmark::State& state) override

+ 21 - 19
Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.cpp

@@ -13,13 +13,14 @@ namespace UnitTest
 {
     AZ::Data::Asset<GradientSignal::ImageAsset> ImageAssetMockAssetHandler::CreateImageAsset(AZ::u32 width, AZ::u32 height, AZ::s32 seed)
     {
-        GradientSignal::ImageAsset* imageData =
-            aznew GradientSignal::ImageAsset(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), AZ::Data::AssetData::AssetStatus::Ready);
-        imageData->m_imageWidth = width;
-        imageData->m_imageHeight = height;
-        imageData->m_bytesPerPixel = 1;
-        imageData->m_imageFormat = ImageProcessingAtom::EPixelFormat::ePixelFormat_R8;
-        imageData->m_imageData.reserve(width * height);
+        auto imageAsset = AZ::Data::AssetManager::Instance().CreateAsset<GradientSignal::ImageAsset>(
+            AZ::Data::AssetId(AZ::Uuid::CreateRandom()), AZ::Data::AssetLoadBehavior::Default);
+
+        imageAsset->m_imageWidth = width;
+        imageAsset->m_imageHeight = height;
+        imageAsset->m_bytesPerPixel = 1;
+        imageAsset->m_imageFormat = ImageProcessingAtom::EPixelFormat::ePixelFormat_R8;
+        imageAsset->m_imageData.reserve(width * height);
 
         size_t value = 0;
         AZStd::hash_combine(value, seed);
@@ -30,23 +31,24 @@ namespace UnitTest
             {
                 AZStd::hash_combine(value, x);
                 AZStd::hash_combine(value, y);
-                imageData->m_imageData.push_back(static_cast<AZ::u8>(value));
+                imageAsset->m_imageData.push_back(static_cast<AZ::u8>(value));
             }
         }
 
-        return AZ::Data::Asset<GradientSignal::ImageAsset>(imageData, AZ::Data::AssetLoadBehavior::Default);
+        return imageAsset;
     }
 
     AZ::Data::Asset<GradientSignal::ImageAsset> ImageAssetMockAssetHandler::CreateSpecificPixelImageAsset(
         AZ::u32 width, AZ::u32 height, AZ::u32 pixelX, AZ::u32 pixelY)
     {
-        GradientSignal::ImageAsset* imageData =
-            aznew GradientSignal::ImageAsset(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), AZ::Data::AssetData::AssetStatus::Ready);
-        imageData->m_imageWidth = width;
-        imageData->m_imageHeight = height;
-        imageData->m_bytesPerPixel = 1;
-        imageData->m_imageFormat = ImageProcessingAtom::EPixelFormat::ePixelFormat_R8;
-        imageData->m_imageData.reserve(width * height);
+        auto imageAsset = AZ::Data::AssetManager::Instance().CreateAsset<GradientSignal::ImageAsset>(
+            AZ::Data::AssetId(AZ::Uuid::CreateRandom()), AZ::Data::AssetLoadBehavior::Default);
+
+        imageAsset->m_imageWidth = width;
+        imageAsset->m_imageHeight = height;
+        imageAsset->m_bytesPerPixel = 1;
+        imageAsset->m_imageFormat = ImageProcessingAtom::EPixelFormat::ePixelFormat_R8;
+        imageAsset->m_imageData.reserve(width * height);
 
         const AZ::u8 pixelValue = 255;
 
@@ -57,16 +59,16 @@ namespace UnitTest
             {
                 if ((x == static_cast<int>(pixelX)) && (y == static_cast<int>(pixelY)))
                 {
-                    imageData->m_imageData.push_back(pixelValue);
+                    imageAsset->m_imageData.push_back(pixelValue);
                 }
                 else
                 {
-                    imageData->m_imageData.push_back(0);
+                    imageAsset->m_imageData.push_back(0);
                 }
             }
         }
 
-        return AZ::Data::Asset<GradientSignal::ImageAsset>(imageData, AZ::Data::AssetLoadBehavior::Default);
+        return imageAsset;
     }
 }
 

+ 3 - 3
Gems/GradientSignal/Code/Tests/GradientSignalTestMocks.h

@@ -47,10 +47,10 @@ namespace UnitTest
         static AZ::Data::Asset<GradientSignal::ImageAsset> CreateSpecificPixelImageAsset(
             AZ::u32 width, AZ::u32 height, AZ::u32 pixelX, AZ::u32 pixelY);
 
-        AZ::Data::AssetPtr CreateAsset(
-            [[maybe_unused]] const AZ::Data::AssetId& id, [[maybe_unused]] const AZ::Data::AssetType& type) override
+        AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, [[maybe_unused]] const AZ::Data::AssetType& type) override
         {
-            return AZ::Data::AssetPtr();
+            // For our mock handler, always mark our assets as immediately ready.
+            return aznew GradientSignal::ImageAsset(id, AZ::Data::AssetData::AssetStatus::Ready);
         }
 
         void DestroyAsset(AZ::Data::AssetPtr ptr) override

+ 0 - 1
Gems/GradientSignal/Code/gradientsignal_editor_tests_files.cmake

@@ -7,6 +7,5 @@
 #
 
 set(FILES
-    Tests/GradientSignalTestFixtures.cpp
     Tests/EditorGradientSignalPreviewTests.cpp
 )

+ 14 - 0
Gems/GradientSignal/Code/gradientsignal_shared_tests_files.cmake

@@ -0,0 +1,14 @@
+#
+# 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/GradientSignalTestFixtures.cpp
+    Tests/GradientSignalTestFixtures.h
+    Tests/GradientSignalTestMocks.cpp
+    Tests/GradientSignalTestMocks.h
+)

+ 0 - 4
Gems/GradientSignal/Code/gradientsignal_tests_files.cmake

@@ -13,10 +13,6 @@ set(FILES
     Tests/GradientSignalServicesTests.cpp
     Tests/GradientSignalSurfaceTests.cpp
     Tests/GradientSignalTransformTests.cpp
-    Tests/GradientSignalTestFixtures.cpp
-    Tests/GradientSignalTestFixtures.h
-    Tests/GradientSignalTestMocks.cpp
-    Tests/GradientSignalTestMocks.h
     Tests/GradientSignalTest.cpp
     Tests/ImageAssetTests.cpp
 )