diff --git a/apps/designcraft/src/gpu/backend.rs b/apps/designcraft/src/gpu/backend.rs index 3cac6089..0a3cda61 100644 --- a/apps/designcraft/src/gpu/backend.rs +++ b/apps/designcraft/src/gpu/backend.rs @@ -138,17 +138,18 @@ pub fn choose(candidates: &[Backend], previous: &Record, version: &str, skipped: let first = *candidates.first()?; let same_version = previous.version == version; let mut failed: Vec = if same_version { previous.failed.clone() } else { Vec::new() }; + // A stored entry that is no candidate here came from another machine and says nothing. The + // backend the previous start was still on is added after this and stays, candidate or not: + // the instance can load a backend that is no candidate — everywhere but Windows OpenGL rides + // along beside the chosen one (`instance_backends`) — and a start that one took down must + // keep it out of the next instance. + failed.retain(|b| candidates.contains(b)); let unfinished = previous.trying.filter(|_| same_version); if let Some(b) = unfinished && !failed.contains(&b) { failed.push(b); } - // A stale entry for a backend this platform cannot use is dropped, but one that can still - // be reached stays: OpenGL is the in-process fallback off Windows (`instance_backends`) - // without being a candidate there, so forgetting it would offer it again after it failed - // to present — on macOS, where Metal is the only candidate, it never got left out at all. - failed.retain(|b| candidates.contains(b) || (!cfg!(windows) && *b == Backend::Gl)); let pick = |failed: &[Backend]| candidates.iter().copied().find(|b| !failed.contains(b) && !skipped.contains(b)); let backend = match pick(&failed) { Some(b) => b, @@ -391,6 +392,23 @@ mod tests { assert_eq!(c.record.failed, &[Backend::Vulkan]); } + /// The backend the previous start was still on stays in `failed` even where it is no + /// candidate: on macOS Metal is the only candidate, but the instance loads OpenGL beside it, + /// so a start OpenGL took down has to keep it out of the next one. Only the stored list is + /// filtered to this platform's candidates — those entries came from another machine. + #[test] + fn the_backend_a_start_was_on_counts_as_failed_even_where_it_is_no_candidate() { + const MAC: &[Backend] = &[Backend::Metal]; + let c = choose(MAC, &record(Some(Backend::Gl), &[]), V, &[]).unwrap(); + assert_eq!(c.backend, Backend::Metal); + assert_eq!(c.record.failed, &[Backend::Gl], "OpenGL ran here, so it failed here"); + assert_eq!(c.switched_from, Some(Backend::Gl)); + assert!(!instance_backends(c.backend, &c.record.failed).contains(wgpu::Backends::GL)); + // Stored on another machine, never run here: dropped, and OpenGL may ride along again. + let c = choose(MAC, &record(None, &[Backend::Gl]), V, &[]).unwrap(); + assert_eq!(c.record.failed, &[]); + } + /// A restart that found no adapter of DirectX 12 names the backend in its skip list: that start /// takes the next candidate, with or without a record, and the record doesn't keep it as failed /// (a later launch tries it again).