Files
o3de/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.cpp
T
Qing Tao 89e6df1c7f ATOM-16575 clean up image builder presets files (#4611)
ATOM-16575 clean up image builder presets files
Removed unused image builder presets
Deprecating preset UUID and use preset name as unique id
Delete all .exportsettings file which were only used for legacy imageProcessing gem.

Signed-off-by: Qing Tao <qingtao@amazon.com>
2021-10-11 15:18:39 -07:00

544 lines
21 KiB
C++

/*
* 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