fix(vk): image, image-buffer and texture-buffer arrays are arrays in the SPIR-V #450
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 @ 8015c9a
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!450
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:fix/descriptor-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 texture buffer, an image buffer or a storage image by an index (a handle read at a computed offset
of a constant buffer) is never drawn right on any GPU. With a texture buffer it is not translated at all (
Array of texture buffers is not implemented). With an image buffer or a storage image the translation makes invalid SPIR-V, sowhat the driver does with it is undefined, and an atomic on one is not translated (
Image indexing is not implemented).How to reproduce
Seen on: macOS 26.7.1, Apple M4 Max, in the game-free test (
nx2/test_image_arrays.py --treemain). The SPIR-V backend is the same code on every OS, so every OS translates these programs the same way. Not seen in the game: whether it
has such an array is not known (its programs were not read).
Settings and switches: defaults (the renderer's profile indexes these three kinds of array as dynamically uniform).
Steps:
python -I -B nx2/test_image_arrays.py --headers <SPIRV-Headers/include> --spirv-val spirv-val --cxx clang++ --tree <a tree of main>: 28 synthetic compute programs with such arrays go through the real texture pass and back end.Expected: every module translates, passes
spirv-val --target-env vulkan1.3and declares each binding with the countShader::Infogives it.Actual: 18 failures over 14 of the 28 cases: the 10 with a texture buffer array or an atomic on an array are refused
(
Array of texture buffers is not implemented,Image indexing is not implemented), and the 4 image buffer and storageimage array modules fail spirv-val (
OpAccessChain reached non-composite type), each array declared as one image.What changed
The SPIR-V back end now declares a texture buffer, image buffer or storage image descriptor with a count above 1 as an
array of that many images, as it already did for textures, and reaches an element through a pointer to one image. An
atomic on such an array takes its element instead of refusing. A count of 1 translates exactly as before.
DefineTextureBuffers,DefineImageBuffersandDefineImagesagainstDefineTextures, thenTextureImage's buffer branch andImage()inemit_spirv_image_atomic.cpp.gpu/vk/eden/shader_recompiler/backend/spirv/spirv_emit_context.cpp.Shader::Info'scount), and the renderer alreadydeclares and writes
countdescriptors for it (ensure_layoutandbuild_descriptorsingpu/vk/vk_draw.cpp). Onlythe back end did not.
DefineTextureBuffers,DefineImageBuffersandDefineImagesnow declare a descriptor of count above 1 as anOpTypeArrayof count images, throughDescType, asDefineTexturesalready did for textures.DefineTextureBuffersno longer throwsArray of texture buffers.TextureImage(emit_spirv_image.cpp) reaches a texture buffer array's element through a pointer to one image(
TextureBufferDefinition::pointer_type, new inspirv_emit_context.h), not through the image type itself.Image(),for image buffer and storage image loads and stores, was already right once the variable is an array.
emit_spirv_image_atomic.cpp) take an array's element the same way, and decorate it and thetexel pointer
NonUniformwhere the profile indexes that kind of array so. They used to throwImage indexingfor anyindex but 0, and the index of an array is its element. A single image takes no index, as before.
nx2/test_image_arrays.py(new) with its fixturetests/image_arrays_ir.cpp(new).and written, the same two added to atomically, a 2-element texture buffer array fetched, and one program with all
four kinds (a 2-element texture array too).
profile does it for textures only); the texture buffer programs also with the out-of-bounds fetch guard every GPU but
NVIDIA's gets (
fetch_bounds,vk_vendor.h). 28 modules.CollectShaderInfoPassand the back end.spirv-val --target-env vulkan1.3, and declare each binding with the countShader::Infogives it. With nonuniform indexing, each array kind must have its capability and itsNonUniformdecorations, an atomic's texel pointer included.
--treeruns the same programs through another checkout's back end;--base-treealso requires the 14single-descriptor modules to be byte for byte that checkout's.
count.tests/that read changed source. The checks that cut code out ofthese files (
test_vendor_translation,test_native_float_atomic_add,test_cas_loop_ceiling,test_global_atomic_add_f32,test_global_table_loop,test_mesh_point_topology,test_mesh_unreachable_outputs,nx2/test_mesh_first_types) needed no change and pass. Nor is the Windows and Linux build box there: this was builton macOS only (Testing).
Measurements
Verdict: fixed, game-free: the new test fails on main (18 failures over 14 of its 28 cases) and passes here, with the
14 single-descriptor modules byte for byte main's; the suite at the stack top #502 adds no failure. A translation fix:
nothing to time.
Commits: main
6c9edffe5d(2026-11-14); this PR fix/descriptor-image-arrays @a66f16b0fb.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:
nx2/test_image_arrays.pywith this PR's back end and with main's (--tree, agit archiveof main); every modulethe repository's real-IR fixtures emit, built against both back ends and compared byte for byte; the full game-free suite
on both
How to reproduce:
python -I -B nx2/test_image_arrays.py --headers <SPIRV-Headers/include> --spirv-val spirv-val --cxx clang++ --base-tree <a tree of main>(add--tree <a tree of main>for main's side)6c9edffea66f16b0nx2/test_image_arrays.py: modules that translateOpAccessChain reached non-composite type)depth_sample_types,unsigned_image_types,texture_phi_dominance,global_load_16and its compare-exchange loop,native_suatom,f2i_immediate)tests/run_all.py --jobs 6)f8f7695c, which carries it, 533 passed, 32 failed, 17 skippedScreenshots: none; nothing on screen (no game run).
Flags and settings
None.
Testing
Tested:
nx2/test_image_arrays.pyfails on main (18 failures over 14 of the 28 cases) and passes here, with all 14 single-descriptormodules byte for byte main's.
depth_sample_types,unsigned_image_types,texture_phi_dominance,global_load_16and its compare-exchange loop,native_suatomandf2i_immediate), builtagainst main's back end and against this PR's with the same fixture sources: all 100 byte for byte the same, and all
128 of this PR's modules (with the 28 above) pass
spirv-val --target-env vulkan1.3 --uniform-buffer-standard-layout,as CI's SPIR-V fixture job validates them. This ran before the rebase, on
5e072fc8; main has not changedgpu/vk/eden,siritorcompatsince, and this PR's back-end files are the same after the rebase.f8f7695c: 2 statuses changed: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).
spirv_emit_context.h(the three changed.cppfiles among them)with the renderer's flags (
nx2/build.py), Apple clang 21, arm64 and x86_64: all 42 exit 0 on main and all 42 here,without the compile-only prelude main's
vk_shader.cppandpipeline_cache_file.hneed on macOS.21 objects were byte for byte main's on both architectures. The other four are the three changed files and
emit_spirv_context_get_set.cpp, which only includes the changed header.aa95d35f, at38ba67dd(a66f16b0before 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:
translates to valid SPIR-V where it was refused or invalid before; nothing else 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 the translation alone, which the test checks on any OS with spirv-val.
std::tupleand the definitions around it.Must be tested:
38ba67dd) and mainaa95d35fthe same way;on Linux run
python -I -B tests/run_all.py image_arrays(it needs spirv-val); start each build to the title andplay a minute, expecting the test 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,no
not translatedline namingArray of texture buffersorImage indexingwhere main's log may have them, and nonew error in
longshot_log.txt(by hand: test request T-XKYH)Risks
What could break: low. Only programs with a texture buffer, image buffer or storage image array change, and on main
those are refused or invalid SPIR-V. Every other module is byte for byte the same.
make_profileingpu/vk/vk_shader.cpp) indexes only sampled image arrays nonuniformly. Thesethree kinds are therefore indexed as dynamically uniform, as the profile already decides for them; the test covers
both profiles. The device is created with every feature it reports (
vk_main.cpp), dynamic indexing of these arraysincluded.
fix/nonuniform-image-arrays) is stacked on this PR and lands after it; landing both in onerelease makes the cache rebuild below happen once. After the rebase on
6c9edffeit conflicts with none of theauthor's other open PRs as rebased there (
git merge-treeof the heads, 2026-11-14).Turning it off: revert the commits.
Compatibility: the back end is in
gpu/vk/eden, one of the translator stamp's folders (nx2/translator_files.pysince main's #370), so the first start after updating translates every program again, as any change to
gpu/vk/edendoes. Programs without these arrays translate to the same SPIR-V as before. 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), head38ba67dd, without a conflict; this PR's owncommits, old → new:
092b9b39→26d59e76,a66f16b0→38ba67dd; 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). At the stack top #502, onmacOS: 533 passed, 17 skipped, and 32 failures that also fail on main
6c9edffeon this Mac, named under Testing. Afterthe rebase onto main
aa95d35f(head38ba67dd): wave S1's merge11a5b4e7, 568/22/17 against main aa95d35f's543/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.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]>8c9cee0df8a66f16b0fbWIP: fix(vk): image, image-buffer and texture-buffer arrays are arrays in the SPIR-Vto fix(vk): image, image-buffer and texture-buffer arrays are arrays in the SPIR-Va66f16b0fb8063f595018063f5950138ba67ddc9Read the back-end diff against
DefineTextures/DescType,TextureImageandImage()in emit_spirv_image.cpp, and the atomics. Count above 1 is now an OpTypeArray for texture buffers, image buffers and storage images, an element is reached through a pointer to one image (pointer_type, new for texture buffers), and a count of 1 is declared as before. The atomic path indexes the element and decorates index, chain and texel pointer when the profile asks, and the capability flags it sets are the ones emit_spirv.cpp already turns into capabilities. The change is ingpu/vk/eden, so the translator stamp changes with it, as the description says. I did not run the tests.Fits with #502: its flags (
support_storage_image_array_nonuniform_indexing, the two texel buffer ones) are the onesImage()here andMarkNonUniformread, and theuses_nonuniform_*members already exist. No conflict seen.Nit: the description names head
a66f16b0in places; the rebase note says38ba67dd. Fine, but a reader following the Measurements commits should know.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.