/* * 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 namespace ImageProcessingAtom { const char* TextureSettings::ExtensionName = ".assetinfo"; TextureSettings::TextureSettings() : m_presetId(0) , m_sizeReduceLevel(0) , m_suppressEngineReduce(false) , m_enableMipmap(true) , m_maintainAlphaCoverage(false) , m_mipGenEval(MipGenEvalType::sum) , m_mipGenType(MipGenType::blackmanHarris) { const int defaultMipMapValue = 50; for (int i = 0; i < s_MaxMipMaps; ++i) { m_mipAlphaAdjust.push_back(defaultMipMapValue); } } void TextureSettings::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serialize = azrtti_cast(context); if (serialize) { serialize->Class() ->Version(2) ->Field("PresetID", &TextureSettings::m_presetId) ->Field("Preset", &TextureSettings::m_preset) ->Field("SizeReduceLevel", &TextureSettings::m_sizeReduceLevel) ->Field("EngineReduce", &TextureSettings::m_suppressEngineReduce) ->Field("EnableMipmap", &TextureSettings::m_enableMipmap) ->Field("MaintainAlphaCoverage", &TextureSettings::m_maintainAlphaCoverage) ->Field("MipMapAlphaAdjustments", &TextureSettings::m_mipAlphaAdjust) ->Field("MipMapGenEval", &TextureSettings::m_mipGenEval) ->Field("MipMapGenType", &TextureSettings::m_mipGenType) ->Field("PlatformSpecificOverrides", &TextureSettings::m_platfromOverrides) ->Field("OverridingPlatform", &TextureSettings::m_overridingPlatform); AZ::EditContext* edit = serialize->GetEditContext(); if (edit) { edit->Class("Texture Setting", "") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->DataElement(AZ::Edit::UIHandlers::Default, &TextureSettings::m_mipAlphaAdjust, "Alpha Test Bias", "Multiplies the mipmap's alpha with a scale value that is based on alpha coverage. \ Set the mip 0 to mip 5 values to offset the alpha test values and ensure the mipmap's alpha coverage matches the original image. Specify a value from 0 to 100.") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) ->ElementAttribute(AZ::Edit::UIHandlers::Handler, AZ::Edit::UIHandlers::Slider) ->ElementAttribute(AZ::Edit::Attributes::Min, 0) ->ElementAttribute(AZ::Edit::Attributes::Max, 100) ->ElementAttribute(AZ::Edit::Attributes::Step, 1) ->DataElement(AZ::Edit::UIHandlers::ComboBox, &TextureSettings::m_mipGenType, "Filter Method", "") ->EnumAttribute(MipGenType::point, "Point") ->EnumAttribute(MipGenType::box, "Average") ->EnumAttribute(MipGenType::triangle, "Linear") ->EnumAttribute(MipGenType::quadratic, "Bilinear") ->EnumAttribute(MipGenType::gaussian, "Gaussian") ->EnumAttribute(MipGenType::blackmanHarris, "BlackmanHarris") ->EnumAttribute(MipGenType::kaiserSinc, "KaiserSinc") ->DataElement(AZ::Edit::UIHandlers::ComboBox, &TextureSettings::m_mipGenEval, "Pixel Sampling Type", "") ->EnumAttribute(MipGenEvalType::max, "Max") ->EnumAttribute(MipGenEvalType::min, "Min") ->EnumAttribute(MipGenEvalType::sum, "Sum") ->DataElement(AZ::Edit::UIHandlers::CheckBox, &TextureSettings::m_maintainAlphaCoverage, "Maintain Alpha Coverage", "Select this option to manually adjust Alpha channel mipmaps.") ; } } } bool TextureSettings::operator!=(const TextureSettings& other) const { return !(*this == other); } bool TextureSettings::operator==(const TextureSettings& other) const { ///////////////////////////// // Compare Alpha Adjust ///////////////////////////// bool matchingAlphaTestAdjust = true; for (AZ::u8 curIndex = 0; curIndex < s_MaxMipMaps; ++curIndex) { if (m_mipAlphaAdjust[curIndex] != other.m_mipAlphaAdjust[curIndex]) { matchingAlphaTestAdjust = false; break; } } return matchingAlphaTestAdjust && m_preset == other.m_preset && m_sizeReduceLevel == other.m_sizeReduceLevel && m_suppressEngineReduce == other.m_suppressEngineReduce && m_maintainAlphaCoverage == other.m_maintainAlphaCoverage && m_mipGenEval == other.m_mipGenEval && m_mipGenType == other.m_mipGenType; } bool TextureSettings::Equals(const TextureSettings& other, AZ::SerializeContext* serializeContext) { ///////////////////////////// // Compare Common Settings ///////////////////////////// if (*this != other) { return false; } ///////////////////////////// // Compare Overrides ///////////////////////////// const MultiplatformTextureSettings selfOverrides = GetMultiplatformTextureSetting(*this, serializeContext); const MultiplatformTextureSettings otherOverrides = GetMultiplatformTextureSetting(other, serializeContext); auto selfOverridesIter = selfOverrides.begin(); auto otherOverridesIter = otherOverrides.begin(); while (selfOverridesIter != selfOverrides.end() && otherOverridesIter != otherOverrides.end()) { if (selfOverridesIter->second != otherOverridesIter->second) { return false; } otherOverridesIter++; selfOverridesIter++; } AZ_Assert(selfOverridesIter == selfOverrides.end() && otherOverridesIter == otherOverrides.end(), "Both iterators must be at the end by now.") return true; } float TextureSettings::ComputeMIPAlphaOffset(AZ::u32 mip) const { if (mip / 2 + 1 >= s_MaxMipMaps) { return 0; } float fVal = static_cast(m_mipAlphaAdjust[s_MaxMipMaps - 1]); if (mip / 2 + 1 < s_MaxMipMaps) { float fInterpolationSlider1 = static_cast(m_mipAlphaAdjust[mip / 2]); float fInterpolationSlider2 = static_cast(m_mipAlphaAdjust[mip / 2 + 1]); fVal = fInterpolationSlider1 + (fInterpolationSlider2 - fInterpolationSlider1) * (mip & 1) * 0.5f; } return 0.5f - fVal / 100.0f; } void TextureSettings::ApplyPreset(PresetName presetName) { const PresetSettings* presetSetting = BuilderSettingManager::Instance()->GetPreset(presetName); if (presetSetting != nullptr) { m_sizeReduceLevel = presetSetting->m_sizeReduceLevel; m_suppressEngineReduce = presetSetting->m_suppressEngineReduce; if (presetSetting->m_mipmapSetting) { m_mipGenType = presetSetting->m_mipmapSetting->m_type; } m_preset = presetName; } else { AZ_Error("Image Processing", false, "Cannot set an invalid preset %s!", presetName.GetCStr()); } } StringOutcome TextureSettings::LoadTextureSetting(const AZStd::string& filepath, TextureSettings& textureSettingPtrOut, AZ::SerializeContext* serializeContext /*= nullptr*/) { auto loadedTextureSettingPtr = AZStd::unique_ptr(AZ::Utils::LoadObjectFromFile(filepath, serializeContext)); if (!loadedTextureSettingPtr) { return AZ::Failure(AZStd::string()); } textureSettingPtrOut = *loadedTextureSettingPtr; // In old format, the preset name doesn't exist. Using preset id to get preset name // We can remove this when we fully deprecate the preset uuid if (textureSettingPtrOut.m_preset.IsEmpty()) { textureSettingPtrOut.m_preset = BuilderSettingManager::Instance()->GetPresetNameFromId(textureSettingPtrOut.m_presetId); } return AZ::Success(AZStd::string()); } StringOutcome TextureSettings::WriteTextureSetting(const AZStd::string& filepath, TextureSettings& textureSetting, AZ::SerializeContext* serializeContext) { if (false == AZ::Utils::SaveObjectToFile(filepath, AZ::DataStream::StreamType::ST_XML, &textureSetting, serializeContext)) { return STRING_OUTCOME_ERROR("Failed to write to file: " + filepath); } return STRING_OUTCOME_SUCCESS; } MultiplatformTextureSettings TextureSettings::GenerateDefaultMultiplatformTextureSettings(const AZStd::string& imageFilepath) { MultiplatformTextureSettings settings; PlatformNameList platformsList = BuilderSettingManager::Instance()->GetPlatformList(); PresetName suggestedPreset = BuilderSettingManager::Instance()->GetSuggestedPreset(imageFilepath); for (PlatformName& platform : platformsList) { TextureSettings textureSettings; textureSettings.ApplyPreset(suggestedPreset); settings.insert(AZStd::pair(platform, textureSettings)); } return settings; } StringOutcome TextureSettings::GetPlatformSpecificTextureSetting(const PlatformName& platformName, const TextureSettings& baseTextureSettings, TextureSettings& textureSettingsOut, AZ::SerializeContext* serializeContext) { // Obtain the DataPatch (if platform exists) auto overrideIter = baseTextureSettings.m_platfromOverrides.find(platformName); if (overrideIter == baseTextureSettings.m_platfromOverrides.end()) { return STRING_OUTCOME_ERROR(AZStd::string::format("TextureSettings preset [%s] does not have override for platform [%s]", baseTextureSettings.m_preset.GetCStr(), platformName.c_str())); } AZ::DataPatch& platformOverride = const_cast(overrideIter->second); // Update settings instance with override values. if (platformOverride.IsData()) { // Apply the AZ::DataPatch to obtain a platform-overridden version of the TextureSettings. AZStd::unique_ptr platformSpecificTextureSettings(platformOverride.Apply(&baseTextureSettings, serializeContext)); AZ_Assert(platformSpecificTextureSettings->m_mipAlphaAdjust.size() == s_MaxMipMaps, "Unexpected m_mipAlphaAdjust size."); // Adjust overrides data to imply 'platformSpecificTextureSettings' *IS* the override. platformSpecificTextureSettings->m_platfromOverrides.clear(); platformSpecificTextureSettings->m_overridingPlatform = platformName; textureSettingsOut = *platformSpecificTextureSettings; } else { textureSettingsOut = baseTextureSettings; } return STRING_OUTCOME_SUCCESS; } const MultiplatformTextureSettings TextureSettings::GetMultiplatformTextureSetting(const TextureSettings& textureSettings, AZ::SerializeContext* serializeContext) { MultiplatformTextureSettings loadedSettingsReturn; PlatformNameList platformsList = BuilderSettingManager::Instance()->GetPlatformList(); // Generate MultiplatformTextureSettings based on existing available overrides. for (const PlatformName& curPlatformName : platformsList) { // Start with a copy of the base settings TextureSettings curPlatformOverride = textureSettings; if (!GetPlatformSpecificTextureSetting(curPlatformName, textureSettings, curPlatformOverride, serializeContext).IsSuccess()) { // We have failed to obtain an override. Maintain base settings to indicate zero overrides. // We still want to designate these TextureSettings as an (empty) override. curPlatformOverride.m_platfromOverrides.clear(); curPlatformOverride.m_overridingPlatform = curPlatformName; } // Add as an entry to the multiplatform texture settings loadedSettingsReturn.insert(AZStd::pair(curPlatformName, curPlatformOverride)); } // return a copy of the results return loadedSettingsReturn; } const MultiplatformTextureSettings TextureSettings::GetMultiplatformTextureSetting(const AZStd::string& imageFilepath, bool& canOverridePreset, AZ::SerializeContext* serializeContext) { TextureSettings loadedTextureSetting; // Attempt to get metadata file path from image path. AZStd::string metadataFilepath = imageFilepath + TextureSettings::ExtensionName; bool hasMetafile = AZ::IO::SystemFile::Exists(metadataFilepath.c_str()); canOverridePreset = true; // If the image has an accompanying metadata... if(hasMetafile) { // Parse the metadata file. if (LoadTextureSetting(metadataFilepath, loadedTextureSetting, serializeContext).IsSuccess()) { canOverridePreset = false; return GetMultiplatformTextureSetting(loadedTextureSetting, serializeContext); } else { AZ_Error("Image processing", false, "Failed to load the image's meta file %s", metadataFilepath.c_str()); } } return GenerateDefaultMultiplatformTextureSettings(imageFilepath); } StringOutcome TextureSettings::ApplySettings(const TextureSettings& settings, const PlatformName& overridePlatform, AZ::SerializeContext* serializeContext) { if (overridePlatform.empty()) { *this = settings; } else { AZ::DataPatch newOverride; if (false == newOverride.Create(this, &settings, AZ::DataPatch::FlagsMap(), AZ::DataPatch::FlagsMap(), serializeContext)) { return STRING_OUTCOME_ERROR("Failed to create TextureSettings platform override data. See AZ_Error log for details."); } m_platfromOverrides[overridePlatform] = newOverride; } return STRING_OUTCOME_SUCCESS; } }