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.
887 lines
42 KiB
C++
887 lines
42 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/ShaderAssetTestUtils.h>
|
|
#include <Common/ErrorMessageFinder.h>
|
|
#include <Material/MaterialAssetTestUtils.h>
|
|
|
|
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
|
|
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
|
|
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
|
|
#include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h>
|
|
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
|
|
#include <Atom/RPI.Edit/Material/MaterialUtils.h>
|
|
|
|
#include <AzCore/Math/Vector2.h>
|
|
#include <AzCore/Math/Vector3.h>
|
|
#include <AzCore/Math/Vector4.h>
|
|
#include <AzCore/Math/Color.h>
|
|
#include <AzCore/Utils/Utils.h>
|
|
|
|
#include <AzFramework/IO/LocalFileIO.h>
|
|
|
|
namespace UnitTest
|
|
{
|
|
using namespace AZ;
|
|
using namespace RPI;
|
|
|
|
class MaterialSourceDataTests
|
|
: public RPITestFixture
|
|
{
|
|
protected:
|
|
RHI::Ptr<RHI::ShaderResourceGroupLayout> m_testMaterialSrgLayout;
|
|
Data::Asset<ShaderAsset> m_testShaderAsset;
|
|
Data::Asset<MaterialTypeAsset> m_testMaterialTypeAsset;
|
|
Data::Asset<ImageAsset> m_testImageAsset;
|
|
|
|
void Reflect(AZ::ReflectContext* context) override
|
|
{
|
|
RPITestFixture::Reflect(context);
|
|
MaterialTypeSourceData::Reflect(context);
|
|
MaterialSourceData::Reflect(context);
|
|
}
|
|
|
|
void SetUp() override
|
|
{
|
|
EXPECT_EQ(nullptr, IO::FileIOBase::GetInstance());
|
|
|
|
RPITestFixture::SetUp();
|
|
|
|
auto localFileIO = AZ::IO::FileIOBase::GetInstance();
|
|
EXPECT_NE(nullptr, localFileIO);
|
|
char rootPath[AZ_MAX_PATH_LEN];
|
|
AZ::Utils::GetExecutableDirectory(rootPath, AZ_MAX_PATH_LEN);
|
|
localFileIO->SetAlias("@exefolder@", rootPath);
|
|
|
|
m_testMaterialSrgLayout = CreateCommonTestMaterialSrgLayout();
|
|
m_testShaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), m_testMaterialSrgLayout);
|
|
m_assetSystemStub.RegisterSourceInfo("@exefolder@/Temp/test.shader", m_testShaderAsset.GetId());
|
|
|
|
// The MaterialSourceData relies on both MaterialTypeSourceData and MaterialTypeAsset. We have to make sure the
|
|
// .materialtype file is present on disk, and that the MaterialTypeAsset is available through the asset database stub...
|
|
|
|
const char* materialTypeJson = R"(
|
|
{
|
|
"version": 10,
|
|
"propertyLayout": {
|
|
"properties": {
|
|
"general": [
|
|
{"name": "MyBool", "type": "bool"},
|
|
{"name": "MyInt", "type": "Int"},
|
|
{"name": "MyUInt", "type": "UInt"},
|
|
{"name": "MyFloat", "type": "Float"},
|
|
{"name": "MyFloat2", "type": "Vector2"},
|
|
{"name": "MyFloat3", "type": "Vector3"},
|
|
{"name": "MyFloat4", "type": "Vector4"},
|
|
{"name": "MyColor", "type": "Color"},
|
|
{"name": "MyImage", "type": "Image"},
|
|
{"name": "MyEnum", "type": "Enum", "enumValues": ["Enum0", "Enum1", "Enum2"], "defaultValue": "Enum0"}
|
|
]
|
|
}
|
|
},
|
|
"shaders": [
|
|
{
|
|
"file": "@exefolder@/Temp/test.shader"
|
|
}
|
|
],
|
|
"versionUpdates": [
|
|
{
|
|
"toVersion": 2,
|
|
"actions": [
|
|
{"op": "rename", "from": "general.testColorNameA", "to": "general.testColorNameB"}
|
|
]
|
|
},
|
|
{
|
|
"toVersion": 4,
|
|
"actions": [
|
|
{"op": "rename", "from": "general.testColorNameB", "to": "general.testColorNameC"}
|
|
]
|
|
},
|
|
{
|
|
"toVersion": 6,
|
|
"actions": [
|
|
{"op": "rename", "from": "oldGroup.MyFloat", "to": "general.MyFloat"},
|
|
{"op": "rename", "from": "oldGroup.MyIntOldName", "to": "general.MyInt"}
|
|
]
|
|
},
|
|
{
|
|
"toVersion": 10,
|
|
"actions": [
|
|
{"op": "rename", "from": "general.testColorNameC", "to": "general.MyColor"}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
)";
|
|
|
|
AZ::Utils::WriteFile(materialTypeJson, "@exefolder@/Temp/test.materialtype");
|
|
|
|
MaterialTypeSourceData materialTypeSourceData;
|
|
LoadTestDataFromJson(materialTypeSourceData, materialTypeJson);
|
|
m_testMaterialTypeAsset = materialTypeSourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()).TakeValue();
|
|
|
|
// 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>{ Data::AssetId{Uuid::CreateRandom(), StreamingImageAsset::GetImageAssetSubId()}, azrtti_typeid<StreamingImageAsset>() };
|
|
|
|
// Register the test assets with the AssetSystemStub so CreateMaterialAsset() can use AssetUtils.
|
|
m_assetSystemStub.RegisterSourceInfo("@exefolder@/Temp/test.materialtype", m_testMaterialTypeAsset.GetId());
|
|
m_assetSystemStub.RegisterSourceInfo("@exefolder@/Temp/test.streamingimage", m_testImageAsset.GetId());
|
|
}
|
|
|
|
void TearDown() override
|
|
{
|
|
m_testMaterialTypeAsset.Reset();
|
|
m_testMaterialSrgLayout = nullptr;
|
|
m_testShaderAsset.Reset();
|
|
m_testImageAsset.Reset();
|
|
|
|
RPITestFixture::TearDown();
|
|
}
|
|
};
|
|
|
|
void AddPropertyGroup(MaterialSourceData& material, AZStd::string_view groupName)
|
|
{
|
|
material.m_properties.insert(groupName);
|
|
}
|
|
|
|
void AddProperty(MaterialSourceData& material, AZStd::string_view groupName, AZStd::string_view propertyName, const MaterialPropertyValue& anyValue)
|
|
{
|
|
material.m_properties[groupName][propertyName].m_value = anyValue;
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_BasicProperties)
|
|
{
|
|
MaterialSourceData sourceData;
|
|
|
|
sourceData.m_materialType = "@exefolder@/Temp/test.materialtype";
|
|
AddPropertyGroup(sourceData, "general");
|
|
AddProperty(sourceData, "general", "MyBool", true);
|
|
AddProperty(sourceData, "general", "MyInt", -10);
|
|
AddProperty(sourceData, "general", "MyUInt", 25u);
|
|
AddProperty(sourceData, "general", "MyFloat", 1.5f);
|
|
AddProperty(sourceData, "general", "MyColor", AZ::Color{0.1f, 0.2f, 0.3f, 0.4f});
|
|
AddProperty(sourceData, "general", "MyFloat2", AZ::Vector2(2.1f, 2.2f));
|
|
AddProperty(sourceData, "general", "MyFloat3", AZ::Vector3(3.1f, 3.2f, 3.3f));
|
|
AddProperty(sourceData, "general", "MyFloat4", AZ::Vector4(4.1f, 4.2f, 4.3f, 4.4f));
|
|
AddProperty(sourceData, "general", "MyImage", AZStd::string("@exefolder@/Temp/test.streamingimage"));
|
|
AddProperty(sourceData, "general", "MyEnum", AZStd::string("Enum1"));
|
|
|
|
auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
|
EXPECT_TRUE(materialAssetOutcome.IsSuccess());
|
|
|
|
Data::Asset<MaterialAsset> materialAsset = materialAssetOutcome.GetValue();
|
|
|
|
// The order here is based on the order in the MaterialTypeSourceData, as added to the the MaterialTypeAssetCreator.
|
|
EXPECT_EQ(materialAsset->GetPropertyValues()[0].GetValue<bool>(), true);
|
|
EXPECT_EQ(materialAsset->GetPropertyValues()[1].GetValue<int32_t>(), -10);
|
|
EXPECT_EQ(materialAsset->GetPropertyValues()[2].GetValue<uint32_t>(), 25u);
|
|
EXPECT_EQ(materialAsset->GetPropertyValues()[3].GetValue<float>(), 1.5f);
|
|
EXPECT_EQ(materialAsset->GetPropertyValues()[4].GetValue<Vector2>(), Vector2(2.1f, 2.2f));
|
|
EXPECT_EQ(materialAsset->GetPropertyValues()[5].GetValue<Vector3>(), Vector3(3.1f, 3.2f, 3.3f));
|
|
EXPECT_EQ(materialAsset->GetPropertyValues()[6].GetValue<Vector4>(), Vector4(4.1f, 4.2f, 4.3f, 4.4f));
|
|
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);
|
|
}
|
|
|
|
void CheckEqual(MaterialSourceData& a, MaterialSourceData& b)
|
|
{
|
|
EXPECT_STREQ(a.m_materialType.data(), b.m_materialType.data());
|
|
EXPECT_STREQ(a.m_description.data(), b.m_description.data());
|
|
EXPECT_STREQ(a.m_parentMaterial.data(), b.m_parentMaterial.data());
|
|
EXPECT_EQ(a.m_materialTypeVersion, b.m_materialTypeVersion);
|
|
|
|
EXPECT_EQ(a.m_properties.size(), b.m_properties.size());
|
|
for (auto& groupA : a.m_properties)
|
|
{
|
|
AZStd::string groupName = groupA.first;
|
|
|
|
auto groupIterB = b.m_properties.find(groupName);
|
|
if (groupIterB == b.m_properties.end())
|
|
{
|
|
EXPECT_TRUE(false) << "groupB[" << groupName.c_str() << "] not found";
|
|
continue;
|
|
}
|
|
|
|
auto& groupB = *groupIterB;
|
|
|
|
EXPECT_EQ(groupA.second.size(), groupB.second.size()) << " for group[" << groupName.c_str() << "]";
|
|
|
|
for (auto& propertyIterA : groupA.second)
|
|
{
|
|
AZStd::string propertyName = propertyIterA.first;
|
|
|
|
auto propertyIterB = groupB.second.find(propertyName);
|
|
if (propertyIterB == groupB.second.end())
|
|
{
|
|
EXPECT_TRUE(false) << "groupB[" << groupName.c_str() << "][" << propertyName.c_str() << "] not found";
|
|
continue;
|
|
}
|
|
|
|
auto& propertyA = propertyIterA.second;
|
|
auto& propertyB = propertyIterB->second;
|
|
|
|
bool typesMatch = propertyA.m_value.GetTypeId() == propertyB.m_value.GetTypeId();
|
|
EXPECT_TRUE(typesMatch);
|
|
if (typesMatch)
|
|
{
|
|
AZStd::string propertyReference = AZStd::string::format(" for property '%s.%s'", groupName.c_str(), propertyName.c_str());
|
|
|
|
auto typeId = propertyA.m_value.GetTypeId();
|
|
|
|
if (typeId == azrtti_typeid<bool>()) { EXPECT_EQ(propertyA.m_value.GetValue<bool>(), propertyB.m_value.GetValue<bool>()) << propertyReference.c_str(); }
|
|
else if (typeId == azrtti_typeid<int32_t>()) { EXPECT_EQ(propertyA.m_value.GetValue<int32_t>(), propertyB.m_value.GetValue<int32_t>()) << propertyReference.c_str(); }
|
|
else if (typeId == azrtti_typeid<uint32_t>()) { EXPECT_EQ(propertyA.m_value.GetValue<uint32_t>(), propertyB.m_value.GetValue<uint32_t>()) << propertyReference.c_str(); }
|
|
else if (typeId == azrtti_typeid<float>()) { EXPECT_NEAR(propertyA.m_value.GetValue<float>(), propertyB.m_value.GetValue<float>(), 0.01) << propertyReference.c_str(); }
|
|
else if (typeId == azrtti_typeid<Vector2>()) { EXPECT_TRUE(propertyA.m_value.GetValue<Vector2>().IsClose(propertyB.m_value.GetValue<Vector2>())) << propertyReference.c_str(); }
|
|
else if (typeId == azrtti_typeid<Vector3>()) { EXPECT_TRUE(propertyA.m_value.GetValue<Vector3>().IsClose(propertyB.m_value.GetValue<Vector3>())) << propertyReference.c_str(); }
|
|
else if (typeId == azrtti_typeid<Vector4>()) { EXPECT_TRUE(propertyA.m_value.GetValue<Vector4>().IsClose(propertyB.m_value.GetValue<Vector4>())) << propertyReference.c_str(); }
|
|
else if (typeId == azrtti_typeid<Color>()) { EXPECT_TRUE(propertyA.m_value.GetValue<Color>().IsClose(propertyB.m_value.GetValue<Color>())) << propertyReference.c_str(); }
|
|
else if (typeId == azrtti_typeid<AZStd::string>()) { EXPECT_STREQ(propertyA.m_value.GetValue<AZStd::string>().c_str(), propertyB.m_value.GetValue<AZStd::string>().c_str()) << propertyReference.c_str(); }
|
|
else
|
|
{
|
|
ADD_FAILURE();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, TestJsonRoundTrip)
|
|
{
|
|
const char* materialTypeJson =
|
|
"{ \n"
|
|
" \"propertyLayout\": { \n"
|
|
" \"version\": 1, \n"
|
|
" \"groups\": [ \n"
|
|
" { \"name\": \"groupA\" }, \n"
|
|
" { \"name\": \"groupB\" }, \n"
|
|
" { \"name\": \"groupC\" } \n"
|
|
" ], \n"
|
|
" \"properties\": { \n"
|
|
" \"groupA\": [ \n"
|
|
" {\"name\": \"MyBool\", \"type\": \"bool\"}, \n"
|
|
" {\"name\": \"MyInt\", \"type\": \"int\"}, \n"
|
|
" {\"name\": \"MyUInt\", \"type\": \"uint\"} \n"
|
|
" ], \n"
|
|
" \"groupB\": [ \n"
|
|
" {\"name\": \"MyFloat\", \"type\": \"float\"}, \n"
|
|
" {\"name\": \"MyFloat2\", \"type\": \"vector2\"}, \n"
|
|
" {\"name\": \"MyFloat3\", \"type\": \"vector3\"} \n"
|
|
" ], \n"
|
|
" \"groupC\": [ \n"
|
|
" {\"name\": \"MyFloat4\", \"type\": \"vector4\"}, \n"
|
|
" {\"name\": \"MyColor\", \"type\": \"color\"}, \n"
|
|
" {\"name\": \"MyImage\", \"type\": \"image\"} \n"
|
|
" ] \n"
|
|
" } \n"
|
|
" } \n"
|
|
"} \n";
|
|
|
|
const char* materialTypeFilePath = "@exefolder@/Temp/roundTripTest.materialtype";
|
|
|
|
AZ::IO::FileIOStream file;
|
|
EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath));
|
|
file.Write(strlen(materialTypeJson), materialTypeJson);
|
|
file.Close();
|
|
|
|
MaterialSourceData sourceDataOriginal;
|
|
sourceDataOriginal.m_materialType = materialTypeFilePath;
|
|
sourceDataOriginal.m_parentMaterial = materialTypeFilePath;
|
|
sourceDataOriginal.m_description = "This is a description";
|
|
sourceDataOriginal.m_materialTypeVersion = 7;
|
|
AddPropertyGroup(sourceDataOriginal, "groupA");
|
|
AddProperty(sourceDataOriginal, "groupA", "MyBool", true);
|
|
AddProperty(sourceDataOriginal, "groupA", "MyInt", -10);
|
|
AddProperty(sourceDataOriginal, "groupA", "MyUInt", 25u);
|
|
AddPropertyGroup(sourceDataOriginal, "groupB");
|
|
AddProperty(sourceDataOriginal, "groupB", "MyFloat", 1.5f);
|
|
AddProperty(sourceDataOriginal, "groupB", "MyFloat2", AZ::Vector2(2.1f, 2.2f));
|
|
AddProperty(sourceDataOriginal, "groupB", "MyFloat3", AZ::Vector3(3.1f, 3.2f, 3.3f));
|
|
AddPropertyGroup(sourceDataOriginal, "groupC");
|
|
AddProperty(sourceDataOriginal, "groupC", "MyFloat4", AZ::Vector4(4.1f, 4.2f, 4.3f, 4.4f));
|
|
AddProperty(sourceDataOriginal, "groupC", "MyColor", AZ::Color{0.1f, 0.2f, 0.3f, 0.4f});
|
|
AddProperty(sourceDataOriginal, "groupC", "MyImage", AZStd::string("@exefolder@/Temp/test.streamingimage"));
|
|
|
|
AZStd::string sourceDataSerialized;
|
|
JsonTestResult storeResult = StoreTestDataToJson(sourceDataOriginal, sourceDataSerialized);
|
|
|
|
MaterialSourceData sourceDataCopy;
|
|
JsonTestResult loadResult = LoadTestDataFromJson(sourceDataCopy, sourceDataSerialized);
|
|
|
|
CheckEqual(sourceDataOriginal, sourceDataCopy);
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, Load_MaterialTypeAfterPropertyList)
|
|
{
|
|
const AZStd::string simpleMaterialTypeJson = R"(
|
|
{
|
|
"propertyLayout": {
|
|
"properties": {
|
|
"general": [
|
|
{
|
|
"name": "testColor",
|
|
"type": "color"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
const char* materialTypeFilePath = "@exefolder@/Temp/simpleMaterialType.materialtype";
|
|
|
|
AZ::IO::FileIOStream file;
|
|
EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath));
|
|
file.Write(simpleMaterialTypeJson.size(), simpleMaterialTypeJson.data());
|
|
file.Close();
|
|
|
|
// It shouldn't matter whether the materialType field appears before the property value list. This allows for the possibility
|
|
// that customer scripts generate material data and happen to use an unexpected order.
|
|
const AZStd::string inputJson = R"(
|
|
{
|
|
"properties": {
|
|
"general": {
|
|
"testColor": [0.1,0.2,0.3]
|
|
}
|
|
},
|
|
"materialType": "@exefolder@/Temp/simpleMaterialType.materialtype"
|
|
}
|
|
)";
|
|
|
|
MaterialSourceData material;
|
|
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
|
|
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
|
|
|
|
AZ::Color testColor = material.m_properties["general"]["testColor"].m_value.GetValue<AZ::Color>();
|
|
EXPECT_TRUE(AZ::Color(0.1f, 0.2f, 0.3f, 1.0f).IsClose(testColor, 0.01));
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, Load_Error_NotAnObject)
|
|
{
|
|
const AZStd::string inputJson = R"(
|
|
[]
|
|
)";
|
|
|
|
MaterialSourceData material;
|
|
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
|
|
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Altered, loadResult.m_jsonResultCode.GetProcessing());
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::Unsupported, loadResult.m_jsonResultCode.GetOutcome());
|
|
|
|
EXPECT_TRUE(loadResult.ContainsMessage("", "Material data must be a JSON object"));
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, Load_Error_NoMaterialType)
|
|
{
|
|
const AZStd::string inputJson = R"(
|
|
{
|
|
"materialTypeVersion": 1,
|
|
"properties": {
|
|
"baseColor": {
|
|
"color": [1.0,1.0,1.0]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
MaterialSourceData material;
|
|
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
|
|
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Halted, loadResult.m_jsonResultCode.GetProcessing());
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::Catastrophic, loadResult.m_jsonResultCode.GetOutcome());
|
|
|
|
EXPECT_TRUE(loadResult.ContainsMessage("", "Required field 'materialType' is missing"));
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, Load_Error_MaterialTypeDoesNotExist)
|
|
{
|
|
const AZStd::string inputJson = R"(
|
|
{
|
|
"materialType": "DoesNotExist.materialtype",
|
|
"materialTypeVersion": 1,
|
|
"properties": {
|
|
"baseColor": {
|
|
"color": [1.0,1.0,1.0]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
MaterialSourceData material;
|
|
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
|
|
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Halted, loadResult.m_jsonResultCode.GetProcessing());
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::Catastrophic, loadResult.m_jsonResultCode.GetOutcome());
|
|
|
|
EXPECT_TRUE(loadResult.ContainsMessage("/materialType", "Failed to load material-type file"));
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, Load_MaterialTypeMessagesAreReported)
|
|
{
|
|
const AZStd::string simpleMaterialTypeJson = R"(
|
|
{
|
|
"propertyLayout": {
|
|
"properties": {
|
|
"general": [
|
|
{
|
|
"name": "testColor",
|
|
"type": "color"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
const char* materialTypeFilePath = "@exefolder@/Temp/simpleMaterialType.materialtype";
|
|
|
|
AZ::IO::FileIOStream file;
|
|
EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath));
|
|
file.Write(simpleMaterialTypeJson.size(), simpleMaterialTypeJson.data());
|
|
file.Close();
|
|
|
|
const AZStd::string inputJson = R"(
|
|
{
|
|
"materialType": "@exefolder@/Temp/simpleMaterialType.materialtype",
|
|
"materialTypeVersion": 1,
|
|
"properties": {
|
|
"general": {
|
|
"testColor": [1.0,1.0,1.0]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
MaterialSourceData material;
|
|
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
|
|
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
|
|
|
|
// propertyLayout is a field in the material type, not the material
|
|
EXPECT_TRUE(loadResult.ContainsMessage("[simpleMaterialType.materialtype]/propertyLayout/properties", "Successfully read"));
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, Load_Error_PropertyNotFound)
|
|
{
|
|
const AZStd::string simpleMaterialTypeJson = R"(
|
|
{
|
|
"propertyLayout": {
|
|
"properties": {
|
|
"general": [
|
|
{
|
|
"name": "testColor",
|
|
"type": "color"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
const char* materialTypeFilePath = "@exefolder@/Temp/simpleMaterialType.materialtype";
|
|
|
|
AZ::IO::FileIOStream file;
|
|
EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath));
|
|
file.Write(simpleMaterialTypeJson.size(), simpleMaterialTypeJson.data());
|
|
file.Close();
|
|
|
|
const AZStd::string inputJson = R"(
|
|
{
|
|
"materialType": "@exefolder@/Temp/simpleMaterialType.materialtype",
|
|
"materialTypeVersion": 1,
|
|
"properties": {
|
|
"general": {
|
|
"doesNotExist": [1.0,1.0,1.0]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
MaterialSourceData material;
|
|
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
|
|
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Processing::PartialAlter, loadResult.m_jsonResultCode.GetProcessing());
|
|
|
|
EXPECT_TRUE(loadResult.ContainsMessage("/properties/general/doesNotExist", "Property 'general.doesNotExist' not found in material type."));
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MultiLevelDataInheritance)
|
|
{
|
|
MaterialSourceData sourceDataLevel1;
|
|
sourceDataLevel1.m_materialType = "@exefolder@/Temp/test.materialtype";
|
|
AddPropertyGroup(sourceDataLevel1, "general");
|
|
AddProperty(sourceDataLevel1, "general", "MyFloat", 1.5f);
|
|
AddProperty(sourceDataLevel1, "general", "MyColor", AZ::Color{0.1f, 0.2f, 0.3f, 0.4f});
|
|
|
|
MaterialSourceData sourceDataLevel2;
|
|
sourceDataLevel2.m_materialType = "@exefolder@/Temp/test.materialtype";
|
|
sourceDataLevel2.m_parentMaterial = "level1.material";
|
|
AddPropertyGroup(sourceDataLevel2, "general");
|
|
AddProperty(sourceDataLevel2, "general", "MyColor", AZ::Color{0.15f, 0.25f, 0.35f, 0.45f});
|
|
AddProperty(sourceDataLevel2, "general", "MyFloat2", AZ::Vector2{4.1f, 4.2f});
|
|
|
|
MaterialSourceData sourceDataLevel3;
|
|
sourceDataLevel3.m_materialType = "@exefolder@/Temp/test.materialtype";
|
|
sourceDataLevel3.m_parentMaterial = "level2.material";
|
|
AddPropertyGroup(sourceDataLevel3, "general");
|
|
AddProperty(sourceDataLevel3, "general", "MyFloat", 3.5f);
|
|
|
|
auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
|
EXPECT_TRUE(materialAssetLevel1.IsSuccess());
|
|
|
|
m_assetSystemStub.RegisterSourceInfo("level1.material", materialAssetLevel1.GetValue().GetId());
|
|
|
|
auto materialAssetLevel2 = sourceDataLevel2.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
|
EXPECT_TRUE(materialAssetLevel2.IsSuccess());
|
|
|
|
m_assetSystemStub.RegisterSourceInfo("level2.material", materialAssetLevel2.GetValue().GetId());
|
|
|
|
auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
|
EXPECT_TRUE(materialAssetLevel3.IsSuccess());
|
|
|
|
auto layout = m_testMaterialTypeAsset->GetMaterialPropertiesLayout();
|
|
MaterialPropertyIndex myFloat = layout->FindPropertyIndex(Name("general.MyFloat"));
|
|
MaterialPropertyIndex myFloat2 = layout->FindPropertyIndex(Name("general.MyFloat2"));
|
|
MaterialPropertyIndex myColor = layout->FindPropertyIndex(Name("general.MyColor"));
|
|
|
|
AZStd::array_view<MaterialPropertyValue> properties;
|
|
|
|
// Check level 1 properties
|
|
properties = materialAssetLevel1.GetValue()->GetPropertyValues();
|
|
EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
|
|
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(0.0f, 0.0f));
|
|
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.1f, 0.2f, 0.3f, 0.4f));
|
|
|
|
// Check level 2 properties
|
|
properties = materialAssetLevel2.GetValue()->GetPropertyValues();
|
|
EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
|
|
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
|
|
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
|
|
|
|
// Check level 3 properties
|
|
properties = materialAssetLevel3.GetValue()->GetPropertyValues();
|
|
EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 3.5f);
|
|
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
|
|
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MultiLevelDataInheritance_Error_MaterialTypesDontMatch)
|
|
{
|
|
Data::Asset<MaterialTypeAsset> otherMaterialType;
|
|
MaterialTypeAssetCreator materialTypeCreator;
|
|
materialTypeCreator.Begin(Uuid::CreateRandom());
|
|
materialTypeCreator.AddShader(m_testShaderAsset);
|
|
AddCommonTestMaterialProperties(materialTypeCreator, "general.");
|
|
EXPECT_TRUE(materialTypeCreator.End(otherMaterialType));
|
|
m_assetSystemStub.RegisterSourceInfo("otherBase.materialtype", otherMaterialType.GetId());
|
|
|
|
MaterialSourceData sourceDataLevel1;
|
|
sourceDataLevel1.m_materialType = "@exefolder@/Temp/test.materialtype";
|
|
|
|
MaterialSourceData sourceDataLevel2;
|
|
sourceDataLevel2.m_materialType = "@exefolder@/Temp/test.materialtype";
|
|
sourceDataLevel2.m_parentMaterial = "level1.material";
|
|
|
|
MaterialSourceData sourceDataLevel3;
|
|
sourceDataLevel3.m_materialType = "@exefolder@/Temp/otherBase.materialtype";
|
|
sourceDataLevel3.m_parentMaterial = "level2.material";
|
|
|
|
auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
|
EXPECT_TRUE(materialAssetLevel1.IsSuccess());
|
|
|
|
m_assetSystemStub.RegisterSourceInfo("level1.material", materialAssetLevel1.GetValue().GetId());
|
|
|
|
auto materialAssetLevel2 = sourceDataLevel2.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
|
EXPECT_TRUE(materialAssetLevel2.IsSuccess());
|
|
|
|
m_assetSystemStub.RegisterSourceInfo("level2.material", materialAssetLevel2.GetValue().GetId());
|
|
|
|
AZ_TEST_START_ASSERTTEST;
|
|
auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
|
AZ_TEST_STOP_ASSERTTEST(1);
|
|
EXPECT_FALSE(materialAssetLevel3.IsSuccess());
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_Error_BadInput)
|
|
{
|
|
// 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 expectError = [](AZStd::function<void(MaterialSourceData& materialSourceData)> setOneBadInput, [[maybe_unused]] uint32_t expectedAsserts = 2)
|
|
{
|
|
MaterialSourceData sourceData;
|
|
|
|
sourceData.m_materialType = "@exefolder@/Temp/test.materialtype";
|
|
|
|
AddPropertyGroup(sourceData, "general");
|
|
|
|
setOneBadInput(sourceData);
|
|
|
|
AZ_TEST_START_ASSERTTEST;
|
|
auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", false);
|
|
AZ_TEST_STOP_ASSERTTEST(expectedAsserts); // Usually one for the initial error, and one for when End() is called
|
|
|
|
EXPECT_FALSE(materialAssetOutcome.IsSuccess());
|
|
};
|
|
|
|
auto expectWarning = [](AZStd::function<void(MaterialSourceData& materialSourceData)> setOneBadInput, [[maybe_unused]] uint32_t expectedAsserts = 1)
|
|
{
|
|
MaterialSourceData sourceData;
|
|
|
|
sourceData.m_materialType = "@exefolder@/Temp/test.materialtype";
|
|
|
|
AddPropertyGroup(sourceData, "general");
|
|
|
|
setOneBadInput(sourceData);
|
|
|
|
AZ_TEST_START_ASSERTTEST;
|
|
auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
|
AZ_TEST_STOP_ASSERTTEST(expectedAsserts); // Usually just one for when End() is called
|
|
|
|
EXPECT_FALSE(materialAssetOutcome.IsSuccess());
|
|
};
|
|
|
|
// Test property does not exist...
|
|
|
|
expectWarning([](MaterialSourceData& materialSourceData)
|
|
{
|
|
AddProperty(materialSourceData, "general", "DoesNotExist", true);
|
|
});
|
|
|
|
expectWarning([](MaterialSourceData& materialSourceData)
|
|
{
|
|
AddProperty(materialSourceData, "general", "DoesNotExist", -10);
|
|
});
|
|
|
|
expectWarning([](MaterialSourceData& materialSourceData)
|
|
{
|
|
AddProperty(materialSourceData, "general", "DoesNotExist", 25u);
|
|
});
|
|
|
|
expectWarning([](MaterialSourceData& materialSourceData)
|
|
{
|
|
AddProperty(materialSourceData, "general", "DoesNotExist", 1.5f);
|
|
});
|
|
|
|
expectWarning([](MaterialSourceData& materialSourceData)
|
|
{
|
|
AddProperty(materialSourceData, "general", "DoesNotExist", AZ::Color{ 0.1f, 0.2f, 0.3f, 0.4f });
|
|
});
|
|
|
|
expectWarning([](MaterialSourceData& materialSourceData)
|
|
{
|
|
AddProperty(materialSourceData, "general", "DoesNotExist", AZStd::string("@exefolder@/Temp/test.streamingimage"));
|
|
});
|
|
|
|
// Missing image reference
|
|
expectError([](MaterialSourceData& materialSourceData)
|
|
{
|
|
AddProperty(materialSourceData, "general", "MyImage", AZStd::string("doesNotExist.streamingimage"));
|
|
}, 3); // Expect a 3rd error because AssetUtils reports its own assertion failure
|
|
}
|
|
|
|
|
|
TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionUpdate)
|
|
{
|
|
const AZStd::string inputJson = R"(
|
|
{
|
|
"materialType": "@exefolder@/Temp/test.materialtype",
|
|
"materialTypeVersion": 1,
|
|
"properties": {
|
|
"general": {
|
|
"testColorNameA": [0.1, 0.2, 0.3]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
MaterialSourceData material;
|
|
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
|
|
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
|
|
|
|
// Initially, the loaded material data will match the .material file exactly. This gives us the accurate representation of
|
|
// what's actually saved on disk.
|
|
|
|
EXPECT_NE(material.m_properties["general"].find("testColorNameA"), material.m_properties["general"].end());
|
|
EXPECT_EQ(material.m_properties["general"].find("testColorNameB"), material.m_properties["general"].end());
|
|
EXPECT_EQ(material.m_properties["general"].find("testColorNameC"), material.m_properties["general"].end());
|
|
EXPECT_EQ(material.m_properties["general"].find("MyColor"), material.m_properties["general"].end());
|
|
|
|
AZ::Color testColor = material.m_properties["general"]["testColorNameA"].m_value.GetValue<AZ::Color>();
|
|
EXPECT_TRUE(AZ::Color(0.1f, 0.2f, 0.3f, 1.0f).IsClose(testColor, 0.01));
|
|
|
|
EXPECT_EQ(1, material.m_materialTypeVersion);
|
|
|
|
// Then we force the material data to update to the latest material type version specification
|
|
ErrorMessageFinder warningFinder; // Note this finds errors and warnings, and we're looking for a warning.
|
|
warningFinder.AddExpectedErrorMessage("Automatic updates are available. Consider updating the .material source file");
|
|
warningFinder.AddExpectedErrorMessage("This material is based on version '1'");
|
|
warningFinder.AddExpectedErrorMessage("material type is now at version '10'");
|
|
material.ApplyVersionUpdates();
|
|
warningFinder.CheckExpectedErrorsFound();
|
|
|
|
// Now the material data should match the latest material type.
|
|
// Look for the property under the latest name in the material type, not the name used in the .material file.
|
|
|
|
EXPECT_EQ(material.m_properties["general"].find("testColorNameA"), material.m_properties["general"].end());
|
|
EXPECT_EQ(material.m_properties["general"].find("testColorNameB"), material.m_properties["general"].end());
|
|
EXPECT_EQ(material.m_properties["general"].find("testColorNameC"), material.m_properties["general"].end());
|
|
EXPECT_NE(material.m_properties["general"].find("MyColor"), material.m_properties["general"].end());
|
|
|
|
testColor = material.m_properties["general"]["MyColor"].m_value.GetValue<AZ::Color>();
|
|
EXPECT_TRUE(AZ::Color(0.1f, 0.2f, 0.3f, 1.0f).IsClose(testColor, 0.01));
|
|
|
|
EXPECT_EQ(10, material.m_materialTypeVersion);
|
|
|
|
// Calling ApplyVersionUpdates() again should not report the warning again, since the material has already been updated.
|
|
warningFinder.Reset();
|
|
material.ApplyVersionUpdates();
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionUpdate_MovePropertiesToAnotherGroup)
|
|
{
|
|
const AZStd::string inputJson = R"(
|
|
{
|
|
"materialType": "@exefolder@/Temp/test.materialtype",
|
|
"materialTypeVersion": 3,
|
|
"properties": {
|
|
"oldGroup": {
|
|
"MyFloat": 1.2,
|
|
"MyIntOldName": 5
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
MaterialSourceData material;
|
|
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
|
|
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
|
|
|
|
// Initially, the loaded material data will match the .material file exactly. This gives us the accurate representation of
|
|
// what's actually saved on disk.
|
|
|
|
EXPECT_NE(material.m_properties["oldGroup"].find("MyFloat"), material.m_properties["oldGroup"].end());
|
|
EXPECT_NE(material.m_properties["oldGroup"].find("MyIntOldName"), material.m_properties["oldGroup"].end());
|
|
EXPECT_EQ(material.m_properties["general"].find("MyFloat"), material.m_properties["general"].end());
|
|
EXPECT_EQ(material.m_properties["general"].find("MyInt"), material.m_properties["general"].end());
|
|
|
|
float myFloat = material.m_properties["oldGroup"]["MyFloat"].m_value.GetValue<float>();
|
|
EXPECT_EQ(myFloat, 1.2f);
|
|
|
|
int32_t myInt = material.m_properties["oldGroup"]["MyIntOldName"].m_value.GetValue<int32_t>();
|
|
EXPECT_EQ(myInt, 5);
|
|
|
|
EXPECT_EQ(3, material.m_materialTypeVersion);
|
|
|
|
// Then we force the material data to update to the latest material type version specification
|
|
ErrorMessageFinder warningFinder; // Note this finds errors and warnings, and we're looking for a warning.
|
|
warningFinder.AddExpectedErrorMessage("Automatic updates are available. Consider updating the .material source file");
|
|
warningFinder.AddExpectedErrorMessage("This material is based on version '3'");
|
|
warningFinder.AddExpectedErrorMessage("material type is now at version '10'");
|
|
material.ApplyVersionUpdates();
|
|
warningFinder.CheckExpectedErrorsFound();
|
|
|
|
// Now the material data should match the latest material type.
|
|
// Look for the property under the latest name in the material type, not the name used in the .material file.
|
|
|
|
EXPECT_EQ(material.m_properties["oldGroup"].find("MyFloat"), material.m_properties["oldGroup"].end());
|
|
EXPECT_EQ(material.m_properties["oldGroup"].find("MyIntOldName"), material.m_properties["oldGroup"].end());
|
|
EXPECT_NE(material.m_properties["general"].find("MyFloat"), material.m_properties["general"].end());
|
|
EXPECT_NE(material.m_properties["general"].find("MyInt"), material.m_properties["general"].end());
|
|
|
|
myFloat = material.m_properties["general"]["MyFloat"].m_value.GetValue<float>();
|
|
EXPECT_EQ(myFloat, 1.2f);
|
|
|
|
myInt = material.m_properties["general"]["MyInt"].m_value.GetValue<int32_t>();
|
|
EXPECT_EQ(myInt, 5);
|
|
|
|
EXPECT_EQ(10, material.m_materialTypeVersion);
|
|
|
|
// Calling ApplyVersionUpdates() again should not report the warning again, since the material has already been updated.
|
|
warningFinder.Reset();
|
|
material.ApplyVersionUpdates();
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionPartialUpdate)
|
|
{
|
|
// This case is similar to Load_MaterialTypeVersionUpdate but we start at a later
|
|
// version so only some of the version updates are applied.
|
|
|
|
const AZStd::string inputJson = R"(
|
|
{
|
|
"materialType": "@exefolder@/Temp/test.materialtype",
|
|
"materialTypeVersion": 3,
|
|
"properties": {
|
|
"general": {
|
|
"testColorNameB": [0.1, 0.2, 0.3]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
MaterialSourceData material;
|
|
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
|
|
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
|
|
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
|
|
|
|
material.ApplyVersionUpdates();
|
|
|
|
AZ::Color testColor = material.m_properties["general"]["MyColor"].m_value.GetValue<AZ::Color>();
|
|
EXPECT_TRUE(AZ::Color(0.1f, 0.2f, 0.3f, 1.0f).IsClose(testColor, 0.01));
|
|
|
|
EXPECT_EQ(10, material.m_materialTypeVersion);
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, Load_Error_MaterialTypeVersionUpdateWithMismatchedVersion)
|
|
{
|
|
const AZStd::string inputJson = R"(
|
|
{
|
|
"materialType": "@exefolder@/Temp/test.materialtype",
|
|
"materialTypeVersion": 3, // At this version, the property should be testColorNameB not testColorNameA
|
|
"properties": {
|
|
"general": {
|
|
"testColorNameA": [0.1, 0.2, 0.3]
|
|
}
|
|
}
|
|
}
|
|
)";
|
|
|
|
MaterialSourceData material;
|
|
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
|
|
|
|
loadResult.ContainsMessage("/properties/general/testColorNameA", "Property 'general.testColorNameA' not found in material type.");
|
|
|
|
EXPECT_FALSE(material.m_properties["general"]["testColorNameA"].m_value.IsValid());
|
|
|
|
material.ApplyVersionUpdates();
|
|
|
|
EXPECT_FALSE(material.m_properties["general"]["MyColor"].m_value.IsValid());
|
|
}
|
|
|
|
}
|
|
|
|
|