ShaderVariantTreeAsset.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  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/Shader/ShaderVariantTreeAsset.h>
  9. #include <AzCore/Casting/numeric_cast.h>
  10. #include <AzCore/IO/Path/Path.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzCore/std/algorithm.h>
  13. #include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
  14. #include <Atom/RPI.Reflect/Shader/ShaderOptionGroupLayout.h>
  15. #include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
  16. namespace AZ
  17. {
  18. namespace RPI
  19. {
  20. void ShaderVariantTreeAsset::Reflect(ReflectContext* context)
  21. {
  22. if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
  23. {
  24. serializeContext->Class<ShaderVariantTreeAsset, AZ::Data::AssetData>()
  25. ->Version(1)
  26. ->Field("ShaderHash", &ShaderVariantTreeAsset::m_shaderHash)
  27. ->Field("Nodes", &ShaderVariantTreeAsset::m_nodes)
  28. ;
  29. }
  30. ShaderVariantTreeNode::Reflect(context);
  31. }
  32. Data::AssetId ShaderVariantTreeAsset::GetShaderVariantTreeAssetIdFromShaderAssetId(const Data::AssetId& shaderAssetId)
  33. {
  34. //From the shaderAssetId We can deduce the path of the shader asset, and from the path of the shader asset we can deduce the path of the ShaderVariantTreeAsset.
  35. AZ::IO::FixedMaxPath shaderAssetPath;
  36. AZ::Data::AssetCatalogRequestBus::BroadcastResult(shaderAssetPath.Native(), &AZ::Data::AssetCatalogRequests::GetAssetPathById
  37. , shaderAssetId);
  38. AZ::IO::FixedMaxPath shaderAssetPathRoot = shaderAssetPath.ParentPath();
  39. AZ::IO::FixedMaxPath shaderAssetPathName = shaderAssetPath.Stem();
  40. AZStd::string shaderVariantTreeAssetDir;
  41. AzFramework::StringFunc::Path::Join(ShaderVariantTreeAsset::CommonSubFolderLowerCase, shaderAssetPathRoot.c_str(), shaderVariantTreeAssetDir);
  42. AZStd::string shaderVariantTreeAssetFilename = AZStd::string::format("%s.%s", shaderAssetPathName.c_str(), ShaderVariantTreeAsset::Extension);
  43. AZStd::string shaderVariantTreeAssetPath;
  44. AzFramework::StringFunc::Path::Join(shaderVariantTreeAssetDir.c_str(), shaderVariantTreeAssetFilename.c_str(), shaderVariantTreeAssetPath);
  45. AZ::Data::AssetId shaderVariantTreeAssetId;
  46. AZ::Data::AssetCatalogRequestBus::BroadcastResult(shaderVariantTreeAssetId, &AZ::Data::AssetCatalogRequests::GetAssetIdByPath
  47. , shaderVariantTreeAssetPath.c_str(), AZ::Data::s_invalidAssetType, false);
  48. if (!shaderVariantTreeAssetId.IsValid())
  49. {
  50. // If the game project did not customize the shadervariantlist, let's see if the original author of the .shader file
  51. // provided a shadervariantlist.
  52. AzFramework::StringFunc::Path::Join(shaderAssetPathRoot.c_str(), shaderVariantTreeAssetFilename.c_str(), shaderVariantTreeAssetPath);
  53. AZ::Data::AssetCatalogRequestBus::BroadcastResult(shaderVariantTreeAssetId, &AZ::Data::AssetCatalogRequests::GetAssetIdByPath
  54. , shaderVariantTreeAssetPath.c_str(), AZ::Data::s_invalidAssetType, false);
  55. }
  56. return shaderVariantTreeAssetId;
  57. }
  58. size_t ShaderVariantTreeAsset::GetNodeCount() const
  59. {
  60. return m_nodes.size();
  61. }
  62. ShaderVariantSearchResult ShaderVariantTreeAsset::FindVariantStableId(const ShaderOptionGroupLayout* shaderOptionGroupLayout, const ShaderVariantId& shaderVariantId) const
  63. {
  64. struct NodeToVisit
  65. {
  66. uint32_t m_branchCount; // Number of static branches
  67. uint32_t m_nodeIndex; // Index of the node to visit
  68. };
  69. struct SearchResult
  70. {
  71. uint32_t m_branchCount; // Number of static branches
  72. ShaderVariantStableId m_variantStableId;
  73. };
  74. // The list of specified options, in order of priority, built from the variant key mask.
  75. auto optionValues = ConvertToValueChain(shaderOptionGroupLayout, shaderVariantId);
  76. // Always add the root to the results.
  77. AZStd::vector<SearchResult> searchResults;
  78. searchResults.push_back({ 0, ShaderAsset::RootShaderVariantStableId });
  79. // All the indices are guaranteed to be unique, so we use queues.
  80. AZStd::queue<NodeToVisit> nodesToVisit;
  81. AZStd::queue<NodeToVisit> nodesToVisitNext;
  82. // Always visit the root node.
  83. nodesToVisit.push({ 0, 0 });
  84. for (uint32_t optionValue : optionValues)
  85. {
  86. while (!nodesToVisit.empty())
  87. {
  88. const NodeToVisit nextNode = nodesToVisit.front();
  89. nodesToVisit.pop();
  90. // Leaf node
  91. if (!GetNode(nextNode.m_nodeIndex).HasChildren())
  92. {
  93. continue;
  94. }
  95. // Two branches need to be searched:
  96. // - The node that is an exact match for the shader option value (specified).
  97. // - The node that can match any shader option value (unspecified).
  98. // The unspecified value node is always the first child.
  99. const uint32_t unspecifiedIndex = nextNode.m_nodeIndex + GetNode(nextNode.m_nodeIndex).GetOffset();
  100. // All the specified value nodes follow the unspecified node.
  101. // The index of the requested node is calculated using the order of the option value.
  102. const uint32_t requestedIndex = unspecifiedIndex + (optionValue + 1);
  103. // If no option value was requested, this index is the same as the unspecified index.
  104. if (requestedIndex > unspecifiedIndex)
  105. {
  106. // Visit this specified node, and increase the weight of visiting the node by 1.
  107. // [GFX TODO] [ATOM-3883] Improve the evaluation of visiting the variant search tree.
  108. nodesToVisitNext.push({ nextNode.m_branchCount + 1, requestedIndex });
  109. // If the specified node has valid data, add it to the matches.
  110. if (GetNode(requestedIndex).GetStableId().IsValid())
  111. {
  112. // Specified nodes have one more static branch than their parent.
  113. searchResults.push_back({ nextNode.m_branchCount + 1, GetNode(requestedIndex).GetStableId() });
  114. }
  115. }
  116. // Always visit the unspecified node.
  117. nodesToVisitNext.push({ nextNode.m_branchCount, unspecifiedIndex });
  118. // If the unspecified node has valid data, add it to the matches.
  119. if (GetNode(unspecifiedIndex).GetStableId().IsValid())
  120. {
  121. // Unspecified nodes have the same number of static branches as their parent.
  122. searchResults.push_back({ nextNode.m_branchCount, GetNode(unspecifiedIndex).GetStableId() });
  123. }
  124. }
  125. // Visit the next nodes.
  126. AZStd::swap(nodesToVisit, nodesToVisitNext);
  127. }
  128. // Count the number of static branches.
  129. uint32_t totalBranchCount = 0;
  130. ShaderVariantStableId bestFitStableId = ShaderAsset::RootShaderVariantStableId;
  131. AZStd::for_each(searchResults.begin(), searchResults.end(), [&](const SearchResult& searchResult)
  132. {
  133. // More static branches is a better fit.
  134. if (searchResult.m_branchCount > totalBranchCount)
  135. {
  136. totalBranchCount = searchResult.m_branchCount;
  137. bestFitStableId = searchResult.m_variantStableId;
  138. }
  139. });
  140. // Calculate the number of dynamic branches.
  141. const uint32_t optionCount = aznumeric_cast<uint32_t>(shaderOptionGroupLayout->GetShaderOptions().size());
  142. return ShaderVariantSearchResult{ bestFitStableId, optionCount - totalBranchCount };
  143. }
  144. const ShaderVariantTreeNode& ShaderVariantTreeAsset::GetNode(uint32_t index) const
  145. {
  146. AZ_Assert(index < m_nodes.size(), "Invalid Node Index");
  147. return m_nodes[index];
  148. }
  149. void ShaderVariantTreeAsset::SetNode(uint32_t index, const ShaderVariantTreeNode& node)
  150. {
  151. AZ_Assert(index < m_nodes.size(), "Invalid Node Index");
  152. m_nodes[index] = node;
  153. }
  154. AZStd::vector<uint32_t> ShaderVariantTreeAsset::ConvertToValueChain(const ShaderOptionGroupLayout* shaderOptionGroupLayout, const ShaderVariantId& shaderVariantId)
  155. {
  156. const auto& options = shaderOptionGroupLayout->GetShaderOptions();
  157. AZStd::vector<uint32_t> optionValues;
  158. optionValues.reserve(options.size());
  159. for (const ShaderOptionDescriptor& option : options)
  160. {
  161. if ((shaderVariantId.m_mask & option.GetBitMask()).any())
  162. {
  163. optionValues.push_back(option.DecodeBits(shaderVariantId.m_key));
  164. }
  165. else
  166. {
  167. optionValues.push_back(UnspecifiedIndex);
  168. }
  169. }
  170. // Remove trailing unspecified option values as they do not contribute anything to the search.
  171. while (!optionValues.empty() && optionValues.back() == UnspecifiedIndex)
  172. {
  173. optionValues.pop_back();
  174. }
  175. return optionValues;
  176. }
  177. void ShaderVariantTreeAsset::SetReady()
  178. {
  179. m_status = AssetStatus::Ready;
  180. }
  181. bool ShaderVariantTreeAsset::FinalizeAfterLoad()
  182. {
  183. return true;
  184. }
  185. ShaderVariantTreeAssetHandler::LoadResult ShaderVariantTreeAssetHandler::LoadAssetData(const Data::Asset<Data::AssetData>& asset, AZStd::shared_ptr<Data::AssetDataStream> stream, const AZ::Data::AssetFilterCB& assetLoadFilterCB)
  186. {
  187. if (Base::LoadAssetData(asset, stream, assetLoadFilterCB) == LoadResult::LoadComplete)
  188. {
  189. return PostLoadInit(asset) ? LoadResult::LoadComplete : LoadResult::Error;
  190. }
  191. return LoadResult::Error;
  192. }
  193. bool ShaderVariantTreeAssetHandler::PostLoadInit(const Data::Asset<Data::AssetData>& asset)
  194. {
  195. if (ShaderVariantTreeAsset* shaderAsset = asset.GetAs<ShaderVariantTreeAsset>())
  196. {
  197. if (!shaderAsset->FinalizeAfterLoad())
  198. {
  199. AZ_Error("ShaderVariantTreeAssetHandler", false, "Shader asset failed to finalize.");
  200. return false;
  201. }
  202. return true;
  203. }
  204. return false;
  205. }
  206. void ShaderVariantTreeNode::Reflect(ReflectContext* context)
  207. {
  208. if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
  209. {
  210. serializeContext->Class<ShaderVariantTreeNode>()
  211. ->Version(0)
  212. ->Field("StableId", &ShaderVariantTreeNode::m_stableId)
  213. ->Field("Offset", &ShaderVariantTreeNode::m_offset)
  214. ;
  215. }
  216. }
  217. ShaderVariantTreeNode::ShaderVariantTreeNode()
  218. : m_stableId(ShaderVariantStableId{ ShaderVariantTreeAsset::UnspecifiedIndex })
  219. , m_offset(0)
  220. {
  221. }
  222. ShaderVariantTreeNode::ShaderVariantTreeNode(const ShaderVariantStableId& stableId, uint32_t offset)
  223. : m_stableId(stableId)
  224. , m_offset(offset)
  225. {
  226. }
  227. const ShaderVariantStableId& ShaderVariantTreeNode::GetStableId() const
  228. {
  229. return m_stableId;
  230. }
  231. uint32_t ShaderVariantTreeNode::GetOffset() const
  232. {
  233. return m_offset;
  234. }
  235. bool ShaderVariantTreeNode::HasChildren() const
  236. {
  237. return m_offset != 0;
  238. }
  239. } // namespace RPI
  240. } // namespace AZ