Repository navigation
Resolve T#/V#/S# on the GPU: a persistent page table, an address-keyed image table, and a miss ring #1612
fisherjoey
started this conversation in
Architecture changes
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Builds on #639 (fixed code, resources supplied separately). Not a replacement for #757/#758/#759 or #1184; the miss ring is something those could use too.
Problem
In a title where every shader receives one SRT pointer and the tables live in a per-frame ring, the driver's per-draw work is dominated by reading and checking descriptor memory that the shader could read itself.
Measured on PPSA01341 (Demon's Souls), tutorial level, Linux, RTX 5070 Ti (595.91.07), main at df16c4c plus local fixes,
APS5_PROFILE_DRAW=1, per 10 s on queue 0:DRAW_INDEX_INDIRECT(4.9 s) and 43kDISPATCH_DIRECT(3.5 s); queue 0 is on-CPU 14.2 s of 20 s; the GPU is busy 200-250 ms per present. 1.1 fps.Graphics::Draw20, of which resources and bindings are most.[capture]sub-phases, 452M word reads in 7.0M captures).ShaderMemory::Regions+TakeRecentRegionsare another 11 % of queue 0 (per-bit scans of the read bitsets).FlattenedSrtbinding, 47 % also use BDA (compute, read-only), 2.1 % take V#s in SGPRs, 0.2 % use a runtime-indexed image table. 545 of 662 receive exactly 2 user-data dwords. Images per variant: median 2, max 56. Samplers: 12 created in a whole run.The recipe, dispatch-cache and draw-cache paths all assume the captured bytes come back; here they do not. Raising the cache sizes to 32k entries changed nothing (run213).
What main and the open PRs already do
BdaPagetable+FaultBufferexist, but the table is a range list built per resource build and bisected per load (BdaResources.cpp,SpirvBdaLookup.cpp); 2437 builds per 10 s here, 97 % served by the 8-entry memo.gpuDescriptorpath (raw loads on main, formats in feat(recompiler): format loads and dword atomics through GPU-selected buffer descriptors #1183, fully in perf(agc): baseline shader recompilation on registration #639).ResourceMetadatawith the 8 T# words, rebuilt per draw, mode picked on the CPU). None reads T#/S# words on the GPU, none keeps a lookup across draws, none has a miss path that creates the view and retries.Proposal
get_bda_pointerkeeps its signature; every BDA load goes from a bisection to two dependent loads. Replaces the per-build range list.BdaAbiversion bump.(address, descriptor words, pc, program, reason), first writer wins per key, read whereCheckFaultruns now. A shader that cannot resolve a descriptor samples zeros, records the miss and continues; it does not kill, so a dispatch's write-back still happens.MIN_LOD_WARN,MIP_STATS_COUNTER_*, the fieldsDecodeTextureResourcealready ignores); value = index into one global update-after-bindSampledImage[]/StorageImage[]/Sampler[], the image mode (perf(agc): baseline shader recompilation on registration #639'sRuntimeImageMode), and a generation. The shader loads the T# through BDA, hashes it, probes the table, switches over the program's own mode list, samples. Miss or mode mismatch: zeros plus a miss record; the driver creates the image through the existing texture cache, writes the array entry, inserts the key. Next use hits. Currency (write-watch stamps, DCC key proofs) is checked once per flip over the whole table instead of per build; a changed surface is refreshed in place and its generation bumped.ReadConstslots become the original scalar loads through BDA from the user-data pointer.FlattenedSrtstays for mapping blocks until nothing needs it.What stays on the CPU: vertex and index buffers, render targets and depth surfaces, push constants and
ShaderData, pipelines, host imports and mirrors, the flush hook, labels. Buffer writes are learned from the fault buffer's page set after completion, as for BDA shaders now; the per-dispatch device-local staging goes away with them.Expected on this title: dispatch 81 -> ~22 us, drawn draw ~115 -> ~27 us, level 1.1 -> 3-4 fps CPU-bound, then GPU-bound at 4-5 fps (the GPU's 2.6k small dispatches per frame are the next limit). About 3x; not a route to 30 fps by itself.
Device side, NVIDIA 595.91.07: update-after-bind sampled images 1,048,576,
runtimeDescriptorArray,nullDescriptoranddescriptorBufferall available;maxSamplerAllocationCountis 4000, which the sampler array has to respect with an LRU. Lavapipe has update-after-bind andruntimeDescriptorArray.Incremental steps
perf(agc): one page table per address space (independent of perf(agc): baseline shader recompilation on registration #639; conflicts with perf(recompiler): loop the BDA byte stores and written-page probes #1265/perf(shader): share the BDA dword span read between read sites #1267/perf(recompiler): load the written-page slot before the CAS #1280 inSpirvBdaLookup.cpp).perf(agc): texture cache key without the streaming-feedback T# fields (measured 0.80 -> 1.10 fps and 0 budget evictions on this title; independent).feat(agc): the miss ring (small; feat(agc): implement image and sampler tables indexed at runtime offsets #757/feat(agc): implement deferred faults for reads through null SRT pointers #759's poison path could report which descriptor was poisoned).We would do 1 and 2 now and 3 after, and prototype 4-6 on a local branch on top of #639 to bring numbers.
Questions
OpSwitchover the program's own mode list, keyed by a table entry, fit the fixed-code rule of perf(agc): baseline shader recompilation on registration #639, or should the mode stay a CPU-written field (in which case the CPU still decodes every T# per draw, and the capture is what remains)?BdaAbiand every BDA shader's lookup?VK_EXT_descriptor_indexing(update-after-bind,runtimeDescriptorArray) for the fixed-code path acceptable, with the current path as the fallback?AI-assisted: yes
All reactions