Skip to content

perf: Guardians of the Galaxy: menu at ~1.7 FPS with the GPU mostly idle, the graphics queue busy recording and the compute queue in WAIT_REG_MEM_64 #10441

Open

Description

@Tamalero

Title and scene

PPSA01748 (01.006.000), the main menu after the intro, presents 1150–1190, no input. The same pattern holds in the first gameplay scene, Star-Lord's quarters on the Milano, at about 1 FPS (figures below).

What is slow

The menu runs at a p50 of 587 ms per frame, while the GPU is busy for only 134 ms per present. Both queue workers are saturated:

  • queue worker 0x0 (graphics): busy 78% (482 ms per frame), with 153 ms per frame in slices under 20 µs.
  • queue worker 0x23 (compute): waits 92% of the time, almost all in Driver.Packet.memory_wait. Single waits last up to 477 ms.

Build: main e13027a with #9694 and #10388 applied (local commit abe0d257a). Neither touches the paths below: #9694 changes one coverage check in a metadata pass the menu does not reach, and #10388 only sizes the startup prebuild pool. RTX 4090, NVIDIA 615.78.08, Linux 7.3, i9-14900K.

A separate run of the same build with APS5_PROFILE_DRAW=1, so these numbers carry profiling overhead and are not part of the capture below, breaks the 10-second [packets] lines down by packet type:

Scene Queue Main packets (10 s)
menu 0x0 DRAW_INDEX_INDIRECT_MULTI ×8,196 5,630 ms, DISPATCH_DIRECT ×2,754 1,912 ms, WAIT_REG_MEM_64 ×25 405 ms
menu 0x23 WAIT_REG_MEM_64 ×345 7,891 ms, DISPATCH_DIRECT ×554 1,989 ms
Star-Lord's quarters 0x0 DRAW_INDEX_INDIRECT_MULTI ×7,881 4,800 ms, DISPATCH_DIRECT ×2,259 2,663 ms, DMA_DATA ×3,063 632 ms
Star-Lord's quarters 0x23 WAIT_REG_MEM_64 ×160 8,491 ms, DISPATCH_DIRECT ×301 1,416 ms

In the quarters, the same run also reports:

  • [indirect]: 9,842 GPU-side indirect draws and 0 CPU-side, so no indirect arguments are read on the CPU.
  • [draw] driver phases: 10,440 draw packets in 10 s at about 501 µs each, of which capture is 96 µs, Graphics::Draw 294 µs, recompile 37 µs and vectors 38 µs.
  • [draws]: about 59,000 draws, 33,204 of them address-based builds, with 0.7–1.0 s in total in their resources.
  • [draw] pipelined: 3,196 packet drains costing 997 ms, by opcode 0x50 ×1,588 and 0x15 ×1,449.
  • [packets] for 0x23 in the menu: of its waits, 1 was satisfied at entry, 280 from the recorder (225 of them "same queue"), and 16 were polled.

I haven't identified which writer the compute queue's waits depend on. If there's a switch that names the awaited label's producer, I can run it.

Summary lines

{"commit":"abe0d257ac4c","dirty":false,"env":{"APS5_TRACE_FRAMES":"1150:40"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.3.0-3-cachyos","cpu":"Intel(R) Core(TM) i9-14900K","cpu_threads":32,"memory_mib":64108,"title_id":"PPSA01748","title":"Marvel's Guardians of the Galaxy","content_version":"01.006.000","resolution":"3840x2160","window":{"spec":"frames 1150:40","first_present":1150,"ms":24540.029},"presents":40,"frame_ms":{"p50":586.747,"p90":771.370,"p99":897.050,"max":897.050},"gpu_busy_ms_per_present":133.690,"worker_busy_pct":{"queue worker 0x0":99.518,"queue worker 0x23":99.226},"top_phases_ms_per_frame":{"Driver.Packet":1213.861,"Driver.Packet.memory_wait":517.411,"Driver.Packet.draw":321.244,"Driver.Draw":321.059,"Driver.Packet.dispatch":269.560,"Driver.Dispatch":241.176,"Graphics.Draw":228.388,"Graphics.Draw.draw_and_resource_release":193.724},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":24.424,"events_lost":1659,"totals_full":false}},"trace":"aps5-trace-abe0d257ac4c-2.json.gz"}

Traces

aps5-trace-abe0d257ac4c-2.json.gz is attached in the first comment.

The window ran out of event space on queue worker 0x0 in its last 0.1 s (events_lost 1,659), so the final frame's tail is missing on that thread.

Digest

39 frames over 23952.7 ms (no summary line found)

**Slowest frames** (largest slices inside each, inclusive)

- present 1171: 897.0 ms at 12581 ms: Driver.Packet (queue worker 0x23) 896.6, Driver.Packet (queue worker 0x0) 884.5, Driver.Packet.memory_wait (queue worker 0x23) 787.3, Driver.Packet.draw (queue worker 0x0) 595.7
- present 1167: 860.7 ms at 10177 ms: Driver.Packet (queue worker 0x23) 856.6, Driver.Packet (queue worker 0x0) 847.6, Driver.Packet.memory_wait (queue worker 0x23) 739.3, Driver.Packet.draw (queue worker 0x0) 548.3
- present 1166: 772.7 ms at 9404 ms: Driver.Packet (queue worker 0x23) 771.6, Driver.Packet (queue worker 0x0) 760.0, Driver.Packet.memory_wait (queue worker 0x23) 605.7, Driver.Packet.draw (queue worker 0x0) 461.5
- present 1176: 771.4 ms at 16029 ms: Driver.Packet (queue worker 0x0) 758.7, Driver.Packet (queue worker 0x23) 748.5, Driver.Packet.memory_wait (queue worker 0x23) 654.8, Driver.Packet.draw (queue worker 0x0) 483.0
- present 1165: 768.1 ms at 8636 ms: Driver.Packet (queue worker 0x23) 767.5, Driver.Packet (queue worker 0x0) 755.5, Driver.Packet.memory_wait (queue worker 0x23) 621.5, Driver.Packet.draw (queue worker 0x0) 449.6

**Threads** (ms per frame)

| thread | busy | wait | untraced | in slices < 20 us | largest |
|---|---|---|---|---|---|
| title | 1.6 (0 %) | 11.3 (2 %) | 601.2 | 0.0 | Recorder.Sync 0.9, Recorder.Submit 0.8 |
| title | 10.5 (2 %) | 30.3 (5 %) | 573.4 | 1.5 | Recorder.Sync 3.7, Shader.ResolveAbi 3.2 |
| queue worker 0x0 | 481.8 (78 %) | 121.3 (20 %) | 11.1 | 152.6 | Driver.Packet 601.5, Driver.EndSubmission 0.0 |
| draw committer | 73.9 (12 %) | 53.7 (9 %) | 486.5 | 6.1 | Graphics.Draw 69.3, Recorder.Submit 4.6 |
| presenter | 0.3 (0 %) | 0.2 (0 %) | 613.6 | 0.1 | VideoOut.Flip 0.5, Driver.Present 0.0 |
| title | 7.0 (1 %) | 8.5 (1 %) | 598.7 | 0.8 | Shader.ResolveAbi 3.1, Shader.ResolveGraphicsStagesAbi 2.0 |
| title | 8.8 (1 %) | 4.5 (1 %) | 600.9 | 0.7 | Recorder.Sync 3.6, Shader.ResolveAbi 3.0 |
| title | 6.7 (1 %) | 9.0 (1 %) | 598.5 | 0.8 | Shader.ResolveAbi 3.0, Shader.ResolveGraphicsStagesAbi 1.9 |
| title | 11.0 (2 %) | 5.2 (1 %) | 598.0 | 0.7 | Recorder.Sync 5.5, Shader.ResolveAbi 3.3 |
| title | 7.1 (1 %) | 3.8 (1 %) | 603.3 | 0.8 | Shader.ResolveAbi 2.9, Recorder.Sync 2.2 |
| title | 5.8 (1 %) | 7.0 (1 %) | 601.3 | 0.6 | Shader.ResolveAbi 2.9, Shader.ResolveGraphicsStagesAbi 2.0 |
| title | 7.0 (1 %) | 3.0 (0 %) | 604.2 | 0.6 | Recorder.Sync 3.1, Shader.ResolveAbi 2.2 |
| title | 7.1 (1 %) | 7.3 (1 %) | 599.8 | 0.7 | Shader.ResolveAbi 2.7, Recorder.Sync 2.5 |
| title | 8.6 (1 %) | 6.3 (1 %) | 599.2 | 0.7 | Recorder.Sync 3.5, Shader.ResolveAbi 3.1 |
| title | 6.6 (1 %) | 5.3 (1 %) | 602.3 | 0.7 | Shader.ResolveAbi 2.7, Recorder.Sync 2.2 |
| queue worker 0x23 | 51.0 (8 %) | 562.4 (92 %) | 0.7 | 2.2 | Driver.Packet 607.9, Driver.EndSubmission 5.5 |
| gpu | 134.0 (22 %) | 0.0 (0 %) | 480.2 | 0.0 | batch 134.0 |

**Largest slices** (ms per frame, inclusive, all threads): Driver.Packet 1209.4, Driver.Packet.memory_wait 522.2, Driver.Packet.draw 320.4, Driver.Draw 320.2, Driver.Packet.dispatch 269.0, Driver.Dispatch 240.4, Graphics.Draw 216.1, Driver.Draw.draw_and_resource_release 188.0, Graphics.Draw.draw_and_resource_release 181.5, batch 134.0

**Longest waits**

- Driver.Packet.memory_wait (queue worker 0x23) 476.6 ms at 12827 ms, present 1171
- Driver.Packet.memory_wait (queue worker 0x23) 429.9 ms at 10403 ms, present 1167
- Driver.Packet.memory_wait (queue worker 0x23) 384.6 ms at 16253 ms, present 1176
- Driver.Packet.memory_wait (queue worker 0x23) 357.5 ms at 9662 ms, present 1166
- Driver.Packet.memory_wait (queue worker 0x23) 336.0 ms at 13706 ms, present 1172
- Driver.Packet.memory_wait (queue worker 0x23) 325.8 ms at 8876 ms, present 1165
- Driver.Packet.memory_wait (queue worker 0x23) 305.2 ms at 20578 ms, present 1183
- Driver.Packet.memory_wait (queue worker 0x23) 286.3 ms at 4076 ms, present 1157
- Driver.Packet.memory_wait (queue worker 0x23) 261.5 ms at 6509 ms, present 1161
- Driver.Packet.memory_wait (queue worker 0x23) 244.6 ms at 4368 ms, present 1157

**Largest GPU batches** (GPU ranges inside, and the CPU slices that recorded each, inclusive)

- batch 1175: 20.3 ms at 6992 ms, present 1162: GPU no ranges; recorded by Driver.Packet (queue worker 0x23) 30.8, Driver.Packet.memory_wait (queue worker 0x23) 21.3, Driver.Packet.dispatch (queue worker 0x23) 9.4, Driver.Dispatch (queue worker 0x23) 9.3
- batch 2715: 20.3 ms at 15478 ms, present 1175: GPU no ranges; recorded by GpuMutex.wait.dispatch (queue worker 0x23) 21.0, Driver.Packet (queue worker 0x23) 6.2, Driver.Packet.dispatch (queue worker 0x23) 6.2, Driver.Dispatch (queue worker 0x23) 6.0
- batch 1359: 20.2 ms at 8120 ms, present 1164: GPU no ranges; recorded by Driver.Packet (queue worker 0x23) 51.0, Driver.Packet.memory_wait (queue worker 0x23) 38.8, Driver.Packet.dispatch (queue worker 0x23) 12.1, Driver.Dispatch (queue worker 0x23) 11.9
- batch 3179: 19.3 ms at 18015 ms, present 1179: GPU no ranges; recorded by GpuMutex.wait.label (draw committer) 2.9, Graphics.Draw (draw committer) 1.6, Graphics.Draw.validate (draw committer) 1.4, GpuMutex.wait.draw (draw committer) 0.2
- batch 1742: 18.3 ms at 10194 ms, present 1167: GPU no ranges; recorded by GpuMutex.wait.dispatch (queue worker 0x23) 26.6, Vulkan.Dispatch.shader_resources (queue worker 0x23) 2.4, Resources.Complete (queue worker 0x23) 2.4, Driver.Packet (queue worker 0x23) 1.4

Capture

  • The caches were warm (the scene ran once and the title was restarted)
  • No other APS5_TRACE_* or APS5_PROFILE_* switch was set and the log was not redirected
  • Captures compared here come from the same machine, scene and settings

Tested and written by Claude (AI assistant) on a contributor's machine.

Activity

  1. Tamalero commented on Oct 24, 2026

    @Tamalero
    ContributorAuthor
  2. Tamalero commented on Oct 24, 2026

    @Tamalero
    ContributorAuthor

    The same pattern shows up in a second title on a different engine: LEGO Batman: Legacy of the Dark Knight (PPSA16833, 01.010.000, Unreal Engine).

    Build: plain main e13027a, with only APS5_PREBUILD_THREADS=16 APS5_TRACE_SUMMARY_EVERY=10. Same machine (RTX 4090, NVIDIA 615.78.08, Linux 7.3, i9-14900K). The title boots, reaches the menu and loads the Mountain Climb level with no errors. Standing still at the start of that level:

    • Graphics mode doesn't matter. The in-game overlay shows 3.1 FPS (316 ms average) in Performance and 3.0 FPS in Fidelity, at about 1,750 draws and 300 dispatches per frame. So the GPU is not the limit.
    • Summary intervals (10 s each, the last four before closing, in Fidelity):
    Interval Presents Frame p50 GPU busy per present Driver.Packet.memory_wait per frame Driver.Packet.dispatch per frame
    1 28 328 ms 94 ms 346 ms 230 ms
    2 31 328 ms 89 ms 326 ms 214 ms
    3 31 315 ms 84 ms 320 ms 207 ms
    4 31 322 ms 88 ms 322 ms 222 ms
    • Both queue workers (0x0 and 0x20) report 99–100% busy, while the whole process uses only about two cores of CPU. As in the Guardians capture above, the time goes to waits and per-packet work on the queue workers, not to the GPU or to raw CPU load.

    This run had no trace window, and no APS5_PROFILE_DRAW, so I can't yet split the waits by queue or packet type the way the Guardians tables do. If it helps, I can capture a clean APS5_TRACE_FRAMES window for this scene to compare with the Guardians trace.

    Tested and written by Claude (AI assistant) on a contributor's machine.

  3. Tamalero commented on Oct 24, 2026

    @Tamalero
    ContributorAuthor

    Follow-up on what queue 0x23 waits for. Same build and menu scene as the report, about 60 s with APS5_TRACE_GPU=1 APS5_PROFILE_DRAW=1 (both add overhead, so only the proportions matter).

    Almost all of the compute queue's wait time is on one label:

    Queue Awaited address Waits (~60 s) Waits ≥ 0.5 ms Total waited
    0x23 0x281170038 529 526 57.1 s (~108 ms each)
    0x0 0x281170058 127 120 4.3 s (~36 ms each)
    0x23 10 others in 0x2a62fe6xx–8xx 10 each 0 0
    • It's a counter. Each wait expects the next value, and at the start of each wait the label is exactly one behind:

      queue 0x23 waits 0x281170038 == 0x102 (now 0x101)
      queue 0x23 waits 0x281170038 == 0x103 (now 0x102)
      queue 0x23 waits 0x281170038 == 0x104 (now 0x103)
      

      Queue 0x0 does the same on 0x281170058 (== 0xc1 (now 0xc0), == 0xc4 (now 0xc3), …).

    • Queue 0x23 mostly waits on its own writes. Per 10 s, the [packets] line for 0x23 shows from recorder 307 (… same queue 255 …) and from recorder 308 (… same queue 248 …), with 11–22 polled and 0–1 at entry over the seven intervals. So about 80% of these waits are on a label that the recorder holds as pending from queue 0x23 itself. The title increments the counter after its own work and then waits for it, and each of those waits takes about 108 ms while the GPU is mostly idle (134 ms of GPU per ~590 ms frame in the clean capture).

    • The GPU-mutex waits on these lines are small by comparison: waited behind (holder/site ms): q0x23/dispatch 61 q0x0/dispatch 29 … and q0x23/dispatch 34 q0x0/draw 14 … per 10 s.

    Within one queue the GPU executes in order. Would it be possible to satisfy a same-queue wait on a label still pending from that queue in submission order (a barrier) instead of a full CPU round trip for every increment? I can run any diagnostic on this scene that would help.

    Tested and written by Claude (AI assistant) on a contributor's machine.

  4. MarcZX10 commented on Oct 24, 2026

    @MarcZX10
    Contributor

    I checked the current wait paths. There is already a recorder-label fast path: waitMemory() returns when a pending recorder write matches the wait value, and the queue-0 draw pipeline has an additional pending-label shortcut in PacketExecution.cpp. Those fast-path hits do not execute a GPU wait packet or enter the long polling loop. That means the reported 255 same-queue recorder hits are being bypassed already; the 526 waits lasting at least 0.5 ms are likely among the other waits, but the counters shown are not correlated per address or with elapsed wait time, so the report does not yet show which ones dominate the 57.1 s.

    A useful next capture would correlate each long wait by address/reference with its eventual outcome (fromRecorder, polled, or timed out) and elapsed time, in one run. In particular, please check whether the repeated waits on 0x281170038 that exceed 0.5 ms are recorder misses or poll-loop waits. If they are misses, the trace should help identify whether the producer write is not yet recorded/submitted or whether the label lookup refuses it; if they are already recorder hits, that would point elsewhere because those return immediately. This will distinguish a missing GPU-side ordering opportunity from the existing fast path working as designed.

  5. MarcZX10 commented on Oct 24, 2026

    @MarcZX10
    Contributor

    I checked the current WaitMemory.cpp diagnostics. There is no switch that prints a producer for every wait, but a short run with APS5_TRACE_GPU=1 APS5_TRACE_LATE_LABELS=1 APS5_PROFILE_DRAW=1 should narrow this further: APS5_TRACE_GPU prints the address and duration for each wait over 0.5 ms, while APS5_TRACE_LATE_LABELS prints any recorder-table hit/refusal examined on the path (up to 200 lines). Please include the paired [gpu] ... waits / done after lines for 0x281170038 and any corresponding [late] lines, plus the same interval's [packets] counters. That will tell us whether those long instances found a recorder label or continued into polling. The label-trace switch may add logging overhead, so a brief representative capture is enough.

  6. MarcZX10 commented on Oct 24, 2026

    @MarcZX10
    Contributor

    Correction to my previous comment: APS5_TRACE_LATE_LABELS logs recorder lookup refusals and late-label hits, not ordinary successful hits. The [gpu] address/duration pairs plus the aggregate [packets] counters will still help separate polling from fast-path behavior; any [late] lines can explain specific rejected candidates. I should not have described that switch as logging every table hit.

  7. Tamalero commented on Oct 24, 2026

    @Tamalero
    ContributorAuthor

    @MarcZX10 thanks. Here's the run you asked for: same build and menu scene, APS5_TRACE_GPU=1 APS5_TRACE_LATE_LABELS=1 APS5_PROFILE_DRAW=1, about 35 s on the menu with no input.

    [late] lines: none, neither in that window nor anywhere in the log from launch.

    Waits on 0x281170038: 216 by queue 0x23, 213 of them with a done after line, for 28.7 s in total (~134 ms each). The first pairs, with the timestamps removed:

    [gpu] queue 0x23 waits 0x281170038 == 0x4c1 (now 0x4bf)
    [gpu] queue 0x23 wait on 0x281170038 done after 242.0 ms
    [gpu] queue 0x23 waits 0x281170038 == 0x4c2 (now 0x4c1)
    [gpu] queue 0x23 wait on 0x281170038 done after 37.6 ms
    [gpu] queue 0x23 waits 0x281170038 == 0x4c3 (now 0x4c2)
    [gpu] queue 0x23 wait on 0x281170038 done after 128.7 ms
    [gpu] queue 0x23 waits 0x281170038 == 0x4c4 (now 0x4c3)
    [gpu] queue 0x23 wait on 0x281170038 done after 192.8 ms
    [gpu] queue 0x23 waits 0x281170038 == 0x4c5 (now 0x4c4)
    [gpu] queue 0x23 wait on 0x281170038 done after 210.2 ms
    [gpu] queue 0x23 waits 0x281170038 == 0x4c6 (now 0x4c5)
    [gpu] queue 0x23 wait on 0x281170038 done after 11.0 ms
    

    Queue 0x0's 0x281170058 had 53 waits for 2.0 s in the same window.

    [packets] for queue 0x23, the four 10 s intervals in the window:

    Interval WAIT_REG_MEM_64 At entry From recorder (while polling, same queue) Polled Timed out Late candidates Late-trusted
    1 ×291, 7,923 ms 1 239 (39, 200) 20 0 0 0
    2 ×281, 7,826 ms 1 232 (32, 200) 25 0 0 0
    3 ×296, 7,884 ms 1 236 (34, 202) 26 0 0 0
    4 ×303, 8,053 ms 0 255 (41, 214) 21 0 0 0

    So about 80–85% of the waits were satisfied from a recorder label, mostly one queue 0x23 recorded itself, and only 20–26 per interval went to polling, with no refusals and nothing late. The long waits are therefore not polling misses. Yet the done after times for those same waits are 11–242 ms. If it helps, I can add a timestamp at each step inside one of these waits on a local build (table lookup, the wait on the producer's batch, return) to show where the ~134 ms goes.

    Tested and written by Claude (AI assistant) on a contributor's machine.

  8. Tamalero commented on Oct 29, 2026

    @Tamalero
    ContributorAuthor

    An update on current main, with a CPU profile of the menu and a list of what does not change it.

    Build: main 319621b with #9789 reverted (the title stops at start-up without that, #11513). The same menu as in this issue: p50 495.9 to 532.9 ms in four runs, with APS5_TRACE_FRAMES=1150:40, no input, warm caches, RTX 4090, NVIDIA 615.78.08, Linux 7.3. The title also still gets through New Game to the Milano on this build, at 0.8 to 2 FPS.

    CPU profile (perf record -p, 10 s on the menu, no trace switches set):

    • 89 % of the samples are in the title's own code, and about 70 % are in one helper at 0x611c30 that reads a global flag and executes pause. It is called from the title's job-system idle wait (a loop of up to 199 calls with a per-thread counter compared against 500). So 14 worker threads are busy-waiting with no work.
    • The emulator's own code is in the low single digits (mostly mutexes and condition variables).

    So the menu is not throughput-bound on either side. The frame seems to be a chain of round trips: the title waits for queue work, and queue 0x23 waits (Driver.Packet.memory_wait, 406 to 460 ms per frame in the base runs) on labels that only advance after its own work completes, as in my earlier comments.

    No change (menu p50, one run each unless noted; base 495.9 / 532.9 / 512.8 / 511.1 ms):

    Change p50
    #12616 (two runs) 589.1 / 520.3 ms
    #9858 503.2 ms
    #11847 532.6 ms
    #7261 486.6 ms
    #12264 517.1 ms
    #9289 517.3 ms
    APS5_POLL_REAP=1 519.8 ms
    APS5_LABEL_FLUSH_US=100 505.0 ms
    APS5_DRAIN_COMPLETION_LABELS=1 515.2 ms
    APS5_POLL_TRY_EACH=1 470.5 ms (the power profile changed during this run)
    APS5_LABEL_SUBMIT_NOW=1 (three runs) 466.6 / 486.8 / 511.1 ms

    APS5_LABEL_SUBMIT_NOW=1 is the one consistent change: GPU busy per present falls from about 115 ms to about 40 ms in all three runs, but the frame time stays within the spread. So GPU time is not what limits this frame.

    From #12640: the dispatch cache hits about 10 % of the time on this menu, and neither 8 variants nor 65,536 entries change the frame time (+0 % and +3.5 %).

    I can rerun any of these with other switches, or capture traces of the base and APS5_LABEL_SUBMIT_NOW runs if those would help.

    Tested and written by Claude (AI assistant) on a contributor's machine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions