fix(vk): storage image and texel buffer arrays are indexed nonuniformly where the GPU can #502
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!502
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:fix/nonuniform-image-arrays"
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 (game-free checks, no game run) · Needs: Windows or Linux, any GPU (AMD preferred)
Why
A shader that picks a storage image, an image buffer or a texture buffer from an array, with an index that can differ
between the pixels or threads of one draw or dispatch, was translated as if they all picked the same one. Vulkan leaves
that undefined, so on a GPU that relies on it some of them read or write the wrong image. Texture arrays were already
translated right.
How to reproduce
Seen on: macOS 26.7.1, Apple M4 Max, in the game-free test (
tests/test_nonuniform_image_arrays.py --treemain and--tree#450's head).make_profileis the same code on every OS; what it decides depends on the GPU's Vulkan 1.2features. Not seen in the game: whether it has such arrays is not known (its programs were not read).
Settings and switches: defaults (no switch: the device's features decide).
Steps:
python -I -B tests/test_nonuniform_image_arrays.py --headers <SPIRV-Headers/include> --spirv-val spirv-val --cxx clang++ --tree <a tree of a66f16b0>(#450's head; main's tree fails earlier, on #450's gap): 13 syntheticprograms translated with the profiles of five stand-in GPUs.
Expected: on a GPU with a kind's feature,
NonUniformon every access chain and atomic texel pointer that reaches anarray of that kind, with the kind's capability; a stamp that keeps GPUs with and without the features apart.
Actual: 37 failures on #450's head: every array of the three kinds left undecorated on a GPU with the feature, and
no stamp word (45 failures on main, where #450's gap comes first).
What changed
The renderer's shader profile now tells the translator to index storage image, image buffer and texture buffer arrays
nonuniformly wherever the GPU reports the Vulkan 1.2 feature for that kind, as it already did for texture arrays. On a
GPU that lacks one of the features the translator stamp gets a word of its own, so programs made with and without the
decorations are kept apart; no back-end file changes.
fix/descriptor-image-arrays), at its heada66f16b0, because #450 is whatdeclares these arrays as arrays. Until #450 merges this PR shows its two commits too; this PR's own change is the
last commit,
f8f7695c. In it, read the two hunks invk_shader.cpp, thenMarkNonUniformand its callers inemit_spirv_image.cppto see that the three flags are all the back end needs.make_profileingpu/vk/vk_shader.cppsetssupport_storage_image_array_nonuniform_indexing,support_uniform_texel_buffer_array_nonuniform_indexingandsupport_storage_texel_buffer_array_nonuniform_indexingfrom the device's Vulkan 1.2 features of the same names (
dev.vk12). Before, they were left false and only thesampled image flag was set (unconditionally, as it still is).
create_deviceinvk_main.cpphands the queried featurechain to
vkCreateDevice, so nothing changes there.(
MarkNonUniforminemit_spirv_image.cpp, andImage()inemit_spirv_image_atomic.cppfrom #450). For anyindex but an immediate it puts
NonUniformon the index, the access chain, the loaded image and an atomic's texelpointer, and declares the kind's capability. No back-end file changes in this PR.
invocation, and an index that differs stays undefined there, as it is on main for every GPU.
translator_stampadds a word (nnua, and which of the three features are missing) on a GPU that lacks one.Programs translated with the decorations are then not used on a GPU without the feature, nor the other way round (a
laptop's two GPUs share one cache folder). A GPU with all three adds nothing.
tests/test_nonuniform_image_arrays.py(new), with its fixturetests/nonuniform_image_arrays_ir.cpp(new).make_profile,storage_binding_align(vk_internal.h) and the stamp's new word out of the tree, andtakes the profile of five stand-in GPUs: none of the three features, each one alone, and all three.
code elimination,
CollectShaderInfoPassand the back end. They have a 2-element image buffer array and a3-element storage image array, each read and written and each added to atomically; a 2-element texture buffer
array and a 2-element texture array fetched; one program with all four kinds; and the same programs with single
descriptors. That makes 65 modules.
spirv-val --target-env vulkan1.3. An array of a kind the GPU indexesnonuniformly (textures always) must have
NonUniformon every access chain and atomic texel pointer that reachesit, and the kind's capability. Any other array must have neither, and a module without such an array no
NonUniformat all.create_devicemust hand the queried Vulkan 1.2 features tovkCreateDevicewithout switching these off.--treeruns it on another checkout;--base-treealso requires every module whose program has no array of a kindthe GPU's features turn on to be byte for byte that checkout's. On the GPU with none of the features that is every
module.
tests/test_amd_subgroup_profiles.pycompilesmake_profilewith a stand-in device, which now has the threefeatures
make_profilereads.shaderSampledImageArrayNonUniformIndexing, as before (thehandle tables check it themselves,
handle_tables_on). Gating it too would change texture arrays on GPUs withoutthat feature, which is outside this PR.
nx2/bake_ds/bake_ds.cppandgpu/shaderbatch.cppkeep copies of an oldermake_profile(no float add,gather offset or fragment shuffle settings either) and are left as they are. Touching
bake_ds.cppwould also changethe lift key (
nx2/make_game.py lift_sources), so every player's next build would lift the game again.Measurements
Verdict: fixed, game-free: the new test fails on main (45 failures) and on #450's head (37) and passes here, with
the 51 modules that have no array of a kind the GPU's features turn on byte for byte #450's; the suite adds no failure
(533 passed against main's 531, the same 32 failures and 17 skips). A translation fix: nothing to time.
Commits: main
6c9edffe5d(2026-11-14); this PR fix/nonuniform-image-arrays @f8f7695c99, on #450 @a66f16b0.Hardware: Apple M4 Max (16 CPUs), macOS 26.7.1, Apple clang 21, spirv-val of SPIRV-Tools v2026.4 (Homebrew spirv-tools 1.4.363.0). Game-free: no GPU, no game
Scene: none (a translation fix; its proof is the test, as CONTRIBUTING's
fixrow allows)Method:
tests/test_nonuniform_image_arrays.pyon this PR with--base-tree#450's head, and with--treemain and--tree#450's head (the fixture and checks of this PR, the profile and back end of that tree); the full game-free suiteHow to reproduce:
python -I -B tests/test_nonuniform_image_arrays.py --headers <SPIRV-Headers/include> --spirv-val spirv-val --cxx clang++ --base-tree <a tree of a66f16b0>; add--tree <a tree of main or of a66f16b0>for their sideBaseline: main @
6c9edffeis the first column. The second, #450 @a66f16b0, is the PR this one is stacked on: it declaresthese arrays as arrays, so against it only this PR's decorations and stamp word differ, while main fails earlier on
#450's gap.
6c9edffea66f16b0f8f7695ctests/test_nonuniform_image_arrays.pyNonUniformon a storage image, image buffer or texture buffer arraya66f16b0translator_stampfor the five GPUstests/run_all.py --jobs 6)Screenshots: none; nothing on screen (no game run).
Flags and settings
None. No switch: the device's features decide.
Testing
Tested:
tests/test_nonuniform_image_arrays.pypasses here, with all 51 modules that have no array of a kind the GPU'sfeatures turn on byte for byte #450's. It fails on main (45 failures) and on #450's head (37 failures: every array
of the three kinds left undecorated on a GPU with the feature, and no stamp word).
spirv-val --target-env vulkan1.3 --uniform-buffer-standard-layout, as CI'sSPIR-V fixture job validates them.
spirv-disof the all-four-kinds program on the GPU with only the storage image feature:NonUniformon the storage image and texture arrays' indices, access chains and loads, withStorageImageArrayNonUniformIndexingandSampledImageArrayNonUniformIndexing; none on the texture buffer and imagebuffer arrays, and neither of their capabilities.
vk_shader.cppcompiled as an object with the renderer's flags (nx2/build.py), Apple clang 21, arm64 and x86_64:exit 0 on both here and on main
6c9edffe(whosevk_shader.cppis #450's). Main's renderer does not build on macOS asit is (
environ), so these compiles used a compile-only prelude declaring it, on both sides.make_profileortranslator_stamppass:test_amd_subgroup_profiles(updated),test_amd_fragment_warp,test_native_float_atomic_add,test_vendor_fixes,test_handle_table_arrays,test_program_stamp_knobs,test_prepared_stamps,test_program_cache_format,test_global_table_loop,test_shader_stamp_inputsandtest_shader_stamp_cache.6c9edffe.test_image_arrays, absent on main,passes here;
test_nonuniform_image_arrays, absent on main, passes here.requests, some are checks main added or changed since
5e072fc8):test_appimage_package_retry,test_appimage_player_build,test_collect_wide,test_control,test_cpm_discovery,test_crash_dialog,test_crash_record,test_env_on,test_game_layout,test_lift_cleanup_error,test_linux_preflight,test_longshot_ctl,test_low_memory_notice,test_make_game_cache,test_mem_gate,test_mem_gate_children,test_mirror_trim,test_oot_state_tap,test_pcache_header_read,test_pipeline_cache_file,test_pipeline_cache_save,test_pipeline_play_cache,test_pipeline_tour,test_prof_symbols,test_program_cache_cleanup,test_program_cache_one_variant,test_readback_late,test_report_run,test_split_giants,test_vk_pgo,test_vram_budget_row,test_walk_key.fault-arm64-2026-11-14-180704.ips, recorder_sync_test.exe-2026-11-14-180744.ips).
vulkaninfoonthe Apple M4 Max.
aa95d35f, at6547c9bf(f8f7695cbefore 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 S1's merge11a5b4e7, which carriesthis head: 568/22/17 (passed/failed/skipped) against main aa95d35f's 543/33/17 on this Mac, no new failure
(
reports/2026-11-15-rebase-sweep-3.md).Not tested:
the feature, the program now carries
NonUniformand the kind's capability; nothing else changes.the decorations; where it doesn't, nothing changes.
run_all.pyskips the new test on Windows, as it skips the other spirv-val checks; onLinux it runs like them. The change is in what the translator is told, which the test checks on any OS with spirv-val.
Must be tested:
6547c9bf, on #450) and mainaa95d35fthesame way; note whether
vulkaninforeportsshaderStorageImageArrayNonUniformIndexing,shaderUniformTexelBufferArrayNonUniformIndexingandshaderStorageTexelBufferArrayNonUniformIndexing; on Linux runpython -I -B tests/run_all.py image_arrays(it needs spirv-val); start each build to the title and play a minute,expecting both tests to pass, this PR's first start to translate every program again (the translator changed), its
vk: prepared N shader programs (... M failed ...)line to count no more failed programs than main's, and no newerror in
longshot_log.txt(by hand: test request T-AUG6)Risks
What could break: low. Only programs with a storage image, image buffer or texture buffer array change, and only on
a GPU with that kind's feature; every other module is byte for byte the same.
pick the same one it runs once [INFERENCE: not measured]. Texture arrays have had the same decorations all along.
is possible on such a GPU without the feature.
with #450 on main
6c9edffe(2026-11-14): #450's two commits first, then this one onto them, all without a conflict.Main has not changed
vk_shader.cpp, the back end or this PR's tests since5e072fc8. Landing it in the same releaseas #450 (which changes the stamp too) makes the cache rebuild below one rebuild, not two. No conflict with the
author's other open PRs as rebased on
6c9edffe(git merge-treeof the heads, 2026-11-14).Turning it off: revert the commit (no switch: the device's features decide).
Compatibility: the caches start over once.
vk_shader.cppis one of the files of the translator stamp(
LONGSHOT_SHADER_STAMP, listed innx2/translator_files.py), so after updating, every player's program cache,Vulkan pipeline cache and prepare record are dropped at the first start and every program is translated again, as for
any change to the translator. On a GPU without one of the three features the new word gives the cache a stamp of its
own. The program cache's format and
Shader::Infoare unchanged; saves, the options file and the vk ABI are unchanged.Checklist
Everyone
type(scope): description) of the status line's type.main: onaa95d35f(#761, 2026-11-15), head6547c9bf, on #45038ba67dd, without aconflict; this PR's own commits, old → new:
f8f7695c→6547c9bf; 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 macOS: 533 passed, 17skipped, and 32 failures that also fail on main
6c9edffeon this Mac, named under Testing. After the rebase onto mainaa95d35f(head6547c9bf): wave S1's merge11a5b4e7, 568/22/17 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.A descriptor of count above 1 (a handle read at a dynamic constant buffer offset) is an array in Shader::Info, and the renderer writes count descriptors to its binding. The SPIR-V back end declared image buffer and storage image arrays as one image and indexed it with an OpAccessChain, which is invalid SPIR-V, and refused texture buffer arrays outright ("Array of texture buffers"). DefineTextureBuffers, DefineImageBuffers and DefineImages now declare DescType's array, as DefineTextures does, and a texture buffer's element is reached through its own pointer type. A count of 1 declares what it did before, so those modules are byte for byte the same. nx2/test_image_arrays.py (tests/image_arrays_ir.cpp): synthetic compute programs with 2-element image buffer and texture buffer arrays and a 3-element storage image array, plain, indexed nonuniformly and with bounded fetches, and the same programs with single descriptors, through the real texture pass and back end. Every module must validate and declare the counts Shader::Info has; --base-tree compares the single-descriptor modules with another tree's. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]>9dbdb36482f8f7695c99WIP: fix(vk): storage image and texel buffer arrays are indexed nonuniformly where the GPU canto fix(vk): storage image and texel buffer arrays are indexed nonuniformly where the GPU canf8f7695c997c0ad5988d7c0ad5988d6547c9bfb8View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.