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/Material/MaterialAssetTests.cpp

317 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/SerializeTester.h>
#include <Common/ShaderAssetTestUtils.h>
#include <Material/MaterialAssetTestUtils.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialAssetCreator.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h>
namespace UnitTest
{
using namespace AZ;
using namespace RPI;
class MaterialAssetTests
: public RPITestFixture
{
protected:
Data::Asset<MaterialTypeAsset> m_testMaterialTypeAsset;
Data::Asset<ImageAsset> m_testImageAsset;
void SetUp() override
{
RPITestFixture::SetUp();
auto materialSrgLayout = CreateCommonTestMaterialSrgLayout();
// Since this test doesn't actually instantiate a Material, it won't need to instantiate this ImageAsset, so all we
// need is an asset reference with a valid ID.
m_testImageAsset = Data::Asset<ImageAsset>{ Uuid::CreateRandom(), azrtti_typeid<StreamingImageAsset>() };
auto shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), materialSrgLayout);
MaterialTypeAssetCreator materialTypeCreator;
materialTypeCreator.Begin(Uuid::CreateRandom());
materialTypeCreator.AddShader(shaderAsset);
AddCommonTestMaterialProperties(materialTypeCreator);
materialTypeCreator.SetPropertyValue(Name{ "MyBool" }, true);
materialTypeCreator.SetPropertyValue(Name{ "MyInt" }, 1);
materialTypeCreator.SetPropertyValue(Name{ "MyUInt" }, 2u);
materialTypeCreator.SetPropertyValue(Name{ "MyFloat" }, 3.3f);
materialTypeCreator.SetPropertyValue(Name{ "MyFloat2" }, Vector2{ 4.4f, 5.5f });
materialTypeCreator.SetPropertyValue(Name{ "MyFloat3" }, Vector3{ 6.6f, 7.7f, 8.8f });
materialTypeCreator.SetPropertyValue(Name{ "MyFloat4" }, Vector4{ 9.9f, 10.1f, 11.11f, 12.12f });
materialTypeCreator.SetPropertyValue(Name{ "MyColor" }, Color{ 0.1f, 0.2f, 0.3f, 0.4f });
materialTypeCreator.SetPropertyValue(Name{ "MyImage" }, m_testImageAsset);
materialTypeCreator.SetPropertyValue(Name{ "MyEnum" }, 1u);
EXPECT_TRUE(materialTypeCreator.End(m_testMaterialTypeAsset));
}
void TearDown() override
{
m_testMaterialTypeAsset.Reset();
RPITestFixture::TearDown();
}
};
TEST_F(MaterialAssetTests, Basic)
{
auto validate = [this](Data::Asset<MaterialAsset> materialAsset)
{
EXPECT_EQ(m_testMaterialTypeAsset, materialAsset->GetMaterialTypeAsset());
EXPECT_EQ(materialAsset->GetPropertyValues().size(), 10);
EXPECT_EQ(materialAsset->GetPropertyValues()[0].GetValue<bool>(), true);
EXPECT_EQ(materialAsset->GetPropertyValues()[1].GetValue<int32_t>(), -2);
EXPECT_EQ(materialAsset->GetPropertyValues()[2].GetValue<uint32_t>(), 12);
EXPECT_EQ(materialAsset->GetPropertyValues()[3].GetValue<float>(), 1.5f);
EXPECT_EQ(materialAsset->GetPropertyValues()[4].GetValue<Vector2>(), Vector2(0.1f, 0.2f));
EXPECT_EQ(materialAsset->GetPropertyValues()[5].GetValue<Vector3>(), Vector3(1.1f, 1.2f, 1.3f));
EXPECT_EQ(materialAsset->GetPropertyValues()[6].GetValue<Vector4>(), Vector4(2.1f, 2.2f, 2.3f, 2.4f));
EXPECT_EQ(materialAsset->GetPropertyValues()[7].GetValue<Color>(), Color(1.0f, 1.0f, 1.0f, 1.0f));
EXPECT_EQ(materialAsset->GetPropertyValues()[8].GetValue<Data::Asset<ImageAsset>>(), m_testImageAsset);
EXPECT_EQ(materialAsset->GetPropertyValues()[9].GetValue<uint32_t>(), 1u);
};
// Test basic process of creating a valid asset...
Data::AssetId assetId(Uuid::CreateRandom());
MaterialAssetCreator creator;
creator.Begin(assetId, *m_testMaterialTypeAsset);
creator.SetPropertyValue(Name{ "MyFloat2" }, Vector2{ 0.1f, 0.2f });
creator.SetPropertyValue(Name{ "MyFloat3" }, Vector3{ 1.1f, 1.2f, 1.3f });
creator.SetPropertyValue(Name{ "MyFloat4" }, Vector4{ 2.1f, 2.2f, 2.3f, 2.4f });
creator.SetPropertyValue(Name{ "MyColor" }, Color{ 1.0f, 1.0f, 1.0f, 1.0f });
creator.SetPropertyValue(Name{ "MyInt" }, -2);
creator.SetPropertyValue(Name{ "MyUInt" }, 12u);
creator.SetPropertyValue(Name{ "MyFloat" }, 1.5f);
creator.SetPropertyValue(Name{ "MyBool" }, true);
creator.SetPropertyValue(Name{ "MyImage" }, m_testImageAsset);
creator.SetPropertyValue(Name{ "MyEnum" }, 1u);
Data::Asset<MaterialAsset> materialAsset;
EXPECT_TRUE(creator.End(materialAsset));
EXPECT_EQ(assetId, materialAsset->GetId());
EXPECT_EQ(Data::AssetData::AssetStatus::Ready, materialAsset->GetStatus());
validate(materialAsset);
// Also test serialization...
SerializeTester<RPI::MaterialAsset> tester(GetSerializeContext());
tester.SerializeOut(materialAsset.Get());
// Using a filter that skips loading assets because we are using a dummy image asset
ObjectStream::FilterDescriptor noAssets{ AZ::Data::AssetFilterNoAssetLoading };
Data::Asset<RPI::MaterialAsset> serializedAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()), noAssets);
validate(serializedAsset);
}
TEST_F(MaterialAssetTests, PropertyDefaultValuesComeFromParentMaterial)
{
Data::AssetId assetId(Uuid::CreateRandom());
MaterialAssetCreator creator;
creator.Begin(assetId, *m_testMaterialTypeAsset);
creator.SetPropertyValue(Name{ "MyFloat" }, 3.14f);
Data::Asset<MaterialAsset> materialAsset;
EXPECT_TRUE(creator.End(materialAsset));
EXPECT_EQ(assetId, materialAsset->GetId());
EXPECT_EQ(Data::AssetData::AssetStatus::Ready, materialAsset->GetStatus());
// Also test serialization...
SerializeTester<RPI::MaterialAsset> tester(GetSerializeContext());
tester.SerializeOut(materialAsset.Get());
// Using a filter that skips loading assets because we are using a dummy image asset
ObjectStream::FilterDescriptor noAssets{ AZ::Data::AssetFilterNoAssetLoading };
materialAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()), noAssets);
EXPECT_EQ(materialAsset->GetPropertyValues().size(), 10);
EXPECT_EQ(materialAsset->GetPropertyValues()[0].GetValue<bool>(), true);
EXPECT_EQ(materialAsset->GetPropertyValues()[1].GetValue<int32_t>(), 1);
EXPECT_EQ(materialAsset->GetPropertyValues()[2].GetValue<uint32_t>(), 2);
EXPECT_EQ(materialAsset->GetPropertyValues()[3].GetValue<float>(), 3.14f);
EXPECT_EQ(materialAsset->GetPropertyValues()[4].GetValue<Vector2>(), Vector2(4.4f, 5.5f));
EXPECT_EQ(materialAsset->GetPropertyValues()[5].GetValue<Vector3>(), Vector3(6.6f, 7.7f, 8.8f));
EXPECT_EQ(materialAsset->GetPropertyValues()[6].GetValue<Vector4>(), Vector4(9.9f, 10.1f, 11.11f, 12.12f));
EXPECT_EQ(materialAsset->GetPropertyValues()[7].GetValue<Color>(), Color(0.1f, 0.2f, 0.3f, 0.4f));
EXPECT_EQ(materialAsset->GetPropertyValues()[8].GetValue<Data::Asset<ImageAsset>>(), m_testImageAsset);
EXPECT_EQ(materialAsset->GetPropertyValues()[9].GetValue<uint32_t>(), 1u);
}
TEST_F(MaterialAssetTests, MaterialWithNoSRGOrProperties)
{
// 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<MaterialTypeAsset> emptyMaterialTypeAsset;
MaterialTypeAssetCreator materialTypeCreator;
materialTypeCreator.Begin(Uuid::CreateRandom());
EXPECT_TRUE(materialTypeCreator.End(emptyMaterialTypeAsset));
Data::Asset<MaterialAsset> materialAsset;
MaterialAssetCreator materialCreator;
materialCreator.Begin(Uuid::CreateRandom(), *emptyMaterialTypeAsset);
EXPECT_TRUE(materialCreator.End(materialAsset));
EXPECT_EQ(emptyMaterialTypeAsset, materialAsset->GetMaterialTypeAsset());
EXPECT_EQ(materialAsset->GetPropertyValues().size(), 0);
}
TEST_F(MaterialAssetTests, SetPropertyWithImageAssetSubclass)
{
// In the above test we called SetProperty(m_testImageAsset) which is an ImageAsset. Just to be safe, we also test
// to make sure it still works when using the leaf type of StreamingImageAsset.
// Since this test doesn't actually instantiate a Material, it won't need to instantiate this ImageAsset, so all we
// need is an asset reference with a valid ID.
Data::Asset<StreamingImageAsset> streamingImageAsset = Data::Asset<StreamingImageAsset>{ Uuid::CreateRandom(), azrtti_typeid<StreamingImageAsset>() };
Data::AssetId assetId(Uuid::CreateRandom());
MaterialAssetCreator creator;
creator.Begin(assetId, *m_testMaterialTypeAsset);
creator.SetPropertyValue(Name{ "MyImage" }, streamingImageAsset);
Data::Asset<MaterialAsset> materialAsset;
EXPECT_TRUE(creator.End(materialAsset));
EXPECT_EQ(materialAsset->GetPropertyValues()[8].GetValue<Data::Asset<ImageAsset>>(), streamingImageAsset);
// Also test serialization...
SerializeTester<RPI::MaterialAsset> tester(GetSerializeContext());
tester.SerializeOut(materialAsset.Get());
// Using a filter that skips loading assets because we are using a dummy image asset
ObjectStream::FilterDescriptor noAssets{ AZ::Data::AssetFilterNoAssetLoading };
Data::Asset<RPI::MaterialAsset> serializedAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()), noAssets);
EXPECT_EQ(serializedAsset->GetPropertyValues()[8].GetValue<Data::Asset<ImageAsset>>(), streamingImageAsset);
}
TEST_F(MaterialAssetTests, Error_NoBegin)
{
Data::AssetId assetId(Uuid::CreateRandom());
AZ_TEST_START_ASSERTTEST;
MaterialAssetCreator creator;
creator.SetPropertyValue(Name{ "MyBool" }, true);
creator.SetPropertyValue(Name{ "MyImage" }, m_testImageAsset);
Data::Asset<MaterialAsset> materialAsset;
EXPECT_FALSE(creator.End(materialAsset));
AZ_TEST_STOP_ASSERTTEST(3);
}
TEST_F(MaterialAssetTests, Error_SetPropertyInvalidInputs)
{
Data::AssetId assetId(Uuid::CreateRandom());
// We use local functions to easily start a new MaterialAssetCreator for each test case because
// the AssetCreator would just skip subsequent operations after the first failure is detected.
auto expectCreatorError = [this](AZStd::function<void(MaterialAssetCreator& creator)> passBadInput)
{
MaterialAssetCreator creator;
creator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
AZ_TEST_START_ASSERTTEST;
passBadInput(creator);
AZ_TEST_STOP_ASSERTTEST(1);
EXPECT_EQ(1, creator.GetErrorCount());
};
auto expectCreatorWarning = [this](AZStd::function<void(MaterialAssetCreator& creator)> passBadInput)
{
MaterialAssetCreator creator;
creator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
passBadInput(creator);
EXPECT_EQ(1, creator.GetWarningCount());
};
// Invalid input ID
expectCreatorWarning([](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "BoolDoesNotExist" }, MaterialPropertyValue(false));
});
// Invalid image input ID
expectCreatorWarning([this](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "ImageDoesNotExist" }, m_testImageAsset);
});
// Test data type mismatches...
expectCreatorError([this](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyBool" }, m_testImageAsset);
});
expectCreatorError([](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyInt" }, 0.0f);
});
expectCreatorError([](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyUInt" }, -1);
});
expectCreatorError([](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyFloat" }, 10u);
});
expectCreatorError([](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyFloat2" }, 1.0f);
});
expectCreatorError([](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyFloat3" }, AZ::Vector4{});
});
expectCreatorError([](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyFloat4" }, AZ::Vector3{});
});
expectCreatorError([](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyColor" }, MaterialPropertyValue(false));
});
expectCreatorError([](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyImage" }, true);
});
expectCreatorError([](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyEnum" }, -1);
});
}
}