Added support for deeply nested material property groups.

The main addition here is the MaterialNameContext class which represents the concept of a namespace for properties, shader options, and SRG fields. This concept was already somewhat supported in LuaMaterialFunctor through bespoke "prefix" fields, but I have generalized it be available for all material functors. Note that I have not yet updated the other material functor types to ensure they take advantage of this feature, that will be in another commit.

Signed-off-by: santorac <55155825+santorac@users.noreply.github.com>
monroegm-disable-blank-issue-2
santorac 4 years ago
parent b1cc469914
commit 2c59f1b8a4

@ -41,7 +41,10 @@ namespace AZ
// Calls a lua function that returns a list of strings. // Calls a lua function that returns a list of strings.
Outcome<AZStd::vector<Name>, void> GetNameListFromLuaScript(AZ::ScriptContext& scriptContext, const char* luaFunctionName) const; Outcome<AZStd::vector<Name>, void> GetNameListFromLuaScript(AZ::ScriptContext& scriptContext, const char* luaFunctionName) const;
FunctorResult CreateFunctor(const AZStd::string& materialTypeSourceFilePath, const MaterialPropertiesLayout* propertiesLayout) const; FunctorResult CreateFunctor(
const AZStd::string& materialTypeSourceFilePath,
const MaterialPropertiesLayout* propertiesLayout,
const MaterialNameContext* materialNameContext) const;
// Only one of these should have data // Only one of these should have data
AZStd::string m_luaSourceFile; AZStd::string m_luaSourceFile;

@ -31,6 +31,7 @@ namespace AZ
class MaterialPropertiesLayout; class MaterialPropertiesLayout;
class ShaderOptionGroupLayout; class ShaderOptionGroupLayout;
class JsonMaterialFunctorSourceDataSerializer; class JsonMaterialFunctorSourceDataSerializer;
class MaterialNameContext;
//! This is an abstract base class for initializing MaterialFunctor objects. //! This is an abstract base class for initializing MaterialFunctor objects.
//! Material functors provide custom logic and calculations to configure shaders, render states, and more. //! Material functors provide custom logic and calculations to configure shaders, render states, and more.
@ -54,11 +55,17 @@ namespace AZ
struct RuntimeContext struct RuntimeContext
{ {
public: public:
RuntimeContext(const AZStd::string& materialTypeFilePath, const MaterialPropertiesLayout* materialPropertiesLayout, const RHI::ShaderResourceGroupLayout* shaderResourceGroupLayout, const ShaderCollection* shaderCollection) RuntimeContext(
const AZStd::string& materialTypeFilePath,
const MaterialPropertiesLayout* materialPropertiesLayout,
const RHI::ShaderResourceGroupLayout* shaderResourceGroupLayout,
const ShaderCollection* shaderCollection,
const MaterialNameContext* materialNameContext)
: m_materialTypeFilePath(materialTypeFilePath) : m_materialTypeFilePath(materialTypeFilePath)
, m_materialPropertiesLayout(materialPropertiesLayout) , m_materialPropertiesLayout(materialPropertiesLayout)
, m_shaderResourceGroupLayout(shaderResourceGroupLayout) , m_shaderResourceGroupLayout(shaderResourceGroupLayout)
, m_shaderCollection(shaderCollection) , m_shaderCollection(shaderCollection)
, m_materialNameContext(materialNameContext)
{} {}
const AZStd::string& GetMaterialTypeSourceFilePath() const { return m_materialTypeFilePath; } const AZStd::string& GetMaterialTypeSourceFilePath() const { return m_materialTypeFilePath; }
@ -66,6 +73,10 @@ namespace AZ
const MaterialPropertiesLayout* GetMaterialPropertiesLayout() const; const MaterialPropertiesLayout* GetMaterialPropertiesLayout() const;
const RHI::ShaderResourceGroupLayout* GetShaderResourceGroupLayout() const; const RHI::ShaderResourceGroupLayout* GetShaderResourceGroupLayout() const;
//! Find the index of a ShaderResourceGroup input. This will automatically apply the MaterialNameContext.
RHI::ShaderInputConstantIndex FindShaderInputConstantIndex(Name inputName) const;
RHI::ShaderInputImageIndex FindShaderInputImageIndex(Name inputName) const;
//! Returns the number of shaders available in this material type. //! Returns the number of shaders available in this material type.
AZStd::size_t GetShaderCount() const; AZStd::size_t GetShaderCount() const;
@ -77,16 +88,18 @@ namespace AZ
AZStd::vector<AZ::Name> GetShaderTags() const; AZStd::vector<AZ::Name> GetShaderTags() const;
//! Find a property's index by its name. It will report error and return a Null index if it fails. //! Find a property's index by its name. It will report error and return a Null index if it fails.
MaterialPropertyIndex FindMaterialPropertyIndex(const Name& propertyName) const; //! This will also automatically apply the MaterialNameContext.
MaterialPropertyIndex FindMaterialPropertyIndex(Name propertyId) const;
//! Find a shader option index using an option name from the shader by index or tag. //! Find a shader option index using an option name from the shader by index or tag.
//! This will also automatically apply the MaterialNameContext.
//! @param shaderIndex index into the material type's list of shader options //! @param shaderIndex index into the material type's list of shader options
//! @param shaderTag tag name to index into the material type's list of shader options //! @param shaderTag tag name to index into the material type's list of shader options
//! @param optionName the name of the option to find //! @param optionName the name of the option to find
//! @param reportErrors if true, report an error message when if the option name was not found. //! @param reportErrors if true, report an error message when if the option name was not found.
//! @return the found index, or an empty handle if the option could not be found //! @return the found index, or an empty handle if the option could not be found
ShaderOptionIndex FindShaderOptionIndex(AZStd::size_t shaderIndex, const Name& optionName, bool reportErrors = true) const; ShaderOptionIndex FindShaderOptionIndex(AZStd::size_t shaderIndex, Name optionName, bool reportErrors = true) const;
ShaderOptionIndex FindShaderOptionIndex(const AZ::Name& shaderTag, const Name& optionName, bool reportErrors = true) const; ShaderOptionIndex FindShaderOptionIndex(const AZ::Name& shaderTag, Name optionName, bool reportErrors = true) const;
//! Return true if a shaderIndex is within the range of the number of shaders defined in this material. //! Return true if a shaderIndex is within the range of the number of shaders defined in this material.
//! And also report an error message if the index is invalid. //! And also report an error message if the index is invalid.
@ -96,19 +109,32 @@ namespace AZ
//! And also report an error message if shaderTag is invalid. //! And also report an error message if shaderTag is invalid.
bool CheckShaderTagValid(const AZ::Name& shaderTag) const; bool CheckShaderTagValid(const AZ::Name& shaderTag) const;
//! Returns the name context for the functor.
//! It acts like a namespace for any names that the MaterialFunctorSourceData might reference. The namespace
//! is automatically applied by the other relevant functions of this RuntimeContext class.
//! Not that by default the MaterialNameContext is not saved as part of the final MaterialFunctor class
//! (most CreateFunctor() implementations should convert names to indexes anyway) but CreateFunctor() can
//! copy it to the created MaterialFunctor for use at runtime if needed.
const MaterialNameContext* GetNameContext() const { return m_materialNameContext; }
private: private:
const AZStd::string m_materialTypeFilePath; const AZStd::string m_materialTypeFilePath;
const MaterialPropertiesLayout* m_materialPropertiesLayout; const MaterialPropertiesLayout* m_materialPropertiesLayout;
const RHI::ShaderResourceGroupLayout* m_shaderResourceGroupLayout; const RHI::ShaderResourceGroupLayout* m_shaderResourceGroupLayout;
const ShaderCollection* m_shaderCollection; const ShaderCollection* m_shaderCollection;
const MaterialNameContext* m_materialNameContext;
}; };
struct EditorContext struct EditorContext
{ {
public: public:
EditorContext(const AZStd::string& materialTypeFilePath, const MaterialPropertiesLayout* materialPropertiesLayout) EditorContext(
const AZStd::string& materialTypeFilePath,
const MaterialPropertiesLayout* materialPropertiesLayout,
const MaterialNameContext* materialNameContext)
: m_materialTypeFilePath(materialTypeFilePath) : m_materialTypeFilePath(materialTypeFilePath)
, m_materialPropertiesLayout(materialPropertiesLayout) , m_materialPropertiesLayout(materialPropertiesLayout)
, m_materialNameContext(materialNameContext)
{} {}
const AZStd::string& GetMaterialTypeSourceFilePath() const { return m_materialTypeFilePath; } const AZStd::string& GetMaterialTypeSourceFilePath() const { return m_materialTypeFilePath; }
@ -116,11 +142,21 @@ namespace AZ
const MaterialPropertiesLayout* GetMaterialPropertiesLayout() const; const MaterialPropertiesLayout* GetMaterialPropertiesLayout() const;
//! Find a property's index by its name. It will report error and return a Null index if it fails. //! Find a property's index by its name. It will report error and return a Null index if it fails.
MaterialPropertyIndex FindMaterialPropertyIndex(const Name& propertyName) const; //! This will also automatically apply the MaterialNameContext.
MaterialPropertyIndex FindMaterialPropertyIndex(Name propertyId) const;
//! Returns the name context for the functor.
//! It acts like a namespace for any names that the MaterialFunctorSourceData might reference. The namespace
//! is automatically applied by the other relevant functions of this RuntimeContext class.
//! Not that by default the MaterialNameContext is not saved as part of the final MaterialFunctor class
//! (most CreateFunctor() implementations should convert names to indexes anyway) but CreateFunctor() can
//! copy it to the created MaterialFunctor for use at runtime if needed.
const MaterialNameContext* GetNameContext() const { return m_materialNameContext; }
private: private:
const AZStd::string m_materialTypeFilePath; const AZStd::string m_materialTypeFilePath;
const MaterialPropertiesLayout* m_materialPropertiesLayout; const MaterialPropertiesLayout* m_materialPropertiesLayout;
const MaterialNameContext* m_materialNameContext;
}; };
//! Creates a fully initialized MaterialFunctor object that is ready to be serialized to the cache. //! Creates a fully initialized MaterialFunctor object that is ready to be serialized to the cache.

@ -23,9 +23,14 @@ namespace AZ
class MaterialPropertyId class MaterialPropertyId
{ {
public: public:
//! Returns whether the name is a valid C-style identifier
static bool IsValidName(AZStd::string_view name); static bool IsValidName(AZStd::string_view name);
static bool IsValidName(const AZ::Name& name); static bool IsValidName(const AZ::Name& name);
//! Returns whether the name is a valid C-style identifier, and reports errors if it is not.
static bool CheckIsValidName(AZStd::string_view name);
static bool CheckIsValidName(const AZ::Name& name);
//! Creates a MaterialPropertyId from a full name string like "groupA.groupB.[...].propertyName" or just "propertyName". //! Creates a MaterialPropertyId from a full name string like "groupA.groupB.[...].propertyName" or just "propertyName".
//! Also checks the name for validity. //! Also checks the name for validity.
static MaterialPropertyId Parse(AZStd::string_view fullPropertyId); static MaterialPropertyId Parse(AZStd::string_view fullPropertyId);

@ -158,6 +158,8 @@ namespace AZ
AZStd::string m_name; AZStd::string m_name;
AZStd::string m_displayName; AZStd::string m_displayName;
AZStd::string m_description; AZStd::string m_description;
AZStd::string m_shaderInputsPrefix; //!< The name of all SRG inputs under this group will get this prefix.
AZStd::string m_shaderOptionsPrefix; //!< The name of all shader options under this group will get this prefix.
PropertyList m_properties; PropertyList m_properties;
AZStd::vector<AZStd::unique_ptr<PropertyGroup>> m_propertyGroups; AZStd::vector<AZStd::unique_ptr<PropertyGroup>> m_propertyGroups;
AZStd::vector<Ptr<MaterialFunctorSourceDataHolder>> m_materialFunctorSourceData; AZStd::vector<Ptr<MaterialFunctorSourceDataHolder>> m_materialFunctorSourceData;
@ -282,7 +284,7 @@ namespace AZ
//! Call back function type used with the enumeration functions. //! Call back function type used with the enumeration functions.
//! Return false to terminate the traversal. //! Return false to terminate the traversal.
using EnumeratePropertyGroupsCallback = AZStd::function<bool( using EnumeratePropertyGroupsCallback = AZStd::function<bool(
const AZStd::string&, // The property ID context (i.e. "levelA.levelB.") const MaterialNameContext&, // The name context used to scope properties and shader connections (i.e. "levelA.levelB.")
const PropertyGroup* // the next property group in the tree const PropertyGroup* // the next property group in the tree
)>; )>;
@ -293,7 +295,7 @@ namespace AZ
//! Call back function type used with the numeration functions. //! Call back function type used with the numeration functions.
//! Return false to terminate the traversal. //! Return false to terminate the traversal.
using EnumeratePropertiesCallback = AZStd::function<bool( using EnumeratePropertiesCallback = AZStd::function<bool(
const AZStd::string&, // The property ID context (i.e. "levelA.levelB." const MaterialNameContext&, // The name context used to scope properties and shader connections (i.e. "levelA.levelB.")
const PropertyDefinition* // the property definition object const PropertyDefinition* // the property definition object
)>; )>;
@ -316,10 +318,12 @@ namespace AZ
PropertyDefinition* FindProperty(AZStd::span<AZStd::string_view> parsedPropertyId, AZStd::span<AZStd::unique_ptr<PropertyGroup>> inPropertyGroupList); PropertyDefinition* FindProperty(AZStd::span<AZStd::string_view> parsedPropertyId, AZStd::span<AZStd::unique_ptr<PropertyGroup>> inPropertyGroupList);
// Function overloads for recursion, returns false to indicate that recursion should end. // Function overloads for recursion, returns false to indicate that recursion should end.
bool EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, AZStd::string propertyIdContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const; bool EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, MaterialNameContext materialNameContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const;
bool EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyIdContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const; bool EnumerateProperties(const EnumeratePropertiesCallback& callback, MaterialNameContext materialNameContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const;
//! Recursively populates a material asset with properties from the tree of material property groups. static MaterialNameContext ExtendNameContext(MaterialNameContext nameContext, const MaterialTypeSourceData::PropertyGroup& propertyGroup);
//! Recursively populates a material type asset with properties from the tree of material property groups.
//! @param materialTypeSourceFilePath path to the material type file that is being processed, used to look up relative paths //! @param materialTypeSourceFilePath path to the material type file that is being processed, used to look up relative paths
//! @param propertyIdContext the accumulated prefix that should be applied to any property names encountered in the current @propertyGroup //! @param propertyIdContext the accumulated prefix that should be applied to any property names encountered in the current @propertyGroup
//! @param propertyGroup the current PropertyGroup that is being processed //! @param propertyGroup the current PropertyGroup that is being processed
@ -327,7 +331,7 @@ namespace AZ
bool BuildPropertyList( bool BuildPropertyList(
const AZStd::string& materialTypeSourceFilePath, const AZStd::string& materialTypeSourceFilePath,
MaterialTypeAssetCreator& materialTypeAssetCreator, MaterialTypeAssetCreator& materialTypeAssetCreator,
AZStd::vector<AZStd::string>& propertyIdContext, MaterialNameContext materialNameContext,
const MaterialTypeSourceData::PropertyGroup* propertyGroup) const; const MaterialTypeSourceData::PropertyGroup* propertyGroup) const;
//! Construct a complete list of group definitions, including implicit groups, arranged in the same order as the source data. //! Construct a complete list of group definitions, including implicit groups, arranged in the same order as the source data.

@ -10,6 +10,7 @@
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h> #include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h> #include <Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h>
#include <Atom/RPI.Reflect/Material/MaterialNameContext.h>
#include <Atom/RHI.Reflect/Limits.h> #include <Atom/RHI.Reflect/Limits.h>
namespace UnitTest namespace UnitTest
@ -60,11 +61,7 @@ namespace AZ
AZStd::unique_ptr<AZ::BehaviorContext> m_sriptBehaviorContext; AZStd::unique_ptr<AZ::BehaviorContext> m_sriptBehaviorContext;
AZStd::unique_ptr<AZ::ScriptContext> m_scriptContext; AZStd::unique_ptr<AZ::ScriptContext> m_scriptContext;
// These are prefix strings that will be applied to every name lookup in the lua functor. MaterialNameContext m_materialNameContext;
// This allows the lua script to be reused in different contexts.
AZStd::string m_propertyNamePrefix;
AZStd::string m_srgNamePrefix;
AZStd::string m_optionsNamePrefix;
enum class ScriptStatus enum class ScriptStatus
{ {
@ -84,15 +81,11 @@ namespace AZ
explicit LuaMaterialFunctorCommonContext(MaterialFunctor::RuntimeContext* runtimeContextImpl, explicit LuaMaterialFunctorCommonContext(MaterialFunctor::RuntimeContext* runtimeContextImpl,
const MaterialPropertyFlags* materialPropertyDependencies, const MaterialPropertyFlags* materialPropertyDependencies,
const AZStd::string& propertyNamePrefix, const MaterialNameContext &materialNameContext);
const AZStd::string& srgNamePrefix,
const AZStd::string& optionsNamePrefix);
explicit LuaMaterialFunctorCommonContext(MaterialFunctor::EditorContext* editorContextImpl, explicit LuaMaterialFunctorCommonContext(MaterialFunctor::EditorContext* editorContextImpl,
const MaterialPropertyFlags* materialPropertyDependencies, const MaterialPropertyFlags* materialPropertyDependencies,
const AZStd::string& propertyNamePrefix, const MaterialNameContext &materialNameContext);
const AZStd::string& srgNamePrefix,
const AZStd::string& optionsNamePrefix);
//! Returns false if PSO changes are not allowed, and may report errors or warnings //! Returns false if PSO changes are not allowed, and may report errors or warnings
bool CheckPsoChangesAllowed(); bool CheckPsoChangesAllowed();
@ -112,11 +105,7 @@ namespace AZ
AZStd::string GetMaterialPropertyDependenciesString() const; AZStd::string GetMaterialPropertyDependenciesString() const;
// These are prefix strings that will be applied to every name lookup in the lua functor. const MaterialNameContext &m_materialNameContext;
// This allows the lua script to be reused in different contexts.
const AZStd::string& m_propertyNamePrefix;
const AZStd::string& m_srgNamePrefix;
const AZStd::string& m_optionsNamePrefix;
private: private:
@ -284,9 +273,7 @@ namespace AZ
explicit LuaMaterialFunctorRuntimeContext(MaterialFunctor::RuntimeContext* runtimeContextImpl, explicit LuaMaterialFunctorRuntimeContext(MaterialFunctor::RuntimeContext* runtimeContextImpl,
const MaterialPropertyFlags* materialPropertyDependencies, const MaterialPropertyFlags* materialPropertyDependencies,
const AZStd::string& propertyNamePrefix, const MaterialNameContext &materialNameContext);
const AZStd::string& srgNamePrefix,
const AZStd::string& optionsNamePrefix);
template<typename Type> template<typename Type>
Type GetMaterialPropertyValue(const char* name) const; Type GetMaterialPropertyValue(const char* name) const;
@ -324,9 +311,7 @@ namespace AZ
explicit LuaMaterialFunctorEditorContext(MaterialFunctor::EditorContext* editorContextImpl, explicit LuaMaterialFunctorEditorContext(MaterialFunctor::EditorContext* editorContextImpl,
const MaterialPropertyFlags* materialPropertyDependencies, const MaterialPropertyFlags* materialPropertyDependencies,
const AZStd::string& propertyNamePrefix, const MaterialNameContext &materialNameContext);
const AZStd::string& srgNamePrefix,
const AZStd::string& optionsNamePrefix);
template<typename Type> template<typename Type>
Type GetMaterialPropertyValue(const char* name) const; Type GetMaterialPropertyValue(const char* name) const;

@ -49,8 +49,8 @@ namespace AZ
//! MaterialFunctor objects provide custom logic and calculations to configure shaders, render states, //! MaterialFunctor objects provide custom logic and calculations to configure shaders, render states,
//! editor metadata, and more. //! editor metadata, and more.
//! Atom will provide an implementation of this class that uses a script to define the custom logic //! Atom provides a LuaMaterialFunctor subclass that uses a script to define the custom logic
//! for a convenient workflow. Clients may also provide their own custom hard-coded implementations //! for a convenient workflow. Developers may also provide their own custom hard-coded implementations
//! as an optimization rather than taking the scripted approach. //! as an optimization rather than taking the scripted approach.
//! Any custom subclasses of MaterialFunctor will also need a corresponding MaterialFunctorSourceData subclass //! Any custom subclasses of MaterialFunctor will also need a corresponding MaterialFunctorSourceData subclass
//! to create the functor at build-time. Depending on the builder context, clients can choose to create a runtime //! to create the functor at build-time. Depending on the builder context, clients can choose to create a runtime

@ -0,0 +1,59 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/std/string/string.h>
namespace AZ
{
class ReflectContext;
class Name;
namespace RPI
{
//! This acts like a namespace description for various types of identifiers that appear in .materialtype files.
//! When reusable property groups are nested inside other property groups, they usually need alternate naming
//! to connect to the appropriate shader inputs. For example, a baseColor property group inside a "layer1" group
//! needs to connect to "m_layer1_baseColor_texture" and the same property definition is repeated inside a "layer2"
//! group where it connects to "m_layer2_baseColor_texture". This data structure provides the name context, like
//! "m_layer1_" or "m_layer2_".
class MaterialNameContext
{
public:
AZ_TYPE_INFO(MaterialNameContext, "{AAC9BB28-F463-455D-8467-F877E50E1FA7}")
static void Reflect(ReflectContext* context);
MaterialNameContext() = default;
//! Extends the name context to a deeper property group.
void ExtendPropertyIdContext(AZStd::string_view nameContext, bool insertDelimiter=true);
void ExtendSrgInputContext(AZStd::string_view nameContext);
void ExtendShaderOptionContext(AZStd::string_view nameContext);
//! Applies the name context to a given leaf name.
bool ContextualizeProperty(Name& propertyName) const;
bool ContextualizeSrgInput(Name& srgInputName) const;
bool ContextualizeShaderOption(Name& shaderOptionName) const;
//! Returns true if there is some non-default name context.
bool HasContextForProperties() const { return !m_propertyIdContext.empty(); }
bool HasContextForSrgInputs() const { return !m_srgInputNameContext.empty(); }
bool HasContextForShaderOptions() const { return !m_shaderOptionNameContext.empty(); }
//! Returns true if the name context is empty.
bool IsDefault() const;
private:
AZStd::string m_propertyIdContext;
AZStd::string m_srgInputNameContext;
AZStd::string m_shaderOptionNameContext;
};
} // namespace RPI
} // namespace AZ

@ -51,7 +51,7 @@ namespace AZ
{ {
AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor; AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor;
materialBuilderDescriptor.m_name = JobKey; materialBuilderDescriptor.m_name = JobKey;
materialBuilderDescriptor.m_version = 117; // new material type file format materialBuilderDescriptor.m_version = 122; // nested property layers
materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.material", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.material", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.materialtype", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.materialtype", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
materialBuilderDescriptor.m_busId = azrtti_typeid<MaterialBuilder>(); materialBuilderDescriptor.m_busId = azrtti_typeid<MaterialBuilder>();
@ -269,7 +269,7 @@ namespace AZ
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
} }
AZ::Data::Asset<MaterialTypeAsset> CreateMaterialTypeAsset(AZStd::string_view materialTypeSourceFilePath, const rapidjson::Value& json) AZ::Data::Asset<MaterialTypeAsset> CreateMaterialTypeAsset(AZStd::string_view materialTypeSourceFilePath, rapidjson::Document& json)
{ {
auto materialType = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourceFilePath, &json); auto materialType = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourceFilePath, &json);

@ -115,16 +115,26 @@ namespace AZ
RPI::LuaMaterialFunctorSourceData::FunctorResult LuaMaterialFunctorSourceData::CreateFunctor( RPI::LuaMaterialFunctorSourceData::FunctorResult LuaMaterialFunctorSourceData::CreateFunctor(
const AZStd::string& materialTypeSourceFilePath, const AZStd::string& materialTypeSourceFilePath,
const MaterialPropertiesLayout* propertiesLayout const MaterialPropertiesLayout* propertiesLayout,
const MaterialNameContext* materialNameContext
) const ) const
{ {
using namespace RPI; using namespace RPI;
RPI::Ptr<LuaMaterialFunctor> functor = aznew LuaMaterialFunctor; RPI::Ptr<LuaMaterialFunctor> functor = aznew LuaMaterialFunctor;
functor->m_propertyNamePrefix = m_propertyNamePrefix; if (materialNameContext->IsDefault())
functor->m_srgNamePrefix = m_srgNamePrefix; {
functor->m_optionsNamePrefix = m_optionsNamePrefix; // 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()) if (!m_luaScript.empty() && !m_luaSourceFile.empty())
{ {
@ -205,14 +215,16 @@ namespace AZ
{ {
return CreateFunctor( return CreateFunctor(
context.GetMaterialTypeSourceFilePath(), context.GetMaterialTypeSourceFilePath(),
context.GetMaterialPropertiesLayout()); context.GetMaterialPropertiesLayout(),
context.GetNameContext());
} }
RPI::LuaMaterialFunctorSourceData::FunctorResult LuaMaterialFunctorSourceData::CreateFunctor(const EditorContext& context) const RPI::LuaMaterialFunctorSourceData::FunctorResult LuaMaterialFunctorSourceData::CreateFunctor(const EditorContext& context) const
{ {
return CreateFunctor( return CreateFunctor(
context.GetMaterialTypeSourceFilePath(), context.GetMaterialTypeSourceFilePath(),
context.GetMaterialPropertiesLayout()); context.GetMaterialPropertiesLayout(),
context.GetNameContext());
} }
} }
} }

@ -10,6 +10,7 @@
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h> #include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/ShaderCollection.h> #include <Atom/RPI.Reflect/Material/ShaderCollection.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h> #include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Reflect/Material/MaterialNameContext.h>
#include <AzCore/Serialization/SerializeContext.h> #include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Json/RegistrationContext.h> #include <AzCore/Serialization/Json/RegistrationContext.h>
@ -66,17 +67,20 @@ namespace AZ
return m_shaderResourceGroupLayout; return m_shaderResourceGroupLayout;
} }
MaterialPropertyIndex MaterialFunctorSourceData::RuntimeContext::FindMaterialPropertyIndex(const Name& propertyName) const MaterialPropertyIndex MaterialFunctorSourceData::RuntimeContext::FindMaterialPropertyIndex(Name propertyId) const
{ {
MaterialPropertyIndex propertyIndex = m_materialPropertiesLayout->FindPropertyIndex(propertyName); m_materialNameContext->ContextualizeProperty(propertyId);
MaterialPropertyIndex propertyIndex = m_materialPropertiesLayout->FindPropertyIndex(propertyId);
AZ_Error("MaterialFunctorSourceData", propertyIndex.IsValid(), "Could not find property '%s'.", propertyName.GetCStr()); AZ_Error("MaterialFunctorSourceData", propertyIndex.IsValid(), "Could not find property '%s'.", propertyId.GetCStr());
return propertyIndex; return propertyIndex;
} }
ShaderOptionIndex MaterialFunctorSourceData::RuntimeContext::FindShaderOptionIndex(AZStd::size_t shaderIndex, const Name& optionName, bool reportErrors) const ShaderOptionIndex MaterialFunctorSourceData::RuntimeContext::FindShaderOptionIndex(AZStd::size_t shaderIndex, Name optionName, bool reportErrors) const
{ {
m_materialNameContext->ContextualizeShaderOption(optionName);
const ShaderOptionGroupLayout* shaderOptionGroupLayout = GetShaderOptionGroupLayout(shaderIndex); const ShaderOptionGroupLayout* shaderOptionGroupLayout = GetShaderOptionGroupLayout(shaderIndex);
if (shaderOptionGroupLayout) if (shaderOptionGroupLayout)
@ -92,8 +96,10 @@ namespace AZ
return ShaderOptionIndex(); return ShaderOptionIndex();
} }
AZ::RPI::ShaderOptionIndex MaterialFunctorSourceData::RuntimeContext::FindShaderOptionIndex(const AZ::Name& shaderTag, const Name& optionName, bool reportErrors /*= true*/) const AZ::RPI::ShaderOptionIndex MaterialFunctorSourceData::RuntimeContext::FindShaderOptionIndex(const AZ::Name& shaderTag, Name optionName, bool reportErrors /*= true*/) const
{ {
m_materialNameContext->ContextualizeShaderOption(optionName);
const ShaderOptionGroupLayout* shaderOptionGroupLayout = GetShaderOptionGroupLayout(shaderTag); const ShaderOptionGroupLayout* shaderOptionGroupLayout = GetShaderOptionGroupLayout(shaderTag);
if (shaderOptionGroupLayout) if (shaderOptionGroupLayout)
@ -123,18 +129,32 @@ namespace AZ
return valid; return valid;
} }
RHI::ShaderInputConstantIndex MaterialFunctorSourceData::RuntimeContext::FindShaderInputConstantIndex(Name inputName) const
{
m_materialNameContext->ContextualizeSrgInput(inputName);
return m_shaderResourceGroupLayout->FindShaderInputConstantIndex(inputName);
}
RHI::ShaderInputImageIndex MaterialFunctorSourceData::RuntimeContext::FindShaderInputImageIndex(Name inputName) const
{
m_materialNameContext->ContextualizeSrgInput(inputName);
return m_shaderResourceGroupLayout->FindShaderInputImageIndex(inputName);
}
const MaterialPropertiesLayout* MaterialFunctorSourceData::EditorContext::GetMaterialPropertiesLayout() const const MaterialPropertiesLayout* MaterialFunctorSourceData::EditorContext::GetMaterialPropertiesLayout() const
{ {
return m_materialPropertiesLayout; return m_materialPropertiesLayout;
} }
MaterialPropertyIndex MaterialFunctorSourceData::EditorContext::FindMaterialPropertyIndex(const Name& propertyName) const MaterialPropertyIndex MaterialFunctorSourceData::EditorContext::FindMaterialPropertyIndex(Name propertyId) const
{ {
MaterialPropertyIndex propertyIndex = m_materialPropertiesLayout->FindPropertyIndex(propertyName); m_materialNameContext->ContextualizeProperty(propertyId);
MaterialPropertyIndex propertyIndex = m_materialPropertiesLayout->FindPropertyIndex(propertyId);
AZ_Error("MaterialFunctorSourceData", propertyIndex.IsValid(), "Could not find property '%s'", propertyName.GetCStr()); AZ_Error("MaterialFunctorSourceData", propertyIndex.IsValid(), "Could not find property '%s'", propertyId.GetCStr());
return propertyIndex; return propertyIndex;
} }
} // namespace RPI } // namespace RPI
} // namespace AZ } // namespace AZ

@ -25,6 +25,24 @@ namespace AZ
return IsValidName(name.GetStringView()); return IsValidName(name.GetStringView());
} }
bool MaterialPropertyId::CheckIsValidName(AZStd::string_view name)
{
if (IsValidName(name))
{
return true;
}
else
{
AZ_Error("MaterialPropertyId", false, "'%.*s' is not a valid identifier", AZ_STRING_ARG(name));
return false;
}
}
bool MaterialPropertyId::CheckIsValidName(const AZ::Name& name)
{
return CheckIsValidName(name.GetStringView());
}
bool MaterialPropertyId::IsValid() const bool MaterialPropertyId::IsValid() const
{ {
return !m_fullName.IsEmpty(); return !m_fullName.IsEmpty();
@ -94,14 +112,14 @@ namespace AZ
{ {
if (!IsValidName(name)) if (!IsValidName(name))
{ {
AZ_Error("MaterialPropertyId", false, "'%s' is not a valid identifier.", name.c_str()); AZ_Error("MaterialPropertyId", false, "Group name '%s' is not a valid identifier.", name.c_str());
return; return;
} }
} }
if (!IsValidName(propertyName)) if (!IsValidName(propertyName))
{ {
AZ_Error("MaterialPropertyId", false, "'%.*s' is not a valid identifier.", AZ_STRING_ARG(propertyName)); AZ_Error("MaterialPropertyId", false, "Property name '%.*s' is not a valid identifier.", AZ_STRING_ARG(propertyName));
return; return;
} }

@ -17,6 +17,7 @@
#include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h> #include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h>
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h> #include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/MaterialVersionUpdate.h> #include <Atom/RPI.Reflect/Material/MaterialVersionUpdate.h>
#include <Atom/RPI.Reflect/Material/MaterialNameContext.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h> #include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <AzCore/Serialization/SerializeContext.h> #include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Json/RegistrationContext.h> #include <AzCore/Serialization/Json/RegistrationContext.h>
@ -89,10 +90,12 @@ namespace AZ
; ;
serializeContext->Class<PropertyGroup>() serializeContext->Class<PropertyGroup>()
->Version(1) ->Version(2)
->Field("name", &PropertyGroup::m_name) ->Field("name", &PropertyGroup::m_name)
->Field("displayName", &PropertyGroup::m_displayName) ->Field("displayName", &PropertyGroup::m_displayName)
->Field("description", &PropertyGroup::m_description) ->Field("description", &PropertyGroup::m_description)
->Field("shaderInputsPrefix", &PropertyGroup::m_shaderInputsPrefix)
->Field("shaderOptionsPrefix", &PropertyGroup::m_shaderOptionsPrefix)
->Field("properties", &PropertyGroup::m_properties) ->Field("properties", &PropertyGroup::m_properties)
->Field("propertyGroups", &PropertyGroup::m_propertyGroups) ->Field("propertyGroups", &PropertyGroup::m_propertyGroups)
->Field("functors", &PropertyGroup::m_materialFunctorSourceData) ->Field("functors", &PropertyGroup::m_materialFunctorSourceData)
@ -134,6 +137,11 @@ namespace AZ
/*static*/ MaterialTypeSourceData::PropertyGroup* MaterialTypeSourceData::PropertyGroup::AddPropertyGroup(AZStd::string_view name, AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& toPropertyGroupList) /*static*/ MaterialTypeSourceData::PropertyGroup* MaterialTypeSourceData::PropertyGroup::AddPropertyGroup(AZStd::string_view name, AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& toPropertyGroupList)
{ {
if (!MaterialPropertyId::CheckIsValidName(name))
{
return nullptr;
}
auto iter = AZStd::find_if(toPropertyGroupList.begin(), toPropertyGroupList.end(), [name](const AZStd::unique_ptr<PropertyGroup>& existingPropertyGroup) auto iter = AZStd::find_if(toPropertyGroupList.begin(), toPropertyGroupList.end(), [name](const AZStd::unique_ptr<PropertyGroup>& existingPropertyGroup)
{ {
return existingPropertyGroup->m_name == name; return existingPropertyGroup->m_name == name;
@ -145,12 +153,6 @@ namespace AZ
return nullptr; return nullptr;
} }
if (!MaterialPropertyId::IsValidName(name))
{
AZ_Error("Material source data", false, "'%.*s' is not a valid identifier", AZ_STRING_ARG(name));
return nullptr;
}
toPropertyGroupList.push_back(AZStd::make_unique<PropertyGroup>()); toPropertyGroupList.push_back(AZStd::make_unique<PropertyGroup>());
toPropertyGroupList.back()->m_name = name; toPropertyGroupList.back()->m_name = name;
return toPropertyGroupList.back().get(); return toPropertyGroupList.back().get();
@ -158,6 +160,11 @@ namespace AZ
MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::PropertyGroup::AddProperty(AZStd::string_view name) MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::PropertyGroup::AddProperty(AZStd::string_view name)
{ {
if (!MaterialPropertyId::CheckIsValidName(name))
{
return nullptr;
}
auto propertyIter = AZStd::find_if(m_properties.begin(), m_properties.end(), [name](const AZStd::unique_ptr<PropertyDefinition>& existingProperty) auto propertyIter = AZStd::find_if(m_properties.begin(), m_properties.end(), [name](const AZStd::unique_ptr<PropertyDefinition>& existingProperty)
{ {
return existingProperty->GetName() == name; return existingProperty->GetName() == name;
@ -180,12 +187,6 @@ namespace AZ
return nullptr; return nullptr;
} }
if (!MaterialPropertyId::IsValidName(name))
{
AZ_Error("Material source data", false, "'%.*s' is not a valid identifier", AZ_STRING_ARG(name));
return nullptr;
}
m_properties.emplace_back(AZStd::make_unique<PropertyDefinition>(name)); m_properties.emplace_back(AZStd::make_unique<PropertyDefinition>(name));
return m_properties.back().get(); return m_properties.back().get();
} }
@ -265,10 +266,10 @@ namespace AZ
if (!subPath.empty()) if (!subPath.empty())
{ {
const MaterialTypeSourceData::PropertyGroup* propertySubset = FindPropertyGroup(subPath, propertyGroup->m_propertyGroups); const MaterialTypeSourceData::PropertyGroup* propertySubgroup = FindPropertyGroup(subPath, propertyGroup->m_propertyGroups);
if (propertySubset) if (propertySubgroup)
{ {
return propertySubset; return propertySubgroup;
} }
} }
} }
@ -376,18 +377,18 @@ namespace AZ
return parts; return parts;
} }
bool MaterialTypeSourceData::EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, AZStd::string propertyIdContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const bool MaterialTypeSourceData::EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, MaterialNameContext materialNameContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const
{ {
for (auto& propertyGroup : inPropertyGroupList) for (auto& propertyGroup : inPropertyGroupList)
{ {
if (!callback(propertyIdContext, propertyGroup.get())) if (!callback(materialNameContext, propertyGroup.get()))
{ {
return false; // Stop processing return false; // Stop processing
} }
const AZStd::string propertyIdContext2 = propertyIdContext + propertyGroup->m_name + "."; MaterialNameContext materialNameContext2 = ExtendNameContext(materialNameContext, *propertyGroup);
if (!EnumeratePropertyGroups(callback, propertyIdContext2, propertyGroup->m_propertyGroups)) if (!EnumeratePropertyGroups(callback, materialNameContext2, propertyGroup->m_propertyGroups))
{ {
return false; // Stop processing return false; // Stop processing
} }
@ -406,22 +407,21 @@ namespace AZ
return EnumeratePropertyGroups(callback, {}, m_propertyLayout.m_propertyGroups); return EnumeratePropertyGroups(callback, {}, m_propertyLayout.m_propertyGroups);
} }
bool MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyIdContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const bool MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback, MaterialNameContext materialNameContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const
{ {
for (auto& propertyGroup : inPropertyGroupList) for (auto& propertyGroup : inPropertyGroupList)
{ {
const AZStd::string propertyIdContext2 = propertyIdContext + propertyGroup->m_name + "."; MaterialNameContext materialNameContext2 = ExtendNameContext(materialNameContext, *propertyGroup);
for (auto& property : propertyGroup->m_properties) for (auto& property : propertyGroup->m_properties)
{ {
if (!callback(propertyIdContext2, property.get())) if (!callback(materialNameContext2, property.get()))
{ {
return false; // Stop processing return false; // Stop processing
} }
} }
if (!EnumerateProperties(callback, propertyIdContext2, propertyGroup->m_propertyGroups)) if (!EnumerateProperties(callback, materialNameContext2, propertyGroup->m_propertyGroups))
{ {
return false; // Stop processing return false; // Stop processing
} }
@ -483,7 +483,7 @@ namespace AZ
enumValues.push_back(uvNamePair.second); enumValues.push_back(uvNamePair.second);
} }
EnumerateProperties([&enumValues](const AZStd::string&, const MaterialTypeSourceData::PropertyDefinition* property) EnumerateProperties([&enumValues](const MaterialNameContext&, const MaterialTypeSourceData::PropertyDefinition* property)
{ {
if (property->m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && property->m_enumIsUv) if (property->m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && property->m_enumIsUv)
{ {
@ -529,24 +529,40 @@ namespace AZ
return groupDefinitions; return groupDefinitions;
} }
MaterialNameContext MaterialTypeSourceData::ExtendNameContext(MaterialNameContext nameContext, const MaterialTypeSourceData::PropertyGroup& propertyGroup)
{
MaterialNameContext materialNameContext2 = nameContext;
materialNameContext2.ExtendPropertyIdContext(propertyGroup.m_name);
materialNameContext2.ExtendShaderOptionContext(propertyGroup.m_shaderOptionsPrefix);
materialNameContext2.ExtendSrgInputContext(propertyGroup.m_shaderInputsPrefix);
return materialNameContext2;
}
bool MaterialTypeSourceData::BuildPropertyList( bool MaterialTypeSourceData::BuildPropertyList(
const AZStd::string& materialTypeSourceFilePath, const AZStd::string& materialTypeSourceFilePath,
MaterialTypeAssetCreator& materialTypeAssetCreator, MaterialTypeAssetCreator& materialTypeAssetCreator,
AZStd::vector<AZStd::string>& propertyIdContext, MaterialNameContext materialNameContext,
const MaterialTypeSourceData::PropertyGroup* propertyGroup) const const MaterialTypeSourceData::PropertyGroup* propertyGroup) const
{ {
if (!MaterialPropertyId::CheckIsValidName(propertyGroup->m_name))
{
return false;
}
materialNameContext = ExtendNameContext(materialNameContext, *propertyGroup);
for (const AZStd::unique_ptr<PropertyDefinition>& property : propertyGroup->m_properties) for (const AZStd::unique_ptr<PropertyDefinition>& property : propertyGroup->m_properties)
{ {
// Register the property... // Register the property...
MaterialPropertyId propertyId{propertyIdContext, property->GetName()}; if (!MaterialPropertyId::CheckIsValidName(property->GetName()))
if (!propertyId.IsValid())
{ {
// MaterialPropertyId reports an error message
return false; return false;
} }
Name propertyId{property->GetName()};
materialNameContext.ContextualizeProperty(propertyId);
auto propertyGroupIter = AZStd::find_if(propertyGroup->GetPropertyGroups().begin(), propertyGroup->GetPropertyGroups().end(), auto propertyGroupIter = AZStd::find_if(propertyGroup->GetPropertyGroups().begin(), propertyGroup->GetPropertyGroups().end(),
[&property](const AZStd::unique_ptr<PropertyGroup>& existingPropertyGroup) [&property](const AZStd::unique_ptr<PropertyGroup>& existingPropertyGroup)
{ {
@ -572,18 +588,23 @@ namespace AZ
{ {
case MaterialPropertyOutputType::ShaderInput: case MaterialPropertyOutputType::ShaderInput:
{ {
materialTypeAssetCreator.ConnectMaterialPropertyToShaderInput(Name{output.m_fieldName}); Name fieldName{output.m_fieldName};
materialNameContext.ContextualizeSrgInput(fieldName);
materialTypeAssetCreator.ConnectMaterialPropertyToShaderInput(fieldName);
break; break;
} }
case MaterialPropertyOutputType::ShaderOption: case MaterialPropertyOutputType::ShaderOption:
{ {
Name fieldName{output.m_fieldName};
materialNameContext.ContextualizeShaderOption(fieldName);
if (output.m_shaderIndex >= 0) if (output.m_shaderIndex >= 0)
{ {
materialTypeAssetCreator.ConnectMaterialPropertyToShaderOption(Name{output.m_fieldName}, output.m_shaderIndex); materialTypeAssetCreator.ConnectMaterialPropertyToShaderOption(fieldName, output.m_shaderIndex);
} }
else else
{ {
materialTypeAssetCreator.ConnectMaterialPropertyToShaderOptions(Name{output.m_fieldName}); materialTypeAssetCreator.ConnectMaterialPropertyToShaderOptions(fieldName);
} }
break; break;
} }
@ -650,17 +671,13 @@ namespace AZ
} }
} }
for (const AZStd::unique_ptr<PropertyGroup>& propertySubset : propertyGroup->m_propertyGroups) for (const AZStd::unique_ptr<PropertyGroup>& propertySubgroup : propertyGroup->m_propertyGroups)
{ {
propertyIdContext.push_back(propertySubset->m_name);
bool success = BuildPropertyList( bool success = BuildPropertyList(
materialTypeSourceFilePath, materialTypeSourceFilePath,
materialTypeAssetCreator, materialTypeAssetCreator,
propertyIdContext, materialNameContext,
propertySubset.get()); propertySubgroup.get());
propertyIdContext.pop_back();
if (!success) if (!success)
{ {
@ -677,7 +694,8 @@ namespace AZ
materialTypeSourceFilePath, materialTypeSourceFilePath,
materialTypeAssetCreator.GetMaterialPropertiesLayout(), materialTypeAssetCreator.GetMaterialPropertiesLayout(),
materialTypeAssetCreator.GetMaterialShaderResourceGroupLayout(), materialTypeAssetCreator.GetMaterialShaderResourceGroupLayout(),
materialTypeAssetCreator.GetShaderCollection() materialTypeAssetCreator.GetShaderCollection(),
&materialNameContext
) )
); );
@ -688,9 +706,10 @@ namespace AZ
{ {
materialTypeAssetCreator.AddMaterialFunctor(functor); materialTypeAssetCreator.AddMaterialFunctor(functor);
for (const AZ::Name& optionName : functorData->GetActualSourceData()->GetShaderOptionDependencies()) for (AZ::Name optionName : functorData->GetActualSourceData()->GetShaderOptionDependencies())
{ {
materialTypeAssetCreator.ClaimShaderOptionOwnership(Name{optionName.GetCStr()}); materialNameContext.ContextualizeShaderOption(optionName);
materialTypeAssetCreator.ClaimShaderOptionOwnership(optionName);
} }
} }
} }
@ -797,9 +816,7 @@ namespace AZ
for (const AZStd::unique_ptr<PropertyGroup>& propertyGroup : m_propertyLayout.m_propertyGroups) for (const AZStd::unique_ptr<PropertyGroup>& propertyGroup : m_propertyLayout.m_propertyGroups)
{ {
AZStd::vector<AZStd::string> propertyIdContext; bool success = BuildPropertyList(materialTypeSourceFilePath, materialTypeAssetCreator, MaterialNameContext{}, propertyGroup.get());
propertyIdContext.push_back(propertyGroup->m_name);
bool success = BuildPropertyList(materialTypeSourceFilePath, materialTypeAssetCreator, propertyIdContext, propertyGroup.get());
if (!success) if (!success)
{ {
@ -807,6 +824,8 @@ namespace AZ
} }
} }
MaterialNameContext nameContext;
// We cannot create the MaterialFunctor until after all the properties are added because // We cannot create the MaterialFunctor until after all the properties are added because
// CreateFunctor() may need to look up properties in the MaterialPropertiesLayout // CreateFunctor() may need to look up properties in the MaterialPropertiesLayout
for (auto& functorData : m_materialFunctorSourceData) for (auto& functorData : m_materialFunctorSourceData)
@ -816,7 +835,8 @@ namespace AZ
materialTypeSourceFilePath, materialTypeSourceFilePath,
materialTypeAssetCreator.GetMaterialPropertiesLayout(), materialTypeAssetCreator.GetMaterialPropertiesLayout(),
materialTypeAssetCreator.GetMaterialShaderResourceGroupLayout(), materialTypeAssetCreator.GetMaterialShaderResourceGroupLayout(),
materialTypeAssetCreator.GetShaderCollection() materialTypeAssetCreator.GetShaderCollection(),
&nameContext
) )
); );

@ -27,6 +27,7 @@ namespace AZ
MaterialAsset::Reflect(context); MaterialAsset::Reflect(context);
MaterialPropertiesLayout::Reflect(context); MaterialPropertiesLayout::Reflect(context);
MaterialFunctor::Reflect(context); MaterialFunctor::Reflect(context);
MaterialNameContext::Reflect(context);
LuaMaterialFunctor::Reflect(context); LuaMaterialFunctor::Reflect(context);
ReflectMaterialDynamicMetadata(context); ReflectMaterialDynamicMetadata(context);
} }

@ -29,9 +29,7 @@ namespace AZ
serializeContext->Class<LuaMaterialFunctor, RPI::MaterialFunctor>() serializeContext->Class<LuaMaterialFunctor, RPI::MaterialFunctor>()
->Version(1) ->Version(1)
->Field("scriptAsset", &LuaMaterialFunctor::m_scriptAsset) ->Field("scriptAsset", &LuaMaterialFunctor::m_scriptAsset)
->Field("propertyNamePrefix", &LuaMaterialFunctor::m_propertyNamePrefix) ->Field("materialNameContext", &LuaMaterialFunctor::m_materialNameContext)
->Field("srgNamePrefix", &LuaMaterialFunctor::m_srgNamePrefix)
->Field("optionsNamePrefix", &LuaMaterialFunctor::m_optionsNamePrefix)
; ;
} }
} }
@ -127,7 +125,7 @@ namespace AZ
if (m_scriptStatus == ScriptStatus::Ready) if (m_scriptStatus == ScriptStatus::Ready)
{ {
LuaMaterialFunctorRuntimeContext luaContext{&context, &GetMaterialPropertyDependencies(), m_propertyNamePrefix, m_srgNamePrefix, m_optionsNamePrefix}; LuaMaterialFunctorRuntimeContext luaContext{&context, &GetMaterialPropertyDependencies(), m_materialNameContext};
AZ::ScriptDataContext call; AZ::ScriptDataContext call;
if (m_scriptContext->Call("Process", call)) if (m_scriptContext->Call("Process", call))
{ {
@ -145,7 +143,7 @@ namespace AZ
if (m_scriptStatus == ScriptStatus::Ready) if (m_scriptStatus == ScriptStatus::Ready)
{ {
LuaMaterialFunctorEditorContext luaContext{&context, &GetMaterialPropertyDependencies(), m_propertyNamePrefix, m_srgNamePrefix, m_optionsNamePrefix}; LuaMaterialFunctorEditorContext luaContext{&context, &GetMaterialPropertyDependencies(), m_materialNameContext};
AZ::ScriptDataContext call; AZ::ScriptDataContext call;
if (m_scriptContext->Call("ProcessEditor", call)) if (m_scriptContext->Call("ProcessEditor", call))
{ {
@ -157,27 +155,19 @@ namespace AZ
LuaMaterialFunctorCommonContext::LuaMaterialFunctorCommonContext(MaterialFunctor::RuntimeContext* runtimeContextImpl, LuaMaterialFunctorCommonContext::LuaMaterialFunctorCommonContext(MaterialFunctor::RuntimeContext* runtimeContextImpl,
const MaterialPropertyFlags* materialPropertyDependencies, const MaterialPropertyFlags* materialPropertyDependencies,
const AZStd::string& propertyNamePrefix, const MaterialNameContext& materialNameContext)
const AZStd::string& srgNamePrefix,
const AZStd::string& optionsNamePrefix)
: m_runtimeContextImpl(runtimeContextImpl) : m_runtimeContextImpl(runtimeContextImpl)
, m_materialPropertyDependencies(materialPropertyDependencies) , m_materialPropertyDependencies(materialPropertyDependencies)
, m_propertyNamePrefix(propertyNamePrefix) , m_materialNameContext(materialNameContext)
, m_srgNamePrefix(srgNamePrefix)
, m_optionsNamePrefix(optionsNamePrefix)
{ {
} }
LuaMaterialFunctorCommonContext::LuaMaterialFunctorCommonContext(MaterialFunctor::EditorContext* editorContextImpl, LuaMaterialFunctorCommonContext::LuaMaterialFunctorCommonContext(MaterialFunctor::EditorContext* editorContextImpl,
const MaterialPropertyFlags* materialPropertyDependencies, const MaterialPropertyFlags* materialPropertyDependencies,
const AZStd::string& propertyNamePrefix, const MaterialNameContext& materialNameContext)
const AZStd::string& srgNamePrefix,
const AZStd::string& optionsNamePrefix)
: m_editorContextImpl(editorContextImpl) : m_editorContextImpl(editorContextImpl)
, m_materialPropertyDependencies(materialPropertyDependencies) , m_materialPropertyDependencies(materialPropertyDependencies)
, m_propertyNamePrefix(propertyNamePrefix) , m_materialNameContext(materialNameContext)
, m_srgNamePrefix(srgNamePrefix)
, m_optionsNamePrefix(optionsNamePrefix)
{ {
} }
@ -256,7 +246,8 @@ namespace AZ
{ {
MaterialPropertyIndex propertyIndex; MaterialPropertyIndex propertyIndex;
Name propertyFullName{m_propertyNamePrefix + name}; Name propertyFullName{name};
m_materialNameContext.ContextualizeProperty(propertyFullName);
propertyIndex = GetMaterialPropertiesLayout()->FindPropertyIndex(propertyFullName); propertyIndex = GetMaterialPropertiesLayout()->FindPropertyIndex(propertyFullName);
@ -361,10 +352,8 @@ namespace AZ
LuaMaterialFunctorRuntimeContext::LuaMaterialFunctorRuntimeContext(MaterialFunctor::RuntimeContext* runtimeContextImpl, LuaMaterialFunctorRuntimeContext::LuaMaterialFunctorRuntimeContext(MaterialFunctor::RuntimeContext* runtimeContextImpl,
const MaterialPropertyFlags* materialPropertyDependencies, const MaterialPropertyFlags* materialPropertyDependencies,
const AZStd::string& propertyNamePrefix, const MaterialNameContext& materialNameContext)
const AZStd::string& srgNamePrefix, : LuaMaterialFunctorCommonContext(runtimeContextImpl, materialPropertyDependencies, materialNameContext)
const AZStd::string& optionsNamePrefix)
: LuaMaterialFunctorCommonContext(runtimeContextImpl, materialPropertyDependencies, propertyNamePrefix, srgNamePrefix, optionsNamePrefix)
, m_runtimeContextImpl(runtimeContextImpl) , m_runtimeContextImpl(runtimeContextImpl)
{ {
} }
@ -379,7 +368,8 @@ namespace AZ
{ {
bool didSetOne = false; bool didSetOne = false;
Name fullOptionName{m_optionsNamePrefix + name}; Name fullOptionName{name};
m_materialNameContext.ContextualizeShaderOption(fullOptionName);
for (AZStd::size_t i = 0; i < m_runtimeContextImpl->m_shaderCollection->size(); ++i) for (AZStd::size_t i = 0; i < m_runtimeContextImpl->m_shaderCollection->size(); ++i)
{ {
@ -429,7 +419,8 @@ namespace AZ
RHI::ShaderInputConstantIndex LuaMaterialFunctorRuntimeContext::GetShaderInputConstantIndex(const char* name, const char* functionName) const RHI::ShaderInputConstantIndex LuaMaterialFunctorRuntimeContext::GetShaderInputConstantIndex(const char* name, const char* functionName) const
{ {
Name fullInputName{m_srgNamePrefix + name}; Name fullInputName{name};
m_materialNameContext.ContextualizeSrgInput(fullInputName);
RHI::ShaderInputConstantIndex index = m_runtimeContextImpl->m_shaderResourceGroup->FindShaderInputConstantIndex(fullInputName); RHI::ShaderInputConstantIndex index = m_runtimeContextImpl->m_shaderResourceGroup->FindShaderInputConstantIndex(fullInputName);
@ -524,10 +515,8 @@ namespace AZ
LuaMaterialFunctorEditorContext::LuaMaterialFunctorEditorContext(MaterialFunctor::EditorContext* editorContextImpl, LuaMaterialFunctorEditorContext::LuaMaterialFunctorEditorContext(MaterialFunctor::EditorContext* editorContextImpl,
const MaterialPropertyFlags* materialPropertyDependencies, const MaterialPropertyFlags* materialPropertyDependencies,
const AZStd::string& propertyNamePrefix, const MaterialNameContext& materialNameContext)
const AZStd::string& srgNamePrefix, : LuaMaterialFunctorCommonContext(editorContextImpl, materialPropertyDependencies, materialNameContext)
const AZStd::string& optionsNamePrefix)
: LuaMaterialFunctorCommonContext(editorContextImpl, materialPropertyDependencies, propertyNamePrefix, srgNamePrefix, optionsNamePrefix)
, m_editorContextImpl(editorContextImpl) , m_editorContextImpl(editorContextImpl)
{ {
} }
@ -598,7 +587,9 @@ namespace AZ
{ {
if (m_editorContextImpl) if (m_editorContextImpl)
{ {
return m_editorContextImpl->SetMaterialPropertyGroupVisibility(Name{m_propertyNamePrefix + name}, visibility); Name fullName{name};
m_materialNameContext.ContextualizeProperty(fullName);
return m_editorContextImpl->SetMaterialPropertyGroupVisibility(fullName, visibility);
} }
return false; return false;
} }
@ -607,7 +598,9 @@ namespace AZ
{ {
if (m_editorContextImpl) if (m_editorContextImpl)
{ {
return m_editorContextImpl->SetMaterialPropertyVisibility(Name{m_propertyNamePrefix + name}, visibility); Name fullName{name};
m_materialNameContext.ContextualizeProperty(fullName);
return m_editorContextImpl->SetMaterialPropertyVisibility(fullName, visibility);
} }
return false; return false;
} }
@ -616,7 +609,9 @@ namespace AZ
{ {
if (m_editorContextImpl) if (m_editorContextImpl)
{ {
return m_editorContextImpl->SetMaterialPropertyDescription(Name{m_propertyNamePrefix + name}, description); Name fullName{name};
m_materialNameContext.ContextualizeProperty(fullName);
return m_editorContextImpl->SetMaterialPropertyDescription(fullName, description);
} }
return false; return false;
} }

@ -0,0 +1,89 @@
/*
* 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.Reflect/Material/MaterialNameContext.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Name/Name.h>
namespace AZ
{
namespace RPI
{
void MaterialNameContext::Reflect(ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<MaterialNameContext>()
->Version(1)
->Field("propertyIdContext", &MaterialNameContext::m_propertyIdContext)
->Field("srgInputNameContext", &MaterialNameContext::m_srgInputNameContext)
->Field("shaderOptionNameContext", &MaterialNameContext::m_shaderOptionNameContext)
;
}
}
bool MaterialNameContext::IsDefault() const
{
return m_propertyIdContext.empty() && m_srgInputNameContext.empty() && m_shaderOptionNameContext.empty();
}
void MaterialNameContext::ExtendPropertyIdContext(AZStd::string_view nameContext, bool insertDelimiter)
{
m_propertyIdContext += nameContext;
if (insertDelimiter && !nameContext.empty() && !nameContext.ends_with("."))
{
m_propertyIdContext += ".";
}
}
void MaterialNameContext::ExtendSrgInputContext(AZStd::string_view nameContext)
{
m_srgInputNameContext += nameContext;
}
void MaterialNameContext::ExtendShaderOptionContext(AZStd::string_view nameContext)
{
m_shaderOptionNameContext += nameContext;
}
bool MaterialNameContext::ContextualizeProperty(Name& propertyName) const
{
if (m_propertyIdContext.empty())
{
return false;
}
propertyName = m_propertyIdContext + propertyName.GetCStr();
return true;
}
bool MaterialNameContext::ContextualizeSrgInput(Name& srgInputName) const
{
if (m_srgInputNameContext.empty())
{
return false;
}
srgInputName = m_srgInputNameContext + srgInputName.GetCStr();
return true;
}
bool MaterialNameContext::ContextualizeShaderOption(Name& shaderOptionName) const
{
if (m_shaderOptionNameContext.empty())
{
return false;
}
shaderOptionName = m_shaderOptionNameContext + shaderOptionName.GetCStr();
return true;
}
} // namespace RPI
} // namespace AZ

@ -35,11 +35,14 @@ namespace UnitTest
LuaMaterialFunctorSourceData functorSourceData; LuaMaterialFunctorSourceData functorSourceData;
functorSourceData.m_luaScript = script; functorSourceData.m_luaScript = script;
MaterialNameContext nameContext;
MaterialFunctorSourceData::RuntimeContext createFunctorContext{ MaterialFunctorSourceData::RuntimeContext createFunctorContext{
"Dummy.materialtype", "Dummy.materialtype",
materialTypeCreator.GetMaterialPropertiesLayout(), materialTypeCreator.GetMaterialPropertiesLayout(),
materialTypeCreator.GetMaterialShaderResourceGroupLayout(), materialTypeCreator.GetMaterialShaderResourceGroupLayout(),
materialTypeCreator.GetShaderCollection() materialTypeCreator.GetShaderCollection(),
&nameContext
}; };
MaterialFunctorSourceData::FunctorResult result = functorSourceData.CreateFunctor(createFunctorContext); MaterialFunctorSourceData::FunctorResult result = functorSourceData.CreateFunctor(createFunctorContext);

@ -15,6 +15,8 @@
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h> #include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h> #include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h>
#include <Atom/RPI.Reflect/Material/MaterialAssetCreator.h> #include <Atom/RPI.Reflect/Material/MaterialAssetCreator.h>
#include <Atom/RPI.Reflect/Material/MaterialNameContext.h>
#include <Atom/RPI.Reflect/Shader/ShaderAssetCreator.h>
#include <Atom/RPI.Edit/Material/MaterialFunctorSourceData.h> #include <Atom/RPI.Edit/Material/MaterialFunctorSourceData.h>
#include <Atom/RPI.Public/Material/Material.h> #include <Atom/RPI.Public/Material/Material.h>
#include <Material/MaterialAssetTestUtils.h> #include <Material/MaterialAssetTestUtils.h>
@ -256,12 +258,15 @@ namespace UnitTest
functorSourceData.m_registedPropertyName = registedPropertyName.GetStringView(); functorSourceData.m_registedPropertyName = registedPropertyName.GetStringView();
functorSourceData.m_unregistedPropertyName = unregistedPropertyName.GetStringView(); functorSourceData.m_unregistedPropertyName = unregistedPropertyName.GetStringView();
MaterialNameContext nameContext;
MaterialFunctorSourceData::FunctorResult result = functorSourceData.CreateFunctor( MaterialFunctorSourceData::FunctorResult result = functorSourceData.CreateFunctor(
MaterialFunctorSourceData::RuntimeContext( MaterialFunctorSourceData::RuntimeContext(
"Dummy.materialtype", "Dummy.materialtype",
materialTypeCreator.GetMaterialPropertiesLayout(), materialTypeCreator.GetMaterialPropertiesLayout(),
materialTypeCreator.GetMaterialShaderResourceGroupLayout(), materialTypeCreator.GetMaterialShaderResourceGroupLayout(),
materialTypeCreator.GetShaderCollection() materialTypeCreator.GetShaderCollection(),
&nameContext
) )
); );
@ -310,4 +315,169 @@ namespace UnitTest
m_testMaterialTypeAsset = {}; m_testMaterialTypeAsset = {};
m_testMaterialAsset = {}; m_testMaterialAsset = {};
} }
TEST_F(MaterialFunctorTests, UseNameContextInFunctorSourceData_PropertyLookup)
{
class FindPropertyIndexTestFunctor : public MaterialFunctor
{
public:
MaterialPropertyIndex m_foundIndex;
};
class FindPropertyIndexTestFunctorSourceData : public MaterialFunctorSourceData
{
public:
Name m_materialPropertyName;
using MaterialFunctorSourceData::CreateFunctor;
FunctorResult CreateFunctor(const RuntimeContext& runtimeContext) const override
{
RPI::Ptr<FindPropertyIndexTestFunctor> functor = aznew FindPropertyIndexTestFunctor;
functor->m_foundIndex = runtimeContext.FindMaterialPropertyIndex(m_materialPropertyName);
return Success(RPI::Ptr<MaterialFunctor>(functor));
}
};
Data::Asset<MaterialTypeAsset> materialTypeAsset;
MaterialTypeAssetCreator materialTypeCreator;
materialTypeCreator.Begin(Uuid::CreateRandom());
materialTypeCreator.BeginMaterialProperty(Name{"layer1.baseColor.factor"}, MaterialPropertyDataType::Float);
materialTypeCreator.EndMaterialProperty();
materialTypeCreator.End(materialTypeAsset);
FindPropertyIndexTestFunctorSourceData sourceData;
sourceData.m_materialPropertyName = "factor";
MaterialNameContext nameContext;
nameContext.ExtendPropertyIdContext("layer1");
nameContext.ExtendPropertyIdContext("baseColor");
MaterialFunctorSourceData::RuntimeContext createFunctorContext(
"",
materialTypeAsset->GetMaterialPropertiesLayout(),
nullptr,
nullptr,
&nameContext);
RPI::Ptr<MaterialFunctor> functor = sourceData.CreateFunctor(createFunctorContext).TakeValue();
EXPECT_TRUE(reinterpret_cast<FindPropertyIndexTestFunctor*>(functor.get())->m_foundIndex.IsValid());
}
TEST_F(MaterialFunctorTests, UseNameContextInFunctorSourceData_ShaderOptionLookup)
{
class FindShaderOptionIndexTestFunctor : public MaterialFunctor
{
public:
ShaderOptionIndex m_foundIndex;
};
class FindShaderOptionIndexTestFunctorSourceData : public MaterialFunctorSourceData
{
public:
Name m_shaderOptionName;
using MaterialFunctorSourceData::CreateFunctor;
FunctorResult CreateFunctor(const RuntimeContext& runtimeContext) const override
{
RPI::Ptr<FindShaderOptionIndexTestFunctor> functor = aznew FindShaderOptionIndexTestFunctor;
functor->m_foundIndex = runtimeContext.FindShaderOptionIndex(0, m_shaderOptionName);
return Success(RPI::Ptr<MaterialFunctor>(functor));
}
};
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionLayout = RPI::ShaderOptionGroupLayout::Create();
shaderOptionLayout->AddShaderOption(
RPI::ShaderOptionDescriptor{Name("o_layer1_baseColor_useTexture"), RPI::ShaderOptionType::Boolean, 0, 0, CreateBoolShaderOptionValues()});
shaderOptionLayout->Finalize();
Data::Asset<ShaderAsset> shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), nullptr, shaderOptionLayout);
Data::Asset<MaterialTypeAsset> materialTypeAsset;
MaterialTypeAssetCreator materialTypeCreator;
materialTypeCreator.Begin(Uuid::CreateRandom());
materialTypeCreator.AddShader(shaderAsset);
materialTypeCreator.End(materialTypeAsset);
FindShaderOptionIndexTestFunctorSourceData sourceData;
sourceData.m_shaderOptionName = "useTexture";
MaterialNameContext nameContext;
nameContext.ExtendShaderOptionContext("o_layer1_baseColor_");
MaterialFunctorSourceData::RuntimeContext createFunctorContext(
"",
nullptr,
nullptr,
&materialTypeAsset->GetShaderCollection(),
&nameContext);
RPI::Ptr<MaterialFunctor> functor = sourceData.CreateFunctor(createFunctorContext).TakeValue();
EXPECT_TRUE(reinterpret_cast<FindShaderOptionIndexTestFunctor*>(functor.get())->m_foundIndex.IsValid());
}
TEST_F(MaterialFunctorTests, UseNameContextInFunctorSourceData_ShaderConstantLookup)
{
class FindShaderInputIndexTestFunctor : public MaterialFunctor
{
public:
RHI::ShaderInputConstantIndex m_foundConstantIndex;
RHI::ShaderInputImageIndex m_foundImageIndex;
};
class FindShaderInputIndexTestFunctorSourceData : public MaterialFunctorSourceData
{
public:
Name m_shaderConstantName;
Name m_shaderImageName;
using MaterialFunctorSourceData::CreateFunctor;
FunctorResult CreateFunctor(const RuntimeContext& runtimeContext) const override
{
RPI::Ptr<FindShaderInputIndexTestFunctor> functor = aznew FindShaderInputIndexTestFunctor;
functor->m_foundConstantIndex = runtimeContext.FindShaderInputConstantIndex(m_shaderConstantName);
functor->m_foundImageIndex = runtimeContext.FindShaderInputImageIndex(m_shaderImageName);
return Success(RPI::Ptr<MaterialFunctor>(functor));
}
};
AZ::RHI::Ptr<AZ::RHI::ShaderResourceGroupLayout> srgLayout = RHI::ShaderResourceGroupLayout::Create();
srgLayout->SetName(Name("MaterialSrg"));
srgLayout->SetUniqueId(Uuid::CreateRandom().ToString<AZStd::string>()); // Any random string will suffice.
srgLayout->SetBindingSlot(SrgBindingSlot::Material);
srgLayout->AddShaderInput(RHI::ShaderInputConstantDescriptor{Name{ "m_layer1_baseColor_factor" }, 0, 4, 0});
srgLayout->AddShaderInput(RHI::ShaderInputImageDescriptor{Name{ "m_layer1_baseColor_texture" }, RHI::ShaderInputImageAccess::Read, RHI::ShaderInputImageType::Image2D, 1, 1});
srgLayout->Finalize();
Data::Asset<ShaderAsset> shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), srgLayout);
Data::Asset<MaterialTypeAsset> materialTypeAsset;
MaterialTypeAssetCreator materialTypeCreator;
materialTypeCreator.Begin(Uuid::CreateRandom());
materialTypeCreator.AddShader(shaderAsset);
materialTypeCreator.End(materialTypeAsset);
FindShaderInputIndexTestFunctorSourceData sourceData;
sourceData.m_shaderConstantName = "factor";
sourceData.m_shaderImageName = "texture";
MaterialNameContext nameContext;
nameContext.ExtendSrgInputContext("m_layer1_baseColor_");
MaterialFunctorSourceData::RuntimeContext createFunctorContext(
"",
nullptr,
srgLayout.get(),
nullptr,
&nameContext);
RPI::Ptr<MaterialFunctor> functor = sourceData.CreateFunctor(createFunctorContext).TakeValue();
EXPECT_TRUE(reinterpret_cast<FindShaderInputIndexTestFunctor*>(functor.get())->m_foundConstantIndex.IsValid());
EXPECT_TRUE(reinterpret_cast<FindShaderInputIndexTestFunctor*>(functor.get())->m_foundImageIndex.IsValid());
}
} }

@ -19,6 +19,7 @@
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h> #include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h> #include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h> #include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h>
#include <Atom/RPI.Reflect/Material/MaterialNameContext.h>
namespace JsonSerializationTests namespace JsonSerializationTests
{ {
@ -256,13 +257,16 @@ namespace UnitTest
JsonTestResult loadResult = LoadTestDataFromJson(*functorData, inputJson); JsonTestResult loadResult = LoadTestDataFromJson(*functorData, inputJson);
MaterialNameContext nameContext;
// Where type resolving happens. // Where type resolving happens.
MaterialFunctorSourceData::FunctorResult functorResult = functorData->CreateFunctor( MaterialFunctorSourceData::FunctorResult functorResult = functorData->CreateFunctor(
MaterialFunctorSourceData::RuntimeContext( MaterialFunctorSourceData::RuntimeContext(
"Dummy.materialtype", "Dummy.materialtype",
m_materialTypeCreator.GetMaterialPropertiesLayout(), m_materialTypeCreator.GetMaterialPropertiesLayout(),
m_materialTypeCreator.GetMaterialShaderResourceGroupLayout(), m_materialTypeCreator.GetMaterialShaderResourceGroupLayout(),
m_materialTypeCreator.GetShaderCollection() m_materialTypeCreator.GetShaderCollection(),
&nameContext
) )
); );

@ -19,6 +19,7 @@
#include <Atom/RPI.Edit/Material/MaterialUtils.h> #include <Atom/RPI.Edit/Material/MaterialUtils.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h> #include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h> #include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <Atom/RPI.Reflect/Material/MaterialNameContext.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h> #include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Material/Material.h> #include <Atom/RPI.Public/Material/Material.h>
@ -252,6 +253,57 @@ namespace UnitTest
AZStd::string m_enablePropertyName; AZStd::string m_enablePropertyName;
}; };
// All this functor does is save the MaterialNameContext
class SaveNameContextTestFunctor final
: public AZ::RPI::MaterialFunctor
{
public:
AZ_RTTI(SaveNameContextTestFunctor, "{FD680069-B430-4278-9E5B-A2B9617627D5}", AZ::RPI::MaterialFunctor);
static void Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SaveNameContextTestFunctor, AZ::RPI::MaterialFunctor>()
->Version(1)
->Field("nameContext", &SaveNameContextTestFunctor::m_nameContext);
}
}
using AZ::RPI::MaterialFunctor::Process;
void Process(AZ::RPI::MaterialFunctor::RuntimeContext&) override
{
}
MaterialNameContext m_nameContext;
};
// All this functor does is save the MaterialNameContext
class SaveNameContextTestFunctorSourceData final
: public MaterialFunctorSourceData
{
public:
AZ_RTTI(SaveNameContextTestFunctorSourceData, "{4261A2EC-4AB6-420E-884A-18D1A36500BE}", MaterialFunctorSourceData);
static void Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SaveNameContextTestFunctorSourceData>()
->Version(1)
;
}
}
using MaterialFunctorSourceData::CreateFunctor;
FunctorResult CreateFunctor([[maybe_unused]] const RuntimeContext& context) const override
{
Ptr<SaveNameContextTestFunctor> functor = aznew SaveNameContextTestFunctor;
functor->m_nameContext = *context.GetNameContext();
return Success(Ptr<MaterialFunctor>(functor));
}
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Reflect(ReflectContext* context) override void Reflect(ReflectContext* context) override
@ -265,6 +317,7 @@ namespace UnitTest
Splat3FunctorSourceData::Reflect(context); Splat3FunctorSourceData::Reflect(context);
EnableShaderFunctorSourceData::Reflect(context); EnableShaderFunctorSourceData::Reflect(context);
SetShaderOptionFunctorSourceData::Reflect(context); SetShaderOptionFunctorSourceData::Reflect(context);
SaveNameContextTestFunctorSourceData::Reflect(context);
} }
void SetUp() override void SetUp() override
@ -276,6 +329,7 @@ namespace UnitTest
AZ::RPI::MaterialFunctorSourceDataRegistration::Get()->RegisterMaterialFunctor("Splat3", azrtti_typeid<Splat3FunctorSourceData>()); AZ::RPI::MaterialFunctorSourceDataRegistration::Get()->RegisterMaterialFunctor("Splat3", azrtti_typeid<Splat3FunctorSourceData>());
AZ::RPI::MaterialFunctorSourceDataRegistration::Get()->RegisterMaterialFunctor("EnableShader", azrtti_typeid<EnableShaderFunctorSourceData>()); AZ::RPI::MaterialFunctorSourceDataRegistration::Get()->RegisterMaterialFunctor("EnableShader", azrtti_typeid<EnableShaderFunctorSourceData>());
AZ::RPI::MaterialFunctorSourceDataRegistration::Get()->RegisterMaterialFunctor("SetShaderOption", azrtti_typeid<SetShaderOptionFunctorSourceData>()); AZ::RPI::MaterialFunctorSourceDataRegistration::Get()->RegisterMaterialFunctor("SetShaderOption", azrtti_typeid<SetShaderOptionFunctorSourceData>());
AZ::RPI::MaterialFunctorSourceDataRegistration::Get()->RegisterMaterialFunctor("SaveNameContext", azrtti_typeid<SaveNameContextTestFunctorSourceData>());
const Name materialSrgId{"MaterialSrg"}; const Name materialSrgId{"MaterialSrg"};
m_testMaterialSrgLayout = RHI::ShaderResourceGroupLayout::Create(); m_testMaterialSrgLayout = RHI::ShaderResourceGroupLayout::Create();
@ -432,20 +486,25 @@ namespace UnitTest
struct EnumeratePropertyGroupsResult struct EnumeratePropertyGroupsResult
{ {
AZStd::string m_propertyIdContext; MaterialNameContext m_materialNameContext;
const MaterialTypeSourceData::PropertyGroup* m_propertyGroup; const MaterialTypeSourceData::PropertyGroup* m_propertyGroup;
void Check(AZStd::string expectedIdContext, const MaterialTypeSourceData::PropertyGroup* expectedPropertyGroup) void Check(AZStd::string expectedIdContext, const MaterialTypeSourceData::PropertyGroup* expectedPropertyGroup)
{ {
EXPECT_EQ(expectedIdContext, m_propertyIdContext); Name groupFullId{m_propertyGroup->GetName()};
m_materialNameContext.ContextualizeProperty(groupFullId);
AZStd::string expectedPropertyId = expectedIdContext + expectedPropertyGroup->GetName();
EXPECT_EQ(expectedPropertyId, groupFullId.GetStringView());
EXPECT_EQ(expectedPropertyGroup, m_propertyGroup); EXPECT_EQ(expectedPropertyGroup, m_propertyGroup);
} }
}; };
AZStd::vector<EnumeratePropertyGroupsResult> enumeratePropertyGroupsResults; AZStd::vector<EnumeratePropertyGroupsResult> enumeratePropertyGroupsResults;
sourceData.EnumeratePropertyGroups([&enumeratePropertyGroupsResults](const AZStd::string& propertyIdContext, const MaterialTypeSourceData::PropertyGroup* propertyGroup) sourceData.EnumeratePropertyGroups([&enumeratePropertyGroupsResults](const MaterialNameContext& materialNameContext, const MaterialTypeSourceData::PropertyGroup* propertyGroup)
{ {
enumeratePropertyGroupsResults.push_back(EnumeratePropertyGroupsResult{propertyIdContext, propertyGroup}); enumeratePropertyGroupsResults.push_back(EnumeratePropertyGroupsResult{materialNameContext, propertyGroup});
return true; return true;
}); });
@ -466,20 +525,25 @@ namespace UnitTest
struct EnumeratePropertiesResult struct EnumeratePropertiesResult
{ {
AZStd::string m_propertyIdContext; MaterialNameContext m_materialNameContext;
const MaterialTypeSourceData::PropertyDefinition* m_propertyDefinition; const MaterialTypeSourceData::PropertyDefinition* m_propertyDefinition;
void Check(AZStd::string expectedIdContext, const MaterialTypeSourceData::PropertyDefinition* expectedPropertyDefinition) void Check(AZStd::string expectedIdContext, const MaterialTypeSourceData::PropertyDefinition* expectedPropertyDefinition)
{ {
EXPECT_EQ(expectedIdContext, m_propertyIdContext); Name propertyFullId{m_propertyDefinition->GetName()};
m_materialNameContext.ContextualizeProperty(propertyFullId);
AZStd::string expectedPropertyId = expectedIdContext + expectedPropertyDefinition->GetName();
EXPECT_EQ(expectedPropertyId, propertyFullId.GetStringView());
EXPECT_EQ(expectedPropertyDefinition, m_propertyDefinition); EXPECT_EQ(expectedPropertyDefinition, m_propertyDefinition);
} }
}; };
AZStd::vector<EnumeratePropertiesResult> enumeratePropertiesResults; AZStd::vector<EnumeratePropertiesResult> enumeratePropertiesResults;
sourceData.EnumerateProperties([&enumeratePropertiesResults](const AZStd::string& propertyIdContext, const MaterialTypeSourceData::PropertyDefinition* propertyDefinition) sourceData.EnumerateProperties([&enumeratePropertiesResults](const MaterialNameContext& materialNameContext, const MaterialTypeSourceData::PropertyDefinition* propertyDefinition)
{ {
enumeratePropertiesResults.push_back(EnumeratePropertiesResult{propertyIdContext, propertyDefinition}); enumeratePropertiesResults.push_back(EnumeratePropertiesResult{materialNameContext, propertyDefinition});
return true; return true;
}); });
@ -1469,7 +1533,7 @@ namespace UnitTest
// Note that serialization of individual fields within material properties is thoroughly tested in // Note that serialization of individual fields within material properties is thoroughly tested in
// MaterialPropertySerializerTests, so the sample property data used here is cursory. // MaterialPropertySerializerTests, so the sample property data used here is cursory.
// We also don't cover fields related to providing name contexts for nested property groups, like // We also don't cover fields related to providing name contexts for nested property groups, like
// "shaderInputPrefix" and "shaderOptionPrefix" as those are covered in CreateMaterialTypeAsset_NestedGroups*. // "shaderInputsPrefix" and "shaderOptionsPrefix" as those are covered in CreateMaterialTypeAsset_NestedGroups*.
const AZStd::string inputJson = R"( const AZStd::string inputJson = R"(
{ {
@ -2014,4 +2078,140 @@ namespace UnitTest
EXPECT_EQ(materialType.FindProperty("myGroup.bar")->m_dataType, MaterialPropertyDataType::Float); EXPECT_EQ(materialType.FindProperty("myGroup.bar")->m_dataType, MaterialPropertyDataType::Float);
} }
TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_NestedGroupNameContext)
{
const Name materialSrgId{"MaterialSrg"};
RHI::Ptr<RHI::ShaderResourceGroupLayout> materialSrgLayout = RHI::ShaderResourceGroupLayout::Create();
materialSrgLayout->SetName(materialSrgId);
materialSrgLayout->SetBindingSlot(SrgBindingSlot::Material);
materialSrgLayout->AddShaderInput(RHI::ShaderInputImageDescriptor{ Name{ "m_unused1" }, RHI::ShaderInputImageAccess::Read, RHI::ShaderInputImageType::Image2D, 1, 1 });
materialSrgLayout->AddShaderInput(RHI::ShaderInputImageDescriptor{ Name{ "m_unused2" }, RHI::ShaderInputImageAccess::Read, RHI::ShaderInputImageType::Image2D, 1, 1 });
materialSrgLayout->AddShaderInput(RHI::ShaderInputImageDescriptor{ Name{ "m_groupA_m_groupB_m_texture" }, RHI::ShaderInputImageAccess::Read, RHI::ShaderInputImageType::Image2D, 1, 1 });
EXPECT_TRUE(materialSrgLayout->Finalize());
Ptr<ShaderOptionGroupLayout> shaderOptions = ShaderOptionGroupLayout::Create();
shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_unused"}, ShaderOptionType::Boolean, 0, 0, CreateBoolShaderOptionValues()});
shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_groupA_o_groupB_o_useTexture"}, ShaderOptionType::Boolean, 1, 1, CreateBoolShaderOptionValues()});
shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_groupA_o_groupB_o_useTextureAlt"}, ShaderOptionType::Boolean, 2, 2, CreateBoolShaderOptionValues()});
shaderOptions->Finalize();
Data::Asset<ShaderAsset> shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), materialSrgLayout, shaderOptions);
Data::AssetInfo testShaderAssetInfo;
testShaderAssetInfo.m_assetId = shaderAsset.GetId();
m_assetSystemStub.RegisterSourceInfo("NestedGroupNameContext.shader", testShaderAssetInfo, "");
const AZStd::string materialTypeJson = R"(
{
"propertyLayout": {
"propertyGroups": [
{
"name": "groupA",
"shaderInputsPrefix": "m_groupA_",
"shaderOptionsPrefix": "o_groupA_",
"propertyGroups": [
{
"name": "groupB",
"shaderInputsPrefix": "m_groupB_",
"shaderOptionsPrefix": "o_groupB_",
"propertyGroups": [
{
"name": "groupC",
"properties": [
{
"name": "textureMap",
"type": "Image",
"connection": {
"type": "ShaderInput",
"name": "m_texture"
}
},
{
"name": "useTextureMap",
"type": "Bool",
"connection": [
{
"type": "ShaderOption",
"name": "o_useTexture"
},
{
"type": "ShaderOption",
"name": "o_useTextureAlt",
"shaderIndex": 0 // Having a specific shaderIndex traverses a different code path
}
]
}
],
"functors": [
{
"type": "SaveNameContext"
}
]
}
]
}
]
}
]
},
"shaders": [
{
"file": "NestedGroupNameContext.shader"
}
]
}
)";
MaterialTypeSourceData materialTypeSourceData;
JsonTestResult loadResult = LoadTestDataFromJson(materialTypeSourceData, materialTypeJson);
auto materialTypeAssetOutcome = materialTypeSourceData.CreateMaterialTypeAsset(Uuid::CreateRandom());
EXPECT_TRUE(materialTypeAssetOutcome.IsSuccess());
Data::Asset<MaterialTypeAsset> materialTypeAsset = materialTypeAssetOutcome.TakeValue();
const MaterialPropertiesLayout* propertiesLayout = materialTypeAsset->GetMaterialPropertiesLayout();
EXPECT_EQ(2, propertiesLayout->GetPropertyCount());
EXPECT_EQ(0, propertiesLayout->FindPropertyIndex(Name("groupA.groupB.groupC.textureMap")).GetIndex());
EXPECT_EQ(1, propertiesLayout->FindPropertyIndex(Name("groupA.groupB.groupC.useTextureMap")).GetIndex());
// groupA.gropuB.groupC.textureMap has a connection to m_groupA_m_groupB_m_texture
MaterialPropertyIndex texturePropertyIndex{0};
EXPECT_EQ(1, propertiesLayout->GetPropertyDescriptor(texturePropertyIndex)->GetOutputConnections().size());
EXPECT_EQ(materialSrgLayout->FindShaderInputImageIndex(Name("m_groupA_m_groupB_m_texture")).GetIndex(),
propertiesLayout->GetPropertyDescriptor(texturePropertyIndex)->GetOutputConnections()[0].m_itemIndex.GetIndex());
// groupA.gropuB.groupC.useTextureMap has a connection to o_groupA_o_groupB_o_useTexture and o_groupA_o_groupB_o_useTextureAlt
MaterialPropertyIndex useTexturePropertyIndex{1};
EXPECT_EQ(2, propertiesLayout->GetPropertyDescriptor(useTexturePropertyIndex)->GetOutputConnections().size());
EXPECT_EQ(shaderOptions->FindShaderOptionIndex(Name("o_groupA_o_groupB_o_useTexture")).GetIndex(),
propertiesLayout->GetPropertyDescriptor(useTexturePropertyIndex)->GetOutputConnections()[0].m_itemIndex.GetIndex());
EXPECT_EQ(shaderOptions->FindShaderOptionIndex(Name("o_groupA_o_groupB_o_useTextureAlt")).GetIndex(),
propertiesLayout->GetPropertyDescriptor(useTexturePropertyIndex)->GetOutputConnections()[1].m_itemIndex.GetIndex());
// There should be one functor, which processes useTextureMap, and it should have the appropriate name context for constructing the correct full names.
EXPECT_EQ(1, materialTypeAsset->GetMaterialFunctors().size());
EXPECT_TRUE(azrtti_istypeof<SaveNameContextTestFunctor>(materialTypeAsset->GetMaterialFunctors()[0].get()));
auto saveNameContextFunctor = azrtti_cast<SaveNameContextTestFunctor*>(materialTypeAsset->GetMaterialFunctors()[0].get());
const MaterialNameContext& nameContext = saveNameContextFunctor->m_nameContext;
Name textureMapProperty{"textureMap"};
Name textureMapShaderInput{"m_texture"};
Name useTextureMapProperty{"useTextureMap"};
Name useTextureShaderOption{"o_useTexture"};
nameContext.ContextualizeProperty(textureMapProperty);
nameContext.ContextualizeProperty(useTextureMapProperty);
nameContext.ContextualizeSrgInput(textureMapShaderInput);
nameContext.ContextualizeShaderOption(useTextureShaderOption);
EXPECT_EQ("groupA.groupB.groupC.useTextureMap", useTextureMapProperty.GetStringView());
EXPECT_EQ("o_groupA_o_groupB_o_useTexture", useTextureShaderOption.GetStringView());
EXPECT_EQ("groupA.groupB.groupC.textureMap", textureMapProperty.GetStringView());
EXPECT_EQ("m_groupA_m_groupB_m_texture", textureMapShaderInput.GetStringView());
}
} }

@ -53,6 +53,7 @@ set(FILES
Include/Atom/RPI.Reflect/Material/MaterialAsset.h Include/Atom/RPI.Reflect/Material/MaterialAsset.h
Include/Atom/RPI.Reflect/Material/MaterialAssetCreator.h Include/Atom/RPI.Reflect/Material/MaterialAssetCreator.h
Include/Atom/RPI.Reflect/Material/MaterialDynamicMetadata.h Include/Atom/RPI.Reflect/Material/MaterialDynamicMetadata.h
Include/Atom/RPI.Reflect/Material/MaterialNameContext.h
Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h
Include/Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h Include/Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h
Include/Atom/RPI.Reflect/Material/MaterialPropertyValue.h Include/Atom/RPI.Reflect/Material/MaterialPropertyValue.h
@ -133,6 +134,7 @@ set(FILES
Source/RPI.Reflect/Material/MaterialPropertyValue.cpp Source/RPI.Reflect/Material/MaterialPropertyValue.cpp
Source/RPI.Reflect/Material/MaterialAsset.cpp Source/RPI.Reflect/Material/MaterialAsset.cpp
Source/RPI.Reflect/Material/MaterialAssetCreator.cpp Source/RPI.Reflect/Material/MaterialAssetCreator.cpp
Source/RPI.Reflect/Material/MaterialNameContext.cpp
Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp
Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp
Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp

@ -17,6 +17,7 @@
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h> #include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h> #include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h> #include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Reflect/Material/MaterialNameContext.h>
#include <AtomCore/Instance/Instance.h> #include <AtomCore/Instance/Instance.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h> #include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h>
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h> #include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
@ -585,9 +586,10 @@ namespace MaterialEditor
bool result = true; bool result = true;
// populate sourceData with properties that meet the filter // populate sourceData with properties that meet the filter
m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& propertyIdContext, const auto& propertyDefinition) { m_materialTypeSourceData.EnumerateProperties([&](const MaterialNameContext& materialNameContext, const auto& propertyDefinition) {
Name propertyId{propertyIdContext + propertyDefinition->GetName()}; Name propertyId{propertyDefinition->GetName()};
materialNameContext.ContextualizeProperty(propertyId);
const auto it = m_properties.find(propertyId); const auto it = m_properties.find(propertyId);
if (it != m_properties.end() && propertyFilter(it->second)) if (it != m_properties.end() && propertyFilter(it->second))
@ -781,14 +783,17 @@ namespace MaterialEditor
// Populate the property map from a combination of source data and assets // Populate the property map from a combination of source data and assets
// Assets must still be used for now because they contain the final accumulated value after all other materials // Assets must still be used for now because they contain the final accumulated value after all other materials
// in the hierarchy are applied // in the hierarchy are applied
m_materialTypeSourceData.EnumeratePropertyGroups([this, &parentPropertyValues](const AZStd::string& propertyIdContext, const MaterialTypeSourceData::PropertyGroup* propertyGroup) m_materialTypeSourceData.EnumeratePropertyGroups([this, &parentPropertyValues](const MaterialNameContext& materialNameContext, const MaterialTypeSourceData::PropertyGroup* propertyGroup)
{ {
AtomToolsFramework::DynamicPropertyConfig propertyConfig; AtomToolsFramework::DynamicPropertyConfig propertyConfig;
for (const auto& propertyDefinition : propertyGroup->GetProperties()) for (const auto& propertyDefinition : propertyGroup->GetProperties())
{ {
// Assign id before conversion so it can be used in dynamic description // Assign id before conversion so it can be used in dynamic description
propertyConfig.m_id = propertyIdContext + propertyGroup->GetName() + "." + propertyDefinition->GetName(); MaterialNameContext groupNameContext = materialNameContext;
groupNameContext.ExtendPropertyIdContext(propertyGroup->GetName());
propertyConfig.m_id = propertyDefinition->GetName();
groupNameContext.ContextualizeProperty(propertyConfig.m_id);
const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
const bool propertyIndexInBounds = propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size(); const bool propertyIndexInBounds = propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size();
@ -878,8 +883,9 @@ namespace MaterialEditor
} }
// Add material functors that are in the top-level functors list. // Add material functors that are in the top-level functors list.
AZ::RPI::MaterialNameContext materialNameContext; // There is no name context for top-level functors, only functors inside PropertyGroups
const MaterialFunctorSourceData::EditorContext editorContext = const MaterialFunctorSourceData::EditorContext editorContext =
MaterialFunctorSourceData::EditorContext(m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); MaterialFunctorSourceData::EditorContext(m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout(), &materialNameContext);
for (Ptr<MaterialFunctorSourceDataHolder> functorData : m_materialTypeSourceData.m_materialFunctorSourceData) for (Ptr<MaterialFunctorSourceDataHolder> functorData : m_materialTypeSourceData.m_materialFunctorSourceData)
{ {
MaterialFunctorSourceData::FunctorResult result2 = functorData->CreateFunctor(editorContext); MaterialFunctorSourceData::FunctorResult result2 = functorData->CreateFunctor(editorContext);
@ -901,10 +907,10 @@ namespace MaterialEditor
// Add any material functors that are located inside each property group. // Add any material functors that are located inside each property group.
bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups( bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups(
[this](const AZStd::string&, const MaterialTypeSourceData::PropertyGroup* propertyGroup) [this](const MaterialNameContext& nameContext, const MaterialTypeSourceData::PropertyGroup* propertyGroup)
{ {
const MaterialFunctorSourceData::EditorContext editorContext = MaterialFunctorSourceData::EditorContext( const MaterialFunctorSourceData::EditorContext editorContext = MaterialFunctorSourceData::EditorContext(
m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout(), &nameContext);
for (Ptr<MaterialFunctorSourceDataHolder> functorData : propertyGroup->GetFunctors()) for (Ptr<MaterialFunctorSourceDataHolder> functorData : propertyGroup->GetFunctors())
{ {

@ -15,6 +15,7 @@
#include <Atom/RPI.Edit/Material/MaterialUtils.h> #include <Atom/RPI.Edit/Material/MaterialUtils.h>
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h> #include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h> #include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Reflect/Material/MaterialNameContext.h>
#include <AtomToolsFramework/Inspector/InspectorPropertyGroupWidget.h> #include <AtomToolsFramework/Inspector/InspectorPropertyGroupWidget.h>
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h> #include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
#include <AtomToolsFramework/Util/Util.h> #include <AtomToolsFramework/Util/Util.h>
@ -104,8 +105,9 @@ namespace AZ
// Get a list of all the editor functors to be used for property editor states // Get a list of all the editor functors to be used for property editor states
auto propertyLayout = m_editData.m_materialAsset->GetMaterialPropertiesLayout(); auto propertyLayout = m_editData.m_materialAsset->GetMaterialPropertiesLayout();
AZ::RPI::MaterialNameContext materialNameContext; // There is no name context for top-level functors, only functors inside PropertyGroups
const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext =
AZ::RPI::MaterialFunctorSourceData::EditorContext(m_editData.m_materialTypeSourcePath, propertyLayout); AZ::RPI::MaterialFunctorSourceData::EditorContext(m_editData.m_materialTypeSourcePath, propertyLayout, &materialNameContext);
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : m_editData.m_materialTypeSourceData.m_materialFunctorSourceData) for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : m_editData.m_materialTypeSourceData.m_materialFunctorSourceData)
{ {
AZ::RPI::MaterialFunctorSourceData::FunctorResult createResult = functorData->CreateFunctor(editorContext); AZ::RPI::MaterialFunctorSourceData::FunctorResult createResult = functorData->CreateFunctor(editorContext);

@ -17,6 +17,7 @@
#include <Atom/RPI.Reflect/Material/MaterialAsset.h> #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h> #include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h> #include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialNameContext.h>
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h> #include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
#include <AzFramework/API/ApplicationAPI.h> #include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h> #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
@ -113,9 +114,11 @@ namespace AZ
// Copy all of the properties from the material asset to the source data that will be exported // Copy all of the properties from the material asset to the source data that will be exported
bool result = true; bool result = true;
editData.m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& propertyIdContext, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition* propertyDefinition) editData.m_materialTypeSourceData.EnumerateProperties([&](const AZ::RPI::MaterialNameContext& materialNameContext, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition* propertyDefinition)
{ {
AZ::Name propertyId(propertyIdContext + propertyDefinition->GetName()); AZ::Name propertyId{propertyDefinition->GetName()};
materialNameContext.ContextualizeProperty(propertyId);
const AZ::RPI::MaterialPropertyIndex propertyIndex = const AZ::RPI::MaterialPropertyIndex propertyIndex =
editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId); editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId);

Loading…
Cancel
Save