/* * 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 #include #include #include #include #include #include #include #include #include namespace UnitTest { using namespace AZ; using namespace AZ::RPI; class SceneTests : public RPITestFixture { protected: AzFramework::OctreeSystemComponent* m_octreeSystemComponent; void SetUp() override { RPITestFixture::SetUp(); m_octreeSystemComponent = new AzFramework::OctreeSystemComponent; TestFeatureProcessor1::Reflect(GetSerializeContext()); TestFeatureProcessor2::Reflect(GetSerializeContext()); TestFeatureProcessorImplementation::Reflect(GetSerializeContext()); TestFeatureProcessorImplementation2::Reflect(GetSerializeContext()); AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessor(); AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessor(); AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessorWithInterface(); AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessorWithInterface(); } void TearDown() override { AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor(); AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor(); AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor(); AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor(); delete m_octreeSystemComponent; RPITestFixture::TearDown(); } }; /** * Unit test to test RPI::Scene's feature processor management functions */ TEST_F(SceneTests, FeatureProcessorManagement) { // Create scene with two test feature processors SceneDescriptor sceneDesc; sceneDesc.m_featureProcessorNames.push_back(AZStd::string(TestFeatureProcessor1::RTTI_TypeName())); ScenePtr testScene = Scene::CreateScene(sceneDesc); EXPECT_TRUE(testScene->GetFeatureProcessor(FeatureProcessorId{ TestFeatureProcessor1::RTTI_TypeName() }) != nullptr); EXPECT_TRUE(testScene->GetFeatureProcessor(FeatureProcessorId{ TestFeatureProcessor2::RTTI_TypeName() }) == nullptr); testScene->DisableAllFeatureProcessors(); EXPECT_TRUE(testScene->GetFeatureProcessor(FeatureProcessorId{ TestFeatureProcessor1::RTTI_TypeName() }) == nullptr); // Enable feature processors FeatureProcessor* testFeature1 = testScene->EnableFeatureProcessor(FeatureProcessorId{ TestFeatureProcessor1::RTTI_TypeName() }); EXPECT_TRUE(testFeature1 != nullptr); TestFeatureProcessor2* testFeature2 = testScene->EnableFeatureProcessor(); EXPECT_TRUE(testFeature2 != nullptr); testScene->DisableFeatureProcessor< TestFeatureProcessor1>(); EXPECT_TRUE(testScene->GetFeatureProcessor(FeatureProcessorId{ TestFeatureProcessor1::RTTI_TypeName() }) == nullptr); testScene->DisableFeatureProcessor(FeatureProcessorId{ testFeature2->RTTI_GetTypeName() }); EXPECT_TRUE(testScene->GetFeatureProcessor< TestFeatureProcessor2>() == nullptr); } /** * Unit test to test RPI::Scene's render pipeline management functions */ TEST_F(SceneTests, RenderPipelineManagement) { RenderPipelineId pipelineId1 = RenderPipelineId("pipeline1"); RenderPipelineId pipelineId2 = RenderPipelineId("pipeline2"); RenderPipelineDescriptor pipelineDesc; pipelineDesc.m_name = pipelineId1.GetCStr(); RenderPipelinePtr pipeline1 = RenderPipeline::CreateRenderPipeline(pipelineDesc); RenderPipelinePtr pipeline2 = RenderPipeline::CreateRenderPipeline(pipelineDesc); SceneDescriptor sceneDesc; ScenePtr testScene = Scene::CreateScene(sceneDesc); testScene->AddRenderPipeline(pipeline1); EXPECT_TRUE(testScene->GetRenderPipeline(pipelineId1) == pipeline1); EXPECT_TRUE(testScene->GetRenderPipeline(pipelineId2) == nullptr); // Asset false with pipeline have same name. And the pipeline won't be added AZ_TEST_START_ASSERTTEST; testScene->AddRenderPipeline(pipeline2); AZ_TEST_STOP_ASSERTTEST(1); EXPECT_TRUE(testScene->GetRenderPipeline(pipelineId2) == nullptr); pipelineDesc.m_name = pipelineId2.GetCStr(); pipeline2 = RenderPipeline::CreateRenderPipeline(pipelineDesc); testScene->AddRenderPipeline(pipeline2); EXPECT_TRUE(testScene->GetRenderPipeline(pipelineId2) != nullptr); EXPECT_TRUE(pipeline2->GetId() == pipelineId2); EXPECT_TRUE(testScene->GetRenderPipeline(pipelineId2) == pipeline2); // create another pipeline with same name as pipeline2 RenderPipelinePtr pipeline3 = RenderPipeline::CreateRenderPipeline(pipelineDesc); AZ_TEST_START_ASSERTTEST; pipeline3->RemoveFromScene(); AZ_TEST_STOP_ASSERTTEST(1); EXPECT_TRUE(testScene->GetRenderPipeline(pipelineId2) == pipeline2); pipeline2->RemoveFromScene(); EXPECT_TRUE(testScene->GetRenderPipeline(pipelineId2) == nullptr); } TEST_F(SceneTests, SceneNotificationTest) { // Create scene with feature processor which enables scene notification handler SceneDescriptor sceneDesc; sceneDesc.m_featureProcessorNames.push_back(AZStd::string(TestFeatureProcessor1::RTTI_TypeName())); ScenePtr testScene = Scene::CreateScene(sceneDesc); testScene->Activate(); auto feature = testScene->GetFeatureProcessor< TestFeatureProcessor1>(); RenderPipelineId pipelineId1 = RenderPipelineId("pipeline1"); RenderPipelineId pipelineId2 = RenderPipelineId("pipeline2"); RenderPipelineDescriptor pipelineDesc; pipelineDesc.m_name = pipelineId1.GetCStr(); RenderPipelinePtr pipeline1 = RenderPipeline::CreateRenderPipeline(pipelineDesc); pipelineDesc.m_name = pipelineId2.GetCStr(); RenderPipelinePtr pipeline2 = RenderPipeline::CreateRenderPipeline(pipelineDesc); // Add one render pipeline testScene->AddRenderPipeline(pipeline1); EXPECT_TRUE(feature->m_pipelineCount == 1); EXPECT_TRUE(pipeline1.get() == feature->m_lastPipeline); // Add another render pipeline testScene->AddRenderPipeline(pipeline2); EXPECT_TRUE(pipeline2.get() == feature->m_lastPipeline); EXPECT_TRUE(feature->m_pipelineCount == 2); // Remove the first render pipeline which was added testScene->RemoveRenderPipeline(pipelineId1); EXPECT_TRUE(feature->m_pipelineCount == 1); EXPECT_TRUE(pipeline1.get() == feature->m_lastPipeline); // Create a raster pass with viewTag "mainCamera" const RPI::PipelineViewTag viewTag{ "mainCamera" }; PassDescriptor passDesc; AZStd::shared_ptr passTemplate = AZStd::make_shared(); AZStd::shared_ptr passData = AZStd::make_shared(); passData->m_drawListTag = "forward"; passData->m_pipelineViewTag = viewTag.GetCStr(); passTemplate->m_passData = passData; passDesc.m_passName = "raster"; passDesc.m_passTemplate = passTemplate; Ptr newPass = RasterPass::Create(passDesc); // Add new pass to render pipeline which should trigger render pipeline pass modify notification pipeline2->GetRootPass()->AddChild(newPass); // This function is called in every RPISystem render tick. We call it manually here to trigger the pass change notification pipeline2->OnPassModified(); EXPECT_TRUE(feature->m_pipelineChangedCount == 1); EXPECT_TRUE(pipeline2.get() == feature->m_lastPipeline); ViewPtr view = View::CreateView(AZ::Name("TestView"), RPI::View::UsageCamera); pipeline2->SetPersistentView(viewTag, view); EXPECT_TRUE(feature->m_viewSetCount == 1); pipeline2->SetPersistentView(viewTag, nullptr); EXPECT_TRUE(feature->m_viewSetCount == 2); testScene->Deactivate(); } TEST_F(SceneTests, SceneNotificationTest_ConnectAfterRenderPipelineAdded) { // Create scene with feature processor which enables scene notification handler SceneDescriptor sceneDesc; ScenePtr testScene = Scene::CreateScene(sceneDesc); testScene->Activate(); RenderPipelineId pipelineId1 = RenderPipelineId("pipeline1"); RenderPipelineId pipelineId2 = RenderPipelineId("pipeline2"); RenderPipelineDescriptor pipelineDesc; pipelineDesc.m_name = pipelineId1.GetCStr(); RenderPipelinePtr pipeline1 = RenderPipeline::CreateRenderPipeline(pipelineDesc); pipelineDesc.m_name = pipelineId2.GetCStr(); RenderPipelinePtr pipeline2 = RenderPipeline::CreateRenderPipeline(pipelineDesc); // Do some render pipeline operations and keep render pipeline1 added testScene->AddRenderPipeline(pipeline1); testScene->AddRenderPipeline(pipeline2); testScene->RemoveRenderPipeline(pipelineId2); // Create a raster pass with viewTag "mainCamera" const RPI::PipelineViewTag viewTag{ "mainCamera" }; PassDescriptor passDesc; AZStd::shared_ptr passTemplate = AZStd::make_shared(); AZStd::shared_ptr passData = AZStd::make_shared(); passData->m_drawListTag = "forward"; passData->m_pipelineViewTag = viewTag.GetCStr(); passTemplate->m_passData = passData; passDesc.m_passName = "raster"; passDesc.m_passTemplate = passTemplate; Ptr newPass = RasterPass::Create(passDesc); // Add new pass to render pipeline which should trigger render pipeline pass modify notification pipeline1->GetRootPass()->AddChild(newPass); // This function is called in every RPISystem render tick. We call it manually here to trigger the pass change notification pipeline1->OnPassModified(); // Set view to pipeline ViewPtr view = View::CreateView(AZ::Name("TestView"), RPI::View::UsageCamera); pipeline1->SetPersistentView(viewTag, view); // Enable the feature processor which has notification handler enabled TestFeatureProcessor1* feature = testScene->EnableFeatureProcessor(); EXPECT_TRUE(feature->m_pipelineCount == 1); EXPECT_TRUE(feature->m_lastPipeline == pipeline1.get()); EXPECT_TRUE(feature->m_viewSetCount == 1); EXPECT_TRUE(feature->m_pipelineChangedCount == 0); testScene->Deactivate(); } TEST_F(SceneTests, GetFeatureProcessor_ByInterface_CanModifyFeatureProcessorViaInterface) { // Create scene with one test feature processor SceneDescriptor sceneDesc; sceneDesc.m_featureProcessorNames.push_back(AZStd::string(TestFeatureProcessorImplementation::RTTI_TypeName())); ScenePtr testScene = Scene::CreateScene(sceneDesc); testScene->Activate(); TestFeatureProcessorInterface* featureProcessorInterface = testScene->GetFeatureProcessor(); EXPECT_TRUE(featureProcessorInterface != nullptr); // Check that the pointer is valid int testValue = 7; featureProcessorInterface->SetValue(testValue); EXPECT_EQ(featureProcessorInterface->GetValue(), testValue); // Check that changes made by the interface apply to the underlying implemented feature processor TestFeatureProcessorImplementation* featureProcessorImplementation = testScene->GetFeatureProcessor(); EXPECT_TRUE(featureProcessorInterface == featureProcessorImplementation); EXPECT_TRUE(featureProcessorImplementation->GetValue() == testValue); // Check that changes made by the implementation apply to the interface int anotherTestValue = 21; featureProcessorImplementation->SetValue(anotherTestValue); EXPECT_TRUE(featureProcessorInterface->GetValue() == anotherTestValue); // Check that the feature processor can be disabled testScene->DisableFeatureProcessor(); EXPECT_TRUE(testScene->GetFeatureProcessor() == nullptr); EXPECT_TRUE(testScene->GetFeatureProcessor() == nullptr); testScene->Deactivate(); } TEST_F(SceneTests, GetFeatureProcessorByNameId_UsingStringForFeatureProcessorId_ReturnsValidFeatureProcessor) { // Create scene with one test feature processor SceneDescriptor sceneDesc; sceneDesc.m_featureProcessorNames.push_back(AZStd::string(TestFeatureProcessor1::RTTI_TypeName())); ScenePtr testScene = Scene::CreateScene(sceneDesc); testScene->Activate(); EXPECT_TRUE(testScene->GetFeatureProcessor(FeatureProcessorId{ "TestFeatureProcessor1" }) != nullptr); testScene->Deactivate(); } // // Two implementations of the same interface // TEST_F(SceneTests, EnableDisableFeatureProcessorByNameId_MultipleImplmentationsOfTheSameInterface_ReturnsValidFeatureProcessor) { SceneDescriptor sceneDesc; ScenePtr testScene = Scene::CreateScene(sceneDesc); testScene->Activate(); // You can enable either implementation, as long as they are not both active in the same scene FeatureProcessor* firstImplementation = testScene->EnableFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorImplementation::RTTI_TypeName() }); EXPECT_TRUE(firstImplementation != nullptr); testScene->DisableFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorImplementation::RTTI_TypeName() }); FeatureProcessor* secondImplementation = testScene->EnableFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorImplementation2::RTTI_TypeName() }); EXPECT_TRUE(secondImplementation != nullptr); testScene->Deactivate(); } TEST_F(SceneTests, EnableDisableFeatureProcessorByType_MultipleImplmentationsOfTheSameInterface_ReturnsValidFeatureProcessor) { SceneDescriptor sceneDesc; ScenePtr testScene = Scene::CreateScene(sceneDesc); testScene->Activate(); // You can enable either implementation, as long as they are not both active in the same scene FeatureProcessor* firstImplementation = testScene->EnableFeatureProcessor(); EXPECT_TRUE(firstImplementation != nullptr); testScene->DisableFeatureProcessor(); FeatureProcessor* secondImplementation = testScene->EnableFeatureProcessor(); EXPECT_TRUE(secondImplementation != nullptr); testScene->Deactivate(); } TEST_F(SceneTests, GetFeatureProcessorByNameId_MultipleImplmentationsOfTheSameInterface_ReturnsValidFeatureProcessor) { SceneDescriptor sceneDesc; ScenePtr testScene = Scene::CreateScene(sceneDesc); testScene->Activate(); // You can get a feature processor via its name ID, no matter which implementation is enabled by the scene FeatureProcessor* firstImplementation = testScene->EnableFeatureProcessor(); FeatureProcessor* featureProcessor = testScene->GetFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorImplementation::RTTI_TypeName() }); EXPECT_TRUE(firstImplementation == featureProcessor); testScene->DisableFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorImplementation::RTTI_TypeName() }); FeatureProcessor* secondImplementation = testScene->EnableFeatureProcessor(); featureProcessor = testScene->GetFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorImplementation2::RTTI_TypeName() }); EXPECT_TRUE(secondImplementation == featureProcessor); testScene->Deactivate(); } TEST_F(SceneTests, GetFeatureProcessorByInterface_MultipleImplmentationsOfTheSameInterface_ReturnsValidFeatureProcessor) { SceneDescriptor sceneDesc; ScenePtr testScene = Scene::CreateScene(sceneDesc); testScene->Activate(); // You can get a feature processor via its interface, no matter which implementation is enabled by the scene // as long as only one is enabled by the scene FeatureProcessor* firstImplementation = testScene->EnableFeatureProcessor(); TestFeatureProcessorInterface* featureProcessorInterface = testScene->GetFeatureProcessor(); EXPECT_TRUE(firstImplementation == featureProcessorInterface); testScene->DisableFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorImplementation::RTTI_TypeName() }); FeatureProcessor* secondImplementation = testScene->EnableFeatureProcessor(); featureProcessorInterface = testScene->GetFeatureProcessor(); EXPECT_TRUE(secondImplementation == featureProcessorInterface); testScene->Deactivate(); } // // Invalid cases // TEST_F(SceneTests, EnableFeatureProcessor_ByInterfaceName_FailsToEnable) { SceneDescriptor sceneDesc; ScenePtr testScene = Scene::CreateScene(sceneDesc); testScene->Activate(); AZ_TEST_START_ASSERTTEST; FeatureProcessor* featureProcessor = testScene->EnableFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorInterface::RTTI_TypeName() }); AZ_TEST_STOP_ASSERTTEST(1); EXPECT_TRUE(featureProcessor == nullptr); EXPECT_TRUE(testScene->GetFeatureProcessor() == nullptr); EXPECT_TRUE(testScene->GetFeatureProcessor() == nullptr); testScene->Deactivate(); } TEST_F(SceneTests, DisableFeatureProcessor_ByInterface_FailsToDisable) { SceneDescriptor sceneDesc; sceneDesc.m_featureProcessorNames.push_back(AZStd::string(TestFeatureProcessorImplementation::RTTI_TypeName())); ScenePtr testScene = Scene::CreateScene(sceneDesc); testScene->Activate(); testScene->DisableFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorInterface::RTTI_TypeName() }); EXPECT_TRUE(testScene->GetFeatureProcessor() != nullptr); EXPECT_TRUE(testScene->GetFeatureProcessor() != nullptr); testScene->Deactivate(); } TEST_F(SceneTests, EnableFeatureProcessor_MultipleImplmentationsOfTheSameInterface_FailsToEnable) { SceneDescriptor sceneDesc; ScenePtr testScene = Scene::CreateScene(sceneDesc); testScene->Activate(); // You can enable one implementation of a feature processor on a scene FeatureProcessor* firstImplementation = testScene->EnableFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorImplementation::RTTI_TypeName() }); EXPECT_TRUE(firstImplementation != nullptr); // But you can't enable two implementations of the same interface in one scene at the same time. // Otherwise, when you get a feature processor by its interface, the scene wouldn't know which one to return. AZ_TEST_START_ASSERTTEST; FeatureProcessor* secondImplementation = testScene->EnableFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorImplementation2::RTTI_TypeName() }); AZ_TEST_STOP_ASSERTTEST(1); // If another implementation that uses the same interface exists, that will be the feature processor that is returned EXPECT_TRUE(secondImplementation == firstImplementation); testScene->Deactivate(); } } // namespace UnitTest