merging dev

Signed-off-by: antonmic <56370189+antonmic@users.noreply.github.com>
This commit is contained in:
antonmic
2021-10-21 22:37:12 -07:00
1159 changed files with 28918 additions and 658019 deletions
@@ -24,6 +24,13 @@ namespace AZ
AZ_RTTI(AZ::RPI::JsonMaterialPropertyValueSerializer, "{A52B1ED8-C849-4269-9AA7-9D0814D2EC59}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
//! A LoadContext object must be passed down to the serializer via JsonDeserializerContext::GetMetadata().Add(...)
struct LoadContext
{
AZ_TYPE_INFO(JsonMaterialPropertyValueSerializer::LoadContext, "{5E0A891A-27F6-4AD7-88A5-B9EA50F88B45}");
uint32_t m_materialTypeVersion; //!< The version number from the .materialtype file
};
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
@@ -50,7 +50,7 @@ namespace AZ
AZStd::string m_parentMaterial; //!< The immediate parent of this material
uint32_t m_propertyLayoutVersion = 0; //!< The version of the property layout, defined in the material type, which was used to configure this material
uint32_t m_materialTypeVersion = 0; //!< The version of the material type that was used to configure this material
struct Property
{
@@ -64,6 +64,18 @@ namespace AZ
PropertyGroupMap m_properties;
enum class ApplyVersionUpdatesResult
{
Failed,
NoUpdates,
UpdatesApplied
};
//! Checks the material type version and potentially applies a series of property changes (most common are simple property renames)
//! based on the MaterialTypeAsset's version update procedure.
//! @param materialSourceFilePath Indicates the path of the .material file that the MaterialSourceData represents. Used for resolving file-relative paths.
ApplyVersionUpdatesResult ApplyVersionUpdates(AZStd::string_view materialSourceFilePath = "");
//! Creates a MaterialAsset from the MaterialSourceData content.
//! @param assetId ID for the MaterialAsset
//! @param materialSourceFilePath Indicates the path of the .material file that the MaterialSourceData represents. Used for resolving file-relative paths.
@@ -13,6 +13,7 @@
#include <Atom/RPI.Reflect/Base.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h>
#include <Atom/RPI.Edit/Material/MaterialFunctorSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
namespace AZ
{
@@ -119,12 +120,36 @@ namespace AZ
using PropertyList = AZStd::vector<PropertyDefinition>;
struct VersionUpdatesRenameOperationDefinition
{
AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::VersionUpdatesRenameOperationDefinition, "{F2295489-E15A-46CC-929F-8D42DEDBCF14}");
AZStd::string m_operation;
AZStd::string m_renameFrom;
AZStd::string m_renameTo;
};
// TODO: Support script operations--At that point, we'll likely need to replace VersionUpdatesRenameOperationDefinition with a more generic
// data structure that has a custom JSON serialize. We will only be supporting rename for now.
using VersionUpdateActions = AZStd::vector<VersionUpdatesRenameOperationDefinition>;
struct VersionUpdateDefinition
{
AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::VersionUpdateDefinition, "{2C9D3B91-0585-4BC9-91D2-4CF0C71BC4B7}");
uint32_t m_toVersion;
VersionUpdateActions m_actions;
};
using VersionUpdates = AZStd::vector<VersionUpdateDefinition>;
struct PropertyLayout
{
AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertyLayout, "{AE53CF3F-5C3B-44F5-B2FB-306F0EB06393}");
//! Indicates the version of the set of available properties. Can be used to detect materials that might need to be updated.
uint32_t m_version = 0;
//! This field is unused, and has been replaced by MaterialTypeSourceData::m_version below. It is kept for legacy file compatibility to suppress warnings and errors.
uint32_t m_versionOld = 0;
//! List of groups that will contain the available properties
AZStd::vector<GroupDefinition> m_groups;
@@ -135,6 +160,11 @@ namespace AZ
AZStd::string m_description;
//! Version 1 is the default and should not contain any version update.
uint32_t m_version = 1;
VersionUpdates m_versionUpdates;
PropertyLayout m_propertyLayout;
//! A list of shader variants that are always used at runtime; they cannot be turned off
@@ -153,7 +183,12 @@ namespace AZ
const GroupDefinition* FindGroup(AZStd::string_view groupName) const;
const PropertyDefinition* FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName) const;
//! Searches for a specific property.
//! Note this function can find properties using old versions of the property name; in that case,
//! the name in the returned PropertyDefinition* will not match the @propertyName that was searched for.
//! @param materialTypeVersion indicates the version number of the property name being passed in. Only renames above this version number will be applied.
//! @return the requested property, or null if it could not be found
const PropertyDefinition* FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName, uint32_t materialTypeVersion = 0) const;
//! Construct a complete list of group definitions, including implicit groups, arranged in the same order as the source data
//! Groups with the same name will be consolidated into a single entry
@@ -179,6 +214,11 @@ namespace AZ
bool ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const;
Outcome<Data::Asset<MaterialTypeAsset>> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const;
//! Possibly renames @propertyId based on the material version update steps.
//! @param materialTypeVersion indicates the version number of the property name being passed in. Only renames above this version number will be applied.
//! @return true if the property was renamed
bool ApplyPropertyRenames(MaterialPropertyId& propertyId, uint32_t materialTypeVersion = 0) const;
};
//! The wrapper class for derived material functors.
@@ -22,6 +22,7 @@
namespace UnitTest
{
class MaterialTests;
class MaterialAssetTests;
}
namespace AZ
@@ -42,10 +43,12 @@ namespace AZ
, public MaterialReloadNotificationBus::Handler
, public AssetInitBus::Handler
{
friend class MaterialVersionUpdate;
friend class MaterialAssetCreator;
friend class MaterialAssetHandler;
friend class MaterialAssetCreatorCommon;
friend class UnitTest::MaterialTests;
friend class UnitTest::MaterialAssetTests;
public:
AZ_RTTI(MaterialAsset, "{522C7BE0-501D-463E-92C6-15184A2B7AD8}", AZ::Data::AssetData);
@@ -119,6 +122,10 @@ namespace AZ
//! from m_materialTypeAsset.
void RealignPropertyValuesAndNames();
//! Checks the material type version and potentially applies a series of property changes (most common are simple property renames)
//! based on the MaterialTypeAsset's version update procedure.
void ApplyVersionUpdates();
//! Called by asset creators to assign the asset to a ready state.
void SetReady();
@@ -143,6 +150,10 @@ namespace AZ
//! If empty, this implies that m_propertyValues is aligned with the entries in m_materialPropertiesLayout.
AZStd::vector<AZ::Name> m_propertyNames;
//! The materialTypeVersion this materialAsset was based of. If the versions do not match at runtime when a
//! materialTypeAsset is loaded, an update will be performed on m_propertyNames if populated.
uint32_t m_materialTypeVersion = 1;
//! A flag to determine if m_propertyValues needs to be aligned with MaterialPropertiesLayout. Set to true whenever
//! m_materialTypeAsset is reinitializing.
bool m_isDirty = true;
@@ -18,6 +18,7 @@
#include <Atom/RPI.Reflect/Material/ShaderCollection.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/MaterialVersionUpdate.h>
namespace AZ
{
@@ -123,6 +124,11 @@ namespace AZ
//! Returns a map from the UV shader inputs to a custom name.
MaterialUvNameMap GetUvNameMap() const;
//! Returns the version of the MaterialTypeAsset.
uint32_t GetVersion() const;
const AZStd::vector<MaterialVersionUpdate>& GetMaterialVersionUpdateList() const { return m_materialVersionUpdates; }
private:
bool PostLoadInit() override;
@@ -162,6 +168,12 @@ namespace AZ
//! Index in @m_shaderCollection of the shader asset that contains the ObjectSrg.
uint32_t m_objectSrgShaderIndex = InvalidShaderIndex;
//! The version of this MaterialTypeAsset. If the version is greater than 1, actions performed
//! to update this MaterialTypeAsset will be in m_materialVersionUpdateMap
uint32_t m_version = 1;
//! Contains actions to perform for each material update version.
AZStd::vector<MaterialVersionUpdate> m_materialVersionUpdates;
};
class MaterialTypeAssetHandler : public AssetHandler<MaterialTypeAsset>
@@ -38,6 +38,11 @@ namespace AZ
void AddShader(const AZ::Data::Asset<ShaderAsset>& shaderAsset, const ShaderVariantId& shaderVaraintId = ShaderVariantId{}, const AZ::Name& shaderTag = Uuid::CreateRandom().ToString<AZ::Name>());
void AddShader(const AZ::Data::Asset<ShaderAsset>& shaderAsset, const AZ::Name& shaderTag);
//! Sets the version of the MaterialTypeAsset
void SetVersion(uint32_t version);
//! Adds a version update object into the MaterialTypeAsset
void AddVersionUpdate(const MaterialVersionUpdate& materialVersionUpdate);
//! Indicates that this MaterialType will own the specified shader option.
//! Material-owned shader options can be connected to material properties (either directly or through functors).
//! They cannot be accessed externally (for example, through the Material::SetSystemShaderOption() function).
@@ -112,6 +117,7 @@ namespace AZ
//! Saves the per-material SRG layout in m_shaderResourceGroupLayout for easier access
void CacheMaterialSrgLayout();
bool ValidateMaterialVersion();
bool ValidateBeginMaterialProperty();
bool ValidateEndMaterialProperty();
@@ -0,0 +1,62 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/string/string.h>
#include <AzCore/Name/Name.h>
namespace AZ
{
namespace RPI
{
class MaterialAsset;
// This class contains a toVersion and a list of actions to specify what operations were performed to upgrade a materialType.
class MaterialVersionUpdate
{
public:
AZ_TYPE_INFO(AZ::RPI::MaterialVersionUpdate, "{B36E7712-AED8-46AA-AFE0-01F8F884C44A}");
static void Reflect(ReflectContext* context);
// At this time, the only supported operation is rename. If/when we add more actions in the future,
// we'll need to improve this, possibly with some virtual interface or union data.
struct RenamePropertyAction
{
AZ_TYPE_INFO(AZ::RPI::MaterialVersionUpdate::RenameAction, "{A1FBEB19-EA05-40F0-9700-57D048DF572B}");
static void Reflect(ReflectContext* context);
AZ::Name m_fromPropertyId;
AZ::Name m_toPropertyId;
};
explicit MaterialVersionUpdate() = default;
explicit MaterialVersionUpdate(uint32_t toVersion);
uint32_t GetVersion() const;
void SetVersion(uint32_t toVersion);
//! Apply version updates to the given material asset.
//! @return true if any changes were made
bool ApplyVersionUpdates(MaterialAsset& materialAsset) const;
using Actions = AZStd::vector<RenamePropertyAction>;
const Actions& GetActions() const;
void AddAction(const RenamePropertyAction& action);
private:
uint32_t m_toVersion;
Actions m_actions;
};
} // namespace RPI
} // namespace AZ
@@ -47,7 +47,7 @@ namespace AZ
{
AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor;
materialBuilderDescriptor.m_name = JobKey;
materialBuilderDescriptor.m_version = 109; // Changed "id" to "name" in serialization
materialBuilderDescriptor.m_version = 110; // Material version auto update feature
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<MaterialBuilder>();
@@ -287,6 +287,11 @@ namespace AZ
return {};
}
if (MaterialSourceData::ApplyVersionUpdatesResult::Failed == material.GetValue().ApplyVersionUpdates(materialSourceFilePath))
{
return {};
}
auto materialAssetOutcome = material.GetValue().CreateMaterialAsset(Uuid::CreateRandom(), materialSourceFilePath, true);
if (!materialAssetOutcome.IsSuccess())
{
@@ -62,6 +62,8 @@ namespace AZ
return context.Report(JsonSerializationResult::Tasks::ReadField, JsonSerializationResult::Outcomes::Catastrophic, "Material type reference not found.");
}
const JsonMaterialPropertyValueSerializer::LoadContext* loadContext = context.GetMetadata().Find<JsonMaterialPropertyValueSerializer::LoadContext>();
// Construct the full property name (groupName.propertyName) by parsing it from the JSON path string.
size_t startPropertyName = context.GetPath().Get().rfind('/');
size_t startGroupName = context.GetPath().Get().rfind('/', startPropertyName-1);
@@ -70,7 +72,7 @@ namespace AZ
JSR::ResultCode result(JSR::Tasks::ReadField);
auto propertyDefinition = materialType->FindProperty(groupName, propertyName);
auto propertyDefinition = materialType->FindProperty(groupName, propertyName, loadContext->m_materialTypeVersion);
if (!propertyDefinition)
{
AZStd::string message = AZStd::string::format("Property '%.*s.%.*s' not found in material type.", AZ_STRING_ARG(groupName), AZ_STRING_ARG(propertyName));
@@ -72,6 +72,62 @@ namespace AZ
materialAssetCreator.SetPropertyValue(propertyId, entry.second);
}
}
MaterialSourceData::ApplyVersionUpdatesResult MaterialSourceData::ApplyVersionUpdates(AZStd::string_view materialSourceFilePath)
{
AZStd::string materialTypeFullPath = AssetUtils::ResolvePathReference(materialSourceFilePath, m_materialType);
auto materialTypeSourceDataOutcome = MaterialUtils::LoadMaterialTypeSourceData(materialTypeFullPath);
if (!materialTypeSourceDataOutcome.IsSuccess())
{
return ApplyVersionUpdatesResult::Failed;
}
MaterialTypeSourceData materialTypeSourceData = materialTypeSourceDataOutcome.TakeValue();
if (m_materialTypeVersion == materialTypeSourceData.m_version)
{
return ApplyVersionUpdatesResult::NoUpdates;
}
bool changesWereApplied = false;
// Note that the only kind of property update currently supported is rename...
for (auto& groupPair : m_properties)
{
PropertyMap& propertyMap = groupPair.second;
PropertyMap newPropertyMap;
for (auto& propertyPair : propertyMap)
{
MaterialPropertyId propertyId{groupPair.first, propertyPair.first};
if (materialTypeSourceData.ApplyPropertyRenames(propertyId, m_materialTypeVersion))
{
newPropertyMap[propertyId.GetPropertyName().GetStringView()] = propertyPair.second;
changesWereApplied = true;
}
else
{
newPropertyMap[propertyPair.first] = propertyPair.second;
}
}
propertyMap = newPropertyMap;
}
if (changesWereApplied)
{
AZ_Warning("MaterialSourceData", false,
"This material is based on version '%u' of '%s', but the material type is now at version '%u'. "
"Automatic updates are available. Consider updating the .material source file.",
m_materialTypeVersion, m_materialType.c_str(), materialTypeSourceData.m_version);
}
m_materialTypeVersion = materialTypeSourceData.m_version;
return changesWereApplied ? ApplyVersionUpdatesResult::UpdatesApplied : ApplyVersionUpdatesResult::NoUpdates;
}
Outcome<Data::Asset<MaterialAsset> > MaterialSourceData::CreateMaterialAsset(Data::AssetId assetId, AZStd::string_view materialSourceFilePath, bool elevateWarnings, bool includeMaterialPropertyNames) const
{
@@ -8,6 +8,7 @@
#include <Atom/RPI.Edit/Material/MaterialSourceDataSerializer.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialPropertyValueSerializer.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Edit/Common/JsonFileLoadContext.h>
#include <Atom/RPI.Edit/Common/JsonUtils.h>
@@ -45,9 +46,9 @@ namespace AZ
}
result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_description, azrtti_typeid<AZStd::string>(), inputValue, "description", context));
result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_materialType, azrtti_typeid<AZStd::string>(), inputValue, "materialType", context));
result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_parentMaterial, azrtti_typeid<AZStd::string>(), inputValue, "parentMaterial", context));
result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_propertyLayoutVersion, azrtti_typeid<uint32_t>(), inputValue, "propertyLayoutVersion", context));
result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_materialType, azrtti_typeid<AZStd::string>(), inputValue, "materialType", context));
result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_materialTypeVersion, azrtti_typeid<uint32_t>(), inputValue, "materialTypeVersion", context));
if (materialSourceData->m_materialType.empty())
{
@@ -118,6 +119,10 @@ namespace AZ
context.GetMetadata().Add(AZStd::move(materialTypeData));
JsonMaterialPropertyValueSerializer::LoadContext materialPropertyValueLoadContext;
materialPropertyValueLoadContext.m_materialTypeVersion = materialSourceData->m_materialTypeVersion;
context.GetMetadata().Add(materialPropertyValueLoadContext);
result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_properties, azrtti_typeid<MaterialSourceData::PropertyGroupMap>(), inputValue, "properties", context));
if (result.GetProcessing() == JsonSerializationResult::Processing::Completed)
@@ -146,9 +151,9 @@ namespace AZ
JSR::ResultCode resultCode(JSR::Tasks::ReadField);
resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "description", &materialSourceData->m_description, nullptr, azrtti_typeid<AZStd::string>(), context));
resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "materialType", &materialSourceData->m_materialType, nullptr, azrtti_typeid<AZStd::string>(), context));
resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "parentMaterial", &materialSourceData->m_parentMaterial, nullptr, azrtti_typeid<AZStd::string>(), context));
resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "propertyLayoutVersion", &materialSourceData->m_propertyLayoutVersion, nullptr, azrtti_typeid<uint32_t>(), context));
resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "materialType", &materialSourceData->m_materialType, nullptr, azrtti_typeid<AZStd::string>(), context));
resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "materialTypeVersion", &materialSourceData->m_materialTypeVersion, nullptr, azrtti_typeid<uint32_t>(), context));
resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "properties", &materialSourceData->m_properties, nullptr, azrtti_typeid<MaterialSourceData::PropertyGroupMap>(), context));
return context.Report(resultCode, "Processed material.");
@@ -16,6 +16,7 @@
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h>
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/MaterialVersionUpdate.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
@@ -59,6 +60,23 @@ namespace AZ
serializeContext->RegisterGenericType<PropertyConnectionList>();
serializeContext->Class<VersionUpdatesRenameOperationDefinition>()
->Version(1)
->Field("op", &VersionUpdatesRenameOperationDefinition::m_operation)
->Field("from", &VersionUpdatesRenameOperationDefinition::m_renameFrom)
->Field("to", &VersionUpdatesRenameOperationDefinition::m_renameTo)
;
serializeContext->RegisterGenericType<VersionUpdateActions>();
serializeContext->Class<VersionUpdateDefinition>()
->Version(1)
->Field("toVersion", &VersionUpdateDefinition::m_toVersion)
->Field("actions", &VersionUpdateDefinition::m_actions)
;
serializeContext->RegisterGenericType<VersionUpdates>();
serializeContext->Class<ShaderVariantReferenceData>()
->Version(2)
->Field("file", &ShaderVariantReferenceData::m_shaderFilePath)
@@ -67,8 +85,8 @@ namespace AZ
;
serializeContext->Class<PropertyLayout>()
->Version(1)
->Field("version", &PropertyLayout::m_version)
->Version(2) // Material Version Update
->Field("version", &PropertyLayout::m_versionOld)
->Field("groups", &PropertyLayout::m_groups)
->Field("properties", &PropertyLayout::m_properties)
;
@@ -76,8 +94,10 @@ namespace AZ
serializeContext->RegisterGenericType<UvNameMap>();
serializeContext->Class<MaterialTypeSourceData>()
->Version(3)
->Version(4) // Material Version Update
->Field("description", &MaterialTypeSourceData::m_description)
->Field("version", &MaterialTypeSourceData::m_version)
->Field("versionUpdates", &MaterialTypeSourceData::m_versionUpdates)
->Field("propertyLayout", &MaterialTypeSourceData::m_propertyLayout)
->Field("shaders", &MaterialTypeSourceData::m_shaderCollection)
->Field("functors", &MaterialTypeSourceData::m_materialFunctorSourceData)
@@ -110,7 +130,38 @@ namespace AZ
return nullptr;
}
const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName) const
bool MaterialTypeSourceData::ApplyPropertyRenames(MaterialPropertyId& propertyId, uint32_t materialTypeVersion) const
{
bool renamed = false;
for (const VersionUpdateDefinition& versionUpdate : m_versionUpdates)
{
if (materialTypeVersion >= versionUpdate.m_toVersion)
{
continue;
}
for (const VersionUpdatesRenameOperationDefinition& action : versionUpdate.m_actions)
{
if (action.m_operation == "rename")
{
if (action.m_renameFrom == propertyId.GetFullName().GetStringView())
{
propertyId = MaterialPropertyId::Parse(action.m_renameTo);
renamed = true;
}
}
else
{
AZ_Warning("Material source data", false, "Unsupported material version update operation '%s'", action.m_operation.c_str());
}
}
}
return renamed;
}
const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName, uint32_t materialTypeVersion) const
{
auto groupIter = m_propertyLayout.m_properties.find(groupName);
if (groupIter == m_propertyLayout.m_properties.end())
@@ -126,6 +177,27 @@ namespace AZ
}
}
// Property has not been found, try looking for renames in the version history
MaterialPropertyId propertyId = MaterialPropertyId{groupName, propertyName};
ApplyPropertyRenames(propertyId, materialTypeVersion);
// Do the search again with the new names
groupIter = m_propertyLayout.m_properties.find(propertyId.GetGroupName().GetStringView());
if (groupIter == m_propertyLayout.m_properties.end())
{
return nullptr;
}
for (const PropertyDefinition& property : groupIter->second)
{
if (property.m_name == propertyId.GetPropertyName().GetStringView())
{
return &property;
}
}
return nullptr;
}
@@ -280,6 +352,41 @@ namespace AZ
materialTypeAssetCreator.SetElevateWarnings(elevateWarnings);
materialTypeAssetCreator.Begin(assetId);
if (m_propertyLayout.m_versionOld != 0)
{
materialTypeAssetCreator.ReportError(
"The field '/propertyLayout/version' is deprecated and moved to '/version'. "
"Please edit this material type source file and move the '\"version\": %u' setting up one level.",
m_propertyLayout.m_versionOld);
return Failure();
}
// Set materialtype version and add each version update object into MaterialTypeAsset.
materialTypeAssetCreator.SetVersion(m_version);
{
const AZ::Name rename = AZ::Name{ "rename" };
for (const auto& versionUpdate : m_versionUpdates)
{
MaterialVersionUpdate materialVersionUpdate{versionUpdate.m_toVersion};
for (const auto& action : versionUpdate.m_actions)
{
if (action.m_operation == rename.GetStringView())
{
materialVersionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction{
AZ::Name{ action.m_renameFrom },
AZ::Name{ action.m_renameTo }
});
}
else
{
materialTypeAssetCreator.ReportWarning("Unsupported material version update operation '%s'", action.m_operation.c_str());
}
}
materialTypeAssetCreator.AddVersionUpdate(materialVersionUpdate);
}
}
// Used to gather all the UV streams used in this material type from its shaders in alphabetical order.
auto semanticComp = [](const RHI::ShaderSemantic& lhs, const RHI::ShaderSemantic& rhs) -> bool
{
@@ -9,6 +9,7 @@
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/MaterialVersionUpdate.h>
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
#include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
@@ -32,8 +33,9 @@ namespace AZ
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<MaterialAsset, AZ::Data::AssetData>()
->Version(10)
->Version(11) // Material version update
->Field("materialTypeAsset", &MaterialAsset::m_materialTypeAsset)
->Field("materialTypeVersion", &MaterialAsset::m_materialTypeVersion)
->Field("propertyValues", &MaterialAsset::m_propertyValues)
->Field("propertyNames", &MaterialAsset::m_propertyNames)
;
@@ -103,9 +105,25 @@ namespace AZ
AZStd::array_view<MaterialPropertyValue> MaterialAsset::GetPropertyValues() const
{
if (!m_propertyNames.empty() && m_isDirty)
// If property names are included, they are used to re-arrange the property value list to align with the
// MaterialPropertiesLayout. This realignment would be necessary if the material type is updated with
// a new property layout, and a corresponding material is not reprocessed by the AP and continues using the
// old property layout.
if (!m_propertyNames.empty())
{
const_cast<MaterialAsset*>(this)->RealignPropertyValuesAndNames();
const uint32_t materialTypeVersion = m_materialTypeAsset->GetVersion();
if (m_materialTypeVersion < materialTypeVersion)
{
// It is possible that the material type has had some properties renamed. If that's the case, and this material
// is still referencing the old property layout, we need to apply any auto updates to rename those properties
// before using them to realign the property values.
const_cast<MaterialAsset*>(this)->ApplyVersionUpdates();
}
if (m_isDirty)
{
const_cast<MaterialAsset*>(this)->RealignPropertyValuesAndNames();
}
}
return m_propertyValues;
@@ -183,6 +201,40 @@ namespace AZ
m_isDirty = false;
}
void MaterialAsset::ApplyVersionUpdates()
{
if (m_materialTypeVersion == m_materialTypeAsset->GetVersion())
{
return;
}
const uint32_t originalVersion = m_materialTypeVersion;
bool changesWereApplied = false;
for (const MaterialVersionUpdate& versionUpdate : m_materialTypeAsset->GetMaterialVersionUpdateList())
{
if (m_materialTypeVersion < versionUpdate.GetVersion())
{
if (versionUpdate.ApplyVersionUpdates(*this))
{
changesWereApplied = true;
m_materialTypeVersion = versionUpdate.GetVersion();
}
}
}
if (changesWereApplied)
{
AZ_Warning("MaterialAsset", false,
"This material is based on version '%u' of %s, but the material type is now at version '%u'. "
"Automatic updates are available. Consider updating the .material source file.",
originalVersion, m_materialTypeAsset.ToString<AZStd::string>().c_str(), m_materialTypeAsset->GetVersion());
}
m_materialTypeVersion = m_materialTypeAsset->GetVersion();
}
void MaterialAsset::ReinitializeMaterialTypeAsset(Data::Asset<Data::AssetData> asset)
{
Data::Asset<MaterialTypeAsset> newMaterialTypeAsset = { asset.GetAs<MaterialTypeAsset>(), AZ::Data::AssetLoadBehavior::PreLoad };
@@ -23,6 +23,7 @@ namespace AZ
if (ValidateIsReady())
{
m_asset->m_materialTypeAsset = parentMaterial.m_materialTypeAsset;
m_asset->m_materialTypeVersion = m_asset->m_materialTypeAsset->GetVersion();
if (!m_asset->m_materialTypeAsset)
{
@@ -69,6 +70,7 @@ namespace AZ
ReportError("MaterialTypeAsset is null");
return;
}
m_asset->m_materialTypeVersion = m_asset->m_materialTypeAsset->GetVersion();
m_materialPropertiesLayout = m_asset->GetMaterialPropertiesLayout();
if (includeMaterialPropertyNames)
@@ -37,6 +37,7 @@ namespace AZ
void MaterialTypeAsset::Reflect(ReflectContext* context)
{
MaterialVersionUpdate::Reflect(context);
UvNamePair::Reflect(context);
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
@@ -44,7 +45,9 @@ namespace AZ
serializeContext->RegisterGenericType<MaterialUvNameMap>();
serializeContext->Class<MaterialTypeAsset, AZ::Data::AssetData>()
->Version(4) // ATOM-15472
->Version(5) // Material version update
->Field("Version", &MaterialTypeAsset::m_version)
->Field("VersionUpdates", &MaterialTypeAsset::m_materialVersionUpdates)
->Field("ShaderCollection", &MaterialTypeAsset::m_shaderCollection)
->Field("MaterialFunctors", &MaterialTypeAsset::m_materialFunctors)
->Field("MaterialSrgShaderIndex", &MaterialTypeAsset::m_materialSrgShaderIndex)
@@ -161,6 +164,11 @@ namespace AZ
return m_uvNameMap;
}
uint32_t MaterialTypeAsset::GetVersion() const
{
return m_version;
}
void MaterialTypeAsset::SetReady()
{
m_status = AssetStatus::Ready;
@@ -38,7 +38,7 @@ namespace AZ
bool MaterialTypeAssetCreator::End(Data::Asset<MaterialTypeAsset>& result)
{
if (!ValidateIsReady() || !ValidateEndMaterialProperty())
if (!ValidateIsReady() || !ValidateEndMaterialProperty() || !ValidateMaterialVersion())
{
return false;
}
@@ -100,6 +100,48 @@ namespace AZ
}
}
bool MaterialTypeAssetCreator::ValidateMaterialVersion()
{
if (m_asset->m_materialVersionUpdates.empty())
{
return true;
}
uint32_t prevVersion = 0;
for(const MaterialVersionUpdate& versionUpdate : m_asset->m_materialVersionUpdates)
{
if (versionUpdate.GetVersion() <= prevVersion)
{
ReportError("Version updates are not sequential. See version update '%u'.", versionUpdate.GetVersion());
return false;
}
if (versionUpdate.GetVersion() > m_asset->m_version)
{
ReportError("Version updates go beyond the current material type version. See version update '%u'.", versionUpdate.GetVersion());
return false;
}
prevVersion = versionUpdate.GetVersion();
}
const auto& lastMaterialVersionUpdate = m_asset->m_materialVersionUpdates.back();
for (const auto& action : lastMaterialVersionUpdate.GetActions())
{
const auto propertyIndex = m_asset->m_materialPropertiesLayout->FindPropertyIndex(AZ::Name{ action.m_toPropertyId });
if (!propertyIndex.IsValid())
{
ReportError("Renamed property '%s' not found in material property layout. Check that the property name has been "
"upgraded to the correct version",
action.m_toPropertyId.GetCStr());
return false;
}
}
return true;
}
void MaterialTypeAssetCreator::AddShader(const AZ::Data::Asset<ShaderAsset>& shaderAsset, const ShaderVariantId& shaderVaraintId, const AZ::Name& shaderTag)
{
if (ValidateIsReady() && ValidateNotNull(shaderAsset, "ShaderAsset"))
@@ -123,6 +165,16 @@ namespace AZ
AddShader(shaderAsset, ShaderVariantId{}, shaderTag);
}
void MaterialTypeAssetCreator::SetVersion(uint32_t version)
{
m_asset->m_version = version;
}
void MaterialTypeAssetCreator::AddVersionUpdate(const MaterialVersionUpdate& materialVersionUpdate)
{
m_asset->m_materialVersionUpdates.push_back(materialVersionUpdate);
}
void MaterialTypeAssetCreator::ClaimShaderOptionOwnership(const Name& shaderOptionName)
{
bool optionFound = false;
@@ -0,0 +1,89 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/RPI.Reflect/Material/MaterialVersionUpdate.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
namespace RPI
{
void MaterialVersionUpdate::RenamePropertyAction::Reflect(ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<RenamePropertyAction>()
->Version(1)
->Field("From", &RenamePropertyAction::m_fromPropertyId)
->Field("To", &RenamePropertyAction::m_toPropertyId)
;
}
}
void MaterialVersionUpdate::Reflect(ReflectContext* context)
{
MaterialVersionUpdate::RenamePropertyAction::Reflect(context);
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->RegisterGenericType<MaterialVersionUpdate::Actions>();
serializeContext->Class<MaterialVersionUpdate>()
->Version(1)
->Field("ToVersion", &MaterialVersionUpdate::m_toVersion)
->Field("Actions", &MaterialVersionUpdate::m_actions)
;
}
}
MaterialVersionUpdate::MaterialVersionUpdate(uint32_t toVersion)
: m_toVersion(toVersion)
{
}
uint32_t MaterialVersionUpdate::GetVersion() const
{
return m_toVersion;
}
void MaterialVersionUpdate::SetVersion(uint32_t toVersion)
{
m_toVersion = toVersion;
}
bool MaterialVersionUpdate::ApplyVersionUpdates(MaterialAsset& materialAsset) const
{
bool changesWereApplied = false;
for (auto& propertyName : materialAsset.m_propertyNames)
{
for (const auto& action : m_actions)
{
if (propertyName == action.m_fromPropertyId)
{
propertyName = action.m_toPropertyId;
changesWereApplied = true;
}
}
}
return changesWereApplied;
}
const AZ::RPI::MaterialVersionUpdate::Actions& MaterialVersionUpdate::GetActions() const
{
return m_actions;
}
void MaterialVersionUpdate::AddAction(const RenamePropertyAction& action)
{
m_actions.push_back(action);
}
} // namespace RPI
} // namespace AZ
@@ -7,6 +7,7 @@
*/
#include <Common/AssetSystemStub.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace UnitTest
{
@@ -36,12 +37,17 @@ namespace UnitTest
// Because GetSourceInfoBySourcePath should always return 0 for the sub-id, since it's about the source file not product file.
sourceInfo.m_assetInfo.m_assetId.m_subId = 0;
m_sourceInfoMap.emplace(sourcePath, sourceInfo);
AZStd::string normalizedSourcePath = sourcePath;
AzFramework::StringFunc::Path::Normalize(normalizedSourcePath);
m_sourceInfoMap.emplace(normalizedSourcePath, sourceInfo);
}
bool AssetSystemStub::GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder)
{
auto iter = m_sourceInfoMap.find(sourcePath);
AZStd::string normalizedSourcePath = sourcePath;
AzFramework::StringFunc::Path::Normalize(normalizedSourcePath);
auto iter = m_sourceInfoMap.find(normalizedSourcePath);
if (iter != m_sourceInfoMap.end())
{
@@ -10,6 +10,7 @@
#include <Common/RPITestFixture.h>
#include <Common/SerializeTester.h>
#include <Common/ShaderAssetTestUtils.h>
#include <Common/ErrorMessageFinder.h>
#include <Material/MaterialAssetTestUtils.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
@@ -63,6 +64,11 @@ namespace UnitTest
RPITestFixture::TearDown();
}
void ReplaceMaterialType(Data::Asset<MaterialAsset> materialAsset, Data::Asset<MaterialTypeAsset> upgradedMaterialTypeAsset)
{
materialAsset->m_materialTypeAsset = upgradedMaterialTypeAsset;
}
};
TEST_F(MaterialAssetTests, Basic)
@@ -202,6 +208,81 @@ namespace UnitTest
EXPECT_EQ(serializedAsset->GetPropertyValues()[8].GetValue<Data::Asset<ImageAsset>>(), streamingImageAsset);
}
TEST_F(MaterialAssetTests, UpgradeMaterialAsset)
{
// Here we test the main way that a material asset upgrade would be applied at runtime: A material type is updated to
// both rename a property *and* change the order in which properties appear in the layout. In this case, the new name
// must be identified and then that new name is used to find the appropriate index in the property layout.
auto materialSrgLayout = CreateCommonTestMaterialSrgLayout();
auto shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), materialSrgLayout);
Data::Asset<MaterialTypeAsset> testMaterialTypeAssetV1;
MaterialTypeAssetCreator materialTypeCreator;
materialTypeCreator.Begin(Uuid::CreateRandom());
materialTypeCreator.AddShader(shaderAsset);
AddMaterialPropertyForSrg(materialTypeCreator, Name{ "MyInt" }, MaterialPropertyDataType::Int, Name{ "m_int" });
AddMaterialPropertyForSrg(materialTypeCreator, Name{ "MyUInt" }, MaterialPropertyDataType::UInt, Name{ "m_uint" });
AddMaterialPropertyForSrg(materialTypeCreator, Name{ "MyFloat" }, MaterialPropertyDataType::Float, Name{ "m_float" });
EXPECT_TRUE(materialTypeCreator.End(testMaterialTypeAssetV1));
// Construct the material asset with materialTypeAsset version 1
Data::AssetId assetId(Uuid::CreateRandom());
MaterialAssetCreator creator;
const bool includePropertyNames = true;
creator.Begin(assetId, *testMaterialTypeAssetV1, includePropertyNames);
creator.SetPropertyValue(Name{ "MyInt" }, 7);
creator.SetPropertyValue(Name{ "MyUInt" }, 8u);
creator.SetPropertyValue(Name{ "MyFloat" }, 9.0f);
Data::Asset<MaterialAsset> materialAsset;
EXPECT_TRUE(creator.End(materialAsset));
// Prepare material type asset version 2 with the update actions
MaterialVersionUpdate versionUpdate(2);
versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction(
{
Name{ "MyInt" },
Name{ "MyIntRenamed" }
}));
Data::Asset<MaterialTypeAsset> testMaterialTypeAssetV2;
materialTypeCreator.Begin(Uuid::CreateRandom());
materialTypeCreator.SetVersion(versionUpdate.GetVersion());
materialTypeCreator.AddVersionUpdate(versionUpdate);
materialTypeCreator.AddShader(shaderAsset);
// Now we add the properties in a different order from before, and use the new name for MyInt.
AddMaterialPropertyForSrg(materialTypeCreator, Name{ "MyUInt" }, MaterialPropertyDataType::UInt, Name{ "m_uint" });
AddMaterialPropertyForSrg(materialTypeCreator, Name{ "MyFloat" }, MaterialPropertyDataType::Float, Name{ "m_float" });
AddMaterialPropertyForSrg(materialTypeCreator, Name{ "MyIntRenamed" }, MaterialPropertyDataType::Int, Name{ "m_int" });
EXPECT_TRUE(materialTypeCreator.End(testMaterialTypeAssetV2));
// This is our way of faking the idea that an old version of the MaterialAsset could be loaded with a new version of the MaterialTypeAsset.
ReplaceMaterialType(materialAsset, testMaterialTypeAssetV2);
// This can find errors and warnings, we are looking for a warning when the version update is applied
ErrorMessageFinder warningFinder;
warningFinder.AddExpectedErrorMessage("Automatic updates are available. Consider updating the .material source file");
warningFinder.AddExpectedErrorMessage("This material is based on version '1'");
warningFinder.AddExpectedErrorMessage("material type is now at version '2'");
// Even though this material was created using the old version of the material type, it's property values should get automatically
// updated to align with the new property layout in the latest MaterialTypeAsset.
MaterialPropertyIndex myIntIndex = materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{"MyIntRenamed"});
EXPECT_EQ(2, myIntIndex.GetIndex());
EXPECT_EQ(7, materialAsset->GetPropertyValues()[myIntIndex.GetIndex()].GetValue<int32_t>());
warningFinder.CheckExpectedErrorsFound();
// Since the MaterialAsset has already been updated, and the warning reported once, we should not see the "consider updating"
// warning reported again on subsequent property accesses.
warningFinder.Reset();
myIntIndex = materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{"MyIntRenamed"});
EXPECT_EQ(2, myIntIndex.GetIndex());
EXPECT_EQ(7, materialAsset->GetPropertyValues()[myIntIndex.GetIndex()].GetValue<int32_t>());
}
TEST_F(MaterialAssetTests, Error_NoBegin)
{
Data::AssetId assetId(Uuid::CreateRandom());
@@ -10,6 +10,7 @@
#include <Common/RPITestFixture.h>
#include <Common/JsonTestUtils.h>
#include <Common/ShaderAssetTestUtils.h>
#include <Common/ErrorMessageFinder.h>
#include <Material/MaterialAssetTestUtils.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
@@ -17,6 +18,7 @@
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Edit/Material/MaterialUtils.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
@@ -60,22 +62,72 @@ namespace UnitTest
localFileIO->SetAlias("@exefolder@", rootPath);
m_testMaterialSrgLayout = CreateCommonTestMaterialSrgLayout();
m_testShaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), m_testMaterialSrgLayout);
m_assetSystemStub.RegisterSourceInfo("@exefolder@/Temp/test.shader", m_testShaderAsset.GetId());
MaterialTypeAssetCreator materialTypeCreator;
materialTypeCreator.Begin(Uuid::CreateRandom());
materialTypeCreator.AddShader(m_testShaderAsset);
AddCommonTestMaterialProperties(materialTypeCreator, "general.");
materialTypeCreator.End(m_testMaterialTypeAsset);
// The MaterialSourceData relies on both MaterialTypeSourceData and MaterialTypeAsset. We have to make sure the
// .materialtype file is present on disk, and that the MaterialTypeAsset is available through the asset database stub...
const char* materialTypeJson = R"(
{
"version": 10,
"propertyLayout": {
"properties": {
"general": [
{"name": "MyBool", "type": "bool"},
{"name": "MyInt", "type": "Int"},
{"name": "MyUInt", "type": "UInt"},
{"name": "MyFloat", "type": "Float"},
{"name": "MyFloat2", "type": "Vector2"},
{"name": "MyFloat3", "type": "Vector3"},
{"name": "MyFloat4", "type": "Vector4"},
{"name": "MyColor", "type": "Color"},
{"name": "MyImage", "type": "Image"},
{"name": "MyEnum", "type": "Enum", "enumValues": ["Enum0", "Enum1", "Enum2"], "defaultValue": "Enum0"}
]
}
},
"shaders": [
{
"file": "@exefolder@/Temp/test.shader"
}
],
"versionUpdates": [
{
"toVersion": 2,
"actions": [
{"op": "rename", "from": "general.testColorNameA", "to": "general.testColorNameB"}
]
},
{
"toVersion": 4,
"actions": [
{"op": "rename", "from": "general.testColorNameB", "to": "general.testColorNameC"}
]
},
{
"toVersion": 10,
"actions": [
{"op": "rename", "from": "general.testColorNameC", "to": "general.MyColor"}
]
}
]
}
)";
AZ::Utils::WriteFile(materialTypeJson, "@exefolder@/Temp/test.materialtype");
MaterialTypeSourceData materialTypeSourceData;
LoadTestDataFromJson(materialTypeSourceData, materialTypeJson);
m_testMaterialTypeAsset = materialTypeSourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()).TakeValue();
// Since this test doesn't actually instantiate a Material, it won't need to instantiate this ImageAsset, so all we
// need is an asset reference with a valid ID.
m_testImageAsset = Data::Asset<ImageAsset>{ Data::AssetId{Uuid::CreateRandom(), StreamingImageAsset::GetImageAssetSubId()}, azrtti_typeid<StreamingImageAsset>() };
// Register the test assets with the AssetSystemStub so CreateMaterialAsset() can use AssetUtils.
m_assetSystemStub.RegisterSourceInfo("test.materialtype", m_testMaterialTypeAsset.GetId());
m_assetSystemStub.RegisterSourceInfo("test.streamingimage", m_testImageAsset.GetId());
m_assetSystemStub.RegisterSourceInfo("@exefolder@/Temp/test.materialtype", m_testMaterialTypeAsset.GetId());
m_assetSystemStub.RegisterSourceInfo("@exefolder@/Temp/test.streamingimage", m_testImageAsset.GetId());
}
void TearDown() override
@@ -88,12 +140,12 @@ namespace UnitTest
RPITestFixture::TearDown();
}
};
void AddPropertyGroup(MaterialSourceData& material, AZStd::string_view groupName)
{
material.m_properties.insert(groupName);
}
void AddProperty(MaterialSourceData& material, AZStd::string_view groupName, AZStd::string_view propertyName, const MaterialPropertyValue& anyValue)
{
material.m_properties[groupName][propertyName].m_value = anyValue;
@@ -103,7 +155,7 @@ namespace UnitTest
{
MaterialSourceData sourceData;
sourceData.m_materialType = "test.materialtype";
sourceData.m_materialType = "@exefolder@/Temp/test.materialtype";
AddPropertyGroup(sourceData, "general");
AddProperty(sourceData, "general", "MyBool", true);
AddProperty(sourceData, "general", "MyInt", -10);
@@ -113,7 +165,7 @@ namespace UnitTest
AddProperty(sourceData, "general", "MyFloat2", AZ::Vector2(2.1f, 2.2f));
AddProperty(sourceData, "general", "MyFloat3", AZ::Vector3(3.1f, 3.2f, 3.3f));
AddProperty(sourceData, "general", "MyFloat4", AZ::Vector4(4.1f, 4.2f, 4.3f, 4.4f));
AddProperty(sourceData, "general", "MyImage", AZStd::string("test.streamingimage"));
AddProperty(sourceData, "general", "MyImage", AZStd::string("@exefolder@/Temp/test.streamingimage"));
AddProperty(sourceData, "general", "MyEnum", AZStd::string("Enum1"));
auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
@@ -139,7 +191,7 @@ namespace UnitTest
EXPECT_STREQ(a.m_materialType.data(), b.m_materialType.data());
EXPECT_STREQ(a.m_description.data(), b.m_description.data());
EXPECT_STREQ(a.m_parentMaterial.data(), b.m_parentMaterial.data());
EXPECT_EQ(a.m_propertyLayoutVersion, b.m_propertyLayoutVersion);
EXPECT_EQ(a.m_materialTypeVersion, b.m_materialTypeVersion);
EXPECT_EQ(a.m_properties.size(), b.m_properties.size());
for (auto& groupA : a.m_properties)
@@ -170,7 +222,7 @@ namespace UnitTest
auto& propertyA = propertyIterA.second;
auto& propertyB = propertyIterB->second;
bool typesMatch = propertyA.m_value.GetTypeId() == propertyB.m_value.GetTypeId();
EXPECT_TRUE(typesMatch);
if (typesMatch)
@@ -229,8 +281,8 @@ namespace UnitTest
" } \n"
"} \n";
const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/roundTripTest.materialtype";
const char* materialTypeFilePath = "@exefolder@/Temp/roundTripTest.materialtype";
AZ::IO::FileIOStream file;
EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath));
file.Write(strlen(materialTypeJson), materialTypeJson);
@@ -240,7 +292,7 @@ namespace UnitTest
sourceDataOriginal.m_materialType = materialTypeFilePath;
sourceDataOriginal.m_parentMaterial = materialTypeFilePath;
sourceDataOriginal.m_description = "This is a description";
sourceDataOriginal.m_propertyLayoutVersion = 7;
sourceDataOriginal.m_materialTypeVersion = 7;
AddPropertyGroup(sourceDataOriginal, "groupA");
AddProperty(sourceDataOriginal, "groupA", "MyBool", true);
AddProperty(sourceDataOriginal, "groupA", "MyInt", -10);
@@ -252,14 +304,14 @@ namespace UnitTest
AddPropertyGroup(sourceDataOriginal, "groupC");
AddProperty(sourceDataOriginal, "groupC", "MyFloat4", AZ::Vector4(4.1f, 4.2f, 4.3f, 4.4f));
AddProperty(sourceDataOriginal, "groupC", "MyColor", AZ::Color{0.1f, 0.2f, 0.3f, 0.4f});
AddProperty(sourceDataOriginal, "groupC", "MyImage", AZStd::string("test.streamingimage"));
AddProperty(sourceDataOriginal, "groupC", "MyImage", AZStd::string("@exefolder@/Temp/test.streamingimage"));
AZStd::string sourceDataSerialized;
JsonTestResult storeResult = StoreTestDataToJson(sourceDataOriginal, sourceDataSerialized);
MaterialSourceData sourceDataCopy;
JsonTestResult loadResult = LoadTestDataFromJson(sourceDataCopy, sourceDataSerialized);
CheckEqual(sourceDataOriginal, sourceDataCopy);
}
@@ -277,10 +329,10 @@ namespace UnitTest
]
}
}
}
}
)";
const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype";
const char* materialTypeFilePath = "@exefolder@/Temp/simpleMaterialType.materialtype";
AZ::IO::FileIOStream file;
EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath));
@@ -296,7 +348,7 @@ namespace UnitTest
"testColor": [0.1,0.2,0.3]
}
},
"materialType": "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype"
"materialType": "@exefolder@/Temp/simpleMaterialType.materialtype"
}
)";
@@ -330,7 +382,7 @@ namespace UnitTest
{
const AZStd::string inputJson = R"(
{
"propertyLayoutVersion": 1,
"materialTypeVersion": 1,
"properties": {
"baseColor": {
"color": [1.0,1.0,1.0]
@@ -354,7 +406,7 @@ namespace UnitTest
const AZStd::string inputJson = R"(
{
"materialType": "DoesNotExist.materialtype",
"propertyLayoutVersion": 1,
"materialTypeVersion": 1,
"properties": {
"baseColor": {
"color": [1.0,1.0,1.0]
@@ -387,10 +439,10 @@ namespace UnitTest
]
}
}
}
}
)";
const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype";
const char* materialTypeFilePath = "@exefolder@/Temp/simpleMaterialType.materialtype";
AZ::IO::FileIOStream file;
EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath));
@@ -399,8 +451,8 @@ namespace UnitTest
const AZStd::string inputJson = R"(
{
"materialType": "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype",
"propertyLayoutVersion": 1,
"materialType": "@exefolder@/Temp/simpleMaterialType.materialtype",
"materialTypeVersion": 1,
"properties": {
"general": {
"testColor": [1.0,1.0,1.0]
@@ -433,10 +485,10 @@ namespace UnitTest
]
}
}
}
}
)";
const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype";
const char* materialTypeFilePath = "@exefolder@/Temp/simpleMaterialType.materialtype";
AZ::IO::FileIOStream file;
EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath));
@@ -445,8 +497,8 @@ namespace UnitTest
const AZStd::string inputJson = R"(
{
"materialType": "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype",
"propertyLayoutVersion": 1,
"materialType": "@exefolder@/Temp/simpleMaterialType.materialtype",
"materialTypeVersion": 1,
"properties": {
"general": {
"doesNotExist": [1.0,1.0,1.0]
@@ -467,20 +519,20 @@ namespace UnitTest
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MultiLevelDataInheritance)
{
MaterialSourceData sourceDataLevel1;
sourceDataLevel1.m_materialType = "test.materialtype";
sourceDataLevel1.m_materialType = "@exefolder@/Temp/test.materialtype";
AddPropertyGroup(sourceDataLevel1, "general");
AddProperty(sourceDataLevel1, "general", "MyFloat", 1.5f);
AddProperty(sourceDataLevel1, "general", "MyColor", AZ::Color{0.1f, 0.2f, 0.3f, 0.4f});
MaterialSourceData sourceDataLevel2;
sourceDataLevel2.m_materialType = "test.materialtype";
sourceDataLevel2.m_materialType = "@exefolder@/Temp/test.materialtype";
sourceDataLevel2.m_parentMaterial = "level1.material";
AddPropertyGroup(sourceDataLevel2, "general");
AddProperty(sourceDataLevel2, "general", "MyColor", AZ::Color{0.15f, 0.25f, 0.35f, 0.45f});
AddProperty(sourceDataLevel2, "general", "MyFloat2", AZ::Vector2{4.1f, 4.2f});
MaterialSourceData sourceDataLevel3;
sourceDataLevel3.m_materialType = "test.materialtype";
sourceDataLevel3.m_materialType = "@exefolder@/Temp/test.materialtype";
sourceDataLevel3.m_parentMaterial = "level2.material";
AddPropertyGroup(sourceDataLevel3, "general");
AddProperty(sourceDataLevel3, "general", "MyFloat", 3.5f);
@@ -497,7 +549,7 @@ namespace UnitTest
auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
EXPECT_TRUE(materialAssetLevel3.IsSuccess());
auto layout = m_testMaterialTypeAsset->GetMaterialPropertiesLayout();
MaterialPropertyIndex myFloat = layout->FindPropertyIndex(Name("general.MyFloat"));
MaterialPropertyIndex myFloat2 = layout->FindPropertyIndex(Name("general.MyFloat2"));
@@ -535,14 +587,14 @@ namespace UnitTest
m_assetSystemStub.RegisterSourceInfo("otherBase.materialtype", otherMaterialType.GetId());
MaterialSourceData sourceDataLevel1;
sourceDataLevel1.m_materialType = "test.materialtype";
sourceDataLevel1.m_materialType = "@exefolder@/Temp/test.materialtype";
MaterialSourceData sourceDataLevel2;
sourceDataLevel2.m_materialType = "test.materialtype";
sourceDataLevel2.m_materialType = "@exefolder@/Temp/test.materialtype";
sourceDataLevel2.m_parentMaterial = "level1.material";
MaterialSourceData sourceDataLevel3;
sourceDataLevel3.m_materialType = "otherBase.materialtype";
sourceDataLevel3.m_materialType = "@exefolder@/Temp/otherBase.materialtype";
sourceDataLevel3.m_parentMaterial = "level2.material";
auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
@@ -570,7 +622,7 @@ namespace UnitTest
{
MaterialSourceData sourceData;
sourceData.m_materialType = "test.materialtype";
sourceData.m_materialType = "@exefolder@/Temp/test.materialtype";
AddPropertyGroup(sourceData, "general");
@@ -587,7 +639,7 @@ namespace UnitTest
{
MaterialSourceData sourceData;
sourceData.m_materialType = "test.materialtype";
sourceData.m_materialType = "@exefolder@/Temp/test.materialtype";
AddPropertyGroup(sourceData, "general");
@@ -629,7 +681,7 @@ namespace UnitTest
expectWarning([](MaterialSourceData& materialSourceData)
{
AddProperty(materialSourceData, "general", "DoesNotExist", AZStd::string("test.streamingimage"));
AddProperty(materialSourceData, "general", "DoesNotExist", AZStd::string("@exefolder@/Temp/test.streamingimage"));
});
// Missing image reference
@@ -638,6 +690,124 @@ namespace UnitTest
AddProperty(materialSourceData, "general", "MyImage", AZStd::string("doesNotExist.streamingimage"));
}, 3); // Expect a 3rd error because AssetUtils reports its own assertion failure
}
TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionUpdate)
{
const AZStd::string inputJson = R"(
{
"materialType": "@exefolder@/Temp/test.materialtype",
"materialTypeVersion": 1,
"properties": {
"general": {
"testColorNameA": [0.1, 0.2, 0.3]
}
}
}
)";
MaterialSourceData material;
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
// Initially, the loaded material data will match the .material file exactly. This gives us the accurate representation of
// what's actually saved on disk.
EXPECT_NE(material.m_properties["general"].find("testColorNameA"), material.m_properties["general"].end());
EXPECT_EQ(material.m_properties["general"].find("testColorNameB"), material.m_properties["general"].end());
EXPECT_EQ(material.m_properties["general"].find("testColorNameC"), material.m_properties["general"].end());
EXPECT_EQ(material.m_properties["general"].find("MyColor"), material.m_properties["general"].end());
AZ::Color testColor = material.m_properties["general"]["testColorNameA"].m_value.GetValue<AZ::Color>();
EXPECT_TRUE(AZ::Color(0.1f, 0.2f, 0.3f, 1.0f).IsClose(testColor, 0.01));
EXPECT_EQ(1, material.m_materialTypeVersion);
// Then we force the material data to update to the latest material type version specification
ErrorMessageFinder warningFinder; // Note this finds errors and warnings, and we're looking for a warning.
warningFinder.AddExpectedErrorMessage("Automatic updates are available. Consider updating the .material source file");
warningFinder.AddExpectedErrorMessage("This material is based on version '1'");
warningFinder.AddExpectedErrorMessage("material type is now at version '10'");
material.ApplyVersionUpdates();
warningFinder.CheckExpectedErrorsFound();
// Now the material data should match the latest material type.
// Look for the property under the latest name in the material type, not the name used in the .material file.
EXPECT_EQ(material.m_properties["general"].find("testColorNameA"), material.m_properties["general"].end());
EXPECT_EQ(material.m_properties["general"].find("testColorNameB"), material.m_properties["general"].end());
EXPECT_EQ(material.m_properties["general"].find("testColorNameC"), material.m_properties["general"].end());
EXPECT_NE(material.m_properties["general"].find("MyColor"), material.m_properties["general"].end());
testColor = material.m_properties["general"]["MyColor"].m_value.GetValue<AZ::Color>();
EXPECT_TRUE(AZ::Color(0.1f, 0.2f, 0.3f, 1.0f).IsClose(testColor, 0.01));
EXPECT_EQ(10, material.m_materialTypeVersion);
// Calling ApplyVersionUpdates() again should not report the warning again, since the material has already been updated.
warningFinder.Reset();
material.ApplyVersionUpdates();
}
TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionPartialUpdate)
{
// This case is similar to Load_MaterialTypeVersionUpdate but we start at a later
// version so only some of the version updates are applied.
const AZStd::string inputJson = R"(
{
"materialType": "@exefolder@/Temp/test.materialtype",
"materialTypeVersion": 3,
"properties": {
"general": {
"testColorNameB": [0.1, 0.2, 0.3]
}
}
}
)";
MaterialSourceData material;
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
material.ApplyVersionUpdates();
AZ::Color testColor = material.m_properties["general"]["MyColor"].m_value.GetValue<AZ::Color>();
EXPECT_TRUE(AZ::Color(0.1f, 0.2f, 0.3f, 1.0f).IsClose(testColor, 0.01));
EXPECT_EQ(10, material.m_materialTypeVersion);
}
TEST_F(MaterialSourceDataTests, Load_Error_MaterialTypeVersionUpdateWithMismatchedVersion)
{
const AZStd::string inputJson = R"(
{
"materialType": "@exefolder@/Temp/test.materialtype",
"materialTypeVersion": 3, // At this version, the property should be testColorNameB not testColorNameA
"properties": {
"general": {
"testColorNameA": [0.1, 0.2, 0.3]
}
}
}
)";
MaterialSourceData material;
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
loadResult.ContainsMessage("/properties/general/testColorNameA", "Property 'general.testColorNameA' not found in material type.");
EXPECT_FALSE(material.m_properties["general"]["testColorNameA"].m_value.IsValid());
material.ApplyVersionUpdates();
EXPECT_FALSE(material.m_properties["general"]["MyColor"].m_value.IsValid());
}
}
@@ -14,6 +14,7 @@
#include <Material/MaterialAssetTestUtils.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h>
#include <Atom/RPI.Reflect/Material/MaterialVersionUpdate.h>
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
@@ -153,6 +154,16 @@ namespace UnitTest
MaterialTypeAssetCreator materialTypeCreator;
materialTypeCreator.Begin(assetId);
// Version updates
MaterialVersionUpdate versionUpdate(2);
versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction(
{
Name{ "EnableSpecialPassPrev" },
Name{ "EnableSpecialPass" }
}));
materialTypeCreator.SetVersion(versionUpdate.GetVersion());
materialTypeCreator.AddVersionUpdate(versionUpdate);
// Built-in shader
materialTypeCreator.AddShader(m_testShaderAsset);
@@ -198,7 +209,7 @@ namespace UnitTest
{
EXPECT_EQ(m_testMaterialSrgLayout, materialTypeAsset->GetMaterialSrgLayout());
EXPECT_EQ(5, materialTypeAsset->GetMaterialPropertiesLayout()->GetPropertyCount());
EXPECT_EQ(2, materialTypeAsset->GetVersion());
// Check aliased properties
const MaterialPropertyIndex colorIndex = materialTypeAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{ "MyColor" });
@@ -490,6 +501,106 @@ namespace UnitTest
});
}
TEST_F(MaterialTypeAssetTests, Error_InvalidMaterialVersionUpdate_WrongName)
{
Data::Asset<MaterialTypeAsset> materialTypeAsset;
Data::AssetId assetId(Uuid::CreateRandom());
MaterialTypeAssetCreator materialTypeCreator;
materialTypeCreator.Begin(assetId);
// Invalid version updates
MaterialVersionUpdate versionUpdate(2);
versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction(
{
Name{ "EnableSpecialPassPrev" },
Name{ "InvalidPropertyName" }
}));
materialTypeCreator.SetVersion(versionUpdate.GetVersion());
materialTypeCreator.AddVersionUpdate(versionUpdate);
materialTypeCreator.AddShader(m_testShaderAsset);
materialTypeCreator.BeginMaterialProperty(Name{ "EnableSpecialPass" }, MaterialPropertyDataType::Bool);
materialTypeCreator.EndMaterialProperty();
AZ_TEST_START_ASSERTTEST;
EXPECT_FALSE(materialTypeCreator.End(materialTypeAsset));
AZ_TEST_STOP_ASSERTTEST(1);
EXPECT_EQ(1, materialTypeCreator.GetErrorCount());
}
TEST_F(MaterialTypeAssetTests, Error_InvalidMaterialVersionUpdate_WrongOrder)
{
MaterialTypeAssetCreator materialTypeCreator;
materialTypeCreator.Begin(Uuid::CreateRandom());
materialTypeCreator.SetVersion(4);
materialTypeCreator.AddShader(m_testShaderAsset);
materialTypeCreator.BeginMaterialProperty(Name{ "d" }, MaterialPropertyDataType::Bool);
materialTypeCreator.EndMaterialProperty();
ErrorMessageFinder errorMessageFinder;
errorMessageFinder.AddExpectedErrorMessage("Version updates are not sequential. See version update '3'");
{
MaterialVersionUpdate versionUpdate(2);
versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction({Name{ "a" },Name{ "b" }}));
materialTypeCreator.AddVersionUpdate(versionUpdate);
}
{
MaterialVersionUpdate versionUpdate(4);
versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction({Name{ "b" },Name{ "c" }}));
materialTypeCreator.AddVersionUpdate(versionUpdate);
}
{
MaterialVersionUpdate versionUpdate(3);
versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction({Name{ "c" },Name{ "d" }}));
materialTypeCreator.AddVersionUpdate(versionUpdate);
}
Data::Asset<MaterialTypeAsset> materialTypeAsset;
EXPECT_FALSE(materialTypeCreator.End(materialTypeAsset));
errorMessageFinder.CheckExpectedErrorsFound();
EXPECT_EQ(1, materialTypeCreator.GetErrorCount());
}
TEST_F(MaterialTypeAssetTests, Error_InvalidMaterialVersionUpdate_GoesTooFar)
{
MaterialTypeAssetCreator materialTypeCreator;
materialTypeCreator.Begin(Uuid::CreateRandom());
materialTypeCreator.SetVersion(3);
materialTypeCreator.AddShader(m_testShaderAsset);
materialTypeCreator.BeginMaterialProperty(Name{ "d" }, MaterialPropertyDataType::Bool);
materialTypeCreator.EndMaterialProperty();
ErrorMessageFinder errorMessageFinder;
errorMessageFinder.AddExpectedErrorMessage("Version updates go beyond the current material type version. See version update '4'");
{
MaterialVersionUpdate versionUpdate(2);
versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction({Name{ "a" },Name{ "b" }}));
materialTypeCreator.AddVersionUpdate(versionUpdate);
}
{
MaterialVersionUpdate versionUpdate(4);
versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction({Name{ "b" },Name{ "c" }}));
materialTypeCreator.AddVersionUpdate(versionUpdate);
}
Data::Asset<MaterialTypeAsset> materialTypeAsset;
EXPECT_FALSE(materialTypeCreator.End(materialTypeAsset));
errorMessageFinder.CheckExpectedErrorsFound();
EXPECT_EQ(1, materialTypeCreator.GetErrorCount());
}
TEST_F(MaterialTypeAssetTests, MaterialTypeWithNoSRGOrProperties)
{
@@ -10,6 +10,7 @@
#include <Common/RPITestFixture.h>
#include <Common/JsonTestUtils.h>
#include <Common/ShaderAssetTestUtils.h>
#include <Common/ErrorMessageFinder.h>
#include <Material/MaterialAssetTestUtils.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
@@ -978,8 +979,16 @@ namespace UnitTest
const AZStd::string inputJson = R"(
{
"description": "This is a general description about the material",
"version": 2,
"versionUpdates": [
{
"toVersion": 2,
"actions": [
{ "op": "rename", "from": "groupA.fooPrev", "to": "groupA.foo" }
]
}
],
"propertyLayout": {
"version": 2,
"groups": [
{
"name": "groupA",
@@ -1062,7 +1071,12 @@ namespace UnitTest
EXPECT_EQ(material.m_description, "This is a general description about the material");
EXPECT_EQ(material.m_propertyLayout.m_version, 2);
EXPECT_EQ(material.m_version, 2);
EXPECT_EQ(material.m_versionUpdates.size(), 1);
EXPECT_EQ(material.m_versionUpdates[0].m_toVersion, 2);
EXPECT_EQ(material.m_versionUpdates[0].m_actions[0].m_operation, "rename");
EXPECT_EQ(material.m_versionUpdates[0].m_actions[0].m_renameFrom, "groupA.fooPrev");
EXPECT_EQ(material.m_versionUpdates[0].m_actions[0].m_renameTo, "groupA.foo");
EXPECT_EQ(material.m_propertyLayout.m_groups.size(), 2);
EXPECT_TRUE(material.FindGroup("groupA") != nullptr);
@@ -1208,8 +1222,6 @@ namespace UnitTest
EXPECT_EQ(material.m_description, "This is a general description about the material");
EXPECT_EQ(material.m_propertyLayout.m_version, 2);
EXPECT_EQ(material.m_propertyLayout.m_groups.size(), 2);
EXPECT_TRUE(material.FindGroup("groupA") != nullptr);
EXPECT_TRUE(material.FindGroup("groupB") != nullptr);
@@ -1266,7 +1278,6 @@ namespace UnitTest
{
"description": "",
"propertyLayout": {
"version": 2,
"groups": [
{
"name": "general",
@@ -1305,4 +1316,191 @@ namespace UnitTest
CheckPropertyValue<Data::Asset<ImageAsset>>(materialTypeAsset, Name{ "general.absolute" }, m_testImageAsset2);
CheckPropertyValue<Data::Asset<ImageAsset>>(materialTypeAsset, Name{ "general.relative" }, m_testImageAsset2);
}
TEST_F(MaterialTypeSourceDataTests, FindPropertyUsingOldName)
{
const AZStd::string inputJson = R"(
{
"version": 10,
"versionUpdates": [
{
"toVersion": 2,
"actions": [
{ "op": "rename", "from": "general.fooA", "to": "general.fooB" }
]
},
{
"toVersion": 4,
"actions": [
{ "op": "rename", "from": "general.barA", "to": "general.barB" }
]
},
{
"toVersion": 6,
"actions": [
{ "op": "rename", "from": "general.fooB", "to": "general.fooC" },
{ "op": "rename", "from": "general.barB", "to": "general.barC" }
]
},
{
"toVersion": 7,
"actions": [
{ "op": "rename", "from": "general.bazA", "to": "otherGroup.bazB" }
]
}
],
"propertyLayout": {
"properties": {
"general": [
{
"name": "fooC",
"type": "Bool"
},
{
"name": "barC",
"type": "Float"
}
],
"otherGroup": [
{
"name": "dontMindMe",
"type": "Bool"
},
{
"name": "bazB",
"type": "Float"
}
]
}
}
}
)";
MaterialTypeSourceData materialType;
JsonTestResult loadResult = LoadTestDataFromJson(materialType, inputJson);
EXPECT_EQ(materialType.m_version, 10);
// First find the properties using their correct current names
const MaterialTypeSourceData::PropertyDefinition* foo = materialType.FindProperty("general", "fooC");
const MaterialTypeSourceData::PropertyDefinition* bar = materialType.FindProperty("general", "barC");
const MaterialTypeSourceData::PropertyDefinition* baz = materialType.FindProperty("otherGroup", "bazB");
EXPECT_TRUE(foo);
EXPECT_TRUE(bar);
EXPECT_TRUE(baz);
EXPECT_EQ(foo->m_name, "fooC");
EXPECT_EQ(bar->m_name, "barC");
EXPECT_EQ(baz->m_name, "bazB");
// Now try doing the property lookup using old versions of the name and make sure the same property can be found
EXPECT_EQ(foo, materialType.FindProperty("general", "fooA"));
EXPECT_EQ(foo, materialType.FindProperty("general", "fooB"));
EXPECT_EQ(bar, materialType.FindProperty("general", "barA"));
EXPECT_EQ(bar, materialType.FindProperty("general", "barB"));
EXPECT_EQ(baz, materialType.FindProperty("general", "bazA"));
EXPECT_EQ(nullptr, materialType.FindProperty("general", "fooX"));
EXPECT_EQ(nullptr, materialType.FindProperty("general", "barX"));
EXPECT_EQ(nullptr, materialType.FindProperty("general", "bazX"));
EXPECT_EQ(nullptr, materialType.FindProperty("general", "bazB"));
EXPECT_EQ(nullptr, materialType.FindProperty("otherGroup", "bazA"));
}
TEST_F(MaterialTypeSourceDataTests, FindPropertyUsingOldName_Error_UnsupportedVersionUpdate)
{
const AZStd::string inputJson = R"(
{
"version": 10,
"versionUpdates": [
{
"toVersion": 2,
"actions": [
{ "op": "notRename", "from": "general.fooA", "to": "general.fooB" }
]
}
],
"propertyLayout": {
"properties": {
"general": [
{
"name": "fooB",
"type": "Bool"
}
]
}
}
}
)";
MaterialTypeSourceData materialType;
JsonTestResult loadResult = LoadTestDataFromJson(materialType, inputJson);
ErrorMessageFinder errorMessageFinder;
errorMessageFinder.AddExpectedErrorMessage("Unsupported material version update operation 'notRename'");
const MaterialTypeSourceData::PropertyDefinition* foo = materialType.FindProperty("general", "fooA");
EXPECT_EQ(nullptr, foo);
errorMessageFinder.CheckExpectedErrorsFound();
}
TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_UnsupportedVersionUpdate)
{
MaterialTypeSourceData sourceData;
MaterialTypeSourceData::PropertyDefinition propertySource;
propertySource.m_name = "a";
propertySource.m_dataType = MaterialPropertyDataType::Int;
propertySource.m_value = 0;
sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource);
sourceData.m_version = 2;
MaterialTypeSourceData::VersionUpdateDefinition versionUpdate;
versionUpdate.m_toVersion = 2;
MaterialTypeSourceData::VersionUpdatesRenameOperationDefinition updateAction;
updateAction.m_operation = "operationNotKnown";
versionUpdate.m_actions.push_back(updateAction);
sourceData.m_versionUpdates.push_back(versionUpdate);
ErrorMessageFinder errorMessageFinder;
errorMessageFinder.AddExpectedErrorMessage("Unsupported material version update operation 'operationNotKnown'");
errorMessageFinder.AddIgnoredErrorMessage("Failed to build MaterialTypeAsset", true);
auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom());
EXPECT_FALSE(materialTypeOutcome.IsSuccess());
errorMessageFinder.CheckExpectedErrorsFound();
}
TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_VersionInWrongLocation)
{
// The version field used to be under the propertyLayout section, but it has been moved up to the top level.
// If any users have their own custom .materialtype with an older format that has the version in the wrong place
// then we will report an error with instructions to move it to the correct location.
ErrorMessageFinder errorMessageFinder;
errorMessageFinder.AddExpectedErrorMessage("The field '/propertyLayout/version' is deprecated and moved to '/version'. Please edit this material type source file and move the '\"version\": 4' setting up one level");
const AZStd::string inputJson = R"(
{
"propertyLayout": {
"version": 4
}
}
)";
MaterialTypeSourceData materialType;
JsonTestResult loadResult = LoadTestDataFromJson(materialType, inputJson);
auto materialTypeOutcome = materialType.CreateMaterialTypeAsset(Uuid::CreateRandom());
EXPECT_FALSE(materialTypeOutcome.IsSuccess());
errorMessageFinder.CheckExpectedErrorsFound();
}
}
@@ -61,6 +61,7 @@ set(FILES
Include/Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h
Include/Atom/RPI.Reflect/Material/ShaderCollection.h
Include/Atom/RPI.Reflect/Material/MaterialFunctor.h
Include/Atom/RPI.Reflect/Material/MaterialVersionUpdate.h
Include/Atom/RPI.Reflect/Pass/ComputePassData.h
Include/Atom/RPI.Reflect/Pass/CopyPassData.h
Include/Atom/RPI.Reflect/Pass/DownsampleMipChainPassData.h
@@ -141,6 +142,7 @@ set(FILES
Source/RPI.Reflect/Material/MaterialTypeAssetCreator.cpp
Source/RPI.Reflect/Material/ShaderCollection.cpp
Source/RPI.Reflect/Material/MaterialFunctor.cpp
Source/RPI.Reflect/Material/MaterialVersionUpdate.cpp
Source/RPI.Reflect/Pass/PassAsset.cpp
Source/RPI.Reflect/Pass/PassAttachmentReflect.cpp
Source/RPI.Reflect/Pass/PassRequest.cpp