MaterialPropertyCollection.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  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.Public/ColorManagement/TransformColor.h>
  9. #include <Atom/RPI.Public/Material/Material.h>
  10. #include <Atom/RPI.Reflect/Image/AttachmentImageAsset.h>
  11. #include <Atom/RPI.Public/Image/AttachmentImage.h>
  12. #include <Atom/RPI.Public/Image/StreamingImage.h>
  13. #include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
  14. #include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
  15. #include <Atom/RPI.Public/Shader/Shader.h>
  16. #include <Atom/RPI.Public/Shader/ShaderSystemInterface.h>
  17. #include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
  18. #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
  19. #include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
  20. #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
  21. #include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
  22. #include <AtomCore/Instance/InstanceDatabase.h>
  23. #include <AtomCore/Utils/ScopedValue.h>
  24. namespace AZ
  25. {
  26. namespace RPI
  27. {
  28. bool MaterialPropertyCollection::Init(
  29. RHI::ConstPtr<MaterialPropertiesLayout> layout,
  30. const AZStd::vector<MaterialPropertyValue>& defaultValues)
  31. {
  32. m_layout = layout;
  33. if (!m_layout)
  34. {
  35. AZ_Error("MaterialPropertyCollection", false, "MaterialPropertiesLayout is invalid");
  36. return false;
  37. }
  38. // If this Init() is actually a re-initialize, we need to re-apply any overridden property values
  39. // after loading the default property values, so we save that data here.
  40. MaterialPropertyFlags prevOverrideFlags = m_propertyOverrideFlags;
  41. AZStd::vector<MaterialPropertyValue> prevPropertyValues = m_propertyValues;
  42. // The property values are cleared to their default state to ensure that SetPropertyValue() does not early-return
  43. // when called below. This is important when Init() is actually a re-initialize.
  44. m_propertyValues.clear();
  45. // Initialize the shader runtime data like shader constant buffers and shader variants by applying the
  46. // material's property values. This will feed through the normal runtime material value-change data flow, which may
  47. // include custom property change handlers provided by the material type.
  48. //
  49. // This baking process could be more efficient by doing it at build-time rather than run-time. However, the
  50. // architectural complexity of supporting separate asset/runtime paths for assigning buffers/images is prohibitive.
  51. m_propertyValues.resize(defaultValues.size());
  52. AZ_Assert(defaultValues.size() == m_layout->GetPropertyCount(), "The number of properties in this material doesn't match the property layout");
  53. AZ_Assert(defaultValues.size() <= Limits::Material::PropertyCountMax, "Too many material properties. Max is %d.", Limits::Material::PropertyCountMax);
  54. for (size_t i = 0; i < defaultValues.size(); ++i)
  55. {
  56. const MaterialPropertyValue& value = defaultValues[i];
  57. MaterialPropertyIndex propertyIndex{i};
  58. if (!SetPropertyValue(propertyIndex, value))
  59. {
  60. return false;
  61. }
  62. }
  63. // Clear all override flags because we just loaded properties from the asset
  64. m_propertyOverrideFlags.reset();
  65. // Now apply any properties that were overridden before
  66. for (size_t i = 0; i < prevPropertyValues.size(); ++i)
  67. {
  68. if (prevOverrideFlags[i])
  69. {
  70. SetPropertyValue(MaterialPropertyIndex{i}, prevPropertyValues[i]);
  71. }
  72. }
  73. return true;
  74. }
  75. const MaterialPropertyValue& MaterialPropertyCollection::GetPropertyValue(MaterialPropertyIndex index) const
  76. {
  77. static const MaterialPropertyValue emptyValue;
  78. if (m_propertyValues.size() <= index.GetIndex())
  79. {
  80. AZ_Error("MaterialPropertyCollection", false, "Property index out of range.");
  81. return emptyValue;
  82. }
  83. return m_propertyValues[index.GetIndex()];
  84. }
  85. const AZStd::vector<MaterialPropertyValue>& MaterialPropertyCollection::GetPropertyValues() const
  86. {
  87. return m_propertyValues;
  88. }
  89. void MaterialPropertyCollection::SetAllPropertyDirtyFlags()
  90. {
  91. m_propertyDirtyFlags.set();
  92. }
  93. void MaterialPropertyCollection::ClearAllPropertyDirtyFlags()
  94. {
  95. m_propertyDirtyFlags.reset();
  96. }
  97. template<typename Type>
  98. bool MaterialPropertyCollection::SetPropertyValue(MaterialPropertyIndex index, const Type& value)
  99. {
  100. if (!index.IsValid())
  101. {
  102. AZ_Assert(false, "SetPropertyValue: Invalid MaterialPropertyIndex");
  103. return false;
  104. }
  105. const MaterialPropertyDescriptor* propertyDescriptor = m_layout->GetPropertyDescriptor(index);
  106. if (!ValidatePropertyAccess<Type>(propertyDescriptor))
  107. {
  108. return false;
  109. }
  110. MaterialPropertyValue& savedPropertyValue = m_propertyValues[index.GetIndex()];
  111. // If the property value didn't actually change, don't waste time running functors and compiling the changes.
  112. if (savedPropertyValue == value)
  113. {
  114. return false;
  115. }
  116. savedPropertyValue = value;
  117. m_propertyDirtyFlags.set(index.GetIndex());
  118. m_propertyOverrideFlags.set(index.GetIndex());
  119. return true;
  120. }
  121. template<>
  122. bool MaterialPropertyCollection::SetPropertyValue<Data::Asset<ImageAsset>>(MaterialPropertyIndex index, const Data::Asset<ImageAsset>& value)
  123. {
  124. Data::Asset<ImageAsset> imageAsset = value;
  125. if (!imageAsset.GetId().IsValid())
  126. {
  127. // The image asset reference is null so set the property to an empty Image instance so the AZStd::any will not be empty.
  128. return SetPropertyValue(index, Data::Instance<Image>());
  129. }
  130. else
  131. {
  132. AZ::Data::AssetType assetType = imageAsset.GetType();
  133. if (assetType != azrtti_typeid<StreamingImageAsset>() && assetType != azrtti_typeid<AttachmentImageAsset>())
  134. {
  135. AZ::Data::AssetInfo assetInfo;
  136. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  137. assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, imageAsset.GetId());
  138. assetType = assetInfo.m_assetType;
  139. }
  140. // There is an issue in the Asset<T>(Asset<U>) copy constructor which is used with the FindOrCreate() calls below.
  141. // If the AssetData is valid, then it will get the actual asset type ID from the AssetData. However, if it is null
  142. // then it will continue using the original type ID. The InstanceDatabase will end up asking the AssetManager for
  143. // the asset using the wrong type (ImageAsset) and will lead to various error messages and in the end the asset
  144. // will never be loaded. So we work around this issue by forcing the asset type ID to the correct value first.
  145. // See https://github.com/o3de/o3de/issues/12224
  146. if (!imageAsset.Get())
  147. {
  148. imageAsset = Data::Asset<ImageAsset>{imageAsset.GetId(), assetType, imageAsset.GetHint()};
  149. }
  150. Data::Instance<Image> image = nullptr;
  151. if (assetType == azrtti_typeid<StreamingImageAsset>())
  152. {
  153. Data::Asset<StreamingImageAsset> streamingImageAsset = imageAsset;
  154. image = StreamingImage::FindOrCreate(streamingImageAsset);
  155. }
  156. else if (assetType == azrtti_typeid<AttachmentImageAsset>())
  157. {
  158. Data::Asset<AttachmentImageAsset> attachmentImageAsset = imageAsset;
  159. image = AttachmentImage::FindOrCreate(attachmentImageAsset);
  160. }
  161. else
  162. {
  163. AZ_Error("MaterialPropertyCollection", false, "Unsupported image asset type: %s", assetType.ToString<AZStd::string>().c_str());
  164. return false;
  165. }
  166. if (!image)
  167. {
  168. AZ_Error("MaterialPropertyCollection", false, "Image asset could not be loaded");
  169. return false;
  170. }
  171. return SetPropertyValue(index, image);
  172. }
  173. }
  174. template bool MaterialPropertyCollection::SetPropertyValue<bool>(MaterialPropertyIndex index, const bool& value);
  175. template bool MaterialPropertyCollection::SetPropertyValue<int32_t>(MaterialPropertyIndex index, const int32_t& value);
  176. template bool MaterialPropertyCollection::SetPropertyValue<uint32_t>(MaterialPropertyIndex index, const uint32_t& value);
  177. template bool MaterialPropertyCollection::SetPropertyValue<float>(MaterialPropertyIndex index, const float& value);
  178. template bool MaterialPropertyCollection::SetPropertyValue<Vector2>(MaterialPropertyIndex index, const Vector2& value);
  179. template bool MaterialPropertyCollection::SetPropertyValue<Vector3>(MaterialPropertyIndex index, const Vector3& value);
  180. template bool MaterialPropertyCollection::SetPropertyValue<Vector4>(MaterialPropertyIndex index, const Vector4& value);
  181. template bool MaterialPropertyCollection::SetPropertyValue<Color>(MaterialPropertyIndex index, const Color& value);
  182. template bool MaterialPropertyCollection::SetPropertyValue<Data::Instance<Image>>(MaterialPropertyIndex index, const Data::Instance<Image>& value);
  183. bool MaterialPropertyCollection::SetPropertyValue(MaterialPropertyIndex propertyIndex, const MaterialPropertyValue& value)
  184. {
  185. if (!value.IsValid())
  186. {
  187. auto descriptor = m_layout->GetPropertyDescriptor(propertyIndex);
  188. if (descriptor)
  189. {
  190. AZ_Assert(false, "Empty value found for material property '%s'", descriptor->GetName().GetCStr());
  191. }
  192. else
  193. {
  194. AZ_Assert(false, "Empty value found for material property [%d], and this property does not have a descriptor.");
  195. }
  196. return false;
  197. }
  198. if (value.Is<bool>())
  199. {
  200. return SetPropertyValue(propertyIndex, value.GetValue<bool>());
  201. }
  202. else if (value.Is<int32_t>())
  203. {
  204. return SetPropertyValue(propertyIndex, value.GetValue<int32_t>());
  205. }
  206. else if (value.Is<uint32_t>())
  207. {
  208. return SetPropertyValue(propertyIndex, value.GetValue<uint32_t>());
  209. }
  210. else if (value.Is<float>())
  211. {
  212. return SetPropertyValue(propertyIndex, value.GetValue<float>());
  213. }
  214. else if (value.Is<Vector2>())
  215. {
  216. return SetPropertyValue(propertyIndex, value.GetValue<Vector2>());
  217. }
  218. else if (value.Is<Vector3>())
  219. {
  220. return SetPropertyValue(propertyIndex, value.GetValue<Vector3>());
  221. }
  222. else if (value.Is<Vector4>())
  223. {
  224. return SetPropertyValue(propertyIndex, value.GetValue<Vector4>());
  225. }
  226. else if (value.Is<Color>())
  227. {
  228. return SetPropertyValue(propertyIndex, value.GetValue<Color>());
  229. }
  230. else if (value.Is<Data::Instance<Image>>())
  231. {
  232. return SetPropertyValue(propertyIndex, value.GetValue<Data::Instance<Image>>());
  233. }
  234. else if (value.Is<Data::Asset<ImageAsset>>())
  235. {
  236. return SetPropertyValue(propertyIndex, value.GetValue<Data::Asset<ImageAsset>>());
  237. }
  238. else
  239. {
  240. AZ_Assert(false, "Unhandled material property value type");
  241. return false;
  242. }
  243. }
  244. template<typename Type>
  245. const Type& MaterialPropertyCollection::GetPropertyValue(MaterialPropertyIndex index) const
  246. {
  247. static const Type defaultValue{};
  248. const MaterialPropertyDescriptor* propertyDescriptor = nullptr;
  249. if (Validation::IsEnabled())
  250. {
  251. if (!index.IsValid())
  252. {
  253. AZ_Assert(false, "GetPropertyValue: Invalid MaterialPropertyIndex");
  254. return defaultValue;
  255. }
  256. propertyDescriptor = m_layout->GetPropertyDescriptor(index);
  257. if (!ValidatePropertyAccess<Type>(propertyDescriptor))
  258. {
  259. return defaultValue;
  260. }
  261. }
  262. const MaterialPropertyValue& value = m_propertyValues[index.GetIndex()];
  263. if (value.Is<Type>())
  264. {
  265. return value.GetValue<Type>();
  266. }
  267. else
  268. {
  269. if (Validation::IsEnabled())
  270. {
  271. AZ_Assert(false, "Material property '%s': Stored property value has the wrong data type. Expected %s but is %s.",
  272. propertyDescriptor->GetName().GetCStr(),
  273. azrtti_typeid<Type>().template ToString<AZStd::string>().data(), // 'template' because clang says "error: use 'template' keyword to treat 'ToString' as a dependent template name"
  274. value.GetTypeId().ToString<AZStd::string>().data());
  275. }
  276. return defaultValue;
  277. }
  278. }
  279. // Using explicit instantiation to restrict GetPropertyValue to the set of types that we support
  280. template const bool& MaterialPropertyCollection::GetPropertyValue<bool> (MaterialPropertyIndex index) const;
  281. template const int32_t& MaterialPropertyCollection::GetPropertyValue<int32_t> (MaterialPropertyIndex index) const;
  282. template const uint32_t& MaterialPropertyCollection::GetPropertyValue<uint32_t> (MaterialPropertyIndex index) const;
  283. template const float& MaterialPropertyCollection::GetPropertyValue<float> (MaterialPropertyIndex index) const;
  284. template const Vector2& MaterialPropertyCollection::GetPropertyValue<Vector2> (MaterialPropertyIndex index) const;
  285. template const Vector3& MaterialPropertyCollection::GetPropertyValue<Vector3> (MaterialPropertyIndex index) const;
  286. template const Vector4& MaterialPropertyCollection::GetPropertyValue<Vector4> (MaterialPropertyIndex index) const;
  287. template const Color& MaterialPropertyCollection::GetPropertyValue<Color> (MaterialPropertyIndex index) const;
  288. template const Data::Instance<Image>& MaterialPropertyCollection::GetPropertyValue<Data::Instance<Image>>(MaterialPropertyIndex index) const;
  289. const MaterialPropertyFlags& MaterialPropertyCollection::GetPropertyDirtyFlags() const
  290. {
  291. return m_propertyDirtyFlags;
  292. }
  293. RHI::ConstPtr<MaterialPropertiesLayout> MaterialPropertyCollection::GetMaterialPropertiesLayout() const
  294. {
  295. return m_layout;
  296. }
  297. template<typename Type>
  298. bool MaterialPropertyCollection::ValidatePropertyAccess(const MaterialPropertyDescriptor* propertyDescriptor) const
  299. {
  300. // Note that we have warnings here instead of errors because this can happen while materials are hot reloading
  301. // after a material property layout changes in the MaterialTypeAsset, as there's a brief time when the data
  302. // might be out of sync between MaterialAssets and MaterialTypeAssets.
  303. if (!propertyDescriptor)
  304. {
  305. AZ_Warning("MaterialPropertyCollection", false, "MaterialPropertyDescriptor is null");
  306. return false;
  307. }
  308. AZ::TypeId accessDataType = azrtti_typeid<Type>();
  309. // Must align with the order in MaterialPropertyDataType
  310. static const AZStd::array<AZ::TypeId, MaterialPropertyDataTypeCount> types =
  311. {{
  312. AZ::TypeId{}, // Invalid
  313. azrtti_typeid<bool>(),
  314. azrtti_typeid<int32_t>(),
  315. azrtti_typeid<uint32_t>(),
  316. azrtti_typeid<float>(),
  317. azrtti_typeid<Vector2>(),
  318. azrtti_typeid<Vector3>(),
  319. azrtti_typeid<Vector4>(),
  320. azrtti_typeid<Color>(),
  321. azrtti_typeid<Data::Instance<Image>>(),
  322. azrtti_typeid<uint32_t>()
  323. }};
  324. AZ::TypeId actualDataType = types[static_cast<size_t>(propertyDescriptor->GetDataType())];
  325. if (accessDataType != actualDataType)
  326. {
  327. AZ_Warning("MaterialPropertyCollection", false, "Material property '%s': Accessed as type %s but is type %s",
  328. propertyDescriptor->GetName().GetCStr(),
  329. GetMaterialPropertyDataTypeString(accessDataType).c_str(),
  330. ToString(propertyDescriptor->GetDataType()));
  331. return false;
  332. }
  333. return true;
  334. }
  335. } // namespace RPI
  336. } // namespace AZ