Enable material version updates
Merge pull request #4338 from aws-lumberyard-dev/Atom/rbarrand/MaterialVersionUpdate ## Overview Added support for automatically updating .material files when the property layout of the .materialtype changes. At this stage, we only support a "rename" operation for renaming or moving material properties. We can add more operations in the future if needed (this will require some improvement to how we represent the data internally, but we can deal with that as the need arises). It's important to note that we currently have a hybrid dependency model for the material system, where materials can depend on materialtypes as job dependencies (property names are processed at asset build time), or material property names can be stored in the cooked assets (property names are processed and resolved at runtime when loading material assets). This means there are two places where we need to apply material property rename auto-updates: in the tools and in the runtime. So you will find that we have ApplyVersionUpdates() functions in both MaterialSourceData and MaterialAsset. (I hope we can move away from this hybrid approach at some point so this can all be simplified, but that depends on new Asset Processor features we don't have yet). ## Main changes - Added version and versionUpdates structures for .materialtype files, MaterialTypeSourceData, MaterialTypeAssetCreator, etc. - The .materialtype version number is now at the top level instead of inside the propertyLayout section, because in the future there are other ways the material type could change besides the property layout which might require version auto-updates. (The AP will fail if the version is found in the old location, and tell the user where it should be moved). - Added ApplyVersionUpdates() and ApplyPropertyRenames() utility functions to facilitate the auto-updates. - Updated MaterialTypeSourceData::FindProperty(name) to support renames because it must find the property data while loading .material files in order to resolve property data types. - These new functions will report warnings when they detect that source files are out of date, and recommend the user update them. The easiest way to do this is open it in the Material Editor and save. (which can be scripted in python if necessary) - Renamed the .material file format "propertyLayoutVersion" to "materialTypeVersion" which is more accurate. This shouldn't hurt existing data as this field wasn't actually used for anything before. ## Unit test improvements - The new code is well unit tested. - MaterialSourceDataTests - Updated to include both a .materialtype file and a MaterialTypeAsset for the test material type. Both are used by the MaterialTypeSourceData class. - The default test material type now includes some version update steps; these are only used for version update tests and won't impact the other test functions. - Updated the path for storing temp files to disk, to just be in a "temp" folder in the exe path. (Originally they were saved to the gem folder near MaterialSourceDataTests.cpp, but at some point someone changed it to be under the exe folder, so there's no reason to use the full gem path anymore). ## Other changes - Fixed AzCore Utils WriteFile() to create the file if it doesn't exist already. - Updated all .materialtype files to have the version number in the new position. - Fixed AssetSystemStub to normalize asset paths for more reliable lookup in unit tests.
This commit is contained in:
@@ -120,7 +120,7 @@ namespace AZ::Utils
|
||||
AZ::Outcome<void, AZStd::string> WriteFile(AZStd::string_view content, AZStd::string_view filePath)
|
||||
{
|
||||
AZ::IO::FixedMaxPath filePathFixed = filePath; // Because FileIOStream requires a null-terminated string
|
||||
AZ::IO::FileIOStream stream(filePathFixed.c_str(), AZ::IO::OpenMode::ModeWrite);
|
||||
AZ::IO::FileIOStream stream(filePathFixed.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath);
|
||||
|
||||
bool success = false;
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"description": "Base material for the reflection probe visualization model.",
|
||||
"version": 1,
|
||||
"propertyLayout": {
|
||||
"version": 1,
|
||||
"properties": {
|
||||
"general": [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"description": "Base material for the reflection probe visualization model.",
|
||||
"version": 1,
|
||||
"propertyLayout": {
|
||||
"version": 1,
|
||||
"properties": {
|
||||
"settings": [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"description": "Material Type with properties used to define Enhanced PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model, with advanced features like subsurface scattering, transmission, and anisotropy.",
|
||||
"version": 3,
|
||||
"propertyLayout": {
|
||||
"version": 3,
|
||||
"groups": [
|
||||
{
|
||||
"name": "baseColor",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"description": "Material Type tailored for rendering skin, with support for blended wrinkle maps that work with animated vertex blend shapes.",
|
||||
"version": 3,
|
||||
"propertyLayout": {
|
||||
"version": 3,
|
||||
"groups": [
|
||||
{
|
||||
"name": "baseColor",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"description": "Similar to StandardPBR but supports multiple layers blended together.",
|
||||
"version": 3,
|
||||
"propertyLayout": {
|
||||
"version": 3,
|
||||
"groups": [
|
||||
{
|
||||
"name": "blend",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"description": "Material Type with properties used to define Standard PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model.",
|
||||
"version": 3,
|
||||
"propertyLayout": {
|
||||
"version": 3,
|
||||
"groups": [
|
||||
{
|
||||
"name": "baseColor",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"description": "This is an example of a custom material type using Atom's PBR shading model: procedurally generated brick or tile.",
|
||||
"version": 3,
|
||||
"propertyLayout": {
|
||||
"version": 3,
|
||||
"groups": [
|
||||
{
|
||||
"name": "shape",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"description": "Base Material with properties used to define Standard PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model.",
|
||||
"version": 3,
|
||||
"propertyLayout": {
|
||||
"version": 3,
|
||||
"groups": [
|
||||
{
|
||||
"name": "settings",
|
||||
|
||||
@@ -230,9 +230,11 @@ namespace MaterialEditor
|
||||
|
||||
// create source data from properties
|
||||
MaterialSourceData sourceData;
|
||||
sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version;
|
||||
sourceData.m_materialType = m_materialSourceData.m_materialType;
|
||||
sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial;
|
||||
|
||||
AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null.");
|
||||
sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion();
|
||||
|
||||
// Force save data to store forward slashes
|
||||
AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/");
|
||||
@@ -302,9 +304,11 @@ namespace MaterialEditor
|
||||
|
||||
// create source data from properties
|
||||
MaterialSourceData sourceData;
|
||||
sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version;
|
||||
sourceData.m_materialType = m_materialSourceData.m_materialType;
|
||||
sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial;
|
||||
|
||||
AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null.");
|
||||
sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion();
|
||||
|
||||
// Force save data to store forward slashes
|
||||
AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/");
|
||||
@@ -373,8 +377,10 @@ namespace MaterialEditor
|
||||
|
||||
// create source data from properties
|
||||
MaterialSourceData sourceData;
|
||||
sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version;
|
||||
sourceData.m_materialType = m_materialSourceData.m_materialType;
|
||||
|
||||
AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null.");
|
||||
sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion();
|
||||
|
||||
// Only assign a parent path if the source was a .material
|
||||
if (AzFramework::StringFunc::Path::IsExtension(m_relativePath.c_str(), MaterialSourceData::Extension))
|
||||
@@ -679,6 +685,12 @@ namespace MaterialEditor
|
||||
return false;
|
||||
}
|
||||
m_materialTypeSourceData = materialTypeOutcome.GetValue();
|
||||
|
||||
if (MaterialSourceData::ApplyVersionUpdatesResult::Failed == m_materialSourceData.ApplyVersionUpdates(m_absolutePath))
|
||||
{
|
||||
AZ_Error("MaterialDocument", false, "Material source data could not be auto updated to the latest version of the material type: '%s'.", m_materialSourceData.m_materialType.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialTypeSourceData::Extension))
|
||||
{
|
||||
|
||||
-1
@@ -99,7 +99,6 @@ namespace AZ
|
||||
{
|
||||
// Construct the material source data object that will be exported
|
||||
AZ::RPI::MaterialSourceData exportData;
|
||||
exportData.m_propertyLayoutVersion = editData.m_materialTypeSourceData.m_propertyLayout.m_version;
|
||||
|
||||
// Converting absolute material paths to relative paths
|
||||
bool result = false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"description": "A material for rendering terrain with a physically-based rendering (PBR) material shading model.",
|
||||
"version": 1,
|
||||
"propertyLayout": {
|
||||
"version": 1,
|
||||
"groups": [
|
||||
{
|
||||
"id": "settings",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"description": "A material for providing terrain with low-fidelity color and normals. This material will get blended with surface detail materials.",
|
||||
"version": 1,
|
||||
"propertyLayout": {
|
||||
"version": 1,
|
||||
"groups": [
|
||||
{
|
||||
"name": "baseColor",
|
||||
|
||||
Reference in New Issue
Block a user