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: