fix(vk): bound compare-exchange loops and give EQUAL depth tests a margin on Intel #548

Merged
fuxau merged 2 commits from fix/intel-arc-cas-depth into main 2026-11-08 16:49:22 +00:00
fuxau commented 2026-11-08 10:08:19 +00:00 (Migrated from github.com)

As asked in #17: the bounded CasLoop and the EQUAL-depth change, one commit each, so they can be tried on other GPUs.

Bounded compare-exchange loops

The four compare-exchange loops of the SPIR-V backend left only on success: CasLoop (the atomics it emulates) and the shared-memory, storage-buffer and global byte/halfword stores (the last one new with #536). Under robust buffer access an atomic on a word out of bounds need not return what it was given, and then the loop never ends. On an Intel Arc B390 the StorageAtomicAddF32 of compute 435a0138b9013040 did that after the intro movie: no device loss, the frame just never came back (the freeze several people report in #17).

Each loop now counts its attempts in a Function variable and leaves on success or at CAS_LOOP_MAX_TRIES (1024). Every failed attempt on a real word means another invocation's succeeded, so contention never comes near the ceiling.

Note: since the foliage-linux-updater release each attempt expects the word the last exchange returned, and with that I no longer saw the hang (9 minutes of play without the ceiling). The ceiling is the guarantee that a loop ends whatever the driver returns.

EQUAL depth margin on Intel

The colour passes after the depth prepass (43ec86f35fd937b9 after 8a8304edb7bdf4bf, 6b4dea… after e28434…, 68642b… after cb71f7…) test EQUAL against depth written by a different program. Intel's compiler does not give the two programs the same position to the last bit, so most ground pixels failed the test: about 550k fragments for the colour pass against 1.8M for the depth one, and the sky showed through the ground.

Where depth is tested, not written, and the game asks for EQUAL, the test becomes LESS_OR_EQUAL with a depth bias of 64 units towards the camera. It is on by default on Intel (vendor 0x8086) only. RX_VK_EQ_BIAS=<units> turns it on for any GPU, so it can be tried elsewhere; RX_VK_EQ_BIAS=0 turns it off.

Testing

  • Intel Arc B390 (Core Ultra X7 358H iGPU), Windows 11, driver 0x1962a0, from the intro movie to Kokiri Forest, played on main at 3d6a388 plus these changes. The ceiling on #536's new global-store loop came with the rebase and is compile-checked only.
  • Kokiri Forest before/after: holes in the ground with the sky showing through → dirt path, moss and shadows drawn. No hang after the intro movie.
  • New game-free checks tests/test_cas_loop_ceiling.py and tests/test_equal_depth_margin.py, wired into source-regressions.yml (paths and run steps). Both pass on this branch and fail on main. The other checks CI runs on Windows pass too, apart from nx2/test_nup_paths.py, which fails the same way on main on this PC (WinError 1314: creating symlinks needs a privilege this account lacks).

Refs #17

As asked in https://git.magicbean.dev/MagicBeanDev/Hookshot/issues/17: the bounded `CasLoop` and the EQUAL-depth change, one commit each, so they can be tried on other GPUs. ### Bounded compare-exchange loops The four compare-exchange loops of the SPIR-V backend left only on success: `CasLoop` (the atomics it emulates) and the shared-memory, storage-buffer and global byte/halfword stores (the last one new with #536). Under robust buffer access an atomic on a word out of bounds need not return what it was given, and then the loop never ends. On an Intel Arc B390 the `StorageAtomicAddF32` of compute `435a0138b9013040` did that after the intro movie: no device loss, the frame just never came back (the freeze several people report in #17). Each loop now counts its attempts in a Function variable and leaves on success or at `CAS_LOOP_MAX_TRIES` (1024). Every failed attempt on a real word means another invocation's succeeded, so contention never comes near the ceiling. Note: since the `foliage-linux-updater` release each attempt expects the word the last exchange returned, and with that I no longer saw the hang (9 minutes of play without the ceiling). The ceiling is the guarantee that a loop ends whatever the driver returns. ### EQUAL depth margin on Intel The colour passes after the depth prepass (`43ec86f35fd937b9` after `8a8304edb7bdf4bf`, `6b4dea…` after `e28434…`, `68642b…` after `cb71f7…`) test EQUAL against depth written by a different program. Intel's compiler does not give the two programs the same position to the last bit, so most ground pixels failed the test: about 550k fragments for the colour pass against 1.8M for the depth one, and the sky showed through the ground. Where depth is tested, not written, and the game asks for EQUAL, the test becomes LESS_OR_EQUAL with a depth bias of 64 units towards the camera. It is on by default on Intel (vendor 0x8086) only. `RX_VK_EQ_BIAS=<units>` turns it on for any GPU, so it can be tried elsewhere; `RX_VK_EQ_BIAS=0` turns it off. ### Testing - Intel Arc B390 (Core Ultra X7 358H iGPU), Windows 11, driver 0x1962a0, from the intro movie to Kokiri Forest, played on main at 3d6a388 plus these changes. The ceiling on #536's new global-store loop came with the rebase and is compile-checked only. - Kokiri Forest before/after: holes in the ground with the sky showing through → dirt path, moss and shadows drawn. No hang after the intro movie. - New game-free checks `tests/test_cas_loop_ceiling.py` and `tests/test_equal_depth_margin.py`, wired into `source-regressions.yml` (paths and run steps). Both pass on this branch and fail on main. The other checks CI runs on Windows pass too, apart from `nx2/test_nup_paths.py`, which fails the same way on main on this PC (WinError 1314: creating symlinks needs a privilege this account lacks). Refs #17
Commenting is not possible because the repository is archived.
No description provided.