/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include namespace AZ { namespace RPI { void LuaMaterialFunctorSourceData::Reflect(AZ::ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(3) ->Field("file", &LuaMaterialFunctorSourceData::m_luaSourceFile) ->Field("propertyNamePrefix", &LuaMaterialFunctorSourceData::m_propertyNamePrefix) ->Field("srgNamePrefix", &LuaMaterialFunctorSourceData::m_srgNamePrefix) ->Field("optionsNamePrefix", &LuaMaterialFunctorSourceData::m_optionsNamePrefix) //[GFX TODO][ATOM-6011] Add support for inline script. Needs a custom "multiline string" json serializer. //->Field("script", &LuaMaterialFunctorSourceData::m_luaScript) ; } } AZStd::vector LuaMaterialFunctorSourceData::GetAssetDependencies() const { if (!m_luaSourceFile.empty()) { AssetDependency dependency; dependency.m_jobKey = "Lua Compile"; dependency.m_sourceFilePath = m_luaSourceFile; return AZStd::vector{dependency}; } else { return {}; } } Outcome, void> LuaMaterialFunctorSourceData::GetNameListFromLuaScript(AZ::ScriptContext& scriptContext, const char* luaFunctionName) const { AZStd::vector result; AZ::ScriptDataContext call; if (scriptContext.Call(luaFunctionName, call)) { if (!call.CallExecute()) { AZ_Error("LuaMaterialFunctorSourceData", false, "Failed calling %s().", luaFunctionName); return Failure(); } if (1 != call.GetNumResults() || !call.IsTable(0)) { AZ_Error("LuaMaterialFunctorSourceData", false, "%s() must return a table.", luaFunctionName); return Failure(); } AZ::ScriptDataContext table; if (!call.InspectTable(0, table)) { AZ_Error("LuaMaterialFunctorSourceData", false, "Failed to inspect table returned by %s().", luaFunctionName); return Failure(); } const char* fieldName; int fieldIndex; int elementIndex; bool foundPropertyError = false; while (table.InspectNextElement(elementIndex, fieldName, fieldIndex)) { if (fieldIndex != -1) { if (!table.IsString(elementIndex)) { AZ_Error("LuaMaterialFunctorSourceData", false, "%s() returned invalid table: element[%d] is not a string", luaFunctionName, fieldIndex); foundPropertyError = true; continue; } const char* materialPropertyName = nullptr; if (!table.ReadValue(elementIndex, materialPropertyName)) { AZ_Error("LuaMaterialFunctorSourceData", false, "%s() returned invalid table: element[%d] is invalid", luaFunctionName, fieldIndex); foundPropertyError = true; continue; } result.push_back(Name{materialPropertyName}); } } if (foundPropertyError) { return Failure(); } } return Success(result); } RPI::LuaMaterialFunctorSourceData::FunctorResult LuaMaterialFunctorSourceData::CreateFunctor( const AZStd::string& materialTypeSourceFilePath, const MaterialPropertiesLayout* propertiesLayout, const MaterialNameContext* materialNameContext ) const { using namespace RPI; RPI::Ptr functor = aznew LuaMaterialFunctor; if (materialNameContext->IsDefault()) { // This is a legacy feature that was used for a while to support reusing the same functor for multiple layers in StandardMultilayerPbr.materialtype. // Now that we have support for nested property groups, this functionality is only supported for functors at the top level, for backward compatibility. functor->m_materialNameContext.ExtendPropertyIdContext(m_propertyNamePrefix, false); functor->m_materialNameContext.ExtendSrgInputContext(m_srgNamePrefix); functor->m_materialNameContext.ExtendShaderOptionContext(m_optionsNamePrefix); } else { functor->m_materialNameContext = *materialNameContext; } if (!m_luaScript.empty() && !m_luaSourceFile.empty()) { AZ_Error("LuaMaterialFunctor", m_luaSourceFile.empty(), "Lua material functor has both a built-in script and an external script file."); return Failure(); } else if (!m_luaScript.empty()) { functor->m_scriptBuffer.assign(m_luaScript.begin(), m_luaScript.end()); } else if (!m_luaSourceFile.empty()) { // The sub ID for script assets must be explicit. // LUA source files output a compiled as well as an uncompiled asset, sub Ids of 1 and 2. auto loadOutcome = RPI::AssetUtils::LoadAsset(materialTypeSourceFilePath, m_luaSourceFile, ScriptAsset::CompiledAssetSubId); if (!loadOutcome) { AZ_Error("LuaMaterialFunctorSourceData", false, "Could not load script file '%s'", m_luaSourceFile.c_str()); return Failure(); } functor->m_scriptAsset = loadOutcome.GetValue(); } else { AZ_Error("LuaMaterialFunctor", false, "Lua material functor has no script data."); return Failure(); } AZ::ScriptContext scriptContext; auto scriptBuffer = functor->GetScriptBuffer(); if (!scriptContext.Execute(scriptBuffer.data(), functor->GetScriptDescription(), scriptBuffer.size())) { AZ_Error("LuaMaterialFunctorSourceData", false, "Error initializing script '%s'.", functor->m_scriptAsset.ToString().c_str()); return Failure(); } // [GFX TODO][ATOM-6012]: Figure out how to make shader option dependencies and material property dependencies get automatically reported auto materialPropertyDependencies = GetNameListFromLuaScript(scriptContext, "GetMaterialPropertyDependencies"); auto shaderOptionDependencies = GetNameListFromLuaScript(scriptContext, "GetShaderOptionDependencies"); if (!materialPropertyDependencies.IsSuccess() || !shaderOptionDependencies.IsSuccess()) { return Failure(); } if (materialPropertyDependencies.GetValue().empty()) { AZ_Error("LuaMaterialFunctorSourceData", false, "Material functor must use at least one material property."); return Failure(); } m_shaderOptionDependencies = shaderOptionDependencies.GetValue(); for (auto& shaderOption : m_shaderOptionDependencies) { shaderOption = Name{m_optionsNamePrefix + shaderOption.GetCStr()}; } for (const Name& materialProperty : materialPropertyDependencies.GetValue()) { Name propertyName{materialProperty}; functor->m_materialNameContext.ContextualizeProperty(propertyName); MaterialPropertyIndex index = propertiesLayout->FindPropertyIndex(propertyName); if (!index.IsValid()) { AZ_Error("LuaMaterialFunctorSourceData", false, "Property '%s' is not found in material type.", materialProperty.GetCStr()); return Failure(); } AddMaterialPropertyDependency(functor, index); } return Success(RPI::Ptr(functor)); } RPI::LuaMaterialFunctorSourceData::FunctorResult LuaMaterialFunctorSourceData::CreateFunctor(const RuntimeContext& context) const { return CreateFunctor( context.GetMaterialTypeSourceFilePath(), context.GetMaterialPropertiesLayout(), context.GetNameContext()); } RPI::LuaMaterialFunctorSourceData::FunctorResult LuaMaterialFunctorSourceData::CreateFunctor(const EditorContext& context) const { return CreateFunctor( context.GetMaterialTypeSourceFilePath(), context.GetMaterialPropertiesLayout(), context.GetNameContext()); } } }