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.
314 lines
14 KiB
C++
314 lines
14 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 <AzTest/AzTest.h>
|
|
#include <Common/RPITestFixture.h>
|
|
#include <Common/JsonTestUtils.h>
|
|
#include <Common/ErrorMessageFinder.h>
|
|
#include <Common/ShaderAssetTestUtils.h>
|
|
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
|
|
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
|
|
#include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h>
|
|
#include <Atom/RPI.Reflect/Material/MaterialAssetCreator.h>
|
|
#include <Atom/RPI.Edit/Material/MaterialFunctorSourceData.h>
|
|
#include <Atom/RPI.Public/Material/Material.h>
|
|
#include <Material/MaterialAssetTestUtils.h>
|
|
|
|
namespace UnitTest
|
|
{
|
|
using namespace AZ;
|
|
using namespace RPI;
|
|
|
|
class MaterialFunctorTests
|
|
: public RPITestFixture
|
|
{
|
|
public:
|
|
class SetShaderOptionFunctor final
|
|
: public MaterialFunctor
|
|
{
|
|
public:
|
|
AZ_RTTI(SetShaderOptionFunctor, "{6316D98D-D2DD-4E9C-808C-58118DC9FF73}", MaterialFunctor);
|
|
|
|
SetShaderOptionFunctor(size_t shaderIndex, ShaderOptionIndex shaderOptionIndex, ShaderOptionValue shaderOptionValue)
|
|
: m_shaderIndex(shaderIndex)
|
|
, m_shaderOptionIndex(shaderOptionIndex)
|
|
, m_shaderOptionValue(shaderOptionValue)
|
|
{
|
|
}
|
|
|
|
using MaterialFunctor::Process;
|
|
void Process(MaterialFunctor::RuntimeContext& context) override
|
|
{
|
|
m_processResult = context.SetShaderOptionValue(0, m_shaderOptionIndex, m_shaderOptionValue);
|
|
}
|
|
|
|
// Note a real functor wouldn't do this, it's just for testing
|
|
bool GetProcessResult()
|
|
{
|
|
return m_processResult;
|
|
}
|
|
|
|
private:
|
|
size_t m_shaderIndex;
|
|
ShaderOptionIndex m_shaderOptionIndex;
|
|
ShaderOptionValue m_shaderOptionValue;
|
|
bool m_processResult = false;
|
|
};
|
|
|
|
class PropertyDependencyTestFunctor final
|
|
: public MaterialFunctor
|
|
{
|
|
public:
|
|
MOCK_METHOD0(ProcessCalled, void());
|
|
|
|
using MaterialFunctor::Process;
|
|
void Process(RuntimeContext& context) override
|
|
{
|
|
ProcessCalled();
|
|
|
|
context.GetMaterialPropertyValue<int32_t>(m_registedPropertyIndex);
|
|
context.GetMaterialPropertyValue<int32_t>(m_registedPropertyName);
|
|
// Should report error in the call.
|
|
context.GetMaterialPropertyValue<int32_t>(m_unregistedPropertyIndex);
|
|
context.GetMaterialPropertyValue<int32_t>(m_unregistedPropertyName);
|
|
}
|
|
|
|
MaterialPropertyIndex m_registedPropertyIndex;
|
|
MaterialPropertyIndex m_unregistedPropertyIndex;
|
|
|
|
AZ::Name m_registedPropertyName;
|
|
AZ::Name m_unregistedPropertyName;
|
|
};
|
|
|
|
class PropertyDependencyTestFunctorSourceData final
|
|
: public MaterialFunctorSourceData
|
|
{
|
|
public:
|
|
using MaterialFunctorSourceData::CreateFunctor;
|
|
FunctorResult CreateFunctor(const RuntimeContext& context) const override
|
|
{
|
|
Ptr<PropertyDependencyTestFunctor> functor = aznew PropertyDependencyTestFunctor;
|
|
|
|
functor->m_registedPropertyIndex = context.FindMaterialPropertyIndex(Name{ m_registedPropertyName });
|
|
EXPECT_TRUE(!functor->m_registedPropertyIndex.IsNull());
|
|
AddMaterialPropertyDependency(functor, functor->m_registedPropertyIndex);
|
|
|
|
functor->m_unregistedPropertyIndex = context.FindMaterialPropertyIndex(Name{ m_unregistedPropertyName });
|
|
EXPECT_TRUE(!functor->m_unregistedPropertyIndex.IsNull());
|
|
// Intended missing registration to m_materialPropertyDependencies
|
|
|
|
functor->m_registedPropertyName = m_registedPropertyName;
|
|
functor->m_unregistedPropertyName = m_unregistedPropertyName;
|
|
|
|
return Success(Ptr<MaterialFunctor>(functor));
|
|
}
|
|
AZStd::string m_registedPropertyName;
|
|
AZStd::string m_unregistedPropertyName;
|
|
};
|
|
|
|
protected:
|
|
void SetUp() override
|
|
{
|
|
RPITestFixture::SetUp();
|
|
}
|
|
|
|
void TearDown() override
|
|
{
|
|
RPITestFixture::TearDown();
|
|
}
|
|
};
|
|
|
|
TEST_F(MaterialFunctorTests, MaterialFunctor_RuntimeContext_ShaderOptionNotOwned)
|
|
{
|
|
using namespace AZ::RPI;
|
|
|
|
AZStd::vector<RPI::ShaderOptionValuePair> boolOptionValues = CreateBoolShaderOptionValues();
|
|
|
|
AZ::RPI::Ptr<AZ::RPI::ShaderOptionGroupLayout> shaderOptions = RPI::ShaderOptionGroupLayout::Create();
|
|
shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_optionA"}, ShaderOptionType::Boolean, 0, 0, boolOptionValues, Name{"False"}});
|
|
shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_optionB"}, ShaderOptionType::Boolean, 1, 1, boolOptionValues, Name{"False"}});
|
|
shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_optionC"}, ShaderOptionType::Boolean, 2, 2, boolOptionValues, Name{"False"}});
|
|
shaderOptions->Finalize();
|
|
|
|
Data::Asset<MaterialTypeAsset> materialTypeAsset;
|
|
|
|
// Note we don't actually need any properties or functors in the material type. We just need to set up some sample data
|
|
// structures that we can pass to the functors below, especially the shader with shader options.
|
|
MaterialTypeAssetCreator materialTypeCreator;
|
|
materialTypeCreator.Begin(Uuid::CreateRandom());
|
|
materialTypeCreator.AddShader(CreateTestShaderAsset(Uuid::CreateRandom(), CreateCommonTestMaterialSrgLayout(), shaderOptions));
|
|
// We claim ownership of options A and B, but not C. So C is a globally accessible option, not owned by the material.
|
|
materialTypeCreator.ClaimShaderOptionOwnership(Name{"o_optionA"});
|
|
materialTypeCreator.ClaimShaderOptionOwnership(Name{"o_optionB"});
|
|
EXPECT_TRUE(materialTypeCreator.End(materialTypeAsset));
|
|
|
|
SetShaderOptionFunctor testFunctorSetOptionA{0, ShaderOptionIndex{0}, ShaderOptionValue{1}};
|
|
SetShaderOptionFunctor testFunctorSetOptionB{0, ShaderOptionIndex{1}, ShaderOptionValue{1}};
|
|
SetShaderOptionFunctor testFunctorSetOptionC{0, ShaderOptionIndex{2}, ShaderOptionValue{1}};
|
|
SetShaderOptionFunctor testFunctorSetOptionInvalid{0, ShaderOptionIndex{3}, ShaderOptionValue{1}};
|
|
|
|
|
|
// Most of this data can be empty since this particular functor doesn't access it.
|
|
AZStd::vector<MaterialPropertyValue> unusedPropertyValues;
|
|
ShaderResourceGroup* unusedSrg = nullptr;
|
|
ShaderCollection shaderCollectionCopy = materialTypeAsset->GetShaderCollection();
|
|
|
|
{
|
|
// Successfully set o_optionA
|
|
MaterialFunctor::RuntimeContext runtimeContext = MaterialFunctor::RuntimeContext{
|
|
unusedPropertyValues,
|
|
materialTypeAsset->GetMaterialPropertiesLayout(),
|
|
&shaderCollectionCopy,
|
|
unusedSrg,
|
|
&testFunctorSetOptionA.GetMaterialPropertyDependencies(),
|
|
AZ::RPI::MaterialPropertyPsoHandling::Allowed
|
|
};
|
|
testFunctorSetOptionA.Process(runtimeContext);
|
|
EXPECT_TRUE(testFunctorSetOptionA.GetProcessResult());
|
|
EXPECT_EQ(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{ 0 }).GetIndex());
|
|
EXPECT_NE(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{ 1 }).GetIndex());
|
|
EXPECT_NE(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{ 2 }).GetIndex());
|
|
}
|
|
|
|
{
|
|
// Successfully set o_optionB
|
|
MaterialFunctor::RuntimeContext runtimeContext = MaterialFunctor::RuntimeContext{
|
|
unusedPropertyValues,
|
|
materialTypeAsset->GetMaterialPropertiesLayout(),
|
|
&shaderCollectionCopy,
|
|
unusedSrg,
|
|
&testFunctorSetOptionB.GetMaterialPropertyDependencies(),
|
|
AZ::RPI::MaterialPropertyPsoHandling::Allowed
|
|
};
|
|
testFunctorSetOptionB.Process(runtimeContext);
|
|
EXPECT_TRUE(testFunctorSetOptionB.GetProcessResult());
|
|
EXPECT_EQ(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{ 0 }).GetIndex());
|
|
EXPECT_EQ(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{ 1 }).GetIndex());
|
|
EXPECT_NE(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{ 2 }).GetIndex());
|
|
}
|
|
|
|
{
|
|
// Fail to set o_optionC because it is not owned by the material type
|
|
AZ_TEST_START_TRACE_SUPPRESSION;
|
|
MaterialFunctor::RuntimeContext runtimeContext = MaterialFunctor::RuntimeContext{
|
|
unusedPropertyValues,
|
|
materialTypeAsset->GetMaterialPropertiesLayout(),
|
|
&shaderCollectionCopy,
|
|
unusedSrg,
|
|
&testFunctorSetOptionC.GetMaterialPropertyDependencies(),
|
|
AZ::RPI::MaterialPropertyPsoHandling::Allowed
|
|
};
|
|
testFunctorSetOptionC.Process(runtimeContext);
|
|
EXPECT_FALSE(testFunctorSetOptionC.GetProcessResult());
|
|
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
|
}
|
|
|
|
{
|
|
// Fail to set option index that is out of range
|
|
AZ_TEST_START_TRACE_SUPPRESSION;
|
|
MaterialFunctor::RuntimeContext runtimeContext = MaterialFunctor::RuntimeContext{
|
|
unusedPropertyValues,
|
|
materialTypeAsset->GetMaterialPropertiesLayout(),
|
|
&shaderCollectionCopy,
|
|
unusedSrg,
|
|
&testFunctorSetOptionInvalid.GetMaterialPropertyDependencies(),
|
|
AZ::RPI::MaterialPropertyPsoHandling::Allowed
|
|
};
|
|
testFunctorSetOptionInvalid.Process(runtimeContext);
|
|
EXPECT_FALSE(testFunctorSetOptionInvalid.GetProcessResult());
|
|
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
|
}
|
|
|
|
EXPECT_EQ(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{0}).GetIndex());
|
|
EXPECT_EQ(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{1}).GetIndex());
|
|
EXPECT_NE(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{2}).GetIndex());
|
|
}
|
|
|
|
TEST_F(MaterialFunctorTests, ReprocessTest)
|
|
{
|
|
Data::Asset<MaterialTypeAsset> m_testMaterialTypeAsset;
|
|
Data::Asset<MaterialAsset> m_testMaterialAsset;
|
|
|
|
AZ::Name registedPropertyName("PropA");
|
|
AZ::Name unregistedPropertyName("PropB");
|
|
AZ::Name unrelatedPropertyName("PropC");
|
|
|
|
MaterialTypeAssetCreator materialTypeCreator;
|
|
materialTypeCreator.Begin(Uuid::CreateRandom());
|
|
|
|
materialTypeCreator.BeginMaterialProperty(registedPropertyName, AZ::RPI::MaterialPropertyDataType::Int);
|
|
materialTypeCreator.EndMaterialProperty();
|
|
materialTypeCreator.BeginMaterialProperty(unregistedPropertyName, AZ::RPI::MaterialPropertyDataType::Int);
|
|
materialTypeCreator.EndMaterialProperty();
|
|
materialTypeCreator.BeginMaterialProperty(unrelatedPropertyName, AZ::RPI::MaterialPropertyDataType::Int);
|
|
materialTypeCreator.EndMaterialProperty();
|
|
|
|
materialTypeCreator.SetPropertyValue(registedPropertyName, 42);
|
|
materialTypeCreator.SetPropertyValue(unregistedPropertyName, 42);
|
|
materialTypeCreator.SetPropertyValue(unrelatedPropertyName, 42);
|
|
|
|
PropertyDependencyTestFunctorSourceData functorSourceData;
|
|
functorSourceData.m_registedPropertyName = registedPropertyName.GetStringView();
|
|
functorSourceData.m_unregistedPropertyName = unregistedPropertyName.GetStringView();
|
|
|
|
MaterialFunctorSourceData::FunctorResult result = functorSourceData.CreateFunctor(
|
|
MaterialFunctorSourceData::RuntimeContext(
|
|
"Dummy.materialtype",
|
|
materialTypeCreator.GetMaterialPropertiesLayout(),
|
|
materialTypeCreator.GetMaterialShaderResourceGroupLayout(),
|
|
materialTypeCreator.GetShaderCollection()
|
|
)
|
|
);
|
|
|
|
EXPECT_TRUE(result.IsSuccess());
|
|
Ptr<MaterialFunctor>& functor = result.GetValue();
|
|
EXPECT_TRUE(functor != nullptr);
|
|
materialTypeCreator.AddMaterialFunctor(functor);
|
|
materialTypeCreator.End(m_testMaterialTypeAsset);
|
|
|
|
MaterialAssetCreator materialCreator;
|
|
materialCreator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, true);
|
|
materialCreator.SetPropertyValue(registedPropertyName, 42);
|
|
materialCreator.SetPropertyValue(unregistedPropertyName, 42);
|
|
materialCreator.SetPropertyValue(unrelatedPropertyName, 42);
|
|
materialCreator.End(m_testMaterialAsset);
|
|
|
|
EXPECT_TRUE(m_testMaterialAsset->GetMaterialFunctors().size() == 1u);
|
|
PropertyDependencyTestFunctor* testFunctor = static_cast<PropertyDependencyTestFunctor*>(m_testMaterialAsset->GetMaterialFunctors()[0].get());
|
|
|
|
ErrorMessageFinder errorMessageFinder;
|
|
|
|
// Expect creation will call functor process once.
|
|
EXPECT_CALL(*testFunctor, ProcessCalled()).Times(1);
|
|
// Suppress 1 error as we know an unregistered dependent property will be accessed.
|
|
errorMessageFinder.Reset();
|
|
errorMessageFinder.AddExpectedErrorMessage("Material functor accessing an unregistered material property", 2);
|
|
Data::Instance<Material> material = Material::FindOrCreate(m_testMaterialAsset);
|
|
errorMessageFinder.CheckExpectedErrorsFound();
|
|
|
|
material->SetPropertyValue(material->FindPropertyIndex(registedPropertyName), int32_t(24));
|
|
|
|
// Expect dependent property change will call functor process once.
|
|
EXPECT_CALL(*testFunctor, ProcessCalled()).Times(1);
|
|
// Suppress 1 error as we know an unregistered dependent property will be accessed.
|
|
errorMessageFinder.Reset();
|
|
errorMessageFinder.AddExpectedErrorMessage("Material functor accessing an unregistered material property", 2);
|
|
material->Compile();
|
|
errorMessageFinder.CheckExpectedErrorsFound();
|
|
|
|
// Expect unrelated property change won't call functor process.
|
|
material->SetPropertyValue(material->FindPropertyIndex(unrelatedPropertyName), int32_t(24));
|
|
|
|
EXPECT_CALL(*testFunctor, ProcessCalled()).Times(0);
|
|
material->Compile();
|
|
|
|
m_testMaterialTypeAsset = {};
|
|
m_testMaterialAsset = {};
|
|
}
|
|
}
|