/* * 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 #include #include #include #include #include #include #include #include #include namespace AZ { namespace RPI { namespace // Avoid conflicts in uber builds { namespace Field { static constexpr const char id[] = "id"; // legacy field, replaced by "name" static constexpr const char name[] = "name"; static constexpr const char displayName[] = "displayName"; static constexpr const char description[] = "description"; static constexpr const char type[] = "type"; static constexpr const char visibility[] = "visibility"; static constexpr const char defaultValue[] = "defaultValue"; static constexpr const char min[] = "min"; static constexpr const char max[] = "max"; static constexpr const char softMin[] = "softMin"; static constexpr const char softMax[] = "softMax"; static constexpr const char step[] = "step"; static constexpr const char connection[] = "connection"; static constexpr const char enumValues[] = "enumValues"; static constexpr const char enumIsUv[] = "enumIsUv"; static constexpr const char vectorLabels[] = "vectorLabels"; } static const AZStd::string_view AcceptedFields[] = { Field::id, Field::name, Field::displayName, Field::description, Field::type, Field::visibility, Field::defaultValue, Field::min, Field::max, Field::softMin, Field::softMax, Field::step, Field::connection, Field::enumValues, Field::enumIsUv, Field::vectorLabels }; } AZ_CLASS_ALLOCATOR_IMPL(JsonMaterialPropertySerializer, SystemAllocator, 0); template JsonSerializationResult::ResultCode JsonMaterialPropertySerializer::LoadVariant( MaterialPropertyValue& intoValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context) { T value; JsonSerializationResult::ResultCode result = ContinueLoading(&value, azrtti_typeid(), inputValue, context); if (result.GetOutcome() == JsonSerializationResult::Outcomes::Success) { intoValue = value; } return result; } template JsonSerializationResult::ResultCode JsonMaterialPropertySerializer::LoadVariant( MaterialPropertyValue& intoValue, const T& defaultValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context) { T value = defaultValue; JsonSerializationResult::ResultCode result = ContinueLoading(&value, azrtti_typeid(), inputValue, context); intoValue = value; return result; } template JsonSerializationResult::ResultCode JsonMaterialPropertySerializer::LoadNumericValues( MaterialTypeSourceData::PropertyDefinition* intoProperty, const T& defaultValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context) { namespace JSR = JsonSerializationResult; JSR::ResultCode result(JSR::Tasks::ReadField); if (inputValue.HasMember(Field::defaultValue)) { ScopedContextPath subPath{context, Field::defaultValue}; result.Combine(LoadVariant(intoProperty->m_value, defaultValue, inputValue[Field::defaultValue], context)); } else { intoProperty->m_value = defaultValue; result.Combine(JSR::ResultCode(JSR::Tasks::ReadField, JSR::Outcomes::PartialDefaults)); } // The following do not report PartialDefault because when these are omitted/null the data in the property will also be null if (inputValue.HasMember(Field::min)) { ScopedContextPath subPath{context, Field::min}; result.Combine(LoadVariant(intoProperty->m_min, inputValue[Field::min], context)); } if (inputValue.HasMember(Field::max)) { ScopedContextPath subPath{context, Field::max}; result.Combine(LoadVariant(intoProperty->m_max, inputValue[Field::max], context)); } if (inputValue.HasMember(Field::softMin)) { ScopedContextPath subPath{ context, Field::softMin }; result.Combine(LoadVariant(intoProperty->m_softMin, inputValue[Field::softMin], context)); } if (inputValue.HasMember(Field::softMax)) { ScopedContextPath subPath{ context, Field::softMax }; result.Combine(LoadVariant(intoProperty->m_softMax, inputValue[Field::softMax], context)); } if (inputValue.HasMember(Field::step)) { ScopedContextPath subPath{context, Field::step}; result.Combine(LoadVariant(intoProperty->m_step, inputValue[Field::step], context)); } return result; } template JsonSerializationResult::ResultCode JsonMaterialPropertySerializer::LoadNonNumericValues( MaterialTypeSourceData::PropertyDefinition* intoProperty, const T& defaultValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context) { namespace JSR = JsonSerializationResult; JSR::ResultCode result(JSR::Tasks::ReadField); if (inputValue.HasMember(Field::defaultValue)) { ScopedContextPath subPath{context, Field::defaultValue}; result.Combine(LoadVariant(intoProperty->m_value, defaultValue, inputValue[Field::defaultValue], context)); } else { intoProperty->m_value = defaultValue; result.Combine(JSR::ResultCode(JSR::Tasks::ReadField, JSR::Outcomes::PartialDefaults)); } return result; } JsonSerializationResult::Result JsonMaterialPropertySerializer::Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue, JsonDeserializerContext& context) { namespace JSR = JsonSerializationResult; AZ_Assert(azrtti_typeid() == outputValueTypeId, "Unable to deserialize material property to json because the provided type is %s", outputValueTypeId.ToString().c_str()); AZ_UNUSED(outputValueTypeId); MaterialTypeSourceData::PropertyDefinition* property = reinterpret_cast(outputValue); AZ_Assert(property, "Output value for JsonMaterialPropertySerializer can't be null."); JSR::ResultCode result(JSR::Tasks::ReadField); if (!inputValue.IsObject()) { return context.Report(JsonSerializationResult::Tasks::ReadField, JsonSerializationResult::Outcomes::Unsupported, "Property definition must be a JSON object."); } // First check for unexpected fields for (auto iter = inputValue.MemberBegin(); iter != inputValue.MemberEnd(); ++iter) { bool matched = false; for (int i = 0; i < AZ_ARRAY_SIZE(AcceptedFields); ++i) { if (iter->name.GetString() == AcceptedFields[i]) { matched = true; break; } } if (!matched) { ScopedContextPath subPath{context, iter->name.GetString()}; result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Skipped, "Skipping unrecognized field")); } } // Field::id is the legacy field, replaced by Field::name. If both are present, Field::name will take priority. result.Combine(ContinueLoadingFromJsonObjectField(&property->m_name, azrtti_typeid(), inputValue, Field::id, context)); result.Combine(ContinueLoadingFromJsonObjectField(&property->m_name, azrtti_typeid(), inputValue, Field::name, context)); result.Combine(ContinueLoadingFromJsonObjectField(&property->m_displayName, azrtti_typeid(), inputValue, Field::displayName, context)); result.Combine(ContinueLoadingFromJsonObjectField(&property->m_description, azrtti_typeid(), inputValue, Field::description, context)); result.Combine(ContinueLoadingFromJsonObjectField(&property->m_dataType, azrtti_typeid(), inputValue, Field::type, context)); switch (property->m_dataType) { case MaterialPropertyDataType::Bool: result.Combine(LoadNonNumericValues(property, false, inputValue, context)); break; case MaterialPropertyDataType::Int: result.Combine(LoadNumericValues(property, 0, inputValue, context)); break; case MaterialPropertyDataType::UInt: result.Combine(LoadNumericValues(property, 0u, inputValue, context)); break; case MaterialPropertyDataType::Float: result.Combine(LoadNumericValues(property, 0.0f, inputValue, context)); break; case MaterialPropertyDataType::Vector2: result.Combine(LoadNonNumericValues(property, Vector2{0.0f, 0.0f}, inputValue, context)); result.Combine(LoadVectorLabels(property, inputValue, context)); break; case MaterialPropertyDataType::Vector3: result.Combine(LoadNonNumericValues(property, Vector3{0.0f, 0.0f, 0.0f}, inputValue, context)); result.Combine(LoadVectorLabels(property, inputValue, context)); break; case MaterialPropertyDataType::Vector4: result.Combine(LoadNonNumericValues(property, Vector4{0.0f, 0.0f, 0.0f, 0.0f}, inputValue, context)); result.Combine(LoadVectorLabels(property, inputValue, context)); break; case MaterialPropertyDataType::Color: result.Combine(LoadNonNumericValues(property, AZ::Colors::White, inputValue, context)); break; case MaterialPropertyDataType::Image: case MaterialPropertyDataType::Enum: result.Combine(LoadNonNumericValues(property, "", inputValue, context)); default: result.Combine(JSR::ResultCode(JSR::Tasks::ReadField, JSR::Outcomes::Skipped)); break; } result.Combine(ContinueLoadingFromJsonObjectField(&property->m_visibility, azrtti_typeid(), inputValue, Field::visibility, context)); if (inputValue.HasMember(Field::connection)) { ScopedContextPath subPath{context, Field::connection}; if (inputValue[Field::connection].IsArray()) { result.Combine(ContinueLoading(&property->m_outputConnections, azrtti_typeid(property->m_outputConnections), inputValue[Field::connection], context)); } else { property->m_outputConnections.push_back(); result.Combine(ContinueLoading(&property->m_outputConnections.back(), azrtti_typeid(property->m_outputConnections.back()), inputValue[Field::connection], context)); } } if (inputValue.HasMember(Field::enumValues)) { result.Combine(ContinueLoading(&property->m_enumValues, azrtti_typeid(property->m_enumValues), inputValue[Field::enumValues], context)); } result.Combine(ContinueLoadingFromJsonObjectField(&property->m_enumIsUv, azrtti_typeid(), inputValue, Field::enumIsUv, context)); if (result.GetProcessing() == JsonSerializationResult::Processing::Completed) { return context.Report(result, "Successfully loaded property definition."); } else { return context.Report(result, "Partially loaded property definition."); } } template JsonSerializationResult::ResultCode JsonMaterialPropertySerializer::StoreNumericValues( rapidjson::Value& outputValue, const MaterialTypeSourceData::PropertyDefinition* property, const T& defaultValue, JsonSerializerContext& context) { namespace JSR = JsonSerializationResult; JSR::ResultCode result(JSR::Tasks::WriteValue); if (property->m_value.Is()) { result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::defaultValue, &property->m_value.GetValue(), &defaultValue, azrtti_typeid(), context)); } if (property->m_min.Is()) { result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::min, &property->m_min.GetValue(), nullptr, azrtti_typeid(), context)); } if (property->m_max.Is()) { result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::max, &property->m_max.GetValue(), nullptr, azrtti_typeid(), context)); } if (property->m_softMin.Is()) { result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::softMin, &property->m_softMin.GetValue(), nullptr, azrtti_typeid(), context)); } if (property->m_softMax.Is()) { result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::softMax, &property->m_softMax.GetValue(), nullptr, azrtti_typeid(), context)); } if (property->m_step.Is()) { result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::step, &property->m_step.GetValue(), nullptr, azrtti_typeid(), context)); } return result; } template JsonSerializationResult::ResultCode JsonMaterialPropertySerializer::StoreNonNumericValues( rapidjson::Value& outputValue, const MaterialTypeSourceData::PropertyDefinition* property, const T& defaultValue, JsonSerializerContext& context) { namespace JSR = JsonSerializationResult; JsonSerializationResult::ResultCode result(JSR::Tasks::WriteValue); if (property->m_value.Is()) { result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::defaultValue, &property->m_value.GetValue(), &defaultValue, azrtti_typeid(), context)); } return result; } JsonSerializationResult::Result JsonMaterialPropertySerializer::Store(rapidjson::Value& outputValue, const void* inputValue, [[maybe_unused]] const void* defaultValue, const Uuid& valueTypeId, JsonSerializerContext& context) { namespace JSR = JsonSerializationResult; AZ_Assert(azrtti_typeid() == valueTypeId, "Unable to serialize material property to json because the provided type is %s", valueTypeId.ToString().c_str()); AZ_UNUSED(valueTypeId); const MaterialTypeSourceData::PropertyDefinition* property = reinterpret_cast(inputValue); AZ_Assert(property, "Input value for JsonMaterialPropertySerializer can't be null."); JSR::ResultCode result(JSR::Tasks::WriteValue); outputValue.SetObject(); const AZStd::string emptyString; result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::name, &property->m_name, &emptyString, azrtti_typeid(), context)); result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::displayName, &property->m_displayName, &emptyString, azrtti_typeid(), context)); result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::description, &property->m_description, &emptyString, azrtti_typeid(), context)); MaterialPropertyDataType defaultDataType = MaterialPropertyDataType::Invalid; result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::type, &property->m_dataType, &defaultDataType, azrtti_typeid(property->m_dataType), context)); result.Combine(StoreVectorLabels(outputValue, property, context)); switch (property->m_dataType) { case MaterialPropertyDataType::Bool: result.Combine(StoreNonNumericValues(outputValue, property, false, context)); break; case MaterialPropertyDataType::Int: result.Combine(StoreNumericValues(outputValue, property, 0, context)); break; case MaterialPropertyDataType::UInt: result.Combine(StoreNumericValues(outputValue, property, 0u, context)); break; case MaterialPropertyDataType::Float: result.Combine(StoreNumericValues(outputValue, property, 0.0f, context)); break; case MaterialPropertyDataType::Vector2: result.Combine(StoreNonNumericValues(outputValue, property, Vector2{0.0f, 0.0f}, context)); break; case MaterialPropertyDataType::Vector3: result.Combine(StoreNonNumericValues(outputValue, property, Vector3{0.0f, 0.0f, 0.0f}, context)); break; case MaterialPropertyDataType::Vector4: result.Combine(StoreNonNumericValues(outputValue, property, Vector4{0.0f, 0.0f, 0.0f, 0.0f}, context)); break; case MaterialPropertyDataType::Color: result.Combine(StoreNonNumericValues(outputValue, property, AZ::Colors::White, context)); break; case MaterialPropertyDataType::Image: case MaterialPropertyDataType::Enum: result.Combine(StoreNonNumericValues(outputValue, property, AZStd::string{""}, context)); break; } const MaterialPropertyVisibility defaultVisibility = MaterialPropertyVisibility::Default; result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::visibility, &property->m_visibility, &defaultVisibility, azrtti_typeid(property->m_visibility), context)); // Support loading a "connection" property as a single entry in m_outputConnections MaterialTypeSourceData::PropertyConnection defaultConnection; if (property->m_outputConnections.size() == 1) { result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::connection, &property->m_outputConnections.back(), &defaultConnection, azrtti_typeid(property->m_outputConnections.back()), context)); } else { result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::connection, &property->m_outputConnections, &defaultConnection, azrtti_typeid(property->m_outputConnections), context)); } // Enum list if (property->m_enumValues.size() > 0) { result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::enumValues, &property->m_enumValues, nullptr, azrtti_typeid(property->m_enumValues), context)); } const bool defaultEnumIsUv = false; result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::enumIsUv, &property->m_enumIsUv, &defaultEnumIsUv, azrtti_typeid(property->m_enumIsUv), context)); if (result.GetProcessing() == JsonSerializationResult::Processing::Completed) { return context.Report(result, "Successfully stored property definition."); } else { return context.Report(result, "Partially stored property definition."); } } JsonSerializationResult::ResultCode JsonMaterialPropertySerializer::LoadVectorLabels(MaterialTypeSourceData::PropertyDefinition* intoProperty, const rapidjson::Value& inputValue, JsonDeserializerContext& context) { namespace JSR = JsonSerializationResult; JSR::ResultCode result(JSR::Tasks::ReadField); if (inputValue.HasMember(Field::vectorLabels)) { result.Combine(ContinueLoading(&intoProperty->m_vectorLabels, azrtti_typeid(intoProperty->m_vectorLabels), inputValue[Field::vectorLabels], context)); } return result; } JsonSerializationResult::ResultCode JsonMaterialPropertySerializer::StoreVectorLabels(rapidjson::Value& outputValue, const MaterialTypeSourceData::PropertyDefinition* property, JsonSerializerContext& context) { AZStd::string emptyString; namespace JSR = JsonSerializationResult; JsonSerializationResult::ResultCode result(JSR::Tasks::WriteValue); if (!property->m_vectorLabels.empty()) { result.Combine(ContinueStoringToJsonObjectField(outputValue, Field::vectorLabels, &property->m_vectorLabels, nullptr, azrtti_typeid(property->m_vectorLabels), context)); } return result; } } // namespace RPI } // namespace AZ