You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/LuaMaterialFunctorSourceDat...

231 lines
9.8 KiB
C++

/*
* 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 <Atom/RPI.Edit/Material/LuaMaterialFunctorSourceData.h>
#include <Atom/RPI.Reflect/Material/LuaMaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Script/ScriptAsset.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
namespace AZ
{
namespace RPI
{
void LuaMaterialFunctorSourceData::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<LuaMaterialFunctorSourceData>()
->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::AssetDependency> LuaMaterialFunctorSourceData::GetAssetDependencies() const
{
if (!m_luaSourceFile.empty())
{
AssetDependency dependency;
dependency.m_jobKey = "Lua Compile";
dependency.m_sourceFilePath = m_luaSourceFile;
return AZStd::vector<AssetDependency>{dependency};
}
else
{
return {};
}
}
Outcome<AZStd::vector<Name>, void> LuaMaterialFunctorSourceData::GetNameListFromLuaScript(AZ::ScriptContext& scriptContext, const char* luaFunctionName) const
{
AZStd::vector<Name> 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<LuaMaterialFunctor> 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<ScriptAsset>(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<AZStd::string>().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())
{
MaterialPropertyIndex index = propertiesLayout->FindPropertyIndex(Name{m_propertyNamePrefix + materialProperty.GetCStr()});
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<MaterialFunctor>(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());
}
}
}