Skip to content

Hot Path PGRAPH Bulk Packet Processing

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

PGRAPH Bulk Packet Processing

Lifecycle: Performance - Implemented · Type: Guarded batch shortcut
State: Present in current main.

Status

Field State at reviewed main
Underlying optimization Implemented
Advanced control Implemented, Process vertex packets in bulk
Default On
Apply boundary Live; next eligible packet
Reference route Scalar method-word processing
Evidence counters No shared #94 shortcut artifact at reviewed main
Qualification Valid/malformed focused coverage; historical performance evidence
Reviewed source 322986f4c5502b3fa887a9605ac8cd88e9b2c579, 2026-09-14
Primary history PR #10; control integration PR #37

Summary

The NV2A guest submits method packets through PFIFO/PGRAPH. Some array methods contain many adjacent values whose meaning and destination are already known from the packet header. The bulk route validates the complete packet and processes its values as one bounded unit instead of repeatedly returning through the one-word dispatch path.

The optimization does not reorder guest methods and does not make malformed input acceptable. Current main first calculates whether the complete ARRAY_ELEMENT16, ARRAY_ELEMENT32, or INLINE_ARRAY packet can fit. An oversized packet is consumed and rejected before draw/index state is mutated.

Why It May Help

The expected saving is approximately:

packet values
× avoided repeated method-dispatch/bookkeeping per value

It is most useful when titles send long non-incrementing inline vertex/index packets. Small packets, incrementing packets handled one word at a time, or workloads dominated by renderer/GPU work may show little end-to-end movement.

Bulk handling can also improve cache locality because packet metadata, destination selection, and pending draw state are resolved once for a run of related values. The hardening preflight itself is a correctness cost, not a speed claim.

Observed in xemu

PR #10 preserved the valid bulk path while moving complete capacity validation ahead of expansion or writes. Focused OpenGL/Vulkan ARRAY_ELEMENT16 and ARRAY_ELEMENT32 tests passed, scalar and bulk metadata/framebuffer results matched, and accepted-packet mean/p95 rows stayed within the defined regression gate. The PR also documented that malformed packets previously could enter assertion-backed mutation paths before the whole packet was known to fit.

This is measured xemu evidence for focused packet workloads. It is not proof that every retail title is PFIFO-dispatch limited.

External Precedent

Related emulator pattern: RPCS3 has a dedicated RSX FIFO parser and exposes accuracy/ordering policies because flattening or reordering command streams can improve throughput only while command boundaries remain correct: RPCS3 RSXFIFO.cpp. This is not a drop-in analogue; the PS3 command format and reservation model differ.

General batching precedent: Khronos' command-buffer performance guidance warns that many tiny command buffers add CPU cost and recommends grouping a reasonable amount of work while preserving ownership/order: Command buffer usage. xemu's packet batching occurs before host command recording, but the amortization principle is similar.

Emulation Boundary

Xbox guest
PFIFO method packet and ordered values
    ↓
xemu PGRAPH semantics
method, width, count, pending draw/index state
    ↓
host implementation choice
bulk loop or scalar dispatch
    ↓
required result
same ordered PGRAPH state and draw input

Bulk packet processing is not an Xbox hardware mode. It is an xemu host dispatch optimization around the same guest-visible method sequence.

Code and Ownership Map

Location Role Owner
hw/xbox/nv2a/pgraph/pgraph.c Packet admission, capacity preflight, bulk/scalar dispatch PFIFO/PGRAPH worker under existing locks
hw/xbox/nv2a/pgraph/inline-elements.h Checked planning/capacity helpers PGRAPH worker
ui/xemu-tweaks.h Atomic effective-bit reader UI publishes; worker reads
ui/xemu-tweaks.c Converts saved boolean to active snapshot UI/control thread
focused packet tests Boundary, equivalence, refusal behavior Test process

Actual Current Flow

flowchart TD
    A[PFIFO dispatch unit] --> B[Decode method, count and increment mode]
    B --> C{ARRAY_ELEMENT16, ARRAY_ELEMENT32 or INLINE_ARRAY?}
    C -->|No| S[Normal method path]
    C -->|Yes| D{Bulk control effective?}
    D -->|No| R[Scalar one-word path]
    D -->|Yes| E[Determine words represented by this dispatch]
    E --> F[Plan pending draw values and destination capacity]
    F --> G{Entire packet fits?}
    G -->|No| H[Consume/drop packet and report guest error]
    H --> I[No partial draw or index mutation]
    G -->|Yes| J[Run method-specific bulk loop]
    J --> K[Preserve value order and trace semantics]
    K --> L[Publish equivalent PGRAPH state]
Loading

Incrementing packets may represent only one word at this point; non-incrementing packets can expose the remaining words as one bulk unit. The exact current helper decides the safe amount rather than assuming every header is bulkable.

Maintained Reference Flow

flowchart TD
    A[Eligible array packet] --> B[Bulk control disabled]
    B --> C[Dispatch one method word]
    C --> D[Run existing scalar semantic handler]
    D --> E{More packet words?}
    E -->|Yes| C
    E -->|No| F[Equivalent final state]
Loading

The reference route intentionally pays repeated dispatch/bookkeeping. It must remain semantically useful for isolating batch-boundary defects.

Eligibility and Protected Invariants

Invariant Why required Failure if weakened
Complete packet capacity known before writes Rejection must be atomic Partial inline/index mutation before failure
Pending DRAW_ARRAYS work included Existing values consume destination capacity Overflow or lost pending draw state
ARRAY_ELEMENT16 accounts for two values/word Packet width differs from word count Underestimated capacity
Guest method order preserved PGRAPH state is ordered Different primitive/index stream
Scalar/bulk handlers share semantics Control must isolate dispatch only Mode-dependent rendering
Malformed packet is consumed consistently PFIFO GET/progress must remain coherent Repeated fault or FIFO desynchronization
Trace/log sequence remains interpretable Diagnostics compare modes False differences from missing values

Capacity and destination selection are not user-controllable guards. The toggle chooses bulk versus scalar only after safe admission.

State, Lifetime, Invalidation, and Fallback

The route consumes the current dispatch unit; it does not retain a reusable packet object across frames. Relevant lifetime is the pending primitive/draw state and destination buffer capacity at the moment of admission.

If the full packet cannot fit, current main takes the controlled rejection path rather than falling back after partial mutation. Valid packets can use scalar mode when the control is off. No renderer restart is required because the next packet reads the published effective bit.

Expected Profile Signature

A workload that benefits should show:

same guest packet/value count
    + fewer scalar method dispatches
    + larger average values per bulk dispatch
    + lower PFIFO/PGRAPH CPU bookkeeping
    + unchanged renderer draw/output

The hypothesis is weakened when packet sizes are mostly one word, the target methods are rare, bulk admission is frequently refused, or renderer/GPU cost dominates the selected window.

A hotter pgraph_method_array_bulk() after enabling bulk can be expected: work moved into the bulk loop. Compare total packet-processing CPU per fixed values, not only function rank.

Counters and Evidence

A future #94 source should define at least:

Counter Unit Meaning
pgraph.array_packets packets Eligible method packet headers seen
pgraph.array_words words Values represented by those packets
pgraph.array_bulk_dispatches dispatches Packets/segments handled by bulk route
pgraph.array_scalar_dispatches dispatches Words handled by reference route
pgraph.array_capacity_rejections packets Complete packets refused before mutation
pgraph.array_element16_values values Expanded 16-bit indices

For a fixed workload, record guest packet/value totals so a lower dispatch count is not caused by less guest progress.

Risks and Known Bugs

  • Under-counting pending values or ELEMENT16 expansion can overflow the destination.
  • Preparing a destination before full admission can leave partial state on rejection.
  • A bulk loop can accidentally skip per-value side effects or trace order.
  • Faster dispatch can expose downstream bottlenecks and increase host CPU because more guest work completes.
  • Oversized packet handling is a correctness path, not a benchmark shortcut.
  • RPCS3's FIFO misalignment reports are a useful warning: command flattening/reordering optimizations fail badly when packet boundaries are inferred incorrectly, even though its hardware model differs.

See PR #10 and Pull Request Lessons.

Qualification

Minimum focused gates:

  1. Boundary cases immediately below, at, and above capacity for all three methods.
  2. Pending-draw plus incoming-packet capacity.
  3. ELEMENT16 odd/even counts and two-values-per-word accounting.
  4. Bulk On/Off state, framebuffer, and metadata equivalence for valid packets.
  5. Controlled malformed packet consumption with no destination mutation.
  6. Trace sequence comparison where tracing is enabled.
  7. OpenGL and Vulkan focused workloads, because PGRAPH feeds both.
  8. Release same-binary route evidence and CPU per fixed packet/value count.
  9. Full applicable XISO/retail correctness before changing default behavior.

A default change requires repeatable end-to-end benefit beyond the synthetic packet tests and no new FIFO, assertion, or rendering divergence.

Clone this wiki locally