Repository navigation
perf(agc): baseline shader recompilation on registration - #639
Conversation
|
Is there anything I can do to help with this without getting in your way? One idea: before you remove the image/sampler specialization, a test-only PR on |
|
Don't worry about changes to the main branch interfering with my progress. Keep up the good work. If my idea works, I'll resolve the conflicts myself and adapt everything. |
|
Thanks! Then I'll leave this area to you: no new PRs from me on moving shader compilation to registration or on resource specialization, since your approach replaces it. I'll keep working on the rest as before. |
|
Regarding #473, it's still not worth making any architecture changes that don't align with shader recompilation at the registration stage. |
|
Testing notes from PPSA26344 (Ghost of Yōtei v1.008) on the runtime buffer path, in case they help. The pattern. Six compute shaders stop on
Replay through this branch. I replayed the six captured requests through this branch's
Two things showed up.
Not verified. The byte stores through these runtime V#s need write access in the BDA table. As far as I can see, Context. At this point in the title the record table is empty ( |
|
Two things from Ghost of Yōtei (PPSA26344) for this branch: a registration check at cf2bf8e, and the sub-dword buffer stores behind most of its 1.3 fps. I ran
On Two smaller questions. 0x80001e5f00 stores to tables it reads: is the bindless storage table refusal meant to stay? And 0x80002c4500's S# is unnormalized with wrap, which #799 still refuses (2391 skipped dispatches), but all 26 dispatches I dumped sample inside its 1408x1408 T#, so a clamp-to-edge sampler with a deferred fault outside it would be exact. On #822 that flag would be a Details
|
…t, recoverable GPU out-of-memory Large registered ranges are imported in windows; imports carry a use counter, and the least recently used ones that no build still holds are retired when the import budget or the driver runs out (a failed vkAllocateMemory is retried after trimming the buffer pool and retiring imports). Built on main's import ownership: a window is reused only while the registered Range containing it still owns it, and dma-buf imports keep their own unwatch rule. ClearHostImports now uncounts the imports it destroys. agc_driver_host_import_windows covers window reuse, retirement of a remapped range's window and the counter. Rebased onto main a8b032b by CouchRiv (the conflicts with boykopovar#639 resolved; built and tested on Linux with an RTX 4070 Ti and RADV, and on Windows with an RTX 5080). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: CouchRiv <270190108+CouchRiv@users.noreply.github.com>
…code and stages The draw key now has a shape: the key without the user words the stages read. A draw whose key misses but whose shape has an entry copies that entry's decode, re-reads only the user words (and merged-stage pointers), and reuses each compiled stage whose captured reads still hold, compiling the rest. APS5_NO_DRAW_DATA_HITS=1 disables it; APS5_VERIFY_DRAW_DATA_HITS=1 also decodes and compiles in full and throws when the results differ. A shape keeps its previous entries and only points at its newest key, so draws of one shape that alternate between stable user words keep their own key hits. The shape is passed only when data hits are on, so APS5_NO_DRAW_DATA_HITS=1 keeps the previous behaviour exactly. Rebased onto main a8b032b by CouchRiv (the conflicts with boykopovar#639 resolved; built and tested on Linux with an RTX 4070 Ti and RADV, and on Windows with an RTX 5080). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: CouchRiv <270190108+CouchRiv@users.noreply.github.com>
… apertures Main's StoreAddressU32x2..x4 (one BDA lookup per wide store, b958425) bypassed the aperture routing: a flat_store_dwordx2 into the shared aperture went to BDA at 0x8000000000000000 and LDS kept its old value. The wide store now stores each component through the aperture like the 32-bit store, and keeps the single BDA lookup outside the apertures. The aperture test's V# word 3 is 0x31016fac (OOB_SELECT 3, raw), as in main's other execution tests: with OOB_SELECT 0 its output buffer read as out of range after boykopovar#639, which was the failure reported on four drivers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… apertures Main's StoreAddressU32x2..x4 (one BDA lookup per wide store, b958425) bypassed the aperture routing: a flat_store_dwordx2 into the shared aperture went to BDA at 0x8000000000000000 and LDS kept its old value. The wide store now stores each component through the aperture like the 32-bit store, and keeps the single BDA lookup outside the apertures. The aperture test's V# word 3 is 0x31016fac (OOB_SELECT 3, raw), as in main's other execution tests: with OOB_SELECT 0 its output buffer read as out of range after boykopovar#639, which was the failure reported on four drivers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…code and stages The draw key now has a shape: the key without the user words the stages read. A draw whose key misses but whose shape has an entry copies that entry's decode, re-reads only the user words (and merged-stage pointers), and reuses each compiled stage whose captured reads still hold, compiling the rest. APS5_NO_DRAW_DATA_HITS=1 disables it; APS5_VERIFY_DRAW_DATA_HITS=1 also decodes and compiles in full and throws when the results differ. A shape keeps its previous entries and only points at its newest key, so draws of one shape that alternate between stable user words keep their own key hits. The shape is passed only when data hits are on, so APS5_NO_DRAW_DATA_HITS=1 keeps the previous behaviour exactly. Rebased onto main a8b032b by CouchRiv (the conflicts with boykopovar#639 resolved; built and tested on Linux with an RTX 4070 Ti and RADV, and on Windows with an RTX 5080), and onto main 6ac63bf by gjonn (main's free-function decoder with its static-ABI mode, its incremental register key as the shape key). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: CouchRiv <270190108+CouchRiv@users.noreply.github.com> Co-Authored-By: Gjon Gojcevic <7807870+gjonn@users.noreply.github.com>
* fix(shader): keep each BDA page-table walk to one inlined copy per read RADV inlines every SPIR-V function, DontInline included, so every get_bda_pointer call site becomes its own copy of the page-table search, and NIR's copy propagation of variables grows with each copy. The lookup now keeps its search bounds in phis and records its header and range faults from one call site each, picking the first failing check; a byte read and the byte fallback of a span read loop over one lookup instead of unrolling one per byte or dword. (cherry picked from commit 2d3a970) Ported from upstream PR boykopovar#3655 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(shader): decode a GPU-selected formatted load from a format table A formatted load whose V# is selected on the GPU emitted all 77 format cases in its shared function, and drivers that inline every function, like RADV, copied all of them into each call site. The load now reads the format's component count, widths, byte and bit offsets and type from a constant table and runs one path for its four components, with the existing conversion of each (type, width) pair selected at run time. Stores and loads through a prepared V# keep the format switch. (cherry picked from commit 369928d) Ported from upstream PR boykopovar#3656 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(shader): pin GPU-selected formatted loads for every buffer format agc_driver_gpu_buffer_format loads the V#'s records and word3 from memory and moves them into SGPRs with v_readfirstlane, so the recompiler sees a GPU-selected descriptor and the load goes through the format table. It checks buffer_load_format x/xy/xyz/xyzw and d16_xyzw on two lanes for formats 1-77, selectors 0/1/4-7 on 1-, 2- and 4-component formats, loads with a component past NUM_RECORDS, and that formats 0 and 78 stop the invocation before its stores. The expected values were captured on the format switch (2d3a970) and are identical with the table. (cherry picked from commit 6aaae03) Ported from upstream PR boykopovar#3656 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(shader): try the last resolved range first in BDA lookups Every access by guest address calls a lookup that binary-searches the BDA range table: about 11 dependent steps over ~1200 ranges, 34 table loads per access. Since boykopovar#639 every buffer access goes through it. The lookups (read, probe, write and atomic) now keep the last range a search of the invocation resolved in four private variables: guest begin and end, device base and permissions. An access inside that range with the wanted permissions returns base + (address - begin) at once. Anything else (another range, a missing permission, an unmapped, zero-byte or wrapping access) takes the full search with every check and fault it records today, and a successful search refills the cache. The variables only hold what the table says, and no shader writes the table, so the result is the search's own answer. On one title's busiest scene (Windows, discrete GPU), GPU time of the draws 59.5 -> 35 ms per frame. APS5_NO_BDA_LOOKUP_CACHE=1 emits the lookups without the cache. The switch is part of the shader disk cache key. (cherry picked from commit 443583a) Ported from upstream PR boykopovar#2837 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(shader): read adjacent BDA dword loads of one block through one span read A scalar load of several dwords (s_load_dwordx8) becomes one LoadAddressU32 per dword, and each took its own span read: a probe of the page table and a fallback that looks every byte up. Loads in one block with equal address operands and offsets within 16 bytes of the first now share its span read. Instructions that write memory end a group; the address operands are compared by content, since each load carries its own GetAddressResource and SelectU32 values. A title's main-menu pixel shader goes from 136 span reads to 34. AMD's Windows compiler inlines the read helpers at every call, and did not finish that shader before. (cherry picked from commit 4577633) Ported from upstream PR boykopovar#4931 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(shader): read GPU-selected V#s of s_buffer_load as SMEM does An s_buffer_load_dword* whose V# the recompiler reads at run time (a GPU-selected V#, for example one built with v_readfirstlane) went through the vector buffer descriptor checks. A nonzero TYPE in word 3 made it return 0 and record an invalid-descriptor fault, and the base was used with its two low bits, so a base off a dword boundary read shifted bytes. SMEM uses only the base, stride and num_records of the V#, and the two low bits of the base and of the final address are ignored (AMD RDNA2 ISA guide, 30 November 2020, section 7.1 Table 27 SBASE and section 7.2.1 "Reads using Buffer Constant"). The capture-time evaluator already follows both rules, and the bound path already drops the two low bits. For scalar buffer loads the runtime path now masks the base and skips the TYPE check; vector, formatted and atomic accesses keep it. agc_driver_scalar_buffer_gpu_descriptor runs s_buffer_load_dword and s_buffer_load_dwordx2 through such a V# with the base 0 to 3 bytes off a dword and TYPE 0 to 3, in wave32 and wave64. (cherry picked from commit 86fdb52) Ported from upstream PR boykopovar#5161 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(recompiler): read the buffer stride and presence at run time in compute modules (cherry picked from commit 573ba3b) Ported from upstream PR boykopovar#5353 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: EdouardSence <103503538+EdouardSence@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: DotDebian <quentin.waguet@hotmail.fr> Co-authored-by: poonasor <rickypoon89@icloud.com> Co-authored-by: Niko <nikobilger2000@gmail.com> Co-authored-by: Alex Collini <pipodi93@gmail.com>
…code and stages The draw key now has a shape: the key without the user words the stages read. A draw whose key misses but whose shape has an entry copies that entry's decode, re-reads only the user words (and merged-stage pointers), and reuses each compiled stage whose captured reads still hold, compiling the rest. APS5_NO_DRAW_DATA_HITS=1 disables it; APS5_VERIFY_DRAW_DATA_HITS=1 also decodes and compiles in full and throws when the results differ. A shape keeps its previous entries and only points at its newest key, so draws of one shape that alternate between stable user words keep their own key hits. The shape is passed only when data hits are on, so APS5_NO_DRAW_DATA_HITS=1 keeps the previous behaviour exactly. Rebased onto main a8b032b by CouchRiv (the conflicts with boykopovar#639 resolved; built and tested on Linux with an RTX 4070 Ti and RADV, and on Windows with an RTX 5080), and onto main 6ac63bf by gjonn (main's free-function decoder with its static-ABI mode, its incremental register key as the shape key). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: CouchRiv <270190108+CouchRiv@users.noreply.github.com> Co-Authored-By: Gjon Gojcevic <7807870+gjonn@users.noreply.github.com>
What
All game shaders pass through
Driver::RegisterShaderduring game initialization. Recompiling them at this stage would allow draw/dispatch to use precompiled SPIR-V without compiling shaders during gameplay.The current recompiler embeds resource parameters into shader code, so resource changes require recompilation. Even without resource changes, the initial compilation still occurs just before rendering during gameplay. Original shaders read these parameters at runtime. Recompiled shaders must do the same: fixed code, with resources supplied separately.
Compiled artifacts are separated from invocation data, and RDNA-to-SPIR-V compilation runs during shader registration and static ABI preparation. Draw/dispatch require prepared artifacts and fail if missing. Pipeline specialization and direct access for CPU-resolvable buffers are partially implemented; specialization validation and serialization have been added. Vertex fetch restoration and image/sampler optimization remain incomplete.
If successful, this will eliminate the last fundamental source of performance overhead compared to a real PS5.
Tested
There's no noticeable performance improvement on Dreaming Sarah. This may be because shader recompilation has never been a significant feature in this game.
Checklist
NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt.mdfiles or images added to the repository