From 53160594050288d5e6f256ef1b5f2ffc74446458 Mon Sep 17 00:00:00 2001 From: Hamza Date: Fri, 16 Oct 2026 20:23:31 +0500 Subject: [PATCH 1/2] Graphics: keep a failed OpenGL out of the instance (macOS) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `choose` pruned the failed list to this platform's candidates, which dropped an OpenGL entry on macOS, where Metal is the only candidate. But OpenGL is not only a candidate: off Windows it is the in-process fallback that `instance_backends` adds whenever it has not failed. So a start that recorded OpenGL and never showed a frame had the entry pruned a line later, and OpenGL went straight back into the instance — the backend that had just failed was offered again on every start, with nothing able to retire it. An entry that can still be reached is kept now. A non-candidate entry still cannot be chosen as a backend, since `pick` only looks at candidates, so which backend starts is unchanged; it only stops a failed OpenGL being handed back to the instance. This is what `a_start_records_its_backend_until_a_frame_is_presented` asserts, and that test failed on macOS: `cargo xtask ci` stopped in the `designcraft` bin before the rest of the workspace ran, so the gate could not go green there at all. --- apps/designcraft/src/gpu/backend.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/designcraft/src/gpu/backend.rs b/apps/designcraft/src/gpu/backend.rs index 7a944b35c..3cac60890 100644 --- a/apps/designcraft/src/gpu/backend.rs +++ b/apps/designcraft/src/gpu/backend.rs @@ -144,7 +144,11 @@ pub fn choose(candidates: &[Backend], previous: &Record, version: &str, skipped: { failed.push(b); } - failed.retain(|b| candidates.contains(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, From 653f83b8cb21f8ce2cdc873ca712a58c73628352 Mon Sep 17 00:00:00 2001 From: skylar taylor-barrick Date: Fri, 16 Oct 2026 20:46:40 -0600 Subject: [PATCH 2/2] Startup: keep the backend a start was on, whichever it is, and cover it Builds on the fix in this branch, which spotted that a failed OpenGL was being forgotten because `failed` is filtered to this platform's candidates and OpenGL is no candidate off Windows. Rather than spare OpenGL from the filter, run the filter before the previous start's backend is added. A stored entry that is no candidate here came from another machine and still says nothing, so it is dropped as before; the backend a start was actually running on is added afterwards and stays, whichever backend it is. That keeps the same crash out of the next instance without naming OpenGL or the platform, and without holding a failure recorded on someone else's machine against this one. Adds the regression test: on macOS, where Metal is the only candidate, a start left on OpenGL has to keep it out of the instance, while a stored OpenGL entry that never ran here is dropped. --- apps/designcraft/src/gpu/backend.rs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/designcraft/src/gpu/backend.rs b/apps/designcraft/src/gpu/backend.rs index 3cac60890..0a3cda618 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).