Repository navigation
perf(agc): cheaper shader-memory captures per draw - #1193
Conversation
7504177 to
80f120a
Compare
|
Parked as a draft: the capture path changed a lot with #639 (captures now run against the prepared invocation), so this needs a rework rather than a rebase. I'll mark it ready again once it is ported. |
|
Thanks for taking the three read-path points in. One thing for Linux: the rebased head doesn't build there. static_assert(sizeof(IrResourcePlan) == 712, "IrResourcePlan changed: update EncodePlan and DecodePlan");With that it builds, and the LLM assistance used. |
The per-draw capture of a shader's memory (the SRT walk and the guest pages it reads) cost about 15 us per draw packet in GTA V Performance mode; it now costs about 6.6. - The SRT evaluator memoizes per plan value in a thread's reused, stamped array instead of a hash table allocated and grown on every walk (evaluate 5.3 -> 3.6 us per capture). - ExtractPlan also stores the plan's values as 32-byte records with argument ids, and the walk evaluates that compact copy: the IR walk spent most of its time in cache misses (3.6 -> 2.7 us). The evaluator is a template over the node view, so both walks share every opcode's semantics, memo, cycle and failure rules. A plan that cannot be expressed compactly keeps the IR walk. APS5_VERIFY_SRT_COMPACT=1 walks both and throws on any difference. - ShaderMemory's valid/read/recent marks are arrays of 64-bit words scanned with countr_zero instead of bitsets tested bit by bit (about 1 us per draw packet). - Captured pages come from a per-thread pool, are found through a short vector with a last-page shortcut, and a page no recorded GPU work writes gets the flush hook a whole-page read would make, then has each 64-byte block copied when a word of it is first read (about 0.3 us per packet). Pages with pending GPU writes and unmapped pages are read as before. - APS5_PROFILE_DRAW splits the capture time into SRT evaluation and materialization, and prints the [capture] line once per 10 s instead of every 500 captures (it cost about 2 us per capture). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-Authored-By: Gjon Gojcevic <7807870+gjonn@users.noreply.github.com>
…'t need A lazy page was found accessible whole when it was made, and its blocks are copied from guest memory on demand; accessible() no longer asks GuestMemory::Accessible for it again on every word the SRT walk checks. A read looks up the code and header snapshots only when it can touch them, and the read and page counters run only under APS5_PROFILE_DRAW, where the [capture] line prints them. Raghav314 measured the three on Hades II (PPSA36082, RTX 4090, Linux): capture_resources 2.48 -> 2.14 ms per frame, frame p50 12.34 -> 11.60 ms. Co-Authored-By: Raghav314 <71544502+Raghav314@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…stdc++ Co-Authored-By: Claude <noreply@anthropic.com>
…luator on main Co-Authored-By: Claude <noreply@anthropic.com>
ce22992 to
eacacc6
Compare
|
Rebased onto current main (0afa6a6) and force-pushed; the previous head is kept at
Tested on Linux (RADV 660M, |
|
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.
|
|
One thing we noticed while porting #1201's version, in case it helps: since this merged, a shader-memory capture page that's marked lazy skips the multisample read guard. Before (042eb7c), a page a capture first touched was read whole with On We haven't seen a title hit it. Running the guard for the page when it's marked lazy would keep the check. Happy to send the small fix with a test if you'd like one. |
Brings the series onto main 4e9a2a8 (boykopovar#1193, boykopovar#1201, boykopovar#1202, boykopovar#6060, boykopovar#6333 and the descriptor word tracking of 682b07e). Resolutions: - ShaderMemory: the column memo's supplied and spilled blocks now sit on boykopovar#1193's page vector, page pool and word masks. Pages are found by binary search, since a spilled block can add many pages, and a block placed into a lazily read page fills the rest of the 16-word blocks it touches, so a later lazy read never overwrites placed words. - ResourceMaterializer and SharedValues: the selected-record path and the words index carry availableDwordMask, as the snapshot hash does. A selected record's dwords past the end of its buffer count as read zeros, which is what a scalar buffer load returns there, so a key past the heap still selects a null image without reading the heap. - DescriptorCache: kept table sets live beside boykopovar#1201's recycled sets. - Tests and CMake: both sides' tests are kept.
Upstream main now carries the final versions of PRs boykopovar#1193, boykopovar#1201, boykopovar#1202, boykopovar#5216 and boykopovar#4465, so their draft copies on this branch give way to main's: the snapshot ring, the pipeline identity memo, the code-hash memo and APS5_RESIDENT_LINEAR_TARGETS are gone with them. The port of boykopovar#4943 (draw-cache relocation across moved user-data pointers, not merged upstream) is dropped: the draw prefetch replaces it. Kept on top of main: the arm64 FEXCore runner and macOS libraries, the MoltenVK settings, rect-list clip and cull distances, GPU-ordered in-place vertex inputs, the draw pipeline's spin-first locks and lock-free overlap check, SharedVector binding flags, the result-memo admission, the draw prefetch (APS5_DRAW_PREFETCH) and the 1D T# sample modes. New for arm64: fegetenv from the runner's guest control words, and the direct-memory reuse map on macOS. On macOS the guest buffers keep one descriptor array (no buffers_coherent or buffers_u8 aliases): MoltenVK aliases a descriptor array only through Metal argument buffers, which the driver turns off, and Vampire Crawlers' first compute pipeline failed to compile.
|
@EagleBearerYS you're right, thanks for catching it. On |
Brings the series onto main 4e9a2a8 (#1193, #1201, #1202, #6060, #6333 and the descriptor word tracking of 682b07e). Resolutions: - ShaderMemory: the column memo's supplied and spilled blocks now sit on #1193's page vector, page pool and word masks. Pages are found by binary search, since a spilled block can add many pages, and a block placed into a lazily read page fills the rest of the 16-word blocks it touches, so a later lazy read never overwrites placed words. - ResourceMaterializer and SharedValues: the selected-record path and the words index carry availableDwordMask, as the snapshot hash does. A selected record's dwords past the end of its buffer count as read zeros, which is what a scalar buffer load returns there, so a key past the heap still selects a null image without reading the heap. - DescriptorCache: kept table sets live beside #1201's recycled sets. - Tests and CMake: both sides' tests are kept.
Since #1193 (4651980) a capture copies a page in 64-byte blocks as it reads them, instead of reading the whole page with GuestMemory::Read the first time it touches it. The page now only gets FlushGpuWrites, which runs the flush hook but not the read guard, so a capture over a multisampled color target's memory, whose samples are only on the host, read the stale guest bytes silently where it threw before. ShaderMemory::page runs GuardRead over the page where the whole-page read stood, before the flush, as Read does. The check stays once per page a capture touches, as before #1193. The block copies need none: a pooled page is reset and goes through page() again, and later reads of a page were not checked again before #1193 either. The test captures a shader whose SRT lies in a live 4x target's memory, then one whose SRT lies just past it, then the first again on the pooled page.
What
The per-draw capture of a shader's memory (the SRT walk and the guest pages it reads) cost about 15 µs per draw packet in GTA V Performance mode. It now costs about 6.6 µs.
ExtractPlanalso stores the plan's values as 32-byte records with argument ids, and the walk evaluates that compact copy. The IR walk spent most of its time in cache misses (3.6 to 2.7 µs). The evaluator is a template over the node view, so both walks share every opcode's semantics, memo, cycle and failure rules. A plan that cannot be expressed compactly keeps the IR walk.APS5_VERIFY_SRT_COMPACT=1walks both and throws on any difference.ShaderMemory's valid/read/recent marks are arrays of 64-bit words scanned withcountr_zero, instead of bitsets tested bit by bit (about 1 µs per draw packet).APS5_PROFILE_DRAWsplits the capture time into SRT evaluation and materialization, and prints the[capture]line once per 10 s instead of every 500 captures (it cost about 2 µs per capture).APS5_PROFILE_DRAW(@Raghav314's review, his commit as co-author; on Hades II, RTX 4090:capture_resources2.48 -> 2.14 ms per frame).denseValueIdsand rebuilds the compact plan on load (ResourceMaterializer::CompactPlan); format 31.Tested
Windows 11 Pro, AMD Ryzen 5 7600, NVIDIA GeForce RTX 5080 (driver 32.0.16.1074), 32 GB, MinGW-w64 GCC 15.2.0.
main872f523 (2026-10-14) on its own; it no longer depends on perf(agc): linear color targets in registered guest memory get a resident image #1191.agc_*andshader*suite passes on Windows: 504 tests, 1 skipped (agc_driver_stencil_ref_export). Not run here: the relinker's Python tests (my local Python does not find their helper modules) andguest_gameupdate.mainand of this PR (GTA V PPSA04264 Performance mode, the same scene, warm caches, traces attached): to follow in a comment.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)🤖 Generated with Claude Code