ui: device picker shows the auto-selected adapter; no wrap on stepper clicks

This commit is contained in:
mchughalex 2026-07-17 06:55:12 +01:00
parent 855ed66c82
commit 2cab1be8eb
4 changed files with 82 additions and 23 deletions

View file

@ -20,6 +20,10 @@ struct GraphicsDeviceList {
// "vulkan_device"); empty when no backend has published a list. // "vulkan_device"); empty when no backend has published a list.
std::string cvar_name; std::string cvar_name;
std::vector<std::string> device_names; std::vector<std::string> device_names;
// Index (in device_names / cvar order) of the device the backend actually
// selected this session; -1 while unknown. Lets UI show the device that
// automatic selection resolved to.
int32_t active_index = -1;
}; };
void SetGraphicsDeviceList(GraphicsDeviceList list); void SetGraphicsDeviceList(GraphicsDeviceList list);

View file

@ -345,6 +345,28 @@ bool D3D12Provider::Initialize() {
dxgi_factory->Release(); dxgi_factory->Release();
return false; return false;
} }
// Publish which cvar-order index the selection landed on (the automatic
// path walks a preference-ordered enumeration, so its index does not match
// the published list) so the settings device picker can show the adapter
// automatic selection resolved to. Matched by LUID against the plain
// enumeration the list was built from.
{
GraphicsDeviceList device_list = GetGraphicsDeviceList();
IDXGIAdapter1* match_adapter;
for (uint32_t i = 0; dxgi_factory->EnumAdapters1(i, &match_adapter) == S_OK; ++i) {
DXGI_ADAPTER_DESC1 match_desc;
const bool matched =
SUCCEEDED(match_adapter->GetDesc1(&match_desc)) &&
match_desc.AdapterLuid.LowPart == adapter_desc.AdapterLuid.LowPart &&
match_desc.AdapterLuid.HighPart == adapter_desc.AdapterLuid.HighPart;
match_adapter->Release();
if (matched) {
device_list.active_index = int32_t(i);
break;
}
}
SetGraphicsDeviceList(std::move(device_list));
}
adapter_vendor_id_ = GpuVendorID(adapter_desc.VendorId); adapter_vendor_id_ = GpuVendorID(adapter_desc.VendorId);
int adapter_name_mb_size = int adapter_name_mb_size =
WideCharToMultiByte(CP_UTF8, 0, adapter_desc.Description, -1, nullptr, 0, nullptr, nullptr); WideCharToMultiByte(CP_UTF8, 0, adapter_desc.Description, -1, nullptr, 0, nullptr, nullptr);

View file

@ -220,16 +220,19 @@ std::vector<std::string_view> GetSimpleSettingsCvars() {
return cvars; return cvars;
} }
// 0 = automatic selection (cvar -1); i+1 = the list's device i. // The picker lists devices directly (no separate automatic entry):
// automatic selection (cvar -1) reads as the device the backend reported
// actually picking.
int DeviceIndexFromCvar(const GraphicsDeviceList& device_list) { int DeviceIndexFromCvar(const GraphicsDeviceList& device_list) {
if (device_list.device_names.empty() || !HasCvar(device_list.cvar_name)) { if (device_list.device_names.empty() || !HasCvar(device_list.cvar_name)) {
return 0; return 0;
} }
int32_t current = rex::cvar::Query<int32_t>(device_list.cvar_name); 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())) { if (current >= 0 && current < static_cast<int32_t>(device_list.device_names.size())) {
return current + 1; return current;
} }
return 0; return std::clamp(device_list.active_index, 0,
static_cast<int32_t>(device_list.device_names.size()) - 1);
} }
int ResolutionIndexFromScale(int32_t scale) { int ResolutionIndexFromScale(int32_t scale) {
@ -952,10 +955,14 @@ void SimpleSettingsDialog::SaveVideo() {
std::clamp(aspect_ratio_index_, 0, static_cast<int>(kAspectRatioLabels.size()) - 1); std::clamp(aspect_ratio_index_, 0, static_cast<int>(kAspectRatioLabels.size()) - 1);
field_of_view_ = std::clamp(field_of_view_, 40.0f, 120.0f); field_of_view_ = std::clamp(field_of_view_, 40.0f, 120.0f);
if (!device_list_.device_names.empty() && HasCvar(device_list_.cvar_name)) { if (!device_list_.device_names.empty() && HasCvar(device_list_.cvar_name)) {
device_index_ = device_index_ = std::clamp(device_index_, 0,
std::clamp(device_index_, 0, static_cast<int>(device_list_.device_names.size())); static_cast<int>(device_list_.device_names.size()) - 1);
// Selecting the device automatic selection already picked keeps the cvar
// automatic, so new hardware still re-picks the best adapter.
rex::cvar::SetFlagByName(device_list_.cvar_name, rex::cvar::SetFlagByName(device_list_.cvar_name,
device_index_ == 0 ? "-1" : std::to_string(device_index_ - 1)); device_index_ == device_list_.active_index
? "-1"
: std::to_string(device_index_));
} }
const auto scale = std::to_string(kResolutionScales[resolution_scale_index_]); const auto scale = std::to_string(kResolutionScales[resolution_scale_index_]);
rex::cvar::SetFlagByName("resolution_scale", scale); rex::cvar::SetFlagByName("resolution_scale", scale);
@ -1058,19 +1065,20 @@ void SimpleSettingsDialog::BuildRows(std::vector<RowSpec>& rows, int category) {
row.kind = RowSpec::kEnum; row.kind = RowSpec::kEnum;
row.label = "Graphics Device"; row.label = "Graphics Device";
row.desc = row.desc =
"Select which GPU renders the game. Auto picks the highest-performance adapter."; "Select which GPU renders the game. The highest-performance "
"adapter is selected automatically.";
// Index-prefixed labels: virtual display drivers (Parsec etc.) make // Index-prefixed labels: virtual display drivers (Parsec etc.) make
// the same physical GPU enumerate several times, and the index is // the same physical GPU enumerate several times, and the index is
// what the device cvar and the startup log speak in. // what the device cvar and the startup log speak in. No separate
row.options.push_back("Auto (recommended)"); // automatic entry; the row shows the adapter automatic selection
// resolved to, and re-selecting it keeps the choice automatic.
for (size_t i = 0; i < device_list_.device_names.size(); ++i) { for (size_t i = 0; i < device_list_.device_names.size(); ++i) {
row.options.push_back(std::to_string(i) + ": " + device_list_.device_names[i]); row.options.push_back(std::to_string(i) + ": " + device_list_.device_names[i]);
} }
row.index = &device_index_; row.index = &device_index_;
if (device_index_ > 0 && device_index_ <= int(device_list_.device_names.size())) { row.reset = [this] {
row.desc_extra = "Current: " + row.options[device_index_]; device_index_ = std::max(device_list_.active_index, 0);
} };
row.reset = [this] { device_index_ = 0; };
rows.push_back(std::move(row)); rows.push_back(std::move(row));
} }
{ {
@ -2333,12 +2341,17 @@ void SimpleSettingsDialog::OnDraw(ImGuiIO& io) {
// a circle greys out (and stops responding) at its end of the range. // a circle greys out (and stops responding) at its end of the range.
const bool can_left = row.enabled && value > 0; const bool can_left = row.enabled && value > 0;
const bool can_right = row.enabled && value < count - 1; const bool can_right = row.enabled && value < count - 1;
bool left_hover = can_left && // Hit areas are tracked separately from enablement: a click on a
mouse_in(left_center.x - chevron_r - 4.0f, cy - chevron_r - 4.0f, // greyed-out stepper must be swallowed, not fall through to the
left_center.x + chevron_r + 4.0f, cy + chevron_r + 4.0f); // row-body wrap-around cycle below.
bool right_hover = can_right && const bool left_hit =
mouse_in(right_center.x - chevron_r - 4.0f, cy - chevron_r - 4.0f, mouse_in(left_center.x - chevron_r - 4.0f, cy - chevron_r - 4.0f,
right_center.x + chevron_r + 4.0f, cy + chevron_r + 4.0f); left_center.x + chevron_r + 4.0f, cy + chevron_r + 4.0f);
const bool right_hit =
mouse_in(right_center.x - chevron_r - 4.0f, cy - chevron_r - 4.0f,
right_center.x + chevron_r + 4.0f, cy + chevron_r + 4.0f);
bool left_hover = can_left && left_hit;
bool right_hover = can_right && right_hit;
const ImU32 chev_disabled = focused ? kColChevDisabledOnDark : kColChevDisabled; const ImU32 chev_disabled = focused ? kColChevDisabledOnDark : kColChevDisabled;
DrawChevron(dl, left_center, chevron_r, true, DrawChevron(dl, left_center, chevron_r, true,
can_left ? (left_hover ? kColInteractHover : kColInteract) can_left ? (left_hover ? kColInteractHover : kColInteract)
@ -2369,10 +2382,14 @@ void SimpleSettingsDialog::OnDraw(ImGuiIO& io) {
text.c_str()); text.c_str());
} }
if (clicked && row.enabled) { if (clicked && row.enabled) {
if (left_hover) { if (left_hit) {
step_row(row, -1); if (can_left) {
} else if (right_hover) { step_row(row, -1);
step_row(row, 1); }
} else if (right_hit) {
if (can_right) {
step_row(row, 1);
}
} else if (hovered) { } else if (hovered) {
activate_row(row); // row-body click cycles with wrap-around activate_row(row); // row-body click cycles with wrap-around
} }

View file

@ -165,6 +165,22 @@ std::unique_ptr<VulkanProvider> VulkanProvider::Create(const bool with_gpu_emula
} }
} }
// Publish which enumeration index the selection landed on (the automatic
// path walks a preference-ordered copy of the list) so the settings device
// picker can show the device automatic selection resolved to.
{
const VkPhysicalDevice active_physical_device =
provider->vulkan_device_->physical_device();
for (size_t i = 0; i < physical_devices.size(); ++i) {
if (physical_devices[i] == active_physical_device) {
GraphicsDeviceList device_list = GetGraphicsDeviceList();
device_list.active_index = int32_t(i);
SetGraphicsDeviceList(std::move(device_list));
break;
}
}
}
if (with_presentation) { if (with_presentation) {
provider->ui_samplers_ = UISamplers::Create(provider->vulkan_device_.get()); provider->ui_samplers_ = UISamplers::Create(provider->vulkan_device_.get());
if (!provider->ui_samplers_) { if (!provider->ui_samplers_) {