MaterialComponentController.cpp 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  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.Reflect/Asset/AssetUtils.h>
  9. #include <Atom/RPI.Reflect/Image/AttachmentImageAsset.h>
  10. #include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
  11. #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
  12. #include <AtomCore/Instance/InstanceDatabase.h>
  13. #include <AzCore/Asset/AssetSerializer.h>
  14. #include <AzCore/Serialization/SerializeContext.h>
  15. #include <Material/MaterialComponentController.h>
  16. namespace AZ
  17. {
  18. namespace Render
  19. {
  20. void MaterialComponentController::Reflect(ReflectContext* context)
  21. {
  22. MaterialComponentConfig::Reflect(context);
  23. if (auto serializeContext = azrtti_cast<SerializeContext*>(context))
  24. {
  25. serializeContext->Class<MaterialComponentController>()
  26. ->Version(1)
  27. ->Field("Configuration", &MaterialComponentController::m_configuration)
  28. ;
  29. }
  30. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  31. {
  32. behaviorContext->EBus<MaterialComponentRequestBus>("MaterialComponentRequestBus")
  33. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  34. ->Attribute(AZ::Script::Attributes::Category, "render")
  35. ->Attribute(AZ::Script::Attributes::Module, "render")
  36. ->Event("GetDefaultMaterialMap", &MaterialComponentRequestBus::Events::GetDefaultMaterialMap, "GetDefautMaterialMap")
  37. ->Event("FindMaterialAssignmentId", &MaterialComponentRequestBus::Events::FindMaterialAssignmentId)
  38. ->Event("GetActiveMaterialAssetId", &MaterialComponentRequestBus::Events::GetMaterialAssetId) // This function is now redundant but cannot be marked deprecated or removed in case it's still referenced in script
  39. ->Event("GetDefaultMaterialAssetId", &MaterialComponentRequestBus::Events::GetDefaultMaterialAssetId)
  40. ->Event("GetMaterialLabel", &MaterialComponentRequestBus::Events::GetMaterialLabel, "GetMaterialSlotLabel")
  41. ->Event("SetMaterialMap", &MaterialComponentRequestBus::Events::SetMaterialMap, "SetMaterialOverrides")
  42. ->Event("GetMaterialMap", &MaterialComponentRequestBus::Events::GetMaterialMap, "GetMaterialOverrides")
  43. ->Event("ClearMaterialMap", &MaterialComponentRequestBus::Events::ClearMaterialMap, "ClearAllMaterialOverrides")
  44. ->Event("SetMaterialAssetIdOnDefaultSlot", &MaterialComponentRequestBus::Events::SetMaterialAssetIdOnDefaultSlot, "SetDefaultMaterialOverride")
  45. ->Event("GetMaterialAssetIdOnDefaultSlot", &MaterialComponentRequestBus::Events::GetMaterialAssetIdOnDefaultSlot, "GetDefaultMaterialOverride")
  46. ->Event("ClearMaterialAssetIdOnDefaultSlot", &MaterialComponentRequestBus::Events::ClearMaterialAssetIdOnDefaultSlot, "ClearDefaultMaterialOverride")
  47. ->Event("ClearMaterialsOnModelSlots", &MaterialComponentRequestBus::Events::ClearMaterialsOnModelSlots, "ClearModelMaterialOverrides")
  48. ->Event("ClearMaterialsOnLodSlots", &MaterialComponentRequestBus::Events::ClearMaterialsOnLodSlots, "ClearLodMaterialOverrides")
  49. ->Event("ClearMaterialsOnInvalidSlots", &MaterialComponentRequestBus::Events::ClearMaterialsOnInvalidSlots, "ClearIncompatibleMaterialOverrides")
  50. ->Event("ClearMaterialsWithMissingAssets", &MaterialComponentRequestBus::Events::ClearMaterialsWithMissingAssets, "ClearInvalidMaterialOverrides")
  51. ->Event("RepairMaterialsWithMissingAssets", &MaterialComponentRequestBus::Events::RepairMaterialsWithMissingAssets, "RepairInvalidMaterialOverrides")
  52. ->Event("RepairMaterialsWithRenamedProperties", &MaterialComponentRequestBus::Events::RepairMaterialsWithRenamedProperties, "ApplyAutomaticPropertyUpdates")
  53. ->Event("SetMaterialAssetId", &MaterialComponentRequestBus::Events::SetMaterialAssetId, "SetMaterialOverride")
  54. ->Event("GetMaterialAssetId", &MaterialComponentRequestBus::Events::GetMaterialAssetId, "GetMaterialOverride")
  55. ->Event("ClearMaterialAssetId", &MaterialComponentRequestBus::Events::ClearMaterialAssetId, "ClearMaterialOverride")
  56. ->Event("IsMaterialAssetIdOverridden", &MaterialComponentRequestBus::Events::IsMaterialAssetIdOverridden)
  57. ->Event("HasPropertiesOverridden", &MaterialComponentRequestBus::Events::HasPropertiesOverridden)
  58. ->Event("SetPropertyValue", &MaterialComponentRequestBus::Events::SetPropertyValue, "SetPropertyOverride")
  59. ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::List)
  60. ->Event("SetPropertyValueBool", &MaterialComponentRequestBus::Events::SetPropertyValueT<bool>, "SetPropertyOverrideBool")
  61. ->Event("SetPropertyValueInt32", &MaterialComponentRequestBus::Events::SetPropertyValueT<int32_t>, "SetPropertyOverrideInt32")
  62. ->Event("SetPropertyValueUInt32", &MaterialComponentRequestBus::Events::SetPropertyValueT<uint32_t>, "SetPropertyOverrideUInt32")
  63. ->Event("SetPropertyValueFloat", &MaterialComponentRequestBus::Events::SetPropertyValueT<float>, "SetPropertyOverrideFloat")
  64. ->Event("SetPropertyValueVector2", &MaterialComponentRequestBus::Events::SetPropertyValueT<AZ::Vector2>, "SetPropertyOverrideVector2")
  65. ->Event("SetPropertyValueVector3", &MaterialComponentRequestBus::Events::SetPropertyValueT<AZ::Vector3>, "SetPropertyOverrideVector3")
  66. ->Event("SetPropertyValueVector4", &MaterialComponentRequestBus::Events::SetPropertyValueT<AZ::Vector4>, "SetPropertyOverrideVector4")
  67. ->Event("SetPropertyValueColor", &MaterialComponentRequestBus::Events::SetPropertyValueT<AZ::Color>, "SetPropertyOverrideColor")
  68. ->Event("SetPropertyValueImage", &MaterialComponentRequestBus::Events::SetPropertyValueT<AZ::Data::AssetId>, "SetPropertyOverrideImage")
  69. ->Event("SetPropertyValueString", &MaterialComponentRequestBus::Events::SetPropertyValueT<AZStd::string>, "SetPropertyOverrideString")
  70. ->Event("SetPropertyValueEnum", &MaterialComponentRequestBus::Events::SetPropertyValueT<uint32_t>, "SetPropertyOverrideEnum")
  71. ->Event("GetPropertyValue", &MaterialComponentRequestBus::Events::GetPropertyValue, "GetPropertyOverride")
  72. ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::List)
  73. ->Event("GetPropertyValueBool", &MaterialComponentRequestBus::Events::GetPropertyValueT<bool>, "GetPropertyOverrideBool")
  74. ->Event("GetPropertyValueInt32", &MaterialComponentRequestBus::Events::GetPropertyValueT<int32_t>, "GetPropertyOverrideInt32")
  75. ->Event("GetPropertyValueUInt32", &MaterialComponentRequestBus::Events::GetPropertyValueT<uint32_t>, "GetPropertyOverrideUInt32")
  76. ->Event("GetPropertyValueFloat", &MaterialComponentRequestBus::Events::GetPropertyValueT<float>, "GetPropertyOverrideFloat")
  77. ->Event("GetPropertyValueVector2", &MaterialComponentRequestBus::Events::GetPropertyValueT<AZ::Vector2>, "GetPropertyOverrideVector2")
  78. ->Event("GetPropertyValueVector3", &MaterialComponentRequestBus::Events::GetPropertyValueT<AZ::Vector3>, "GetPropertyOverrideVector3")
  79. ->Event("GetPropertyValueVector4", &MaterialComponentRequestBus::Events::GetPropertyValueT<AZ::Vector4>, "GetPropertyOverrideVector4")
  80. ->Event("GetPropertyValueColor", &MaterialComponentRequestBus::Events::GetPropertyValueT<AZ::Color>, "GetPropertyOverrideColor")
  81. ->Event("GetPropertyValueImage", &MaterialComponentRequestBus::Events::GetPropertyValueT<AZ::Data::AssetId>, "GetPropertyOverrideImage")
  82. ->Event("GetPropertyValueString", &MaterialComponentRequestBus::Events::GetPropertyValueT<AZStd::string>, "GetPropertyOverrideString")
  83. ->Event("GetPropertyValueEnum", &MaterialComponentRequestBus::Events::GetPropertyValueT<uint32_t>, "GetPropertyOverrideEnum")
  84. ->Event("ClearPropertyValue", &MaterialComponentRequestBus::Events::ClearPropertyValue, "ClearPropertyOverride")
  85. ->Event("ClearPropertyValues", &MaterialComponentRequestBus::Events::ClearPropertyValues, "ClearPropertyOverrides")
  86. ->Event("ClearAllPropertyValues", &MaterialComponentRequestBus::Events::ClearAllPropertyValues, "ClearAllPropertyOverrides")
  87. ->Event("SetPropertyValues", &MaterialComponentRequestBus::Events::SetPropertyValues, "SetPropertyOverrides")
  88. ->Event("GetPropertyValues", &MaterialComponentRequestBus::Events::GetPropertyValues, "GetPropertyOverrides")
  89. ;
  90. }
  91. }
  92. void MaterialComponentController::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  93. {
  94. provided.push_back(AZ_CRC_CE("MaterialProviderService"));
  95. }
  96. void MaterialComponentController::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  97. {
  98. incompatible.push_back(AZ_CRC_CE("MaterialProviderService"));
  99. }
  100. void MaterialComponentController::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  101. {
  102. required.push_back(AZ_CRC_CE("MaterialConsumerService"));
  103. }
  104. MaterialComponentController::MaterialComponentController(const MaterialComponentConfig& config)
  105. : m_configuration(config)
  106. {
  107. ConvertAssetsForSerialization();
  108. }
  109. void MaterialComponentController::Activate(EntityId entityId)
  110. {
  111. m_entityId = entityId;
  112. m_queuedMaterialsCreatedNotification = false;
  113. m_queuedMaterialsUpdatedNotification = false;
  114. MaterialComponentRequestBus::Handler::BusConnect(m_entityId);
  115. MaterialConsumerNotificationBus::Handler::BusConnect(m_entityId);
  116. LoadMaterials();
  117. }
  118. void MaterialComponentController::Deactivate()
  119. {
  120. MaterialComponentRequestBus::Handler::BusDisconnect();
  121. MaterialConsumerNotificationBus::Handler::BusDisconnect();
  122. ReleaseMaterials();
  123. // Sending notification to wipe any previously assigned material overrides
  124. MaterialComponentNotificationBus::Event(
  125. m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, MaterialAssignmentMap());
  126. m_entityId = AZ::EntityId(AZ::EntityId::InvalidEntityId);
  127. }
  128. void MaterialComponentController::SetConfiguration(const MaterialComponentConfig& config)
  129. {
  130. m_configuration = config;
  131. ConvertAssetsForSerialization();
  132. }
  133. const MaterialComponentConfig& MaterialComponentController::GetConfiguration() const
  134. {
  135. return m_configuration;
  136. }
  137. void MaterialComponentController::OnAssetReady(Data::Asset<Data::AssetData> asset)
  138. {
  139. InitializeMaterialInstance(asset);
  140. }
  141. void MaterialComponentController::OnAssetReloaded(Data::Asset<Data::AssetData> asset)
  142. {
  143. InitializeMaterialInstance(asset);
  144. }
  145. void MaterialComponentController::OnAssetError(Data::Asset<Data::AssetData> asset)
  146. {
  147. DisplayMissingAssetWarning(asset);
  148. InitializeMaterialInstance(asset);
  149. }
  150. void MaterialComponentController::OnAssetReloadError(Data::Asset<Data::AssetData> asset)
  151. {
  152. DisplayMissingAssetWarning(asset);
  153. InitializeMaterialInstance(asset);
  154. }
  155. void MaterialComponentController::OnSystemTick()
  156. {
  157. if (m_queuedLoadMaterials)
  158. {
  159. m_queuedLoadMaterials = false;
  160. LoadMaterials();
  161. }
  162. if (m_queuedMaterialsCreatedNotification)
  163. {
  164. m_queuedMaterialsCreatedNotification = false;
  165. MaterialComponentNotificationBus::Event(
  166. m_entityId, &MaterialComponentNotifications::OnMaterialsCreated, m_configuration.m_materials);
  167. }
  168. bool propertiesChanged = false;
  169. AZStd::unordered_set<MaterialAssignmentId> materialsWithDirtyProperties;
  170. AZStd::swap(m_materialsWithDirtyProperties, materialsWithDirtyProperties);
  171. // Iterate through all MaterialAssignmentId's that have property overrides and attempt to apply them
  172. for (const auto& materialAssignmentId : materialsWithDirtyProperties)
  173. {
  174. const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
  175. if (materialIt != m_configuration.m_materials.end())
  176. {
  177. if (materialIt->second.ApplyProperties())
  178. {
  179. propertiesChanged = true;
  180. }
  181. else
  182. {
  183. // If the material had properties to apply and it could not be compiled then queue it again
  184. m_materialsWithDirtyProperties.emplace(materialAssignmentId);
  185. }
  186. }
  187. }
  188. if (propertiesChanged)
  189. {
  190. MaterialComponentNotificationBus::Event(
  191. m_entityId, &MaterialComponentNotifications::OnMaterialPropertiesUpdated, m_configuration.m_materials);
  192. }
  193. // Only send notifications that materials have been updated after all pending properties have been applied
  194. if (m_queuedMaterialsUpdatedNotification && m_materialsWithDirtyProperties.empty())
  195. {
  196. // Materials have been edited and instances have changed but the notification will only be sent once per tick
  197. m_queuedMaterialsUpdatedNotification = false;
  198. MaterialComponentNotificationBus::Event(
  199. m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, m_configuration.m_materials);
  200. }
  201. // Only disconnect from the tick bus if there is no remaining work for the next tick. It's possible that additional work was
  202. // queued while notifications were in progress.
  203. if (!m_queuedLoadMaterials &&
  204. !m_queuedMaterialsCreatedNotification &&
  205. !m_queuedMaterialsUpdatedNotification &&
  206. m_materialsWithDirtyProperties.empty())
  207. {
  208. SystemTickBus::Handler::BusDisconnect();
  209. }
  210. }
  211. void MaterialComponentController::LoadMaterials()
  212. {
  213. // Caching previously loaded unique materials to avoid unloading and reloading assets that have not changed
  214. AZStd::unordered_map<AZ::Data::AssetId, AZ::Data::Asset<AZ::RPI::MaterialAsset>> uniqueMaterialMapBeforeLoad;
  215. AZStd::swap(uniqueMaterialMapBeforeLoad, m_uniqueMaterialMap);
  216. // Clear any previously loaded or queued material assets, instances, or notifications
  217. ReleaseMaterials();
  218. MaterialConsumerRequestBus::EventResult(
  219. m_defaultMaterialMap, m_entityId, &MaterialConsumerRequestBus::Events::GetDefaultMaterialMap);
  220. // Build tables of all referenced materials so that we can load and look up defaults
  221. for (const auto& [materialAssignmentId, materialAssignment] : m_defaultMaterialMap)
  222. {
  223. const auto& defaultMaterialAsset = materialAssignment.m_materialAsset;
  224. m_uniqueMaterialMap[defaultMaterialAsset.GetId()] = defaultMaterialAsset;
  225. auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
  226. if (materialIt != m_configuration.m_materials.end())
  227. {
  228. const auto& overrideMaterialAsset = materialIt->second.m_materialAsset;
  229. m_uniqueMaterialMap[overrideMaterialAsset.GetId()] = overrideMaterialAsset;
  230. materialIt->second.m_defaultMaterialAsset = defaultMaterialAsset;
  231. }
  232. }
  233. // Begin loading all unique, referenced material assets
  234. bool anyQueued = false;
  235. for (auto& [assetId, uniqueMaterial] : m_uniqueMaterialMap)
  236. {
  237. if (uniqueMaterial.GetId().IsValid())
  238. {
  239. AZ::Data::AssetLoadParameters loadParams;
  240. loadParams.m_dependencyRules = AZ::Data::AssetDependencyLoadRules::LoadAll;
  241. loadParams.m_reloadMissingDependencies = true;
  242. if (uniqueMaterial.QueueLoad(loadParams))
  243. {
  244. anyQueued = true;
  245. }
  246. else
  247. {
  248. DisplayMissingAssetWarning(uniqueMaterial);
  249. }
  250. AZ::Data::AssetBus::MultiHandler::BusConnect(uniqueMaterial.GetId());
  251. }
  252. }
  253. if (!anyQueued)
  254. {
  255. QueueMaterialsUpdatedNotification();
  256. }
  257. }
  258. void MaterialComponentController::InitializeMaterialInstance(const Data::Asset<Data::AssetData>& asset)
  259. {
  260. bool allReady = true;
  261. auto updateAsset = [&](AZ::Data::Asset<AZ::RPI::MaterialAsset>& materialAsset)
  262. {
  263. if (materialAsset.GetId() == asset.GetId())
  264. {
  265. materialAsset = asset;
  266. }
  267. if (materialAsset.GetId().IsValid() && !materialAsset.IsReady() && !materialAsset.IsError())
  268. {
  269. allReady = false;
  270. }
  271. };
  272. // Update all of the material asset containers to reference the newly loaded asset
  273. for (auto& materialPair : m_uniqueMaterialMap)
  274. {
  275. updateAsset(materialPair.second);
  276. }
  277. for (auto& materialPair : m_defaultMaterialMap)
  278. {
  279. updateAsset(materialPair.second.m_materialAsset);
  280. updateAsset(materialPair.second.m_defaultMaterialAsset);
  281. }
  282. for (auto& materialPair : m_configuration.m_materials)
  283. {
  284. updateAsset(materialPair.second.m_materialAsset);
  285. updateAsset(materialPair.second.m_defaultMaterialAsset);
  286. }
  287. if (allReady)
  288. {
  289. // Only start updating materials and instances after all assets that can be loaded have been loaded.
  290. // This ensures that property changes and notifications only occur once everything is fully loaded.
  291. for (auto& materialPair : m_configuration.m_materials)
  292. {
  293. materialPair.second.RebuildInstance();
  294. QueuePropertyChanges(materialPair.first);
  295. }
  296. QueueMaterialsCreatedNotification();
  297. QueueMaterialsUpdatedNotification();
  298. }
  299. }
  300. void MaterialComponentController::ReleaseMaterials()
  301. {
  302. SystemTickBus::Handler::BusDisconnect();
  303. Data::AssetBus::MultiHandler::BusDisconnect();
  304. m_defaultMaterialMap.clear();
  305. m_uniqueMaterialMap.clear();
  306. m_materialsWithDirtyProperties.clear();
  307. m_queuedMaterialsCreatedNotification = false;
  308. m_queuedMaterialsUpdatedNotification = false;
  309. for (auto& materialPair : m_configuration.m_materials)
  310. {
  311. materialPair.second.Release();
  312. }
  313. }
  314. MaterialAssignmentMap MaterialComponentController::GetDefaultMaterialMap() const
  315. {
  316. return m_defaultMaterialMap;
  317. }
  318. MaterialAssignmentId MaterialComponentController::FindMaterialAssignmentId(
  319. const MaterialAssignmentLodIndex lod, const AZStd::string& label) const
  320. {
  321. MaterialAssignmentId materialAssignmentId;
  322. MaterialConsumerRequestBus::EventResult(
  323. materialAssignmentId, m_entityId, &MaterialConsumerRequestBus::Events::FindMaterialAssignmentId, lod, label);
  324. return materialAssignmentId;
  325. }
  326. AZ::Data::AssetId MaterialComponentController::GetDefaultMaterialAssetId(const MaterialAssignmentId& materialAssignmentId) const
  327. {
  328. const auto materialIt = m_defaultMaterialMap.find(materialAssignmentId);
  329. return materialIt != m_defaultMaterialMap.end() ? materialIt->second.m_materialAsset.GetId() : AZ::Data::AssetId();
  330. }
  331. AZStd::string MaterialComponentController::GetMaterialLabel(const MaterialAssignmentId& materialAssignmentId) const
  332. {
  333. MaterialAssignmentLabelMap labels;
  334. MaterialConsumerRequestBus::EventResult(labels, m_entityId, &MaterialConsumerRequestBus::Events::GetMaterialLabels);
  335. auto labelIt = labels.find(materialAssignmentId);
  336. return labelIt != labels.end() ? labelIt->second : "<unknown>";
  337. }
  338. void MaterialComponentController::SetMaterialMap(const MaterialAssignmentMap& materials)
  339. {
  340. m_configuration.m_materials = materials;
  341. ConvertAssetsForSerialization();
  342. QueueLoadMaterials();
  343. }
  344. const MaterialAssignmentMap& MaterialComponentController::GetMaterialMap() const
  345. {
  346. return m_configuration.m_materials;
  347. }
  348. void MaterialComponentController::ClearMaterialMap()
  349. {
  350. if (!m_configuration.m_materials.empty())
  351. {
  352. m_configuration.m_materials.clear();
  353. QueueMaterialsUpdatedNotification();
  354. }
  355. }
  356. void MaterialComponentController::ClearMaterialsOnModelSlots()
  357. {
  358. const auto numErased = AZStd::erase_if(m_configuration.m_materials, [](const auto& materialPair) {
  359. return materialPair.first.IsSlotIdOnly();
  360. });
  361. if (numErased > 0)
  362. {
  363. QueueMaterialsUpdatedNotification();
  364. }
  365. }
  366. void MaterialComponentController::ClearMaterialsOnLodSlots()
  367. {
  368. const auto numErased = AZStd::erase_if(m_configuration.m_materials, [](const auto& materialPair) {
  369. return materialPair.first.IsLodAndSlotId();
  370. });
  371. if (numErased > 0)
  372. {
  373. QueueMaterialsUpdatedNotification();
  374. }
  375. }
  376. void MaterialComponentController::ClearMaterialsOnInvalidSlots()
  377. {
  378. const auto numErased = AZStd::erase_if(m_configuration.m_materials, [this](const auto& materialPair) {
  379. return m_defaultMaterialMap.find(materialPair.first) == m_defaultMaterialMap.end();
  380. });
  381. if (numErased > 0)
  382. {
  383. QueueMaterialsUpdatedNotification();
  384. }
  385. }
  386. void MaterialComponentController::ClearMaterialsWithMissingAssets()
  387. {
  388. const auto numErased = AZStd::erase_if(m_configuration.m_materials, [](const auto& materialPair) {
  389. if (materialPair.second.m_materialAsset.GetId().IsValid())
  390. {
  391. AZ::Data::AssetInfo assetInfo;
  392. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  393. assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById,
  394. materialPair.second.m_materialAsset.GetId());
  395. return !assetInfo.m_assetId.IsValid();
  396. }
  397. return false;
  398. });
  399. if (numErased > 0)
  400. {
  401. QueueMaterialsUpdatedNotification();
  402. }
  403. }
  404. void MaterialComponentController::RepairMaterialsWithMissingAssets()
  405. {
  406. for (auto& materialPair : m_configuration.m_materials)
  407. {
  408. if (materialPair.second.m_materialAsset.GetId().IsValid())
  409. {
  410. AZ::Data::AssetInfo assetInfo;
  411. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  412. assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById,
  413. materialPair.second.m_materialAsset.GetId());
  414. if (!assetInfo.m_assetId.IsValid())
  415. {
  416. materialPair.second.m_materialAsset = {};
  417. QueueMaterialsUpdatedNotification();
  418. }
  419. }
  420. }
  421. }
  422. uint32_t MaterialComponentController::RepairMaterialsWithRenamedProperties()
  423. {
  424. uint32_t propertiesUpdated = 0;
  425. for (auto& materialAssignmentPair : m_configuration.m_materials)
  426. {
  427. MaterialAssignment& materialAssignment = materialAssignmentPair.second;
  428. AZStd::vector<AZStd::pair<Name, Name>> renamedProperties;
  429. for (const auto& propertyPair : materialAssignment.m_propertyOverrides)
  430. {
  431. Name propertyId = propertyPair.first;
  432. if (materialAssignment.m_materialInstance->GetAsset()->GetMaterialTypeAsset()->ApplyPropertyRenames(propertyId))
  433. {
  434. renamedProperties.emplace_back(propertyPair.first, propertyId);
  435. ++propertiesUpdated;
  436. }
  437. }
  438. for (const auto& [oldName, newName] : renamedProperties)
  439. {
  440. materialAssignment.m_propertyOverrides[newName] = materialAssignment.m_propertyOverrides[oldName];
  441. materialAssignment.m_propertyOverrides.erase(oldName);
  442. }
  443. QueuePropertyChanges(materialAssignmentPair.first);
  444. }
  445. QueueMaterialsUpdatedNotification();
  446. return propertiesUpdated;
  447. }
  448. void MaterialComponentController::SetMaterialAssetIdOnDefaultSlot(const AZ::Data::AssetId& materialAssetId)
  449. {
  450. SetMaterialAssetId(DefaultMaterialAssignmentId, materialAssetId);
  451. }
  452. const AZ::Data::AssetId MaterialComponentController::GetMaterialAssetIdOnDefaultSlot() const
  453. {
  454. return GetMaterialAssetId(DefaultMaterialAssignmentId);
  455. }
  456. void MaterialComponentController::ClearMaterialAssetIdOnDefaultSlot()
  457. {
  458. ClearMaterialAssetId(DefaultMaterialAssignmentId);
  459. }
  460. void MaterialComponentController::SetMaterialAssetId(
  461. const MaterialAssignmentId& materialAssignmentId, const AZ::Data::AssetId& materialAssetId)
  462. {
  463. auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
  464. if (materialIt != m_configuration.m_materials.end())
  465. {
  466. // If the asset ID is invalid and there are no other property overrides then clear the entry
  467. if (!materialAssetId.IsValid() &&
  468. materialIt->second.m_propertyOverrides.empty() &&
  469. materialIt->second.m_matModUvOverrides.empty())
  470. {
  471. m_configuration.m_materials.erase(materialAssignmentId);
  472. QueueMaterialsUpdatedNotification();
  473. return;
  474. }
  475. // If the asset ID is different than what's already assigned then replace it
  476. if (materialIt->second.m_materialAsset.GetId() != materialAssetId)
  477. {
  478. materialIt->second.m_materialAsset =
  479. AZ::Data::Asset<AZ::RPI::MaterialAsset>(materialAssetId, AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid());
  480. QueueLoadMaterials();
  481. return;
  482. }
  483. }
  484. m_configuration.m_materials[materialAssignmentId].m_materialAsset =
  485. AZ::Data::Asset<AZ::RPI::MaterialAsset>(materialAssetId, AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid());
  486. QueueLoadMaterials();
  487. }
  488. AZ::Data::AssetId MaterialComponentController::GetMaterialAssetId(const MaterialAssignmentId& materialAssignmentId) const
  489. {
  490. // If there is a material override return that asset ID
  491. const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
  492. if (materialIt != m_configuration.m_materials.end() && materialIt->second.m_materialAsset.GetId().IsValid())
  493. {
  494. return materialIt->second.m_materialAsset.GetId();
  495. }
  496. // Otherwise return the cached default material asset ID
  497. return GetDefaultMaterialAssetId(materialAssignmentId);
  498. }
  499. void MaterialComponentController::ClearMaterialAssetId(const MaterialAssignmentId& materialAssignmentId)
  500. {
  501. SetMaterialAssetId(materialAssignmentId, {});
  502. }
  503. bool MaterialComponentController::IsMaterialAssetIdOverridden(const MaterialAssignmentId& materialAssignmentId) const
  504. {
  505. const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
  506. return materialIt != m_configuration.m_materials.end() && materialIt->second.m_materialAsset.GetId().IsValid();
  507. }
  508. bool MaterialComponentController::HasPropertiesOverridden(const MaterialAssignmentId& materialAssignmentId) const
  509. {
  510. const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
  511. return materialIt != m_configuration.m_materials.end() && !materialIt->second.m_propertyOverrides.empty();
  512. }
  513. void MaterialComponentController::SetPropertyValue(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::any& value)
  514. {
  515. auto& materialAssignment = m_configuration.m_materials[materialAssignmentId];
  516. const bool wasEmpty = materialAssignment.m_propertyOverrides.empty();
  517. materialAssignment.m_propertyOverrides[AZ::Name(propertyName)] = ConvertAssetsForSerialization(value);
  518. if (materialAssignment.RequiresLoading())
  519. {
  520. QueueLoadMaterials();
  521. return;
  522. }
  523. if (wasEmpty != materialAssignment.m_propertyOverrides.empty())
  524. {
  525. materialAssignment.RebuildInstance();
  526. QueueMaterialsCreatedNotification();
  527. QueueMaterialsUpdatedNotification();
  528. }
  529. QueuePropertyChanges(materialAssignmentId);
  530. }
  531. AZStd::any MaterialComponentController::GetPropertyValue(
  532. const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const
  533. {
  534. AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset;
  535. // First, check if there is an explicit property value override matching the name and material slot
  536. const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
  537. if (materialIt != m_configuration.m_materials.end())
  538. {
  539. // If there is an explicit property value override return it immediately
  540. const auto propertyIt = materialIt->second.m_propertyOverrides.find(AZ::Name(propertyName));
  541. if (propertyIt != materialIt->second.m_propertyOverrides.end() && !propertyIt->second.empty())
  542. {
  543. return propertyIt->second;
  544. }
  545. // Otherwise, determine the highest priority material asset to pull property values from
  546. materialAsset = materialIt->second.m_materialAsset;
  547. if (!materialAsset.IsReady())
  548. {
  549. materialAsset = materialIt->second.m_defaultMaterialAsset;
  550. }
  551. }
  552. // No matching value or asset has been found, so check against the default material map
  553. if (!materialAsset.IsReady())
  554. {
  555. const auto defaultMaterialIt = m_defaultMaterialMap.find(materialAssignmentId);
  556. if (defaultMaterialIt != m_defaultMaterialMap.end() && defaultMaterialIt->second.m_materialAsset.IsReady())
  557. {
  558. materialAsset = defaultMaterialIt->second.m_materialAsset;
  559. }
  560. }
  561. // If a valid, ready asset was identified, attempt to read the default property value from it.
  562. if (materialAsset.IsReady())
  563. {
  564. const auto layout = materialAsset->GetMaterialPropertiesLayout();
  565. const auto index = layout->FindPropertyIndex(AZ::Name(propertyName));
  566. if (index.IsValid())
  567. {
  568. auto propertyValue = materialAsset->GetPropertyValues()[index.GetIndex()];
  569. return ConvertAssetsForSerialization(AZ::RPI::MaterialPropertyValue::ToAny(propertyValue));
  570. }
  571. }
  572. return {};
  573. }
  574. void MaterialComponentController::ClearPropertyValue(
  575. const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName)
  576. {
  577. auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
  578. if (materialIt == m_configuration.m_materials.end())
  579. {
  580. return;
  581. }
  582. auto propertyIt = materialIt->second.m_propertyOverrides.find(AZ::Name(propertyName));
  583. if (propertyIt == materialIt->second.m_propertyOverrides.end())
  584. {
  585. return;
  586. }
  587. materialIt->second.m_propertyOverrides.erase(propertyIt);
  588. if (materialIt->second.m_propertyOverrides.empty())
  589. {
  590. materialIt->second.RebuildInstance();
  591. QueueMaterialsCreatedNotification();
  592. QueueMaterialsUpdatedNotification();
  593. }
  594. QueuePropertyChanges(materialAssignmentId);
  595. }
  596. void MaterialComponentController::ClearPropertyValues(const MaterialAssignmentId& materialAssignmentId)
  597. {
  598. auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
  599. if (materialIt == m_configuration.m_materials.end())
  600. {
  601. return;
  602. }
  603. if (!materialIt->second.m_propertyOverrides.empty())
  604. {
  605. materialIt->second.m_propertyOverrides = {};
  606. materialIt->second.RebuildInstance();
  607. QueueMaterialsCreatedNotification();
  608. QueueMaterialsUpdatedNotification();
  609. }
  610. }
  611. void MaterialComponentController::ClearAllPropertyValues()
  612. {
  613. for (auto& materialPair : m_configuration.m_materials)
  614. {
  615. if (!materialPair.second.m_propertyOverrides.empty())
  616. {
  617. materialPair.second.m_propertyOverrides = {};
  618. materialPair.second.RebuildInstance();
  619. QueueMaterialsCreatedNotification();
  620. QueueMaterialsUpdatedNotification();
  621. }
  622. }
  623. }
  624. void MaterialComponentController::SetPropertyValues(
  625. const MaterialAssignmentId& materialAssignmentId, const MaterialPropertyOverrideMap& propertyOverrides)
  626. {
  627. auto& materialAssignment = m_configuration.m_materials[materialAssignmentId];
  628. const bool wasEmpty = materialAssignment.m_propertyOverrides.empty();
  629. materialAssignment.m_propertyOverrides = propertyOverrides;
  630. ConvertAssetsForSerialization();
  631. if (materialAssignment.RequiresLoading())
  632. {
  633. QueueLoadMaterials();
  634. return;
  635. }
  636. if (wasEmpty != materialAssignment.m_propertyOverrides.empty())
  637. {
  638. materialAssignment.RebuildInstance();
  639. QueueMaterialsCreatedNotification();
  640. QueueMaterialsUpdatedNotification();
  641. }
  642. QueuePropertyChanges(materialAssignmentId);
  643. }
  644. MaterialPropertyOverrideMap MaterialComponentController::GetPropertyValues(const MaterialAssignmentId& materialAssignmentId) const
  645. {
  646. MaterialPropertyOverrideMap properties;
  647. AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset;
  648. const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
  649. if (materialIt != m_configuration.m_materials.end())
  650. {
  651. // First, insert all the explicit material property overrides into the returned property map
  652. properties.insert(materialIt->second.m_propertyOverrides.begin(), materialIt->second.m_propertyOverrides.end());
  653. // Determine if there is an asset ready with additional values to populate the map
  654. materialAsset = materialIt->second.m_materialAsset;
  655. if (!materialAsset.IsReady())
  656. {
  657. materialAsset = materialIt->second.m_defaultMaterialAsset;
  658. }
  659. }
  660. // If no hacen has been identified, fall back to the default material mapping
  661. if (!materialAsset.IsReady())
  662. {
  663. const auto defaultMaterialIt = m_defaultMaterialMap.find(materialAssignmentId);
  664. if (defaultMaterialIt != m_defaultMaterialMap.end() && defaultMaterialIt->second.m_materialAsset.IsReady())
  665. {
  666. materialAsset = defaultMaterialIt->second.m_materialAsset;
  667. }
  668. }
  669. // If any valid, already has said was found then read the rest of the material values from it
  670. if (materialAsset.IsReady() && materialAsset->GetMaterialTypeAsset().IsReady())
  671. {
  672. const auto layout = materialAsset->GetMaterialPropertiesLayout();
  673. for (size_t propertyIndex = 0; propertyIndex < layout->GetPropertyCount(); ++propertyIndex)
  674. {
  675. auto descriptor = layout->GetPropertyDescriptor(AZ::RPI::MaterialPropertyIndex{ propertyIndex });
  676. auto propertyValue = materialAsset->GetPropertyValues()[propertyIndex];
  677. properties.insert({ descriptor->GetName().GetStringView(),
  678. ConvertAssetsForSerialization(AZ::RPI::MaterialPropertyValue::ToAny(propertyValue)) });
  679. }
  680. }
  681. return properties;
  682. }
  683. void MaterialComponentController::SetModelUvOverrides(
  684. const MaterialAssignmentId& materialAssignmentId, const AZ::RPI::MaterialModelUvOverrideMap& modelUvOverrides)
  685. {
  686. auto& materialAssignment = m_configuration.m_materials[materialAssignmentId];
  687. materialAssignment.m_matModUvOverrides = modelUvOverrides;
  688. if (materialAssignment.RequiresLoading())
  689. {
  690. QueueLoadMaterials();
  691. return;
  692. }
  693. // Unlike material properties which are applied to the material itself, UV overrides are applied outside the material
  694. // by the MeshFeatureProcessor. So all we have to do is notify the mesh component that the materials were updated and it
  695. // will pass the updated data to the MeshFeatureProcessor.
  696. QueueMaterialsUpdatedNotification();
  697. }
  698. AZ::RPI::MaterialModelUvOverrideMap MaterialComponentController::GetModelUvOverrides(
  699. const MaterialAssignmentId& materialAssignmentId) const
  700. {
  701. const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
  702. return materialIt != m_configuration.m_materials.end() ? materialIt->second.m_matModUvOverrides
  703. : AZ::RPI::MaterialModelUvOverrideMap();
  704. }
  705. void MaterialComponentController::OnMaterialAssignmentSlotsChanged()
  706. {
  707. LoadMaterials();
  708. MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialSlotLayoutChanged);
  709. }
  710. void MaterialComponentController::QueuePropertyChanges(const MaterialAssignmentId& materialAssignmentId)
  711. {
  712. m_materialsWithDirtyProperties.emplace(materialAssignmentId);
  713. SystemTickBus::Handler::BusConnect();
  714. }
  715. void MaterialComponentController::QueueMaterialsCreatedNotification()
  716. {
  717. m_queuedMaterialsCreatedNotification = true;
  718. SystemTickBus::Handler::BusConnect();
  719. }
  720. void MaterialComponentController::QueueMaterialsUpdatedNotification()
  721. {
  722. m_queuedMaterialsUpdatedNotification = true;
  723. SystemTickBus::Handler::BusConnect();
  724. }
  725. void MaterialComponentController::QueueLoadMaterials()
  726. {
  727. m_queuedLoadMaterials = true;
  728. SystemTickBus::Handler::BusConnect();
  729. }
  730. void MaterialComponentController::ConvertAssetsForSerialization()
  731. {
  732. for (auto& materialAssignmentPair : m_configuration.m_materials)
  733. {
  734. ConvertAssetsForSerialization(materialAssignmentPair.second.m_propertyOverrides);
  735. }
  736. }
  737. void MaterialComponentController::ConvertAssetsForSerialization(MaterialPropertyOverrideMap& propertyMap)
  738. {
  739. for (auto& propertyPair : propertyMap)
  740. {
  741. ConvertAssetsForSerialization(propertyPair.second);
  742. }
  743. }
  744. AZStd::any MaterialComponentController::ConvertAssetsForSerialization(const AZStd::any& value) const
  745. {
  746. if (value.is<AZ::Data::Asset<AZ::Data::AssetData>>())
  747. {
  748. return AZStd::any(AZStd::any_cast<AZ::Data::Asset<AZ::Data::AssetData>>(value).GetId());
  749. }
  750. if (value.is<AZ::Data::Asset<AZ::RPI::AttachmentImageAsset>>())
  751. {
  752. return AZStd::any(AZStd::any_cast<AZ::Data::Asset<AZ::RPI::AttachmentImageAsset>>(value).GetId());
  753. }
  754. if (value.is<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>())
  755. {
  756. return AZStd::any(AZStd::any_cast<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>(value).GetId());
  757. }
  758. if (value.is<AZ::Data::Asset<AZ::RPI::ImageAsset>>())
  759. {
  760. return AZStd::any(AZStd::any_cast<AZ::Data::Asset<AZ::RPI::ImageAsset>>(value).GetId());
  761. }
  762. return value;
  763. }
  764. void MaterialComponentController::DisplayMissingAssetWarning([[maybe_unused]] Data::Asset<Data::AssetData> asset) const
  765. {
  766. AZ_Warning(
  767. "MaterialComponent",
  768. false,
  769. "Material component on entity %s failed to load asset %s. The material component might contain additional material and "
  770. "property data if the component was copied or the associated model changed. This data can be cleared using the material "
  771. "component request bus or from the editor material component context menu.",
  772. m_entityId.ToString().c_str(),
  773. asset.ToString<AZStd::string>().c_str());
  774. }
  775. } // namespace Render
  776. } // namespace AZ