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.
This commit is contained in:
skylar taylor-barrick
2026-10-16 20:46:40 -06:00
parent 5316059405
commit 653f83b8cb
+23 -5
View File
@@ -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<Backend> = 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).