3
0

LuaMaterialFunctorSourceData.cpp 9.8 KB

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