LuaMaterialFunctorSourceData.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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.Edit/Material/LuaMaterialFunctorSourceData.h>
  9. #include <Atom/RPI.Reflect/Material/LuaMaterialFunctor.h>
  10. #include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzCore/Script/ScriptAsset.h>
  13. #include <AzCore/Script/ScriptSystemBus.h>
  14. #include <Atom/RPI.Edit/Common/AssetUtils.h>
  15. namespace AZ
  16. {
  17. namespace RPI
  18. {
  19. void LuaMaterialFunctorSourceData::Reflect(AZ::ReflectContext* context)
  20. {
  21. if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
  22. {
  23. serializeContext->Class<LuaMaterialFunctorSourceData>()
  24. ->Version(3)
  25. ->Field("file", &LuaMaterialFunctorSourceData::m_luaSourceFile)
  26. ->Field("propertyNamePrefix", &LuaMaterialFunctorSourceData::m_propertyNamePrefix)
  27. ->Field("srgNamePrefix", &LuaMaterialFunctorSourceData::m_srgNamePrefix)
  28. ->Field("optionsNamePrefix", &LuaMaterialFunctorSourceData::m_optionsNamePrefix)
  29. //[GFX TODO][ATOM-6011] Add support for inline script. Needs a custom "multiline string" json serializer.
  30. //->Field("script", &LuaMaterialFunctorSourceData::m_luaScript)
  31. ;
  32. }
  33. }
  34. AZStd::vector<LuaMaterialFunctorSourceData::AssetDependency> LuaMaterialFunctorSourceData::GetAssetDependencies() const
  35. {
  36. if (!m_luaSourceFile.empty())
  37. {
  38. AssetDependency dependency;
  39. dependency.m_jobKey = "Lua Compile";
  40. dependency.m_sourceFilePath = m_luaSourceFile;
  41. return AZStd::vector<AssetDependency>{dependency};
  42. }
  43. else
  44. {
  45. return {};
  46. }
  47. }
  48. Outcome<AZStd::vector<Name>, void> LuaMaterialFunctorSourceData::GetNameListFromLuaScript(AZ::ScriptContext& scriptContext, const char* luaFunctionName) const
  49. {
  50. AZStd::vector<Name> result;
  51. AZ::ScriptDataContext call;
  52. if (scriptContext.Call(luaFunctionName, call))
  53. {
  54. if (!call.CallExecute())
  55. {
  56. AZ_Error("LuaMaterialFunctorSourceData", false, "Failed calling %s().", luaFunctionName);
  57. return Failure();
  58. }
  59. if (1 != call.GetNumResults() || !call.IsTable(0))
  60. {
  61. AZ_Error("LuaMaterialFunctorSourceData", false, "%s() must return a table.", luaFunctionName);
  62. return Failure();
  63. }
  64. AZ::ScriptDataContext table;
  65. if (!call.InspectTable(0, table))
  66. {
  67. AZ_Error("LuaMaterialFunctorSourceData", false, "Failed to inspect table returned by %s().", luaFunctionName);
  68. return Failure();
  69. }
  70. const char* fieldName;
  71. int fieldIndex;
  72. int elementIndex;
  73. bool foundPropertyError = false;
  74. while (table.InspectNextElement(elementIndex, fieldName, fieldIndex))
  75. {
  76. if (fieldIndex != -1)
  77. {
  78. if (!table.IsString(elementIndex))
  79. {
  80. AZ_Error("LuaMaterialFunctorSourceData", false, "%s() returned invalid table: element[%d] is not a string", luaFunctionName, fieldIndex);
  81. foundPropertyError = true;
  82. continue;
  83. }
  84. const char* materialPropertyName = nullptr;
  85. if (!table.ReadValue(elementIndex, materialPropertyName))
  86. {
  87. AZ_Error("LuaMaterialFunctorSourceData", false, "%s() returned invalid table: element[%d] is invalid", luaFunctionName, fieldIndex);
  88. foundPropertyError = true;
  89. continue;
  90. }
  91. result.push_back(Name{materialPropertyName});
  92. }
  93. }
  94. if (foundPropertyError)
  95. {
  96. return Failure();
  97. }
  98. }
  99. return Success(result);
  100. }
  101. RPI::LuaMaterialFunctorSourceData::FunctorResult LuaMaterialFunctorSourceData::CreateFunctor(
  102. const AZStd::string& materialTypeSourceFilePath,
  103. const MaterialPropertiesLayout* propertiesLayout,
  104. const MaterialNameContext* materialNameContext
  105. ) const
  106. {
  107. using namespace RPI;
  108. RPI::Ptr<LuaMaterialFunctor> functor = aznew LuaMaterialFunctor;
  109. if (materialNameContext->IsDefault())
  110. {
  111. // This is a legacy feature that was used for a while to support reusing the same functor for multiple layers in StandardMultilayerPbr.materialtype.
  112. // Now that we have support for nested property groups, this functionality is only supported for functors at the top level, for backward compatibility.
  113. functor->m_materialNameContext.ExtendPropertyIdContext(m_propertyNamePrefix, false);
  114. functor->m_materialNameContext.ExtendSrgInputContext(m_srgNamePrefix);
  115. functor->m_materialNameContext.ExtendShaderOptionContext(m_optionsNamePrefix);
  116. }
  117. else
  118. {
  119. functor->m_materialNameContext = *materialNameContext;
  120. }
  121. if (!m_luaScript.empty() && !m_luaSourceFile.empty())
  122. {
  123. AZ_Error("LuaMaterialFunctor", m_luaSourceFile.empty(), "Lua material functor has both a built-in script and an external script file.");
  124. return Failure();
  125. }
  126. else if (!m_luaScript.empty())
  127. {
  128. functor->m_scriptBuffer.assign(m_luaScript.begin(), m_luaScript.end());
  129. }
  130. else if (!m_luaSourceFile.empty())
  131. {
  132. auto loadOutcome =
  133. RPI::AssetUtils::LoadAsset<ScriptAsset>(materialTypeSourceFilePath, m_luaSourceFile, ScriptAsset::CompiledAssetSubId);
  134. if (!loadOutcome)
  135. {
  136. AZ_Error("LuaMaterialFunctorSourceData", false, "Could not load script file '%s'", m_luaSourceFile.c_str());
  137. return Failure();
  138. }
  139. functor->m_scriptAsset = loadOutcome.GetValue();
  140. }
  141. else
  142. {
  143. AZ_Error("LuaMaterialFunctor", false, "Lua material functor has no script data.");
  144. return Failure();
  145. }
  146. ScriptContext* scriptContext{};
  147. ScriptSystemRequestBus::BroadcastResult(scriptContext, &ScriptSystemRequests::GetContext, ScriptContextIds::DefaultScriptContextId);
  148. if (scriptContext == nullptr)
  149. {
  150. AZ_ErrorOnce("LuaMaterialFunctorSourceData", false, "Global script context is not available. Cannot execute script");
  151. return Failure();
  152. }
  153. auto scriptBuffer = functor->GetScriptBuffer();
  154. // Remove any GetMaterialPropertyDependencies and GetShaderOptionDependencies functions on the global table
  155. scriptContext->RemoveGlobal("GetMaterialPropertyDependencies");
  156. scriptContext->RemoveGlobal("GetShaderOptionDependencies");
  157. if (!scriptContext->Execute(scriptBuffer.data(), functor->GetScriptDescription(), scriptBuffer.size()))
  158. {
  159. AZ_Error("LuaMaterialFunctorSourceData", false, "Error initializing script '%s'.", functor->m_scriptAsset.ToString<AZStd::string>().c_str());
  160. return Failure();
  161. }
  162. // [GFX TODO][ATOM-6012]: Figure out how to make shader option dependencies and material property dependencies get automatically reported
  163. auto materialPropertyDependencies = GetNameListFromLuaScript(*scriptContext, "GetMaterialPropertyDependencies");
  164. auto shaderOptionDependencies = GetNameListFromLuaScript(*scriptContext, "GetShaderOptionDependencies");
  165. if (!materialPropertyDependencies.IsSuccess() || !shaderOptionDependencies.IsSuccess())
  166. {
  167. return Failure();
  168. }
  169. if (materialPropertyDependencies.GetValue().empty())
  170. {
  171. AZ_Error("LuaMaterialFunctorSourceData", false, "Material functor must use at least one material property.");
  172. return Failure();
  173. }
  174. m_shaderOptionDependencies = shaderOptionDependencies.GetValue();
  175. for (auto& shaderOption : m_shaderOptionDependencies)
  176. {
  177. shaderOption = Name{m_optionsNamePrefix + shaderOption.GetCStr()};
  178. }
  179. for (const Name& materialProperty : materialPropertyDependencies.GetValue())
  180. {
  181. Name propertyName{materialProperty};
  182. functor->m_materialNameContext.ContextualizeProperty(propertyName);
  183. MaterialPropertyIndex index = propertiesLayout->FindPropertyIndex(propertyName);
  184. if (index.IsValid())
  185. {
  186. AddMaterialPropertyDependency(functor, index);
  187. }
  188. // This allows missing dependencies to make scripts more flexible - they can depend on properties that may
  189. // or may not exist, and it's up to the script to call HasMaterialProperty() before accessing a property
  190. // if necessary.
  191. }
  192. return Success(RPI::Ptr<MaterialFunctor>(functor));
  193. }
  194. RPI::LuaMaterialFunctorSourceData::FunctorResult LuaMaterialFunctorSourceData::CreateFunctor(const RuntimeContext& context) const
  195. {
  196. return CreateFunctor(
  197. context.GetMaterialTypeSourceFilePath(),
  198. context.GetMaterialPropertiesLayout(),
  199. context.GetNameContext());
  200. }
  201. RPI::LuaMaterialFunctorSourceData::FunctorResult LuaMaterialFunctorSourceData::CreateFunctor(const EditorContext& context) const
  202. {
  203. return CreateFunctor(
  204. context.GetMaterialTypeSourceFilePath(),
  205. context.GetMaterialPropertiesLayout(),
  206. context.GetNameContext());
  207. }
  208. }
  209. }