fix(menu): the GPU device row keeps its card, not its place in the list #736

Merged
HumanBean merged 1 commit from dyst/Longshot:fix/gpu-by-card into main 2026-11-15 08:11:50 +00:00
Member

Type: fix · Risk: low · Tested on: Windows 11, RTX 3080 (game-free checks) · Needs: Windows or Linux with two GPUs (a laptop's, or a second card)

Why

On a PC with more than one graphics card, the GPU device row can pick the wrong card after an update. It was saved as a number, the card's place in the list (gpu_device=2). That place changes when a card is added or taken out, when a driver lists the cards in another order, or when an update into a new folder loses Windows' own GPU choice for the program. The same number then picked another card, or Auto. Players saw their chosen GPU not kept after updating.

How to reproduce

Seen on: Windows, two GPUs (an integrated and a dedicated one), any driver
Settings and switches: GPU device set to the second card listed
Steps: change the order the cards are listed in (move the program's Windows graphics preference, update the driver, or plug in an eGPU), then start the game
Expected: the same card
Actual: another card, or Auto when the list got shorter

What changed

The row is now saved as the card itself: gpu_device_id=<vendor>:<device>:<n>, the card's PCI ids in hex, with n telling two of the same model apart. At the start, that card is found wherever it is in this run's list.

  • gpu/vk/gpu_choice.h: card_id and menu_of_card, plain functions with tests.
  • gpu/vk/vk_main.cpp pick_device: lists the usable GPUs' models (gpu_device_cards, declared in vk_internal.h). The log's vk: device lines now name each card's ids.
  • gpu/vk/vk_menu.cpp:
    • menu_set_gpu_count: finds the saved card before the row's range and its restart value are set, and logs where it is (vk: GPU device: the saved card ...).
    • save(): writes gpu_device_id= for the chosen card.
    • A saved card that isn't there this run (an eGPU unplugged) is Auto for the run, and stays saved until the row is changed.
    • A file from before keeps its place (gpu_device=) and saves the card at its next save.
  • README: the GPU device note says how it's saved.

Measurements

Nothing to measure: which card is picked at the start, not the frame. The checks under Testing are what count.

Flags and settings

One new ini key, written by the menu; no switch or menu row changes.

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
gpu_device_id (longshot_options.ini) The GPU device row's card, <vendor>:<device>:<n> not there written with the row's card The chosen card is found where it is now Helps with two GPUs whose list order changes; tested game-free Written by the menu; nothing to set by hand

Testing

Tested:

  • tests/test_gpu_choice.cpp (new cases):
    • a card found where it moved;
    • a card taken out (none);
    • two of the same model kept apart;
    • the second of a model when one is left;
    • ids round-tripping;
    • hand-edited ids refused.
  • tests/test_gpu_choice.py (new wiring checks):
    • pick_device lists the models before the row is counted;
    • menu_set_gpu_count finds the saved card before the range and restart value are set;
    • save() writes the card, drops its old line, and keeps a missing card;
    • menu_init reads it.
  • tests/test_auto_settings.py, test_menu_env_overrides.py, test_upscaler_round_trip.py and test_menu_keys.py pass with the new names added to their stubs.
  • python -I -B tests/run_all.py on Windows: 497 passed, and the failures beside the one this PR fixed (see below) are the 17 that fail on a clean main on this PC: test_appimage_player_build, test_bake_portability, test_cpm_discovery, test_cpp_preflight, test_crash_dialog, test_env_on, test_lift_cleanup_error, test_linux_preflight, test_make_game_cache, test_make_game_prepare, test_missing_nup, test_oot_state_tap, test_readback_late, test_tested_configurations, test_vk_pgo, test_vram_budget_row, test_walk_key. The first run caught test_auto_settings reading a line of save() that this PR had changed; the new key went on its own line, and the test passes. After the rebase onto a4821d56, the affected checks were run again: 21 passed.
  • vk_main.cpp and vk_menu.cpp compile with the build's flags (llvm-mingw, Windows). On Linux (WSL Ubuntu, g++): test_gpu_choice, test_menu_env_overrides, test_upscaler_round_trip, test_menu_keys and test_auto_settings pass.

Not tested:

  • A game run: this PC has one GPU, so a run here wouldn't show the card being found after the list's order changes.

Must be tested:

  • Windows or Linux with two GPUs:

    1. Choose the second card in GPU device and restart.
    2. Check that gpu_device_id= is in longshot_options.ini.
    3. Change the list's order: on Windows, set the program's graphics preference to the other GPU in Settings, Display, Graphics; or plug an eGPU in or out.
    4. Start again.

    Expect the same card, and vk: GPU device: the saved card (...) is <n>: <name> now in the log when its place changed (by hand).

Risks

What could break: on a PC with two identical cards, the card is told apart by its order among that model (n). If those two swap places, it's still "the second of that model", as before this PR. A hand-edited gpu_device_id that doesn't parse is ignored, and the row's gpu_device= is used as before.

Turning it off: revert the commit. Deleting the gpu_device_id= line also goes back to the place in the list.

Compatibility: longshot_options.ini gets a gpu_device_id= line at the next save once a card is chosen. Builds from before ignore it and keep reading gpu_device=, which is still written. Nothing else changes: no cache, save or renderer interface.

Issues

See MagicBeanDev/LongshotLauncher#90 (settings lost between updates).

None found for this exact case (the tracker searched). See Longshot #729 and LongshotLauncher #84, which keep settings across updates.

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how.
  • Rebased on current main.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named).
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
  • Docs updated.

🤖 Generated with Claude Code

**Type:** fix · **Risk:** low · **Tested on:** Windows 11, RTX 3080 (game-free checks) · **Needs:** Windows or Linux with two GPUs (a laptop's, or a second card) ## Why On a PC with more than one graphics card, the GPU device row can pick the wrong card after an update. It was saved as a number, the card's place in the list (`gpu_device=2`). That place changes when a card is added or taken out, when a driver lists the cards in another order, or when an update into a new folder loses Windows' own GPU choice for the program. The same number then picked another card, or Auto. Players saw their chosen GPU not kept after updating. ## How to reproduce **Seen on:** Windows, two GPUs (an integrated and a dedicated one), any driver **Settings and switches:** GPU device set to the second card listed **Steps:** change the order the cards are listed in (move the program's Windows graphics preference, update the driver, or plug in an eGPU), then start the game **Expected:** the same card **Actual:** another card, or Auto when the list got shorter ## What changed The row is now saved as the card itself: `gpu_device_id=<vendor>:<device>:<n>`, the card's PCI ids in hex, with n telling two of the same model apart. At the start, that card is found wherever it is in this run's list. - `gpu/vk/gpu_choice.h`: `card_id` and `menu_of_card`, plain functions with tests. - `gpu/vk/vk_main.cpp` `pick_device`: lists the usable GPUs' models (`gpu_device_cards`, declared in `vk_internal.h`). The log's `vk: device` lines now name each card's ids. - `gpu/vk/vk_menu.cpp`: - `menu_set_gpu_count`: finds the saved card before the row's range and its restart value are set, and logs where it is (`vk: GPU device: the saved card ...`). - `save()`: writes `gpu_device_id=` for the chosen card. - A saved card that isn't there this run (an eGPU unplugged) is Auto for the run, and stays saved until the row is changed. - A file from before keeps its place (`gpu_device=`) and saves the card at its next save. - README: the GPU device note says how it's saved. ## Measurements Nothing to measure: which card is picked at the start, not the frame. The checks under Testing are what count. ### Flags and settings One new ini key, written by the menu; no switch or menu row changes. | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | `gpu_device_id` (longshot_options.ini) | The GPU device row's card, `<vendor>:<device>:<n>` | not there | written with the row's card | The chosen card is found where it is now | Helps with two GPUs whose list order changes; tested game-free | Written by the menu; nothing to set by hand | ## Testing **Tested:** - `tests/test_gpu_choice.cpp` (new cases): - a card found where it moved; - a card taken out (none); - two of the same model kept apart; - the second of a model when one is left; - ids round-tripping; - hand-edited ids refused. - `tests/test_gpu_choice.py` (new wiring checks): - `pick_device` lists the models before the row is counted; - `menu_set_gpu_count` finds the saved card before the range and restart value are set; - `save()` writes the card, drops its old line, and keeps a missing card; - `menu_init` reads it. - `tests/test_auto_settings.py`, `test_menu_env_overrides.py`, `test_upscaler_round_trip.py` and `test_menu_keys.py` pass with the new names added to their stubs. - `python -I -B tests/run_all.py` on Windows: 497 passed, and the failures beside the one this PR fixed (see below) are the 17 that fail on a clean `main` on this PC: test_appimage_player_build, test_bake_portability, test_cpm_discovery, test_cpp_preflight, test_crash_dialog, test_env_on, test_lift_cleanup_error, test_linux_preflight, test_make_game_cache, test_make_game_prepare, test_missing_nup, test_oot_state_tap, test_readback_late, test_tested_configurations, test_vk_pgo, test_vram_budget_row, test_walk_key. The first run caught `test_auto_settings` reading a line of `save()` that this PR had changed; the new key went on its own line, and the test passes. After the rebase onto a4821d56, the affected checks were run again: 21 passed. - `vk_main.cpp` and `vk_menu.cpp` compile with the build's flags (llvm-mingw, Windows). On Linux (WSL Ubuntu, g++): test_gpu_choice, test_menu_env_overrides, test_upscaler_round_trip, test_menu_keys and test_auto_settings pass. **Not tested:** - **A game run:** this PC has one GPU, so a run here wouldn't show the card being found after the list's order changes. **Must be tested:** - **Windows or Linux with two GPUs:** 1. Choose the second card in GPU device and restart. 2. Check that `gpu_device_id=` is in `longshot_options.ini`. 3. Change the list's order: on Windows, set the program's graphics preference to the other GPU in Settings, Display, Graphics; or plug an eGPU in or out. 4. Start again. Expect the same card, and `vk: GPU device: the saved card (...) is <n>: <name> now` in the log when its place changed (by hand). ## Risks **What could break:** on a PC with two identical cards, the card is told apart by its order among that model (`n`). If those two swap places, it's still "the second of that model", as before this PR. A hand-edited `gpu_device_id` that doesn't parse is ignored, and the row's `gpu_device=` is used as before. **Turning it off:** revert the commit. Deleting the `gpu_device_id=` line also goes back to the place in the list. **Compatibility:** `longshot_options.ini` gets a `gpu_device_id=` line at the next save once a card is chosen. Builds from before ignore it and keep reading `gpu_device=`, which is still written. Nothing else changes: no cache, save or renderer interface. ## Issues See https://git.magicbean.dev/MagicBeanDev/LongshotLauncher/issues/90 (settings lost between updates). None found for this exact case (the tracker searched). See Longshot #729 and LongshotLauncher #84, which keep settings across updates. ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. - [x] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(menu): the GPU device row keeps its card, not its place in the list
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 5s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 5s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 5s
cbf9e65ab4
The row was saved as a number, the card's place among the Vulkan 1.3 GPUs (gpu_device=). The place changes
when a card is added or taken out, a driver lists them in another order, or Windows' own GPU choice for the
program is lost with an update into a new folder, and the same number then picked another card or Auto.

- gpu_choice.h: card_id (vendor:device:n, n the how-manieth of that model) and menu_of_card, which finds a
  saved card where it is in this run's list
- pick_device lists the usable GPUs' models (gpu_device_cards); menu_set_gpu_count finds the saved card
  (gpu_device_id=) before the row and its restart value are set, and logs where it is
- save writes gpu_device_id= for the chosen card; a saved card that is not here this run (an eGPU unplugged)
  is Auto for the run and stays saved while the row is not changed; a file from before keeps its place and
  saves the card at its next save
- the log's vk: device lines name each card's ids
- tests: gpu_choice's card ids; the wiring; the fixtures that cut save() get the new names

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
HumanBean changed title from WIP: fix(menu): the GPU device row keeps its card, not its place in the list to fix(menu): the GPU device row keeps its card, not its place in the list 2026-11-15 08:11:44 +00:00
HumanBean deleted branch fix/gpu-by-card 2026-11-15 08:11:50 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!736
No description provided.