Skip to content

fix(shader): apply the V# DST_SEL to formatted buffer stores - #5172

Closed
nikowolf123 wants to merge 2 commits into
boykopovar:mainfrom
nikowolf123:fix/shader-buffer-format-dst-sel
Closed

nikowolf123 wants to merge 2 commits into
boykopovar:mainfrom
nikowolf123:fix/shader-buffer-format-dst-sel

Conversation

@nikowolf123

@nikowolf123 nikowolf123 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What

buffer_store_format_* and its D16 forms ignored the V#'s DST_SEL. Memory component i was always written from data VGPR i. Loads through the same V# do apply DST_SEL, so a store through a swizzled V# did not read back what it stored. With 8_8_8_8_UNORM and DST_SEL Z,Y,X,W (word3 0x31038f2e, a B8G8R8A8 buffer), storing (1.0, 0.75, 0.2, 0.25) wrote 0x4033bfff instead of 0x40ffbf33: red and blue were swapped. This affects any shader that writes a formatted buffer through a V# whose DST_SEL reorders the channels (B8G8R8A8, for example); no title is known to hit it.

A formatted store now writes each format component from the data slot whose selector names it, but only for the DST_SELs that are measured or covered by conformance tests for that format. Hardware measurements show that this rule does not hold for every permutation, so every other DST_SEL is refused instead of guessed.

Accepted:

  • The identity on the format's components, each component from its own slot. This is main's behaviour, unchanged; the selectors of components the format does not have are ignored, as on main.
  • Z,Y,X,W on 8_8_8_8_UNORM and 16_16_16_16_UINT, the two formats it is measured on.
  • 0,0,0,X on 8_UNORM and 8_UINT.

Refused: every other DST_SEL, including the other 22 permutations on 16_16_16_16_UINT, Z,Y,X,W on any other format and 0,0,0,X on any other one-component format. On the host path the V# throws in DescriptorBindingBuilder::Prepare; a GPU-selected V# records an invalid-descriptor BDA fault and skips the store.

Measured by simonbbby on RDNA2 gfx1036 (Ryzen 9 9950X iGPU) with the project's HSA hardware oracle, wave32 and wave64, using this PR's probes:

  • First comment: Z,Y,X,W on 8_8_8_8_UNORM (_xyzw and _d16_xyzw) stores and reads back as modelled. In an isolated 16_16_16_16_UINT store of (0x1234, 0x5678, 0x9abc, 0xdef0), X,Y,Z,W writes 56781234 def09abc and Z,Y,X,W writes 56789abc def01234, as modelled, and both read back the input. The rotation Y,Z,X,W writes def01234 9abc5678, where this PR first predicted 12349abc def05678. The 8_UINT 0,0,0,X words of the full UINT probe (_xyzw, then _x to the next byte) equal the predicted aaaa00ff and aaaa0044; main would give aaaaffff and aaaa1111.
  • Second comment: of all 24 permutations of X,Y,Z,W on 16_16_16_16_UINT, 12 match the model and 12 do not. Being its own inverse is not enough: Y,X,Z,W writes 56781234 9abcdef0 and X,W,Z,Y writes 56789abc def01234, where the model gives 12345678 def09abc and def01234 56789abc.

Sources for 0,0,0,X on 8_UNORM:

The b8g8r8a8_{unorm,snorm,uint,sint} and a2r10g10b10_{unorm,uint}_pack32 cases of the same group store equal X and Z components at every texel for the same reason, so they pass whether or not the store swaps X and Z and do not test Z,Y,X,W. Z,Y,X,W on 8_8_8_8 SNORM, UINT and SINT, on the 2_10_10_10 formats and on every other four-component format, and 0,0,0,X on other one-component formats, are neither measured nor tested, and the sweep shows that the store does not apply DST_SEL as a general permutation, so they stay refused.

Changes:

  • SpirvRuntimeBuffer.cpp, formatted(): a MUBUF formatted store writes each format component from the data slot whose DST_SEL names it, or 0 if no slot the instruction provides names it. TBUFFER stores keep the identity selectors.
  • SettledFormattedStoreSwizzle and IsSettledFormattedStoreDstSel (SpirvBufferFormat.cpp) hold the accepted set, used by DescriptorBindingBuilder::Prepare for a present V# and by the GPU-selected store for each format. ResourceTracker records which buffers have formatted stores (BufferResource::formattedWritten, disk cache format 30, since main took 29 in fix(shader): read the ancillary render target index from Layer when the draw exports it #5097).
  • docs/dev/debt/shader.md records the accepted set, both measurements, why the B8G8R8A8 and A2R10G10B10 cases do not count, and what is not measured: narrower stores (_x, _xy, _xyz) through Z,Y,X,W and through 0,0,0,X on 8_UNORM, D16 stores through Z,Y,X,W on 16_16_16_16_UINT, buffer stores with a selector naming a channel the format lacks (feat(agc): implement sint storage images through uint views #944 measured gfx1035 writing channel 1 of a two-channel image from the W slot), and gfx1013.
  • New agc_driver_buffer_store_format_dst_sel (tests/execution/BufferStoreFormatDstSel.cpp; its target gets the recompiler's include directories, like other execution tests that call recompiler functions), wave32 and wave64, 64 lanes, every value checked:
    • buffer_store_format_xyzw, _d16_xyzw and _x through 8_8_8_8_UNORM Z,Y,X,W; _xyzw through 16_16_16_16_UINT Z,Y,X,W and X,Y,Z,W, with lane 0 pinned to the gfx1036 raw dwords; _xyzw and _x through 8_UINT 0,0,0,X; _xyzw through 32_UINT X,0,0,1.
    • buffer_load_format_xyzw reads the first three back and must return the stored data.
    • IsSettledFormattedStoreDstSel must accept exactly X,Y,Z,W and Z,Y,X,W of the 24 permutations on 16_16_16_16_UINT, and for each of the 77 formats: the identity always, Z,Y,X,W and 0,0,0,X only on the listed formats, and X,0,0,1 and X,0,0 with the reserved selector 2 only on one-component formats. X,X,Z,W and Z,Y,X,1 on 8_8_8_8_UNORM and 1,0,0,X on 32_UINT must be refused.
    • Seven V#s must throw when the binding plan is prepared: the measured Y,Z,X,W, Y,X,Z,W and X,W,Z,Y on 16_16_16_16_UINT, Z,Y,X,W on 16_16_16_16_FLOAT, 8_8_8_8_UINT and FORMAT 50, and 0,0,0,X on 32_UINT. With NUM_RECORDS 0 a refused V# compiles. 0,0,0,X on 8_UNORM and X,0,0 with the reserved selector 2 on 32_UINT must compile.
    • A store through a GPU-selected V# compiles to a module that passes SPIR-V validation.

Tested

Rebased onto main 0214069 (no conflicts; #5097 had taken disk cache format 29, so this PR uses 31, as the open #4883 takes 30; and #4250 changed sub-dword buffer stores in SpirvRuntimeBuffer.cpp, so all results below are on the rebased branch).

Ubuntu 26.04 under WSL2, GCC 15.2.0, configured like CI (Release, Ninja, -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON, ccache), lavapipe from Mesa 26.0.8 with VK_ICD_FILENAMES=<lvp_icd.json> ANYPS5_REQUIRE_VULKAN=1 GALLIUM_OVERRIDE_CPU_CAPS=avx, ctest -j4. The machine was heavily loaded by other builds (load average around 110), so the times are long:

  • ctest -R '^agc_driver_buffer_store_format_dst_sel$' passes 3 of 3 runs in 32 to 39 s.
  • On main with the new test added (without the IsSettledFormattedStoreDstSel checks, which need this PR's function), it fails 3 of 3: wave32 lane 0 buffer_store_format_xyzw 8_8_8_8_UNORM Z,Y,X,W is 0x4033bfff, expected 0x40ffbf33.
  • On the first commit alone, with the same test, it fails 3 of 3: a store through DST_SEL Y,Z,X,W on 16_16_16_16_UINT compiled.
  • ctest -R 'agc_shader|agc_driver|shader|recompiler': on main, 354 passed and 36 skipped of 390; with this PR, 355 passed and 36 skipped of 391.
  • Full ctest: on main, 1033 passed, 43 skipped and 1 timed out of 1077; with this PR, 1034 passed, 43 skipped and 1 timed out of 1078. Compared test by test, the only difference is the new test. The timeout is guest_pthread_start (20 s limit) in both runs; alone it passes 3 of 3 on this branch in 17 to 19 s. The skips are the existing lavapipe skips.
  • A throwaway program linked against the recompiler library compared IsSettledFormattedStoreDstSel with a transcription of the GPU-selected check for every DST_SEL value (4096) on each of the 77 formats, with three patterns in the other word3 bits: they agree on all 946176 words and accept exactly the list above, and every accepted word stores as main does or as the listed swizzle, for _x to _xyzw.
  • main's tools/check_conventions.py --base origin/main passes, with --title and --body.

Native Windows 11, WinLibs GCC 15.2.0, NVIDIA GeForce RTX 5080 (driver 616.92), this branch: agc_driver_buffer_store_format_dst_sel and agc_shader_disk_cache pass 3 of 3 runs (the new test in 1.6 to 1.9 s), and ctest -R 'agc_shader|agc_driver|shader|recompiler' passes 389 of 390 with 1 skipped (agc_driver_stencil_ref_export).

A store through a GPU-selected formatted V# did not finish within 150 s on lavapipe, on main as well and also with 32_32_32_32_UINT, so that path is compiled and validated by SPIRV-Tools here but not run. Its selector check is emitted in each of the 77 format cases of the GPU-selected store helper.

AMD hardware: the RDNA2 gfx1036 results above are simonbbby's measurements, linked in What; I have no AMD GPU. The PS5's gfx1013 is not measured.

Verified on hardware by @simonbbby on this head (e5c8d15) (comment):

  • agc_driver_buffer_store_format_dst_sel, agc_shader_memory and agc_shader_disk_cache pass 3 of 3 runs each on gfx1036 (RADV, Mesa 25.2.8) and on an RTX 3090 (NVIDIA 595.99.02). With only DescriptorBindingBuilder.cpp restored from the parent, the test fails with a store through DST_SEL Y,Z,X,W on 16_16_16_16_UINT compiled.
  • On gfx1036, buffer_store_format_xyzw through 8_UNORM with 0,0,0,X stores the W component (raw aaaaaa40 for W = 0.25 and aaaaaaff for W = 1, Y differing), which settles the Y-or-W case the CTS left open.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • GPUs tested: lavapipe (Mesa 26.0.8, llvmpipe) on WSL2 Ubuntu 26.04; NVIDIA GeForce RTX 5080, driver 616.92, Windows 11; by @simonbbby: AMD gfx1036 (RADV, Mesa 25.2.8) and NVIDIA RTX 3090 (595.99.02). AMD RDNA2 gfx1036 results are simonbbby's, not mine
  • AI-assisted: yes. Written with Claude Code; I reviewed every line and ran the tests above.

🤖 Generated with Claude Code

@MarcZX10

Copy link
Copy Markdown
Contributor

Linux validation: configured a Release build with GCC 12.2 and built agc_driver_buffer_store_format_dst_sel_tests. CTest agc_driver_buffer_store_format_dst_sel passed (1/1, 21.82 s). This validates the regression test on Linux; it does not exercise an AMD GPU.

@simonbbby

Copy link
Copy Markdown
Contributor

Measured the supplied probes for head e2340c799883584a26e120d6026b8c846d12b3f0 on gfx1036 RDNA2, Ryzen 9 9950X iGPU / PCI 1002:13c0, using the project's HSA hardware oracle. Its --target reports gfx1036; this is not gfx1013/PS5 measurement.

The supplied UNORM probe matches both predicted rows exactly, in wave32 and wave64. The supplied UINT probe does not match the predicted three-cycle store/readback. Both wave sizes produce:

def01234 9abc5678 0000def0 00005678 00001234 00009abc aaaa00ff 00000000 00000000 00000000 000000ff
00440011 00330022 00000044 00000022 00000011 00000033 aaaa0044 00000000 00000000 00000000 00000044

Your predicted first two words are 12349abc def05678 and 00110033 00440022. The measured formatted loads also differ from the original input, so this is more than a raw packing discrepancy.

I isolated the 16_16_16_16_UINT store by removing the second descriptor and all its stores/loads, retaining only the first descriptor, buffer_store_format_xyzw, the store wait, raw dwordx2 load, formatted xyzw load, and the final read wait. Repeated the two input rows 32 times (64 lanes), three runs per descriptor and wave size:

word3 / DST_SEL Raw first two words for input 1234 5678 9abc def0 Formatted readback
31045fac / X,Y,Z,W 56781234 def09abc original input
31045f2e / Z,Y,X,W 56789abc def01234 original input
31045f35 / Y,Z,X,W def01234 9abc5678 def0 5678 1234 9abc

All repetitions agree in wave32 and wave64, including the second input row. Thus the isolated three-cycle result is the same as the full supplied probe; the identity/swap controls do not show the mismatch. Used exactly your --ieee 0 --dx10-clamp 1 --denorm32 0 --denorm16 3 --round32 0 --round16 0 --fp16-overflow 0 settings.

This supports the measured UNORM swap examples, but does not support the predicted general inverse mapping for the supplied UINT cycle. I have not established a general replacement rule, run the PR's host implementation, or measured the other permutations/formats or GPU-selected descriptor path. Exact assembly, inputs and raw logs retained.

@github-actions github-actions Bot added shader core/shader docs agc libSceAgc* labels Oct 13, 2026
@nikowolf123

Copy link
Copy Markdown
Contributor Author

Thanks, this is decisive. The rotation result means the store mapping for selector permutations that are not their own inverse isn't settled, and this PR's test pins the wrong value for that 16_16_16_16_UINT Y,Z,X,W row.

I'll narrow the PR to what your measurement and the cited CTS cases settle (identity, the Z,Y,X,W swap, and the CTS 0/1 selector patterns). Other permutations will be refused, and your rotation result will go into the technical debt entry. I'll post here when it's pushed.

@simonbbby

Copy link
Copy Markdown
Contributor

One additional boundary for the narrowing: being its own inverse is not sufficient on gfx1036 for this UINT format. Keeping only identity and the measured Z,Y,X,W swap avoids these two additional counterexamples.

I swept all 24 permutations of X/Y/Z/W in the isolated 16_16_16_16_UINT probe, changing only word3's low 12 bits. Same two inputs repeated to 64 lanes, wave32 and wave64, and the same explicit FP controls as above. Twelve permutations match the PR's inverse-store/original-readback prediction for those inputs; twelve do not. The two wave sizes agree. This is limited to this format and these inputs, not a general selector rule.

For input 1234 5678 9abc def0, these self-inverse cases produce:

DST_SEL / word3 Raw memory words Formatted readback
Y,X,Z,W / 31045fa5 56781234 9abcdef0 5678 1234 def0 9abc
X,W,Z,Y / 31045bbc 56789abc def01234 9abc def0 1234 5678

The inverse-store prediction would instead give raw 12345678 def09abc and def01234 56789abc, respectively, and read back the original input. Repeated both counterexamples three times per wave size with 64 lanes per run; every repetition agrees, including the second input row. Identity and Z,Y,X,W continue to match the prediction in the sweep. Full permutation outputs and repeated-counterexample logs retained; no PR implementation change or gfx1013 measurement claimed.

nikowolf123 and others added 2 commits October 13, 2026 19:30
buffer_store_format_* and its D16 forms wrote memory component i from
data VGPR i and ignored the V#'s DST_SEL, while loads through the same
V# apply it. A store through a swizzled V# therefore did not read back
what it stored: with 8_8_8_8_UNORM and DST_SEL Z,Y,X,W, storing
(1.0, 0.75, 0.2, 0.25) wrote 0x4033bfff instead of 0x40ffbf33.

The RDNA 2 ISA guide (section 8.1.4, table 31) takes DST_SEL from the
resource for BUFFER_STORE_FORMAT_*. RADV builds texel-buffer V#s with
the format's swizzle (Z,Y,X,W for B8G8R8A8, 0,0,0,X for A8) and ACO
stores through them unchanged, so each format component takes the data
slot whose selector names it, and slots that select 0 or 1 write
nothing. StoreTexel applies the same rule to image stores. TBUFFER
stores keep the identity selectors.

These sources settle only a DST_SEL that names each format component
once. A V# that names one twice or not at all, or holds a reserved
selector, now throws when the binding plan is prepared, and a
GPU-selected one records an invalid-descriptor fault and skips the
store. ShaderInfo records which buffers have formatted stores (disk
cache format 31, as the
open boykopovar#4883 takes 30). Technical debt records that none of this is measured
on hardware.

agc_driver_buffer_store_format_dst_sel stores through Z,Y,X,W
8_8_8_8_UNORM (also D16 and store_format_x), Y,Z,X,W 16_16_16_16_UINT,
0,0,0,X 8_UINT and X,0,0,1 32_UINT V#s in wave32 and wave64, reads the
first three back with buffer_load_format_xyzw, checks that three
unsettled selector sets are refused, and compiles a store through a
GPU-selected V#. On main it fails with red and blue swapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simonbbby measured this PR's hw-oracle probes on RDNA2 gfx1036. The
identity and Z,Y,X,W on 16_16_16_16_UINT, Z,Y,X,W on 8_8_8_8_UNORM and
0,0,0,X on 8_UINT store as modelled, but a sweep of all 24 permutations
on 16_16_16_16_UINT matches the model for only 12. Being its own
inverse is not enough: Y,X,Z,W and X,W,Z,Y store (0x1234, 0x5678,
0x9abc, 0xdef0) as 56781234 9abcdef0 and 56789abc def01234, and the
rotation Y,Z,X,W stores it as def01234 9abc5678.

A formatted MUBUF store now accepts a DST_SEL only if it is the
identity on the format's components, as on main, or one of the
swizzles that are measured or conformance-tested for that format:
Z,Y,X,W on 8_8_8_8_UNORM and 16_16_16_16_UINT, and 0,0,0,X on 8_UNORM
and 8_UINT. 0,0,0,X on 8_UNORM is the V# RADV builds for the VK-GL-CTS
without_format a8_unorm buffer store, which Mesa's CI passes on Navi
21. The b8g8r8a8 and a2r10g10b10 cases of that group store equal X and
Z components, so they do not test Z,Y,X,W, and the other 8_8_8_8 and
2_10_10_10 formats stay refused. SettledFormattedStoreSwizzle holds
this list for both the binding plan check and the GPU-selected path.
Every other DST_SEL throws in DescriptorBindingBuilder::Prepare, and a
GPU-selected V# with one records an invalid-descriptor fault and skips
the store. The selectors of components a format lacks are ignored, as
on main, so a reserved or repeated one there no longer throws.

The test also stores through 16_16_16_16_UINT X,Y,Z,W and pins lane 0
of it and of Z,Y,X,W to the measured raw dwords. It checks the
accepted set for every format and for all 24 permutations on
16_16_16_16_UINT, and expects V#s with Y,Z,X,W, Y,X,Z,W and X,W,Z,Y on
16_16_16_16_UINT, Z,Y,X,W on 16_16_16_16_FLOAT, 8_8_8_8_UINT and
FORMAT 50, and 0,0,0,X on 32_UINT to throw; its target gets the
recompiler's include directories for that. The technical debt entry
records the measurements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nikowolf123
nikowolf123 force-pushed the fix/shader-buffer-format-dst-sel branch from e2340c7 to e5c8d15 Compare October 13, 2026 17:33
@nikowolf123

Copy link
Copy Markdown
Contributor Author

Thanks for the full sweep, and for isolating the 16_16_16_16_UINT store first. The two self-inverse counterexamples show that my last comment drew the line in the wrong place: being its own inverse does not make a permutation safe.

e5c8d15 limits the store mapping to what your runs show, plus one VK-GL-CTS case. A formatted MUBUF store now accepts only:

  • the identity on the format's components, which is main's behaviour (selectors of components the format does not have are ignored, as on main);
  • Z,Y,X,W on 8_8_8_8_UNORM and 16_16_16_16_UINT, the two formats you measured it on;
  • 0,0,0,X on 8_UINT (the 8_UINT words of your full UINT probe, aaaa00ff and aaaa0044, matched) and on 8_UNORM (CTS dEQP-VK.image.store.without_format.buffer.a8_unorm, which Mesa's CI passes on Navi 21).

Everything else is refused. On the host path the V# throws in DescriptorBindingBuilder::Prepare; a GPU-selected V# records an invalid-descriptor fault and skips the store. That covers the other 22 permutations on 16_16_16_16_UINT, including your Y,Z,X,W, Y,X,Z,W and X,W,Z,Y. It also covers Z,Y,X,W on any other four-component format and 0,0,0,X on any other one-component format.

The PR body also cited the CTS b8g8r8a8_unorm buffer store case for the store mapping, and that was wrong. Its buffer is 64 texels by 1, so at texel x the case stores x, 63 - x, x and 63 - x: X and Z are equal, and the case passes with or without the swap. The same holds for the other b8g8r8a8_* and a2r10g10b10_*_pack32 cases, so Z,Y,X,W stays refused on every four-component format you did not measure. For the same reason the a8_unorm case only shows that the alpha byte comes from the Y or the W slot; your 8_UINT words are what show W.

The test checks that the accepted set holds exactly X,Y,Z,W and Z,Y,X,W of the 24 permutations on 16_16_16_16_UINT, and that V#s with your Y,Z,X,W, Y,X,Z,W and X,W,Z,Y throw when the binding plan is prepared. It also stores through X,Y,Z,W and Z,Y,X,W and pins lane 0 to your raw dwords (56781234 def09abc and 56789abc def01234). docs/dev/debt/shader.md links both of your comments and records the 12-of-24 result, the two counterexamples and the rotation.

I also rebased onto main, which took disk cache format 29 in #5097. This PR now uses 31, because the open #4883 takes 30.

AI-assisted: written with Claude Code; I checked the change and ran the tests described in the PR body.

@github-actions github-actions Bot added the build label Oct 13, 2026
@simonbbby

Copy link
Copy Markdown
Contributor

Verified the narrowed head e5c8d15db24290e1123a49f81fe1313692d6e08f on Linux (Ubuntu 24.04 / Linux 7.0, GCC 13.3, Ninja Release, SPIRV-Tools ON):

  • Built agc_driver_buffer_store_format_dst_sel_tests, agc_shader_memory_tests and agc_shader_disk_cache_tests.
  • agc_driver_buffer_store_format_dst_sel, agc_shader_memory and agc_shader_disk_cache each pass three consecutive runs on both gfx1036/RADV Mesa 25.2.8 and RTX 3090/NVIDIA 595.99.02. No skips or timeouts. The formatted-store fixture executes wave32 and wave64, checks the pinned raw words, checks the accepted set, and checks compilation refusals.
  • Negative control: restored only DescriptorBindingBuilder.cpp from parent 0cf94c939e0be7e364653d6078e90f9a7a157a06, retaining the narrowed backend/helper and new fixture. After rebuilding, the fixture fails with a store through DST_SEL Y,Z,X,W on 16_16_16_16_UINT compiled. Restored the PR file byte-for-byte, rebuilt and repeated the fixture three times on RADV: all pass again. Checkout clean.

I also measured the remaining 8_UNORM Y/W ambiguity directly on gfx1036. With word3 0x31001800 (0,0,0,X), buffer_store_format_xyzw, and a dword prefilled with aaaaaaaa:

Input X,Y,Z,W Raw dword after store Formatted xyzw readback
1, 0.75, 0.2, 0.25 aaaaaa40 00000000 00000000 00000000 3e808081
0, 0.25, 0.75, 1 aaaaaaff 00000000 00000000 00000000 3f800000

Thus these distinct Y/W inputs select W on this hardware. Two rows repeated to 64 lanes, three runs in wave32 and three in wave64, all agreeing; same explicit FP controls as my earlier probes. Exact assembly/inputs/raw logs retained.

The CTest legs pin the respective ICD and set ANYPS5_GPU, ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1, serial execution and a 30 s per-test bound. Logs confirm each device. GPU-selected descriptors are compiled/validated by this fixture, not executed. No full-suite, title, performance or gfx1013/PS5 measurement claim.

@nikowolf123

Copy link
Copy Markdown
Contributor Author

Thank you, that closes the last open point. Your 8_UNORM rows (aaaaaa40 and aaaaaaff for W = 0.25 and 1) show the 0,0,0,X store takes W on gfx1036, which is what the narrowed set applies; the debt entry had only the 8_UINT words and the CTS case (Y or W) for it. I'll add your RADV and RTX 3090 runs and the 8_UNORM measurement to the description.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Closing: no title is known to reach this. With the queue at 700+ open PRs, please hold off on new PRs that don't name a title needing the change until it's down. If a title stops on this on current main, reopen with the title ID and the log line.

RINNECODER added a commit to RINNECODER/AnyPS5 that referenced this pull request Oct 14, 2026
* Metal semantics: run upstream image execution tests through the Metal driver

The existing semantics shim binds only guest buffers. A second shim
(tests/metal_semantics/driver_shim) runs the upstream execution tests that
bind images, samplers and color targets through the native Metal driver:
dispatches bind their descriptors with MetalShaderResources and draws go
through MetalDraw::DrawSynchronously, as in a title. The tests' host arrays
are the guest memory; the device borrows every range the shader's buffer
and image descriptors and the color targets name.

ImagePlaneAddressOnLine and ImageVolumeAddressOnPlane (already on main)
now run on Metal and pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

* fix(shader): capture image descriptors read from constant buffers at uniform offsets

Removes the tracker check that refused a T# whose dwords come from a
ReadConstBuffer, so a T# loaded with s_buffer_load from a table record that
user data selects is captured like any other runtime descriptor.

Metal: tests/metal_semantics/ImageTableRecord.cpp runs the upstream
program through the Metal driver (MetalShaderResources image binding) and
checks that the record user data selects is the texture sampled. It fails
on main with the removed "is not a valid runtime value" error.

Ported from upstream PR boykopovar#5188

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

* fix(shader): accept TYPE=0 images during materialization

A T# with TYPE=0 is null on AMD hardware even when its base and other
words are nonzero; the materializer now accepts it as a null image
instead of refusing it as invalid (reserved TYPE=1 is still refused).
Upstream's frontend test is ported to tests/ShaderMemory.cpp.

Metal: the Metal image binding already treats TYPE=0 as null
(IsNullTextureResource), so such an image binds no texture. The Metal
ImageTableRecord test now also samples a TYPE=0 record with a nonzero
base and reads zero; on main it fails with "runtime image descriptor is
invalid". Debt note added to docs/dev/TechnicalDebt.md.

Ported from upstream PR boykopovar#5473

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

* fix(shader): read 1D T#s through 3D image_sample and image_load accesses

A DIM 3D image_sample_lz, plain image_sample or mip-less image_load
whose T# is 1D gets a runtime image mode that binds the 1D texture: the
sample reads the texel u selects, and the load reads the x texel when z
is 0 and zero otherwise, as measured on RDNA1. Other 3D accesses of such
an image still throw.

ImageResource gains flatVolumeLineCompatible, so the shader disk cache
FormatVersion goes from 35 to 36. This is the only bump in this series:
the later image-mode fields reuse it.

Metal: the 1D mode binds through the existing Image1D shape (a 2D
texture of height 1) with no new view. Upstream's ImageVolumeAddressOnLine
execution test runs unchanged through the Metal driver
(anyps5_metal_driver_semantics_imagevolumeaddressonline). It fails
without the materializer hunk ("incompatible with the static runtime
image interface"). Debt note added to docs/dev/TechnicalDebt.md.

Ported from upstream PR boykopovar#5375

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

* fix(shader): sample single-level volumes through biased 2D accesses

A pixel-stage, direct, plain 32-bit image_sample_b with DIM 2D whose T#
is a one-slice, single-level 3D image gets the 3D runtime image mode:
the sample supplies a zero third coordinate and keeps the bias, as
measured on RDNA2. The binding requires a normalized point sampler and
the descriptor one slice and mip zero; other cases throw.

ImageResource gains singleMipPlaneBiasCompatible; it is encoded by the
shader disk cache under the FormatVersion 36 bump of this series
(upstream's own bump to 31 is not taken).

Metal: the 3D mode binds the volume as the Image3D shape it already has,
with no new view. Upstream's ImagePlaneBiasOnSingleLevelVolume execution
test (a pixel draw) runs unchanged through MetalDraw
(anyps5_metal_driver_semantics_imageplanebiasonsinglelevelvolume). It
fails without the tracker hunk ("incompatible with the static runtime
image interface"). Debt note added to docs/dev/TechnicalDebt.md.

Ported from upstream PR boykopovar#5461

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

* fix(shader): decode forced degamma on biased RGBA8_UINT samples

A direct RGBA8_UINT image sampled only by plain biased pixel accesses
(the one-slice volume case of boykopovar#5461) through samplers that all set
FORCE_DEGAMMA is materialized as RGBA8_SRGB, which returns the same
decoded floats as the hardware. Mixed sampler states, indirect images,
other accesses and formats still throw.

Metal: MetalSampler refused every FORCE_DEGAMMA S#, so this (and the
existing sRGB case of ImageSampleForceDegamma) could not bind. Metal now
does what the Vulkan driver does: MetalShaderResources::Bindings creates
the sampler with forceDegammaPaired and refuses a sampled image of a
non-sRGB format paired with a forcing sampler (the same message as
RequireDegammaFormat). The rewritten descriptor binds through the
existing sRGB pixel-format view. Upstream's extended
ImagePlaneBiasOnSingleLevelVolume and the existing
ImageSampleForceDegamma tests now pass on Metal; both failed with "forces
sRGB decoding which is not implemented". Debt note added to
docs/dev/TechnicalDebt.md.

Ported from upstream PR boykopovar#5472

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

* fix(shader): read cube T#s through DIM 2D image_sample and image_load as face 0

A DIM 2D image_sample_lz, plain image_sample, image_sample_l or mip-less
image_load whose T# is a cube gets the cube runtime image mode and reads
the T#'s BASE_ARRAY face with face coordinate 0, as measured on RDNA1.
Other DIM 2D accesses through a cube T# still throw.

ImageResource gains cubeFaceCompatible; encoded under the series'
FormatVersion 36 bump.

Metal: a cube T# is already a 2D-array texture of faces and the cube mode
binds the Image2DArray shape, so no new view is needed. Upstream's
ImagePlaneAddressOnCube execution test runs unchanged through the Metal
driver (anyps5_metal_driver_semantics_imageplaneaddressoncube) and fails
without the materializer hunk. Debt note added to
docs/dev/TechnicalDebt.md.

Ported from upstream PR boykopovar#5509

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

* fix(shader): compare through 2D image_sample_c on 1D T#s

The flat-line (2D access through a 1D T#) compatibility now ignores the
compare flag, so image_sample_c and image_sample_c_lz with DIM 2D on a
1D T# take the 1D mode like their non-compare forms.

Metal: the 1D mode binds through the existing Image1D shape (a 2D texture
of height 1), with no new view. Upstream's ImagePlaneCompareOnLine execution test runs unchanged
through the Metal driver
(anyps5_metal_driver_semantics_imageplanecompareonline) and fails without
the tracker hunk. The debt note in docs/dev/TechnicalDebt.md is updated
as upstream's.

Ported from upstream PR boykopovar#5456

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

* fix(shader): apply the V# DST_SEL to formatted buffer stores

buffer_store_format_* (D16 included) wrote memory component i from data
VGPR i whatever the V#'s DST_SEL. A formatted store now writes each
format component from the slot its selector names, only for the
DST_SELs measured on RDNA2 or covered by conformance tests: the identity,
Z,Y,X,W on 8_8_8_8_UNORM and 16_16_16_16_UINT, and 0,0,0,X on 8_UNORM
and 8_UINT. Every other DST_SEL on a formatted store is refused: a host V#
throws in DescriptorBindingBuilder::Prepare, and a GPU-selected V#
records an invalid-descriptor BDA fault and skips the store.

BufferResource gains formattedWritten; encoded under the series'
FormatVersion 36 bump.

Metal: the store is lowered in the shared SPIR-V (SpirvRuntimeBuffer), so
no Metal binding change. Upstream's BufferStoreFormatDstSel execution test
runs unchanged through the Metal driver
(anyps5_metal_driver_semantics_bufferstoreformatdstsel). Without the
SpirvRuntimeBuffer hunk it fails (Z,Y,X,W on 8_8_8_8_UNORM writes
0x4033bfff, expected 0x40ffbf33). Debt note added to
docs/dev/TechnicalDebt.md.

Ported from upstream PR boykopovar#5172

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build docs needs rebase Conflicts with main, rebase needed shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants