perf(vk): drop another driver's pipeline cache by its header, before reading it #2488

Merged
Joey-Magnus merged 1 commit from perf/vk-cache-header-evict-set-mirror-skip into main 2026-11-11 22:24:05 +00:00
Joey-Magnus commented 2026-11-11 20:49:27 +00:00 (Migrated from github.com)

After a driver update, or with a cache copied from another GPU, every start read the whole mkw_vk_pipelines.bin (hundreds of MB) into memory only to throw it away on the header check. This reads and checks the 32-byte header first. It also takes out two CPU costs on hot paths. None of the three changes what the game does.

These come from King Kuranes' Hookshot 2.1.0 performance package, shared in Discord (he said GitHub is not his thing). This PR keeps only the parts that are self-contained and keep behavior the same; see "Not included" below.

What changed

  • gpu/vk/vk_draw.cpp pcache_file_read: checks the header (size, version, vendor, device, pipeline cache UUID) before it seeks to the end and reads the rest. A cache from this device and driver reaches vkCreatePipelineCache byte for byte as before; a short, truncated or mismatched file is still dropped.
  • gpu/vk/vk_texture.cpp, the texture budget pass: the evicted textures go in an unordered_set. Before, the cache walk ran std::find over a vector of them for every live texture. The same textures are removed.
  • gpu/vk/vk_memory.cpp mirror_sync: skips a clean 2 MB region (region_gen <= synced_gen) from any page in it, not only from its first page. A pool that starts mid-region used to check up to 511 clean pages one by one. region_gen is updated whenever a page in its region is written, and copy runs already handle gaps, so the copies recorded are the same.

Tests

  • New tests/test_pcache_header_read.py: extracts pcache_file_read from vk_draw.cpp and checks that a matching cache comes back unchanged; that another vendor, device or UUID, or a bad header size, is rejected after reading only 32 bytes; and that a truncated, empty or missing file returns nothing. Against main's version the 32-byte check fails, because the whole 1 MB file is read.
  • tests/run_all.py on Linux: 410 passed, 11 skipped (Windows, GPU or game only).
  • clang++ -fsyntax-only with the back end's flags on the three files.
  • Not run in game. The package's author also says his gameplay testing was on a GTX 1050 only; these three changes are not what made his frame rate gain.

Not included (could be follow-ups, each needing an in-game test)

  • New eviction priorities while paused or between render passes ("CP08"/"CP17"), with tuned constants, on by default. The package's "CP24 eviction sort refactor" only speeds up those priorities, so it is left out too.
  • An opt-in BC4 3D render path for NVIDIA Pascal (RX_VK_BC4_STUDIO_SLICES).
  • An ExeFS/RomFS mod merger and installer with bundled third-party binaries.
  • Ordering pipeline prediction reads by cache-file order: main already does this with program_cache_keys.
After a driver update, or with a cache copied from another GPU, every start read the whole `mkw_vk_pipelines.bin` (hundreds of MB) into memory only to throw it away on the header check. This reads and checks the 32-byte header first. It also takes out two CPU costs on hot paths. None of the three changes what the game does. These come from King Kuranes' Hookshot 2.1.0 performance package, shared in Discord (he said GitHub is not his thing). This PR keeps only the parts that are self-contained and keep behavior the same; see "Not included" below. **What changed** - `gpu/vk/vk_draw.cpp` `pcache_file_read`: checks the header (size, version, vendor, device, pipeline cache UUID) before it seeks to the end and reads the rest. A cache from this device and driver reaches `vkCreatePipelineCache` byte for byte as before; a short, truncated or mismatched file is still dropped. - `gpu/vk/vk_texture.cpp`, the texture budget pass: the evicted textures go in an `unordered_set`. Before, the cache walk ran `std::find` over a vector of them for every live texture. The same textures are removed. - `gpu/vk/vk_memory.cpp` `mirror_sync`: skips a clean 2 MB region (`region_gen <= synced_gen`) from any page in it, not only from its first page. A pool that starts mid-region used to check up to 511 clean pages one by one. `region_gen` is updated whenever a page in its region is written, and copy runs already handle gaps, so the copies recorded are the same. **Tests** - New `tests/test_pcache_header_read.py`: extracts `pcache_file_read` from `vk_draw.cpp` and checks that a matching cache comes back unchanged; that another vendor, device or UUID, or a bad header size, is rejected after reading only 32 bytes; and that a truncated, empty or missing file returns nothing. Against `main`'s version the 32-byte check fails, because the whole 1 MB file is read. - `tests/run_all.py` on Linux: 410 passed, 11 skipped (Windows, GPU or game only). - `clang++ -fsyntax-only` with the back end's flags on the three files. - Not run in game. The package's author also says his gameplay testing was on a GTX 1050 only; these three changes are not what made his frame rate gain. **Not included** (could be follow-ups, each needing an in-game test) - New eviction priorities while paused or between render passes ("CP08"/"CP17"), with tuned constants, on by default. The package's "CP24 eviction sort refactor" only speeds up those priorities, so it is left out too. - An opt-in BC4 3D render path for NVIDIA Pascal (`RX_VK_BC4_STUDIO_SLICES`). - An ExeFS/RomFS mod merger and installer with bundled third-party binaries. - Ordering pipeline prediction reads by cache-file order: `main` already does this with `program_cache_keys`.
hckrman101 (Migrated from github.com) approved these changes 2026-11-11 22:20:45 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks, this is a tidy cut of the package: three changes that don't alter behavior, and the risky parts left out.

I checked each one against the code at b489c38:

  • pcache_file_read: it accepts and drops the same files as before. sz > 32 and head_size <= sz still hold, and a short first read leaves head_size = 0, so the check fails before head is compared. A file that matches gets the 32 bytes it already read copied back in front, so vkCreatePipelineCache gets the same bytes. _fseeki64 still maps to fseeko through host/posix/windows.h on Linux.
  • mirror_sync: skipping from a page in the middle of a region is safe. Every place that writes a page generation (stamp_page, and the whole-region region_full path) also sets region_gen for that region, and generations only go up, so region_gen is never below any page's generation in its region. A clean page did nothing in the old loop (every side effect is behind dirty), and memory_written_since/memory_written_runs already skip partial regions the same way. Clamping to end_page keeps the loop bound as it was.
  • The eviction set: <unordered_set> is already included in vk_texture.cpp, each pointer is taken out of gone before textures.erase frees the texture, and the set is only used for lookups, so its order doesn't matter.

The new test uses the {cxx}/{vulkan_headers} arguments that run_all.py on main supports. Counting fread bytes is a good way to pin the early drop. No existing test cuts out the changed code: test_capture_copy_runs.py only checks that mirror_sync still calls append_capture_copy, which it does.

The PR is 32 commits behind main and merges cleanly. No CI checks have run on the branch yet. Approving.

Thanks, this is a tidy cut of the package: three changes that don't alter behavior, and the risky parts left out. I checked each one against the code at b489c38: - `pcache_file_read`: it accepts and drops the same files as before. `sz > 32` and `head_size <= sz` still hold, and a short first read leaves `head_size = 0`, so the check fails before `head` is compared. A file that matches gets the 32 bytes it already read copied back in front, so `vkCreatePipelineCache` gets the same bytes. `_fseeki64` still maps to `fseeko` through `host/posix/windows.h` on Linux. - `mirror_sync`: skipping from a page in the middle of a region is safe. Every place that writes a page generation (`stamp_page`, and the whole-region `region_full` path) also sets `region_gen` for that region, and generations only go up, so `region_gen` is never below any page's generation in its region. A clean page did nothing in the old loop (every side effect is behind `dirty`), and `memory_written_since`/`memory_written_runs` already skip partial regions the same way. Clamping to `end_page` keeps the loop bound as it was. - The eviction set: `<unordered_set>` is already included in `vk_texture.cpp`, each pointer is taken out of `gone` before `textures.erase` frees the texture, and the set is only used for lookups, so its order doesn't matter. The new test uses the `{cxx}`/`{vulkan_headers}` arguments that `run_all.py` on main supports. Counting `fread` bytes is a good way to pin the early drop. No existing test cuts out the changed code: `test_capture_copy_runs.py` only checks that `mirror_sync` still calls `append_capture_copy`, which it does. The PR is 32 commits behind main and merges cleanly. No CI checks have run on the branch yet. Approving.
Commenting is not possible because the repository is archived.
No description provided.