fix(vk): give storage buffer tables descriptor sets instead of pushes on MoltenVK #643

Closed
mooki wants to merge 1 commit from mooki/Longshot:mac/mvk-push into main
Member

Type: fix · Risk: low · Tested on: macOS 26.7.1, Apple M4 Max (MoltenVK 1.4.2), game-free at this head · Needs: Windows, Linux

Why

On a Mac the Vulkan renderer runs on MoltenVK, and there every shader program that reaches the game's memory through a
storage buffer table (an array of storage buffers: the global memory search's tables, and the task and mesh programs'
pointer table) failed to build. MoltenVK binds a pushed descriptor set one resource at a time, outside Metal's argument
buffers, and in that mode SPIRV-Cross cannot translate a shader that reads one table through several typed views. The
first Mac start lost 256 compute pipelines this way, and every draw or dispatch that needed one was left out. This PR gives
those programs an ordinary descriptor set on MoltenVK, which MoltenVK keeps in a Metal 3 argument buffer, where the shader
translates. Other drivers are unchanged. It is part of the macOS port. See #420.

How to reproduce

Seen on: macOS 26.7.1, Apple M4 Max, MoltenVK 1.4.2, arm64 (main does not build on macOS yet: seen on the macOS port's
integration build a5f2d6f9, the first windowless start mac-arm64-a5f2d6f9-01)
Settings and switches: defaults
Steps: start the game on a fresh profile and let the first start prepare its shaders, then read longshot_log.txt.
Game-free, on main: copy this PR's tests/test_set_for_tables.py and tests/test_vendor_fixes.py into main's tree and run
python -I -B tests/test_set_for_tables.py --cxx clang++ --headers <Vulkan-Headers/include>.
Expected: every program's pipeline is made; the test passes.
Actual: 256 pairs of [mvk-error] SPIR-V to MSL conversion error: Attempting to alias same binding with a descriptor array which is not implemented through argument buffers. This is unsupported. and [mvk-error] VK_ERROR_INITIALIZATION_FAILED: Compute shader function could not be compiled into pipeline. (start 01,
reports/2026-11-14-mac-startup-01.md). On main the test pushes 7 of its 79 layouts that need a set (want=set got=push).

What changed

On MoltenVK a program whose descriptor set has an array of storage buffers is no longer pushed: it gets an ordinary set
from the recording's descriptor pool, the path that programs over the push limit already take. Every other program is still
pushed, the bindings are the same either way, and the SPIR-V, the translator's stamp and the program cache are unchanged.
A switch forces it either way, and the log says whether it is on.

  • vk: gpu/vk/vk_vendor.h: a new fix, VendorFixes::set_for_tables, on by default when driver_id == VK_DRIVER_ID_MOLTENVK (vendor_fixes()), and forced by RX_VK_SET_FOR_TABLES in vendor_fixes_env(), like the other
    vendor fixes.
  • vk: layout_pushes(bindings, total) in gpu/vk/vk_draw.cpp now holds ensure_layout's push decision: pushed where the
    device pushes and the set fits max_push_descriptors, as before, but with the fix on, not a set with a
    VK_DESCRIPTOR_TYPE_STORAGE_BUFFER binding whose descriptorCount is more than 1. Such a set is allocated in
    bind_descriptors and bound with vkCmdBindDescriptorSets. The back end declares a storage buffer array once per typed
    view at one binding (DefineSsbos); texture arrays and constant buffers are one variable per binding and stay pushed. No
    other line of vk_draw.cpp changes.
  • vk: create_device in gpu/vk/vk_main.cpp: the vk: vendor fixes line gains storage buffer tables in descriptor sets, not pushed %d; on MoltenVK with MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0 (no argument buffers at all) it warns that these
    programs cannot translate.
  • docs: the RX_VK_SET_FOR_TABLES row in docs/development.md; the Descriptors bullet of gpu/vk/VULKAN.md.
  • tests: tests/test_set_for_tables.py (new) compiles vk_draw.cpp from kPushSize to the end of ensure_layout (so
    layout_pushes too) with stub programs and devices and the real vk_vendor.h, -Wall -Werror, and reads the push flag
    of each set layout made: 79 layouts. On MoltenVK a compute, task-stage or fragment-stage table gets a set; a plain set
    (storage buffers, a texture array of 4, texel buffers, a storage image) and a constant buffer of count 2 are pushed; over
    the push limit or without push descriptors, a set. Ten other drivers (NVIDIA, NVK, AMD, AMDVLK, RADV, Intel Windows,
    ANV, llvmpipe, Qualcomm, Turnip) push as before. =0, =1 and ="" work as documented, and the bindings are the same
    pushed or not. tests/test_vendor_fixes.py adds a MoltenVK device (17 devices), forces the switch both ways on every
    device, and checks that the fix stays out of translator_stamp() and make_profile(), that layout_pushes reads it,
    that ensure_layout decides through it, and that the log line names it.

Measurements

Verdict: passes: the new test fails on main (7 of 79 layouts pushed where a set is needed) and passes at this head, two
interleaved runs each; in the game (history, integration builds) the 256 alias errors went to 0 on arm64 and under Rosetta.

Hardware: macOS 26.7.1, Apple M4 Max, 16-core CPU, 64 GB, Apple clang 21.0.0 (game-free: no GPU, no game)
Scene: none for the tests (a layout fix: its proof is the test). The game rows below are history: the first start's shader
preparation, windowless, defaults
Method: tests/test_set_for_tables.py and tests/test_vendor_fixes.py run as this head, main, this head, main (arm64,
fresh HOME and TMPDIR, tools/slot.py), main's tree with this PR's two test files copied in (SHA-256 equal)
How to reproduce: python -I -B tests/test_set_for_tables.py --cxx clang++ --headers <Vulkan-Headers/include> and
python -I -B tests/test_vendor_fixes.py --cxx clang++ --headers <Vulkan-Headers/include>, in this PR's tree and in main's
with the two test files copied in

Before: main @ 6c9edffe After: this PR @ 77a91fc9
tests/test_set_for_tables.py, arm64, 2 runs each fails both runs: 7 of 79 layouts want=set got=push passes both runs: 79 of 79
tests/test_vendor_fixes.py, arm64, 2 runs each fails both runs: no layout_pushes to read passes both runs: 17 devices
main @ 6c9edffe (this PR's test):
  CASE moltenvk.table_compute driver=14 want=set got=push flag=1 FAIL
  CASE moltenvk.task_table driver=14 want=set got=push flag=1 FAIL
  CASE moltenvk.graphics_table driver=14 want=set got=push flag=1 FAIL
  CASE moltenvk.switch1.table_compute driver=14 RX_VK_SET_FOR_TABLES=1 want=set got=push flag=1 FAIL
  CASE nvidia.switch1.table_compute driver=4 RX_VK_SET_FOR_TABLES=1 want=set got=push flag=1 FAIL
  CASE nvidia.switch1.graphics_table driver=4 RX_VK_SET_FOR_TABLES=1 want=set got=push flag=1 FAIL
  CASE moltenvk.switch_empty.table_compute driver=14 RX_VK_SET_FOR_TABLES= want=set got=push flag=1 FAIL
  SUMMARY cases=79 failures=7
this PR @ 77a91fc9:
ok: 79 layouts; on MoltenVK a set with a storage buffer table is not pushed, and only such a set; every other driver as before; RX_VK_SET_FOR_TABLES forces it both ways; the bindings are the same pushed or not
ok: 17 devices, each fix on only where it is for, none on NVIDIA's driver; every switch forces its fix both ways

History, not this head: the macOS port's integration builds, where this commit (as 0c2fea2a) was first merged on
a5f2d6f9, and the GPU check of the mechanism.

Run Without it: integration a5f2d6f9 With it: integration b7a7c95f (a5f2d6f9 + this commit)
Windowless start, arm64 (starts 01 and 02) 256 [mvk-error] pairs, all the alias error 0 [mvk-error]; vk: vendor fixes for MoltenVK (1.4.2): … storage buffer tables in descriptor sets, not pushed 1
Windowless start, x86_64 under Rosetta (start 03) (not run) 0 [mvk-error], the same vendor fixes line

The GPU check (reports/2026-11-14-mvk-push.md, revision 02, both CPU routes): the LS table shape (three typed views of a
16-entry table at one binding) fails pipeline creation with a push layout, with exactly the game's two MoltenVK errors, and
with an ordinary set is made and loads, stores and atomically adds through all three views of the right entries (5 of 5
data checks; arm64 and x86_64 both status=PASS). Start reports: reports/2026-11-14-mac-startup-01.md, -02.md, -03.md.

Flags and settings

One new switch; its default is on with MoltenVK only.

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
RX_VK_SET_FOR_TABLES 0 or 1: a program with a storage buffer table gets an ordinary descriptor set instead of a pushed one (no switch: pushed wherever the set fits the push limit) unset: on with MoltenVK, off with every other driver; 0 off, 1 on, whatever the driver on: these programs' sets are allocated from the recording's pool and bound with vkCmdBindDescriptorSets (an allocate, a write and a bind per draw or dispatch instead of a push) helps: MoltenVK (the pipelines are made instead of failing); doesn't help: other drivers, which push these programs fine; untested: =1 on another driver in the game leave unset; =0 only to see the failure on a Mac, =1 only to try the set path on another GPU

Testing

Tested: on macOS 26.7.1, Apple M4 Max, at 77a91fc9, this PR's head before the rebases onto e090cc48 and aa95d35f
(main 6c9edffe plus this PR), game-free:

  • tests/test_set_for_tables.py and tests/test_vendor_fixes.py, arm64: pass at this head (2 runs each); fail on main
    6c9edffe with this PR's test files (2 runs each), as in Measurements. The failing side ran on arm64 only: the owner's rule
    for these fixtures' negatives, after the employer's endpoint agent killed the x86_64 build of test_set_for_tables'
    negative in an earlier lane.
  • The same two tests with their fixtures compiled for x86_64 (clang++ -arch x86_64, run under Rosetta) at this head:
    test_vendor_fixes passes (17 devices). test_set_for_tables' x86_64 fixture was killed (SIGKILL, exit -9, no crash
    report: the endpoint agent [inference]); by policy it was not retried. The same test at the top of this stack (#646's
    version, which keeps all 79 of these cases and checks the flags more strictly) passed on x86_64.
  • The 133 tests that read the files this stack changes (tests naming a changed file, or cutting ensure_layout,
    make_pool, layout_pushes and their neighbours), through tools/ls-suite.py at this head and on main 6c9edffe: here
    122 passed and 10 failed, main 121 passed and 10 failed. The same 10 fail on both (main's macOS gaps: the pipeline-cache
    and program-cache fixtures on posix_fadvise/posix_fallocate, test_appimage_player_build, test_crash_dialog,
    test_pipeline_tour, and the two Windows-only guard checks); the only status change is the new test_set_for_tables
    passing (related-P17-77a91fc9/compare-main-names-summary.txt).
  • vk_draw.cpp, vk_main.cpp and vk_progcache.cpp compiled for arm64 and x86_64 at this head and on main, with main's
    renderer flags (nx2/build.py's vflags plus -arch) and a compile-only prelude declaring posix_fallocate and
    posix_fadvise, which main's renderer calls and macOS lacks (the same prelude on both sides): exit 0 for all; vk_main.o
    holds the new log strings.
  • The translator's stamp (nx2/translator_files.py stamp()) is ebbf160ce9626041 on main and at this head.
  • git merge-tree of this head with each open macOS PR's head (#431, #437, #438, #479, #480, #490, #491, #492): no
    conflict.
  • Logs: reports/PubWave-files/PubMvk/proof/P17-*.log, objects2-*, merge-tree.txt.
  • After the rebase onto main aa95d35f, at ca17df3b (77a91fc9 before the rebases onto e090cc48 and aa95d35f):
    Sweep3's checks of this head (the C/C++ it changes compiled, and its own tests run, on arm64 and x86_64 where they
    apply) gave the same results as before; python -I -B tests/run_all.py on wave B's merge db0e2e16, which carries
    this head: 569/32/18 (passed/failed/skipped) against main aa95d35f's 543/33/17 on this Mac, no new failure
    (reports/2026-11-15-rebase-sweep-3.md).
  • macOS game runs with this PR in the stack: arm64, windowless: run E9a on I41 78ec1c47, which carries every Mac
    PR through #708 and the I41 fixes: a start from the field-dawn save to a 1,197 s hold in play, the runner's 12 of 12
    checks passed, 0 skipped draws in the hold, 0 XPC errors, a clean exit (reports/2026-11-15-mac-perf-E9.md); x86_64
    under Rosetta 2: run #34 on I38 3f872b0c: 16 m 52 s of play, 19 of 19 checks, 0 skipped draws, 1,906 draws a frame,
    no device loss, a clean exit (reports/2026-11-14-mac-startup-34-x86_64.md).
  • E9a's log (line 200): vk: vendor fixes for MoltenVK (1.4.3): … storage buffer tables in descriptor sets, not pushed 1, …, and 0 lines with Attempting to alias same binding. Under Rosetta, #34's 19 of 19 checks stand for it;
    that log's lines for this PR are not quoted here.

Not tested: the game at this head: per-PR game runs are not planned; the stack's runs with it, E9a on arm64 and #34
under Rosetta, are under Tested. The cost of the set path against a push in a real scene (RX_VK_PROF_FINE=1 counts
sets allocated); KosmicKrisp; an Intel Mac (where MoltenVK may run without argument buffers and these programs would
still fail). Windows and Linux: not built or run there (its test request covers the build and a start); their layouts do
not change (the test's ten other drivers).

Must be tested:

  • Windows and Linux, any GPU: build this PR (head ca17df3b) and main aa95d35f the same way and start each once to the title,
    expecting the build to succeed, the vk: vendor fixes line to end storage buffer tables in descriptor sets, not pushed 0, and every other line to match main's (by hand: test request T-2JEU)

Risks

What could break: on MoltenVK the table programs now allocate, write and bind a set per draw or dispatch instead of a
push. That cost is unmeasured. A recording's pool (8192 sets, 16384 storage buffers) that runs out is followed by another
of the same size, as for programs over the push limit; pools are reset per recording and never fragment. On other drivers
the layouts are identical; the log line has one more field.

Turning it off: RX_VK_SET_FOR_TABLES=0 (MoltenVK then fails these pipelines as on main), or revert the commit.

Compatibility: Nothing changes in saves, longshot_options.ini or the vk ABI (MKWVK_API_VERSION). The shader caches
are kept: none of the changed files is in the translator's stamp (ebbf160ce9626041 before and after) and the program
cache's format is the same. On Windows and Linux the vk: vendor fixes line has one more field (0). On macOS the table
programs' pipelines are made now, so Metal compiles them once.

Reviewer notes

  • Review 77a91fc9 (one commit, cherry-picked with -x from macos/mvk-push 0c2fea2a): layout_pushes and
    ensure_layout's call in vk_draw.cpp first, then vk_vendor.h and the log line in vk_main.cpp.
  • Stacking: on main 6c9edffe, no dependency. #644, #645 and #646 stack on it in that order and widen layout_pushes.
  • Conflict with main, resolved in the cherry-pick: docs/development.md, one block. Main's #191 (7ec25fd1, native float
    atomics) rewrote the RX_VK_NATIVE_FADD row beside which this PR adds its RX_VK_SET_FOR_TABLES row; kept this PR's
    row, then main's row unchanged. No semantic rework: ensure_layout is outside #252's Intel compiler gate and #190's
    PcacheFetch, and the layout code is byte for byte the topic's.
  • With the open macOS PRs (#431, #437, #438, #479, #480, #490, #491, #492): none (git merge-tree).
  • With the PRs of this wave: #650 (prep-fast) changes the same vk: vendor fixes logf in vk_main.cpp (a conflict in
    git merge-tree against its branch tip f5905178); whichever lands second keeps one logf holding every field, in
    order (each side's text as its own literal). vk_vendor.h and tests/test_vendor_fixes.py merge on their own.
  • The fine profile's counter label "sets allocated (over the push limit)" (vk_draw.cpp:142) now also counts these sets;
    left as is.

Issues

See #420

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how. (Test request T-2JEU.)
  • Rebased on current main: on aa95d35f (#761, 2026-11-15), head ca17df3b, without a conflict; this PR's own
    commits, old → new: 77a91fc9 → ca17df3b; the commit names elsewhere in this description are from before the
    rebase.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named). (On the wave's merge of every macOS PR built with this one, 453eb678: 553 passed, 32 failed, 18 skipped against main 6c9edffe's 531/32/17 on this Mac; the failures are main's own except test_split_ub_flush, which #658's tree at the time broke and its head fixes; test_collect_wide now passes; the new tests pass and test_darwin_audio_service, Darwin-only, is skipped by the runner; reports/PubWave-files/waveA/. After the rebase onto main aa95d35f (head ca17df3b): wave B's merge db0e2e16, 569/32/18 against main aa95d35f's 543/33/17, no new failure.)
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value.
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Docs updated.
**Type:** fix · **Risk:** low · **Tested on:** macOS 26.7.1, Apple M4 Max (MoltenVK 1.4.2), game-free at this head · **Needs:** Windows, Linux ## Why On a Mac the Vulkan renderer runs on MoltenVK, and there every shader program that reaches the game's memory through a storage buffer table (an array of storage buffers: the global memory search's tables, and the task and mesh programs' pointer table) failed to build. MoltenVK binds a pushed descriptor set one resource at a time, outside Metal's argument buffers, and in that mode SPIRV-Cross cannot translate a shader that reads one table through several typed views. The first Mac start lost 256 compute pipelines this way, and every draw or dispatch that needed one was left out. This PR gives those programs an ordinary descriptor set on MoltenVK, which MoltenVK keeps in a Metal 3 argument buffer, where the shader translates. Other drivers are unchanged. It is part of the macOS port. See #420. ## How to reproduce **Seen on:** macOS 26.7.1, Apple M4 Max, MoltenVK 1.4.2, arm64 (main does not build on macOS yet: seen on the macOS port's integration build a5f2d6f9, the first windowless start `mac-arm64-a5f2d6f9-01`) **Settings and switches:** defaults **Steps:** start the game on a fresh profile and let the first start prepare its shaders, then read longshot_log.txt. Game-free, on main: copy this PR's `tests/test_set_for_tables.py` and `tests/test_vendor_fixes.py` into main's tree and run `python -I -B tests/test_set_for_tables.py --cxx clang++ --headers <Vulkan-Headers/include>`. **Expected:** every program's pipeline is made; the test passes. **Actual:** 256 pairs of `[mvk-error] SPIR-V to MSL conversion error: Attempting to alias same binding with a descriptor array which is not implemented through argument buffers. This is unsupported.` and `[mvk-error] VK_ERROR_INITIALIZATION_FAILED: Compute shader function could not be compiled into pipeline.` (start 01, `reports/2026-11-14-mac-startup-01.md`). On main the test pushes 7 of its 79 layouts that need a set (`want=set got=push`). ## What changed On MoltenVK a program whose descriptor set has an array of storage buffers is no longer pushed: it gets an ordinary set from the recording's descriptor pool, the path that programs over the push limit already take. Every other program is still pushed, the bindings are the same either way, and the SPIR-V, the translator's stamp and the program cache are unchanged. A switch forces it either way, and the log says whether it is on. - vk: `gpu/vk/vk_vendor.h`: a new fix, `VendorFixes::set_for_tables`, on by default when `driver_id == VK_DRIVER_ID_MOLTENVK` (`vendor_fixes()`), and forced by `RX_VK_SET_FOR_TABLES` in `vendor_fixes_env()`, like the other vendor fixes. - vk: `layout_pushes(bindings, total)` in `gpu/vk/vk_draw.cpp` now holds `ensure_layout`'s push decision: pushed where the device pushes and the set fits `max_push_descriptors`, as before, but with the fix on, not a set with a `VK_DESCRIPTOR_TYPE_STORAGE_BUFFER` binding whose `descriptorCount` is more than 1. Such a set is allocated in `bind_descriptors` and bound with `vkCmdBindDescriptorSets`. The back end declares a storage buffer array once per typed view at one binding (`DefineSsbos`); texture arrays and constant buffers are one variable per binding and stay pushed. No other line of `vk_draw.cpp` changes. - vk: `create_device` in `gpu/vk/vk_main.cpp`: the `vk: vendor fixes` line gains `storage buffer tables in descriptor sets, not pushed %d`; on MoltenVK with `MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0` (no argument buffers at all) it warns that these programs cannot translate. - docs: the `RX_VK_SET_FOR_TABLES` row in `docs/development.md`; the Descriptors bullet of `gpu/vk/VULKAN.md`. - tests: `tests/test_set_for_tables.py` (new) compiles `vk_draw.cpp` from `kPushSize` to the end of `ensure_layout` (so `layout_pushes` too) with stub programs and devices and the real `vk_vendor.h`, `-Wall -Werror`, and reads the push flag of each set layout made: 79 layouts. On MoltenVK a compute, task-stage or fragment-stage table gets a set; a plain set (storage buffers, a texture array of 4, texel buffers, a storage image) and a constant buffer of count 2 are pushed; over the push limit or without push descriptors, a set. Ten other drivers (NVIDIA, NVK, AMD, AMDVLK, RADV, Intel Windows, ANV, llvmpipe, Qualcomm, Turnip) push as before. `=0`, `=1` and `=""` work as documented, and the bindings are the same pushed or not. `tests/test_vendor_fixes.py` adds a MoltenVK device (17 devices), forces the switch both ways on every device, and checks that the fix stays out of `translator_stamp()` and `make_profile()`, that `layout_pushes` reads it, that `ensure_layout` decides through it, and that the log line names it. ## Measurements **Verdict:** passes: the new test fails on main (7 of 79 layouts pushed where a set is needed) and passes at this head, two interleaved runs each; in the game (history, integration builds) the 256 alias errors went to 0 on arm64 and under Rosetta. Hardware: macOS 26.7.1, Apple M4 Max, 16-core CPU, 64 GB, Apple clang 21.0.0 (game-free: no GPU, no game) Scene: none for the tests (a layout fix: its proof is the test). The game rows below are history: the first start's shader preparation, windowless, defaults Method: `tests/test_set_for_tables.py` and `tests/test_vendor_fixes.py` run as this head, main, this head, main (arm64, fresh HOME and TMPDIR, `tools/slot.py`), main's tree with this PR's two test files copied in (SHA-256 equal) How to reproduce: `python -I -B tests/test_set_for_tables.py --cxx clang++ --headers <Vulkan-Headers/include>` and `python -I -B tests/test_vendor_fixes.py --cxx clang++ --headers <Vulkan-Headers/include>`, in this PR's tree and in main's with the two test files copied in | | Before: main @ 6c9edffe | After: this PR @ 77a91fc9 | |---|---|---| | `tests/test_set_for_tables.py`, arm64, 2 runs each | fails both runs: 7 of 79 layouts `want=set got=push` | passes both runs: 79 of 79 | | `tests/test_vendor_fixes.py`, arm64, 2 runs each | fails both runs: no `layout_pushes` to read | passes both runs: 17 devices | ``` main @ 6c9edffe (this PR's test): CASE moltenvk.table_compute driver=14 want=set got=push flag=1 FAIL CASE moltenvk.task_table driver=14 want=set got=push flag=1 FAIL CASE moltenvk.graphics_table driver=14 want=set got=push flag=1 FAIL CASE moltenvk.switch1.table_compute driver=14 RX_VK_SET_FOR_TABLES=1 want=set got=push flag=1 FAIL CASE nvidia.switch1.table_compute driver=4 RX_VK_SET_FOR_TABLES=1 want=set got=push flag=1 FAIL CASE nvidia.switch1.graphics_table driver=4 RX_VK_SET_FOR_TABLES=1 want=set got=push flag=1 FAIL CASE moltenvk.switch_empty.table_compute driver=14 RX_VK_SET_FOR_TABLES= want=set got=push flag=1 FAIL SUMMARY cases=79 failures=7 this PR @ 77a91fc9: ok: 79 layouts; on MoltenVK a set with a storage buffer table is not pushed, and only such a set; every other driver as before; RX_VK_SET_FOR_TABLES forces it both ways; the bindings are the same pushed or not ok: 17 devices, each fix on only where it is for, none on NVIDIA's driver; every switch forces its fix both ways ``` History, not this head: the macOS port's integration builds, where this commit (as `0c2fea2a`) was first merged on `a5f2d6f9`, and the GPU check of the mechanism. | Run | Without it: integration a5f2d6f9 | With it: integration b7a7c95f (a5f2d6f9 + this commit) | |---|---|---| | Windowless start, arm64 (starts 01 and 02) | 256 `[mvk-error]` pairs, all the alias error | 0 `[mvk-error]`; `vk: vendor fixes for MoltenVK (1.4.2): … storage buffer tables in descriptor sets, not pushed 1` | | Windowless start, x86_64 under Rosetta (start 03) | (not run) | 0 `[mvk-error]`, the same vendor fixes line | The GPU check (`reports/2026-11-14-mvk-push.md`, revision 02, both CPU routes): the LS table shape (three typed views of a 16-entry table at one binding) fails pipeline creation with a push layout, with exactly the game's two MoltenVK errors, and with an ordinary set is made and loads, stores and atomically adds through all three views of the right entries (5 of 5 data checks; arm64 and x86_64 both `status=PASS`). Start reports: `reports/2026-11-14-mac-startup-01.md`, `-02.md`, `-03.md`. ### Flags and settings One new switch; its default is on with MoltenVK only. | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | `RX_VK_SET_FOR_TABLES` | `0` or `1`: a program with a storage buffer table gets an ordinary descriptor set instead of a pushed one | (no switch: pushed wherever the set fits the push limit) | unset: on with MoltenVK, off with every other driver; `0` off, `1` on, whatever the driver | on: these programs' sets are allocated from the recording's pool and bound with `vkCmdBindDescriptorSets` (an allocate, a write and a bind per draw or dispatch instead of a push) | helps: MoltenVK (the pipelines are made instead of failing); doesn't help: other drivers, which push these programs fine; untested: `=1` on another driver in the game | leave unset; `=0` only to see the failure on a Mac, `=1` only to try the set path on another GPU | ## Testing **Tested:** on macOS 26.7.1, Apple M4 Max, at `77a91fc9`, this PR's head before the rebases onto e090cc48 and aa95d35f (main 6c9edffe plus this PR), game-free: - `tests/test_set_for_tables.py` and `tests/test_vendor_fixes.py`, arm64: pass at this head (2 runs each); fail on main 6c9edffe with this PR's test files (2 runs each), as in Measurements. The failing side ran on arm64 only: the owner's rule for these fixtures' negatives, after the employer's endpoint agent killed the x86_64 build of `test_set_for_tables`' negative in an earlier lane. - The same two tests with their fixtures compiled for x86_64 (`clang++ -arch x86_64`, run under Rosetta) at this head: `test_vendor_fixes` passes (17 devices). `test_set_for_tables`' x86_64 fixture was killed (SIGKILL, exit -9, no crash report: the endpoint agent [inference]); by policy it was not retried. The same test at the top of this stack (#646's version, which keeps all 79 of these cases and checks the flags more strictly) passed on x86_64. - The 133 tests that read the files this stack changes (tests naming a changed file, or cutting `ensure_layout`, `make_pool`, `layout_pushes` and their neighbours), through `tools/ls-suite.py` at this head and on main 6c9edffe: here 122 passed and 10 failed, main 121 passed and 10 failed. The same 10 fail on both (main's macOS gaps: the pipeline-cache and program-cache fixtures on `posix_fadvise`/`posix_fallocate`, `test_appimage_player_build`, `test_crash_dialog`, `test_pipeline_tour`, and the two Windows-only guard checks); the only status change is the new `test_set_for_tables` passing (`related-P17-77a91fc9/compare-main-names-summary.txt`). - `vk_draw.cpp`, `vk_main.cpp` and `vk_progcache.cpp` compiled for arm64 and x86_64 at this head and on main, with main's renderer flags (`nx2/build.py`'s `vflags` plus `-arch`) and a compile-only prelude declaring `posix_fallocate` and `posix_fadvise`, which main's renderer calls and macOS lacks (the same prelude on both sides): exit 0 for all; `vk_main.o` holds the new log strings. - The translator's stamp (`nx2/translator_files.py` `stamp()`) is `ebbf160ce9626041` on main and at this head. - `git merge-tree` of this head with each open macOS PR's head (#431, #437, #438, #479, #480, #490, #491, #492): no conflict. - Logs: `reports/PubWave-files/PubMvk/proof/P17-*.log`, `objects2-*`, `merge-tree.txt`. - **After the rebase onto main aa95d35f**, at `ca17df3b` (`77a91fc9` before the rebases onto e090cc48 and aa95d35f): Sweep3's checks of this head (the C/C++ it changes compiled, and its own tests run, on arm64 and x86_64 where they apply) gave the same results as before; `python -I -B tests/run_all.py` on wave B's merge `db0e2e16`, which carries this head: 569/32/18 (passed/failed/skipped) against main aa95d35f's 543/33/17 on this Mac, no new failure (`reports/2026-11-15-rebase-sweep-3.md`). - **macOS game runs with this PR in the stack:** arm64, windowless: run E9a on I41 `78ec1c47`, which carries every Mac PR through #708 and the I41 fixes: a start from the field-dawn save to a 1,197 s hold in play, the runner's 12 of 12 checks passed, 0 skipped draws in the hold, 0 XPC errors, a clean exit (`reports/2026-11-15-mac-perf-E9.md`); x86_64 under Rosetta 2: run #34 on I38 `3f872b0c`: 16 m 52 s of play, 19 of 19 checks, 0 skipped draws, 1,906 draws a frame, no device loss, a clean exit (`reports/2026-11-14-mac-startup-34-x86_64.md`). - **E9a's log** (line 200): `vk: vendor fixes for MoltenVK (1.4.3): … storage buffer tables in descriptor sets, not pushed 1, …`, and 0 lines with `Attempting to alias same binding`. Under Rosetta, #34's 19 of 19 checks stand for it; that log's lines for this PR are not quoted here. **Not tested:** the game at this head: per-PR game runs are not planned; the stack's runs with it, E9a on arm64 and #34 under Rosetta, are under Tested. The cost of the set path against a push in a real scene (`RX_VK_PROF_FINE=1` counts sets allocated); KosmicKrisp; an Intel Mac (where MoltenVK may run without argument buffers and these programs would still fail). Windows and Linux: not built or run there (its test request covers the build and a start); their layouts do not change (the test's ten other drivers). **Must be tested:** - Windows and Linux, any GPU: build this PR (head ca17df3b) and main aa95d35f the same way and start each once to the title, expecting the build to succeed, the `vk: vendor fixes` line to end `storage buffer tables in descriptor sets, not pushed 0`, and every other line to match main's (by hand: test request T-2JEU) ## Risks **What could break:** on MoltenVK the table programs now allocate, write and bind a set per draw or dispatch instead of a push. That cost is unmeasured. A recording's pool (8192 sets, 16384 storage buffers) that runs out is followed by another of the same size, as for programs over the push limit; pools are reset per recording and never fragment. On other drivers the layouts are identical; the log line has one more field. **Turning it off:** `RX_VK_SET_FOR_TABLES=0` (MoltenVK then fails these pipelines as on main), or revert the commit. **Compatibility:** Nothing changes in saves, `longshot_options.ini` or the vk ABI (`MKWVK_API_VERSION`). The shader caches are kept: none of the changed files is in the translator's stamp (`ebbf160ce9626041` before and after) and the program cache's format is the same. On Windows and Linux the `vk: vendor fixes` line has one more field (`0`). On macOS the table programs' pipelines are made now, so Metal compiles them once. ## Reviewer notes - Review `77a91fc9` (one commit, cherry-picked with `-x` from `macos/mvk-push` `0c2fea2a`): `layout_pushes` and `ensure_layout`'s call in `vk_draw.cpp` first, then `vk_vendor.h` and the log line in `vk_main.cpp`. - Stacking: on main 6c9edffe, no dependency. #644, #645 and #646 stack on it in that order and widen `layout_pushes`. - Conflict with main, resolved in the cherry-pick: `docs/development.md`, one block. Main's #191 (`7ec25fd1`, native float atomics) rewrote the `RX_VK_NATIVE_FADD` row beside which this PR adds its `RX_VK_SET_FOR_TABLES` row; kept this PR's row, then main's row unchanged. No semantic rework: `ensure_layout` is outside #252's Intel compiler gate and #190's `PcacheFetch`, and the layout code is byte for byte the topic's. - With the open macOS PRs (#431, #437, #438, #479, #480, #490, #491, #492): none (`git merge-tree`). - With the PRs of this wave: #650 (prep-fast) changes the same `vk: vendor fixes` `logf` in `vk_main.cpp` (a conflict in `git merge-tree` against its branch tip `f5905178`); whichever lands second keeps one `logf` holding every field, in order (each side's text as its own literal). `vk_vendor.h` and `tests/test_vendor_fixes.py` merge on their own. - The fine profile's counter label "sets allocated (over the push limit)" (`vk_draw.cpp:142`) now also counts these sets; left as is. ## Issues See #420 ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. (Test request T-2JEU.) - [x] Rebased on current `main`: on aa95d35f (#761, 2026-11-15), head `ca17df3b`, without a conflict; this PR's own commits, old → new: `77a91fc9` → `ca17df3b`; the commit names elsewhere in this description are from before the rebase. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). (On the wave's merge of every macOS PR built with this one, `453eb678`: 553 passed, 32 failed, 18 skipped against main 6c9edffe's 531/32/17 on this Mac; the failures are main's own except `test_split_ub_flush`, which #658's tree at the time broke and its head fixes; `test_collect_wide` now passes; the new tests pass and `test_darwin_audio_service`, Darwin-only, is skipped by the runner; `reports/PubWave-files/waveA/`. After the rebase onto main aa95d35f (head `ca17df3b`): wave B's merge `db0e2e16`, 569/32/18 against main aa95d35f's 543/33/17, no new failure.) - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Docs updated.
fix(macos): storage buffer tables get descriptor sets on MoltenVK
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 4s
77a91fc936
MoltenVK 1.4.2 binds a push descriptor set's resources one by one, never
through a Metal argument buffer (MVKDescriptorSet.mm pickArgumentBufferMode
returns Off for every push layout, and the pipeline names that set to
SPIRV-Cross as discrete). The SPIR-V back end declares a storage buffer
table (count > 1: Info::global_tables, the task and mesh programs'
c0+0x880 table) as one fixed-size array per typed view, all at one binding
(DefineSsbos; any global memory access uses the U32, U32x2 and U32x4
views), and SPIRV-Cross 6c09849f aliases such arrays only inside an
argument buffer. So every pushed program with a table failed: 256
"Attempting to alias same binding with a descriptor array which is not
implemented through argument buffers" errors and as many failed compute
pipelines in the first Mac start (mac-arm64-a5f2d6f9-01).

vk_vendor.h gets a fix, set_for_tables, on by default only for
VK_DRIVER_ID_MOLTENVK, with RX_VK_SET_FOR_TABLES to force it either way.
ensure_layout's push decision moves into layout_pushes(): with the fix on,
a set that has an array of storage buffers is not pushed but allocated
from the recording's pool, the path programs over the push limit already
take, where MoltenVK's sets are Metal 3 argument buffers. Every other set
is still pushed, the bindings are the same either way, and the SPIR-V,
the translator stamp and the program cache are unchanged. The vendor
fixes' log line at device creation says whether it is on, and a MoltenVK
run with MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0 is warned that these
programs cannot translate.

tests/test_set_for_tables.py compiles vk_draw.cpp's layout code (kPushSize
to the end of ensure_layout) on made-up programs and devices: on MoltenVK
a set with a table (compute, task, fragment) is a set, a set without one
(texture arrays and a constant buffer of count 2 included) is pushed;
ten other drivers push as before; the switch works both ways. It fails
on the base with 7 of 79 cases. tests/test_vendor_fixes.py adds a MoltenVK
device and the switch, and checks that the fix stays out of the
translation's profile and stamp. Windows and Linux: same layouts as
before; the log line gains one field.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 0c2fea2a303a5158d12ebc87bcd28467486486ef)
mooki force-pushed mac/mvk-push from 77a91fc936
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 4s
to 9778fd0ee8
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 16s
CI / Workflow lint (actionlint) (pull_request) Successful in 22s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 22s
Third-party parts / As their notes say (pull_request) Successful in 33s
PR description / Description follows the template (pull_request_target) Successful in 17s
2026-11-15 07:16:16 +00:00
Compare
mooki force-pushed mac/mvk-push from 9778fd0ee8
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 16s
CI / Workflow lint (actionlint) (pull_request) Successful in 22s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 22s
Third-party parts / As their notes say (pull_request) Successful in 33s
PR description / Description follows the template (pull_request_target) Successful in 17s
to ca17df3be6
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 6s
Third-party parts / As their notes say (pull_request) Successful in 13s
Website / Website on Cloudflare (pull_request_target) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 5s
2026-11-15 13:09:49 +00:00
Compare
mooki changed title from WIP: fix(vk): give storage buffer tables descriptor sets instead of pushes on MoltenVK to fix(vk): give storage buffer tables descriptor sets instead of pushes on MoltenVK 2026-11-15 14:17:59 +00:00
Author
Member

Folded into #847 (fix(vk): MoltenVK's descriptor limits in descriptor sets)

Folded into #847 (fix(vk): MoltenVK's descriptor limits in descriptor sets)
mooki closed this pull request 2026-11-15 18:07:46 +00:00
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 6s
Third-party parts / As their notes say (pull_request) Successful in 13s
Website / Website on Cloudflare (pull_request_target) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 5s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!643
No description provided.