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.
717 lines
28 KiB
C++
717 lines
28 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 <QCoreApplication>
|
|
#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/ImageToProcess.h>
|
|
#include <Processing/PixelFormatInfo.h>
|
|
#include <Processing/Utils.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/Assets/Config/";
|
|
const char* BuilderSettingManager::s_projectConfigRelativeFolder = "Config/AtomImageBuilder/";
|
|
const char* BuilderSettingManager::s_builderSettingFileName = "ImageBuilder.settings";
|
|
const char* BuilderSettingManager::s_presetFileExtension = "preset";
|
|
|
|
const char FileMaskDelimiter = '_';
|
|
|
|
namespace
|
|
{
|
|
static constexpr const char* const LogWindow = "Image Processing";
|
|
}
|
|
|
|
#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(2)
|
|
->Field("BuildSettings", &BuilderSettingManager::m_builderSettings)
|
|
->Field("PresetsByFileMask", &BuilderSettingManager::m_presetFilterMap)
|
|
->Field("DefaultPreset", &BuilderSettingManager::m_defaultPreset)
|
|
->Field("DefaultPresetAlpha", &BuilderSettingManager::m_defaultPresetAlpha)
|
|
->Field("DefaultPresetNonePOT", &BuilderSettingManager::m_defaultPresetNonePOT)
|
|
// deprecated properties
|
|
->Field("DefaultPresetsByFileMask", &BuilderSettingManager::m_defaultPresetByFileMask)
|
|
->Field("AnalysisFingerprint", &BuilderSettingManager::m_analysisFingerprint);
|
|
}
|
|
}
|
|
|
|
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) const
|
|
{
|
|
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;
|
|
}
|
|
|
|
AZStd::vector<AZStd::string> BuilderSettingManager::GetFileMasksForPreset(const PresetName& presetName) const
|
|
{
|
|
AZStd::vector<AZStd::string> fileMasks;
|
|
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
|
|
for (const auto& mapping:m_presetFilterMap)
|
|
{
|
|
for (const auto& preset : mapping.second)
|
|
{
|
|
if (preset == presetName)
|
|
{
|
|
fileMasks.push_back(mapping.first);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return fileMasks;
|
|
}
|
|
|
|
const BuilderSettings* BuilderSettingManager::GetBuilderSetting(const PlatformName& platform) const
|
|
{
|
|
auto itr = m_builderSettings.find(platform);
|
|
if (itr != m_builderSettings.end())
|
|
{
|
|
return &itr->second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const PlatformNameList BuilderSettingManager::GetPlatformList() const
|
|
{
|
|
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() const
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
|
|
return m_presetFilterMap;
|
|
}
|
|
|
|
const AZStd::unordered_set<PresetName>& BuilderSettingManager::GetFullPresetList() const
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
|
|
AZStd::string noFilter = AZStd::string();
|
|
return m_presetFilterMap.find(noFilter)->second;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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"));
|
|
}
|
|
|
|
if (auto engineRoot = fileIoBase->ResolvePath("@engroot@"); engineRoot.has_value())
|
|
{
|
|
m_defaultConfigFolder = *engineRoot;
|
|
m_defaultConfigFolder /= s_defaultConfigRelativeFolder;
|
|
}
|
|
|
|
if (auto sourceGameRoot = fileIoBase->ResolvePath("@projectroot@"); sourceGameRoot.has_value())
|
|
{
|
|
m_projectConfigFolder = *sourceGameRoot;
|
|
m_projectConfigFolder /= s_projectConfigRelativeFolder;
|
|
}
|
|
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
|
|
ClearSettings();
|
|
|
|
outcome = LoadSettings();
|
|
|
|
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(m_defaultConfigFolder.Native());
|
|
LoadPresets(m_projectConfigFolder.Native());
|
|
}
|
|
|
|
// Collect extra file masks from preset files
|
|
CollectFileMasksFromPresets();
|
|
|
|
|
|
if (QCoreApplication::instance())
|
|
{
|
|
m_fileWatcher.reset(new QFileSystemWatcher);
|
|
// track preset files
|
|
// Note, the QT signal would only works for AP but not AssetBuilder
|
|
// We use file time stamp to track preset file change in builder's CreateJob
|
|
for (auto& preset : m_presets)
|
|
{
|
|
m_fileWatcher.data()->addPath(QString(preset.second.m_presetFilePath.c_str()));
|
|
}
|
|
m_fileWatcher.data()->addPath(QString(m_defaultConfigFolder.c_str()));
|
|
m_fileWatcher.data()->addPath(QString(m_projectConfigFolder.c_str()));
|
|
QObject::connect(m_fileWatcher.data(), &QFileSystemWatcher::fileChanged, this, &BuilderSettingManager::OnFileChanged);
|
|
QObject::connect(m_fileWatcher.data(), &QFileSystemWatcher::directoryChanged, this, &BuilderSettingManager::OnFolderChanged);
|
|
}
|
|
|
|
return outcome;
|
|
}
|
|
|
|
void BuilderSettingManager::LoadPresets(AZStd::string_view presetFolder)
|
|
{
|
|
QDirIterator it(presetFolder.data(), QStringList() << "*.preset", QDir::Files, QDirIterator::NoIteratorFlags);
|
|
while (it.hasNext())
|
|
{
|
|
QString filePath = it.next();
|
|
LoadPreset(filePath.toUtf8().data());
|
|
}
|
|
}
|
|
|
|
bool BuilderSettingManager::LoadPreset(const AZStd::string& filePath)
|
|
{
|
|
QFileInfo fileInfo (filePath.c_str());
|
|
|
|
if (!fileInfo.exists())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
MultiplatformPresetSettings preset;
|
|
auto result = AZ::JsonSerializationUtils::LoadObjectFromFile(preset, filePath);
|
|
if (!result.IsSuccess())
|
|
{
|
|
AZ_Warning(LogWindow, false, "Failed to load preset file %s. Error: %s",
|
|
filePath.c_str(), result.GetError().c_str());
|
|
return false;
|
|
}
|
|
|
|
PresetName presetName(fileInfo.baseName().toUtf8().data());
|
|
|
|
AZ_Warning(LogWindow, presetName == preset.GetPresetName(), "Preset file name '%s' is not"
|
|
" same as preset name '%s'. Using preset file name as preset name",
|
|
filePath.c_str(), preset.GetPresetName().GetCStr());
|
|
|
|
preset.SetPresetName(presetName);
|
|
|
|
m_presets[presetName] = PresetEntry{preset, filePath.c_str(), fileInfo.lastModified()};
|
|
return true;
|
|
}
|
|
|
|
void BuilderSettingManager::ReloadPreset(const PresetName& presetName)
|
|
{
|
|
// Find the preset file from project or default config folder
|
|
AZStd::string presetFileName = AZStd::string::format("%s.%s", presetName.GetCStr(), s_presetFileExtension);
|
|
AZ::IO::FixedMaxPath filePath = m_projectConfigFolder/presetFileName;
|
|
QFileInfo fileInfo (filePath.c_str());
|
|
if (!fileInfo.exists())
|
|
{
|
|
filePath = (m_defaultConfigFolder/presetFileName).c_str();
|
|
fileInfo = QFileInfo(filePath.c_str());
|
|
}
|
|
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
|
|
|
|
//Skip the loading if the file wasn't chagned
|
|
if (fileInfo.exists())
|
|
{
|
|
if (m_presets.find(presetName) != m_presets.end())
|
|
{
|
|
if (m_presets[presetName].m_lastModifiedTime == fileInfo.lastModified()
|
|
&& m_presets[presetName].m_presetFilePath == filePath.c_str())
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove preset
|
|
m_presets.erase(presetName);
|
|
|
|
if (fileInfo.exists())
|
|
{
|
|
LoadPreset(filePath.c_str());
|
|
}
|
|
}
|
|
|
|
StringOutcome BuilderSettingManager::LoadConfigFromFolder(AZStd::string_view configFolder)
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
|
|
|
|
// 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);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void BuilderSettingManager::ReportDeprecatedSettings()
|
|
{
|
|
// reported deprecated attributes in image builder settings
|
|
if (!m_analysisFingerprint.empty())
|
|
{
|
|
AZ_Warning(LogWindow, false, "'AnalysisFingerprint' is deprecated and it should be removed from file [%s]", s_builderSettingFileName);
|
|
}
|
|
if (!m_defaultPresetByFileMask.empty())
|
|
{
|
|
AZ_Warning(LogWindow, false, "'DefaultPresetsByFileMask' is deprecated and it should be removed from file [%s]. Use PresetsByFileMask instead", s_builderSettingFileName);
|
|
}
|
|
}
|
|
|
|
StringOutcome BuilderSettingManager::LoadSettings()
|
|
{
|
|
// If the project image build setting file exist, it will merge image builder settings from project folder to the settings from default config folder.
|
|
bool needMerge = false;
|
|
AZStd::string projectSettingFile{ (m_projectConfigFolder / s_builderSettingFileName).Native() };
|
|
|
|
if (AZ::IO::SystemFile::Exists(projectSettingFile.c_str()))
|
|
{
|
|
needMerge = true;
|
|
}
|
|
|
|
AZ::Outcome<void, AZStd::string> outcome;
|
|
AZStd::string defaultSettingFile{ (m_defaultConfigFolder / s_builderSettingFileName).Native() };
|
|
if (needMerge)
|
|
{
|
|
auto outcome1 = AZ::JsonSerializationUtils::ReadJsonFile(defaultSettingFile);
|
|
auto outcome2 = AZ::JsonSerializationUtils::ReadJsonFile(projectSettingFile);
|
|
|
|
// return error if it failed to load default settings
|
|
if (!outcome1.IsSuccess())
|
|
{
|
|
return STRING_OUTCOME_ERROR(outcome1.GetError());
|
|
}
|
|
|
|
// if project config was loaded successfully, apply merge patch
|
|
rapidjson::Document& originDoc = outcome1.GetValue();
|
|
if (outcome2.IsSuccess())
|
|
{
|
|
const rapidjson::Document& patchDoc = outcome2.GetValue();
|
|
AZ::JsonSerializationResult::ResultCode result =
|
|
AZ::JsonSerialization::ApplyPatch(originDoc, originDoc.GetAllocator(), patchDoc, AZ::JsonMergeApproach::JsonMergePatch);
|
|
|
|
if (result.GetProcessing() == AZ::JsonSerializationResult::Processing::Completed)
|
|
{
|
|
AZStd::vector<char> outBuffer;
|
|
AZ::IO::ByteContainerStream<AZStd::vector<char>> outStream{ &outBuffer };
|
|
AZ::JsonSerializationUtils::WriteJsonStream(originDoc, outStream);
|
|
|
|
outStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
|
|
|
|
outcome = AZ::JsonSerializationUtils::LoadObjectFromStream(*this, outStream);
|
|
if (!outcome.IsSuccess())
|
|
{
|
|
return STRING_OUTCOME_ERROR(outcome.GetError());
|
|
}
|
|
|
|
ReportDeprecatedSettings();
|
|
|
|
|
|
// Generate config file fingerprint
|
|
outStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
|
|
AZ::u64 hash = AssetBuilderSDK::GetHashFromIOStream(outStream);
|
|
m_analysisFingerprint = AZStd::string::format("%llX", hash);
|
|
}
|
|
else
|
|
{
|
|
needMerge = false;
|
|
AZ_Warning(LogWindow, false, "Failed to fully merge data into image builder settings. Skipping project build setting file [%s]", projectSettingFile.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AZ_Warning(LogWindow, false, "Failed to load project setting file [%s]. Skipping", projectSettingFile.c_str());
|
|
}
|
|
}
|
|
|
|
if (!needMerge)
|
|
{
|
|
outcome = AZ::JsonSerializationUtils::LoadObjectFromFile(*this, defaultSettingFile);
|
|
if (!outcome.IsSuccess())
|
|
{
|
|
return STRING_OUTCOME_ERROR(outcome.GetError());
|
|
}
|
|
|
|
ReportDeprecatedSettings();
|
|
|
|
// Generate config file fingerprint
|
|
AZ::u64 hash = AssetBuilderSDK::GetFileHash(defaultSettingFile.c_str());
|
|
m_analysisFingerprint = AZStd::string::format("%llX", hash);
|
|
}
|
|
|
|
return STRING_OUTCOME_SUCCESS;
|
|
}
|
|
|
|
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::CollectFileMasksFromPresets()
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
|
|
|
|
AZStd::string noFilter = AZStd::string();
|
|
|
|
AZStd::string extraString;
|
|
|
|
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(LogWindow, false, "File mask '%s' is invalid. It must start with '%c'.", filemask.c_str(), FileMaskDelimiter);
|
|
continue;
|
|
}
|
|
else if (filemask.size() < 2)
|
|
{
|
|
AZ_Warning(LogWindow, 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(LogWindow, false, "File mask '%s' is invalid. It must contain only a single '%c' character.", filemask.c_str(), FileMaskDelimiter);
|
|
continue;
|
|
}
|
|
|
|
extraString += (filemask + preset.m_name.GetCStr());
|
|
|
|
m_presetFilterMap[filemask].insert(preset.m_name);
|
|
}
|
|
}
|
|
|
|
if (!extraString.empty())
|
|
{
|
|
AZ::u64 hash = AZStd::hash<AZStd::string>{}(extraString);
|
|
m_analysisFingerprint += AZStd::string::format("%llX", hash);
|
|
}
|
|
}
|
|
|
|
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) const
|
|
{
|
|
PresetName emptyPreset;
|
|
|
|
//get file mask of this image file
|
|
AZStd::string fileMask = GetFileMask(imageFilePath);
|
|
|
|
PresetName 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());
|
|
}
|
|
}
|
|
|
|
if (outPreset == emptyPreset)
|
|
{
|
|
outPreset = m_defaultPreset;
|
|
}
|
|
|
|
return outPreset;
|
|
}
|
|
|
|
AZStd::vector<AZStd::string> BuilderSettingManager::GetPossiblePresetPaths(const PresetName& presetName) const
|
|
{
|
|
AZStd::vector<AZStd::string> paths;
|
|
AZStd::string presetFile = AZStd::string::format("%s.preset", presetName.GetCStr());
|
|
paths.push_back((m_defaultConfigFolder / presetFile).c_str());
|
|
paths.push_back((m_projectConfigFolder / presetFile).c_str());
|
|
return paths;
|
|
}
|
|
|
|
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(LogWindow, 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(LogWindow, 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());
|
|
}
|
|
}
|
|
}
|
|
|
|
void BuilderSettingManager::OnFileChanged(const QString &path)
|
|
{
|
|
// handles preset file change
|
|
// Note: this signal only works with AP but not AssetBuilder
|
|
AZ_TracePrintf(LogWindow, "File changed %s\n", path.toUtf8().data());
|
|
QFileInfo info(path);
|
|
|
|
// skip if the file is not a preset file
|
|
// Note: for .settings file change it's handled when restart AP.
|
|
if (info.suffix() != s_presetFileExtension)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ReloadPreset(PresetName(info.baseName().toUtf8().data()));
|
|
}
|
|
|
|
void BuilderSettingManager::OnFolderChanged([[maybe_unused]] const QString &path)
|
|
{
|
|
// handles new file added or removed
|
|
// Note: this signal only works with AP but not AssetBuilder
|
|
AZ_TracePrintf(LogWindow, "folder changed %s\n", path.toUtf8().data());
|
|
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_presetMapLock);
|
|
m_presets.clear();
|
|
LoadPresets(m_defaultConfigFolder.Native());
|
|
LoadPresets(m_projectConfigFolder.Native());
|
|
|
|
for (auto& preset : m_presets)
|
|
{
|
|
m_fileWatcher.data()->addPath(QString(preset.second.m_presetFilePath.c_str()));
|
|
}
|
|
}
|
|
} // namespace ImageProcessingAtom
|