Skip to content

MCP perf tooling: CPU-scope profiling, GPU-timing accuracy under load, overdraw debug view #519

Description

@drsnuggles8

Follow-up to #306 and #316, surfaced by two live MCP perf-diagnosis sessions (2026-07-02,
Sponza CSM/atrium, Dist build) run against the real-GPU-timings tooling from #517. Tracks
the remaining perf-diagnostics gaps that are neither scriptable-repro-setup (#316 Part 5)
nor part of #306's now-complete write-consent epic.

New tool wanted

  • olo_perf_cpu_scopes — expose PerformanceProfiler per-scope CPU timings (every
    system in Scene.cpp is wrapped in OLO_PERF_SCOPE) over MCP, read-only. Today this
    data only reaches the editor Statistics panel, and OLO_PERF_SCOPE is compiled out
    entirely in Dist, so a Dist session has zero CPU-scope visibility (frame totals only).
    Shipped in Add olo_perf_cpu_scopes MCP tool for per-scope CPU timings (#519, first slice) #537.

Accuracy bugs in the #517 GPU-timing tooling (diagnosed, not fixed)

Root-caused during a heavy-GPU-load session (frame >250 ms, Sponza atrium, Dist, maximized
viewport) — surfaced in the #316 comment thread (2026-07-02/07-03 live perf sessions):

  • cpuMs exceeds frameTimeMs at saturation (e.g. cpu 905 ms vs frame 519 ms).
    Root cause: RendererProfiler measures m_FrameTime at BeginFrame (previous
    frame-boundary delta) but m_CPUTime at EndFrame (current bracket), and the MCP
    snapshot reads m_CurrentFrame mid-frame — the two numbers describe different
    frames
    whenever frame times swing.
  • gpuWaitMs reads 0.00 even when the GPU is the bottleneck by ~100x. Only the
    FrameResourceManager::BeginFrame fence wait is attributed; under deep GPU backlogs
    the real stall lands in SwapBuffers, outside the profiler bracket entirely (and the
    editor's over-budget render throttle further suppresses fence waits).
  • Per-pass GPU numbers get erratic on very long frames (ShadowPass read 13.9 ms at
    native vs 0.59 ms at 1080p vs 4.3 ms at 4K with a fixed camera — shadow cost cannot
    depend on viewport resolution). GPUPassTimerPool has a 4-slot timestamp ring; when
    the GPU falls more than 3 frames behind, slots are dropped rather than awaited and
    published results go stale. gpuResultsAgeFrames (already in the payload) is the
    honesty signal to check before trusting numbers on very long frames — worth
    surfacing more prominently (e.g. a warning flag) rather than requiring the caller to
    know to check it.

Missing debug view

  • No overdraw debug view (shipped in feat(mcp): add overdraw debug view (#519) #558) in olo_render_set_debug_view (only ssao/gtao/ssr/ssgi
    exist today). An overdraw heatmap mode (per-fragment counting target + heatmap
    composite) would make "how many layers deep is this frame" measurable directly
    instead of inferred from A/B toggling passes. Deliberately scoped out of the
    overdraw work's own PR to keep it reviewable — it's a real renderer feature, not
    a tooling patch.

Smaller findings

  • Boot inconsistency: the settings-derived depth-prepass value (on, because
    ForwardPlusAutoSwitch defaults true) disagrees with the live boot state (off) —
    ApplyRendererSettings never runs at startup, so setting depthprepass=auto
    immediately after boot changes the state instead of no-oping.
  • First perf-lever write right after scene load doesn't take effect on the GPU
    (repeatable A/B afterwards) — likely an editor-settle/render-throttle transient;
    worth a beat of settle time after load before trusting the first A/B reading.

Status — updated 2026-07-07

olo_perf_cpu_scopes (#537), the overdraw debug view (#558), and all three GPU-timing
accuracy bugs (#571) have shipped. The feature/mcp-perf-scenepass-tooling branch
referenced in earlier updates landed through those same PRs and no longer exists as a
separate branch. Remaining scope is just the two "Smaller findings" above (boot
inconsistency, first-write-after-load not taking effect) — everything else in this issue
is done.

Refs: #306, #316, #517.

Score

capability: 3
craft: 1
stability: 2
decay: 1
effort: 2
confidence: 0.8
learning: 3
fun: 3
kano: table-stakes
blocked_by: []
blocks: []

Rated per issue-scoring · score = confidence × (capability + craft + stability + decay) / effort, derived by the picker.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfeatureNew feature or requesttoolingMCP / dev-tooling / codegen — exempt from feature freeze

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions