Skip to content

Hot Path PGRAPH Fast Fence Polling

github-actions[bot] edited this page Sep 14, 2026 · 3 revisions

PGRAPH Fast Fence Polling

Lifecycle: Performance - Implemented · Type: Lock-bypass shortcut
State: Present in current main.

Status

Field State at reviewed main
Underlying optimization Implemented, inherited from upstream xemu commit bc8bef11
Advanced control Implemented, Fast GPU fence polling
Default On
Apply boundary Live; next exact fence read
Reference route General locked PGRAPH register read
Evidence counters No shared #94 route artifact at reviewed main
Qualification Upstream source-confirmed; strong historical PGR2 movement, incomplete direct attribution
Reviewed source 322986f4c5502b3fa887a9605ac8cd88e9b2c579, 2026-09-14
Primary history Upstream xemu; local PR #9 is archival evidence; PR #37 added control

Summary

Xbox software can busy-poll NV_PGRAPH_PATT_COLOR0 as a GPU fence. The general PGRAPH MMIO reader takes pg->lock, but the PGRAPH/FIFO worker also needs that lock to advance the fence. Repeated polling can therefore make the reader and writer convoy on a coarse mutex.

Current main recognizes only an exact 32-bit read of that register. When the control is enabled, the vCPU atomically reads the existing register slot, executes the paired memory-order barrier, logs the read, and returns without entering the general lock path. Every other address or access width still uses the locked implementation.

Why It May Help

A guest busy loop can issue thousands or millions of reads while waiting for one asynchronous fence value. The potential saving is:

exact fence polls
× avoided mutex acquisition / contention / wake overhead

The optimization may improve guest progress twice: the polling vCPU spends less time acquiring the lock, and the PGRAPH worker has fewer reader lock acquisitions competing with the write that ends the poll loop.

It will not help a title that does not poll this register, uses a different access width, waits through another mechanism, or is limited after the fence completes.

Observed in xemu

Local PR #9 reconstructed and measured the already-upstream implementation. In a historical PGR2 comparison, OpenGL and Vulkan guest display-write cadence reached the 30-FPS cap and p95/p99 improved substantially. Mean host CPU rose, especially on Vulkan, which is compatible with more guest work completing rather than lower CPU per unit of work.

The report did not publish direct lock-wait duration or exact fence-read counts. Therefore the historical movement supports the path's importance in PGR2 but does not quantify how much time each avoided lock saved on current main.

External Precedent

Direct project precedent: The live behavior originated upstream in xemu: upstream commit bc8bef11. The fork must preserve that attribution; local PR #9 is an archival measurement and review record, not the origin of the implementation.

General synchronization precedent: Vulkan's synchronization guidance similarly separates memory visibility/order from mere completion and requires explicit dependencies where one agent publishes work another observes: Khronos synchronization examples. Vulkan fences are not the emulated NV2A register, but the ordering lesson is directly relevant.

No stronger cross-emulator analogue was verified for this exact lock-free guest register contract.

Emulation Boundary

Xbox guest CPU
polls NV_PGRAPH_PATT_COLOR0
    ↓
xemu vCPU MMIO callback
locked generic read or narrow atomic read
    ↓
xemu PGRAPH worker
publishes ordered fence value
    ↓
guest observes completion value

The shortcut does not emulate a different NV2A fence. It changes how xemu's host threads communicate the same register value.

Code and Ownership Map

Location Role Owner
hw/xbox/nv2a/pgraph/pgraph.c::pgraph_read() Recognizes exact address/width and performs atomic read vCPU thread
PGRAPH fence method writer in pgraph.c Completes prior effects, executes write barrier, atomically publishes register PFIFO/PGRAPH worker under pg->lock
pg->regs_[NV_PGRAPH_PATT_COLOR0] Single shared register storage; no duplicate shadow value vCPU reader / PGRAPH writer
ui/xemu-tweaks.[ch] Publishes/reads active control bit UI publishes; hot threads read
VMState/reset paths Restore/reset register state through established ownership Migration/reset owner

Actual Current Flow

sequenceDiagram
    participant G as Guest/vCPU
    participant R as pgraph_read
    participant W as PGRAPH worker
    G->>R: 32-bit read PATT_COLOR0
    alt Fast fence control enabled
        R->>R: Atomic load existing register slot
        R->>R: Read barrier pairs with writer publication
        R-->>G: Current or momentarily stale fence value
    else Control disabled or other width/address
        R->>R: Acquire pg->lock
        R->>R: General register read
        R->>R: Release pg->lock
        R-->>G: Register value
    end
    W->>W: Complete preceding PGRAPH effects
    W->>W: Write barrier
    W->>W: Atomic register publication
Loading

A momentarily stale read is acceptable for a polling register: the guest continues polling. Publishing a new value before prior PGRAPH effects are visible is not acceptable.

Maintained Reference Flow

flowchart TD
    A[Guest reads PGRAPH register] --> B[Acquire pg lock]
    B --> C[Read register or special register state]
    C --> D[Release pg lock]
    D --> E[Return value]
Loading

The reference route remains mandatory for:

  • all other registers;
  • non-32-bit accesses;
  • the target register when the control is Off;
  • special registers whose reads mutate state or depend on other protected fields.

Eligibility and Protected Invariants

Invariant Why required Failure if weakened
Exact register address Only this slot has reviewed asynchronous polling semantics Lock bypass for stateful/special registers
Exact 32-bit access Atomicity contract matches storage/access Torn/sub-width or widened behavior
Existing register slot is shared Avoid split sources of truth Reset/load/writer divergence
Writer remains serialized Multiple writers/order must not race Non-monotonic or lost publication
Writer barrier precedes atomic value Fence must not become visible before prior effects Guest proceeds too early
Reader barrier follows observed value Subsequent host reads must observe ordered effects Stale dependent state
General path unchanged Other MMIO semantics remain protected Broad PGRAPH race

The setting gates only the narrow read. It never relaxes writer ordering or other PGRAPH locking.

State, Lifetime, Invalidation, and Fallback

There is no separate fast-path cache. The atomic and locked routes read the same register array entry. Reset, snapshot/load, and normal register ownership therefore must continue to update that single source of truth.

The control is live because it changes only how the next eligible read obtains the value. Disabling it immediately returns those reads to pg->lock.

Fallback is deterministic: any address/size mismatch or disabled bit enters the general locked path.

Expected Profile Signature

A fence-poll-limited workload should show:

same guest fence loop and completion value
    + high exact PATT_COLOR0 read count
    + fewer pg lock acquisitions by vCPU
    + less vCPU/PGRAPH lock contention
    + earlier guest exit from polling loop

Potential secondary effects:

  • higher overall CPU utilization because the emulator completes more guest frames/work;
  • a new downstream renderer bottleneck;
  • reaching an FPS cap, which hides further cadence benefit.

The hypothesis is weakened when exact poll counts are low or lock wait is not present during slow intervals.

Counters and Evidence

Useful owner-local/relaxed evidence would include:

Counter Unit Meaning
pgraph.fence.reads_exact32 reads Eligible fence polls
pgraph.fence.atomic_reads reads Optimized route taken
pgraph.fence.locked_reference_reads reads Eligible reads forced through lock
pgraph.fence.other_width_reads reads Ineligible accesses
pgraph.fence.publications writes Ordered fence updates
pgraph.fence.poll_iterations_per_value reads/value Approximate guest polling intensity

A direct ETW/WPA lock profile should correlate vCPU waits on pg->lock with the PGRAPH writer before attributing a new regression to this path.

Risks and Known Bugs

  • Missing or misplaced barriers can publish the fence before the work it represents.
  • A separate cached fence variable would drift during reset, load, or unusual writes; current main deliberately uses the existing register slot.
  • Expanding eligibility beyond exact 32-bit reads can change MMIO width semantics.
  • Faster empty polling can increase host CPU without increasing useful guest progress.
  • A workload reaching a 30-FPS cap cannot quantify additional headroom from cadence alone.
  • Historical PR #9 measurements compare a source-scoped pair and do not replace current-main fixed-work profiling.

See PR #9 and Pull Request Lessons.

Qualification

Required gates for any change to this shortcut:

  1. Exact address/width positive and negative tests.
  2. Writer/read ordering test where dependent state is published before the fence.
  3. Repeated polling under concurrent writer progress.
  4. Reset and snapshot/load value consistency.
  5. Control On/Off equivalence for final fence values and guest progression.
  6. Direct counts of atomic/locked reads.
  7. Lock-wait attribution on a fence-heavy workload such as the historical PGR2 case.
  8. Fixed guest work plus host CPU time, not utilization alone.
  9. P95/p99/max and cap awareness.
  10. OpenGL and Vulkan coverage because the guest/PGRAPH fence path is shared.

A default change should require current-main evidence that the reference route remains correct and that the atomic route does not create ordering, reset, or migration regressions.

Clone this wiki locally