From 5d88dedb252687facea492b1d7337341a0768903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 27 Feb 2019 22:26:45 +0100 Subject: [PATCH] GL: ability to control GPU preference on Windows and Linux/BSD. Using the NvOptimusEnablement and AmdPowerXpressRequestHighPerformance executable-local symbols on Windows and using the DRI_PRIME environment variable on Linux. --- CMakeLists.txt | 8 +++ doc/building.dox | 17 +++-- doc/changelog.dox | 4 ++ src/Magnum/GL/Context.cpp | 68 +++++++++++++++++++ src/Magnum/GL/Context.h | 19 ++++++ src/Magnum/Platform/CMakeLists.txt | 9 +++ .../Implementation/OpenGLFunctionLoader.cpp | 30 ++++++++ .../Platform/Implementation/configure.h.cmake | 26 +++++++ 8 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 src/Magnum/Platform/Implementation/configure.h.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index ab74c5315f..4a79c28f38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,14 @@ endif() # OpenGLTester library, built by default only if GL tests are enabled cmake_dependent_option(WITH_OPENGLTESTER "Build OpenGLTester library" OFF "NOT BUILD_GL_TESTS" ON) +# GPU preference symbols (NvOptimusEnablement etc.) on Windows, enabled by +# default so the --magnum-gpu-preference switching works out of the box. The +# application or a 3rd party library might be already defining these, in which +# case we want to be able to disable them. +if(CORRADE_TARGET_WINDOWS) + option(BUILD_GPU_PREFERENCE_SYMBOLS "Build context and application libraries with exported GPU preference symbols" ON) +endif() + # Dynamic linking is meaningless on Emscripten and too inconvenient on Android if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(BUILD_STATIC ON) diff --git a/doc/building.dox b/doc/building.dox index 0e7fb35887..c994ead200 100644 --- a/doc/building.dox +++ b/doc/building.dox @@ -480,13 +480,20 @@ available for desktop OpenGL only, see @ref requires-gl. enabled. - `TARGET_VK` --- Build libraries with Vulkan interoperability enabled. Enabled by default when `WITH_VK` is enabled. Disabling this will cause - libraries to not depend on the @ref Vk library, but doesn't affect the + libraries to not depend on the @ref Vk library, but doesn't affect the @ref Vk library itself. -By default the engine is built in a way that allows having multiple independent -thread-local Magnum contents. This might cause some performance penalties --- -if you are sure that you will never need such feature, you can disable it via -the `BUILD_MULTITHREADED` option. +There are further options affecting to engine behavior, all enabled by default: + +- `BUILD_MULTITHREADED` --- Allow having multiple independent thread-local + Magnum contents. This might cause some performance penalties --- if you are + sure that you will never need such feature, disable this option. +- `BUILD_GPU_PREFERENCE_SYMBOLS` --- Export symbols used to control GPU + preference on Windows, using the `--magnum-gpu-preference` + @ref GL-Context-command-line "command-line option". If your application (or + another 3rd party lib you use) already exports symbols like + `NvOptimusEnablement` and you're getting linker errors due to that, disable + this option. The features used can be conveniently detected in depending projects both in CMake and C++ sources, see @ref cmake and @ref Magnum/Magnum.h for more diff --git a/doc/changelog.dox b/doc/changelog.dox index b03543051f..de6a4ba480 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -55,6 +55,10 @@ See also: and @cpp "mesa-implementation-color-read-format-dsa-explicit-binding" @ce, because it's 2019 and GL drivers are *still* awful. See @ref opengl-workarounds for more information. +- Ability to control GPU preference (integrated/dedicated) using the + `--magnum-gpu-preference` command-line option and environment variables on + Windows and Linux/BSD systems. See @ref GL-Context-command-line for more + information. @subsection changelog-latest-changes Changes and improvements diff --git a/src/Magnum/GL/Context.cpp b/src/Magnum/GL/Context.cpp index fe514bb0e4..12232bfceb 100644 --- a/src/Magnum/GL/Context.cpp +++ b/src/Magnum/GL/Context.cpp @@ -61,6 +61,31 @@ #include "Magnum/GL/Implementation/TransformFeedbackState.h" #endif +#ifdef CORRADE_TARGET_WINDOWS +/* We *don't* want to #include . Kindly borrowed from + https://github.com/Leandros/WindowsHModular; uncomment the include below + to check that the declarations match. */ +extern "C" { + #define WINAPI __stdcall + typedef char CHAR; + typedef const CHAR * LPCSTR; + typedef void * LPVOID; + typedef LPVOID HANDLE; + typedef HANDLE HINSTANCE; + typedef HINSTANCE HMODULE; + HMODULE WINAPI GetModuleHandleA(LPCSTR lpModuleName); + + #if defined(_WIN64) + typedef int64_t INT_PTR; + typedef INT_PTR (WINAPI *FARPROC)(void); + #else + typedef int (WINAPI *FARPROC)(void); + #endif + FARPROC WINAPI GetProcAddress(HMODULE hModule, LPCSTR lProcName); +} +// #include +#endif + namespace Magnum { namespace GL { const std::vector& Extension::extensions(Version version) { @@ -458,9 +483,11 @@ Context::Context(NoCreateT, Utility::Arguments& args, Int argc, const char** arg .setHelp("disable-workarounds", "driver workarounds to disable\n (see https://doc.magnum.graphics/magnum/opengl-workarounds.html for detailed info)", "LIST") .addOption("disable-extensions").setHelp("disable-extensions", "OpenGL extensions to disable", "LIST") .addOption("log", "default").setHelp("log", "console logging", "default|quiet|verbose") + .addOption("gpu-preference", "none").setHelp("gpu-preference", "GPU preference", "none|integrated|dedicated") .setFromEnvironment("disable-workarounds") .setFromEnvironment("disable-extensions") .setFromEnvironment("log") + .setFromEnvironment("gpu-preference") .parse(argc, argv); /* Decide whether to display initialization log */ @@ -473,6 +500,47 @@ Context::Context(NoCreateT, Utility::Arguments& args, Int argc, const char** arg /* Disable extensions */ for(auto&& extension: Utility::String::splitWithoutEmptyParts(args.value("disable-extensions"))) _disabledExtensions.push_back(extension); + + /* GPU preference on Windows or Unix-like systems (except Apple platforms + and Android) */ + if(args.value("gpu-preference") != "none") { + #if defined(CORRADE_TARGET_WINDOWS) || (defined(CORRADE_TARGET_UNIX) && !defined(CORRADE_TARGET_APPLE) && !defined(CORRADE_TARGET_ANDROID)) + Int enable = 0; + if(args.value("gpu-preference") == "integrated") enable = 0; + else if(args.value("gpu-preference") == "dedicated") enable = 1; + else Fatal{} << "Unsupported value for --magnum-gpu-preference. Allowed values are none|integrated|dedicated."; + + std::ostream* verbose = args.value("log") == "verbose" ? Debug::output() : nullptr; + + /* On Windows we'll try to access the NvOptimusEnablement / + AmdPowerXpressRequestHighPerformance defined by the executable -- + GetModuleHandle(nullptr) is supposed to open a handle to the exe + (and not the DLL) */ + #ifdef CORRADE_TARGET_WINDOWS + HMODULE const self = GetModuleHandleA(nullptr); + Int* const nvOptimusEnablement = reinterpret_cast(GetProcAddress(self, "NvOptimusEnablement")); + Int* const amdPowerXpressRequestHighPerformance = reinterpret_cast(GetProcAddress(self, "AmdPowerXpressRequestHighPerformance")); + if(!nvOptimusEnablement && !amdPowerXpressRequestHighPerformance) + Warning{} << "GL::Context: neither NvOptimusEnablement nor AmdPowerXpressRequestHighPerformance symbols found, can't set GPU preference"; + if(nvOptimusEnablement) *nvOptimusEnablement = enable; + if(amdPowerXpressRequestHighPerformance) *amdPowerXpressRequestHighPerformance = enable; + Debug{verbose} << "GL::Context: setting NvOptimusEnablement / AmdPowerXpressRequestHighPerformance symbols to" << enable; + + /* On Linux (BSD, etc., except Apple platforms or Android) we'll try to + set the DRI_PRIME environment variable. If the system is running on + Mesa drivers supporting PRIME, it'll cause it to switch. The + variable is only set for the lifetime of the application, thus + doesn't affect other apps. */ + #elif defined(CORRADE_TARGET_UNIX) && !defined(CORRADE_TARGET_APPLE) && !defined(CORRADE_TARGET_ANDROID) + setenv("DRI_PRIME", enable ? "1" : "0", 1); + Debug{verbose} << "GL::Context: setting DRI_PRIME environment variable to" << enable; + #else + #error unhandled platform for GPU preference setup + #endif + #else + Warning{} << "GL::Context: setting GPU preference is not supported on this platform"; + #endif + } } Context::Context(Context&& other): _version{other._version}, diff --git a/src/Magnum/GL/Context.h b/src/Magnum/GL/Context.h index 51d91d45ae..07f276a315 100644 --- a/src/Magnum/GL/Context.h +++ b/src/Magnum/GL/Context.h @@ -134,11 +134,30 @@ either from the `Platform::*Application` classes or from the (environment: `MAGNUM_DISABLE_EXTENSIONS`) - `--magnum-log default|quiet|verbose` --- console logging (environment: `MAGNUM_LOG`) (default: `default`) +- `--magnum-gpu-preference none|integrated|dedicated` GPU preference + (environment: `MAGNUM_GPU_PREFERENCE`) (default: `none`) Note that all options are prefixed with `--magnum-` to avoid conflicts with options passed to the application itself. Options that don't have this prefix are completely ignored, see documentation of the @ref Utility-Arguments-delegating "Utility::Arguments" class for details. + +The GPU preference allows you to request either the integrated or dedicated GPU +on systems having both onboard and dedicated GPUs. Currently this works only +on Windows and Linux / BSD systems, on other systems this option prints a +warning and otherwise does nothing. + +- On Windows this is done by querying the `NvOptimusEnablement` and + `AmdPowerXpressRequestHighPerformance` symbols in the application + executable and, if they exist, setting them to either `0` for the + `integrated` option or `1` for the `dedicated` option. The symbols have to + be exported by the application executable (not a dynamic library). These + symbols are exported by default when you link to one of the (static) + `*Context` or `*Application` libraries, and can be controlled using the + `BUILD_GPU_PREFERENCE_SYMBOLS` CMake option when building Magnum (see + @ref building-features for more information). +- On Linux and BSD this affects the `DRI_PRIME` environment variable that's + used by Mesa drivers that support PRIME. */ class MAGNUM_GL_EXPORT Context { public: diff --git a/src/Magnum/Platform/CMakeLists.txt b/src/Magnum/Platform/CMakeLists.txt index 5cef925278..a2a23d86b0 100644 --- a/src/Magnum/Platform/CMakeLists.txt +++ b/src/Magnum/Platform/CMakeLists.txt @@ -662,6 +662,15 @@ endif() set(MagnumContext_SRCS ) if(NOT CORRADE_TARGET_IOS) list(APPEND MagnumContext_SRCS Implementation/OpenGLFunctionLoader.cpp) + + if(CORRADE_TARGET_WINDOWS) + # GPU preference symbol export in OpenGLFunctionLoader.cpp + if(BUILD_GPU_PREFERENCE_SYMBOLS) + set(_MAGNUM_BUILD_GPU_PREFERENCE_SYMBOLS 1) + endif() + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Implementation/configure.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/Implementation/configure.h) + endif() endif() if(NOT MAGNUM_TARGET_GLES) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GL/flextGLPlatform.cpp) diff --git a/src/Magnum/Platform/Implementation/OpenGLFunctionLoader.cpp b/src/Magnum/Platform/Implementation/OpenGLFunctionLoader.cpp index b5bd918909..377ef8e5b8 100644 --- a/src/Magnum/Platform/Implementation/OpenGLFunctionLoader.cpp +++ b/src/Magnum/Platform/Implementation/OpenGLFunctionLoader.cpp @@ -51,6 +51,30 @@ #error unsupported platform #endif +#ifdef CORRADE_TARGET_WINDOWS +#include "Magnum/Platform/Implementation/configure.h" + +#ifdef _MAGNUM_BUILD_GPU_PREFERENCE_SYMBOLS +static_assert(sizeof(int) == 4, "int is not 32bit?!"); + +extern "C" { + +/* Exported symbols. Default to the iGPU. These symbols are fetched from + GL::Context and set to 1 if `--magnum-gpu-preference` is set to `dedicated`. + In case the application already exports these and the symbols are + conflicting, compile Magnum with the BUILD_GPU_PREFERENCE_SYMBOLS option + disabled. */ + +/* https://community.amd.com/thread/169965 */ +__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 0; + +/* http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf */ +__declspec(dllexport) int NvOptimusEnablement = 0; + +} +#endif +#endif + namespace Magnum { namespace Platform { namespace Implementation { /* EGL-specific implementation */ @@ -83,6 +107,12 @@ auto OpenGLFunctionLoader::load(const char* const name) -> FunctionPointer { #elif defined(CORRADE_TARGET_WINDOWS) OpenGLFunctionLoader::OpenGLFunctionLoader() { library = GetModuleHandleA("OpenGL32.dll"); + + /* Make sure the linker doesn't discard these symbols */ + #ifdef _MAGNUM_BUILD_GPU_PREFERENCE_SYMBOLS + NvOptimusEnablement &= 0xffff; + AmdPowerXpressRequestHighPerformance &= 0xffff; + #endif } /** @todo closing the library is not needed? */ diff --git a/src/Magnum/Platform/Implementation/configure.h.cmake b/src/Magnum/Platform/Implementation/configure.h.cmake new file mode 100644 index 0000000000..dd6109f297 --- /dev/null +++ b/src/Magnum/Platform/Implementation/configure.h.cmake @@ -0,0 +1,26 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#cmakedefine _MAGNUM_BUILD_GPU_PREFERENCE_SYMBOLS