diff options
26 files changed, 718 insertions, 32 deletions
diff --git a/.github/dco.yml b/.github/dco.yml new file mode 100644 index 00000000..7993b95c --- /dev/null +++ b/.github/dco.yml @@ -0,0 +1,2 @@ +allowRemediationCommits: + individual: true diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c5144f6..958e6ef6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -238,9 +238,6 @@ elseif(UNIX) # Set RPATH SET(Launcher_BINARY_RPATH "$ORIGIN/") - # jars path is determined on runtime, relative to "Application root path", generally /usr or the root of the portable bundle - set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_JARS_LOCATION=${JARS_DEST_DIR}") - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${LAUNCHER_DESKTOP_DEST_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${LAUNCHER_METAINFO_DEST_DIR}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${LAUNCHER_ICON_DEST_DIR}) @@ -299,6 +296,7 @@ add_subdirectory(libraries/classparser) # class parser library add_subdirectory(libraries/optional-bare) add_subdirectory(libraries/tomlc99) # toml parser add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much +add_subdirectory(libraries/gamemode) ############################### Built Artifacts ############################### diff --git a/launcher/Application.cpp b/launcher/Application.cpp index bafb928b..0ef641ba 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -334,10 +334,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues) FS::updateTimestamp(m_rootPath); #endif - -#ifdef LAUNCHER_JARS_LOCATION - m_jarsPath = TOSTRING(LAUNCHER_JARS_LOCATION); -#endif } QString adjustedBy; @@ -638,6 +634,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("UseNativeOpenAL", false); m_settings->registerSetting("UseNativeGLFW", false); + // Peformance related options + m_settings->registerSetting("EnableFeralGamemode", false); + m_settings->registerSetting("EnableMangoHud", false); + m_settings->registerSetting("UseDiscreteGpu", false); + // Game time m_settings->registerSetting("ShowGameTime", true); m_settings->registerSetting("ShowGlobalGameTime", true); @@ -1557,13 +1558,22 @@ shared_qobject_ptr<Meta::Index> Application::metadataIndex() return m_metadataIndex; } -QString Application::getJarsPath() +QString Application::getJarPath(QString jarFile) { - if(m_jarsPath.isEmpty()) + QStringList potentialPaths = { +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + FS::PathCombine(m_rootPath, "share/jars"), +#endif + FS::PathCombine(m_rootPath, "jars"), + FS::PathCombine(applicationDirPath(), "jars") + }; + for(QString p : potentialPaths) { - return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars"); + QString jarPath = FS::PathCombine(p, jarFile); + if (QFileInfo(jarPath).isFile()) + return jarPath; } - return FS::PathCombine(m_rootPath, m_jarsPath); + return {}; } QString Application::getMSAClientID() diff --git a/launcher/Application.h b/launcher/Application.h index 09007160..18461ad8 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -157,7 +157,11 @@ public: shared_qobject_ptr<Meta::Index> metadataIndex(); - QString getJarsPath(); + /*! + * Finds and returns the full path to a jar file. + * Returns a null-string if it could not be found. + */ + QString getJarPath(QString jarFile); QString getMSAClientID(); QString getCurseKey(); @@ -241,7 +245,6 @@ private: std::shared_ptr<GenericPageProvider> m_globalSettingsProvider; std::map<QString, std::unique_ptr<ITheme>> m_themes; std::unique_ptr<MCEditTool> m_mcedit; - QString m_jarsPath; QSet<QString> m_features; QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers; diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 66177614..2a94dcc6 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -188,6 +188,7 @@ public: * Create envrironment variables for running the instance */ virtual QProcessEnvironment createEnvironment() = 0; + virtual QProcessEnvironment createLaunchEnvironment() = 0; /*! * Returns a matcher that can maps relative paths within the instance to whether they are 'log files' diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index dff5cb67..eb5a68cd 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -972,6 +972,13 @@ target_link_libraries(Launcher_logic BuildConfig Katabasis ) + +if (UNIX AND NOT CYGWIN AND NOT APPLE) + target_link_libraries(Launcher_logic + gamemode + ) +endif() + target_link_libraries(Launcher_logic Qt5::Core Qt5::Xml diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index 17278d86..ae6cd247 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -1,4 +1,5 @@ #include "JavaCommon.h" +#include "java/JavaUtils.h" #include "ui/dialogs/CustomMessageBox.h" #include <MMCStrings.h> @@ -65,6 +66,13 @@ void JavaCommon::javaBinaryWasBad(QWidget *parent, JavaCheckResult result) CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); } +void JavaCommon::javaCheckNotFound(QWidget *parent) +{ + QString text; + text += QObject::tr("Java checker library could not be found. Please check your installation"); + CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); +} + void JavaCommon::TestCheck::run() { if (!JavaCommon::checkJVMArgs(m_args, m_parent)) @@ -72,6 +80,11 @@ void JavaCommon::TestCheck::run() emit finished(); return; } + if (JavaUtils::getJavaCheckPath().isEmpty()) { + javaCheckNotFound(m_parent); + emit finished(); + return; + } checker.reset(new JavaChecker()); connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, SLOT(checkFinished(JavaCheckResult))); diff --git a/launcher/JavaCommon.h b/launcher/JavaCommon.h index ca98145c..59cb7a67 100644 --- a/launcher/JavaCommon.h +++ b/launcher/JavaCommon.h @@ -10,12 +10,14 @@ namespace JavaCommon { bool checkJVMArgs(QString args, QWidget *parent); - // Show a dialog saying that the Java binary was not usable - void javaBinaryWasBad(QWidget *parent, JavaCheckResult result); - // Show a dialog saying that the Java binary was not usable because of bad options - void javaArgsWereBad(QWidget *parent, JavaCheckResult result); // Show a dialog saying that the Java binary was usable void javaWasOk(QWidget *parent, JavaCheckResult result); + // Show a dialog saying that the Java binary was not usable because of bad options + void javaArgsWereBad(QWidget *parent, JavaCheckResult result); + // Show a dialog saying that the Java binary was not usable + void javaBinaryWasBad(QWidget *parent, JavaCheckResult result); + // Show a dialog if we couldn't find Java Checker + void javaCheckNotFound(QWidget *parent); class TestCheck : public QObject { diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index ed421433..9b0a9331 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -39,6 +39,10 @@ public: { return QProcessEnvironment(); } + QProcessEnvironment createLaunchEnvironment() override + { + return QProcessEnvironment(); + } QMap<QString, QString> getVariables() const override { return QMap<QString, QString>(); diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 946599c5..15b22260 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -16,7 +16,13 @@ JavaChecker::JavaChecker(QObject *parent) : QObject(parent) void JavaChecker::performCheck() { - QString checkerJar = FS::PathCombine(APPLICATION->getJarsPath(), "JavaCheck.jar"); + QString checkerJar = JavaUtils::getJavaCheckPath(); + + if (checkerJar.isEmpty()) + { + qDebug() << "Java checker library could not be found. Please check your installation."; + return; + } QStringList args; diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 65a8b1db..24a1556e 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -24,6 +24,7 @@ #include "java/JavaUtils.h" #include "java/JavaInstallList.h" #include "FileSystem.h" +#include "Application.h" #define IBUS "@im=ibus" @@ -437,3 +438,8 @@ QList<QString> JavaUtils::FindJavaPaths() return addJavasFromEnv(javas); } #endif + +QString JavaUtils::getJavaCheckPath() +{ + return APPLICATION->getJarPath("JavaCheck.jar"); +} diff --git a/launcher/java/JavaUtils.h b/launcher/java/JavaUtils.h index 3152d143..26d8003b 100644 --- a/launcher/java/JavaUtils.h +++ b/launcher/java/JavaUtils.h @@ -39,4 +39,6 @@ public: #ifdef Q_OS_WIN QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName, QString keyJavaDir, QString subkeySuffix = ""); #endif + + static QString getJavaCheckPath(); }; diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp index ef5db2c9..db56b652 100644 --- a/launcher/launch/steps/CheckJava.cpp +++ b/launcher/launch/steps/CheckJava.cpp @@ -34,6 +34,7 @@ */ #include "CheckJava.h" +#include "java/JavaUtils.h" #include <launch/LaunchTask.h> #include <FileSystem.h> #include <QStandardPaths> @@ -71,6 +72,14 @@ void CheckJava::executeTask() emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::Launcher); } + if (JavaUtils::getJavaCheckPath().isEmpty()) + { + const char *reason = QT_TR_NOOP("Java checker library could not be found. Please check your installation."); + emit logLine(tr(reason), MessageLevel::Fatal); + emitFailed(tr(reason)); + return; + } + QFileInfo javaInfo(realJavaPath); qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch(); auto storedUnixTime = settings->get("JavaTimestamp").toLongLong(); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 88b1f7f3..889c6dde 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -154,6 +154,12 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride); + // Peformance related options + auto performanceOverride = m_settings->registerSetting("OverridePerformance", false); + m_settings->registerOverride(globalSettings->getSetting("EnableFeralGamemode"), performanceOverride); + m_settings->registerOverride(globalSettings->getSetting("EnableMangoHud"), performanceOverride); + m_settings->registerOverride(globalSettings->getSetting("UseDiscreteGpu"), performanceOverride); + // Game time auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride); @@ -435,6 +441,36 @@ QProcessEnvironment MinecraftInstance::createEnvironment() return env; } +QProcessEnvironment MinecraftInstance::createLaunchEnvironment() +{ + // prepare the process environment + QProcessEnvironment env = createEnvironment(); + +#ifdef Q_OS_LINUX + if (settings()->get("EnableMangoHud").toBool()) + { + auto preload = env.value("LD_PRELOAD", "") + ":libMangoHud_dlsym.so:libMangoHud.so"; + auto lib_path = env.value("LD_LIBRARY_PATH", "") + ":/usr/local/$LIB/mangohud/:/usr/$LIB/mangohud/"; + + env.insert("LD_PRELOAD", preload); + env.insert("LD_LIBRARY_PATH", lib_path); + env.insert("MANGOHUD", "1"); + } + + if (settings()->get("UseDiscreteGpu").toBool()) + { + // Open Source Drivers + env.insert("DRI_PRIME", "1"); + // Proprietary Nvidia Drivers + env.insert("__NV_PRIME_RENDER_OFFLOAD", "1"); + env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only"); + env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia"); + } +#endif + + return env; +} + static QString replaceTokensIn(QString text, QMap<QString, QString> with) { QString result; diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index fda58aa7..05450d41 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -91,6 +91,7 @@ public: /// create an environment for launching processes QProcessEnvironment createEnvironment() override; + QProcessEnvironment createLaunchEnvironment() override; /// guess log level from a line of minecraft log MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; diff --git a/launcher/minecraft/launch/DirectJavaLaunch.cpp b/launcher/minecraft/launch/DirectJavaLaunch.cpp index 742170fa..152485b3 100644 --- a/launcher/minecraft/launch/DirectJavaLaunch.cpp +++ b/launcher/minecraft/launch/DirectJavaLaunch.cpp @@ -12,13 +12,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #include "DirectJavaLaunch.h" + +#include <QStandardPaths> + #include <launch/LaunchTask.h> #include <minecraft/MinecraftInstance.h> #include <FileSystem.h> #include <Commandline.h> -#include <QStandardPaths> + +#ifdef Q_OS_LINUX +#include "gamemode_client.h" +#endif DirectJavaLaunch::DirectJavaLaunch(LaunchTask *parent) : LaunchStep(parent) { @@ -50,7 +55,7 @@ void DirectJavaLaunch::executeTask() auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); - m_process.setProcessEnvironment(instance->createEnvironment()); + m_process.setProcessEnvironment(instance->createLaunchEnvironment()); // make detachable - this will keep the process running even if the object is destroyed m_process.setDetachable(true); @@ -79,6 +84,17 @@ void DirectJavaLaunch::executeTask() { m_process.start(javaPath, args); } + +#ifdef Q_OS_LINUX + if (instance->settings()->get("EnableFeralGamemode").toBool()) + { + auto pid = m_process.processId(); + if (pid) + { + gamemode_request_start_for(pid); + } + } +#endif } void DirectJavaLaunch::on_state(LoggedProcess::State state) diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index fe8a1b1b..9965ef89 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -44,6 +44,10 @@ #include "Commandline.h" #include "Application.h" +#ifdef Q_OS_LINUX +#include "gamemode_client.h" +#endif + LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent) { auto instance = parent->instance(); @@ -92,6 +96,15 @@ bool fitsInLocal8bit(const QString & string) void LauncherPartLaunch::executeTask() { + QString jarPath = APPLICATION->getJarPath("NewLaunch.jar"); + if (jarPath.isEmpty()) + { + const char *reason = QT_TR_NOOP("Launcher library could not be found. Please check your installation."); + emit logLine(tr(reason), MessageLevel::Fatal); + emitFailed(tr(reason)); + return; + } + auto instance = m_parent->instance(); std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); @@ -102,13 +115,13 @@ void LauncherPartLaunch::executeTask() auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); - m_process.setProcessEnvironment(instance->createEnvironment()); + m_process.setProcessEnvironment(instance->createLaunchEnvironment()); // make detachable - this will keep the process running even if the object is destroyed m_process.setDetachable(true); auto classPath = minecraftInstance->getClassPath(); - classPath.prepend(FS::PathCombine(APPLICATION->getJarsPath(), "NewLaunch.jar")); + classPath.prepend(jarPath); auto natPath = minecraftInstance->getNativePath(); #ifdef Q_OS_WIN @@ -167,6 +180,17 @@ void LauncherPartLaunch::executeTask() { m_process.start(javaPath, args); } + +#ifdef Q_OS_LINUX + if (instance->settings()->get("EnableFeralGamemode").toBool()) + { + auto pid = m_process.processId(); + if (pid) + { + gamemode_request_start_for(pid); + } + } +#endif } void LauncherPartLaunch::on_state(LoggedProcess::State state) diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 025771e8..2cee15bf 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -127,6 +127,11 @@ void JavaPage::loadSettings() void JavaPage::on_javaDetectBtn_clicked() { + if (JavaUtils::getJavaCheckPath().isEmpty()) { + JavaCommon::javaCheckNotFound(this); + return; + } + JavaInstallPtr java; VersionSelectDialog vselect(APPLICATION->javalist().get(), tr("Select a Java version"), this, true); diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp index f49f5a92..e3ac7e7c 100644 --- a/launcher/ui/pages/global/MinecraftPage.cpp +++ b/launcher/ui/pages/global/MinecraftPage.cpp @@ -87,6 +87,11 @@ void MinecraftPage::applySettings() s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked()); s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked()); + // Peformance related options + s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); + s->set("EnableMangoHud", ui->enableMangoHud->isChecked()); + s->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked()); + // Game time s->set("ShowGameTime", ui->showGameTime->isChecked()); s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked()); @@ -109,6 +114,14 @@ void MinecraftPage::loadSettings() ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool()); ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool()); + ui->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool()); + ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool()); + ui->useDiscreteGpuCheck->setChecked(s->get("UseDiscreteGpu").toBool()); + +#if !defined(Q_OS_LINUX) + ui->perfomanceGroupBox->setVisible(false); +#endif + ui->showGameTime->setChecked(s->get("ShowGameTime").toBool()); ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool()); ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui index 353390bd..640f436d 100644 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ b/launcher/ui/pages/global/MinecraftPage.ui @@ -135,6 +135,45 @@ </widget> </item> <item> + <widget class="QGroupBox" name="perfomanceGroupBox"> + <property name="title"> + <string>Performance</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QCheckBox" name="enableFeralGamemodeCheck"> + <property name="toolTip"> + <string><html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html></string> + </property> + <property name="text"> + <string>Enable Feral GameMode</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="enableMangoHud"> + <property name="toolTip"> + <string><html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html></string> + </property> + <property name="text"> + <string>Enable MangoHud</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="useDiscreteGpuCheck"> + <property name="toolTip"> + <string><html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html></string> + </property> + <property name="text"> + <string>Use discrete GPU</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <widget class="QGroupBox" name="gameTimeGroupBox"> <property name="title"> <string>Game time</string> @@ -181,15 +220,15 @@ </widget> </item> <item> - <widget class="QCheckBox" name="quitAfterGameStopCheck"> - <property name="toolTip"> - <string><html><head/><body><p>The launcher will automatically quit after the game exits or crashes.</p></body></html></string> - </property> - <property name="text"> - <string>&Quit the launcher after game window closes</string> - </property> - </widget> - </item> + <widget class="QCheckBox" name="quitAfterGameStopCheck"> + <property name="toolTip"> + <string><html><head/><body><p>The launcher will automatically quit after the game exits or crashes.</p></body></html></string> + </property> + <property name="text"> + <string>&Quit the launcher after game window closes</string> + </property> + </widget> + </item> </layout> </widget> </item> @@ -218,6 +257,9 @@ <tabstop>windowHeightSpinBox</tabstop> <tabstop>useNativeGLFWCheck</tabstop> <tabstop>useNativeOpenALCheck</tabstop> + <tabstop>enableFeralGamemodeCheck</tabstop> + <tabstop>enableMangoHud</tabstop> + <tabstop>useDiscreteGpuCheck</tabstop> </tabstops> <resources/> <connections/> diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index b4562843..1d8cd1d7 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -50,6 +50,7 @@ #include "Application.h" #include "java/JavaInstallList.h" +#include "java/JavaUtils.h" #include "FileSystem.h" @@ -232,6 +233,22 @@ void InstanceSettingsPage::applySettings() m_settings->reset("UseNativeGLFW"); } + // Performance + bool performance = ui->perfomanceGroupBox->isChecked(); + m_settings->set("OverridePerformance", performance); + if(performance) + { + m_settings->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); + m_settings->set("EnableMangoHud", ui->enableMangoHud->isChecked()); + m_settings->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked()); + } + else + { + m_settings->reset("EnableFeralGamemode"); + m_settings->reset("EnableMangoHud"); + m_settings->reset("UseDiscreteGpu"); + } + // Game time bool gameTime = ui->gameTimeGroupBox->isChecked(); m_settings->set("OverrideGameTime", gameTime); @@ -325,6 +342,16 @@ void InstanceSettingsPage::loadSettings() ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool()); ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool()); + // Performance + ui->perfomanceGroupBox->setChecked(m_settings->get("OverridePerformance").toBool()); + ui->enableFeralGamemodeCheck->setChecked(m_settings->get("EnableFeralGamemode").toBool()); + ui->enableMangoHud->setChecked(m_settings->get("EnableMangoHud").toBool()); + ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool()); + + #if !defined(Q_OS_LINUX) + ui->perfomanceGroupBox->setVisible(false); + #endif + // Miscellanous ui->gameTimeGroupBox->setChecked(m_settings->get("OverrideGameTime").toBool()); ui->showGameTime->setChecked(m_settings->get("ShowGameTime").toBool()); @@ -336,6 +363,11 @@ void InstanceSettingsPage::loadSettings() void InstanceSettingsPage::on_javaDetectBtn_clicked() { + if (JavaUtils::getJavaCheckPath().isEmpty()) { + JavaCommon::javaCheckNotFound(this); + return; + } + JavaInstallPtr java; VersionSelectDialog vselect(APPLICATION->javalist().get(), tr("Select a Java version"), this, true); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index cb66b3ce..8b3c3370 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -455,6 +455,74 @@ </item> </layout> </widget> + <widget class="QWidget" name="performancePage"> + <attribute name="title"> + <string>Performance</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_14"> + <item> + <widget class="QGroupBox" name="perfomanceGroupBox"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="title"> + <string>Performance</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_13"> + <item> + <widget class="QCheckBox" name="enableFeralGamemodeCheck"> + <property name="toolTip"> + <string><html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html></string> + </property> + <property name="text"> + <string>Enable Feral GameMode</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="enableMangoHud"> + <property name="toolTip"> + <string><html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html></string> + </property> + <property name="text"> + <string>Enable MangoHud</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="useDiscreteGpuCheck"> + <property name="toolTip"> + <string><html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html></string> + </property> + <property name="text"> + <string>Use discrete GPU</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> <widget class="QWidget" name="miscellaneousPage"> <attribute name="title"> <string>Miscellaneous</string> diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 340518b1..f0765909 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -11,6 +11,7 @@ #include <sys.h> +#include "JavaCommon.h" #include "java/JavaInstall.h" #include "java/JavaUtils.h" #include "FileSystem.h" @@ -133,6 +134,10 @@ void JavaSettingsWidget::initialize() void JavaSettingsWidget::refresh() { + if (JavaUtils::getJavaCheckPath().isEmpty()) { + JavaCommon::javaCheckNotFound(this); + return; + } m_versionWidget->loadList(); } diff --git a/libraries/README.md b/libraries/README.md index 49879a26..360c34b1 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -9,6 +9,14 @@ This library has served as a base for some (much more full-featured and advanced Copyright belongs to Petr Mrázek, unless explicitly stated otherwise in the source files. Available under the Apache 2.0 license. +## gamemode + +A performance optimization daemon. + +See [github repo](https://github.com/FeralInteractive/gamemode). + +BSD licensed + ## hoedown Hoedown is a revived fork of Sundown, the Markdown parser based on the original code of the Upskirt library by Natacha Porté. @@ -179,3 +187,4 @@ Licenced under the MIT licence. Tiny implementation of LZMA2 de/compression. This format is only used by Forge to save bandwidth. Public domain. + diff --git a/libraries/gamemode/CMakeLists.txt b/libraries/gamemode/CMakeLists.txt new file mode 100644 index 00000000..9e07f34a --- /dev/null +++ b/libraries/gamemode/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.9.4) +project(gamemode + VERSION 1.6.1) + +add_library(gamemode) +target_include_directories(gamemode PUBLIC include) +target_link_libraries(gamemode PUBLIC ${CMAKE_DL_LIBS}) diff --git a/libraries/gamemode/include/gamemode_client.h b/libraries/gamemode/include/gamemode_client.h new file mode 100644 index 00000000..b6f7afd4 --- /dev/null +++ b/libraries/gamemode/include/gamemode_client.h @@ -0,0 +1,365 @@ +/* + +Copyright (c) 2017-2019, Feral Interactive +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Feral Interactive nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + */ +#ifndef CLIENT_GAMEMODE_H +#define CLIENT_GAMEMODE_H +/* + * GameMode supports the following client functions + * Requests are refcounted in the daemon + * + * int gamemode_request_start() - Request gamemode starts + * 0 if the request was sent successfully + * -1 if the request failed + * + * int gamemode_request_end() - Request gamemode ends + * 0 if the request was sent successfully + * -1 if the request failed + * + * GAMEMODE_AUTO can be defined to make the above two functions apply during static init and + * destruction, as appropriate. In this configuration, errors will be printed to stderr + * + * int gamemode_query_status() - Query the current status of gamemode + * 0 if gamemode is inactive + * 1 if gamemode is active + * 2 if gamemode is active and this client is registered + * -1 if the query failed + * + * int gamemode_request_start_for(pid_t pid) - Request gamemode starts for another process + * 0 if the request was sent successfully + * -1 if the request failed + * -2 if the request was rejected + * + * int gamemode_request_end_for(pid_t pid) - Request gamemode ends for another process + * 0 if the request was sent successfully + * -1 if the request failed + * -2 if the request was rejected + * + * int gamemode_query_status_for(pid_t pid) - Query status of gamemode for another process + * 0 if gamemode is inactive + * 1 if gamemode is active + * 2 if gamemode is active and this client is registered + * -1 if the query failed + * + * const char* gamemode_error_string() - Get an error string + * returns a string describing any of the above errors + * + * Note: All the above requests can be blocking - dbus requests can and will block while the daemon + * handles the request. It is not recommended to make these calls in performance critical code + */ + +#include <stdbool.h> +#include <stdio.h> + +#include <dlfcn.h> +#include <string.h> + +#include <sys/types.h> + +static char internal_gamemode_client_error_string[512] = { 0 }; + +/** + * Load libgamemode dynamically to dislodge us from most dependencies. + * This allows clients to link and/or use this regardless of runtime. + * See SDL2 for an example of the reasoning behind this in terms of + * dynamic versioning as well. + */ +static volatile int internal_libgamemode_loaded = 1; + +/* Typedefs for the functions to load */ +typedef int (*api_call_return_int)(void); +typedef const char *(*api_call_return_cstring)(void); +typedef int (*api_call_pid_return_int)(pid_t); + +/* Storage for functors */ +static api_call_return_int REAL_internal_gamemode_request_start = NULL; +static api_call_return_int REAL_internal_gamemode_request_end = NULL; +static api_call_return_int REAL_internal_gamemode_query_status = NULL; +static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; +static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; +static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; +static api_call_pid_return_int REAL_internal_gamemode_query_status_for = NULL; + +/** + * Internal helper to perform the symbol binding safely. + * + * Returns 0 on success and -1 on failure + */ +__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol( + void *handle, const char *name, void **out_func, size_t func_size, bool required) +{ + void *symbol_lookup = NULL; + char *dl_error = NULL; + + /* Safely look up the symbol */ + symbol_lookup = dlsym(handle, name); + dl_error = dlerror(); + if (required && (dl_error || !symbol_lookup)) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "dlsym failed - %s", + dl_error); + return -1; + } + + /* Have the symbol correctly, copy it to make it usable */ + memcpy(out_func, &symbol_lookup, func_size); + return 0; +} + +/** + * Loads libgamemode and needed functions + * + * Returns 0 on success and -1 on failure + */ +__attribute__((always_inline)) static inline int internal_load_libgamemode(void) +{ + /* We start at 1, 0 is a success and -1 is a fail */ + if (internal_libgamemode_loaded != 1) { + return internal_libgamemode_loaded; + } + + /* Anonymous struct type to define our bindings */ + struct binding { + const char *name; + void **functor; + size_t func_size; + bool required; + } bindings[] = { + { "real_gamemode_request_start", + (void **)&REAL_internal_gamemode_request_start, + sizeof(REAL_internal_gamemode_request_start), + true }, + { "real_gamemode_request_end", + (void **)&REAL_internal_gamemode_request_end, + sizeof(REAL_internal_gamemode_request_end), + true }, + { "real_gamemode_query_status", + (void **)&REAL_internal_gamemode_query_status, + sizeof(REAL_internal_gamemode_query_status), + false }, + { "real_gamemode_error_string", + (void **)&REAL_internal_gamemode_error_string, + sizeof(REAL_internal_gamemode_error_string), + true }, + { "real_gamemode_request_start_for", + (void **)&REAL_internal_gamemode_request_start_for, + sizeof(REAL_internal_gamemode_request_start_for), + false }, + { "real_gamemode_request_end_for", + (void **)&REAL_internal_gamemode_request_end_for, + sizeof(REAL_internal_gamemode_request_end_for), + false }, + { "real_gamemode_query_status_for", + (void **)&REAL_internal_gamemode_query_status_for, + sizeof(REAL_internal_gamemode_query_status_for), + false }, + }; + + void *libgamemode = NULL; + + /* Try and load libgamemode */ + libgamemode = dlopen("libgamemode.so.0", RTLD_NOW); + if (!libgamemode) { + /* Attempt to load unversioned library for compatibility with older + * versions (as of writing, there are no ABI changes between the two - + * this may need to change if ever ABI-breaking changes are made) */ + libgamemode = dlopen("libgamemode.so", RTLD_NOW); + if (!libgamemode) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "dlopen failed - %s", + dlerror()); + internal_libgamemode_loaded = -1; + return -1; + } + } + + /* Attempt to bind all symbols */ + for (size_t i = 0; i < sizeof(bindings) / sizeof(bindings[0]); i++) { + struct binding *binder = &bindings[i]; + + if (internal_bind_libgamemode_symbol(libgamemode, + binder->name, + binder->functor, + binder->func_size, + binder->required)) { + internal_libgamemode_loaded = -1; + return -1; + }; + } + + /* Success */ + internal_libgamemode_loaded = 0; + return 0; +} + +/** + * Redirect to the real libgamemode + */ +__attribute__((always_inline)) static inline const char *gamemode_error_string(void) +{ + /* If we fail to load the system gamemode, or we have an error string already, return our error + * string instead of diverting to the system version */ + if (internal_load_libgamemode() < 0 || internal_gamemode_client_error_string[0] != '\0') { + return internal_gamemode_client_error_string; + } + + return REAL_internal_gamemode_error_string(); +} + +/** + * Redirect to the real libgamemode + * Allow automatically requesting game mode + * Also prints errors as they happen. + */ +#ifdef GAMEMODE_AUTO +__attribute__((constructor)) +#else +__attribute__((always_inline)) static inline +#endif +int gamemode_request_start(void) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { +#ifdef GAMEMODE_AUTO + fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); +#endif + return -1; + } + + if (REAL_internal_gamemode_request_start() < 0) { +#ifdef GAMEMODE_AUTO + fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); +#endif + return -1; + } + + return 0; +} + +/* Redirect to the real libgamemode */ +#ifdef GAMEMODE_AUTO +__attribute__((destructor)) +#else +__attribute__((always_inline)) static inline +#endif +int gamemode_request_end(void) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { +#ifdef GAMEMODE_AUTO + fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); +#endif + return -1; + } + + if (REAL_internal_gamemode_request_end() < 0) { +#ifdef GAMEMODE_AUTO + fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); +#endif + return -1; + } + + return 0; +} + +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_query_status(void) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + if (REAL_internal_gamemode_query_status == NULL) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "gamemode_query_status missing (older host?)"); + return -1; + } + + return REAL_internal_gamemode_query_status(); +} + +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + if (REAL_internal_gamemode_request_start_for == NULL) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "gamemode_request_start_for missing (older host?)"); + return -1; + } + + return REAL_internal_gamemode_request_start_for(pid); +} + +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_request_end_for(pid_t pid) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + if (REAL_internal_gamemode_request_end_for == NULL) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "gamemode_request_end_for missing (older host?)"); + return -1; + } + + return REAL_internal_gamemode_request_end_for(pid); +} + +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_query_status_for(pid_t pid) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + if (REAL_internal_gamemode_query_status_for == NULL) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "gamemode_query_status_for missing (older host?)"); + return -1; + } + + return REAL_internal_gamemode_query_status_for(pid); +} + +#endif // CLIENT_GAMEMODE_H |