MaterialPipelineScriptRunner.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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 "MaterialPipelineScriptRunner.h"
  9. #include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
  10. #include <Atom/RPI.Edit/Common/AssetUtils.h>
  11. #include <Atom/RPI.Reflect/Material/LuaScriptUtilities.h>
  12. #include <AzCore/Script/ScriptAsset.h>
  13. #include <AzCore/Script/ScriptSystemBus.h>
  14. #include <AzCore/Utils/Utils.h>
  15. namespace AZ
  16. {
  17. namespace RPI
  18. {
  19. /*static*/ void MaterialPipelineScriptRunner::ScriptExecutionContext::Reflect(ReflectContext* reflect)
  20. {
  21. if (auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(reflect))
  22. {
  23. behaviorContext->Class<ScriptExecutionContext>()
  24. ->Method("GetLightingModelName", &ScriptExecutionContext::GetLightingModelName)
  25. ->Method("IncludeAllShaders", &ScriptExecutionContext::IncludeAllShaders)
  26. ->Method("ExcludeAllShaders", &ScriptExecutionContext::ExcludeAllShaders)
  27. ->Method("IncludeShader", &ScriptExecutionContext::IncludeShader)
  28. ->Method("ExcludeShader", &ScriptExecutionContext::ExcludeShader)
  29. ;
  30. }
  31. }
  32. MaterialPipelineScriptRunner::ScriptExecutionContext::ScriptExecutionContext(const MaterialTypeSourceData& materialType, const ShaderTemplatesList& availableShaderTemplates)
  33. : m_materialType(materialType)
  34. {
  35. for (auto shaderTemplate : availableShaderTemplates)
  36. {
  37. AZ::IO::Path shaderName = shaderTemplate.m_shader;
  38. shaderName = shaderName.Filename(); // Removes the folder path
  39. shaderName = shaderName.ReplaceExtension(""); // This will remove the ".template" extension
  40. shaderName = shaderName.ReplaceExtension(""); // This will remove the ".shader" extension
  41. ShaderTemplateInfo shaderTemplateInfo;
  42. shaderTemplateInfo.m_template = shaderTemplate;
  43. shaderTemplateInfo.m_isIncluded = true;
  44. m_shaderTemplateStatusMap.emplace(AZStd::move(shaderName), AZStd::move(shaderTemplateInfo));
  45. }
  46. }
  47. MaterialPipelineScriptRunner::ScriptExecutionContext::ShaderTemplateStatusMap::iterator
  48. MaterialPipelineScriptRunner::ScriptExecutionContext::GetShaderStatusIterator(const AZStd::string& shaderTemplateName)
  49. {
  50. auto iter = m_shaderTemplateStatusMap.find(shaderTemplateName);
  51. if (iter == m_shaderTemplateStatusMap.end())
  52. {
  53. AZStd::vector<AZStd::string> availableShaderTemplateList;
  54. for (auto& [availableShaderTemplateName, enabled] : m_shaderTemplateStatusMap)
  55. {
  56. AZ_UNUSED(enabled);
  57. availableShaderTemplateList.push_back(availableShaderTemplateName);
  58. }
  59. AZStd::string availableShaderTemplateListString;
  60. AzFramework::StringFunc::Join(availableShaderTemplateListString, availableShaderTemplateList.begin(), availableShaderTemplateList.end(), ",");
  61. LuaScriptUtilities::Error(AZStd::string::format("Shader template named '%s' does not exist. The available shader templates are [%s]",
  62. shaderTemplateName.c_str(), availableShaderTemplateListString.c_str()));
  63. }
  64. return iter;
  65. }
  66. void MaterialPipelineScriptRunner::ScriptExecutionContext::SetIncludeShader(const AZStd::string& shaderTemplateName, bool include)
  67. {
  68. auto iter = GetShaderStatusIterator(shaderTemplateName);
  69. if (iter != m_shaderTemplateStatusMap.end())
  70. {
  71. iter->second.m_isIncluded = include;
  72. }
  73. }
  74. void MaterialPipelineScriptRunner::ScriptExecutionContext::IncludeAllShaders()
  75. {
  76. for (auto& [shaderTemplateName, shaderTemplateInfo] : m_shaderTemplateStatusMap)
  77. {
  78. shaderTemplateInfo.m_isIncluded = true;
  79. }
  80. }
  81. void MaterialPipelineScriptRunner::ScriptExecutionContext::ExcludeAllShaders()
  82. {
  83. for (auto& [shaderTemplateName, shaderTemplateInfo] : m_shaderTemplateStatusMap)
  84. {
  85. shaderTemplateInfo.m_isIncluded = false;
  86. }
  87. }
  88. void MaterialPipelineScriptRunner::ScriptExecutionContext::IncludeShader(const char* shaderTemplateName)
  89. {
  90. SetIncludeShader(shaderTemplateName, true);
  91. }
  92. void MaterialPipelineScriptRunner::ScriptExecutionContext::ExcludeShader(const char* shaderTemplateName)
  93. {
  94. SetIncludeShader(shaderTemplateName, false);
  95. }
  96. MaterialPipelineScriptRunner::ShaderTemplatesList MaterialPipelineScriptRunner::ScriptExecutionContext::GetIncludedShaderTemplates() const
  97. {
  98. ShaderTemplatesList includedTemplates;
  99. for (auto& [shaderTemplateName, shaderTemplateInfo] : m_shaderTemplateStatusMap)
  100. {
  101. if (shaderTemplateInfo.m_isIncluded)
  102. {
  103. includedTemplates.push_back(shaderTemplateInfo.m_template);
  104. }
  105. }
  106. return includedTemplates;
  107. }
  108. AZStd::string MaterialPipelineScriptRunner::ScriptExecutionContext::GetLightingModelName() const
  109. {
  110. return m_materialType.m_lightingModel;
  111. }
  112. MaterialPipelineScriptRunner::MaterialPipelineScriptRunner() = default;
  113. void MaterialPipelineScriptRunner::Reflect(ReflectContext* context)
  114. {
  115. ScriptExecutionContext::Reflect(context);
  116. }
  117. void MaterialPipelineScriptRunner::Reset()
  118. {
  119. m_relevantShaderTemplates.clear();
  120. }
  121. const MaterialPipelineScriptRunner::ShaderTemplatesList& MaterialPipelineScriptRunner::GetRelevantShaderTemplates() const
  122. {
  123. return m_relevantShaderTemplates;
  124. }
  125. bool MaterialPipelineScriptRunner::RunScript(
  126. const AZ::IO::Path& materialPipelineFile,
  127. const MaterialPipelineSourceData& materialPipeline,
  128. const MaterialTypeSourceData& materialType)
  129. {
  130. Reset();
  131. if (materialPipeline.m_pipelineScript.empty())
  132. {
  133. m_relevantShaderTemplates = materialPipeline.m_shaderTemplates;
  134. return true;
  135. }
  136. const AZStd::string scriptPath = RPI::AssetUtils::ResolvePathReference(materialPipelineFile.c_str(), materialPipeline.m_pipelineScript);
  137. auto reportError = [&]([[maybe_unused]] const AZStd::string& message)
  138. {
  139. AZ_Error(DebugName, false, "Script '%s' failed. %s", scriptPath.c_str(), message.c_str());
  140. };
  141. // TODO(MaterialPipeline): At some point it would be nice if we didn't have to parse the lua script every time we need to run it, and
  142. // instead just use the corresponding ScriptAsset, similar to how LuaMaterialFunctorSourceData works. But AssetProcessor does not allow
  143. // an asset job for the "common" to load a product from the catalog of some specific platform, nor does it support loading any assets
  144. // from the "common" catalog. See https://github.com/o3de/o3de/issues/12863
  145. // (Remember this will require replacing the source dependency with a job dependency).
  146. const size_t MaxScriptFileSize = 1024 * 1024;
  147. auto luaScriptContent = AZ::Utils::ReadFile(scriptPath, MaxScriptFileSize);
  148. if (!luaScriptContent)
  149. {
  150. reportError(AZStd::string::format("Could not load script. %s", luaScriptContent.GetError().c_str()));
  151. return false;
  152. }
  153. AZ::ScriptContext* scriptContext{};
  154. AZ::ScriptSystemRequestBus::BroadcastResult(scriptContext, &AZ::ScriptSystemRequests::GetContext, AZ::ScriptContextIds::DefaultScriptContextId);
  155. if (scriptContext == nullptr)
  156. {
  157. reportError("Unable to query global script context. Is the ScriptSystemComponent active?");
  158. return false;
  159. }
  160. // Remove any MaterialTypeSetup functions from the lua global table before loading in new script
  161. scriptContext->RemoveGlobal(MainFunctionName);
  162. if (!scriptContext->Execute(luaScriptContent.GetValue().data(), materialPipeline.m_pipelineScript.c_str(), luaScriptContent.GetValue().size()))
  163. {
  164. reportError("Error initializing script.");
  165. return false;
  166. }
  167. AZ::ScriptDataContext call;
  168. if (!scriptContext->Call(MainFunctionName, call))
  169. {
  170. reportError(AZStd::string::format("Function %s() is not defined.", MainFunctionName));
  171. return false;
  172. }
  173. ScriptExecutionContext luaContext{materialType, materialPipeline.m_shaderTemplates};
  174. call.PushArg(luaContext);
  175. if (!call.CallExecute())
  176. {
  177. reportError(AZStd::string::format("Failed calling %s().", MainFunctionName));
  178. return false;
  179. }
  180. if (1 != call.GetNumResults() || !call.IsBoolean(0))
  181. {
  182. reportError(AZStd::string::format("%s() must return a boolean.", MainFunctionName));
  183. return false;
  184. }
  185. bool result = false;
  186. if (!call.ReadResult(0, result))
  187. {
  188. reportError(AZStd::string::format("Failed reading the result of %s().", MainFunctionName));
  189. return false;
  190. }
  191. if (result)
  192. {
  193. m_relevantShaderTemplates = luaContext.GetIncludedShaderTemplates();
  194. }
  195. else
  196. {
  197. reportError(AZStd::string::format("%s() returned false.", MainFunctionName).c_str());
  198. }
  199. return result;
  200. }
  201. } // namespace RPI
  202. } // namespace AZ