From 214f2fb805233116fdbdf02a7a5ac4ba5b4fcfb3 Mon Sep 17 00:00:00 2001 From: Sigmachan Date: Mon, 15 Jun 2026 04:07:06 +0300 Subject: [PATCH] render: prefer a discrete GPU when no device is pinned On hybrid systems (e.g. an integrated GPU alongside a discrete one), the physical-device selection in CVulkanDevice::selectPhysDev() has no VkSurface to filter against on the headless/standalone path, so it selects whichever device enumerates first. That is frequently the integrated GPU, causing gamescope to composite on the slower GPU. Tested on an RTX 5090 + Raphael iGPU: standalone gamescope selected the RADV iGPU instead of the discrete card. When the user has not pinned a device via --prefer-vk-device, upgrade an integrated selection to a discrete GPU. Explicit --prefer-vk-device and the surface-presentation check still take precedence, and nested selection is unchanged. --- src/rendervulkan.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp index aefc6b21e9..113d8c1402 100644 --- a/src/rendervulkan.cpp +++ b/src/rendervulkan.cpp @@ -345,6 +345,15 @@ bool CVulkanDevice::selectPhysDev(VkSurfaceKHR surface) bTryComputeOnly = false; } + // Track whether the currently-selected device is discrete, so that when no + // device is explicitly preferred we upgrade from an integrated GPU to a + // discrete one. Without this, on hybrid systems (e.g. an AMD iGPU alongside + // a discrete NVIDIA/AMD GPU) the headless/standalone path with no surface to + // filter against would pick whichever device enumerates first — frequently + // the iGPU — and composite on the wrong, slower GPU. + const bool bHavePreferredDevice = ( g_preferVendorID != 0 || g_preferDeviceID != 0 ); + bool bSelectedIsDiscrete = false; + for (auto cphysDev : physDevs) { VkPhysicalDeviceProperties deviceProperties; @@ -370,9 +379,16 @@ bool CVulkanDevice::selectPhysDev(VkSurfaceKHR surface) if (generalIndex != ~0u || computeOnlyIndex != ~0u) { - // Select the device if it's the first one or the preferred one - if (!m_physDev || - (g_preferVendorID == deviceProperties.vendorID && g_preferDeviceID == deviceProperties.deviceID)) + const bool bIsPreferred = bHavePreferredDevice && + g_preferVendorID == deviceProperties.vendorID && + g_preferDeviceID == deviceProperties.deviceID; + const bool bIsDiscrete = deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + // Upgrade integrated -> discrete only when the user hasn't pinned a device. + const bool bDiscreteUpgrade = !bHavePreferredDevice && m_physDev && !bSelectedIsDiscrete && bIsDiscrete; + + // Select the device if it's the first one, the preferred one, or a + // discrete GPU superseding an integrated pick. + if (!m_physDev || bIsPreferred || bDiscreteUpgrade) { // if we have a surface, check that the queue family can actually present on it if (surface) { @@ -397,6 +413,7 @@ bool CVulkanDevice::selectPhysDev(VkSurfaceKHR surface) m_queueFamily = computeOnlyIndex == ~0u ? generalIndex : computeOnlyIndex; m_generalQueueFamily = generalIndex; m_physDev = cphysDev; + bSelectedIsDiscrete = bIsDiscrete; /* When Intel uses compute-only queue for Gamescope composition, some games * experience performance loss. Using the general queue alleviates the issue