Changed .material serialization to avoid loading the .materialtype file, since the .material builder doesn't declare a source dependency on the .materialtype. Otherwise there can be ambiguous edge cases where changes to the .materialtype might or might not impact the baked MaterialAsset. Note that another option would have been to add a the appropriate source dependency, but that would hurt iteration time as any change to the .materialtype file would cause every .material file and .fbx to rebuild.

These changes have the added benefit of simplifying some of the serialization code. MaterialSourceDataSerializer is no longer needed, as its main purpose was to pass the MaterialTypeSourceData down to the MaterialPropertyValueSerializer.

Before, the JSON serialization system gave a lot of data flexibility because it did best-effort conversions, like allowing a float to be loaded as an int for example. But now the material serialization code doesn't know target data type, so it has to assume the data type based on what's in the .material file, and then the MaterialAsset will convert the data to the appropriate type later when Finalize() is called.

Signed-off-by: santorac <55155825+santorac@users.noreply.github.com>
monroegm-disable-blank-issue-2
santorac 4 years ago
parent 3bbb8d78cd
commit a896ff11bc

@ -24,13 +24,6 @@ namespace AZ
AZ_RTTI(AZ::RPI::JsonMaterialPropertyValueSerializer, "{A52B1ED8-C849-4269-9AA7-9D0814D2EC59}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
//! A LoadContext object must be passed down to the serializer via JsonDeserializerContext::GetMetadata().Add(...)
struct LoadContext
{
AZ_TYPE_INFO(JsonMaterialPropertyValueSerializer::LoadContext, "{5E0A891A-27F6-4AD7-88A5-B9EA50F88B45}");
uint32_t m_materialTypeVersion; //!< The version number from the .materialtype file
};
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;

@ -54,7 +54,7 @@ namespace AZ
//! The resolved value with a valid type of a property. It needs to be mutable to allow post-resolving when parent objects are declared as const.
mutable MaterialPropertyValue m_resolvedValue;
//! Candidate values from serialization.
AZStd::map<AZ::TypeId, MaterialPropertyValue> m_possibleValues;
AZStd::map<AZ::TypeId, MaterialPropertyValue> m_possibleValues;
};
} // namespace RPI
} // namespace AZ

@ -1,40 +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 <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
namespace AZ
{
class ReflectContext;
namespace RPI
{
//! This custom serializer is needed to load the material type file and saves its data in the
//! JsonDeserializerSettings for JsonMaterialPropertyValueSerializer to use.
//! (Note we could have made a custom serializer specifically for the 'materialType' field but that
//! would require 'materialType' to appear before 'properties'. By having a custom serializer for the common
//! parent of 'materialType' and 'properties', we can avoid an order dependency within the JSON file).
class JsonMaterialSourceDataSerializer
: public BaseJsonSerializer
{
public:
AZ_RTTI(AZ::RPI::JsonMaterialSourceDataSerializer, "{008A7423-8DF6-4BA3-BF5E-B0C189CCBE58}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue,
const void* defaultValue, const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
} // namespace RPI
} // namespace AZ

@ -186,9 +186,8 @@ namespace AZ
//! Searches for a specific property.
//! Note this function can find properties using old versions of the property name; in that case,
//! the name in the returned PropertyDefinition* will not match the @propertyName that was searched for.
//! @param materialTypeVersion indicates the version number of the property name being passed in. Only renames above this version number will be applied.
//! @return the requested property, or null if it could not be found
const PropertyDefinition* FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName, uint32_t materialTypeVersion = 0) const;
const PropertyDefinition* FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName) const;
//! Construct a complete list of group definitions, including implicit groups, arranged in the same order as the source data
//! Groups with the same name will be consolidated into a single entry
@ -212,9 +211,8 @@ namespace AZ
Outcome<Data::Asset<MaterialTypeAsset>> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const;
//! Possibly renames @propertyId based on the material version update steps.
//! @param materialTypeVersion indicates the version number of the property name being passed in. Only renames above this version number will be applied.
//! @return true if the property was renamed
bool ApplyPropertyRenames(MaterialPropertyId& propertyId, uint32_t materialTypeVersion = 0) const;
bool ApplyPropertyRenames(MaterialPropertyId& propertyId) const;
};
//! The wrapper class for derived material functors.

@ -8,7 +8,6 @@
#include <Atom/RPI.Edit/Material/MaterialPropertyValueSerializer.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialSourceDataSerializer.h>
#include <Atom/RPI.Edit/Material/MaterialPropertySerializer.h>
#include <AzCore/Math/Color.h>
@ -55,15 +54,6 @@ namespace AZ
MaterialSourceData::Property* property = reinterpret_cast<MaterialSourceData::Property*>(outputValue);
AZ_Assert(property, "Output value for JsonMaterialPropertyValueSerializer can't be null.");
const MaterialTypeSourceData* materialType = context.GetMetadata().Find<MaterialTypeSourceData>();
if (!materialType)
{
AZ_Assert(false, "Material type reference not found");
return context.Report(JsonSerializationResult::Tasks::ReadField, JsonSerializationResult::Outcomes::Catastrophic, "Material type reference not found.");
}
const JsonMaterialPropertyValueSerializer::LoadContext* loadContext = context.GetMetadata().Find<JsonMaterialPropertyValueSerializer::LoadContext>();
// Construct the full property name (groupName.propertyName) by parsing it from the JSON path string.
size_t startPropertyName = context.GetPath().Get().rfind('/');
size_t startGroupName = context.GetPath().Get().rfind('/', startPropertyName-1);
@ -72,48 +62,70 @@ namespace AZ
JSR::ResultCode result(JSR::Tasks::ReadField);
auto propertyDefinition = materialType->FindProperty(groupName, propertyName, loadContext->m_materialTypeVersion);
if (!propertyDefinition)
if (inputValue.IsBool())
{
AZStd::string message = AZStd::string::format("Property '%.*s.%.*s' not found in material type.", AZ_STRING_ARG(groupName), AZ_STRING_ARG(propertyName));
return context.Report(JsonSerializationResult::Tasks::ReadField, JsonSerializationResult::Outcomes::Unsupported, message);
result.Combine(LoadVariant<bool>(property->m_value, false, inputValue, context));
}
else
else if (inputValue.IsInt() || inputValue.IsInt64())
{
result.Combine(LoadVariant<int32_t>(property->m_value, 0, inputValue, context));
}
else if (inputValue.IsUint() || inputValue.IsUint64())
{
result.Combine(LoadVariant<uint32_t>(property->m_value, 0u, inputValue, context));
}
else if (inputValue.IsFloat() || inputValue.IsDouble())
{
result.Combine(LoadVariant<float>(property->m_value, 0.0f, inputValue, context));
}
else if (inputValue.IsArray() && inputValue.Size() == 4)
{
result.Combine(LoadVariant<Vector4>(property->m_value, Vector4{0.0f, 0.0f, 0.0f, 0.0f}, inputValue, context));
}
else if (inputValue.IsArray() && inputValue.Size() == 3)
{
result.Combine(LoadVariant<Vector3>(property->m_value, Vector3{0.0f, 0.0f, 0.0f}, inputValue, context));
}
else if (inputValue.IsArray() && inputValue.Size() == 2)
{
switch (propertyDefinition->m_dataType)
result.Combine(LoadVariant<Vector2>(property->m_value, Vector2{0.0f, 0.0f}, inputValue, context));
}
else if (inputValue.IsObject())
{
JsonSerializationResult::ResultCode resultCode = LoadVariant<Color>(property->m_value, Color::CreateZero(), inputValue, context);
if(resultCode.GetProcessing() != JsonSerializationResult::Processing::Completed)
{
resultCode = LoadVariant<Vector4>(property->m_value, Vector4::CreateZero(), inputValue, context);
}
if(resultCode.GetProcessing() != JsonSerializationResult::Processing::Completed)
{
resultCode = LoadVariant<Vector3>(property->m_value, Vector3::CreateZero(), inputValue, context);
}
if(resultCode.GetProcessing() != JsonSerializationResult::Processing::Completed)
{
resultCode = LoadVariant<Vector2>(property->m_value, Vector2::CreateZero(), inputValue, context);
}
if(resultCode.GetProcessing() == JsonSerializationResult::Processing::Completed)
{
result.Combine(resultCode);
}
else
{
case MaterialPropertyDataType::Bool:
result.Combine(LoadVariant<bool>(property->m_value, false, inputValue, context));
break;
case MaterialPropertyDataType::Int:
result.Combine(LoadVariant<int32_t>(property->m_value, 0, inputValue, context));
break;
case MaterialPropertyDataType::UInt:
result.Combine(LoadVariant<uint32_t>(property->m_value, 0u, inputValue, context));
break;
case MaterialPropertyDataType::Float:
result.Combine(LoadVariant<float>(property->m_value, 0.0f, inputValue, context));
break;
case MaterialPropertyDataType::Vector2:
result.Combine(LoadVariant<Vector2>(property->m_value, Vector2{0.0f, 0.0f}, inputValue, context));
break;
case MaterialPropertyDataType::Vector3:
result.Combine(LoadVariant<Vector3>(property->m_value, Vector3{0.0f, 0.0f, 0.0f}, inputValue, context));
break;
case MaterialPropertyDataType::Vector4:
result.Combine(LoadVariant<Vector4>(property->m_value, Vector4{0.0f, 0.0f, 0.0f, 0.0f}, inputValue, context));
break;
case MaterialPropertyDataType::Color:
result.Combine(LoadVariant<Color>(property->m_value, AZ::Colors::White, inputValue, context));
break;
case MaterialPropertyDataType::Image:
case MaterialPropertyDataType::Enum:
result.Combine(LoadVariant<AZStd::string>(property->m_value, AZStd::string{}, inputValue, context));
break;
default:
return context.Report(JsonSerializationResult::Tasks::ReadField, JsonSerializationResult::Outcomes::Unsupported, "Unknown data type");
}
}
else if (inputValue.IsString())
{
result.Combine(LoadVariant<AZStd::string>(property->m_value, AZStd::string{}, inputValue, context));
}
else
{
return context.Report(JsonSerializationResult::Tasks::ReadField, JsonSerializationResult::Outcomes::Unsupported, "Unknown data type");
}
if (result.GetProcessing() == JsonSerializationResult::Processing::Completed)
{

@ -8,7 +8,6 @@
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialPropertyValueSerializer.h>
#include <Atom/RPI.Edit/Material/MaterialSourceDataSerializer.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
#include <Atom/RPI.Edit/Material/MaterialUtils.h>
@ -45,13 +44,17 @@ namespace AZ
{
if (JsonRegistrationContext* jsonContext = azrtti_cast<JsonRegistrationContext*>(context))
{
jsonContext->Serializer<JsonMaterialSourceDataSerializer>()->HandlesType<MaterialSourceData>();
jsonContext->Serializer<JsonMaterialPropertyValueSerializer>()->HandlesType<MaterialSourceData::Property>();
}
else if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<MaterialSourceData>()
->Version(1)
->Version(2)
->Field("description", &MaterialSourceData::m_description)
->Field("materialType", &MaterialSourceData::m_materialType)
->Field("materialTypeVersion", &MaterialSourceData::m_materialTypeVersion)
->Field("parentMaterial", &MaterialSourceData::m_parentMaterial)
->Field("properties", &MaterialSourceData::m_properties)
;
serializeContext->RegisterGenericType<PropertyMap>();
@ -80,6 +83,12 @@ namespace AZ
MaterialAssetCreator materialAssetCreator;
materialAssetCreator.SetElevateWarnings(elevateWarnings);
if (m_materialType.empty())
{
AZ_Error("MaterialSourceData", false, "materialType was not specified");
return Failure();
}
Outcome<Data::AssetId> materialTypeAssetId = AssetUtils::MakeAssetId(materialSourceFilePath, m_materialType, 0);
if (!materialTypeAssetId)
{
@ -194,6 +203,12 @@ namespace AZ
bool elevateWarnings,
AZStd::unordered_set<AZStd::string>* sourceDependencies) const
{
if (m_materialType.empty())
{
AZ_Error("MaterialSourceData", false, "materialType was not specified");
return Failure();
}
const auto materialTypeSourcePath = AssetUtils::ResolvePathReference(materialSourceFilePath, m_materialType);
const auto materialTypeAssetId = AssetUtils::MakeAssetId(materialTypeSourcePath, 0);
if (!materialTypeAssetId.IsSuccess())

@ -1,162 +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.Edit/Material/MaterialSourceDataSerializer.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialPropertyValueSerializer.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Edit/Common/JsonFileLoadContext.h>
#include <Atom/RPI.Edit/Common/JsonUtils.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
#include <AzCore/Serialization/Json/JsonSerializationResult.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/StackedString.h>
namespace AZ
{
namespace RPI
{
AZ_CLASS_ALLOCATOR_IMPL(JsonMaterialSourceDataSerializer, SystemAllocator, 0);
JsonSerializationResult::Result JsonMaterialSourceDataSerializer::Load(void* outputValue, const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult;
AZ_Assert(azrtti_typeid<MaterialSourceData>() == outputValueTypeId,
"Unable to deserialize material to json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(outputValueTypeId);
MaterialSourceData* materialSourceData = reinterpret_cast<MaterialSourceData*>(outputValue);
AZ_Assert(materialSourceData, "Output value for JsonMaterialSourceDataSerializer can't be null.");
JSR::ResultCode result(JSR::Tasks::ReadField);
if (!inputValue.IsObject())
{
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported, "Material data must be a JSON object");
}
result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_description, azrtti_typeid<AZStd::string>(), inputValue, "description", context));
result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_parentMaterial, azrtti_typeid<AZStd::string>(), inputValue, "parentMaterial", context));
result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_materialType, azrtti_typeid<AZStd::string>(), inputValue, "materialType", context));
result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_materialTypeVersion, azrtti_typeid<uint32_t>(), inputValue, "materialTypeVersion", context));
if (materialSourceData->m_materialType.empty())
{
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Catastrophic, "Required field 'materialType' is missing or invalid");
}
JsonFileLoadContext* jsonFileLoadContext = context.GetMetadata().Find<JsonFileLoadContext>();
if (!jsonFileLoadContext)
{
// Go ahead and create a JsonFileLoadContext because we'll need to use it below when loading the material type
context.GetMetadata().Add(JsonFileLoadContext{});
jsonFileLoadContext = context.GetMetadata().Find<JsonFileLoadContext>();
}
// Load the material type file because we need the property type information in order to know how to read the property values
MaterialTypeSourceData materialTypeData;
{
AZStd::string materialTypePath = AssetUtils::ResolvePathReference(jsonFileLoadContext->GetFilePath(), materialSourceData->m_materialType);
auto materialTypeJson = JsonSerializationUtils::ReadJsonFile(materialTypePath, AZ::RPI::JsonUtils::DefaultMaxFileSize);
if (!materialTypeJson.IsSuccess())
{
AZStd::string failureMessage;
failureMessage = AZStd::string::format("Failed to load material-type file '%s': %s", materialTypePath.c_str(), materialTypeJson.GetError().c_str());
ScopedContextPath subPath{context, "materialType"};
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Catastrophic, failureMessage);
}
else
{
// Since we're about to load a different file the JsonFileLoadContext needs to be changed to reflect the file that's being loaded.
jsonFileLoadContext->PushFilePath(materialTypePath);
// We also need a special reporting function for the material type, to note the fact that the issue is in the material type not this file.
auto reportingPrev = context.GetReporter();
context.PushReporter([materialTypePath, reportingPrev](AZStd::string_view message, JSR::ResultCode result, AZStd::string_view path) -> JSR::ResultCode
{
AZStd::string materialTypeFilename;
if (!AzFramework::StringFunc::Path::GetFullFileName(materialTypePath.c_str(), materialTypeFilename))
{
materialTypeFilename = materialTypePath;
}
AZStd::string newPath = AZStd::string::format("[%.*s]%.*s", AZ_STRING_ARG(materialTypeFilename), AZ_STRING_ARG(path));
return reportingPrev(message, result, newPath);
});
JsonDeserializerSettings settings;
settings.m_metadata = context.GetMetadata();
settings.m_reporting = context.GetReporter();
settings.m_registrationContext = context.GetRegistrationContext();
settings.m_serializeContext = context.GetSerializeContext();
settings.m_clearContainers = context.ShouldClearContainers();
JsonSerializationResult::ResultCode materialTypeLoadResult = JsonSerialization::Load(materialTypeData, materialTypeJson.GetValue(), settings);
materialTypeData.ResolveUvEnums();
// Restore prior configuration
context.PopReporter();
jsonFileLoadContext->PopFilePath();
// Even though results from the material type file is a separate JSON serialization, we combine the results to make sure
// any issues are bubbled up. I'm not sure if this is the most desirable approach, but better to over-report issues than
// under-report them.
result.Combine(materialTypeLoadResult);
}
}
context.GetMetadata().Add(AZStd::move(materialTypeData));
JsonMaterialPropertyValueSerializer::LoadContext materialPropertyValueLoadContext;
materialPropertyValueLoadContext.m_materialTypeVersion = materialSourceData->m_materialTypeVersion;
context.GetMetadata().Add(materialPropertyValueLoadContext);
result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_properties, azrtti_typeid<MaterialSourceData::PropertyGroupMap>(), inputValue, "properties", context));
if (result.GetProcessing() == JsonSerializationResult::Processing::Completed)
{
return context.Report(result, "Successfully loaded material.");
}
else
{
return context.Report(result, "Partially loaded material.");
}
}
JsonSerializationResult::Result JsonMaterialSourceDataSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
[[maybe_unused]] const void* defaultValue, const Uuid& valueTypeId, JsonSerializerContext& context)
{
namespace JSR = JsonSerializationResult;
AZ_Assert(azrtti_typeid<MaterialSourceData>() == valueTypeId,
"Unable to serialize material to json because the provided type is %s",
valueTypeId.ToString<AZStd::string>().c_str());
AZ_UNUSED(valueTypeId);
const MaterialSourceData* materialSourceData = reinterpret_cast<const MaterialSourceData*>(inputValue);
AZ_Assert(materialSourceData, "Input value for JsonMaterialSourceDataSerializer can't be null.");
JSR::ResultCode resultCode(JSR::Tasks::ReadField);
resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "description", &materialSourceData->m_description, nullptr, azrtti_typeid<AZStd::string>(), context));
resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "parentMaterial", &materialSourceData->m_parentMaterial, nullptr, azrtti_typeid<AZStd::string>(), context));
resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "materialType", &materialSourceData->m_materialType, nullptr, azrtti_typeid<AZStd::string>(), context));
resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "materialTypeVersion", &materialSourceData->m_materialTypeVersion, nullptr, azrtti_typeid<uint32_t>(), context));
resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "properties", &materialSourceData->m_properties, nullptr, azrtti_typeid<MaterialSourceData::PropertyGroupMap>(), context));
return context.Report(resultCode, "Processed material.");
}
} // namespace RPI
} // namespace AZ

@ -130,17 +130,12 @@ namespace AZ
return nullptr;
}
bool MaterialTypeSourceData::ApplyPropertyRenames(MaterialPropertyId& propertyId, uint32_t materialTypeVersion) const
bool MaterialTypeSourceData::ApplyPropertyRenames(MaterialPropertyId& propertyId) const
{
bool renamed = false;
for (const VersionUpdateDefinition& versionUpdate : m_versionUpdates)
{
if (materialTypeVersion >= versionUpdate.m_toVersion)
{
continue;
}
for (const VersionUpdatesRenameOperationDefinition& action : versionUpdate.m_actions)
{
if (action.m_operation == "rename")
@ -161,7 +156,7 @@ namespace AZ
return renamed;
}
const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName, uint32_t materialTypeVersion) const
const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName) const
{
auto groupIter = m_propertyLayout.m_properties.find(groupName);
if (groupIter != m_propertyLayout.m_properties.end())
@ -178,7 +173,7 @@ namespace AZ
// Property has not been found, try looking for renames in the version history
MaterialPropertyId propertyId = MaterialPropertyId{groupName, propertyName};
ApplyPropertyRenames(propertyId, materialTypeVersion);
ApplyPropertyRenames(propertyId);
// Do the search again with the new names

@ -109,6 +109,77 @@ namespace AZ
return m_wasPreFinalized;
}
template<typename T>
MaterialPropertyValue CastNumericMaterialPropertyValue(const MaterialPropertyValue& value)
{
TypeId typeId = value.GetTypeId();
if (typeId == azrtti_typeid<bool>())
{
return aznumeric_cast<T>(value.GetValue<bool>());
}
else if (typeId == azrtti_typeid<int32_t>())
{
return aznumeric_cast<T>(value.GetValue<int32_t>());
}
else if (typeId == azrtti_typeid<uint32_t>())
{
return aznumeric_cast<T>(value.GetValue<uint32_t>());
}
else if (typeId == azrtti_typeid<float>())
{
return aznumeric_cast<T>(value.GetValue<float>());
}
else
{
return value;
}
}
template<typename VectorT>
MaterialPropertyValue CastVectorMaterialPropertyValue(const MaterialPropertyValue& value)
{
float values[4] = {};
TypeId typeId = value.GetTypeId();
if (typeId == azrtti_typeid<Vector2>())
{
value.GetValue<Vector2>().StoreToFloat2(values);
}
else if (typeId == azrtti_typeid<Vector3>())
{
value.GetValue<Vector3>().StoreToFloat3(values);
}
else if (typeId == azrtti_typeid<Vector4>())
{
value.GetValue<Vector4>().StoreToFloat4(values);
}
else
{
return value;
}
typeId = azrtti_typeid<VectorT>();
if (typeId == azrtti_typeid<Vector2>())
{
return Vector2::CreateFromFloat2(values);
}
else if (typeId == azrtti_typeid<Vector3>())
{
return Vector3::CreateFromFloat3(values);
}
else if (typeId == azrtti_typeid<Vector4>())
{
return Vector4::CreateFromFloat4(values);
}
else
{
return value;
}
}
void MaterialAsset::Finalize(AZStd::function<void(const char*)> reportWarning, AZStd::function<void(const char*)> reportError)
{
if (m_wasPreFinalized)
@ -180,9 +251,66 @@ namespace AZ
}
else
{
if (ValidateMaterialPropertyDataType(value.GetTypeId(), name, propertyDescriptor, reportError))
// The material asset could be finalized sometime after the original JSON is loaded, and the material type might not have been available
// at that time, so the data type would not be known for each property. So each raw property's type could be based on what appeared in the JSON
// and this is the first opportunity we have to resolve that value with the actual type. For example, a float property could have been specified in
// the JSON as 7 instead of 7.0, which is valid. Similarly, a Color and a Vector3 can both be specified as "[0.0,0.0,0.0]" in the JSON file.
MaterialPropertyValue finalValue = value;
switch (propertyDescriptor->GetDataType())
{
case MaterialPropertyDataType::Bool:
finalValue = CastNumericMaterialPropertyValue<bool>(value);
break;
case MaterialPropertyDataType::Int:
finalValue = CastNumericMaterialPropertyValue<int32_t>(value);
break;
case MaterialPropertyDataType::UInt:
finalValue = CastNumericMaterialPropertyValue<uint32_t>(value);
break;
case MaterialPropertyDataType::Float:
finalValue = CastNumericMaterialPropertyValue<float>(value);
break;
case MaterialPropertyDataType::Color:
if (value.GetTypeId() == azrtti_typeid<Vector3>())
{
finalValue = Color::CreateFromVector3(value.GetValue<Vector3>());
}
else if (value.GetTypeId() == azrtti_typeid<Vector4>())
{
Vector4 vector4 = value.GetValue<Vector4>();
finalValue = Color::CreateFromVector3AndFloat(vector4.GetAsVector3(), vector4.GetW());
}
break;
case MaterialPropertyDataType::Vector2:
finalValue = CastVectorMaterialPropertyValue<Vector2>(value);
break;
case MaterialPropertyDataType::Vector3:
if (value.GetTypeId() == azrtti_typeid<Color>())
{
finalValue = value.GetValue<Color>().GetAsVector3();
}
else
{
finalValue = CastVectorMaterialPropertyValue<Vector3>(value);
}
break;
case MaterialPropertyDataType::Vector4:
if (value.GetTypeId() == azrtti_typeid<Color>())
{
finalValue = value.GetValue<Color>().GetAsVector4();
}
else
{
finalValue = CastVectorMaterialPropertyValue<Vector4>(value);
}
break;
}
if (ValidateMaterialPropertyDataType(finalValue.GetTypeId(), name, propertyDescriptor, reportError))
{
finalizedPropertyValues[propertyIndex.GetIndex()] = value;
finalizedPropertyValues[propertyIndex.GetIndex()] = finalValue;
}
}
}

@ -449,37 +449,7 @@ namespace UnitTest
expectCreatorError("Type mismatch",
[](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyInt" }, 0.0f);
});
expectCreatorError("Type mismatch",
[](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyUInt" }, -1);
});
expectCreatorError("Type mismatch",
[](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyFloat" }, 10u);
});
expectCreatorError("Type mismatch",
[](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyFloat2" }, 1.0f);
});
expectCreatorError("Type mismatch",
[](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyFloat3" }, AZ::Vector4{});
});
expectCreatorError("Type mismatch",
[](MaterialAssetCreator& creator)
{
creator.SetPropertyValue(Name{ "MyFloat4" }, AZ::Vector3{});
creator.SetPropertyValue(Name{ "MyFloat" }, AZ::Vector4{});
});
expectCreatorError("Type mismatch",

@ -297,6 +297,63 @@ namespace UnitTest
checkRawPropertyValues();
}
// Can return a Vector4 or a Color as a Vector4
Vector4 GetAsVector4(const MaterialPropertyValue& value)
{
if (value.GetTypeId() == azrtti_typeid<Vector4>())
{
return value.GetValue<Vector4>();
}
else if (value.GetTypeId() == azrtti_typeid<Color>())
{
return value.GetValue<Color>().GetAsVector4();
}
else
{
return Vector4::CreateZero();
}
}
// Can return a Int or a UInt as a Int
int32_t GetAsInt(const MaterialPropertyValue& value)
{
if (value.GetTypeId() == azrtti_typeid<int32_t>())
{
return value.GetValue<int32_t>();
}
else if (value.GetTypeId() == azrtti_typeid<uint32_t>())
{
return aznumeric_cast<int32_t>(value.GetValue<uint32_t>());
}
else
{
return 0;
}
}
template<typename TargetTypeT>
bool AreTypesCompatible(const MaterialPropertyValue& a, const MaterialPropertyValue& b)
{
auto fixupType = [](TypeId t)
{
if (t == azrtti_typeid<uint32_t>())
{
return azrtti_typeid<int32_t>();
}
if (t == azrtti_typeid<Color>())
{
return azrtti_typeid<Vector4>();
}
return t;
};
TypeId targetTypeId = azrtti_typeid<TargetTypeT>();
return fixupType(a.GetTypeId()) == fixupType(targetTypeId) && fixupType(b.GetTypeId()) == fixupType(targetTypeId);
}
void CheckEqual(MaterialSourceData& a, MaterialSourceData& b)
{
EXPECT_STREQ(a.m_materialType.data(), b.m_materialType.data());
@ -334,27 +391,41 @@ namespace UnitTest
auto& propertyA = propertyIterA.second;
auto& propertyB = propertyIterB->second;
bool typesMatch = propertyA.m_value.GetTypeId() == propertyB.m_value.GetTypeId();
EXPECT_TRUE(typesMatch);
if (typesMatch)
AZStd::string propertyReference = AZStd::string::format(" for property '%s.%s'", groupName.c_str(), propertyName.c_str());
// We allow some types like Vector4 and Color or Int and UInt to be interchangeable since they serialize the same and can be converted when the MaterialAsset is finalized.
if (AreTypesCompatible<bool>(propertyA.m_value, propertyB.m_value))
{
AZStd::string propertyReference = AZStd::string::format(" for property '%s.%s'", groupName.c_str(), propertyName.c_str());
auto typeId = propertyA.m_value.GetTypeId();
if (typeId == azrtti_typeid<bool>()) { EXPECT_EQ(propertyA.m_value.GetValue<bool>(), propertyB.m_value.GetValue<bool>()) << propertyReference.c_str(); }
else if (typeId == azrtti_typeid<int32_t>()) { EXPECT_EQ(propertyA.m_value.GetValue<int32_t>(), propertyB.m_value.GetValue<int32_t>()) << propertyReference.c_str(); }
else if (typeId == azrtti_typeid<uint32_t>()) { EXPECT_EQ(propertyA.m_value.GetValue<uint32_t>(), propertyB.m_value.GetValue<uint32_t>()) << propertyReference.c_str(); }
else if (typeId == azrtti_typeid<float>()) { EXPECT_NEAR(propertyA.m_value.GetValue<float>(), propertyB.m_value.GetValue<float>(), 0.01) << propertyReference.c_str(); }
else if (typeId == azrtti_typeid<Vector2>()) { EXPECT_TRUE(propertyA.m_value.GetValue<Vector2>().IsClose(propertyB.m_value.GetValue<Vector2>())) << propertyReference.c_str(); }
else if (typeId == azrtti_typeid<Vector3>()) { EXPECT_TRUE(propertyA.m_value.GetValue<Vector3>().IsClose(propertyB.m_value.GetValue<Vector3>())) << propertyReference.c_str(); }
else if (typeId == azrtti_typeid<Vector4>()) { EXPECT_TRUE(propertyA.m_value.GetValue<Vector4>().IsClose(propertyB.m_value.GetValue<Vector4>())) << propertyReference.c_str(); }
else if (typeId == azrtti_typeid<Color>()) { EXPECT_TRUE(propertyA.m_value.GetValue<Color>().IsClose(propertyB.m_value.GetValue<Color>())) << propertyReference.c_str(); }
else if (typeId == azrtti_typeid<AZStd::string>()) { EXPECT_STREQ(propertyA.m_value.GetValue<AZStd::string>().c_str(), propertyB.m_value.GetValue<AZStd::string>().c_str()) << propertyReference.c_str(); }
else
{
ADD_FAILURE();
}
EXPECT_EQ(propertyA.m_value.GetValue<bool>(), propertyB.m_value.GetValue<bool>()) << propertyReference.c_str();
}
else if (AreTypesCompatible<int32_t>(propertyA.m_value, propertyB.m_value))
{
EXPECT_EQ(GetAsInt(propertyA.m_value), GetAsInt(propertyB.m_value)) << propertyReference.c_str();
}
else if (AreTypesCompatible<float>(propertyA.m_value, propertyB.m_value))
{
EXPECT_NEAR(propertyA.m_value.GetValue<float>(), propertyB.m_value.GetValue<float>(), 0.01) << propertyReference.c_str();
}
else if (AreTypesCompatible<Vector2>(propertyA.m_value, propertyB.m_value))
{
EXPECT_TRUE(propertyA.m_value.GetValue<Vector2>().IsClose(propertyB.m_value.GetValue<Vector2>())) << propertyReference.c_str();
}
else if (AreTypesCompatible<Vector3>(propertyA.m_value, propertyB.m_value))
{
EXPECT_TRUE(propertyA.m_value.GetValue<Vector3>().IsClose(propertyB.m_value.GetValue<Vector3>())) << propertyReference.c_str();
}
else if (AreTypesCompatible<Vector4>(propertyA.m_value, propertyB.m_value))
{
EXPECT_TRUE(GetAsVector4(propertyA.m_value).IsClose(GetAsVector4(propertyB.m_value))) << propertyReference.c_str();
}
else if (AreTypesCompatible<AZStd::string>(propertyA.m_value, propertyB.m_value))
{
EXPECT_STREQ(propertyA.m_value.GetValue<AZStd::string>().c_str(), propertyB.m_value.GetValue<AZStd::string>().c_str()) << propertyReference.c_str();
}
else
{
ADD_FAILURE();
}
}
}
@ -363,42 +434,8 @@ namespace UnitTest
TEST_F(MaterialSourceDataTests, TestJsonRoundTrip)
{
const char* materialTypeJson =
"{ \n"
" \"propertyLayout\": { \n"
" \"version\": 1, \n"
" \"groups\": [ \n"
" { \"name\": \"groupA\" }, \n"
" { \"name\": \"groupB\" }, \n"
" { \"name\": \"groupC\" } \n"
" ], \n"
" \"properties\": { \n"
" \"groupA\": [ \n"
" {\"name\": \"MyBool\", \"type\": \"bool\"}, \n"
" {\"name\": \"MyInt\", \"type\": \"int\"}, \n"
" {\"name\": \"MyUInt\", \"type\": \"uint\"} \n"
" ], \n"
" \"groupB\": [ \n"
" {\"name\": \"MyFloat\", \"type\": \"float\"}, \n"
" {\"name\": \"MyFloat2\", \"type\": \"vector2\"}, \n"
" {\"name\": \"MyFloat3\", \"type\": \"vector3\"} \n"
" ], \n"
" \"groupC\": [ \n"
" {\"name\": \"MyFloat4\", \"type\": \"vector4\"}, \n"
" {\"name\": \"MyColor\", \"type\": \"color\"}, \n"
" {\"name\": \"MyImage\", \"type\": \"image\"} \n"
" ] \n"
" } \n"
" } \n"
"} \n";
const char* materialTypeFilePath = "@exefolder@/Temp/roundTripTest.materialtype";
AZ::IO::FileIOStream file;
EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath));
file.Write(strlen(materialTypeJson), materialTypeJson);
file.Close();
MaterialSourceData sourceDataOriginal;
sourceDataOriginal.m_materialType = materialTypeFilePath;
sourceDataOriginal.m_parentMaterial = materialTypeFilePath;
@ -434,8 +471,8 @@ namespace UnitTest
"properties": {
"general": [
{
"name": "testColor",
"type": "color"
"name": "testValue",
"type": "Float"
}
]
}
@ -456,7 +493,7 @@ namespace UnitTest
{
"properties": {
"general": {
"testColor": [0.1,0.2,0.3]
"testValue": 1.2
}
},
"materialType": "@exefolder@/Temp/simpleMaterialType.materialtype"
@ -469,27 +506,11 @@ namespace UnitTest
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
AZ::Color testColor = material.m_properties["general"]["testColor"].m_value.GetValue<AZ::Color>();
EXPECT_TRUE(AZ::Color(0.1f, 0.2f, 0.3f, 1.0f).IsClose(testColor, 0.01));
}
TEST_F(MaterialSourceDataTests, Load_Error_NotAnObject)
{
const AZStd::string inputJson = R"(
[]
)";
MaterialSourceData material;
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Altered, loadResult.m_jsonResultCode.GetProcessing());
EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::Unsupported, loadResult.m_jsonResultCode.GetOutcome());
EXPECT_TRUE(loadResult.ContainsMessage("", "Material data must be a JSON object"));
float testValue = material.m_properties["general"]["testValue"].m_value.GetValue<float>();
EXPECT_FLOAT_EQ(1.2f, testValue);
}
TEST_F(MaterialSourceDataTests, Load_Error_NoMaterialType)
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_NoMaterialType)
{
const AZStd::string inputJson = R"(
{
@ -505,14 +526,29 @@ namespace UnitTest
MaterialSourceData material;
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Halted, loadResult.m_jsonResultCode.GetProcessing());
EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::Catastrophic, loadResult.m_jsonResultCode.GetOutcome());
const bool elevateWarnings = false;
EXPECT_TRUE(loadResult.ContainsMessage("", "Required field 'materialType' is missing"));
}
ErrorMessageFinder errorMessageFinder;
TEST_F(MaterialSourceDataTests, Load_Error_MaterialTypeDoesNotExist)
errorMessageFinder.AddExpectedErrorMessage("materialType was not specified");
auto result = material.CreateMaterialAsset(AZ::Uuid::CreateRandom(), "test.material", AZ::RPI::MaterialAssetProcessingMode::DeferredBake, elevateWarnings);
EXPECT_FALSE(result.IsSuccess());
errorMessageFinder.CheckExpectedErrorsFound();
errorMessageFinder.Reset();
errorMessageFinder.AddExpectedErrorMessage("materialType was not specified");
result = material.CreateMaterialAsset(AZ::Uuid::CreateRandom(), "test.material", AZ::RPI::MaterialAssetProcessingMode::PreBake, elevateWarnings);
EXPECT_FALSE(result.IsSuccess());
errorMessageFinder.CheckExpectedErrorsFound();
errorMessageFinder.Reset();
errorMessageFinder.AddExpectedErrorMessage("materialType was not specified");
result = material.CreateMaterialAssetFromSourceData(AZ::Uuid::CreateRandom(), "test.material", elevateWarnings);
EXPECT_FALSE(result.IsSuccess());
errorMessageFinder.CheckExpectedErrorsFound();
}
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MaterialTypeDoesNotExist)
{
const AZStd::string inputJson = R"(
{
@ -529,102 +565,43 @@ namespace UnitTest
MaterialSourceData material;
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Halted, loadResult.m_jsonResultCode.GetProcessing());
EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::Catastrophic, loadResult.m_jsonResultCode.GetOutcome());
const bool elevateWarnings = false;
EXPECT_TRUE(loadResult.ContainsMessage("/materialType", "Failed to load material-type file"));
}
ErrorMessageFinder errorMessageFinder;
TEST_F(MaterialSourceDataTests, Load_MaterialTypeMessagesAreReported)
{
const AZStd::string simpleMaterialTypeJson = R"(
{
"propertyLayout": {
"properties": {
"general": [
{
"name": "testColor",
"type": "color"
}
]
}
}
}
)";
const char* materialTypeFilePath = "@exefolder@/Temp/simpleMaterialType.materialtype";
AZ::IO::FileIOStream file;
EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath));
file.Write(simpleMaterialTypeJson.size(), simpleMaterialTypeJson.data());
file.Close();
const AZStd::string inputJson = R"(
{
"materialType": "@exefolder@/Temp/simpleMaterialType.materialtype",
"materialTypeVersion": 1,
"properties": {
"general": {
"testColor": [1.0,1.0,1.0]
}
}
}
)";
errorMessageFinder.AddExpectedErrorMessage("Could not find asset [DoesNotExist.materialtype]");
auto result = material.CreateMaterialAsset(AZ::Uuid::CreateRandom(), "test.material", AZ::RPI::MaterialAssetProcessingMode::DeferredBake, elevateWarnings);
EXPECT_FALSE(result.IsSuccess());
errorMessageFinder.CheckExpectedErrorsFound();
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());
// propertyLayout is a field in the material type, not the material
EXPECT_TRUE(loadResult.ContainsMessage("[simpleMaterialType.materialtype]/propertyLayout/properties", "Successfully read"));
errorMessageFinder.Reset();
errorMessageFinder.AddExpectedErrorMessage("Could not find asset [DoesNotExist.materialtype]");
result = material.CreateMaterialAsset(AZ::Uuid::CreateRandom(), "test.material", AZ::RPI::MaterialAssetProcessingMode::PreBake, elevateWarnings);
EXPECT_FALSE(result.IsSuccess());
errorMessageFinder.CheckExpectedErrorsFound();
errorMessageFinder.Reset();
errorMessageFinder.AddExpectedErrorMessage("Could not find asset [DoesNotExist.materialtype]");
errorMessageFinder.AddIgnoredErrorMessage("Failed to create material type asset ID", true);
result = material.CreateMaterialAssetFromSourceData(AZ::Uuid::CreateRandom(), "test.material", elevateWarnings);
EXPECT_FALSE(result.IsSuccess());
errorMessageFinder.CheckExpectedErrorsFound();
}
TEST_F(MaterialSourceDataTests, Load_Error_PropertyNotFound)
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MaterialPropertyNotFound)
{
const AZStd::string simpleMaterialTypeJson = R"(
{
"propertyLayout": {
"properties": {
"general": [
{
"name": "testColor",
"type": "color"
}
]
}
}
}
)";
const char* materialTypeFilePath = "@exefolder@/Temp/simpleMaterialType.materialtype";
AZ::IO::FileIOStream file;
EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath));
file.Write(simpleMaterialTypeJson.size(), simpleMaterialTypeJson.data());
file.Close();
const AZStd::string inputJson = R"(
{
"materialType": "@exefolder@/Temp/simpleMaterialType.materialtype",
"materialTypeVersion": 1,
"properties": {
"general": {
"doesNotExist": [1.0,1.0,1.0]
}
}
}
)";
MaterialSourceData material;
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
EXPECT_EQ(AZ::JsonSerializationResult::Processing::PartialAlter, loadResult.m_jsonResultCode.GetProcessing());
material.m_materialType = "@exefolder@/Temp/test.materialtype";
AddPropertyGroup(material, "general");
AddProperty(material, "general", "FieldDoesNotExist", 1.5f);
const bool elevateWarnings = true;
EXPECT_TRUE(loadResult.ContainsMessage("/properties/general/doesNotExist", "Property 'general.doesNotExist' not found in material type."));
ErrorMessageFinder errorMessageFinder("\"general.FieldDoesNotExist\" is not found");
errorMessageFinder.AddIgnoredErrorMessage("Failed to build MaterialAsset", true);
auto result = material.CreateMaterialAsset(AZ::Uuid::CreateRandom(), "test.material", AZ::RPI::MaterialAssetProcessingMode::PreBake, elevateWarnings);
EXPECT_FALSE(result.IsSuccess());
errorMessageFinder.CheckExpectedErrorsFound();
}
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MultiLevelDataInheritance)
@ -896,7 +873,92 @@ namespace UnitTest
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
}
template<typename PropertyTypeT>
void CheckSimilar(PropertyTypeT a, PropertyTypeT b);
template<> void CheckSimilar<float>(float a, float b) { EXPECT_FLOAT_EQ(a, b); }
template<> void CheckSimilar<Vector2>(Vector2 a, Vector2 b) { EXPECT_TRUE(a.IsClose(b)); }
template<> void CheckSimilar<Vector3>(Vector3 a, Vector3 b) { EXPECT_TRUE(a.IsClose(b)); }
template<> void CheckSimilar<Vector4>(Vector4 a, Vector4 b) { EXPECT_TRUE(a.IsClose(b)); }
template<> void CheckSimilar<Color>(Color a, Color b) { EXPECT_TRUE(a.IsClose(b)); }
template<typename PropertyTypeT> void CheckSimilar(PropertyTypeT a, PropertyTypeT b) { EXPECT_EQ(a, b); }
template<typename PropertyTypeT>
void CheckEndToEndDataTypeResolution(const char* propertyName, const char* jsonValue, PropertyTypeT expectedFinalValue)
{
const char* groupName = "general";
const AZStd::string inputJson = AZStd::string::format(R"(
{
"materialType": "@exefolder@/Temp/test.materialtype",
"properties": {
"%s": {
"%s": %s
}
}
}
)", groupName, propertyName, jsonValue);
MaterialSourceData material;
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
auto materialAssetResult = material.CreateMaterialAsset(Uuid::CreateRandom(), "test.material", AZ::RPI::MaterialAssetProcessingMode::PreBake);
EXPECT_TRUE(materialAssetResult);
MaterialPropertyIndex propertyIndex = materialAssetResult.GetValue()->GetMaterialPropertiesLayout()->FindPropertyIndex(MaterialPropertyId{groupName, propertyName}.GetFullName());
CheckSimilar(expectedFinalValue, materialAssetResult.GetValue()->GetPropertyValues()[propertyIndex.GetIndex()].GetValue<PropertyTypeT>());
}
TEST_F(MaterialSourceDataTests, TestEndToEndDataTypeResolution)
{
// Data types in .material files don't have to exactly match the types in .materialtype files as specified in the properties layout.
// The exact location of the data type resolution has moved around over the life of the project, but the important thing is that
// the data type in the source .material file gets applied correctly by the time a finalized MaterialAsset comes out the other side.
CheckEndToEndDataTypeResolution("MyBool", "true", true);
CheckEndToEndDataTypeResolution("MyBool", "false", false);
CheckEndToEndDataTypeResolution("MyBool", "1", true);
CheckEndToEndDataTypeResolution("MyBool", "0", false);
CheckEndToEndDataTypeResolution("MyBool", "1.0", true);
CheckEndToEndDataTypeResolution("MyBool", "0.0", false);
CheckEndToEndDataTypeResolution("MyInt", "5", 5);
CheckEndToEndDataTypeResolution("MyInt", "-6", -6);
CheckEndToEndDataTypeResolution("MyInt", "-7.0", -7);
CheckEndToEndDataTypeResolution("MyInt", "false", 0);
CheckEndToEndDataTypeResolution("MyInt", "true", 1);
CheckEndToEndDataTypeResolution("MyUInt", "8", 8u);
CheckEndToEndDataTypeResolution("MyUInt", "9.0", 9u);
CheckEndToEndDataTypeResolution("MyUInt", "false", 0u);
CheckEndToEndDataTypeResolution("MyUInt", "true", 1u);
CheckEndToEndDataTypeResolution("MyFloat", "2", 2.0f);
CheckEndToEndDataTypeResolution("MyFloat", "-2", -2.0f);
CheckEndToEndDataTypeResolution("MyFloat", "2.1", 2.1f);
CheckEndToEndDataTypeResolution("MyFloat", "false", 0.0f);
CheckEndToEndDataTypeResolution("MyFloat", "true", 1.0f);
CheckEndToEndDataTypeResolution("MyColor", "[0.1,0.2,0.3]", Color{0.1f, 0.2f, 0.3f, 1.0});
CheckEndToEndDataTypeResolution("MyColor", "[0.1, 0.2, 0.3, 0.5]", Color{0.1f, 0.2f, 0.3f, 0.5f});
CheckEndToEndDataTypeResolution("MyColor", "{\"RGB8\": [255, 0, 255, 0]}", Color{1.0f, 0.0f, 1.0f, 0.0f});
CheckEndToEndDataTypeResolution("MyFloat2", "[0.1,0.2]", Vector2{0.1f, 0.2f});
CheckEndToEndDataTypeResolution("MyFloat2", "{\"y\":0.2, \"x\":0.1}", Vector2{0.1f, 0.2f});
CheckEndToEndDataTypeResolution("MyFloat2", "{\"y\":0.2, \"x\":0.1, \"Z\":0.3}", Vector2{0.1f, 0.2f});
CheckEndToEndDataTypeResolution("MyFloat2", "{\"y\":0.2, \"W\":0.4, \"x\":0.1, \"Z\":0.3}", Vector2{0.1f, 0.2f});
CheckEndToEndDataTypeResolution("MyFloat3", "[0.1,0.2,0.3]", Vector3{0.1f, 0.2f, 0.3f});
CheckEndToEndDataTypeResolution("MyFloat3", "{\"y\":0.2, \"x\":0.1}", Vector3{0.1f, 0.2f, 0.0f});
CheckEndToEndDataTypeResolution("MyFloat3", "{\"y\":0.2, \"x\":0.1, \"Z\":0.3}", Vector3{0.1f, 0.2f, 0.3f});
CheckEndToEndDataTypeResolution("MyFloat3", "{\"y\":0.2, \"W\":0.4, \"x\":0.1, \"Z\":0.3}", Vector3{0.1f, 0.2f, 0.3f});
CheckEndToEndDataTypeResolution("MyFloat4", "[0.1,0.2,0.3,0.4]", Vector4{0.1f, 0.2f, 0.3f, 0.4f});
CheckEndToEndDataTypeResolution("MyFloat4", "{\"y\":0.2, \"x\":0.1}", Vector4{0.1f, 0.2f, 0.0f, 0.0f});
CheckEndToEndDataTypeResolution("MyFloat4", "{\"y\":0.2, \"x\":0.1, \"Z\":0.3}", Vector4{0.1f, 0.2f, 0.3f, 0.0f});
CheckEndToEndDataTypeResolution("MyFloat4", "{\"y\":0.2, \"W\":0.4, \"x\":0.1, \"Z\":0.3}", Vector4{0.1f, 0.2f, 0.3f, 0.4f});
}
}

@ -25,7 +25,6 @@ set(FILES
Include/Atom/RPI.Edit/Material/MaterialPropertyValueSourceData.h
Include/Atom/RPI.Edit/Material/MaterialPropertyValueSourceDataSerializer.h
Include/Atom/RPI.Edit/Material/MaterialSourceData.h
Include/Atom/RPI.Edit/Material/MaterialSourceDataSerializer.h
Include/Atom/RPI.Edit/Material/MaterialFunctorSourceData.h
Include/Atom/RPI.Edit/Material/MaterialFunctorSourceDataSerializer.h
Include/Atom/RPI.Edit/Material/MaterialFunctorSourceDataRegistration.h
@ -45,7 +44,6 @@ set(FILES
Source/RPI.Edit/Material/MaterialPropertyValueSourceData.cpp
Source/RPI.Edit/Material/MaterialPropertyValueSourceDataSerializer.cpp
Source/RPI.Edit/Material/MaterialSourceData.cpp
Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp
Source/RPI.Edit/Material/MaterialFunctorSourceData.cpp
Source/RPI.Edit/Material/MaterialFunctorSourceDataSerializer.cpp
Source/RPI.Edit/Material/MaterialFunctorSourceDataRegistration.cpp

Loading…
Cancel
Save