3
0

MaterialSourceData.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  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 <Atom/RPI.Edit/Material/MaterialSourceData.h>
  9. #include <Atom/RPI.Edit/Material/MaterialPropertyValueSerializer.h>
  10. #include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
  11. #include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
  12. #include <Atom/RPI.Edit/Material/MaterialUtils.h>
  13. #include <Atom/RPI.Edit/Material/MaterialConverterBus.h>
  14. #include <Atom/RPI.Edit/Common/AssetUtils.h>
  15. #include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
  16. #include <Atom/RPI.Edit/Common/JsonUtils.h>
  17. #include <Atom/RPI.Reflect/Material/MaterialAssetCreator.h>
  18. #include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
  19. #include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
  20. #include <Atom/RPI.Public/Image/StreamingImage.h>
  21. #include <AzCore/Serialization/Json/JsonUtils.h>
  22. #include <AzCore/Serialization/SerializeContext.h>
  23. #include <AzCore/Serialization/Json/JsonSerialization.h>
  24. #include <AzCore/IO/TextStreamWriters.h>
  25. #include <AzCore/IO/GenericStreams.h>
  26. #include <AzCore/IO/IOUtils.h>
  27. #include <AzCore/JSON/prettywriter.h>
  28. #include <AzCore/std/algorithm.h>
  29. #include <AzCore/Serialization/Json/RegistrationContext.h>
  30. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  31. namespace AZ
  32. {
  33. namespace RPI
  34. {
  35. void MaterialSourceData::Reflect(ReflectContext* context)
  36. {
  37. if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
  38. {
  39. serializeContext->Class<MaterialSourceData>()
  40. ->Version(2)
  41. ->Field("description", &MaterialSourceData::m_description)
  42. ->Field("materialType", &MaterialSourceData::m_materialType)
  43. ->Field("materialTypeVersion", &MaterialSourceData::m_materialTypeVersion)
  44. ->Field("parentMaterial", &MaterialSourceData::m_parentMaterial)
  45. ->Field("properties", &MaterialSourceData::m_propertiesOld)
  46. ->Field("propertyValues", &MaterialSourceData::m_propertyValues)
  47. ;
  48. serializeContext->RegisterGenericType<PropertyValueMap>();
  49. serializeContext->RegisterGenericType<PropertyGroupMap>();
  50. }
  51. }
  52. // Helper function for CreateMaterialAsset, for applying basic material property values
  53. template<typename T>
  54. void ApplyMaterialValues(MaterialAssetCreator& materialAssetCreator, const AZStd::map<Name, T>& values)
  55. {
  56. for (auto& entry : values)
  57. {
  58. const Name& propertyId = entry.first;
  59. materialAssetCreator.SetPropertyValue(propertyId, entry.second);
  60. }
  61. }
  62. void MaterialSourceData::SetPropertyValue(const Name& propertyId, const MaterialPropertyValue& value)
  63. {
  64. if (!propertyId.IsEmpty())
  65. {
  66. m_propertyValues[propertyId] = value;
  67. }
  68. }
  69. const MaterialPropertyValue& MaterialSourceData::GetPropertyValue(const Name& propertyId) const
  70. {
  71. auto iter = m_propertyValues.find(propertyId);
  72. if (iter == m_propertyValues.end())
  73. {
  74. return m_invalidValue;
  75. }
  76. else
  77. {
  78. return iter->second;
  79. }
  80. }
  81. void MaterialSourceData::RemovePropertyValue(const Name& propertyId)
  82. {
  83. m_propertyValues.erase(propertyId);
  84. }
  85. const MaterialSourceData::PropertyValueMap& MaterialSourceData::GetPropertyValues() const
  86. {
  87. return m_propertyValues;
  88. }
  89. bool MaterialSourceData::HasPropertyValue(const Name& propertyId) const
  90. {
  91. return m_propertyValues.find(propertyId) != m_propertyValues.end();
  92. }
  93. void MaterialSourceData::UpgradeLegacyFormat()
  94. {
  95. for (const auto& [groupName, propertyList] : m_propertiesOld)
  96. {
  97. for (const auto& [propertyName, propertyValue] : propertyList)
  98. {
  99. SetPropertyValue(MaterialPropertyId{groupName, propertyName}, propertyValue);
  100. }
  101. }
  102. m_propertiesOld.clear();
  103. }
  104. Outcome<Data::Asset<MaterialAsset>> MaterialSourceData::CreateMaterialAsset(
  105. Data::AssetId assetId, const AZStd::string& materialSourceFilePath, bool elevateWarnings) const
  106. {
  107. if (m_materialType.empty())
  108. {
  109. AZ_Error("MaterialSourceData", false, "materialType was not specified");
  110. return Failure();
  111. }
  112. const auto& materialTypeSourcePath =
  113. MaterialUtils::GetFinalMaterialTypeSourcePath(materialSourceFilePath, m_materialType);
  114. if (materialTypeSourcePath.empty())
  115. {
  116. AZ_Error("MaterialSourceData", false, "Could not find material type file: '%s'.", m_materialType.c_str());
  117. return Failure();
  118. }
  119. // Images are set to pre-load, so they will fully load when loading a material or material type asset.
  120. // To create the material asset, we don't need to fully load the images that are referenced.
  121. // So we use this filter to ignore the image assets
  122. Data::AssetLoadParameters dontLoadImageAssets{ [](const AZ::Data::AssetFilterInfo& filterInfo)
  123. {
  124. return
  125. filterInfo.m_assetType != AZ::AzTypeInfo<StreamingImageAsset>::Uuid() &&
  126. filterInfo.m_assetType != AZ::AzTypeInfo<AttachmentImageAsset>::Uuid() &&
  127. filterInfo.m_assetType != AZ::AzTypeInfo<ImageAsset>::Uuid();
  128. } };
  129. // In this case we need to load the material type data in preparation for the material->Finalize() step below.
  130. const auto& materialTypeAssetOutcome =
  131. AssetUtils::LoadAsset<MaterialTypeAsset>(materialTypeSourcePath, 0, AssetUtils::TraceLevel::Error, dontLoadImageAssets);
  132. if (!materialTypeAssetOutcome)
  133. {
  134. return Failure();
  135. }
  136. const auto& materialTypeAsset = materialTypeAssetOutcome.GetValue();
  137. const auto& materialTypeAssetId = materialTypeAsset.GetId();
  138. MaterialAssetCreator materialAssetCreator;
  139. materialAssetCreator.SetElevateWarnings(elevateWarnings);
  140. materialAssetCreator.Begin(assetId, materialTypeAsset);
  141. materialAssetCreator.SetMaterialTypeVersion(m_materialTypeVersion);
  142. if (!m_parentMaterial.empty())
  143. {
  144. const auto& parentMaterialAssetOutcome = AssetUtils::LoadAsset<MaterialAsset>(
  145. materialSourceFilePath, m_parentMaterial, 0, AssetUtils::TraceLevel::Error, dontLoadImageAssets);
  146. if (!parentMaterialAssetOutcome.IsSuccess())
  147. {
  148. return Failure();
  149. }
  150. const auto& parentMaterialAsset = parentMaterialAssetOutcome.GetValue();
  151. const auto& parentMaterialTypeAsset = parentMaterialAsset->GetMaterialTypeAsset();
  152. const auto& parentsMaterialTypeId = parentMaterialTypeAsset.GetId();
  153. // Make sure the parent material has the same material type
  154. if (materialTypeAssetId != parentsMaterialTypeId)
  155. {
  156. AZ_Error("MaterialSourceData", false, "This material and its parent material do not share the same material type.");
  157. return Failure();
  158. }
  159. // Inherit the parent's property values...
  160. const MaterialPropertiesLayout* propertiesLayout = parentMaterialAsset->GetMaterialPropertiesLayout();
  161. if (parentMaterialAsset->GetPropertyValues().size() != propertiesLayout->GetPropertyCount())
  162. {
  163. AZ_Assert(
  164. false,
  165. "The parent material should have been finalized with %zu properties but it has %zu. Something is out of sync.",
  166. propertiesLayout->GetPropertyCount(),
  167. parentMaterialAsset->GetPropertyValues().size());
  168. return Failure();
  169. }
  170. for (size_t propertyIndex = 0; propertyIndex < propertiesLayout->GetPropertyCount(); ++propertyIndex)
  171. {
  172. materialAssetCreator.SetPropertyValue(
  173. propertiesLayout->GetPropertyDescriptor(MaterialPropertyIndex{ propertyIndex })->GetName(),
  174. parentMaterialAsset->GetPropertyValues()[propertyIndex]);
  175. }
  176. }
  177. ApplyPropertiesToAssetCreator(materialAssetCreator, materialSourceFilePath);
  178. Data::Asset<MaterialAsset> material;
  179. if (materialAssetCreator.End(material))
  180. {
  181. return Success(material);
  182. }
  183. return Failure();
  184. }
  185. Outcome<Data::Asset<MaterialAsset>> MaterialSourceData::CreateMaterialAssetFromSourceData(
  186. Data::AssetId assetId,
  187. AZStd::string_view materialSourceFilePath,
  188. bool elevateWarnings,
  189. MaterialUtils::ImportedJsonFiles* sourceDependencies) const
  190. {
  191. if (m_materialType.empty())
  192. {
  193. AZ_Error("MaterialSourceData", false, "materialType was not specified");
  194. return Failure();
  195. }
  196. const auto& materialTypeSourcePath = MaterialUtils::GetFinalMaterialTypeSourcePath(materialSourceFilePath, m_materialType);
  197. const auto& materialTypeAssetId = MaterialUtils::GetFinalMaterialTypeAssetId(materialSourceFilePath, m_materialType);
  198. if (!materialTypeAssetId.IsSuccess() || materialTypeSourcePath.empty())
  199. {
  200. AZ_Error("MaterialSourceData", false, "Could not find material type file: '%s'.", m_materialType.c_str());
  201. return Failure();
  202. }
  203. const auto& materialTypeLoadOutcome =
  204. MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourcePath, nullptr, sourceDependencies);
  205. if (!materialTypeLoadOutcome)
  206. {
  207. AZ_Error("MaterialSourceData", false, "Failed to load MaterialTypeSourceData: '%s'.", materialTypeSourcePath.c_str());
  208. return Failure();
  209. }
  210. const auto& materialTypeSourceData = materialTypeLoadOutcome.GetValue();
  211. const auto& materialTypeAsset =
  212. materialTypeSourceData.CreateMaterialTypeAsset(materialTypeAssetId.GetValue(), materialTypeSourcePath, elevateWarnings);
  213. if (!materialTypeAsset.IsSuccess())
  214. {
  215. AZ_Error("MaterialSourceData", false, "Failed to create material type asset from source data: '%s'.", materialTypeSourcePath.c_str());
  216. return Failure();
  217. }
  218. // Track all of the material and material type assets loaded while trying to create a material asset from source data. This will
  219. // be used for evaluating circular dependencies and returned for external monitoring or other use.
  220. AZStd::unordered_set<AZStd::string> dependencies;
  221. dependencies.insert(materialSourceFilePath);
  222. dependencies.insert(materialTypeSourcePath);
  223. // Load and build a stack of MaterialSourceData from all of the parent materials in the hierarchy. Properties from the source
  224. // data will be applied in reverse to the asset creator.
  225. AZStd::vector<AZStd::pair<AZStd::string, MaterialSourceData>> parentSourceDataStack;
  226. AZStd::string parentSourceRelPath = m_parentMaterial;
  227. AZStd::string parentSourceAbsPath = AssetUtils::ResolvePathReference(materialSourceFilePath, parentSourceRelPath);
  228. while (!parentSourceRelPath.empty())
  229. {
  230. if (!dependencies.insert(parentSourceAbsPath).second)
  231. {
  232. AZ_Error("MaterialSourceData", false, "Detected circular dependency between materials: '%s' and '%s'.", materialSourceFilePath.data(), parentSourceAbsPath.c_str());
  233. return Failure();
  234. }
  235. const auto& loadParentResult = MaterialUtils::LoadMaterialSourceData(parentSourceAbsPath);
  236. if (!loadParentResult)
  237. {
  238. AZ_Error("MaterialSourceData", false, "Failed to load MaterialSourceData for parent material: '%s'.", parentSourceAbsPath.c_str());
  239. return Failure();
  240. }
  241. const auto& parentSourceData = loadParentResult.GetValue();
  242. // Make sure that all materials in the hierarchy share the same material type
  243. const auto& parentTypeAssetId = MaterialUtils::GetFinalMaterialTypeAssetId(parentSourceAbsPath, parentSourceData.m_materialType);
  244. if (!parentTypeAssetId)
  245. {
  246. AZ_Error("MaterialSourceData", false, "Parent material asset ID wasn't found: '%s'.", parentSourceAbsPath.c_str());
  247. return Failure();
  248. }
  249. if (parentTypeAssetId.GetValue() != materialTypeAssetId.GetValue())
  250. {
  251. AZ_Error("MaterialSourceData", false, "This material and its parent material do not share the same material type.");
  252. return Failure();
  253. }
  254. // Record the material source data and its absolute path so that asset references can be resolved relative to it
  255. parentSourceDataStack.emplace_back(parentSourceAbsPath, parentSourceData);
  256. // Get the location of the next parent material and push the source data onto the stack
  257. parentSourceRelPath = parentSourceData.m_parentMaterial;
  258. parentSourceAbsPath = AssetUtils::ResolvePathReference(parentSourceAbsPath, parentSourceRelPath);
  259. }
  260. // Create the material asset from all the previously loaded source data
  261. MaterialAssetCreator materialAssetCreator;
  262. materialAssetCreator.SetElevateWarnings(elevateWarnings);
  263. materialAssetCreator.Begin(assetId, materialTypeAsset.GetValue());
  264. materialAssetCreator.SetMaterialTypeVersion(m_materialTypeVersion);
  265. // Traverse the parent source data stack in reverse, applying properties from each material parent source data on to the asset
  266. // creator. This will manually accumulate all material property values in the hierarchy.
  267. while (!parentSourceDataStack.empty())
  268. {
  269. // Images and other assets must be resolved relative to the parent source data absolute path, not the path passed into this
  270. // function that is the final material being created.
  271. const auto& parentPath = parentSourceDataStack.back().first;
  272. const auto& parentData = parentSourceDataStack.back().second;
  273. parentData.ApplyPropertiesToAssetCreator(materialAssetCreator, parentPath);
  274. parentSourceDataStack.pop_back();
  275. }
  276. // Finally, apply properties from the source data that was initially requested. This could also go into the stack but is being
  277. // used for other purposes.
  278. ApplyPropertiesToAssetCreator(materialAssetCreator, materialSourceFilePath);
  279. Data::Asset<MaterialAsset> material;
  280. if (materialAssetCreator.End(material))
  281. {
  282. if (sourceDependencies)
  283. {
  284. sourceDependencies->insert(dependencies.begin(), dependencies.end());
  285. }
  286. return Success(material);
  287. }
  288. return Failure();
  289. }
  290. void MaterialSourceData::ApplyPropertiesToAssetCreator(
  291. AZ::RPI::MaterialAssetCreator& materialAssetCreator, const AZStd::string_view& materialSourceFilePath) const
  292. {
  293. for (const auto& [propertyId, propertyValue] : m_propertyValues)
  294. {
  295. if (!propertyValue.IsValid())
  296. {
  297. materialAssetCreator.ReportWarning("Value for material property '%s' is invalid.", propertyId.GetCStr());
  298. }
  299. else
  300. {
  301. // If the source value type is a string, there are two possible property types: Image and Enum. If there is a "." in
  302. // the string (for the extension) we assume it's an Image and look up the referenced Asset. Otherwise, we can assume
  303. // it's an Enum value and just preserve the original string.
  304. if (MaterialUtils::LooksLikeImageFileReference(propertyValue))
  305. {
  306. Data::Asset<ImageAsset> imageAsset;
  307. MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference(
  308. imageAsset, materialSourceFilePath, propertyValue.GetValue<AZStd::string>());
  309. if (result == MaterialUtils::GetImageAssetResult::Missing)
  310. {
  311. materialAssetCreator.ReportWarning(
  312. "Material property '%s': Could not find the image '%s'", propertyId.GetCStr(),
  313. propertyValue.GetValue<AZStd::string>().data());
  314. }
  315. materialAssetCreator.SetPropertyValue(propertyId, imageAsset);
  316. }
  317. else
  318. {
  319. materialAssetCreator.SetPropertyValue(propertyId, propertyValue);
  320. }
  321. }
  322. }
  323. }
  324. MaterialSourceData MaterialSourceData::CreateAllPropertyDefaultsMaterial(const Data::Asset<MaterialTypeAsset>& materialType, const AZStd::string& materialTypeSourcePath)
  325. {
  326. MaterialSourceData material;
  327. material.m_materialType = materialTypeSourcePath;
  328. material.m_materialTypeVersion = materialType->GetVersion();
  329. material.m_description = "For reference, lists the default values for every available property in '" + materialTypeSourcePath + "'";
  330. auto propertyLayout = materialType->GetMaterialPropertiesLayout();
  331. for (size_t i = 0; i < propertyLayout->GetPropertyCount(); ++i)
  332. {
  333. const MaterialPropertyDescriptor* descriptor = propertyLayout->GetPropertyDescriptor(MaterialPropertyIndex{i});
  334. Name propertyId = descriptor->GetName();
  335. MaterialPropertyValue defaultValue = materialType->GetDefaultPropertyValues()[i];
  336. if (defaultValue.Is<Data::Asset<ImageAsset>>())
  337. {
  338. Data::AssetId assetId = defaultValue.GetValue<Data::Asset<ImageAsset>>().GetId();
  339. Data::AssetInfo assetInfo;
  340. AZStd::string watchFolder;
  341. bool result = false;
  342. AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourceUUID, assetId.m_guid, assetInfo, watchFolder);
  343. if (result)
  344. {
  345. material.SetPropertyValue(propertyId, assetInfo.m_relativePath);
  346. }
  347. else
  348. {
  349. material.SetPropertyValue(propertyId, AZStd::string{});
  350. }
  351. }
  352. else if (descriptor->GetDataType() == MaterialPropertyDataType::Enum)
  353. {
  354. AZ_Assert(defaultValue.Is<uint32_t>(), "Enum property definitions should always have a default value of type unsigned int");
  355. material.SetPropertyValue(propertyId, descriptor->GetEnumName(defaultValue.GetValue<uint32_t>()));
  356. }
  357. else
  358. {
  359. material.SetPropertyValue(propertyId, defaultValue);
  360. }
  361. }
  362. return material;
  363. }
  364. } // namespace RPI
  365. } // namespace AZ