Bugfix: blank menu text on AMD GPUs - order a new texture's clear before its first upload #228

Merged
Segmentree merged 1 commit from fix-menu-text into main 2026-11-07 17:20:11 +00:00
Segmentree commented 2026-11-07 16:47:57 +00:00 (Migrated from github.com)

What this fixes

All title, menu and save text is blank on AMD: the menu bars draw but have no letters.

RX 6750 XT (RDNA2), AMD driver 32.0.21045.16010 (Adrenalin 26.10.1), Windows 10 22H2, Ryzen 5 5600X, 32 GB.
Game 0400486024DEC000 v131072, build e0f3ccfa, built from a .nup with the amd-shared-memory
HookshotBuilder (bundled source = cbed9ad).

Each string's letters are written by the CPU into a new linear R8 texture (256x46, 640x46, …), which program
0ed5737e4ec64d5d draws once into the string's label. On the GPU that R8 texture is all zero, so the label
stays transparent:

vk: dump 347 pass c0 prog 0ed5737e4ec64d5d #12: tex 0x100121cba00 format 37 242x40: ... mean 0 0 0 0, min 0 0 0 0, max 0 0 0 0
vk: dump 349 sampled by 0ed5737e4ec64d5d #14: tex 0x106fe71600 format 9 256x46: ... mean 0 0 0 1, min 0 0 0 1, max 0 0 0 1

The letters are in guest memory before the texture's first upload, and upload() reads them: a temporary
probe on the linear path counted 2703 non-zero bytes of 11776, read in place, with no capture shadow. They
get lost on the GPU side.

Cause

create() moves a new image to GENERAL and clears it in the upload command buffer. The first upload, a
vkCmdCopyBufferToImage in upload(), is recorded right after it in the same command buffer, with no barrier
between the clear and the copy. With no dependency between two transfer writes to the same image, they can
execute in either order. NVIDIA's driver runs them in order. On AMD the (fast) clear lands last, so a texture
uploaded in the recording that created it reads zeros. Textures redrawn every frame hide this, but a label drawn
only once stays blank. Compressed textures (ASTC/BC) aren't cleared, so they're unaffected.

Change

gpu/vk/vk_texture.cpp: an image barrier right after the creation clear (transfer writes → all commands,
GENERAL → GENERAL), only for the images that are cleared. 13 lines; nothing changes for images that aren't cleared.

Tested

  • Same PC and build folder; only hookshot_vk.dll and hookshot.exe rebuilt with the change (on cbed9ad).
  • The title shows "New Game" and "Options / ZELDA NOTES Setting". The same dump now reads:
    vk: dump 355 pass c0 prog 0ed5737e4ec64d5d #12: tex 0x100121cba00 format 37 242x40: ... mean 0.1871 0.1871 0.1871 0.1871, min 0 0 0 0, max 1 1 1 1
    vk: dump 357 sampled by 0ed5737e4ec64d5d #14: tex 0x106fe71600 format 9 256x46: ... mean 0.1743 0 0 1, min 0 0 0 1, max 1 0 0 1
    
  • Played a new game from the opening (the Deku Tree and Navi scene) into Kokiri Forest: menu and dialogue text
    readable.
  • Back-to-back headless title runs, old vs fixed: 26.3 vs 26.0 fps, the same within noise.
  • None of the existing switches fix it: RX_VK_NO_FOLLOW=1, RX_VK_OOT_VISUAL=0, HOS_NO_RENDERER_WAIT=1,
    RX_VK_MIRROR=0, RX_VK_PRECOMPILE=0, RX_VK_CPU_OVER_GPU=1, RX_VK_UPLOAD_ALL=1, RX_VK_TEX_CAPTURE=0,
    RX_VK_CAPTURE=0.
  • The branch is rebased on 2a9fe7f (current main, with the Linux support). Upstream's changes to vk_texture.cpp
    since cbed9ad are elsewhere in the file; the hunk applies as tested.

Not covered

  • Not tested on NVIDIA, where the clear and copy already ran in order.
  • No regression test: the race needs a real AMD GPU, and the CI regressions are GPU-free.
  • Separate issue: the driver's compiler crash on pipeline 5fa56485a6b07619 after the Deku Tree scene. Same as
    #166, and avoided here with RX_VK_NOOPT=1.
  • Possibly the same race, not verified: #40 and #44 (menu text missing at random), #160 (AMD 780M, "New Game"
    text corrupted).

No game files, shader binaries, caches, images or saves are included.

## What this fixes All title, menu and save text is blank on AMD: the menu bars draw but have no letters. **RX 6750 XT (RDNA2), AMD driver 32.0.21045.16010 (Adrenalin 26.10.1), Windows 10 22H2, Ryzen 5 5600X, 32 GB.** Game `0400486024DEC000` v131072, build `e0f3ccfa`, built from a `.nup` with the `amd-shared-memory` HookshotBuilder (bundled source = `cbed9ad`). Each string's letters are written by the CPU into a new linear R8 texture (256x46, 640x46, …), which program `0ed5737e4ec64d5d` draws once into the string's label. On the GPU that R8 texture is all zero, so the label stays transparent: ``` vk: dump 347 pass c0 prog 0ed5737e4ec64d5d #12: tex 0x100121cba00 format 37 242x40: ... mean 0 0 0 0, min 0 0 0 0, max 0 0 0 0 vk: dump 349 sampled by 0ed5737e4ec64d5d #14: tex 0x106fe71600 format 9 256x46: ... mean 0 0 0 1, min 0 0 0 1, max 0 0 0 1 ``` The letters are in guest memory before the texture's first upload, and `upload()` reads them: a temporary probe on the linear path counted 2703 non-zero bytes of 11776, read in place, with no capture shadow. They get lost on the GPU side. ## Cause `create()` moves a new image to GENERAL and clears it in the upload command buffer. The first upload, a `vkCmdCopyBufferToImage` in `upload()`, is recorded right after it in the same command buffer, with no barrier between the clear and the copy. With no dependency between two transfer writes to the same image, they can execute in either order. NVIDIA's driver runs them in order. On AMD the (fast) clear lands last, so a texture uploaded in the recording that created it reads zeros. Textures redrawn every frame hide this, but a label drawn only once stays blank. Compressed textures (ASTC/BC) aren't cleared, so they're unaffected. ## Change `gpu/vk/vk_texture.cpp`: an image barrier right after the creation clear (transfer writes → all commands, GENERAL → GENERAL), only for the images that are cleared. 13 lines; nothing changes for images that aren't cleared. ## Tested - Same PC and build folder; only `hookshot_vk.dll` and `hookshot.exe` rebuilt with the change (on `cbed9ad`). - The title shows "New Game" and "Options / ZELDA NOTES Setting". The same dump now reads: ``` vk: dump 355 pass c0 prog 0ed5737e4ec64d5d #12: tex 0x100121cba00 format 37 242x40: ... mean 0.1871 0.1871 0.1871 0.1871, min 0 0 0 0, max 1 1 1 1 vk: dump 357 sampled by 0ed5737e4ec64d5d #14: tex 0x106fe71600 format 9 256x46: ... mean 0.1743 0 0 1, min 0 0 0 1, max 1 0 0 1 ``` - Played a new game from the opening (the Deku Tree and Navi scene) into Kokiri Forest: menu and dialogue text readable. - Back-to-back headless title runs, old vs fixed: 26.3 vs 26.0 fps, the same within noise. - None of the existing switches fix it: `RX_VK_NO_FOLLOW=1`, `RX_VK_OOT_VISUAL=0`, `HOS_NO_RENDERER_WAIT=1`, `RX_VK_MIRROR=0`, `RX_VK_PRECOMPILE=0`, `RX_VK_CPU_OVER_GPU=1`, `RX_VK_UPLOAD_ALL=1`, `RX_VK_TEX_CAPTURE=0`, `RX_VK_CAPTURE=0`. - The branch is rebased on `2a9fe7f` (current `main`, with the Linux support). Upstream's changes to `vk_texture.cpp` since `cbed9ad` are elsewhere in the file; the hunk applies as tested. ## Not covered - Not tested on NVIDIA, where the clear and copy already ran in order. - No regression test: the race needs a real AMD GPU, and the CI regressions are GPU-free. - Separate issue: the driver's compiler crash on pipeline `5fa56485a6b07619` after the Deku Tree scene. Same as #166, and avoided here with `RX_VK_NOOPT=1`. - Possibly the same race, not verified: #40 and #44 (menu text missing at random), #160 (AMD 780M, "New Game" text corrupted). No game files, shader binaries, caches, images or saves are included.
shayderrr commented 2026-11-07 17:20:06 +00:00 (Migrated from github.com)

Thank you!

Thank you!
Commenting is not possible because the repository is archived.
No description provided.