fix(vk): give storage buffer tables descriptor sets instead of pushes on MoltenVK #643
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!643
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/mvk-push"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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.pyandtests/test_vendor_fixes.pyinto main's tree and runpython -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.
gpu/vk/vk_vendor.h: a new fix,VendorFixes::set_for_tables, on by default whendriver_id == VK_DRIVER_ID_MOLTENVK(vendor_fixes()), and forced byRX_VK_SET_FOR_TABLESinvendor_fixes_env(), like the othervendor fixes.
layout_pushes(bindings, total)ingpu/vk/vk_draw.cppnow holdsensure_layout's push decision: pushed where thedevice pushes and the set fits
max_push_descriptors, as before, but with the fix on, not a set with aVK_DESCRIPTOR_TYPE_STORAGE_BUFFERbinding whosedescriptorCountis more than 1. Such a set is allocated inbind_descriptorsand bound withvkCmdBindDescriptorSets. The back end declares a storage buffer array once per typedview at one binding (
DefineSsbos); texture arrays and constant buffers are one variable per binding and stay pushed. Noother line of
vk_draw.cppchanges.create_deviceingpu/vk/vk_main.cpp: thevk: vendor fixesline gainsstorage buffer tables in descriptor sets, not pushed %d; on MoltenVK withMVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0(no argument buffers at all) it warns that theseprograms cannot translate.
RX_VK_SET_FOR_TABLESrow indocs/development.md; the Descriptors bullet ofgpu/vk/VULKAN.md.tests/test_set_for_tables.py(new) compilesvk_draw.cppfromkPushSizeto the end ofensure_layout(solayout_pushestoo) with stub programs and devices and the realvk_vendor.h,-Wall -Werror, and reads the push flagof 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,=1and=""work as documented, and the bindings are the samepushed or not.
tests/test_vendor_fixes.pyadds a MoltenVK device (17 devices), forces the switch both ways on everydevice, and checks that the fix stays out of
translator_stamp()andmake_profile(), thatlayout_pushesreads it,that
ensure_layoutdecides 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.pyandtests/test_vendor_fixes.pyrun 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>andpython -I -B tests/test_vendor_fixes.py --cxx clang++ --headers <Vulkan-Headers/include>, in this PR's tree and in main'swith the two test files copied in
6c9edffe77a91fc9tests/test_set_for_tables.py, arm64, 2 runs eachwant=set got=pushtests/test_vendor_fixes.py, arm64, 2 runs eachlayout_pushesto readHistory, not this head: the macOS port's integration builds, where this commit (as
0c2fea2a) was first merged ona5f2d6f9, and the GPU check of the mechanism.[mvk-error]pairs, all the alias error[mvk-error];vk: vendor fixes for MoltenVK (1.4.2): … storage buffer tables in descriptor sets, not pushed 1[mvk-error], the same vendor fixes lineThe GPU check (
reports/2026-11-14-mvk-push.md, revision 02, both CPU routes): the LS table shape (three typed views of a16-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.
RX_VK_SET_FOR_TABLES0or1: a program with a storage buffer table gets an ordinary descriptor set instead of a pushed one0off,1on, whatever the drivervkCmdBindDescriptorSets(an allocate, a write and a bind per draw or dispatch instead of a push)=1on another driver in the game=0only to see the failure on a Mac,=1only to try the set path on another GPUTesting
Tested: on macOS 26.7.1, Apple M4 Max, at
77a91fc9, this PR's head before the rebases ontoe090cc48andaa95d35f(main
6c9edffeplus this PR), game-free:tests/test_set_for_tables.pyandtests/test_vendor_fixes.py, arm64: pass at this head (2 runs each); fail on main6c9edffewith this PR's test files (2 runs each), as in Measurements. The failing side ran on arm64 only: the owner's rulefor these fixtures' negatives, after the employer's endpoint agent killed the x86_64 build of
test_set_for_tables'negative in an earlier lane.
clang++ -arch x86_64, run under Rosetta) at this head:test_vendor_fixespasses (17 devices).test_set_for_tables' x86_64 fixture was killed (SIGKILL, exit -9, no crashreport: 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.
ensure_layout,make_pool,layout_pushesand their neighbours), throughtools/ls-suite.pyat this head and on main6c9edffe: here122 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 newtest_set_for_tablespassing (
related-P17-77a91fc9/compare-main-names-summary.txt).vk_draw.cpp,vk_main.cppandvk_progcache.cppcompiled for arm64 and x86_64 at this head and on main, with main'srenderer flags (
nx2/build.py'svflagsplus-arch) and a compile-only prelude declaringposix_fallocateandposix_fadvise, which main's renderer calls and macOS lacks (the same prelude on both sides): exit 0 for all;vk_main.oholds the new log strings.
nx2/translator_files.pystamp()) isebbf160ce9626041on main and at this head.git merge-treeof this head with each open macOS PR's head (#431, #437, #438, #479, #480, #490, #491, #492): noconflict.
reports/PubWave-files/PubMvk/proof/P17-*.log,objects2-*,merge-tree.txt.aa95d35f, atca17df3b(77a91fc9before the rebases ontoe090cc48andaa95d35f):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.pyon wave B's mergedb0e2e16, which carriesthis 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).78ec1c47, which carries every MacPR 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_64under 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).vk: vendor fixes for MoltenVK (1.4.3): … storage buffer tables in descriptor sets, not pushed 1, …, and 0 lines withAttempting 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=1countssets 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:
ca17df3b) and mainaa95d35fthe same way and start each once to the title,expecting the build to succeed, the
vk: vendor fixesline to endstorage 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.inior the vk ABI (MKWVK_API_VERSION). The shader cachesare kept: none of the changed files is in the translator's stamp (
ebbf160ce9626041before and after) and the programcache's format is the same. On Windows and Linux the
vk: vendor fixesline has one more field (0). On macOS the tableprograms' pipelines are made now, so Metal compiles them once.
Reviewer notes
77a91fc9(one commit, cherry-picked with-xfrommacos/mvk-push0c2fea2a):layout_pushesandensure_layout's call invk_draw.cppfirst, thenvk_vendor.hand the log line invk_main.cpp.6c9edffe, no dependency. #644, #645 and #646 stack on it in that order and widenlayout_pushes.docs/development.md, one block. Main's #191 (7ec25fd1, native floatatomics) rewrote the
RX_VK_NATIVE_FADDrow beside which this PR adds itsRX_VK_SET_FOR_TABLESrow; kept this PR'srow, then main's row unchanged. No semantic rework:
ensure_layoutis outside #252's Intel compiler gate and #190'sPcacheFetch, and the layout code is byte for byte the topic's.git merge-tree).vk: vendor fixeslogfinvk_main.cpp(a conflict ingit merge-treeagainst its branch tipf5905178); whichever lands second keeps onelogfholding every field, inorder (each side's text as its own literal).
vk_vendor.handtests/test_vendor_fixes.pymerge on their own.vk_draw.cpp:142) now also counts these sets;left as is.
Issues
See #420
Checklist
Everyone
type(scope): description) of the status line's type.main: onaa95d35f(#761, 2026-11-15), headca17df3b, without a conflict; this PR's owncommits, old → new:
77a91fc9→ca17df3b; the commit names elsewhere in this description are from before therebase.
python -I -B tests/run_all.pypasses (any failure that also fails onmainis 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 excepttest_split_ub_flush, which #658's tree at the time broke and its head fixes;test_collect_widenow passes; the new tests pass andtest_darwin_audio_service, Darwin-only, is skipped by the runner;reports/PubWave-files/waveA/. After the rebase onto mainaa95d35f(headca17df3b): wave B's mergedb0e2e16, 569/32/18 against main aa95d35f's 543/33/17, no new failure.)third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.mainand passing here.tests/that reads changed source updated in this PR, not weakened.77a91fc9369778fd0ee89778fd0ee8ca17df3be6WIP: fix(vk): give storage buffer tables descriptor sets instead of pushes on MoltenVKto fix(vk): give storage buffer tables descriptor sets instead of pushes on MoltenVKFolded into #847 (fix(vk): MoltenVK's descriptor limits in descriptor sets)
Pull request closed