/* * 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 #include #include #include "ProjectDefines.h" #include "EditorDefs.h" #include "GraphicsSettingsDialog.h" #include #include #include #include #include #include #include #include #include #include #include // AzFramework #include #include #include #include // Editor #include "Util/AutoDirectoryRestoreFileDialog.h" AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING #include "ui_graphicssettingsdialog.h" AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING QString groupNames[] = { "Game Effects", "Light", "Object Detail", "Particles", "Physics", "Post Processing", "Quality", "Shading", "Shadows", "Sound", "Texture", "Texture Resolution", "Volumetric Effects", "Water", "Miscellaneous" }; GraphicsSettingsDialog::GraphicsSettingsDialog(QWidget* parent /* = nullptr */) : QDialog(parent) , m_ui(new Ui::GraphicsSettingsDialog()) { //Update qlabel color when disabled setStyleSheet("QLabel::disabled{color: gray;}"); // Start initialization the dialog m_ui->setupUi(this); setWindowTitle("Graphics Settings"); m_ui->m_applyButton->setDefault(true); m_headerView = new GraphicsSettingsHeaderView(this, Qt::Horizontal); m_headerView->setMouseTracking(true); m_ui->m_graphicsSettingsTreeView->setHeader(m_headerView); ///////////////////////////////////////////// m_currentPlatform = GetISystem()->GetConfigPlatform(); m_currentSpecIndex = 0; m_dirtyCVarCount = 0; m_showCustomSpec = true; ShowCustomSpecOption(false); // Show categories, disable apply button m_showCategories = true; m_ui->m_applyButton->setEnabled(false); m_graphicsSettingsModel = new GraphicsSettingsModel(this); m_ui->m_graphicsSettingsTreeView->setModel(m_graphicsSettingsModel); m_cfgFiles[CONFIG_PC].push_back("pc_low.cfg"); m_cfgFiles[CONFIG_PC].push_back("pc_medium.cfg"); m_cfgFiles[CONFIG_PC].push_back("pc_high.cfg"); m_cfgFiles[CONFIG_PC].push_back("pc_veryhigh.cfg"); m_cfgFiles[CONFIG_OSX_METAL].push_back("osx_metal_low.cfg"); m_cfgFiles[CONFIG_OSX_METAL].push_back("osx_metal_medium.cfg"); m_cfgFiles[CONFIG_OSX_METAL].push_back("osx_metal_high.cfg"); m_cfgFiles[CONFIG_OSX_METAL].push_back("osx_metal_veryhigh.cfg"); m_cfgFiles[CONFIG_ANDROID].push_back("android_low.cfg"); m_cfgFiles[CONFIG_ANDROID].push_back("android_medium.cfg"); m_cfgFiles[CONFIG_ANDROID].push_back("android_high.cfg"); m_cfgFiles[CONFIG_ANDROID].push_back("android_veryhigh.cfg"); m_cfgFiles[CONFIG_IOS].push_back("ios_low.cfg"); m_cfgFiles[CONFIG_IOS].push_back("ios_medium.cfg"); m_cfgFiles[CONFIG_IOS].push_back("ios_high.cfg"); m_cfgFiles[CONFIG_IOS].push_back("ios_veryhigh.cfg"); #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)\ m_cfgFiles[CONFIG_##CODENAME].push_back(#publicname "_low.cfg");\ m_cfgFiles[CONFIG_##CODENAME].push_back(#publicname "_medium.cfg");\ m_cfgFiles[CONFIG_##CODENAME].push_back(#publicname "_high.cfg"); AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS #undef AZ_RESTRICTED_PLATFORM_EXPANSION #endif // Since the layout order is set by the .ui file we need to match the order here so that tabbing works correctly. m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_gameeffects.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_light.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_objectdetail.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_particles.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_physics.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_postprocessing.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_quality.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_shading.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_shadows.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_sound.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_texture.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_textureresolution.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_volumetriceffects.cfg"); m_cvarGroupOrder.push_back("Config/CVarGroups/sys_spec_water.cfg"); m_cvarGroupOrder.push_back("miscellaneous"); m_platformStrings.push_back(AZStd::make_pair("PC", CONFIG_PC)); m_platformStrings.push_back(AZStd::make_pair("OSX Metal", CONFIG_OSX_METAL)); m_platformStrings.push_back(AZStd::make_pair("Android", CONFIG_ANDROID)); m_platformStrings.push_back(AZStd::make_pair("iOS", CONFIG_IOS)); #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)\ m_platformStrings.push_back(AZStd::make_pair(PublicAuxName2, CONFIG_##CODENAME)); AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS #undef AZ_RESTRICTED_PLATFORM_EXPANSION #endif m_platformStrings.push_back(AZStd::make_pair("Custom", CONFIG_INVALID_PLATFORM)); for (auto& platformString : m_platformStrings) { QString platform = platformString.first.c_str(); m_ui->m_platformEntry->addItem(platform); } QSettings settings("O3DE", "O3DE"); settings.beginGroup("GraphicsSettingsDialog"); if (settings.contains("Platform")) { QString platformName = settings.value("Platform").toString(); AZStd::string azPlatform = platformName.toUtf8().data(); m_currentPlatform = GetConfigPlatformFromName(azPlatform); if (m_currentPlatform == CONFIG_INVALID_PLATFORM) { ShowCustomSpecOption(true); } } settings.endGroup(); SetPlatformEntry(m_currentPlatform); BuildUI(); connect(m_ui->m_graphicsSettingsTreeView, &QTreeView::collapsed, this, [&](const QModelIndex& index) { SetCollapsed(index, true); }); connect(m_ui->m_graphicsSettingsTreeView, &QTreeView::expanded, this, [&](const QModelIndex& index) { SetCollapsed(index, false); }); AzQtComponents::StyleManager::setStyleSheet(this, "style:GraphicsSettingsDialog.qss"); } GraphicsSettingsDialog::~GraphicsSettingsDialog() { QSettings settings("O3DE", "O3DE"); settings.beginGroup("GraphicsSettingsDialog"); auto platformCheck = [this](AZStd::pair& stringConfigPair) { return stringConfigPair.second == m_currentPlatform; }; auto platformStringsIterator = AZStd::find_if(m_platformStrings.begin(), m_platformStrings.end(), platformCheck); if (platformStringsIterator != m_platformStrings.end()) { settings.setValue("Platform", platformStringsIterator->first.c_str()); } else { settings.remove("Platform"); } settings.beginGroup("cvarGroup"); for (auto& collapseIterator : m_uiCollapseGroup) { settings.setValue(collapseIterator->m_groupName, collapseIterator->m_isCollapsed); } settings.endGroup(); settings.endGroup(); settings.sync(); for (CollapseGroup* group : m_uiCollapseGroup) { // Destructor of CollapseGroup will set the members // pointing to nullptr. The actually destruction of the // widgets will be done on destruction of m_ui delete group; } for (ParameterWidget* widget : m_parameterWidgets) { // Destructor of ParameterWidget will set the members // pointing to nullptr. The actually destruction of the // widgets will be done on destruction of m_ui delete widget; } // Delete m_ui will destruct all the UI elements with in the ui file // Since m_ui is a QScopedPointer which will cleanup itself on the end // of the scope. } void GraphicsSettingsDialog::SetSettingsTree(int numColumns) { m_graphicsSettingsModel->clear(); m_uiCollapseGroup.clear(); m_graphicsSettingsModel->setColumnCount(numColumns); for (int i = 1; i < numColumns; ++i) { m_ui->m_graphicsSettingsTreeView->header()->setSectionResizeMode(i, QHeaderView::Stretch); } m_ui->m_graphicsSettingsTreeView->setColumnWidth(0, 200); m_ui->m_graphicsSettingsTreeView->header()->setMinimumSectionSize(100); m_graphicsSettingsModel->setHeaderData(0, Qt::Horizontal, "Properties"); auto parentItem = m_graphicsSettingsModel->invisibleRootItem(); for (int i = 0; i < aznumeric_cast(GraphicsSettings::numSettings); ++i) { QStandardItem* row = new QStandardItem(); row->setData(groupNames[i], Qt::DisplayRole); parentItem->appendRow(row); SetCollapsedLayout(groupNames[i].replace(" ", ""), row); } m_numColumns = numColumns; m_cvarGroupData["Config/CVarGroups/sys_spec_gameeffects.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::GameEffects)); m_cvarGroupData["Config/CVarGroups/sys_spec_light.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::Light)); m_cvarGroupData["Config/CVarGroups/sys_spec_objectdetail.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::ObjectDetail)); m_cvarGroupData["Config/CVarGroups/sys_spec_particles.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::Particles)); m_cvarGroupData["Config/CVarGroups/sys_spec_physics.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::Physics)); m_cvarGroupData["Config/CVarGroups/sys_spec_postprocessing.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::PostProcessing)); m_cvarGroupData["Config/CVarGroups/sys_spec_quality.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::Quality)); m_cvarGroupData["Config/CVarGroups/sys_spec_shading.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::Shading)); m_cvarGroupData["Config/CVarGroups/sys_spec_shadows.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::Shadows)); m_cvarGroupData["Config/CVarGroups/sys_spec_sound.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::Sound)); m_cvarGroupData["Config/CVarGroups/sys_spec_texture.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::Texture)); m_cvarGroupData["Config/CVarGroups/sys_spec_textureresolution.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::TextureResolution)); m_cvarGroupData["Config/CVarGroups/sys_spec_volumetriceffects.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::VolumetricEffects)); m_cvarGroupData["Config/CVarGroups/sys_spec_water.cfg"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::Water)); m_cvarGroupData["miscellaneous"].m_treeRowItem = m_graphicsSettingsModel->item(aznumeric_cast(GraphicsSettings::Miscellaneous)); } void GraphicsSettingsDialog::OnLoadConfigurationEntry(const char* strKey, const char* strValue, const char* strGroup) { AZStd::string key = strKey; ICVar* cvar = gEnv->pConsole->GetCVar(strKey); if (cvar) { AZStd::transform(key.begin(), key.end(), key.begin(), tolower); if (azstricmp(key.c_str(), "sys_spec_full") == 0 || key.find("sys_spec_") == key.npos) { int type = cvar->GetType(); AZStd::any val; if (type == CVAR_INT) { val = atoi(strValue); } else if (type == CVAR_FLOAT) { val = static_cast(atof(strValue)); } else { val = AZStd::string(strValue); } // Platform cfg file (ex. pc_veryhigh.cfg) if (strGroup[0] == '\0') { // New cvar loaded into map if (m_cVarTracker.find(key) == m_cVarTracker.end()) { m_cVarTracker[key].type = type; m_cVarTracker[key].cvarGroup = "miscellaneous"; AZStd::any empty; if (type == CVAR_INT) { empty = 0; } else if (type == CVAR_FLOAT) { empty = 0.0f; } else { empty = AZStd::string(""); } m_cVarTracker[key].fileVals.resize(m_numSpecLevels, CVarFileStatus(empty, empty, empty)); } m_cVarTracker[key].fileVals[m_currentSpecIndex].editedValue = val; m_cVarTracker[key].fileVals[m_currentSpecIndex].overwrittenValue = val; } // default group in sys_spec cfg file else if (azstricmp(strGroup, "default") == 0) { CVarFileStatus defaultVal(val, val, val); if (m_cVarTracker.find(key) == m_cVarTracker.end()) { // New cvar loaded into map CVarInfo& currentCVar = m_cVarTracker[key]; currentCVar.type = cvar->GetType(); currentCVar.fileVals.resize(m_numSpecLevels, defaultVal); } else { // Reset values, if there's a platform override it always follows the sys_spec_*.cfg files. // Resetting avoids the issue where some spec levels are never set because of an extra platform // override load happening earlier just to store the value of sys_spec_full. for (int specLevel = 0; specLevel < m_numSpecLevels; ++specLevel) { m_cVarTracker[key].fileVals[specLevel] = defaultVal; } } // Overwrite miscellaneous if mentioned in platform config file m_cVarTracker[key].cvarGroup = m_currentConfigFilename; } // specific index in sys_spec cfg file else { int group = 0; if (azsscanf(strGroup, "%d", &group) == 1) { auto sysSpecFull = m_cVarTracker.find("sys_spec_full"); if (sysSpecFull != m_cVarTracker.end()) { CVarFileStatus indexAssignment(val, val, val); for (int specLevel = 0; specLevel < m_numSpecLevels; ++specLevel) { // Only apply cvar change to configurations with sys_spec_Full matching the index int overwrittenValue = 0; if (AZStd::any_numeric_cast(&sysSpecFull->second.fileVals[specLevel].overwrittenValue, overwrittenValue) && group == overwrittenValue) { m_cVarTracker[key].fileVals[specLevel] = indexAssignment; } } } } } } } } // Loads UI column for specific cfg file (ex. pc_low.cfg) void GraphicsSettingsDialog::BuildColumn(int specLevel) { if (specLevel < 0 || specLevel >= m_numSpecLevels) { return; } for (auto& it : m_cvarGroupData) { it.second.m_currentRow = 0; } for (auto& it : m_cVarTracker) { QString str = it.first.c_str(); QWidget* input = nullptr; if (it.second.type == CVAR_INT) { AzQtComponents::SpinBox* intval = new AzQtComponents::SpinBox(nullptr); intval->setFocusPolicy(Qt::StrongFocus); intval->setMaximum(INT32_MAX); intval->setMinimum(INT32_MIN); int editedValue; if (AZStd::any_numeric_cast(&it.second.fileVals[specLevel].editedValue, editedValue)) { intval->setValue(editedValue); } m_cvarGroupData[it.second.cvarGroup].m_cvarSpinBoxes.push_back(intval); connect(intval, SIGNAL(valueChanged(int)), this, SLOT(CVarChanged(int))); input = intval; } else if (it.second.type == CVAR_FLOAT) { AzQtComponents::DoubleSpinBox* doubleval = new AzQtComponents::DoubleSpinBox(nullptr); doubleval->setFocusPolicy(Qt::StrongFocus); doubleval->setMaximum(FLT_MAX); doubleval->setMinimum(-FLT_MAX); float editedValue; if (AZStd::any_numeric_cast(&it.second.fileVals[specLevel].editedValue, editedValue)) { doubleval->setValue(editedValue); } m_cvarGroupData[it.second.cvarGroup].m_cvarDoubleSpinBoxes.push_back(doubleval); connect(doubleval, SIGNAL(valueChanged(double)), this, SLOT(CVarChanged(double))); input = doubleval; } else { QLineEdit* stringval = new QLineEdit(nullptr); AZStd::string* editedValue = AZStd::any_cast(&it.second.fileVals[specLevel].editedValue); stringval->setText(editedValue->c_str()); m_cvarGroupData[it.second.cvarGroup].m_cvarLineEdits.push_back(stringval); connect(stringval, SIGNAL(textChanged(const QString&)), this, SLOT(CVarChanged(const QString&))); input = stringval; } if (input) { input->setObjectName(str); input->setProperty("specLevel", specLevel); QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); sizePolicy.setHeightForWidth(input->sizePolicy().hasHeightForWidth()); input->setSizePolicy(sizePolicy); input->setMinimumSize(QSize(INPUT_MIN_WIDTH, INPUT_MIN_HEIGHT)); QStandardItem* parentItem = m_cvarGroupData[it.second.cvarGroup].m_treeRowItem; QModelIndex parentIndex = parentItem->index(); QModelIndex thisIndex = m_graphicsSettingsModel->index(m_cvarGroupData[it.second.cvarGroup].m_currentRow++, specLevel + CVAR_VALUE_COLUMN_OFFSET, parentIndex); m_ui->m_graphicsSettingsTreeView->setIndexWidget(thisIndex, input); m_parameterWidgets.push_back(new ParameterWidget(input, it.first.c_str())); m_cvarGroupData[it.second.cvarGroup].m_widgetInsertOrder.push_back(input); } } } void GraphicsSettingsDialog::LoadPlatformConfigurations() { const int numColumns = m_cfgFiles[m_currentPlatform].size() + 1; SetSettingsTree(numColumns); for (ParameterWidget* widget : m_parameterWidgets) { delete widget; } m_parameterWidgets.clear(); m_ui->m_applyButton->setEnabled(false); m_dirtyCVarCount = 0; // Load platform cfg files to load in sys_spec_Full for (size_t cfgFileIndex = 0; cfgFileIndex < m_cfgFiles[m_currentPlatform].size(); ++cfgFileIndex) { m_currentSpecIndex = cfgFileIndex; m_currentConfigFilename = m_cfgFiles[m_currentPlatform][cfgFileIndex]; GetISystem()->LoadConfiguration(m_currentConfigFilename.c_str(), this, true); } if (m_cVarTracker.find("sys_spec_full") == m_cVarTracker.end()) { m_cVarTracker.clear(); CleanUI(); ShowCategories(false); QMessageBox::warning(this, "Warning", "Invalid custom spec file (missing sys_spec_full).", QMessageBox::Ok); return; } // Load sys_spec cfgs based on sys_spec_Full values LoadCVarGroupDirectory(m_cvarGroupsFolder); setUpdatesEnabled(false); // Reload platform cfg files to override sys_spec index assignments and load rows with filenames of given platform for (size_t cfgFileIndex = 0; cfgFileIndex < m_cfgFiles[m_currentPlatform].size(); ++cfgFileIndex) { m_currentSpecIndex = cfgFileIndex; m_currentConfigFilename = m_cfgFiles[m_currentPlatform][cfgFileIndex]; GetISystem()->LoadConfiguration(m_currentConfigFilename.c_str(), this, true); /*CVarGroupInfo& specFileGroup =*/ m_cvarGroupData["SpecFile"]; m_graphicsSettingsModel->setHeaderData(cfgFileIndex + CVAR_VALUE_COLUMN_OFFSET, Qt::Horizontal, QApplication::translate("GraphicsSettingsDialog", m_cfgFiles[m_currentPlatform][cfgFileIndex].c_str())); } // Loads column of cvar names for (auto& it : m_cVarTracker) { QLabel* cVarLabel = new QLabel(nullptr); QString str = it.first.c_str(); QString strLabel = str + "Label"; cVarLabel->setObjectName(strLabel); QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); sizePolicy.setHeightForWidth(cVarLabel->sizePolicy().hasHeightForWidth()); cVarLabel->setSizePolicy(sizePolicy); cVarLabel->setMinimumSize(QSize(INPUT_MIN_WIDTH, INPUT_MIN_HEIGHT)); cVarLabel->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); QStandardItem* cVarItem = new QStandardItem(); cVarItem->setData(QApplication::translate("GraphicsSettingsDialog", it.first.c_str(), 0), Qt::DisplayRole); QList items{ cVarItem }; for (int i = 1; i < numColumns; ++i) { items << new QStandardItem(); } m_cvarGroupData[it.second.cvarGroup].m_treeRowItem->appendRow(items); cVarLabel->setText(QApplication::translate("GraphicsSettingsDialog", it.first.c_str(), 0)); m_cvarGroupData[it.second.cvarGroup].m_cvarLabels.push_back(cVarLabel); if (ICVar* cvar = gEnv->pConsole->GetCVar(it.first.c_str())) { cVarLabel->setToolTip(cvar->GetHelp()); } } // Loads columns of cvar values for each platform cfg file for (int cfgFileIndex = 0; cfgFileIndex < m_cfgFiles[m_currentPlatform].size(); ++cfgFileIndex) { BuildColumn(cfgFileIndex); } // Remove any section that has no visible controls for (auto& it : m_cvarGroupData) { int totalControlCount = 0; CVarGroupInfo& currentCvarGroupInfo = it.second; totalControlCount += currentCvarGroupInfo.m_cvarDoubleSpinBoxes.size(); totalControlCount += currentCvarGroupInfo.m_cvarSpinBoxes.size(); totalControlCount += currentCvarGroupInfo.m_cvarLineEdits.size(); totalControlCount += currentCvarGroupInfo.m_platformLabels.size(); if (totalControlCount == 0) { if (currentCvarGroupInfo.m_treeRowItem) { int i = 0; for (auto& collapseIterator : m_uiCollapseGroup) { if (collapseIterator->m_groupRow == currentCvarGroupInfo.m_treeRowItem) { m_uiCollapseGroup.remove(i); break; } ++i; } m_graphicsSettingsModel->removeRow(currentCvarGroupInfo.m_treeRowItem->row()); } } } setUpdatesEnabled(true); QSettings settings("O3DE", "O3DE"); settings.beginGroup("GraphicsSettingsDialog"); settings.beginGroup("cvarGroup"); for (auto& collapseIterator : m_uiCollapseGroup) { bool groupCollapsed = settings.value(collapseIterator->m_groupName, collapseIterator->m_isCollapsed).toBool(); if (collapseIterator->m_isCollapsed != groupCollapsed) { collapseIterator->ToggleCollapsed(); } } settings.endGroup(); settings.endGroup(); } void GraphicsSettingsDialog::LoadCVarGroupDirectory(const AZStd::string& path) { AZ::IO::LocalFileIO::FindFilesCallbackType fileFinderCb; fileFinderCb = [&](const char* fullPath) -> bool { if (gEnv->pFileIO->IsDirectory(fullPath)) { // recurse into subdirectory gEnv->pFileIO->FindFiles(fullPath, "*.cfg", fileFinderCb); } else { m_currentConfigFilename = fullPath; GetISystem()->LoadConfiguration(fullPath, this, false); } return true; // keep searching }; gEnv->pFileIO->FindFiles(path.c_str(), "*.cfg", fileFinderCb); } //Build UI, link signals and set the data for device list. void GraphicsSettingsDialog::BuildUI() { connect(m_ui->m_cancelButton, &QPushButton::clicked, this, &GraphicsSettingsDialog::reject); connect(m_ui->m_applyButton, &QPushButton::clicked, this, &GraphicsSettingsDialog::accept); connect(m_ui->m_platformEntry, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(PlatformChanged(const QString&))); connect(m_ui->m_selectCustomSpecButton, &QPushButton::clicked, this, &GraphicsSettingsDialog::OpenCustomSpecDialog); if (!m_cfgFiles[m_currentPlatform].empty()) { LoadPlatformConfigurations(); } else { m_cVarTracker.clear(); CleanUI(); ShowCategories(false); } } void GraphicsSettingsDialog::CleanUI() { setUpdatesEnabled(false); QSettings settings("O3DE", "O3DE"); settings.beginGroup("GraphicsSettingsDialog"); settings.beginGroup("cvarGroup"); for (auto& collapseIterator : m_uiCollapseGroup) { settings.setValue(collapseIterator->m_groupName, collapseIterator->m_isCollapsed); } settings.endGroup(); settings.endGroup(); settings.sync(); m_cVarTracker.clear(); // Uncollapse groups for (auto& collapseIt : m_uiCollapseGroup) { if (collapseIt->m_isCollapsed) { collapseIt->ToggleCollapsed(); } } setUpdatesEnabled(true); } void GraphicsSettingsDialog::ShowCategories(bool show) { if (m_showCategories == show) { return; } m_showCategories = show; m_ui->m_graphicsSettingsTreeView->setVisible(show); } void GraphicsSettingsDialog::ShowCustomSpecOption(bool show) { if (m_showCustomSpec == show) { return; } m_showCustomSpec = show; m_ui->m_selectCustomSpecButton->setVisible(m_showCustomSpec); m_ui->m_lineSpacer->setVisible(m_showCustomSpec); } void GraphicsSettingsDialog::PlatformChanged(const QString& platform) { bool change = true; if (m_dirtyCVarCount > 0 && !SendUnsavedChangesWarning(false)) { change = false; } if (change) { AZStd::string azPlatform = platform.toStdString().c_str(); m_currentPlatform = GetConfigPlatformFromName(azPlatform); if (m_currentPlatform == CONFIG_INVALID_PLATFORM) // "Custom" selected { ShowCustomSpecOption(true); CleanUI(); if (m_cfgFiles[CONFIG_INVALID_PLATFORM].empty()) // if we don't have a custom spec { ShowCategories(false); m_ui->m_applyButton->setEnabled(false); m_dirtyCVarCount = 0; } else { ShowCategories(true); LoadPlatformConfigurations(); } } else { ShowCustomSpecOption(false); ShowCategories(true); CleanUI(); LoadPlatformConfigurations(); } } else { m_ui->m_platformEntry->blockSignals(true); SetPlatformEntry(m_currentPlatform); m_ui->m_platformEntry->blockSignals(false); } } bool GraphicsSettingsDialog::SendUnsavedChangesWarning(bool cancel) { int result = QMessageBox::Yes; if (cancel) { result = QMessageBox::question(this, "Warning", "There are currently unsaved changed. Are you sure you want to cancel?", QMessageBox::Yes, QMessageBox::No); } else { result = QMessageBox::question(this, "Warning", "There are currently unsaved changed. Are you sure you want to change configurations?", QMessageBox::Yes, QMessageBox::No); } return (result == QMessageBox::Yes); } bool GraphicsSettingsDialog::CVarChanged(AZStd::any val, const char* cvarName, int specLevel) { // Checking if the edited value (before change) is equal to the overwritten value bool dirtyBefore = false; AZStd::string azcvarName = cvarName; AZStd::pair cvarInfo = AZStd::make_pair(azcvarName, m_cVarTracker[cvarName]); if (CheckCVarStatesForDiff(&cvarInfo, specLevel, EDITED_OVERWRITTEN_COMPARE)) { dirtyBefore = true; } if (azstricmp(cvarName, "sys_spec_full") == 0) { //Pop out the warning dialog for sys_spec_Full since all cvars will be changed int result = QMessageBox::Ok; result = QMessageBox::question(this, "Warning", "Modifying sys_spec_full will override any unsaved changes.", QMessageBox::Ok, QMessageBox::Cancel); // Cancel - Change sys_spec_full qspinbox value back if (result == QMessageBox::Cancel) { return false; } else // OK - reload column { // Updating sys_spec_full for when adding cvargroup directory m_cVarTracker[cvarName].fileVals[specLevel].editedValue = val; gEnv->pSystem->AddCVarGroupDirectory(m_cvarGroupsFolder); m_currentConfigFilename = m_cfgFiles[m_currentPlatform][specLevel]; m_currentSpecIndex = specLevel; GetISystem()->LoadConfiguration(m_currentConfigFilename.c_str(), this, true); // Updating sys_spec_full since overwritten from loading platform cfg m_cVarTracker[cvarName].fileVals[specLevel].editedValue = val; BuildColumn(specLevel); } } else { m_cVarTracker[cvarName].fileVals[specLevel].editedValue = val; } // Checking if the newly edited value is equal to the overwritten value cvarInfo = AZStd::make_pair(azcvarName, m_cVarTracker[cvarName]); if (CheckCVarStatesForDiff(&cvarInfo, specLevel, EDITED_OVERWRITTEN_COMPARE)) { if (!dirtyBefore) { m_ui->m_applyButton->setEnabled(true); m_dirtyCVarCount++; } } else { if (dirtyBefore) { m_dirtyCVarCount--; if (m_dirtyCVarCount == 0) { m_ui->m_applyButton->setEnabled(false); } } } return true; } void GraphicsSettingsDialog::CVarChanged(int i) { AzQtComponents::SpinBox* box = static_cast(sender()); QString str = box->objectName(); QByteArray ba = str.toUtf8(); const char* cvarName = ba.data(); int specLevel = box->property("specLevel").toInt(); AZStd::any val; val = i; if (!CVarChanged(val, cvarName, specLevel)) { // sys_spec_full warning cancelled box->blockSignals(true); int editedValue; if (AZStd::any_numeric_cast(&m_cVarTracker[cvarName].fileVals[specLevel].editedValue, editedValue)) { box->setValue(editedValue); } box->blockSignals(false); } } void GraphicsSettingsDialog::CVarChanged(double d) { AzQtComponents::DoubleSpinBox* box = static_cast(sender()); QString str = box->objectName(); QByteArray ba = str.toUtf8(); const char* cvarName = ba.data(); AZStd::any val; val = d; int specLevel = box->property("specLevel").toInt(); if (!CVarChanged(val, cvarName, specLevel)) { // only can return false from sys_spec_full, which is an int cvar } } void GraphicsSettingsDialog::CVarChanged(const QString& s) { QLineEdit* box = qobject_cast(sender()); QString str = box->objectName(); QByteArray ba = str.toUtf8(); const char* cvarName = ba.data(); AZStd::any val; val = AZStd::string(s.toStdString().c_str()); int specLevel = box->property("specLevel").toInt(); if (!CVarChanged(val, cvarName, specLevel)) { // only can return false from sys_spec_full, which is an int cvar } } // Returns true if there is a difference between the two cvar states bool GraphicsSettingsDialog::CheckCVarStatesForDiff(AZStd::pair* it, int cfgFileIndex, CVarStateComparison cmp) { if (it->second.type == CVAR_INT) { int editedVal, overwrittenVal, originalVal; if (AZStd::any_numeric_cast(&it->second.fileVals[cfgFileIndex].editedValue, editedVal) && AZStd::any_numeric_cast(&it->second.fileVals[cfgFileIndex].overwrittenValue, overwrittenVal) && AZStd::any_numeric_cast(&it->second.fileVals[cfgFileIndex].originalValue, originalVal)) { if ((cmp == EDITED_OVERWRITTEN_COMPARE && editedVal != overwrittenVal) || (cmp == EDITED_ORIGINAL_COMPARE && editedVal != originalVal) || (cmp == OVERWRITTEN_ORIGINAL_COMPARE && overwrittenVal != originalVal)) { return true; } } } else if (it->second.type == CVAR_FLOAT) { float editedVal, overwrittenVal, originalVal; if (AZStd::any_numeric_cast(&it->second.fileVals[cfgFileIndex].editedValue, editedVal) && AZStd::any_numeric_cast(&it->second.fileVals[cfgFileIndex].overwrittenValue, overwrittenVal) && AZStd::any_numeric_cast(&it->second.fileVals[cfgFileIndex].originalValue, originalVal)) { if ((cmp == EDITED_OVERWRITTEN_COMPARE && editedVal != overwrittenVal) || (cmp == EDITED_ORIGINAL_COMPARE && editedVal != originalVal) || (cmp == OVERWRITTEN_ORIGINAL_COMPARE && overwrittenVal != originalVal)) { return true; } } } else { AZStd::string editedVal = *AZStd::any_cast(&it->second.fileVals[cfgFileIndex].editedValue); AZStd::string overwrittenVal = *AZStd::any_cast(&it->second.fileVals[cfgFileIndex].overwrittenValue); AZStd::string originalVal = *AZStd::any_cast(&it->second.fileVals[cfgFileIndex].originalValue); if ((cmp == EDITED_OVERWRITTEN_COMPARE && editedVal.compare(overwrittenVal) != 0) || (cmp == EDITED_ORIGINAL_COMPARE && editedVal.compare(originalVal) != 0) || (cmp == OVERWRITTEN_ORIGINAL_COMPARE && overwrittenVal.compare(originalVal) != 0)) { return true; } } return false; } /////////////////////////////////////////////////////////////////////// // settings file management void GraphicsSettingsDialog::reject() { if (m_dirtyCVarCount > 0) { if (SendUnsavedChangesWarning(true)) { QDialog::reject(); } } else { QDialog::reject(); } } void GraphicsSettingsDialog::accept() { int result = QMessageBox::Yes; //Pop out the warning dialog for customized setting result = QMessageBox::question(this, "Warning", "A non-tested setting could potentially crash the game if the setting does not match the device. Are you sure you want to apply the customized setting?", QMessageBox::Yes, QMessageBox::No); //Save and exit if (result == QMessageBox::Yes) { SaveSystemSettings(); } } void GraphicsSettingsDialog::OpenCustomSpecDialog() { AZ::IO::FixedMaxPath projectPath = AZ::Utils::GetProjectPath(); projectPath /= SETTINGS_FILE_PATH.toUtf8().constData(); QString settingsPath = QString::fromUtf8(projectPath.c_str(), aznumeric_cast(projectPath.Native().size())); CAutoDirectoryRestoreFileDialog importCustomSpecDialog(QFileDialog::AcceptOpen, QFileDialog::ExistingFile, ".cfg", settingsPath, CFG_FILEFILTER, {}, {}, this); if (importCustomSpecDialog.exec()) { QString file = importCustomSpecDialog.selectedFiles().first(); if (!file.isEmpty()) { ApplyCustomSpec(file); } } } void GraphicsSettingsDialog::ApplyCustomSpec(const QString& customFilePath) { if (customFilePath.isEmpty()) { return; } QFile customFile(customFilePath); if (!customFile.exists()) { QMessageBox::warning(this, "Warning", "Could not find custom spec file.", QMessageBox::Ok); return; } bool change = true; if (m_dirtyCVarCount > 0 && !SendUnsavedChangesWarning(false)) { change = false; } if (change) { AZStd::string filename = customFilePath.toStdString().c_str(); filename = filename.substr(filename.find_last_of('/') + 1); m_currentPlatform = CONFIG_INVALID_PLATFORM; bool alreadyLoaded = IsCustomSpecAlreadyLoaded(filename); if (m_cfgFiles[CONFIG_INVALID_PLATFORM].size() < m_numSpecLevels && !alreadyLoaded) { m_cfgFiles[CONFIG_INVALID_PLATFORM].push_back(filename); } else if (alreadyLoaded) { QMessageBox::warning(this, "Warning", "The selected custom spec file is already loaded. No changes have been made.", QMessageBox::Ok); return; } if (m_cfgFiles[CONFIG_INVALID_PLATFORM].size() == m_numSpecLevels) { m_ui->m_selectCustomSpecButton->setEnabled(false); } ShowCategories(true); CleanUI(); LoadPlatformConfigurations(); } } void GraphicsSettingsDialog::UnloadCustomSpec(int specLevel) { if (specLevel < 0 || specLevel >= m_cfgFiles[CONFIG_INVALID_PLATFORM].size()) { return; } bool shouldUnload = m_dirtyCVarCount <= 0 || SendUnsavedChangesWarning(false); if (shouldUnload) { m_cfgFiles[CONFIG_INVALID_PLATFORM].erase(m_cfgFiles[CONFIG_INVALID_PLATFORM].begin() + specLevel); CleanUI(); if (!m_cfgFiles[CONFIG_INVALID_PLATFORM].empty()) { LoadPlatformConfigurations(); } else { ShowCategories(false); } } } bool GraphicsSettingsDialog::IsCustomSpecAlreadyLoaded(const AZStd::string& filename) const { auto& customConfigFilenames = m_cfgFiles.at(CONFIG_INVALID_PLATFORM); const auto& findResult = AZStd::find(customConfigFilenames.begin(), customConfigFilenames.end(), filename.c_str()); return findResult != customConfigFilenames.end(); } void GraphicsSettingsDialog::SetCollapsedLayout(const QString& groupName, QStandardItem* groupRow) { CollapseGroup* cgroup = new CollapseGroup(m_ui->m_graphicsSettingsTreeView); cgroup->m_groupName = groupName; cgroup->m_groupRow = groupRow; m_uiCollapseGroup.push_back(cgroup); } void GraphicsSettingsDialog::SetPlatformEntry(ESystemConfigPlatform platform) { auto platformCheck = [&platform](AZStd::pair& stringConfigPair) { return stringConfigPair.second == platform; }; auto platformStringsIterator = AZStd::find_if(m_platformStrings.begin(), m_platformStrings.end(), platformCheck); if (platformStringsIterator != m_platformStrings.end()) { int platformIndex = m_ui->m_platformEntry->findText(platformStringsIterator->first.c_str()); m_ui->m_platformEntry->setCurrentIndex(platformIndex); } else { AZ_Assert(false, "Platform not found in platform strings vector."); } } ESystemConfigPlatform GraphicsSettingsDialog::GetConfigPlatformFromName(const AZStd::string& platformName) { ESystemConfigPlatform configPlatform = CONFIG_INVALID_PLATFORM; auto platformNameCheck = [&platformName](AZStd::pair& stringConfigPair) { return (stringConfigPair.first == platformName); }; auto configPlatformIterator = AZStd::find_if(m_platformStrings.begin(), m_platformStrings.end(), platformNameCheck); if (configPlatformIterator != m_platformStrings.end()) { configPlatform = configPlatformIterator->second; } else { AZ_Assert(false, "Platform name not found in platform strings vector."); } return configPlatform; } // Save the current UI options to system qsettings void GraphicsSettingsDialog::SaveSystemSettings() { AZStd::vector successFiles; AZStd::vector nochangeFiles; for (int cfgFileIndex = 0; cfgFileIndex < m_cfgFiles[m_currentPlatform].size(); ++cfgFileIndex) { const QString eq = " = "; const QString cvarGroupString1 = "\n------------------------\n-- "; const QString cvarGroupString2 = "\n------------------------\n"; QString commandList = ""; int sysSpecFull; if (AZStd::any_numeric_cast(&m_cVarTracker["sys_spec_full"].fileVals[cfgFileIndex].editedValue, sysSpecFull)) { commandList += "sys_spec_full" + eq + QString::number(sysSpecFull) + '\n'; } QMap cvarGroupStrings; // Set to true as soon as cvar is found which is unique from current cfg file and index assignment bool saveOut = false; // Adding any dirty cvars not equal to sys_spec_full or any cvar in miscellaneous to command list for (auto& it : m_cVarTracker) { if ((CheckCVarStatesForDiff(&it, cfgFileIndex, EDITED_ORIGINAL_COMPARE) || it.second.cvarGroup == "miscellaneous") && azstricmp(it.first.c_str(), "sys_spec_full")) { AZStd::string cvarGroup = it.second.cvarGroup; if (azstricmp(cvarGroup.c_str(), "miscellaneous") != 0) { int fileIndex = cvarGroup.find_last_of('/') + 1; cvarGroup = cvarGroup.substr(fileIndex, cvarGroup.size() - fileIndex - 4); } QString qcvarGroup = cvarGroup.c_str(); if (cvarGroupStrings.find(qcvarGroup) == cvarGroupStrings.end()) { cvarGroupStrings[qcvarGroup] = cvarGroupString1 + qcvarGroup + cvarGroupString2; } if (it.second.type == CVAR_INT) { int val; if (AZStd::any_numeric_cast(&it.second.fileVals[cfgFileIndex].editedValue, val)) { cvarGroupStrings[qcvarGroup] += it.first.c_str() + eq + QString::number(val) + '\n'; } } else if (it.second.type == CVAR_FLOAT) { float val; if (AZStd::any_numeric_cast(&it.second.fileVals[cfgFileIndex].editedValue, val)) { cvarGroupStrings[qcvarGroup] += it.first.c_str() + eq + QString::number(val) + '\n'; } } else { cvarGroupStrings[qcvarGroup] += it.first.c_str() + eq + AZStd::any_cast(it.second.fileVals[cfgFileIndex].editedValue).c_str() + '\n'; } } if (!saveOut) { saveOut = CheckCVarStatesForDiff(&it, cfgFileIndex, EDITED_OVERWRITTEN_COMPARE); } } // Adding the project name to the path so that the file is created there if it doesn't already exist // as we don't want to modify the version in Engine/config. AZ::IO::FixedMaxPath projectPath = AZ::Utils::GetProjectPath(); projectPath /= SETTINGS_FILE_PATH.toUtf8().constData(); QString settingsPath = QString::fromUtf8(projectPath.c_str(), aznumeric_cast(projectPath.Native().size())); QString settingsFile = settingsPath + m_cfgFiles[m_currentPlatform][cfgFileIndex].c_str(); // Check if current settings differ from existing cfg if (saveOut) { // Adding any dirty cvars not equal to sys_spec_full to command list for (auto& it : cvarGroupStrings) { commandList += it; } if (CFileUtil::OverwriteFile(settingsFile.toStdString().c_str())) { if (!CFileUtil::CreateDirectory(settingsPath.toStdString().c_str())) { QMessageBox::warning(this, "Warning", "Could not create the directory for file \"" + settingsFile + "\". Failed to apply Graphics Setting.", QMessageBox::Ok); continue; } QByteArray dataArray = commandList.toUtf8().data(); QFile file(settingsFile); if (!file.open(QIODevice::WriteOnly) || file.write(dataArray) != dataArray.size()) { QMessageBox::warning(this, "Warning", "Could not write settings to file \"" + settingsFile + ". Failed to apply Graphics Setting.", QMessageBox::Ok); file.close(); continue; } successFiles.push_back(settingsFile); // Update platform cvars to reflect new values for (auto& it : m_cVarTracker) { it.second.fileVals[cfgFileIndex].overwrittenValue = it.second.fileVals[cfgFileIndex].editedValue; } file.close(); } else { QMessageBox::warning(this, "Warning", "Could not check out or make file writable: \"" + settingsFile + "\". Failed to apply Graphics Setting.", QMessageBox::Ok); } } else { nochangeFiles.push_back(settingsFile); } } // Print list of files which had no changes made if (nochangeFiles.size() > 0) { QString message = "No changes have been made to the following files:\n"; for (int i = 0; i < nochangeFiles.size(); ++i) { message += nochangeFiles[i] + "\n"; } QMessageBox::information(this, "Log", message, QMessageBox::Ok); } // Print list of files which were successfully saved if (successFiles.size() > 0) { QString message = "Updated the graphics setting correctly for the following files:\n"; for (int i = 0; i < successFiles.size(); ++i) { message += successFiles[i] + "\n"; } QMessageBox::information(this, "Log", message, QMessageBox::Ok); } // if we saved all of the files that we needed to then disable the save button again if (nochangeFiles.size() + successFiles.size() == m_cfgFiles[m_currentPlatform].size()) { m_dirtyCVarCount = 0; m_ui->m_applyButton->setEnabled(false); } } void GraphicsSettingsDialog::SetCollapsed(const QModelIndex& index, bool flags) { QStandardItem* item = m_graphicsSettingsModel->itemFromIndex(index); for (auto& collapseIterator : m_uiCollapseGroup) { if (collapseIterator->m_groupRow == item) { collapseIterator->m_isCollapsed = flags; break; } } } ///////////////////////////////////////////////////////////////////////// // Collapse Group GraphicsSettingsDialog::CollapseGroup::CollapseGroup(QTreeView* treeView) : m_treeView(treeView) , m_isCollapsed(true) { } void GraphicsSettingsDialog::CollapseGroup::ToggleCollapsed() { m_isCollapsed = !m_isCollapsed; const QModelIndex index = m_groupRow->index(); if (m_isCollapsed) { m_treeView->collapse(index); } else { m_treeView->expand(index); } } /////////////////////////////////////////////////////////////////// // ParameterWidget GraphicsSettingsDialog::ParameterWidget::ParameterWidget(QWidget* widget, QString parameterName) : m_widget(widget) , m_parameterName(parameterName) { m_widget->setToolTip(GetToolTip()); } QString GraphicsSettingsDialog::ParameterWidget::GetToolTip() const { if (!m_parameterName.isEmpty()) { QString tooltipText = QString(PARAMETER_TOOLTIP).arg(m_parameterName); return tooltipText; } return ""; } GraphicsSettingsTreeView::GraphicsSettingsTreeView(QWidget* parent) : QTreeView(parent) { } GraphicsSettingsHeaderView::GraphicsSettingsHeaderView(GraphicsSettingsDialog* dialog, Qt::Orientation orientation, QWidget* parent) : QHeaderView(orientation, parent) , m_dialog(dialog) { setDefaultAlignment(Qt::AlignLeft); } bool GraphicsSettingsHeaderView::event(QEvent* e) { if (m_dialog->IsCustom()) { GraphicsSettingsTreeView* treeView = qobject_cast(parent()); if (treeView) { switch (e->type()) { case QEvent::Enter: m_index = logicalIndexAt(static_cast(e)->pos()); if (m_index > 0) { treeView->setSortingEnabled(true); treeView->sortByColumn(m_index, Qt::AscendingOrder); } break; case QEvent::Leave: treeView->setSortingEnabled(false); break; } } } return QHeaderView::event(e); } void GraphicsSettingsHeaderView::mouseReleaseEvent(QMouseEvent* e) { AZ_UNUSED(e); if (QMessageBox::question( this, QObject::tr("Unload Resource"), QObject::tr("Are you sure you want to unload the resource?"), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { m_dialog->UnloadCustomSpec(m_index - 1); } } void GraphicsSettingsHeaderView::mouseMoveEvent(QMouseEvent* e) { AZ_UNUSED(e); if (m_dialog->IsCustom()) { GraphicsSettingsTreeView* treeView = qobject_cast(parent()); int index = logicalIndexAt(e->pos()); if (index != m_index) { m_index = index; if (m_index > 0) { treeView->setSortingEnabled(true); treeView->sortByColumn(m_index, Qt::AscendingOrder); } else { treeView->setSortingEnabled(false); } } } } GraphicsSettingsModel::GraphicsSettingsModel(QObject* parent) : QStandardItemModel(parent) { } Qt::ItemFlags GraphicsSettingsModel::flags(const QModelIndex& index) const { Qt::ItemFlags flags = Qt::NoItemFlags; if (index.column() == 0) { flags |= Qt::ItemIsSelectable | Qt::ItemIsEnabled; } return flags; } #include