Skip to content

fix(agc): write the console's DRAW_INDEX_MULTI_INST packet for sceAgcDcbDrawIndexMultiInstanced - #11313

Merged
oneandonlydean merged 3 commits into
boykopovar:mainfrom
frankea:fix/agc-draw-index-multi-instanced
Oct 27, 2026
Merged

oneandonlydean merged 3 commits into
boykopovar:mainfrom
frankea:fix/agc-draw-index-multi-instanced

Conversation

@frankea

@frankea frankea commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What

On a PS5, sceAgcDcbDrawIndexMultiInstanced(cb, a1, a2, a3, a4, modifier) writes 15 dwords and sceAgcDcbDrawIndexMultiInstancedGetSize returns 60:

c0017904 00000342 c6000006
c0073a00 <a1> <a2 lo> <a2 hi> <a4, at least 1> <a3 lo> <a3 hi> <a4> 00000080
c0017904 00000342 c6000000

The initiator is 0x80 whatever the modifier, and the builder checks no argument: null and odd addresses, zero counts and 0xffffffff are written as given. AnyPS5 wrote 8 dwords (NUM_INSTANCES with a4, then DRAW_INDEX_2 reading a1 as the index count and a2 as the index address), threw for a non-null a3 and returned 32. The builder now writes the console's words and GetSize returns 60.

Measurement: PS5 on firmware 13.40, inside Gran Turismo 7 (PPSA01317), 2026-10-24 and 2026-10-25. A payload attached to the game with ptrace and called the game's own libSceAgc builder on a 0x200-byte command buffer filled with 0xcc; every call advanced the cursor by 60 bytes and wrote the two tag writes above around the packet. With ia = 0x123456789a00 and oa = 0x222233334440:

(a1, a2, a3, a4, modifier)                0x3a packet
(100, ia, NULL, 3, 0)                     c0073a00 00000064 56789a00 00001234 00000003 00000000 00000000 00000003 00000080
(100, ia, NULL, 1, 0)                     c0073a00 00000064 56789a00 00001234 00000001 00000000 00000000 00000001 00000080
(0, ia, NULL, 3, 0)                       c0073a00 00000000 56789a00 00001234 00000003 00000000 00000000 00000003 00000080
(100, ia, oa, 3, 0)                       c0073a00 00000064 56789a00 00001234 00000003 33334440 00002222 00000003 00000080
(100, ia, oa, 3, m)                       the same, for m = 0x10, 0x8 | 3 << 24, 0x1 | 2 << 9 | 0x4 | 5 << 19 and 0x100000000
(100, NULL, NULL, 3, 0)                   c0073a00 00000064 00000000 00000000 00000003 00000000 00000000 00000003 00000080
(100, ia + 1, oa + 2, 3, 0)               c0073a00 00000064 56789a01 00001234 00000003 33334442 00002222 00000003 00000080
(100, ia, oa, 0, 0)                       c0073a00 00000064 56789a00 00001234 00000001 33334440 00002222 00000000 00000080
(0xffffffff, ia, oa, 0xffffffff, 0)       c0073a00 ffffffff 56789a00 00001234 ffffffff 33334440 00002222 ffffffff 00000080

What the four arguments mean is not known, and so neither is what the packet draws. Opcode 0x3a is DRAW_INDEX_MULTI_INST on gfx10 (Linux nvd.h). AMD's PFP layout of that packet, as carried in Google Dive's pfp_pm4_packets.h, is instance count, object ID address, max size, index base, index count, initiator. Laid over the console's words, that makes a1 the instance count, a2 the object ID address, a3 the index base and a4 the index count, with the max size clamped to at least 1 the way AnyPS5's sceAgcDcbDrawIndexOffset clamps it. That is the reverse of the names AnyPS5 and SharpProspero give the arguments (index count, index address, object IDs, instance count). Initiator bit 7 is UNROLLED_INST (gc_10_1_0_sh_mask.h): the command processor unrolls the instances, and how each one gets its object ID and instance ID is not documented.

So the driver does not guess: it refuses 0x3a with DRAW_INDEX_MULTI_INST is not implemented: which fields hold the instance count, object ID address, index base and index count, and how object IDs reach the shaders, are not known instead of the generic draw message, and the opcode catalog names 0x3a DRAW_INDEX_MULTI_INST (it said DISPATCH_DRAW_PREAMBLE, which nvd.h puts at 0x8c). The argument names in the header stay as they were; the libSceAgc and libSceAgcDriver debt files record both open questions.

Behaviour change: a title that calls the builder now stops when it submits the packet, with the refusal above. Before, a call with a null a3 drew the guessed instanced draw, and a non-null a3 threw in the builder. Of the six titles whose imports I checked (Astro Bot, Ghostrunner, Gran Turismo 7, Horizon Call of the Mountain, NHL 26 and Onimusha), only Ghostrunner (PPSA03685) imports the function. In a Ghostrunner run on main 14eb2cd + #9703 with a local trace of the builder's arguments, RTX 4090, the title made no call before it stopped on #7459 at 147 s.

Tested

Windows 11 (build 26200), MinGW GCC (WinLibs 15.2) Release build with BUILD_TESTING=ON, each test run three times (ctest --repeat until-fail:3).

  • Refreshed onto main ca6e29e (head 549368f), with agc_driver_pm4's assertion that the driver refuses the 0x3a packet removed: the 41 ctest entries below pass 41/41, and 41/41 under the validation layer (same settings as below), where the loader inserted the layer and the log has no VUID- line or was not found but was requested by env var VK_INSTANCE_LAYERS. main ca6e29e passes its 40 the same way.
  • agc_draw_index_multi_instanced (new) checks the size and the 15 words of each of the twelve measured calls above against the console's. agc_driver_pm4 checks that the catalog names 0x3a DRAW_INDEX_MULTI_INST.
  • The 41 ctest entries of the 25 libSceAgc test executables plus agc_draw_index_multi_instanced_tests, agc_driver_pm4_tests and agc_driver_pm4_replay_tests:
    • This PR (37857ce on main 14eb2cd): 41/41.
    • Control: main 14eb2cd with only this PR's tests: agc_draw_index_multi_instanced fails (multi-instanced draw size is not 60 bytes) and agc_driver_pm4 fails (opcode 0x3a is not DRAW_INDEX_MULTI_INST); the other 39 pass.
    • main 14eb2cd: 40/40 (it has no agc_draw_index_multi_instanced).
  • Validation layer (VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, Vulkan SDK 1.4.363.0, with VK_LOADER_DEBUG=layer, ANYPS5_NO_SHADER_CACHE=1, ANYPS5_REQUIRE_VULKAN=1 and ctest -V) on the same entries: this PR 41/41 and main 40/40; the loader inserted the layer, and neither log has a VUID- line or was not found but was requested by env var VK_INSTANCE_LAYERS.
  • Not run on Linux. tools/check_conventions.py --base origin/main passes.

Overlap

#11290 and #11306 (mine) change the indexed indirect multi draw and the tag headers next to this builder; each merges with this branch without conflicts. 151 open PRs change the same files, mostly the two debt files and libSceAgc's CMakeLists.txt; none of them conflicts with this branch beyond its own conflicts with main, and none changes opcode 0x3a or this builder.

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
  • Fixes: none
  • Unblocks: none known
  • GPUs tested: NVIDIA RTX 4090 (driver 616.92); the change is CPU-side command building and PM4 validation
  • AI-assisted: yes (Claude Code)

…DcbDrawIndexMultiInstanced

On a PS5 the builder writes 15 dwords: a tag write of 0xc6000006, opcode 0x3a with its arguments, initiator 0x80 whatever the modifier, and a closing tag write; GetSize returns 60. AnyPS5 wrote NUM_INSTANCES and DRAW_INDEX_2 under argument meanings nothing confirms. The driver refuses 0x3a, whose field meanings are not known, and names it DRAW_INDEX_MULTI_INST as Linux's nvd.h does.
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

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 2434 docs/dev/debt/libSceAgcDriver.md
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 4449 docs/dev/debt/libSceAgcDriver.md
PR 5021 core/libs/prx/libSceAgc/CMakeLists.txt
PR 5372 docs/dev/debt/libSceAgcDriver.md
PR 5925 docs/dev/debt/libSceAgcDriver.md
PR 6188 core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6280 docs/dev/debt/libSceAgcDriver.md
PR 6300 docs/dev/debt/libSceAgcDriver.md
PR 6412 docs/dev/debt/libSceAgcDriver.md
PR 6481 docs/dev/debt/libSceAgcDriver.md
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp
core/libs/prx/libSceAgcDriver/tests/Pm4.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6804 docs/dev/debt/libSceAgcDriver.md
PR 6808 docs/dev/debt/libSceAgcDriver.md
PR 6959 docs/dev/debt/libSceAgcDriver.md
PR 6977 docs/dev/debt/libSceAgcDriver.md
PR 7256 docs/dev/debt/libSceAgcDriver.md
PR 7449 docs/dev/debt/libSceAgcDriver.md
PR 7604 docs/dev/debt/libSceAgcDriver.md
PR 7642 docs/dev/debt/libSceAgcDriver.md
PR 8223 docs/dev/debt/libSceAgcDriver.md
PR 8314 docs/dev/debt/libSceAgc.md
PR 8319 docs/dev/debt/libSceAgcDriver.md
PR 8395 docs/dev/debt/libSceAgcDriver.md
PR 8542 docs/dev/debt/libSceAgcDriver.md
PR 8592 docs/dev/debt/libSceAgcDriver.md
PR 9356 docs/dev/debt/libSceAgcDriver.md
PR 9387 docs/dev/debt/libSceAgcDriver.md
PR 9431 docs/dev/debt/libSceAgcDriver.md
PR 9432 docs/dev/debt/libSceAgcDriver.md
PR 9508 docs/dev/debt/libSceAgcDriver.md
PR 9592 docs/dev/debt/libSceAgcDriver.md
PR 9672 docs/dev/debt/libSceAgcDriver.md
PR 9673 docs/dev/debt/libSceAgcDriver.md
PR 9674 docs/dev/debt/libSceAgcDriver.md
PR 9675 docs/dev/debt/libSceAgcDriver.md
PR 9676 docs/dev/debt/libSceAgcDriver.md
PR 9748 docs/dev/debt/libSceAgcDriver.md
PR 9842 docs/dev/debt/libSceAgcDriver.md
PR 9891 docs/dev/debt/libSceAgcDriver.md
PR 9892 docs/dev/debt/libSceAgcDriver.md
PR 9893 core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp
core/libs/prx/libSceAgcDriver/tests/Pm4.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9895 docs/dev/debt/libSceAgcDriver.md
PR 9950 docs/dev/debt/libSceAgcDriver.md
PR 10026 docs/dev/debt/libSceAgcDriver.md
PR 10064 docs/dev/debt/libSceAgcDriver.md
PR 10193 docs/dev/debt/libSceAgcDriver.md
PR 10233 docs/dev/debt/libSceAgcDriver.md
PR 10249 core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp
PR 10333 docs/dev/debt/libSceAgcDriver.md
PR 10435 docs/dev/debt/libSceAgcDriver.md
PR 11557 docs/dev/debt/libSceAgcDriver.md

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the packet builder against the attached PS5 capture matrix and the cited gfx10 packet layout. The emitted 15 dwords, tag framing, 0x80 initiator, 60-byte size, and deliberately unchecked/aliased input values match the evidence stated in the PR; the implementation correctly avoids claiming the argument semantics or shader-visible object-ID behavior are understood. The PM4 path now refuses opcode 0x3a with a specific reason instead of executing the prior guessed draw, and debt records the remaining uncertainty. The reported Windows main/control/change tests and same-GPU validation-layer comparison are detailed; hosted eligibility, conventions, intake, labels and report checks pass, while product and relinker jobs were skipped by path policy. No blocking issue found.

@oneandonlydean oneandonlydean self-assigned this Oct 26, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

@frankea One test change: core/libs/prx/libSceAgcDriver/tests/Pm4.cpp:87 adds an expectFailure on a valid, measured DRAW_INDEX_MULTI_INST packet that requires "not implemented". That would fail once the packet is executed. Could you remove it, and keep the opcode-name assertion and the builder-word/GetSize checks? The driver can keep refusing the packet and documenting the unknown semantics in the debt entry.

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The measured builder implementation and its 12-call word/size regression are supported by the PS5 capture evidence in the PR. I agree with the maintainer's follow-up on the PM4 test: tests/Pm4.cpp currently asserts that a structurally valid DRAW_INDEX_MULTI_INST packet is rejected as unimplemented. That makes the test fail as soon as the driver gains support for the packet, even though the builder behavior remains correct. Please remove that rejection assertion while retaining the opcode-name assertion and the focused builder-word/size regression; the conservative runtime refusal and its debt documentation can remain until execution semantics are established.

@frankea

frankea commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

@oneandonlydean @MarcZX10 Done in 549368f: the expectFailure on the valid 0x3a packet is gone. agc_driver_pm4 keeps the opcode-name check and agc_draw_index_multi_instanced keeps the words and sizes of the twelve measured calls; the driver still refuses the packet and the debt entry is unchanged. The branch also merges main ca6e29e (no conflicts), and the focused set and its validation-layer run are rerun on the result: the first bullet under Tested.

AI-assisted: yes (Claude Code)

@oneandonlydean
oneandonlydean merged commit abed035 into boykopovar:main Oct 27, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants