fix(vk): missing floor grass without mesh shaders: indirect task draws ran no mesh workgroups #1510

Merged
rakefire-impala merged 1 commit from fix/mesh-fallback-missing-floor-grass-indirect into main 2026-11-10 16:45:18 +00:00
rakefire-impala commented 2026-11-10 05:43:42 +00:00 (Migrated from github.com)

On a GPU without mesh shaders, every DrawMeshTasksIndirect drew nothing. The floor grass in Link's house is one. This makes an indirect task draw's mesh stage run.

Part of splitting up #1023.

Problem

DrawMeshTasksIndirect records no task count, because the count lives in GPU memory: nvn_native.c zeroes the command. In the fallback, a task draw's mesh stage ran slots = groups × threads workgroups, and for an indirect draw groups is 0. So the mesh stage was never dispatched, the draw's vertex count was 0, and every indirect task draw drew nothing.

Change

An indirect task draw takes every slot, as an indirect mesh draw without a task already does (capacity × vertices per workgroup).

  • The task stage runs from its converted arguments as before, and writes the started flag of each slot it reaches.
  • The payload clear covers every slot, so the slots it doesn't reach stay unstarted and their mesh workgroups output nothing.

Host code only; no translated program changes, so no re-translation.

Known limit: the record capacity is still 1024 slots, so a draw with more meshlets than that is cut off past them. The floor grass starts about 13,900. The records-allocator PR, which builds on this one, removes that limit.

Tests

tests/test_mesh_fallback.py checks the slots and the vertex count. The check fails on main without this change.

Tested on

Tested on RX 5500M (RDNA1, 4 GB VRAM, 16 GB RAM), Windows 11, AMD driver; Hookshot main 0c412ad (v2.0.11-383) with these PRs, 2026-11-10
Result Floor grass in Link's house drawn, together with #1511. On its own this PR is capped at 1024 slots, which covers only part of the floor grass
On a GPU without mesh shaders, every `DrawMeshTasksIndirect` drew nothing. The floor grass in Link's house is one. This makes an indirect task draw's mesh stage run. Part of splitting up #1023. ## Problem `DrawMeshTasksIndirect` records no task count, because the count lives in GPU memory: `nvn_native.c` zeroes the command. In the fallback, a task draw's mesh stage ran `slots = groups × threads` workgroups, and for an indirect draw `groups` is 0. So the mesh stage was never dispatched, the draw's vertex count was 0, and every indirect task draw drew nothing. ## Change An indirect task draw takes **every slot**, as an indirect mesh draw *without* a task already does (`capacity × vertices per workgroup`). - The task stage runs from its converted arguments as before, and writes the started flag of each slot it reaches. - The payload clear covers every slot, so the slots it doesn't reach stay unstarted and their mesh workgroups output nothing. Host code only; no translated program changes, so no re-translation. **Known limit:** the record capacity is still 1024 slots, so a draw with more meshlets than that is cut off past them. The floor grass starts about 13,900. The records-allocator PR, which builds on this one, removes that limit. ## Tests `tests/test_mesh_fallback.py` checks the slots and the vertex count. The check fails on main without this change. ## Tested on | | | |---|---| | Tested on | RX 5500M (RDNA1, 4 GB VRAM, 16 GB RAM), Windows 11, AMD driver; Hookshot main 0c412ad (v2.0.11-383) with these PRs, 2026-11-10 | | Result | Floor grass in Link's house drawn, **together with #1511**. On its own this PR is capped at 1024 slots, which covers only part of the floor grass |
hckrman101 (Migrated from github.com) approved these changes 2026-11-10 16:38:40 +00:00
hckrman101 (Migrated from github.com) left a comment

Looks good. I traced fallback_record: for an indirect task draw groups is 0, so groups * threads gave 0 slots, the mesh dispatch was skipped and the vertex count was 0. Using capacity is consistent with how an indirect mesh draw without a task already behaves. The task stage still runs through vkCmdDispatchIndirect on the converted args. The payload clear now covers all slots, so slots the task doesn't reach stay unstarted and output nothing. If there are no converted args the task send returns false before any of this, so there is no new failure path. CI is green.

Nit: the new test matches the exact source text (d.indirect ? capacity with the alignment spaces), so a clang-format reflow would break it. Fine to leave given the rest of that test works the same way.

The 1024-slot cap for the floor grass is noted in the description and handled by the follow-up.

Looks good. I traced `fallback_record`: for an indirect task draw `groups` is 0, so `groups * threads` gave 0 slots, the mesh dispatch was skipped and the vertex count was 0. Using `capacity` is consistent with how an indirect mesh draw without a task already behaves. The task stage still runs through `vkCmdDispatchIndirect` on the converted args. The payload clear now covers all slots, so slots the task doesn't reach stay unstarted and output nothing. If there are no converted args the task send returns false before any of this, so there is no new failure path. CI is green. Nit: the new test matches the exact source text (`d.indirect ? capacity` with the alignment spaces), so a clang-format reflow would break it. Fine to leave given the rest of that test works the same way. The 1024-slot cap for the floor grass is noted in the description and handled by the follow-up.
CiLiBi18 commented 2026-11-10 18:04:14 +00:00 (Migrated from github.com)

Tested main (88b554e) + #1509, #1510, #1511, #1512, #1513 and #1581 on the GTX 1070, as asked in #1023. They merged cleanly.

Setup: Linux, GTX 1070 8 GB / NVIDIA 580.178.04, i7-7700HQ, output 1920x1080 at 75%. This was a fresh game folder, so the first start had an empty pipeline cache (precompile: 12421 programs, 0 graphics …), the same condition as my #1023 run. The session lasted 23 minutes.

  • Geometry: everything I looked at is drawn. That covers the Deku Tree's walls and push block, up to and including the boss, and Link's house: its door is there and the black faceted shape is gone. #1023 had fixed the Deku Tree, but I hadn't checked the house on it.
  • Stability: 0 driver faulted, 0 failed fallback pipelines. The only failed programs are the 10 movie-upscaler networks, which are left out on purpose on this card.
  • Stalls (#1512): with #1023's head, the first two minutes in a new area had seven submits of 1.7–9.8 s, and one fallback pipeline took 101 s to compile (55d1da3fea1603d4). Here, under the same empty-cache condition:
    • the longest submit was 1.07 s, the only one over 1 s; 3 were over 500 ms and 9 over 200 ms;
    • no SLOW compile line at all, and 55d1da3fea1603d4 doesn't show up;
    • 4 times, fallback draw of … left out: its compute pipelines are still compiling. The draw is skipped instead of the frame waiting for it.
    • 2,140 graphics pipelines were compiled during the session, 95 s in total.
  • Frame rate: median 41 fps over the 271 five-second windows (menus included), 53% at 40 or more and 4% under 20. In gameplay frames (300+ draws): median 35 fps at ~575 draws and ~100 fallback dispatches, GPU time 16 ms (p90 22 ms), submit 13 ms (p90 28 ms). Not the same scenes as my #1023 run (30–40 fps), so it isn't a like-for-like comparison.
  • Flicker, specks and tree shadows (#1509, #1513, #1581): I didn't notice any.
  • Unrelated to these PRs: the movie after the boss stuttered at first while the counter stayed at 60 fps. The log shows the movie threads waiting about 250 ms for a core (MovieEventHandleThread … ran without a core after 251.0 ms, then MediaExtractorWorker, VideoDecoderThread, longest 251.1 ms). No pipeline was compiling then (0 jobs queued). So it looks like the 4-core CPU, not the GPU. The log also had two over the driver's video-memory allowance (7.3 of 7.2 GB) lines.

I haven't done the second start on a filled cache yet. I'll add it here when I have.

Tested main (`88b554e`) + #1509, #1510, #1511, #1512, #1513 and #1581 on the GTX 1070, as asked in #1023. They merged cleanly. **Setup:** Linux, GTX 1070 8 GB / NVIDIA 580.178.04, i7-7700HQ, output 1920x1080 at 75%. This was a fresh game folder, so the first start had an empty pipeline cache (`precompile: 12421 programs, 0 graphics …`), the same condition as my #1023 run. The session lasted 23 minutes. - **Geometry:** everything I looked at is drawn. That covers the Deku Tree's walls and push block, up to and including the boss, and Link's house: its door is there and the black faceted shape is gone. #1023 had fixed the Deku Tree, but I hadn't checked the house on it. - **Stability:** 0 `driver faulted`, 0 failed fallback pipelines. The only failed programs are the 10 movie-upscaler networks, which are left out on purpose on this card. - **Stalls (#1512):** with #1023's head, the first two minutes in a new area had seven submits of 1.7–9.8 s, and one fallback pipeline took 101 s to compile (`55d1da3fea1603d4`). Here, under the same empty-cache condition: - the longest submit was 1.07 s, the only one over 1 s; 3 were over 500 ms and 9 over 200 ms; - no `SLOW compile` line at all, and `55d1da3fea1603d4` doesn't show up; - 4 times, `fallback draw of … left out: its compute pipelines are still compiling`. The draw is skipped instead of the frame waiting for it. - 2,140 graphics pipelines were compiled during the session, 95 s in total. - **Frame rate:** median 41 fps over the 271 five-second windows (menus included), 53% at 40 or more and 4% under 20. In gameplay frames (300+ draws): median 35 fps at ~575 draws and ~100 fallback dispatches, GPU time 16 ms (p90 22 ms), submit 13 ms (p90 28 ms). Not the same scenes as my #1023 run (30–40 fps), so it isn't a like-for-like comparison. - **Flicker, specks and tree shadows (#1509, #1513, #1581):** I didn't notice any. - **Unrelated to these PRs:** the movie after the boss stuttered at first while the counter stayed at 60 fps. The log shows the movie threads waiting about 250 ms for a core (`MovieEventHandleThread … ran without a core after 251.0 ms`, then `MediaExtractorWorker`, `VideoDecoderThread`, `longest 251.1 ms`). No pipeline was compiling then (`0 jobs queued`). So it looks like the 4-core CPU, not the GPU. The log also had two `over the driver's video-memory allowance (7.3 of 7.2 GB)` lines. I haven't done the second start on a filled cache yet. I'll add it here when I have.
rakefire-impala commented 2026-11-11 00:00:59 +00:00 (Migrated from github.com)

Thanks, that's a thorough run, and good that you checked Link's house too. #1512 is doing what it's meant to. The four left out: its compute pipelines are still compiling lines mean the draw was skipped instead of the frame waiting for it. There was also no SLOW compile, and the 101 s pipeline didn't come back.

All of #1505–#1514 have been merged since your test, so plain main now has everything you tested except #1581, which I rebased today and is still open. For the filled-cache start, main plus #1581 would match what you tested.

On the two things you flagged: the movie-thread waits look like CPU contention to me too, since your log shows nothing compiling at that moment. The video-memory lines don't come from these PRs as far as I can tell. The slot count #1511 raised only sizes the slot map and the task payloads, not the 64 MB record area. Before #1511, the per-slot buffers took about 650 MB for the floor grass alone.

Thanks, that's a thorough run, and good that you checked Link's house too. #1512 is doing what it's meant to. The four `left out: its compute pipelines are still compiling` lines mean the draw was skipped instead of the frame waiting for it. There was also no `SLOW compile`, and the 101 s pipeline didn't come back. All of #1505–#1514 have been merged since your test, so plain main now has everything you tested except #1581, which I rebased today and is still open. For the filled-cache start, main plus #1581 would match what you tested. On the two things you flagged: the movie-thread waits look like CPU contention to me too, since your log shows nothing compiling at that moment. The video-memory lines don't come from these PRs as far as I can tell. The slot count #1511 raised only sizes the slot map and the task payloads, not the 64 MB record area. Before #1511, the per-slot buffers took about 650 MB for the floor grass alone.
Commenting is not possible because the repository is archived.
No description provided.