123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- /*
- * 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 "BuilderSettingManager.h"
- #include <QDirIterator>
- #include <QFile>
- #include <QFileInfo>
- #include <QString>
- #include <Atom/ImageProcessing/PixelFormats.h>
- #include <BuilderSettings/CubemapSettings.h>
- #include <BuilderSettings/TextureSettings.h>
- #include <Converters/Cubemap.h>
- #include <Processing/PixelFormatInfo.h>
- #include <Processing/ImageToProcess.h>
- #include <ImageLoader/ImageLoaders.h>
- #include <ImageProcessing_Traits_Platform.h>
- #include <AssetBuilderSDK/AssetBuilderSDK.h>
- #include <AzCore/IO/Path/Path.h>
- #include <AzCore/Math/Sha1.h>
- #include <AzCore/Serialization/Json/JsonSerialization.h>
- #include <AzCore/Serialization/SerializeContext.h>
- #include <AzCore/Serialization/ObjectStream.h>
- #include <AzCore/Serialization/Utils.h>
- #include <AzCore/std/smart_ptr/make_shared.h>
- #include <AzCore/std/string/string.h>
- #include <AzFramework/IO/LocalFileIO.h>
- #include <AzFramework/StringFunc/StringFunc.h>
- #include <AzFramework/Application/Application.h>
- #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
- #include <AzCore/Serialization/Json/JsonUtils.h>
- namespace ImageProcessingAtom
- {
- const char* BuilderSettingManager::s_defaultConfigRelativeFolder = "Gems/Atom/Asset/ImageProcessingAtom/Config/";
- const char* BuilderSettingManager::s_projectConfigRelativeFolder = "Config/AtomImageBuilder/";
- const char* BuilderSettingManager::s_builderSettingFileName = "ImageBuilder.settings";
- const char* BuilderSettingManager::s_presetFileExtension = ".preset";
- const char FileMaskDelimiter = '_';
- #if defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS)
- #define AZ_RESTRICTED_PLATFORM_EXPANSION(CodeName, CODENAME, codename, PrivateName, PRIVATENAME, privatename, PublicName, PUBLICNAME, publicname, PublicAuxName1, PublicAuxName2, PublicAuxName3) \
- namespace ImageProcess##PrivateName \
- { \
- bool DoesSupport(AZStd::string); \
- }
- AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS
- #undef AZ_RESTRICTED_PLATFORM_EXPANSION
- #endif //AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS
- const char* BuilderSettingManager::s_environmentVariableName = "ImageBuilderSettingManager_Atom";
- AZ::EnvironmentVariable<BuilderSettingManager*> BuilderSettingManager::s_globalInstance = nullptr;
- AZStd::mutex BuilderSettingManager::s_instanceMutex;
- const PlatformName BuilderSettingManager::s_defaultPlatform = AZ_TRAIT_IMAGEPROCESSING_DEFAULT_PLATFORM;
-
- void BuilderSettingManager::Reflect(AZ::ReflectContext* context)
- {
- AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
- if (serialize)
- {
- serialize->Class<BuilderSettingManager>()
- ->Version(1)
- ->Field("AnalysisFingerprint", &BuilderSettingManager::m_analysisFingerprint)
- ->Field("BuildSettings", &BuilderSettingManager::m_builderSettings)
- ->Field("DefaultPresetsByFileMask", &BuilderSettingManager::m_defaultPresetByFileMask)
- ->Field("DefaultPreset", &BuilderSettingManager::m_defaultPreset)
- ->Field("DefaultPresetAlpha", &BuilderSettingManager::m_defaultPresetAlpha)
- ->Field("DefaultPresetNonePOT", &BuilderSettingManager::m_defaultPresetNonePOT);
- }
- }
- BuilderSettingManager* BuilderSettingManager::Instance()
- {
- AZStd::lock_guard<AZStd::mutex> lock(s_instanceMutex);
- if (!s_globalInstance)
- {
- s_globalInstance = AZ::Environment::FindVariable<BuilderSettingManager*>(s_environmentVariableName);
- }
- AZ_Assert(s_globalInstance, "BuilderSettingManager not created!");
- return s_globalInstance.Get();
- }
- void BuilderSettingManager::CreateInstance()
- {
- AZStd::lock_guard<AZStd::mutex> lock(s_instanceMutex);
- if (s_globalInstance)
- {
- AZ_Assert(false, "BuilderSettingManager already created!");
- return;
- }
- if (!s_globalInstance)
- {
- s_globalInstance = AZ::Environment::CreateVariable<BuilderSettingManager*>(s_environmentVariableName);
- }
- if (!s_globalInstance.Get())
- {
- s_globalInstance.Set(aznew BuilderSettingManager());
- }
- }
-
- void BuilderSettingManager::DestroyInstance()
- {
- AZStd::lock_guard<AZStd::mutex> lock(s_instanceMutex);
- AZ_Assert(s_globalInstance, "Invalid call to DestroyInstance - no instance exists.");
- AZ_Assert(s_globalInstance.Get(), "You can only call DestroyInstance if you have called CreateInstance.");
- delete s_globalInstance.Get();
- s_globalInstance.Reset();
- }
-
- const PresetSettings* BuilderSettingManager::GetPreset(const PresetName& presetName, const PlatformName& platform, AZStd::string_view* settingsFilePathOut)
- {
- AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
- auto itr = m_presets.find(presetName);
- if (itr != m_presets.end())
- {
- if (settingsFilePathOut)
- {
- *settingsFilePathOut = itr->second.m_presetFilePath;
- }
- return itr->second.m_multiPreset.GetPreset(platform);
- }
- return nullptr;
- }
- const BuilderSettings* BuilderSettingManager::GetBuilderSetting(const PlatformName& platform)
- {
- if (m_builderSettings.find(platform) != m_builderSettings.end())
- {
- return &m_builderSettings[platform];
- }
- return nullptr;
- }
- const PlatformNameList BuilderSettingManager::GetPlatformList()
- {
- PlatformNameList platforms;
- for (auto& builderSetting : m_builderSettings)
- {
- if (builderSetting.second.m_enablePlatform)
- {
- platforms.push_back(builderSetting.first);
- }
- }
- return platforms;
- }
- const AZStd::map <FileMask, AZStd::unordered_set<PresetName>>& BuilderSettingManager::GetPresetFilterMap()
- {
- AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
- return m_presetFilterMap;
- }
- const PresetName BuilderSettingManager::GetPresetNameFromId(const AZ::Uuid& presetId)
- {
- AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
- for (const auto& itr : m_presets)
- {
- if (itr.second.m_multiPreset.GetPresetId() == presetId)
- {
- return itr.second.m_multiPreset.GetPresetName();
- }
- }
- return {};
- }
- void BuilderSettingManager::ClearSettings()
- {
- AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
- m_presetFilterMap.clear();
- m_builderSettings.clear();
- m_presets.clear();
- m_defaultPresetByFileMask.clear();
- }
- StringOutcome BuilderSettingManager::LoadConfig()
- {
- StringOutcome outcome = STRING_OUTCOME_ERROR("");
- auto fileIoBase = AZ::IO::FileIOBase::GetInstance();
- if (fileIoBase == nullptr)
- {
- return AZ::Failure(AZStd::string("File IO instance needs to be initialized to resolve ImageProcessing builder file aliases"));
- }
- // Construct the default setting path
- AZ::IO::FixedMaxPath defaultConfigFolder;
- if (auto engineRoot = fileIoBase->ResolvePath("@engroot@"); engineRoot.has_value())
- {
- defaultConfigFolder = *engineRoot;
- defaultConfigFolder /= s_defaultConfigRelativeFolder;
- }
- AZ::IO::FixedMaxPath projectConfigFolder;
- if (auto sourceGameRoot = fileIoBase->ResolvePath("@projectroot@"); sourceGameRoot.has_value())
- {
- projectConfigFolder = *sourceGameRoot;
- projectConfigFolder /= s_projectConfigRelativeFolder;
- }
- AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
- ClearSettings();
-
- outcome = LoadSettings((projectConfigFolder / s_builderSettingFileName).Native());
- if (!outcome.IsSuccess())
- {
- outcome = LoadSettings((defaultConfigFolder / s_builderSettingFileName).Native());
- }
-
- if (outcome.IsSuccess())
- {
- // Load presets in default folder first, then load from project folder.
- // The same presets which loaded last will overwrite previous loaded one.
- LoadPresets(defaultConfigFolder.Native());
- LoadPresets(projectConfigFolder.Native());
- // Regenerate file mask mapping after all presets loaded
- RegenerateMappings();
- }
- return outcome;
- }
- void BuilderSettingManager::LoadPresets(AZStd::string_view presetFolder)
- {
- AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
- QDirIterator it(presetFolder.data(), QStringList() << "*.preset", QDir::Files, QDirIterator::NoIteratorFlags);
- while (it.hasNext())
- {
- QString filePath = it.next();
- QFileInfo fileInfo = it.fileInfo();
- MultiplatformPresetSettings preset;
- auto result = AZ::JsonSerializationUtils::LoadObjectFromFile(preset, filePath.toUtf8().data());
- if (!result.IsSuccess())
- {
- AZ_Warning("Image Processing", false, "Failed to load preset file %s. Error: %s",
- filePath.toUtf8().data(), result.GetError().c_str());
- }
- PresetName presetName(fileInfo.baseName().toUtf8().data());
- AZ_Warning("Image Processing", presetName == preset.GetPresetName(), "Preset file name '%s' is not"
- " same as preset name '%s'. Using preset file name as preset name",
- filePath.toUtf8().data(), preset.GetPresetName().GetCStr());
- preset.SetPresetName(presetName);
- m_presets[presetName] = PresetEntry{preset, filePath.toUtf8().data()};
- }
- }
- StringOutcome BuilderSettingManager::LoadConfigFromFolder(AZStd::string_view configFolder)
- {
- // Load builder settings
- AZStd::string settingFilePath = AZStd::string::format("%.*s%s", aznumeric_cast<int>(configFolder.size()),
- configFolder.data(), s_builderSettingFileName);
- auto result = LoadSettings(settingFilePath);
- // Load presets
- if (result.IsSuccess())
- {
- LoadPresets(configFolder);
- RegenerateMappings();
- }
- return result;
- }
- StringOutcome BuilderSettingManager::LoadSettings(AZStd::string_view filepath)
- {
- AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
- auto result = AZ::JsonSerializationUtils::LoadObjectFromFile(*this, filepath);
- if (!result.IsSuccess())
- {
- return STRING_OUTCOME_ERROR(result.GetError());
- }
- //enable builder settings for enabled restricted platforms. These settings should be disabled by default in the setting file
- #if defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS)
- #define AZ_RESTRICTED_PLATFORM_EXPANSION(CodeName, CODENAME, codename, PrivateName, PRIVATENAME, privatename, PublicName, PUBLICNAME, publicname, PublicAuxName1, PublicAuxName2, PublicAuxName3) \
- for (auto& buildSetting : m_builderSettings) \
- { \
- if (ImageProcess##PrivateName::DoesSupport(buildSetting.first)) \
- { \
- buildSetting.second.m_enablePlatform = true; \
- break; \
- } \
- }
- AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS
- #undef AZ_RESTRICTED_PLATFORM_EXPANSION
- #endif //AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS
-
- return STRING_OUTCOME_SUCCESS;
- }
- StringOutcome BuilderSettingManager::WriteSettings(AZStd::string_view filepath)
- {
- AZ::JsonSerializerSettings saveSettings;
- saveSettings.m_keepDefaults = true;
- auto result = AZ::JsonSerializationUtils::SaveObjectToFile(this, filepath,
- (BuilderSettingManager*)nullptr, &saveSettings);
- if (!result.IsSuccess())
- {
- return STRING_OUTCOME_ERROR(result.GetError());
- }
- return STRING_OUTCOME_SUCCESS;
- }
- const AZStd::string& BuilderSettingManager::GetAnalysisFingerprint() const
- {
- return m_analysisFingerprint;
- }
- void BuilderSettingManager::RegenerateMappings()
- {
- AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
- AZStd::string noFilter = AZStd::string();
- m_presetFilterMap.clear();
- for (const auto& presetIter : m_presets)
- {
- const MultiplatformPresetSettings& multiPreset = presetIter.second.m_multiPreset;
- const PresetSettings& preset = multiPreset.GetDefaultPreset();
-
- //Put into no filter preset list
- m_presetFilterMap[noFilter].insert(preset.m_name);
- //Put into file mask preset list if any
- for (const PlatformName& filemask : preset.m_fileMasks)
- {
- if (filemask.empty() || filemask[0] != FileMaskDelimiter)
- {
- AZ_Warning("Image Processing", false, "File mask '%s' is invalid. It must start with '%c'.", filemask.c_str(), FileMaskDelimiter);
- continue;
- }
- else if (filemask.size() < 2)
- {
- AZ_Warning("Image Processing", false, "File mask '%s' is invalid. The '%c' must be followed by at least one other character.", filemask.c_str());
- continue;
- }
- else if (filemask.find(FileMaskDelimiter, 1) != AZStd::string::npos)
- {
- AZ_Warning("Image Processing", false, "File mask '%s' is invalid. It must contain only a single '%c' character.", filemask.c_str(), FileMaskDelimiter);
- continue;
- }
- m_presetFilterMap[filemask].insert(preset.m_name);
- }
- }
- }
- void BuilderSettingManager::MetafilePathFromImagePath(AZStd::string_view imagePath, AZStd::string& metafilePath)
- {
- // Determine if we have a meta file
- AZ::IO::LocalFileIO fileIO;
- AZStd::string settingFilePath = AZStd::string::format("%.*s%s", aznumeric_cast<int>(imagePath.size()),
- imagePath.data(), TextureSettings::ExtensionName);
- if (fileIO.Exists(settingFilePath.c_str()))
- {
- metafilePath = settingFilePath;
- return;
- }
- metafilePath = AZStd::string();
- }
- AZStd::string GetFileMask(AZStd::string_view imageFilePath)
- {
- //get file name
- AZStd::string fileName;
- QString lowerFileName = imageFilePath.data();
- lowerFileName = lowerFileName.toLower();
- AzFramework::StringFunc::Path::GetFileName(lowerFileName.toUtf8().constData(), fileName);
- //get the substring from last '_'
- size_t lastUnderScore = fileName.find_last_of(FileMaskDelimiter);
- if (lastUnderScore != AZStd::string::npos)
- {
- return fileName.substr(lastUnderScore);
- }
- return AZStd::string();
- }
- bool BuilderSettingManager::IsValidPreset(PresetName presetName) const
- {
- if (presetName.IsEmpty())
- {
- return false;
- }
- return m_presets.find(presetName) != m_presets.end();
- }
- PresetName BuilderSettingManager::GetSuggestedPreset(AZStd::string_view imageFilePath, IImageObjectPtr imageFromFile)
- {
- PresetName emptyPreset;
- //load the image to get its size for later use
- IImageObjectPtr image = imageFromFile;
- //if the input image is empty we will try to load it from the path
- if (imageFromFile == nullptr)
- {
- image = IImageObjectPtr(LoadImageFromFile(imageFilePath));
- }
- if (image == nullptr)
- {
- return emptyPreset;
- }
- //get file mask of this image file
- AZStd::string fileMask = GetFileMask(imageFilePath);
- PresetName outPreset = emptyPreset;
- //check default presets for some file masks
- if (m_defaultPresetByFileMask.find(fileMask) != m_defaultPresetByFileMask.end())
- {
- outPreset = m_defaultPresetByFileMask[fileMask];
- if (!IsValidPreset(outPreset))
- {
- outPreset = emptyPreset;
- }
- }
- //use the preset filter map to find
- if (outPreset.IsEmpty() && !fileMask.empty())
- {
- auto& presetFilterMap = GetPresetFilterMap();
- if (presetFilterMap.find(fileMask) != presetFilterMap.end())
- {
- outPreset = *(presetFilterMap.find(fileMask)->second.begin());
- }
- }
- const PresetSettings* presetInfo = nullptr;
- if (!outPreset.IsEmpty())
- {
- presetInfo = GetPreset(outPreset);
- //special case for cubemap
- if (presetInfo && presetInfo->m_cubemapSetting)
- {
- // If it's not a latitude-longitude map or it doesn't match any cubemap layouts then reset its preset
- if (!IsValidLatLongMap(image) && CubemapLayout::GetCubemapLayoutInfo(image) == nullptr)
- {
- outPreset = emptyPreset;
- }
- }
- }
- if (outPreset == emptyPreset)
- {
- if (image->GetAlphaContent() == EAlphaContent::eAlphaContent_Absent)
- {
- outPreset = m_defaultPreset;
- }
- else
- {
- outPreset = m_defaultPresetAlpha;
- }
- }
- //get the pixel format for selected preset
- presetInfo = GetPreset(outPreset);
- if (presetInfo)
- {
- //valid whether image size work with pixel format
- if (CPixelFormats::GetInstance().IsImageSizeValid(presetInfo->m_pixelFormat,
- image->GetWidth(0), image->GetHeight(0), false))
- {
- return outPreset;
- }
- else
- {
- AZ_Warning("Image Processing", false, "Image dimensions are not compatible with preset '%s'. The default preset will be used.", presetInfo->m_name.GetCStr());
- }
- }
- //uncompressed one which could be used for almost everything
- return m_defaultPresetNonePOT;
- }
- bool BuilderSettingManager::DoesSupportPlatform(AZStd::string_view platformId)
- {
- bool rv = m_builderSettings.find(platformId) != m_builderSettings.end();
- return rv;
- }
-
- void BuilderSettingManager::SavePresets(AZStd::string_view outputFolder)
- {
- for (const auto& element : m_presets)
- {
- const PresetEntry& presetEntry = element.second;
- AZStd::string fileName = AZStd::string::format("%s.preset", presetEntry.m_multiPreset.GetDefaultPreset().m_name.GetCStr());
- AZStd::string filePath;
- if (!AzFramework::StringFunc::Path::Join(outputFolder.data(), fileName.c_str(), filePath))
- {
- AZ_Warning("Image Processing", false, "Failed to construct path with folder '%.*s' and file: '%s' to save preset",
- aznumeric_cast<int>(outputFolder.size()), outputFolder.data(), filePath.c_str());
- continue;
- }
- auto result = AZ::JsonSerializationUtils::SaveObjectToFile(&presetEntry.m_multiPreset, filePath);
- if (!result.IsSuccess())
- {
- AZ_Warning("Image Processing", false, "Failed to save preset '%s' to file '%s'. Error: %s",
- presetEntry.m_multiPreset.GetDefaultPreset().m_name.GetCStr(), filePath.c_str(), result.GetError().c_str());
- }
- }
- }
- } // namespace ImageProcessingAtom
|