perf(vk): a new pipeline is asked of the caches by its first draw before the draw is left out #2882

Open
github-sync wants to merge 2 commits from github/pr/2882 into main
Owner

@hckrman101 opened this pull request on GitHub · 2026-11-12 18:22 UTC · original

Status: draft, not ready. A first game test shows this change alone does not fix the pop-in; this PR will be updated with the missing part (see "First game test").

First game test

A new game's opening on a freshly prepared folder (RTX 3090, Linux, this change on):

that session
new pipelines the draws asked the caches for 423, all answered, 0 compiled
cost of an ask 2.8 ms on average (per 5 s window: median 3.2, worst 17.6 ms)
new pipelines past the frame's budget (the old path) 1,650
draws and dispatches left out 5,675, in 113 frames
pipeline waits 219 (395 ms)
frames with over 3 ms / over 8 ms of pipeline work 199 / 31 (worst 23 ms)

Every ask was answered by the driver's disk cache, none by the game's own pipeline cache. An answer from the driver's disk cache costs about 3 ms, so the 3 ms budget carries about one ask a frame: most new pipelines still take the old path, and the asks that do run add frame time.

Plan for this PR: the game's first start after a preparation also runs the predicted pipelines through its own pipeline cache (as it already does for the noted ones), so later first-draw asks are answered from memory. That cost is still to be measured (expected well under 1 ms; if it is not, this PR is closed). Also to be measured: the larger cache file, its load time and memory, and the first start's extra time.

What

A draw whose pipeline is new (no list names it) now asks the pipeline caches itself, without compiling, before the draw is left out. When the driver's cache or ours answers, the draw goes on in the same frame. Only a pipeline that really needs compiling goes to a worker and has its draw left out, as before.

Why

A draw with a new pipeline was left out until a worker had made the pipeline, even where the driver's cache had it and would have answered in a fraction of a millisecond.

One session that shows it (RTX 3090, a new game's opening on a prepared folder, about 8 minutes):

that session
new pipelines no list names 834
of those, compiled 0 (all answered by the driver's cache)
draws and dispatches left out 1,906 (30 of 98 five-second windows, up to 11.4 a frame)
waits 290 (353 ms)

So nothing was compiled in play, and the player still saw pop-in.

Change

  • graphics_pipeline: a new pipeline is first asked of the caches by the draw, with the build's existing fail-on-compile-required ask. If it answers, the entry is ready and the draw goes on.
  • gpu/vk/first_ask.h (new): the budget. On each recording thread, at most 24 asks and 3 ms a frame; no ask begins once the time is spent. Past the budget the old path is taken, so a burst of new states is served over a few frames. Proposed from an ask being about 0.2 ms median, 99 in 100 under 5 ms on an RTX 3090.
  • Both load modes take this path. The precompile code is not touched.
  • RX_VK_FIRST_ASK=0: off. Documented in docs/development.md. New log lines first draws: ... count asked, answered and past the budget.

Tests

  • tests/test_first_ask.py (new): compiles and runs the budget rule alone, and checks in the source that the ask is without compiling, inside the budget and before the worker hand-off.
  • test_draw_side_effects passes. The renderer builds on Linux.
  • The full test suite was not run.

Not tested yet

  • No game run: draws left out, waits and frame times with the asks on the recording threads, before and after.
  • A cold driver cache (every ask fails and the draw takes the old path: the cost of the failed asks is to be measured).
  • AMD, Intel, Windows.
  • A second session of the same areas (the pipelines are noted when first drawn, so the next start has them listed): not run yet.

Why the predicted pipelines do not help here today

They do cover these states, but only inside the driver's cache. The preparation predicts about 50,000 pipelines for program variants no list names, builds each into the cache and lets it go; no note is kept for a predicted pipeline. So at its first draw there is no pipeline object in either load mode: the draw found it in the driver's cache (every one of them in the session above, none compiled) but only after a worker had asked, with the draw left out meanwhile. This change is what lets the prediction's work show on the first frame. Read from the code and that session's log; not yet confirmed by a run.

> [@hckrman101](https://github.com/hckrman101) opened this pull request on GitHub · 2026-11-12 18:22 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2882) **Status: draft, not ready. A first game test shows this change alone does not fix the pop-in; this PR will be updated with the missing part (see "First game test").** ## First game test A new game's opening on a freshly prepared folder (RTX 3090, Linux, this change on): | | that session | |---|---| | new pipelines the draws asked the caches for | 423, all answered, 0 compiled | | cost of an ask | 2.8 ms on average (per 5 s window: median 3.2, worst 17.6 ms) | | new pipelines past the frame's budget (the old path) | 1,650 | | draws and dispatches left out | 5,675, in 113 frames | | pipeline waits | 219 (395 ms) | | frames with over 3 ms / over 8 ms of pipeline work | 199 / 31 (worst 23 ms) | Every ask was answered by the driver's disk cache, none by the game's own pipeline cache. An answer from the driver's disk cache costs about 3 ms, so the 3 ms budget carries about one ask a frame: most new pipelines still take the old path, and the asks that do run add frame time. **Plan for this PR:** the game's first start after a preparation also runs the predicted pipelines through its own pipeline cache (as it already does for the noted ones), so later first-draw asks are answered from memory. That cost is still to be measured (expected well under 1 ms; if it is not, this PR is closed). Also to be measured: the larger cache file, its load time and memory, and the first start's extra time. ## What A draw whose pipeline is new (no list names it) now asks the pipeline caches itself, without compiling, before the draw is left out. When the driver's cache or ours answers, the draw goes on in the same frame. Only a pipeline that really needs compiling goes to a worker and has its draw left out, as before. ## Why A draw with a new pipeline was left out until a worker had made the pipeline, even where the driver's cache had it and would have answered in a fraction of a millisecond. One session that shows it (RTX 3090, a new game's opening on a prepared folder, about 8 minutes): | | that session | |---|---| | new pipelines no list names | 834 | | of those, compiled | 0 (all answered by the driver's cache) | | draws and dispatches left out | 1,906 (30 of 98 five-second windows, up to 11.4 a frame) | | waits | 290 (353 ms) | So nothing was compiled in play, and the player still saw pop-in. ## Change - `graphics_pipeline`: a new pipeline is first asked of the caches by the draw, with the build's existing fail-on-compile-required ask. If it answers, the entry is ready and the draw goes on. - `gpu/vk/first_ask.h` (new): the budget. On each recording thread, at most 24 asks and 3 ms a frame; no ask begins once the time is spent. Past the budget the old path is taken, so a burst of new states is served over a few frames. Proposed from an ask being about 0.2 ms median, 99 in 100 under 5 ms on an RTX 3090. - Both load modes take this path. The precompile code is not touched. - `RX_VK_FIRST_ASK=0`: off. Documented in `docs/development.md`. New log lines `first draws: ...` count asked, answered and past the budget. ## Tests - `tests/test_first_ask.py` (new): compiles and runs the budget rule alone, and checks in the source that the ask is without compiling, inside the budget and before the worker hand-off. - `test_draw_side_effects` passes. The renderer builds on Linux. - The full test suite was not run. ## Not tested yet - No game run: draws left out, waits and frame times with the asks on the recording threads, before and after. - A cold driver cache (every ask fails and the draw takes the old path: the cost of the failed asks is to be measured). - AMD, Intel, Windows. - A second session of the same areas (the pipelines are noted when first drawn, so the next start has them listed): not run yet. ## Why the predicted pipelines do not help here today They do cover these states, but only inside the driver's cache. The preparation predicts about 50,000 pipelines for program variants no list names, builds each into the cache and lets it go; no note is kept for a predicted pipeline. So at its first draw there is no pipeline object in either load mode: the draw found it in the driver's cache (every one of them in the session above, none compiled) but only after a worker had asked, with the draw left out meanwhile. This change is what lets the prediction's work show on the first frame. Read from the code and that session's log; not yet confirmed by a run. <!-- github-sync:item:2882 -->
A draw whose pipeline no list named was left out until a worker had made the pipeline,
even where the driver's cache had it and answered in a fraction of a millisecond. A
new game's opening on a prepared folder drew 834 such pipelines, all from the driver's
cache and none compiled, and still left 1,906 draws and dispatches out (30 of 98
five-second windows, up to 11.4 a frame) with 290 waits.

The draw now asks the caches itself, without compiling (the build's existing
fail-on-compile-required ask), and goes on with the pipeline when they answer. Only one
that needs compiling goes to the worker and is left out, as before. The asks have a
budget a frame on each recording thread (first_ask.h: 24 asks, 3 ms; an ask is about
0.2 ms, 99 in 100 under 5 ms on an RTX 3090), past which the old path is taken, so a
burst of new states is served over a few frames. Both load modes take this path.
RX_VK_FIRST_ASK=0: off. The log's "first draws:" lines count asked, answered and past
the budget.

Co-Authored-By: Claude Fable 5.2 <[email protected]>
Co-Authored-By: Claude Fable 5.2 <[email protected]>
Commenting is not possible because the repository is archived.
No description provided.