vk: bring back #640 with mesh stages kept on mesh-shader GPUs #792

Closed
hckrman101 wants to merge 7 commits from reland-640-mesh-stages into main
hckrman101 commented 2026-11-08 21:29:58 +00:00 (Migrated from github.com)

Brings back ea4eced (#640, by @shayderrr: task and mesh programs run as compute shaders on GPUs without VK_EXT_mesh_shader), reverted in 806c566 / b11058c, with the fix for what made GPUs that do have mesh shaders lose the device.

Why it was reverted

With #640 merged, an RTX 4060 Ti lost the device at the title (mesh shaders present, the fallback off). The same happened here on an RTX 3090 (driver 615.78.08). Every run of a build containing #640 hit, within 20-40 s of start:

  • kernel Xid 109 (CTX SWITCH TIMEOUT), then Xid 31 MMU faults (GPCCLIENT_GCC faulted @ 0x1070_1200f000 ... FAULT_PDE ACCESS_TYPE_VIRT_READ, UNRECOGNIZED_CLIENT faulted @ 0x0_00000040)
  • vk: device lost; presents stop, the replay thread spins in the driver and memory use keeps growing

Cause

build_graphics (gpu/vk/vk_draw.cpp) left out every stage with the compute, task or mesh bit, to keep the fallback's dispatches out of the graphics pipeline:

if (st.vk_stage & (VK_SHADER_STAGE_COMPUTE_BIT | VK_SHADER_STAGE_TASK_BIT_EXT | VK_SHADER_STAGE_MESH_BIT_EXT))
    continue;

On the fallback, the task and mesh stages are VK_SHADER_STAGE_COMPUTE_BIT already. On a GPU with mesh shaders, though, the task and mesh stages are a mesh program's pre-rasterization stages. So every native mesh pipeline was made with a fragment stage alone, and its draws faulted the GPU. The rest of #640 is gated on dev.mesh_fallback and doesn't change anything on such a GPU.

Fix

  • build_graphics leaves out only VK_SHADER_STAGE_COMPUTE_BIT stages (the fallback's), so native mesh pipelines keep their task and mesh stages.
  • RX_VK_FORCE_MESH_FALLBACK=1, a test switch: leaves VK_EXT_mesh_shader off, so a GPU that has mesh shaders runs the fallback.

The first commit is #640 as merged (its author kept). The second commit is the fix.

Evidence (RTX 3090, Linux, driver 615.78.08)

Same save and scripted start each run (Kokiri Forest), with the same game executable for every Vulkan back-end variant. A run counts as clean after 300 s in play with no Xid.

Vulkan back end Result
main before #640 (with #645) clean, 300 s, 0 Xid
main with #640 (d09dacd) fault after 24 s (2 Xid)
… + 40c0042 (CAS loop ceiling) fault after 22 s (2 Xid, device lost)
… without the shader-recompiler PRs of that day (#548 #597 #598 #447 #566 #588) fault after 39 s (2 Xid)
… without #445 and #442 fault after 19 s (2 Xid)
… all of gpu/vk from before #640's day clean, 300 s, 0 Xid
… #640 reverted clean, 300 s, 0 Xid
… only #640's push-constant size reverted fault after 27 s (2 Xid, device lost)
… only #640's vk_draw.cpp reverted clean, 300 s, 0 Xid
… with this fix (stage filter only) clean, ~16 min of normal play, 0 Xid
this branch (main + #640 + fix), full build clean, 300 s, 0 Xid
this branch, RX_VK_FORCE_MESH_FALLBACK=1 0 Xid, but the process crashes in NVIDIA's SPIR-V compiler at the first mesh draw (see below)

Tested

  • RTX 3090: a full build of this branch, 300 s in play from a Kokiri Forest save: 0 Xid, no device lost.
  • The fallback, forced on the RTX 3090 with RX_VK_FORCE_MESH_FALLBACK=1 (the log says issue 640 fallback): the game reaches play, then crashes in the driver's SPIR-V compiler (libnvidia-glvkspirv.so+0xfc0d5, a write to 0xffffffff00000008) at the first mesh draw. That is #640's fallback path, which this PR doesn't change. It may be SPIR-V that NVIDIA's compiler doesn't accept, or an NVIDIA-only problem. Either way, the fallback is not verified here.
  • tests/run_all.py --strict: 101 passed, including test_mesh_fallback.py and test_cas_loop_ceiling.py. 2 failed, the same way without this change (test_chunk_split.py, fixed by #735; test_cpp_preflight.py expects a Visual Studio message on a PC without the fake toolchain on PATH). 4 skipped (Windows/Unicorn-only).
  • Not tested: a real GPU without mesh shaders (RDNA1 or older). The fallback was only run on the RTX 3090 through the switch above.
Brings back ea4eced (#640, by @shayderrr: task and mesh programs run as compute shaders on GPUs without `VK_EXT_mesh_shader`), reverted in 806c566 / b11058c, with the fix for what made GPUs that *do* have mesh shaders lose the device. ## Why it was reverted With #640 merged, an RTX 4060 Ti lost the device at the title (mesh shaders present, the fallback off). The same happened here on an RTX 3090 (driver 615.78.08). Every run of a build containing #640 hit, within 20-40 s of start: - kernel `Xid 109` (CTX SWITCH TIMEOUT), then `Xid 31` MMU faults (`GPCCLIENT_GCC faulted @ 0x1070_1200f000 ... FAULT_PDE ACCESS_TYPE_VIRT_READ`, `UNRECOGNIZED_CLIENT faulted @ 0x0_00000040`) - `vk: device lost`; presents stop, the replay thread spins in the driver and memory use keeps growing ## Cause `build_graphics` (gpu/vk/vk_draw.cpp) left out every stage with the compute, **task or mesh** bit, to keep the fallback's dispatches out of the graphics pipeline: ```cpp if (st.vk_stage & (VK_SHADER_STAGE_COMPUTE_BIT | VK_SHADER_STAGE_TASK_BIT_EXT | VK_SHADER_STAGE_MESH_BIT_EXT)) continue; ``` On the fallback, the task and mesh stages are `VK_SHADER_STAGE_COMPUTE_BIT` already. On a GPU with mesh shaders, though, the task and mesh stages *are* a mesh program's pre-rasterization stages. So every native mesh pipeline was made with a fragment stage alone, and its draws faulted the GPU. The rest of #640 is gated on `dev.mesh_fallback` and doesn't change anything on such a GPU. ## Fix - `build_graphics` leaves out only `VK_SHADER_STAGE_COMPUTE_BIT` stages (the fallback's), so native mesh pipelines keep their task and mesh stages. - `RX_VK_FORCE_MESH_FALLBACK=1`, a test switch: leaves `VK_EXT_mesh_shader` off, so a GPU that has mesh shaders runs the fallback. The first commit is #640 as merged (its author kept). The second commit is the fix. ## Evidence (RTX 3090, Linux, driver 615.78.08) Same save and scripted start each run (Kokiri Forest), with the same game executable for every Vulkan back-end variant. A run counts as clean after 300 s in play with no Xid. | Vulkan back end | Result | |---|---| | main before #640 (with #645) | clean, 300 s, 0 Xid | | main with #640 (d09dacd) | fault after 24 s (2 Xid) | | … + 40c0042 (CAS loop ceiling) | fault after 22 s (2 Xid, device lost) | | … without the shader-recompiler PRs of that day (#548 #597 #598 #447 #566 #588) | fault after 39 s (2 Xid) | | … without #445 and #442 | fault after 19 s (2 Xid) | | … all of gpu/vk from before #640's day | clean, 300 s, 0 Xid | | … #640 reverted | clean, 300 s, 0 Xid | | … only #640's push-constant size reverted | fault after 27 s (2 Xid, device lost) | | … only #640's vk_draw.cpp reverted | clean, 300 s, 0 Xid | | … with this fix (stage filter only) | clean, ~16 min of normal play, 0 Xid | | this branch (main + #640 + fix), full build | clean, 300 s, 0 Xid | | this branch, `RX_VK_FORCE_MESH_FALLBACK=1` | 0 Xid, but the process crashes in NVIDIA's SPIR-V compiler at the first mesh draw (see below) | ## Tested - RTX 3090: a full build of this branch, 300 s in play from a Kokiri Forest save: 0 Xid, no device lost. - The fallback, forced on the RTX 3090 with `RX_VK_FORCE_MESH_FALLBACK=1` (the log says `issue 640 fallback`): the game reaches play, then crashes in the driver's SPIR-V compiler (`libnvidia-glvkspirv.so+0xfc0d5`, a write to `0xffffffff00000008`) at the first mesh draw. That is #640's fallback path, which this PR doesn't change. It may be SPIR-V that NVIDIA's compiler doesn't accept, or an NVIDIA-only problem. Either way, the fallback is not verified here. - `tests/run_all.py --strict`: 101 passed, including `test_mesh_fallback.py` and `test_cas_loop_ceiling.py`. 2 failed, the same way without this change (`test_chunk_split.py`, fixed by #735; `test_cpp_preflight.py` expects a Visual Studio message on a PC without the fake toolchain on PATH). 4 skipped (Windows/Unicorn-only). - Not tested: a real GPU without mesh shaders (RDNA1 or older). The fallback was only run on the RTX 3090 through the switch above.
joao0205-lab commented 2026-11-08 21:54:33 +00:00 (Migrated from github.com)

Hi! I'm the author of issue #640.

I have an AMD Radeon RX 5500 XT 8 GB (RDNA1), and I'm willing to test the compute mesh shader fallback on real hardware.

In previous Hookshot builds, I experienced missing geometry, including buildings, terrain, foliage, and a chest/box. I also tested Lichii, where some of that missing geometry renders correctly on the same GPU.

If there's a Windows test build containing PR #792, I'd be happy to try it and report FPS, missing geometry, crashes, and logs.

Thank you for continuing to work on this!

Hi! I'm the author of issue #640. I have an AMD Radeon RX 5500 XT 8 GB (RDNA1), and I'm willing to test the compute mesh shader fallback on real hardware. In previous Hookshot builds, I experienced missing geometry, including buildings, terrain, foliage, and a chest/box. I also tested Lichii, where some of that missing geometry renders correctly on the same GPU. If there's a Windows test build containing PR #792, I'd be happy to try it and report FPS, missing geometry, crashes, and logs. Thank you for continuing to work on this!
s074 commented 2026-11-08 22:22:54 +00:00 (Migrated from github.com)

If there's a Windows test build containing PR #792, I'd be happy to try it and report FPS, missing geometry, crashes, and logs.

You can build from source, just clone the repository from this PR's branch. Build instructions are in CONTRIBUTING.md

I have an older Pascal Nvidia GPU (doesn't have mesh shader support) which I will test later today when I get home.

> > If there's a Windows test build containing PR #792, I'd be happy to try it and report FPS, missing geometry, crashes, and logs. > > You can build from source, just clone the repository from this PR's branch. Build instructions are in CONTRIBUTING.md I have an older Pascal Nvidia GPU (doesn't have mesh shader support) which I will test later today when I get home.
joao0205-lab commented 2026-11-08 22:35:40 +00:00 (Migrated from github.com)

Thanks for the information! I'm on Windows with an AMD Radeon RX 5500 XT 8 GB (RDNA1).

Would it be possible to provide a Windows test build of PR #792? I'd be happy to test it and share screenshots, FPS results, and hookshot_log.txt.

I'm also looking forward to hearing how the Pascal GPU test goes!

If there's a Windows test build containing PR #792, I'd be happy to try it and report FPS, missing geometry, crashes, and logs.

You can build from source, just clone the repository from this PR's branch. Build instructions are in CONTRIBUTING.md

I have an older Pascal Nvidia GPU (doesn't have mesh shader support) which I will test later today when I get home.

Se houver uma compilação de teste do Windows contendo PR #792, ficarei feliz em testá-la e relatar FPS, geometria ausente, falhas e logs.

Você pode construir a partir do código-fonte, basta clonar o repositório da ramificação deste PR. As instruções de construção estão em CONTRIBUTING.md

Tenho uma GPU Pascal Nvidia mais antiga (não tem suporte a shader de malha) que testarei mais tarde hoje quando chegar em casa.

Thanks for the information! I'm on Windows with an AMD Radeon RX 5500 XT 8 GB (RDNA1). Would it be possible to provide a Windows test build of PR #792? I'd be happy to test it and share screenshots, FPS results, and hookshot_log.txt. I'm also looking forward to hearing how the Pascal GPU test goes! > > If there's a Windows test build containing PR #792, I'd be happy to try it and report FPS, missing geometry, crashes, and logs. > > You can build from source, just clone the repository from this PR's branch. Build instructions are in CONTRIBUTING.md > > I have an older Pascal Nvidia GPU (doesn't have mesh shader support) which I will test later today when I get home. > > Se houver uma compilação de teste do Windows contendo PR #792, ficarei feliz em testá-la e relatar FPS, geometria ausente, falhas e logs. > > Você pode construir a partir do código-fonte, basta clonar o repositório da ramificação deste PR. As instruções de construção estão em CONTRIBUTING.md > > Tenho uma GPU Pascal Nvidia mais antiga (não tem suporte a shader de malha) que testarei mais tarde hoje quando chegar em casa.
AmiriisCustoms commented 2026-11-09 00:59:32 +00:00 (Migrated from github.com)

Tested this PR on an RDNA1-class GPU without VK_EXT_mesh_shader: a PS5 running Linux (Bazzite, kernel 7.2.3), RADV GFX1013 (Oberon), Mesa 26.2.1, built from 3eb85bc with the v2.0.5 AppImage toolchain. The fallback turns on as expected (vk: no mesh shaders: task and mesh programs run as compute shaders (issue 640 fallback)), and the title screen runs at 57-60 fps.

Two bugs in emit_spirv_mesh.cpp that crash RADV

Both show up only on GPUs that take the fallback, which is probably why nobody hit them yet.

  1. PushU64 is given byte offsets instead of struct member indices. It does OpAccessChain(rescaling_push_constants, Const(member)), but the payload calls pass MESH_FALLBACK_PAYLOAD_PUSH_OFFSET (64). spirv-val on the fallback compute shaders: OpAccessChain cannot find index 64 into the structure ResolutionInfo. This structure has 6 members. A debug RADV asserts (glsl_get_struct_field: index < t->length); the release build segfaults inside libvulkan_radeon.so at the first task/mesh draw. Fix: pass ctx.mesh_fallback_payload_member_index.
  2. A bool is stored into the u32 "started" word in TaskEpilogue. started is an OpULessThan result, stored straight into a u32 device-address word. spirv-val: OpStore Pointer's type does not match Object's type. Debug RADV: SPIR-V parsing FAILED: Source and destination types of SpvOpStore do not match: uint vs. bool. Fix: OpSelect(U32, started, 1, 0) (the reader in MeshFallbackEpilogue already tests started == 0).
3eb85bc
diff --git a/gpu/vk/eden/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp b/gpu/vk/eden/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp
index 20c1f72..edb82e8 100644
--- a/gpu/vk/eden/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp
+++ b/gpu/vk/eden/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp
@@ -46,7 +46,7 @@ Id PayloadWord(EmitContext& ctx, Id index) {
         const Id first{ctx.OpIAdd(
             ctx.U32[1], ctx.Const(MESH_FALLBACK_CAPACITY),
             ctx.OpIMul(ctx.U32[1], slot, ctx.Const(ctx.mesh_info.payload_words)))};
-        return BufferWordAt(ctx, PushU64(ctx, MESH_FALLBACK_PAYLOAD_PUSH_OFFSET),
+        return BufferWordAt(ctx, PushU64(ctx, ctx.mesh_fallback_payload_member_index),
                             ctx.OpIAdd(ctx.U32[1], first,
                                        Clamped(ctx, index, ctx.mesh_info.payload_words)));
     }
@@ -202,7 +202,7 @@ void MeshFallbackEpilogue(EmitContext& ctx) {
         if (ctx.runtime_info.mesh_fallback_task) {
             // (a task thread starts at most one mesh workgroup: the slots it did not start output nothing)
             const Id started{ctx.OpLoad(
-                ctx.U32[1], BufferWordAt(ctx, PushU64(ctx, MESH_FALLBACK_PAYLOAD_PUSH_OFFSET), slot))};
+                ctx.U32[1], BufferWordAt(ctx, PushU64(ctx, ctx.mesh_fallback_payload_member_index), slot))};
             count = ctx.OpSelect(ctx.U32[1], ctx.OpIEqual(ctx.U1, started, ctx.u32_zero_value),
                                  ctx.u32_zero_value, count);
         }
@@ -304,15 +304,17 @@ void TaskEpilogue(EmitContext& ctx) {
         // and the rest stay culled.)
         const Id started{ctx.OpULessThan(ctx.U1, LocalInvocation(ctx), count)};
         If(ctx, ctx.OpULessThan(ctx.U1, slot, ctx.Const(MESH_FALLBACK_CAPACITY)), [&] {
-            ctx.OpStore(BufferWordAt(ctx, PushU64(ctx, MESH_FALLBACK_PAYLOAD_PUSH_OFFSET), slot),
-                        started);
+            // (the word is a u32: 1 when the meshlet started, 0 when culled; a bool stored there is invalid SPIR-V,
+            // which RADV's parser rejects and its release build turns into a crash)
+            ctx.OpStore(BufferWordAt(ctx, PushU64(ctx, ctx.mesh_fallback_payload_member_index), slot),
+                        ctx.OpSelect(ctx.U32[1], started, ctx.Const(1u), ctx.u32_zero_value));
             If(ctx, started, [&] {
                 const Id first{ctx.OpIAdd(
                     ctx.U32[1], ctx.Const(MESH_FALLBACK_CAPACITY),
                     ctx.OpIMul(ctx.U32[1], slot, ctx.Const(ctx.mesh_info.payload_words)))};
                 const Id invocation{LocalInvocation(ctx)};
                 Strided(ctx, ctx.mesh_info.payload_words, ctx.mesh_threads, invocation, [&](Id index) {
-                    ctx.OpStore(BufferWordAt(ctx, PushU64(ctx, MESH_FALLBACK_PAYLOAD_PUSH_OFFSET),
+                    ctx.OpStore(BufferWordAt(ctx, PushU64(ctx, ctx.mesh_fallback_payload_member_index),
                                              ctx.OpIAdd(ctx.U32[1], first, index)),
                                 ctx.OpLoad(ctx.U32[1], PayloadWord(ctx, index)));
                 });

With both fixes the title runs, and the save loads into gameplay without the driver crashing.

Still broken: GPU page fault while Hyrule Field loads

About 15-20 s after Load, every run (stock RADV) ends with:

amdgpu: [gfxhub] page fault (src_id:0 ring:24 vmid:6)
  in page starting at address 0x00000080013a0000 from client 0x1b (UTCL2)
  Faulty UTCL2 client ID: SQC (data) (0xa)
  PERMISSION_FAULTS: 0x3  MAPPING_ERROR: 0x0  RW: 0x0
amdgpu: ring gfx_0.0.0 timeout

It's a scalar read of what looks like a raw guest GPU address (0x80_xxxx_xxxx) that was never translated, from a fallback task/mesh draw (the log just before shows program 1bd1: stage 6 with a 256-byte payload, mesh draw kind 0: 0 4 512). memory_guest_pages() and the fallback addresses are both pushed (push_constants, p.uses_push is true for fallback programs), so it seems to be a pointer path in the converted task/mesh code that skips the guest page table.

spirv-val also flags the fallback's generated vertex stage: Memory accesses with PhysicalStorageBuffer must use Aligned (the record loads). RADV accepts that, but it may be worth adding Aligned 4.

I can test further builds on this hardware.

Tested this PR on an RDNA1-class GPU without `VK_EXT_mesh_shader`: a PS5 running Linux (Bazzite, kernel 7.2.3), **RADV GFX1013 (Oberon), Mesa 26.2.1**, built from `3eb85bc` with the v2.0.5 AppImage toolchain. The fallback turns on as expected (`vk: no mesh shaders: task and mesh programs run as compute shaders (issue 640 fallback)`), and the title screen runs at 57-60 fps. ## Two bugs in `emit_spirv_mesh.cpp` that crash RADV Both show up only on GPUs that take the fallback, which is probably why nobody hit them yet. 1. **`PushU64` is given byte offsets instead of struct member indices.** It does `OpAccessChain(rescaling_push_constants, Const(member))`, but the payload calls pass `MESH_FALLBACK_PAYLOAD_PUSH_OFFSET` (64). `spirv-val` on the fallback compute shaders: `OpAccessChain cannot find index 64 into the structure ResolutionInfo. This structure has 6 members.` A debug RADV asserts (`glsl_get_struct_field: index < t->length`); the release build segfaults inside `libvulkan_radeon.so` at the first task/mesh draw. Fix: pass `ctx.mesh_fallback_payload_member_index`. 2. **A bool is stored into the u32 "started" word in `TaskEpilogue`.** `started` is an `OpULessThan` result, stored straight into a `u32` device-address word. `spirv-val`: `OpStore Pointer's type does not match Object's type`. Debug RADV: `SPIR-V parsing FAILED: Source and destination types of SpvOpStore do not match: uint vs. bool`. Fix: `OpSelect(U32, started, 1, 0)` (the reader in `MeshFallbackEpilogue` already tests `started == 0`). ```diff 3eb85bc diff --git a/gpu/vk/eden/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp b/gpu/vk/eden/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp index 20c1f72..edb82e8 100644 --- a/gpu/vk/eden/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp +++ b/gpu/vk/eden/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp @@ -46,7 +46,7 @@ Id PayloadWord(EmitContext& ctx, Id index) { const Id first{ctx.OpIAdd( ctx.U32[1], ctx.Const(MESH_FALLBACK_CAPACITY), ctx.OpIMul(ctx.U32[1], slot, ctx.Const(ctx.mesh_info.payload_words)))}; - return BufferWordAt(ctx, PushU64(ctx, MESH_FALLBACK_PAYLOAD_PUSH_OFFSET), + return BufferWordAt(ctx, PushU64(ctx, ctx.mesh_fallback_payload_member_index), ctx.OpIAdd(ctx.U32[1], first, Clamped(ctx, index, ctx.mesh_info.payload_words))); } @@ -202,7 +202,7 @@ void MeshFallbackEpilogue(EmitContext& ctx) { if (ctx.runtime_info.mesh_fallback_task) { // (a task thread starts at most one mesh workgroup: the slots it did not start output nothing) const Id started{ctx.OpLoad( - ctx.U32[1], BufferWordAt(ctx, PushU64(ctx, MESH_FALLBACK_PAYLOAD_PUSH_OFFSET), slot))}; + ctx.U32[1], BufferWordAt(ctx, PushU64(ctx, ctx.mesh_fallback_payload_member_index), slot))}; count = ctx.OpSelect(ctx.U32[1], ctx.OpIEqual(ctx.U1, started, ctx.u32_zero_value), ctx.u32_zero_value, count); } @@ -304,15 +304,17 @@ void TaskEpilogue(EmitContext& ctx) { // and the rest stay culled.) const Id started{ctx.OpULessThan(ctx.U1, LocalInvocation(ctx), count)}; If(ctx, ctx.OpULessThan(ctx.U1, slot, ctx.Const(MESH_FALLBACK_CAPACITY)), [&] { - ctx.OpStore(BufferWordAt(ctx, PushU64(ctx, MESH_FALLBACK_PAYLOAD_PUSH_OFFSET), slot), - started); + // (the word is a u32: 1 when the meshlet started, 0 when culled; a bool stored there is invalid SPIR-V, + // which RADV's parser rejects and its release build turns into a crash) + ctx.OpStore(BufferWordAt(ctx, PushU64(ctx, ctx.mesh_fallback_payload_member_index), slot), + ctx.OpSelect(ctx.U32[1], started, ctx.Const(1u), ctx.u32_zero_value)); If(ctx, started, [&] { const Id first{ctx.OpIAdd( ctx.U32[1], ctx.Const(MESH_FALLBACK_CAPACITY), ctx.OpIMul(ctx.U32[1], slot, ctx.Const(ctx.mesh_info.payload_words)))}; const Id invocation{LocalInvocation(ctx)}; Strided(ctx, ctx.mesh_info.payload_words, ctx.mesh_threads, invocation, [&](Id index) { - ctx.OpStore(BufferWordAt(ctx, PushU64(ctx, MESH_FALLBACK_PAYLOAD_PUSH_OFFSET), + ctx.OpStore(BufferWordAt(ctx, PushU64(ctx, ctx.mesh_fallback_payload_member_index), ctx.OpIAdd(ctx.U32[1], first, index)), ctx.OpLoad(ctx.U32[1], PayloadWord(ctx, index))); }); ``` With both fixes the title runs, and the save loads into gameplay without the driver crashing. ## Still broken: GPU page fault while Hyrule Field loads About 15-20 s after Load, every run (stock RADV) ends with: ``` amdgpu: [gfxhub] page fault (src_id:0 ring:24 vmid:6) in page starting at address 0x00000080013a0000 from client 0x1b (UTCL2) Faulty UTCL2 client ID: SQC (data) (0xa) PERMISSION_FAULTS: 0x3 MAPPING_ERROR: 0x0 RW: 0x0 amdgpu: ring gfx_0.0.0 timeout ``` It's a scalar read of what looks like a raw guest GPU address (`0x80_xxxx_xxxx`) that was never translated, from a fallback task/mesh draw (the log just before shows program `1bd1`: stage 6 with a 256-byte payload, `mesh draw kind 0: 0 4 512`). `memory_guest_pages()` and the fallback addresses are both pushed (`push_constants`, `p.uses_push` is true for fallback programs), so it seems to be a pointer path in the converted task/mesh code that skips the guest page table. `spirv-val` also flags the fallback's generated vertex stage: `Memory accesses with PhysicalStorageBuffer must use Aligned` (the record loads). RADV accepts that, but it may be worth adding `Aligned 4`. I can test further builds on this hardware.
hckrman101 commented 2026-11-09 02:25:25 +00:00 (Migrated from github.com)

@AmiriisCustoms thank you, this was a great report! Both of your fixes are in, in a commit that credits you: the payload address now comes from the push block's member index, and the task's started flag is stored as a u32 (1 or 0). The fallback's PhysicalStorageBuffer loads and stores now have Aligned 4 as well.

After that we dumped every stage the fallback generates (task and mesh as compute, the generated vertex stage, and the fragment stage) and ran spirv-val on them. One more error came up in every task and mesh stage: the strided copy loops declared a function variable in the middle of the function. The loops are unrolled now, and all 5243 stages from a Kokiri Forest run validate.

On an RTX 3090 with the fallback forced on, the driver's shader compiler no longer crashes at the first mesh draw (that was the same invalid SPIR-V). The game reaches gameplay at about 50 fps and geometry and foliage draw. There were no GPU faults, including while running around the village. Native mesh shaders on the same card still work as before. One known gap: some grass that the native path draws is still missing with the fallback.

About the page fault: we couldn't reproduce it here, but we found and fixed two fallback bugs that could cause it, or at least wrong geometry:

  • Normally a draw whose state hasn't changed since the last draw reuses its descriptors. For a fallback draw, a compute dispatch in between had already replaced them with the compute program's, and those were pushed for the task/mesh dispatches with the fallback's layout. The task and mesh stages then read another program's buffers, plus whatever earlier pushes had left in the other bindings. Stale descriptors like that would explain a scalar read at an unmapped address, and Hyrule Field has many culling dispatches between mesh draws. A fallback draw now rebuilds its descriptors in that case.
  • A fallback mesh workgroup got its global slot as its workgroup index instead of its meshlet's index within its task workgroup (what native mesh shaders give it). Every task workgroup after the first read other meshlets' data.

Could you retest at the new commit (b9d6c6f), ideally the same Hyrule Field load? If it still faults, the log lines just before it would help a lot.

@joao0205-lab thanks for offering to test! We can't build or distribute a Windows test build from here: a build contains game files, so it has to be built from source with your own game files, following CONTRIBUTING.md. If you build this branch on your RX 5500 XT, reports with screenshots and hookshot_log.txt are very welcome.

@AmiriisCustoms thank you, this was a great report! Both of your fixes are in, in a commit that credits you: the payload address now comes from the push block's member index, and the task's started flag is stored as a u32 (1 or 0). The fallback's PhysicalStorageBuffer loads and stores now have `Aligned 4` as well. After that we dumped every stage the fallback generates (task and mesh as compute, the generated vertex stage, and the fragment stage) and ran spirv-val on them. One more error came up in every task and mesh stage: the strided copy loops declared a function variable in the middle of the function. The loops are unrolled now, and all 5243 stages from a Kokiri Forest run validate. On an RTX 3090 with the fallback forced on, the driver's shader compiler no longer crashes at the first mesh draw (that was the same invalid SPIR-V). The game reaches gameplay at about 50 fps and geometry and foliage draw. There were no GPU faults, including while running around the village. Native mesh shaders on the same card still work as before. One known gap: some grass that the native path draws is still missing with the fallback. About the page fault: we couldn't reproduce it here, but we found and fixed two fallback bugs that could cause it, or at least wrong geometry: - Normally a draw whose state hasn't changed since the last draw reuses its descriptors. For a fallback draw, a compute dispatch in between had already replaced them with the compute program's, and those were pushed for the task/mesh dispatches with the fallback's layout. The task and mesh stages then read another program's buffers, plus whatever earlier pushes had left in the other bindings. Stale descriptors like that would explain a scalar read at an unmapped address, and Hyrule Field has many culling dispatches between mesh draws. A fallback draw now rebuilds its descriptors in that case. - A fallback mesh workgroup got its global slot as its workgroup index instead of its meshlet's index within its task workgroup (what native mesh shaders give it). Every task workgroup after the first read other meshlets' data. Could you retest at the new commit (b9d6c6f), ideally the same Hyrule Field load? If it still faults, the log lines just before it would help a lot. @joao0205-lab thanks for offering to test! We can't build or distribute a Windows test build from here: a build contains game files, so it has to be built from source with your own game files, following CONTRIBUTING.md. If you build this branch on your RX 5500 XT, reports with screenshots and `hookshot_log.txt` are very welcome.
hckrman101 commented 2026-11-09 02:48:47 +00:00 (Migrated from github.com)

BTW the CI builds Windows builder, that actually is probably what you are looking for ^

BTW the CI builds Windows builder, that actually is probably what you are looking for ^
joao0205-lab commented 2026-11-09 02:51:12 +00:00 (Migrated from github.com)

BTW the CI builds Windows builder, that actually is probably what you are looking for ^

Hey! Thanks for the information!

I'm already building Hookshot from source on Windows using the PR #792 branch, at commit b9d6c6f, following the CONTRIBUTING.md instructions.

My PC specs:

  • GPU: AMD Radeon RX 5500 XT 8GB (RDNA1)
  • CPU: Intel Xeon E5-2650 v4
  • RAM: 16GB
  • OS: Windows

The game version check passed successfully, and the full build is currently running with LLVM-MinGW.

Once the build finishes, I'll test the mesh shader fallback, check whether the missing geometry and foliage appear, and report back with screenshots, FPS results, and hookshot_log.txt.

Really excited to help test this on RDNA1 hardware! Thanks for all your work on the fallback!

> BTW the CI builds Windows builder, that actually is probably what you are looking for ^ Hey! Thanks for the information! I'm already building Hookshot from source on Windows using the PR #792 branch, at commit `b9d6c6f`, following the CONTRIBUTING.md instructions. My PC specs: - **GPU:** AMD Radeon RX 5500 XT 8GB (RDNA1) - **CPU:** Intel Xeon E5-2650 v4 - **RAM:** 16GB - **OS:** Windows The game version check passed successfully, and the full build is currently running with LLVM-MinGW. Once the build finishes, I'll test the mesh shader fallback, check whether the missing geometry and foliage appear, and report back with screenshots, FPS results, and `hookshot_log.txt`. Really excited to help test this on RDNA1 hardware! Thanks for all your work on the fallback!
NotRexed commented 2026-11-09 04:20:43 +00:00 (Migrated from github.com)

Thank you for the careful investigation (the Xid traces were very useful)! #779 brings back the same #640 fallback (ea4eced) with the same fix for mesh-shader GPUs: build_graphics leaves out task/mesh stages only for fallback programs, so native mesh programs keep theirs. #779 has been taken into the maintainer's tree for the next release after a game test, so I'm closing this one as superseded. If testing turns up something #779 misses that this branch handles, please say so here or open a follow-up.

Thank you for the careful investigation (the Xid traces were very useful)! #779 brings back the same #640 fallback (ea4eced) with the same fix for mesh-shader GPUs: build_graphics leaves out task/mesh stages only for fallback programs, so native mesh programs keep theirs. #779 has been taken into the maintainer's tree for the next release after a game test, so I'm closing this one as superseded. If testing turns up something #779 misses that this branch handles, please say so here or open a follow-up.
s074 commented 2026-11-09 08:03:37 +00:00 (Migrated from github.com)

Well, I had already tested it before I realized it was closed, so might as well share:

Tested on both, a GTX 1660 Super and a 1080 Ti. Game runs well and the visual defects seem fixed on Pascal: foliage in the trees finally renders, as well as some walls in the Kokiri Village houses that were not rendering before. The 1660 does support mesh shaders, and it didn't crash, so that works too

The PR that superseded this one makes the game crash on the menus after selecting load game, so this one works better.

GPU: GTX 1080 Ti
Driver: 582.78
OS: Windows 11
Does it start and reach gameplay on in this PR? yes, and the shaders for foliage, walls, etc are now working
Logs: hookshot_log.txt

Before this mesh shader fix:
image

After this fix:
image

Well, I had already tested it before I realized it was closed, so might as well share: Tested on both, a GTX 1660 Super and a 1080 Ti. Game runs well and the visual defects seem fixed on Pascal: foliage in the trees finally renders, as well as some walls in the Kokiri Village houses that were not rendering before. The 1660 does support mesh shaders, and it didn't crash, so that works too The PR that superseded this one makes the game crash on the menus after selecting load game, so this one works better. **GPU**: GTX 1080 Ti **Driver**: 582.78 **OS**: Windows 11 **Does it start and reach gameplay on in this PR?** yes, and the shaders for foliage, walls, etc are now working **Logs**: [hookshot_log.txt](https://github.com/user-attachments/files/34569184/hookshot_log.txt) Before this mesh shader fix: ![image](https://github.com/user-attachments/assets/9a245a8c-a77c-450d-b79a-d48a11e816ef) After this fix: ![image](https://github.com/user-attachments/assets/2e576240-c88e-470a-ab7c-eca36bc03feb)
s074 commented 2026-11-09 18:41:58 +00:00 (Migrated from github.com)

If testing turns up something #779 misses that this branch handles, please say so here or open a follow-up.

#779 is still crashing the game for graphics cards with no mesh shader support. This branch fixes it, verified by me (GTX 1080 ti, details posted above) and one other person running an AMD Radeon RX 580, see #1001

Requesting that this PR be reopened and re-evaluated @NotRexed

> If testing turns up something #779 misses that this branch handles, please say so here or open a follow-up. #779 is still crashing the game for graphics cards with no mesh shader support. This branch fixes it, verified by me (GTX 1080 ti, details posted above) and one other person running an AMD Radeon RX 580, see https://git.magicbean.dev/MagicBeanDev/Hookshot/issues/1001 Requesting that this PR be reopened and re-evaluated @NotRexed
Commenting is not possible because the repository is archived.
No description provided.