1a99103999
Here we formalize the concept of a Property Set, which replaces property "groups", containing the group name and description, properties, and functors all in one place. The Property Set structure will allow arbitrarily deep nesting, whereas before you only had one level of grouping. This nesting is not fully supported yet throughout the system, particularly in the Material Editor. It was easier to go ahead and put in some of the nesting mechanims, parituclar in the implementation of MaterialTypeSourceData. This change is backward compatible, which is proved with unit tests, and by the fact that only MinimalPBR.materialtype has been updated to the new format. StandardPBR, EnhancedPBR, and others are still using the old format. (In a subsequent commit I'll update these as well, to prove that the new format works correctly). Other changes and improvements... - A new constructor for MaterialPropertyId - Improved API for MaterialTypeSourceData that hides a good deal more of it's data as private, with clear and convenient APIs. Especially AddProperty, AddPropertySet, FindProperty, FindPropertySet, EnumerateProperties, EnumeratePropertySets. - Added lots of new unit tests - Updated MinimalPBR.materialtype to the new format. Testing: - Updated unit tests. - Reprocessed Atom material assets. - Ran AtomSampleViewer material screenshot test. - Opened, edited, saved material in the Material Editor. - Opened a level, edited material property overrides, saved and reloaded. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com>
661 lines
30 KiB
C++
661 lines
30 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 <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 <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);
|
|
|
|
MaterialTypeAssetCreator materialTypeCreator;
|
|
materialTypeCreator.Begin(Uuid::CreateRandom());
|
|
materialTypeCreator.AddShader(m_testShaderAsset);
|
|
AddCommonTestMaterialProperties(materialTypeCreator, "general.");
|
|
materialTypeCreator.End(m_testMaterialTypeAsset);
|
|
|
|
// 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("test.materialtype", m_testMaterialTypeAsset.GetId());
|
|
m_assetSystemStub.RegisterSourceInfo("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 = "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("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_propertyLayoutVersion, b.m_propertyLayoutVersion);
|
|
|
|
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 =
|
|
R"(
|
|
{
|
|
"propertyLayout": {
|
|
"propertySets": [
|
|
{
|
|
"name": "groupA",
|
|
"properties": [
|
|
{"name": "MyBool", "type": "bool"},
|
|
{"name": "MyInt", "type": "int"},
|
|
{"name": "MyUInt", "type": "uint"}
|
|
]
|
|
},
|
|
{
|
|
"name": "groupB",
|
|
"properties": [
|
|
{"name": "MyFloat", "type": "float"},
|
|
{"name": "MyFloat2", "type": "vector2"},
|
|
{"name": "MyFloat3", "type": "vector3"}
|
|
]
|
|
},
|
|
{
|
|
"name": "groupC",
|
|
"properties": [
|
|
{"name": "MyFloat4", "type": "vector4"},
|
|
{"name": "MyColor", "type": "color"},
|
|
{"name": "MyImage", "type": "image"}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
)";
|
|
|
|
const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/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_propertyLayoutVersion = 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("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": {
|
|
"propertySets":
|
|
[
|
|
{
|
|
"name": "general",
|
|
"properties": [
|
|
{
|
|
"name": "testColor",
|
|
"type": "color"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
)";
|
|
|
|
const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/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@/Gems/Atom/RPI/Code/Tests/Material/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"(
|
|
{
|
|
"propertyLayoutVersion": 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",
|
|
"propertyLayoutVersion": 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": {
|
|
"propertySets":
|
|
[
|
|
{
|
|
"name": "general",
|
|
"properties": [
|
|
{
|
|
"name": "testColor",
|
|
"type": "color"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
)";
|
|
|
|
const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/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@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype",
|
|
"propertyLayoutVersion": 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/propertySets", "Successfully read"));
|
|
}
|
|
|
|
TEST_F(MaterialSourceDataTests, Load_Error_PropertyNotFound)
|
|
{
|
|
const AZStd::string simpleMaterialTypeJson = R"(
|
|
{
|
|
"propertyLayout": {
|
|
"propertySets":
|
|
[
|
|
{
|
|
"name": "general",
|
|
"properties": [
|
|
{
|
|
"name": "testColor",
|
|
"type": "color"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
)";
|
|
|
|
const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/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@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype",
|
|
"propertyLayoutVersion": 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 = "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 = "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 = "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 = "test.materialtype";
|
|
|
|
MaterialSourceData sourceDataLevel2;
|
|
sourceDataLevel2.m_materialType = "test.materialtype";
|
|
sourceDataLevel2.m_parentMaterial = "level1.material";
|
|
|
|
MaterialSourceData sourceDataLevel3;
|
|
sourceDataLevel3.m_materialType = "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 = "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 = "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("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
|
|
}
|
|
}
|
|
|
|
|