/* * 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 namespace UnitTest { using namespace AZ; using namespace RPI; class MaterialTests : public RPITestFixture { protected: Data::Asset m_testMaterialShaderAsset; AZ::RHI::Ptr m_testMaterialSrgLayout; Data::Asset m_testMaterialTypeAsset; Data::Asset m_testMaterialAsset; Data::Asset m_testImageAsset; Data::Instance m_testImage; Data::Asset CreateTestImageAsset() const { Data::Asset testImageAsset; Data::Asset mipChainAsset; ImageMipChainAssetCreator mipChainCreator; mipChainCreator.Begin(Uuid::CreateRandom(), 1, 1); mipChainCreator.BeginMip(RHI::GetImageSubresourceLayout(RHI::Size{ 1,1,1 }, RHI::Format::R8_UNORM)); uint8_t pixel = 0; mipChainCreator.AddSubImage(&pixel, 1); mipChainCreator.EndMip(); mipChainCreator.End(mipChainAsset); StreamingImageAssetCreator imageCreator; imageCreator.Begin(Uuid::CreateRandom()); imageCreator.AddMipChainAsset(*mipChainAsset.Get()); imageCreator.SetFlags(StreamingImageFlags::NotStreamable); imageCreator.SetPoolAssetId(ImageSystemInterface::Get()->GetSystemStreamingPool()->GetAssetId()); imageCreator.End(testImageAsset); return testImageAsset; } void SetUp() override { RPITestFixture::SetUp(); m_testMaterialSrgLayout = CreateCommonTestMaterialSrgLayout(); m_testMaterialShaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), m_testMaterialSrgLayout); MaterialTypeAssetCreator materialTypeCreator; materialTypeCreator.Begin(Uuid::CreateRandom()); materialTypeCreator.AddShader(m_testMaterialShaderAsset); AddCommonTestMaterialProperties(materialTypeCreator); materialTypeCreator.SetPropertyValue(Name{ "MyFloat2" }, Vector2{ 10.1f, 10.2f }); materialTypeCreator.SetPropertyValue(Name{ "MyFloat3" }, Vector3{ 11.1f, 11.2f, 11.3f }); materialTypeCreator.SetPropertyValue(Name{ "MyFloat4" }, Vector4{ 12.1f, 12.2f, 12.3f, 12.4f }); materialTypeCreator.SetPropertyValue(Name{ "MyColor" }, Color{ 0.1f, 0.2f, 0.3f, 0.4f }); materialTypeCreator.SetPropertyValue(Name{ "MyInt" }, -12); materialTypeCreator.SetPropertyValue(Name{ "MyUInt" }, 112u); materialTypeCreator.SetPropertyValue(Name{ "MyFloat" }, 11.5f); materialTypeCreator.SetPropertyValue(Name{ "MyEnum" }, 1u); materialTypeCreator.End(m_testMaterialTypeAsset); m_testImageAsset = CreateTestImageAsset(); m_testImage = StreamingImage::FindOrCreate(m_testImageAsset); MaterialAssetCreator materialCreator; materialCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset); materialCreator.SetPropertyValue(Name{ "MyFloat2" }, Vector2{ 0.1f, 0.2f }); materialCreator.SetPropertyValue(Name{ "MyFloat3" }, Vector3{ 1.1f, 1.2f, 1.3f }); materialCreator.SetPropertyValue(Name{ "MyFloat4" }, Vector4{ 2.1f, 2.2f, 2.3f, 2.4f }); materialCreator.SetPropertyValue(Name{ "MyColor" }, Color{ 1.0f, 1.0f, 1.0f, 1.0f }); materialCreator.SetPropertyValue(Name{ "MyInt" }, -2); materialCreator.SetPropertyValue(Name{ "MyUInt" }, 12u); materialCreator.SetPropertyValue(Name{ "MyFloat" }, 1.5f); materialCreator.SetPropertyValue(Name{ "MyBool" }, true); materialCreator.SetPropertyValue(Name{ "MyImage" }, Data::Asset(m_testImageAsset)); materialCreator.SetPropertyValue(Name{ "MyEnum" }, 2u); materialCreator.End(m_testMaterialAsset); } void TearDown() override { m_testMaterialShaderAsset.Reset(); m_testMaterialSrgLayout = nullptr; m_testMaterialTypeAsset.Reset(); m_testMaterialAsset.Reset(); m_testImageAsset.Reset(); m_testImage = nullptr; RPITestFixture::TearDown(); } void ValidateInitialValuesFromMaterialType(Data::Instance material) { // Test reading the values directly... EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyBool" })), false); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" })), -12); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyUInt" })), 112u); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" })), 11.5f); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat2" })), Vector2(10.1f, 10.2f)); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat3" })), Vector3(11.1f, 11.2f, 11.3f)); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat4" })), Vector4(12.1f, 12.2f, 12.3f, 12.4f)); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyColor" })), Color(0.1f, 0.2f, 0.3f, 0.4f)); EXPECT_EQ(material->GetPropertyValue>(material->FindPropertyIndex(Name{ "MyImage" })), nullptr); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyEnum" })), 1u); // Dig in to the SRG to make sure the values were applied there as well... const RHI::ShaderResourceGroup* srg = material->GetRHIShaderResourceGroup(); const RHI::ShaderResourceGroupData& srgData = srg->GetData(); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_bool" })), false); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_int" })), -12); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_uint" })), 112u); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float" })), 11.5f); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float2" })), Vector2(10.1f, 10.2f)); // Currently srgData.GetConstant isn't supported so we check the individual floats EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float3" }), 0), 11.1f); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float3" }), 1), 11.2f); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float3" }), 2), 11.3f); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float4" })), Vector4(12.1f, 12.2f, 12.3f, 12.4f)); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_color" })), TransformColor(Color(0.1f, 0.2f, 0.3f, 0.4f), ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg)); EXPECT_EQ(srgData.GetImageView(srgData.FindShaderInputImageIndex(Name{ "m_image" }), 0), nullptr); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_enum" })), 1u); } void ValidateInitialValuesFromMaterial(Data::Instance material) { // Test reading the values directly... EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyBool" })), true); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" })), -2); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyUInt" })), 12u); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" })), 1.5f); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat2" })), Vector2(0.1f, 0.2f)); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat3" })), Vector3(1.1f, 1.2f, 1.3f)); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat4" })), Vector4(2.1f, 2.2f, 2.3f, 2.4f)); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyColor" })), Color(1.0f, 1.0f, 1.0f, 1.0f)); EXPECT_EQ(material->GetPropertyValue>(material->FindPropertyIndex(Name{ "MyImage" })), m_testImage); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyEnum" })), 2u); // Dig in to the SRG to make sure the values were applied there as well... const RHI::ShaderResourceGroup* srg = material->GetRHIShaderResourceGroup(); const RHI::ShaderResourceGroupData& srgData = srg->GetData(); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_bool" })), true); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_int" })), -2); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_uint" })), 12u); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float" })), 1.5f); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float2" })), Vector2(0.1f, 0.2f)); // Currently srgData.GetConstant isn't supported so we check the individual floats EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float3" }), 0), 1.1f); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float3" }), 1), 1.2f); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float3" }), 2), 1.3f); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float4" })), Vector4(2.1f, 2.2f, 2.3f, 2.4f)); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_color" })), TransformColor(Color(1.0f, 1.0f, 1.0f, 1.0f), ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg)); EXPECT_EQ(srgData.GetImageView(srgData.FindShaderInputImageIndex(Name{ "m_image" }), 0), m_testImage->GetImageView()); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_enum" })), 2u); } //! Provides write access to private material asset property values, primarily for simulating //! MaterialAsset hot reload. MaterialPropertyValue& AccessMaterialAssetPropertyValue(Data::Asset materialAsset, Name propertyName) { return materialAsset->m_propertyValues[materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyName).GetIndex()]; } }; TEST_F(MaterialTests, TestCreateVsFindOrCreate) { Data::Instance materialInstance1 = Material::FindOrCreate(m_testMaterialAsset); Data::Instance materialInstance2 = Material::FindOrCreate(m_testMaterialAsset); Data::Instance materialInstance3 = Material::Create(m_testMaterialAsset); Data::Instance materialInstance4 = Material::Create(m_testMaterialAsset); EXPECT_TRUE(materialInstance1); EXPECT_TRUE(materialInstance2); EXPECT_TRUE(materialInstance3); EXPECT_TRUE(materialInstance4); // Instances created via FindOrCreate should be the same object EXPECT_EQ(materialInstance1, materialInstance2); EXPECT_NE(materialInstance1, materialInstance3); EXPECT_NE(materialInstance1, materialInstance4); EXPECT_NE(materialInstance3, materialInstance4); } #if AZ_TRAIT_DISABLE_FAILED_ATOM_RPI_TESTS TEST_F(MaterialTests, DISABLED_TestInitialValuesFromMaterial) #else TEST_F(MaterialTests, TestInitialValuesFromMaterial) #endif // AZ_TRAIT_DISABLE_FAILED_ATOM_RPI_TESTS { Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); ValidateInitialValuesFromMaterial(material); } #if AZ_TRAIT_DISABLE_FAILED_ATOM_RPI_TESTS TEST_F(MaterialTests, DISABLED_TestSetPropertyValue) #else TEST_F(MaterialTests, TestSetPropertyValue) #endif // AZ_TRAIT_DISABLE_FAILED_ATOM_RPI_TESTS { Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); Data::Asset otherTestImageAsset; Data::Instance otherTestImage; otherTestImageAsset = CreateTestImageAsset(); otherTestImage = StreamingImage::FindOrCreate(otherTestImageAsset); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyBool" }), false)); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" }), -5)); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyUInt" }), 123u)); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" }), 2.5f)); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat2" }), Vector2(10.1f, 10.2f))); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat3" }), Vector3(11.1f, 11.2f, 11.3f))); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat4" }), Vector4(12.1f, 12.2f, 12.3f, 12.4f))); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyColor" }), Color(0.1f, 0.2f, 0.3f, 0.4f))); EXPECT_TRUE(material->SetPropertyValue>(material->FindPropertyIndex(Name{ "MyImage" }), otherTestImage)); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyEnum" }), 3u)); // Test reading the values directly... EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyBool" })), false); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" })), -5); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyUInt" })), 123u); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" })), 2.5f); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat2" })), Vector2(10.1f, 10.2f)); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat3" })), Vector3(11.1f, 11.2f, 11.3f)); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat4" })), Vector4(12.1f, 12.2f, 12.3f, 12.4f)); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyColor" })), Color(0.1f, 0.2f, 0.3f, 0.4f)); EXPECT_EQ(material->GetPropertyValue>(material->FindPropertyIndex(Name{ "MyImage" })), otherTestImage); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyEnum" })), 3u); ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName()); material->Compile(); // Dig in to the SRG to make sure the values were applied there as well... const RHI::ShaderResourceGroup* srg = material->GetRHIShaderResourceGroup(); const RHI::ShaderResourceGroupData& srgData = srg->GetData(); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_bool" })), false); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_int" })), -5); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_uint" })), 123u); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float" })), 2.5f); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float2" })), Vector2(10.1f, 10.2f)); // Currently srgData.GetConstant isn't supported so we check the individual floats EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float3" }), 0), 11.1f); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float3" }), 1), 11.2f); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float3" }), 2), 11.3f); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float4" })), Vector4(12.1f, 12.2f, 12.3f, 12.4f)); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_color" })), TransformColor(Color(0.1f, 0.2f, 0.3f, 0.4f), ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg)); EXPECT_EQ(srgData.GetImageView(srgData.FindShaderInputImageIndex(Name{ "m_image" }), 0), otherTestImage->GetImageView()); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_enum" })), 3u); } TEST_F(MaterialTests, TestSetPropertyValueToMultipleShaderSettings) { Data::Asset materialTypeAsset; Data::Asset materialAsset; MaterialTypeAssetCreator materialTypeCreator; materialTypeCreator.Begin(Uuid::CreateRandom()); materialTypeCreator.AddShader(m_testMaterialShaderAsset); materialTypeCreator.BeginMaterialProperty(Name{ "MyInt" }, MaterialPropertyDataType::Int); materialTypeCreator.ConnectMaterialPropertyToShaderInput(Name{ "m_int" }); materialTypeCreator.ConnectMaterialPropertyToShaderInput(Name{ "m_uint" }); materialTypeCreator.EndMaterialProperty(); materialTypeCreator.End(materialTypeAsset); MaterialAssetCreator materialAssetCreator; materialAssetCreator.Begin(Uuid::CreateRandom(), *materialTypeAsset); materialAssetCreator.End(materialAsset); Data::Instance material = Material::FindOrCreate(materialAsset); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" }), 42)); // Test reading the value directly... EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" })), 42); ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName()); material->Compile(); // Dig in to the SRG to make sure the values were applied to both shader constants... const RHI::ShaderResourceGroup* srg = material->GetRHIShaderResourceGroup(); const RHI::ShaderResourceGroupData& srgData = srg->GetData(); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_int" })), 42); EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_uint" })), 42u); } TEST_F(MaterialTests, TestSetPropertyValueWhenValueIsUnchanged) { Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" }), 2.5f)); ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName()); EXPECT_TRUE(material->Compile()); // Taint the SRG so we can check whether it was set by the SetPropertyValue() calls below. const RHI::ShaderResourceGroup* srg = material->GetRHIShaderResourceGroup(); const RHI::ShaderResourceGroupData& srgData = srg->GetData(); const_cast(&srgData)->SetConstant(m_testMaterialSrgLayout->FindShaderInputConstantIndex(Name{"m_float"}), 0.0f); // Set the properties to the same values as before EXPECT_FALSE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" }), 2.5f)); ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName()); EXPECT_FALSE(material->Compile()); // Make sure the SRG is still tainted, because the SetPropertyValue() functions weren't processed EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float" })), 0.0f); } TEST_F(MaterialTests, TestImageNotProvided) { Data::Asset materialAssetWithEmptyImage; MaterialAssetCreator materialCreator; materialCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset); materialCreator.SetPropertyValue(Name{"MyFloat2"}, Vector2{0.1f, 0.2f}); materialCreator.SetPropertyValue(Name{"MyFloat3"}, Vector3{1.1f, 1.2f, 1.3f}); materialCreator.SetPropertyValue(Name{"MyFloat4"}, Vector4{2.1f, 2.2f, 2.3f, 2.4f}); materialCreator.SetPropertyValue(Name{"MyColor"}, Color{1.0f, 1.0f, 1.0f, 1.0f}); materialCreator.SetPropertyValue(Name{"MyInt"}, -2); materialCreator.SetPropertyValue(Name{"MyUInt"}, 12u); materialCreator.SetPropertyValue(Name{"MyFloat"}, 1.5f); materialCreator.SetPropertyValue(Name{"MyBool"}, true); // We don't set "MyImage" materialCreator.End(materialAssetWithEmptyImage); Data::Instance material = Material::FindOrCreate(materialAssetWithEmptyImage); Data::Instance nullImageInstance; Data::Instance actualImageInstance = material->GetPropertyValue>(material->FindPropertyIndex(Name{"MyImage"})); EXPECT_EQ(actualImageInstance, nullImageInstance); ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName()); material->Compile(); const RHI::ShaderResourceGroupData& srgData = material->GetRHIShaderResourceGroup()->GetData(); EXPECT_EQ(srgData.GetImageView(srgData.FindShaderInputImageIndex(Name{"m_image"}), 0), nullptr); } TEST_F(MaterialTests, TestMaterialWithNoSRGOrProperties) { // Making a material with no properties and no SRG allows us to create simple shaders // that don't need any input, for example a debug shader that just renders surface normals. Data::Asset emptyMaterialTypeAsset; MaterialTypeAssetCreator materialTypeCreator; materialTypeCreator.Begin(Uuid::CreateRandom()); EXPECT_TRUE(materialTypeCreator.End(emptyMaterialTypeAsset)); Data::Asset emptyMaterialAsset; MaterialAssetCreator materialCreator; materialCreator.Begin(Uuid::CreateRandom(), *emptyMaterialTypeAsset); EXPECT_TRUE(materialCreator.End(emptyMaterialAsset)); Data::Instance material = Material::FindOrCreate(emptyMaterialAsset); EXPECT_TRUE(material); EXPECT_FALSE(material->GetRHIShaderResourceGroup()); } Ptr CreateTestOptionsLayout() { AZStd::vector enumOptionValues; enumOptionValues.push_back({Name("Low"), RPI::ShaderOptionValue(0)}); enumOptionValues.push_back({Name("Med"), RPI::ShaderOptionValue(1)}); enumOptionValues.push_back({Name("High"), RPI::ShaderOptionValue(2)}); AZStd::vector boolOptionValues; boolOptionValues.push_back({Name("False"), RPI::ShaderOptionValue(0)}); boolOptionValues.push_back({Name("True"), RPI::ShaderOptionValue(1)}); AZStd::vector rangeOptionValues; rangeOptionValues.push_back({Name("1"), RPI::ShaderOptionValue(1)}); rangeOptionValues.push_back({Name("10"), RPI::ShaderOptionValue(10)}); Ptr shaderOptions = ShaderOptionGroupLayout::Create(); uint32_t order = 0; shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_enumA"}, ShaderOptionType::Enumeration, 0, order++, enumOptionValues, Name{"Low"}}); shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_enumB"}, ShaderOptionType::Enumeration, 2, order++, enumOptionValues, Name{"Low"}}); shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_enumC"}, ShaderOptionType::Enumeration, 4, order++, enumOptionValues, Name{"Low"}}); shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_boolA"}, ShaderOptionType::Boolean, 6, order++, boolOptionValues, Name{"False"}}); shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_boolB"}, ShaderOptionType::Boolean, 7, order++, boolOptionValues, Name{"False"}}); shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_boolC"}, ShaderOptionType::Boolean, 8, order++, boolOptionValues, Name{"False"}}); shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_rangeA"}, ShaderOptionType::IntegerRange, 9, order++, rangeOptionValues, Name{"1"}}); shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_rangeB"}, ShaderOptionType::IntegerRange, 13, order++, rangeOptionValues, Name{"1"}}); shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_rangeC"}, ShaderOptionType::IntegerRange, 17, order++, rangeOptionValues, Name{"1"}}); shaderOptions->Finalize(); return shaderOptions; } TEST_F(MaterialTests, TestSetPropertyValue_ConnectedToShaderOptions_AllTypes) { Ptr optionsLayout = CreateTestOptionsLayout(); Data::Asset shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), m_testMaterialSrgLayout, optionsLayout); MaterialTypeAssetCreator materialTypeCreator; materialTypeCreator.Begin(Uuid::CreateRandom()); materialTypeCreator.AddShader(shaderAsset); materialTypeCreator.BeginMaterialProperty(Name{"EnumA"}, MaterialPropertyDataType::Int); materialTypeCreator.ConnectMaterialPropertyToShaderOption(Name{"o_enumA"}, 0); materialTypeCreator.EndMaterialProperty(); materialTypeCreator.BeginMaterialProperty(Name{"EnumB"}, MaterialPropertyDataType::UInt); materialTypeCreator.ConnectMaterialPropertyToShaderOption(Name{"o_enumB"}, 0); materialTypeCreator.EndMaterialProperty(); materialTypeCreator.BeginMaterialProperty(Name{"Bool"}, MaterialPropertyDataType::Bool); materialTypeCreator.ConnectMaterialPropertyToShaderOption(Name{"o_boolA"}, 0); materialTypeCreator.EndMaterialProperty(); materialTypeCreator.BeginMaterialProperty(Name{"RangeA"}, MaterialPropertyDataType::Int); materialTypeCreator.ConnectMaterialPropertyToShaderOption(Name{"o_rangeA"}, 0); materialTypeCreator.EndMaterialProperty(); materialTypeCreator.BeginMaterialProperty(Name{"RangeB"}, MaterialPropertyDataType::UInt); materialTypeCreator.ConnectMaterialPropertyToShaderOption(Name{"o_rangeB"}, 0); materialTypeCreator.EndMaterialProperty(); materialTypeCreator.SetPropertyValue(Name{"EnumA"}, 2); materialTypeCreator.SetPropertyValue(Name{"EnumB"}, 1u); materialTypeCreator.SetPropertyValue(Name{"Bool"}, true); materialTypeCreator.SetPropertyValue(Name{"RangeA"}, 5); materialTypeCreator.SetPropertyValue(Name{"RangeB"}, 10u); materialTypeCreator.End(m_testMaterialTypeAsset); MaterialAssetCreator materialAssetCreator; materialAssetCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset); materialAssetCreator.End(m_testMaterialAsset); Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); auto& optionEnumA = optionsLayout->GetShaderOption(optionsLayout->FindShaderOptionIndex(Name{"o_enumA"})); auto& optionEnumB = optionsLayout->GetShaderOption(optionsLayout->FindShaderOptionIndex(Name{"o_enumB"})); auto& optionBoolA = optionsLayout->GetShaderOption(optionsLayout->FindShaderOptionIndex(Name{"o_boolA"})); auto& optionRangeA = optionsLayout->GetShaderOption(optionsLayout->FindShaderOptionIndex(Name{"o_rangeA"})); auto& optionRangeB = optionsLayout->GetShaderOption(optionsLayout->FindShaderOptionIndex(Name{"o_rangeB"})); // Check the values on the properties themselves EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{"EnumA"})), 2); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{"EnumB"})), 1u); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{"Bool"})), true); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{"RangeA"})), 5); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{"RangeB"})), 10u); // Check the values on the underlying ShaderCollection::Item ShaderOptionGroup options{optionsLayout, material->GetShaderCollection()[0].GetShaderVariantId()}; EXPECT_EQ(optionEnumA.Get(options).GetIndex(), optionEnumA.FindValue(Name{"High"}).GetIndex()); EXPECT_EQ(optionEnumB.Get(options).GetIndex(), optionEnumB.FindValue(Name{"Med"}).GetIndex()); EXPECT_EQ(optionBoolA.Get(options).GetIndex(), optionBoolA.FindValue(Name{"True"}).GetIndex()); EXPECT_EQ(optionRangeA.Get(options).GetIndex(), 5); EXPECT_EQ(optionRangeB.Get(options).GetIndex(), 10); // Now call SetPropertyValue to change the values, and check again EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{"EnumA"}), 1)); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{"EnumB"}), 0u)); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{"Bool"}), false)); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{"RangeA"}), 3)); EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{"RangeB"}), 7u)); // Check the values on the properties themselves EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{"EnumA"})), 1); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{"EnumB"})), 0u); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{"Bool"})), false); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{"RangeA"})), 3); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{"RangeB"})), 7u); // Check the values on the underlying ShaderCollection::Item ShaderOptionGroup options2{optionsLayout, material->GetShaderCollection()[0].GetShaderVariantId()}; EXPECT_EQ(optionEnumA.Get(options2).GetIndex(), optionEnumA.FindValue(Name{"Med"}).GetIndex()); EXPECT_EQ(optionEnumB.Get(options2).GetIndex(), optionEnumB.FindValue(Name{"Low"}).GetIndex()); EXPECT_EQ(optionBoolA.Get(options2).GetIndex(), optionBoolA.FindValue(Name{"False"}).GetIndex()); EXPECT_EQ(optionRangeA.Get(options2).GetIndex(), 3); EXPECT_EQ(optionRangeB.Get(options2).GetIndex(), 7); } TEST_F(MaterialTests, TestSetPropertyValue_ConnectedToShaderOptions_WithMultipleShaders) { Ptr optionsLayout = CreateTestOptionsLayout(); Data::Asset shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), m_testMaterialSrgLayout, optionsLayout); MaterialTypeAssetCreator materialTypeCreator; materialTypeCreator.Begin(Uuid::CreateRandom()); // Adding more than one shader materialTypeCreator.AddShader(shaderAsset); materialTypeCreator.AddShader(shaderAsset); materialTypeCreator.AddShader(shaderAsset); materialTypeCreator.BeginMaterialProperty(Name{"Value"}, MaterialPropertyDataType::Int); materialTypeCreator.ConnectMaterialPropertyToShaderOption(Name{"o_rangeC"}, 1); materialTypeCreator.ConnectMaterialPropertyToShaderOption(Name{"o_rangeA"}, 2); materialTypeCreator.ConnectMaterialPropertyToShaderOptions(Name{"o_rangeB"}); // Applies to all shaders materialTypeCreator.EndMaterialProperty(); materialTypeCreator.SetPropertyValue(Name{"Value"}, 2); materialTypeCreator.End(m_testMaterialTypeAsset); MaterialAssetCreator materialAssetCreator; materialAssetCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset); materialAssetCreator.End(m_testMaterialAsset); Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); auto& optionRangeA = optionsLayout->GetShaderOption(optionsLayout->FindShaderOptionIndex(Name{"o_rangeA"})); auto& optionRangeB = optionsLayout->GetShaderOption(optionsLayout->FindShaderOptionIndex(Name{"o_rangeB"})); auto& optionRangeC = optionsLayout->GetShaderOption(optionsLayout->FindShaderOptionIndex(Name{"o_rangeC"})); // Check the values on the underlying ShaderVariantReferences { ShaderOptionGroup options0{optionsLayout, material->GetShaderCollection()[0].GetShaderVariantId()}; ShaderOptionGroup options1{optionsLayout, material->GetShaderCollection()[1].GetShaderVariantId()}; ShaderOptionGroup options2{optionsLayout, material->GetShaderCollection()[2].GetShaderVariantId()}; EXPECT_EQ(optionRangeA.Get(options0).GetIndex(), -1); EXPECT_EQ(optionRangeA.Get(options1).GetIndex(), -1); EXPECT_EQ(optionRangeA.Get(options2).GetIndex(), 2); EXPECT_EQ(optionRangeB.Get(options0).GetIndex(), 2); EXPECT_EQ(optionRangeB.Get(options1).GetIndex(), 2); EXPECT_EQ(optionRangeB.Get(options2).GetIndex(), 2); EXPECT_EQ(optionRangeC.Get(options0).GetIndex(), -1); EXPECT_EQ(optionRangeC.Get(options1).GetIndex(), 2); EXPECT_EQ(optionRangeC.Get(options2).GetIndex(), -1); } // Now call SetPropertyValue to change the values, and check again EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{"Value"}), 5)); // Check the values on the underlying ShaderVariantReferences { ShaderOptionGroup options0{optionsLayout, material->GetShaderCollection()[0].GetShaderVariantId()}; ShaderOptionGroup options1{optionsLayout, material->GetShaderCollection()[1].GetShaderVariantId()}; ShaderOptionGroup options2{optionsLayout, material->GetShaderCollection()[2].GetShaderVariantId()}; EXPECT_EQ(optionRangeA.Get(options0).GetIndex(), -1); EXPECT_EQ(optionRangeA.Get(options1).GetIndex(), -1); EXPECT_EQ(optionRangeA.Get(options2).GetIndex(), 5); EXPECT_EQ(optionRangeB.Get(options0).GetIndex(), 5); EXPECT_EQ(optionRangeB.Get(options1).GetIndex(), 5); EXPECT_EQ(optionRangeB.Get(options2).GetIndex(), 5); EXPECT_EQ(optionRangeC.Get(options0).GetIndex(), -1); EXPECT_EQ(optionRangeC.Get(options1).GetIndex(), 5); EXPECT_EQ(optionRangeC.Get(options2).GetIndex(), -1); } } TEST_F(MaterialTests, TestSetSystemShaderOption) { Ptr optionsLayout = CreateTestOptionsLayout(); Data::Asset shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), m_testMaterialSrgLayout, optionsLayout); MaterialTypeAssetCreator materialTypeCreator; materialTypeCreator.Begin(Uuid::CreateRandom()); materialTypeCreator.AddShader(shaderAsset); materialTypeCreator.AddShader(shaderAsset); materialTypeCreator.AddShader(shaderAsset); materialTypeCreator.BeginMaterialProperty(Name{"RangeValue"}, MaterialPropertyDataType::Int); materialTypeCreator.ConnectMaterialPropertyToShaderOption(Name{"o_rangeA"}, 1); materialTypeCreator.ConnectMaterialPropertyToShaderOption(Name{"o_rangeB"}, 2); materialTypeCreator.EndMaterialProperty(); materialTypeCreator.BeginMaterialProperty(Name{"BoolValue"}, MaterialPropertyDataType::Bool); materialTypeCreator.ConnectMaterialPropertyToShaderOptions(Name{"o_boolA"}); // Applies to all shaders materialTypeCreator.EndMaterialProperty(); materialTypeCreator.ClaimShaderOptionOwnership(Name{"o_boolB"}); materialTypeCreator.SetPropertyValue(Name{"RangeValue"}, 1); materialTypeCreator.End(m_testMaterialTypeAsset); MaterialAssetCreator materialAssetCreator; materialAssetCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset); materialAssetCreator.End(m_testMaterialAsset); Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); EXPECT_EQ(3, material->SetSystemShaderOption(Name{"o_enumA"}, ShaderOptionValue{0}).GetValue()); EXPECT_EQ(3, material->SetSystemShaderOption(Name{"o_enumB"}, ShaderOptionValue{1}).GetValue()); EXPECT_EQ(3, material->SetSystemShaderOption(Name{"o_enumC"}, ShaderOptionValue{2}).GetValue()); EXPECT_FALSE(material->SetSystemShaderOption(Name{"o_boolA"}, ShaderOptionValue{1}).IsSuccess()); EXPECT_FALSE(material->SetSystemShaderOption(Name{"o_boolB"}, ShaderOptionValue{1}).IsSuccess()); EXPECT_EQ(3, material->SetSystemShaderOption(Name{"o_boolC"}, ShaderOptionValue{1}).GetValue()); EXPECT_FALSE(material->SetSystemShaderOption(Name{"o_rangeA"}, ShaderOptionValue{3}).IsSuccess()); EXPECT_FALSE(material->SetSystemShaderOption(Name{"o_rangeB"}, ShaderOptionValue{4}).IsSuccess()); EXPECT_EQ(3, material->SetSystemShaderOption(Name{"o_rangeC"}, ShaderOptionValue{5}).GetValue()); // Try setting a shader option that does not exist in this material auto result = material->SetSystemShaderOption(Name{"o_someOtherOption"}, ShaderOptionValue{1}); EXPECT_TRUE(result.IsSuccess()); EXPECT_EQ(0, result.GetValue()); for (size_t i = 0; i < material->GetShaderCollection().size(); ++i) { auto& shaderItem = material->GetShaderCollection()[i]; EXPECT_EQ(0, shaderItem.GetShaderOptions()->GetValue(Name{"o_enumA"}).GetIndex()); EXPECT_EQ(1, shaderItem.GetShaderOptions()->GetValue(Name{"o_enumB"}).GetIndex()); EXPECT_EQ(2, shaderItem.GetShaderOptions()->GetValue(Name{"o_enumC"}).GetIndex()); EXPECT_EQ(1, shaderItem.GetShaderOptions()->GetValue(Name{"o_boolC"}).GetIndex()); EXPECT_EQ(5, shaderItem.GetShaderOptions()->GetValue(Name{"o_rangeC"}).GetIndex()); // We don't care whether a material-owned shader option is unspecified or is initialized to its default state. // The important thing is that it did not change from its default value. auto checkValueNotChanged = [&shaderItem](const Name& name, ShaderOptionValue expectedValue) { ShaderOptionValue value = shaderItem.GetShaderOptions()->GetValue(name); if (value.IsValid()) { EXPECT_EQ(expectedValue.GetIndex(), value.GetIndex()); } }; checkValueNotChanged(Name{"o_boolA"}, ShaderOptionValue{0}); checkValueNotChanged(Name{"o_boolB"}, ShaderOptionValue{0}); checkValueNotChanged(Name{"o_rangeA"}, ShaderOptionValue{1}); checkValueNotChanged(Name{"o_rangeB"}, ShaderOptionValue{1}); } } TEST_F(MaterialTests, Error_InvalidShaderOptionValue) { Ptr optionsLayout = CreateTestOptionsLayout(); Data::Asset shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), m_testMaterialSrgLayout, optionsLayout); MaterialTypeAssetCreator materialTypeCreator; materialTypeCreator.Begin(Uuid::CreateRandom()); materialTypeCreator.AddShader(shaderAsset); materialTypeCreator.BeginMaterialProperty(Name{"Value"}, MaterialPropertyDataType::Int); materialTypeCreator.ConnectMaterialPropertyToShaderOptions(Name{"o_rangeA"}); materialTypeCreator.EndMaterialProperty(); materialTypeCreator.SetPropertyValue(Name{"Value"}, 1); materialTypeCreator.End(m_testMaterialTypeAsset); MaterialAssetCreator materialAssetCreator; materialAssetCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset); materialAssetCreator.End(m_testMaterialAsset); Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); AZ_TEST_START_ASSERTTEST; EXPECT_FALSE(material->SetPropertyValue(material->FindPropertyIndex(Name{"Value"}), 100)); AZ_TEST_STOP_ASSERTTEST(1); } TEST_F(MaterialTests, Error_ImageNotFound) { Data::Asset materialAsset; MaterialAssetCreator materialCreator; materialCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset); materialCreator.SetPropertyValue(Name{ "MyFloat2" }, Vector2{ 0.1f, 0.2f }); materialCreator.SetPropertyValue(Name{ "MyFloat3" }, Vector3{ 1.1f, 1.2f, 1.3f }); materialCreator.SetPropertyValue(Name{ "MyFloat4" }, Vector4{ 2.1f, 2.2f, 2.3f, 2.4f }); materialCreator.SetPropertyValue(Name{ "MyColor" }, Color{ 1.0f, 1.0f, 1.0f, 1.0f }); materialCreator.SetPropertyValue(Name{ "MyInt" }, -2); materialCreator.SetPropertyValue(Name{ "MyUInt" }, 12u); materialCreator.SetPropertyValue(Name{ "MyFloat" }, 1.5f); materialCreator.SetPropertyValue(Name{ "MyBool" }, true); // Set the image to an empty asset handle that isn't associated with any actual data. StreamingImage::FindOrCreate will fail. materialCreator.SetPropertyValue(Name{ "MyImage" }, Data::Asset(Uuid::CreateRandom(), azrtti_typeid())); materialCreator.End(materialAsset); ErrorMessageFinder errorMessageFinder{"Image asset could not be loaded"}; // The material may trigger a blocking load of the image asset, but there is no catalog in unit tests. errorMessageFinder.AddIgnoredErrorMessage("this type doesn't have a catalog", true); errorMessageFinder.AddIgnoredErrorMessage("Failed to retrieve required information for asset", true); errorMessageFinder.AddIgnoredErrorMessage("GetAsset called for asset which does not exist", true); Data::Instance material = Material::FindOrCreate(materialAsset); errorMessageFinder.CheckExpectedErrorsFound(); EXPECT_EQ(nullptr, material); } TEST_F(MaterialTests, Error_AccessInvalidProperty) { Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); AZ_TEST_START_ASSERTTEST; EXPECT_FALSE(material->SetPropertyValue(MaterialPropertyIndex(), 0.0f)); material->GetPropertyValue(MaterialPropertyIndex()); AZ_TEST_STOP_ASSERTTEST(2); } #if AZ_TRAIT_DISABLE_FAILED_ATOM_RPI_TESTS TEST_F(MaterialTests, DISABLED_Error_SetPropertyValue_WrongDataType) #else TEST_F(MaterialTests, Error_SetPropertyValue_WrongDataType) #endif // AZ_TRAIT_DISABLE_FAILED_ATOM_RPI_TESTS { Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); { ErrorMessageFinder finder; finder.AddExpectedErrorMessage("Accessed as type", 9); finder.AddExpectedErrorMessage("but is type", 9); EXPECT_FALSE(material->SetPropertyValue(material->FindPropertyIndex(Name{"MyImage"}), false)); EXPECT_FALSE(material->SetPropertyValue(material->FindPropertyIndex(Name{"MyBool"}), -5)); EXPECT_FALSE(material->SetPropertyValue(material->FindPropertyIndex(Name{"MyInt"}), 123u)); EXPECT_FALSE(material->SetPropertyValue(material->FindPropertyIndex(Name{"MyUInt"}), 2.5f)); EXPECT_FALSE(material->SetPropertyValue(material->FindPropertyIndex(Name{"MyFloat"}), Vector2(10.1f, 10.2f))); EXPECT_FALSE(material->SetPropertyValue(material->FindPropertyIndex(Name{"MyFloat2"}), Vector3(11.1f, 11.2f, 11.3f))); EXPECT_FALSE(material->SetPropertyValue(material->FindPropertyIndex(Name{"MyFloat3"}), Vector4(12.1f, 12.2f, 12.3f, 12.4f))); EXPECT_FALSE(material->SetPropertyValue(material->FindPropertyIndex(Name{"MyFloat4"}), Color(0.1f, 0.2f, 0.3f, 0.4f))); EXPECT_FALSE(material->SetPropertyValue>(material->FindPropertyIndex(Name{"MyColor"}), m_testImage)); finder.CheckExpectedErrorsFound(); } // Make sure the values have not changed ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName()); material->Compile(); ValidateInitialValuesFromMaterial(material); } TEST_F(MaterialTests, Error_GetPropertyValue_WrongDataType) { Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); ErrorMessageFinder finder; finder.AddExpectedErrorMessage("Accessed as type", 9); finder.AddExpectedErrorMessage("but is type", 9); material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyImage" })); material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyBool" })); material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" })); material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyUInt" })); material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" })); material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat2" })); material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat3" })); material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat4" })); material->GetPropertyValue>(material->FindPropertyIndex(Name{ "MyColor" })); finder.CheckExpectedErrorsFound(); } TEST_F(MaterialTests, ColorPropertyCanMapToFloat3) { Data::Asset materialTypeAsset; Data::Asset materialAsset; RHI::Ptr srgLayout = RHI::ShaderResourceGroupLayout::Create(); srgLayout->SetName(Name("MaterialSrg")); srgLayout->SetBindingSlot(SrgBindingSlot::Material); srgLayout->AddShaderInput(RHI::ShaderInputConstantDescriptor{Name{"m_color"}, 0, 12, 0}); ASSERT_TRUE(srgLayout->Finalize()); Data::Asset shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), srgLayout); MaterialTypeAssetCreator materialTypeCreator; materialTypeCreator.Begin(Uuid::CreateRandom()); materialTypeCreator.AddShader(shaderAsset); materialTypeCreator.BeginMaterialProperty(Name{ "MyColor" }, MaterialPropertyDataType::Color); materialTypeCreator.ConnectMaterialPropertyToShaderInput(Name{ "m_color" }); materialTypeCreator.EndMaterialProperty(); materialTypeCreator.End(materialTypeAsset); MaterialAssetCreator materialAssetCreator; materialAssetCreator.Begin(Uuid::CreateRandom(), *materialTypeAsset); materialAssetCreator.End(materialAsset); Data::Instance material = Material::FindOrCreate(materialAsset); const AZ::Color inputColor{1.0f, 2.0f, 3.0f, 0.0f}; MaterialPropertyIndex colorProperty{0}; material->SetPropertyValue(colorProperty, inputColor); ProcessQueuedSrgCompilations(shaderAsset, srgLayout->GetName()); material->Compile(); AZ::Color colorFromMaterial = material->GetPropertyValue(colorProperty); RHI::ShaderInputConstantIndex colorConstant{0}; AZ::Color colorFromSrg; const float* floatsFromSrg = reinterpret_cast(material->GetRHIShaderResourceGroup()->GetData().GetConstantRaw(colorConstant).data()); colorFromSrg = AZ::Color::CreateFromVector3(AZ::Vector3::CreateFromFloat3(floatsFromSrg)); for (int i = 0; i < 3; ++i) { EXPECT_EQ((float)TransformColor(inputColor, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg).GetElement(i), (float)colorFromSrg.GetElement(i)); EXPECT_EQ((float)inputColor.GetElement(i), (float)colorFromMaterial.GetElement(i)); } } TEST_F(MaterialTests, TestReinitializeForHotReload) { Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); const RHI::ShaderResourceGroupData* srgData = &material->GetRHIShaderResourceGroup()->GetData(); ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName()); // Check the default property value EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" })), 1.5f); EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_float" })), 1.5f); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" })), -2); EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_int" })), -2); // Override a property value EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" }), 5.5f)); // Apply the changes EXPECT_TRUE(material->Compile()); ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName()); // Check the updated values with one overridden EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" })), 5.5f); EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_float" })), 5.5f); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" })), -2); EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_int" })), -2); // Pretend there was a hot-reload with new default values AccessMaterialAssetPropertyValue(m_testMaterialAsset, Name{"MyFloat"}) = 0.5f; AccessMaterialAssetPropertyValue(m_testMaterialAsset, Name{"MyInt"}) = -7; AZ::Data::AssetBus::Event(m_testMaterialAsset.GetId(), &AZ::Data::AssetBus::Handler::OnAssetReloaded, m_testMaterialAsset); srgData = &material->GetRHIShaderResourceGroup()->GetData(); ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName()); // Make sure the override values are still there EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_float" })), 5.5f); EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" })), 5.5f); // Make sure the new default value is applied where it was not overridden EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" })), -7); EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_int" })), -7); } TEST_F(MaterialTests, TestFindPropertyIndexUsingOldName) { MaterialTypeAssetCreator materialTypeCreator; materialTypeCreator.Begin(Uuid::CreateRandom()); materialTypeCreator.AddShader(m_testMaterialShaderAsset); AddCommonTestMaterialProperties(materialTypeCreator); materialTypeCreator.SetVersion(2); MaterialVersionUpdate versionUpdate(2); versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction({Name{ "OldName" },Name{ "MyInt" }})); materialTypeCreator.AddVersionUpdate(versionUpdate); materialTypeCreator.End(m_testMaterialTypeAsset); MaterialAssetCreator materialCreator; materialCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset); materialCreator.End(m_testMaterialAsset); Data::Instance material = Material::FindOrCreate(m_testMaterialAsset); bool wasRenamed = false; Name newName; MaterialPropertyIndex indexFromOldName = material->FindPropertyIndex(Name{"OldName"}, &wasRenamed, &newName); EXPECT_TRUE(wasRenamed); EXPECT_EQ(newName, Name{"MyInt"}); MaterialPropertyIndex indexFromNewName = material->FindPropertyIndex(Name{"MyInt"}, &wasRenamed, &newName); EXPECT_FALSE(wasRenamed); EXPECT_EQ(indexFromOldName, indexFromNewName); } template void CheckPropertyValueRoundTrip(const T& value) { AZ::RPI::MaterialPropertyValue materialPropertyValue{value}; AZStd::any anyValue{value}; AZ::RPI::MaterialPropertyValue materialPropertyValueFromAny = MaterialPropertyValue::FromAny(anyValue); AZ::RPI::MaterialPropertyValue materialPropertyValueFromRoundTrip = MaterialPropertyValue::FromAny(MaterialPropertyValue::ToAny(materialPropertyValue)); EXPECT_EQ(materialPropertyValue, materialPropertyValueFromAny); EXPECT_EQ(materialPropertyValue, materialPropertyValueFromRoundTrip); if (materialPropertyValue.Is>()) { EXPECT_EQ(materialPropertyValue.GetValue>().GetHint(), materialPropertyValueFromAny.GetValue>().GetHint()); EXPECT_EQ(materialPropertyValue.GetValue>().GetHint(), materialPropertyValueFromRoundTrip.GetValue>().GetHint()); } } TEST_F(MaterialTests, TestMaterialPropertyValueAsAny) { CheckPropertyValueRoundTrip(true); CheckPropertyValueRoundTrip(false); CheckPropertyValueRoundTrip(7); CheckPropertyValueRoundTrip(8u); CheckPropertyValueRoundTrip(9.0f); CheckPropertyValueRoundTrip(AZ::Vector2(1.0f, 2.0f)); CheckPropertyValueRoundTrip(AZ::Vector3(1.0f, 2.0f, 3.0f)); CheckPropertyValueRoundTrip(AZ::Vector4(1.0f, 2.0f, 3.0f, 4.0f)); CheckPropertyValueRoundTrip(AZ::Color(1.0f, 2.0f, 3.0f, 4.0f)); CheckPropertyValueRoundTrip(Data::Asset{}); CheckPropertyValueRoundTrip(Data::Asset{}); CheckPropertyValueRoundTrip(Data::Asset{}); CheckPropertyValueRoundTrip(Data::Asset{Uuid::CreateRandom(), azrtti_typeid(), "TestAssetPath.png"}); CheckPropertyValueRoundTrip(Data::Asset{Uuid::CreateRandom(), azrtti_typeid(), "TestAssetPath.png"}); CheckPropertyValueRoundTrip(Data::Asset{Uuid::CreateRandom(), azrtti_typeid(), "TestAssetPath.png"}); CheckPropertyValueRoundTrip(m_testImageAsset); CheckPropertyValueRoundTrip(Data::Instance{m_testImage}); CheckPropertyValueRoundTrip(AZStd::string{"hello"}); } }