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.
234 lines
9.9 KiB
C++
234 lines
9.9 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())
|
|
{
|
|
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<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());
|
|
}
|
|
}
|
|
}
|