diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/LuaMaterialFunctorSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/LuaMaterialFunctorSourceData.h index 7deda73c05..e08ee90fda 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/LuaMaterialFunctorSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/LuaMaterialFunctorSourceData.h @@ -41,7 +41,10 @@ namespace AZ // Calls a lua function that returns a list of strings. Outcome, 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 AZStd::string m_luaSourceFile; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialFunctorSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialFunctorSourceData.h index a8f4f4c524..8bf88342c3 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialFunctorSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialFunctorSourceData.h @@ -31,6 +31,7 @@ namespace AZ class MaterialPropertiesLayout; class ShaderOptionGroupLayout; class JsonMaterialFunctorSourceDataSerializer; + class MaterialNameContext; //! This is an abstract base class for initializing MaterialFunctor objects. //! Material functors provide custom logic and calculations to configure shaders, render states, and more. @@ -54,11 +55,17 @@ namespace AZ struct RuntimeContext { 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_materialPropertiesLayout(materialPropertiesLayout) , m_shaderResourceGroupLayout(shaderResourceGroupLayout) , m_shaderCollection(shaderCollection) + , m_materialNameContext(materialNameContext) {} const AZStd::string& GetMaterialTypeSourceFilePath() const { return m_materialTypeFilePath; } @@ -66,6 +73,10 @@ namespace AZ const MaterialPropertiesLayout* GetMaterialPropertiesLayout() 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. AZStd::size_t GetShaderCount() const; @@ -77,16 +88,18 @@ namespace AZ AZStd::vector GetShaderTags() const; //! 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. + //! This will also automatically apply the MaterialNameContext. //! @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 optionName the name of the option to find //! @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 - ShaderOptionIndex FindShaderOptionIndex(AZStd::size_t shaderIndex, const Name& optionName, bool reportErrors = true) const; - ShaderOptionIndex FindShaderOptionIndex(const AZ::Name& shaderTag, 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, Name optionName, bool reportErrors = true) const; //! 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. @@ -96,19 +109,32 @@ namespace AZ //! And also report an error message if shaderTag is invalid. 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: const AZStd::string m_materialTypeFilePath; const MaterialPropertiesLayout* m_materialPropertiesLayout; const RHI::ShaderResourceGroupLayout* m_shaderResourceGroupLayout; const ShaderCollection* m_shaderCollection; + const MaterialNameContext* m_materialNameContext; }; struct EditorContext { public: - EditorContext(const AZStd::string& materialTypeFilePath, const MaterialPropertiesLayout* materialPropertiesLayout) + EditorContext( + const AZStd::string& materialTypeFilePath, + const MaterialPropertiesLayout* materialPropertiesLayout, + const MaterialNameContext* materialNameContext) : m_materialTypeFilePath(materialTypeFilePath) , m_materialPropertiesLayout(materialPropertiesLayout) + , m_materialNameContext(materialNameContext) {} const AZStd::string& GetMaterialTypeSourceFilePath() const { return m_materialTypeFilePath; } @@ -116,11 +142,21 @@ namespace AZ const MaterialPropertiesLayout* GetMaterialPropertiesLayout() const; //! 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: const AZStd::string m_materialTypeFilePath; const MaterialPropertiesLayout* m_materialPropertiesLayout; + const MaterialNameContext* m_materialNameContext; }; //! Creates a fully initialized MaterialFunctor object that is ready to be serialized to the cache. diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyId.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyId.h index 8c78d4b491..424a3a187a 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyId.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyId.h @@ -22,9 +22,14 @@ namespace AZ //! The groups are optional, in which case the full property ID will just be like "propertyName". class MaterialPropertyId { - public: + public: + //! Returns whether the name is a valid C-style identifier static bool IsValidName(AZStd::string_view 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". //! Also checks the name for validity. diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h index 400556bf2f..70d92ffccf 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h @@ -158,6 +158,8 @@ namespace AZ AZStd::string m_name; AZStd::string m_displayName; 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; AZStd::vector> m_propertyGroups; AZStd::vector> m_materialFunctorSourceData; @@ -282,7 +284,7 @@ namespace AZ //! Call back function type used with the enumeration functions. //! Return false to terminate the traversal. using EnumeratePropertyGroupsCallback = AZStd::function; @@ -293,7 +295,7 @@ namespace AZ //! Call back function type used with the numeration functions. //! Return false to terminate the traversal. using EnumeratePropertiesCallback = AZStd::function; @@ -316,10 +318,12 @@ namespace AZ PropertyDefinition* FindProperty(AZStd::span parsedPropertyId, AZStd::span> inPropertyGroupList); // Function overloads for recursion, returns false to indicate that recursion should end. - bool EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertyGroupList) const; - bool EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertyGroupList) const; + bool EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, MaterialNameContext materialNameContext, const AZStd::vector>& inPropertyGroupList) const; + bool EnumerateProperties(const EnumeratePropertiesCallback& callback, MaterialNameContext materialNameContext, const AZStd::vector>& inPropertyGroupList) const; + + static MaterialNameContext ExtendNameContext(MaterialNameContext nameContext, const MaterialTypeSourceData::PropertyGroup& propertyGroup); - //! Recursively populates a material asset with properties from the tree of material property groups. + //! 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 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 @@ -327,7 +331,7 @@ namespace AZ bool BuildPropertyList( const AZStd::string& materialTypeSourceFilePath, MaterialTypeAssetCreator& materialTypeAssetCreator, - AZStd::vector& propertyIdContext, + MaterialNameContext materialNameContext, const MaterialTypeSourceData::PropertyGroup* propertyGroup) const; //! Construct a complete list of group definitions, including implicit groups, arranged in the same order as the source data. diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h index 5f4dce40e5..1bce036c73 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h @@ -10,6 +10,7 @@ #include #include +#include #include namespace UnitTest @@ -60,12 +61,8 @@ namespace AZ AZStd::unique_ptr m_sriptBehaviorContext; AZStd::unique_ptr m_scriptContext; - // These are prefix strings that will be applied to every name lookup in the lua functor. - // This allows the lua script to be reused in different contexts. - AZStd::string m_propertyNamePrefix; - AZStd::string m_srgNamePrefix; - AZStd::string m_optionsNamePrefix; - + MaterialNameContext m_materialNameContext; + enum class ScriptStatus { Uninitialized, @@ -84,15 +81,11 @@ namespace AZ explicit LuaMaterialFunctorCommonContext(MaterialFunctor::RuntimeContext* runtimeContextImpl, const MaterialPropertyFlags* materialPropertyDependencies, - const AZStd::string& propertyNamePrefix, - const AZStd::string& srgNamePrefix, - const AZStd::string& optionsNamePrefix); + const MaterialNameContext &materialNameContext); explicit LuaMaterialFunctorCommonContext(MaterialFunctor::EditorContext* editorContextImpl, const MaterialPropertyFlags* materialPropertyDependencies, - const AZStd::string& propertyNamePrefix, - const AZStd::string& srgNamePrefix, - const AZStd::string& optionsNamePrefix); + const MaterialNameContext &materialNameContext); //! Returns false if PSO changes are not allowed, and may report errors or warnings bool CheckPsoChangesAllowed(); @@ -112,11 +105,7 @@ namespace AZ AZStd::string GetMaterialPropertyDependenciesString() const; - // These are prefix strings that will be applied to every name lookup in the lua functor. - // 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; + const MaterialNameContext &m_materialNameContext; private: @@ -284,9 +273,7 @@ namespace AZ explicit LuaMaterialFunctorRuntimeContext(MaterialFunctor::RuntimeContext* runtimeContextImpl, const MaterialPropertyFlags* materialPropertyDependencies, - const AZStd::string& propertyNamePrefix, - const AZStd::string& srgNamePrefix, - const AZStd::string& optionsNamePrefix); + const MaterialNameContext &materialNameContext); template Type GetMaterialPropertyValue(const char* name) const; @@ -324,9 +311,7 @@ namespace AZ explicit LuaMaterialFunctorEditorContext(MaterialFunctor::EditorContext* editorContextImpl, const MaterialPropertyFlags* materialPropertyDependencies, - const AZStd::string& propertyNamePrefix, - const AZStd::string& srgNamePrefix, - const AZStd::string& optionsNamePrefix); + const MaterialNameContext &materialNameContext); template Type GetMaterialPropertyValue(const char* name) const; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h index 92a9ab789a..459dd601f9 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h @@ -49,8 +49,8 @@ namespace AZ //! MaterialFunctor objects provide custom logic and calculations to configure shaders, render states, //! editor metadata, and more. - //! Atom will provide an implementation of this class that uses a script to define the custom logic - //! for a convenient workflow. Clients may also provide their own custom hard-coded implementations + //! Atom provides a LuaMaterialFunctor subclass that uses a script to define the custom logic + //! for a convenient workflow. Developers may also provide their own custom hard-coded implementations //! as an optimization rather than taking the scripted approach. //! 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 diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialNameContext.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialNameContext.h new file mode 100644 index 0000000000..3ecdfeb449 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialNameContext.h @@ -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 + +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 diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp index c81b91d777..bf65a09cef 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp @@ -51,7 +51,7 @@ namespace AZ { AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor; 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("*.materialtype", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); materialBuilderDescriptor.m_busId = azrtti_typeid(); @@ -269,7 +269,7 @@ namespace AZ response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; } - AZ::Data::Asset CreateMaterialTypeAsset(AZStd::string_view materialTypeSourceFilePath, const rapidjson::Value& json) + AZ::Data::Asset CreateMaterialTypeAsset(AZStd::string_view materialTypeSourceFilePath, rapidjson::Document& json) { auto materialType = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourceFilePath, &json); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/LuaMaterialFunctorSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/LuaMaterialFunctorSourceData.cpp index 14ef3bb17d..c614222ee6 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/LuaMaterialFunctorSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/LuaMaterialFunctorSourceData.cpp @@ -115,16 +115,26 @@ namespace AZ RPI::LuaMaterialFunctorSourceData::FunctorResult LuaMaterialFunctorSourceData::CreateFunctor( const AZStd::string& materialTypeSourceFilePath, - const MaterialPropertiesLayout* propertiesLayout + const MaterialPropertiesLayout* propertiesLayout, + const MaterialNameContext* materialNameContext ) const { using namespace RPI; RPI::Ptr functor = aznew LuaMaterialFunctor; - functor->m_propertyNamePrefix = m_propertyNamePrefix; - functor->m_srgNamePrefix = m_srgNamePrefix; - functor->m_optionsNamePrefix = m_optionsNamePrefix; + if (materialNameContext->IsDefault()) + { + // This is a legacy feature that was used for a while to support reusing the same functor for multiple layers in StandardMultilayerPbr.materialtype. + // Now that we have support for nested property groups, this functionality is only supported for functors at the top level, for backward compatibility. + functor->m_materialNameContext.ExtendPropertyIdContext(m_propertyNamePrefix, false); + functor->m_materialNameContext.ExtendSrgInputContext(m_srgNamePrefix); + functor->m_materialNameContext.ExtendShaderOptionContext(m_optionsNamePrefix); + } + else + { + functor->m_materialNameContext = *materialNameContext; + } if (!m_luaScript.empty() && !m_luaSourceFile.empty()) { @@ -205,14 +215,16 @@ namespace AZ { return CreateFunctor( context.GetMaterialTypeSourceFilePath(), - context.GetMaterialPropertiesLayout()); + context.GetMaterialPropertiesLayout(), + context.GetNameContext()); } RPI::LuaMaterialFunctorSourceData::FunctorResult LuaMaterialFunctorSourceData::CreateFunctor(const EditorContext& context) const { return CreateFunctor( context.GetMaterialTypeSourceFilePath(), - context.GetMaterialPropertiesLayout()); + context.GetMaterialPropertiesLayout(), + context.GetNameContext()); } } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialFunctorSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialFunctorSourceData.cpp index 36217af1b6..cdc75b8c05 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialFunctorSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialFunctorSourceData.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -66,17 +67,20 @@ namespace AZ 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; } - 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); if (shaderOptionGroupLayout) @@ -92,8 +96,10 @@ namespace AZ 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); if (shaderOptionGroupLayout) @@ -122,19 +128,33 @@ namespace AZ AZ_Error("MaterialFunctorSourceData", valid, "Shader tag '%s' is invalid", shaderTag.GetCStr()); 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 { 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; } + } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp index 1f5581e417..843fa44dd7 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp @@ -25,11 +25,29 @@ namespace AZ 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 { return !m_fullName.IsEmpty(); } - + MaterialPropertyId MaterialPropertyId::Parse(AZStd::string_view fullPropertyId) { AZStd::vector tokens; @@ -94,14 +112,14 @@ namespace AZ { 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; } } 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; } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index b7808abf17..9b251213fe 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -89,10 +90,12 @@ namespace AZ ; serializeContext->Class() - ->Version(1) + ->Version(2) ->Field("name", &PropertyGroup::m_name) ->Field("displayName", &PropertyGroup::m_displayName) ->Field("description", &PropertyGroup::m_description) + ->Field("shaderInputsPrefix", &PropertyGroup::m_shaderInputsPrefix) + ->Field("shaderOptionsPrefix", &PropertyGroup::m_shaderOptionsPrefix) ->Field("properties", &PropertyGroup::m_properties) ->Field("propertyGroups", &PropertyGroup::m_propertyGroups) ->Field("functors", &PropertyGroup::m_materialFunctorSourceData) @@ -134,22 +137,21 @@ namespace AZ /*static*/ MaterialTypeSourceData::PropertyGroup* MaterialTypeSourceData::PropertyGroup::AddPropertyGroup(AZStd::string_view name, AZStd::vector>& toPropertyGroupList) { + if (!MaterialPropertyId::CheckIsValidName(name)) + { + return nullptr; + } + auto iter = AZStd::find_if(toPropertyGroupList.begin(), toPropertyGroupList.end(), [name](const AZStd::unique_ptr& existingPropertyGroup) { return existingPropertyGroup->m_name == name; }); - + if (iter != toPropertyGroupList.end()) { AZ_Error("Material source data", false, "PropertyGroup named '%.*s' already exists", AZ_STRING_ARG(name)); 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()); toPropertyGroupList.back()->m_name = name; @@ -158,6 +160,11 @@ namespace AZ 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& existingProperty) { return existingProperty->GetName() == name; @@ -180,12 +187,6 @@ namespace AZ 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(name)); return m_properties.back().get(); } @@ -265,10 +266,10 @@ namespace AZ if (!subPath.empty()) { - const MaterialTypeSourceData::PropertyGroup* propertySubset = FindPropertyGroup(subPath, propertyGroup->m_propertyGroups); - if (propertySubset) + const MaterialTypeSourceData::PropertyGroup* propertySubgroup = FindPropertyGroup(subPath, propertyGroup->m_propertyGroups); + if (propertySubgroup) { - return propertySubset; + return propertySubgroup; } } } @@ -376,18 +377,18 @@ namespace AZ return parts; } - bool MaterialTypeSourceData::EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertyGroupList) const + bool MaterialTypeSourceData::EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, MaterialNameContext materialNameContext, const AZStd::vector>& inPropertyGroupList) const { for (auto& propertyGroup : inPropertyGroupList) { - if (!callback(propertyIdContext, propertyGroup.get())) + if (!callback(materialNameContext, propertyGroup.get())) { return false; // Stop processing } + + MaterialNameContext materialNameContext2 = ExtendNameContext(materialNameContext, *propertyGroup); - const AZStd::string propertyIdContext2 = propertyIdContext + propertyGroup->m_name + "."; - - if (!EnumeratePropertyGroups(callback, propertyIdContext2, propertyGroup->m_propertyGroups)) + if (!EnumeratePropertyGroups(callback, materialNameContext2, propertyGroup->m_propertyGroups)) { return false; // Stop processing } @@ -406,22 +407,21 @@ namespace AZ return EnumeratePropertyGroups(callback, {}, m_propertyLayout.m_propertyGroups); } - bool MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertyGroupList) const + bool MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback, MaterialNameContext materialNameContext, const AZStd::vector>& inPropertyGroupList) const { - for (auto& propertyGroup : inPropertyGroupList) { - const AZStd::string propertyIdContext2 = propertyIdContext + propertyGroup->m_name + "."; + MaterialNameContext materialNameContext2 = ExtendNameContext(materialNameContext, *propertyGroup); for (auto& property : propertyGroup->m_properties) { - if (!callback(propertyIdContext2, property.get())) + if (!callback(materialNameContext2, property.get())) { return false; // Stop processing } } - if (!EnumerateProperties(callback, propertyIdContext2, propertyGroup->m_propertyGroups)) + if (!EnumerateProperties(callback, materialNameContext2, propertyGroup->m_propertyGroups)) { return false; // Stop processing } @@ -483,7 +483,7 @@ namespace AZ 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) { @@ -529,24 +529,40 @@ namespace AZ 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( const AZStd::string& materialTypeSourceFilePath, MaterialTypeAssetCreator& materialTypeAssetCreator, - AZStd::vector& propertyIdContext, + MaterialNameContext materialNameContext, const MaterialTypeSourceData::PropertyGroup* propertyGroup) const - { + { + if (!MaterialPropertyId::CheckIsValidName(propertyGroup->m_name)) + { + return false; + } + + materialNameContext = ExtendNameContext(materialNameContext, *propertyGroup); + for (const AZStd::unique_ptr& property : propertyGroup->m_properties) { // Register the property... - MaterialPropertyId propertyId{propertyIdContext, property->GetName()}; - - if (!propertyId.IsValid()) + if (!MaterialPropertyId::CheckIsValidName(property->GetName())) { - // MaterialPropertyId reports an error message return false; } + Name propertyId{property->GetName()}; + materialNameContext.ContextualizeProperty(propertyId); + auto propertyGroupIter = AZStd::find_if(propertyGroup->GetPropertyGroups().begin(), propertyGroup->GetPropertyGroups().end(), [&property](const AZStd::unique_ptr& existingPropertyGroup) { @@ -572,18 +588,23 @@ namespace AZ { case MaterialPropertyOutputType::ShaderInput: { - materialTypeAssetCreator.ConnectMaterialPropertyToShaderInput(Name{output.m_fieldName}); + Name fieldName{output.m_fieldName}; + materialNameContext.ContextualizeSrgInput(fieldName); + materialTypeAssetCreator.ConnectMaterialPropertyToShaderInput(fieldName); break; } case MaterialPropertyOutputType::ShaderOption: { + Name fieldName{output.m_fieldName}; + materialNameContext.ContextualizeShaderOption(fieldName); + if (output.m_shaderIndex >= 0) { - materialTypeAssetCreator.ConnectMaterialPropertyToShaderOption(Name{output.m_fieldName}, output.m_shaderIndex); + materialTypeAssetCreator.ConnectMaterialPropertyToShaderOption(fieldName, output.m_shaderIndex); } else { - materialTypeAssetCreator.ConnectMaterialPropertyToShaderOptions(Name{output.m_fieldName}); + materialTypeAssetCreator.ConnectMaterialPropertyToShaderOptions(fieldName); } break; } @@ -650,17 +671,13 @@ namespace AZ } } - for (const AZStd::unique_ptr& propertySubset : propertyGroup->m_propertyGroups) + for (const AZStd::unique_ptr& propertySubgroup : propertyGroup->m_propertyGroups) { - propertyIdContext.push_back(propertySubset->m_name); - bool success = BuildPropertyList( materialTypeSourceFilePath, materialTypeAssetCreator, - propertyIdContext, - propertySubset.get()); - - propertyIdContext.pop_back(); + materialNameContext, + propertySubgroup.get()); if (!success) { @@ -677,7 +694,8 @@ namespace AZ materialTypeSourceFilePath, materialTypeAssetCreator.GetMaterialPropertiesLayout(), materialTypeAssetCreator.GetMaterialShaderResourceGroupLayout(), - materialTypeAssetCreator.GetShaderCollection() + materialTypeAssetCreator.GetShaderCollection(), + &materialNameContext ) ); @@ -688,9 +706,10 @@ namespace AZ { 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); } } } @@ -795,11 +814,9 @@ namespace AZ } } - for (const AZStd::unique_ptr& propertyGroup : m_propertyLayout.m_propertyGroups) + for (const AZStd::unique_ptr& propertyGroup : m_propertyLayout.m_propertyGroups) { - AZStd::vector propertyIdContext; - propertyIdContext.push_back(propertyGroup->m_name); - bool success = BuildPropertyList(materialTypeSourceFilePath, materialTypeAssetCreator, propertyIdContext, propertyGroup.get()); + bool success = BuildPropertyList(materialTypeSourceFilePath, materialTypeAssetCreator, MaterialNameContext{}, propertyGroup.get()); if (!success) { @@ -807,6 +824,8 @@ namespace AZ } } + MaterialNameContext nameContext; + // We cannot create the MaterialFunctor until after all the properties are added because // CreateFunctor() may need to look up properties in the MaterialPropertiesLayout for (auto& functorData : m_materialFunctorSourceData) @@ -816,7 +835,8 @@ namespace AZ materialTypeSourceFilePath, materialTypeAssetCreator.GetMaterialPropertiesLayout(), materialTypeAssetCreator.GetMaterialShaderResourceGroupLayout(), - materialTypeAssetCreator.GetShaderCollection() + materialTypeAssetCreator.GetShaderCollection(), + &nameContext ) ); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/MaterialSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/MaterialSystem.cpp index b76fa9d676..ae7fe6cc60 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/MaterialSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/MaterialSystem.cpp @@ -27,6 +27,7 @@ namespace AZ MaterialAsset::Reflect(context); MaterialPropertiesLayout::Reflect(context); MaterialFunctor::Reflect(context); + MaterialNameContext::Reflect(context); LuaMaterialFunctor::Reflect(context); ReflectMaterialDynamicMetadata(context); } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp index 4212583e60..7eedb82ecc 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp @@ -29,9 +29,7 @@ namespace AZ serializeContext->Class() ->Version(1) ->Field("scriptAsset", &LuaMaterialFunctor::m_scriptAsset) - ->Field("propertyNamePrefix", &LuaMaterialFunctor::m_propertyNamePrefix) - ->Field("srgNamePrefix", &LuaMaterialFunctor::m_srgNamePrefix) - ->Field("optionsNamePrefix", &LuaMaterialFunctor::m_optionsNamePrefix) + ->Field("materialNameContext", &LuaMaterialFunctor::m_materialNameContext) ; } } @@ -127,7 +125,7 @@ namespace AZ if (m_scriptStatus == ScriptStatus::Ready) { - LuaMaterialFunctorRuntimeContext luaContext{&context, &GetMaterialPropertyDependencies(), m_propertyNamePrefix, m_srgNamePrefix, m_optionsNamePrefix}; + LuaMaterialFunctorRuntimeContext luaContext{&context, &GetMaterialPropertyDependencies(), m_materialNameContext}; AZ::ScriptDataContext call; if (m_scriptContext->Call("Process", call)) { @@ -145,7 +143,7 @@ namespace AZ if (m_scriptStatus == ScriptStatus::Ready) { - LuaMaterialFunctorEditorContext luaContext{&context, &GetMaterialPropertyDependencies(), m_propertyNamePrefix, m_srgNamePrefix, m_optionsNamePrefix}; + LuaMaterialFunctorEditorContext luaContext{&context, &GetMaterialPropertyDependencies(), m_materialNameContext}; AZ::ScriptDataContext call; if (m_scriptContext->Call("ProcessEditor", call)) { @@ -157,27 +155,19 @@ namespace AZ LuaMaterialFunctorCommonContext::LuaMaterialFunctorCommonContext(MaterialFunctor::RuntimeContext* runtimeContextImpl, const MaterialPropertyFlags* materialPropertyDependencies, - const AZStd::string& propertyNamePrefix, - const AZStd::string& srgNamePrefix, - const AZStd::string& optionsNamePrefix) + const MaterialNameContext& materialNameContext) : m_runtimeContextImpl(runtimeContextImpl) , m_materialPropertyDependencies(materialPropertyDependencies) - , m_propertyNamePrefix(propertyNamePrefix) - , m_srgNamePrefix(srgNamePrefix) - , m_optionsNamePrefix(optionsNamePrefix) + , m_materialNameContext(materialNameContext) { } LuaMaterialFunctorCommonContext::LuaMaterialFunctorCommonContext(MaterialFunctor::EditorContext* editorContextImpl, const MaterialPropertyFlags* materialPropertyDependencies, - const AZStd::string& propertyNamePrefix, - const AZStd::string& srgNamePrefix, - const AZStd::string& optionsNamePrefix) + const MaterialNameContext& materialNameContext) : m_editorContextImpl(editorContextImpl) , m_materialPropertyDependencies(materialPropertyDependencies) - , m_propertyNamePrefix(propertyNamePrefix) - , m_srgNamePrefix(srgNamePrefix) - , m_optionsNamePrefix(optionsNamePrefix) + , m_materialNameContext(materialNameContext) { } @@ -256,7 +246,8 @@ namespace AZ { MaterialPropertyIndex propertyIndex; - Name propertyFullName{m_propertyNamePrefix + name}; + Name propertyFullName{name}; + m_materialNameContext.ContextualizeProperty(propertyFullName); propertyIndex = GetMaterialPropertiesLayout()->FindPropertyIndex(propertyFullName); @@ -361,10 +352,8 @@ namespace AZ LuaMaterialFunctorRuntimeContext::LuaMaterialFunctorRuntimeContext(MaterialFunctor::RuntimeContext* runtimeContextImpl, const MaterialPropertyFlags* materialPropertyDependencies, - const AZStd::string& propertyNamePrefix, - const AZStd::string& srgNamePrefix, - const AZStd::string& optionsNamePrefix) - : LuaMaterialFunctorCommonContext(runtimeContextImpl, materialPropertyDependencies, propertyNamePrefix, srgNamePrefix, optionsNamePrefix) + const MaterialNameContext& materialNameContext) + : LuaMaterialFunctorCommonContext(runtimeContextImpl, materialPropertyDependencies, materialNameContext) , m_runtimeContextImpl(runtimeContextImpl) { } @@ -379,7 +368,8 @@ namespace AZ { 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) { @@ -429,7 +419,8 @@ namespace AZ 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); @@ -524,10 +515,8 @@ namespace AZ LuaMaterialFunctorEditorContext::LuaMaterialFunctorEditorContext(MaterialFunctor::EditorContext* editorContextImpl, const MaterialPropertyFlags* materialPropertyDependencies, - const AZStd::string& propertyNamePrefix, - const AZStd::string& srgNamePrefix, - const AZStd::string& optionsNamePrefix) - : LuaMaterialFunctorCommonContext(editorContextImpl, materialPropertyDependencies, propertyNamePrefix, srgNamePrefix, optionsNamePrefix) + const MaterialNameContext& materialNameContext) + : LuaMaterialFunctorCommonContext(editorContextImpl, materialPropertyDependencies, materialNameContext) , m_editorContextImpl(editorContextImpl) { } @@ -598,7 +587,9 @@ namespace AZ { 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; } @@ -607,7 +598,9 @@ namespace AZ { 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; } @@ -616,7 +609,9 @@ namespace AZ { 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; } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialNameContext.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialNameContext.cpp new file mode 100644 index 0000000000..bf457a0a93 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialNameContext.cpp @@ -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 +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + void MaterialNameContext::Reflect(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->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 diff --git a/Gems/Atom/RPI/Code/Tests/Material/LuaMaterialFunctorTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/LuaMaterialFunctorTests.cpp index 15f66916a6..1ed920ebc0 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/LuaMaterialFunctorTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/LuaMaterialFunctorTests.cpp @@ -35,11 +35,14 @@ namespace UnitTest LuaMaterialFunctorSourceData functorSourceData; functorSourceData.m_luaScript = script; + MaterialNameContext nameContext; + MaterialFunctorSourceData::RuntimeContext createFunctorContext{ "Dummy.materialtype", materialTypeCreator.GetMaterialPropertiesLayout(), materialTypeCreator.GetMaterialShaderResourceGroupLayout(), - materialTypeCreator.GetShaderCollection() + materialTypeCreator.GetShaderCollection(), + &nameContext }; MaterialFunctorSourceData::FunctorResult result = functorSourceData.CreateFunctor(createFunctorContext); diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialFunctorTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialFunctorTests.cpp index 08049f3393..dbf911b09a 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialFunctorTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialFunctorTests.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -256,12 +258,15 @@ namespace UnitTest functorSourceData.m_registedPropertyName = registedPropertyName.GetStringView(); functorSourceData.m_unregistedPropertyName = unregistedPropertyName.GetStringView(); + MaterialNameContext nameContext; + MaterialFunctorSourceData::FunctorResult result = functorSourceData.CreateFunctor( MaterialFunctorSourceData::RuntimeContext( "Dummy.materialtype", materialTypeCreator.GetMaterialPropertiesLayout(), materialTypeCreator.GetMaterialShaderResourceGroupLayout(), - materialTypeCreator.GetShaderCollection() + materialTypeCreator.GetShaderCollection(), + &nameContext ) ); @@ -310,4 +315,169 @@ namespace UnitTest m_testMaterialTypeAsset = {}; 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 functor = aznew FindPropertyIndexTestFunctor; + functor->m_foundIndex = runtimeContext.FindMaterialPropertyIndex(m_materialPropertyName); + return Success(RPI::Ptr(functor)); + } + }; + + Data::Asset 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 functor = sourceData.CreateFunctor(createFunctorContext).TakeValue(); + + EXPECT_TRUE(reinterpret_cast(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 functor = aznew FindShaderOptionIndexTestFunctor; + functor->m_foundIndex = runtimeContext.FindShaderOptionIndex(0, m_shaderOptionName); + return Success(RPI::Ptr(functor)); + } + }; + + RPI::Ptr shaderOptionLayout = RPI::ShaderOptionGroupLayout::Create(); + shaderOptionLayout->AddShaderOption( + RPI::ShaderOptionDescriptor{Name("o_layer1_baseColor_useTexture"), RPI::ShaderOptionType::Boolean, 0, 0, CreateBoolShaderOptionValues()}); + shaderOptionLayout->Finalize(); + + Data::Asset shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), nullptr, shaderOptionLayout); + + Data::Asset 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 functor = sourceData.CreateFunctor(createFunctorContext).TakeValue(); + + EXPECT_TRUE(reinterpret_cast(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 functor = aznew FindShaderInputIndexTestFunctor; + functor->m_foundConstantIndex = runtimeContext.FindShaderInputConstantIndex(m_shaderConstantName); + functor->m_foundImageIndex = runtimeContext.FindShaderInputImageIndex(m_shaderImageName); + return Success(RPI::Ptr(functor)); + } + }; + + AZ::RHI::Ptr srgLayout = RHI::ShaderResourceGroupLayout::Create(); + srgLayout->SetName(Name("MaterialSrg")); + srgLayout->SetUniqueId(Uuid::CreateRandom().ToString()); // 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 = CreateTestShaderAsset(Uuid::CreateRandom(), srgLayout); + + Data::Asset 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 functor = sourceData.CreateFunctor(createFunctorContext).TakeValue(); + + EXPECT_TRUE(reinterpret_cast(functor.get())->m_foundConstantIndex.IsValid()); + EXPECT_TRUE(reinterpret_cast(functor.get())->m_foundImageIndex.IsValid()); + } + + + } diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertyValueSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertyValueSourceDataTests.cpp index 5dde21ece1..f67a58cf97 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertyValueSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertyValueSourceDataTests.cpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace JsonSerializationTests { @@ -256,13 +257,16 @@ namespace UnitTest JsonTestResult loadResult = LoadTestDataFromJson(*functorData, inputJson); + MaterialNameContext nameContext; + // Where type resolving happens. MaterialFunctorSourceData::FunctorResult functorResult = functorData->CreateFunctor( MaterialFunctorSourceData::RuntimeContext( "Dummy.materialtype", m_materialTypeCreator.GetMaterialPropertiesLayout(), m_materialTypeCreator.GetMaterialShaderResourceGroupLayout(), - m_materialTypeCreator.GetShaderCollection() + m_materialTypeCreator.GetShaderCollection(), + &nameContext ) ); diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp index 5be02df935..98a86fe9ce 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -252,6 +253,57 @@ namespace UnitTest 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(context)) + { + serializeContext->Class() + ->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(context)) + { + serializeContext->Class() + ->Version(1) + ; + } + } + + using MaterialFunctorSourceData::CreateFunctor; + FunctorResult CreateFunctor([[maybe_unused]] const RuntimeContext& context) const override + { + Ptr functor = aznew SaveNameContextTestFunctor; + functor->m_nameContext = *context.GetNameContext(); + return Success(Ptr(functor)); + } + }; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void Reflect(ReflectContext* context) override @@ -265,6 +317,7 @@ namespace UnitTest Splat3FunctorSourceData::Reflect(context); EnableShaderFunctorSourceData::Reflect(context); SetShaderOptionFunctorSourceData::Reflect(context); + SaveNameContextTestFunctorSourceData::Reflect(context); } void SetUp() override @@ -276,6 +329,7 @@ namespace UnitTest AZ::RPI::MaterialFunctorSourceDataRegistration::Get()->RegisterMaterialFunctor("Splat3", azrtti_typeid()); AZ::RPI::MaterialFunctorSourceDataRegistration::Get()->RegisterMaterialFunctor("EnableShader", azrtti_typeid()); AZ::RPI::MaterialFunctorSourceDataRegistration::Get()->RegisterMaterialFunctor("SetShaderOption", azrtti_typeid()); + AZ::RPI::MaterialFunctorSourceDataRegistration::Get()->RegisterMaterialFunctor("SaveNameContext", azrtti_typeid()); const Name materialSrgId{"MaterialSrg"}; m_testMaterialSrgLayout = RHI::ShaderResourceGroupLayout::Create(); @@ -432,20 +486,25 @@ namespace UnitTest struct EnumeratePropertyGroupsResult { - AZStd::string m_propertyIdContext; + MaterialNameContext m_materialNameContext; const MaterialTypeSourceData::PropertyGroup* m_propertyGroup; 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); } }; AZStd::vector 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; }); @@ -466,20 +525,25 @@ namespace UnitTest struct EnumeratePropertiesResult { - AZStd::string m_propertyIdContext; + MaterialNameContext m_materialNameContext; const MaterialTypeSourceData::PropertyDefinition* m_propertyDefinition; 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); } }; AZStd::vector 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; }); @@ -1469,7 +1533,7 @@ namespace UnitTest // Note that serialization of individual fields within material properties is thoroughly tested in // 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 - // "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"( { @@ -2014,4 +2078,140 @@ namespace UnitTest EXPECT_EQ(materialType.FindProperty("myGroup.bar")->m_dataType, MaterialPropertyDataType::Float); } + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_NestedGroupNameContext) + { + const Name materialSrgId{"MaterialSrg"}; + RHI::Ptr 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 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 = 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 = 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(materialTypeAsset->GetMaterialFunctors()[0].get())); + + auto saveNameContextFunctor = azrtti_cast(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()); + } + } diff --git a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake index 6e9cdfeb4e..c28911f4af 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake @@ -53,6 +53,7 @@ set(FILES Include/Atom/RPI.Reflect/Material/MaterialAsset.h Include/Atom/RPI.Reflect/Material/MaterialAssetCreator.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/MaterialPropertiesLayout.h Include/Atom/RPI.Reflect/Material/MaterialPropertyValue.h @@ -133,6 +134,7 @@ set(FILES Source/RPI.Reflect/Material/MaterialPropertyValue.cpp Source/RPI.Reflect/Material/MaterialAsset.cpp Source/RPI.Reflect/Material/MaterialAssetCreator.cpp + Source/RPI.Reflect/Material/MaterialNameContext.cpp Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index c441762710..aaad77266c 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -585,9 +586,10 @@ namespace MaterialEditor bool result = true; // 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); 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 // Assets must still be used for now because they contain the final accumulated value after all other materials // 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; for (const auto& propertyDefinition : propertyGroup->GetProperties()) { // 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 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. + AZ::RPI::MaterialNameContext materialNameContext; // There is no name context for top-level functors, only functors inside PropertyGroups 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 functorData : m_materialTypeSourceData.m_materialFunctorSourceData) { MaterialFunctorSourceData::FunctorResult result2 = functorData->CreateFunctor(editorContext); @@ -901,10 +907,10 @@ namespace MaterialEditor // Add any material functors that are located inside each property group. 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( - m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); + m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout(), &nameContext); for (Ptr functorData : propertyGroup->GetFunctors()) { diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index dde81e77b5..376aba2208 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -80,7 +81,7 @@ namespace AZ m_materialAssignmentId); } - if (!materialAssetId.IsValid()) + if (!materialAssetId.IsValid()) { UnloadMaterial(); return false; @@ -104,8 +105,9 @@ namespace AZ // Get a list of all the editor functors to be used for property editor states 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 = - AZ::RPI::MaterialFunctorSourceData::EditorContext(m_editData.m_materialTypeSourcePath, propertyLayout); + AZ::RPI::MaterialFunctorSourceData::EditorContext(m_editData.m_materialTypeSourcePath, propertyLayout, &materialNameContext); for (AZ::RPI::Ptr functorData : m_editData.m_materialTypeSourceData.m_materialFunctorSourceData) { AZ::RPI::MaterialFunctorSourceData::FunctorResult createResult = functorData->CreateFunctor(editorContext); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp index dde9644c50..320d5e2e08 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -113,9 +114,11 @@ namespace AZ // Copy all of the properties from the material asset to the source data that will be exported 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 = editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId);