MaterialPipelineScriptRunner.cpp 9.6 KB

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