feat: Add a mechanism to select the integrated GPU on Windows (#10713) 986c92af9a Co-authored-by: Chris Dalton <99840794+csmartdalton@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head index 9f9cb77..8a9bed8 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -608fb2781f361c0c795b4522ac6d9662eb42b4b8 +986c92af9ab29f5c68bb3a6a39c1f05924683028
diff --git a/renderer/path_fiddle/fiddle_context_vulkan.cpp b/renderer/path_fiddle/fiddle_context_vulkan.cpp index ceda390..d868c99 100644 --- a/renderer/path_fiddle/fiddle_context_vulkan.cpp +++ b/renderer/path_fiddle/fiddle_context_vulkan.cpp
@@ -59,6 +59,7 @@ *m_instance, VulkanDevice::Options{ .coreFeaturesOnly = options.coreFeaturesOnly, + .gpuNameFilter = options.gpuNameFilter, }); m_renderContext = RenderContextVulkanImpl::MakeContext(
diff --git a/renderer/path_fiddle/path_fiddle.cpp b/renderer/path_fiddle/path_fiddle.cpp index dd46564..ac7929e 100644 --- a/renderer/path_fiddle/path_fiddle.cpp +++ b/renderer/path_fiddle/path_fiddle.cpp
@@ -43,6 +43,24 @@ "Linux" #endif "/vk_swiftshader_icd.json"; + +#ifdef _WIN32 +extern "C" +{ + // https://stackoverflow.com/questions/68469954/how-to-choose-specific-gpu-when-create-opengl-context: + // + // OpenGL, or rather the Win32 GDI integration of it, doesn't offer means + // to explicitly select the desired device. However the drivers of Nvidia + // and AMD offer a workaround to have programs select, that they prefer to + // execute on the discrete GPU rather than the CPU integrated one. + // + // These also appear to select the discrete "Arc" GPU on an Intel system, + // and to influence the GPU selection on D3D11. + __declspec(dllexport) uint32_t NvOptimusEnablement = 1; + __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} +#endif + static FiddleContextOptions options; static GLFWwindow* window = nullptr; static int msaa = 0; @@ -612,10 +630,14 @@ { options.enableVulkanValidationLayers = true; } - else if (!strcmp(argv[i], "--gpu") || !strcmp(argv[i], "-G")) + else if (!strcmp(argv[i], "--gpu") || !strcmp(argv[i], "-g")) { options.gpuNameFilter = argv[++i]; } + else if (!strcmp(argv[i], "--integrated") || !strcmp(argv[i], "-i")) + { + options.gpuNameFilter = "integrated"; + } else if (!strncmp(argv[i], "--", 2)) { fprintf(stderr, "Unknown command-line option %s\n", argv[i]); @@ -627,6 +649,21 @@ } } +#ifdef _WIN32 + // Set our backdoor GPU selection variables in case the API doesn't allow us + // to select explicitly. + if (options.gpuNameFilter != nullptr) + { + // "i" and "integrated" are special-case gpuNameFilters that mean "use + // the integrated GPU". + bool wantIntegratedGPU = static_cast<uint32_t>( + strcmp(options.gpuNameFilter, "integrated") == 0 || + strcmp(options.gpuNameFilter, "i") == 0); + NvOptimusEnablement = !wantIntegratedGPU; + AmdPowerXpressRequestHighPerformance = !wantIntegratedGPU; + } +#endif + glfwSetErrorCallback(glfw_error_callback); if (!glfwInit())
diff --git a/renderer/rive_vk_bootstrap/src/vulkan_device.cpp b/renderer/rive_vk_bootstrap/src/vulkan_device.cpp index 699720b..bf7939c 100644 --- a/renderer/rive_vk_bootstrap/src/vulkan_device.cpp +++ b/renderer/rive_vk_bootstrap/src/vulkan_device.cpp
@@ -249,6 +249,16 @@ nameFilter = nullptr; } + auto desiredDeviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + if (nameFilter != nullptr && + (strcmp(nameFilter, "integrated") == 0 || strcmp(nameFilter, "i") == 0)) + { + // "i" and "integrated" are special-case nameFilters that mean "use the + // integrated GPU". + desiredDeviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; + nameFilter = nullptr; + } + std::vector<VkPhysicalDevice> physicalDevices; { DEFINE_AND_LOAD_INSTANCE_FUNC(vkEnumeratePhysicalDevices, instance); @@ -344,9 +354,9 @@ } else { - // Without a filter we are going to search for any device, but do a - // first pass looking for discrete devices only. - for (auto onlyAcceptDiscrete : {true, false}) + // Without a nameFilter we are going to search for any device, but do a + // first pass looking for the desired type of devices only. + for (auto onlyAcceptDesiredType : {true, false}) { for (const auto& device : physicalDevices) { @@ -361,8 +371,8 @@ continue; } - if (!onlyAcceptDiscrete || - props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) + if (!onlyAcceptDesiredType || + props.deviceType == desiredDeviceType) { return { .physicalDevice = device, @@ -479,4 +489,4 @@ void VulkanDevice::waitUntilIdle() const { m_vkDeviceWaitIdle(m_device); } -} // namespace rive_vkb \ No newline at end of file +} // namespace rive_vkb
diff --git a/tests/common/testing_window.cpp b/tests/common/testing_window.cpp index eb8ed85..cc4fd5d 100644 --- a/tests/common/testing_window.cpp +++ b/tests/common/testing_window.cpp
@@ -12,6 +12,23 @@ #include <windows.h> #endif +#ifdef _WIN32 +extern "C" +{ + // https://stackoverflow.com/questions/68469954/how-to-choose-specific-gpu-when-create-opengl-context: + // + // OpenGL, or rather the Win32 GDI integration of it, doesn't offer means + // to explicitly select the desired device. However the drivers of Nvidia + // and AMD offer a workaround to have programs select, that they prefer to + // execute on the discrete GPU rather than the CPU integrated one. + // + // These also appear to select the discrete "Arc" GPU on an Intel system, + // and to influence the GPU selection on D3D11. + __declspec(dllexport) uint32_t NvOptimusEnablement = 1; + __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} +#endif + // Call TestingWindow::Destroy if you want to delete the window singleton TestingWindow* s_TestingWindow = nullptr; @@ -239,10 +256,26 @@ Visibility visibility, void* platformWindow) { - if (backend == Backend::rhi) - assert(s_TestingWindow); - else - assert(!s_TestingWindow); + assert((backend == Backend::rhi) == (s_TestingWindow != nullptr)); + +#ifdef _WIN32 + // Set our backdoor GPU selection variables in case the API doesn't + // allow us to select explicitly. + const char* nameFilter = backendParams.gpuNameFilter.c_str(); + if (const char* gpuFromEnv = getenv("RIVE_GPU"); gpuFromEnv != nullptr) + { + // Override the program's GPU filter with one from the environment if + // it's set. + nameFilter = gpuFromEnv; + } + // "i" and "integrated" are special-case gpuNameFilters that mean "use + // the integrated GPU". + bool wantIntegratedGPU = static_cast<uint32_t>( + strcmp(nameFilter, "integrated") == 0 || strcmp(nameFilter, "i") == 0); + NvOptimusEnablement = !wantIntegratedGPU; + AmdPowerXpressRequestHighPerformance = !wantIntegratedGPU; +#endif + switch (backend) { case Backend::gl: