diff --git a/include/rex/ui/d3d12/d3d12_api.h b/include/rex/ui/d3d12/d3d12_api.h index d0e7252..788a407 100644 --- a/include/rex/ui/d3d12/d3d12_api.h +++ b/include/rex/ui/d3d12/d3d12_api.h @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include // For Microsoft::WRL::ComPtr. #include diff --git a/include/rex/ui/graphics_device_list.h b/include/rex/ui/graphics_device_list.h new file mode 100644 index 0000000..6a90d39 --- /dev/null +++ b/include/rex/ui/graphics_device_list.h @@ -0,0 +1,28 @@ +/** + * @file rex/ui/graphics_device_list.h + * + * @brief Registry of the graphics devices the active backend enumerated. + */ +#pragma once + +#include +#include + +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 device_names; +}; + +void SetGraphicsDeviceList(GraphicsDeviceList list); +GraphicsDeviceList GetGraphicsDeviceList(); + +} // namespace rex::ui diff --git a/include/rex/ui/overlay/simple_settings_overlay.h b/include/rex/ui/overlay/simple_settings_overlay.h index 8c13d7d..37c2df4 100644 --- a/include/rex/ui/overlay/simple_settings_overlay.h +++ b/include/rex/ui/overlay/simple_settings_overlay.h @@ -10,6 +10,7 @@ #include #include +#include #include 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; diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 1df1d90..beb5909 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -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 diff --git a/src/ui/d3d12/d3d12_provider.cpp b/src/ui/d3d12/d3d12_provider.cpp index bf8252b..b501e5c 100644 --- a/src/ui/d3d12/d3d12_provider.cpp +++ b/src/ui/d3d12/d3d12_provider.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -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), diff --git a/src/ui/graphics_device_list.cpp b/src/ui/graphics_device_list.cpp new file mode 100644 index 0000000..b081c09 --- /dev/null +++ b/src/ui/graphics_device_list.cpp @@ -0,0 +1,29 @@ +/** + * @file ui/graphics_device_list.cpp + * + * @brief Registry of the graphics devices the active backend enumerated. + */ +#include + +#include +#include + +namespace rex::ui { +namespace { + +std::mutex g_graphics_device_list_mutex; +GraphicsDeviceList g_graphics_device_list; + +} // namespace + +void SetGraphicsDeviceList(GraphicsDeviceList list) { + std::lock_guard lock(g_graphics_device_list_mutex); + g_graphics_device_list = std::move(list); +} + +GraphicsDeviceList GetGraphicsDeviceList() { + std::lock_guard lock(g_graphics_device_list_mutex); + return g_graphics_device_list; +} + +} // namespace rex::ui diff --git a/src/ui/overlay/simple_settings_overlay.cpp b/src/ui/overlay/simple_settings_overlay.cpp index ade8ef6..1a638d1 100644 --- a/src/ui/overlay/simple_settings_overlay.cpp +++ b/src/ui/overlay/simple_settings_overlay.cpp @@ -41,8 +41,16 @@ bool HasCvar(std::string_view name) { std::vector GetSimpleSettingsCvars() { std::vector 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 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(device_list.cvar_name); + if (current >= 0 && current < static_cast(device_list.device_names.size())) { + return current + 1; + } + return 0; +} + int ResolutionIndexFromCvar() { int32_t current = rex::cvar::Query("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("skate3_ultrawide")) || @@ -297,6 +320,12 @@ void SimpleSettingsDialog::SaveVideo() { aspect_ratio_index_ = std::clamp(aspect_ratio_index_, 0, static_cast(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(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 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 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(device_labels.size())); + EndFieldRow(); + } + BeginFieldRow("Resolution Scale"); ImGui::Combo("##resolution_scale", &resolution_scale_index_, kResolutionLabels.data(), static_cast(kResolutionLabels.size())); diff --git a/src/ui/vulkan/vulkan_provider.cpp b/src/ui/vulkan/vulkan_provider.cpp index 9eeb902..359e6f7 100644 --- a/src/ui/vulkan/vulkan_provider.cpp +++ b/src/ui/vulkan/vulkan_provider.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -67,12 +68,18 @@ std::unique_ptr 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::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::Create(const bool with_gpu_emula std::vector 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}); }