Repository navigation
Hot Path PGRAPH Fast Fence Polling
Lifecycle: Performance - Implemented · Type: Lock-bypass shortcut
State: Present in currentmain.
| 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 |
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.
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.
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.
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.
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.
| 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 |
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
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.
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]
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.
| 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.
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.
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.
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.
- 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.
Required gates for any change to this shortcut:
- Exact address/width positive and negative tests.
- Writer/read ordering test where dependent state is published before the fence.
- Repeated polling under concurrent writer progress.
- Reset and snapshot/load value consistency.
- Control On/Off equivalence for final fence values and guest progression.
- Direct counts of atomic/locked reads.
- Lock-wait attribution on a fence-heavy workload such as the historical PGR2 case.
- Fixed guest work plus host CPU time, not utilization alone.
- P95/p99/max and cap awareness.
- 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.
DSP tooling guide and navigation updated November 2, 2026 for experimental PR #384, source 8f51d237. Capture/replay usage and limits.
The earlier wiki-wide review was published November 1, 2026. Emulator source: b7140848. Reviewed wiki content: 1d2b671. Current status · Release availability · Publication process.
- Home
- Current status
- Stable versus main
- Implemented mechanisms
- Controls and defaults
- Measured results and limits
- Complete change register
- System
- CPU and vCPU
- Memory and DMA
- Graphics / NV2A
- APU and audio
- Timing and timers
- Platform and I/O
- Hardware observations
- Fidelity boundaries
- Methodology
- PGR2 routes
- Debugging lag / hot paths
- Tools and commands
- DSP capture, replay and fuzzing
- HLE readiness and public XISO proof
- Concurrent DSP shadow and OpenAL proof
- DSP HLE operation inventory
- DSP HLE fix examples and testing
- Development
- Upstream shortlist
- Wiki maintenance
- Research / retired work
- Optimization catalog
- Hot-path shortcuts
- Researched experiments
- Unproven proposal directory
- PR lessons
- September26 snapshot
Repository · PRs · Issues