Skip to content

feat(libSceAgcDriver): execute COND_WRITE - #977

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
hugodearaujo:feat/agc-driver-cond-write
Oct 7, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
hugodearaujo:feat/agc-driver-cond-write

Conversation

@hugodearaujo

Copy link
Copy Markdown
Contributor

What

The AGC driver rejected COND_WRITE (opcode 0x45) as "not known in the reference". It now validates and executes it when the poll and the destination are both memory, the case sceAgcCbCondWrite writes (#975):

  • Validate: 9 dwords; control holds only the compare function, the poll space and the write space; poll space memory; compare function 0-6; write space 1 (memory); 4-byte aligned poll and write addresses. Register polls and register or scratch destinations are rejected, as register-space WAIT_REG_MEM and register or GDS WRITE_DATA destinations already are.
  • Execute: reads the poll dword through GuestMemory::Read (range synchronization, read site Wait), compares it with waitCompares, and on a match writes the data dword through GuestMemory::Write. Dwords 1-5 of COND_WRITE have the same layout as a 32-bit WAIT_REG_MEM: control, poll address, reference and mask.
  • Ordering: 0x45 joins the opcodes that drain before running on the CPU, next to WRITE_DATA, COPY_DATA and DMA_DATA, and AccessesMemory. The drain records queued labels and waits for earlier GPU work, so the poll read sees values that earlier packets wrote on the GPU, and the write lands only after earlier GPU work that may read the destination.
  • Catalog: COND_WRITE is named in Pm4Opcodes.hpp.

This PR does not depend on #975: the tests build the packets by hand.

Tested

Linux (Arch, GCC), Release with BUILD_TESTING=ON, on current main. In agc_driver_pm4_tests:

  • The catalog count goes from 54 to 55.
  • testConditionalWrite:
    • header 0xc0074500, name, not unsupported, AccessesMemory;
    • all seven compare functions on a masked poll value, writing only when the comparison passes;
    • rejection of a short packet, reserved control bits, a register poll, compare 7, write space 0 or 2, and a misaligned poll or write address;
    • the poll read and the write both go through the range synchronization hook.
  • testConditionalWriteSubmission: through sceAgcDriverSubmitDcb, a COND_WRITE sees the value an earlier WRITE_DATA of its queue stored, and a COND_WRITE whose comparison fails writes nothing.

Mutations: inverted comparison, any write space accepted, 0x45 removed from AccessesMemory, the mask written instead of the data, the reserved-bits check removed, and the poll read bypassing the synchronization each make the test fail.

One mutation is not caught: removing 0x45 from the drain list. Without a Vulkan device, queued labels are written by the CPU between packets, so the order holds anyway in this test. The drain matters when labels are recorded on the GPU. I did not find a device-backed test that covers it; pointers are welcome.

Full ctest: 362/362 passed. A simulated merge with every open PR shows no conflict; the new tests are placed apart from #898, which adds tests to the same file. Not tested with a title.

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 in TechnicalDebt
  • 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
  • AI-assisted: yes

🤖 Generated with Claude Code

COND_WRITE (opcode 0x45) with a memory poll and a memory destination is
validated and executed: the driver drains the queue like WRITE_DATA,
reads the poll dword through the range synchronization, compares it
masked with the reference like a 32-bit WAIT_REG_MEM, and writes the
data dword on a match. Register polls and register or scratch
destinations are rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hugodearaujo

Copy link
Copy Markdown
Contributor Author

Usage by titles: the packet comes from sceAgcCbCondWrite, which OpenAGC game_compat.c lists for New Joe & Mac Caveman Ninja (PPSA02801) and PS5PCEM agc_table.zig binds for a shipped title (details in #975). Without this change, a title that builds the packet fails when it submits it.

@github-actions github-actions Bot added the agc libSceAgc* label Oct 6, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks. The validation mirrors WAIT_REG_MEM and WRITE_DATA well, and it's good that the uncaught drain mutation is stated openly.

Same question as on #975: which title submits COND_WRITE? Since the drain change only matters when labels are recorded on the GPU, a title that emits the packet is the only way to check that part. If you have one, please name it.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks, same as on #975: I asked before I'd seen your note. PPSA02801 building the packet through sceAgcCbCondWrite is a good enough reason.

I'll approve the CI run and check it on Dreaming Sarah together with #975. No changes needed from you for now.

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

Labels

agc libSceAgc*

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants