Changed the .material file format to use a flat list for material property values, instead of a tree structure.

This is needed to support deeply nested material property groups, it just makes the serialization code a lot simpler than trying to support nested groups in the .material file. It also makes the file more readable and easier to search all files for particular properties.
I also updated MaterialSourceData to hide the property values behind a clean API.

Signed-off-by: santorac <55155825+santorac@users.noreply.github.com>
This commit is contained in:
santorac
2022-02-04 12:04:50 -08:00
parent 4bb93e4c94
commit 1daa9fbbed
11 changed files with 535 additions and 168 deletions
@@ -52,7 +52,7 @@ namespace AZ
{
AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor;
materialBuilderDescriptor.m_name = JobKey;
materialBuilderDescriptor.m_version = 117; // new material type file format
materialBuilderDescriptor.m_version = 119; // new material file format
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>();
@@ -129,38 +129,6 @@ namespace AZ
}
}
template<typename MaterialSourceDataT>
AZ::Outcome<MaterialSourceDataT> LoadSourceData(const rapidjson::Value& value, const AZStd::string& filePath)
{
MaterialSourceDataT material;
JsonDeserializerSettings settings;
JsonReportingHelper reportingHelper;
reportingHelper.Attach(settings);
// This is required by some custom material serializers to support relative path references.
JsonFileLoadContext fileLoadContext;
fileLoadContext.PushFilePath(filePath);
settings.m_metadata.Add(fileLoadContext);
JsonSerialization::Load(material, value, settings);
if (reportingHelper.ErrorsReported())
{
return AZ::Failure();
}
else if (reportingHelper.WarningsReported())
{
AZ_Error(MaterialBuilderName, false, "Warnings reported while loading '%s'", filePath.c_str());
return AZ::Failure();
}
else
{
return AZ::Success(AZStd::move(material));
}
}
void MaterialBuilder::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
{
if (m_isShuttingDown)
@@ -295,7 +263,7 @@ namespace AZ
AZ::Data::Asset<MaterialAsset> MaterialBuilder::CreateMaterialAsset(AZStd::string_view materialSourceFilePath, const rapidjson::Value& json) const
{
auto material = LoadSourceData<MaterialSourceData>(json, materialSourceFilePath);
auto material = MaterialUtils::LoadMaterialSourceData(materialSourceFilePath, &json, true);
if (!material.IsSuccess())
{
@@ -54,10 +54,11 @@ namespace AZ
->Field("materialType", &MaterialSourceData::m_materialType)
->Field("materialTypeVersion", &MaterialSourceData::m_materialTypeVersion)
->Field("parentMaterial", &MaterialSourceData::m_parentMaterial)
->Field("properties", &MaterialSourceData::m_properties)
->Field("properties", &MaterialSourceData::m_propertiesOld)
->Field("propertyValues", &MaterialSourceData::m_propertyValues)
;
serializeContext->RegisterGenericType<PropertyMap>();
serializeContext->RegisterGenericType<PropertyValueMap>();
serializeContext->RegisterGenericType<PropertyGroupMap>();
}
}
@@ -73,6 +74,55 @@ namespace AZ
}
}
void MaterialSourceData::SetPropertyValue(const Name& propertyId, const MaterialPropertyValue& value)
{
if (!propertyId.IsEmpty())
{
m_propertyValues[propertyId] = value;
}
}
const MaterialPropertyValue& MaterialSourceData::GetPropertyValue(const Name& propertyId) const
{
auto iter = m_propertyValues.find(propertyId);
if (iter == m_propertyValues.end())
{
return m_invalidValue;
}
else
{
return iter->second;
}
}
void MaterialSourceData::RemovePropertyValue(const Name& propertyId)
{
m_propertyValues.erase(propertyId);
}
MaterialSourceData::PropertyValueMap MaterialSourceData::GetPropertyValues() const
{
return m_propertyValues;
}
bool MaterialSourceData::HasPropertyValue(const Name& propertyId) const
{
return m_propertyValues.find(propertyId) != m_propertyValues.end();
}
void MaterialSourceData::ConvertToNewDataFormat()
{
for (auto& [groupName, propertyList] : m_propertiesOld)
{
for (auto& [propertyName, propertyValue] : propertyList)
{
SetPropertyValue(MaterialPropertyId{groupName, propertyName}, propertyValue);
}
}
m_propertiesOld.clear();
}
Outcome<Data::Asset<MaterialAsset>> MaterialSourceData::CreateMaterialAsset(
Data::AssetId assetId, AZStd::string_view materialSourceFilePath, MaterialAssetProcessingMode processingMode, bool elevateWarnings) const
{
@@ -250,12 +300,14 @@ namespace AZ
return Failure();
}
MaterialSourceData parentSourceData;
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(parentSourceAbsPath, parentSourceData))
auto loadParentResult = MaterialUtils::LoadMaterialSourceData(parentSourceAbsPath);
if (!loadParentResult)
{
AZ_Error("MaterialSourceData", false, "Failed to load MaterialSourceData for parent material: '%s'.", parentSourceAbsPath.c_str());
return Failure();
}
MaterialSourceData parentSourceData = loadParentResult.TakeValue();
// Make sure that all materials in the hierarchy share the same material type
const auto parentTypeAssetId = AssetUtils::MakeAssetId(parentSourceAbsPath, parentSourceData.m_materialType, 0);
@@ -314,30 +366,29 @@ namespace AZ
void MaterialSourceData::ApplyPropertiesToAssetCreator(
AZ::RPI::MaterialAssetCreator& materialAssetCreator, const AZStd::string_view& materialSourceFilePath) const
{
for (auto& group : m_properties)
for (auto& [propertyId, propertyValue] : m_propertyValues)
{
for (auto& property : group.second)
if (!propertyValue.IsValid())
{
materialAssetCreator.ReportWarning("Source data for material property value is invalid.");
}
else
{
MaterialPropertyId propertyId{ group.first, property.first };
if (!property.second.IsValid())
{
materialAssetCreator.ReportWarning("Source data for material property value is invalid.");
}
// 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.Is<AZStd::string>() && AzFramework::StringFunc::Contains(property.second.GetValue<AZStd::string>(), "."))
if (propertyValue.Is<AZStd::string>() && AzFramework::StringFunc::Contains(propertyValue.GetValue<AZStd::string>(), "."))
{
Data::Asset<ImageAsset> imageAsset;
MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference(
imageAsset, materialSourceFilePath, property.second.GetValue<AZStd::string>());
imageAsset, materialSourceFilePath, propertyValue.GetValue<AZStd::string>());
if (result == MaterialUtils::GetImageAssetResult::Missing)
{
materialAssetCreator.ReportWarning(
"Material property '%s': Could not find the image '%s'", propertyId.GetCStr(),
property.second.GetValue<AZStd::string>().data());
propertyValue.GetValue<AZStd::string>().data());
}
imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad);
@@ -345,7 +396,7 @@ namespace AZ
}
else
{
materialAssetCreator.SetPropertyValue(propertyId, property.second);
materialAssetCreator.SetPropertyValue(propertyId, propertyValue);
}
}
}
@@ -100,9 +100,9 @@ namespace AZ
serializeContext->Class<PropertyLayout>()
->Version(3) // Added propertyGroups
->Field("version", &PropertyLayout::m_versionOld) //< Deprecated, preserved for backward compatibility, replaced by MaterialTypeSourceData::version
->Field("groups", &PropertyLayout::m_groupsOld) //< Deprecated, preserved for backward compatibility, replaced by propertyGroups
->Field("properties", &PropertyLayout::m_propertiesOld) //< Deprecated, preserved for backward compatibility, replaced by propertyGroups
->Field("version", &PropertyLayout::m_versionOld) //< @deprecated: preserved for backward compatibility, replaced by MaterialTypeSourceData::version
->Field("groups", &PropertyLayout::m_groupsOld) //< @deprecated: preserved for backward compatibility, replaced by propertyGroups
->Field("properties", &PropertyLayout::m_propertiesOld) //< @deprecated: preserved for backward compatibility, replaced by propertyGroups
->Field("propertyGroups", &PropertyLayout::m_propertyGroups)
;
@@ -12,6 +12,7 @@
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
#include <Atom/RPI.Edit/Common/JsonFileLoadContext.h>
@@ -82,7 +83,7 @@ namespace AZ
loadOutcome = AZ::JsonSerializationUtils::ReadJsonFile(filePath, AZ::RPI::JsonUtils::DefaultMaxFileSize);
if (!loadOutcome.IsSuccess())
{
AZ_Error("AZ::RPI::JsonUtils", false, "%s", loadOutcome.GetError().c_str());
AZ_Error("MaterialUtils", false, "%s", loadOutcome.GetError().c_str());
return AZ::Failure();
}
@@ -114,6 +115,46 @@ namespace AZ
return AZ::Success(AZStd::move(materialType));
}
}
AZ::Outcome<MaterialSourceData> LoadMaterialSourceData(const AZStd::string& filePath, const rapidjson::Value* document, bool warningsAsErrors)
{
AZ::Outcome<rapidjson::Document, AZStd::string> loadOutcome;
if (document == nullptr)
{
loadOutcome = AZ::JsonSerializationUtils::ReadJsonFile(filePath, AZ::RPI::JsonUtils::DefaultMaxFileSize);
if (!loadOutcome.IsSuccess())
{
AZ_Error("MaterialUtils", false, "%s", loadOutcome.GetError().c_str());
return AZ::Failure();
}
document = &loadOutcome.GetValue();
}
MaterialSourceData material;
JsonDeserializerSettings settings;
JsonReportingHelper reportingHelper;
reportingHelper.Attach(settings);
JsonSerialization::Load(material, *document, settings);
material.ConvertToNewDataFormat();
if (reportingHelper.ErrorsReported())
{
return AZ::Failure();
}
else if (warningsAsErrors && reportingHelper.WarningsReported())
{
AZ_Error("MaterialUtils", false, "Warnings reported while loading '%s'", filePath.c_str());
return AZ::Failure();
}
else
{
return AZ::Success(AZStd::move(material));
}
}
void CheckForUnrecognizedJsonFields(const AZStd::string_view* acceptedFieldNames, uint32_t acceptedFieldNameCount, const rapidjson::Value& object, JsonDeserializerContext& context, JsonSerializationResult::ResultCode &result)
{