vk: draws and clears into BC textures through block views (RADV) #1332

Open
BiasedMonkey wants to merge 1 commit from BiasedMonkey/fix/bc-block-view-targets into main
BiasedMonkey commented 2026-11-09 21:59:52 +00:00 (Migrated from github.com)

What: OoT draws into and clears a 16-layer BC4 texture array through an RGBA16UI view of its blocks (one 8-byte block = one RGBA16UI texel). Compressed images were never created with COLOR_ATTACHMENT, so make_view() returned null for those views and the draws and clears were silently left out. This is the view of texture 0x1133ccb000 (vk 139) as vk 95 type 1 levels 0+1 layers N+1 not made line found in the AMD haze reports (#914, #1101), 16 times, one per layer.

Changes (gpu/vk/vk_texture.cpp, vk_draw.cpp, vk_xfer.cpp):

  • BC images get COLOR_ATTACHMENT when the raw block format can be a color attachment and vkGetPhysicalDeviceImageFormatProperties accepts it. This is asked separately from STORAGE, so a refusal does not cost the raw storage views (the amdvlk note there).
  • If image creation fails, it is retried without COLOR_ATTACHMENT first, then without STORAGE (as before).
  • The render area of a block view (render targets in targets(), region clears in clear_tex()) is computed in blocks (view_texels()), not in the image's texels. The clear rect is clamped to it.
  • On by default with Mesa RADV only. NVIDIA, amdvlk and the others are unchanged. RX_VK_BC_TARGETS=1 / 0 turns it on / off on any driver.
  • Diagnostics: the "not made" line now says why (usage … not in the image's …, or the vkCreateImageView result), and compressed images log when one of their block-view usages is refused.

How it was found:

  1. A standalone Vulkan repro (BC4 image with MUTABLE_FORMAT | BLOCK_TEXEL_VIEW_COMPATIBLE | EXTENDED_USAGE, plus a storage view as RGBA16UI / R32G32UI) passes on RADV with the Khronos validation layer and no message. So the driver was not refusing it.
  2. With the diagnostic build, the line read not made: usage 0x10 not in the image's 0xf. The image had STORAGE; the game wanted COLOR_ATTACHMENT.
  3. A second repro created the BC4 image with COLOR_ATTACHMENT, cleared one layer through an RGBA16UI view with dynamic rendering, and read it back: all 16384 blocks held the clear value, with no validation message, on an RX 7700S (NAVI33) and a 780M (PHOENIX), Mesa 26.2.6.

Tested: v2.0.11 + this commit (rebased here onto main, builds), RX 7700S, CachyOS, Mesa 26.2.6. Only hookshot_vk.so was rebuilt (RX_VK_ONLY=1).

Log before (v2.0.11):

[hos Presentation Thread] vk: view of texture 0x1133ccb000 (vk 139) as vk 95 type 1 levels 0+1 layers 0+1 not made
[hos Presentation Thread] vk: view of texture 0x1133ccb000 (vk 139) as vk 95 type 1 levels 0+1 layers 1+1 not made
… (16 lines, layers 0 to 15)

Diagnostic build:

vk: view of texture 0x1133ccb000 (vk 139) as vk 95 type 1 levels 0+1 layers 0+1 not made: usage 0x10 not in the image's 0xf

After:

[hos Presentation Thread] vk: draws and clears into compressed textures on (RX_VK_BC_TARGETS, on with RADV)
(no "not made" line, gameplay up to 1560 presents)

Not tested: NVIDIA, amdvlk, Intel (unchanged by default; RX_VK_BC_TARGETS=1 to try). No visible change on this machine: the white haze over outdoor scenes (#1004) is still there with this commit, so it has another cause. This PR only stops the draws and clears into that texture from being skipped.

(Update: on this machine the washed-out picture turned out to be #1042, the swapchain's colour space on KDE Wayland: with #1086's change applied to v2.0.11 the picture is right again, so #1086 is confirmed on an RX 7700S, RADV, Mesa 26.2.6. This PR is independent of it.)

AI-assisted (Claude Code) on behalf of the author: diagnosis, repros, patch and write-up; built and run on the machine above.

🤖 Generated with Claude Code

**What:** OoT draws into and clears a 16-layer BC4 texture array through an RGBA16UI view of its blocks (one 8-byte block = one RGBA16UI texel). Compressed images were never created with `COLOR_ATTACHMENT`, so `make_view()` returned null for those views and the draws and clears were silently left out. This is the `view of texture 0x1133ccb000 (vk 139) as vk 95 type 1 levels 0+1 layers N+1 not made` line found in the AMD haze reports (#914, #1101), 16 times, one per layer. **Changes** (`gpu/vk/vk_texture.cpp`, `vk_draw.cpp`, `vk_xfer.cpp`): - BC images get `COLOR_ATTACHMENT` when the raw block format can be a color attachment and `vkGetPhysicalDeviceImageFormatProperties` accepts it. This is asked separately from `STORAGE`, so a refusal does not cost the raw storage views (the amdvlk note there). - If image creation fails, it is retried without `COLOR_ATTACHMENT` first, then without `STORAGE` (as before). - The render area of a block view (render targets in `targets()`, region clears in `clear_tex()`) is computed in blocks (`view_texels()`), not in the image's texels. The clear rect is clamped to it. - **On by default with Mesa RADV only.** NVIDIA, amdvlk and the others are unchanged. `RX_VK_BC_TARGETS=1` / `0` turns it on / off on any driver. - Diagnostics: the "not made" line now says why (`usage … not in the image's …`, or the `vkCreateImageView` result), and compressed images log when one of their block-view usages is refused. **How it was found:** 1. A standalone Vulkan repro (BC4 image with `MUTABLE_FORMAT | BLOCK_TEXEL_VIEW_COMPATIBLE | EXTENDED_USAGE`, plus a storage view as RGBA16UI / R32G32UI) passes on RADV with the Khronos validation layer and no message. So the driver was not refusing it. 2. With the diagnostic build, the line read `not made: usage 0x10 not in the image's 0xf`. The image had `STORAGE`; the game wanted `COLOR_ATTACHMENT`. 3. A second repro created the BC4 image with `COLOR_ATTACHMENT`, cleared one layer through an RGBA16UI view with dynamic rendering, and read it back: all 16384 blocks held the clear value, with no validation message, on an RX 7700S (NAVI33) and a 780M (PHOENIX), Mesa 26.2.6. **Tested:** v2.0.11 + this commit (rebased here onto main, builds), RX 7700S, CachyOS, Mesa 26.2.6. Only `hookshot_vk.so` was rebuilt (`RX_VK_ONLY=1`). Log before (v2.0.11): ``` [hos Presentation Thread] vk: view of texture 0x1133ccb000 (vk 139) as vk 95 type 1 levels 0+1 layers 0+1 not made [hos Presentation Thread] vk: view of texture 0x1133ccb000 (vk 139) as vk 95 type 1 levels 0+1 layers 1+1 not made … (16 lines, layers 0 to 15) ``` Diagnostic build: ``` vk: view of texture 0x1133ccb000 (vk 139) as vk 95 type 1 levels 0+1 layers 0+1 not made: usage 0x10 not in the image's 0xf ``` After: ``` [hos Presentation Thread] vk: draws and clears into compressed textures on (RX_VK_BC_TARGETS, on with RADV) (no "not made" line, gameplay up to 1560 presents) ``` **Not tested:** NVIDIA, amdvlk, Intel (unchanged by default; `RX_VK_BC_TARGETS=1` to try). No visible change on this machine: the white haze over outdoor scenes (#1004) is still there with this commit, so it has another cause. This PR only stops the draws and clears into that texture from being skipped. (Update: on this machine the washed-out picture turned out to be #1042, the swapchain's colour space on KDE Wayland: with #1086's change applied to v2.0.11 the picture is right again, so #1086 is confirmed on an RX 7700S, RADV, Mesa 26.2.6. This PR is independent of it.) AI-assisted (Claude Code) on behalf of the author: diagnosis, repros, patch and write-up; built and run on the machine above. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Nitnek commented 2026-11-09 22:37:32 +00:00 (Migrated from github.com)

@BiasedMonkey we hit the same views from the NVIDIA side, and I opened #1338 eight minutes after this without having seen it. Sorry for the overlap. The two are close, and I think they combine well. What #1338 adds, and could just as well be folded in here:

  • The texture is OoT's dither noise. The views write two 64³ BC4 noise volumes, rendered once while an area loads by 53509044401d78d2. The foliage's LOD crossfade reads that noise. With it at zero, trees go bare at mid distance, full again near and far (#1151).
  • That program is still on RX_VK_SKIP_PROG's default list on main, so even with the views made, the volumes are never written unless it comes off. It was put there for device losses on first starts. Those fit the render area being the texels' 64×64 over a 16×16 view, which both PRs now size in blocks.
  • NVIDIA data: RTX 3070, Windows 11, attachment usage on and the skip removed. 12 of 12 starts ran the pass with 0 device lost, the volume written every time, and the needles kept at mid distance. Before, the comment there counts 7 losses in 14 first starts.

So I'd suggest enabling it on NVIDIA too, not only RADV, and taking 53509044401d78d2 off the default skip list. I'm happy to close #1338 in favour of this PR if you add those, or to rebase #1338 onto it. Whichever the maintainers prefer. I can test a build of this PR on the 3070 at the same spots.

@BiasedMonkey we hit the same views from the NVIDIA side, and I opened #1338 eight minutes after this without having seen it. Sorry for the overlap. The two are close, and I think they combine well. What #1338 adds, and could just as well be folded in here: - **The texture is OoT's dither noise.** The views write two 64³ BC4 noise volumes, rendered once while an area loads by `53509044401d78d2`. The foliage's LOD crossfade reads that noise. With it at zero, trees go bare at mid distance, full again near and far (#1151). - **That program is still on `RX_VK_SKIP_PROG`'s default list on main**, so even with the views made, the volumes are never written unless it comes off. It was put there for device losses on first starts. Those fit the render area being the texels' 64×64 over a 16×16 view, which both PRs now size in blocks. - **NVIDIA data:** RTX 3070, Windows 11, attachment usage on and the skip removed. 12 of 12 starts ran the pass with 0 device lost, the volume written every time, and the needles kept at mid distance. Before, the comment there counts 7 losses in 14 first starts. So I'd suggest enabling it on NVIDIA too, not only RADV, and taking `53509044401d78d2` off the default skip list. I'm happy to close #1338 in favour of this PR if you add those, or to rebase #1338 onto it. Whichever the maintainers prefer. I can test a build of this PR on the 3070 at the same spots.
hckrman101 (Migrated from github.com) reviewed 2026-11-09 22:45:55 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks, the diagnosis is solid and the diagnostics (the reason in the "not made" line, the dropped-usage log) are useful on their own. The retry chain (without COLOR_ATTACHMENT, then without STORAGE) is also the safer way to add the usage. A few things:

  1. The noise pass is still skipped by default. 53509044401d78d2 is still on RX_VK_SKIP_PROG's default list in vk_draw.cpp, so on RADV the draws of that program never run and the volume stays zero (this fits your "no visible change"). As #1338 / Nitnek's comment on this PR say, that program renders OoT's 64^3 BC4 noise volumes that the foliage LOD crossfade reads (#1151). The skip was added for device losses that fit the oversized render area, which you now fix, so I'd drop it from the default list here (or in a follow-up), otherwise the fix isn't exercised in normal play.

  2. RADV-only default. Understood, since you only tested RADV. But Nitnek reports 12/12 clean starts on an RTX 3070 with the usage on, so it looks safe to enable for NVIDIA too. The query-gated retry chain here makes that low risk, so I'd widen it once you're happy with that data.

  3. 3D images. #1338 mentions the noise volumes are 3D (64^3) and that the usage query doesn't cover the 2D_ARRAY_COMPATIBLE per-slice views, so it takes the usage for 3D images unqueried. Here a 3D BC image whose query fails simply doesn't get the usage, so those volumes would stay unwritten. Worth checking which of 2D array / 3D the game's texture 0x1133ccb000 actually is; if 3D, this needs the same handling (your fallback chain already covers the case where creation is then refused).

  4. Clear rect units. In clear_tex() the render area becomes vw x vh blocks, but s.x/s.y/s.w/s.h are still in image texels and are only clamped, not divided by 4. For a sub-rect clear of a block view this clears the wrong region (full-surface clears are fine). Probably a conversion with view_texels-style rounding is what you want. Nit-ish since OoT's clears are probably full-surface.

  5. Nit: no test is added (#1338 adds tests/test_bc_raw_targets.py, a source check; you could borrow it, adjusting for the retry chain). CI shows no checks on the branch, so the Windows build hasn't been exercised; nothing in the diff looks like it would break it.

On overlap with #1338: both make the same targets() block-size fix and add COLOR_ATTACHMENT for BC images. This PR is more complete on the Vulkan side (clear_tex path, retry on image-creation failure, diagnostics). #1338 is more complete on what ships (3D images, removes the skip-list entry, all drivers, test). Neither alone is the full fix, and they will conflict in vk_texture.cpp / vk_draw.cpp. Folding items 1-3 into this one (as Nitnek offered) and closing #1338 looks like the cleanest path.

Thanks, the diagnosis is solid and the diagnostics (the reason in the "not made" line, the dropped-usage log) are useful on their own. The retry chain (without COLOR_ATTACHMENT, then without STORAGE) is also the safer way to add the usage. A few things: 1. **The noise pass is still skipped by default.** `53509044401d78d2` is still on `RX_VK_SKIP_PROG`'s default list in `vk_draw.cpp`, so on RADV the draws of that program never run and the volume stays zero (this fits your "no visible change"). As #1338 / Nitnek's comment on this PR say, that program renders OoT's 64^3 BC4 noise volumes that the foliage LOD crossfade reads (#1151). The skip was added for device losses that fit the oversized render area, which you now fix, so I'd drop it from the default list here (or in a follow-up), otherwise the fix isn't exercised in normal play. 2. **RADV-only default.** Understood, since you only tested RADV. But Nitnek reports 12/12 clean starts on an RTX 3070 with the usage on, so it looks safe to enable for NVIDIA too. The query-gated retry chain here makes that low risk, so I'd widen it once you're happy with that data. 3. **3D images.** #1338 mentions the noise volumes are 3D (64^3) and that the usage query doesn't cover the 2D_ARRAY_COMPATIBLE per-slice views, so it takes the usage for 3D images unqueried. Here a 3D BC image whose query fails simply doesn't get the usage, so those volumes would stay unwritten. Worth checking which of 2D array / 3D the game's texture 0x1133ccb000 actually is; if 3D, this needs the same handling (your fallback chain already covers the case where creation is then refused). 4. **Clear rect units.** In `clear_tex()` the render area becomes `vw x vh` blocks, but `s.x/s.y/s.w/s.h` are still in image texels and are only clamped, not divided by 4. For a sub-rect clear of a block view this clears the wrong region (full-surface clears are fine). Probably a conversion with `view_texels`-style rounding is what you want. Nit-ish since OoT's clears are probably full-surface. 5. Nit: no test is added (#1338 adds `tests/test_bc_raw_targets.py`, a source check; you could borrow it, adjusting for the retry chain). CI shows no checks on the branch, so the Windows build hasn't been exercised; nothing in the diff looks like it would break it. On overlap with #1338: both make the same `targets()` block-size fix and add COLOR_ATTACHMENT for BC images. This PR is more complete on the Vulkan side (clear_tex path, retry on image-creation failure, diagnostics). #1338 is more complete on what ships (3D images, removes the skip-list entry, all drivers, test). Neither alone is the full fix, and they will conflict in `vk_texture.cpp` / `vk_draw.cpp`. Folding items 1-3 into this one (as Nitnek offered) and closing #1338 looks like the cleanest path.
Nitnek commented 2026-11-09 23:02:03 +00:00 (Migrated from github.com)

Tested this PR on NVIDIA (RTX 3070, Windows 11), merged onto main 7dbdcdc. Each run loads a Hyrule Field save near Lon Lon Ranch and is dumped 10 s later with RX_VK_DUMP_SAMPLED on the spruce LOD programs. The table reads the noise volume the foliage samples.

Config Raw views of 0x1133ccb000 Noise volume (a dumped slice) Starts
default (off on NVIDIA) 16 not made all 0 1
RX_VK_BC_TARGETS=1 made still all 0: 53509044401d78d2 is left out (RX_VK_SKIP_PROG) 1
RX_VK_BC_TARGETS=1 + RX_VK_SKIP_PROG=- made values 23..247, mean 135 4, 0 device lost, written every time

So on NVIDIA the PR works once the noise pass is allowed to run. That confirms point 1 of @hckrman101's review.

On the 2D array vs 3D question: the texture is 3D, 64×64×64, BC4 (NVN 0x49), target 2. The game renders its 64 slices one by one as 2D views, two volumes, 128 draws.

Tested this PR on NVIDIA (RTX 3070, Windows 11), merged onto main 7dbdcdc. Each run loads a Hyrule Field save near Lon Lon Ranch and is dumped 10 s later with `RX_VK_DUMP_SAMPLED` on the spruce LOD programs. The table reads the noise volume the foliage samples. | Config | Raw views of `0x1133ccb000` | Noise volume (a dumped slice) | Starts | |---|---|---|---| | default (off on NVIDIA) | 16 `not made` | all 0 | 1 | | `RX_VK_BC_TARGETS=1` | made | **still all 0**: `53509044401d78d2` is `left out (RX_VK_SKIP_PROG)` | 1 | | `RX_VK_BC_TARGETS=1` + `RX_VK_SKIP_PROG=-` | made | values 23..247, mean 135 | 4, 0 device lost, written every time | So on NVIDIA the PR works once the noise pass is allowed to run. That confirms point 1 of @hckrman101's review. On the 2D array vs 3D question: the texture is **3D**, 64×64×64, BC4 (NVN 0x49), target 2. The game renders its 64 slices one by one as 2D views, two volumes, 128 draws.
hckrman101 commented 2026-11-10 17:49:08 +00:00 (Migrated from github.com)

Heads-up: this conflicts with main now (a lot landed today). Could you merge main into the branch (or rebase) and resolve the conflicts? Thanks!

Heads-up: this conflicts with main now (a lot landed today). Could you merge main into the branch (or rebase) and resolve the conflicts? Thanks!
Joey-Magnus (Migrated from github.com) reviewed 2026-11-11 21:45:51 +00:00
Joey-Magnus (Migrated from github.com) left a comment

I think this can be closed. Main already does what it does:

  • #1338 (merged as 9d6936a) adds COLOR_ATTACHMENT to BC images on every driver, not only RADV, after asking vkGetPhysicalDeviceImageFormatProperties. It also sizes targets() in blocks for raw views and takes 53509044401d78d2 off the default skip list.
  • Commit 5daa99c made clear_tex convert the area and the clear rect to blocks and retry without the attachment usage. That's the point-4 bug the earlier review raised, which this branch still has.
  • #2327 handles 3D BC slices through a separate uncompressed mip. Nitnek showed the noise volume is 3D, so the 2D-only path here wouldn't write it.

The extra "not made" reason in the log is the only part main lacks, and it would be a small follow-up on current main. Thanks for the diagnosis; it's what led to the fix that landed.

I think this can be closed. Main already does what it does: - #1338 (merged as 9d6936a) adds COLOR_ATTACHMENT to BC images on every driver, not only RADV, after asking `vkGetPhysicalDeviceImageFormatProperties`. It also sizes `targets()` in blocks for raw views and takes 53509044401d78d2 off the default skip list. - Commit 5daa99c made clear_tex convert the area and the clear rect to blocks and retry without the attachment usage. That's the point-4 bug the earlier review raised, which this branch still has. - #2327 handles 3D BC slices through a separate uncompressed mip. Nitnek showed the noise volume is 3D, so the 2D-only path here wouldn't write it. The extra "not made" reason in the log is the only part main lacks, and it would be a small follow-up on current main. Thanks for the diagnosis; it's what led to the fix that landed.
Commenting is not possible because the repository is archived.
No description provided.