/* * 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 #include #include #include #include #include #include #include #include #include #include namespace UnitTest { using ::testing::NiceMock; using ::testing::Return; class TerrainSystemBenchmarkFixture : public UnitTest::AllocatorsBenchmarkFixture , public UnitTest::TraceBusRedirector { public: void SetUp(const benchmark::State& state) override { InternalSetUp(state); } void SetUp(benchmark::State& state) override { InternalSetUp(state); } void TearDown(const benchmark::State& state) override { InternalTearDown(state); } void TearDown(benchmark::State& state) override { InternalTearDown(state); } void InternalSetUp(const benchmark::State& state) { AZ::Debug::TraceMessageBus::Handler::BusConnect(); UnitTest::AllocatorsBenchmarkFixture::SetUp(state); m_app = AZStd::make_unique(); ASSERT_TRUE(m_app != nullptr); AZ::ComponentApplication::Descriptor componentAppDesc; AZ::Entity* systemEntity = m_app->Create(componentAppDesc); ASSERT_TRUE(systemEntity != nullptr); m_app->AddEntity(systemEntity); AZ::AllocatorInstance::Create(); } void InternalTearDown(const benchmark::State& state) { AZ::AllocatorInstance::Destroy(); m_app->Destroy(); m_app.reset(); UnitTest::AllocatorsBenchmarkFixture::TearDown(state); AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); } AZStd::unique_ptr CreateEntity() { return AZStd::make_unique(); } void ActivateEntity(AZ::Entity* entity) { entity->Init(); entity->Activate(); } template Component* CreateComponent(AZ::Entity* entity, const Configuration& config) { m_app->RegisterComponentDescriptor(Component::CreateDescriptor()); return entity->CreateComponent(config); } template Component* CreateComponent(AZ::Entity* entity) { m_app->RegisterComponentDescriptor(Component::CreateDescriptor()); return entity->CreateComponent(); } // Create a terrain system with reasonable defaults for testing, but with the ability to override the defaults // on a test-by-test basis. AZStd::unique_ptr CreateAndActivateTerrainSystem( float queryResolution = 1.0f, AZ::Aabb worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-128.0f), AZ::Vector3(128.0f))) { // Create the terrain system and give it one tick to fully initialize itself. auto terrainSystem = AZStd::make_unique(); terrainSystem->SetTerrainAabb(worldBounds); terrainSystem->SetTerrainHeightQueryResolution(queryResolution); terrainSystem->Activate(); AZ::TickBus::Broadcast(&AZ::TickBus::Events::OnTick, 0.f, AZ::ScriptTimePoint{}); return terrainSystem; } // Create a mock shape bus listener that will listen to the given EntityId for shape requests and returns the following: // - GetEncompassingAabb - returns the given Aabb // - GetTransformAndLocalBounds - returns the center of the Aabb as the transform, and the size of the Aabb as the local bounds // - IsPointInside - true if the point is in the Aabb, false if not AZStd::unique_ptr> CreateMockShape( const AZ::Aabb& spawnerBox, const AZ::EntityId& shapeEntityId) { AZStd::unique_ptr> mockShape = AZStd::make_unique>(shapeEntityId); ON_CALL(*mockShape, GetEncompassingAabb).WillByDefault(Return(spawnerBox)); ON_CALL(*mockShape, GetTransformAndLocalBounds) .WillByDefault( [spawnerBox](AZ::Transform& transform, AZ::Aabb& bounds) { transform = AZ::Transform::CreateTranslation(spawnerBox.GetCenter()); bounds = spawnerBox.GetTranslated(-spawnerBox.GetCenter()); }); ON_CALL(*mockShape, IsPointInside) .WillByDefault( [spawnerBox](const AZ::Vector3& point) -> bool { return spawnerBox.Contains(point); }); return mockShape; } // Create an entity with a Random Gradient on it that can be used for gradient queries. AZStd::unique_ptr CreateTestRandomGradientEntity(const AZ::Aabb& spawnerBox, uint32_t randomSeed) { // Create the base entity AZStd::unique_ptr testGradientEntity = CreateEntity(); // Add a mock AABB Shape so that the shape requirement is fulfilled. CreateComponent(testGradientEntity.get()); // Create the Random Gradient Component with some default parameters. GradientSignal::RandomGradientConfig config; config.m_randomSeed = randomSeed; CreateComponent(testGradientEntity.get(), config); // Create the Gradient Transform Component with some default parameters. GradientSignal::GradientTransformConfig gradientTransformConfig; gradientTransformConfig.m_wrappingType = GradientSignal::WrappingType::None; CreateComponent(testGradientEntity.get(), gradientTransformConfig); // Set the transform to match the given spawnerBox auto transform = CreateComponent(testGradientEntity.get()); transform->SetLocalTM(AZ::Transform::CreateTranslation(spawnerBox.GetCenter())); transform->SetWorldTM(AZ::Transform::CreateTranslation(spawnerBox.GetCenter())); return testGradientEntity; } AZStd::unique_ptr CreateTestLayerSpawnerEntity( const AZ::Aabb& spawnerBox, const AZ::EntityId& heightGradientEntityId, const Terrain::TerrainSurfaceGradientListConfig& surfaceConfig) { // Create the base entity AZStd::unique_ptr testLayerSpawnerEntity = CreateEntity(); // Add a mock AABB Shape so that the shape requirement is fulfilled. CreateComponent(testLayerSpawnerEntity.get()); // Add a Terrain Layer Spawner CreateComponent(testLayerSpawnerEntity.get()); // Add a Terrain Height Gradient List with one entry pointing to the given gradient entity Terrain::TerrainHeightGradientListConfig heightConfig; heightConfig.m_gradientEntities.emplace_back(heightGradientEntityId); CreateComponent(testLayerSpawnerEntity.get(), heightConfig); // Add a Terrain Surface Gradient List with however many entries we were given CreateComponent(testLayerSpawnerEntity.get(), surfaceConfig); // Set the transform to match the given spawnerBox auto transform = CreateComponent(testLayerSpawnerEntity.get()); transform->SetLocalTM(AZ::Transform::CreateTranslation(spawnerBox.GetCenter())); transform->SetWorldTM(AZ::Transform::CreateTranslation(spawnerBox.GetCenter())); return testLayerSpawnerEntity; } void RunTerrainApiBenchmark( benchmark::State& state, AZStd::function ApiCaller) { // Get the ranges for querying from our benchmark parameters float boundsRange = aznumeric_cast(state.range(0)); uint32_t numSurfaces = aznumeric_cast(state.range(1)); AzFramework::Terrain::TerrainDataRequests::Sampler sampler = static_cast(state.range(2)); // Set up our world bounds and query resolution AZ::Aabb worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-boundsRange / 2.0f), AZ::Vector3(boundsRange / 2.0f)); float queryResolution = 1.0f; // Create a Random Gradient to use as our height provider const uint32_t heightRandomSeed = 12345; auto heightGradientEntity = CreateTestRandomGradientEntity(worldBounds, heightRandomSeed); auto heightGradientShapeRequests = CreateMockShape(worldBounds, heightGradientEntity->GetId()); ActivateEntity(heightGradientEntity.get()); // Create a set of Random Gradients to use as our surface providers Terrain::TerrainSurfaceGradientListConfig surfaceConfig; AZStd::vector> surfaceGradientEntities; AZStd::vector>> surfaceGradientShapeRequests; for (uint32_t surfaces = 0; surfaces < numSurfaces; surfaces++) { const uint32_t surfaceRandomSeed = 23456 + surfaces; auto surfaceGradientEntity = CreateTestRandomGradientEntity(worldBounds, surfaceRandomSeed); auto shapeRequests = CreateMockShape(worldBounds, surfaceGradientEntity->GetId()); ActivateEntity(surfaceGradientEntity.get()); // Give each gradient a new surface tag surfaceConfig.m_gradientSurfaceMappings.emplace_back( surfaceGradientEntity->GetId(), SurfaceData::SurfaceTag(AZStd::string::format("test%u", surfaces))); surfaceGradientEntities.emplace_back(AZStd::move(surfaceGradientEntity)); surfaceGradientShapeRequests.emplace_back(AZStd::move(shapeRequests)); } // Create a single Terrain Layer Spawner that covers the entire terrain world bounds // (Do this *after* creating and activating the height and surface gradients) auto testLayerSpawnerEntity = CreateTestLayerSpawnerEntity(worldBounds, heightGradientEntity->GetId(), surfaceConfig); auto spawnerShapeRequests = CreateMockShape(worldBounds, testLayerSpawnerEntity->GetId()); ActivateEntity(testLayerSpawnerEntity.get()); // Create the terrain system (do this after creating the terrain layer entity to ensure that we don't need any data refreshes) auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution, worldBounds); // Call the terrain API we're testing for every height and width in our ranges. for ([[maybe_unused]] auto stateIterator : state) { ApiCaller(queryResolution, worldBounds, sampler); } testLayerSpawnerEntity.reset(); spawnerShapeRequests.reset(); heightGradientEntity.reset(); heightGradientShapeRequests.reset(); surfaceGradientEntities.clear(); surfaceGradientShapeRequests.clear(); } void GenerateInputPositionsList(float queryResolution, const AZ::Aabb& worldBounds, AZStd::vector& positions) { const size_t numSamplesX = aznumeric_cast(ceil(worldBounds.GetExtents().GetX() / queryResolution)); const size_t numSamplesY = aznumeric_cast(ceil(worldBounds.GetExtents().GetY() / queryResolution)); for (size_t y = 0; y < numSamplesY; y++) { float fy = aznumeric_cast(worldBounds.GetMin().GetY() + (y * queryResolution)); for (size_t x = 0; x < numSamplesX; x++) { float fx = aznumeric_cast(worldBounds.GetMin().GetX() + (x * queryResolution)); positions.emplace_back(fx, fy, 0.0f); } } } protected: AZStd::unique_ptr m_app; }; BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_GetHeight)(benchmark::State& state) { // Run the benchmark RunTerrainApiBenchmark( state, []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { float worldMinZ = worldBounds.GetMin().GetZ(); for (float y = worldBounds.GetMin().GetY(); y < worldBounds.GetMax().GetY(); y += 1.0f) { for (float x = worldBounds.GetMin().GetX(); x < worldBounds.GetMax().GetX(); x += 1.0f) { float terrainHeight = worldMinZ; bool terrainExists = false; AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( terrainHeight, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y, sampler, &terrainExists); benchmark::DoNotOptimize(terrainHeight); } } }); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_GetHeight) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessHeightsRegion)(benchmark::State& state) { // Run the benchmark RunTerrainApiBenchmark( state, []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { auto perPositionCallback = []([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex, const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists) { benchmark::DoNotOptimize(surfacePoint.m_position.GetZ()); }; AZ::Vector2 stepSize = AZ::Vector2(queryResolution); AzFramework::Terrain::TerrainDataRequestBus::Broadcast( &AzFramework::Terrain::TerrainDataRequests::ProcessHeightsFromRegion, worldBounds, stepSize, perPositionCallback, sampler); } ); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessHeightsRegion) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessHeightsList)(benchmark::State& state) { // Run the benchmark RunTerrainApiBenchmark( state, [this]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { AZStd::vector inPositions; GenerateInputPositionsList(queryResolution, worldBounds, inPositions); auto perPositionCallback = [](const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists) { benchmark::DoNotOptimize(surfacePoint.m_position.GetZ()); }; AzFramework::Terrain::TerrainDataRequestBus::Broadcast( &AzFramework::Terrain::TerrainDataRequests::ProcessHeightsFromList, inPositions, perPositionCallback, sampler); } ); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessHeightsList) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_GetNormal)(benchmark::State& state) { // Run the benchmark RunTerrainApiBenchmark( state, []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { for (float y = worldBounds.GetMin().GetY(); y < worldBounds.GetMax().GetY(); y += 1.0f) { for (float x = worldBounds.GetMin().GetX(); x < worldBounds.GetMax().GetX(); x += 1.0f) { AZ::Vector3 terrainNormal; bool terrainExists = false; AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( terrainNormal, &AzFramework::Terrain::TerrainDataRequests::GetNormalFromFloats, x, y, sampler, &terrainExists); benchmark::DoNotOptimize(terrainNormal); } } }); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_GetNormal) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessNormalsRegion)(benchmark::State& state) { // Run the benchmark RunTerrainApiBenchmark( state, []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { auto perPositionCallback = []([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex, const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists) { benchmark::DoNotOptimize(surfacePoint.m_normal); }; AZ::Vector2 stepSize = AZ::Vector2(queryResolution); AzFramework::Terrain::TerrainDataRequestBus::Broadcast( &AzFramework::Terrain::TerrainDataRequests::ProcessNormalsFromRegion, worldBounds, stepSize, perPositionCallback, sampler); } ); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessNormalsRegion) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessNormalsList)(benchmark::State& state) { // Run the benchmark RunTerrainApiBenchmark( state, [this]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { AZStd::vector inPositions; GenerateInputPositionsList(queryResolution, worldBounds, inPositions); auto perPositionCallback = [](const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists) { benchmark::DoNotOptimize(surfacePoint.m_normal); }; AzFramework::Terrain::TerrainDataRequestBus::Broadcast( &AzFramework::Terrain::TerrainDataRequests::ProcessNormalsFromList, inPositions, perPositionCallback, sampler); } ); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessNormalsList) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_GetSurfaceWeights)(benchmark::State& state) { // Run the benchmark RunTerrainApiBenchmark( state, []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { AzFramework::SurfaceData::SurfaceTagWeightList surfaceWeights; for (float y = worldBounds.GetMin().GetY(); y < worldBounds.GetMax().GetY(); y += 1.0f) { for (float x = worldBounds.GetMin().GetX(); x < worldBounds.GetMax().GetX(); x += 1.0f) { bool terrainExists = false; AzFramework::Terrain::TerrainDataRequestBus::Broadcast( &AzFramework::Terrain::TerrainDataRequests::GetSurfaceWeightsFromFloats, x, y, surfaceWeights, sampler, &terrainExists); benchmark::DoNotOptimize(surfaceWeights); } } }); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_GetSurfaceWeights) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 1024, 2, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 2, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 1024, 4, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 4, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfaceWeightsRegion)(benchmark::State& state) { // Run the benchmark RunTerrainApiBenchmark( state, []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { auto perPositionCallback = []([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex, const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists) { benchmark::DoNotOptimize(surfacePoint.m_surfaceTags); }; AZ::Vector2 stepSize = AZ::Vector2(queryResolution); AzFramework::Terrain::TerrainDataRequestBus::Broadcast( &AzFramework::Terrain::TerrainDataRequests::ProcessSurfaceWeightsFromRegion, worldBounds, stepSize, perPositionCallback, sampler); } ); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfaceWeightsRegion) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 1024, 2, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 2, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 1024, 4, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 4, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfaceWeightsList)(benchmark::State& state) { // Run the benchmark RunTerrainApiBenchmark( state, [this]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { AZStd::vector inPositions; GenerateInputPositionsList(queryResolution, worldBounds, inPositions); auto perPositionCallback = [](const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists) { benchmark::DoNotOptimize(surfacePoint.m_surfaceTags); }; AzFramework::Terrain::TerrainDataRequestBus::Broadcast( &AzFramework::Terrain::TerrainDataRequests::ProcessSurfaceWeightsFromList, inPositions, perPositionCallback, sampler); } ); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfaceWeightsList) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 1024, 2, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 2, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 1024, 4, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 4, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_GetSurfacePoints)(benchmark::State& state) { // Run the benchmark RunTerrainApiBenchmark( state, []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { AzFramework::SurfaceData::SurfacePoint surfacePoint; for (float y = worldBounds.GetMin().GetY(); y < worldBounds.GetMax().GetY(); y += 1.0f) { for (float x = worldBounds.GetMin().GetX(); x < worldBounds.GetMax().GetX(); x += 1.0f) { bool terrainExists = false; AzFramework::Terrain::TerrainDataRequestBus::Broadcast( &AzFramework::Terrain::TerrainDataRequests::GetSurfacePointFromFloats, x, y, surfacePoint, sampler, &terrainExists); benchmark::DoNotOptimize(surfacePoint); } } }); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_GetSurfacePoints) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfacePointsRegion)(benchmark::State& state) { // Run the benchmark RunTerrainApiBenchmark( state, []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { auto perPositionCallback = []([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex, const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists) { benchmark::DoNotOptimize(surfacePoint); }; AZ::Vector2 stepSize = AZ::Vector2(queryResolution); AzFramework::Terrain::TerrainDataRequestBus::Broadcast( &AzFramework::Terrain::TerrainDataRequests::ProcessSurfacePointsFromRegion, worldBounds, stepSize, perPositionCallback, sampler); } ); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfacePointsRegion) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfacePointsList)(benchmark::State& state) { // Run the benchmark RunTerrainApiBenchmark( state, [this]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { AZStd::vector inPositions; GenerateInputPositionsList(queryResolution, worldBounds, inPositions); auto perPositionCallback = [](const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists) { benchmark::DoNotOptimize(surfacePoint); }; AzFramework::Terrain::TerrainDataRequestBus::Broadcast( &AzFramework::Terrain::TerrainDataRequests::ProcessSurfacePointsFromList, inPositions, perPositionCallback, sampler); } ); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfacePointsList) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) }) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_GetClosestIntersectionRandom)(benchmark::State& state) { // Run the benchmark const uint32_t numRays = aznumeric_cast(state.range(1)); RunTerrainApiBenchmark( state, [numRays]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, [[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { // Cast rays starting at random positions above the terrain, // and ending at a random positions below the terrain. AZ::SimpleLcgRandom random; AzFramework::RenderGeometry::RayRequest ray; AzFramework::RenderGeometry::RayResult result; for (uint32_t i = 0; i < numRays; ++i) { ray.m_startWorldPosition.SetX(worldBounds.GetMin().GetX() + (random.GetRandomFloat() * worldBounds.GetXExtent())); ray.m_startWorldPosition.SetY(worldBounds.GetMin().GetY() + (random.GetRandomFloat() * worldBounds.GetYExtent())); ray.m_startWorldPosition.SetZ(worldBounds.GetMax().GetZ()); ray.m_endWorldPosition.SetX(worldBounds.GetMin().GetX() + (random.GetRandomFloat() * worldBounds.GetXExtent())); ray.m_endWorldPosition.SetY(worldBounds.GetMin().GetY() + (random.GetRandomFloat() * worldBounds.GetYExtent())); ray.m_endWorldPosition.SetZ(worldBounds.GetMin().GetZ()); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( result, &AzFramework::Terrain::TerrainDataRequests::GetClosestIntersection, ray); } }); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_GetClosestIntersectionRandom) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 1024, 10, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 10, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 4096, 10, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 1024, 100, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 100, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 4096, 100, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 1024, 1000, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1000, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 4096, 1000, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_GetClosestIntersectionWorstCase)(benchmark::State& state) { // Run the benchmark const uint32_t numRays = aznumeric_cast(state.range(1)); RunTerrainApiBenchmark( state, [numRays]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, [[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { // Cast rays starting at an upper corner of the terrain world, // and ending at the opposite top corner of the terrain world, // traversing the entire grid without finding an intersection. AzFramework::RenderGeometry::RayRequest ray; AzFramework::RenderGeometry::RayResult result; ray.m_startWorldPosition = worldBounds.GetMax(); ray.m_endWorldPosition = worldBounds.GetMin(); ray.m_endWorldPosition.SetZ(ray.m_startWorldPosition.GetZ()); for (uint32_t i = 0; i < numRays; ++i) { AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( result, &AzFramework::Terrain::TerrainDataRequests::GetClosestIntersection, ray); } }); } BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_GetClosestIntersectionWorstCase) ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 1024, 10, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 10, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 4096, 10, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 1024, 100, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 100, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 4096, 100, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 1024, 1000, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1000, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 4096, 1000, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); #endif }