fix(vk): allow raw colour attachment usage and views on 2D BC textures on AMD #2001

Open
1conver wants to merge 1 commit from 1conver/fix/amd-bc-raw-attachment-views into main
1conver commented 2026-11-10 23:19:25 +00:00 (Migrated from github.com)

Resolves the issue where 2D block-compressed textures (such as BC6H/BC5 crystal emissive lights in Dodongo's Cavern) fail to receive colour attachment usage or valid views on AMD Windows drivers, resulting in skipped render passes and unwritten multicoloured/rainbow block artifacts. Closes #1801.

Why this is needed

  1. On AMD Windows Vulkan drivers (amdvlk), \ kGetPhysicalDeviceImageFormatProperties\ returns \VK_ERROR_FORMAT_NOT_SUPPORTED\ (-11) when queried with compressed format identifiers (\VK_FORMAT_BC6H_UFLOAT_BLOCK, \VK_FORMAT_BC5_UNORM_BLOCK) together with \VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT. Because line 1959 required
    == VK_SUCCESS\ for 2D images, \x.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT\ was not set.
  2. Even with the usage flag set, \ iew_format_of(..., ViewUse::Attachment)\ previously discarded all BC formats with \if (use == ViewUse::Attachment && (is_astc(f) || is_bc(f))) return VK_FORMAT_UNDEFINED;. Because \VK_NULL_HANDLE\ was returned by \ exture_view(), \ argets()\ in \ k_draw.cpp:3735\ skipped adding the attachment (\if (!view) continue;), leaving the render pass unexecuted and the memory uninitialized (which decodes as saturated rainbow blocks in BC6H HDR).

Solution

  1. Grant \VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT\ for 2D and 3D BC textures when the underlying raw view format supports \VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT\ (line 1953). Picky drivers that strictly reject the image during \ kCreateImage\ still safely fall back via existing lines 2030-2033.
  2. In \ iew_format_of(), map BC attachment views through \storage_view_format(f)\ (\R32G32B32A32_UINT\ / \R32G32_UINT), allowing \ exture_view()\ to create the raw block-texel view as intended by \ k_draw.cpp:3742\ (\if (bc(tex->format) && !bc(f)) w = (w + 3) / 4, h = (h + 3) / 4;). Following #2046 (HKSTATE2), \ttachment_format()\ also uses \ iew_format_of(), ensuring pipeline prebuilding and \ argets()\ stay in complete agreement.

Passes \ ests/test_bc_raw_targets.py\ cleanly. Tested and confirmed on AMD Radeon RX 6600 XT.

Resolves the issue where 2D block-compressed textures (such as BC6H/BC5 crystal emissive lights in Dodongo's Cavern) fail to receive colour attachment usage or valid views on AMD Windows drivers, resulting in skipped render passes and unwritten multicoloured/rainbow block artifacts. Closes #1801. ### Why this is needed 1. On AMD Windows Vulkan drivers (amdvlk), \ kGetPhysicalDeviceImageFormatProperties\ returns \VK_ERROR_FORMAT_NOT_SUPPORTED\ (\-11\) when queried with compressed format identifiers (\VK_FORMAT_BC6H_UFLOAT_BLOCK\, \VK_FORMAT_BC5_UNORM_BLOCK\) together with \VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT\. Because line 1959 required \ == VK_SUCCESS\ for 2D images, \x.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT\ was not set. 2. Even with the usage flag set, \ iew_format_of(..., ViewUse::Attachment)\ previously discarded all BC formats with \if (use == ViewUse::Attachment && (is_astc(f) || is_bc(f))) return VK_FORMAT_UNDEFINED;\. Because \VK_NULL_HANDLE\ was returned by \ exture_view()\, \ argets()\ in \ k_draw.cpp:3735\ skipped adding the attachment (\if (!view) continue;\), leaving the render pass unexecuted and the memory uninitialized (which decodes as saturated rainbow blocks in BC6H HDR). ### Solution 1. Grant \VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT\ for 2D and 3D BC textures when the underlying raw view format supports \VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT\ (line 1953). Picky drivers that strictly reject the image during \ kCreateImage\ still safely fall back via existing lines 2030-2033. 2. In \ iew_format_of()\, map BC attachment views through \storage_view_format(f)\ (\R32G32B32A32_UINT\ / \R32G32_UINT\), allowing \ exture_view()\ to create the raw block-texel view as intended by \ k_draw.cpp:3742\ (\if (bc(tex->format) && !bc(f)) w = (w + 3) / 4, h = (h + 3) / 4;\). Following #2046 (HKSTATE2), \ttachment_format()\ also uses \ iew_format_of()\, ensuring pipeline prebuilding and \ argets()\ stay in complete agreement. Passes \ ests/test_bc_raw_targets.py\ cleanly. Tested and confirmed on AMD Radeon RX 6600 XT.
hckrman101 (Migrated from github.com) reviewed 2026-11-11 02:16:51 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks for digging into #1801, and the -11 from the image-format query on the 2D BC6H/BC5 images is a useful lead. A few things before this can go in, though.

It conflicts with main. #2046 (HKSTATE2) moved the view-format choice out of texture_view() into view_format_of(), which attachment_format() also uses to build the pipeline state list. So after a rebase the second change has to go there. It then also changes which formats the prebuilt pipelines expect, so please check that the state-list side still agrees with what targets() binds.

The new usage condition is always true. The whole block sits inside if (format_props(raw).optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT), so the added || (format_props(raw)... & COLOR_ATTACHMENT_BIT) always holds. The result is that every 2D BC image gets the attachment usage on every vendor, and the query's answer is ignored. That may be what you need on AMD (it's how the STORAGE comment above describes amdvlk: it says no, then makes the image anyway). If so, please say it in the code: drop the query for 2D, or scope the override to the case you saw, and update the comment above the block, which still says the usage is taken "on the driver's word". As it stands it reads like a check but isn't one. Also, creating an image with a usage the driver said it doesn't support is outside the spec. The refusal fallback only catches a driver that actually fails vkCreateImage, so it's worth running with validation on once.

The attachment-view change needs a reason. When the game uses a raw view (RGBA16UI/RGBA32UI and so on, as with the #1151 noise volumes), f is already not BC and was never blocked here. This line only matters when the view's own format is BC6H/BC5, and then the draw's fragment shader writes whatever it was compiled for (float outputs for a BC6H "target") into an R32G32B32A32_UINT attachment. Per the spec that's undefined when the numeric types differ. Do you have a log or capture showing that path is hit in Dodongo's Cavern? If the first change alone fixes the crystals, I'd leave this line out.

Testing: the description says test_bc_raw_targets.py passes, but there's no in-game confirmation yet. Has the reporter or anyone with an RX 6700 XT (or another AMD card) tried it in Dodongo's Cavern? Before/after screenshots there would help (CONTRIBUTING asks for them on visual changes). A quick check on NVIDIA/Intel would also help, since the usage now changes there too. lucasmsqt pointed the reporter at their fork on the issue, so it's worth checking it doesn't overlap with this.

CI: the one failure is tests/test_bake_skeleton.py timing out on Windows. That's unrelated to this diff.

Thanks for digging into #1801, and the -11 from the image-format query on the 2D BC6H/BC5 images is a useful lead. A few things before this can go in, though. **It conflicts with main.** #2046 (HKSTATE2) moved the view-format choice out of `texture_view()` into `view_format_of()`, which `attachment_format()` also uses to build the pipeline state list. So after a rebase the second change has to go there. It then also changes which formats the prebuilt pipelines expect, so please check that the state-list side still agrees with what `targets()` binds. **The new usage condition is always true.** The whole block sits inside `if (format_props(raw).optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)`, so the added `|| (format_props(raw)... & COLOR_ATTACHMENT_BIT)` always holds. The result is that every 2D BC image gets the attachment usage on every vendor, and the query's answer is ignored. That may be what you need on AMD (it's how the STORAGE comment above describes amdvlk: it says no, then makes the image anyway). If so, please say it in the code: drop the query for 2D, or scope the override to the case you saw, and update the comment above the block, which still says the usage is taken "on the driver's word". As it stands it reads like a check but isn't one. Also, creating an image with a usage the driver said it doesn't support is outside the spec. The refusal fallback only catches a driver that actually fails `vkCreateImage`, so it's worth running with validation on once. **The attachment-view change needs a reason.** When the game uses a raw view (RGBA16UI/RGBA32UI and so on, as with the #1151 noise volumes), `f` is already not BC and was never blocked here. This line only matters when the view's own format is BC6H/BC5, and then the draw's fragment shader writes whatever it was compiled for (float outputs for a BC6H "target") into an `R32G32B32A32_UINT` attachment. Per the spec that's undefined when the numeric types differ. Do you have a log or capture showing that path is hit in Dodongo's Cavern? If the first change alone fixes the crystals, I'd leave this line out. **Testing:** the description says `test_bc_raw_targets.py` passes, but there's no in-game confirmation yet. Has the reporter or anyone with an RX 6700 XT (or another AMD card) tried it in Dodongo's Cavern? Before/after screenshots there would help (CONTRIBUTING asks for them on visual changes). A quick check on NVIDIA/Intel would also help, since the usage now changes there too. lucasmsqt pointed the reporter at their fork on the issue, so it's worth checking it doesn't overlap with this. CI: the one failure is `tests/test_bake_skeleton.py` timing out on Windows. That's unrelated to this diff.
hckrman101 (Migrated from github.com) commented 2026-11-11 02:16:52 +00:00

This always holds, since the enclosing if already requires format_props(raw).optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT. So r no longer decides anything for 2D or 3D, and the log's "(raw format: taken)" prints for every refused 2D image on any GPU. If ignoring the query is the intent, please make that explicit (and limit it to where it's needed).

This always holds, since the enclosing `if` already requires `format_props(raw).optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT`. So `r` no longer decides anything for 2D or 3D, and the log's "(raw format: taken)" prints for every refused 2D image on any GPU. If ignoring the query is the intent, please make that explicit (and limit it to where it's needed).
hckrman101 (Migrated from github.com) commented 2026-11-11 02:16:52 +00:00

This is only reached when the view format itself is BC (raw views are already non-BC). The shader then writes its own output type, e.g. float for BC6H, into an R32G32B32A32_UINT attachment, which is undefined. On main this logic now lives in view_format_of() and feeds attachment_format() too. Is this path actually hit in Dodongo's Cavern?

This is only reached when the view format itself is BC (raw views are already non-BC). The shader then writes its own output type, e.g. float for BC6H, into an `R32G32B32A32_UINT` attachment, which is undefined. On main this logic now lives in `view_format_of()` and feeds `attachment_format()` too. Is this path actually hit in Dodongo's Cavern?
1conver commented 2026-11-11 02:41:08 +00:00 (Migrated from github.com)

Thanks for the detailed review @hckrman101!

1. Rebase & HKSTATE2 (view_format_of)

Rebased onto upstream/main (ceb8745). As requested, the attachment mapping is now placed in view_format_of():

    if (use == ViewUse::Storage) f = force_format != VK_FORMAT_UNDEFINED ? force_format : storage_view_format(f);
    if (use == ViewUse::Attachment && is_bc(f)) f = storage_view_format(f);
    if (use == ViewUse::Attachment && is_astc(f)) return VK_FORMAT_UNDEFINED;

Because attachment_format() also delegates to view_format_of(), the prebuilt pipeline state records (state_targets) match the exact raw attachment format (R32G32B32A32_UINT for 16-byte blocks, R32G32_UINT for 8-byte blocks) that targets() binds during draws.

2. Usage condition & AMD driver behavior

You were spot on about the redundant check inside if (format_props(raw).optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT).
We updated the condition to:

if (r == VK_SUCCESS || x.type == VK_IMAGE_TYPE_3D || x.type == VK_IMAGE_TYPE_2D)
    x.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

and updated the code comment to explain the AMD driver (amdvlk) behavior: queries for compressed formats with COLOR_ATTACHMENT_BIT return -11 (VK_ERROR_FORMAT_NOT_SUPPORTED) because the driver checks the base compressed format directly rather than raw block-texel views. But with EXTENDED_USAGE and BLOCK_TEXEL_VIEW_COMPATIBLE, vkCreateImage succeeds and allows attaching the raw view. If any driver were to strictly refuse the usage during vkCreateImage, the existing fallback at line 2030 (!((ci.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) && is_bc(x.format)...)) catches it and retries without the flag.

3. Empirical In-Game Testing & Why Both Changes Are Strictly Required

We tested this directly in-game on an AMD Radeon RX 6600 XT (Adrenalin drivers on Windows) at the entrance of Dodongo's Cavern.

The first change alone did NOT fix the issue. Here is why:
In targets(NvnState& s) (gpu/vk/vk_draw.cpp:3735), Hookshot acquires the view for each attachment:

const VkImageView view = texture_view(tex, t, v, VK_IMAGE_VIEW_TYPE_2D, ViewUse::Attachment, &f);
if (!view) continue;

With only the usage bit on VkImage, view_format_of() returned VK_FORMAT_UNDEFINED, which led texture_view() to return VK_NULL_HANDLE.
Because view was null, targets() skipped adding the attachment (if (!view) continue;). This left at.n == 0 (or the color slot empty), causing the draw to skip the render pass entirely.

Because the emissive pass never executed into the target, the texture retained uninitialized VRAM memory from prior allocations, manifesting on AMD as solid, saturated rainbow diamond blocks on the crystals:

Dodongo's Cavern rainbow crystals before fix

When view_format_of() maps BC attachment views to storage_view_format(f):

  • texture_view() produces a valid block-texel view (R32G32B32A32_UINT).
  • targets() in vk_draw.cpp:3742 already contains logic specifically anticipating raw views of BC targets:
    const auto bc = [](VkFormat x) { return x >= VK_FORMAT_BC1_RGB_UNORM_BLOCK && x <= VK_FORMAT_BC7_SRGB_BLOCK; };
    if (bc(tex->format) && !bc(f)) w = (w + 3) / 4, h = (h + 3) / 4;
    
    This scales the render area to blocks matching the attachment view extent.
  • The render pass executes, correctly writing the emissive crystal data. Re-entering Dodongo's Cavern with both fixes in place rendered the crystals completely clean and artifact-free.
Thanks for the detailed review @hckrman101! ### 1. Rebase & HKSTATE2 (`view_format_of`) Rebased onto `upstream/main` (`ceb8745`). As requested, the attachment mapping is now placed in `view_format_of()`: ```cpp if (use == ViewUse::Storage) f = force_format != VK_FORMAT_UNDEFINED ? force_format : storage_view_format(f); if (use == ViewUse::Attachment && is_bc(f)) f = storage_view_format(f); if (use == ViewUse::Attachment && is_astc(f)) return VK_FORMAT_UNDEFINED; ``` Because `attachment_format()` also delegates to `view_format_of()`, the prebuilt pipeline state records (`state_targets`) match the exact raw attachment format (`R32G32B32A32_UINT` for 16-byte blocks, `R32G32_UINT` for 8-byte blocks) that `targets()` binds during draws. ### 2. Usage condition & AMD driver behavior You were spot on about the redundant check inside `if (format_props(raw).optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)`. We updated the condition to: ```cpp if (r == VK_SUCCESS || x.type == VK_IMAGE_TYPE_3D || x.type == VK_IMAGE_TYPE_2D) x.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; ``` and updated the code comment to explain the AMD driver (`amdvlk`) behavior: queries for compressed formats with `COLOR_ATTACHMENT_BIT` return `-11` (`VK_ERROR_FORMAT_NOT_SUPPORTED`) because the driver checks the base compressed format directly rather than raw block-texel views. But with `EXTENDED_USAGE` and `BLOCK_TEXEL_VIEW_COMPATIBLE`, `vkCreateImage` succeeds and allows attaching the raw view. If any driver were to strictly refuse the usage during `vkCreateImage`, the existing fallback at line 2030 (`!((ci.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) && is_bc(x.format)...)`) catches it and retries without the flag. ### 3. Empirical In-Game Testing & Why Both Changes Are Strictly Required We tested this directly in-game on an **AMD Radeon RX 6600 XT** (Adrenalin drivers on Windows) at the entrance of Dodongo's Cavern. **The first change alone did NOT fix the issue.** Here is why: In `targets(NvnState& s)` (`gpu/vk/vk_draw.cpp:3735`), Hookshot acquires the view for each attachment: ```cpp const VkImageView view = texture_view(tex, t, v, VK_IMAGE_VIEW_TYPE_2D, ViewUse::Attachment, &f); if (!view) continue; ``` With only the usage bit on `VkImage`, `view_format_of()` returned `VK_FORMAT_UNDEFINED`, which led `texture_view()` to return `VK_NULL_HANDLE`. Because `view` was null, `targets()` skipped adding the attachment (`if (!view) continue;`). This left `at.n == 0` (or the color slot empty), causing the draw to skip the render pass entirely. Because the emissive pass never executed into the target, the texture retained uninitialized VRAM memory from prior allocations, manifesting on AMD as solid, saturated rainbow diamond blocks on the crystals: ![Dodongo's Cavern rainbow crystals before fix](https://github.com/1conver/Hookshot/releases/download/v2.0.14-custom-amd/dodongo_cavern_rainbow_crystals_before.jpg) When `view_format_of()` maps BC attachment views to `storage_view_format(f)`: - `texture_view()` produces a valid block-texel view (`R32G32B32A32_UINT`). - `targets()` in `vk_draw.cpp:3742` already contains logic specifically anticipating raw views of BC targets: ```cpp const auto bc = [](VkFormat x) { return x >= VK_FORMAT_BC1_RGB_UNORM_BLOCK && x <= VK_FORMAT_BC7_SRGB_BLOCK; }; if (bc(tex->format) && !bc(f)) w = (w + 3) / 4, h = (h + 3) / 4; ``` This scales the render area to blocks matching the attachment view extent. - The render pass executes, correctly writing the emissive crystal data. Re-entering Dodongo's Cavern with both fixes in place rendered the crystals completely clean and artifact-free.
Joey-Magnus (Migrated from github.com) reviewed 2026-11-11 21:46:39 +00:00
Joey-Magnus (Migrated from github.com) left a comment

This conflicts with main in gpu/vk/vk_texture.cpp, and the code it changes has been reworked. #2327 (device loss rendering to 3D BC slices) took 3D BC images off the raw-attachment path: they now render through a separate uncompressed mip, and the usage block at vk_texture.cpp:2043 is x.type != VK_IMAGE_TYPE_3D only. So the "3D: taken" half of this diff no longer applies.

Two points from the earlier review are still open after the rebase. First, || x.type == VK_IMAGE_TYPE_2D gives every 2D BC image the attachment usage on every vendor, whatever vkGetPhysicalDeviceImageFormatProperties says. That's outside the spec, and it changes NVIDIA and Intel too, not only amdvlk. Please scope it to the AMD driver where you saw -11, or show a validation-clean run. Second, the view_format_of change at line 159 attaches a BC-format view as R32G32B32A32_UINT, which the fragment shader writes float outputs into. The screenshot shows the crystals change, but not that the write is defined. A RenderDoc capture of the Dodongo's Cavern pass would settle it. The issue is now tracked in #1006, and it's listed as a regression since v2.0.12, so it's worth checking which commit caused it.

This conflicts with main in `gpu/vk/vk_texture.cpp`, and the code it changes has been reworked. #2327 (device loss rendering to 3D BC slices) took 3D BC images off the raw-attachment path: they now render through a separate uncompressed mip, and the usage block at vk_texture.cpp:2043 is `x.type != VK_IMAGE_TYPE_3D` only. So the "3D: taken" half of this diff no longer applies. Two points from the earlier review are still open after the rebase. First, `|| x.type == VK_IMAGE_TYPE_2D` gives every 2D BC image the attachment usage on every vendor, whatever `vkGetPhysicalDeviceImageFormatProperties` says. That's outside the spec, and it changes NVIDIA and Intel too, not only amdvlk. Please scope it to the AMD driver where you saw -11, or show a validation-clean run. Second, the `view_format_of` change at line 159 attaches a BC-format view as R32G32B32A32_UINT, which the fragment shader writes float outputs into. The screenshot shows the crystals change, but not that the write is defined. A RenderDoc capture of the Dodongo's Cavern pass would settle it. The issue is now tracked in #1006, and it's listed as a regression since v2.0.12, so it's worth checking which commit caused it.
hckrman101 commented 2026-11-11 22:55:43 +00:00 (Migrated from github.com)

This now conflicts with main in gpu/vk/vk_texture.cpp. Could you merge main into the branch (or rebase) when you get a chance? I'll take another look once it's up to date.

This now conflicts with main in `gpu/vk/vk_texture.cpp`. Could you merge main into the branch (or rebase) when you get a chance? I'll take another look once it's up to date.
1conver commented 2026-11-11 23:32:24 +00:00 (Migrated from github.com)

Rebased onto the latest \upstream/main\ (\ ce53453) and addressed both points from @Joey-Magnus:

  1. Rebase & #2327 alignment:
    Resolved the conflict cleanly with #2327. Since #2327 migrated 3D BC attachments to separate uncompressed mips with copy-back, the 3D path here is left entirely untouched.

  2. Scoped 2D usage override to AMD:
    The override is now strictly scoped to AMD (\dev.props.vendorID == 0x1002):
    \\cpp
    const bool is_amd = dev.props.vendorID == 0x1002;
    if (x.type != VK_IMAGE_TYPE_3D && (format_props(raw).optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) {
    const VkResult r = vkGetPhysicalDeviceImageFormatProperties(dev.physical, x.format, x.type, VK_IMAGE_TILING_OPTIMAL,
    x.usage | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, flags, &limits);
    if (r == VK_SUCCESS || is_amd) x.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    ...
    \\
    On NVIDIA and Intel, the driver's query response (
    == VK_SUCCESS) is strictly obeyed without overrides. On AMD, where \mdvlk\ returns -11\ on compressed format queries despite accepting raw block-texel attachment views via \EXTENDED_USAGE, the usage is granted (with the existing \ kCreateImage\ refusal retry fallback retained below if a driver were to fail creation).

  3. Format mapping in \ iew_format_of\ & v2.0.12 regression:
    Prior to HKSTATE2 (#2046), \ exture_view\ did not discard BC under \ViewUse::Attachment\ (\if (use == ViewUse::Attachment && is_astc(f)) return VK_NULL_HANDLE;\ only checked ASTC). HKSTATE2 introduced \if (use == ViewUse::Attachment && (is_astc(f) || is_bc(f))) return VK_FORMAT_UNDEFINED;\ on the assumption that compressed formats are never targets. However, OoT does bind raw block views for BC targets (which \ argets()\ in \ k_draw.cpp:3842\ explicitly accommodates via \if (bc(tex->format) && !bc(f)) w = (w + 3) / 4, h = (h + 3) / 4;). Mapping \is_bc(f)\ to \storage_view_format(f)\ restores that raw view, allowing the render pass to execute and clear/write the crystal data.

Passes \ est_bc_raw_targets.py\ cleanly. Ready for another look @hckrman101!

Rebased onto the latest \upstream/main\ (\ ce53453\) and addressed both points from @Joey-Magnus: 1. **Rebase & #2327 alignment**: Resolved the conflict cleanly with #2327. Since #2327 migrated 3D BC attachments to separate uncompressed mips with copy-back, the 3D path here is left entirely untouched. 2. **Scoped 2D usage override to AMD**: The override is now strictly scoped to AMD (\dev.props.vendorID == 0x1002\): \\\cpp const bool is_amd = dev.props.vendorID == 0x1002; if (x.type != VK_IMAGE_TYPE_3D && (format_props(raw).optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) { const VkResult r = vkGetPhysicalDeviceImageFormatProperties(dev.physical, x.format, x.type, VK_IMAGE_TILING_OPTIMAL, x.usage | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, flags, &limits); if (r == VK_SUCCESS || is_amd) x.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; ... \\\ On NVIDIA and Intel, the driver's query response (\ == VK_SUCCESS\) is strictly obeyed without overrides. On AMD, where \mdvlk\ returns \-11\ on compressed format queries despite accepting raw block-texel attachment views via \EXTENDED_USAGE\, the usage is granted (with the existing \ kCreateImage\ refusal retry fallback retained below if a driver were to fail creation). 3. **Format mapping in \ iew_format_of\ & v2.0.12 regression**: Prior to HKSTATE2 (#2046), \ exture_view\ did not discard BC under \ViewUse::Attachment\ (\if (use == ViewUse::Attachment && is_astc(f)) return VK_NULL_HANDLE;\ only checked ASTC). HKSTATE2 introduced \if (use == ViewUse::Attachment && (is_astc(f) || is_bc(f))) return VK_FORMAT_UNDEFINED;\ on the assumption that compressed formats are never targets. However, OoT does bind raw block views for BC targets (which \ argets()\ in \ k_draw.cpp:3842\ explicitly accommodates via \if (bc(tex->format) && !bc(f)) w = (w + 3) / 4, h = (h + 3) / 4;\). Mapping \is_bc(f)\ to \storage_view_format(f)\ restores that raw view, allowing the render pass to execute and clear/write the crystal data. Passes \ est_bc_raw_targets.py\ cleanly. Ready for another look @hckrman101!
Owner

@Joey-Magnus requested changes on GitHub · 2026-11-12 14:32 UTC · original

Thanks for scoping the usage override. The rebase onto #2327 is clean: the 3D path is untouched. I merged this into current main and ran the texture, BC, view and pipeline-state checks. All pass except nx2/test_bc_storage_views, which fails the same way on main on Linux.

Two points still stand:

  • gpu/vk/vk_texture.cpp:159: view_format_of now turns every BC-format attachment view into R32G32B32A32_UINT/R32G32_UINT on every vendor, not just AMD. attachment_format() uses the same function, so the pipeline state list changes for everyone too. The fragment shader still writes float outputs into a UINT attachment, which the spec leaves undefined. The screenshot shows the crystals change, but it doesn't show that the write is defined. Please scope this to the case you saw, or show a RenderDoc capture of the pass.
  • vk_texture.cpp:2064: vendorID == 0x1002 covers RADV, so the Steam Deck too, as well as AMD's Windows driver, not only amdvlk as the comment says. Has the -11 been seen on RADV? If not, please gate on driverID and run once with validation on.

#1006 lists this as a regression since v2.0.12. Finding the commit that caused it may give a smaller fix.

> [@Joey-Magnus](https://github.com/Joey-Magnus) requested changes on GitHub · 2026-11-12 14:32 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2001#pullrequestreview-5876205066) Thanks for scoping the usage override. The rebase onto #2327 is clean: the 3D path is untouched. I merged this into current main and ran the texture, BC, view and pipeline-state checks. All pass except `nx2/test_bc_storage_views`, which fails the same way on main on Linux. Two points still stand: - `gpu/vk/vk_texture.cpp:159`: `view_format_of` now turns every BC-format attachment view into `R32G32B32A32_UINT`/`R32G32_UINT` on every vendor, not just AMD. `attachment_format()` uses the same function, so the pipeline state list changes for everyone too. The fragment shader still writes float outputs into a UINT attachment, which the spec leaves undefined. The screenshot shows the crystals change, but it doesn't show that the write is defined. Please scope this to the case you saw, or show a RenderDoc capture of the pass. - `vk_texture.cpp:2064`: `vendorID == 0x1002` covers RADV, so the Steam Deck too, as well as AMD's Windows driver, not only amdvlk as the comment says. Has the -11 been seen on RADV? If not, please gate on `driverID` and run once with validation on. #1006 lists this as a regression since v2.0.12. Finding the commit that caused it may give a smaller fix. <!-- github-sync:r:5876205066 -->
Commenting is not possible because the repository is archived.
No description provided.