Repository navigation
Hot Path Vulkan Color Download Folding
Lifecycle: Performance - Implemented · Type: Submission shortcut
State: Present in currentmain.
| Field | State at reviewed main
|
|---|---|
| Underlying optimization | Implemented |
| Advanced control |
Implemented, Combine color downloads with rendering
|
| Default | On |
| Apply boundary | Live; next eligible surface download |
| Reference route | Finish/submit active work, then use auxiliary single-time download commands |
| Evidence counters | Existing NV2A/Vulkan profiling; shared #94 route artifact absent |
| Qualification | Favorable historical PGR2 sample; not freshly isolated on reviewed main |
| Reviewed source |
322986f4c5502b3fa887a9605ac8cd88e9b2c579, 2026-09-14 |
| Primary history | PR #5 archival evidence; later current-main safety corrections |
When xemu must download a GPU-authored color surface back to host/guest-visible memory, the copy must execute after the commands that produced the surface and before CPU-side consumption. The conservative route finishes active work and records the download in a separate auxiliary command buffer.
For eligible color surfaces, current main can instead record the transition and image-to-buffer copy into the active producer command buffer. Existing command order provides the dependency, and the caller later waits for the containing submission rather than creating an extra submit boundary solely for this copy.
Depth/stencil conversion and other unsupported cases retain the auxiliary path.
The GPU copy itself remains. The expected saving is:
eligible color downloads
× avoided active-buffer finish / separate command-buffer begin / extra submit boundary
This can reduce host submission work, driver calls, and completion waits in workloads that frequently read color surfaces while a producer command buffer is active.
It may not help when downloads occur outside active command buffers, surface production predates the active buffer, depth/stencil conversion is required, capacity growth already submitted the buffer, or GPU copy/readback latency dominates regardless of submission placement.
PR #5 preserved a historical PGR2 Vulkan pair where guest display-write cadence improved about 2.27%, p95 about 4%, p99 about 2.21%, and mean host CPU fell about 12% relative to its source-scoped parent. Morrowind was near neutral/slightly adverse in that isolated observation.
Those results support the mechanism in one workload but are not a current-main claim. Current main also includes a later correction: buffer-capacity growth can submit the active command buffer, so eligibility is recomputed after capacity checks before the command buffer is selected.
Direct API ordering precedent: Vulkan command order plus explicit barriers can keep a producer and dependent copy in one command stream when their resource/layout dependencies are correct: Khronos synchronization examples.
Related batching precedent: Khronos' command-buffer performance sample explains that excessive small command buffers and frequent allocation/reset work add CPU overhead, while grouping a reasonable amount of work can reduce it: Command buffer usage.
These precedents justify investigating the boundary. They do not prove xemu's surface lifetime, layout transitions, or guest readback ordering.
Xbox-visible event
GPU-authored surface becomes CPU/guest-visible
↓
xemu coherency decision
which surface, which bytes, when producer work completes
↓
Vulkan realization
copy in active producer command buffer OR auxiliary submission
↓
required result
same downloaded bytes available before CPU use
A Vulkan submission boundary is not an NV2A hardware concept. xemu can reorganize it only while preserving guest-visible surface data and ordering.
| Location | Role | Owner |
|---|---|---|
hw/xbox/nv2a/pgraph/vk/surface.c::download_surface_to_buffer() |
Determines finish/fold route, records transitions/copy, maps result | Renderer/PFIFO thread |
pgraph_vk_finish() |
Submits/waits active command work for required reasons | Renderer thread |
pgraph_vk_begin_nondraw_commands() |
Records dependent nondraw work in active buffer | Renderer thread |
pgraph_vk_begin_single_time_commands() |
Auxiliary reference route | Renderer thread |
vk/buffer.c capacity helpers |
May finish and replace staging/compute buffers | Renderer thread |
ui/xemu-tweaks.[ch] |
Live route bit | UI publishes; renderer reads |
flowchart TD
A[Dirty surface download requested] --> B[Classify color/depth-stencil and conversion]
B --> C[Check active command buffer and surface draw time]
C --> D{Color surface produced after active buffer start and folding enabled?}
D -->|No| R[Reference finish/auxiliary route]
D -->|Yes| E[Initial folded eligibility]
E --> F[Ensure staging/compute capacity]
F --> G{Capacity growth submitted/replaced active buffer?}
G -->|Yes| H[Recompute eligibility from current ownership]
G -->|No| H
H --> I{Still eligible color download?}
I -->|No| R
I -->|Yes| J[Use active nondraw command buffer]
J --> K[Transition producer image to transfer source]
K --> L[Record image-to-buffer copy]
L --> M[Restore image layout]
M --> N[Containing submission establishes completion]
R --> O[Finish active work if ordering requires]
O --> P[Begin single-time command buffer]
P --> K
N --> Q[Map/convert/swizzle and publish guest-visible bytes]
The source re-evaluates eligibility after capacity preparation because resource growth is allowed to finish the active submission.
flowchart TD
A[Surface download] --> B{Active producer work may own surface?}
B -->|Yes| C[pgraph_vk_finish with surface-down reason]
B -->|No| D[Continue]
C --> D
D --> E[Begin auxiliary single-time command buffer]
E --> F[Transitions and image-to-buffer copy]
F --> G[Submit and wait auxiliary work]
G --> H[CPU conversion/copy to guest memory]
Depth/stencil compute packing, older surfaces, inactive command-buffer cases, and any failed folded proof use this path.
| Invariant | Why required | Failure if weakened |
|---|---|---|
| Active command buffer exists | There must be a producer submission to fold into | Invalid command handle/order |
surface->draw_time > command_buffer_start_time |
Active buffer actually produced current contents | Copy before producer work |
| Surface is color | Current folded route excludes special depth/stencil packing | Wrong bytes/layout |
| No depth/stencil compute conversion | Conversion has separate buffers/order | Missing conversion or hazards |
| Capacity remains valid after preparation | Growth may finish/replace command state | Record into stale/non-active buffer |
| Image transitions/barriers preserved | Copy needs correct access/layout | Validation error or stale data |
| Caller-visible completion unchanged | CPU cannot consume early | Guest reads stale/incomplete bytes |
| Swizzle/downscale/packing semantics unchanged | Submission placement must not change data | Different downloaded image |
The control gates placement of the eligible copy. It does not disable surface coherency or completion.
The active command buffer, its start time, and the surface draw time establish producer ownership. Storage buffers used by the copy have their own allocation/mapping lifetime. Capacity growth can invalidate the initial assumption by submitting active work and replacing an allocation.
The folded route therefore has two decision points: before capacity preparation and immediately before command-buffer selection. Any lost condition falls back to the auxiliary route.
After recording, CPU-side access remains blocked until the containing submission is complete. The shortcut cannot treat “copy recorded” as “bytes available.”
A useful workload should show:
same surface download count and bytes
+ folded color downloads > 0
+ fewer SURFACE_DOWN finishes / auxiliary submissions
+ fewer submit-and-wait boundaries
+ unchanged readback output
Potential next bottlenecks:
- GPU copy or layout transition time;
- command-buffer completion wait;
- CPU swizzle/downscale/format conversion;
- staging-buffer growth;
- unrelated surface readbacks.
If finish counts fall but total waits do not, the dependency may have moved to the containing submission.
| Counter | Unit | Meaning |
|---|---|---|
vk.surface.download_requests |
downloads | All surface readbacks |
download_color/depth_stencil |
downloads | Route class |
download_fold_eligible |
downloads | Initial/current proof passed |
download_folded |
downloads | Copy recorded in active buffer |
download_auxiliary |
downloads | Reference route used |
download_finish_surface_down |
finishes | Active producer explicitly finished |
download_capacity_invalidations |
downloads | Initial eligibility lost after growth |
download_bytes |
bytes | Fixed-work denominator |
| submit/wait CPU and GPU time | ns/us | Boundary impact |
For a fixed mode, fold_eligible should be explainable as folded plus forced-reference/fallback categories. Record bytes and guest progress so fewer downloads are not mistaken for optimization.
- Folding a surface whose producer is outside the active command buffer copies stale data.
- Reusing the initial eligibility after capacity growth records against invalid ownership; current main explicitly recomputes.
- Depth/stencil and compute-packing cases cannot be treated like ordinary color copies.
- Removing an explicit finish can relocate the same wait to the containing submission.
- Incorrect transitions/barriers may pass on one driver and fail on another.
- A CPU map before containing submission completion is a correctness failure.
- Historical PR #5 evidence is favorable but source-scoped and lacks direct submission counters.
See PR #5, PR #83's wait-relocation lesson, and Pull Request Lessons.
- Eligible active color surface produced inside current command buffer.
- Surface produced before active buffer start (must use reference).
- No active command buffer.
- Depth/stencil and compute-conversion cases.
- Capacity-growth case that submits/replaces active work before recomputation.
- Downscale, swizzle, linear, and format-specific output oracles.
- Exact downloaded-byte/hash comparison On versus Off.
- Vulkan validation across multiple drivers where practical.
- Counters for finishes, folded/auxiliary copies, submissions, waits, and bytes.
- PGR2/Morrowind same-binary cells with p95/p99/max and host CPU/GPU.
- Command-buffer rollover, failure injection, teardown, and snapshot/lifecycle checks.
A performance claim requires evidence that a synchronization boundary was removed rather than merely renamed or moved.
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