GPU selection: prefer the discrete GPU + settings device picker

This commit is contained in:
mchughalex 2026-07-10 22:51:13 +01:00
parent 96de5455f2
commit ef55119407
8 changed files with 209 additions and 25 deletions

View file

@ -25,7 +25,7 @@
#include <d3d12.h>
#include <d3d12sdklayers.h>
#include <d3dcompiler.h>
#include <dxgi1_5.h>
#include <dxgi1_6.h>
#include <dxgidebug.h>
// For Microsoft::WRL::ComPtr.
#include <wrl/client.h>

View file

@ -0,0 +1,28 @@
/**
* @file rex/ui/graphics_device_list.h
*
* @brief Registry of the graphics devices the active backend enumerated.
*/
#pragma once
#include <string>
#include <vector>
namespace rex::ui {
// Devices the active graphics backend enumerated during initialization, in
// cvar index order: element i is selected by setting the backend's device
// cvar to i, and -1 selects automatically. Published by the provider so UI
// like the settings overlay can offer a device picker without linking against
// a specific backend.
struct GraphicsDeviceList {
// Init-only int32 cvar that selects a device by index ("d3d12_adapter" or
// "vulkan_device"); empty when no backend has published a list.
std::string cvar_name;
std::vector<std::string> device_names;
};
void SetGraphicsDeviceList(GraphicsDeviceList list);
GraphicsDeviceList GetGraphicsDeviceList();
} // namespace rex::ui

View file

@ -10,6 +10,7 @@
#include <string>
#include <vector>
#include <rex/ui/graphics_device_list.h>
#include <rex/ui/imgui_dialog.h>
namespace rex::ui {
@ -66,6 +67,8 @@ class SimpleSettingsDialog final : public ImGuiDialog {
RestartGameCallback restart_game_;
SimpleProfileState profiles_;
bool visible_ = false;
GraphicsDeviceList device_list_;
int device_index_ = 0;
int resolution_scale_index_ = 0;
int frame_cap_index_ = 0;
int aspect_ratio_index_ = 0;

View file

@ -2,6 +2,7 @@
# Core UI sources
set(REXUI_CORE_SOURCES
graphics_device_list.cpp
graphics_upload_buffer_pool.cpp
graphics_util.cpp
imgui_dialog.cpp

View file

@ -14,6 +14,7 @@
#include <rex/cvar.h>
#include <rex/logging.h>
#include <rex/math.h>
#include <rex/ui/graphics_device_list.h>
#include <rex/ui/d3d12/d3d12_immediate_drawer.h>
#include <rex/ui/d3d12/d3d12_presenter.h>
#include <rex/ui/d3d12/d3d12_provider.h>
@ -250,10 +251,63 @@ bool D3D12Provider::Initialize() {
return false;
}
// Log all adapters so GPU-selection problems are diagnosable from user logs,
// and publish them for the settings UI device picker (indices match the
// 'd3d12_adapter' cvar).
{
GraphicsDeviceList device_list;
device_list.cvar_name = "d3d12_adapter";
IDXGIAdapter1* logged_adapter;
for (uint32_t i = 0; dxgi_factory->EnumAdapters1(i, &logged_adapter) == S_OK; ++i) {
std::string device_name = "Adapter " + std::to_string(i);
DXGI_ADAPTER_DESC1 logged_desc;
if (SUCCEEDED(logged_adapter->GetDesc1(&logged_desc))) {
char logged_name_mb[512] = {};
WideCharToMultiByte(CP_UTF8, 0, logged_desc.Description, -1, logged_name_mb,
sizeof(logged_name_mb) - 1, nullptr, nullptr);
if (logged_name_mb[0]) {
device_name = logged_name_mb;
}
REXGPU_INFO("DXGI adapter {}: {} (vendor 0x{:04X}, device 0x{:04X}){}", i, logged_name_mb,
logged_desc.VendorId, logged_desc.DeviceId,
(logged_desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) ? " [software]" : "");
}
device_list.device_names.push_back(std::move(device_name));
logged_adapter->Release();
}
SetGraphicsDeviceList(std::move(device_list));
}
// Choose the adapter.
uint32_t adapter_index = 0;
IDXGIAdapter1* adapter = nullptr;
while (dxgi_factory->EnumAdapters1(adapter_index, &adapter) == S_OK) {
// With no explicit adapter requested, ask DXGI for adapters sorted by GPU
// preference so hybrid-graphics systems (laptop Optimus / PowerXpress) pick
// the discrete GPU instead of whichever adapter enumerates first (usually
// the integrated one the display is wired to). IDXGIFactory6 requires
// Windows 10 1803+; fall back to plain enumeration order without it.
if (REXCVAR_GET(d3d12_adapter) == -1) {
IDXGIFactory6* dxgi_factory6 = nullptr;
if (SUCCEEDED(dxgi_factory->QueryInterface(IID_PPV_ARGS(&dxgi_factory6)))) {
while (dxgi_factory6->EnumAdapterByGpuPreference(adapter_index,
DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE,
IID_PPV_ARGS(&adapter)) == S_OK) {
DXGI_ADAPTER_DESC1 adapter_desc;
if (SUCCEEDED(adapter->GetDesc1(&adapter_desc)) &&
!(adapter_desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) &&
SUCCEEDED(pfn_d3d12_create_device_(adapter, D3D_FEATURE_LEVEL_11_0,
_uuidof(ID3D12Device), nullptr))) {
break;
}
adapter->Release();
adapter = nullptr;
++adapter_index;
}
dxgi_factory6->Release();
}
}
adapter_index = 0;
while (adapter == nullptr && dxgi_factory->EnumAdapters1(adapter_index, &adapter) == S_OK) {
DXGI_ADAPTER_DESC1 adapter_desc;
if (SUCCEEDED(adapter->GetDesc1(&adapter_desc))) {
if (SUCCEEDED(pfn_d3d12_create_device_(adapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device),

View file

@ -0,0 +1,29 @@
/**
* @file ui/graphics_device_list.cpp
*
* @brief Registry of the graphics devices the active backend enumerated.
*/
#include <rex/ui/graphics_device_list.h>
#include <mutex>
#include <utility>
namespace rex::ui {
namespace {
std::mutex g_graphics_device_list_mutex;
GraphicsDeviceList g_graphics_device_list;
} // namespace
void SetGraphicsDeviceList(GraphicsDeviceList list) {
std::lock_guard<std::mutex> lock(g_graphics_device_list_mutex);
g_graphics_device_list = std::move(list);
}
GraphicsDeviceList GetGraphicsDeviceList() {
std::lock_guard<std::mutex> lock(g_graphics_device_list_mutex);
return g_graphics_device_list;
}
} // namespace rex::ui

View file

@ -41,8 +41,16 @@ bool HasCvar(std::string_view name) {
std::vector<std::string_view> GetSimpleSettingsCvars() {
std::vector<std::string_view> cvars;
cvars.reserve(14);
cvars.reserve(15);
cvars.insert(cvars.end(), kCoreSimpleSettingsCvars.begin(), kCoreSimpleSettingsCvars.end());
// Literal names rather than the registry's string to keep the string_views
// pointing at static storage.
const std::string device_cvar = GetGraphicsDeviceList().cvar_name;
if (device_cvar == "d3d12_adapter" && HasCvar("d3d12_adapter")) {
cvars.push_back("d3d12_adapter");
} else if (device_cvar == "vulkan_device" && HasCvar("vulkan_device")) {
cvars.push_back("vulkan_device");
}
if (HasCvar("skate3_ultrawide")) {
cvars.push_back("skate3_ultrawide");
}
@ -70,6 +78,18 @@ std::vector<std::string_view> GetSimpleSettingsCvars() {
return cvars;
}
// 0 = automatic selection (cvar -1); i+1 = the list's device i.
int DeviceIndexFromCvar(const GraphicsDeviceList& device_list) {
if (device_list.device_names.empty() || !HasCvar(device_list.cvar_name)) {
return 0;
}
int32_t current = rex::cvar::Query<int32_t>(device_list.cvar_name);
if (current >= 0 && current < static_cast<int32_t>(device_list.device_names.size())) {
return current + 1;
}
return 0;
}
int ResolutionIndexFromCvar() {
int32_t current = rex::cvar::Query<int32_t>("resolution_scale");
int best = 0;
@ -233,6 +253,8 @@ void SimpleSettingsDialog::Show() {
}
void SimpleSettingsDialog::LoadSettingsFromCvars() {
device_list_ = GetGraphicsDeviceList();
device_index_ = DeviceIndexFromCvar(device_list_);
resolution_scale_index_ = ResolutionIndexFromCvar();
frame_cap_index_ = FrameCapIndexFromCvar();
aspect_ratio_index_ =
@ -246,7 +268,8 @@ void SimpleSettingsDialog::LoadSettingsFromCvars() {
}
bool SimpleSettingsDialog::HasSettingsChanges() const {
return resolution_scale_index_ != ResolutionIndexFromCvar() ||
return device_index_ != DeviceIndexFromCvar(device_list_) ||
resolution_scale_index_ != ResolutionIndexFromCvar() ||
frame_cap_index_ != FrameCapIndexFromCvar() ||
(HasCvar("skate3_ultrawide") &&
(aspect_ratio_index_ != 0) != rex::cvar::Query<bool>("skate3_ultrawide")) ||
@ -297,6 +320,12 @@ void SimpleSettingsDialog::SaveVideo() {
aspect_ratio_index_ =
std::clamp(aspect_ratio_index_, 0, static_cast<int>(kAspectRatioLabels.size()) - 1);
field_of_view_ = std::clamp(field_of_view_, 40.0f, 120.0f);
if (!device_list_.device_names.empty() && HasCvar(device_list_.cvar_name)) {
device_index_ =
std::clamp(device_index_, 0, static_cast<int>(device_list_.device_names.size()));
rex::cvar::SetFlagByName(device_list_.cvar_name,
device_index_ == 0 ? "-1" : std::to_string(device_index_ - 1));
}
const auto scale = std::to_string(kResolutionScales[resolution_scale_index_]);
rex::cvar::SetFlagByName("resolution_scale", scale);
rex::cvar::SetFlagByName("draw_resolution_scale_x", scale);
@ -430,6 +459,27 @@ void SimpleSettingsDialog::OnDraw(ImGuiIO& io) {
if (selected_tab_ == 0) {
SectionTitle("Video");
if (!device_list_.device_names.empty() && HasCvar(device_list_.cvar_name)) {
BeginFieldRow("Graphics Device");
// Index-prefixed labels: virtual display drivers (Parsec etc.) make the
// same physical GPU enumerate several times, and the index is what the
// device cvar and the startup log speak in.
std::vector<std::string> device_label_storage;
device_label_storage.reserve(device_list_.device_names.size());
for (size_t i = 0; i < device_list_.device_names.size(); ++i) {
device_label_storage.push_back(std::to_string(i) + ": " + device_list_.device_names[i]);
}
std::vector<const char*> device_labels;
device_labels.reserve(device_label_storage.size() + 1);
device_labels.push_back("Auto (recommended)");
for (const auto& label : device_label_storage) {
device_labels.push_back(label.c_str());
}
ImGui::Combo("##graphics_device", &device_index_, device_labels.data(),
static_cast<int>(device_labels.size()));
EndFieldRow();
}
BeginFieldRow("Resolution Scale");
ImGui::Combo("##resolution_scale", &resolution_scale_index_, kResolutionLabels.data(),
static_cast<int>(kResolutionLabels.size()));

View file

@ -14,6 +14,7 @@
#include <rex/cvar.h>
#include <rex/logging.h>
#include <rex/ui/graphics_device_list.h>
#include <rex/ui/vulkan/immediate_drawer.h>
#include <rex/ui/vulkan/presenter.h>
#include <rex/ui/vulkan/provider.h>
@ -67,12 +68,18 @@ std::unique_ptr<VulkanProvider> VulkanProvider::Create(const bool with_gpu_emula
REXLOG_WARN(
"Available Vulkan physical devices (use the 'vulkan_device' "
"configuration variable to force a specific device):");
for (size_t physical_device_index = 0; physical_device_index < physical_devices.size();
++physical_device_index) {
VkPhysicalDeviceProperties physical_device_properties;
ifn.vkGetPhysicalDeviceProperties(physical_devices[physical_device_index],
&physical_device_properties);
REXLOG_WARN("* {}: {}", physical_device_index, physical_device_properties.deviceName);
{
GraphicsDeviceList device_list;
device_list.cvar_name = "vulkan_device";
for (size_t physical_device_index = 0; physical_device_index < physical_devices.size();
++physical_device_index) {
VkPhysicalDeviceProperties physical_device_properties;
ifn.vkGetPhysicalDeviceProperties(physical_devices[physical_device_index],
&physical_device_properties);
REXLOG_WARN("* {}: {}", physical_device_index, physical_device_properties.deviceName);
device_list.device_names.push_back(physical_device_properties.deviceName);
}
SetGraphicsDeviceList(std::move(device_list));
}
if (REXCVAR_GET(vulkan_device) >= 0 &&
@ -88,9 +95,7 @@ std::unique_ptr<VulkanProvider> VulkanProvider::Create(const bool with_gpu_emula
bool prefer_fragment_stores = REXCVAR_GET(vulkan_prefer_fragment_stores_and_atomics);
bool prefer_vertex_stores = REXCVAR_GET(vulkan_prefer_vertex_pipeline_stores_and_atomics);
bool prefer_fill_mode_non_solid = REXCVAR_GET(vulkan_prefer_fill_mode_non_solid);
if (with_gpu_emulation && physical_devices.size() > 1 &&
(prefer_geometry_shader || prefer_fragment_stores || prefer_vertex_stores ||
prefer_fill_mode_non_solid)) {
if (physical_devices.size() > 1) {
struct PhysicalDeviceScore {
VkPhysicalDevice physical_device;
uint32_t score;
@ -98,20 +103,34 @@ std::unique_ptr<VulkanProvider> VulkanProvider::Create(const bool with_gpu_emula
std::vector<PhysicalDeviceScore> scored_devices;
scored_devices.reserve(physical_devices.size());
for (const VkPhysicalDevice physical_device : physical_devices) {
VkPhysicalDeviceFeatures supported_features = {};
ifn.vkGetPhysicalDeviceFeatures(physical_device, &supported_features);
uint32_t score = 0;
if (prefer_geometry_shader && supported_features.geometryShader) {
++score;
if (with_gpu_emulation) {
VkPhysicalDeviceFeatures supported_features = {};
ifn.vkGetPhysicalDeviceFeatures(physical_device, &supported_features);
if (prefer_geometry_shader && supported_features.geometryShader) {
score += 4;
}
if (prefer_fragment_stores && supported_features.fragmentStoresAndAtomics) {
score += 4;
}
if (prefer_vertex_stores && supported_features.vertexPipelineStoresAndAtomics) {
score += 4;
}
if (prefer_fill_mode_non_solid && supported_features.fillModeNonSolid) {
score += 4;
}
}
if (prefer_fragment_stores && supported_features.fragmentStoresAndAtomics) {
++score;
}
if (prefer_vertex_stores && supported_features.vertexPipelineStoresAndAtomics) {
++score;
}
if (prefer_fill_mode_non_solid && supported_features.fillModeNonSolid) {
++score;
// Prefer the discrete GPU on hybrid-graphics systems. Weighted below
// the emulation feature bits so feature support still wins, but it
// breaks the tie that previously left device choice to enumeration
// order (typically the integrated GPU first on laptops).
VkPhysicalDeviceProperties physical_device_properties;
ifn.vkGetPhysicalDeviceProperties(physical_device, &physical_device_properties);
if (physical_device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
score += 2;
} else if (physical_device_properties.deviceType ==
VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) {
score += 1;
}
scored_devices.push_back({physical_device, score});
}