Repository navigation
Conversation
|
This pull request is labeled |
|
@Raghav314 thanks for the clear notes on what was dropped and what #9508 keeps. We'll measure #9508 on Resident Evil Village (PPSA01556) on our RTX 3070 Laptop GPU, whose driver (617.42) exposes |
|
One checklist detail appears inconsistent with the repository rule for |
|
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.
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.
|
MarcZX10
left a comment
There was a problem hiding this comment.
The updated body now satisfies the performance-report format with same-scene summary lines, trace attachments and digests; I withdraw that earlier concern. I am requesting changes for a separate correctness gap in the non-heap fallback. In core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp, shader.heap is nullable and heapStage is set when any stage is in GPU mode. In Complete()/build(), the if (heapStage) RefreshHeapStage(shaders); call applies to the entire shader span. RefreshHeapStage (Execution/src/Driver/Draw/HeapStages.cpp) requires every shader stage to be GPU mode and to have a heap-backed descriptor plan. But this PR’s stated fallback explicitly allows a stage whose module cannot map onto the heap, or a mixed draw, to use the existing per-draw path. A graphics pipeline with one heap-eligible stage and one ineligible stage therefore appears to take the documented mixed/per-draw fallback and then hit a Require while refreshing all stages. Please either make refresh handle the mixed set by skipping non-heap stages (and keep their per-draw resources valid), or route the entire draw through the per-draw path before heapStage is marked. Add a regression with mixed eligible/ineligible stages on an extension-capable device, or a test of the selection/refresh helper. Hosted relinker matrix and ASan, eligibility/conventions/intake/report checks pass; product Linux/Windows jobs are skipped. The Windows runtime and title measurements in the body are author-reported. I did not run local GPU tests.
|
@MarcZX10 thanks for looking. LLM assistance used. |
MarcZX10
left a comment
There was a problem hiding this comment.
The author has added the missing mixed-stage regression and clarified the path it exercises. I reviewed that update and withdraw my earlier mixed-stage correctness concern: the test now covers the heap-eligible vertex plus an unmappable fragment, verifies image output, checks one retry to per-draw, and checks the repeated pair remains per-draw.
I am keeping the review at changes requested for the new hosted Windows relinker result. That run completed 232/233 tests and failed only conventions_function_bodies_shard_11 by timeout. Since the other 232 passed and the failure is a timeout, it may be a runner-load issue, but the check is currently red; please rerun it or provide a green Windows matrix result before merge. The other hosted relinker jobs (Ubuntu, macOS arm64/x86_64 and ASAN), conventions, eligibility, labels and report checks pass. Linux/Windows product jobs are skipped. The Hades II trace evidence and Vulkan validation comparison are author-reported; I did not run local GPU tests.
|
I reviewed the new mixed-stage fallback regression and withdraw my earlier correctness concern; it now covers the eligible/unmappable stage pair and per-draw retry. The latest Windows relinker run is still red: 232/233 tests passed, with |
Rebuilds boykopovar#7642's device columns inside boykopovar#9508's GPU-mode stages. The recompiler takes SpirvTarget::deviceImageTables (part of the recompile cache key). ResourceTracker plans a V# table column on the device table only in a GPU-mode stage that reads its table map by address (gpuResolvedImages and descriptorMemory, vertex, pixel or compute, Int64 and buffer device addresses), the reverse of boykopovar#7642's gate: plain column, sampled reads, gathers, queries and loads only, a direct S# that is not forced unnormalized, no depth compare, packed or by-element access. ImageResource::deviceTable marks such an image, and the result's deviceImageTables tells the driver. A device column needs no table snapshot, capture memory or map records: the snapshot and ResolveImageTables skip it, so its map part stays empty, and a stage whose only tables are device tables still gets its table map, which carries the trailer. Each device-table image's interface is registered when a result is materialized, and a direct S# paired with one gets its sampler class as specialization constant DeviceSamplerClassBase plus the sampler index. The access calls device_image_lookup with the T# words, the interface id, the sampler class and the pc. It takes the bucket and miss ring chunk addresses from the four trailer words before HeaderElementBase and reads both through buffer device addresses; a map without a table (zero addresses) misses and records nothing. A hit samples the heap table array of the mode's kind at the bucket's slot, which aliases the GPU heap's first sampled binding, so the device table now takes its slots in that kind. A miss or a faulted bucket reads zeros and appends one record per key and batch to the ring; the driver resolves it for the next batch, and a program the table cannot serve is recompiled on boykopovar#9508's CPU map. writeHeapTableMap prepares the device table for the open batch and writes its trailer for a stage that reads it. The shader disk cache encodes the new fields; its format moves to 48. agc_shader_device_image_tables compiles a compute and a pixel program reading a 2,000-entry V# table at a user-data index with a direct S#: in a GPU-mode stage the column reads the device table, the capture reads no record, the map has no record for it, the module calls the lookup keyed by the registered interface and the S#'s class, reads all four trailer words, declares no set 2 or 3, aliases the first sampled heap binding and passes spirv-val; with the option off, or on the per-draw path, the column stays on the CPU map, as do a storage write, a depth compare, a sampler table and an unnormalized S#. Based on fable-void's d35b01a and 81119ee (wip/new-gpu-path).
fb07b25 to
d52c109
Compare
|
The new performance evidence in the PR still does not meet the repository report rule for a |
MarcZX10
left a comment
There was a problem hiding this comment.
The new mixed-stage fallback test resolves my previous code-path concern, and the Linux test report is useful. I am requesting changes for the repository-required evidence only: this perf PR needs compliant same-scene/same-machine warm-cache summary lines, attached base/change traces and tools/trace_digest.py output, and the AGC main/change focused validation-layer comparison. Please add those artifacts or remove the performance claim/perf labeling. I did not run local tests; hosted Linux/Windows product jobs were skipped.
d52c109 to
23b2a3f
Compare
Rebuilds boykopovar#7642's device columns inside boykopovar#9508's GPU-mode stages. The recompiler takes SpirvTarget::deviceImageTables (part of the recompile cache key). ResourceTracker plans a V# table column on the device table only in a GPU-mode stage that reads its table map by address (gpuResolvedImages and descriptorMemory, vertex, pixel or compute, Int64 and buffer device addresses), the reverse of boykopovar#7642's gate: plain column, sampled reads, gathers, queries and loads only, a direct S# that is not forced unnormalized, no depth compare, packed or by-element access. ImageResource::deviceTable marks such an image, and the result's deviceImageTables tells the driver. A device column needs no table snapshot, capture memory or map records: the snapshot and ResolveImageTables skip it, so its map part stays empty, and a stage whose only tables are device tables still gets its table map, which carries the trailer. Each device-table image's interface is registered when a result is materialized, and a direct S# paired with one gets its sampler class as specialization constant DeviceSamplerClassBase plus the sampler index. The access calls device_image_lookup with the T# words, the interface id, the sampler class and the pc. It takes the bucket and miss ring chunk addresses from the four trailer words before HeaderElementBase and reads both through buffer device addresses; a map without a table (zero addresses) misses and records nothing. A hit samples the heap table array of the mode's kind at the bucket's slot, which aliases the GPU heap's first sampled binding, so the device table now takes its slots in that kind. A miss or a faulted bucket reads zeros and appends one record per key and batch to the ring; the driver resolves it for the next batch, and a program the table cannot serve is recompiled on boykopovar#9508's CPU map. writeHeapTableMap prepares the device table for the open batch and writes its trailer for a stage that reads it. The shader disk cache encodes the new fields; its format moves to 48. agc_shader_device_image_tables compiles a compute and a pixel program reading a 2,000-entry V# table at a user-data index with a direct S#: in a GPU-mode stage the column reads the device table, the capture reads no record, the map has no record for it, the module calls the lookup keyed by the registered interface and the S#'s class, reads all four trailer words, declares no set 2 or 3, aliases the first sampled heap binding and passes spirv-val; with the option off, or on the per-draw path, the column stays on the CPU map, as do a storage write, a depth compare, a sampler table and an unnormalized S#. Based on fable-void's d35b01a and 81119ee (wip/new-gpu-path).
|
@MarcZX10 thanks, updated the description. Your points on warm caches,
What changed:
LLM assistance used. |
MarcZX10
left a comment
There was a problem hiding this comment.
Correction to my 02:04 review: I rechecked the current body against the performance form and the author's 02:54 response. I withdraw my claim that the required summary lines, warm-cache capture details, trace digests, or AGC validation-layer comparison are missing. The body now reports the same-machine comparison and 54 matching base tests / 134 change tests; the layer is found in both logs with zero VUID errors. Hosted relinker jobs also now pass, resolving the earlier Windows timeout.
Two items still need correction before merge:
- The Performance report links appear swapped: the main bullet names
aps5-trace-de49f41349a9-624652.json.gzbut links to the568369d27a2e-625697heap capture; the heap bullet names568369d27a2e-625697but links to thede49f41349a9-624652main capture. Please correct the attachments/links so each scenario points to its own trace. The baseline trace also reportstrace_dropped: true; please keep that limitation explicit when describing its trace detail. - The source and all new validation evidence are still based on main
a6dcea9279(plus the declared #6946/#6300 overlays), while current upstream main isc61d20f54801efc3798555829f6fedcff71ad7ff. Please update the branch and rerun conventions/overlap and the focused validation comparison on the updated tree. For the performance comparison, retain the #6300 integration needed to reach the same Hades II scene and identify the exact current-main integration base in the report.
The mixed-stage fallback regression and the latest hosted relinker matrix resolve my earlier code-path and timeout concerns. I did not run local GPU tests.
|
@Raghav314, an overlap note from #9503 (mine), which the reviewer asked me to coordinate with you. Both PRs change the same
The two changes don't contradict each other. Whichever lands second keeps both: your wrapped call, placed inside the lambda after #9503 is a one-line fix that has already been validated on two AMD drivers, so I'd suggest it goes first. Your rebase would then be just moving the wrapped call into the lambda. If you'd rather land first, I'll rebase #9503 onto yours instead. Either order works for me; tell me which. |
|
@Raghav314 a merge-order note for two small drafts next to this PR:
Neither side needs to wait, and the rebase is ours either way: if this PR merges first, #9563 and #9564 rebase onto it, and if they merge first, I'll put the resolved merge on a branch of my fork for you to take. Does that work for you? |
The heap branch encodes new artifact (gpuResolvedImages, sampler slot and buffer address dwords, pushed data, push items, data-address push, BDA stores), invocation (heap slot words) and binding-layout fields (data address, slot and address dword counts, pushed data, heap storage images, GPU-resolved images), so its entries need their own format number above main's.
|
@Raghav314 New overlap coordination: #16086 restores nonuniform first-draw initialization as a small follow-up to #15986, without the reverted constructor guest read or sampled-layout rewrite. I compared your Proposed order: #15985 → #15986 → #16086 before #9508 for this shared section. If the heap work lands first, I can adapt the initializer to it. This is a coordination proposal, not an agreed dependency. Main/parent comparisons, failing controls and two-vendor evidence cover the normal uploader only, not a combined heap run. AI-assisted: yes (Codex). Snapshot readback coordination: #6987 is now adapted at |
|
@Amoenus Thanks for the heads-up. No coordination needed: whichever PR lands first, the other rebases onto it. If #16086 lands before #9508, I'll keep the |
… device A nearly full GPU heap used to send every draw down the per-draw path until owners released slots. Like vkd3d-proton, which leaves descriptor lifetime to the heap's owner, the heap now frees the image slots no draw has used for two submissions. An evicted slot moves its generation, so templates holding it take fresh slots on their next draw, and it is handed out again only after the submission that followed the eviction completes. Draws that keep using a slot refresh it on every heap stage write. APS5_NO_HEAP_EVICTION turns it off. The image heap length is now the HeapImageSlots specialization constant instead of a constant in the module, the way vkd3d-proton sizes its heap arrays at pipeline creation. Pipelines set it to the device heap's slot count, which grows past the ABI's 233472 into resource heap space the shader data ring leaves unused, up to 524288 slots. Shader disk cache format 58. The 10 s [gpu-heap] line adds the slot count, evictions and their waits, and ring waits, the largest bytes one batch wrote into the ring and the largest span.
…ers them Review of the eviction found that the margin counted evicted slots before any submission had a serial for them, so a busy batch could drain the free list without refusing the heap and fail a draw with "the global image heap is full". The margin now counts only evicted slots a submission covers, eviction starts at twice the margin, scans at most once per submission, needs the recorder timeline, and reclaims once per submit instead of per taken slot. A blocking reclaim waits only on submitted serials, heap stages refresh their slots once per submission, and the validator checks the image table alias is an array before reading its length. The [gpu-heap] line adds ring MiB written per 10 s and image slots held.
A draw that reuses a ring span it wrote earlier reopens the span's chunk, so the chunk takes the serial of the newest batch, and when the ring came round to it later it waited for a batch the GPU had only just started: every ring wait in a MCD2 run was of that kind. Like vkd3d-proton, which takes another scratch buffer instead of waiting for one the GPU still reads, the ring now skips such a chunk. Only when every chunk is taken or busy does it wait, and then on the chunk whose batch is oldest. APS5_HEAP_RING_MIB sets the ring size between 4 and 64 MiB (default 64, less when it sits in a smaller resource heap). MCD2 writes up to ~10 MiB in one batch and stops with "one batch's shader data exceeds its ring" at 8 MiB, so the default stays as large as the heap allows.
Shader data, flattened SRT data and table maps lived in the descriptor ring inside the resource heap. A batch writing more than the ring held aborted the run, and the ring took heap room from images. They now go into a pool of 2 MiB blocks, the way vkd3d-proton's uniform upload pool works. A batch takes new blocks instead of waiting, a span larger than a block gets a block of its own, a block is reused once the GPU has finished the batches that read it, and idle blocks over 32 MiB are freed. Shaders read the data through 64-bit addresses pushed per draw (shader data, table map, addressed flattened SRT), so the shared ring address push item is gone; the push-data size stays about the same. The ring inside the heap now holds only per-draw buffer descriptors and defaults to 16 MiB. The shader cache format moves to 59.
…ptor ring A batch writing more per-draw buffer descriptors than the ring holds aborted the run. Before each draw and dispatch the recorder now submits once the open batch holds half the ring's chunks, and one draw may take at most a quarter of the ring, so a batch always fits.
|
Pushed five commits on top of 8be80b2. Heap image slots: sized per pipeline, plus eviction (
Ring: skip busy chunks (
Shader data in an upload pool (
Early submit when the descriptor ring fills (
Tested (RTX 4090, same machine and scene)
|
A sampler whose four descriptor words are constants in the shader code still took a sampler heap slot, its index was patched into the shader data on every draw, and the shader loaded it before fetching the sampler from the heap. vkd3d-proton maps a root signature's static samplers as samplers embedded in the pipeline's descriptor mapping instead (HEAP_WITH_CONSTANT_OFFSET with pEmbeddedSampler). GPU heap stages now do the same. The recompiler marks a sampler whose words are all immediates, gives each of its elements its own sampler variable in set 6, and records the words (with the point-filter form for point-sampled elements) in the sampler binding. Pipeline creation maps those variables to embedded samplers built from the words and the binding's compare, unnormalized and border flags, deduplicated across pipelines. Draws no longer take or patch a slot for them. Past the device's maxDescriptorHeapEmbeddedSamplers a constant sampler reads a sampler heap slot at a fixed offset instead. APS5_NO_EMBEDDED_SAMPLERS turns it off; APS5_EMBEDDED_SAMPLER_CAP lowers the embedded limit for testing. Shader disk cache format 60.
|
@Raghav314 I have a question on 73c5187 (embedded constant samplers), from reading it against the compute pipeline cache. Two compute stages that differ only in a constant sampler's words look like they share one pipeline, so the second one would sample with the first stage's embedded sampler. What I'm reading at 73c5187:
A check. I compiled
A possible fix, in case it helps:
Could you take a look? |
…tity RecompileResult::Identity() mixed the SPIR-V hash, specialization constants, subgroup flags and each binding's set, binding, kind and count, but not the embedded sampler state. ComputePipelineKey::Of and the pipeline journal key on that identity, so under APS5_GPU_HEAP two stages with the same code and different constant-sampler words or flags shared one pipeline and sampled with the first stage's embedded sampler. Mix samplerEmbedded, samplerEmbeddedWords and the depth-compare, unnormalized, integer-border and coordinates-ignored flags the embedded sampler is built from, and test that the words, the element index and each flag change the identity. Raised by EagleBearerYS. Fix-up on Raghav314's change.
When fewer than a margin of image or sampler slots were free, the GPU heap refused every draw and dispatch until owners released slots, and they took the per-draw descriptor path meanwhile. vkd3d-proton never leaves heap mode, and our heap can always make room: slots are only a cache of descriptors whose owners are still alive. Now the nearly full state asks the next draw or dispatch entry to submit the open batch. At that point no draw is being prepared, so the heap evicts the least recently used slots outright; they retire with that submission's serial, and a slot request that finds no free slot waits for it to complete. Other submissions, including ones made while a draw is being prepared, keep the two-idle-submission rule, so a slot a draw already took is never freed under it. Sampler slots get the same eviction as image slots (use epochs, touch on lookup and on template reuse, retiring entries), except samplers whose slot index a pipeline bakes in (the embedded-sampler fallback), which are pinned. Eviction runs only under the GPU mutex. A heap whose recorder has no timeline cannot evict and still refuses, as does every heap under APS5_NO_HEAP_EVICTION. The [gpu-heap] line counts evicted samplers and pressure submits.
APS5_NO_EMBEDDED_SAMPLERS, APS5_NO_HEAP_PUSHED_DATA, APS5_NO_HEAP_DEVICE_DESCRIPTORS and APS5_NO_HEAP_EVICTION only switched back to older behaviour that the heap no longer needs. Constant samplers are always embedded, heap stages always push their hottest shader data, the heap and data pool are always tried in device-local host-visible memory first, and a nearly full heap always evicts. Only a heap whose recorder has no timeline still refuses draws when full. The capped-heap test variants now run the eviction path (draws at five image slots, image tables at eight) instead of the refusal path.
A fragment stage that merges into its own color target (a partial write of a packed target, or blending unclamped normalized color) reads and writes the target as a storage image whose descriptor comes from the draw's color target state, not from guest memory. The heap template path built its key and per-draw binding words from the captured snapshot alone, which lacks that image, so every such draw on the heap threw an out-of-range error and failed. MaterializeHeap now appends the merge target descriptors the same way the per-draw path does, and keys the template on them only through the address-free binding plan words, so draws merging into different targets of one form share a template and carry their own target in the per-draw words. The heap walk memo keys and compares the merge descriptors too: it is keyed on the variant, which no longer names the target, and otherwise served the first target's words to a draw into another one, which then stored into the wrong image. The heap draw test gains an unclamped blend draw into two targets, which checks the blended texels, that the heap took the stage and that the second target reused the first one's template.
Identity() keyed compute pipelines, graphics pipelines and pipeline libraries alike, so stages differing only in a constant sampler shared one pipeline on both paths. The compute heap test now dispatches the border clamp and wrap forms of the constant-sampler kernel and checks each one's sample, and requires the clamp, wrap and mirror forms to have distinct identities (wrap and mirror differ only in the first sampler word, since the recompiler clears the border color word of samplers that never clamp to the border). The heap draw test draws a border clamp and a wrap form of one fragment shader through PM4 and checks the pixels. Both fail in heap mode without the identity fix.
|
@EagleBearerYS You're right, thanks for tracing it. Dean's 31d84cf fixes it in Graphics was affected as well. Graphics pipelines and pipeline libraries are keyed on 57108a0 adds end-to-end tests on top of Dean's unit test:
Before the rebase onto Dean's commit, the focused set passed 639/639 in default and heap mode with 0 |
What
An opt-in GPU descriptor heap,
APS5_GPU_HEAP=1, built onVK_EXT_descriptor_heap. Stages that can use it stop rebuilding and binding descriptor sets on the CPU for every draw and dispatch. It builds on the draw pipeline (#6946, merged) and replaces #6947, which is closed.APS5_HEAP_SLOT_CAP) and one sampler heap (2048 slots, D3D12's limit), both in device-local host-visible memory when the device has it (APS5_NO_HEAP_DEVICE_DESCRIPTORSkeeps them in host-visible memory). Per-draw shader data, the flattened SRT and the image table map go into a ring. When the resource heap has room for the ring (at mostmaxResourceHeapSize, in whole 2 MiB chunks; 20 MiB on NVIDIA), the ring sits inside the resource heap after the descriptors, and each stage reads its shader data as a uniform buffer mapped from heap data (VK_DESCRIPTOR_MAPPING_SOURCE_RESOURCE_HEAP_DATA_EXT) at the ring offset already in its push data, the way vkd3d-proton reads root constant buffers as uniform buffers. Each stage also writes a run of storage buffer descriptors into the ring, its flattened SRT then its guest buffers, and the pipeline maps the stage's SRT and buffer bindings over that run withVK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_PUSH_INDEX_EXT, the way vkd3d-proton binds descriptor tables. Otherwise the ring is its own buffer and shaders reach shader data, the SRT and guest buffers by pushed address. The table map is always read by address. A heap draw binds no descriptor set.gpuResolvedImages). Its image metadata names the binding element, and the driver writes the resolved heap slots into the stage's shader data. Guest buffers are read by device address (addressedBuffers), not through storage buffer descriptors: through read-only (NonWritable) pointers when the shader writes no guest memory, the rule Mesa infers for per-draw bindings, and with clamped, branch-free loads that return zero out of range. A stage whose module doesn't map onto the heap, or a draw whose stages come out mixed, is rebuilt on the per-draw path.vkWriteResourceDescriptorsEXTagain (keyed by view, layout and kind, with a generation check), as vkd3d-proton copies CPU descriptors.VK_EXT_descriptor_heapis an older revision than the released one (specVersionbelowVK_EXT_DESCRIPTOR_HEAP_SPEC_VERSION; a pre-release revision crashed every heap test on an earlier 595.99.02 build, and 595.99.02 now runs the heap), or whenVK_LAYER_KHRONOS_validationolder than 1.4.363 is enabled (older layers check heap push data against the pre-heap rules).VK_EXT_descriptor_heap.APS5_VERIFY_GPU_HEAP=1checks sampled heap slots against the bound textures and storage heap slots against the bound image and view, rebuilds 1 in N GPU-mode stages on the per-draw path and compares them, and fails when a reused stage data span would differ ([heap-slots],[gpu-heap]).APS5_GPU_HEAP_EXCLUDEsends stage classes (compute,pixel, ...,tables,storage,sampled) to the per-draw path while the heap stays on, for bisecting.PPSA36082 (Hades II)
Title and scene: PPSA36082 (Hades II) 01.006.000, the hub (Crossroads), player standing still, 1920x1080,
APS5_TRACE_SECONDS=60:10. Captured as the Performance report form describes: same machine, scene and settings, in one session on 2026-10-31,mainfirst; caches warm (each build ran once into the hub and the title was restarted before its captures; no shader cache file was written during any capture); no otherAPS5_TRACE_*orAPS5_PROFILE_*switch; log not redirected (each capture ran in its own terminal window). All withAPS5_PIPELINED_DRAWS=32 APS5_PIPELINE_DISPATCH=1. Every build had its submodules at their pins ("dirty":false).Current
mainstill stops Hades II while the hub loads (native multisample depth metadata fill transition is not implemented, thenexternal GPU writes over native multisample depth are not implementedwith #6300 alone). #9533 (EagleBearerYS) fixes both, so both builds have #9533 (dcf420e) merged locally, without conflicts:main0835ec8 + fix(agc): import native multisample D32S8 targets with a separate stencil plane and a depth-only HTILE #9533: local merge 9837bacfcd;main0835ec8 + #9533,APS5_DRAW_PIPELINE=1APS5_DRAW_PIPELINE=1 APS5_GPU_HEAP=1main0835ec8 + #9533 (no draw pipeline)The first two rows are the comparison (same draw pipeline, captured back to back): with the heap the frame p50 is 8% lower and presents 8% higher. The GPU is busy about 2.3 ms of a 10 ms frame in all of them: Hades II is bound by the queue worker recording draws, which is what the heap shortens. The third row is
mainwith its default settings, for reference.Summary lines
main0835ec8 + #9533,APS5_DRAW_PIPELINE=1{"commit":"9837bacfcdac","dirty":false,"env":{"APS5_DRAW_PIPELINE":"1","APS5_PIPELINED_DRAWS":"32","APS5_PIPELINE_DISPATCH":"1","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"60:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.3.0-3-cachyos","cpu":"AMD Ryzen 7 7800X3D 8-Core Processor","cpu_threads":16,"memory_mib":31742,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 60:10","first_present":5794,"ms":10004.558},"presents":952,"frame_ms":{"p50":10.414,"p90":11.198,"p99":12.259,"max":13.377},"gpu_busy_ms_per_present":2.344,"worker_busy_pct":{"queue worker 0x0":96.595},"top_phases_ms_per_frame":{"Driver.Packet":10.142,"Driver.Packet.draw":7.277,"Graphics.Draw":7.264,"Driver.Draw":7.206,"Driver.DrawFinish":6.438,"Graphics.Draw.draw_and_resource_release":6.036,"Driver.Draw.prepare":4.157,"Driver.DrawFinish.shader_compile_and_link":3.928},"trace_dropped":false,"trace":"aps5-trace-9837bacfcdac-823993.json.gz"}this PR 95e1f33 + #9533,
APS5_DRAW_PIPELINE=1 APS5_GPU_HEAP=1{"commit":"37724aa7680e","dirty":false,"env":{"APS5_DRAW_PIPELINE":"1","APS5_GPU_HEAP":"1","APS5_PIPELINED_DRAWS":"32","APS5_PIPELINE_DISPATCH":"1","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"60:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.3.0-3-cachyos","cpu":"AMD Ryzen 7 7800X3D 8-Core Processor","cpu_threads":16,"memory_mib":31742,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 60:10","first_present":6131,"ms":10008.044},"presents":1032,"frame_ms":{"p50":9.552,"p90":10.734,"p99":11.937,"max":13.199},"gpu_busy_ms_per_present":2.285,"worker_busy_pct":{"queue worker 0x0":96.278},"top_phases_ms_per_frame":{"Driver.Packet":9.327,"Driver.Packet.draw":6.576,"Driver.Draw":6.507,"Driver.DrawFinish":6.079,"Graphics.Draw":6.022,"Graphics.Draw.draw_and_resource_release":4.888,"Driver.DrawFinish.shader_compile_and_link":4.021,"Driver.Draw.prepare":3.902},"trace_dropped":false,"trace":"aps5-trace-37724aa7680e-824541.json.gz"}main0835ec8 + #9533 (no draw pipeline){"commit":"9837bacfcdac","dirty":false,"env":{"APS5_PIPELINED_DRAWS":"32","APS5_PIPELINE_DISPATCH":"1","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"60:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.3.0-3-cachyos","cpu":"AMD Ryzen 7 7800X3D 8-Core Processor","cpu_threads":16,"memory_mib":31742,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 60:10","first_present":5563,"ms":10006.024},"presents":902,"frame_ms":{"p50":11.019,"p90":11.793,"p99":12.945,"max":15.162},"gpu_busy_ms_per_present":2.436,"worker_busy_pct":{"queue worker 0x0":97.470},"top_phases_ms_per_frame":{"Driver.Packet":10.801,"Driver.Packet.draw":9.072,"Driver.Draw":8.983,"Graphics.Draw":7.347,"Graphics.Draw.draw_and_resource_release":6.089,"Driver.Draw.shader_compile_and_link":5.295,"Shader.DrawStage":3.481,"Shader.DrawStage.capture_resources":1.745},"trace_dropped":false,"trace":"aps5-trace-9837bacfcdac-821781.json.gz"}Traces:
main0835ec8 + fix(agc): import native multisample D32S8 targets with a separate stencil plane and a depth-only HTILE #9533,APS5_DRAW_PIPELINE=1: aps5-trace-9837bacfcdac-823993.json.gzAPS5_DRAW_PIPELINE=1 APS5_GPU_HEAP=1: aps5-trace-37724aa7680e-824541.json.gzmain0835ec8 + fix(agc): import native multisample D32S8 targets with a separate stencil plane and a depth-only HTILE #9533 (no draw pipeline): aps5-trace-9837bacfcdac-821781.json.gzDigests (
python3 tools/trace_digest.py <trace>)`main` 0835ec8 + #9533, `APS5_DRAW_PIPELINE=1`
seconds 60:10 from present 5794: 952 presents, frame p50 10.4 / p90 11.2 / p99 12.3 / max 13.4 ms, GPU busy 2.3 ms per present
NVIDIA GeForce RTX 4090, NVIDIA 615.78.08, Linux 7.3.0-3-cachyos, 1920x1080, commit 9837bacfcdac, dropped: no
{"commit":"9837bacfcdac","dirty":false,"env":{"APS5_DRAW_PIPELINE":"1","APS5_PIPELINED_DRAWS":"32","APS5_PIPELINE_DISPATCH":"1","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"60:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.3.0-3-cachyos","cpu":"AMD Ryzen 7 7800X3D 8-Core Processor","cpu_threads":16,"memory_mib":31742,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 60:10","first_present":5794,"ms":10004.558},"presents":952,"frame_ms":{"p50":10.414,"p90":11.198,"p99":12.259,"max":13.377},"gpu_busy_ms_per_present":2.344,"worker_busy_pct":{"queue worker 0x0":96.595},"top_phases_ms_per_frame":{"Driver.Packet":10.142,"Driver.Packet.draw":7.277,"Graphics.Draw":7.264,"Driver.Draw":7.206,"Driver.DrawFinish":6.438,"Graphics.Draw.draw_and_resource_release":6.036,"Driver.Draw.prepare":4.157,"Driver.DrawFinish.shader_compile_and_link":3.928},"trace_dropped":false,"trace":"aps5-trace-9837bacfcdac-823993.json.gz"}Slowest frames (largest slices inside each, inclusive)
Threads (ms per frame)
Largest slices (ms per frame, inclusive, all threads): Graphics.Draw 2.9, batch 2.3, Driver.Packet 2.2, Graphics.Draw.draw_and_resource_release 2.1, Driver.Packet.draw 1.5, Driver.Draw 1.5, Driver.DrawFinish 1.5, Driver.DrawFinish.draw_enqueued 1.0, Driver.Draw.prepare 0.6, Driver.Draw.draw_handed_off 0.6
Longest waits
Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)
this PR 95e1f33 + #9533, `APS5_DRAW_PIPELINE=1 APS5_GPU_HEAP=1`
seconds 60:10 from present 6131: 1032 presents, frame p50 9.6 / p90 10.7 / p99 11.9 / max 13.2 ms, GPU busy 2.3 ms per present
NVIDIA GeForce RTX 4090, NVIDIA 615.78.08, Linux 7.3.0-3-cachyos, 1920x1080, commit 37724aa7680e, dropped: no
{"commit":"37724aa7680e","dirty":false,"env":{"APS5_DRAW_PIPELINE":"1","APS5_GPU_HEAP":"1","APS5_PIPELINED_DRAWS":"32","APS5_PIPELINE_DISPATCH":"1","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"60:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.3.0-3-cachyos","cpu":"AMD Ryzen 7 7800X3D 8-Core Processor","cpu_threads":16,"memory_mib":31742,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 60:10","first_present":6131,"ms":10008.044},"presents":1032,"frame_ms":{"p50":9.552,"p90":10.734,"p99":11.937,"max":13.199},"gpu_busy_ms_per_present":2.285,"worker_busy_pct":{"queue worker 0x0":96.278},"top_phases_ms_per_frame":{"Driver.Packet":9.327,"Driver.Packet.draw":6.576,"Driver.Draw":6.507,"Driver.DrawFinish":6.079,"Graphics.Draw":6.022,"Graphics.Draw.draw_and_resource_release":4.888,"Driver.DrawFinish.shader_compile_and_link":4.021,"Driver.Draw.prepare":3.902},"trace_dropped":false,"trace":"aps5-trace-37724aa7680e-824541.json.gz"}Slowest frames (largest slices inside each, inclusive)
Threads (ms per frame)
Largest slices (ms per frame, inclusive, all threads): batch 2.3, Driver.Packet 1.8, Driver.Packet.draw 1.3, Driver.Draw 1.2, Graphics.Draw 1.1, Driver.DrawFinish 1.1, Graphics.Draw.draw_and_resource_release 0.9, Driver.Draw.prepare 0.7, Driver.DrawFinish.draw_enqueued 0.6, Recorder.Sync 0.5
Longest waits
Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)
`main` 0835ec8 + #9533 (no draw pipeline)
seconds 60:10 from present 5563: 902 presents, frame p50 11.0 / p90 11.8 / p99 12.9 / max 15.2 ms, GPU busy 2.4 ms per present
NVIDIA GeForce RTX 4090, NVIDIA 615.78.08, Linux 7.3.0-3-cachyos, 1920x1080, commit 9837bacfcdac, dropped: no
{"commit":"9837bacfcdac","dirty":false,"env":{"APS5_PIPELINED_DRAWS":"32","APS5_PIPELINE_DISPATCH":"1","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"60:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.3.0-3-cachyos","cpu":"AMD Ryzen 7 7800X3D 8-Core Processor","cpu_threads":16,"memory_mib":31742,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 60:10","first_present":5563,"ms":10006.024},"presents":902,"frame_ms":{"p50":11.019,"p90":11.793,"p99":12.945,"max":15.162},"gpu_busy_ms_per_present":2.436,"worker_busy_pct":{"queue worker 0x0":97.47},"top_phases_ms_per_frame":{"Driver.Packet":10.801,"Driver.Packet.draw":9.072,"Driver.Draw":8.983,"Graphics.Draw":7.347,"Graphics.Draw.draw_and_resource_release":6.089,"Driver.Draw.shader_compile_and_link":5.295,"Shader.DrawStage":3.481,"Shader.DrawStage.capture_resources":1.745},"trace_dropped":false,"trace":"aps5-trace-9837bacfcdac-821781.json.gz"}Slowest frames (largest slices inside each, inclusive)
Threads (ms per frame)
Largest slices (ms per frame, inclusive, all threads): Graphics.Draw 2.8, Driver.Packet 2.8, Driver.Packet.draw 2.7, Driver.Draw 2.6, batch 2.4, Graphics.Draw.draw_and_resource_release 2.0, Driver.Draw.shader_compile_and_link 0.5, Driver.Draw.draw_enqueued 0.5, VideoOut.Flip 0.4, VideoOut.Flip.present 0.4
Longest waits
Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)
Previous capture (superseded):
main1189ec3 + #9533 against this PR d8c243b + #9533, 2026-10-30main1189ec3 + #9533APS5_DRAW_PIPELINE=1APS5_DRAW_PIPELINE=1 APS5_GPU_HEAP=1Traces: https://github.com/Raghav314/AnyPS5/releases/tag/perf-traces-9508-hades-hub
Earlier comparison (superseded):
maina6dcea9 + #6300 against this PR 23b2a3f + #6300That comparison is kept for the record. Its
maintrace (de49f41349a9) reporteddropped: yes: the trace dropped events, so its digest undercounts slices for that capture, while its summary line's frame times are complete. The two PR traces reporteddropped: no. None of the three captures above dropped events.Title and scene: PPSA36082 (Hades II), the hub (Crossroads), player standing still,
APS5_TRACE_SECONDS=120:10. Caches warm: each setting was run once into the hub and the title restarted before its capture. No otherAPS5_TRACE_*orAPS5_PROFILE_*switch, log not redirected (the title ran in a terminal). All three withAPS5_PIPELINED_DRAWS=32 APS5_PIPELINE_DISPATCH=1and nothing else changed.Current
mainstops Hades II while the hub loads (native multisample depth metadata fill transition is not implemented), so every build here has #6300 merged locally. Themainbuild is a6dcea9 + #6300 (local merge commit de49f41349); this PR's is 23b2a3f + #6300 (local merge commit 568369d27a), the PR as of the descriptor heap rebind fix onmaina6dcea9. Since then the PR's commits changed only by test-only commits and rebases onto newermain; the numbers were not captured again on the current head.maina6dcea9 + #6300APS5_DRAW_PIPELINE=1APS5_DRAW_PIPELINE=1 APS5_GPU_HEAP=1The heap is +14% over the draw pipeline alone and +44% over
main.Summary lines
maina6dcea9 + #6300{"commit":"de49f41349a9","dirty":false,"env":{"APS5_PIPELINED_DRAWS":"32","APS5_PIPELINE_DISPATCH":"1","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"120:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.2.10-1-cachyos","cpu":"AMD Ryzen 7 7800X3D 8-Core Processor","cpu_threads":16,"memory_mib":31743,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 120:10","first_present":8859,"ms":10001.243},"presents":706,"frame_ms":{"p50":13.955,"p90":15.665,"p99":18.680,"max":25.064},"gpu_busy_ms_per_present":2.224,"worker_busy_pct":{"queue worker 0x0":97.766},"top_phases_ms_per_frame":{"Driver.Packet":13.844,"Driver.Packet.draw":11.959,"Driver.Draw":11.850,"Driver.Draw.shader_compile_and_link":8.284,"Graphics.Draw":8.166,"Graphics.Draw.draw_and_resource_release":6.859,"Shader.DrawStage":5.206,"Shader.DrawStage.capture_resources":3.196},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":6.816,"events_lost":132221,"totals_full":false}},"trace":"aps5-trace-de49f41349a9-624652.json.gz"}this PR + #6300,
APS5_DRAW_PIPELINE=1{"commit":"568369d27a2e","dirty":false,"env":{"APS5_DRAW_PIPELINE":"1","APS5_PIPELINED_DRAWS":"32","APS5_PIPELINE_DISPATCH":"1","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"120:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.2.10-1-cachyos","cpu":"AMD Ryzen 7 7800X3D 8-Core Processor","cpu_threads":16,"memory_mib":31743,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 120:10","first_present":10851,"ms":10010.638},"presents":886,"frame_ms":{"p50":11.172,"p90":12.083,"p99":13.652,"max":41.180},"gpu_busy_ms_per_present":2.340,"worker_busy_pct":{"queue worker 0x0":96.015},"top_phases_ms_per_frame":{"Driver.Packet":10.838,"Driver.Packet.draw":8.115,"Graphics.Draw":8.087,"Driver.Draw":8.030,"Driver.DrawFinish":7.151,"Graphics.Draw.draw_and_resource_release":6.678,"Driver.Draw.prepare":4.793,"Driver.DrawFinish.shader_compile_and_link":4.199},"trace_dropped":false,"trace":"aps5-trace-568369d27a2e-626697.json.gz"}this PR + #6300,
APS5_DRAW_PIPELINE=1 APS5_GPU_HEAP=1{"commit":"568369d27a2e","dirty":false,"env":{"APS5_DRAW_PIPELINE":"1","APS5_GPU_HEAP":"1","APS5_PIPELINED_DRAWS":"32","APS5_PIPELINE_DISPATCH":"1","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"120:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.2.10-1-cachyos","cpu":"AMD Ryzen 7 7800X3D 8-Core Processor","cpu_threads":16,"memory_mib":31743,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 120:10","first_present":12138,"ms":10004.892},"presents":1014,"frame_ms":{"p50":9.743,"p90":10.718,"p99":12.061,"max":17.616},"gpu_busy_ms_per_present":2.080,"worker_busy_pct":{"queue worker 0x0":95.834},"top_phases_ms_per_frame":{"Driver.Packet":9.445,"Driver.Packet.draw":6.921,"Driver.Draw":6.847,"Graphics.Draw":6.376,"Driver.DrawFinish":6.362,"Graphics.Draw.draw_and_resource_release":5.216,"Driver.Draw.prepare":3.983,"Driver.DrawFinish.shader_compile_and_link":3.903},"trace_dropped":false,"trace":"aps5-trace-568369d27a2e-625697.json.gz"}Traces:
maina6dcea9 + feat(agc): import cleared native multisample D32S8 targets and whole HTILE fills #6300:aps5-trace-de49f41349a9-624652.json.gzaps5-trace-de49f41349a9-624652.json.gzthis PR + feat(agc): import cleared native multisample D32S8 targets and whole HTILE fills #6300,
APS5_DRAW_PIPELINE=1:aps5-trace-568369d27a2e-626697.json.gzaps5-trace-568369d27a2e-626697.json.gzthis PR + feat(agc): import cleared native multisample D32S8 targets and whole HTILE fills #6300,
APS5_DRAW_PIPELINE=1 APS5_GPU_HEAP=1:aps5-trace-568369d27a2e-625697.json.gzaps5-trace-568369d27a2e-625697.json.gzDigests (
python3 tools/trace_digest.py <trace>)`main` a6dcea9 + #6300
seconds 120:10 from present 8859: 706 presents, frame p50 14.0 / p90 15.7 / p99 18.7 / max 25.1 ms, GPU busy 2.2 ms per present
NVIDIA GeForce RTX 4090, NVIDIA 615.78.08, Linux 7.2.10-1-cachyos, 1920x1080, commit de49f41349a9, dropped: yes
{"commit":"de49f41349a9","dirty":false,"env":{"APS5_PIPELINED_DRAWS":"32","APS5_PIPELINE_DISPATCH":"1","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"120:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.2.10-1-cachyos","cpu":"AMD Ryzen 7 7800X3D 8-Core Processor","cpu_threads":16,"memory_mib":31743,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 120:10","first_present":8859,"ms":10001.243},"presents":706,"frame_ms":{"p50":13.955,"p90":15.665,"p99":18.68,"max":25.064},"gpu_busy_ms_per_present":2.224,"worker_busy_pct":{"queue worker 0x0":97.766},"top_phases_ms_per_frame":{"Driver.Packet":13.844,"Driver.Packet.draw":11.959,"Driver.Draw":11.85,"Driver.Draw.shader_compile_and_link":8.284,"Graphics.Draw":8.166,"Graphics.Draw.draw_and_resource_release":6.859,"Shader.DrawStage":5.206,"Shader.DrawStage.capture_resources":3.196},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":6.816,"events_lost":132221,"totals_full":false}},"trace":"aps5-trace-de49f41349a9-624652.json.gz"}Slowest frames (largest slices inside each, inclusive)
Threads (ms per frame)
Largest slices (ms per frame, inclusive, all threads): Driver.Packet 3.6, Driver.Packet.draw 3.5, Driver.Draw 3.4, Graphics.Draw 2.5, batch 2.2, Graphics.Draw.draw_and_resource_release 1.6, Driver.Draw.shader_compile_and_link 1.4, Recorder.Sync 0.3, VideoOut.Flip 0.3, VideoOut.Flip.present 0.3
Longest waits
Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)
this PR + #6300, `APS5_DRAW_PIPELINE=1`
seconds 120:10 from present 10851: 886 presents, frame p50 11.2 / p90 12.1 / p99 13.7 / max 41.2 ms, GPU busy 2.3 ms per present
NVIDIA GeForce RTX 4090, NVIDIA 615.78.08, Linux 7.2.10-1-cachyos, 1920x1080, commit 568369d27a2e, dropped: no
{"commit":"568369d27a2e","dirty":false,"env":{"APS5_DRAW_PIPELINE":"1","APS5_PIPELINED_DRAWS":"32","APS5_PIPELINE_DISPATCH":"1","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"120:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.2.10-1-cachyos","cpu":"AMD Ryzen 7 7800X3D 8-Core Processor","cpu_threads":16,"memory_mib":31743,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 120:10","first_present":10851,"ms":10010.638},"presents":886,"frame_ms":{"p50":11.172,"p90":12.083,"p99":13.652,"max":41.18},"gpu_busy_ms_per_present":2.34,"worker_busy_pct":{"queue worker 0x0":96.015},"top_phases_ms_per_frame":{"Driver.Packet":10.838,"Driver.Packet.draw":8.115,"Graphics.Draw":8.087,"Driver.Draw":8.03,"Driver.DrawFinish":7.151,"Graphics.Draw.draw_and_resource_release":6.678,"Driver.Draw.prepare":4.793,"Driver.DrawFinish.shader_compile_and_link":4.199},"trace_dropped":false,"trace":"aps5-trace-568369d27a2e-626697.json.gz"}Slowest frames (largest slices inside each, inclusive)
Threads (ms per frame)
Largest slices (ms per frame, inclusive, all threads): Graphics.Draw 2.6, Driver.Packet 2.4, batch 2.3, Driver.Packet.draw 1.9, Driver.Draw 1.8, Graphics.Draw.draw_and_resource_release 1.8, Driver.DrawFinish 1.7, Driver.DrawFinish.draw_enqueued 1.0, Driver.Draw.prepare 0.9, Driver.Draw.draw_handed_off 0.5
Longest waits
Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)
this PR + #6300, `APS5_DRAW_PIPELINE=1 APS5_GPU_HEAP=1`
seconds 120:10 from present 12138: 1014 presents, frame p50 9.7 / p90 10.7 / p99 12.1 / max 17.6 ms, GPU busy 2.1 ms per present
NVIDIA GeForce RTX 4090, NVIDIA 615.78.08, Linux 7.2.10-1-cachyos, 1920x1080, commit 568369d27a2e, dropped: no
{"commit":"568369d27a2e","dirty":false,"env":{"APS5_DRAW_PIPELINE":"1","APS5_GPU_HEAP":"1","APS5_PIPELINED_DRAWS":"32","APS5_PIPELINE_DISPATCH":"1","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"120:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.2.10-1-cachyos","cpu":"AMD Ryzen 7 7800X3D 8-Core Processor","cpu_threads":16,"memory_mib":31743,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 120:10","first_present":12138,"ms":10004.892},"presents":1014,"frame_ms":{"p50":9.743,"p90":10.718,"p99":12.061,"max":17.616},"gpu_busy_ms_per_present":2.08,"worker_busy_pct":{"queue worker 0x0":95.834},"top_phases_ms_per_frame":{"Driver.Packet":9.445,"Driver.Packet.draw":6.921,"Driver.Draw":6.847,"Graphics.Draw":6.376,"Driver.DrawFinish":6.362,"Graphics.Draw.draw_and_resource_release":5.216,"Driver.Draw.prepare":3.983,"Driver.DrawFinish.shader_compile_and_link":3.903},"trace_dropped":false,"trace":"aps5-trace-568369d27a2e-625697.json.gz"}Slowest frames (largest slices inside each, inclusive)
Threads (ms per frame)
Largest slices (ms per frame, inclusive, all threads): batch 2.1, Driver.Packet 2.0, Driver.Packet.draw 1.6, Driver.Draw 1.5, Driver.DrawFinish 1.4, Graphics.Draw 1.2, Graphics.Draw.draw_and_resource_release 1.0, Driver.DrawFinish.draw_enqueued 0.8, Driver.Draw.prepare 0.7, Driver.Draw.draw_handed_off 0.6
Longest waits
Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)
Where this can take more frame rate
The heap moves descriptor work off the CPU, but Hades II is still CPU-bound: the queue worker is 96–98% busy in all three captures and the GPU is busy about 2.1 ms of a 9.7 ms frame. Follow-ups that build on the heap:
Contributions
Tested
Linux 7.3.0-3-cachyos (CachyOS), x86-64, GCC, Ryzen 7 7800X3D / RTX 4090, NVIDIA 615.78.08.
main90c9759 (this PR's last merge ofmain) vs this PR's head 229b988 (2026-10-31, same machine and GPU, each tree built Release with submodules at their pins):main90c9759$T*_gpu_heapvariants)APS5_GPU_HEAP=1was not found but was requested by env var VK_INSTANCE_LAYERSVUID-linesThe new tests were checked against the code before their fix:
agc_driver_image_converted_unorm_gpu_heapfailed with the sampler slot word error before 039b020, and EagleBearerYS'sagc_driver_address_draw_space_reuse_heapand_heap_transitionsfailed on the code before his three commits (the recipe replayed after a changed snapshot; a later free waited outAPS5_PIN_WAIT_MS).APS5_GPU_HEAP=1 APS5_DRAW_PIPELINE=1 APS5_PIPELINE_DISPATCH=1) on the head's build with and withoutAPS5_NO_HEAP_DATA_UNIFORM APS5_NO_HEAP_DEVICE_DESCRIPTORS: no driver failures, noVUID-lines. (APS5_NO_HEAP_DATA_UNIFORMhas since been removed.)mainc9b2660, the whole ctest (2128 tests) on the PR as it was then: all pass exceptagc_driver_sample_order,agc_driver_sample_order_grid_referenceandagc_driver_sample_order_recipe_depth_locations, which fail the same way without this PR.agc_host_import_sparseandagc_driver_sparse_dma_buf_importskip on this machine (its udmabuf size limit is above 1024 MiB).*_gpu_heaptests run the draw pipeline PR's switched tests and the sampling, table, atomics, compute, mesh relocation and thread-dimension dispatch tests withAPS5_GPU_HEAP=1 APS5_VERIFY_GPU_HEAP=1.Known limits
VK_EXT_descriptor_heap, and heaps that hold the shared image slot pool and 2048 samplers. Other devices run the existing path._gpu_heaptest. milescoviello confirmed the white-out is gone, and the full heap runs in-game to his 1,200 s timeout.Testing other titles
Please post:
APS5_TRACE_SECONDS=70:10, captured as the Performance report form describes: warm caches, no other trace or profile switch, log not redirected) withAPS5_DRAW_PIPELINE=1and withAPS5_GPU_HEAP=1 APS5_DRAW_PIPELINE=1, both withAPS5_PIPELINED_DRAWS=32 APS5_PIPELINE_DISPATCH=1and nothing else changed;APS5_VERIFY_GPU_HEAP=1, the last[heap-slots]and[gpu-heap]lines (a slot holding another texture or a differing stage is a bug).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)