From 52a6102c02c0a4d09139dbfe3217e4b3c0a68c1f Mon Sep 17 00:00:00 2001 From: Xenny Heartsong <commits@xenh.at> Date: Wed, 22 Sep 2021 22:07:42 -0400 Subject: [PATCH] FeralInteractive's GameMode integration --- indra/newview/CMakeLists.txt | 2 + indra/newview/algamemode.cpp | 75 ++++ indra/newview/algamemode.h | 43 ++ .../newview/app_settings/settings_alchemy.xml | 11 + indra/newview/gamemode_client.h | 367 ++++++++++++++++++ indra/newview/linux_tools/wrapper.sh | 4 + indra/newview/llappviewerlinux.cpp | 6 + indra/newview/llfloaterpreference.cpp | 8 + .../xui/en/panel_preferences_advanced.xml | 10 + .../newview/skins/default/xui/en/strings.xml | 1 + 10 files changed, 527 insertions(+) create mode 100644 indra/newview/algamemode.cpp create mode 100644 indra/newview/algamemode.h create mode 100644 indra/newview/gamemode_client.h diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index b0c04e0fc35..88b9927418d 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1531,6 +1531,8 @@ endif (DARWIN) if (LINUX) LIST(APPEND viewer_SOURCE_FILES llappviewerlinux.cpp) LIST(APPEND viewer_SOURCE_FILES llappviewerlinux_api_dbus.cpp) + LIST(APPEND viewer_SOURCE_FILES algamemode.cpp) + LIST(APPEND viewer_HEADER_FILES algamemode.h) SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") set(viewer_LIBRARIES diff --git a/indra/newview/algamemode.cpp b/indra/newview/algamemode.cpp new file mode 100644 index 00000000000..580e2576713 --- /dev/null +++ b/indra/newview/algamemode.cpp @@ -0,0 +1,75 @@ +/** + * @file algamemode.cpp + * @brief Support for FeralInteractive's GameMode + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Copyright (C) 2021, XenHat <me@xenh.at> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "algamemode.h" + +#include "llerror.h" +#include "llviewercontrol.h" + +#include "gamemode_client.h" + +#include <boost/signals2.hpp> + +void ALGameMode::init() +{ + enable(gSavedSettings.getBool("AlchemyGameModeEnable")); + gSavedSettings.getControl("AlchemyGameModeEnable")->getCommitSignal()->connect(boost::bind(&ALGameMode::onToggleGameModeControl, this)); +} + +void ALGameMode::shutdown() +{ + gamemode_request_end(); +} + +void ALGameMode::onToggleGameModeControl() +{ + enable(gSavedSettings.getBool("AlchemyGameModeEnable")); +} + +void ALGameMode::enable(const bool enable) +{ + if (enable && getenv("DISABLE_GAMEMODE") != NULL) + { + LL_WARNS() << "The DISABLE_GAMEMODE environment variable has been set and therefore GameMode will not run." << LL_ENDL; + } + else if (mEnabled != enable) + { + mEnabled = enable; + enable ? gamemode_request_start() : gamemode_request_end(); + if (gamemode_query_status() > 0) + { + LL_INFOS() << "GameMode enabled successfully" << LL_ENDL; + } + else + { + LL_INFOS() << "GameMode disabled." << LL_ENDL; + std::string errstr = gamemode_error_string(); + if (errstr.length() > 0) + { + LL_WARNS() << "Gamemode returned the following error: '" << gamemode_error_string() << "'" << LL_ENDL; + } + } + } +} diff --git a/indra/newview/algamemode.h b/indra/newview/algamemode.h new file mode 100644 index 00000000000..4ee293fff81 --- /dev/null +++ b/indra/newview/algamemode.h @@ -0,0 +1,43 @@ +/** + * @file algamemode.h + * @brief Support for FeralInteractive's GameMode + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Copyright (C) 2021, XenHat <me@xenh.at> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#ifndef AL_GAMEMODE_H +#define AL_GAMEMODE_H + +class ALGameMode final +{ + +public: + static ALGameMode &instance() { static ALGameMode inst; return inst;} + + void enable(bool enable); + void init(); + void shutdown(); + +protected: + void onToggleGameModeControl(); + bool mEnabled; + short mStatus; +}; + +#endif // AL_GAMEMODE_H diff --git a/indra/newview/app_settings/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml index 051dbb72657..ea2c30b2cdf 100644 --- a/indra/newview/app_settings/settings_alchemy.xml +++ b/indra/newview/app_settings/settings_alchemy.xml @@ -277,6 +277,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>AlchemyGameModeEnable</key> + <map> + <key>Comment</key> + <string>Enables GameMode (Linux Only) for better performance.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> <key>AlchemyHudTextFadeDistance</key> <map> <key>Comment</key> diff --git a/indra/newview/gamemode_client.h b/indra/newview/gamemode_client.h new file mode 100644 index 00000000000..6d234ccfbbb --- /dev/null +++ b/indra/newview/gamemode_client.h @@ -0,0 +1,367 @@ +/* + +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 +// hack: Forcefully disable auto game mode +#undef GAMEMODE_AUTO +/* + * 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 diff --git a/indra/newview/linux_tools/wrapper.sh b/indra/newview/linux_tools/wrapper.sh index e03fd1e1f42..8df3b31ac38 100755 --- a/indra/newview/linux_tools/wrapper.sh +++ b/indra/newview/linux_tools/wrapper.sh @@ -12,6 +12,10 @@ ## - Avoids using the FMOD Studio or FMOD Ex ALSA audio driver. #export LL_BAD_FMOD_ALSA=x +# Completely prevent gamemode from enabling even if set to true in the settings +# This can be useful if you run Alchemy on a battery-operated device (i.e. laptop) +# export DISABLE_GAMEMODE=1 + ## - Avoids the optional OpenGL extensions which have proven most problematic ## on some hardware. Disabling this option may cause BETTER PERFORMANCE but ## may also cause CRASHES and hangs on some unstable combinations of drivers diff --git a/indra/newview/llappviewerlinux.cpp b/indra/newview/llappviewerlinux.cpp index c7099cec47d..8c383ab15e6 100644 --- a/indra/newview/llappviewerlinux.cpp +++ b/indra/newview/llappviewerlinux.cpp @@ -40,6 +40,8 @@ #include <exception> +#include "algamemode.h" + // Sentry (https://sentry.io) crash reporting tool #if defined(USE_SENTRY) #include <sentry.h> @@ -116,6 +118,8 @@ int main( int argc, char **argv ) sentry_close(); #endif + ALGameMode::instance().shutdown(); + return 0; } @@ -142,6 +146,8 @@ bool LLAppViewerLinux::init() bool success = LLAppViewer::init(); + ALGameMode::instance().init(); + return success; } diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index ecad0e40ac9..38d854d5017 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -2637,6 +2637,14 @@ BOOL LLPanelPreference::postBuild() gSavedSettings.getControl("ThrottleBandwidthKBPS")->getSignal()->connect(boost::bind(&LLPanelPreference::Updater::update, mBandWidthUpdater, _2)); } +#ifndef LL_LINUX + if (hasChild("enable_game_mode_check", TRUE)) + { + getChild<LLCheckBoxCtrl>("enable_game_mode_check")->setEnabled(FALSE); + getChild<LLCheckBoxCtrl>("enable_game_mode_check")->setToolTip(LLTrans::getString("NotAvailableOnPlatform")); + } +#endif + #ifdef EXTERNAL_TOS LLRadioGroup* ext_browser_settings = getChild<LLRadioGroup>("preferred_browser_behavior"); if (ext_browser_settings) diff --git a/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml b/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml index 5aa5ff4f8e2..85bd6c704a2 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml @@ -247,6 +247,16 @@ name="allow_multiple_viewer_check" top_pad="10" width="237"/> + <check_box + control_name="AlchemyGameModeEnable" + follows="top|left" + height="15" + label="Enable Game Mode" + layout="topleft" + left="10" + name="enable_game_mode_check" + top_pad="5" + width="237"/> <check_box control_name="ForceShowGrid" follows="top|left" diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 41cd06ccef3..f4c2ef2530d 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -4366,5 +4366,6 @@ and report the problem. <string name="NotifyIncomingMessage">Incoming message from [NAME]...</string> <string name="AvatarTyping">Typing</string> <string name="UnknownAvatar">Unknown Avatar</string> + <string name="NotAvailableOnPlatform">Not availalbe on this platform</string> </strings> -- GitLab