WIP: feat(menu): Don't wait for the GPU, a row for the game's present poll answered at once (off by default) #656

Draft
mooki wants to merge 2 commits from mooki/Longshot:fix/present-nowait-menu into main
Member

Type: feat · Risk: low · Tested on: macOS 26.7.1, Apple M4 Max: game-free checks only, no game run · Needs: Windows, Linux (both untested), and a game run with the row On on any platform

Why

After the game hands a frame's work to the GPU, its present waits until the GPU has finished that frame before the game
starts the next one. Where the GPU is the slower part, the GPU then sits idle while the CPU builds the next frame, and a
frame costs the GPU's time plus the CPU's instead of the larger of the two. On an Apple M4 Max each 33 ms frame in Hyrule
Field was about 23 ms of GPU work plus about 10 ms in which the GPU had nothing to do.

HOS_PRESENT_NOWAIT=1 already lets the game go on without that wait (the render thread, Threaded rendering On, does the
same as its rule), but only as a switch for testing. This PR puts it in the Insert menu as Don't wait for the GPU, off
by default, on every platform: a player whose GPU is the limit can trade up to one frame more input delay for more frames
per second. Nothing changes for anyone who leaves it off.

The issue tracker was searched (forge.py issue list --state all, 188 issues): none is about the present's wait. #410
and #402 are low frame rates where the CPU is the limit, which this row does not help.

What changed

A Graphics row, Don't wait for the GPU (Off, On), in Frame generation and latency after the Game frame rate cap, kept
in longshot_options.ini as present_nowait= beside frame_rate_cap=. On, the game's present poll is answered at once
without the render thread too, exactly as HOS_PRESENT_NOWAIT=1 answered it; Off, the default, the poll is as before. The
row applies in play, and HOS_PRESENT_NOWAIT, when set, still decides for a run, as HOS_FPS_MAX does over the cap.

  • host: present_nowait() in host/nvn_native.c (new; nvnEventGetValue asks it at OoT's two poll sites, as the old
    block did). HOS_PRESENT_NOWAIT set: its answer for the run. Unset: at once while the render thread runs
    (HOS_OPT_RENDER_THREAD, as before); otherwise the row, asked at each poll through the renderer
    (HOS_OPT_PRESENT_NOWAIT), and the options file's present_nowait= until the renderer answers, as host/nn_vi.c
    fps_refresh reads the cap.
  • menu: opts.present_nowait (gpu/vk/vk_menu.h, 0); the row in gpu/vk/vk_menu.cpp (tx_onoff, changes at once, no
    preset sets it); ini_key saves it as present_nowait. Its help says the trade: "more frames per second where the GPU
    holds the game back, for up to one frame more input delay".
  • renderer: HOS_OPT_PRESENT_NOWAIT = 70 in gpu/vk/vk_api.h (56 and 59 to 69 are taken by open pull requests),
    answered by the existing option callback in gpu/vk/vk_replay.cpp: no new MkwVkHost/MkwVkRenderer member, so
    MKWVK_API_VERSION is unchanged.
  • translations: the row's name and help in all 13 tables of gpu/vk/menu_i18n.h (machine-made, as the file says; the
    untranslated row name "Threaded rendering" is kept in English where the help names it), counts updated.
  • checks: tests/test_present_nowait.py (new); tests/test_threaded_rendering.py reads the poll's decision from
    present_nowait() now; tests/test_menu_sections.py lists the row in its section; tests/test_audio_output_menu.py
    and tests/test_stick_dead_zone.py get present_nowait in their Options stand-ins (they compile ini_key).
  • docs: the README's option table and a paragraph under the Game frame rate cap's; HOS_PRESENT_NOWAIT in
    docs/development.md's switches; a line in docs/graphics-and-controls.md.

Measurements

Verdict: not measured yet: no game was run for this PR. With the row Off (the default) the present poll gets the same
answer as on main, which tests/test_present_nowait.py checks; what On gains in frames per second and costs in input
delay still has to be measured in the game (Testing, Must be tested). The game-free checks:

Hardware: macOS 26.7.1, Apple M4 Max
Method: tools/ls-suite.py --full (tests/run_all.py --jobs 6) on each tree, compared by test name
Scene: none in the game for the checks below (no game ran); the game measurement still owed is Hyrule Field by day,
standing still, Threaded rendering Off, Game frame rate cap Uncapped, FPS counter at Breakdown (Must be tested).
How to reproduce: python -I -B tests/run_all.py in a checkout of main and of this PR, compared by test name (here
tools/ls-suite.py --full, 6 at once); python -I -B tests/test_present_nowait.py alone for the new check.

Before: main @ 6c9edffe After: this PR @ de74a101
tests/run_all.py 531 passed, 32 failed, 17 skipped 532 passed, 32 failed, 17 skipped: the same 32, plus tests/test_present_nowait.py
tests/test_present_nowait.py fails ("vk_menu.h: the row is not Off (0) out of the box") passes
main 6c9edffe:        531 passed, 32 failed, 17 skipped in 181 s (linux, 6 at once)
this PR @ de74a101:   532 passed, 32 failed, 17 skipped in 336 s (linux, 6 at once)

Flags and settings

A new menu row and its ini key; HOS_PRESENT_NOWAIT now also goes over that row.

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
Don't wait for the GPU (present_nowait= in longshot_options.ini) Graphics row, Frame generation and latency none Off (0) On: the game starts its next frame while the GPU still draws the last one, without the render thread too Untested in the game. Expected to help only where the GPU is the limit (not where the CPU is, #410, #402); does nothing with Threaded rendering on Off by default, still being tested; On to try where the GPU is the limit
HOS_PRESENT_NOWAIT=1 or 0 Switch for testing 1 at once, 0 polled, for the run; unset: the render thread's rule The same, over the row too As before As before Testing only

Testing

Tested:

  • On macOS 26.7.1, Apple M4 Max, game-free only.
  • tests/test_present_nowait.py (new) fails on main 6c9edffe and passes here. It compiles the menu's real ini_key,
    save() and menu_init's reading of a line into the rows, and saves the row and reads it back beside
    frame_rate_cap= (Off without a line, On and Off again, values past the ends, two lines: the last). It compiles
    host/nvn_native.c's present_nowait and runs it with no switch, with present_nowait=1, with the row turned On and
    Off in play, with the render thread, and with HOS_PRESENT_NOWAIT=1 over the row Off and =0 over the row On, the
    file's line and the render thread.
  • The checks that read the changed sources pass: tests/test_threaded_rendering.py, tests/test_menu_sections.py,
    tests/test_menu_i18n.py (it checks the new translations: texts the menu's own, sorted, counts),
    tests/test_readme_menu_rows.py, tests/test_frame_rate_cap_step.py, tests/test_menu_env_overrides.py,
    tests/test_preset_exempt.py, tests/test_upscaler_rows.py, tests/test_bug_report_defaults.py, every
    tests/test_menu_*.py and tests/test_nvn_*.py, tests/test_hle_stats.py, tests/test_present_trace.py, and
    tests/test_audio_output_menu.py and tests/test_stick_dead_zone.py with their stand-ins updated (a full run of the
    first commit showed them failing on the new ini_key line).
  • The full suite against main 6c9edffe, compared by test name: the same 32 checks fail on both, each with the same
    error lines as on main (compared with temporary paths taken out); the only difference is the new
    tests/test_present_nowait.py, which passes. The 32 (on this Mac; why each fails here is not this PR's):
    test_appimage_package_retry, test_appimage_player_build, test_collect_wide, test_control, test_cpm_discovery,
    test_crash_dialog, test_crash_record, test_env_on, test_game_layout, test_lift_cleanup_error,
    test_linux_preflight, test_longshot_ctl, test_low_memory_notice, test_make_game_cache, test_mem_gate,
    test_mem_gate_children, test_mirror_trim, test_oot_state_tap, test_pcache_header_read,
    test_pipeline_cache_file, test_pipeline_cache_save, test_pipeline_play_cache, test_pipeline_tour,
    test_prof_symbols, test_program_cache_cleanup, test_program_cache_one_variant, test_readback_late,
    test_report_run, test_split_giants, test_vk_pgo, test_vram_budget_row, test_walk_key.
  • gpu/vk/vk_menu.cpp and gpu/vk/vk_replay.cpp pass clang++ -fsyntax-only with the renderer's build defines, with
    the same two Linux-only errors in vk_menu.cpp as main has on a Mac (posix_spawn_file_actions_addclosefrom_np,
    sigtimedwait).
  • After the rebase onto main aa95d35f, at 0c01c1fa (de74a101 before the rebases onto e090cc48 and aa95d35f):
    Sweep3's checks of this head (the C/C++ it changes compiled, and its own tests run, on arm64 and x86_64 where they
    apply) gave the same results as before, once the end commit gave test_present_nowait's fixture main's #736 names
    (gpu_device_cards, gpu_saved_card, gpu_card_missing); python -I -B tests/run_all.py on wave S1's merge
    11a5b4e7, which carries this head: 568/22/17 (passed/failed/skipped) against main aa95d35f's 543/33/17 on this Mac,
    no new failure (reports/2026-11-15-rebase-sweep-3.md).

Not tested:

  • No game run anywhere: what On does to frames per second and input delay, the row turned On and Off in play, the row
    and its translations on screen.
  • Windows and Linux: untested, and not built. The host and renderer were not built on the Mac either; the new host code is
    compiled only inside the test above. The test requests T-E4TU (Windows) and T-9WPL (Linux) ask for the build, the
    frames per second and the input delay.

Must be tested:

  • Windows, any GPU (a GPU-limited scene shows the gain): build this PR (head 0c01c1fa) and main aa95d35f the same way; on
    both, Threaded rendering Off, the Game frame rate cap at Uncapped, the FPS counter at Breakdown, standing still in Hyrule
    Field by day. Read the frames per second a minute each on main, then on this PR with the row Off, On and Off again, and
    film screen and controller at 240 fps to count the frames from pressing B to the sword swing, 10 presses each on main,
    the row Off and the row On, expecting Off as main, On at least as fast where the GPU is the limit with the same picture
    and no hang or stall when it is switched in play, and On's delay at most one frame more (by hand: test request T-E4TU).
  • Linux, any GPU (a GPU-limited scene shows the gain): the same steps, expecting the same results as on Windows (by hand:
    test request T-9WPL).
  • macOS, Apple M4 Max: the same frames-per-second steps from the field-dawn save, expecting On at least as fast as Off
    (each 33 ms frame here was about 23 ms of GPU work), the same picture and no hang or stall when it is switched (by hand,
    me).
  • Any platform, the menu in German and in Polish: open Graphics, Frame generation and latency, expecting the row's name
    and help to fit on screen, not cut off (by hand).

Risks

What could break: only with the row On or HOS_PRESENT_NOWAIT=1. The game goes on while the GPU still draws the last
frame, so input reaches the screen up to a frame later, and GPU results the game reads without waiting for them can be a
frame older. With the row Off (the default) the poll gets the answer main gives. The poll asks the renderer's option
callback once per poll at OoT's two poll sites, where main read a cached value.

Turning it off: the row Off (the default), or HOS_PRESENT_NOWAIT=0 for a run; reverting the commit is enough.

Compatibility: longshot_options.ini gets a present_nowait=0 line at the next save; a file without it loads Off,
and an older build keeps the line as one it does not know. No saves, caches or builder tools change, and the vk ABI is
unchanged (MKWVK_API_VERSION). A renderer from before this PR answers the new option with 0: the row's line is read
until the renderer is up, then Off.

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. (Test requests T-E4TU and T-9WPL.)
  • Rebased on current main: on aa95d35f (#761, 2026-11-15), head 0c01c1fa, with two conflicts:
    docs/development.md (#488's HOS_MOVIE_PACE row and this PR's row, both kept) and gpu/vk/menu_i18n.h's
    per-language counts (main's after #735/#736, plus this PR's 2); and an end commit: test_present_nowait compiles the
    menu's real save(), which since #736 writes the GPU card, so its fixture gets main's
    gpu_device_cards/gpu_saved_card/gpu_card_missing stand-ins; this PR's own commits, old → new: de74a101 →
    78b7d65a; end commits added by the rebases: 0c01c1fa; the commit names elsewhere in this description are from
    before the rebase.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named). After the rebase onto main aa95d35f (head 0c01c1fa): wave S1's merge 11a5b4e7, 568/22/17 against main aa95d35f's 543/33/17, no new failure.
  • 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.
  • Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported. (waits for its
    Mac measurements and the two requests' runs)
  • Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value.
  • Screenshots of the same spot, captioned like the Measurements columns, for a change that shows on screen. (waits
    for its Mac measurements: the row on screen)
  • A file in docs/tested/ for each new measured run (format in docs/tested-configurations.md). (waits for its Mac
    measurements)
  • 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. (waits for the builds in T-E4TU and
    T-9WPL)
  • Docs updated.
**Type:** feat · **Risk:** low · **Tested on:** macOS 26.7.1, Apple M4 Max: game-free checks only, no game run · **Needs:** Windows, Linux (both untested), and a game run with the row On on any platform ## Why After the game hands a frame's work to the GPU, its present waits until the GPU has finished that frame before the game starts the next one. Where the GPU is the slower part, the GPU then sits idle while the CPU builds the next frame, and a frame costs the GPU's time plus the CPU's instead of the larger of the two. On an Apple M4 Max each 33 ms frame in Hyrule Field was about 23 ms of GPU work plus about 10 ms in which the GPU had nothing to do. `HOS_PRESENT_NOWAIT=1` already lets the game go on without that wait (the render thread, Threaded rendering On, does the same as its rule), but only as a switch for testing. This PR puts it in the Insert menu as **Don't wait for the GPU**, off by default, on every platform: a player whose GPU is the limit can trade up to one frame more input delay for more frames per second. Nothing changes for anyone who leaves it off. The issue tracker was searched (`forge.py issue list --state all`, 188 issues): none is about the present's wait. #410 and #402 are low frame rates where the CPU is the limit, which this row does not help. ## What changed A Graphics row, **Don't wait for the GPU** (Off, On), in Frame generation and latency after the Game frame rate cap, kept in `longshot_options.ini` as `present_nowait=` beside `frame_rate_cap=`. On, the game's present poll is answered at once without the render thread too, exactly as `HOS_PRESENT_NOWAIT=1` answered it; Off, the default, the poll is as before. The row applies in play, and `HOS_PRESENT_NOWAIT`, when set, still decides for a run, as `HOS_FPS_MAX` does over the cap. - host: `present_nowait()` in `host/nvn_native.c` (new; `nvnEventGetValue` asks it at OoT's two poll sites, as the old block did). `HOS_PRESENT_NOWAIT` set: its answer for the run. Unset: at once while the render thread runs (`HOS_OPT_RENDER_THREAD`, as before); otherwise the row, asked at each poll through the renderer (`HOS_OPT_PRESENT_NOWAIT`), and the options file's `present_nowait=` until the renderer answers, as `host/nn_vi.c` `fps_refresh` reads the cap. - menu: `opts.present_nowait` (`gpu/vk/vk_menu.h`, 0); the row in `gpu/vk/vk_menu.cpp` (`tx_onoff`, changes at once, no preset sets it); `ini_key` saves it as `present_nowait`. Its help says the trade: "more frames per second where the GPU holds the game back, for up to one frame more input delay". - renderer: `HOS_OPT_PRESENT_NOWAIT = 70` in `gpu/vk/vk_api.h` (56 and 59 to 69 are taken by open pull requests), answered by the existing option callback in `gpu/vk/vk_replay.cpp`: no new `MkwVkHost`/`MkwVkRenderer` member, so `MKWVK_API_VERSION` is unchanged. - translations: the row's name and help in all 13 tables of `gpu/vk/menu_i18n.h` (machine-made, as the file says; the untranslated row name "Threaded rendering" is kept in English where the help names it), counts updated. - checks: `tests/test_present_nowait.py` (new); `tests/test_threaded_rendering.py` reads the poll's decision from `present_nowait()` now; `tests/test_menu_sections.py` lists the row in its section; `tests/test_audio_output_menu.py` and `tests/test_stick_dead_zone.py` get `present_nowait` in their `Options` stand-ins (they compile `ini_key`). - docs: the README's option table and a paragraph under the Game frame rate cap's; `HOS_PRESENT_NOWAIT` in `docs/development.md`'s switches; a line in `docs/graphics-and-controls.md`. ## Measurements **Verdict:** not measured yet: no game was run for this PR. With the row Off (the default) the present poll gets the same answer as on main, which `tests/test_present_nowait.py` checks; what On gains in frames per second and costs in input delay still has to be measured in the game (Testing, Must be tested). The game-free checks: Hardware: macOS 26.7.1, Apple M4 Max Method: `tools/ls-suite.py --full` (`tests/run_all.py --jobs 6`) on each tree, compared by test name Scene: none in the game for the checks below (no game ran); the game measurement still owed is Hyrule Field by day, standing still, Threaded rendering Off, Game frame rate cap Uncapped, FPS counter at Breakdown (Must be tested). How to reproduce: `python -I -B tests/run_all.py` in a checkout of main and of this PR, compared by test name (here `tools/ls-suite.py --full`, 6 at once); `python -I -B tests/test_present_nowait.py` alone for the new check. | | Before: main @ 6c9edffe | After: this PR @ de74a101 | |---|---|---| | `tests/run_all.py` | 531 passed, 32 failed, 17 skipped | 532 passed, 32 failed, 17 skipped: the same 32, plus `tests/test_present_nowait.py` | | `tests/test_present_nowait.py` | fails ("vk_menu.h: the row is not Off (0) out of the box") | passes | ``` main 6c9edffe: 531 passed, 32 failed, 17 skipped in 181 s (linux, 6 at once) this PR @ de74a101: 532 passed, 32 failed, 17 skipped in 336 s (linux, 6 at once) ``` ### Flags and settings A new menu row and its ini key; `HOS_PRESENT_NOWAIT` now also goes over that row. | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | Don't wait for the GPU (`present_nowait=` in `longshot_options.ini`) | Graphics row, Frame generation and latency | none | Off (0) | On: the game starts its next frame while the GPU still draws the last one, without the render thread too | Untested in the game. Expected to help only where the GPU is the limit (not where the CPU is, #410, #402); does nothing with Threaded rendering on | Off by default, still being tested; On to try where the GPU is the limit | | `HOS_PRESENT_NOWAIT=1` or `0` | Switch for testing | 1 at once, 0 polled, for the run; unset: the render thread's rule | The same, over the row too | As before | As before | Testing only | ## Testing **Tested:** - On macOS 26.7.1, Apple M4 Max, game-free only. - `tests/test_present_nowait.py` (new) fails on main 6c9edffe and passes here. It compiles the menu's real `ini_key`, `save()` and `menu_init`'s reading of a line into the rows, and saves the row and reads it back beside `frame_rate_cap=` (Off without a line, On and Off again, values past the ends, two lines: the last). It compiles `host/nvn_native.c`'s `present_nowait` and runs it with no switch, with `present_nowait=1`, with the row turned On and Off in play, with the render thread, and with `HOS_PRESENT_NOWAIT=1` over the row Off and `=0` over the row On, the file's line and the render thread. - The checks that read the changed sources pass: `tests/test_threaded_rendering.py`, `tests/test_menu_sections.py`, `tests/test_menu_i18n.py` (it checks the new translations: texts the menu's own, sorted, counts), `tests/test_readme_menu_rows.py`, `tests/test_frame_rate_cap_step.py`, `tests/test_menu_env_overrides.py`, `tests/test_preset_exempt.py`, `tests/test_upscaler_rows.py`, `tests/test_bug_report_defaults.py`, every `tests/test_menu_*.py` and `tests/test_nvn_*.py`, `tests/test_hle_stats.py`, `tests/test_present_trace.py`, and `tests/test_audio_output_menu.py` and `tests/test_stick_dead_zone.py` with their stand-ins updated (a full run of the first commit showed them failing on the new `ini_key` line). - The full suite against main 6c9edffe, compared by test name: the same 32 checks fail on both, each with the same error lines as on main (compared with temporary paths taken out); the only difference is the new `tests/test_present_nowait.py`, which passes. The 32 (on this Mac; why each fails here is not this PR's): `test_appimage_package_retry`, `test_appimage_player_build`, `test_collect_wide`, `test_control`, `test_cpm_discovery`, `test_crash_dialog`, `test_crash_record`, `test_env_on`, `test_game_layout`, `test_lift_cleanup_error`, `test_linux_preflight`, `test_longshot_ctl`, `test_low_memory_notice`, `test_make_game_cache`, `test_mem_gate`, `test_mem_gate_children`, `test_mirror_trim`, `test_oot_state_tap`, `test_pcache_header_read`, `test_pipeline_cache_file`, `test_pipeline_cache_save`, `test_pipeline_play_cache`, `test_pipeline_tour`, `test_prof_symbols`, `test_program_cache_cleanup`, `test_program_cache_one_variant`, `test_readback_late`, `test_report_run`, `test_split_giants`, `test_vk_pgo`, `test_vram_budget_row`, `test_walk_key`. - `gpu/vk/vk_menu.cpp` and `gpu/vk/vk_replay.cpp` pass `clang++ -fsyntax-only` with the renderer's build defines, with the same two Linux-only errors in `vk_menu.cpp` as main has on a Mac (`posix_spawn_file_actions_addclosefrom_np`, `sigtimedwait`). - **After the rebase onto main aa95d35f**, at `0c01c1fa` (`de74a101` before the rebases onto e090cc48 and aa95d35f): Sweep3's checks of this head (the C/C++ it changes compiled, and its own tests run, on arm64 and x86_64 where they apply) gave the same results as before, once the end commit gave `test_present_nowait`'s fixture main's #736 names (`gpu_device_cards`, `gpu_saved_card`, `gpu_card_missing`); `python -I -B tests/run_all.py` on wave S1's merge `11a5b4e7`, which carries this head: 568/22/17 (passed/failed/skipped) against main aa95d35f's 543/33/17 on this Mac, no new failure (`reports/2026-11-15-rebase-sweep-3.md`). **Not tested:** - No game run anywhere: what On does to frames per second and input delay, the row turned On and Off in play, the row and its translations on screen. - Windows and Linux: untested, and not built. The host and renderer were not built on the Mac either; the new host code is compiled only inside the test above. The test requests T-E4TU (Windows) and T-9WPL (Linux) ask for the build, the frames per second and the input delay. **Must be tested:** - Windows, any GPU (a GPU-limited scene shows the gain): build this PR (head 0c01c1fa) and main aa95d35f the same way; on both, Threaded rendering Off, the Game frame rate cap at Uncapped, the FPS counter at Breakdown, standing still in Hyrule Field by day. Read the frames per second a minute each on main, then on this PR with the row Off, On and Off again, and film screen and controller at 240 fps to count the frames from pressing B to the sword swing, 10 presses each on main, the row Off and the row On, expecting Off as main, On at least as fast where the GPU is the limit with the same picture and no hang or stall when it is switched in play, and On's delay at most one frame more (by hand: test request T-E4TU). - Linux, any GPU (a GPU-limited scene shows the gain): the same steps, expecting the same results as on Windows (by hand: test request T-9WPL). - macOS, Apple M4 Max: the same frames-per-second steps from the field-dawn save, expecting On at least as fast as Off (each 33 ms frame here was about 23 ms of GPU work), the same picture and no hang or stall when it is switched (by hand, me). - Any platform, the menu in German and in Polish: open Graphics, Frame generation and latency, expecting the row's name and help to fit on screen, not cut off (by hand). ## Risks **What could break:** only with the row On or `HOS_PRESENT_NOWAIT=1`. The game goes on while the GPU still draws the last frame, so input reaches the screen up to a frame later, and GPU results the game reads without waiting for them can be a frame older. With the row Off (the default) the poll gets the answer main gives. The poll asks the renderer's option callback once per poll at OoT's two poll sites, where main read a cached value. **Turning it off:** the row Off (the default), or `HOS_PRESENT_NOWAIT=0` for a run; reverting the commit is enough. **Compatibility:** `longshot_options.ini` gets a `present_nowait=0` line at the next save; a file without it loads Off, and an older build keeps the line as one it does not know. No saves, caches or builder tools change, and the vk ABI is unchanged (`MKWVK_API_VERSION`). A renderer from before this PR answers the new option with 0: the row's line is read until the renderer is up, then Off. ## 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. (Test requests T-E4TU and T-9WPL.) - [x] Rebased on current `main`: on aa95d35f (#761, 2026-11-15), head `0c01c1fa`, with two conflicts: `docs/development.md` (#488's `HOS_MOVIE_PACE` row and this PR's row, both kept) and `gpu/vk/menu_i18n.h`'s per-language counts (main's after #735/#736, plus this PR's 2); and an end commit: `test_present_nowait` compiles the menu's real `save()`, which since #736 writes the GPU card, so its fixture gets main's `gpu_device_cards`/`gpu_saved_card`/`gpu_card_missing` stand-ins; this PR's own commits, old → new: `de74a101` → `78b7d65a`; end commits added by the rebases: `0c01c1fa`; the commit names elsewhere in this description are from before the rebase. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). After the rebase onto main aa95d35f (head `0c01c1fa`): wave S1's merge `11a5b4e7`, 568/22/17 against main aa95d35f's 543/33/17, no new failure. - [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. - [ ] Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported. (waits for its Mac measurements and the two requests' runs) - [x] Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value. - [ ] Screenshots of the same spot, captioned like the Measurements columns, for a change that shows on screen. (waits for its Mac measurements: the row on screen) - [ ] A file in `docs/tested/` for each new measured run (format in `docs/tested-configurations.md`). (waits for its Mac measurements) - [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. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (waits for the builds in T-E4TU and T-9WPL) - [x] Docs updated.
feat(menu): Don't wait for the GPU, a row for the game's present poll answered at once (off by default)
All checks were successful
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 6s
Third-party parts / As their notes say (pull_request) Successful in 8s
PR description / Description follows the template (pull_request_target) Successful in 15s
de74a10173
The game's present waits for its own frame's GPU work before the next frame
is built. HOS_PRESENT_NOWAIT=1 answered that poll at once, as the render
thread does; the new Graphics row (Frame generation and latency, after the
Game frame rate cap) offers it to players: more frames per second where the
GPU is the limit, for up to one frame more input delay. Off by default.

- host/nvn_native.c present_nowait: HOS_PRESENT_NOWAIT, set, decides for the
  run; else the render thread answers at once; else the row, asked at each
  poll (HOS_OPT_PRESENT_NOWAIT, 70), the options file's present_nowait= until
  the renderer answers, as host/nn_vi.c reads the cap.
- gpu/vk: opts.present_nowait, the row (tx_onoff, changes at once, no preset
  sets it), saved as present_nowait=, answered by the option callback;
  translated in the 13 languages.
- tests/test_present_nowait.py: the row saved and read back beside
  frame_rate_cap=, and the switch over the row, the row in play and the render
  thread. test_threaded_rendering and test_menu_sections follow the source;
  test_audio_output_menu and test_stick_dead_zone get the new member in their
  Options stand-ins (they compile ini_key).
- README, docs/development.md, docs/graphics-and-controls.md.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mooki force-pushed fix/present-nowait-menu from de74a10173
All checks were successful
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 6s
Third-party parts / As their notes say (pull_request) Successful in 8s
PR description / Description follows the template (pull_request_target) Successful in 15s
to 88f59e339c
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
Website / Website on Cloudflare (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 4s
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 7s
Third-party parts / As their notes say (pull_request) Successful in 13s
2026-11-15 07:14:28 +00:00
Compare
mooki force-pushed fix/present-nowait-menu from 88f59e339c
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
Website / Website on Cloudflare (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 4s
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 7s
Third-party parts / As their notes say (pull_request) Successful in 13s
to 0c01c1fafd
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
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 6s
Third-party parts / As their notes say (pull_request) Successful in 14s
Website / Website on Cloudflare (pull_request_target) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 5s
2026-11-15 13:54:43 +00:00
Compare
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
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 6s
Third-party parts / As their notes say (pull_request) Successful in 14s
Website / Website on Cloudflare (pull_request_target) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 5s
This pull request is marked as a work in progress.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'fix/present-nowait-menu:mooki-fix/present-nowait-menu'
git switch 'mooki-fix/present-nowait-menu'
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!656
No description provided.