feat(vk): GPU- and settings-independent pipeline state list (HKSTATE2) #2046

Merged
hckrman101 merged 1 commit from pipeline-states-v2 into main 2026-11-11 01:25:51 +00:00
hckrman101 commented 2026-11-11 01:04:47 +00:00 (Migrated from github.com)

A pipeline state list only helps a player whose GPU and settings make the same pipelines as the PC that recorded it. Version 1 records store that PC's own Vulkan choices: the formats it rendered in, and on a GPU with the mesh fallback, the fallback's topology and stage numbering. So a list recorded on one GPU or at one setting misses on others, and on a GPU without mesh shaders every mesh record was skipped.

What changes

Version 2 records (HKSTATE2, 552 bytes) store what the game asked for:

  • each target's NVN texture format and the NVN format it is drawn in, and the depth target's NVN format;
  • whether the draw was a mesh draw, and the draw's own topology;
  • the variant's texture answers, with stages numbered without the mesh fallback's generated vertex stage (answers_portable, portable_variant).

At prepare time each record goes through the same functions the draw uses, so there is one mapping, not two:

  • attachment_format (built from image_format_of and view_format_of, which texture_get's create and texture_view now call);
  • color_types_of, pulled out of draw();
  • pipeline_topology, also used by draw() and by the predictor's builds;
  • answers_here in record_answers.

Mesh records are now prepared on GPUs that use the mesh fallback too.

Version 1 lists still work:

  • The game reads either version. A play folder's own v1 list is kept as hookshot_pipeline_states_v1.bin and still prepared; new records go to a v2 list.
  • nx2/pipeline_states.py reads both. As soon as any source is v2, merge, check and add convert v1 records to v2 (--v2 converts a v1 list on its own).
    • Conversion maps each Vulkan format back to an NVN format that every GPU renders the same way (V1_NVN, checked against vk_formats.cpp, nvn_api.h and the Vulkan headers) and renumbers v1 mesh records made with the fallback.
    • All 11,527 pipelines in assets/pipeline_states/ convert, none lost.
    • The committed sources stay v1 in this PR.

Results

RTX 3090, cold caches (another stamp, no compute list, no driver pipeline cache, empty NVIDIA shader cache, then --prepare-shaders), a save loaded into Hyrule Field. The number is the total of draws left out (not ready) from the menus into field play.

Shipped list Prepared and played with Draws left out
none (as releases ship today) the recording settings 130,656
v1 list, current main mesh shaders off (RX_VK_FORCE_MESH_FALLBACK=1) 192
v2 list recorded with mesh shaders on mesh shaders off: all 56 mesh records built as fallback pipelines 12
v2 list recorded at the lowest settings (game's upscaler, HDR off, frame generation off, 1080p) DLSS 1440p 24
same DLAA + mesh shaders off 12
same FSR 3.1 24
same HDR (Expanded), 1440p 0
same 1440p, game's upscaler 0

The 12–24 left over are the same one or two programs every time. The recording run never drew them, so they aren't in the list at all; it's a coverage gap, not the mapping.

The prepare's state-list step takes under a second. Total prepare time with a list is about the same as without one.

Not proven yet

  • HDR "Native (the game's)" (hdr=2): the headless test reported SDR to the game, so the game's own HDR path never ran. It needs a run on an HDR display.
  • FSR 3.1 with frame generation: one run left out 1,536 draws, all from scene shaders not in the list, so it looks like that run played a different scene. FSR 3.1 alone was clean; it needs a rerun.
  • ASTC targets: a GPU that samples ASTC natively can't render into it and leaves the target out, exactly as its draws do. No game target seen so far is ASTC.

With #2040

#2040 (sources split into the tour's list and contributed files) is already in main, and this PR is built on it.

Follow-up I can open: convert each committed source to v2 in place (nx2/pipeline_states.py --v2, notes kept) and update test_pipeline_state_sources.py's pre-split hash, which is computed over the v1 bytes. After that the tour and contributors write v2 directly.

Tests

  • New tests/test_pipeline_state_portable.py compiles the real mapping code against two made-up GPUs: one samples ASTC and has mesh shaders, the other decodes ASTC and uses the mesh fallback.
    • the draw's format equals the list's format for every NVN format;
    • a mesh program gives the same record whether recorded natively or through the fallback, and maps back to each GPU's own stages and variant;
    • v1 fallback records are renumbered; both versions read back.
  • test_pipeline_states.py and test_pipeline_state_sources.py cover the v2 layout, conversion and mixed-version merge, add and check.
  • Six source checks that read the changed code are updated.
A pipeline state list only helps a player whose GPU and settings make the same pipelines as the PC that recorded it. Version 1 records store that PC's own Vulkan choices: the formats it rendered in, and on a GPU with the mesh fallback, the fallback's topology and stage numbering. So a list recorded on one GPU or at one setting misses on others, and on a GPU without mesh shaders every mesh record was skipped. ## What changes Version 2 records (`HKSTATE2`, 552 bytes) store what the game asked for: - each target's NVN texture format and the NVN format it is drawn in, and the depth target's NVN format; - whether the draw was a mesh draw, and the draw's own topology; - the variant's texture answers, with stages numbered without the mesh fallback's generated vertex stage (`answers_portable`, `portable_variant`). At prepare time each record goes through the same functions the draw uses, so there is one mapping, not two: - `attachment_format` (built from `image_format_of` and `view_format_of`, which `texture_get`'s `create` and `texture_view` now call); - `color_types_of`, pulled out of `draw()`; - `pipeline_topology`, also used by `draw()` and by the predictor's builds; - `answers_here` in `record_answers`. Mesh records are now prepared on GPUs that use the mesh fallback too. Version 1 lists still work: - The game reads either version. A play folder's own v1 list is kept as `hookshot_pipeline_states_v1.bin` and still prepared; new records go to a v2 list. - `nx2/pipeline_states.py` reads both. As soon as any source is v2, merge, check and add convert v1 records to v2 (`--v2` converts a v1 list on its own). - Conversion maps each Vulkan format back to an NVN format that every GPU renders the same way (`V1_NVN`, checked against `vk_formats.cpp`, `nvn_api.h` and the Vulkan headers) and renumbers v1 mesh records made with the fallback. - All 11,527 pipelines in `assets/pipeline_states/` convert, none lost. - The committed sources stay v1 in this PR. ## Results RTX 3090, cold caches (another stamp, no compute list, no driver pipeline cache, empty NVIDIA shader cache, then `--prepare-shaders`), a save loaded into Hyrule Field. The number is the total of `draws left out (not ready)` from the menus into field play. | Shipped list | Prepared and played with | Draws left out | |---|---|---| | none (as releases ship today) | the recording settings | 130,656 | | v1 list, current main | mesh shaders off (`RX_VK_FORCE_MESH_FALLBACK=1`) | 192 | | v2 list recorded with mesh shaders on | mesh shaders off: all 56 mesh records built as fallback pipelines | 12 | | v2 list recorded at the lowest settings (game's upscaler, HDR off, frame generation off, 1080p) | DLSS 1440p | 24 | | same | DLAA + mesh shaders off | 12 | | same | FSR 3.1 | 24 | | same | HDR (Expanded), 1440p | 0 | | same | 1440p, game's upscaler | 0 | The 12–24 left over are the same one or two programs every time. The recording run never drew them, so they aren't in the list at all; it's a coverage gap, not the mapping. The prepare's state-list step takes under a second. Total prepare time with a list is about the same as without one. ## Not proven yet - **HDR "Native (the game's)" (`hdr=2`):** the headless test reported SDR to the game, so the game's own HDR path never ran. It needs a run on an HDR display. - **FSR 3.1 with frame generation:** one run left out 1,536 draws, all from scene shaders not in the list, so it looks like that run played a different scene. FSR 3.1 alone was clean; it needs a rerun. - **ASTC targets:** a GPU that samples ASTC natively can't render into it and leaves the target out, exactly as its draws do. No game target seen so far is ASTC. ## With #2040 #2040 (sources split into the tour's list and contributed files) is already in main, and this PR is built on it. Follow-up I can open: convert each committed source to v2 in place (`nx2/pipeline_states.py --v2`, notes kept) and update `test_pipeline_state_sources.py`'s pre-split hash, which is computed over the v1 bytes. After that the tour and contributors write v2 directly. ## Tests - New `tests/test_pipeline_state_portable.py` compiles the real mapping code against two made-up GPUs: one samples ASTC and has mesh shaders, the other decodes ASTC and uses the mesh fallback. - the draw's format equals the list's format for every NVN format; - a mesh program gives the same record whether recorded natively or through the fallback, and maps back to each GPU's own stages and variant; - v1 fallback records are renumbered; both versions read back. - `test_pipeline_states.py` and `test_pipeline_state_sources.py` cover the v2 layout, conversion and mixed-version merge, add and check. - Six source checks that read the changed code are updated.
Arkex-ai commented 2026-11-11 01:33:53 +00:00 (Migrated from github.com)

The HKSTATE2 Windows source fixture still fails to compile with the MSVC/UCRT headers. The same fopen deprecation promoted to an error appears in this PR's Windows run and the #2060 Windows run. #2060 changes only the Python setup parser and its fixture. Other source fixtures already use the project's Windows CRT define when compiling production C/C++ slices.

The HKSTATE2 Windows source fixture still fails to compile with the MSVC/UCRT headers. The same `fopen` deprecation promoted to an error appears in [this PR's Windows run](https://github.com/NotRexed/Hookshot/actions/runs/42936268301/job/130247000314) and [the #2060 Windows run](https://github.com/NotRexed/Hookshot/actions/runs/42939238766/job/130256813819). #2060 changes only the Python setup parser and its fixture. Other source fixtures already use the project's Windows CRT define when compiling production C/C++ slices.
Commenting is not possible because the repository is archived.
No description provided.