3
0

MaterialSourceDataTests.cpp 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzTest/AzTest.h>
  9. #include <Common/RPITestFixture.h>
  10. #include <Common/JsonTestUtils.h>
  11. #include <Common/ShaderAssetTestUtils.h>
  12. #include <Common/ErrorMessageFinder.h>
  13. #include <Common/SerializeTester.h>
  14. #include <Material/MaterialAssetTestUtils.h>
  15. #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
  16. #include <Atom/RPI.Edit/Material/MaterialSourceData.h>
  17. #include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
  18. #include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h>
  19. #include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
  20. #include <Atom/RPI.Edit/Material/MaterialUtils.h>
  21. #include <AzCore/Math/Vector2.h>
  22. #include <AzCore/Math/Vector3.h>
  23. #include <AzCore/Math/Vector4.h>
  24. #include <AzCore/Math/Color.h>
  25. #include <AzCore/Utils/Utils.h>
  26. #include <AzFramework/IO/LocalFileIO.h>
  27. namespace UnitTest
  28. {
  29. using namespace AZ;
  30. using namespace RPI;
  31. class MaterialSourceDataTests
  32. : public RPITestFixture
  33. {
  34. protected:
  35. RHI::Ptr<RHI::ShaderResourceGroupLayout> m_testMaterialSrgLayout;
  36. Data::Asset<ShaderAsset> m_testShaderAsset;
  37. Data::Asset<MaterialTypeAsset> m_testMaterialTypeAsset;
  38. Data::Asset<ImageAsset> m_testImageAsset;
  39. void Reflect(AZ::ReflectContext* context) override
  40. {
  41. RPITestFixture::Reflect(context);
  42. MaterialPropertySourceData::Reflect(context);
  43. MaterialTypeSourceData::Reflect(context);
  44. MaterialSourceData::Reflect(context);
  45. }
  46. void SetUp() override
  47. {
  48. EXPECT_EQ(nullptr, IO::FileIOBase::GetInstance());
  49. RPITestFixture::SetUp();
  50. auto localFileIO = AZ::IO::FileIOBase::GetInstance();
  51. EXPECT_NE(nullptr, localFileIO);
  52. char rootPath[AZ_MAX_PATH_LEN];
  53. AZ::Utils::GetExecutableDirectory(rootPath, AZ_MAX_PATH_LEN);
  54. localFileIO->SetAlias("@exefolder@", rootPath);
  55. m_testMaterialSrgLayout = CreateCommonTestMaterialSrgLayout();
  56. EXPECT_NE(nullptr, m_testMaterialSrgLayout);
  57. m_testShaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), m_testMaterialSrgLayout);
  58. EXPECT_TRUE(m_testShaderAsset.GetId().IsValid());
  59. EXPECT_TRUE(m_testShaderAsset.IsReady());
  60. m_assetSystemStub.RegisterSourceInfo(DeAliasPath("@exefolder@/Temp/test.shader"), m_testShaderAsset.GetId());
  61. m_testMaterialTypeAsset = CreateTestMaterialTypeAsset(Uuid::CreateRandom());
  62. EXPECT_TRUE(m_testMaterialTypeAsset.GetId().IsValid());
  63. EXPECT_TRUE(m_testMaterialTypeAsset.IsReady());
  64. // Since this test doesn't actually instantiate a Material, it won't need to instantiate this ImageAsset, so all we
  65. // need is an asset reference with a valid ID.
  66. m_testImageAsset = Data::Asset<ImageAsset>{ Data::AssetId{Uuid::CreateRandom(), StreamingImageAsset::GetImageAssetSubId()}, azrtti_typeid<StreamingImageAsset>() };
  67. EXPECT_TRUE(m_testImageAsset.GetId().IsValid());
  68. // Register the test assets with the AssetSystemStub so CreateMaterialAsset() can use AssetUtils.
  69. m_assetSystemStub.RegisterSourceInfo(DeAliasPath("@exefolder@/Temp/test.materialtype"), m_testMaterialTypeAsset.GetId());
  70. m_assetSystemStub.RegisterSourceInfo(DeAliasPath("@exefolder@/Temp/test.streamingimage"), m_testImageAsset.GetId());
  71. }
  72. void TearDown() override
  73. {
  74. m_testMaterialTypeAsset.Reset();
  75. m_testMaterialSrgLayout = nullptr;
  76. m_testShaderAsset.Reset();
  77. m_testImageAsset.Reset();
  78. RPITestFixture::TearDown();
  79. }
  80. AZStd::string DeAliasPath(const AZStd::string& sourcePath) const
  81. {
  82. AZ::IO::FixedMaxPath sourcePathNoAlias;
  83. AZ::IO::FileIOBase::GetInstance()->ReplaceAlias(sourcePathNoAlias, AZ::IO::PathView{ sourcePath });
  84. return sourcePathNoAlias.LexicallyNormal().String();
  85. }
  86. AZStd::string GetTestMaterialTypeJson()
  87. {
  88. const char* materialTypeJson = R"(
  89. {
  90. "version": 10,
  91. "propertyLayout": {
  92. "propertyGroups": [
  93. {
  94. "name": "general",
  95. "properties": [
  96. {"name": "MyBool", "type": "bool"},
  97. {"name": "MyInt", "type": "Int"},
  98. {"name": "MyUInt", "type": "UInt"},
  99. {"name": "MyFloat", "type": "Float"},
  100. {"name": "MyFloat2", "type": "Vector2"},
  101. {"name": "MyFloat3", "type": "Vector3"},
  102. {"name": "MyFloat4", "type": "Vector4"},
  103. {"name": "MyColor", "type": "Color"},
  104. {"name": "MyImage", "type": "Image"},
  105. {"name": "MyEnum", "type": "Enum", "enumValues": ["Enum0", "Enum1", "Enum2"], "defaultValue": "Enum0"}
  106. ]
  107. }
  108. ]
  109. },
  110. "shaders": [
  111. {
  112. "file": "@exefolder@/Temp/test.shader"
  113. }
  114. ],
  115. "versionUpdates": [
  116. {
  117. "toVersion": 2,
  118. "actions": [
  119. {"op": "rename", "from": "general.testColorNameA", "to": "general.testColorNameB"}
  120. ]
  121. },
  122. {
  123. "toVersion": 4,
  124. "actions": [
  125. {"op": "rename", "from": "general.testColorNameB", "to": "general.testColorNameC"}
  126. ]
  127. },
  128. {
  129. "toVersion": 6,
  130. "actions": [
  131. {"op": "rename", "from": "oldGroup.MyFloat", "to": "general.MyFloat"},
  132. {"op": "rename", "from": "oldGroup.MyIntOldName", "to": "general.MyInt"}
  133. ]
  134. },
  135. {
  136. "toVersion": 10,
  137. "actions": [
  138. {"op": "rename", "from": "general.testColorNameC", "to": "general.MyColor"}
  139. ]
  140. }
  141. ]
  142. }
  143. )";
  144. return materialTypeJson;
  145. }
  146. Data::Asset<MaterialTypeAsset> CreateTestMaterialTypeAsset(Data::AssetId assetId)
  147. {
  148. MaterialTypeSourceData materialTypeSourceData;
  149. LoadTestDataFromJson(materialTypeSourceData, GetTestMaterialTypeJson());
  150. return materialTypeSourceData.CreateMaterialTypeAsset(assetId).TakeValue();
  151. }
  152. };
  153. void AddPropertyGroup(MaterialSourceData&, AZStd::string_view)
  154. {
  155. // Old function, left blank intentionally
  156. }
  157. void AddProperty(MaterialSourceData& material, AZStd::string_view groupName, AZStd::string_view propertyName, const MaterialPropertyValue& value)
  158. {
  159. MaterialPropertyId id{groupName, propertyName};
  160. material.SetPropertyValue(id, value);
  161. }
  162. TEST_F(MaterialSourceDataTests, CreateMaterialAsset_BasicProperties)
  163. {
  164. MaterialSourceData sourceData;
  165. sourceData.m_materialType = "@exefolder@/Temp/test.materialtype";
  166. AddPropertyGroup(sourceData, "general");
  167. AddProperty(sourceData, "general", "MyBool", true);
  168. AddProperty(sourceData, "general", "MyInt", -10);
  169. AddProperty(sourceData, "general", "MyUInt", 25u);
  170. AddProperty(sourceData, "general", "MyFloat", 1.5f);
  171. AddProperty(sourceData, "general", "MyColor", AZ::Color{0.1f, 0.2f, 0.3f, 0.4f});
  172. AddProperty(sourceData, "general", "MyFloat2", AZ::Vector2(2.1f, 2.2f));
  173. AddProperty(sourceData, "general", "MyFloat3", AZ::Vector3(3.1f, 3.2f, 3.3f));
  174. AddProperty(sourceData, "general", "MyFloat4", AZ::Vector4(4.1f, 4.2f, 4.3f, 4.4f));
  175. AddProperty(sourceData, "general", "MyImage", AZStd::string("@exefolder@/Temp/test.streamingimage"));
  176. AddProperty(sourceData, "general", "MyEnum", AZStd::string("Enum1"));
  177. auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
  178. EXPECT_TRUE(materialAssetOutcome.IsSuccess());
  179. Data::Asset<MaterialAsset> materialAsset = materialAssetOutcome.GetValue();
  180. // The order here is based on the order in the MaterialTypeSourceData, as added to the MaterialTypeAssetCreator.
  181. EXPECT_EQ(materialAsset->GetPropertyValues()[0].GetValue<bool>(), true);
  182. EXPECT_EQ(materialAsset->GetPropertyValues()[1].GetValue<int32_t>(), -10);
  183. EXPECT_EQ(materialAsset->GetPropertyValues()[2].GetValue<uint32_t>(), 25u);
  184. EXPECT_EQ(materialAsset->GetPropertyValues()[3].GetValue<float>(), 1.5f);
  185. EXPECT_EQ(materialAsset->GetPropertyValues()[4].GetValue<Vector2>(), Vector2(2.1f, 2.2f));
  186. EXPECT_EQ(materialAsset->GetPropertyValues()[5].GetValue<Vector3>(), Vector3(3.1f, 3.2f, 3.3f));
  187. EXPECT_EQ(materialAsset->GetPropertyValues()[6].GetValue<Vector4>(), Vector4(4.1f, 4.2f, 4.3f, 4.4f));
  188. EXPECT_EQ(materialAsset->GetPropertyValues()[7].GetValue<Color>(), Color(0.1f, 0.2f, 0.3f, 0.4f));
  189. EXPECT_EQ(materialAsset->GetPropertyValues()[8].GetValue<Data::Asset<ImageAsset>>(), m_testImageAsset);
  190. EXPECT_EQ(materialAsset->GetPropertyValues()[9].GetValue<uint32_t>(), 1u);
  191. }
  192. TEST_F(MaterialSourceDataTests, CreateMaterialAsset_VersionUpdate_ReportTheSpecifiedMaterialTypeVersion)
  193. {
  194. // This is in response to a specific issue where the material type version update reported the wrong version
  195. // because MaterialSourceData was not feeding it to the MaterialAsset
  196. AZ::Utils::WriteFile(GetTestMaterialTypeJson(), "@exefolder@/Temp/test.materialtype");
  197. MaterialSourceData sourceData;
  198. sourceData.m_materialType = "@exefolder@/Temp/test.materialtype";
  199. sourceData.m_materialTypeVersion = 5;
  200. AddPropertyGroup(sourceData, "oldGroup");
  201. AddProperty(sourceData, "oldGroup", "MyFloat", 1.2f);
  202. ErrorMessageFinder findVersionWarning;
  203. findVersionWarning.AddExpectedErrorMessage("This material is based on version '5'");
  204. findVersionWarning.AddExpectedErrorMessage("the material type is now at version '10'");
  205. findVersionWarning.AddExpectedErrorMessage("Consider updating the .material source file");
  206. findVersionWarning.ResetCounts();
  207. sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "");
  208. findVersionWarning.CheckExpectedErrorsFound();
  209. findVersionWarning.ResetCounts();
  210. sourceData.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
  211. findVersionWarning.CheckExpectedErrorsFound();
  212. }
  213. TEST_F(MaterialSourceDataTests, CreateMaterialAsset_VersionUpdate_ReportUnspecifiedMaterialTypeVersion)
  214. {
  215. // This is in response to a specific issue where the material type version update reported the wrong version
  216. // because MaterialSourceData was not feeding it to the MaterialAsset.
  217. // It's the same as CreateMaterialAsset_VersionUpdate_ReportTheSpecifiedMaterialTypeVersion except it looks
  218. // for "<Unspecified>" in the warning message.
  219. AZ::Utils::WriteFile(GetTestMaterialTypeJson(), "@exefolder@/Temp/test.materialtype");
  220. MaterialSourceData sourceData;
  221. sourceData.m_materialType = "@exefolder@/Temp/test.materialtype";
  222. // We intentionally do not set sourceData.m_materialTypeVersion here
  223. AddPropertyGroup(sourceData, "oldGroup");
  224. AddProperty(sourceData, "oldGroup", "MyFloat", 1.2f);
  225. ErrorMessageFinder findVersionWarning;
  226. findVersionWarning.AddExpectedErrorMessage("This material is based on version <Unspecified>");
  227. findVersionWarning.AddExpectedErrorMessage("the material type is now at version '10'");
  228. findVersionWarning.AddExpectedErrorMessage("Consider updating the .material source file");
  229. findVersionWarning.ResetCounts();
  230. sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "");
  231. findVersionWarning.CheckExpectedErrorsFound();
  232. findVersionWarning.ResetCounts();
  233. sourceData.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
  234. findVersionWarning.CheckExpectedErrorsFound();
  235. }
  236. // Can return a Vector4 or a Color as a Vector4
  237. Vector4 GetAsVector4(const MaterialPropertyValue& value)
  238. {
  239. if (value.GetTypeId() == azrtti_typeid<Vector4>())
  240. {
  241. return value.GetValue<Vector4>();
  242. }
  243. else if (value.GetTypeId() == azrtti_typeid<Color>())
  244. {
  245. return value.GetValue<Color>().GetAsVector4();
  246. }
  247. else
  248. {
  249. return Vector4::CreateZero();
  250. }
  251. }
  252. // Can return a Int or a UInt as a Int
  253. int32_t GetAsInt(const MaterialPropertyValue& value)
  254. {
  255. if (value.GetTypeId() == azrtti_typeid<int32_t>())
  256. {
  257. return value.GetValue<int32_t>();
  258. }
  259. else if (value.GetTypeId() == azrtti_typeid<uint32_t>())
  260. {
  261. return aznumeric_cast<int32_t>(value.GetValue<uint32_t>());
  262. }
  263. else
  264. {
  265. return 0;
  266. }
  267. }
  268. template<typename TargetTypeT>
  269. bool AreTypesCompatible(const MaterialPropertyValue& a, const MaterialPropertyValue& b)
  270. {
  271. auto fixupType = [](TypeId t)
  272. {
  273. if (t == azrtti_typeid<uint32_t>())
  274. {
  275. return azrtti_typeid<int32_t>();
  276. }
  277. if (t == azrtti_typeid<Color>())
  278. {
  279. return azrtti_typeid<Vector4>();
  280. }
  281. return t;
  282. };
  283. TypeId targetTypeId = azrtti_typeid<TargetTypeT>();
  284. return fixupType(a.GetTypeId()) == fixupType(targetTypeId) && fixupType(b.GetTypeId()) == fixupType(targetTypeId);
  285. }
  286. void CheckEqual(const MaterialSourceData& a, const MaterialSourceData& b)
  287. {
  288. EXPECT_STREQ(a.m_materialType.c_str(), b.m_materialType.c_str());
  289. EXPECT_STREQ(a.m_description.c_str(), b.m_description.c_str());
  290. EXPECT_STREQ(a.m_parentMaterial.c_str(), b.m_parentMaterial.c_str());
  291. EXPECT_EQ(a.m_materialTypeVersion, b.m_materialTypeVersion);
  292. EXPECT_EQ(a.GetPropertyValues().size(), b.GetPropertyValues().size());
  293. for (auto& [propertyId, propertyValue] : a.GetPropertyValues())
  294. {
  295. if (!b.HasPropertyValue(propertyId))
  296. {
  297. EXPECT_TRUE(false) << "Property '" << propertyId.GetCStr() << "' not found in material B";
  298. continue;
  299. }
  300. auto& propertyA = propertyValue;
  301. auto& propertyB = b.GetPropertyValue(propertyId);
  302. AZStd::string propertyReference = AZStd::string::format(" for property '%s'", propertyId.GetCStr());
  303. // We allow some types like Vector4 and Color or Int and UInt to be interchangeable since they serialize the same and can be converted when the MaterialAsset is finalized.
  304. if (AreTypesCompatible<bool>(propertyA, propertyB))
  305. {
  306. EXPECT_EQ(propertyA.GetValue<bool>(), propertyB.GetValue<bool>()) << propertyReference.c_str();
  307. }
  308. else if (AreTypesCompatible<int32_t>(propertyA, propertyB))
  309. {
  310. EXPECT_EQ(GetAsInt(propertyA), GetAsInt(propertyB)) << propertyReference.c_str();
  311. }
  312. else if (AreTypesCompatible<float>(propertyA, propertyB))
  313. {
  314. EXPECT_NEAR(propertyA.GetValue<float>(), propertyB.GetValue<float>(), 0.01) << propertyReference.c_str();
  315. }
  316. else if (AreTypesCompatible<Vector2>(propertyA, propertyB))
  317. {
  318. EXPECT_TRUE(propertyA.GetValue<Vector2>().IsClose(propertyB.GetValue<Vector2>())) << propertyReference.c_str();
  319. }
  320. else if (AreTypesCompatible<Vector3>(propertyA, propertyB))
  321. {
  322. EXPECT_TRUE(propertyA.GetValue<Vector3>().IsClose(propertyB.GetValue<Vector3>())) << propertyReference.c_str();
  323. }
  324. else if (AreTypesCompatible<Vector4>(propertyA, propertyB))
  325. {
  326. EXPECT_TRUE(GetAsVector4(propertyA).IsClose(GetAsVector4(propertyB))) << propertyReference.c_str();
  327. }
  328. else if (AreTypesCompatible<AZStd::string>(propertyA, propertyB))
  329. {
  330. EXPECT_STREQ(propertyA.GetValue<AZStd::string>().c_str(), propertyB.GetValue<AZStd::string>().c_str()) << propertyReference.c_str();
  331. }
  332. else
  333. {
  334. ADD_FAILURE();
  335. }
  336. }
  337. }
  338. TEST_F(MaterialSourceDataTests, TestJsonRoundTrip)
  339. {
  340. const char* materialTypeFilePath = "@exefolder@/Temp/roundTripTest.materialtype";
  341. MaterialSourceData sourceDataOriginal;
  342. sourceDataOriginal.m_materialType = materialTypeFilePath;
  343. sourceDataOriginal.m_parentMaterial = materialTypeFilePath;
  344. sourceDataOriginal.m_description = "This is a description";
  345. sourceDataOriginal.m_materialTypeVersion = 7;
  346. AddPropertyGroup(sourceDataOriginal, "groupA");
  347. AddProperty(sourceDataOriginal, "groupA", "MyBool", true);
  348. AddProperty(sourceDataOriginal, "groupA", "MyInt", -10);
  349. AddProperty(sourceDataOriginal, "groupA", "MyUInt", 25u);
  350. AddPropertyGroup(sourceDataOriginal, "groupB");
  351. AddProperty(sourceDataOriginal, "groupB", "MyFloat", 1.5f);
  352. AddProperty(sourceDataOriginal, "groupB", "MyFloat2", AZ::Vector2(2.1f, 2.2f));
  353. AddProperty(sourceDataOriginal, "groupB", "MyFloat3", AZ::Vector3(3.1f, 3.2f, 3.3f));
  354. AddPropertyGroup(sourceDataOriginal, "groupC");
  355. AddProperty(sourceDataOriginal, "groupC", "MyFloat4", AZ::Vector4(4.1f, 4.2f, 4.3f, 4.4f));
  356. AddProperty(sourceDataOriginal, "groupC", "MyColor", AZ::Color{0.1f, 0.2f, 0.3f, 0.4f});
  357. AddProperty(sourceDataOriginal, "groupC", "MyImage", AZStd::string("@exefolder@/Temp/test.streamingimage"));
  358. AZStd::string sourceDataSerialized;
  359. JsonTestResult storeResult = StoreTestDataToJson(sourceDataOriginal, sourceDataSerialized);
  360. MaterialSourceData sourceDataCopy;
  361. JsonTestResult loadResult = LoadTestDataFromJson(sourceDataCopy, sourceDataSerialized);
  362. CheckEqual(sourceDataOriginal, sourceDataCopy);
  363. }
  364. TEST_F(MaterialSourceDataTests, TestLoadLegacyFormat)
  365. {
  366. const AZStd::string inputJson = R"(
  367. {
  368. "materialType": "test.materialtype", // Doesn't matter, this isn't loaded
  369. "properties": {
  370. "groupA": {
  371. "myBool": true,
  372. "myInt": 5,
  373. "myFloat": 0.5
  374. },
  375. "groupB": {
  376. "myFloat2": [0.1, 0.2],
  377. "myFloat3": [0.3, 0.4, 0.5],
  378. "myFloat4": [0.6, 0.7, 0.8, 0.9],
  379. "myString": "Hello"
  380. }
  381. }
  382. }
  383. )";
  384. MaterialSourceData material;
  385. LoadTestDataFromJson(material, inputJson);
  386. material.UpgradeLegacyFormat();
  387. MaterialSourceData expectedMaterial;
  388. expectedMaterial.m_materialType = "test.materialtype";
  389. expectedMaterial.SetPropertyValue(Name{"groupA.myBool"}, true);
  390. expectedMaterial.SetPropertyValue(Name{"groupA.myInt"}, 5);
  391. expectedMaterial.SetPropertyValue(Name{"groupA.myFloat"}, 0.5f);
  392. expectedMaterial.SetPropertyValue(Name{"groupB.myFloat2"}, Vector2(0.1f, 0.2f));
  393. expectedMaterial.SetPropertyValue(Name{"groupB.myFloat3"}, Vector3(0.3f, 0.4f, 0.5f));
  394. expectedMaterial.SetPropertyValue(Name{"groupB.myFloat4"}, Vector4(0.6f, 0.7f, 0.8f, 0.9f));
  395. expectedMaterial.SetPropertyValue(Name{"groupB.myString"}, AZStd::string{"Hello"});
  396. CheckEqual(expectedMaterial, material);
  397. }
  398. TEST_F(MaterialSourceDataTests, TestPropertyValues)
  399. {
  400. MaterialSourceData material;
  401. Name foo{"foo"};
  402. Name bar{"bar"};
  403. Name baz{"baz"};
  404. EXPECT_EQ(0, material.GetPropertyValues().size());
  405. EXPECT_FALSE(material.HasPropertyValue(foo));
  406. EXPECT_FALSE(material.HasPropertyValue(bar));
  407. EXPECT_FALSE(material.HasPropertyValue(baz));
  408. EXPECT_FALSE(material.GetPropertyValue(foo).IsValid());
  409. EXPECT_FALSE(material.GetPropertyValue(bar).IsValid());
  410. EXPECT_FALSE(material.GetPropertyValue(baz).IsValid());
  411. material.SetPropertyValue(Name{"foo"}, 2);
  412. material.SetPropertyValue(Name{"bar"}, true);
  413. material.SetPropertyValue(Name{"baz"}, 0.5f);
  414. EXPECT_EQ(3, material.GetPropertyValues().size());
  415. EXPECT_TRUE(material.HasPropertyValue(foo));
  416. EXPECT_TRUE(material.HasPropertyValue(bar));
  417. EXPECT_TRUE(material.HasPropertyValue(baz));
  418. EXPECT_TRUE(material.GetPropertyValue(foo).IsValid());
  419. EXPECT_TRUE(material.GetPropertyValue(bar).IsValid());
  420. EXPECT_TRUE(material.GetPropertyValue(baz).IsValid());
  421. EXPECT_EQ(material.GetPropertyValue(foo).GetValue<int32_t>(), 2);
  422. EXPECT_EQ(material.GetPropertyValue(bar).GetValue<bool>(), true);
  423. EXPECT_EQ(material.GetPropertyValue(baz).GetValue<float>(), 0.5f);
  424. material.RemovePropertyValue(bar);
  425. EXPECT_EQ(2, material.GetPropertyValues().size());
  426. EXPECT_TRUE(material.HasPropertyValue(foo));
  427. EXPECT_FALSE(material.HasPropertyValue(bar));
  428. EXPECT_TRUE(material.HasPropertyValue(baz));
  429. EXPECT_TRUE(material.GetPropertyValue(foo).IsValid());
  430. EXPECT_FALSE(material.GetPropertyValue(bar).IsValid());
  431. EXPECT_TRUE(material.GetPropertyValue(baz).IsValid());
  432. EXPECT_EQ(material.GetPropertyValue(foo).GetValue<int32_t>(), 2);
  433. EXPECT_EQ(material.GetPropertyValue(baz).GetValue<float>(), 0.5f);
  434. }
  435. TEST_F(MaterialSourceDataTests, Load_MaterialTypeAfterPropertyList)
  436. {
  437. const AZStd::string simpleMaterialTypeJson = R"(
  438. {
  439. "propertyLayout": {
  440. "propertyGroups":
  441. [
  442. {
  443. "name": "general",
  444. "properties": [
  445. {
  446. "name": "testValue",
  447. "type": "Float"
  448. }
  449. ]
  450. }
  451. ]
  452. }
  453. }
  454. )";
  455. AZ::Utils::WriteFile(simpleMaterialTypeJson, "@exefolder@/Temp/simpleMaterialType.materialtype");
  456. // It shouldn't matter whether the materialType field appears before the property value list. This allows for the possibility
  457. // that customer scripts generate material data and happen to use an unexpected order.
  458. const AZStd::string inputJson = R"(
  459. {
  460. "propertyValues": {
  461. "general.testValue": 1.2
  462. },
  463. "materialType": "@exefolder@/Temp/simpleMaterialType.materialtype"
  464. }
  465. )";
  466. MaterialSourceData material;
  467. JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
  468. EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
  469. EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
  470. float testValue = material.GetPropertyValue(Name{"general.testValue"}).GetValue<float>();
  471. EXPECT_FLOAT_EQ(1.2f, testValue);
  472. }
  473. TEST_F(MaterialSourceDataTests, CreateMaterialAsset_NoMaterialType)
  474. {
  475. const AZStd::string inputJson = R"(
  476. {
  477. "materialTypeVersion": 1,
  478. "propertyValues": {
  479. "baseColor.color": [1.0,1.0,1.0]
  480. }
  481. }
  482. )";
  483. MaterialSourceData material;
  484. JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
  485. const bool elevateWarnings = false;
  486. ErrorMessageFinder errorMessageFinder;
  487. errorMessageFinder.Reset();
  488. errorMessageFinder.AddExpectedErrorMessage("materialType was not specified");
  489. auto result = material.CreateMaterialAsset(AZ::Uuid::CreateRandom(), "test.material", elevateWarnings);
  490. EXPECT_FALSE(result.IsSuccess());
  491. errorMessageFinder.CheckExpectedErrorsFound();
  492. errorMessageFinder.Reset();
  493. errorMessageFinder.AddExpectedErrorMessage("materialType was not specified");
  494. result = material.CreateMaterialAssetFromSourceData(AZ::Uuid::CreateRandom(), "test.material", elevateWarnings);
  495. EXPECT_FALSE(result.IsSuccess());
  496. errorMessageFinder.CheckExpectedErrorsFound();
  497. }
  498. TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MaterialTypeDoesNotExist)
  499. {
  500. const AZStd::string inputJson = R"(
  501. {
  502. "materialType": "DoesNotExist.materialtype",
  503. "materialTypeVersion": 1,
  504. "propertyValues": {
  505. "baseColor.color": [1.0,1.0,1.0]
  506. }
  507. }
  508. )";
  509. MaterialSourceData material;
  510. JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
  511. const bool elevateWarnings = false;
  512. ErrorMessageFinder errorMessageFinder;
  513. errorMessageFinder.Reset();
  514. errorMessageFinder.AddExpectedErrorMessage("Could not find asset for source file [DoesNotExist.materialtype]");
  515. auto result = material.CreateMaterialAsset(AZ::Uuid::CreateRandom(), "test.material", elevateWarnings);
  516. EXPECT_FALSE(result.IsSuccess());
  517. errorMessageFinder.CheckExpectedErrorsFound();
  518. errorMessageFinder.Reset();
  519. errorMessageFinder.AddExpectedErrorMessage("Could not find asset for source file [DoesNotExist.materialtype]");
  520. errorMessageFinder.AddIgnoredErrorMessage("Could not find material type file", true);
  521. errorMessageFinder.AddIgnoredErrorMessage("Failed to create material type asset ID", true);
  522. result = material.CreateMaterialAssetFromSourceData(AZ::Uuid::CreateRandom(), "test.material", elevateWarnings);
  523. EXPECT_FALSE(result.IsSuccess());
  524. errorMessageFinder.CheckExpectedErrorsFound();
  525. }
  526. TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MaterialPropertyNotFound)
  527. {
  528. MaterialSourceData material;
  529. material.m_materialType = "@exefolder@/Temp/test.materialtype";
  530. AddPropertyGroup(material, "general");
  531. AddProperty(material, "general", "FieldDoesNotExist", 1.5f);
  532. const bool elevateWarnings = true;
  533. ErrorMessageFinder errorMessageFinder("\"general.FieldDoesNotExist\" is not found");
  534. errorMessageFinder.AddIgnoredErrorMessage("Failed to build MaterialAsset", true);
  535. auto result = material.CreateMaterialAsset(AZ::Uuid::CreateRandom(), "test.material", elevateWarnings);
  536. EXPECT_FALSE(result.IsSuccess());
  537. errorMessageFinder.CheckExpectedErrorsFound();
  538. }
  539. TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MultiLevelDataInheritance)
  540. {
  541. MaterialSourceData sourceDataLevel1;
  542. sourceDataLevel1.m_materialType = "@exefolder@/Temp/test.materialtype";
  543. AddPropertyGroup(sourceDataLevel1, "general");
  544. AddProperty(sourceDataLevel1, "general", "MyFloat", 1.5f);
  545. AddProperty(sourceDataLevel1, "general", "MyColor", AZ::Color{0.1f, 0.2f, 0.3f, 0.4f});
  546. MaterialSourceData sourceDataLevel2;
  547. sourceDataLevel2.m_materialType = "@exefolder@/Temp/test.materialtype";
  548. sourceDataLevel2.m_parentMaterial = "level1.material";
  549. AddPropertyGroup(sourceDataLevel2, "general");
  550. AddProperty(sourceDataLevel2, "general", "MyColor", AZ::Color{0.15f, 0.25f, 0.35f, 0.45f});
  551. AddProperty(sourceDataLevel2, "general", "MyFloat2", AZ::Vector2{4.1f, 4.2f});
  552. MaterialSourceData sourceDataLevel3;
  553. sourceDataLevel3.m_materialType = "@exefolder@/Temp/test.materialtype";
  554. sourceDataLevel3.m_parentMaterial = "level2.material";
  555. AddPropertyGroup(sourceDataLevel3, "general");
  556. AddProperty(sourceDataLevel3, "general", "MyFloat", 3.5f);
  557. auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
  558. EXPECT_TRUE(materialAssetLevel1.IsSuccess());
  559. m_assetSystemStub.RegisterSourceInfo("level1.material", materialAssetLevel1.GetValue().GetId());
  560. auto materialAssetLevel2 = sourceDataLevel2.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
  561. EXPECT_TRUE(materialAssetLevel2.IsSuccess());
  562. m_assetSystemStub.RegisterSourceInfo("level2.material", materialAssetLevel2.GetValue().GetId());
  563. auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
  564. EXPECT_TRUE(materialAssetLevel3.IsSuccess());
  565. auto layout = m_testMaterialTypeAsset->GetMaterialPropertiesLayout();
  566. MaterialPropertyIndex myFloat = layout->FindPropertyIndex(Name("general.MyFloat"));
  567. MaterialPropertyIndex myFloat2 = layout->FindPropertyIndex(Name("general.MyFloat2"));
  568. MaterialPropertyIndex myColor = layout->FindPropertyIndex(Name("general.MyColor"));
  569. AZStd::span<const MaterialPropertyValue> properties;
  570. // Check level 1 properties
  571. properties = materialAssetLevel1.GetValue()->GetPropertyValues();
  572. EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
  573. EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(0.0f, 0.0f));
  574. EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.1f, 0.2f, 0.3f, 0.4f));
  575. // Check level 2 properties
  576. properties = materialAssetLevel2.GetValue()->GetPropertyValues();
  577. EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
  578. EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
  579. EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
  580. // Check level 3 properties
  581. properties = materialAssetLevel3.GetValue()->GetPropertyValues();
  582. EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 3.5f);
  583. EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
  584. EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
  585. }
  586. TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MultiLevelDataInheritance_Error_MaterialTypesDontMatch)
  587. {
  588. Data::Asset<MaterialTypeAsset> otherMaterialType;
  589. MaterialTypeAssetCreator materialTypeCreator;
  590. materialTypeCreator.Begin(Uuid::CreateRandom());
  591. materialTypeCreator.AddShader(m_testShaderAsset);
  592. AddCommonTestMaterialProperties(materialTypeCreator, "general.");
  593. EXPECT_TRUE(materialTypeCreator.End(otherMaterialType));
  594. m_assetSystemStub.RegisterSourceInfo("otherBase.materialtype", otherMaterialType.GetId());
  595. MaterialSourceData sourceDataLevel1;
  596. sourceDataLevel1.m_materialType = "@exefolder@/Temp/test.materialtype";
  597. MaterialSourceData sourceDataLevel2;
  598. sourceDataLevel2.m_materialType = "@exefolder@/Temp/test.materialtype";
  599. sourceDataLevel2.m_parentMaterial = "level1.material";
  600. MaterialSourceData sourceDataLevel3;
  601. sourceDataLevel3.m_materialType = "@exefolder@/Temp/otherBase.materialtype";
  602. sourceDataLevel3.m_parentMaterial = "level2.material";
  603. auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
  604. EXPECT_TRUE(materialAssetLevel1.IsSuccess());
  605. m_assetSystemStub.RegisterSourceInfo("level1.material", materialAssetLevel1.GetValue().GetId());
  606. auto materialAssetLevel2 = sourceDataLevel2.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
  607. EXPECT_TRUE(materialAssetLevel2.IsSuccess());
  608. m_assetSystemStub.RegisterSourceInfo("level2.material", materialAssetLevel2.GetValue().GetId());
  609. AZ_TEST_START_ASSERTTEST;
  610. auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
  611. AZ_TEST_STOP_ASSERTTEST(1);
  612. EXPECT_FALSE(materialAssetLevel3.IsSuccess());
  613. }
  614. TEST_F(MaterialSourceDataTests, CreateMaterialAsset_Error_BadInput)
  615. {
  616. // We use local functions to easily start a new MaterialAssetCreator for each test case because
  617. // the AssetCreator would just skip subsequent operations after the first failure is detected.
  618. auto expectWarning = [](const char* expectedErrorMessage, const char* secondExpectedErrorMessage, AZStd::function<void(MaterialSourceData& materialSourceData)> setOneBadInput)
  619. {
  620. MaterialSourceData sourceData;
  621. sourceData.m_materialType = "@exefolder@/Temp/test.materialtype";
  622. AddPropertyGroup(sourceData, "general");
  623. setOneBadInput(sourceData);
  624. ErrorMessageFinder errorFinder;
  625. errorFinder.AddExpectedErrorMessage(expectedErrorMessage);
  626. if (secondExpectedErrorMessage)
  627. {
  628. errorFinder.AddExpectedErrorMessage(secondExpectedErrorMessage);
  629. }
  630. errorFinder.AddIgnoredErrorMessage("Failed to build", true);
  631. auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
  632. errorFinder.CheckExpectedErrorsFound();
  633. EXPECT_FALSE(materialAssetOutcome.IsSuccess());
  634. };
  635. // Test property does not exist...
  636. expectWarning("\"general.DoesNotExist\" is not found in the material properties layout", nullptr,
  637. [](MaterialSourceData& materialSourceData)
  638. {
  639. AddProperty(materialSourceData, "general", "DoesNotExist", true);
  640. });
  641. expectWarning("\"general.DoesNotExist\" is not found in the material properties layout", nullptr,
  642. [](MaterialSourceData& materialSourceData)
  643. {
  644. AddProperty(materialSourceData, "general", "DoesNotExist", -10);
  645. });
  646. expectWarning("\"general.DoesNotExist\" is not found in the material properties layout", nullptr,
  647. [](MaterialSourceData& materialSourceData)
  648. {
  649. AddProperty(materialSourceData, "general", "DoesNotExist", 25u);
  650. });
  651. expectWarning("\"general.DoesNotExist\" is not found in the material properties layout", nullptr,
  652. [](MaterialSourceData& materialSourceData)
  653. {
  654. AddProperty(materialSourceData, "general", "DoesNotExist", 1.5f);
  655. });
  656. expectWarning("\"general.DoesNotExist\" is not found in the material properties layout", nullptr,
  657. [](MaterialSourceData& materialSourceData)
  658. {
  659. AddProperty(materialSourceData, "general", "DoesNotExist", AZ::Color{ 0.1f, 0.2f, 0.3f, 0.4f });
  660. });
  661. expectWarning("\"general.DoesNotExist\" is not found in the material properties layout", nullptr,
  662. [](MaterialSourceData& materialSourceData)
  663. {
  664. AddProperty(materialSourceData, "general", "DoesNotExist", AZStd::string("@exefolder@/Temp/test.streamingimage"));
  665. });
  666. // Missing image reference
  667. expectWarning("Could not find the image 'doesNotExist.streamingimage'",
  668. "Material at path could not resolve image doesNotExist.streamingimage, using invalid UUID {00000BAD-0BAD-0BAD-0BAD-000000000BAD}. To resolve this, verify the image exists at the relative path to a scan folder matching this reference. Verify a portion of the scan folder is not in the relative path, which is a common cause of this issue.",
  669. [](MaterialSourceData& materialSourceData)
  670. {
  671. AddProperty(materialSourceData, "general", "MyImage", AZStd::string("doesNotExist.streamingimage"));
  672. }); // In this case, the warning does happen even when the asset is not finalized, because the image path is checked earlier than that
  673. }
  674. template<typename PropertyTypeT>
  675. void CheckSimilar(PropertyTypeT a, PropertyTypeT b);
  676. template<> void CheckSimilar<float>(float a, float b) { EXPECT_FLOAT_EQ(a, b); }
  677. template<> void CheckSimilar<Vector2>(Vector2 a, Vector2 b) { EXPECT_TRUE(a.IsClose(b)); }
  678. template<> void CheckSimilar<Vector3>(Vector3 a, Vector3 b) { EXPECT_TRUE(a.IsClose(b)); }
  679. template<> void CheckSimilar<Vector4>(Vector4 a, Vector4 b) { EXPECT_TRUE(a.IsClose(b)); }
  680. template<> void CheckSimilar<Color>(Color a, Color b) { EXPECT_TRUE(a.IsClose(b)); }
  681. template<typename PropertyTypeT> void CheckSimilar(PropertyTypeT a, PropertyTypeT b) { EXPECT_EQ(a, b); }
  682. template<typename PropertyTypeT>
  683. void CheckEndToEndDataTypeResolution(const char* propertyName, const char* jsonValue, PropertyTypeT expectedFinalValue)
  684. {
  685. const char* groupName = "general";
  686. const AZStd::string inputJson = AZStd::string::format(R"(
  687. {
  688. "materialType": "@exefolder@/Temp/test.materialtype",
  689. "propertyValues": {
  690. "%s.%s": %s
  691. }
  692. }
  693. )", groupName, propertyName, jsonValue);
  694. MaterialSourceData material;
  695. JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
  696. auto materialAssetResult = material.CreateMaterialAsset(Uuid::CreateRandom(), "test.material");
  697. EXPECT_TRUE(materialAssetResult);
  698. MaterialPropertyIndex propertyIndex = materialAssetResult.GetValue()->GetMaterialPropertiesLayout()->FindPropertyIndex(MaterialPropertyId{groupName, propertyName});
  699. CheckSimilar(expectedFinalValue, materialAssetResult.GetValue()->GetPropertyValues()[propertyIndex.GetIndex()].GetValue<PropertyTypeT>());
  700. }
  701. TEST_F(MaterialSourceDataTests, TestEndToEndDataTypeResolution)
  702. {
  703. // Data types in .material files don't have to exactly match the types in .materialtype files as specified in the properties layout.
  704. // The exact location of the data type resolution has moved around over the life of the project, but the important thing is that
  705. // the data type in the source .material file gets applied correctly by the time a finalized MaterialAsset comes out the other side.
  706. CheckEndToEndDataTypeResolution("MyBool", "true", true);
  707. CheckEndToEndDataTypeResolution("MyBool", "false", false);
  708. CheckEndToEndDataTypeResolution("MyBool", "1", true);
  709. CheckEndToEndDataTypeResolution("MyBool", "0", false);
  710. CheckEndToEndDataTypeResolution("MyBool", "1.0", true);
  711. CheckEndToEndDataTypeResolution("MyBool", "0.0", false);
  712. CheckEndToEndDataTypeResolution("MyInt", "5", 5);
  713. CheckEndToEndDataTypeResolution("MyInt", "-6", -6);
  714. CheckEndToEndDataTypeResolution("MyInt", "-7.0", -7);
  715. CheckEndToEndDataTypeResolution("MyInt", "false", 0);
  716. CheckEndToEndDataTypeResolution("MyInt", "true", 1);
  717. CheckEndToEndDataTypeResolution("MyUInt", "8", 8u);
  718. CheckEndToEndDataTypeResolution("MyUInt", "9.0", 9u);
  719. CheckEndToEndDataTypeResolution("MyUInt", "false", 0u);
  720. CheckEndToEndDataTypeResolution("MyUInt", "true", 1u);
  721. CheckEndToEndDataTypeResolution("MyFloat", "2", 2.0f);
  722. CheckEndToEndDataTypeResolution("MyFloat", "-2", -2.0f);
  723. CheckEndToEndDataTypeResolution("MyFloat", "2.1", 2.1f);
  724. CheckEndToEndDataTypeResolution("MyFloat", "false", 0.0f);
  725. CheckEndToEndDataTypeResolution("MyFloat", "true", 1.0f);
  726. CheckEndToEndDataTypeResolution("MyColor", "[0.1,0.2,0.3]", Color{0.1f, 0.2f, 0.3f, 1.0});
  727. CheckEndToEndDataTypeResolution("MyColor", "[0.1, 0.2, 0.3, 0.5]", Color{0.1f, 0.2f, 0.3f, 0.5f});
  728. CheckEndToEndDataTypeResolution("MyColor", "{\"RGB8\": [255, 0, 255, 0]}", Color{1.0f, 0.0f, 1.0f, 0.0f});
  729. CheckEndToEndDataTypeResolution("MyFloat2", "[0.1,0.2]", Vector2{0.1f, 0.2f});
  730. CheckEndToEndDataTypeResolution("MyFloat2", "{\"y\":0.2, \"x\":0.1}", Vector2{0.1f, 0.2f});
  731. CheckEndToEndDataTypeResolution("MyFloat2", "{\"y\":0.2, \"x\":0.1, \"Z\":0.3}", Vector2{0.1f, 0.2f});
  732. CheckEndToEndDataTypeResolution("MyFloat2", "{\"y\":0.2, \"W\":0.4, \"x\":0.1, \"Z\":0.3}", Vector2{0.1f, 0.2f});
  733. CheckEndToEndDataTypeResolution("MyFloat3", "[0.1,0.2,0.3]", Vector3{0.1f, 0.2f, 0.3f});
  734. CheckEndToEndDataTypeResolution("MyFloat3", "{\"y\":0.2, \"x\":0.1}", Vector3{0.1f, 0.2f, 0.0f});
  735. CheckEndToEndDataTypeResolution("MyFloat3", "{\"y\":0.2, \"x\":0.1, \"Z\":0.3}", Vector3{0.1f, 0.2f, 0.3f});
  736. CheckEndToEndDataTypeResolution("MyFloat3", "{\"y\":0.2, \"W\":0.4, \"x\":0.1, \"Z\":0.3}", Vector3{0.1f, 0.2f, 0.3f});
  737. CheckEndToEndDataTypeResolution("MyFloat4", "[0.1,0.2,0.3,0.4]", Vector4{0.1f, 0.2f, 0.3f, 0.4f});
  738. CheckEndToEndDataTypeResolution("MyFloat4", "{\"y\":0.2, \"x\":0.1}", Vector4{0.1f, 0.2f, 0.0f, 0.0f});
  739. CheckEndToEndDataTypeResolution("MyFloat4", "{\"y\":0.2, \"x\":0.1, \"Z\":0.3}", Vector4{0.1f, 0.2f, 0.3f, 0.0f});
  740. CheckEndToEndDataTypeResolution("MyFloat4", "{\"y\":0.2, \"W\":0.4, \"x\":0.1, \"Z\":0.3}", Vector4{0.1f, 0.2f, 0.3f, 0.4f});
  741. }
  742. TEST_F(MaterialSourceDataTests, CreateMaterialAssetFromSourceData_MultiLevelDataInheritance)
  743. {
  744. // Note the data being tested here is based on CreateMaterialAsset_MultiLevelDataInheritance()
  745. const AZStd::string simpleMaterialTypeJson = R"(
  746. {
  747. "propertyLayout": {
  748. "propertyGroups":
  749. [
  750. {
  751. "name": "general",
  752. "properties": [
  753. {
  754. "name": "MyFloat",
  755. "type": "Float"
  756. },
  757. {
  758. "name": "MyFloat2",
  759. "type": "Vector2"
  760. },
  761. {
  762. "name": "MyColor",
  763. "type": "Color"
  764. }
  765. ]
  766. }
  767. ]
  768. },
  769. "shaders": [
  770. {
  771. "file": "test.shader"
  772. }
  773. ]
  774. }
  775. )";
  776. AZ::Utils::WriteFile(simpleMaterialTypeJson, "@exefolder@/Temp/test.materialtype");
  777. const AZStd::string material1Json = R"(
  778. {
  779. "materialType": "@exefolder@/Temp/test.materialtype",
  780. "propertyValues": {
  781. "general.MyFloat": 1.5,
  782. "general.MyColor": [0.1, 0.2, 0.3, 0.4]
  783. }
  784. }
  785. )";
  786. AZ::Utils::WriteFile(material1Json, "@exefolder@/Temp/m1.material");
  787. const AZStd::string material2Json = R"(
  788. {
  789. "materialType": "@exefolder@/Temp/test.materialtype",
  790. "parentMaterial": "@exefolder@/Temp/m1.material",
  791. "propertyValues": {
  792. "general.MyFloat2": [4.1, 4.2],
  793. "general.MyColor": [0.15, 0.25, 0.35, 0.45]
  794. }
  795. }
  796. )";
  797. AZ::Utils::WriteFile(material2Json, "@exefolder@/Temp/m2.material");
  798. const AZStd::string material3Json = R"(
  799. {
  800. "materialType": "@exefolder@/Temp/test.materialtype",
  801. "parentMaterial": "@exefolder@/Temp/m2.material",
  802. "propertyValues": {
  803. "general.MyFloat": 3.5
  804. }
  805. }
  806. )";
  807. AZ::Utils::WriteFile(material3Json, "@exefolder@/Temp/m3.material");
  808. MaterialSourceData sourceDataLevel1 = MaterialUtils::LoadMaterialSourceData("@exefolder@/Temp/m1.material").TakeValue();
  809. MaterialSourceData sourceDataLevel2 = MaterialUtils::LoadMaterialSourceData("@exefolder@/Temp/m2.material").TakeValue();
  810. MaterialSourceData sourceDataLevel3 = MaterialUtils::LoadMaterialSourceData("@exefolder@/Temp/m3.material").TakeValue();
  811. auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
  812. ASSERT_TRUE(materialAssetLevel1.IsSuccess());
  813. auto materialAssetLevel2 = sourceDataLevel2.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
  814. ASSERT_TRUE(materialAssetLevel2.IsSuccess());
  815. auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
  816. ASSERT_TRUE(materialAssetLevel3.IsSuccess());
  817. auto layout = materialAssetLevel1.GetValue()->GetMaterialPropertiesLayout();
  818. MaterialPropertyIndex myFloat = layout->FindPropertyIndex(Name("general.MyFloat"));
  819. MaterialPropertyIndex myFloat2 = layout->FindPropertyIndex(Name("general.MyFloat2"));
  820. MaterialPropertyIndex myColor = layout->FindPropertyIndex(Name("general.MyColor"));
  821. AZStd::span<const MaterialPropertyValue> properties;
  822. // Check level 1 properties
  823. properties = materialAssetLevel1.GetValue()->GetPropertyValues();
  824. EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
  825. EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(0.0f, 0.0f));
  826. EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.1f, 0.2f, 0.3f, 0.4f));
  827. // Check level 2 properties
  828. properties = materialAssetLevel2.GetValue()->GetPropertyValues();
  829. EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
  830. EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
  831. EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
  832. // Check level 3 properties
  833. properties = materialAssetLevel3.GetValue()->GetPropertyValues();
  834. EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 3.5f);
  835. EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
  836. EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
  837. }
  838. TEST_F(MaterialSourceDataTests, CreateMaterialAssetFromSourceData_MultiLevelDataInheritance_OldFormat)
  839. {
  840. // This test is the same as CreateMaterialAssetFromSourceData_MultiLevelDataInheritance except it uses the old format
  841. // where material property values in the .material file were nested, with properties listed under a group object,
  842. // rather than using a flat list of property values.
  843. // Basically, we are making sure that MaterialSourceData::UpgradeLegacyFormat() is getting called.
  844. const AZStd::string simpleMaterialTypeJson = R"(
  845. {
  846. "propertyLayout": {
  847. "propertyGroups":
  848. [
  849. {
  850. "name": "general",
  851. "properties": [
  852. {
  853. "name": "MyFloat",
  854. "type": "Float"
  855. },
  856. {
  857. "name": "MyFloat2",
  858. "type": "Vector2"
  859. },
  860. {
  861. "name": "MyColor",
  862. "type": "Color"
  863. }
  864. ]
  865. }
  866. ]
  867. },
  868. "shaders": [
  869. {
  870. "file": "test.shader"
  871. }
  872. ]
  873. }
  874. )";
  875. AZ::Utils::WriteFile(simpleMaterialTypeJson, "@exefolder@/Temp/test.materialtype");
  876. const AZStd::string material1Json = R"(
  877. {
  878. "materialType": "@exefolder@/Temp/test.materialtype",
  879. "properties": {
  880. "general": {
  881. "MyFloat": 1.5,
  882. "MyColor": [0.1, 0.2, 0.3, 0.4]
  883. }
  884. }
  885. }
  886. )";
  887. AZ::Utils::WriteFile(material1Json, "@exefolder@/Temp/m1.material");
  888. const AZStd::string material2Json = R"(
  889. {
  890. "materialType": "@exefolder@/Temp/test.materialtype",
  891. "parentMaterial": "@exefolder@/Temp/m1.material",
  892. "properties": {
  893. "general": {
  894. "MyFloat2": [4.1, 4.2],
  895. "MyColor": [0.15, 0.25, 0.35, 0.45]
  896. }
  897. }
  898. }
  899. )";
  900. AZ::Utils::WriteFile(material2Json, "@exefolder@/Temp/m2.material");
  901. const AZStd::string material3Json = R"(
  902. {
  903. "materialType": "@exefolder@/Temp/test.materialtype",
  904. "parentMaterial": "@exefolder@/Temp/m2.material",
  905. "properties": {
  906. "general": {
  907. "MyFloat": 3.5
  908. }
  909. }
  910. }
  911. )";
  912. AZ::Utils::WriteFile(material3Json, "@exefolder@/Temp/m3.material");
  913. MaterialSourceData sourceDataLevel1 = MaterialUtils::LoadMaterialSourceData("@exefolder@/Temp/m1.material").TakeValue();
  914. MaterialSourceData sourceDataLevel2 = MaterialUtils::LoadMaterialSourceData("@exefolder@/Temp/m2.material").TakeValue();
  915. MaterialSourceData sourceDataLevel3 = MaterialUtils::LoadMaterialSourceData("@exefolder@/Temp/m3.material").TakeValue();
  916. auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
  917. EXPECT_TRUE(materialAssetLevel1.IsSuccess());
  918. auto materialAssetLevel2 = sourceDataLevel2.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
  919. EXPECT_TRUE(materialAssetLevel2.IsSuccess());
  920. auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
  921. EXPECT_TRUE(materialAssetLevel3.IsSuccess());
  922. auto layout = materialAssetLevel1.GetValue()->GetMaterialPropertiesLayout();
  923. MaterialPropertyIndex myFloat = layout->FindPropertyIndex(Name("general.MyFloat"));
  924. MaterialPropertyIndex myFloat2 = layout->FindPropertyIndex(Name("general.MyFloat2"));
  925. MaterialPropertyIndex myColor = layout->FindPropertyIndex(Name("general.MyColor"));
  926. AZStd::span<const MaterialPropertyValue> properties;
  927. // Check level 1 properties
  928. properties = materialAssetLevel1.GetValue()->GetPropertyValues();
  929. EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
  930. EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(0.0f, 0.0f));
  931. EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.1f, 0.2f, 0.3f, 0.4f));
  932. // Check level 2 properties
  933. properties = materialAssetLevel2.GetValue()->GetPropertyValues();
  934. EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
  935. EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
  936. EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
  937. // Check level 3 properties
  938. properties = materialAssetLevel3.GetValue()->GetPropertyValues();
  939. EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 3.5f);
  940. EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
  941. EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
  942. }
  943. TEST_F(MaterialSourceDataTests, CreateAllPropertyDefaultsMaterial)
  944. {
  945. const char* materialTypeJson = R"(
  946. {
  947. "version": 3,
  948. "propertyLayout": {
  949. "propertyGroups": [
  950. {
  951. "name": "general",
  952. "properties": [
  953. {"name": "MyBool", "type": "bool", "defaultValue": true},
  954. {"name": "MyInt", "type": "Int", "defaultValue": -7},
  955. {"name": "MyUInt", "type": "UInt", "defaultValue": 78},
  956. {"name": "MyFloat", "type": "Float", "defaultValue": 1.5},
  957. {"name": "MyFloat2", "type": "Vector2", "defaultValue": [0.1,0.2]},
  958. {"name": "MyFloat3", "type": "Vector3", "defaultValue": [0.1,0.2,0.3]},
  959. {"name": "MyFloat4", "type": "Vector4", "defaultValue": [0.1,0.2,0.3,0.4]},
  960. {"name": "MyColor", "type": "Color", "defaultValue": [0.1,0.2,0.3,0.5]},
  961. {"name": "MyImage1", "type": "Image"},
  962. {"name": "MyImage2", "type": "Image", "defaultValue": "@exefolder@/Temp/test.streamingimage"},
  963. {"name": "MyEnum", "type": "Enum", "enumValues": ["Enum0", "Enum1", "Enum2"], "defaultValue": "Enum1"}
  964. ]
  965. }
  966. ]
  967. },
  968. "shaders": [
  969. {
  970. "file": "@exefolder@/Temp/test.shader"
  971. }
  972. ]
  973. }
  974. )";
  975. MaterialTypeSourceData materialTypeSourceData;
  976. LoadTestDataFromJson(materialTypeSourceData, materialTypeJson);
  977. Data::Asset<MaterialTypeAsset> materialType = materialTypeSourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()).TakeValue();
  978. MaterialSourceData material = MaterialSourceData::CreateAllPropertyDefaultsMaterial(materialType, "@exefolder@/Temp/test.materialtype");
  979. MaterialSourceData expecteMaterial;
  980. expecteMaterial.m_materialType = "@exefolder@/Temp/test.materialtype";
  981. expecteMaterial.m_description = "For reference, lists the default values for every available property in '@exefolder@/Temp/test.materialtype'";
  982. expecteMaterial.m_materialTypeVersion = 3;
  983. expecteMaterial.SetPropertyValue(Name{"general.MyBool"}, true);
  984. expecteMaterial.SetPropertyValue(Name{"general.MyInt"}, -7);
  985. expecteMaterial.SetPropertyValue(Name{"general.MyUInt"}, 78);
  986. expecteMaterial.SetPropertyValue(Name{"general.MyFloat"}, 1.5f);
  987. expecteMaterial.SetPropertyValue(Name{"general.MyFloat2"}, Vector2{0.1f,0.2f});
  988. expecteMaterial.SetPropertyValue(Name{"general.MyFloat3"}, Vector3{0.1f,0.2f,0.3f});
  989. expecteMaterial.SetPropertyValue(Name{"general.MyFloat4"}, Vector4{0.1f,0.2f,0.3f,0.4f});
  990. expecteMaterial.SetPropertyValue(Name{"general.MyColor"}, Color{0.1f,0.2f,0.3f,0.5f});
  991. expecteMaterial.SetPropertyValue(Name{"general.MyImage1"}, AZStd::string{});
  992. expecteMaterial.SetPropertyValue(Name{"general.MyImage2"}, DeAliasPath("@exefolder@/Temp/test.streamingimage"));
  993. expecteMaterial.SetPropertyValue(Name{"general.MyEnum"}, AZStd::string{"Enum1"});
  994. CheckEqual(expecteMaterial, material);
  995. }
  996. }