/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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::s_globalInstance = nullptr; AZStd::mutex BuilderSettingManager::s_instanceMutex; const PlatformName BuilderSettingManager::s_defaultPlatform = AZ_TRAIT_IMAGEPROCESSING_DEFAULT_PLATFORM; AZ::Outcome, 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 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 colorSpaceMap{ { "linear" , ColorSpace::linear }, { "sRGB" , ColorSpace::sRGB }, { "auto" , ColorSpace::autoSelect } }; const AZStd::map rgbWeightMap{ { "uniform" , RGBWeight::uniform }, { "luminance" , RGBWeight::luminance }, { "ciexyz" , RGBWeight::ciexyz } }; //cm_ftype: gaussian, cone, disc, cosine, cosine_power, ggx const AZStd::map 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(); } 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(); } } 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(); 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(); 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(); 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(); 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(); 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(); 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(); } } 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(); } 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 & 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(legacyPresetName, modernPresetName)); } rcINI.endGroup(); } void BuilderSettingManager::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serialize = azrtti_cast(context); if (serialize) { serialize->Class() ->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 lock(s_instanceMutex); if (!s_globalInstance) { s_globalInstance = AZ::Environment::FindVariable(s_environmentVariableName); } AZ_Assert(s_globalInstance, "BuilderSettingManager not created!"); return s_globalInstance.Get(); } void BuilderSettingManager::CreateInstance() { AZStd::lock_guard lock(s_instanceMutex); if (s_globalInstance) { AZ_Assert(false, "BuilderSettingManager already created!"); return; } if (!s_globalInstance) { s_globalInstance = AZ::Environment::CreateVariable(s_environmentVariableName); } if (!s_globalInstance.Get()) { s_globalInstance.Set(aznew BuilderSettingManager()); } } void BuilderSettingManager::DestroyInstance() { AZStd::lock_guard 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 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().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 >& BuilderSettingManager::GetPresetFilterMap() { AZStd::lock_guard lock(m_presetMapLock); return m_presetFilterMap; } const AZ::Uuid BuilderSettingManager::GetPresetIdFromName(const PresetName& presetName) { AZStd::lock_guard 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 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 lock(m_presetMapLock); m_presetFilterMap.clear(); m_presetAliases.clear(); m_builderSettings.clear(); } StringOutcome BuilderSettingManager::LoadBuilderSettings() { StringOutcome outcome = STRING_OUTCOME_ERROR(""); AZ::IO::FileIOBase* ioInstance = AZ::IO::FileIOBase::GetInstance(); // 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(AZ::Utils::LoadObjectFromFile(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(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(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 outcome = ProcessPreset(groupName, set, all_platforms); AZ_ENSURE_STRING_OUTCOME(outcome); } 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 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