Skip to content

Hot Path Vulkan Unchanged Scanout

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

Vulkan Unchanged Scanout Reuse

Lifecycle: Performance - Implemented · Type: Retained-result shortcut
State: Present in current main.

Status

Field State at reviewed main
Underlying optimization Implemented
Advanced control Planned in issue #96, absent from reviewed main
Current behavior Reuse automatically occurs when the full current key/eligibility matches
Planned modes Auto / Disabled
Planned apply boundary Live, with renderer-owned reuse invalidation on policy change
Maintained reference Fresh display conversion/render/submission
Evidence counters Planned; absent from reviewed main
Qualification Source-confirmed route; isolated same-binary benefit not yet established
Reviewed source 322986f4c5502b3fa887a9605ac8cd88e9b2c579, 2026-09-14
Primary history Current display implementation; issue #96 control/evidence plan; related issue #90

Summary

xemu's Vulkan display path converts the current guest scanout/surface state into a completed host display image used by the presenter. If the current display request is identical to the state that produced the already-completed image, repeating conversion and submission is unnecessary.

Current main retains a reuse key and simply returns without calling render_display() when:

  • TCG mode permits the path;
  • PVIDEO is not active;
  • no surface upload is pending;
  • display resources were not recreated;
  • every field of the retained key matches.

On a miss, xemu invalidates the old key, renders a fresh display image, waits until it is externally usable, and publishes a new key only if the result remains reusable.

Issue #96 plans an Auto / Disabled control around the final reuse return. The route itself is already live; the control/counters are not.

Why It May Help

The potential saving is:

repeated display updates with unchanged effective scanout
× avoided render_display conversion, command recording, submit/wait and image work

This can matter when the UI/presenter requests updates more often than guest-visible scanout changes, when the same rendered surface persists across guest frames, or when a display conversion is relatively expensive.

It will not help continuously changing scenes, active PVIDEO composition, pending uploads, resource recreation, non-TCG execution modes, or any state that changes one reuse-key field.

Observed in xemu

The route is source-confirmed in current main. The implementation compares a full key rather than only the scanout address and documents that render_display() waits until the display image is externally usable before the key is republished.

Issue #96 was created because the reviewed binary cannot force eligible hits through the fresh path or export reasoned reuse counters. Therefore current evidence cannot yet isolate the saved conversion cost with one executable.

External Precedent

Related presentation pattern: DXGI 1.2 dirty rectangles let an application tell the presentation system which regions changed while the system retains the rest of the prior frame: DXGI presentation improvements.

Related retained-frame pattern: The DXGI Desktop Duplication API reports dirty and moved regions relative to a retained desktop image so consumers process only changes: Desktop Duplication API.

These are related host presentation patterns, not direct emulator analogues. xemu's proof includes guest surface lifetime, draw time, frame time, scanout geometry, PVIDEO, upload state, and Vulkan resource completion.

Emulation Boundary

Xbox-visible scanout and overlay state
    ↓
xemu surface/display state and change/lifetime proof
    ↓
Vulkan host image
reuse completed image OR render fresh image
    ↓
presenter consumes externally usable image

The retained Vulkan image is not Xbox hardware. Reusing it is valid only when it is the same output the current guest state would freshly produce.

Code and Ownership Map

Location Role Owner
hw/xbox/nv2a/pgraph/vk/display.c Reuse key compare/publish, eligibility, render_display, resource destruction Renderer/PFIFO thread
PGRAPHVkDisplayState::reuse in renderer state Retained key validity and fields Renderer
SurfaceBinding Surface lifetime ID, draw time, upload state Renderer surface owner
PVIDEO state Overlay composition eligibility PGRAPH/display owner
framebuffer/presenter handoff Externally usable completed image Renderer + UI presenter ownership
ui/xemu-tweaks.[ch] Planned #96 effective bit/generation; absent at reviewed main Future control layer

Actual Current Flow

flowchart TD
    A[Display update requested] --> B[Resolve scanout surface and display parameters]
    B --> C{Display resources recreated during update?}
    C -->|Yes| R[Fresh path]
    C -->|No| D{TCG enabled?}
    D -->|No| R
    D -->|Yes| E{PVIDEO disabled?}
    E -->|No| R
    E -->|Yes| F{Surface upload not pending?}
    F -->|No| R
    F -->|Yes| G{Retained key valid and every field matches?}
    G -->|Yes| H[Return without render_display; current completed image remains]
    G -->|No| R
    R --> I[Invalidate old reuse key]
    I --> J[render_display]
    J --> K[Record/submit display conversion]
    K --> L[Wait until display image is externally usable]
    L --> M{Still TCG, PVIDEO off and no upload pending?}
    M -->|Yes| N[Publish full new reuse key]
    M -->|No| O[Leave reuse key invalid]
Loading

The reuse hit does not “return a cached image object” to new ownership; it leaves the already-current completed display image in place and omits fresh rendering.

Maintained Reference Flow

Planned Disabled mode should be:

flowchart TD
    A[All existing reuse guards and key fields match] --> B[Record reuse eligible]
    B --> C{Planned policy Auto?}
    C -->|Yes| D[Return with retained completed image]
    C -->|Disabled| E[Invalidate diagnostic boundary]
    E --> F[Render fresh display image]
    F --> G[Wait for external usability]
    G --> H[Publish new key under current policy/state]
Loading

A live policy change must also invalidate the old key before the next update so an image produced on one side of the A/B boundary cannot cross into the other unnoticed.

Eligibility and Protected Invariants

Key/guard Why required Failure if removed
reuse.valid Result must have completed/published history Reuse uninitialized/stale image
Surface lifetime ID Same address can hold a new object Reuse prior surface generation
Surface draw time Surface can change without moving Stale rendered contents
Guest frame time Display-visible state/progression may change Reuse across guest output change
Scanout address Source selection changed Wrong framebuffer
VGA line offset/pitch state Row interpretation changed Distorted image
Width/height Output geometry changed Wrong dimensions
Surface scale factor Conversion resolution changed Wrong scaled image
Interlace mode Field/progressive interpretation changed Wrong lines/fields
TCG eligibility Current route's assumptions are execution-mode-specific Unsupported synchronization semantics
PVIDEO disabled Overlay composition can change output Missing/stale overlay
No pending upload Host surface must contain current guest data Present pre-upload image
No resource recreation Retained image/descriptor/pipeline may be replaced Invalid host object
Fresh render externally complete before publish Key must represent a usable image Presenter races incomplete image

Address equality alone proves almost nothing.

State, Lifetime, Invalidation, and Fallback

The display state owns the completed image and a key describing the guest/host state that produced it. Destroying/recreating the display image sets reuse.valid = false.

Invalidators include:

  • surface destruction/recreation/lifetime change;
  • surface draw/change time;
  • guest frame time;
  • scanout address/line offset/dimensions/scale/interlace;
  • PVIDEO enable/state;
  • pending surface upload;
  • display image/resource recreation;
  • renderer teardown;
  • planned policy-generation change.

The renderer thread must observe a live control change and invalidate its own state. The UI thread should not directly write renderer-owned key fields.

Any mismatch falls back to fresh rendering. A fresh render that ends in an ineligible state leaves the key invalid.

Expected Profile Signature

After #96 exists, a suitable static/repeated-output workload should show:

same display update requests
    + reuse_eligible > 0
Auto:
    reuse_hits > 0
    fewer render_display calls/conversions/submissions
Disabled:
    forced_reference == eligible
    fresh_conversions rise

A changing-scene workload may legitimately show zero eligible hits. It cannot judge the route.

Local improvement may appear as lower display conversion CPU/GPU, fewer submits/waits, or lower presenter preparation. End-to-end movement depends on display conversion being on the limiting path.

Counters and Evidence

Planned minimum:

Counter Unit Meaning
vk.scanout.update_calls updates Display path entered
reuse_key_checks checks Full key compared
reuse_eligible updates All guards/key matched
reuse_hits updates Retained image used
reuse_forced_reference updates Eligible but Disabled
fresh_conversions updates render_display executed
reuse_invalidations events Key deliberately invalidated
blocked_execution/pvideo/upload/recreated updates Eligibility reasons
key_mismatch_* checks Lifetime/draw/frame/address/geometry/etc.
display render/submit/wait CPU/GPU ns/us Local work

For a fixed-policy window:

reuse_eligible = reuse_hits + reuse_forced_reference

Key comparison should occur once; detailed mismatch counters should be returned by that comparison rather than rechecking state.

Risks and Known Bugs

  • Same address with a new surface lifetime is not the same image.
  • A surface can be redrawn without address/geometry change.
  • PVIDEO composition invalidates otherwise identical base scanout.
  • Pending upload means the host resource is not current.
  • Publishing the key before external completion lets the presenter race incomplete work.
  • Live policy changes without invalidation contaminate A/B boundaries.
  • UI/framebuffer ownership can block renderer lifecycle operations; PR #92 demonstrates that display ownership is not local to one function.
  • Reuse can make a stale-image bug look like a performance gain.
  • Planned detailed mismatch counters must not duplicate expensive/side-effectful work.

See issue #96, issue #90, PR #92, display/lifetime PR lessons, and Pull Request Lessons.

Qualification

  1. Equal full key produces a hit; change each field independently to force fresh path.
  2. Same scanout address with new surface lifetime.
  3. Same address/lifetime with changed draw time.
  4. Guest frame-time change without geometry change.
  5. Line offset, dimensions, scale, and interlace changes.
  6. PVIDEO enable/change and color-key/overlay composition.
  7. Pending upload and resource recreation.
  8. Fresh render completion before key publication/presenter use.
  9. Planned live Auto/Disabled transition with owner-thread invalidation and first post-change fresh render.
  10. Exact output/image hash and Vulkan validation.
  11. Static/repeated-output workload proving nonzero eligibility plus changing-scene control.
  12. Display CPU/GPU/submit/wait counters and p50/p95/p99/max.
  13. PGR2/Morrowind, full Vulkan XISO, resize/scale, teardown, snapshot, renderer-switch/framebuffer ownership.

The route should remain Auto only if retained output is provably complete and identical; no amount of saved conversion work permits a stale frame.

Clone this wiki locally