fix(vk): retry sparse blocks whose bind failed #2743

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

@juliotec opened this pull request on GitHub · 2026-11-12 10:31 UTC · original

sparse_queue marks a tile (or mip tail) bound when it queues its bind. If
vkQueueBindSparse then failed, sparse_flush only logged it: the blocks stayed
marked bound, so no later write queued them again, and the copies went into
unbacked memory. The fence it waits on was also created unchecked.

What changed (gpu/vk/vk_texture.cpp and .h, sparse_flush, sparse_take): a failed
bind, or a fence that could not be created, marks the queued tiles and mip tails
unbound again (new sparse_unqueue) and returns false, so the next write retries
them. The memory those binds had taken goes to a per-texture free list
(Texture::sparse_free) that sparse_take gives out first, so a bind that keeps
failing reuses it instead of taking new memory on every retry. Since a failing
bind is now retried on later writes, its "binding failed" and "no fence"
messages are capped at 8, like the other sparse messages. The success path is
unchanged.

A texture is only sparse when it is a 2D array over RX_VK_TEX_MAX_MB (1.5 GB)
with sparse support, MKW's course-map case; OoT likely never makes one.

How tested: release build; Zelda OoT on Radeon RX 9070 XT (Windows,
Adrenalin), which checks that the change does not affect non-sparse textures.
The sparse path itself was not run.

Assisted-by: GitHub Copilot

> [@juliotec](https://github.com/juliotec) opened this pull request on GitHub · 2026-11-12 10:31 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2743) sparse_queue marks a tile (or mip tail) bound when it queues its bind. If vkQueueBindSparse then failed, sparse_flush only logged it: the blocks stayed marked bound, so no later write queued them again, and the copies went into unbacked memory. The fence it waits on was also created unchecked. What changed (gpu/vk/vk_texture.cpp and .h, sparse_flush, sparse_take): a failed bind, or a fence that could not be created, marks the queued tiles and mip tails unbound again (new sparse_unqueue) and returns false, so the next write retries them. The memory those binds had taken goes to a per-texture free list (Texture::sparse_free) that sparse_take gives out first, so a bind that keeps failing reuses it instead of taking new memory on every retry. Since a failing bind is now retried on later writes, its "binding failed" and "no fence" messages are capped at 8, like the other sparse messages. The success path is unchanged. A texture is only sparse when it is a 2D array over RX_VK_TEX_MAX_MB (1.5 GB) with sparse support, MKW's course-map case; OoT likely never makes one. How tested: release build; Zelda OoT on Radeon RX 9070 XT (Windows, Adrenalin), which checks that the change does not affect non-sparse textures. The sparse path itself was not run. Assisted-by: GitHub Copilot <!-- github-sync:item:2743 -->
sparse_queue marks a tile (or mip tail) bound when it queues its bind. If
vkQueueBindSparse then failed, sparse_flush only logged it: the blocks stayed
marked bound, so no later write queued them again, and the copies went into
unbacked memory. The fence it waits on was also created unchecked.

What changed (gpu/vk/vk_texture.cpp and .h, sparse_flush, sparse_take): a failed bind, or a fence
that could not be created, marks the queued tiles and mip tails unbound again
(new sparse_unqueue) and returns false, so the next write retries them. The
memory those binds had taken goes to a per-texture free list (Texture::sparse_free)
that sparse_take gives out first, so a bind that keeps failing reuses it instead
of taking new memory on every retry. The success path is unchanged.

How tested: release build; Zelda OoT on Radeon RX 9070 XT (Windows,
Adrenalin).

Assisted-by: GitHub Copilot
Author
Owner

@Joey-Magnus reviewed on GitHub · 2026-11-12 14:34 UTC · original

CI never ran here. I merged this with #2718, #2719, #2741 and #2742 onto current main (vk_texture.cpp merges cleanly with #2741), compiled vk_texture.cpp with clang++, and ran tests/run_all.py with the Vulkan headers. None of the failures come from this PR.

The logic is right. sparse_unqueue marks the queued tiles and mip tails unbound again by the same math sparse_queue uses. The memory goes to a free list that only references the texture's own chunks, which are freed with it. The accounting matches the success path.

Two notes:

  • A texture is only sparse when it is a 2D array over RX_VK_TEX_MAX_MB (1.5 GB) with sparse support (gpu/vk/vk_texture.cpp:1957), which is MKW's course-map case. OoT likely never makes one, so the RX 9070 XT test can't have reached this code. The description should say so.
  • With retries, a bind that keeps failing now logs "binding failed" at :3721 on every write to that texture, with no cap, and each attempt also flushes the queue under present_mu. Please cap the log like the other sparse messages (logged++ < 8).
> [@Joey-Magnus](https://github.com/Joey-Magnus) reviewed on GitHub · 2026-11-12 14:34 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2743#pullrequestreview-5876244596) CI never ran here. I merged this with #2718, #2719, #2741 and #2742 onto current main (vk_texture.cpp merges cleanly with #2741), compiled vk_texture.cpp with clang++, and ran tests/run_all.py with the Vulkan headers. None of the failures come from this PR. The logic is right. sparse_unqueue marks the queued tiles and mip tails unbound again by the same math sparse_queue uses. The memory goes to a free list that only references the texture's own chunks, which are freed with it. The accounting matches the success path. Two notes: - A texture is only sparse when it is a 2D array over RX_VK_TEX_MAX_MB (1.5 GB) with sparse support (gpu/vk/vk_texture.cpp:1957), which is MKW's course-map case. OoT likely never makes one, so the RX 9070 XT test can't have reached this code. The description should say so. - With retries, a bind that keeps failing now logs "binding failed" at :3721 on every write to that texture, with no cap, and each attempt also flushes the queue under present_mu. Please cap the log like the other sparse messages (logged++ < 8). <!-- github-sync:r:5876244596 -->
Author
Owner

@juliotec commented on GitHub · 2026-11-12 18:54 UTC · original

Thanks. Pushed 8a42a6d1: the "binding failed" and "no fence for its binds" messages are capped at 8, like the other sparse messages. The description now says a texture is only sparse in MKW's course-map case, so the OoT test only shows non-sparse textures are unaffected and the sparse path was not run.

> [@juliotec](https://github.com/juliotec) commented on GitHub · 2026-11-12 18:54 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2743#issuecomment-6832955328) Thanks. Pushed 8a42a6d1: the "binding failed" and "no fence for its binds" messages are capped at 8, like the other sparse messages. The description now says a texture is only sparse in MKW's course-map case, so the OoT test only shows non-sparse textures are unaffected and the sparse path was not run. <!-- github-sync:c:6832955328 -->
Commenting is not possible because the repository is archived.
No description provided.