Merge pull request #6982 from aws-lumberyard-dev/Atom/santorac/MaterialAssetDeferredBaking_Recovery2
Formalized Material System Deferred Bake vs Pre Bake Asset Processing
This commit is contained in:
@@ -49,7 +49,7 @@ namespace AZ
|
||||
}
|
||||
|
||||
// Extract exactly which texture asset we need to load from the given material and map type (diffuse, normal, etc).
|
||||
static AZ::Data::Asset<AZ::RPI::StreamingImageAsset> GetStreamingImageAsset(const AZ::RPI::MaterialAsset& materialAsset, const AZ::Name& propertyName)
|
||||
static AZ::Data::Asset<AZ::RPI::StreamingImageAsset> GetStreamingImageAsset(AZ::RPI::MaterialAsset& materialAsset, const AZ::Name& propertyName)
|
||||
{
|
||||
if (!materialAsset.IsReady())
|
||||
{
|
||||
@@ -84,7 +84,7 @@ namespace AZ
|
||||
static AZ::Data::Asset<AZ::RPI::StreamingImageAsset> GetStreamingImageAsset(const AZ::Data::Asset<Data::AssetData> materialAssetData, const AZ::Name& propertyName)
|
||||
{
|
||||
AZ_Assert(materialAssetData->IsReady(), "GetStreamingImageAsset() called with AssetData that is not ready.");
|
||||
const AZ::RPI::MaterialAsset* materialAsset = materialAssetData.GetAs<AZ::RPI::MaterialAsset>();
|
||||
AZ::RPI::MaterialAsset* materialAsset = materialAssetData.GetAs<AZ::RPI::MaterialAsset>();
|
||||
return GetStreamingImageAsset(*materialAsset, propertyName);
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ namespace AZ
|
||||
return m_textureArrayPacked[mapType];
|
||||
}
|
||||
|
||||
bool DecalTextureArray::IsValidDecalMaterial(const AZ::RPI::MaterialAsset& materialAsset)
|
||||
bool DecalTextureArray::IsValidDecalMaterial(AZ::RPI::MaterialAsset& materialAsset)
|
||||
{
|
||||
return GetStreamingImageAsset(materialAsset, GetMapName(DecalMapType_Diffuse)).IsReady();
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace AZ
|
||||
// often different (BC5 for normals, BC7 for diffuse, etc)
|
||||
const Data::Instance<RPI::StreamingImage>& GetPackedTexture(const DecalMapType mapType) const;
|
||||
|
||||
static bool IsValidDecalMaterial(const RPI::MaterialAsset& materialAsset);
|
||||
static bool IsValidDecalMaterial(RPI::MaterialAsset& materialAsset);
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace AZ
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static AZ::RHI::Size GetTextureSizeFromMaterialAsset(const AZ::RPI::MaterialAsset* materialAsset)
|
||||
static AZ::RHI::Size GetTextureSizeFromMaterialAsset(AZ::RPI::MaterialAsset* materialAsset)
|
||||
{
|
||||
for (const auto& elem : materialAsset->GetPropertyValues())
|
||||
{
|
||||
@@ -375,7 +375,7 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
AZStd::optional<AZ::Render::DecalTextureArrayFeatureProcessor::DecalLocation> DecalTextureArrayFeatureProcessor::AddMaterialToTextureArrays(const AZ::RPI::MaterialAsset* materialAsset)
|
||||
AZStd::optional<AZ::Render::DecalTextureArrayFeatureProcessor::DecalLocation> DecalTextureArrayFeatureProcessor::AddMaterialToTextureArrays(AZ::RPI::MaterialAsset* materialAsset)
|
||||
{
|
||||
const RHI::Size textureSize = GetTextureSizeFromMaterialAsset(materialAsset);
|
||||
|
||||
@@ -410,7 +410,7 @@ namespace AZ
|
||||
AZ_PROFILE_SCOPE(AzRender, "DecalTextureArrayFeatureProcessor: OnAssetReady");
|
||||
const Data::AssetId& assetId = asset->GetId();
|
||||
|
||||
const RPI::MaterialAsset* materialAsset = asset.GetAs<AZ::RPI::MaterialAsset>();
|
||||
RPI::MaterialAsset* materialAsset = asset.GetAs<AZ::RPI::MaterialAsset>();
|
||||
const bool validDecalMaterial = materialAsset && DecalTextureArray::IsValidDecalMaterial(*materialAsset);
|
||||
if (validDecalMaterial)
|
||||
{
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace AZ
|
||||
void CacheShaderIndices();
|
||||
|
||||
// This call could fail (returning nullopt) if we run out of texture arrays
|
||||
AZStd::optional<DecalLocation> AddMaterialToTextureArrays(const AZ::RPI::MaterialAsset* materialAsset);
|
||||
AZStd::optional<DecalLocation> AddMaterialToTextureArrays(AZ::RPI::MaterialAsset* materialAsset);
|
||||
|
||||
int FindTextureArrayWithSize(const RHI::Size& size) const;
|
||||
void RemoveMaterialFromDecal(const uint16_t decalIndex);
|
||||
|
||||
@@ -28,8 +28,7 @@ namespace AZ
|
||||
serializeContext->Class<MaterialConverterSettings>()
|
||||
->Version(2)
|
||||
->Field("Enable", &MaterialConverterSettings::m_enable)
|
||||
->Field("DefaultMaterial", &MaterialConverterSettings::m_defaultMaterial)
|
||||
->Field("IncludeMaterialPropertyNames", &MaterialConverterSettings::m_includeMaterialPropertyNames);
|
||||
->Field("DefaultMaterial", &MaterialConverterSettings::m_defaultMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,11 +69,6 @@ namespace AZ
|
||||
return m_settings.m_enable;
|
||||
}
|
||||
|
||||
bool MaterialConverterSystemComponent::ShouldIncludeMaterialPropertyNames() const
|
||||
{
|
||||
return m_settings.m_includeMaterialPropertyNames;
|
||||
}
|
||||
|
||||
bool MaterialConverterSystemComponent::ConvertMaterial(
|
||||
const AZ::SceneAPI::DataTypes::IMaterialData& materialData, RPI::MaterialSourceData& sourceData)
|
||||
{
|
||||
|
||||
@@ -26,11 +26,6 @@ namespace AZ
|
||||
|
||||
bool m_enable = true;
|
||||
AZStd::string m_defaultMaterial;
|
||||
//! Sets whether to include material property names when generating material assets. If this
|
||||
//! setting is true, material property name resolution and validation is deferred into load
|
||||
//! time rather than at build time, allowing to break some dependencies (e.g. fbx files will no
|
||||
//! longer need to be dependent on materialtype files).
|
||||
bool m_includeMaterialPropertyNames = true;
|
||||
};
|
||||
|
||||
//! Atom's implementation of converting SceneAPI data into Atom's default material: StandardPBR
|
||||
@@ -50,7 +45,6 @@ namespace AZ
|
||||
|
||||
// MaterialConverterBus overrides ...
|
||||
bool IsEnabled() const override;
|
||||
bool ShouldIncludeMaterialPropertyNames() const override;
|
||||
bool ConvertMaterial(const AZ::SceneAPI::DataTypes::IMaterialData& materialData, RPI::MaterialSourceData& out) override;
|
||||
AZStd::string GetMaterialTypePath() const override;
|
||||
AZStd::string GetDefaultMaterialPath() const override;
|
||||
|
||||
@@ -32,10 +32,6 @@ namespace AZ
|
||||
|
||||
virtual bool IsEnabled() const = 0;
|
||||
|
||||
//! Returns true if material property names should be included in azmaterials. This allows unlinking of dependencies for some
|
||||
//! file types to materialtype files (e.g. fbx).
|
||||
virtual bool ShouldIncludeMaterialPropertyNames() const = 0;
|
||||
|
||||
//! Converts data from a IMaterialData object to an Atom MaterialSourceData.
|
||||
//! Only works when IsEnabled() is true.
|
||||
//! @return true if the MaterialSourceData output was populated with converted material data.
|
||||
|
||||
@@ -33,6 +33,12 @@ namespace AZ
|
||||
class MaterialAsset;
|
||||
class MaterialAssetCreator;
|
||||
|
||||
enum class MaterialAssetProcessingMode
|
||||
{
|
||||
PreBake, //!< all material asset processing is done in the Asset Processor, producing a finalized material asset
|
||||
DeferredBake //!< some material asset processing is deferred, and the material asset is finalized at runtime after loading
|
||||
};
|
||||
|
||||
//! This is a simple data structure for serializing in/out material source files.
|
||||
class MaterialSourceData final
|
||||
{
|
||||
@@ -72,35 +78,28 @@ namespace AZ
|
||||
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.
|
||||
//! @param processingMode Indicates whether to finalize the material asset using data from the MaterialTypeAsset.
|
||||
//! @param elevateWarnings Indicates whether to treat warnings as errors
|
||||
//! @param includeMaterialPropertyNames Indicates whether to save material property names into the material asset file
|
||||
Outcome<Data::Asset<MaterialAsset>> CreateMaterialAsset(
|
||||
Data::AssetId assetId,
|
||||
AZStd::string_view materialSourceFilePath = "",
|
||||
bool elevateWarnings = true,
|
||||
bool includeMaterialPropertyNames = true) const;
|
||||
AZStd::string_view materialSourceFilePath,
|
||||
MaterialAssetProcessingMode processingMode,
|
||||
bool elevateWarnings = true) const;
|
||||
|
||||
//! 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.
|
||||
//! @param elevateWarnings Indicates whether to treat warnings as errors
|
||||
//! @param includeMaterialPropertyNames Indicates whether to save material property names into the material asset file
|
||||
//! @param sourceDependencies if not null, will be populated with a set of all of the loaded material and material type paths
|
||||
Outcome<Data::Asset<MaterialAsset>> CreateMaterialAssetFromSourceData(
|
||||
Data::AssetId assetId,
|
||||
AZStd::string_view materialSourceFilePath = "",
|
||||
bool elevateWarnings = true,
|
||||
bool includeMaterialPropertyNames = true,
|
||||
AZStd::unordered_set<AZStd::string>* sourceDependencies = nullptr) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -64,6 +64,11 @@ namespace AZ
|
||||
void CheckForUnrecognizedJsonFields(
|
||||
const AZStd::string_view* acceptedFieldNames, uint32_t acceptedFieldNameCount,
|
||||
const rapidjson::Value& object, JsonDeserializerContext& context, JsonSerializationResult::ResultCode& result);
|
||||
|
||||
//! Materials assets can either be finalized during asset-processing time or when materials are loaded at runtime.
|
||||
//! Finalizing during asset processing reduces load times and obfuscates the material data.
|
||||
//! Waiting to finalize at load time reduces dependencies on the material type data, resulting in fewer asset rebuilds and less time spent processing assets.
|
||||
bool BuildersShouldFinalizeMaterialAssets();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace AZ
|
||||
// [GFX TODO] We need to iterate on this concept at some point. We may want to expose it through cvars or something
|
||||
// like that, or we may not need this at all. For now it's helpful for testing.
|
||||
void SetElevateWarnings(bool elevated);
|
||||
bool GetElevateWarnings() const { return m_warningsElevated; }
|
||||
|
||||
int GetErrorCount() const { return m_errorCount; }
|
||||
int GetWarningCount() const { return m_warningCount; }
|
||||
|
||||
@@ -34,8 +34,6 @@ namespace AZ
|
||||
class MaterialAssetHandler;
|
||||
|
||||
//! MaterialAsset defines a single material, which can be used to create a Material instance for rendering at runtime.
|
||||
//! It fetches MaterialTypeSourceData from the MaterialTypeAsset it owned.
|
||||
//!
|
||||
//! Use a MaterialAssetCreator to create a MaterialAsset.
|
||||
class MaterialAsset
|
||||
: public AZ::Data::AssetData
|
||||
@@ -46,7 +44,6 @@ namespace AZ
|
||||
friend class MaterialVersionUpdate;
|
||||
friend class MaterialAssetCreator;
|
||||
friend class MaterialAssetHandler;
|
||||
friend class MaterialAssetCreatorCommon;
|
||||
friend class UnitTest::MaterialTests;
|
||||
friend class UnitTest::MaterialAssetTests;
|
||||
|
||||
@@ -112,15 +109,29 @@ namespace AZ
|
||||
//!
|
||||
//! Note that even though material source data files contain only override values and inherit the rest from
|
||||
//! their parent material, they all get flattened at build time so every MaterialAsset has the full set of values.
|
||||
AZStd::array_view<MaterialPropertyValue> GetPropertyValues() const;
|
||||
//!
|
||||
//! Calling GetPropertyValues() will automatically finalize the material asset if it isn't finalized already. The
|
||||
//! MaterialTypeAsset must be loaded and ready.
|
||||
const AZStd::vector<MaterialPropertyValue>& GetPropertyValues();
|
||||
|
||||
//! Returns true if material was created in a finalize state, as opposed to being finalized after loading from disk.
|
||||
bool WasPreFinalized() const;
|
||||
|
||||
//! Returns the list of raw values for all properties in this material, as listed in the source .material file(s), before the material asset was Finalized.
|
||||
//!
|
||||
//! The MaterialAsset can be created in a "half-baked" state (see MaterialUtils::BuildersShouldFinalizeMaterialAssets) where
|
||||
//! minimal processing has been done because it did not yet have access to the MaterialTypeAsset. In that case, the list will
|
||||
//! be populated with values copied from the source .material file with little or no validation or other processing. It includes
|
||||
//! all parent .material files, with properties listed in low-to-high priority order.
|
||||
//! This list will be empty however if the asset was finalized at build-time (i.e. WasPreFinalized() returns true).
|
||||
const AZStd::vector<AZStd::pair<Name, MaterialPropertyValue>>& GetRawPropertyValues() const;
|
||||
|
||||
private:
|
||||
bool PostLoadInit() override;
|
||||
|
||||
//! Realigns property value and name indices with MaterialProperiesLayout by using m_propertyNames. Property names not found in the
|
||||
//! MaterialPropertiesLayout are discarded, while property names not included in m_propertyNames will use the default value
|
||||
//! from m_materialTypeAsset.
|
||||
void RealignPropertyValuesAndNames();
|
||||
|
||||
//! If the material asset is not finalized yet, this does the final processing of the raw property values to get the material asset ready to be used.
|
||||
//! MaterialTypeAsset must be valid before this is called.
|
||||
void Finalize(AZStd::function<void(const char*)> reportWarning = nullptr, AZStd::function<void(const char*)> reportError = nullptr);
|
||||
|
||||
//! 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.
|
||||
@@ -145,18 +156,34 @@ namespace AZ
|
||||
|
||||
//! Holds values for each material property, used to initialize Material instances.
|
||||
//! This is indexed by MaterialPropertyIndex and aligns with entries in m_materialPropertiesLayout.
|
||||
AZStd::vector<MaterialPropertyValue> m_propertyValues;
|
||||
//! This is used to realign m_propertyValues as well as itself with MaterialPropertiesLayout when not empty.
|
||||
//! If empty, this implies that m_propertyValues is aligned with the entries in m_materialPropertiesLayout.
|
||||
AZStd::vector<AZ::Name> m_propertyNames;
|
||||
mutable AZStd::vector<MaterialPropertyValue> m_propertyValues;
|
||||
|
||||
//! 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.
|
||||
//! The MaterialAsset can be created in a "half-baked" state where minimal processing has been done because it does
|
||||
//! not yet have access to the MaterialTypeAsset. In that case, this list will be populated with values copied from
|
||||
//! the source .material file with little or no validation or other processing, and the m_propertyValues list will be empty.
|
||||
//! Once a MaterialTypeAsset is available, Finalize() must be called to finish processing these values into the
|
||||
//! final m_propertyValues list.
|
||||
//! Note that the content of this list will remain after finalizing in order to support hot-reload of the MaterialTypeAsset.
|
||||
//! The reason we use a vector instead of a map is to ensure inherited property values are applied in the right order;
|
||||
//! if the material has a parent, and that parent uses an older material type version with renamed properties, then
|
||||
//! m_rawPropertyValues could be holding two values for the same property under different names. The auto-rename process
|
||||
//! can't be applied until the MaterialTypeAsset is available, so we have to keep the properties in the same order they
|
||||
//! were originally encountered.
|
||||
AZStd::vector<AZStd::pair<Name, MaterialPropertyValue>> m_rawPropertyValues;
|
||||
|
||||
//! Tracks whether Finalize() has been called, meaning m_propertyValues is populated with data matching the material type's property layout.
|
||||
//! (This value is intentionally not serialized, it is set by the Finalize() function)
|
||||
mutable bool m_isFinalized = false;
|
||||
|
||||
//! Tracks whether the MaterialAsset was already in a finalized state when it was loaded.
|
||||
bool m_wasPreFinalized = false;
|
||||
|
||||
//! The materialTypeVersion this materialAsset was based off. If the versions do not match at runtime when a
|
||||
//! materialTypeAsset is loaded, automatic updates will be attempted at runtime. Note this is not needed to
|
||||
//! determine which updates to apply, but simply as an optimization to ignore the update procedure when the
|
||||
//! version numbers match. (We determine which updates to apply by simply checking the property name, and not
|
||||
//! allowing the same name to ever be used for two different properties, see MaterialTypeAssetCreator::ValidateMaterialVersion)
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -9,31 +9,38 @@
|
||||
|
||||
#include <Atom/RPI.Reflect/AssetCreator.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialAssetCreatorCommon.h>
|
||||
#include <Atom/RPI.Reflect/Image/AttachmentImageAssetCreator.h>
|
||||
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
{
|
||||
//! Use a MaterialAssetCreator to create and configure a new MaterialAsset.
|
||||
//! The MaterialAsset will be based on a MaterialTypeAsset or another MaterialAsset.
|
||||
//! Either way, the base provides the necessary data to define the layout
|
||||
//! and behavior of the material. The MaterialAsset only provides property value overrides.
|
||||
class MaterialAssetCreator
|
||||
//!
|
||||
//! There are two options for how to create the MaterialAsset, whether it should be finalized now or deferred.
|
||||
//! - Finalized now: This requires the MaterialTypeAsset to be fully populated so it can read the property layout.
|
||||
//! - Deferred finalize: This only requires the MaterialTypeAsset to have a valid AssetId; the data inside will not be used. MaterialAsset::Finalize()
|
||||
//! will need to be called later when the final MaterialTypeAsset is available, presumably after loading the MaterialAsset at runtime.
|
||||
class MaterialAssetCreator
|
||||
: public AssetCreator<MaterialAsset>
|
||||
, public MaterialAssetCreatorCommon
|
||||
{
|
||||
public:
|
||||
friend class MaterialSourceData;
|
||||
|
||||
void Begin(const Data::AssetId& assetId, MaterialAsset& parentMaterial, bool includeMaterialPropertyNames = true);
|
||||
void Begin(const Data::AssetId& assetId, MaterialTypeAsset& materialType, bool includeMaterialPropertyNames = true);
|
||||
void Begin(const Data::AssetId& assetId, const Data::Asset<MaterialTypeAsset>& materialType, bool shouldFinalize);
|
||||
bool End(Data::Asset<MaterialAsset>& result);
|
||||
|
||||
private:
|
||||
void PopulatePropertyNameList();
|
||||
void SetMaterialTypeVersion(uint32_t version);
|
||||
|
||||
void SetPropertyValue(const Name& name, const MaterialPropertyValue& value);
|
||||
|
||||
void SetPropertyValue(const Name& name, const Data::Asset<ImageAsset>& imageAsset);
|
||||
void SetPropertyValue(const Name& name, const Data::Asset<StreamingImageAsset>& imageAsset);
|
||||
void SetPropertyValue(const Name& name, const Data::Asset<AttachmentImageAsset>& imageAsset);
|
||||
|
||||
const MaterialPropertiesLayout* m_materialPropertiesLayout = nullptr;
|
||||
private:
|
||||
bool m_shouldFinalize = false;
|
||||
};
|
||||
} // namespace RPI
|
||||
} // namespace AZ
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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/Name/Name.h>
|
||||
#include <Atom/RPI.Reflect/Image/ImageAsset.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
|
||||
#include <Atom/RHI.Reflect/ShaderSemantic.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialPropertyValue.h>
|
||||
|
||||
// These classes are not directly referenced in this header only because the SetPropertyValue()
|
||||
// function is templatized. But the API is still specific to these data types so we include them here.
|
||||
#include <AzCore/Math/Vector2.h>
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzCore/Math/Vector4.h>
|
||||
#include <AzCore/Math/Color.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
{
|
||||
class StreamingImageAsset;
|
||||
class AttachmentImageAsset;
|
||||
|
||||
//! Provides common functionality to both MaterialTypeAssetCreator and MaterialAssetCreator.
|
||||
class MaterialAssetCreatorCommon
|
||||
{
|
||||
public:
|
||||
void SetPropertyValue(const Name& name, const Data::Asset<ImageAsset>& imageAsset);
|
||||
void SetPropertyValue(const Name& name, const Data::Asset<StreamingImageAsset>& imageAsset);
|
||||
void SetPropertyValue(const Name& name, const Data::Asset<AttachmentImageAsset>& imageAsset);
|
||||
|
||||
//! Sets a property value using data in AZStd::variant-based MaterialPropertyValue. The contained data must match
|
||||
//! the data type of the property. For type Image, the value must be a Data::Asset<ImageAsset>.
|
||||
void SetPropertyValue(const Name& name, const MaterialPropertyValue& value);
|
||||
|
||||
protected:
|
||||
MaterialAssetCreatorCommon() = default;
|
||||
|
||||
void OnBegin(
|
||||
const MaterialPropertiesLayout* propertyLayout,
|
||||
AZStd::vector<MaterialPropertyValue>* propertyValues,
|
||||
const AZStd::function<void(const char*)>& warningFunc,
|
||||
const AZStd::function<void(const char*)>& errorFunc);
|
||||
void OnEnd();
|
||||
|
||||
private:
|
||||
bool PropertyCheck(TypeId typeId, const Name& name);
|
||||
|
||||
//! Returns the MaterialPropertyDataType value that corresponds to typeId
|
||||
MaterialPropertyDataType GetMaterialPropertyDataType(TypeId typeId) const;
|
||||
|
||||
//! Checks that the TypeId typeId matches the type expected by materialPropertyDescriptor
|
||||
bool ValidateDataType(TypeId typeId, const Name& propertyName, const MaterialPropertyDescriptor* materialPropertyDescriptor);
|
||||
|
||||
const MaterialPropertiesLayout* m_propertyLayout = nullptr;
|
||||
//! Points to the m_propertyValues list in a MaterialAsset or MaterialTypeAsset
|
||||
AZStd::vector<MaterialPropertyValue>* m_propertyValues = nullptr;
|
||||
|
||||
AZStd::function<void(const char*)> m_reportWarning = nullptr;
|
||||
AZStd::function<void(const char*)> m_reportError = nullptr;
|
||||
};
|
||||
|
||||
} // namespace RPI
|
||||
} // namespace AZ
|
||||
@@ -17,6 +17,8 @@ namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
{
|
||||
class MaterialPropertyDescriptor;
|
||||
|
||||
struct MaterialPropertyIndexType {
|
||||
AZ_TYPE_INFO(MaterialPropertyIndexType, "{cfc09268-f3f1-4474-bd8f-f2c8de27c5f1}");
|
||||
};
|
||||
@@ -76,6 +78,9 @@ namespace AZ
|
||||
const char* ToString(MaterialPropertyDataType materialPropertyDataType);
|
||||
|
||||
AZStd::string GetMaterialPropertyDataTypeString(AZ::TypeId typeId);
|
||||
|
||||
//! Checks that the TypeId matches the type expected by materialPropertyDescriptor
|
||||
bool ValidateMaterialPropertyDataType(TypeId typeId, const Name& propertyName, const MaterialPropertyDescriptor* materialPropertyDescriptor, AZStd::function<void(const char*)> onError);
|
||||
|
||||
//! A material property is any data input to a material, like a bool, float, Vector, Image, Buffer, etc.
|
||||
//! This descriptor defines a single input property, including it's name ID, and how it maps
|
||||
|
||||
@@ -30,6 +30,10 @@ namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
{
|
||||
//! This is a variant data type that represents the value of a material property.
|
||||
//! For convenience, it supports all the types necessary for *both* the runtime data (MaterialAsset) as well as .material file data (MaterialSourceData).
|
||||
//! For example, Instance<Image> is exclusive to the runtime data and AZStd::string is primarily for image file paths in .material files. Most other
|
||||
//! data types are relevant in both contexts.
|
||||
class MaterialPropertyValue final
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -54,7 +54,6 @@ namespace AZ
|
||||
{
|
||||
friend class MaterialTypeAssetCreator;
|
||||
friend class MaterialTypeAssetHandler;
|
||||
friend class MaterialAssetCreatorCommon;
|
||||
|
||||
public:
|
||||
AZ_RTTI(MaterialTypeAsset, "{CD7803AB-9C4C-4A33-9A14-7412F1665464}", AZ::Data::AssetData);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <Atom/RPI.Reflect/AssetCreator.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialAssetCreatorCommon.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
|
||||
#include <AtomCore/std/containers/array_view.h>
|
||||
|
||||
@@ -27,7 +26,6 @@ namespace AZ
|
||||
//! which provides the MaterialTypeAsset and default property values.
|
||||
class MaterialTypeAssetCreator
|
||||
: public AssetCreator<MaterialTypeAsset>
|
||||
, public MaterialAssetCreatorCommon
|
||||
{
|
||||
public:
|
||||
//! Begin creating a MaterialTypeAsset
|
||||
@@ -71,6 +69,14 @@ namespace AZ
|
||||
|
||||
//! Finishes creating a material property.
|
||||
void EndMaterialProperty();
|
||||
|
||||
void SetPropertyValue(const Name& name, const Data::Asset<ImageAsset>& imageAsset);
|
||||
void SetPropertyValue(const Name& name, const Data::Asset<StreamingImageAsset>& imageAsset);
|
||||
void SetPropertyValue(const Name& name, const Data::Asset<AttachmentImageAsset>& imageAsset);
|
||||
|
||||
//! Sets a property value using data in AZStd::variant-based MaterialPropertyValue. The contained data must match
|
||||
//! the data type of the property. For type Image, the value must be a Data::Asset<ImageAsset>.
|
||||
void SetPropertyValue(const Name& name, const MaterialPropertyValue& value);
|
||||
|
||||
//! Adds a MaterialFunctor.
|
||||
//! Material functors provide custom logic and calculations to configure shaders, render states, and more.See MaterialFunctor.h for details.
|
||||
@@ -101,7 +107,9 @@ namespace AZ
|
||||
private:
|
||||
|
||||
void AddMaterialProperty(MaterialPropertyDescriptor&& materialProperty);
|
||||
|
||||
|
||||
bool PropertyCheck(TypeId typeId, const Name& name);
|
||||
|
||||
//! The material type holds references to shader assets that contain SRGs that are supposed to be the same across all passes in the material.
|
||||
//! This function searches for an SRG given a @bindingSlot. If a valid one is found it makes sure it is the same across all shaders
|
||||
//! and records in srgShaderIndexToUpdate the index of the ShaderAsset in the ShaderCollection where it was found.
|
||||
|
||||
@@ -43,17 +43,24 @@ namespace AZ
|
||||
|
||||
const char* MaterialBuilder::JobKey = "Atom Material Builder";
|
||||
|
||||
AZStd::string GetBuilderSettingsFingerprint()
|
||||
{
|
||||
return AZStd::string::format("[BuildersShouldFinalizeMaterialAssets=%d]", MaterialUtils::BuildersShouldFinalizeMaterialAssets());
|
||||
}
|
||||
|
||||
void MaterialBuilder::RegisterBuilder()
|
||||
{
|
||||
AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor;
|
||||
materialBuilderDescriptor.m_name = JobKey;
|
||||
materialBuilderDescriptor.m_version = 110; // Material version auto update feature
|
||||
materialBuilderDescriptor.m_version = 115; // material dependency improvements updated
|
||||
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>();
|
||||
materialBuilderDescriptor.m_createJobFunction = AZStd::bind(&MaterialBuilder::CreateJobs, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
|
||||
materialBuilderDescriptor.m_processJobFunction = AZStd::bind(&MaterialBuilder::ProcessJob, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
|
||||
|
||||
materialBuilderDescriptor.m_analysisFingerprint = GetBuilderSettingsFingerprint();
|
||||
|
||||
BusConnect(materialBuilderDescriptor.m_busId);
|
||||
|
||||
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, materialBuilderDescriptor);
|
||||
@@ -63,7 +70,7 @@ namespace AZ
|
||||
{
|
||||
BusDisconnect();
|
||||
}
|
||||
|
||||
|
||||
bool MaterialBuilder::ReportMaterialAssetWarningsAsErrors() const
|
||||
{
|
||||
bool warningsAsErrors = false;
|
||||
@@ -77,15 +84,18 @@ namespace AZ
|
||||
//! Adds all relevant dependencies for a referenced source file, considering that the path might be relative to the original file location or a full asset path.
|
||||
//! This will usually include multiple source dependencies and a single job dependency, but will include only source dependencies if the file is not found.
|
||||
//! Note the AssetBuilderSDK::JobDependency::m_platformIdentifier will not be set by this function. The calling code must set this value before passing back
|
||||
//! to the AssetBuilderSDK::CreateJobsResponse. If isOrderedOnceForMaterialTypes is true and the dependency is a .materialtype file, the job dependency type
|
||||
//! will be set to JobDependencyType::OrderOnce.
|
||||
void AddPossibleDependencies(AZStd::string_view currentFilePath,
|
||||
AZStd::string_view referencedParentPath,
|
||||
//! to the AssetBuilderSDK::CreateJobsResponse.
|
||||
void AddPossibleDependencies(
|
||||
const AZStd::string& currentFilePath,
|
||||
const AZStd::string& referencedParentPath,
|
||||
const char* jobKey,
|
||||
AZStd::vector<AssetBuilderSDK::JobDependency>& jobDependencies,
|
||||
bool isOrderedOnceForMaterialTypes = false)
|
||||
AZStd::vector<AssetBuilderSDK::JobDependency>& jobDependencies)
|
||||
{
|
||||
bool dependencyFileFound = false;
|
||||
|
||||
const bool currentFileIsMaterial = AzFramework::StringFunc::Path::IsExtension(currentFilePath.c_str(), MaterialSourceData::Extension);
|
||||
const bool referencedFileIsMaterialType = AzFramework::StringFunc::Path::IsExtension(referencedParentPath.c_str(), MaterialTypeSourceData::Extension);
|
||||
const bool shouldFinalizeMaterialAssets = MaterialUtils::BuildersShouldFinalizeMaterialAssets();
|
||||
|
||||
AZStd::vector<AZStd::string> possibleDependencies = RPI::AssetUtils::GetPossibleDepenencyPaths(currentFilePath, referencedParentPath);
|
||||
for (auto& file : possibleDependencies)
|
||||
@@ -103,9 +113,15 @@ namespace AZ
|
||||
AssetBuilderSDK::JobDependency jobDependency;
|
||||
jobDependency.m_jobKey = jobKey;
|
||||
jobDependency.m_sourceFile.m_sourceFileDependencyPath = file;
|
||||
|
||||
const bool isMaterialTypeFile = AzFramework::StringFunc::Path::IsExtension(file.c_str(), MaterialTypeSourceData::Extension);
|
||||
jobDependency.m_type = (isMaterialTypeFile && isOrderedOnceForMaterialTypes) ? AssetBuilderSDK::JobDependencyType::OrderOnce : AssetBuilderSDK::JobDependencyType::Order;
|
||||
jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
|
||||
|
||||
// If we aren't finalizing material assets, then a normal job dependency isn't needed because the MaterialTypeAsset data won't be used.
|
||||
// However, we do still need at least an OrderOnce dependency to ensure the Asset Processor knows about the material type asset so the builder can get it's AssetId.
|
||||
// This can significantly reduce AP processing time when a material type or its shaders are edited.
|
||||
if (currentFileIsMaterial && referencedFileIsMaterialType && !shouldFinalizeMaterialAssets)
|
||||
{
|
||||
jobDependency.m_type = AssetBuilderSDK::JobDependencyType::OrderOnce;
|
||||
}
|
||||
|
||||
jobDependencies.push_back(jobDependency);
|
||||
}
|
||||
@@ -156,7 +172,8 @@ namespace AZ
|
||||
// We'll build up this one JobDescriptor and reuse it to register each of the platforms
|
||||
AssetBuilderSDK::JobDescriptor outputJobDescriptor;
|
||||
outputJobDescriptor.m_jobKey = JobKey;
|
||||
|
||||
outputJobDescriptor.m_additionalFingerprintInfo = GetBuilderSettingsFingerprint();
|
||||
|
||||
// Load the file so we can detect and report dependencies.
|
||||
// If the file is a .materialtype, report dependencies on the .shader files.
|
||||
// If the file is a .material, report a dependency on the .materialtype and parent .material file
|
||||
@@ -233,24 +250,12 @@ namespace AZ
|
||||
parentMaterialPath = materialTypePath;
|
||||
}
|
||||
|
||||
// If includeMaterialPropertyNames is false, then a job dependency is needed so the material builder can validate MaterialAsset properties
|
||||
// against the MaterialTypeAsset at asset build time.
|
||||
// If includeMaterialPropertyNames is true, the material properties will be validated at runtime when the material is loaded, so the job dependency
|
||||
// is needed only for first-time processing to set up the initial MaterialAsset. This speeds up AP processing time when a materialtype file
|
||||
// is edited (e.g. 10s when editing StandardPBR.materialtype on AtomTest project from 45s).
|
||||
bool includeMaterialPropertyNames = true;
|
||||
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->Get(includeMaterialPropertyNames, "/O3DE/Atom/RPI/MaterialBuilder/IncludeMaterialPropertyNames");
|
||||
}
|
||||
|
||||
// Register dependency on the parent material source file so we can load it and use it's data to build this variant material.
|
||||
// Note, we don't need a direct dependency on the material type because the parent material will depend on it.
|
||||
AddPossibleDependencies(request.m_sourceFile,
|
||||
parentMaterialPath,
|
||||
JobKey,
|
||||
outputJobDescriptor.m_jobDependencyList,
|
||||
includeMaterialPropertyNames);
|
||||
outputJobDescriptor.m_jobDependencyList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,12 +302,9 @@ namespace AZ
|
||||
return {};
|
||||
}
|
||||
|
||||
if (MaterialSourceData::ApplyVersionUpdatesResult::Failed == material.GetValue().ApplyVersionUpdates(materialSourceFilePath))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
MaterialAssetProcessingMode processingMode = MaterialUtils::BuildersShouldFinalizeMaterialAssets() ? MaterialAssetProcessingMode::PreBake : MaterialAssetProcessingMode::DeferredBake;
|
||||
|
||||
auto materialAssetOutcome = material.GetValue().CreateMaterialAsset(Uuid::CreateRandom(), materialSourceFilePath, ReportMaterialAssetWarningsAsErrors());
|
||||
auto materialAssetOutcome = material.GetValue().CreateMaterialAsset(Uuid::CreateRandom(), materialSourceFilePath, processingMode, ReportMaterialAssetWarningsAsErrors());
|
||||
if (!materialAssetOutcome.IsSuccess())
|
||||
{
|
||||
return {};
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
|
||||
#include <Atom/RPI.Edit/Material/MaterialConverterBus.h>
|
||||
#include <Atom/RPI.Edit/Material/MaterialUtils.h>
|
||||
#include <Atom/RPI.Edit/Common/AssetUtils.h>
|
||||
|
||||
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
|
||||
@@ -44,7 +45,8 @@ namespace AZ
|
||||
if (auto* serialize = azrtti_cast<SerializeContext*>(context))
|
||||
{
|
||||
serialize->Class<MaterialAssetDependenciesComponent, Component>()
|
||||
->Version(5) // Set materialtype dependency to OrderOnce
|
||||
->Version(5) // <<<<< If you have made changes to material code and need to force scene files to be reprocessed, this probably is
|
||||
// NOT the version number you want to bump . What you're looking for is MaterialAssetBuilderComponent::Reflect below.
|
||||
->Attribute(Edit::Attributes::SystemComponentTags, AZStd::vector<Crc32>({ AssetBuilderSDK::ComponentTags::AssetBuilder }));
|
||||
}
|
||||
}
|
||||
@@ -93,9 +95,11 @@ namespace AZ
|
||||
// material properties will be validated at runtime when the material is loaded, so the job dependency is needed only for
|
||||
// first-time processing to set up the initial MaterialAsset. This speeds up AP processing time when a materialtype file is
|
||||
// edited (e.g. 10s when editing StandardPBR.materialtype on AtomTest project from 45s).
|
||||
bool includeMaterialPropertyNames = true;
|
||||
RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames);
|
||||
jobDependency.m_type = includeMaterialPropertyNames ? AssetBuilderSDK::JobDependencyType::OrderOnce : AssetBuilderSDK::JobDependencyType::Order;
|
||||
|
||||
// If we aren't finalizing material assets, then a normal job dependency isn't needed because the MaterialTypeAsset data won't be used.
|
||||
// However, we do still need at least an OrderOnce dependency to ensure the Asset Processor knows about the material type asset so the builder can get it's AssetId.
|
||||
// This can significantly reduce AP processing time when a material type or its shaders are edited.
|
||||
jobDependency.m_type = MaterialUtils::BuildersShouldFinalizeMaterialAssets() ? AssetBuilderSDK::JobDependencyType::OrderOnce : AssetBuilderSDK::JobDependencyType::Order;
|
||||
|
||||
jobDependencyList.push_back(jobDependency);
|
||||
}
|
||||
@@ -108,10 +112,8 @@ namespace AZ
|
||||
bool conversionEnabled = false;
|
||||
RPI::MaterialConverterBus::BroadcastResult(conversionEnabled, &RPI::MaterialConverterBus::Events::IsEnabled);
|
||||
fingerprintInfo.insert(AZStd::string::format("[MaterialConverter enabled=%d]", conversionEnabled));
|
||||
|
||||
bool includeMaterialPropertyNames = true;
|
||||
RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames);
|
||||
fingerprintInfo.insert(AZStd::string::format("[MaterialConverter includeMaterialPropertyNames=%d]", includeMaterialPropertyNames));
|
||||
|
||||
fingerprintInfo.insert(AZStd::string::format("[BuildersShouldFinalizeMaterialAssets=%d]", MaterialUtils::BuildersShouldFinalizeMaterialAssets()));
|
||||
|
||||
if (!conversionEnabled)
|
||||
{
|
||||
@@ -126,7 +128,7 @@ namespace AZ
|
||||
if (auto* serialize = azrtti_cast<SerializeContext*>(context))
|
||||
{
|
||||
serialize->Class<MaterialAssetBuilderComponent, SceneAPI::SceneCore::ExportingComponent>()
|
||||
->Version(16); // Optional material conversion
|
||||
->Version(21); // material dependency improvements updated
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,9 +232,9 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialAssetProcessingMode processingMode = MaterialUtils::BuildersShouldFinalizeMaterialAssets() ? MaterialAssetProcessingMode::PreBake : MaterialAssetProcessingMode::DeferredBake;
|
||||
|
||||
bool includeMaterialPropertyNames = true;
|
||||
RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames);
|
||||
// Build material assets.
|
||||
for (auto& itr : materialSourceDataByUid)
|
||||
{
|
||||
@@ -240,7 +242,7 @@ namespace AZ
|
||||
Data::AssetId assetId(sourceSceneUuid, GetMaterialAssetSubId(materialUid));
|
||||
|
||||
auto materialSourceData = itr.second;
|
||||
Outcome<Data::Asset<MaterialAsset>> result = materialSourceData.m_data.CreateMaterialAsset(assetId, "", false, includeMaterialPropertyNames);
|
||||
Outcome<Data::Asset<MaterialAsset>> result = materialSourceData.m_data.CreateMaterialAsset(assetId, "", processingMode, false);
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
context.m_outputMaterialsByUid[materialUid] = { result.GetValue(), materialSourceData.m_name };
|
||||
|
||||
@@ -74,77 +74,49 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
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...
|
||||
|
||||
PropertyGroupMap newPropertyGroups;
|
||||
for (auto& groupPair : m_properties)
|
||||
{
|
||||
PropertyMap& propertyMap = groupPair.second;
|
||||
|
||||
for (auto& propertyPair : propertyMap)
|
||||
{
|
||||
MaterialPropertyId propertyId{groupPair.first, propertyPair.first};
|
||||
|
||||
if (materialTypeSourceData.ApplyPropertyRenames(propertyId, m_materialTypeVersion))
|
||||
{
|
||||
changesWereApplied = true;
|
||||
}
|
||||
|
||||
newPropertyGroups[propertyId.GetGroupName().GetStringView()][propertyId.GetPropertyName().GetStringView()] = propertyPair.second;
|
||||
}
|
||||
}
|
||||
|
||||
if (changesWereApplied)
|
||||
{
|
||||
m_properties = AZStd::move(newPropertyGroups);
|
||||
|
||||
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: '%s'.",
|
||||
m_materialTypeVersion, materialTypeFullPath.c_str(), materialTypeSourceData.m_version, materialSourceFilePath.data());
|
||||
}
|
||||
|
||||
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
|
||||
Data::AssetId assetId, AZStd::string_view materialSourceFilePath, MaterialAssetProcessingMode processingMode, bool elevateWarnings) const
|
||||
{
|
||||
MaterialAssetCreator materialAssetCreator;
|
||||
materialAssetCreator.SetElevateWarnings(elevateWarnings);
|
||||
|
||||
if (m_parentMaterial.empty())
|
||||
Outcome<Data::AssetId> materialTypeAssetId = AssetUtils::MakeAssetId(materialSourceFilePath, m_materialType, 0);
|
||||
if (!materialTypeAssetId)
|
||||
{
|
||||
auto materialTypeAsset = AssetUtils::LoadAsset<MaterialTypeAsset>(materialSourceFilePath, m_materialType);
|
||||
if (!materialTypeAsset.IsSuccess())
|
||||
return Failure();
|
||||
}
|
||||
|
||||
Data::Asset<MaterialTypeAsset> materialTypeAsset;
|
||||
|
||||
switch (processingMode)
|
||||
{
|
||||
case MaterialAssetProcessingMode::DeferredBake:
|
||||
{
|
||||
// Don't load the material type data, just create a reference to it
|
||||
materialTypeAsset = Data::Asset<MaterialTypeAsset>{ materialTypeAssetId.GetValue(), azrtti_typeid<MaterialTypeAsset>(), m_materialType };
|
||||
break;
|
||||
}
|
||||
case MaterialAssetProcessingMode::PreBake:
|
||||
{
|
||||
// In this case we need to load the material type data in preparation for the material->Finalize() step below.
|
||||
auto materialTypeAssetOutcome = AssetUtils::LoadAsset<MaterialTypeAsset>(materialTypeAssetId.GetValue());
|
||||
if (!materialTypeAssetOutcome)
|
||||
{
|
||||
return Failure();
|
||||
}
|
||||
materialTypeAsset = materialTypeAssetOutcome.GetValue();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
AZ_Assert(false, "Unhandled MaterialAssetProcessingMode");
|
||||
return Failure();
|
||||
}
|
||||
|
||||
materialAssetCreator.Begin(assetId, *materialTypeAsset.GetValue().Get(), includeMaterialPropertyNames);
|
||||
}
|
||||
else
|
||||
|
||||
materialAssetCreator.Begin(assetId, materialTypeAsset, processingMode == MaterialAssetProcessingMode::PreBake);
|
||||
|
||||
if (!m_parentMaterial.empty())
|
||||
{
|
||||
auto parentMaterialAsset = AssetUtils::LoadAsset<MaterialAsset>(materialSourceFilePath, m_parentMaterial);
|
||||
if (!parentMaterialAsset.IsSuccess())
|
||||
@@ -154,25 +126,53 @@ namespace AZ
|
||||
|
||||
// Make sure the parent material has the same material type
|
||||
{
|
||||
auto materialTypeIdOutcome = AssetUtils::MakeAssetId(materialSourceFilePath, m_materialType, 0);
|
||||
if (!materialTypeIdOutcome.IsSuccess())
|
||||
{
|
||||
return Failure();
|
||||
}
|
||||
|
||||
Data::AssetId expectedMaterialTypeId = materialTypeIdOutcome.GetValue();
|
||||
Data::AssetId parentMaterialId = parentMaterialAsset.GetValue().GetId();
|
||||
// This will only be valid if the parent material is not a material type
|
||||
Data::AssetId parentsMaterialTypeId = parentMaterialAsset.GetValue()->GetMaterialTypeAsset().GetId();
|
||||
|
||||
if (expectedMaterialTypeId != parentMaterialId && expectedMaterialTypeId != parentsMaterialTypeId)
|
||||
if (materialTypeAssetId.GetValue() != parentsMaterialTypeId)
|
||||
{
|
||||
AZ_Error("MaterialSourceData", false, "This material and its parent material do not share the same material type.");
|
||||
return Failure();
|
||||
}
|
||||
}
|
||||
|
||||
materialAssetCreator.Begin(assetId, *parentMaterialAsset.GetValue().Get(), includeMaterialPropertyNames);
|
||||
// Inherit the parent's property values...
|
||||
switch (processingMode)
|
||||
{
|
||||
case MaterialAssetProcessingMode::DeferredBake:
|
||||
{
|
||||
for (auto& property : parentMaterialAsset.GetValue()->GetRawPropertyValues())
|
||||
{
|
||||
materialAssetCreator.SetPropertyValue(property.first, property.second);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case MaterialAssetProcessingMode::PreBake:
|
||||
{
|
||||
const MaterialPropertiesLayout* propertiesLayout = parentMaterialAsset.GetValue()->GetMaterialPropertiesLayout();
|
||||
|
||||
if (parentMaterialAsset.GetValue()->GetPropertyValues().size() != propertiesLayout->GetPropertyCount())
|
||||
{
|
||||
AZ_Assert(false, "The parent material should have been finalized with %zu properties but it has %zu. Something is out of sync.",
|
||||
propertiesLayout->GetPropertyCount(), parentMaterialAsset.GetValue()->GetPropertyValues().size());
|
||||
return Failure();
|
||||
}
|
||||
|
||||
for (size_t propertyIndex = 0; propertyIndex < propertiesLayout->GetPropertyCount(); ++propertyIndex)
|
||||
{
|
||||
materialAssetCreator.SetPropertyValue(
|
||||
propertiesLayout->GetPropertyDescriptor(MaterialPropertyIndex{propertyIndex})->GetName(),
|
||||
parentMaterialAsset.GetValue()->GetPropertyValues()[propertyIndex]);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
AZ_Assert(false, "Unhandled MaterialAssetProcessingMode");
|
||||
return Failure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ApplyPropertiesToAssetCreator(materialAssetCreator, materialSourceFilePath);
|
||||
@@ -192,7 +192,6 @@ namespace AZ
|
||||
Data::AssetId assetId,
|
||||
AZStd::string_view materialSourceFilePath,
|
||||
bool elevateWarnings,
|
||||
bool includeMaterialPropertyNames,
|
||||
AZStd::unordered_set<AZStd::string>* sourceDependencies) const
|
||||
{
|
||||
const auto materialTypeSourcePath = AssetUtils::ResolvePathReference(materialSourceFilePath, m_materialType);
|
||||
@@ -266,11 +265,18 @@ namespace AZ
|
||||
parentSourceAbsPath = AssetUtils::ResolvePathReference(parentSourceAbsPath, parentSourceRelPath);
|
||||
parentSourceDataStack.emplace_back(AZStd::move(parentSourceData));
|
||||
}
|
||||
|
||||
// Unlike CreateMaterialAsset(), we can always finalize the material here because we loaded created the MaterialTypeAsset from
|
||||
// the source .materialtype file, so the necessary data is always available.
|
||||
// (In case you are wondering why we don't use CreateMaterialAssetFromSourceData in MaterialBuilder: that would require a
|
||||
// source dependency between the .materialtype and .material file, which would cause all .material files to rebuild when you
|
||||
// edit the .materialtype; it's faster to not read the material type data at all ... until it's needed at runtime)
|
||||
const bool finalize = true;
|
||||
|
||||
// Create the material asset from all the previously loaded source data
|
||||
MaterialAssetCreator materialAssetCreator;
|
||||
materialAssetCreator.SetElevateWarnings(elevateWarnings);
|
||||
materialAssetCreator.Begin(assetId, *materialTypeAsset.GetValue().Get(), includeMaterialPropertyNames);
|
||||
materialAssetCreator.Begin(assetId, materialTypeAsset.GetValue(), finalize);
|
||||
|
||||
while (!parentSourceDataStack.empty())
|
||||
{
|
||||
@@ -306,59 +312,29 @@ namespace AZ
|
||||
{
|
||||
materialAssetCreator.ReportWarning("Source data for material property value is invalid.");
|
||||
}
|
||||
else
|
||||
// If the source value type is a string, there are two possible property types: Image and Enum. If there is a "." in
|
||||
// the string (for the extension) we assume it's an Image and look up the referenced Asset. Otherwise, we can assume
|
||||
// it's an Enum value and just preserve the original string.
|
||||
else if (property.second.m_value.Is<AZStd::string>() && AzFramework::StringFunc::Contains(property.second.m_value.GetValue<AZStd::string>(), "."))
|
||||
{
|
||||
MaterialPropertyIndex propertyIndex =
|
||||
materialAssetCreator.m_materialPropertiesLayout->FindPropertyIndex(propertyId.GetFullName());
|
||||
if (propertyIndex.IsValid())
|
||||
{
|
||||
const MaterialPropertyDescriptor* propertyDescriptor =
|
||||
materialAssetCreator.m_materialPropertiesLayout->GetPropertyDescriptor(propertyIndex);
|
||||
switch (propertyDescriptor->GetDataType())
|
||||
{
|
||||
case MaterialPropertyDataType::Image:
|
||||
{
|
||||
Data::Asset<ImageAsset> imageAsset;
|
||||
Data::Asset<ImageAsset> imageAsset;
|
||||
|
||||
MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference(
|
||||
imageAsset, materialSourceFilePath, property.second.m_value.GetValue<AZStd::string>());
|
||||
MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference(
|
||||
imageAsset, materialSourceFilePath, property.second.m_value.GetValue<AZStd::string>());
|
||||
|
||||
if (result == MaterialUtils::GetImageAssetResult::Missing)
|
||||
{
|
||||
materialAssetCreator.ReportWarning(
|
||||
"Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(),
|
||||
property.second.m_value.GetValue<AZStd::string>().data());
|
||||
}
|
||||
|
||||
imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad);
|
||||
materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset);
|
||||
}
|
||||
break;
|
||||
case MaterialPropertyDataType::Enum:
|
||||
{
|
||||
AZ::Name enumName = AZ::Name(property.second.m_value.GetValue<AZStd::string>());
|
||||
uint32_t enumValue = propertyDescriptor->GetEnumValue(enumName);
|
||||
if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue)
|
||||
{
|
||||
materialAssetCreator.ReportError(
|
||||
"Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr());
|
||||
}
|
||||
else
|
||||
{
|
||||
materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), enumValue);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), property.second.m_value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (result == MaterialUtils::GetImageAssetResult::Missing)
|
||||
{
|
||||
materialAssetCreator.ReportWarning(
|
||||
"Can not find property id '%s' in MaterialPropertyLayout", propertyId.GetFullName().GetStringView().data());
|
||||
"Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(),
|
||||
property.second.m_value.GetValue<AZStd::string>().data());
|
||||
}
|
||||
|
||||
imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad);
|
||||
materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset);
|
||||
}
|
||||
else
|
||||
{
|
||||
materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), property.second.m_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerializationResult.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
@@ -135,6 +136,20 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BuildersShouldFinalizeMaterialAssets()
|
||||
{
|
||||
// We default to the faster workflow for developers. Enable this registry setting when releasing the
|
||||
// game for faster load times and obfuscation of material assets.
|
||||
bool shouldFinalize = false;
|
||||
|
||||
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->Get(shouldFinalize, "/O3DE/Atom/RPI/MaterialBuilder/FinalizeMaterialAssets");
|
||||
}
|
||||
|
||||
return shouldFinalize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +33,12 @@ namespace AZ
|
||||
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<MaterialAsset, AZ::Data::AssetData>()
|
||||
->Version(11) // Material version update
|
||||
->Version(14) // added m_rawPropertyValues
|
||||
->Field("materialTypeAsset", &MaterialAsset::m_materialTypeAsset)
|
||||
->Field("materialTypeVersion", &MaterialAsset::m_materialTypeVersion)
|
||||
->Field("propertyValues", &MaterialAsset::m_propertyValues)
|
||||
->Field("propertyNames", &MaterialAsset::m_propertyNames)
|
||||
->Field("rawPropertyValues", &MaterialAsset::m_rawPropertyValues)
|
||||
->Field("finalized", &MaterialAsset::m_wasPreFinalized)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -102,32 +103,119 @@ namespace AZ
|
||||
{
|
||||
return m_materialTypeAsset->GetMaterialPropertiesLayout();
|
||||
}
|
||||
|
||||
AZStd::array_view<MaterialPropertyValue> MaterialAsset::GetPropertyValues() const
|
||||
|
||||
bool MaterialAsset::WasPreFinalized() const
|
||||
{
|
||||
// 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 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();
|
||||
}
|
||||
return m_wasPreFinalized;
|
||||
}
|
||||
|
||||
if (m_isDirty)
|
||||
void MaterialAsset::Finalize(AZStd::function<void(const char*)> reportWarning, AZStd::function<void(const char*)> reportError)
|
||||
{
|
||||
if (m_wasPreFinalized)
|
||||
{
|
||||
m_isFinalized = true;
|
||||
}
|
||||
|
||||
if (m_isFinalized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!reportWarning)
|
||||
{
|
||||
reportWarning = []([[maybe_unused]] const char* message)
|
||||
{
|
||||
const_cast<MaterialAsset*>(this)->RealignPropertyValuesAndNames();
|
||||
AZ_Warning(s_debugTraceName, false, "%s", message);
|
||||
};
|
||||
}
|
||||
|
||||
if (!reportError)
|
||||
{
|
||||
reportError = []([[maybe_unused]] const char* message)
|
||||
{
|
||||
AZ_Error(s_debugTraceName, false, "%s", message);
|
||||
};
|
||||
}
|
||||
|
||||
const uint32_t materialTypeVersion = m_materialTypeAsset->GetVersion();
|
||||
if (m_materialTypeVersion < materialTypeVersion)
|
||||
{
|
||||
// It is possible that the material type has had some properties renamed or otherwise updated. 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.
|
||||
ApplyVersionUpdates();
|
||||
}
|
||||
|
||||
const MaterialPropertiesLayout* propertyLayout = GetMaterialPropertiesLayout();
|
||||
|
||||
AZStd::vector<MaterialPropertyValue> finalizedPropertyValues(m_materialTypeAsset->GetDefaultPropertyValues().begin(), m_materialTypeAsset->GetDefaultPropertyValues().end());
|
||||
|
||||
for (const auto& [name, value] : m_rawPropertyValues)
|
||||
{
|
||||
const MaterialPropertyIndex propertyIndex = propertyLayout->FindPropertyIndex(name);
|
||||
if (propertyIndex.IsValid())
|
||||
{
|
||||
const MaterialPropertyDescriptor* propertyDescriptor = propertyLayout->GetPropertyDescriptor(propertyIndex);
|
||||
|
||||
if (value.Is<AZStd::string>() && propertyDescriptor->GetDataType() == MaterialPropertyDataType::Enum)
|
||||
{
|
||||
AZ::Name enumName = AZ::Name(value.GetValue<AZStd::string>());
|
||||
uint32_t enumValue = propertyDescriptor->GetEnumValue(enumName);
|
||||
if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue)
|
||||
{
|
||||
reportWarning(AZStd::string::format("Material property name \"%s\" has invalid enum value \"%s\".", name.GetCStr(), enumName.GetCStr()).c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
finalizedPropertyValues[propertyIndex.GetIndex()] = enumValue;
|
||||
}
|
||||
}
|
||||
else if (value.Is<AZStd::string>() && propertyDescriptor->GetDataType() == MaterialPropertyDataType::Image)
|
||||
{
|
||||
// Here we assume that the material asset builder resolved any image source file paths to an ImageAsset reference.
|
||||
// So the only way a string could be present is if it's an empty image path reference, meaning no image should be bound.
|
||||
AZ_Assert(value.GetValue<AZStd::string>().empty(), "Material property '%s' references in image '%s'. Image file paths must be resolved by the material asset builder.");
|
||||
|
||||
finalizedPropertyValues[propertyIndex.GetIndex()] = Data::Asset<ImageAsset>{};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ValidateMaterialPropertyDataType(value.GetTypeId(), name, propertyDescriptor, reportError))
|
||||
{
|
||||
finalizedPropertyValues[propertyIndex.GetIndex()] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reportWarning(AZStd::string::format("Material property name \"%s\" is not found in the material properties layout and will not be used.", name.GetCStr()).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
m_propertyValues.swap(finalizedPropertyValues);
|
||||
|
||||
m_isFinalized = true;
|
||||
}
|
||||
|
||||
const AZStd::vector<MaterialPropertyValue>& MaterialAsset::GetPropertyValues()
|
||||
{
|
||||
// This can't be done in MaterialAssetHandler::LoadAssetData because the MaterialTypeAsset isn't necessarily loaded at that point.
|
||||
// And it can't be done in PostLoadInit() because that happens on the next frame which might be too late.
|
||||
// And overriding AssetHandler::InitAsset in MaterialAssetHandler didn't work, because there seems to be non-determinism on the order
|
||||
// of InitAsset calls when a ModelAsset references a MaterialAsset, the model gets initialized first and then fails to use the material.
|
||||
// So we finalize just-in-time when properties are accessed.
|
||||
// If we could solve the problem with InitAsset, that would be the ideal place to call Finalize() and we could make GetPropertyValues() const again.
|
||||
Finalize();
|
||||
|
||||
AZ_Assert(GetMaterialPropertiesLayout() && m_propertyValues.size() == GetMaterialPropertiesLayout()->GetPropertyCount(), "MaterialAsset should be finalized but does not have the right number of property values.");
|
||||
|
||||
return m_propertyValues;
|
||||
}
|
||||
|
||||
const AZStd::vector<AZStd::pair<Name, MaterialPropertyValue>>& MaterialAsset::GetRawPropertyValues() const
|
||||
{
|
||||
return m_rawPropertyValues;
|
||||
}
|
||||
|
||||
void MaterialAsset::SetReady()
|
||||
{
|
||||
@@ -173,34 +261,6 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialAsset::RealignPropertyValuesAndNames()
|
||||
{
|
||||
const MaterialPropertiesLayout* propertyLayout = GetMaterialPropertiesLayout();
|
||||
AZStd::vector<MaterialPropertyValue> alignedPropertyValues(m_materialTypeAsset->GetDefaultPropertyValues().begin(), m_materialTypeAsset->GetDefaultPropertyValues().end());
|
||||
for (size_t i = 0; i < m_propertyNames.size(); ++i)
|
||||
{
|
||||
const MaterialPropertyIndex propertyIndex = propertyLayout->FindPropertyIndex(m_propertyNames[i]);
|
||||
if (propertyIndex.IsValid())
|
||||
{
|
||||
alignedPropertyValues[propertyIndex.GetIndex()] = m_propertyValues[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning(s_debugTraceName, false, "Material property name \"%s\" is not found in the material properties layout and will not be used.", m_propertyNames[i].GetCStr());
|
||||
}
|
||||
}
|
||||
m_propertyValues.swap(alignedPropertyValues);
|
||||
|
||||
const size_t propertyCount = propertyLayout->GetPropertyCount();
|
||||
m_propertyNames.resize(propertyCount);
|
||||
for (size_t i = 0; i < propertyCount; ++i)
|
||||
{
|
||||
m_propertyNames[i] = propertyLayout->GetPropertyDescriptor(MaterialPropertyIndex{ i })->GetName();
|
||||
}
|
||||
|
||||
m_isDirty = false;
|
||||
}
|
||||
|
||||
void MaterialAsset::ApplyVersionUpdates()
|
||||
{
|
||||
if (m_materialTypeVersion == m_materialTypeAsset->GetVersion())
|
||||
@@ -248,7 +308,13 @@ namespace AZ
|
||||
// This also covers the case where just the MaterialTypeAsset is reloaded and not the MaterialAsset.
|
||||
m_materialTypeAsset = newMaterialTypeAsset;
|
||||
|
||||
m_isDirty = true;
|
||||
// If the material asset was not finalized on disk, then we clear the previously finalized property values to force re-finalize.
|
||||
// This is necessary in case the property layout changed in some way.
|
||||
if (!m_wasPreFinalized)
|
||||
{
|
||||
m_isFinalized = false;
|
||||
m_propertyValues.clear();
|
||||
}
|
||||
|
||||
// Notify interested parties that this MaterialAsset is changed and may require other data to reinitialize as well
|
||||
MaterialReloadNotificationBus::Event(GetId(), &MaterialReloadNotifications::OnMaterialAssetReinitialized, Data::Asset<MaterialAsset>{this, AZ::Data::AssetLoadBehavior::PreLoad});
|
||||
|
||||
@@ -16,89 +16,24 @@ namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
{
|
||||
void MaterialAssetCreator::Begin(const Data::AssetId& assetId, MaterialAsset& parentMaterial, bool includeMaterialPropertyNames)
|
||||
{
|
||||
BeginCommon(assetId);
|
||||
|
||||
if (ValidateIsReady())
|
||||
{
|
||||
m_asset->m_materialTypeAsset = parentMaterial.m_materialTypeAsset;
|
||||
m_asset->m_materialTypeVersion = m_asset->m_materialTypeAsset->GetVersion();
|
||||
|
||||
if (!m_asset->m_materialTypeAsset)
|
||||
{
|
||||
ReportError("MaterialTypeAsset is null");
|
||||
return;
|
||||
}
|
||||
|
||||
m_materialPropertiesLayout = m_asset->GetMaterialPropertiesLayout();
|
||||
if (!m_materialPropertiesLayout)
|
||||
{
|
||||
ReportError("MaterialPropertiesLayout is null");
|
||||
return;
|
||||
}
|
||||
if (includeMaterialPropertyNames)
|
||||
{
|
||||
PopulatePropertyNameList();
|
||||
}
|
||||
|
||||
// Note we don't have to check the validity of these property values because the parent material's AssetCreator already did that.
|
||||
m_asset->m_propertyValues.assign(parentMaterial.GetPropertyValues().begin(), parentMaterial.GetPropertyValues().end());
|
||||
|
||||
auto warningFunc = [this](const char* message)
|
||||
{
|
||||
ReportWarning("%s", message);
|
||||
};
|
||||
auto errorFunc = [this](const char* message)
|
||||
{
|
||||
ReportError("%s", message);
|
||||
};
|
||||
MaterialAssetCreatorCommon::OnBegin(m_materialPropertiesLayout, &(m_asset->m_propertyValues), warningFunc, errorFunc);
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialAssetCreator::Begin(const Data::AssetId& assetId, MaterialTypeAsset& materialType, bool includeMaterialPropertyNames)
|
||||
void MaterialAssetCreator::Begin(const Data::AssetId& assetId, const Data::Asset<MaterialTypeAsset>& materialType, bool shouldFinalize)
|
||||
{
|
||||
BeginCommon(assetId);
|
||||
|
||||
if (ValidateIsReady())
|
||||
{
|
||||
m_asset->m_materialTypeAsset = { &materialType, AZ::Data::AssetLoadBehavior::PreLoad };
|
||||
m_shouldFinalize = shouldFinalize;
|
||||
|
||||
if (!m_asset->m_materialTypeAsset)
|
||||
m_asset->m_materialTypeAsset = materialType;
|
||||
m_asset->m_materialTypeAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad);
|
||||
|
||||
if (shouldFinalize && !m_asset->m_materialTypeAsset)
|
||||
{
|
||||
ReportError("MaterialTypeAsset is null");
|
||||
return;
|
||||
ReportError("MaterialTypeAsset is null, the MaterialAsset cannot be finalized");
|
||||
}
|
||||
m_asset->m_materialTypeVersion = m_asset->m_materialTypeAsset->GetVersion();
|
||||
|
||||
m_materialPropertiesLayout = m_asset->GetMaterialPropertiesLayout();
|
||||
if (includeMaterialPropertyNames)
|
||||
{
|
||||
PopulatePropertyNameList();
|
||||
}
|
||||
|
||||
if (!m_materialPropertiesLayout)
|
||||
{
|
||||
ReportError("MaterialPropertiesLayout is null");
|
||||
return;
|
||||
}
|
||||
|
||||
// Note we don't have to check the validity of these property values because the parent material's AssetCreator already did that.
|
||||
m_asset->m_propertyValues.assign(materialType.GetDefaultPropertyValues().begin(), materialType.GetDefaultPropertyValues().end());
|
||||
|
||||
auto warningFunc = [this](const char* message)
|
||||
{
|
||||
ReportWarning("%s", message);
|
||||
};
|
||||
auto errorFunc = [this](const char* message)
|
||||
{
|
||||
ReportError("%s", message);
|
||||
};
|
||||
MaterialAssetCreatorCommon::OnBegin(m_materialPropertiesLayout, &(m_asset->m_propertyValues), warningFunc, errorFunc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool MaterialAssetCreator::End(Data::Asset<MaterialAsset>& result)
|
||||
{
|
||||
if (!ValidateIsReady())
|
||||
@@ -106,22 +41,71 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
m_materialPropertiesLayout = nullptr;
|
||||
MaterialAssetCreatorCommon::OnEnd();
|
||||
|
||||
m_asset->SetReady();
|
||||
|
||||
if (m_shouldFinalize)
|
||||
{
|
||||
m_asset->Finalize(
|
||||
[this](const char* message) { ReportWarning("%s", message); },
|
||||
[this](const char* message) { ReportError("%s", message); });
|
||||
|
||||
m_asset->m_wasPreFinalized = true;
|
||||
|
||||
// Finalize() doesn't clear the raw property data because that's the same function used at runtime, which does need to maintain the raw data
|
||||
// to support hot reload. But here we are pre-baking with the assumption that AP build dependencies will keep the material type
|
||||
// and material asset in sync, so we can discard the raw property data and just rely on the data in the material type asset.
|
||||
m_asset->m_rawPropertyValues.clear();
|
||||
}
|
||||
|
||||
return EndCommon(result);
|
||||
}
|
||||
|
||||
void MaterialAssetCreator::PopulatePropertyNameList()
|
||||
|
||||
void MaterialAssetCreator::SetMaterialTypeVersion(uint32_t version)
|
||||
{
|
||||
for (int i = 0; i < m_materialPropertiesLayout->GetPropertyCount(); ++i)
|
||||
if (ValidateIsReady())
|
||||
{
|
||||
MaterialPropertyIndex propertyIndex{ i };
|
||||
auto& propertyName = m_materialPropertiesLayout->GetPropertyDescriptor(propertyIndex)->GetName();
|
||||
m_asset->m_propertyNames.emplace_back(propertyName);
|
||||
m_asset->m_materialTypeVersion = version;
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialAssetCreator::SetPropertyValue(const Name& name, const MaterialPropertyValue& value)
|
||||
{
|
||||
if (ValidateIsReady())
|
||||
{
|
||||
// Here we are careful to keep the properties in the same order they were encountered. When the MaterialAsset
|
||||
// is later finalized with a MaterialTypeAsset, there could be a version update procedure that includes renamed
|
||||
// properties. So it's possible that the same property could be encountered twice but with two different names.
|
||||
// Preserving the original order will ensure that the later properties still overwrite the earlier ones even after
|
||||
// renames have been applied.
|
||||
|
||||
auto iter = AZStd::find_if(m_asset->m_rawPropertyValues.begin(), m_asset->m_rawPropertyValues.end(), [&name](const AZStd::pair<Name, MaterialPropertyValue>& pair)
|
||||
{
|
||||
return pair.first == name;
|
||||
});
|
||||
|
||||
if (iter != m_asset->m_rawPropertyValues.end())
|
||||
{
|
||||
m_asset->m_rawPropertyValues.erase(iter);
|
||||
}
|
||||
|
||||
m_asset->m_rawPropertyValues.emplace_back(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialAssetCreator::SetPropertyValue(const Name& name, const Data::Asset<ImageAsset>& imageAsset)
|
||||
{
|
||||
SetPropertyValue(name, MaterialPropertyValue{imageAsset});
|
||||
}
|
||||
|
||||
void MaterialAssetCreator::SetPropertyValue(const Name& name, const Data::Asset<StreamingImageAsset>& imageAsset)
|
||||
{
|
||||
SetPropertyValue(name, Data::Asset<ImageAsset>(imageAsset));
|
||||
}
|
||||
|
||||
void MaterialAssetCreator::SetPropertyValue(const Name& name, const Data::Asset<AttachmentImageAsset>& imageAsset)
|
||||
{
|
||||
SetPropertyValue(name, Data::Asset<ImageAsset>(imageAsset));
|
||||
}
|
||||
|
||||
} // namespace RPI
|
||||
} // namespace AZ
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* 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/MaterialAssetCreatorCommon.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
{
|
||||
void MaterialAssetCreatorCommon::OnBegin(
|
||||
const MaterialPropertiesLayout* propertyLayout,
|
||||
AZStd::vector<MaterialPropertyValue>* propertyValues,
|
||||
const AZStd::function<void(const char*)>& warningFunc,
|
||||
const AZStd::function<void(const char*)>& errorFunc)
|
||||
{
|
||||
m_propertyLayout = propertyLayout;
|
||||
m_propertyValues = propertyValues;
|
||||
m_reportWarning = warningFunc;
|
||||
m_reportError = errorFunc;
|
||||
}
|
||||
|
||||
void MaterialAssetCreatorCommon::OnEnd()
|
||||
{
|
||||
m_propertyLayout = nullptr;
|
||||
m_propertyValues = nullptr;
|
||||
m_reportWarning = nullptr;
|
||||
m_reportError = nullptr;
|
||||
}
|
||||
|
||||
MaterialPropertyDataType MaterialAssetCreatorCommon::GetMaterialPropertyDataType(TypeId typeId) const
|
||||
{
|
||||
if (typeId == azrtti_typeid<bool>()) { return MaterialPropertyDataType::Bool; }
|
||||
if (typeId == azrtti_typeid<int32_t>()) { return MaterialPropertyDataType::Int; }
|
||||
if (typeId == azrtti_typeid<uint32_t>()) { return MaterialPropertyDataType::UInt; }
|
||||
if (typeId == azrtti_typeid<float>()) { return MaterialPropertyDataType::Float; }
|
||||
if (typeId == azrtti_typeid<Vector2>()) { return MaterialPropertyDataType::Vector2; }
|
||||
if (typeId == azrtti_typeid<Vector3>()) { return MaterialPropertyDataType::Vector3; }
|
||||
if (typeId == azrtti_typeid<Vector4>()) { return MaterialPropertyDataType::Vector4; }
|
||||
if (typeId == azrtti_typeid<Color>()) { return MaterialPropertyDataType::Color; }
|
||||
if (typeId == azrtti_typeid<Data::Asset<ImageAsset>>()) { return MaterialPropertyDataType::Image; }
|
||||
else
|
||||
{
|
||||
return MaterialPropertyDataType::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
bool MaterialAssetCreatorCommon::ValidateDataType(TypeId typeId, const Name& propertyName, const MaterialPropertyDescriptor* materialPropertyDescriptor)
|
||||
{
|
||||
auto expectedDataType = materialPropertyDescriptor->GetDataType();
|
||||
auto actualDataType = GetMaterialPropertyDataType(typeId);
|
||||
|
||||
if (expectedDataType == MaterialPropertyDataType::Enum)
|
||||
{
|
||||
if (actualDataType != MaterialPropertyDataType::UInt)
|
||||
{
|
||||
m_reportError(
|
||||
AZStd::string::format("Material property '%s' is a Enum type, can only accept UInt value, input value is %s",
|
||||
propertyName.GetCStr(),
|
||||
ToString(actualDataType)
|
||||
).data());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (expectedDataType != actualDataType)
|
||||
{
|
||||
m_reportError(
|
||||
AZStd::string::format("Material property '%s': Type mismatch. Expected %s but was %s",
|
||||
propertyName.GetCStr(),
|
||||
ToString(expectedDataType),
|
||||
ToString(actualDataType)
|
||||
).data());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaterialAssetCreatorCommon::PropertyCheck(TypeId typeId, const Name& name)
|
||||
{
|
||||
if (!m_reportWarning || !m_reportError)
|
||||
{
|
||||
AZ_Assert(false, "Call Begin() on the AssetCreator before using it.");
|
||||
return false;
|
||||
}
|
||||
|
||||
MaterialPropertyIndex propertyIndex = m_propertyLayout->FindPropertyIndex(name);
|
||||
if (!propertyIndex.IsValid())
|
||||
{
|
||||
m_reportWarning(
|
||||
AZStd::string::format("Material property '%s' not found",
|
||||
name.GetCStr()
|
||||
).data());
|
||||
return false;
|
||||
}
|
||||
|
||||
const MaterialPropertyDescriptor* materialPropertyDescriptor = m_propertyLayout->GetPropertyDescriptor(propertyIndex);
|
||||
if (!materialPropertyDescriptor)
|
||||
{
|
||||
m_reportError("A material property index was found but the property descriptor was null");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValidateDataType(typeId, name, materialPropertyDescriptor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MaterialAssetCreatorCommon::SetPropertyValue(const Name& name, const Data::Asset<ImageAsset>& imageAsset)
|
||||
{
|
||||
return SetPropertyValue(name, MaterialPropertyValue(imageAsset));
|
||||
}
|
||||
|
||||
void MaterialAssetCreatorCommon::SetPropertyValue(const Name& name, const MaterialPropertyValue& value)
|
||||
{
|
||||
if (PropertyCheck(value.GetTypeId(), name))
|
||||
{
|
||||
MaterialPropertyIndex propertyIndex = m_propertyLayout->FindPropertyIndex(name);
|
||||
(*m_propertyValues)[propertyIndex.GetIndex()] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialAssetCreatorCommon::SetPropertyValue(const Name& name, const Data::Asset<StreamingImageAsset>& imageAsset)
|
||||
{
|
||||
SetPropertyValue(name, Data::Asset<ImageAsset>(imageAsset));
|
||||
}
|
||||
|
||||
void MaterialAssetCreatorCommon::SetPropertyValue(const Name& name, const Data::Asset<AttachmentImageAsset>& imageAsset)
|
||||
{
|
||||
SetPropertyValue(name, Data::Asset<ImageAsset>(imageAsset));
|
||||
}
|
||||
} // namespace RPI
|
||||
} // namespace AZ
|
||||
@@ -97,6 +97,57 @@ namespace AZ
|
||||
return AZStd::string::format("<Unkonwn type %s>", typeId.ToString<AZStd::string>().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateMaterialPropertyDataType(TypeId typeId, const Name& propertyName, const MaterialPropertyDescriptor* materialPropertyDescriptor, AZStd::function<void(const char*)> onError)
|
||||
{
|
||||
auto toMaterialPropertyDataType = [](TypeId typeId)
|
||||
{
|
||||
if (typeId == azrtti_typeid<bool>()) { return MaterialPropertyDataType::Bool; }
|
||||
if (typeId == azrtti_typeid<int32_t>()) { return MaterialPropertyDataType::Int; }
|
||||
if (typeId == azrtti_typeid<uint32_t>()) { return MaterialPropertyDataType::UInt; }
|
||||
if (typeId == azrtti_typeid<float>()) { return MaterialPropertyDataType::Float; }
|
||||
if (typeId == azrtti_typeid<Vector2>()) { return MaterialPropertyDataType::Vector2; }
|
||||
if (typeId == azrtti_typeid<Vector3>()) { return MaterialPropertyDataType::Vector3; }
|
||||
if (typeId == azrtti_typeid<Vector4>()) { return MaterialPropertyDataType::Vector4; }
|
||||
if (typeId == azrtti_typeid<Color>()) { return MaterialPropertyDataType::Color; }
|
||||
if (typeId == azrtti_typeid<Data::Asset<ImageAsset>>()) { return MaterialPropertyDataType::Image; }
|
||||
else
|
||||
{
|
||||
return MaterialPropertyDataType::Invalid;
|
||||
}
|
||||
};
|
||||
|
||||
auto expectedDataType = materialPropertyDescriptor->GetDataType();
|
||||
auto actualDataType = toMaterialPropertyDataType(typeId);
|
||||
|
||||
if (expectedDataType == MaterialPropertyDataType::Enum)
|
||||
{
|
||||
if (actualDataType != MaterialPropertyDataType::UInt)
|
||||
{
|
||||
onError(
|
||||
AZStd::string::format("Material property '%s' is a Enum type, can only accept UInt value, input value is %s",
|
||||
propertyName.GetCStr(),
|
||||
ToString(actualDataType)
|
||||
).data());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (expectedDataType != actualDataType)
|
||||
{
|
||||
onError(
|
||||
AZStd::string::format("Material property '%s': Type mismatch. Expected %s but was %s",
|
||||
propertyName.GetCStr(),
|
||||
ToString(expectedDataType),
|
||||
ToString(actualDataType)
|
||||
).data());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MaterialPropertyOutputId::Reflect(ReflectContext* context)
|
||||
{
|
||||
|
||||
@@ -22,17 +22,6 @@ namespace AZ
|
||||
{
|
||||
m_materialPropertiesLayout = aznew MaterialPropertiesLayout;
|
||||
m_asset->m_materialPropertiesLayout = m_materialPropertiesLayout;
|
||||
|
||||
auto warningFunc = [this](const char* message)
|
||||
{
|
||||
ReportWarning("%s", message);
|
||||
};
|
||||
auto errorFunc = [this](const char* message)
|
||||
{
|
||||
ReportError("%s", message);
|
||||
};
|
||||
// Set empty for UV names as material type asset doesn't have overrides.
|
||||
MaterialAssetCreatorCommon::OnBegin(m_materialPropertiesLayout, &(m_asset->m_propertyValues), warningFunc, errorFunc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +37,6 @@ namespace AZ
|
||||
m_materialShaderResourceGroupLayout = nullptr;
|
||||
m_materialPropertiesLayout = nullptr;
|
||||
|
||||
MaterialAssetCreatorCommon::OnEnd();
|
||||
|
||||
return EndCommon(result);
|
||||
}
|
||||
|
||||
@@ -122,6 +109,21 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't allow previously renamed property names to be reused for new properties. This would just complicate too many things,
|
||||
// as every use of every property name (like in Material Component, or in scripts, for example) would have to have a version number
|
||||
// associated with it, in order to know whether or which rename to apply.
|
||||
for (size_t propertyIndex = 0; propertyIndex < m_asset->m_materialPropertiesLayout->GetPropertyCount(); ++propertyIndex)
|
||||
{
|
||||
Name originalPropertyName = m_asset->m_materialPropertiesLayout->GetPropertyDescriptor(MaterialPropertyIndex{propertyIndex})->GetName();
|
||||
Name newPropertyName = originalPropertyName;
|
||||
if (versionUpdate.ApplyPropertyRenames(newPropertyName))
|
||||
{
|
||||
ReportError("There was a material property named '%s' at material type version %d. This name cannot be reused for another property.",
|
||||
originalPropertyName.GetCStr(), versionUpdate.GetVersion());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
prevVersion = versionUpdate.GetVersion();
|
||||
}
|
||||
|
||||
@@ -484,6 +486,54 @@ namespace AZ
|
||||
|
||||
m_wipMaterialProperty = MaterialPropertyDescriptor{};
|
||||
}
|
||||
|
||||
bool MaterialTypeAssetCreator::PropertyCheck(TypeId typeId, const Name& name)
|
||||
{
|
||||
MaterialPropertyIndex propertyIndex = m_materialPropertiesLayout->FindPropertyIndex(name);
|
||||
if (!propertyIndex.IsValid())
|
||||
{
|
||||
ReportWarning("Material property '%s' not found", name.GetCStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
const MaterialPropertyDescriptor* materialPropertyDescriptor = m_materialPropertiesLayout->GetPropertyDescriptor(propertyIndex);
|
||||
if (!materialPropertyDescriptor)
|
||||
{
|
||||
ReportError("A material property index was found but the property descriptor was null");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValidateMaterialPropertyDataType(typeId, name, materialPropertyDescriptor, [this](const char* message){ReportError("%s", message);}))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MaterialTypeAssetCreator::SetPropertyValue(const Name& name, const Data::Asset<ImageAsset>& imageAsset)
|
||||
{
|
||||
return SetPropertyValue(name, MaterialPropertyValue(imageAsset));
|
||||
}
|
||||
|
||||
void MaterialTypeAssetCreator::SetPropertyValue(const Name& name, const MaterialPropertyValue& value)
|
||||
{
|
||||
if (PropertyCheck(value.GetTypeId(), name))
|
||||
{
|
||||
MaterialPropertyIndex propertyIndex = m_materialPropertiesLayout->FindPropertyIndex(name);
|
||||
m_asset->m_propertyValues[propertyIndex.GetIndex()] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialTypeAssetCreator::SetPropertyValue(const Name& name, const Data::Asset<StreamingImageAsset>& imageAsset)
|
||||
{
|
||||
SetPropertyValue(name, Data::Asset<ImageAsset>(imageAsset));
|
||||
}
|
||||
|
||||
void MaterialTypeAssetCreator::SetPropertyValue(const Name& name, const Data::Asset<AttachmentImageAsset>& imageAsset)
|
||||
{
|
||||
SetPropertyValue(name, Data::Asset<ImageAsset>(imageAsset));
|
||||
}
|
||||
|
||||
void MaterialTypeAssetCreator::AddMaterialFunctor(const Ptr<MaterialFunctor>& functor)
|
||||
{
|
||||
|
||||
@@ -77,18 +77,14 @@ namespace AZ
|
||||
{
|
||||
bool changesWereApplied = false;
|
||||
|
||||
for (auto& propertyName : materialAsset.m_propertyNames)
|
||||
for (auto& [name, value] : materialAsset.m_rawPropertyValues)
|
||||
{
|
||||
for (const auto& action : m_actions)
|
||||
if (ApplyPropertyRenames(name))
|
||||
{
|
||||
if (propertyName == action.m_fromPropertyId)
|
||||
{
|
||||
propertyName = action.m_toPropertyId;
|
||||
changesWereApplied = true;
|
||||
}
|
||||
changesWereApplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return changesWereApplied;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ namespace UnitTest
|
||||
|
||||
Data::Asset<MaterialAsset> materialAsset;
|
||||
MaterialAssetCreator materialCreator;
|
||||
materialCreator.Begin(Uuid::CreateRandom(), *m_materialTypeAsset);
|
||||
materialCreator.Begin(Uuid::CreateRandom(), m_materialTypeAsset, true);
|
||||
EXPECT_TRUE(materialCreator.End(materialAsset));
|
||||
|
||||
m_material = Material::Create(materialAsset);
|
||||
@@ -138,7 +138,7 @@ namespace UnitTest
|
||||
|
||||
Data::Asset<MaterialAsset> materialAsset;
|
||||
MaterialAssetCreator materialCreator;
|
||||
materialCreator.Begin(Uuid::CreateRandom(), *m_materialTypeAsset);
|
||||
materialCreator.Begin(Uuid::CreateRandom(),m_materialTypeAsset, true);
|
||||
EXPECT_TRUE(materialCreator.End(materialAsset));
|
||||
|
||||
m_material = Material::Create(materialAsset);
|
||||
@@ -165,7 +165,7 @@ namespace UnitTest
|
||||
|
||||
Data::Asset<MaterialAsset> materialAsset;
|
||||
MaterialAssetCreator materialCreator;
|
||||
materialCreator.Begin(Uuid::CreateRandom(), *m_materialTypeAsset);
|
||||
materialCreator.Begin(Uuid::CreateRandom(), m_materialTypeAsset, true);
|
||||
EXPECT_TRUE(materialCreator.End(materialAsset));
|
||||
|
||||
m_material = Material::Create(materialAsset);
|
||||
@@ -194,7 +194,7 @@ namespace UnitTest
|
||||
|
||||
Data::Asset<MaterialAsset> materialAsset;
|
||||
MaterialAssetCreator materialCreator;
|
||||
materialCreator.Begin(Uuid::CreateRandom(), *m_materialTypeAsset);
|
||||
materialCreator.Begin(Uuid::CreateRandom(), m_materialTypeAsset, true);
|
||||
EXPECT_TRUE(materialCreator.End(materialAsset));
|
||||
|
||||
m_material = Material::Create(materialAsset);
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace UnitTest
|
||||
Data::AssetId assetId(Uuid::CreateRandom());
|
||||
|
||||
MaterialAssetCreator creator;
|
||||
creator.Begin(assetId, *m_testMaterialTypeAsset);
|
||||
creator.Begin(assetId, m_testMaterialTypeAsset, true);
|
||||
creator.SetPropertyValue(Name{ "MyFloat2" }, Vector2{ 0.1f, 0.2f });
|
||||
creator.SetPropertyValue(Name{ "MyFloat3" }, Vector3{ 1.1f, 1.2f, 1.3f });
|
||||
creator.SetPropertyValue(Name{ "MyFloat4" }, Vector4{ 2.1f, 2.2f, 2.3f, 2.4f });
|
||||
@@ -111,6 +111,10 @@ namespace UnitTest
|
||||
|
||||
EXPECT_EQ(assetId, materialAsset->GetId());
|
||||
EXPECT_EQ(Data::AssetData::AssetStatus::Ready, materialAsset->GetStatus());
|
||||
|
||||
EXPECT_TRUE(materialAsset->WasPreFinalized());
|
||||
EXPECT_EQ(0, materialAsset->GetRawPropertyValues().size());
|
||||
|
||||
validate(materialAsset);
|
||||
|
||||
// Also test serialization...
|
||||
@@ -123,13 +127,64 @@ namespace UnitTest
|
||||
Data::Asset<RPI::MaterialAsset> serializedAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()), noAssets);
|
||||
validate(serializedAsset);
|
||||
}
|
||||
|
||||
TEST_F(MaterialAssetTests, DeferredFinalize)
|
||||
{
|
||||
Data::AssetId assetId(Uuid::CreateRandom());
|
||||
|
||||
MaterialAssetCreator creator;
|
||||
bool shouldFinalize = false;
|
||||
creator.Begin(assetId, m_testMaterialTypeAsset, shouldFinalize);
|
||||
|
||||
creator.SetPropertyValue(Name{ "MyFloat2" }, Vector2{ 0.1f, 0.2f });
|
||||
creator.SetPropertyValue(Name{ "MyFloat3" }, Vector3{ 1.1f, 1.2f, 1.3f });
|
||||
creator.SetPropertyValue(Name{ "MyFloat4" }, Vector4{ 2.1f, 2.2f, 2.3f, 2.4f });
|
||||
creator.SetPropertyValue(Name{ "MyColor" }, Color{ 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
creator.SetPropertyValue(Name{ "MyInt" }, -2);
|
||||
creator.SetPropertyValue(Name{ "MyUInt" }, 12u);
|
||||
creator.SetPropertyValue(Name{ "MyFloat" }, 1.5f);
|
||||
creator.SetPropertyValue(Name{ "MyBool" }, true);
|
||||
creator.SetPropertyValue(Name{ "MyImage" }, m_testImageAsset);
|
||||
creator.SetPropertyValue(Name{ "MyEnum" }, 1u);
|
||||
|
||||
Data::Asset<MaterialAsset> materialAsset;
|
||||
EXPECT_TRUE(creator.End(materialAsset));
|
||||
|
||||
EXPECT_FALSE(materialAsset->WasPreFinalized());
|
||||
EXPECT_EQ(10, materialAsset->GetRawPropertyValues().size());
|
||||
|
||||
// Also test serialization...
|
||||
|
||||
SerializeTester<RPI::MaterialAsset> tester(GetSerializeContext());
|
||||
tester.SerializeOut(materialAsset.Get());
|
||||
|
||||
// Using a filter that skips loading assets because we are using a dummy image asset
|
||||
ObjectStream::FilterDescriptor noAssets{ AZ::Data::AssetFilterNoAssetLoading };
|
||||
Data::Asset<RPI::MaterialAsset> serializedAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()), noAssets);
|
||||
|
||||
EXPECT_FALSE(materialAsset->WasPreFinalized());
|
||||
EXPECT_EQ(10, materialAsset->GetRawPropertyValues().size());
|
||||
|
||||
// GetPropertyValues() will automatically finalize the material asset, so we can go ahead and check the property values.
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues().size(), 10);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[0].GetValue<bool>(), true);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[1].GetValue<int32_t>(), -2);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[2].GetValue<uint32_t>(), 12);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[3].GetValue<float>(), 1.5f);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[4].GetValue<Vector2>(), Vector2(0.1f, 0.2f));
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[5].GetValue<Vector3>(), Vector3(1.1f, 1.2f, 1.3f));
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[6].GetValue<Vector4>(), Vector4(2.1f, 2.2f, 2.3f, 2.4f));
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[7].GetValue<Color>(), Color(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[8].GetValue<Data::Asset<ImageAsset>>(), m_testImageAsset);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[9].GetValue<uint32_t>(), 1u);
|
||||
}
|
||||
|
||||
TEST_F(MaterialAssetTests, PropertyDefaultValuesComeFromParentMaterial)
|
||||
{
|
||||
Data::AssetId assetId(Uuid::CreateRandom());
|
||||
|
||||
MaterialAssetCreator creator;
|
||||
creator.Begin(assetId, *m_testMaterialTypeAsset);
|
||||
creator.Begin(assetId, m_testMaterialTypeAsset, true);
|
||||
creator.SetPropertyValue(Name{ "MyFloat" }, 3.14f);
|
||||
|
||||
Data::Asset<MaterialAsset> materialAsset;
|
||||
@@ -171,7 +226,7 @@ namespace UnitTest
|
||||
|
||||
Data::Asset<MaterialAsset> materialAsset;
|
||||
MaterialAssetCreator materialCreator;
|
||||
materialCreator.Begin(Uuid::CreateRandom(), *emptyMaterialTypeAsset);
|
||||
materialCreator.Begin(Uuid::CreateRandom(), emptyMaterialTypeAsset, true);
|
||||
EXPECT_TRUE(materialCreator.End(materialAsset));
|
||||
EXPECT_EQ(emptyMaterialTypeAsset, materialAsset->GetMaterialTypeAsset());
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues().size(), 0);
|
||||
@@ -189,7 +244,7 @@ namespace UnitTest
|
||||
Data::AssetId assetId(Uuid::CreateRandom());
|
||||
|
||||
MaterialAssetCreator creator;
|
||||
creator.Begin(assetId, *m_testMaterialTypeAsset);
|
||||
creator.Begin(assetId, m_testMaterialTypeAsset, true);
|
||||
creator.SetPropertyValue(Name{ "MyImage" }, streamingImageAsset);
|
||||
|
||||
Data::Asset<MaterialAsset> materialAsset;
|
||||
@@ -231,8 +286,8 @@ namespace UnitTest
|
||||
Data::AssetId assetId(Uuid::CreateRandom());
|
||||
|
||||
MaterialAssetCreator creator;
|
||||
const bool includePropertyNames = true;
|
||||
creator.Begin(assetId, *testMaterialTypeAssetV1, includePropertyNames);
|
||||
const bool shouldFinalize = false;
|
||||
creator.Begin(assetId, testMaterialTypeAssetV1, shouldFinalize);
|
||||
creator.SetPropertyValue(Name{ "MyInt" }, 7);
|
||||
creator.SetPropertyValue(Name{ "MyUInt" }, 8u);
|
||||
creator.SetPropertyValue(Name{ "MyFloat" }, 9.0f);
|
||||
@@ -266,13 +321,13 @@ namespace UnitTest
|
||||
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"
|
||||
@@ -307,26 +362,68 @@ namespace UnitTest
|
||||
// We use local functions to easily start a new MaterialAssetCreator for each test case because
|
||||
// the AssetCreator would just skip subsequent operations after the first failure is detected.
|
||||
|
||||
auto expectCreatorError = [this](AZStd::function<void(MaterialAssetCreator& creator)> passBadInput)
|
||||
auto expectCreatorError = [this](const char* expectedErrorMessage, AZStd::function<void(MaterialAssetCreator& creator)> passBadInput)
|
||||
{
|
||||
MaterialAssetCreator creator;
|
||||
creator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
|
||||
// Test with finalizing enabled
|
||||
{
|
||||
MaterialAssetCreator creator;
|
||||
creator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, true);
|
||||
|
||||
AZ_TEST_START_ASSERTTEST;
|
||||
passBadInput(creator);
|
||||
AZ_TEST_STOP_ASSERTTEST(1);
|
||||
ErrorMessageFinder errorMessageFinder;
|
||||
errorMessageFinder.AddExpectedErrorMessage(expectedErrorMessage);
|
||||
errorMessageFinder.AddIgnoredErrorMessage("Failed to build", true);
|
||||
|
||||
EXPECT_EQ(1, creator.GetErrorCount());
|
||||
passBadInput(creator);
|
||||
|
||||
Data::Asset<MaterialAsset> materialAsset;
|
||||
EXPECT_FALSE(creator.End(materialAsset));
|
||||
|
||||
errorMessageFinder.CheckExpectedErrorsFound();
|
||||
|
||||
EXPECT_TRUE(creator.GetErrorCount() > 0);
|
||||
}
|
||||
|
||||
// Test with finalizing disabled, so no validation occurs because the MaterialTypeAsset data is not used.
|
||||
{
|
||||
MaterialAssetCreator creator;
|
||||
creator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, false);
|
||||
|
||||
passBadInput(creator);
|
||||
|
||||
Data::Asset<MaterialAsset> materialAsset;
|
||||
EXPECT_TRUE(creator.End(materialAsset));
|
||||
|
||||
EXPECT_EQ(creator.GetErrorCount(), 0);
|
||||
}
|
||||
};
|
||||
|
||||
auto expectCreatorWarning = [this](AZStd::function<void(MaterialAssetCreator& creator)> passBadInput)
|
||||
{
|
||||
MaterialAssetCreator creator;
|
||||
creator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
|
||||
// Test with finalizing enabled
|
||||
{
|
||||
MaterialAssetCreator creator;
|
||||
creator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, true);
|
||||
|
||||
passBadInput(creator);
|
||||
passBadInput(creator);
|
||||
|
||||
EXPECT_EQ(1, creator.GetWarningCount());
|
||||
Data::Asset<MaterialAsset> material;
|
||||
creator.End(material);
|
||||
|
||||
EXPECT_EQ(1, creator.GetWarningCount());
|
||||
}
|
||||
|
||||
// Test with finalizing disabled, so no validation occurs because the MaterialTypeAsset data is not used.
|
||||
{
|
||||
MaterialAssetCreator creator;
|
||||
creator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, false);
|
||||
|
||||
passBadInput(creator);
|
||||
|
||||
Data::Asset<MaterialAsset> material;
|
||||
creator.End(material);
|
||||
|
||||
EXPECT_EQ(0, creator.GetWarningCount());
|
||||
}
|
||||
};
|
||||
|
||||
// Invalid input ID
|
||||
@@ -343,55 +440,65 @@ namespace UnitTest
|
||||
|
||||
// Test data type mismatches...
|
||||
|
||||
expectCreatorError([this](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyBool" }, m_testImageAsset);
|
||||
});
|
||||
expectCreatorError("Type mismatch",
|
||||
[this](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyBool" }, m_testImageAsset);
|
||||
});
|
||||
|
||||
expectCreatorError([](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyInt" }, 0.0f);
|
||||
});
|
||||
expectCreatorError("Type mismatch",
|
||||
[](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyInt" }, 0.0f);
|
||||
});
|
||||
|
||||
expectCreatorError([](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyUInt" }, -1);
|
||||
});
|
||||
expectCreatorError("Type mismatch",
|
||||
[](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyUInt" }, -1);
|
||||
});
|
||||
|
||||
expectCreatorError([](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyFloat" }, 10u);
|
||||
});
|
||||
expectCreatorError("Type mismatch",
|
||||
[](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyFloat" }, 10u);
|
||||
});
|
||||
|
||||
expectCreatorError([](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyFloat2" }, 1.0f);
|
||||
});
|
||||
expectCreatorError("Type mismatch",
|
||||
[](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyFloat2" }, 1.0f);
|
||||
});
|
||||
|
||||
expectCreatorError([](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyFloat3" }, AZ::Vector4{});
|
||||
});
|
||||
expectCreatorError("Type mismatch",
|
||||
[](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyFloat3" }, AZ::Vector4{});
|
||||
});
|
||||
|
||||
expectCreatorError([](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyFloat4" }, AZ::Vector3{});
|
||||
});
|
||||
expectCreatorError("Type mismatch",
|
||||
[](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyFloat4" }, AZ::Vector3{});
|
||||
});
|
||||
|
||||
expectCreatorError([](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyColor" }, MaterialPropertyValue(false));
|
||||
});
|
||||
expectCreatorError("Type mismatch",
|
||||
[](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyColor" }, MaterialPropertyValue(false));
|
||||
});
|
||||
|
||||
expectCreatorError([](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyImage" }, true);
|
||||
});
|
||||
expectCreatorError("Type mismatch",
|
||||
[](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyImage" }, true);
|
||||
});
|
||||
|
||||
expectCreatorError([](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyEnum" }, -1);
|
||||
});
|
||||
expectCreatorError("can only accept UInt value",
|
||||
[](MaterialAssetCreator& creator)
|
||||
{
|
||||
creator.SetPropertyValue(Name{ "MyEnum" }, -1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ namespace UnitTest
|
||||
materialTypeCreator.End(m_testMaterialTypeAsset);
|
||||
|
||||
MaterialAssetCreator materialCreator;
|
||||
materialCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
|
||||
materialCreator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, true);
|
||||
materialCreator.SetPropertyValue(registedPropertyName, 42);
|
||||
materialCreator.SetPropertyValue(unregistedPropertyName, 42);
|
||||
materialCreator.SetPropertyValue(unrelatedPropertyName, 42);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <Common/JsonTestUtils.h>
|
||||
#include <Common/ShaderAssetTestUtils.h>
|
||||
#include <Common/ErrorMessageFinder.h>
|
||||
#include <Common/SerializeTester.h>
|
||||
#include <Material/MaterialAssetTestUtils.h>
|
||||
|
||||
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
|
||||
@@ -65,9 +66,29 @@ namespace UnitTest
|
||||
m_testShaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), m_testMaterialSrgLayout);
|
||||
m_assetSystemStub.RegisterSourceInfo("@exefolder@/Temp/test.shader", m_testShaderAsset.GetId());
|
||||
|
||||
// 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...
|
||||
m_testMaterialTypeAsset = CreateTestMaterialTypeAsset(Uuid::CreateRandom());
|
||||
|
||||
// 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("@exefolder@/Temp/test.materialtype", m_testMaterialTypeAsset.GetId());
|
||||
m_assetSystemStub.RegisterSourceInfo("@exefolder@/Temp/test.streamingimage", m_testImageAsset.GetId());
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_testMaterialTypeAsset.Reset();
|
||||
m_testMaterialSrgLayout = nullptr;
|
||||
m_testShaderAsset.Reset();
|
||||
m_testImageAsset.Reset();
|
||||
|
||||
RPITestFixture::TearDown();
|
||||
}
|
||||
|
||||
Data::Asset<MaterialTypeAsset> CreateTestMaterialTypeAsset(Data::AssetId assetId)
|
||||
{
|
||||
const char* materialTypeJson = R"(
|
||||
{
|
||||
"version": 10,
|
||||
@@ -122,29 +143,10 @@ namespace UnitTest
|
||||
}
|
||||
)";
|
||||
|
||||
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("@exefolder@/Temp/test.materialtype", m_testMaterialTypeAsset.GetId());
|
||||
m_assetSystemStub.RegisterSourceInfo("@exefolder@/Temp/test.streamingimage", m_testImageAsset.GetId());
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_testMaterialTypeAsset.Reset();
|
||||
m_testMaterialSrgLayout = nullptr;
|
||||
m_testShaderAsset.Reset();
|
||||
m_testImageAsset.Reset();
|
||||
|
||||
RPITestFixture::TearDown();
|
||||
return materialTypeSourceData.CreateMaterialTypeAsset(assetId).TakeValue();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -175,12 +177,15 @@ namespace UnitTest
|
||||
AddProperty(sourceData, "general", "MyImage", AZStd::string("@exefolder@/Temp/test.streamingimage"));
|
||||
AddProperty(sourceData, "general", "MyEnum", AZStd::string("Enum1"));
|
||||
|
||||
auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
||||
auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::PreBake, true);
|
||||
EXPECT_TRUE(materialAssetOutcome.IsSuccess());
|
||||
|
||||
Data::Asset<MaterialAsset> materialAsset = materialAssetOutcome.GetValue();
|
||||
|
||||
// The order here is based on the order in the MaterialTypeSourceData, as added to the the MaterialTypeAssetCreator.
|
||||
EXPECT_TRUE(materialAsset->WasPreFinalized());
|
||||
EXPECT_EQ(0, materialAsset->GetRawPropertyValues().size()); // A pre-baked material has no need for the original raw property names and values
|
||||
|
||||
// The order here is based on the order in the MaterialTypeSourceData, as added to the MaterialTypeAssetCreator.
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[0].GetValue<bool>(), true);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[1].GetValue<int32_t>(), -10);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[2].GetValue<uint32_t>(), 25u);
|
||||
@@ -192,6 +197,105 @@ namespace UnitTest
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[8].GetValue<Data::Asset<ImageAsset>>(), m_testImageAsset);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[9].GetValue<uint32_t>(), 1u);
|
||||
}
|
||||
|
||||
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_DeferredBake)
|
||||
{
|
||||
// This test is similar to CreateMaterialAsset_BasicProperties but uses MaterialAssetProcessingMode::DeferredBake instead of PreBake.
|
||||
|
||||
Data::AssetId materialTypeAssetId = Uuid::CreateRandom();
|
||||
|
||||
// This material type asset will be known by the asset system (stub) but doesn't exist in the AssetManager.
|
||||
// This demonstrates that the CreateMaterialAsset does not attempt to access the MaterialTypeAsset data in MaterialAssetProcessingMode::DeferredBake.
|
||||
m_assetSystemStub.RegisterSourceInfo("testDeferredBake.materialtype", materialTypeAssetId);
|
||||
|
||||
MaterialSourceData sourceData;
|
||||
|
||||
sourceData.m_materialType = "testDeferredBake.materialtype";
|
||||
AddPropertyGroup(sourceData, "general");
|
||||
AddProperty(sourceData, "general", "MyBool" , true);
|
||||
AddProperty(sourceData, "general", "MyInt" , -10);
|
||||
AddProperty(sourceData, "general", "MyUInt" , 25u);
|
||||
AddProperty(sourceData, "general", "MyFloat" , 1.5f);
|
||||
AddProperty(sourceData, "general", "MyColor" , AZ::Color{0.1f, 0.2f, 0.3f, 0.4f});
|
||||
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("@exefolder@/Temp/test.streamingimage"));
|
||||
AddProperty(sourceData, "general", "MyEnum" , AZStd::string("Enum1"));
|
||||
|
||||
auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::DeferredBake, true);
|
||||
EXPECT_TRUE(materialAssetOutcome.IsSuccess());
|
||||
|
||||
Data::Asset<MaterialAsset> materialAsset = materialAssetOutcome.GetValue();
|
||||
|
||||
EXPECT_FALSE(materialAsset->WasPreFinalized());
|
||||
|
||||
// Note we avoid calling GetPropertyValues() because that will auto-finalize the material. We want to check its raw property values first.
|
||||
|
||||
auto findRawPropertyValue = [materialAsset](const char* propertyId)
|
||||
{
|
||||
auto iter = AZStd::find_if(materialAsset->GetRawPropertyValues().begin(), materialAsset->GetRawPropertyValues().end(), [propertyId](const AZStd::pair<Name, MaterialPropertyValue>& pair)
|
||||
{
|
||||
return pair.first == AZ::Name{propertyId};
|
||||
});
|
||||
|
||||
if (iter == materialAsset->GetRawPropertyValues().end())
|
||||
{
|
||||
return MaterialPropertyValue{};
|
||||
}
|
||||
else
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
};
|
||||
|
||||
auto checkRawPropertyValues = [findRawPropertyValue, this]()
|
||||
{
|
||||
EXPECT_EQ(findRawPropertyValue("general.MyBool" ).GetValue<bool>(), true);
|
||||
EXPECT_EQ(findRawPropertyValue("general.MyInt" ).GetValue<int32_t>(), -10);
|
||||
EXPECT_EQ(findRawPropertyValue("general.MyUInt" ).GetValue<uint32_t>(), 25u);
|
||||
EXPECT_EQ(findRawPropertyValue("general.MyFloat" ).GetValue<float>(), 1.5f);
|
||||
EXPECT_EQ(findRawPropertyValue("general.MyFloat2").GetValue<Vector2>(), Vector2(2.1f, 2.2f));
|
||||
EXPECT_EQ(findRawPropertyValue("general.MyFloat3").GetValue<Vector3>(), Vector3(3.1f, 3.2f, 3.3f));
|
||||
EXPECT_EQ(findRawPropertyValue("general.MyFloat4").GetValue<Vector4>(), Vector4(4.1f, 4.2f, 4.3f, 4.4f));
|
||||
EXPECT_EQ(findRawPropertyValue("general.MyColor" ).GetValue<Color>(), Color(0.1f, 0.2f, 0.3f, 0.4f));
|
||||
EXPECT_EQ(findRawPropertyValue("general.MyImage" ).GetValue<Data::Asset<ImageAsset>>(), m_testImageAsset);
|
||||
// The raw value for an enum is the original string, not the numerical value, because the material type holds the necessary metadata to match the name to the value.
|
||||
EXPECT_EQ(findRawPropertyValue("general.MyEnum" ).GetValue<AZStd::string>(), AZStd::string("Enum1"));
|
||||
};
|
||||
|
||||
// We check the raw property values before the material type asset is even available
|
||||
checkRawPropertyValues();
|
||||
|
||||
// Now we'll create the material type asset in memory so the material will have what it needs to finalize itself.
|
||||
Data::Asset<MaterialTypeAsset> testMaterialTypeAsset = CreateTestMaterialTypeAsset(materialTypeAssetId);
|
||||
|
||||
// The MaterialAsset is still holding an reference to an unloaded asset, so we run it through the serializer which causes the loaded MaterialAsset
|
||||
// to have access to the testMaterialTypeAsset. This is similar to how the AP would save the MaterialAsset to the cache and the runtime would load it.
|
||||
SerializeTester<RPI::MaterialAsset> tester(GetSerializeContext());
|
||||
tester.SerializeOut(materialAsset.Get());
|
||||
materialAsset = tester.SerializeIn(Uuid::CreateRandom(), ObjectStream::FilterDescriptor{AZ::Data::AssetFilterNoAssetLoading});
|
||||
|
||||
// We check that the asset is still in the original un-finalized state after going through the serialization process.
|
||||
EXPECT_FALSE(materialAsset->WasPreFinalized());
|
||||
checkRawPropertyValues();
|
||||
|
||||
// Now all the property values should be available through the main GetPropertyValues() API.
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[0].GetValue<bool>(), true);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[1].GetValue<int32_t>(), -10);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[2].GetValue<uint32_t>(), 25u);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[3].GetValue<float>(), 1.5f);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[4].GetValue<Vector2>(), Vector2(2.1f, 2.2f));
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[5].GetValue<Vector3>(), Vector3(3.1f, 3.2f, 3.3f));
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[6].GetValue<Vector4>(), Vector4(4.1f, 4.2f, 4.3f, 4.4f));
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[7].GetValue<Color>(), Color(0.1f, 0.2f, 0.3f, 0.4f));
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[8].GetValue<Data::Asset<ImageAsset>>(), m_testImageAsset);
|
||||
EXPECT_EQ(materialAsset->GetPropertyValues()[9].GetValue<uint32_t>(), 1u);
|
||||
|
||||
// The raw property values are still available (because they are needed if a hot-reload of the MaterialTypeAsset occurs)
|
||||
EXPECT_FALSE(materialAsset->WasPreFinalized());
|
||||
checkRawPropertyValues();
|
||||
}
|
||||
|
||||
void CheckEqual(MaterialSourceData& a, MaterialSourceData& b)
|
||||
{
|
||||
@@ -544,18 +648,21 @@ namespace UnitTest
|
||||
AddPropertyGroup(sourceDataLevel3, "general");
|
||||
AddProperty(sourceDataLevel3, "general", "MyFloat", 3.5f);
|
||||
|
||||
auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
||||
auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::PreBake, true);
|
||||
EXPECT_TRUE(materialAssetLevel1.IsSuccess());
|
||||
EXPECT_TRUE(materialAssetLevel1.GetValue()->WasPreFinalized());
|
||||
|
||||
m_assetSystemStub.RegisterSourceInfo("level1.material", materialAssetLevel1.GetValue().GetId());
|
||||
|
||||
auto materialAssetLevel2 = sourceDataLevel2.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
||||
auto materialAssetLevel2 = sourceDataLevel2.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::PreBake, true);
|
||||
EXPECT_TRUE(materialAssetLevel2.IsSuccess());
|
||||
EXPECT_TRUE(materialAssetLevel2.GetValue()->WasPreFinalized());
|
||||
|
||||
m_assetSystemStub.RegisterSourceInfo("level2.material", materialAssetLevel2.GetValue().GetId());
|
||||
|
||||
auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
||||
auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::PreBake, true);
|
||||
EXPECT_TRUE(materialAssetLevel3.IsSuccess());
|
||||
EXPECT_TRUE(materialAssetLevel3.GetValue()->WasPreFinalized());
|
||||
|
||||
auto layout = m_testMaterialTypeAsset->GetMaterialPropertiesLayout();
|
||||
MaterialPropertyIndex myFloat = layout->FindPropertyIndex(Name("general.MyFloat"));
|
||||
@@ -582,6 +689,96 @@ namespace UnitTest
|
||||
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
|
||||
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
|
||||
}
|
||||
|
||||
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MultiLevelDataInheritance_DeferredBake)
|
||||
{
|
||||
// This test is similar to CreateMaterialAsset_MultiLevelDataInheritance but uses MaterialAssetProcessingMode::DeferredBake instead of PreBake.
|
||||
|
||||
Data::AssetId materialTypeAssetId = Uuid::CreateRandom();
|
||||
|
||||
// This material type asset will be known by the asset system (stub) but doesn't exist in the AssetManager.
|
||||
// This demonstrates that the CreateMaterialAsset does not attempt to access the MaterialTypeAsset data in MaterialAssetProcessingMode::DeferredBake.
|
||||
m_assetSystemStub.RegisterSourceInfo("testDeferredBake.materialtype", materialTypeAssetId);
|
||||
|
||||
MaterialSourceData sourceDataLevel1;
|
||||
sourceDataLevel1.m_materialType = "testDeferredBake.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 = "testDeferredBake.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 = "testDeferredBake.materialtype";
|
||||
sourceDataLevel3.m_parentMaterial = "level2.material";
|
||||
AddPropertyGroup(sourceDataLevel3, "general");
|
||||
AddProperty(sourceDataLevel3, "general", "MyFloat", 3.5f);
|
||||
|
||||
auto materialAssetLevel1Result = sourceDataLevel1.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::DeferredBake, true);
|
||||
EXPECT_TRUE(materialAssetLevel1Result.IsSuccess());
|
||||
Data::Asset<MaterialAsset> materialAssetLevel1 = materialAssetLevel1Result.TakeValue();
|
||||
EXPECT_FALSE(materialAssetLevel1->WasPreFinalized());
|
||||
|
||||
m_assetSystemStub.RegisterSourceInfo("level1.material", materialAssetLevel1.GetId());
|
||||
|
||||
auto materialAssetLevel2Result = sourceDataLevel2.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::DeferredBake, true);
|
||||
EXPECT_TRUE(materialAssetLevel2Result.IsSuccess());
|
||||
Data::Asset<MaterialAsset> materialAssetLevel2 = materialAssetLevel2Result.TakeValue();
|
||||
EXPECT_FALSE(materialAssetLevel2->WasPreFinalized());
|
||||
|
||||
m_assetSystemStub.RegisterSourceInfo("level2.material", materialAssetLevel2.GetId());
|
||||
|
||||
auto materialAssetLevel3Result = sourceDataLevel3.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::DeferredBake, true);
|
||||
EXPECT_TRUE(materialAssetLevel3Result.IsSuccess());
|
||||
Data::Asset<MaterialAsset> materialAssetLevel3 = materialAssetLevel3Result.TakeValue();
|
||||
EXPECT_FALSE(materialAssetLevel3->WasPreFinalized());
|
||||
|
||||
// Now we'll create the material type asset in memory so the materials will have what they need to finalize.
|
||||
Data::Asset<MaterialTypeAsset> testMaterialTypeAsset = CreateTestMaterialTypeAsset(materialTypeAssetId);
|
||||
|
||||
auto layout = testMaterialTypeAsset->GetMaterialPropertiesLayout();
|
||||
MaterialPropertyIndex myFloat = layout->FindPropertyIndex(Name("general.MyFloat"));
|
||||
MaterialPropertyIndex myFloat2 = layout->FindPropertyIndex(Name("general.MyFloat2"));
|
||||
MaterialPropertyIndex myColor = layout->FindPropertyIndex(Name("general.MyColor"));
|
||||
|
||||
|
||||
// The MaterialAsset is still holding an reference to an unloaded asset, so we run it through the serializer which causes the loaded MaterialAsset
|
||||
// to have access to the testMaterialTypeAsset. This is similar to how the AP would save the MaterialAsset to the cache and the runtime would load it.
|
||||
SerializeTester<RPI::MaterialAsset> tester(GetSerializeContext());
|
||||
tester.SerializeOut(materialAssetLevel1.Get());
|
||||
materialAssetLevel1 = tester.SerializeIn(Uuid::CreateRandom(), ObjectStream::FilterDescriptor{AZ::Data::AssetFilterNoAssetLoading});
|
||||
tester.SerializeOut(materialAssetLevel2.Get());
|
||||
materialAssetLevel2 = tester.SerializeIn(Uuid::CreateRandom(), ObjectStream::FilterDescriptor{AZ::Data::AssetFilterNoAssetLoading});
|
||||
tester.SerializeOut(materialAssetLevel3.Get());
|
||||
materialAssetLevel3 = tester.SerializeIn(Uuid::CreateRandom(), ObjectStream::FilterDescriptor{AZ::Data::AssetFilterNoAssetLoading});
|
||||
|
||||
// The properties will finalize automatically when we call GetPropertyValues()...
|
||||
|
||||
AZStd::array_view<MaterialPropertyValue> properties;
|
||||
|
||||
// Check level 1 properties
|
||||
properties = materialAssetLevel1->GetPropertyValues();
|
||||
EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
|
||||
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(0.0f, 0.0f));
|
||||
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.1f, 0.2f, 0.3f, 0.4f));
|
||||
|
||||
// Check level 2 properties
|
||||
properties = materialAssetLevel2->GetPropertyValues();
|
||||
EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
|
||||
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
|
||||
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
|
||||
|
||||
// Check level 3 properties
|
||||
properties = materialAssetLevel3->GetPropertyValues();
|
||||
EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 3.5f);
|
||||
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
|
||||
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
|
||||
}
|
||||
|
||||
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MultiLevelDataInheritance_Error_MaterialTypesDontMatch)
|
||||
{
|
||||
@@ -604,18 +801,18 @@ namespace UnitTest
|
||||
sourceDataLevel3.m_materialType = "@exefolder@/Temp/otherBase.materialtype";
|
||||
sourceDataLevel3.m_parentMaterial = "level2.material";
|
||||
|
||||
auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
||||
auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::PreBake, true);
|
||||
EXPECT_TRUE(materialAssetLevel1.IsSuccess());
|
||||
|
||||
m_assetSystemStub.RegisterSourceInfo("level1.material", materialAssetLevel1.GetValue().GetId());
|
||||
|
||||
auto materialAssetLevel2 = sourceDataLevel2.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
||||
auto materialAssetLevel2 = sourceDataLevel2.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::PreBake, true);
|
||||
EXPECT_TRUE(materialAssetLevel2.IsSuccess());
|
||||
|
||||
m_assetSystemStub.RegisterSourceInfo("level2.material", materialAssetLevel2.GetValue().GetId());
|
||||
|
||||
AZ_TEST_START_ASSERTTEST;
|
||||
auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
||||
auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::PreBake, true);
|
||||
AZ_TEST_STOP_ASSERTTEST(1);
|
||||
EXPECT_FALSE(materialAssetLevel3.IsSuccess());
|
||||
}
|
||||
@@ -625,7 +822,7 @@ namespace UnitTest
|
||||
// We use local functions to easily start a new MaterialAssetCreator for each test case because
|
||||
// the AssetCreator would just skip subsequent operations after the first failure is detected.
|
||||
|
||||
auto expectWarning = [](AZStd::function<void(MaterialSourceData& materialSourceData)> setOneBadInput, [[maybe_unused]] uint32_t expectedAsserts = 1)
|
||||
auto expectWarning = [](const char* expectedErrorMessage, AZStd::function<void(MaterialSourceData& materialSourceData)> setOneBadInput, bool warningOccursBeforeFinalize = false)
|
||||
{
|
||||
MaterialSourceData sourceData;
|
||||
|
||||
@@ -635,233 +832,69 @@ namespace UnitTest
|
||||
|
||||
setOneBadInput(sourceData);
|
||||
|
||||
AZ_TEST_START_ASSERTTEST;
|
||||
auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", true);
|
||||
AZ_TEST_STOP_ASSERTTEST(expectedAsserts); // Usually just one for when End() is called
|
||||
// Check with MaterialAssetProcessingMode::PreBake
|
||||
{
|
||||
ErrorMessageFinder errorFinder;
|
||||
errorFinder.AddExpectedErrorMessage(expectedErrorMessage);
|
||||
errorFinder.AddIgnoredErrorMessage("Failed to build", true);
|
||||
auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::PreBake, true);
|
||||
errorFinder.CheckExpectedErrorsFound();
|
||||
|
||||
EXPECT_FALSE(materialAssetOutcome.IsSuccess());
|
||||
EXPECT_FALSE(materialAssetOutcome.IsSuccess());
|
||||
}
|
||||
|
||||
// Check with MaterialAssetProcessingMode::DeferredBake, no validation occurs because the MaterialTypeAsset cannot be used and so the MaterialAsset is not finalized
|
||||
if(!warningOccursBeforeFinalize)
|
||||
{
|
||||
auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", MaterialAssetProcessingMode::DeferredBake, true);
|
||||
EXPECT_TRUE(materialAssetOutcome.IsSuccess());
|
||||
}
|
||||
};
|
||||
|
||||
// Test property does not exist...
|
||||
|
||||
expectWarning([](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "DoesNotExist", true);
|
||||
});
|
||||
expectWarning("\"general.DoesNotExist\" is not found in the material properties layout",
|
||||
[](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "DoesNotExist", true);
|
||||
});
|
||||
|
||||
expectWarning([](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "DoesNotExist", -10);
|
||||
});
|
||||
expectWarning("\"general.DoesNotExist\" is not found in the material properties layout",
|
||||
[](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "DoesNotExist", -10);
|
||||
});
|
||||
|
||||
expectWarning([](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "DoesNotExist", 25u);
|
||||
});
|
||||
expectWarning("\"general.DoesNotExist\" is not found in the material properties layout",
|
||||
[](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "DoesNotExist", 25u);
|
||||
});
|
||||
|
||||
expectWarning([](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "DoesNotExist", 1.5f);
|
||||
});
|
||||
expectWarning("\"general.DoesNotExist\" is not found in the material properties layout",
|
||||
[](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "DoesNotExist", 1.5f);
|
||||
});
|
||||
|
||||
expectWarning([](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "DoesNotExist", AZ::Color{ 0.1f, 0.2f, 0.3f, 0.4f });
|
||||
});
|
||||
expectWarning("\"general.DoesNotExist\" is not found in the material properties layout",
|
||||
[](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "DoesNotExist", AZ::Color{ 0.1f, 0.2f, 0.3f, 0.4f });
|
||||
});
|
||||
|
||||
expectWarning([](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "DoesNotExist", AZStd::string("@exefolder@/Temp/test.streamingimage"));
|
||||
});
|
||||
expectWarning("\"general.DoesNotExist\" is not found in the material properties layout",
|
||||
[](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "DoesNotExist", AZStd::string("@exefolder@/Temp/test.streamingimage"));
|
||||
});
|
||||
|
||||
// Missing image reference
|
||||
expectWarning([](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "MyImage", AZStd::string("doesNotExist.streamingimage"));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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_MaterialTypeVersionUpdate_MovePropertiesToAnotherGroup)
|
||||
{
|
||||
const AZStd::string inputJson = R"(
|
||||
{
|
||||
"materialType": "@exefolder@/Temp/test.materialtype",
|
||||
"materialTypeVersion": 3,
|
||||
"properties": {
|
||||
"oldGroup": {
|
||||
"MyFloat": 1.2,
|
||||
"MyIntOldName": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
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["oldGroup"].find("MyFloat"), material.m_properties["oldGroup"].end());
|
||||
EXPECT_NE(material.m_properties["oldGroup"].find("MyIntOldName"), material.m_properties["oldGroup"].end());
|
||||
EXPECT_EQ(material.m_properties["general"].find("MyFloat"), material.m_properties["general"].end());
|
||||
EXPECT_EQ(material.m_properties["general"].find("MyInt"), material.m_properties["general"].end());
|
||||
|
||||
float myFloat = material.m_properties["oldGroup"]["MyFloat"].m_value.GetValue<float>();
|
||||
EXPECT_EQ(myFloat, 1.2f);
|
||||
|
||||
int32_t myInt = material.m_properties["oldGroup"]["MyIntOldName"].m_value.GetValue<int32_t>();
|
||||
EXPECT_EQ(myInt, 5);
|
||||
|
||||
EXPECT_EQ(3, 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 '3'");
|
||||
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["oldGroup"].find("MyFloat"), material.m_properties["oldGroup"].end());
|
||||
EXPECT_EQ(material.m_properties["oldGroup"].find("MyIntOldName"), material.m_properties["oldGroup"].end());
|
||||
EXPECT_NE(material.m_properties["general"].find("MyFloat"), material.m_properties["general"].end());
|
||||
EXPECT_NE(material.m_properties["general"].find("MyInt"), material.m_properties["general"].end());
|
||||
|
||||
myFloat = material.m_properties["general"]["MyFloat"].m_value.GetValue<float>();
|
||||
EXPECT_EQ(myFloat, 1.2f);
|
||||
|
||||
myInt = material.m_properties["general"]["MyInt"].m_value.GetValue<int32_t>();
|
||||
EXPECT_EQ(myInt, 5);
|
||||
|
||||
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());
|
||||
expectWarning("Could not find the image 'doesNotExist.streamingimage'",
|
||||
[](MaterialSourceData& materialSourceData)
|
||||
{
|
||||
AddProperty(materialSourceData, "general", "MyImage", AZStd::string("doesNotExist.streamingimage"));
|
||||
}, true); // In this case, the warning does happen even when the asset is not finalized, because the image path is checked earlier than that
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace UnitTest
|
||||
m_testImage = StreamingImage::FindOrCreate(m_testImageAsset);
|
||||
|
||||
MaterialAssetCreator materialCreator;
|
||||
materialCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
|
||||
materialCreator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, true);
|
||||
materialCreator.SetPropertyValue(Name{ "MyFloat2" }, Vector2{ 0.1f, 0.2f });
|
||||
materialCreator.SetPropertyValue(Name{ "MyFloat3" }, Vector3{ 1.1f, 1.2f, 1.3f });
|
||||
materialCreator.SetPropertyValue(Name{ "MyFloat4" }, Vector4{ 2.1f, 2.2f, 2.3f, 2.4f });
|
||||
@@ -289,7 +289,7 @@ namespace UnitTest
|
||||
materialTypeCreator.End(materialTypeAsset);
|
||||
|
||||
MaterialAssetCreator materialAssetCreator;
|
||||
materialAssetCreator.Begin(Uuid::CreateRandom(), *materialTypeAsset);
|
||||
materialAssetCreator.Begin(Uuid::CreateRandom(), materialTypeAsset, true);
|
||||
materialAssetCreator.End(materialAsset);
|
||||
|
||||
Data::Instance<Material> material = Material::FindOrCreate(materialAsset);
|
||||
@@ -341,7 +341,7 @@ namespace UnitTest
|
||||
Data::Asset<MaterialAsset> materialAssetWithEmptyImage;
|
||||
|
||||
MaterialAssetCreator materialCreator;
|
||||
materialCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
|
||||
materialCreator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, true);
|
||||
materialCreator.SetPropertyValue(Name{"MyFloat2"}, Vector2{0.1f, 0.2f});
|
||||
materialCreator.SetPropertyValue(Name{"MyFloat3"}, Vector3{1.1f, 1.2f, 1.3f});
|
||||
materialCreator.SetPropertyValue(Name{"MyFloat4"}, Vector4{2.1f, 2.2f, 2.3f, 2.4f});
|
||||
@@ -379,7 +379,7 @@ namespace UnitTest
|
||||
|
||||
Data::Asset<MaterialAsset> emptyMaterialAsset;
|
||||
MaterialAssetCreator materialCreator;
|
||||
materialCreator.Begin(Uuid::CreateRandom(), *emptyMaterialTypeAsset);
|
||||
materialCreator.Begin(Uuid::CreateRandom(), emptyMaterialTypeAsset, true);
|
||||
EXPECT_TRUE(materialCreator.End(emptyMaterialAsset));
|
||||
|
||||
Data::Instance<Material> material = Material::FindOrCreate(emptyMaterialAsset);
|
||||
@@ -450,7 +450,7 @@ namespace UnitTest
|
||||
materialTypeCreator.End(m_testMaterialTypeAsset);
|
||||
|
||||
MaterialAssetCreator materialAssetCreator;
|
||||
materialAssetCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
|
||||
materialAssetCreator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, true);
|
||||
materialAssetCreator.End(m_testMaterialAsset);
|
||||
|
||||
Data::Instance<Material> material = Material::FindOrCreate(m_testMaterialAsset);
|
||||
@@ -521,7 +521,7 @@ namespace UnitTest
|
||||
materialTypeCreator.End(m_testMaterialTypeAsset);
|
||||
|
||||
MaterialAssetCreator materialAssetCreator;
|
||||
materialAssetCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
|
||||
materialAssetCreator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, true);
|
||||
materialAssetCreator.End(m_testMaterialAsset);
|
||||
|
||||
Data::Instance<Material> material = Material::FindOrCreate(m_testMaterialAsset);
|
||||
@@ -591,7 +591,7 @@ namespace UnitTest
|
||||
materialTypeCreator.End(m_testMaterialTypeAsset);
|
||||
|
||||
MaterialAssetCreator materialAssetCreator;
|
||||
materialAssetCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
|
||||
materialAssetCreator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, true);
|
||||
materialAssetCreator.End(m_testMaterialAsset);
|
||||
|
||||
Data::Instance<Material> material = Material::FindOrCreate(m_testMaterialAsset);
|
||||
@@ -655,7 +655,7 @@ namespace UnitTest
|
||||
materialTypeCreator.End(m_testMaterialTypeAsset);
|
||||
|
||||
MaterialAssetCreator materialAssetCreator;
|
||||
materialAssetCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
|
||||
materialAssetCreator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, true);
|
||||
materialAssetCreator.End(m_testMaterialAsset);
|
||||
|
||||
Data::Instance<Material> material = Material::FindOrCreate(m_testMaterialAsset);
|
||||
@@ -669,7 +669,7 @@ namespace UnitTest
|
||||
{
|
||||
Data::Asset<MaterialAsset> materialAsset;
|
||||
MaterialAssetCreator materialCreator;
|
||||
materialCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
|
||||
materialCreator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, true);
|
||||
materialCreator.SetPropertyValue(Name{ "MyFloat2" }, Vector2{ 0.1f, 0.2f });
|
||||
materialCreator.SetPropertyValue(Name{ "MyFloat3" }, Vector3{ 1.1f, 1.2f, 1.3f });
|
||||
materialCreator.SetPropertyValue(Name{ "MyFloat4" }, Vector4{ 2.1f, 2.2f, 2.3f, 2.4f });
|
||||
@@ -778,7 +778,7 @@ namespace UnitTest
|
||||
materialTypeCreator.End(materialTypeAsset);
|
||||
|
||||
MaterialAssetCreator materialAssetCreator;
|
||||
materialAssetCreator.Begin(Uuid::CreateRandom(), *materialTypeAsset);
|
||||
materialAssetCreator.Begin(Uuid::CreateRandom(), materialTypeAsset, true);
|
||||
materialAssetCreator.End(materialAsset);
|
||||
|
||||
Data::Instance<Material> material = Material::FindOrCreate(materialAsset);
|
||||
@@ -859,7 +859,7 @@ namespace UnitTest
|
||||
materialTypeCreator.End(m_testMaterialTypeAsset);
|
||||
|
||||
MaterialAssetCreator materialCreator;
|
||||
materialCreator.Begin(Uuid::CreateRandom(), *m_testMaterialTypeAsset);
|
||||
materialCreator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset, true);
|
||||
materialCreator.End(m_testMaterialAsset);
|
||||
|
||||
Data::Instance<Material> material = Material::FindOrCreate(m_testMaterialAsset);
|
||||
|
||||
@@ -51,7 +51,6 @@ set(FILES
|
||||
Include/Atom/RPI.Reflect/Image/StreamingImagePoolAssetCreator.h
|
||||
Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h
|
||||
Include/Atom/RPI.Reflect/Material/MaterialAsset.h
|
||||
Include/Atom/RPI.Reflect/Material/MaterialAssetCreatorCommon.h
|
||||
Include/Atom/RPI.Reflect/Material/MaterialAssetCreator.h
|
||||
Include/Atom/RPI.Reflect/Material/MaterialDynamicMetadata.h
|
||||
Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h
|
||||
@@ -133,7 +132,6 @@ set(FILES
|
||||
Source/RPI.Reflect/Image/StreamingImagePoolAssetCreator.cpp
|
||||
Source/RPI.Reflect/Material/MaterialPropertyValue.cpp
|
||||
Source/RPI.Reflect/Material/MaterialAsset.cpp
|
||||
Source/RPI.Reflect/Material/MaterialAssetCreatorCommon.cpp
|
||||
Source/RPI.Reflect/Material/MaterialAssetCreator.cpp
|
||||
Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp
|
||||
Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp
|
||||
|
||||
@@ -680,12 +680,6 @@ 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))
|
||||
{
|
||||
@@ -719,7 +713,7 @@ namespace MaterialEditor
|
||||
// Long term, the material document should not be concerned with assets at all. The viewport window should be the
|
||||
// only thing concerned with assets or instances.
|
||||
auto materialAssetResult =
|
||||
m_materialSourceData.CreateMaterialAssetFromSourceData(Uuid::CreateRandom(), m_absolutePath, elevateWarnings, true, &m_sourceDependencies);
|
||||
m_materialSourceData.CreateMaterialAssetFromSourceData(Uuid::CreateRandom(), m_absolutePath, elevateWarnings, &m_sourceDependencies);
|
||||
if (!materialAssetResult)
|
||||
{
|
||||
AZ_Error("MaterialDocument", false, "Material asset could not be created from source data: '%s'.", m_absolutePath.c_str());
|
||||
@@ -759,7 +753,7 @@ namespace MaterialEditor
|
||||
}
|
||||
|
||||
auto parentMaterialAssetResult = parentMaterialSourceData.CreateMaterialAssetFromSourceData(
|
||||
parentMaterialAssetIdResult.GetValue(), m_materialSourceData.m_parentMaterial, true, true);
|
||||
parentMaterialAssetIdResult.GetValue(), m_materialSourceData.m_parentMaterial, true);
|
||||
if (!parentMaterialAssetResult)
|
||||
{
|
||||
AZ_Error("MaterialDocument", false, "Material parent asset could not be created from source data: '%s'.", m_materialSourceData.m_parentMaterial.c_str());
|
||||
|
||||
Reference in New Issue
Block a user