You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.cpp

561 lines
22 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 <AtomCore/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 PresetSettings* BuilderSettingManager::GetPreset(const AZ::Uuid presetId, const PlatformName& platform, AZStd::string_view* settingsFilePathOut)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
for (const auto& namePreset : m_presets)
{
if (namePreset.second.m_multiPreset.GetPresetId() == presetId)
{
if (settingsFilePathOut)
{
*settingsFilePathOut = namePreset.second.m_presetFilePath;
}
return namePreset.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::set<PresetName>>& BuilderSettingManager::GetPresetFilterMap()
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
return m_presetFilterMap;
}
const AZ::Uuid BuilderSettingManager::GetPresetIdFromName(const PresetName& presetName)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
auto itr = m_presets.find(presetName);
if (itr != m_presets.end())
{
return itr->second.m_multiPreset.GetPresetId();
}
return AZ::Uuid::CreateNull();
}
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 "Unknown";
}
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("@devassets@"); 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().c_str());
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();
}
AZ::Uuid BuilderSettingManager::GetSuggestedPreset(AZStd::string_view imageFilePath, IImageObjectPtr imageFromFile)
{
//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 AZ::Uuid::CreateNull();
}
//get file mask of this image file
AZStd::string fileMask = GetFileMask(imageFilePath);
AZ::Uuid outPreset = AZ::Uuid::CreateNull();
//check default presets for some file masks
if (m_defaultPresetByFileMask.find(fileMask) != m_defaultPresetByFileMask.end())
{
outPreset = m_defaultPresetByFileMask[fileMask];
}
//use the preset filter map to find
if (outPreset.IsNull() && !fileMask.empty())
{
auto& presetFilterMap = GetPresetFilterMap();
if (presetFilterMap.find(fileMask) != presetFilterMap.end())
{
AZStd::string presetName = *(presetFilterMap.find(fileMask)->second.begin());
outPreset = GetPresetIdFromName(presetName);
}
}
const PresetSettings* presetInfo = nullptr;
if (!outPreset.IsNull())
{
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 = AZ::Uuid::CreateNull();
}
}
}
if (outPreset.IsNull())
{
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.c_str());
}
}
//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.c_str());
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.c_str(), filePath.c_str(), result.GetError().c_str());
}
}
}
} // namespace ImageProcessingAtom