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/ImageProcessing/Code/Source/BuilderSettings/BuilderSettingManager.cpp

1115 lines
44 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ImageProcessing_precompiled.h"
#include "BuilderSettingManager.h"
#include <QSettings>
#include <QFile>
#include <QTextStream>
#include <QString>
#include <QSettings>
#include <ImageProcessing/PixelFormats.h>
#include <BuilderSettings/CubemapSettings.h>
#include <BuilderSettings/TextureSettings.h>
#include <Processing/PixelFormatInfo.h>
#include <Processing/ImageToProcess.h>
#include <Converters/Cubemap.h>
#include <ImageLoader/ImageLoaders.h>
#include <ImageProcessing_Traits_Platform.h>
#include <AzCore/Math/Sha1.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/Utils.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/Application/Application.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
namespace ImageProcessing
{
#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";
AZ::EnvironmentVariable<BuilderSettingManager*> BuilderSettingManager::s_globalInstance = nullptr;
AZStd::mutex BuilderSettingManager::s_instanceMutex;
const PlatformName BuilderSettingManager::s_defaultPlatform = AZ_TRAIT_IMAGEPROCESSING_DEFAULT_PLATFORM;
AZ::Outcome<AZStd::vector<AZStd::string>, AZStd::string> GetPlatformNamesFromRC(AZStd::string& filePath)
{
QFile inputFile(filePath.c_str());
if (inputFile.exists() == false)
{
return AZ::Failure(AZStd::string::format("'%s' does not exist", filePath.c_str()));
}
AZStd::vector<AZStd::string> all_platforms;
if (inputFile.open(QIODevice::ReadOnly))
{
QTextStream in(&inputFile);
while (!in.atEnd())
{
if (in.readLine() == "[_platform]")
{
QString name_line = in.readLine();
if (name_line.contains("name="))
{
QString name_value = name_line.split("=")[1];
// Remove name alias after ','
AZStd::string az_name_value = name_value.split(",")[0].toUtf8().constData();
all_platforms.push_back(az_name_value);
}
}
}
inputFile.close();
}
return AZ::Success(all_platforms);
}
StringOutcome ParseKeyToData(QString settingKey, const QSettings& rcINI, PresetSettings& presetSettings)
{
// We may be parsing platform-specific settings denoted by a colon and a platform (ex. mintexturesize:ios=35).
// We always extract the actual setting name to determine the setting we are parsing. We must reference the key
// as a whole to properly index into the rcINI file content.
QString key = settingKey.split(":")[0];
// There is no standard way to map an enum to string--
// Also, can't seem to make this static because it allocates something that the destructor doesn't catch.
const AZStd::map<QString, ColorSpace> colorSpaceMap{
{ "linear" , ColorSpace::linear },
{ "sRGB" , ColorSpace::sRGB },
{ "auto" , ColorSpace::autoSelect }
};
const AZStd::map<QString, RGBWeight> rgbWeightMap{
{ "uniform" , RGBWeight::uniform },
{ "luminance" , RGBWeight::luminance },
{ "ciexyz" , RGBWeight::ciexyz }
};
//cm_ftype: gaussian, cone, disc, cosine, cosine_power, ggx
const AZStd::map<QString, CubemapFilterType> cubemapFilterTypeMap{
{ "cone" , CubemapFilterType::cone },
{ "gaussian" , CubemapFilterType::gaussian },
{ "ggx" , CubemapFilterType::ggx },
{ "cosine" , CubemapFilterType::cosine },
{ "cosine_power" , CubemapFilterType::cosine_power }
};
// To avoid abusing '#define', we use lambda instead.
auto INI_VALUE = [&rcINI, &settingKey]() { return rcINI.value(settingKey); };
auto INI_VALUE_QSTRING = [&rcINI, &settingKey]() { return rcINI.value(settingKey).toString(); };
/************************************************************************/
/* GENERAL PRESET SETTINGS */
/************************************************************************/
if (key == "rgbweights")
{
auto rgbWeightStr = INI_VALUE_QSTRING().toUtf8();
auto rgbWeightIter = rgbWeightMap.find(rgbWeightStr);
if (rgbWeightIter != rgbWeightMap.end())
{
presetSettings.m_rgbWeight = rgbWeightIter->second;
}
else
{
return AZ::Failure(AZStd::string("Unmapped rgbweights enum detected."));
}
}
else if (key == "powof2")
{
presetSettings.m_isPowerOf2 = INI_VALUE().toBool();
}
else if (key == "discardalpha")
{
presetSettings.m_discardAlpha = INI_VALUE().toBool();
}
else if (key == "reduce")
{
int reduce = INI_VALUE().toInt();
if (reduce > 0)
{
presetSettings.m_sizeReduceLevel = reduce;
}
}
else if (key == "ser")
{
presetSettings.m_suppressEngineReduce = INI_VALUE().toBool();
}
else if (key == "colorchart")
{
presetSettings.m_isColorChart = INI_VALUE().toBool();
}
else if (key == "highpass")
{
presetSettings.m_highPassMip = INI_VALUE().toInt();
}
else if (key == "glossfromnormals")
{
presetSettings.m_glossFromNormals = INI_VALUE().toBool();
}
else if (key == "glosslegacydist")
{
presetSettings.m_isLegacyGloss = INI_VALUE().toBool();
}
else if (key == "swizzle")
{
presetSettings.m_swizzle = INI_VALUE_QSTRING().toUtf8().constData();
}
else if (key == "mipnormalize")
{
presetSettings.m_isMipRenormalize = INI_VALUE().toBool();
}
else if (key == "numstreamablemips")
{
presetSettings.m_numStreamableMips = INI_VALUE().toInt();
}
else if (key == "colorspace")
{
// By default, RC.ini contains data written in non-standard INI format inherited from CryEngine.
// We need to parse in the value as string list.
// Example:
//
// [MyValues]
// colorspace=src,dst
//
QVariant paramValue = rcINI.value(key, QString());
if (paramValue.type() != QVariant::StringList)
{
return AZ::Failure(AZStd::string("Expect ColorSpace parameter to be a string list!"));
}
QStringList stringValueList = paramValue.toStringList(); // The order of values for this key is... (SRC, DST)
if (stringValueList.size() != 2)
{
return AZ::Failure(AZStd::string("Expect ColorSpace parameter list size to be 2!"));
}
auto srcColorSpaceIter = colorSpaceMap.find(stringValueList[0]);
if (srcColorSpaceIter != colorSpaceMap.end())
{
presetSettings.m_srcColorSpace = srcColorSpaceIter->second;
}
else
{
return AZ::Failure(AZStd::string("Unmapped ColorSpace enum detected."));
}
auto dstColorSpaceIter = colorSpaceMap.find(stringValueList[1]);
if (dstColorSpaceIter != colorSpaceMap.end())
{
presetSettings.m_destColorSpace = dstColorSpaceIter->second;
}
else
{
return AZ::Failure(AZStd::string("Unmapped ColorSpace enum detected."));
}
}
else if (key == "filemasks")
{
QVariant iniVariant = rcINI.value(settingKey);
QStringList stringValueList = iniVariant.toStringList();
for (QString value : stringValueList)
{
//remove stars. For example: "*_ddna*" => "_ddna"
QString suffix = value.mid(1, value.length()-2);
QByteArray suffixByteArray = suffix.toUtf8();
presetSettings.m_fileMasks.emplace_back(suffixByteArray.constData());
}
}
else if (key == "pixelformat")
{
auto pixelFormatQBytes = INI_VALUE_QSTRING().toUtf8();
const char* pixelFormatString = pixelFormatQBytes.constData();
EPixelFormat pixelFormatEnum = CPixelFormats::GetInstance().FindPixelFormatByLegacyName(pixelFormatString);
if (pixelFormatEnum == EPixelFormat::ePixelFormat_Unknown)
{
return AZ::Failure(AZStd::string::format("Unsupported ePixelFormat detected: %s", pixelFormatString));
}
presetSettings.m_pixelFormat = pixelFormatEnum;
presetSettings.m_pixelFormatName = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormatEnum)->szName;
}
else if (key == "pixelformatalpha")
{
auto pixelFormatQBytes = INI_VALUE_QSTRING().toUtf8();
const char* pixelFormatString = pixelFormatQBytes.constData();
EPixelFormat pixelFormatEnum = CPixelFormats::GetInstance().FindPixelFormatByLegacyName(pixelFormatString);
if (pixelFormatEnum == EPixelFormat::ePixelFormat_Unknown)
{
return AZ::Failure(AZStd::string::format("Unsupported ePixelFormat detected: %s", pixelFormatString));
}
presetSettings.m_pixelFormatAlpha = pixelFormatEnum;
presetSettings.m_pixelFormatAlphaName = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormatEnum)->szName;
}
else if (key == "maxtexturesize")
{
bool isOk = false;
auto maxTextureSize = INI_VALUE().toUInt(&isOk);
if (isOk)
{
presetSettings.m_maxTextureSize = maxTextureSize;
}
else
{
return AZ::Failure(AZStd::string::format("Invalid number for key 'maxtexturesize' for [%s]", presetSettings.m_name.c_str()));
}
}
else if (key == "mintexturesize")
{
bool isOk = false;
auto minTextureSize = INI_VALUE().toUInt(&isOk);
if (isOk)
{
presetSettings.m_minTextureSize = minTextureSize;
}
else
{
return AZ::Failure(AZStd::string::format("Invalid number for key 'mintexturesize' for [%s]", presetSettings.m_name.c_str()));
}
}
/************************************************************************/
/* CUBEMAP PRESET SETTINGS */
/************************************************************************/
else if (key == "cm")
{
if (presetSettings.m_cubemapSetting == nullptr && INI_VALUE().toBool())
{
presetSettings.m_cubemapSetting = AZStd::make_unique<CubemapSettings>();
}
else
{
return AZ::Failure(AZStd::string("Multiple CubeMap settings detected. Reduce to a single settings entry."));
}
}
else if (key == "cm_ftype")
{
if (presetSettings.m_cubemapSetting == nullptr)
{
if (rcINI.value("cm").toBool())
{
presetSettings.m_cubemapSetting = AZStd::make_unique<CubemapSettings>();
}
}
if (presetSettings.m_cubemapSetting)
{
auto filterTypeStr = INI_VALUE_QSTRING().toUtf8();
auto filterTypeIter = cubemapFilterTypeMap.find(filterTypeStr);
if (filterTypeIter != cubemapFilterTypeMap.end())
{
presetSettings.m_cubemapSetting->m_filter = filterTypeIter->second;
}
else
{
return AZ::Failure(AZStd::string("Unmapped cubemap filter type enum detected."));
}
}
}
else if (key == "cm_fangle")
{
if (presetSettings.m_cubemapSetting == nullptr)
{
if (rcINI.value("cm").toBool())
{
presetSettings.m_cubemapSetting = AZStd::make_unique<CubemapSettings>();
presetSettings.m_cubemapSetting->m_angle = INI_VALUE().toFloat();
}
}
else
{
presetSettings.m_cubemapSetting->m_angle = INI_VALUE().toFloat();
}
}
else if (key == "cm_fmipangle")
{
if (presetSettings.m_cubemapSetting == nullptr)
{
if (rcINI.value("cm").toBool())
{
presetSettings.m_cubemapSetting = AZStd::make_unique<CubemapSettings>();
presetSettings.m_cubemapSetting->m_mipAngle = INI_VALUE().toFloat();
}
}
else
{
presetSettings.m_cubemapSetting->m_mipAngle = INI_VALUE().toFloat();
}
}
else if (key == "cm_fmipslope")
{
if (presetSettings.m_cubemapSetting == nullptr)
{
if (rcINI.value("cm").toBool())
{
presetSettings.m_cubemapSetting = AZStd::make_unique<CubemapSettings>();
presetSettings.m_cubemapSetting->m_mipSlope = INI_VALUE().toFloat();
}
}
else
{
presetSettings.m_cubemapSetting->m_mipSlope = INI_VALUE().toFloat();
}
}
else if (key == "cm_edgefixup")
{
if (presetSettings.m_cubemapSetting == nullptr)
{
if (rcINI.value("cm").toBool())
{
presetSettings.m_cubemapSetting = AZStd::make_unique<CubemapSettings>();
presetSettings.m_cubemapSetting->m_edgeFixup = INI_VALUE().toFloat();
}
}
else
{
presetSettings.m_cubemapSetting->m_edgeFixup = INI_VALUE().toFloat();
}
}
else if (key == "cm_diff")
{
if (presetSettings.m_cubemapSetting == nullptr)
{
if (rcINI.value("cm").toBool())
{
presetSettings.m_cubemapSetting = AZStd::make_unique<CubemapSettings>();
presetSettings.m_cubemapSetting->m_generateDiff = INI_VALUE().toBool();
}
}
else
{
presetSettings.m_cubemapSetting->m_generateDiff = INI_VALUE().toBool();
}
}
else if (key == "cm_diffpreset")
{
QByteArray presetNameByteArray = INI_VALUE().toString().toUtf8();
AZ::Uuid presetID = BuilderSettingManager::Instance()->GetPresetIdFromName(presetNameByteArray.constData());
if (presetID.IsNull())
{
return STRING_OUTCOME_ERROR(AZStd::string::format("Parsing error [cm_diffpreset]. Unable to find UUID for preset: %s", presetNameByteArray.constData()));
}
if (presetSettings.m_cubemapSetting == nullptr)
{
if (rcINI.value("cm").toBool())
{
presetSettings.m_cubemapSetting = AZStd::make_unique<CubemapSettings>();
presetSettings.m_cubemapSetting->m_diffuseGenPreset = presetID;
}
}
else
{
presetSettings.m_cubemapSetting->m_diffuseGenPreset = presetID;
}
}
/************************************************************************/
/* MIPMAP PRESET SETTINGS */
/************************************************************************/
else if (key == "mipmaps")
{
// We convey whether 'mipmaps' is enabled/available by whether the pointer is valid or empty.
if (presetSettings.m_mipmapSetting == nullptr && INI_VALUE().toBool())
{
presetSettings.m_mipmapSetting = AZStd::make_unique<MipmapSettings>();
}
}
else if (key == "mipgentype")
{
// We must handle parsing settings of missing/disabled parent settings ("mipmaps")
if (rcINI.value("mipmaps") == QVariant())
{
return AZ::Failure(AZStd::string("'mipgentype' specified, but dependent 'mipmaps' setting is missing in rc.ini."));
}
// If we are missing the mipmap settings (possibly yet to be parsed)...
if (presetSettings.m_mipmapSetting == nullptr)
{
if (rcINI.value("mipmaps").toBool())
{
presetSettings.m_mipmapSetting = AZStd::make_unique<MipmapSettings>();
}
else
{
return AZ::Failure(AZStd::string::format(
"Cannot assign 'mipgentype' because current Preset [%s] has 'mipmaps' disabled.", presetSettings.m_name.c_str()));
}
}
presetSettings.m_mipmapSetting->m_type = MipGenType::blackmanHarris;
if ("average" == INI_VALUE_QSTRING().toUtf8())
{
presetSettings.m_mipmapSetting->m_type = MipGenType::box;
}
}
else
{
QByteArray keyByteArray = key.toUtf8();
return STRING_OUTCOME_WARNING(AZStd::string::format("Unsupported key parsed from RC.ini: %s", keyByteArray.constData()));
}
return STRING_OUTCOME_SUCCESS;
}
void LoadPresetAliasFromRC(QSettings& rcINI, AZStd::map <PresetName, PresetName>& presetAliases)
{
rcINI.beginGroup("_presetAliases");
for (QString legacyPresetString : rcINI.childKeys())
{
QString modernPresetString = rcINI.value(legacyPresetString).toString();
// We must store this intermediary data in order to convert to a c-string.
QByteArray legacyPresetUtf8 = legacyPresetString.toUtf8();
QByteArray modernPresetUtf8 = modernPresetString.toUtf8();
PresetName legacyPresetName(legacyPresetUtf8.constData());
PresetName modernPresetName(modernPresetUtf8.constData());
presetAliases.insert(AZStd::pair<PresetName, PresetName>(legacyPresetName, modernPresetName));
}
rcINI.endGroup();
}
void BuilderSettingManager::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<BuilderSettingManager>()
->Version(1)
->Field("BuildSettings", &BuilderSettingManager::m_builderSettings)
->Field("PresetAliases", &BuilderSettingManager::m_presetAliases)
->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 AZ::Uuid presetId, const PlatformName& platform)
{
AZStd::lock_guard<AZStd::mutex> lock(m_presetMapLock);
PlatformName platformName = platform;
if (platformName.empty())
{
platformName = BuilderSettingManager::s_defaultPlatform;
}
if (m_builderSettings.find(platformName) != m_builderSettings.end())
{
const BuilderSettings& platformBuilderSetting = m_builderSettings[platformName];
auto settingsIter = platformBuilderSetting.m_presets.find(presetId);
if (settingsIter != platformBuilderSetting.m_presets.end())
{
return &settingsIter->second;
}
else
{
AZ_Error("Image Processing", false, "Cannot find preset settings on platform [%s] for preset id: %s", platformName.c_str(), presetId.ToString<AZStd::string>().c_str());
}
}
else
{
AZ_Error("Image Processing", false, "Cannot find platform [%s]", platformName.c_str());
}
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::mutex> lock(m_presetMapLock);
return m_presetFilterMap;
}
const AZ::Uuid BuilderSettingManager::GetPresetIdFromName(const PresetName& presetName)
{
AZStd::lock_guard<AZStd::mutex> lock(m_presetMapLock);
// Each preset shares the same UUID across platforms, therefore, it's safe to pick a random
// platform to search for the preset UUID. We'll use PC, in this case.
const PlatformName defaultPlatform = BuilderSettingManager::s_defaultPlatform;
if (m_builderSettings.find(defaultPlatform) != m_builderSettings.end())
{
auto presets = m_builderSettings[defaultPlatform].m_presets;
for (auto curIter : presets)
{
if (curIter.second.m_name == presetName)
{
return curIter.first;
}
}
}
return AZ::Uuid::CreateNull();
}
const PresetName BuilderSettingManager::GetPresetNameFromId(const AZ::Uuid& presetId)
{
AZStd::lock_guard<AZStd::mutex> lock(m_presetMapLock);
const PlatformName defaultPlatform = BuilderSettingManager::s_defaultPlatform;
if (m_builderSettings.find(defaultPlatform) != m_builderSettings.end())
{
auto& presetMap = m_builderSettings[defaultPlatform].m_presets;
auto presetIter = presetMap.find(presetId);
if (presetIter != presetMap.end())
{
return presetIter->second.m_name;
}
}
return "Unknown";
}
void BuilderSettingManager::ClearSettings()
{
AZStd::lock_guard<AZStd::mutex> lock(m_presetMapLock);
m_presetFilterMap.clear();
m_presetAliases.clear();
m_builderSettings.clear();
}
StringOutcome BuilderSettingManager::LoadBuilderSettings()
{
StringOutcome outcome = STRING_OUTCOME_ERROR("");
// Construct the project setting path that is used by the tool for loading setting file
const char* gameFolderPath = nullptr;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(gameFolderPath, &AzToolsFramework::AssetSystemRequestBus::Events::GetAbsoluteDevGameFolderPath);
AZStd::string projectSettingPath = "";
if (gameFolderPath)
{
AzFramework::StringFunc::Path::Join(gameFolderPath, "Config/ImageBuilder/ImageBuilderPresets.settings", projectSettingPath);
outcome = LoadBuilderSettings(projectSettingPath);
}
if (!outcome.IsSuccess())
{
AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "Failed to read project specific preset setting at [%s], will use default setting file.\n", projectSettingPath.c_str());
// Construct the default setting path
const char* engineRoot = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
if (engineRoot)
{
AZStd::string defaultSettingPath = "";
AzFramework::StringFunc::Path::Join(engineRoot, "Gems/ImageProcessing/Code/Source/ImageBuilderDefaultPresets.settings", defaultSettingPath);
outcome = LoadBuilderSettings(defaultSettingPath);
}
}
return outcome;
}
StringOutcome BuilderSettingManager::LoadBuilderSettings(AZStd::string filepath, AZ::SerializeContext* context)
{
// Ensure filepath exists.
AZ::IO::FileIOBase* fileReader = AZ::IO::FileIOBase::GetInstance();
if(false == fileReader->Exists(filepath.c_str()))
{
return AZ::Failure(AZStd::string::format("Build settings file not found: %s", filepath.c_str()));
}
AZ::IO::HandleType settingsFileHandle;
m_builderSettingsFileVersion = 0;
if (fileReader->Open(filepath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, settingsFileHandle) == AZ::IO::ResultCode::Success)
{
// Read the contents of the file and hash it. The first u32 of the result of this will be used as a version
// number. Any changes to the builder settings file will then cause all textures to be reconverted to pick
// up that change.
AZStd::string settingsFileBuffer;
AZ::u64 settingsFileSize = 0;
fileReader->Size(settingsFileHandle, settingsFileSize);
settingsFileBuffer.resize_no_construct(settingsFileSize);
fileReader->Read(settingsFileHandle, settingsFileBuffer.data(), settingsFileSize);
fileReader->Close(settingsFileHandle);
AZ::Sha1 block;
AZ::u32 hashDigest[5];
block.ProcessBytes(settingsFileBuffer.data(), settingsFileSize);
block.GetDigest(hashDigest);
m_builderSettingsFileVersion = hashDigest[0];
}
auto loadedSettingsPtr = AZStd::unique_ptr<BuilderSettingManager>(AZ::Utils::LoadObjectFromFile<BuilderSettingManager>(filepath, context));
// Ensure file is loaded.
if (!loadedSettingsPtr)
{
return AZ::Failure(AZStd::string::format("Failed to read from file: %s", filepath.c_str()));
}
m_presetMapLock.lock();
// Normally, we would perform a deep-copy from the loaded settings onto 'this' via assignment operator overload. However, we have deleted our
// assignment operator overloads, because we intend for this class to be a singleton. Instead, we will manually perform a deep-copy
// in this function since it's the only time we require something akin to assignment operation.
m_builderSettings = loadedSettingsPtr->m_builderSettings;
m_presetAliases = loadedSettingsPtr->m_presetAliases;
m_defaultPresetByFileMask = loadedSettingsPtr->m_defaultPresetByFileMask;
m_defaultPreset = loadedSettingsPtr->m_defaultPreset;
m_defaultPresetAlpha = loadedSettingsPtr->m_defaultPresetAlpha;
m_defaultPresetNonePOT = loadedSettingsPtr->m_defaultPresetNonePOT;
m_presetFilterMap.clear();
//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
//convert pixel format string to enum for each preset
for (auto& buildSetting : m_builderSettings)
{
for (auto& preset : buildSetting.second.m_presets)
{
preset.second.m_pixelFormat = CPixelFormats::GetInstance().FindPixelFormatByName(preset.second.m_pixelFormatName.c_str());
preset.second.m_pixelFormatAlpha = CPixelFormats::GetInstance().FindPixelFormatByName(preset.second.m_pixelFormatAlphaName.c_str());
}
}
m_presetMapLock.unlock();
RegenerateMappings();
return AZ::Success(AZStd::string());
}
StringOutcome BuilderSettingManager::WriteBuilderSettings(AZStd::string filepath, AZ::SerializeContext* context)
{
if( false == AZ::Utils::SaveObjectToFile<BuilderSettingManager>(filepath, AZ::DataStream::StreamType::ST_XML, this, context))
{
return STRING_OUTCOME_ERROR(AZStd::string::format("Failed to write file: %s", filepath.c_str()));
}
return STRING_OUTCOME_SUCCESS;
}
const PresetName BuilderSettingManager::TranslateLegacyPresetName(const PresetName& legacyName)
{
auto iterResult = m_presetAliases.find(legacyName);
if (iterResult == m_presetAliases.end())
{
return legacyName;
}
return iterResult->second;
}
StringOutcome BuilderSettingManager::LoadBuilderSettingsFromRC(AZStd::string& filePath)
{
//Clear previous settings first
ClearSettings();
// Find all the platforms
auto outcome = GetPlatformNamesFromRC(filePath);
AZ_ENSURE_STRING_OUTCOME(outcome);
PlatformNameVector all_platforms = outcome.TakeValue();
m_presetMapLock.lock();
// Register all the platforms with empty settings.
for (const AZStd::string& platformName : all_platforms)
{
auto newEntry = AZStd::pair<AZStd::string, BuilderSettings>(platformName, BuilderSettings());
m_builderSettings.insert(newEntry);
}
// Open settings for parsing
QSettings set(filePath.c_str(), QSettings::IniFormat);
// Load the preset alias mapping
LoadPresetAliasFromRC(set, m_presetAliases);
QStringList childGroups = set.childGroups();
QStringList exemptGroups;
exemptGroups.append("_platform");
exemptGroups.append("_presetAliases");
// We must generate new preset settings and UUID's prior to parsing the rest of the data, because
// some preset settings make references to other presets within RC.ini. We must make sure all presets
// are identified, before making references to them via their UUID.
for (QString groupName : childGroups)
{
auto newPresetUuid = AZ::Uuid::CreateRandom();
PresetSettings newPresetSetting;
newPresetSetting.m_name = groupName.toUtf8().constData();
newPresetSetting.m_uuid = newPresetUuid;
for (const PlatformName& platform : all_platforms)
{
m_builderSettings[platform].m_presets.insert(AZStd::make_pair(newPresetUuid, newPresetSetting));
}
}
m_presetMapLock.unlock();
// Apply preset settings from file to the existing presets (process each platform).
for(QString groupName : childGroups)
{
// Only process Presets from here on out.
if (exemptGroups.contains(groupName))
{
continue;
}
auto outcome2 = ProcessPreset(groupName, set, all_platforms);
AZ_ENSURE_STRING_OUTCOME(outcome2);
}
RegenerateMappings();
// The original rc.ini doesn't have the information below. Included here for GetSuggestedPreset() to work properly.
m_defaultPresetByFileMask["_diff"] = GetPresetIdFromName("Albedo");
m_defaultPresetByFileMask["_spec"] = GetPresetIdFromName("Reflectance");
m_defaultPresetByFileMask["_refl"] = GetPresetIdFromName("Reflectance");
m_defaultPresetByFileMask["_ddn"] = GetPresetIdFromName("Normals");
m_defaultPresetByFileMask["_ddna"] = GetPresetIdFromName("NormalsWithSmoothness");
m_defaultPresetByFileMask["_cch"] = GetPresetIdFromName("ColorChart");
m_defaultPresetByFileMask["_cm"] = GetPresetIdFromName("EnvironmentProbeHDR");
m_defaultPreset = GetPresetIdFromName("Albedo");
m_defaultPresetAlpha = GetPresetIdFromName("AlbedoWithGenericAlpha");
m_defaultPresetNonePOT = GetPresetIdFromName("ReferenceImage");
return STRING_OUTCOME_SUCCESS;
}
AZ::u32 BuilderSettingManager::BuilderSettingsVersion() const
{
return m_builderSettingsFileVersion;
}
void BuilderSettingManager::RegenerateMappings()
{
AZStd::lock_guard<AZStd::mutex> lock(m_presetMapLock);
AZStd::string noFilter = AZStd::string();
m_presetFilterMap.clear();
for (auto& builderSettingIter : m_builderSettings)
{
const BuilderSettings& builderSetting = builderSettingIter.second;
for (auto& presetIter : builderSetting.m_presets)
{
//Put into no filter preset list
m_presetFilterMap[noFilter].insert(presetIter.second.m_name);
//Put into file mask preset list if any
for (const PlatformName& filemask : presetIter.second.m_fileMasks)
{
m_presetFilterMap[filemask].insert(presetIter.second.m_name);
}
}
}
}
StringOutcome BuilderSettingManager::ProcessPreset(QString& preset, QSettings& rcINI, PlatformNameVector& all_platforms)
{
// Early-out check. We should have the preset available before processing.
QByteArray presetByteArray = preset.toUtf8();
AZ::Uuid parsingPresetUuid = GetPresetIdFromName(presetByteArray.constData());
if (parsingPresetUuid.IsNull())
{
return STRING_OUTCOME_ERROR(AZStd::string::format("Unable to find UUID for preset: %s", presetByteArray.constData()));
}
rcINI.beginGroup(preset);
QStringList groupKeys = rcINI.allKeys();
// Build a list for common & platform-specific settings
QStringList commonPresetSettingKeys;
QStringList platformSpecificPresetSettingKeys;
for (QString key : groupKeys)
{
if (key.contains(":"))
{
platformSpecificPresetSettingKeys.append(key);
}
else
{
commonPresetSettingKeys.append(key);
}
}
// Parse the common settings (retain preset name & uuid)
PresetSettings commonPresetSettings;
commonPresetSettings.m_name = presetByteArray.constData();
commonPresetSettings.m_uuid = parsingPresetUuid;
for (QString settingKey : commonPresetSettingKeys)
{
AZ_ENSURE_STRING_OUTCOME(ParseKeyToData(settingKey, rcINI, commonPresetSettings));
}
// When loading a preset, the UUID is the same per-preset, regardless of the target-platform.
for (AZStd::string& platformId : all_platforms)
{
// Begin platform-specific settings loading with a copy of common Preset settings.
PresetSettings currentPlatformPresetSetting = commonPresetSettings;
// Obtain platform-specific settings
QString platformFilter = QString(":%1").arg(platformId.c_str()); // Example results-> ":ios", ":osx", ":es3"
QStringList currentPlatformSettings = platformSpecificPresetSettingKeys.filter(platformFilter);
// Overwrite values for platform-specific settings...
for (QString platformSetting : currentPlatformSettings)
{
AZ_ENSURE_STRING_OUTCOME(ParseKeyToData(platformSetting, rcINI, currentPlatformPresetSetting));
}
// Assign the overridden platform preset settings to the BuilderSettingManager.
m_builderSettings[platformId].m_presets[parsingPresetUuid] = currentPlatformPresetSetting;
}
rcINI.endGroup();
return STRING_OUTCOME_SUCCESS;
}
void BuilderSettingManager::MetafilePathFromImagePath(const AZStd::string& imagePath, AZStd::string& metafilePath)
{
// Determine if we have a meta file (legacy or modern).
AZ::IO::LocalFileIO fileIO;
AZStd::string modernMetaFilepath = imagePath + TextureSettings::modernExtensionName;
if (fileIO.Exists(modernMetaFilepath.c_str()))
{
metafilePath = modernMetaFilepath;
return;
}
AZStd::string legacyMetaFilepath = imagePath + TextureSettings::legacyExtensionName;
if (fileIO.Exists(legacyMetaFilepath.c_str()))
{
metafilePath = legacyMetaFilepath;
return;
}
// We found neither.
metafilePath = AZStd::string();
}
AZStd::string GetFileMask(const AZStd::string& imageFilePath)
{
//get file name
AZStd::string fileName;
QString lowerFileName = imageFilePath.c_str();
lowerFileName = lowerFileName.toLower();
AzFramework::StringFunc::Path::GetFileName(lowerFileName.toUtf8().constData(), fileName);
//get the substring from last '_'
size_t lastUnderScore = fileName.find_last_of('_');
if (lastUnderScore != AZStd::string::npos)
{
return fileName.substr(lastUnderScore);
}
return AZStd::string();
}
AZ::Uuid BuilderSettingManager::GetSuggestedPreset(const AZStd::string& 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)
{
AZ_Error("Image Processing", image, "Cannot load image file [%s]. Invalid image format or corrupt data. Note that \"Indexed Color\" is not currently supported for .tga files.", imageFilePath.c_str());
return AZ::Uuid::CreateNull();
}
//get file mask of this image file
AZStd::string fileMask = GetFileMask(imageFilePath);
AZ::Uuid outPreset = AZ::Uuid::CreateNull();
if ("_diff" == fileMask && image->GetAlphaContent() != EAlphaContent::eAlphaContent_Absent)
{
outPreset = m_defaultPresetAlpha;
}
else
{
//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 (CubemapLayout::GetCubemapLayoutInfo(image) == nullptr)
{
outPreset = AZ::Uuid::CreateNull();
}
}
}
if (outPreset.IsNull())
{
if (!image->HasPowerOfTwoSizes())
{
// The resource compiler used the non power of 2 preset if the width or the height is not power of 2
// even if it would have been possible to compress the image, so this behavior is matched here.
return m_defaultPresetNonePOT;
}
else 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;
}
}
//uncompressed one which could be used for almost everything
return m_defaultPresetNonePOT;
}
bool BuilderSettingManager::DoesSupportPlatform(const AZStd::string& platformId)
{
bool rv = m_builderSettings.find(platformId) != m_builderSettings.end();
return rv;
}
} // namespace ImageProcessing