Files
o3de/Gems/Atom/RPI/Code/Tests/Pass/PassTests.cpp
T
Qing Tao fe8dac7989 ATOM-16489 Add find passes functions for Scene or RenderPipeline in PassSystemInterface (#4739)
* ATOM-16489 Add find passes functions for Scene or RenderPipeline in PassSystemInterface
Introduced new PassSystemInterface::ForEachPass() funtion to replace PassSystemInterface::FindPasses(), PassSystemInterface::GetPassesByTemplateName and ParentPass::FindPassByNameRecursive() functions.
Update all the places which were using those three functions.
The new pass finding filter support any combination of pass name, pass template name, pass class type, pass hirechary, owner scene, owner render pipeline.
Update unit tests.

Signed-off-by: Qing Tao <qingtao@amazon.com>
2021-10-25 10:07:55 -07:00

776 lines
32 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.Reflect/Pass/ComputePassData.h>
#include <Atom/RPI.Reflect/Pass/PassRequest.h>
#include <Atom/RPI.Reflect/Pass/PassTemplate.h>
#include <Atom/RPI.Public/Pass/ComputePass.h>
#include <Atom/RPI.Public/Pass/CopyPass.h>
#include <Atom/RPI.Public/Pass/ParentPass.h>
#include <Atom/RPI.Public/Pass/Pass.h>
#include <Atom/RPI.Public/Pass/PassFactory.h>
#include <Atom/RPI.Public/Pass/PassFilter.h>
#include <Atom/RPI.Public/Pass/PassSystem.h>
#include <Atom/RPI.Public/Pass/RasterPass.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <Common/RPITestFixture.h>
namespace UnitTest
{
using namespace AZ;
using namespace RPI;
// This class holds and sets up some data for the tests
// This is it's own class so we can delete it before the teardown phase, otherwise
// name dictionary fails because we have remaining names in the PassTemplates
struct PassTestData
{
AZ_CLASS_ALLOCATOR(PassTestData, SystemAllocator, 0);
PassTemplate m_parentPass;
// Child passes
PassTemplate m_depthPrePass;
PassTemplate m_lightCullPass;
PassTemplate m_forwardPass;
PassTemplate m_postProcessPass;
static const size_t NumberOfChildPasses = 4;
void CreatePassTemplates()
{
// Depth Pre Pass Dummy...
{
m_depthPrePass.m_name = "DepthPrePass";
m_depthPrePass.m_passClass = "Pass";
{
PassSlot slot;
slot.m_name = "DepthOutput";
slot.m_slotType = RPI::PassSlotType::Output;
slot.m_scopeAttachmentUsage = RHI::ScopeAttachmentUsage::RenderTarget;
m_depthPrePass.AddSlot(slot);
}
{
PassImageAttachmentDesc depthBuffer;
depthBuffer.m_name = "DepthBuffer";
depthBuffer.m_imageDescriptor = RHI::ImageDescriptor::Create2D(RHI::ImageBindFlags::DepthStencil | RHI::ImageBindFlags::ShaderReadWrite, 1600, 900, RHI::Format::D32_FLOAT_S8X24_UINT);
depthBuffer.m_lifetime = RHI::AttachmentLifetimeType::Transient;
m_depthPrePass.AddImageAttachment(depthBuffer);
}
{
PassConnection connection;
connection.m_localSlot = "DepthOutput";
connection.m_attachmentRef.m_pass = "This";
connection.m_attachmentRef.m_attachment = "DepthBuffer";
m_depthPrePass.AddOutputConnection(connection);
}
}
// Light Culling Pass Dummy...
{
m_lightCullPass.m_name = "LightCullPass";
m_lightCullPass.m_passClass = "Pass";
{
PassSlot slot;
slot.m_name = "LightListOutput";
slot.m_slotType = RPI::PassSlotType::Output;
slot.m_scopeAttachmentUsage = RHI::ScopeAttachmentUsage::Shader;
m_lightCullPass.AddSlot(slot);
}
{
PassBufferAttachmentDesc lightListBuffer;
lightListBuffer.m_name = "LightList";
lightListBuffer.m_lifetime = RHI::AttachmentLifetimeType::Transient;
m_lightCullPass.AddBufferAttachment(lightListBuffer);
}
{
PassConnection connection;
connection.m_localSlot = "LightListOutput";
connection.m_attachmentRef.m_pass = "This";
connection.m_attachmentRef.m_attachment = "LightList";
m_lightCullPass.AddOutputConnection(connection);
}
}
// Forward Pass Dummy...
{
m_forwardPass.m_name = "ForwardPass";
m_forwardPass.m_passClass = "Pass";
{
PassSlot slot;
slot.m_name = "DepthInputOutput";
slot.m_slotType = RPI::PassSlotType::InputOutput;
slot.m_scopeAttachmentUsage = RHI::ScopeAttachmentUsage::DepthStencil;
slot.m_formatFilter.push_back(RHI::Format::D24_UNORM_S8_UINT);
slot.m_formatFilter.push_back(RHI::Format::D24_UNORM_S8_UINT);
slot.m_formatFilter.push_back(RHI::Format::D32_FLOAT_S8X24_UINT);
m_forwardPass.AddSlot(slot);
}
{
PassSlot slot;
slot.m_name = "LightListInput";
slot.m_slotType = RPI::PassSlotType::Input;
m_forwardPass.AddSlot(slot);
}
{
PassSlot slot;
slot.m_name = "LightingOutput";
slot.m_slotType = RPI::PassSlotType::Output;
slot.m_scopeAttachmentUsage = RHI::ScopeAttachmentUsage::RenderTarget;
m_forwardPass.AddSlot(slot);
}
{
PassImageAttachmentDesc lightingOutput;
lightingOutput.m_name = "LightingBuffer";
lightingOutput.m_imageDescriptor.m_format = RHI::Format::R16G16B16A16_FLOAT;
lightingOutput.m_lifetime = RHI::AttachmentLifetimeType::Transient;
lightingOutput.m_sizeSource.m_source.m_pass = "This";
lightingOutput.m_sizeSource.m_source.m_attachment = "DepthInputOutput";
lightingOutput.m_sizeSource.m_multipliers.m_widthMultiplier = 1.0f;
lightingOutput.m_sizeSource.m_multipliers.m_heightMultiplier = 1.0f;
m_forwardPass.AddImageAttachment(lightingOutput);
}
{
PassConnection connection;
connection.m_localSlot = "LightingOutput";
connection.m_attachmentRef.m_pass = "This";
connection.m_attachmentRef.m_attachment = "LightingBuffer";
m_forwardPass.AddOutputConnection(connection);
}
}
// Post Process Pass Dummy...
{
m_postProcessPass.m_name = "PostProcessPass";
m_postProcessPass.m_passClass = "Pass";
{
PassSlot slot;
slot.m_name = "DepthInput";
slot.m_slotType = RPI::PassSlotType::Input;
slot.m_scopeAttachmentUsage = RHI::ScopeAttachmentUsage::Shader;
slot.m_formatFilter.push_back(RHI::Format::D24_UNORM_S8_UINT);
slot.m_formatFilter.push_back(RHI::Format::D24_UNORM_S8_UINT);
slot.m_formatFilter.push_back(RHI::Format::D32_FLOAT_S8X24_UINT);
m_postProcessPass.AddSlot(slot);
}
{
PassSlot slot;
slot.m_name = "LightingInput";
slot.m_slotType = RPI::PassSlotType::Input;
slot.m_scopeAttachmentUsage = RHI::ScopeAttachmentUsage::Shader;
slot.m_formatFilter.push_back(RHI::Format::R16G16B16A16_FLOAT);
m_postProcessPass.AddSlot(slot);
}
{
PassSlot slot;
slot.m_name = "FinalOutput";
slot.m_slotType = RPI::PassSlotType::Output;
slot.m_scopeAttachmentUsage = RHI::ScopeAttachmentUsage::Shader;
m_postProcessPass.AddSlot(slot);
}
{
PassImageAttachmentDesc finalImage;
finalImage.m_name = "FinalImage";
finalImage.m_lifetime = RHI::AttachmentLifetimeType::Transient;
finalImage.m_formatSource.m_pass = "This";
finalImage.m_formatSource.m_attachment = "LightingInput";
finalImage.m_sizeSource.m_source.m_pass = "This";
finalImage.m_sizeSource.m_source.m_attachment = "LightingInput";
finalImage.m_sizeSource.m_multipliers.m_widthMultiplier = 0.5f;
finalImage.m_sizeSource.m_multipliers.m_heightMultiplier = 0.5f;
m_postProcessPass.AddImageAttachment(finalImage);
}
{
PassConnection connection;
connection.m_localSlot = "FinalOutput";
connection.m_attachmentRef.m_pass = "This";
connection.m_attachmentRef.m_attachment = "FinalImage";
m_postProcessPass.AddOutputConnection(connection);
}
}
// Parent Pass Dummy...
{
m_parentPass.m_name = "ParentPass";
m_parentPass.m_passClass = "ParentPass";
{
PassSlot slot;
slot.m_name = "Output";
slot.m_slotType = RPI::PassSlotType::Output;
m_parentPass.AddSlot(slot);
}
{
PassRequest request;
request.m_passName = "DepthPrePass";
request.m_templateName = "DepthPrePass";
m_parentPass.AddPassRequest(request);
}
{
PassRequest request;
request.m_passName = "LightCullPass";
request.m_templateName = "LightCullPass";
m_parentPass.AddPassRequest(request);
}
{
PassRequest request;
request.m_passName = "ForwardPass";
request.m_templateName = "ForwardPass";
PassConnection connection;
connection.m_localSlot = "DepthInputOutput";
connection.m_attachmentRef.m_pass = "DepthPrePass";
connection.m_attachmentRef.m_attachment = "DepthOutput";
request.AddInputConnection(connection);
connection.m_localSlot = "LightListInput";
connection.m_attachmentRef.m_pass = "LightCullPass";
connection.m_attachmentRef.m_attachment = "LightListOutput";
request.AddInputConnection(connection);
m_parentPass.AddPassRequest(request);
}
{
PassRequest request;
request.m_passName = "PostProcessPass";
request.m_templateName = "PostProcessPass";
PassConnection connection;
connection.m_localSlot = "DepthInput";
connection.m_attachmentRef.m_pass = "ForwardPass";
connection.m_attachmentRef.m_attachment = "DepthInputOutput";
request.AddInputConnection(connection);
connection.m_localSlot = "LightingInput";
connection.m_attachmentRef.m_pass = "ForwardPass";
connection.m_attachmentRef.m_attachment = "LightingOutput";
request.AddInputConnection(connection);
m_parentPass.AddPassRequest(request);
}
{
PassConnection connection;
connection.m_localSlot = "Output";
connection.m_attachmentRef.m_pass = "PostProcessPass";
connection.m_attachmentRef.m_attachment = "FinalOutput";
m_parentPass.AddOutputConnection(connection);
}
}
}
void AddPassTemplatesToLibrary()
{
PassSystemInterface* passSystem = PassSystemInterface::Get();
passSystem->AddPassTemplate(Name("DepthPrePass"), m_depthPrePass.Clone());
passSystem->AddPassTemplate(Name("LightCullPass"), m_lightCullPass.Clone());
passSystem->AddPassTemplate(Name("ForwardPass"), m_forwardPass.Clone());
passSystem->AddPassTemplate(Name("PostProcessPass"), m_postProcessPass.Clone());
passSystem->AddPassTemplate(Name("ParentPass"), m_parentPass.Clone());
}
};
class PassTests
: public RPITestFixture
{
protected:
static Ptr<Pass> Create(const PassDescriptor& descriptor)
{
Ptr<Pass> pass = aznew Pass(descriptor);
return pass;
}
// Setup functions...
void SetUp() override
{
RPITestFixture::SetUp();
m_passSystem = azrtti_cast<PassSystem*>(PassSystemInterface::Get());
m_data = aznew PassTestData();
// We don't ever instantiate the base Pass class in the runtime, however we use it here to facilitate testing
m_passSystem->AddPassCreator(Name("Pass"), &PassTests::Create);
m_data->CreatePassTemplates();
}
void TearDown() override
{
// Delete PassTemplates so Names are released
delete m_data;
RPITestFixture::TearDown();
}
// Members...
PassSystem* m_passSystem;
PassTestData* m_data;
// Tests...
// Tests that we can build the passes outlined in PassTestData and that the validation of the pass hierarchy returns no errors
void TestPassConstructionAndValidation()
{
m_data->AddPassTemplatesToLibrary();
Ptr<Pass> parentPass = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("ParentPass"));
parentPass->Reset();
parentPass->Build();
PassValidationResults validationResults;
parentPass->Validate(validationResults);
EXPECT_TRUE(validationResults.IsValid());
EXPECT_TRUE(parentPass->m_name == Name("ParentPass"));
EXPECT_TRUE(parentPass->AsParent()->GetChildren().size() == PassTestData::NumberOfChildPasses);
}
// Tests that validation correctly fails when a connected slot does not match any of the format filters
void TestFormatFilterFailure()
{
AZ_TEST_START_TRACE_SUPPRESSION;
// Set the format filter to block the connected attachment
m_data->m_postProcessPass.m_slots[1].m_formatFilter.clear();
m_data->m_postProcessPass.m_slots[1].m_formatFilter.push_back(RHI::Format::R8G8B8A8_SNORM);
m_data->AddPassTemplatesToLibrary();
Ptr<Pass> parentPass = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("ParentPass"));
parentPass->Reset();
parentPass->Build();
PassValidationResults validationResults;
parentPass->Validate(validationResults);
EXPECT_FALSE(validationResults.IsValid());
EXPECT_EQ(1, validationResults.m_passesWithErrors.size());
AZ_TEST_STOP_TRACE_SUPPRESSION(2);
}
// Tests that validation correctly fails when connection's local slot name is set to a garbage value
void TestInvalidLocalSlotName()
{
AZ_TEST_START_TRACE_SUPPRESSION;
// Set the connection's local slot name to a garbage value
m_data->m_parentPass.m_passRequests[3].m_connections[1].m_localSlot = "NonExistantName";
m_data->AddPassTemplatesToLibrary();
Ptr<Pass> parentPass = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("ParentPass"));
parentPass->Reset();
parentPass->Build();
PassValidationResults validationResults;
parentPass->Validate(validationResults);
EXPECT_FALSE(validationResults.IsValid());
EXPECT_EQ(1, validationResults.m_passesWithErrors.size());
AZ_TEST_STOP_TRACE_SUPPRESSION(2);
}
// Tests that validation correctly fails when connection's target slot name is set to a garbage value
void TestInvalidConnectedSlotName()
{
AZ_TEST_START_TRACE_SUPPRESSION;
// Set the connection's target slot name to a garbage value
m_data->m_parentPass.m_passRequests[3].m_connections[1].m_attachmentRef.m_attachment = "NonExistantName";
m_data->AddPassTemplatesToLibrary();
Ptr<Pass> parentPass = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("ParentPass"));
parentPass->m_flags.m_partOfHierarchy = true;
parentPass->OnHierarchyChange();
parentPass->Reset();
parentPass->Build();
PassValidationResults validationResults;
parentPass->Validate(validationResults);
EXPECT_FALSE(validationResults.IsValid());
EXPECT_EQ(1, validationResults.m_passesWithErrors.size());
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
// Tests that validation correctly fails when connection's target slot name is set to a garbage value
void TestInvalidConnectedPassName()
{
AZ_TEST_START_TRACE_SUPPRESSION;
// Set the connection's target pass name to a garbage value
m_data->m_parentPass.m_passRequests[3].m_connections[1].m_attachmentRef.m_pass = "NonExistantName";
m_data->AddPassTemplatesToLibrary();
Ptr<Pass> parentPass = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("ParentPass"));
parentPass->m_flags.m_partOfHierarchy = true;
parentPass->OnHierarchyChange();
parentPass->Reset();
parentPass->Build();
PassValidationResults validationResults;
parentPass->Validate(validationResults);
EXPECT_FALSE(validationResults.IsValid());
EXPECT_EQ(1, validationResults.m_passesWithErrors.size());
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
// Tests that validation correctly fails when connection's slots are mismatched
void TestSlotTypeMismatch()
{
AZ_TEST_START_TRACE_SUPPRESSION;
// Set one of the inputs to be connected to another input, which is invalid
m_data->m_parentPass.m_passRequests[3].m_connections[1].m_attachmentRef.m_attachment = "LightListInput";
m_data->AddPassTemplatesToLibrary();
Ptr<Pass> parentPass = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("ParentPass"));
parentPass->m_flags.m_partOfHierarchy = true;
parentPass->OnHierarchyChange();
parentPass->Reset();
parentPass->Build();
PassValidationResults validationResults;
parentPass->Validate(validationResults);
EXPECT_FALSE(validationResults.IsValid());
EXPECT_EQ(1, validationResults.m_passesWithErrors.size());
AZ_TEST_STOP_TRACE_SUPPRESSION(2);
}
// Tests that validation correctly fails when parent-child connection's slots are mismatched (mismatches are different for parent to child connections)
void TestParentChildSlotTypeMismatch()
{
AZ_TEST_START_TRACE_SUPPRESSION;
// Set parent output to be connect to child input, which is invalid
m_data->m_parentPass.m_connections[0].m_attachmentRef.m_attachment = "LightingInput";
m_data->AddPassTemplatesToLibrary();
Ptr<Pass> parentPass = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("ParentPass"));
parentPass->m_flags.m_partOfHierarchy = true;
parentPass->OnHierarchyChange();
parentPass->Reset();
parentPass->Build();
PassValidationResults validationResults;
parentPass->Validate(validationResults);
EXPECT_FALSE(validationResults.IsValid());
EXPECT_EQ(1, validationResults.m_passesWithErrors.size());
AZ_TEST_STOP_TRACE_SUPPRESSION(2);
}
};
TEST_F(PassTests, ConstructionAndValidation)
{
TestPassConstructionAndValidation();
}
TEST_F(PassTests, FormatFilterFailure)
{
TestFormatFilterFailure();
}
TEST_F(PassTests, InvalidLocalSlotName)
{
TestInvalidLocalSlotName();
}
TEST_F(PassTests, InvalidConnectedSlotName)
{
TestInvalidConnectedSlotName();
}
TEST_F(PassTests, InvalidConnectedPassName)
{
TestInvalidConnectedPassName();
}
TEST_F(PassTests, SlotTypeMismatch)
{
TestSlotTypeMismatch();
}
TEST_F(PassTests, ParentChildSlotTypeMismatch)
{
TestParentChildSlotTypeMismatch();
}
// Checks that pass factory has all the default name
TEST_F(PassTests, FactoryDefaultCreators)
{
EXPECT_TRUE(m_passSystem->HasCreatorForClass(Name("ParentPass")) );
EXPECT_TRUE(m_passSystem->HasCreatorForClass(Name("RasterPass")) );
EXPECT_TRUE(m_passSystem->HasCreatorForClass(Name("CopyPass")) );
EXPECT_TRUE(m_passSystem->HasCreatorForClass(Name("FullScreenTriangle")) );
EXPECT_TRUE(m_passSystem->HasCreatorForClass(Name("ComputePass")) );
EXPECT_TRUE(m_passSystem->HasCreatorForClass(Name("MSAAResolvePass")) );
EXPECT_TRUE(m_passSystem->HasCreatorForClass(Name("DownsampleMipChainPass")) );
}
// Tests that all creation methods return null with invalid arguments
TEST_F(PassTests, CreationMethodsFailure)
{
Name doesNotExist("doesNotExist");
Ptr<Pass> pass;
AZStd::shared_ptr<PassTemplate> nullTemplate = nullptr;
AZ_TEST_START_TRACE_SUPPRESSION;
pass = m_passSystem->CreatePassFromClass(doesNotExist, doesNotExist);
EXPECT_TRUE(pass == nullptr);
pass = m_passSystem->CreatePassFromTemplate(doesNotExist, doesNotExist);
EXPECT_TRUE(pass == nullptr);
pass = m_passSystem->CreatePassFromTemplate(nullTemplate, doesNotExist);
EXPECT_TRUE(pass == nullptr);
pass = m_passSystem->CreatePassFromRequest(nullptr);
EXPECT_TRUE(pass == nullptr);
AZ_TEST_STOP_TRACE_SUPPRESSION(4);
}
// Tests that all creation methods successfully create passes with valid arguments
TEST_F(PassTests, CreationMethodsSuccess)
{
m_data->AddPassTemplatesToLibrary();
Ptr<Pass> pass = m_passSystem->CreatePassFromClass(Name("Pass"), Name("Test01"));
EXPECT_TRUE(pass != nullptr);
pass = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("Test02"));
EXPECT_TRUE(pass != nullptr);
AZStd::shared_ptr<PassTemplate> parentPassTemplate = m_data->m_parentPass.Clone();
pass = m_passSystem->CreatePassFromTemplate(parentPassTemplate, Name("Test03"));
EXPECT_TRUE(pass != nullptr);
PassRequest request;
request.m_passName = "Test04";
request.m_templateName = "ParentPass";
pass = m_passSystem->CreatePassFromRequest(&request);
EXPECT_TRUE(pass != nullptr);
pass = m_passSystem->CreatePass<CopyPass>(Name("Test05"));
EXPECT_TRUE(pass != nullptr);
}
TEST_F(PassTests, PassFilter_PassHierarchy)
{
m_data->AddPassTemplatesToLibrary();
// create a pass tree
Ptr<Pass> pass = m_passSystem->CreatePassFromClass(Name("Pass"), Name("pass1"));
Ptr<Pass> parent1 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent1"));
Ptr<Pass> parent2 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent2"));
Ptr<Pass> parent3 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent3"));
parent3->AsParent()->AddChild(parent2);
parent2->AsParent()->AddChild(parent1);
parent1->AsParent()->AddChild(pass);
{
// Filter with pass hierarchy which has only one element
PassFilter filter = PassFilter::CreateWithPassHierarchy({Name("pass1")});
EXPECT_TRUE(filter.Matches(pass.get()));
}
{
// Filter with empty pass hierarchy, triggers one assert
AZ_TEST_START_TRACE_SUPPRESSION;
PassFilter filter = PassFilter::CreateWithPassHierarchy(AZStd::vector<Name>{});
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
{
// Filters with partial hierarchy by using string vector
AZStd::vector<AZStd::string> passHierarchy1 = { "parent1", "pass1" };
PassFilter filter1 = PassFilter::CreateWithPassHierarchy(passHierarchy1);
EXPECT_TRUE(filter1.Matches(pass.get()));
AZStd::vector<AZStd::string> passHierarchy2 = { "parent2", "pass1" };
PassFilter filter2 = PassFilter::CreateWithPassHierarchy(passHierarchy2);
EXPECT_TRUE(filter2.Matches(pass.get()));
AZStd::vector<AZStd::string> passHierarchy3 = { "parent3", "parent2", "pass1" };
PassFilter filter3 = PassFilter::CreateWithPassHierarchy(passHierarchy3);
EXPECT_TRUE(filter3.Matches(pass.get()));
}
{
// Filters with partial hierarchy by using Name vector
AZStd::vector<Name> passHierarchy1 = { Name("parent1"), Name("pass1") };
PassFilter filter1 = PassFilter::CreateWithPassHierarchy(passHierarchy1);
EXPECT_TRUE(filter1.Matches(pass.get()));
AZStd::vector<Name> passHierarchy2 = { Name("parent2"), Name("pass1")};
PassFilter filter2 = PassFilter::CreateWithPassHierarchy(passHierarchy2);
EXPECT_TRUE(filter2.Matches(pass.get()));
AZStd::vector<Name> passHierarchy3 = { Name("parent3"), Name("parent2"), Name("pass1") };
PassFilter filter3 = PassFilter::CreateWithPassHierarchy(passHierarchy3);
EXPECT_TRUE(filter3.Matches(pass.get()));
}
{
// Find non-leaf pass
PassFilter filter1 = PassFilter::CreateWithPassHierarchy(AZStd::vector<AZStd::string>{"parent3", "parent1"});
EXPECT_TRUE(filter1.Matches(parent1.get()));
PassFilter filter2 = PassFilter::CreateWithPassHierarchy({ Name("parent1") });
EXPECT_TRUE(filter2.Matches(parent1.get()));
EXPECT_FALSE(filter2.Matches(pass.get()));
}
{
// Failed to find pass
// Mis-matching hierarchy
PassFilter filter1 = PassFilter::CreateWithPassHierarchy(AZStd::vector<AZStd::string>{"Parent1", "Parent3", "pass1"});
EXPECT_FALSE(filter1.Matches(pass.get()));
// Mis-matching name
PassFilter filter2 = PassFilter::CreateWithPassHierarchy(AZStd::vector<AZStd::string>{"Parent1", "pass1"});
EXPECT_FALSE(filter2.Matches(parent1.get()));
}
}
TEST_F(PassTests, PassFilter_Empty_Success)
{
m_data->AddPassTemplatesToLibrary();
// create a pass tree
Ptr<Pass> pass = m_passSystem->CreatePassFromClass(Name("Pass"), Name("pass1"));
Ptr<Pass> parent1 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent1"));
Ptr<Pass> parent2 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent2"));
Ptr<Pass> parent3 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent3"));
parent3->AsParent()->AddChild(parent2);
parent2->AsParent()->AddChild(parent1);
parent1->AsParent()->AddChild(pass);
PassFilter filter;
// Any pass can match an empty filter
EXPECT_TRUE(filter.Matches(pass.get()));
EXPECT_TRUE(filter.Matches(parent1.get()));
EXPECT_TRUE(filter.Matches(parent2.get()));
EXPECT_TRUE(filter.Matches(parent3.get()));
}
TEST_F(PassTests, PassFilter_PassClass_Success)
{
m_data->AddPassTemplatesToLibrary();
// create a pass tree
Ptr<Pass> pass = m_passSystem->CreatePassFromClass(Name("Pass"), Name("pass1"));
Ptr<Pass> depthPass = m_passSystem->CreatePassFromTemplate(Name("DepthPrePass"), Name("depthPass"));
Ptr<Pass> parent1 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent1"));
parent1->AsParent()->AddChild(pass);
parent1->AsParent()->AddChild(depthPass);
PassFilter filter1 = PassFilter::CreateWithPassClass<Pass>();
EXPECT_TRUE(filter1.Matches(pass.get()));
EXPECT_FALSE(filter1.Matches(parent1.get()));
PassFilter filter2 = PassFilter::CreateWithPassClass<ParentPass>();
EXPECT_FALSE(filter2.Matches(pass.get()));
EXPECT_TRUE(filter2.Matches(parent1.get()));
}
TEST_F(PassTests, PassFilter_PassTemplate_Success)
{
m_data->AddPassTemplatesToLibrary();
// create a pass tree
Ptr<Pass> childPass = m_passSystem->CreatePassFromClass(Name("Pass"), Name("pass1"));
Ptr<Pass> parent1 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent1"));
PassFilter filter1 = PassFilter::CreateWithTemplateName(Name("Pass"), (Scene*) nullptr);
// childPass doesn't have a template
EXPECT_FALSE(filter1.Matches(childPass.get()));
PassFilter filter2 = PassFilter::CreateWithTemplateName(Name("ParentPass"), (Scene*) nullptr);
EXPECT_TRUE(filter2.Matches(parent1.get()));
}
TEST_F(PassTests, ForEachPass_PassTemplateFilter_Success)
{
m_data->AddPassTemplatesToLibrary();
// create a pass tree
Ptr<Pass> pass = m_passSystem->CreatePassFromClass(Name("Pass"), Name("pass1"));
Ptr<Pass> parent1 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent1"));
Ptr<Pass> parent2 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent2"));
Ptr<Pass> parent3 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent3"));
parent3->AsParent()->AddChild(parent2);
parent2->AsParent()->AddChild(parent1);
parent1->AsParent()->AddChild(pass);
// Create render pipeline
const RPI::PipelineViewTag viewTag{ "viewTag1" };
RPI::RenderPipelineDescriptor desc;
desc.m_mainViewTagName = viewTag.GetStringView();
desc.m_name = "TestPipeline";
RPI::RenderPipelinePtr pipeline = RPI::RenderPipeline::CreateRenderPipeline(desc);
Ptr<Pass> parent4 = m_passSystem->CreatePassFromTemplate(Name("ParentPass"), Name("parent4"));
pipeline->GetRootPass()->AddChild(parent4);
Name templateName = Name("ParentPass");
PassFilter filter1 = PassFilter::CreateWithTemplateName(templateName, (RenderPipeline*)nullptr);
int count = 0;
m_passSystem->ForEachPass(filter1, [&count, templateName](RPI::Pass* pass) -> PassFilterExecutionFlow
{
EXPECT_TRUE(pass->GetPassTemplate()->m_name == templateName);
count++;
return PassFilterExecutionFlow::ContinueVisitingPasses;
});
// three from CreatePassFromTemplate() calls and one from Render Pipeline.
EXPECT_TRUE(count == 4);
count = 0;
m_passSystem->ForEachPass(filter1, [&count, templateName](RPI::Pass* pass) -> PassFilterExecutionFlow
{
EXPECT_TRUE(pass->GetPassTemplate()->m_name == templateName);
count++;
return PassFilterExecutionFlow::StopVisitingPasses;
});
EXPECT_TRUE(count == 1);
PassFilter filter2 = PassFilter::CreateWithTemplateName(templateName, pipeline.get());
count = 0;
m_passSystem->ForEachPass(filter2, [&count]([[maybe_unused]] RPI::Pass* pass) -> PassFilterExecutionFlow
{
count++;
return PassFilterExecutionFlow::ContinueVisitingPasses;
});
// only the ParentPass in the render pipeline was found
EXPECT_TRUE(count == 1);
}
}