Graphics: apply the chosen card, or say when it could not (#8341) (#9252)

* claim: #8341

Co-authored-by: Cursor <cursoragent@cursor.com>

* Graphics: keep a preferred card when surface marks it incompatible (#8341)

Prefer the saved adapter by exact then case-insensitive name, including when only listed as surface-incompatible, and explain in Preferences when another card is still in use.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Format and clippy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
SilentWolf75
2026-10-31 12:00:52 +00:00
committed by GitHub
parent 7c013333db
commit 1bcc1125f0
4 changed files with 64 additions and 10 deletions
+6
View File
@@ -1126,6 +1126,12 @@ fn main() -> eframe::Result {
})
.collect();
let info = rs.adapter.get_info();
if !app.preferences.graphics_adapter.is_empty() && !info.name.eq_ignore_ascii_case(app.preferences.graphics_adapter.as_str()) {
eprintln!(
"SolidCraft graphics: preferred adapter {:?} was not used; using {:?} instead",
app.preferences.graphics_adapter, info.name
);
}
let samples = scene_msaa_request(saved_samples);
let supported = scene_sample_choices(depth_bits, STENCIL_BITS, |depth| {
eframe::egui_wgpu::supported_msaa_samples(&rs.adapter, &rs.device, rs.target_format, depth)
+25 -10
View File
@@ -137,8 +137,11 @@ pub struct AdapterChoice {
pub compatible: bool,
}
/// Choose only surface-compatible adapters. Names saved in preferences are exact; the
/// environment override follows wgpu's case-insensitive substring convention.
/// Choose an adapter by name. The environment override follows wgpu's case-insensitive
/// substring convention among surface-compatible adapters. A saved preference prefers an
/// exact (then case-insensitive) name among compatible adapters, and if the named card is
/// only listed as incompatible with the surface still picks it (#8341: otherwise the
/// preference is kept in the dialog while wgpu silently uses another GPU).
/// No match leaves selection to the normal wgpu request.
pub fn choose_adapter(adapters: &[AdapterChoice], environment: Option<&str>, preference: Option<&str>) -> Option<usize> {
// wgpu's environment helper panics on a missing name; an override must remain recoverable.
@@ -149,12 +152,14 @@ pub fn choose_adapter(adapters: &[AdapterChoice], environment: Option<&str>, pre
return Some(index);
}
}
if let Some(name) = preference.filter(|s| !s.is_empty())
&& let Some((index, _)) = compatible().find(|(_, a)| a.name == name)
{
return Some(index);
}
None
let name = preference.filter(|s| !s.is_empty())?;
let find = |pred: &dyn Fn(&AdapterChoice) -> bool, only_compatible: bool| {
adapters.iter().enumerate().find(|(_, a)| (!only_compatible || a.compatible) && pred(a)).map(|(i, _)| i)
};
find(&|a| a.name == name, true)
.or_else(|| find(&|a| a.name.eq_ignore_ascii_case(name), true))
.or_else(|| find(&|a| a.name == name, false))
.or_else(|| find(&|a| a.name.eq_ignore_ascii_case(name), false))
}
/// Leave Automatic on wgpu's normal request path; named choices may defer to it too.
@@ -416,7 +421,8 @@ mod tests {
assert_eq!(choose_adapter(&cards, None, Some("removed")), None);
assert_eq!(choose_adapter(&cards, Some("missing"), Some("Discrete small")), Some(1));
assert_eq!(choose_adapter(&cards, Some(""), Some("Discrete small")), Some(1));
assert_eq!(choose_adapter(&cards, None, Some("Discrete")), None, "saved names are exact");
assert_eq!(choose_adapter(&cards, None, Some("discrete small")), Some(1), "preference matches without regard to case");
assert_eq!(choose_adapter(&cards, None, Some("Discrete")), None, "a preference is the full name, not a substring");
}
#[test]
@@ -425,11 +431,20 @@ mod tests {
assert_eq!(choose_adapter(&cards, None, None), None);
assert_eq!(choose_adapter(&cards, Some(""), Some("")), None);
cards[2].compatible = false;
assert_eq!(choose_adapter(&cards, Some("large"), Some("Discrete large")), None);
// Environment still needs a compatible adapter; a preference keeps the named card (#8341).
assert_eq!(choose_adapter(&cards, Some("large"), Some("Discrete large")), Some(2));
assert_eq!(choose_adapter(&cards, Some("small"), None), Some(1));
assert_eq!(choose_adapter(&[], None, None), None);
}
#[test]
fn preference_keeps_a_named_card_even_when_surface_says_incompatible() {
let mut cards = cards();
cards[1].compatible = false;
assert_eq!(choose_adapter(&cards, None, Some("Discrete small")), Some(1));
assert_eq!(choose_adapter(&cards, None, Some("discrete SMALL")), Some(1));
}
#[test]
fn automatic_adapter_preserves_the_previous_request_configuration() {
use eframe::egui_wgpu::WgpuSetupCreateNew;
@@ -0,0 +1,26 @@
{
"en": {
"prefs.graphics_card_unused": "The saved graphics card was not used for this window; the card in use is shown in Help › About and in the status line. Try Automatic, or set WGPU_ADAPTER_NAME to a unique part of the card's name."
},
"ar": {
"prefs.graphics_card_unused": "لم تُستخدم بطاقة الرسوميات المحفوظة لهذه النافذة؛ البطاقة المستخدمة تظهر في مساعدة › حول وفي شريط الحالة. جرّب تلقائي، أو عيّن WGPU_ADAPTER_NAME إلى جزء فريد من اسم البطاقة."
},
"de": {
"prefs.graphics_card_unused": "Die gespeicherte Grafikkarte wurde für dieses Fenster nicht verwendet; die tatsächlich genutzte Karte steht unter Hilfe › Info und in der Statuszeile. Versuchen Sie Automatisch, oder setzen Sie WGPU_ADAPTER_NAME auf einen eindeutigen Teil des Kartennamens."
},
"he": {
"prefs.graphics_card_unused": "כרטיס המסך השמור לא שימש לחלון זה; הכרטיס שבשימוש מוצג בעזרה › אודות ובשורת המצב. נסו אוטומטי, או הגדירו את WGPU_ADAPTER_NAME לחלק ייחודי משם הכרטיס."
},
"ja": {
"prefs.graphics_card_unused": "保存したグラフィックスカードはこのウィンドウでは使われていません。使用中のカードは「ヘルプ › SolidCraft について」とステータス行に表示されます。自動を試すか、カード名の一意な部分を WGPU_ADAPTER_NAME に設定してください。"
},
"vi": {
"prefs.graphics_card_unused": "Card đồ họa đã lưu không được dùng cho cửa sổ này; card đang dùng hiện trong Trợ giúp › Giới thiệu và trên dòng trạng thái. Thử Tự động, hoặc đặt WGPU_ADAPTER_NAME thành phần tên card mang tính duy nhất."
},
"zh": {
"prefs.graphics_card_unused": "已保存的显卡未用于此窗口;实际使用的显卡显示在“帮助 › 关于”和状态栏中。请尝试“自动”,或将 WGPU_ADAPTER_NAME 设为显卡名称中的唯一部分。"
},
"zh-TW": {
"prefs.graphics_card_unused": "已儲存的顯示卡未用於此視窗;實際使用的顯示卡顯示在「說明 › 關於」和狀態列中。請嘗試「自動」,或將 WGPU_ADAPTER_NAME 設為顯示卡名稱中的唯一部分。"
}
}
+7
View File
@@ -31,6 +31,13 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
ui_kit::text(ui, app.tr("prefs.graphics_card_restart"), ui_kit::Text::Hint("graphics_card_restart"));
if missing {
ui_kit::text(ui, app.tr("prefs.graphics_card_missing"), ui_kit::Text::Hint("graphics_card_missing"));
} else if !app.preferences.graphics_adapter.is_empty() {
// Status / Help show the adapter actually in use; the dropdown shows the saved
// preference. When they differ the saved card was not applied (#8341).
let active = app.viewport.adapter.rsplit(" · ").next().unwrap_or("");
if !active.is_empty() && !active.eq_ignore_ascii_case(&app.preferences.graphics_adapter) {
ui_kit::text(ui, app.tr("prefs.graphics_card_unused"), ui_kit::Text::Hint("graphics_card_unused"));
}
}
});
}