Skip to content

[RFC]: Measurement framework + per-workload data for readbacks_mode, and a proposal for deferred-batching #4316

Description

@akitaonrails

Motivation

readbacks_mode ships off by default because the implementation has a known perf and stability cost that has been called out across PR #2668, #3178, #3305 ("currently slow and will most likely be rewritten"), #4085, #4120, and issues #3322, #3346, #3826, #4215. Every one of those threads argues from hand-measured numbers (fps deltas, "12 fps on AMD", "significant stuttering") collected with ad-hoc methodology. There is no shared measurement framework, which means successive PRs are debated on incomparable data. PR #3404's fence-detection rewrite stalled on correctness concerns that, in hindsight, could have been narrowed if a common harness existed.

I built one to investigate a separate issue (DriveClub race-start dim, CUSA00003) and it landed me on concrete numbers that I'd like to share, and on a proposal for what would actually reduce the structural cost. I can only validate on one GPU, so I want feedback before going further.

Proposed Change

Two independent pieces, in order of risk.

1. Land a measurement harness upstreamsrc/common/readback_metrics.{h,cpp} + ~30 lines of instrumentation across vk_scheduler, buffer_cache, texture_cache, fault_manager, page_manager, vk_presenter. Atomic counters always live (~5 ns / op), wall-clock timing gated on SHADPS4_READBACKS_METRICS=<csv path>. Zero behavioural change. CSV dump on exit (atexit, at_quick_exit, SIGTERM chain).

Counters: Scheduler::Finish / Wait / Submit counts and cumulative ns; BufferCache::ReadMemory + DownloadBufferMemory bytes + ns; TextureCache::DownloadImageMemory; FaultManager::ProcessFaultBuffer dispatches; PageManager::Protect syscalls + ns; bounded frame-time ring for p50/p95/p99.

Rationale: fills the "everyone measures differently" gap. Lets future readback PRs be A/B'd against a common baseline. Small, self-contained, opt-in at runtime.

2. Propose GpuReadbacksMode::Batched — coalesce BufferCache::ReadMemory calls within a fault window into a single Scheduler::Finish flushed at the next PM4 sync packet (WaitRegMem / fence EventWrite). Ordering guard: a guest read targeting a range with a pending batched download force-flushes first.

Rationale: per-call measured at ~68 µs on RTX 5090, ~106 ReadMemory/frame in Bloodborne gameplay = 7.23 ms/frame of cumulative CPU stall. One Finish per batch collapses that regardless of per-Finish-wait cost, which is the axis that scales poorly on AMD (see #3322, #4120).

Explicitly not proposing: flipping the global default, removing Relaxed, or reviving PR #3404's fence-detection prepass. Those are separate calls.

Data

Single hardware: RTX 5090 + Ryzen 7950X3D + Arch Linux + Nvidia proprietary. Branch akitaonrails/shadPS4:readbacks-perf, commit 133e3d7c. Methodology + raw CSVs at docs/readbacks-perf/.

game / workload mode p50ms p95ms p99ms Finish/f Finish_ms/f mprotect/f
DriveClub menu idle disabled 16.67 16.67 16.71 0 0.00 86
DriveClub menu idle relaxed 16.67 16.67 16.71 14.9 2.66 83
DriveClub menu idle precise 16.67 16.67 16.68 15.4 2.84 111
DriveClub race 1m precise 33.33 33.34 33.36 19.1 2.76 170
Bloodborne menu idle precise 33.33 33.34 33.35 0.9 0.17 4
Bloodborne gameplay precise 33.33 33.38 50.00 106 7.23 809
Bloodborne gameplay disabled 33.33 33.35 50.00 0 0.00 462

Observations (this hardware only):

Explicit non-conclusion: the data above does not justify flipping the global default. It only says Precise is viable on one high-end Nvidia config for two games. The default-flip question needs exactly the kind of data that a landed harness would let others produce.

Feedback Period

2 weeks, or until a maintainer weighs in on whether the harness is welcome and whether the batching direction aligns with the internal roadmap (especially vs PR #3404's approach).

Questions

  1. Would you merge the measurement harness as a standalone PR, behind the opt-in env var? Or do you prefer this routed through Tracy zones instead?
  2. Is GpuReadbacksMode::Batched a direction you'd consider, or does it overlap too much with PR video_core: Readback optimizations #3404? If so, I'd rather rebase on video_core: Readback optimizations #3404's work than start fresh.
  3. Is there an existing contributor with AMD RDNA 2 / 3 / 4 hardware who'd be willing to run the harness against their setup? That's the only way to close the "does batching actually fix the AMD regression" loop.
  4. Any game-specific repro cases you'd want run through the harness before deciding? (I still have Bloodborne + DriveClub set up.)

Any Other Things

  • Full research dossier that seeded this work: docs/readbacks-perf/research/readbacks-research.md. Covers PR/issue timeline, per-frame cost breakdown from the source, failure modes, comparable techniques from yuzu / Ryujinx / Dolphin / PCSX2 / PPSSPP.
  • Related prior work on this fork: the same harness was how I traced DriveClub CUSA00003's race-start blackout back to readbacks_mode being off (issue: guest auto-exposure is a GPU→CPU histogram feedback loop that fails silently under Disabled). Write-up at docs/driveclub-investigation/phase-31-readbacks-mode-fix.md on the gamma-debug branch.
  • All benchmarks are reproducible via scripts/benchmark_readbacks.sh <mode> <label> <seconds> plus scripts/analyze_readbacks.py <run-dir> on the readbacks-perf branch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions