fix(vk): use descriptor sets for stages with more than 8 read_write textures on MoltenVK #645
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!645
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/mvk-rwtex"
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, Metal lets one shader function use at most 8 textures it can both read and write. MoltenVK binds a pushed
descriptor set's images directly to the function, and every storage image and storage texel buffer the renderer's shaders
declare is read-and-write there (the back end marks none of them read-only or write-only). So a shader stage with more than
8 of them fails to compile when its set is pushed, and the draw or dispatch that needs it is left out. In an ordinary set
(a Metal 3 argument buffer) the same stage compiles. This PR gives such stages an ordinary set on MoltenVK, under #643's
switch. Other drivers are unchanged. It is part of the macOS port and stacks on #644. See #420.
How to reproduce
Seen on: macOS 26.7.1, Apple M4 Max, MoltenVK 1.4.2, arm64 and x86_64 under Rosetta: TexelDescProbe's TD5 arm, a
compute stage with 40 storage images and 9 storage texel buffers (
reports/2026-11-14-texel-desc.md; main does not buildon macOS yet)
Settings and switches: defaults
Steps: game-free: in #644's tree (this PR's base), copy in this PR's
tests/test_set_for_tables.pyandtests/test_vendor_fixes.py, and runpython -I -B tests/test_set_for_tables.py --cxx clang++ --headers <Vulkan-Headers/include>. On the GPU: the probe's TD5 arm, pushed and in a set.Expected: a stage with more than 8 storage images and storage texel buffers gets a set on MoltenVK, and its pipeline
is made.
Actual: with a push layout Metal refuses the function:
[mvk-error] VK_ERROR_INITIALIZATION_FAILED: Shader library compile failed (Error code 3): … error: number of textures with read_write access exceeds maximum supported (8), thenCompute shader function could not be compiled into pipeline, on both CPU routes; in a set the same stage works. The testpushes 7 of its 134 layouts that need a set (
want=set got=push).What changed
Under #643's
set_for_tablesfix (MoltenVK), a program is no longer pushed when any one of its stages has more than 8storage images and storage texel buffers, counting every element of an array. Stages are counted one by one, as Metal
counts per function: the task and mesh stages of the mesh fallback share a stage bit but are two functions. A line at
close says how many programs it moved. The SPIR-V, the translator's stamp, the program cache and the vendor fixes' log line
are unchanged.
layout_pushes(p, bindings, stage_first, total)ingpu/vk/vk_draw.cpp:kPushReadWriteTextures = 8, with itssource; with
dev.fixes.set_for_tables, after the table check, it sums each stage'sSTORAGE_IMAGEandSTORAGE_TEXEL_BUFFERdescriptorCountand refuses the push past 8, counted inst_sets_for_rw_textures.ensure_layoutrecords where each stage's bindings begin (stage_first) and passes it.draw_layouts_report()addsvk: read_write textures (RX_VK_SET_FOR_TABLES): N programs given a descriptor set, not pushed, …when the fix is on.gpu/vk/vk_vendor.h(set_for_tables' comment covers both shapes, with the reason),gpu/vk/vk_draw.h(thereport's comment).
RX_VK_SET_FOR_TABLESrow indocs/development.mdcovers the read_write case and the close line;gpu/vk/VULKAN.md's Descriptors bullet.tests/test_set_for_tables.pygrows from 79 to 134 layouts: on MoltenVK 8 storage images, 5 images and 3 texelbuffers, an image array of 8, vertex 8 plus fragment 8, and the mesh fallback's task 5 plus mesh 5 stay pushed; 9
images, 5 plus 4, 9 texel buffers, an array of 9, a fragment stage of 9 and task 4 plus mesh 9 get a set; read-only
textures (20 uniform texel buffers, sampled images and arrays) do not count; the switch works both ways; other drivers
push as before; and the fixture's counter must equal 7.
tests/test_vendor_fixes.pychecks thatensure_layoutdecides through the new call.
Measurements
Verdict: passes: the new test fails on #644's head (7 of 134 layouts pushed where a set is needed, and no counter) and
passes at this head, two interleaved runs each.
Baseline: #644 @
e90ba9a6, this PR's base in the stack. Main lacks #643'slayout_pushes, which this PR extends, and #644changed its signature, so main against this PR would measure them too.
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 (a layout fix: its proof is the test; the game's count of such programs is in the history below)
Method:
tests/test_set_for_tables.pyandtests/test_vendor_fixes.pyrun as this head, base, this head, base (arm64,fresh HOME and TMPDIR,
tools/slot.py), the base 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#644's with the two test files copied in
e90ba9a6fc5a86dctests/test_set_for_tables.py, arm64, 2 runs eachwant=set got=push; no read_write counttests/test_vendor_fixes.py, arm64, 2 runs eachensure_layout does not decide the push through layout_pushes(the new call)History, not this head: TexelDescProbe (
reports/2026-11-14-texel-desc.md, TD5, arm64 and x86_64 under Rosetta): pushed,the 49-texture stage failed to compile with Metal's "read_write access exceeds maximum supported (8)"; in a set it was
exact (1000 writer/reader pairs). Start 12 (
reports/2026-11-14-mac-startup-12.md, integration build a7711f3c carryingthis commit as
162aca1b) counted 0 programs moved for read_write textures in its precompile: the game's programs seenso far stay under 8, and this PR guards the shape the translator can produce.
Flags and settings
No new switch: this widens #643's
RX_VK_SET_FOR_TABLES, whose failure mode is the same (a pipeline MoltenVK cannotmake when pushed, which a set takes). (
RX_VK_SET_FOR_LENGTHS, in this PR's diff until #644 merges, is #644's andunchanged here.)
RX_VK_SET_FOR_TABLES0or1(#643): which programs get an ordinary descriptor set instead of a pushed one0turns both off,1turns both on with any driver=0only to see the failures on a MacTesting
Tested: on macOS 26.7.1, Apple M4 Max, at
fc5a86dc, this PR's head before the rebases ontoe090cc48andaa95d35f(main
6c9edffeplus #643, #644 and this PR), game-free:tests/test_set_for_tables.pyandtests/test_vendor_fixes.py, arm64: pass at this head (2 runs each); fail on #644'shead 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).
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 crash report: the endpointagent [inference]); by policy it was not retried. The same test at the top of this stack (#646's version, which keeps all
134 of these cases, with
rw.sampledheld at 16 samplers) passed on x86_64.tests/test_set_for_lengths.py(#644's, whose fixture compiles the same code), at this head: passes on arm64 and x86_64(138 layouts, 0 not as expected).
tools/ls-suite.pyat this head and on main6c9edffe:here 123 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 changes are thestack's two new tests passing (
related-P28-fc5a86dc/compare-main-names-summary.txt).vk_draw.cpp,vk_main.cppandvk_progcache.cppcompiled for arm64 and x86_64 at this head with main's rendererflags (
nx2/build.py'svflagsplus-arch) and a compile-only prelude declaringposix_fallocateandposix_fadvise, which main's renderer calls and macOS lacks (#476 gives them Darwin paths): exit 0 for all six.byte for byte the topic's after the rebase (
kPushSizeto the end ofensure_layout), so it was not run again.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/P28-*.log,related-P28-*,objects2-P28-*,merge-tree.txt.aa95d35f, atb8b1ba99(fc5a86dcbefore 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: read_write textures (RX_VK_SET_FOR_TABLES): 0 programs given a descriptor set, not pushed, because a stage has more storage images and storage texel buffers than the 8 read_write textures …,and 0 lines with
read_write access exceeds: no program in this scene needed the path, so the game did not exerciseit; the game-free test above covers it. 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). A pushed vertex or fragment stage with 9 read_write textures was not run on the GPU:
only a compute kernel was observed failing (Apple documents read_write textures for all three function kinds).
KosmicKrisp. 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:
b8b1ba99, it carries #643 and #644) and mainaa95d35fthe same way, starteach to the title and quit, expecting the build to succeed, no
vk: read_write texturesline at close, thevk: vendor fixesline as #643 and #644 leave it (not pushed 0), and every other line to match main's (by hand: test requestT-N2VG)
Risks
What could break: on MoltenVK a program with a stage over 8 now allocates, writes and binds a set per draw or dispatch
instead of a push (none were counted in start 12). The count is per stage, from
stage_first; counting by stage bitinstead would have moved mesh fallback programs that push fine (the test's mutation shows it). Other drivers: unchanged
(
set_for_tablesis off there).Turning it off:
RX_VK_SET_FOR_TABLES=0turns off this and #643's tables together, or revert the commit.Compatibility: Nothing changes in saves,
longshot_options.ini, the vk ABI (MKWVK_API_VERSION) or the shader caches(none of the changed files is in the translator's stamp,
ebbf160ce9626041before and after; the program cache's formatis the same). No log line changes off macOS. On macOS a moved program's pipeline is made now, so Metal compiles it once.
Reviewer notes
fc5a86dc(this PR's one commit, cherry-picked with-xfrommacos/mvk-rwtex162aca1b):layout_pushes,ensure_layout'sstage_firstanddraw_layouts_reportinvk_draw.cpp.e90ba9a6, and shows both their commitsuntil they merge. #646 stacks on this one.
docs/development.mdconflict (#1917ec25fd1, the row next to it) was takencare of in #643's and #644's cherry-picks, so this commit applied without one. No semantic rework: the layout code is
byte for byte the topic's.
git merge-treeagainst their branch tips (#650f5905178meets only #643's and#644's
vk_main.cppline). #663 (seg-bind, a later wave) is to rebase after this PR: its edits totests/test_set_for_tables.py(--source, a segmented case) conflict with this PR's in three blocks (docstring, theCASESsum,main()); keep both sides, as the integration did. Once #663 sets segmented pieces in the bindings'counts, this rule counts each piece with no further change.
NonWritableand write-only onesNonReadablein theback end would leave only images both read and written in the count, but changes the SPIR-V and the stamp on every
platform.
Issues
See #420
Checklist
Everyone
type(scope): description) of the status line's type.main: onaa95d35f(#761, 2026-11-15), headb8b1ba99, on #644f08eb702, without aconflict; this PR's own commits, old → new:
fc5a86dc→b8b1ba99; the commit names elsewhere in this descriptionare from before the rebase.
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(headb8b1ba99): 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. (Failing on #644's head, this PR's base.)tests/that reads changed source updated in this PR, not weakened.fc5a86dcbabff8bdc326bff8bdc326b8b1ba998aWIP: fix(vk): use descriptor sets for stages with more than 8 read_write textures on MoltenVKto fix(vk): use descriptor sets for stages with more than 8 read_write textures on MoltenVKFolded into #847 (fix(vk): MoltenVK's descriptor limits in descriptor sets)
Pull request closed