ShaderBuildArgumentsManager.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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 "ShaderBuildArgumentsManager.h"
  9. #include <AzCore/Settings/SettingsRegistry.h>
  10. #include <AzCore/Utils/Utils.h>
  11. #include <AzCore/Serialization/Json/JsonUtils.h>
  12. #include <AzCore/IO/FileIO.h>
  13. #include <Atom/RPI.Edit/Common/JsonUtils.h>
  14. #include <Atom/RHI.Edit/ShaderBuildOptions.h>
  15. namespace AZ
  16. {
  17. namespace ShaderBuilder
  18. {
  19. static AZStd::string ResolvePathAliases(AZStd::string_view inPath)
  20. {
  21. AZ::IO::FixedMaxPath resolvedPath;
  22. auto fileIO = AZ::IO::FileIOBase::GetInstance();
  23. const bool success = fileIO->ResolvePath(resolvedPath, inPath);
  24. if (success)
  25. {
  26. return AZStd::string(resolvedPath.c_str());
  27. }
  28. return { inPath };
  29. }
  30. void ShaderBuildArgumentsManager::Init()
  31. {
  32. AZStd::unordered_map<AZStd::string, AZ::RHI::ShaderBuildArguments> removeBuildArgumentsMap;
  33. AZStd::unordered_map<AZStd::string, AZ::RHI::ShaderBuildArguments> addBuildArgumentsMap;
  34. auto configFiles = DiscoverConfigurationFiles();
  35. for (auto const& [scopeName, jsonFilePath] : configFiles)
  36. {
  37. AZ::RHI::ShaderBuildOptions shaderBuildOptions;
  38. if (!AZ::RPI::JsonUtils::LoadObjectFromFile(jsonFilePath.c_str(), shaderBuildOptions))
  39. {
  40. AZ_Error(
  41. LogName, false, "Failed to load shader build options file=<%s> for scope=<%s>", jsonFilePath.c_str(),
  42. scopeName.c_str());
  43. continue;
  44. }
  45. [[maybe_unused]] const auto addedDefinitionCount =
  46. shaderBuildOptions.m_addBuildArguments.AppendDefinitions(shaderBuildOptions.m_definitions);
  47. AZ_Assert(addedDefinitionCount >= 0, "Failed to add definitions");
  48. removeBuildArgumentsMap.emplace(scopeName, AZStd::move(shaderBuildOptions.m_removeBuildArguments));
  49. addBuildArgumentsMap.emplace(scopeName, AZStd::move(shaderBuildOptions.m_addBuildArguments));
  50. }
  51. Init(AZStd::move(removeBuildArgumentsMap), AZStd::move(addBuildArgumentsMap));
  52. }
  53. void ShaderBuildArgumentsManager::Init(AZStd::unordered_map<AZStd::string, AZ::RHI::ShaderBuildArguments> && removeBuildArgumentsMap
  54. , AZStd::unordered_map<AZStd::string, AZ::RHI::ShaderBuildArguments> && addBuildArgumentsMap)
  55. {
  56. m_removeBuildArgumentsMap.clear();
  57. m_removeBuildArgumentsMap.swap(removeBuildArgumentsMap);
  58. m_addBuildArgumentsMap.clear();
  59. m_addBuildArgumentsMap.swap(addBuildArgumentsMap);
  60. m_argumentsStack = {};
  61. m_argumentsNameStack = {};
  62. m_argumentsStack.push(m_addBuildArgumentsMap[""]);
  63. m_argumentsNameStack.push("");
  64. }
  65. const AZ::RHI::ShaderBuildArguments& ShaderBuildArgumentsManager::PushArgumentsInternal(const AZStd::string& name, const AZ::RHI::ShaderBuildArguments& arguments)
  66. {
  67. m_argumentsNameStack.push(name);
  68. m_argumentsStack.push(arguments);
  69. return m_argumentsStack.top();
  70. }
  71. const AZ::RHI::ShaderBuildArguments& ShaderBuildArgumentsManager::PushArgumentScope(const AZStd::string& name)
  72. {
  73. AZ_Assert(!name.empty(), "This function requires non empty names");
  74. const auto& currentTopName = m_argumentsNameStack.top();
  75. auto newTopName = currentTopName.empty() ? name : AZStd::string::format("%s.%s", currentTopName.c_str(), name.c_str());
  76. auto it = m_addBuildArgumentsMap.find(newTopName);
  77. if (it == m_addBuildArgumentsMap.end())
  78. {
  79. // It is normal not to have arguments for a specific key. Because this class works as a stack we'll just push a copy
  80. // of whatever is currently at the top of the stack.
  81. return PushArgumentsInternal(newTopName, GetCurrentArguments());
  82. }
  83. // Init() guarantees that if there's an Add set of arguments, there is also a Remove set of arguments. BUT either of both, the Add and the Remove Sets can be empty,
  84. // what matters is that they are valid instances of ShaderBuildArguments class.
  85. auto removeIt = m_removeBuildArgumentsMap.find(newTopName);
  86. AZ_Assert(removeIt != m_removeBuildArgumentsMap.end(), "There must be an instance of arguments to remove for %s", currentTopName.c_str());
  87. auto newArguments = GetCurrentArguments() - removeIt->second;
  88. return PushArgumentsInternal(newTopName, newArguments + it->second);
  89. }
  90. const AZ::RHI::ShaderBuildArguments& ShaderBuildArgumentsManager::PushArgumentScope(const AZ::RHI::ShaderBuildArguments& removeArguments,
  91. const AZ::RHI::ShaderBuildArguments& addArguments, const AZStd::vector<AZStd::string>& definitions)
  92. {
  93. const AZStd::string anyName("?");
  94. const auto& currentTopName = m_argumentsNameStack.top();
  95. auto newTopName = currentTopName.empty() ? anyName : AZStd::string::format("%s.%s", currentTopName.c_str(), anyName.c_str());
  96. auto newArguments = GetCurrentArguments() - removeArguments;
  97. [[maybe_unused]] const auto addedDefinitionCount = newArguments.AppendDefinitions(definitions);
  98. AZ_Assert(addedDefinitionCount >= 0, "Failed to add definitions");
  99. return PushArgumentsInternal(newTopName, newArguments + addArguments);
  100. }
  101. const AZ::RHI::ShaderBuildArguments& ShaderBuildArgumentsManager::GetCurrentArguments() const
  102. {
  103. return m_argumentsStack.top();
  104. }
  105. void ShaderBuildArgumentsManager::PopArgumentScope()
  106. {
  107. // We always keep the global scope.
  108. if (m_argumentsStack.size() > 1)
  109. {
  110. m_argumentsStack.pop();
  111. m_argumentsNameStack.pop();
  112. }
  113. }
  114. void DiscoverConfigurationFilesInDirectoryRecursively(const AZ::IO::FixedMaxPath& dirPath, const AZStd::string& keyName,
  115. AZStd::unordered_map<AZStd::string, AZ::IO::FixedMaxPath>& discoveredFiles)
  116. {
  117. auto findFileCB = [&](const char * fileName, bool isFile) -> bool
  118. {
  119. if (fileName[0] == '.')
  120. {
  121. return true;
  122. }
  123. AZ::IO::FixedMaxPath fullPath = dirPath / fileName;
  124. if (isFile)
  125. {
  126. discoveredFiles[keyName] = fullPath;
  127. }
  128. else
  129. {
  130. AZStd::string subKeyName = AZStd::string::format("%s%s%s", keyName.c_str(), keyName.empty() ? "" : ".", fileName);
  131. DiscoverConfigurationFilesInDirectoryRecursively(fullPath, subKeyName, discoveredFiles);
  132. }
  133. return true;
  134. };
  135. AZ::IO::FixedMaxPath filter(dirPath);
  136. filter /= "*";
  137. AZ::IO::SystemFile::FindFiles(filter.c_str(), findFileCB);
  138. }
  139. AZStd::unordered_map<AZStd::string, AZ::IO::FixedMaxPath> ShaderBuildArgumentsManager::DiscoverConfigurationFilesInDirectory(const AZ::IO::FixedMaxPath& dirPath)
  140. {
  141. AZStd::unordered_map<AZStd::string, AZ::IO::FixedMaxPath> configurationFiles;
  142. auto jsonPath = dirPath / ShaderBuildOptionsJson;
  143. if (AZ::IO::SystemFile::Exists(jsonPath.c_str()))
  144. {
  145. // The global scope has no name.
  146. configurationFiles[""] = jsonPath;
  147. }
  148. AZ::IO::FixedMaxPath platformsDirPath(dirPath);
  149. platformsDirPath /= PlatformsDir;
  150. DiscoverConfigurationFilesInDirectoryRecursively(platformsDirPath, "", configurationFiles);
  151. return configurationFiles;
  152. }
  153. AZ::IO::FixedMaxPath ShaderBuildArgumentsManager::GetDefaultConfigDirectoryPath()
  154. {
  155. AZStd::string defaultConfigDirectory(DefaultConfigPathDirectory);
  156. defaultConfigDirectory = ResolvePathAliases(defaultConfigDirectory);
  157. // The default directory, which contains factory settings, must always exist.
  158. AZ_Assert(AZ::IO::SystemFile::Exists(defaultConfigDirectory.c_str()), "The default directory with shader build arguments must exist: %s", defaultConfigDirectory.c_str());
  159. return AZ::IO::FixedMaxPath{ defaultConfigDirectory };
  160. }
  161. AZ::IO::FixedMaxPath ShaderBuildArgumentsManager::GetUserConfigDirectoryPath()
  162. {
  163. AZStd::string userConfig;
  164. auto settingsRegistry = AZ::SettingsRegistry::Get();
  165. if (settingsRegistry)
  166. {
  167. settingsRegistry->Get(userConfig, ConfigPathRegistryKey);
  168. }
  169. if (userConfig.empty())
  170. {
  171. return {};
  172. }
  173. userConfig = ResolvePathAliases(userConfig);
  174. return AZ::IO::FixedMaxPath{ userConfig };
  175. }
  176. AZStd::unordered_map<AZStd::string, AZ::IO::FixedMaxPath> ShaderBuildArgumentsManager::DiscoverConfigurationFiles()
  177. {
  178. const auto defaultConfigDirectoryPath = GetDefaultConfigDirectoryPath();
  179. auto configFiles = DiscoverConfigurationFilesInDirectory(defaultConfigDirectoryPath);
  180. auto userConfigPath = GetUserConfigDirectoryPath();
  181. if (userConfigPath.empty() || (defaultConfigDirectoryPath == userConfigPath))
  182. {
  183. // The user chose not to customize the command line arguments.
  184. // Let's return the Atom's default.
  185. return configFiles;
  186. }
  187. auto userConfigFiles = DiscoverConfigurationFilesInDirectory(userConfigPath);
  188. // Replace only the file paths that are customized by the user.
  189. for (auto const& [key, val] : configFiles)
  190. {
  191. const auto itor = userConfigFiles.find(key);
  192. if (itor == userConfigFiles.end())
  193. {
  194. continue;
  195. }
  196. configFiles[key] = userConfigFiles[key];
  197. }
  198. return configFiles;
  199. }
  200. } // namespace ShaderBuilder
  201. } // AZ