You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/RPI/Code/Tests/System/SceneTests.cpp

440 lines
21 KiB
C++

/*
* 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 <Atom/RPI.Public/Base.h>
#include <Atom/RPI.Public/FeatureProcessor.h>
#include <Atom/RPI.Public/FeatureProcessorFactory.h>
#include <Atom/RPI.Public/Pass/RasterPass.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/View.h>
#include <Atom/RPI.Reflect/Pass/RasterPassData.h>
#include <AzFramework/Visibility/OctreeSystemComponent.h>
#include <AzTest/AzTest.h>
#include <Common/RPITestFixture.h>
#include <Common/SerializeTester.h>
#include <Common/ErrorMessageFinder.h>
#include <Common/TestFeatureProcessors.h>
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<TestFeatureProcessor1>();
AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessor<TestFeatureProcessor2>();
AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessorWithInterface<TestFeatureProcessorImplementation, TestFeatureProcessorInterface>();
AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessorWithInterface<TestFeatureProcessorImplementation2, TestFeatureProcessorInterface>();
}
void TearDown() override
{
AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor<TestFeatureProcessor1>();
AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor<TestFeatureProcessor2>();
AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor<TestFeatureProcessorImplementation>();
AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor<TestFeatureProcessorImplementation2>();
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<TestFeatureProcessor2>();
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> passTemplate = AZStd::make_shared<PassTemplate>();
AZStd::shared_ptr<RasterPassData> passData = AZStd::make_shared<RasterPassData>();
passData->m_drawListTag = "forward";
passData->m_pipelineViewTag = viewTag.GetCStr();
passTemplate->m_passData = passData;
passDesc.m_passName = "raster";
passDesc.m_passTemplate = passTemplate;
Ptr<RasterPass> 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> passTemplate = AZStd::make_shared<PassTemplate>();
AZStd::shared_ptr<RasterPassData> passData = AZStd::make_shared<RasterPassData>();
passData->m_drawListTag = "forward";
passData->m_pipelineViewTag = viewTag.GetCStr();
passTemplate->m_passData = passData;
passDesc.m_passName = "raster";
passDesc.m_passTemplate = passTemplate;
Ptr<RasterPass> 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<TestFeatureProcessor1>();
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<TestFeatureProcessorInterface>();
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<TestFeatureProcessorImplementation>();
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<TestFeatureProcessorImplementation>();
EXPECT_TRUE(testScene->GetFeatureProcessor<TestFeatureProcessorInterface>() == nullptr);
EXPECT_TRUE(testScene->GetFeatureProcessor<TestFeatureProcessorImplementation>() == 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<TestFeatureProcessorImplementation>();
EXPECT_TRUE(firstImplementation != nullptr);
testScene->DisableFeatureProcessor<TestFeatureProcessorImplementation>();
FeatureProcessor* secondImplementation = testScene->EnableFeatureProcessor<TestFeatureProcessorImplementation2>();
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<TestFeatureProcessorImplementation>();
FeatureProcessor* featureProcessor = testScene->GetFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorImplementation::RTTI_TypeName() });
EXPECT_TRUE(firstImplementation == featureProcessor);
testScene->DisableFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorImplementation::RTTI_TypeName() });
FeatureProcessor* secondImplementation = testScene->EnableFeatureProcessor<TestFeatureProcessorImplementation2>();
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<TestFeatureProcessorImplementation>();
TestFeatureProcessorInterface* featureProcessorInterface = testScene->GetFeatureProcessor<TestFeatureProcessorInterface>();
EXPECT_TRUE(firstImplementation == featureProcessorInterface);
testScene->DisableFeatureProcessor(FeatureProcessorId{ TestFeatureProcessorImplementation::RTTI_TypeName() });
FeatureProcessor* secondImplementation = testScene->EnableFeatureProcessor<TestFeatureProcessorImplementation2>();
featureProcessorInterface = testScene->GetFeatureProcessor<TestFeatureProcessorInterface>();
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<TestFeatureProcessorImplementation>() == nullptr);
EXPECT_TRUE(testScene->GetFeatureProcessor<TestFeatureProcessorInterface>() == 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<TestFeatureProcessorImplementation>() != nullptr);
EXPECT_TRUE(testScene->GetFeatureProcessor<TestFeatureProcessorInterface>() != 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