feat(vk): RX_VK_WAIT_FIRST_PART=1, an A/B of #2419's fence-wait cost … #2587

Closed
SadTransGirl wants to merge 1 commit from SadTransGirl/feat/wait-first-part-ab into main
SadTransGirl commented 2026-11-12 00:23:59 +00:00 (Migrated from github.com)

…where pools have VRAM mirrors

#2419 made the game's fence waits cover the last part of a split submission, which fixed one-frame flashes on iGPUs (#997), where the later parts read guest memory in place. It costs frame rate: 45-70 -> 38-49 fps on the ROG Ally X it was measured on, and it was not measured on discrete GPUs, where the buffer pools have VRAM mirrors synced at submit.

RX_VK_WAIT_FIRST_PART=1 maps a value to its first part again, as before #2419, and only when mirrors are on. The default is unchanged. Pools left without a mirror (budget full, over 1 GB) are still read in place, so this is an A/B to measure the cost on NVIDIA and discrete AMD, not a fix.

Assisted-by: Claude Opus 5.5, Claude Mythos 5.2

…where pools have VRAM mirrors #2419 made the game's fence waits cover the last part of a split submission, which fixed one-frame flashes on iGPUs (#997), where the later parts read guest memory in place. It costs frame rate: 45-70 -> 38-49 fps on the ROG Ally X it was measured on, and it was not measured on discrete GPUs, where the buffer pools have VRAM mirrors synced at submit. RX_VK_WAIT_FIRST_PART=1 maps a value to its first part again, as before #2419, and only when mirrors are on. The default is unchanged. Pools left without a mirror (budget full, over 1 GB) are still read in place, so this is an A/B to measure the cost on NVIDIA and discrete AMD, not a fix. Assisted-by: Claude Opus 5.5, Claude Mythos 5.2
github-sync changed title from WIP: feat(vk): RX_VK_WAIT_FIRST_PART=1, an A/B of #2419's fence-wait cost … to feat(vk): RX_VK_WAIT_FIRST_PART=1, an A/B of #2419's fence-wait cost … 2026-11-12 11:36:03 +00:00
Owner

@Waryxxful commented on GitHub · 2026-11-12 11:21 UTC · original

Measured on a discrete AMD card, as the description asks. Short version: on this card #2419's wait costs about half the frame rate in Kokiri Forest, and RX_VK_WAIT_FIRST_PART=1 brings back #997's whole-screen flashes. The flashes come from a pool the full mirror budget left in system memory.

Setup

  • GPU: RX 5600 XT 6 GB (RDNA1, no mesh shaders: the compute fallback). Adrenalin 26.10.1, Windows 11.
  • CPU / RAM: Ryzen 5 5600X, 24 GB.
  • Build: this PR merged on main de866017 (renderer only). Conflicts with main resolved: pools_mirrored() and memory_mirrors_on() kept side by side, and the first_part static moved above map_recording. tests/test_value_rec_last_part.py passes.
  • Settings: 1920x1080, frame generation off, the user's own options.
  • Method: every run starts from the same copied save and uses a scripted route through the control bridge: teleport, settle 15 s, 60 s standing. The fps are the medians of the log's N presents lines.
Kokiri Forest fps Hyrule Field fps GPU time (Kokiri)
main, 4 runs 34.0 / 31.5 / 34.6 / 32.4 34.7 / 32.1 / 34.8 / 33.6 18.2-18.9 ms
this PR, switch off 33.4 / 32.8 33.6 18.3-18.5 ms
this PR, RX_VK_WAIT_FIRST_PART=1 62.1 / 63.6 / 61.0 45.7 / 54.8 12.7-13.8 ms

The bridge's own fps agree: 61.1 against the log's 62.1. The log confirms the switch: fence waits return after a split submission's first part.

Flashes (RX_VK_FRAMELOG for the two 40 s windows, scripts/framelog.py):

  • switch off: 2,503 frames, 0 events;
  • switch on: 4,591 frames, 7 whole-screen one-frame flashes (100-136 of 144 blocks brighter, peak 221-248), plus 26 small ones. The worst ones are the white frames of #997: the scene blown out for one frame, then back.

Why, on this card. The mirror budget fills, and a pool is read in place:

vk: pool 0x7fd3000000 (160 MB) stays in system memory: the mirrors have 688 MB of 734

That is the case the description warns about (pools left without a mirror ... are still read in place). On a 6 GB card it happens in normal play: VRAM sits at the driver's allowance, 4,480 of 4,480 MB.

Suggestion. The gain is so large here (2x in Kokiri) that the last-part wait looks worth narrowing rather than dropping. For example, a value keeps its first part's recording only when none of the later parts reads or writes a pool without a mirror, so the unmirrored pool decides per submission instead of per run. I'm happy to measure a version like that on this card, flashes included.

> [@Waryxxful](https://github.com/Waryxxful) commented on GitHub · 2026-11-12 11:21 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2587#issuecomment-6822201504) Measured on a discrete AMD card, as the description asks. Short version: on this card #2419's wait costs **about half the frame rate in Kokiri Forest**, and `RX_VK_WAIT_FIRST_PART=1` **brings back #997's whole-screen flashes**. The flashes come from a pool the full mirror budget left in system memory. **Setup** - **GPU:** RX 5600 XT 6 GB (RDNA1, no mesh shaders: the compute fallback). Adrenalin 26.10.1, Windows 11. - **CPU / RAM:** Ryzen 5 5600X, 24 GB. - **Build:** this PR merged on main `de866017` (renderer only). Conflicts with main resolved: `pools_mirrored()` and `memory_mirrors_on()` kept side by side, and the `first_part` static moved above `map_recording`. `tests/test_value_rec_last_part.py` passes. - **Settings:** 1920x1080, frame generation off, the user's own options. - **Method:** every run starts from the same copied save and uses a scripted route through the control bridge: teleport, settle 15 s, 60 s standing. The fps are the medians of the log's `N presents` lines. | | Kokiri Forest fps | Hyrule Field fps | GPU time (Kokiri) | |---|---|---|---| | main, 4 runs | 34.0 / 31.5 / 34.6 / 32.4 | 34.7 / 32.1 / 34.8 / 33.6 | 18.2-18.9 ms | | this PR, switch off | 33.4 / 32.8 | 33.6 | 18.3-18.5 ms | | this PR, `RX_VK_WAIT_FIRST_PART=1` | **62.1 / 63.6 / 61.0** | **45.7 / 54.8** | 12.7-13.8 ms | The bridge's own fps agree: 61.1 against the log's 62.1. The log confirms the switch: `fence waits return after a split submission's first part`. **Flashes** (`RX_VK_FRAMELOG` for the two 40 s windows, `scripts/framelog.py`): - **switch off:** 2,503 frames, **0 events**; - **switch on:** 4,591 frames, **7 whole-screen one-frame flashes** (100-136 of 144 blocks brighter, peak 221-248), plus 26 small ones. The worst ones are the white frames of #997: the scene blown out for one frame, then back. **Why, on this card.** The mirror budget fills, and a pool is read in place: ``` vk: pool 0x7fd3000000 (160 MB) stays in system memory: the mirrors have 688 MB of 734 ``` That is the case the description warns about (`pools left without a mirror ... are still read in place`). On a 6 GB card it happens in normal play: VRAM sits at the driver's allowance, 4,480 of 4,480 MB. **Suggestion.** The gain is so large here (2x in Kokiri) that the last-part wait looks worth narrowing rather than dropping. For example, a value keeps its first part's recording only when none of the later parts reads or writes a pool without a mirror, so the unmirrored pool decides per submission instead of per run. I'm happy to measure a version like that on this card, flashes included. <!-- github-sync:c:6822201504 -->
github-sync closed this pull request 2026-11-12 16:55:12 +00:00
Owner

@SadTransGirl commented on GitHub · 2026-11-12 16:43 UTC · original

Thanks for measuring this on the RX 5600 XT, @Waryxxful. That settles it: the switch's gain comes with #997's flashes back wherever a pool is read in place, and on a 6 GB card that's normal play. On NVIDIA, #2602 has since taken back most of #2419's cost (Castle Town to the field within about 1 fps of the switch here), so an A/B switch isn't worth carrying. Closing this.

Your narrower idea (a value keeps its first part's recording only when no later part reads or writes a pool without a mirror) looks like the real fix for AMD. It's a different change from this one, so it would be its own PR.

> [@SadTransGirl](https://github.com/SadTransGirl) commented on GitHub · 2026-11-12 16:43 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2587#issuecomment-6829803862) Thanks for measuring this on the RX 5600 XT, @Waryxxful. That settles it: the switch's gain comes with #997's flashes back wherever a pool is read in place, and on a 6 GB card that's normal play. On NVIDIA, #2602 has since taken back most of #2419's cost (Castle Town to the field within about 1 fps of the switch here), so an A/B switch isn't worth carrying. Closing this. Your narrower idea (a value keeps its first part's recording only when no later part reads or writes a pool without a mirror) looks like the real fix for AMD. It's a different change from this one, so it would be its own PR. <!-- github-sync:c:6829803862 -->
Commenting is not possible because the repository is archived.
No description provided.