Skip to content

fix(shader): take lane-mask arms and a merged high word in a pointer-keyed record pointer - #8939

Open
EagleBearerYS wants to merge 18 commits into
boykopovar:mainfrom
EagleBearerYS:fix/shader-pointer-key-lane-masks
Open

EagleBearerYS wants to merge 18 commits into
boykopovar:mainfrom
EagleBearerYS:fix/shader-pointer-key-lane-masks

Conversation

@EagleBearerYS

@EagleBearerYS EagleBearerYS commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What

Depends on: #7395, which is stacked on #7223. Until both merge, this PR's diff shows their commits too.

#7395 lets a T# or S# be read through a pointer that a scalar-buffer record holds, with the pointer's low word merged by Phis with constants, lone Ballot words and copies of the record word. Two titles' chains still refuse on the pointer's words:

  • Little Nightmares Enhanced Edition (PPSA10737), run by klosixgaming-arch (comment). Windows 11, RTX 5090, version 01.004.000, on feat(shader): read samplers through nested and pointer-keyed descriptor tables #7395's head of 2026-10-19 plus fix(shader): link a table call whose descriptor load sits before its loop #7413; it stops 2 min 46 s in:
    [dispatch] failed: compute shader 0x2010fa0000, stage 0: ShaderRecompiler::Recompile: GetImageResource dword 0 is not a valid runtime value (value=LoadAddressU32[…] … GetAddressResource@pc=0x3384(arg0=Phi(arg0=Phi(arg0=Phi(arg0=BitwiseAnd32@pc=0x1600(arg0=CompositeExtractU32x4@pc=0x15d4(arg0=Ballot@pc=0x15d4(…)), arg1=imm=0x0), arg1=BitwiseNot32@pc=0x15fc(…)) …
    
    One arm of the low word is the waterfall loop's Ballot mask ANDed with the NOT of the loop's lane bit, the s_andn2 that clears the current lane.
  • Ratchet & Clank: Rift Apart (PPSA01474), run by ps54all (comment), on main 82f0c9a plus open PRs and feat(shader): read samplers through nested and pointer-keyed descriptor tables #7395's earlier commits:
    compute shader 0x116bb67000: … GetImageResource dword 0 is not a valid runtime value; chain: LoadAddressU32@pc=0x00009a30(GetAddressResource(Phi(… BitwiseAnd32(…, CompositeExtractU32x4(Ballot(Immediate), Immediate))) …
    
    A Ballot word is ANDed into the low word, and the high word is merged through a Phi. One arm of that Phi reads a second buffer, and on that edge the low word is a lane mask: the waterfall loop reusing the SGPR pair, as oneandonlydean read it from the IR (comment).

The change, in ResourceTracker.cpp, for pointer-keyed columns only:

  1. fix(shader): take lane-mask ANDs and a high-word Phi in a pointer-keyed record pointer.
    • Lane-mask ANDs. An AND with a lane-mask operand is taken. A lane-mask operand is a Ballot word, or an AND or Phi made only of those and immediates. The other operand is walked on, so a record read in it must still match the column. An AND with no lane-mask operand still refuses.
    • A merged high word. The high word now goes through the same walk as the low word. Its record reads must be reads of the same column at the low word's offset + 4. When it has no single dominating read, the table comes from the first record read whose V# words dominate the descriptor load. If none does, the column is refused.
    • A high-word arm read from a second buffer (a V# loaded through a pointer, then read at offset 0) stays refused, since it isn't a read of the record. Ratchet & Clank's chain has such an arm too (Tamalero's data on #6191). The fourth commit takes it where its edge's low word is a lane mask.
  2. fix(shader): skip lane-mask arms built from a waterfall's lane bit in a pointer-keyed record pointer.
    • A lane-mask operand also takes the loop's lane bit, ShiftLeftLogical32(1, index), and ORs and NOTs of lane-mask operands.
      • The index can come from FindILsb32, FindUMsb32 or ReadFirstLane, through BitwiseAnd32, IAdd32, ISub32, immediates and the arms of SelectU32.
      • That covers s_ff1_i32_b32, s_ff1_i32_b64, s_bitset0_b32 and the shift's own AND 31.
    • An arm made only of lane-mask operands is skipped. Little Nightmares' arm is one.
  3. docs(shader): the image-table debt entry lists the new arms and the merged high word, and says that a high word read from a second buffer stays refused.
  4. fix(shader): pair a pointer-keyed record pointer's two words by Phi edge, from oneandonlydean's reading of Ratchet & Clank: Rift Apart's IR (comment).
    • The low and high words are walked together. Where both are Phis of one block, each low-word edge takes the high word that arrives from the same predecessor.
    • An edge whose low word is an immediate or a lane mask is skipped with its high word. Rift Apart's second-buffer high word reaches the load only on such an edge, as oneandonlydean read it, so the walk no longer refuses that edge.
    • A record low word still needs every high word it can meet to be an immediate, a lane mask or the same column at offset + 4. A second-buffer high word paired with the record's low word still refuses.
    • One shape that planned before now refuses: both Phis in one block, with the record's low word on one edge (its high word an immediate) and the record's high word on another (its low word a lane mask). No edge carries a whole record pointer, so its old plan could only fault at run time.
  5. docs(shader): the debt entry says the two words are walked edge by edge, and that a second-buffer high word stays refused on an edge whose low word is the record word.

At run time, a pointer built from a lane mask faults as outside the snapshot, as #7395's lane-mask arms do.

Disk cache: no plan field changes, so the format stays at #7395's number. The disk-cache format ladder along this stack is main 44, #7223 45, #7395 46 (which this PR keeps) and #9633 47. Whichever lands next takes the next number, and the ones above it renumber in their rebase.

  • A shader that planned before plans the same column, apart from the fourth commit's one newly refused shape.
  • Shapes that refused before now plan. A refusal is never cached.

Marvel's Spider-Man 2 (PPSA08338), run by spiff637 (comment), stops on a record → pointer → V# → T# chain keyed by ReadFirstLane(SelectU32(LogicalAnd, IAdd32, Phi)). The second commit's agc_driver_pointer_vsharp_select_table builds a record → pointer → V# → T# chain with the T# read under a second key, and it passes without this PR's tracker changes. The title doesn't take it yet: hugodearaujo's run of the US release, Marvel's Spider-Man 2 (PPSA03016), still stops on the #6191 chain with this PR and #7679 (comment). There the T# is read at a fixed offset of the pointer-loaded V#, with no second key, and the same shader then reads its next block through a linked pointer. The test stays as coverage of the keyed form.

Tested

  • New tests in tests/execution/ImageTable.cpp, on lavapipe. They are synthetic wave64 waterfall loops, assembled with llvm-mc -mcpu=gfx1030 -mattr=+wavefrontsize64. The fourth commit's is the second-buffer shader with one instruction added (s_mov_b32 s44, exec_lo, 0xbeac037e) and its three crossing branch offsets adjusted, checked by disassembly.
    • The first commit's: agc_driver_pointer_phi_lane_and_table (lane-mask ANDs), agc_driver_pointer_phi_high_table (a merged high word) and agc_driver_pointer_phi_high_second_table (the second-buffer arm, which must refuse).
    • The second commit's: agc_driver_pointer_phi_lane_not_table (Little Nightmares' BitwiseNot32 and s_andn2 arms) and agc_driver_pointer_vsharp_select_table (Spider-Man 2's chain).
    • The fourth commit's: agc_driver_pointer_phi_high_second_lane_table (Rift Apart's edge: the second-buffer high word with a lane-mask low word, which must plan).
    • What they check: each lane samples the T# its key's record selects. Each arm whose pointer wasn't captured faults as outside the snapshot, publishes no output and keeps the artifact id.
  • Red, with only ResourceTracker.cpp reverted and the tests kept, on Linux with CI's configuration:
    • On the first commit's base, lane_and and high fail with the dword-0 refusal, and high_second passes (refused). Each half of the commit fixes only its own test.
    • On the second commit's base, lane_not fails: GetImageResource dword 0 is not a valid runtime value; chain: LoadAddressU32@pc=0x000000c0(GetAddressResource(Phi(Immediate, Phi(ReadConstBuffer@pc=0x00000074(…), Phi(BitwiseAnd32(CompositeExtractU32x4(Ballot(LogicalAnd), Immediate), BitwiseNot32(ShiftLeftLogical32(Immediate, BitwiseAnd32))), …. vsharp_select passes (coverage).
    • On the fourth commit's base (e9d4626), high_second_lane fails: GetImageResource dword 0 is not a valid runtime value; chain: LoadAddressU32@pc=0x00000108(GetAddressResource(Phi(Phi(ReadConstBuffer@pc=0x0000007c(…. The other 10 image-table cases pass, high_second among them (refused).
  • Green at the head, Linux, lavapipe (Mesa 25.2.8, llvmpipe LLVM 20.1.2), CI's configuration, on a cloud worker:
    • 17 tests pass 10 runs each: the five new tests, feat(shader): read samplers through nested and pointer-keyed descriptor tables #7395's pointer-Phi ballot and copies tests, the image-table family and agc_shader_disk_cache.
    • A mirror of CI's Linux job: 1,718 of 1,721 pass, against 1,716 of 1,719 on the base. The 3 failures on both are that machine's IPv6 socket tests.
    • The Khronos validation layer reports no new VUIDs on the focused tests.
  • GitHub's runners (run, at 2788b8f, the head before the docs commit): the five new tests, feat(shader): read samplers through nested and pointer-keyed descriptor tables #7395's pointer-Phi and nested sampler tests and agc_shader_disk_cache pass 5 runs in a row on Linux. On Windows the GPU ones skip and the rest pass. The libc and libkernel suites pass on both.
  • At the head, a7d1e62 (the fourth and fifth commits):
    • A cloud worker, Linux with CI's configuration: the 11 image-table cases pass 20 runs each. A mirror of CI's Linux job: 1,807 of 1,813 pass. The six failures are network tests that depend on that machine (no IPv6, and its socket buffers); the five beyond its known IPv6 failure fail the same way on e9d4626.
    • GitHub's runners (run): on Linux the 12 image-table cases and agc_shader_disk_cache_large_file pass 5 runs each. Windows: builds; the Vulkan cases skip on the runner.
    • Linux ^agc (run): 873 of 874 pass, 60 skip. The one failure is agc_driver_sgpr_pattern_fill_keys, a timing test, which passed in the worker's full run of the same head.
  • Ratchet & Clank: Rift Apart (PPSA01474), retail, by @Tamalero (comment): version 01.005.003, Linux, RTX 4090 (NVIDIA 615.78.08), cold shader cache, one unattended run per build.
    • feat(shader): read samplers through nested and pointer-keyed descriptor tables #7395's head 6c1371b stops at 275 s on compute 0x116bb67500: GetImageResource dword 0, LoadAddressU32@pc=0x9a30.
    • This PR at a7d1e62 gets past it, with no regression up to the next stop, as they report. At 270 s it stops on a different shader, compute 0x116bb5ad00: GetImageResource dword 0, LoadAddressU32@pc=0x10b84.
    • As they read the new chain, it has the same shape one level deeper. The low word is a Phi of the record read at pc=0xfd94 and a Ballot lane-mask word. The high word's other arm is ReadConstBuffer@pc=0x10350, through a V# whose four dwords are loaded at pc=0x10338 from another pointer.
  • Not tested: Little Nightmares Enhanced Edition (PPSA10737). @klosixgaming-arch, could you run it on this branch? It needs fix(shader): link a table call whose descriptor load sits before its loop #7413 too, as in your run.

Checklist

yabanana and others added 10 commits October 17, 2026 18:48
…mers

A T# read from a nested child table stayed a runtime read when one of
its words also fed another instruction. That read goes through a V#
that exists only in the snapshot, so the dispatch read a garbage
address; lavapipe crashed in the shader. The nested branch now refuses
a child whose descriptor words are used by anything but equal handles,
as it already does for a V# with another consumer.

(cherry picked from commit 0df7d32)
An S# read from a V# that a scalar record selects through a pointer
was refused, so recompiling failed with "GetSamplerResource dword 0 is
not a valid runtime value". The nested branch now takes sampler
columns: they get child tables per distinct V# like nested T#s, share
equal children, count against the nested key budget and the S# entry
budget, and fault on an unmapped, non-buffer or uncaptured parent. The
plan decoder accepts a nested sampler column; the plan encoding is
unchanged.
An S# or T# loaded through a pointer that a scalar record holds was
refused when a Phi merged the pointer's low word with constants, so
recompiling failed with "GetSamplerResource dword 0 is not a valid
runtime value". Such a column is now pointer-keyed: the snapshot
captures every record's pointer and the descriptor behind it, the map
holds boykopovar#7223's sorted pointer dictionary with descriptor records as its
codes, and the GPU looks up the actual 64-bit pointer. A pointer that
was not captured faults as outside the snapshot, and a null one as
unmapped.

The plan gains TableColumn::pointerKeyed and the snapshot gains
ImageTableColumnSnapshot::pointerKeyed, so the disk-cache format moves
to 43 and the image-table ABI to 4.
A T# loaded through a record pointer whose low word passes nested Phis
was refused when one Phi arm held a word of a Ballot lane mask, as in a
waterfall loop that reuses the pointer's SGPR for an s_and_saveexec or
v_cmp mask, so recompiling failed with "GetImageResource dword 0 is not
a valid runtime value". A pointer-keyed column now accepts such a word
as an arm that is not a record pointer, as it does for constants, and
still refuses any other foreign arm. At run time a pointer built from a
lane mask is not a captured pointer and faults as outside the snapshot.

A pointer-keyed column also accepts low-word reads, and a high-word read,
of the same record word under different keys: same table, record offset,
stride and addend. The GPU looks the pointer up by its value, so the key
does not matter there. The column takes its table and record offset from
the high-word read, which dominates the descriptor load.

A shader that planned before plans the same way, so the disk-cache
format stays at 43.
…ed record pointer

A T# loaded through a record pointer was still refused with "GetImageResource
dword 0 is not a valid runtime value" when the pointer's low word passed
ANDs with lane masks, as a waterfall loop that copies EXEC into the
pointer's SGPR builds them, or when its high word was a Phi of the
record's high word read under several keys.

For a pointer-keyed column, the walk of the low word now takes an AND
with a lane-mask operand (a Ballot word, or an AND or Phi of only Ballot
words and immediates) and keeps walking its other operand, so its
record reads must still match the column and anything else still
refuses. The high word is walked the same way: its record reads must
be the record word at the low word's offset plus 4. A high word that is
one read still gives the column its table and record offset, as before.
Otherwise the table comes from a record read whose V# words are defined
before the descriptor load, and the record offset from the high word,
which the GPU does not read for a pointer-keyed column.

A high-word arm read from another buffer, such as one whose V# is
loaded through a pointer, stays refused. A shader that planned before
plans the same way, so the disk-cache format does not change.
… a pointer-keyed record pointer

A T# loaded through a record pointer was still refused with "GetImageResource
dword 0 is not a valid runtime value" when a Phi of the pointer's low word
merged the record read, a zero, and the waterfall's lane mask with the
handled lane's bit cleared, BitwiseAnd32(CompositeExtractU32x4(Ballot(...), 0),
BitwiseNot32(ShiftLeftLogical32(1, index))), as s_ff1, s_lshl and s_not with
s_and, or s_andn2, build it in the SGPR the pointer reuses. The walk took only
Ballot words and ANDs of them as lane masks, so it walked into the NOT and
refused.

A lane-mask operand is now a Ballot word or the loop's lane bit, a shift of 1
by an index from FindILsb32, FindUMsb32 or ReadFirstLane (through the AND,
add, subtract and select that s_ff1_i32_b64, s_bitset0 and the shift's own
mask put around it), or an AND, OR, NOT or Phi of only those and immediates.
An arm of the pointer that is entirely such an operand is skipped. An AND
with a record read in it still has the read matched against the column, and
anything else still refuses.

agc_driver_pointer_phi_lane_not_table builds that shape in a wave64
waterfall: the low word's Phis merge the record read at +0, a zero and the
lane mask without the handled lane's bit, cleared by s_not and s_and and by
s_andn2, and the high word is one read at +4. Each lane samples the T# of its
record, and every masked word faults outside the snapshot.
agc_driver_pointer_vsharp_select_table reads a record keyed by
ReadFirstLane(SelectU32(LogicalAnd, IAdd32, Phi)), a pointer from it, a V#
through the pointer and the T# through the V#; it passes without this change
and keeps that chain checked, including a record pointer moved to the other
V# without a recompile.

Shaders that planned before plan the same way, and a refusal is never cached,
so the disk-cache format does not change.
…keyed records

A pointer-keyed record pointer now also takes lane-mask ANDs, the
waterfall loop's lane bit and its OR and NOT forms, and a high word
merged from the record's next word. A high word read from a second buffer
stays refused.
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 6858 core/shader/recompiler/ShaderDiskCache.hpp L16
PR 7880 core/shader/recompiler/Optimization/src/ResourceTracker.cpp L753, L791, L804
PR 8564 core/shader/recompiler/ShaderDiskCache.hpp L16
PR 8881 core/shader/recompiler/ShaderDiskCache.hpp L16
PR 9020 core/shader/recompiler/ShaderDiskCache.hpp L16
PR 9335 core/shader/recompiler/ShaderDiskCache.hpp L16
PR 9889 core/shader/recompiler/ShaderDiskCache.hpp L16
PR 9897 core/shader/recompiler/ShaderDiskCache.hpp L16
PR 9957 core/shader/recompiler/ShaderDiskCache.hpp L16

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 907 core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp
PR 1368 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 2434 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4072 core/shader/recompiler/Recompiler.cpp
PR 5021 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp
docs/dev/debt/shader.md
PR 5371 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp
docs/dev/debt/shader.md
PR 5431 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5609 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/shader/recompiler/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.hpp
PR 6044 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6169 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6186 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6187 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6192 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6209 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6235 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
docs/dev/debt/shader.md
PR 6280 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
core/shader/recompiler/Optimization/src/ResourceTracker.cpp
core/shader/recompiler/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.hpp
core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp
docs/dev/debt/shader.md
PR 6292 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6300 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6412 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourcePlan.hpp
core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.hpp
docs/dev/debt/shader.md
PR 6481 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/execution/ImageTable.cpp
PR 6488 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/ImageTableAbi.hpp
core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ControlFlowInfo.hpp
core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourcePlan.hpp
core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
core/shader/recompiler/Optimization/src/ResourceTracker.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.hpp
core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp
docs/dev/debt/shader.md
PR 6501 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6539 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
PR 6612 core/shader/recompiler/Recompiler.cpp
docs/dev/debt/shader.md
PR 6615 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6741 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6788 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6808 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6868 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6879 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
docs/dev/debt/shader.md
PR 6924 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.hpp
docs/dev/debt/shader.md
PR 7201 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/shader/recompiler/Recompiler.cpp
PR 7275 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7457 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7550 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
core/shader/recompiler/Optimization/src/ResourceTracker.cpp
core/shader/recompiler/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.hpp
PR 7604 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7684 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/libs/prx/libSceAgcDriver/tests/execution/ImageTable.cpp
core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ControlFlowInfo.hpp
core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
core/shader/recompiler/Optimization/src/ResourceTracker.cpp
core/shader/recompiler/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.hpp
docs/dev/debt/shader.md
PR 7689 core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
core/shader/recompiler/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.hpp
PR 7705 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7803 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
docs/dev/debt/shader.md
PR 8077 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
core/shader/recompiler/Optimization/src/ResourceTracker.cpp
core/shader/recompiler/ShaderDiskCache.cpp
docs/dev/debt/shader.md
PR 8246 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8281 docs/dev/debt/shader.md
PR 8365 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8406 docs/dev/debt/shader.md
PR 8484 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8527 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8542 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8552 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8592 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8625 core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp
docs/dev/debt/shader.md
PR 8672 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8998 core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp
PR 9356 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.hpp
PR 9387 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.hpp
PR 9748 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/ShaderDiskCache.cpp
core/shader/recompiler/ShaderDiskCache.hpp
docs/dev/debt/shader.md
PR 9892 core/libs/prx/libSceAgcDriver/CMakeLists.txt

@github-actions github-actions Bot added build shader core/shader docs agc libSceAgc* labels Oct 21, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 21, 2026
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 21, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

@EagleBearerYS thanks for #8939. We ran PPSA01474 on main plus #7395 and #8939 (f877d5e; the pointer walk is the same at e9d4626) and dumped the IR of the shader that stops. It still stops at LoadAddressU32@pc=0x9a30 with GetImageResource dword 0 is not a valid runtime value. Its address changes from run to run (0x116bb5d300, 0x116bb66700, 0x116bb5c500 and 0x116bb5c900 are the same program).

The IR shows which check refuses it, and why that arm isn't a pointer:

  • The descriptor at 0x9a30 is loaded through GetAddressResource(low, high), and both words are Phis that merge at pc 0x5a48.
  • Low word: immediates, lane masks, and the record column read at pc 0x1aa8. Your lane-mask handling accepts all of these.
  • High word: the same record column's high word, plus one arm from the second buffer (ReadConstBuffer at offset 0 of a buffer whose V# is loaded through the record pointer, pc 0x900c/0x908c). PointerRecordReads refuses that arm on the high-word walk, because it isn't the same record column.
  • Pairing the Phi edges: the second-buffer high word reaches the load on exactly one incoming edge. On that edge the low word is BitwiseAnd32(Phi(lane masks), CompositeExtractU32x4(Ballot(true), 0)), a pure lane mask. Every edge that carries a record-column low word carries the record-column high word.

So on the one path where the high word comes from the second buffer, the "pointer" is (a lane mask, a T#-table dword). That's the SGPR pair being reused by the waterfall loop, not an address. Tamalero's dump on #6191 (issuecomment-6255548496) agrees: the record's high word is 0x00000012 in all 27,396 populated records, and no dword of that T# table equals 0x12.

Could the walk pair the two words by incoming edge? Concretely:

  1. Walk the low- and high-word Phis together, edge by edge.
  2. On an edge where the low word is lane-mask-only (or an immediate), skip both words, as the low-word walk already skips lane-mask arms on its own.
  3. Refuse as now on any edge that pairs a record-column low word with a high word from anywhere other than the same column at offset + 4.
  4. agc_driver_pointer_phi_high_second_table keeps refusing when the second-buffer high word is paired with a record-column low word. A new case pairs it with a lane-mask low word, which should plan, and its record lanes should sample the T# their record selects.

This decides it at plan time from the IR alone, with no run-time fault path and no new plan field. If you'd rather keep the walks separate, a run-time fault for pointers built from that arm (as lane-mask pointers already do) would also work for us. We can share the IR excerpt around 0x9a30, and we're happy to run the title on the result.

The low and high words of a pointer-keyed record pointer were walked
separately, so a high-word arm on a path whose low word is a lane mask
refused the plan even though that pair is no pointer. Ratchet & Clank:
Rift Apart (PPSA01474) stops this way at LoadAddressU32@pc=0x9a30: one
high-word arm reads a second buffer, and on that edge the low word is a
lane mask, the waterfall loop reusing the SGPR pair.

Walk the two words together. Where both are Phis of one block, each
low-word edge takes the high word of the same predecessor. An edge whose
low word is a constant or a lane mask is skipped with its high word. A
record low word still needs every high word it can meet to be a constant,
a lane mask or the same record column at offset + 4, so a second-buffer
high word paired with the record word stays refused.

agc_driver_pointer_phi_high_second_lane_table adds the lane-mask low word
to the second-buffer arm: it plans, each group samples the T# its record
selects, and that arm faults outside the snapshot.
…nter

The two words of the pointer are now walked edge by edge, and an edge
whose low word is a constant or a lane mask is skipped with its high
word. A high word read from a second buffer stays refused on an edge
whose low word is the record word.
@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@oneandonlydean, thank you for reading the IR this closely. The edge-by-edge pairing is the better fix, so I've written it as you describe, with both cases: agc_driver_pointer_phi_high_second_table keeps refusing the second-buffer high word paired with the record-column low word, and a new case pairs it with a lane-mask low word and checks that it plans and that each record lane samples the T# its record selects. It's on GitHub's runners now. I'll post here once it's pushed to this branch, so you can run Ratchet & Clank: Rift Apart (PPSA01474) on it.

EagleBearerYS added a commit to EagleBearerYS/AnyPS5 that referenced this pull request Oct 21, 2026
@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@oneandonlydean, thank you for the IR dump and the steps. a7d1e62 on this branch pairs the two words as you describe:

  1. PointerRecordPairs walks the low- and high-word Phis together. Where both are Phis of one block, each low-word edge takes the high word that arrives from the same predecessor. A word that is no Phi there stays the same on every edge.
  2. An edge whose low word is an immediate or a lane mask is skipped with its high word.
  3. A record-column low word still needs every high word it can meet to be an immediate, a lane mask, or the same record column at offset + 4. Anything else refuses, as before.
  4. agc_driver_pointer_phi_high_second_table still refuses its second-buffer high word, which it pairs with a record-column low word. The new agc_driver_pointer_phi_high_second_lane_table is the same shader with s_mov_b32 s44, exec_lo added to the second-buffer arm. That read is BitwiseAnd32(exec_lo, CompositeExtractU32x4(Ballot(true), 0)) in the IR, the form you list. It plans, each group's lanes sample the T# their record selects, and that arm faults outside the snapshot when a group takes it.

There's no new plan field and no new run-time path.

Tested on GitHub's runners, on main b6fe513 (run 39613056959):

  • Linux (lavapipe): the 12 image-table cases, including both pointer_phi_high_second cases, plus agc_shader_disk_cache_large_file, 5 runs each, all pass.
  • Windows: builds; the Vulkan cases skip on the runner.
  • Linux ^agc (run 39613110357): 873 of 874 pass, 60 skip. The one failure is agc_driver_sgpr_pattern_fill_keys, a timing test, which passed in the full run of the same head below.
  • Linux with CI's flags on a separate machine:
    • the 11 image-table cases, 20 of 20 each;
    • red: with only ResourceTracker.cpp from e9d4626, the new case fails with "GetImageResource dword 0 is not a valid runtime value", and agc_driver_pointer_phi_high_second_table still refuses as before;
    • the full ctest: 1807 of 1813. The six failures are network tests that depend on that machine (no IPv6, and its socket buffers); the five beyond its known IPv6 failure fail the same way on e9d4626.

One shape that planned before is now refused. Both Phis are in one block, one edge carries the record's low word with an immediate high word, and the other carries a lane mask with the record's high word. No edge carries a whole record pointer, so the old plan could only fault at run time there. If you'd rather keep it planned, falling back to the old separate high-word walk when the paired walk finds no high read keeps it as before.

Could you run Ratchet & Clank: Rift Apart (PPSA01474) on a7d1e62? If it stops again, the IR excerpt around the new stop would help.

@Tamalero

Copy link
Copy Markdown
Contributor

Retail check of a7d1e62 on Ratchet & Clank: Rift Apart (PPSA01474, 01.005.003), Linux, RTX 4090 (NVIDIA 615.78.08), cold shader cache, one unattended run per build:

Build Result
#7395 head 6c1371b (contains #7223 8716815 and main b6fe513) 275 s, stops on compute 0x116bb67500: GetImageResource dword 0, LoadAddressU32@pc=0x9a30, the stop you dumped
this PR, a7d1e62 270 s, gets past 0x9a30 and stops on a different shader, compute 0x116bb5ad00: GetImageResource dword 0, LoadAddressU32@pc=0x10b84

The new chain has the same overall shape one level deeper. The low word is a Phi of the record read at pc=0xfd94 and a Ballot lane-mask word (CompositeExtractU32x4). The high word arm is ReadConstBuffer@pc=0x10350 through a V# whose four dwords are loaded at pc=0x10338 from another pointer:

LoadAddressU32@pc=0x00010b84(GetAddressResource(Phi(...ReadConstBuffer@pc=0x0000fd94(GetBufferResource, Immediate)..., CompositeExtractU32x4...),
  Phi(...ReadConstBuffer@pc=0x0000fd94..., ReadConstBuffer@pc=0x00010350(GetBufferResource(LoadAddressU32@pc=0x00010338(GetAddressResource, Immediate, Immediate, Immediate) x4), Immediate))), ...)

So a7d1e62 fixes the 0x9a30 stop on the retail title. No regression up to the new stop. I can dump this shader's IR or the runtime values of the pc=0x10338/0x10350 reads if that helps.

Tested and written by Claude (AI assistant) on a contributor's machine.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@Tamalero, thank you for the retail run. It's now in the description: this PR gets Ratchet & Clank: Rift Apart (PPSA01474) past the 0x9a30 stop.

We'd like to look at the 0x10b84 stop next. If you have time, the IR dump would help most:

  • Run with APS5_DUMP_IR=116bb5ad00: the shader's code address, in lowercase hex without 0x.
  • It prints ==== IR 0x116bb5ad00 after <pass> blocks to stderr. Only the last block before the stop is needed.
  • The address can change from run to run (dean saw this PR's first stop at several, comment). If the stop names another address, use that one.
  • That run skips the shader disk cache's plans, which is expected.

If it's easy, the runtime values would help too: on the dispatch that stops, the dword the pc=0x10350 read returns, and the record's high word, the dword after the pc=0xfd94 read.

We'll read the dump before saying what the fix is.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 21, 2026
Brings boykopovar#8991 (match image-table T# halves offset before and after
readfirstlane).

ResourceTracker.cpp: main's FirstLaneOffset, ParseFirstLaneOffset and
SameFirstLaneOffset go before TryMakeTableColumn, which keeps this
branch's signature with the buffer parameter. Main's first-lane base
check in the column loop merged on its own.
…ane-masks

Brings boykopovar#7395's merge of main bd4d801, with boykopovar#8991. It merged without conflicts.
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 21, 2026
@hugodearaujo

Copy link
Copy Markdown
Contributor

Marvel's Spider-Man 2 (PPSA03016 01.004.003, the US release of spiff637's PPSA08338) still stops on the #6191 chain with this PR, so #7395 with #7223 doesn't take this title's shape yet.

Build Stop
main 0c6a5bd 151 s, INDEX_BUFFER_SIZE has not been set (the stop #7679 fixes)
main + #7679 142 s and 146 s (two runs), compute 0x11e2b44c00 / 0x11e2b34400: GetImageResource dword 0 is not a valid runtime value, ReadConstBuffer@pc=0x8560(GetBufferResource(LoadAddressU32@pc=0x854c(GetAddressResource(ReadConstBuffer@pc=0x1fc4, …
this PR a7d1e62 + #7679 137 s in both runs, compute 0x11e2b37100 / 0x11e2b39b00, the same chain

Linux 7.2.11, RX 9070 XT, RADV Mesa 26.2.5, Release. One run on main.

The shape. agc_shader_replay on the .prepare.req that APS5_DUMP_SHADERS=1 wrote reproduces the refusal offline. The _failed.req doesn't, since it lacks the linked callee.

  • pc 0x1fc4: a waterfall loop reads a 16-byte record from an SRT V#, keyed by ReadFirstLane(…) << 4. Dwords 0–1 are a callee address (the linked s_swappc_b64 targets). Dwords 2–3 are an argument pointer.
  • pc 0x854c, in the callee: a V# is loaded at the argument pointer + an immediate.
  • pc 0x8560: the T# is read at a fixed offset of that buffer, ReadConstBuffer(V#, 0x0). There is no second key.
  • TryMakeTableColumn refuses before it reaches the nested path, because MatchAffineOffset fails on the immediate offset. agc_driver_pointer_vsharp_select_table reads its T# under a second key, which is why that fixture passes.

A local change for that part. A fixed offset under a pointer-loaded V# becomes a one-key child: key 0, stride the descriptor size, addend the immediate. It applies only on the nested path; a fixed offset under a runtime V# still refuses as before.

  • The replay gets past 0x8560.
  • agc_driver_image_table and the 13 nested and pointer image-table tests pass on the RX 9070 XT.
  • The diff is below.
diff on a7d1e62
@@ -660,7 +660,13 @@
         if (address && immediateU32(offset, descriptorOffset)) {
             return TryMakeIndirectColumn(handle, *heapHandle, sampler, words, descriptorOffset, first, plan);
         }
-        if (!MatchAffineOffset(offset, key, stride, addend)) {
+        std::uint32_t fixedOffset = 0;
+        const bool fixed = !address && !buffer && immediateU32(offset, fixedOffset);
+        if (fixed) {
+            key = &m_builder.Constant(0u);
+            stride = width * static_cast<std::uint32_t>(sizeof(std::uint32_t));
+            addend = fixedOffset;
+        } else if (!MatchAffineOffset(offset, key, stride, addend)) {
             if (!MatchMaskedOffset(offset, key, stride)) {
                 return false;
             }
@@ -668,7 +674,9 @@
         }
         DescriptorSource heapSource;
         std::uint32_t heapIndex = 0;
-        if (!MakeRuntimeSource(*heapHandle, address ? IrOpcode::GetAddressResource : IrOpcode::GetBufferResource, address ? 2u : 4u, heapIndex, heapSource)) {
+        if (MakeRuntimeSource(*heapHandle, address ? IrOpcode::GetAddressResource : IrOpcode::GetBufferResource, address ? 2u : 4u, heapIndex, heapSource)) {
+            if (fixed) return false;
+        } else {

The next refusal, in the same shader (pc 0x8f54 and 0x8fa0):

  • The low word of the V#'s pointer is a Phi of two kinds of arm. Some arms are record reads under different keys (the loop's iterations). One arm is a dword the callee reads from the material buffer itself, ReadConstBuffer@pc=0x9000 at +0x4c0: a link to the next block.
  • The high word is a Phi of record reads too.
  • So PointerRecordReads refuses, because the arms aren't equivalent reads.
  • The V# also has a consumer besides the T# read: the link read. feat(shader): resolve nested image tables through runtime pointers #7223's shared-consumer check refuses that.

I haven't tried to take this second shape; how a linked pointer should be planned is your call. I can share the request file and the IR around 0x8f54.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@hugodearaujo, thank you for the runs and for tracing both refusals. Your fixed-offset change is right: a T# at a fixed offset of a V# loaded through the record pointer has one record (key 0, the descriptor size as stride, the immediate as addend), which the nested path already serves, and a fixed offset under a runtime V# still refuses as before.

We'd like to carry it as your own commit, with you as its author, in a new PR stacked on this one, together with tests of ours for that shape. If you'd rather open it yourself, say so and we'll leave it to you.

For the linked-pointer shape at 0x8f54 and 0x8fa0, the IR text around those reads would help. We don't need the request file, since it holds the title's shader code.

@Tamalero

Copy link
Copy Markdown
Contributor

@EagleBearerYS here's the IR reading for the 0x10b84 stop. The address changes per run (0x116bb5ad00, 0x116bb61300 and 0x116bb5c500 in three runs on a7d1e62), so I captured the failing request with APS5_DUMP_SHADERS=1 and dumped the IR offline with APS5_DUMP_IR through agc_shader_replay. That reproduces the refusal exactly. I traced the walk with temporary prints in a scratch copy of ResourceTracker.cpp.

Short version: the edge pairing works here. Every (low, high) pair the walk pops is consistent, and the case your PR still refuses (a record low word under a second-buffer high word) doesn't occur. The stop has a different cause: the pointer has three sources, and only the first is the record column.

  • R: the record pair ReadConstBuffer@0x1aa8, dwords 2/3 of the 16-byte record (offsets 8/0xc), stride 16 from ReadFirstLane(...) << 4. Accepted first; it fixes lowReads.
  • H1: ReadConstBuffer@0xfd94, offsets 0/4, where both words come from one read. Its V# is loaded at @0xfd88 from offsets 0x60..0x6c of a pointer that is itself a Phi of R.
  • H2: ReadConstBuffer@0x10350, offsets 0/4, again both words from one read. Its V# is loaded at @0x10338 from pointer+0x60, where the pointer is the Phi of R and H1.
  • Skipped as intended: the lane-mask (Ballot / BitwiseAnd32) and immediate low words (0xea, 0x3f400000, 0x40c00000), with their high words. There's one join where the low and high Phis sit in different blocks; the walk falls back to unpaired there.

H1 and H2 both fail PointerRecordRead (ResourceTracker.cpp:845, called from :871): offset 0 against the record's 8, and a different buffer, so SameRecordColumn fails. The walk then returns false and TryMakeIndirectColumn refuses.

So the shader chases the pointer conditionally, up to twice (P → V# at P+0x60 → dwords 0/1 → next P), and loads the T# at the final P+0. The final pointer is one of R, H(R) or H(H(R)). Since the column is pointer-keyed and the runtime looks the T# up by pointer value (LookupTablePointer), one way forward could be:

  • treat this chase as a hop source (V# at +0x60, read at +0), not a record column;
  • have the materializer add the closure pointers H(R) and H(H(R)) of every record pointer, each with its T# at P+0, bounded at depth 2 here.

The existing pointerHops don't fit as they are: they're unconditional, and a merged pointer refuses them. Forcing the two failures to continue (wrong, only to look further) plans with the R pointers alone and then stops on sampler resource limit exceeded, which may be the next wall after this one.

I haven't checked whether the H(R)/H(H(R)) targets fall inside the captured regions. I can run a values pass on this shader if that helps.

Tested and written by Claude (AI assistant) on a contributor's machine.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@Tamalero thanks, this is the reading we needed, and the replay through agc_shader_replay is a reproduction we can check a candidate against.

It's new work rather than something #9457 covers. #9457's hops follow unconditional chains of plain address loads, while here the descriptor pointer is a Phi of R, H(R) and H(H(R)), and each hop reads the next pointer through a V# at P+0x60. A chase-hop closure along the lines you describe is planned as a separate PR stacked on #9457, built and tested with a synthetic chase shader. The sampler resource limit exceeded you hit with the arms forced to continue stays out of scope unless it shows up with the real closure.

If you can run the values pass, three things would help:

  1. whether H(R) and H(H(R)) fall inside the captured regions, at each depth;
  2. how often each depth is taken at run time, i.e. whether the chase ever goes past R;
  3. at the sampler wall, how many sampler sources the plan had, and whether the S# handles come from the same pointer.

No need to send the capture itself. Once there's a candidate, a replay on your side is what we'd ask for.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@EagleBearerYS I ran 8704f73 (stacked on #7395 and #7223) on PPSA01474. The edge pairing works: the 0x9a30 stop is gone, and the lane-mask edges there are skipped as intended. The same program now stops later, at LoadAddressU32@pc=0x10b84, with the same generic GetImageResource dword 0 is not a valid runtime value. I read the IR from an APS5_DUMP_IR=all run (the "after srt" pass) to see which check refuses it.

The pointer is GetAddressResource(low Phi, high Phi). Paired edge by edge, its live arms are:

  • the record column you already plan (keyed ReadConstBuffer at pc 0x1a88, high word at the column +4);
  • a 64-bit pointer read as low/high from offset 0 of a second buffer whose V# is four LoadAddressU32 words (pc 0xfd84..0xfeb8: ReadConstBuffer@0xfd94 over LoadAddressU32@0xfd88);
  • the same shape at pc 0x1032c..0x104a0 (ReadConstBuffer@0x10350 over LoadAddressU32@0x10338).

The other edges are lane masks paired with stale high words (including a LoadAddressU32 through the 0x9a30 pointer at pc 0xa4b8), which your pairing now skips.

As far as I can tell, PointerRecordPairs → PointerRecordRead refuses here because every low read must be the same record column as the first (SameRecordColumn: same handle and affine key). The two extra arms come from different buffers, and those buffers' descriptors are themselves loaded from memory. Unlike the 0x9a30 arm, these look like real pointers (low and high from the same handle, paired with a read rather than a lane mask). So this is three pointer sources merged into one SGPR pair, two of them one level deeper.

I don't think this belongs in #8939's current scope, which is one record column per pointer. I'm posting it so you know where the program goes next. Would you plan a multi-source pointer like this (one column per source, selected at run time), or does it need a general runtime T# fetch? I can share the IR block for $2037 and the pairing output if useful.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@oneandonlydean thanks for running it on Ratchet & Clank: Rift Apart (PPSA01474) and for the IR read. Good to see the 0x9a30 stop gone. 0x10b84 is the stop Tamalero traced (reply): the same three arms, the record column and two chased pointers, each read from offset 0 of a buffer whose V# is loaded from the pointer before it.

To your question: neither, quite. The plan is one pointer-keyed column whose entries cover every pointer the shader can reach from each record: the record's pointer R and the chased H(R) and H(H(R)), read from the snapshot when the dispatch is captured. The shader then looks its run-time pointer up in that column as it does today. So no general run-time T# fetch is needed while the shader fixes the chase depth (two sites here) and the chased memory is in the snapshot. A chase that loops would need a run-time fetch, and it would be refused instead. It's planned as a separate PR stacked on #9457, built with a synthetic shader, and we'll ask you to run it when it's up.

Yes, please post the $2037 IR block and the pairing output here. We'd compare them with the synthetic shader's IR.

@hugodearaujo

Copy link
Copy Markdown
Contributor

@EagleBearerYS thanks for carrying the fixed-offset change in #9457 as my commit. I'm running Spider-Man 2 on #9457 now and will post the result there.

Here is the IR around the 0x8f54 and 0x8fa0 reads, after srt, the last pass before the refusal. The ; notes are mine. Reading it again, I described the +0x4c0 arm wrongly above: it isn't a link to another block. Its two words are dwords 0 and 1 of a T# that also feeds an ImageSampleRaw. So the pair looks like registers being reused, as with the lane masks at Rift Apart's 0x9a30.

Block $184 pc=0x1fa4..0x1fe4                          ; waterfall over the material id
  %7732 = ReadFirstLane %7664, %7730
  %7738 = ShiftLeftLogical32 %7732, 0x00000004
  %7754 = GetBufferResource %7742, %7745, %7748, %7751  ; record table V# from the SRT
  %7757 = ReadConstBuffer %7754, %7738                  ; record dword 2, argument pointer low
  %7758 = ReadConstBuffer %7754, %7738                  ; record dword 3, argument pointer high
...
  %8506 = Phi [%7757, $297], [0x000000ea, $302]        ; $303 pc=0x86c0
  %8524 = Phi [%7757, $295], [%8506, $305]             ; $306 pc=0x840c
  %8551 = Phi [%8524, $306], [%7757, $316]             ; $317 pc=0x840c
...
Block $353 pc=0x8f14..0x9078
  %10717 = GetAddressResource %8551, %7758
  %10718..%10721 = LoadAddressU32 %10717, 0x0, 0x0, true    ; pc 0x8f54, the V#
  %10906 = GetBufferResource %10718, %10719, %10720, %10721
  %10907..%10914 = ReadConstBuffer %10906, 0x0              ; T# -> %10975
  %10975 = GetImageResource %10907, ..., %10914             ; -> ImageSampleRaw %10978
  %10987 = GetBufferResource %10718, %10719, %10720, %10721
  %10988..%10995 = ReadConstBuffer %10987, 0x0              ; the pc 0x9000 read at +0x4c0
  %11013 = GetImageResource %10988, ..., %10995             ; -> ImageSampleRaw %11016
Block $354 pc=0x9078..0x90b0
  %11182 = Phi [%8551, $352], [%10988, $353]                ; pointer low
  %11181 = Phi [%7758, $352], [%10989, $353]                ; pointer high
Block $357 pc=0x9234..0x923c
  %11740 = Phi [%8551, $346], [%11182, $356]
  %11739 = Phi [%7758, $346], [%11181, $356]
Block $360 pc=0x8428
  %11785 = Phi [%8551, $322], [%11740, $359]
  %11784 = Phi [%7758, $322], [%11739, $359]
Block $372 pc=0x8428
  %11969 = Phi [%11785, $360], [%8551, $371]
  %11968 = Phi [%11784, $360], [%7758, $371]
  ; 15 GetAddressResource %11969, %11968 follow (pc 0x923c..0x9c2c), each with 4 LoadAddressU32
  • The pair that reaches the loads after pc 0x8428 comes from one of two places. On most edges it is the record's argument pointer, dwords 2 and 3 (%7757/%8551 and %7758). On the edge from $353 it is dwords 0 and 1 of the T# %11013, which that block reads at +0x4c0 and samples with.
  • So it isn't the chase that Tamalero and dean traced at Rift Apart's 0x10b84. There, the extra arms are 64-bit pointers read on their own. Here no arm is a pointer read: one is the record column, the other is a T#'s first two dwords.
  • I can't tell from the IR whether a load after 0x8428 ever runs with that pair.
  • The V# at 0x8f54 also feeds two GetBufferResource handles, one per T#.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build docs shader core/shader unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants