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 b5e7eecd0d..0daa0a4c24 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 @@ -23,8 +23,10 @@ namespace AZ { class MaterialTypeAsset; class MaterialFunctorSourceDataHolder; + class JsonMaterialPropertySerializer; //! This is a simple data structure for serializing in/out material type source files. + //! Note that there may be a mixture of public and private members, as we are gradually introducing a proper API. class MaterialTypeSourceData final { public: @@ -69,15 +71,22 @@ namespace AZ struct PropertyDefinition { + friend class JsonMaterialPropertySerializer; + AZ_CLASS_ALLOCATOR(PropertyDefinition, SystemAllocator, 0); AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertyDefinition, "{E0DB3C0D-75DB-4ADB-9E79-30DA63FA18B7}"); static const float DefaultMin; static const float DefaultMax; static const float DefaultStep; + + PropertyDefinition() = default; - // TODO: Consider making this private and readonly because it is used as the key for lookups and collision validation. - AZStd::string m_name; //!< The name of the property within the property group. The full property ID will be groupName.propertyName. + explicit PropertyDefinition(AZStd::string_view name) : m_name(name) + { + } + + const AZStd::string& GetName() const { return m_name; } MaterialPropertyVisibility m_visibility = MaterialPropertyVisibility::Default; @@ -99,6 +108,51 @@ namespace AZ MaterialPropertyValue m_softMin; MaterialPropertyValue m_softMax; MaterialPropertyValue m_step; + + private: + + // We are gradually moving toward having a more proper API for MaterialTypeSourceData code, but we still some public members + // like above. However, it's important for m_name to be private because it is used as the key for lookups, collision validation, etc. + AZStd::string m_name; //!< The name of the property within the property group. The full property ID will be groupName.propertyName. + }; + + using PropertyList = AZStd::vector>; + + struct PropertySet + { + friend class MaterialTypeSourceData; + + AZ_CLASS_ALLOCATOR(PropertySet, SystemAllocator, 0); + AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertySet, "{BA3AA0E4-C74D-4FD0-ADB2-00B060F06314}"); + + public: + + PropertySet() = default; + AZ_DISABLE_COPY(PropertySet) + + const AZStd::string& GetName() const { return m_name; } + const AZStd::string& GetDisplayName() const { return m_displayName; } + const AZStd::string& GetDescription() const { return m_description; } + const PropertyList& GetProperties() const { return m_properties; } + const AZStd::vector>& GetPropertySets() const { return m_propertySets; } + const AZStd::vector>& GetFunctors() const { return m_materialFunctorSourceData; } + + void SetDisplayName(AZStd::string_view displayName) { m_displayName = displayName; } + void SetDescription(AZStd::string_view description) { m_description = description; } + + PropertyDefinition* AddProperty(AZStd::string_view name); + PropertySet* AddPropertySet(AZStd::string_view name); + + private: + + static PropertySet* AddPropertySet(AZStd::string_view name, AZStd::vector>& toPropertySetList); + + AZStd::string m_name; + AZStd::string m_displayName; + AZStd::string m_description; + PropertyList m_properties; + AZStd::vector> m_propertySets; + AZStd::vector> m_materialFunctorSourceData; }; struct ShaderVariantReferenceData @@ -144,47 +198,6 @@ namespace AZ using VersionUpdates = AZStd::vector; - using PropertyList = AZStd::vector>; - - struct PropertySet - { - friend class MaterialTypeSourceData; - - AZ_CLASS_ALLOCATOR(PropertySet, SystemAllocator, 0); - AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertySet, "{BA3AA0E4-C74D-4FD0-ADB2-00B060F06314}"); - - public: - - PropertySet() = default; - AZ_DISABLE_COPY(PropertySet) - - const AZStd::string& GetName() const { return m_name; } - const AZStd::string& GetDisplayName() const { return m_displayName; } - const AZStd::string& GetDescription() const { return m_description; } - const PropertyList& GetProperties() const { return m_properties; } - const AZStd::vector>& GetPropertySets() const { return m_propertySets; } - const AZStd::vector>& GetFunctors() const { return m_materialFunctorSourceData; } - - void SetDisplayName(AZStd::string_view displayName) { m_displayName = displayName; } - void SetDescription(AZStd::string_view description) { m_description = description; } - - PropertyDefinition* AddProperty(AZStd::string_view name); - PropertySet* AddPropertySet(AZStd::string_view name); - - private: - - static PropertySet* AddPropertySet(AZStd::string_view name, AZStd::vector>& toPropertySetList); - - AZStd::string m_name; - AZStd::string m_displayName; - AZStd::string m_description; - PropertyList m_properties; - AZStd::vector> m_propertySets; - AZStd::vector> m_materialFunctorSourceData; - }; - - using VersionUpdates = AZStd::vector; - struct PropertyLayout { AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertyLayout, "{AE53CF3F-5C3B-44F5-B2FB-306F0EB06393}"); @@ -206,52 +219,69 @@ namespace AZ AZStd::vector> m_propertySets; }; - PropertySet* AddPropertySet(AZStd::string_view propertySetId); - PropertyDefinition* AddProperty(AZStd::string_view propertyId); - - const PropertyLayout& GetPropertyLayout() const { return m_propertyLayout; } - - AZStd::string m_description; //< TODO: Make this private + AZStd::string m_description; //! Version 1 is the default and should not contain any version update. - uint32_t m_version = 1; //< TODO: Make this private - - VersionUpdates m_versionUpdates; //< TODO: Make this private + uint32_t m_version = 1; + + VersionUpdates m_versionUpdates; //! A list of shader variants that are always used at runtime; they cannot be turned off - AZStd::vector m_shaderCollection; //< TODO: Make this private + AZStd::vector m_shaderCollection; //! Material functors provide custom logic and calculations to configure shaders, render states, and more. See MaterialFunctor.h for details. - AZStd::vector> m_materialFunctorSourceData; //< TODO: Make this private + AZStd::vector> m_materialFunctorSourceData; //! Override names for UV input in the shaders of this material type. //! Using ordered map to sort names on loading. using UvNameMap = AZStd::map; - UvNameMap m_uvNameMap; //< TODO: Make this private + UvNameMap m_uvNameMap; //! Copy over UV custom names to the properties enum values. void ResolveUvEnums(); - - const PropertySet* FindPropertySet(AZStd::string_view propertySetId) const; + //! Add a new PropertySet for containing properties or other PropertySets. + //! @param propertySetId The ID of the new property set. To add as a nested PropertySet, use a full path ID like "levelA.levelB.levelC"; in this case a property set "levelA.levelB" must already exist. + //! @return a pointer to the new PropertySet or null if there was a problem (an AZ_Error will be reported). + PropertySet* AddPropertySet(AZStd::string_view propertySetId); + + //! Add a new property to a PropertySet. + //! @param propertyId The ID of the new property, like "layerBlend.factor" or "layer2.roughness.texture". The indicated property set must already exist. + //! @return a pointer to the new PropertyDefinition or null if there was a problem (an AZ_Error will be reported). + PropertyDefinition* AddProperty(AZStd::string_view propertyId); + + //! Return the PropertyLayout containing the tree of property sets and property definitions. + const PropertyLayout& GetPropertyLayout() const { return m_propertyLayout; } + + //! Find the PropertySet with the given ID. + //! @param propertySetId The full ID of a property set to find, like "levelA.levelB.levelC". + //! @return the found PropertySet or null if it doesn't exist. + const PropertySet* FindPropertySet(AZStd::string_view propertySetId) const; + + //! Find the definition for a property with the given ID. + //! @param propertyId The full ID of a property to find, like "baseColor.texture". + //! @return the found PropertyDefinition or null if it doesn't exist. const PropertyDefinition* FindProperty(AZStd::string_view propertyId) const; - //! Tokenizes an ID string like "itemA.itemB.itemC" into a vector like ["itemA", "itemB", "itemC"] + //! Tokenizes an ID string like "itemA.itemB.itemC" into a vector like ["itemA", "itemB", "itemC"]. static AZStd::vector TokenizeId(AZStd::string_view id); - //! Splits an ID string like "itemA.itemB.itemC" into a vector like ["itemA.itemB", "itemC"] + //! Splits an ID string like "itemA.itemB.itemC" into a vector like ["itemA.itemB", "itemC"]. static AZStd::vector SplitId(AZStd::string_view id); - //! Call back function type used with the enumeration functions + //! Call back function type used with the enumeration functions. + //! Return false to terminate the traversal. using EnumeratePropertySetsCallback = AZStd::function; + //! Recursively traverses all of the property sets contained in the material type, executing a callback function for each. //! @return false if the enumeration was terminated early by the callback returning false. bool EnumeratePropertySets(const EnumeratePropertySetsCallback& callback) const; - //! Call back function type used with the numeration functions + //! Call back function type used with the numeration functions. + //! Return false to terminate the traversal. using EnumeratePropertiesCallback = AZStd::function> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const; + //! If the data was loaded from an old format file (i.e. where "groups" and "properties" were separate sections), + //! this converts to the new format where properties are listed inside property sets. bool ConvertToNewDataFormat(); private: 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 bf24b98ac8..046dee9507 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -160,7 +160,7 @@ namespace AZ { auto propertyIter = AZStd::find_if(m_properties.begin(), m_properties.end(), [name](const AZStd::unique_ptr& existingProperty) { - return existingProperty->m_name == name; + return existingProperty->GetName() == name; }); if (propertyIter != m_properties.end()) @@ -186,8 +186,7 @@ namespace AZ return nullptr; } - m_properties.emplace_back(AZStd::make_unique()); - m_properties.back()->m_name = name; + m_properties.emplace_back(AZStd::make_unique(name)); return m_properties.back().get(); } @@ -195,7 +194,7 @@ namespace AZ { auto iter = AZStd::find_if(m_properties.begin(), m_properties.end(), [name](const AZStd::unique_ptr& existingProperty) { - return existingProperty->m_name == name; + return existingProperty->GetName() == name; }); if (iter != m_properties.end()) @@ -298,7 +297,7 @@ namespace AZ { for (AZStd::unique_ptr& property : propertySet->m_properties) { - if (property->m_name == subPath[0]) + if (property->GetName() == subPath[0]) { return property.get(); } @@ -440,7 +439,7 @@ namespace AZ propertySet = m_propertyLayout.m_propertySets.back().get(); } - PropertyDefinition* newProperty = propertySet->AddProperty(propertyDefinition.m_name); + PropertyDefinition* newProperty = propertySet->AddProperty(propertyDefinition.GetName()); *newProperty = propertyDefinition; } @@ -518,7 +517,7 @@ namespace AZ { // Register the property... - MaterialPropertyId propertyId{propertyNameContext, property->m_name}; + MaterialPropertyId propertyId{propertyNameContext, property->GetName()}; if (!propertyId.IsValid()) { @@ -529,7 +528,7 @@ namespace AZ auto propertySetIter = AZStd::find_if(propertySet->GetPropertySets().begin(), propertySet->GetPropertySets().end(), [&property](const AZStd::unique_ptr& existingPropertySet) { - return existingPropertySet->GetName() == property->m_name; + return existingPropertySet->GetName() == property->GetName(); }); if (propertySetIter != propertySet->GetPropertySets().end()) diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp index 9afc05a237..904860d4fc 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp @@ -45,8 +45,7 @@ namespace JsonSerializationTests AZStd::shared_ptr CreatePartialDefaultInstance() override { - auto result = AZStd::make_shared(); - result->m_name = "testProperty"; + auto result = AZStd::make_shared("testProperty"); result->m_dataType = AZ::RPI::MaterialPropertyDataType::Float; result->m_step = 1.0f; result->m_value = 0.0f; @@ -65,8 +64,7 @@ namespace JsonSerializationTests AZStd::shared_ptr CreateFullySetInstance() override { - auto result = AZStd::make_shared(); - result->m_name = "testProperty"; + auto result = AZStd::make_shared("testProperty"); result->m_description = "description"; result->m_displayName = "display_name"; result->m_dataType = AZ::RPI::MaterialPropertyDataType::Float; @@ -135,7 +133,7 @@ namespace JsonSerializationTests const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& lhs, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& rhs) override { - if (lhs.m_name != rhs.m_name) { return false; } + if (lhs.GetName() != rhs.GetName()) { return false; } if (lhs.m_description != rhs.m_description) { return false; } if (lhs.m_displayName != rhs.m_displayName) { return false; } if (lhs.m_dataType != rhs.m_dataType) { return false; } @@ -216,7 +214,7 @@ namespace UnitTest EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::PartialDefaults, loadResult.m_jsonResultCode.GetOutcome()); - EXPECT_EQ("testProperty", propertyData.m_name); + EXPECT_EQ("testProperty", propertyData.GetName()); EXPECT_EQ("Test Property", propertyData.m_displayName); EXPECT_EQ("This is a property description", propertyData.m_description); EXPECT_EQ(MaterialPropertyDataType::Float, propertyData.m_dataType); @@ -851,7 +849,7 @@ namespace UnitTest EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask()); EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); - EXPECT_EQ("testProperty", propertyData.m_name); + EXPECT_EQ("testProperty", propertyData.GetName()); EXPECT_EQ(1, propertyData.m_outputConnections.size()); EXPECT_EQ(MaterialPropertyOutputType::ShaderOption, propertyData.m_outputConnections[0].m_type); @@ -934,7 +932,7 @@ namespace UnitTest EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask()); EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); - EXPECT_EQ(propertyData.m_name, "testProperty"); + EXPECT_EQ(propertyData.GetName(), "testProperty"); EXPECT_EQ(propertyData.m_dataType, MaterialPropertyDataType::Float); EXPECT_EQ(propertyData.m_outputConnections.size(), 0); @@ -964,7 +962,7 @@ namespace UnitTest EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask()); EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); - EXPECT_EQ(propertyData.m_name, "testProperty"); + EXPECT_EQ(propertyData.GetName(), "testProperty"); EXPECT_EQ(propertyData.m_dataType, MaterialPropertyDataType::Float); EXPECT_EQ(propertyData.m_outputConnections.size(), 1); EXPECT_EQ(propertyData.m_outputConnections[0].m_fieldName, "o_foo"); diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp index f07e1da6c2..2ecf2369f9 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp @@ -1645,12 +1645,12 @@ namespace UnitTest EXPECT_NE(material.FindProperty("groupC.groupD.foo"), nullptr); EXPECT_NE(material.FindProperty("groupC.groupE.bar"), nullptr); - EXPECT_EQ(material.FindProperty("groupA.foo")->m_name, "foo"); - EXPECT_EQ(material.FindProperty("groupA.bar")->m_name, "bar"); - EXPECT_EQ(material.FindProperty("groupB.foo")->m_name, "foo"); - EXPECT_EQ(material.FindProperty("groupB.bar")->m_name, "bar"); - EXPECT_EQ(material.FindProperty("groupC.groupD.foo")->m_name, "foo"); - EXPECT_EQ(material.FindProperty("groupC.groupE.bar")->m_name, "bar"); + EXPECT_EQ(material.FindProperty("groupA.foo")->GetName(), "foo"); + EXPECT_EQ(material.FindProperty("groupA.bar")->GetName(), "bar"); + EXPECT_EQ(material.FindProperty("groupB.foo")->GetName(), "foo"); + EXPECT_EQ(material.FindProperty("groupB.bar")->GetName(), "bar"); + EXPECT_EQ(material.FindProperty("groupC.groupD.foo")->GetName(), "foo"); + EXPECT_EQ(material.FindProperty("groupC.groupE.bar")->GetName(), "bar"); EXPECT_EQ(material.FindProperty("groupA.foo")->m_dataType, MaterialPropertyDataType::Bool); EXPECT_EQ(material.FindProperty("groupA.bar")->m_dataType, MaterialPropertyDataType::Image); EXPECT_EQ(material.FindProperty("groupB.foo")->m_dataType, MaterialPropertyDataType::Float); @@ -1823,10 +1823,10 @@ namespace UnitTest EXPECT_TRUE(material.FindProperty("groupB.foo") != nullptr); EXPECT_TRUE(material.FindProperty("groupB.bar") != nullptr); - EXPECT_EQ(material.FindProperty("groupA.foo")->m_name, "foo"); - EXPECT_EQ(material.FindProperty("groupA.bar")->m_name, "bar"); - EXPECT_EQ(material.FindProperty("groupB.foo")->m_name, "foo"); - EXPECT_EQ(material.FindProperty("groupB.bar")->m_name, "bar"); + EXPECT_EQ(material.FindProperty("groupA.foo")->GetName(), "foo"); + EXPECT_EQ(material.FindProperty("groupA.bar")->GetName(), "bar"); + EXPECT_EQ(material.FindProperty("groupB.foo")->GetName(), "foo"); + EXPECT_EQ(material.FindProperty("groupB.bar")->GetName(), "bar"); EXPECT_EQ(material.FindProperty("groupA.foo")->m_dataType, MaterialPropertyDataType::Bool); EXPECT_EQ(material.FindProperty("groupA.bar")->m_dataType, MaterialPropertyDataType::Image); EXPECT_EQ(material.FindProperty("groupB.foo")->m_dataType, MaterialPropertyDataType::Float);