Repository navigation
Vulkan Vertex Uploads and Render Pass Lifetime
Reviewed: November 1, 2026 · Source: main
b7140848. Release availability: Vertex/surface freshness #85, vertex versions #89 and checked CPU-decode fixes #125 are included in v0.8.136-0-g458730bf5373 (September 30, 2026). This page describes their current contracts.
Two different problems meet here: read the newest vertex bytes, and preserve the bytes earlier recorded draws must read. Freshness repair is a correctness benefit. Direct writes and private versions can avoid copies or pass breaks only after that repair is satisfied.
#85 and #125 are merged. #89 is also merged and includes eligible unread-page updates and bounded immutable private vertex versions. The earlier #87 closed unmerged; portions of its direction were incorporated into #89. It is not the current implementation branch. Separate descriptor-capacity #83 and state-reuse #88 experiments also closed unmerged.
The current Upload only used vertex ranges and Avoid ordered vertex copies controls default on and apply to eligible subsequent draws. The former avoids copying unused leading vertices when attributes are repacked; the latter permits lifetime-proven shortcuts. See Using Performance Controls.
Vertex fetches may overlap a GPU-authored surface newer than guest VRAM. Compute and validate the complete consumed span against DMA and VRAM bounds, then reconcile overlapping dirty surfaces before CPU decoding, constant-attribute refresh or copying bytes. An invalid span rejects the draw segment; a failed required Vulkan readback cannot silently supply stale geometry.
Surface overlap is checked using the actual consumed bytes before expanding upload/dirty tracking to page boundaries. Page alignment must not cause an unrelated adjacent surface to be downloaded. A stable DMA handle or a clean mirror alone is not a freshness proof.
CONSERVATIVE ORDERED PATH
reconcile source -> staging allocation/copy -> end pass if needed
-> vertex-read/transfer barrier -> vkCmdCopyBuffer -> transfer/vertex barrier
-> later draw
CURRENT ELIGIBLE PATHS
reconcile source -> no earlier active-batch page reader? -> direct mapped write
-> conflict + bounded private draw eligible? -> immutable slice
-> otherwise -> conservative ordered copy
flowchart TD
A[Validate consumed DMA and VRAM spans] --> B[Reconcile actual dirty surface overlap]
B --> C{Guest dirty or fixed mirror stale?}
C -->|No| D[Use fixed mirror]
C -->|Yes| E{No active batch or proven unread pages?}
E -->|Yes| F[Direct mapped mirror update]
E -->|No| G{Eligible small private draw and no surface overlap?}
G -->|Yes| H[Capture all active attributes into private immutable version]
G -->|No| I[Ordered staging copy with barriers]
H --> J[Keep fixed mirror pages marked stale]
D --> K[Prepare resources and record draw]
F --> K
I --> K
J --> K
K --> L[Mark fixed-mirror reads only after actual recording]
Direct mirror write. Without an active command buffer, a mapped write is safe. In an active batch, the shortcut requires enabled copy shortcuts, active conservative read tracking and no prior recorded reader of the affected pages. Earlier recorded commands can execute later: overwriting their shared bytes prematurely would break guest order. The surrounding finish path supplies host-write visibility.
Private immutable version. For a conflicting small draw, copy every active attribute into a private inline slice rather than overwrite the fixed mirror. Eligibility requires active tracking, bounded DMA/VRAM spans, valid non-overlapping stride/element layout, at most 256 vertices and a 64 KiB scratch buffer minus 256 bytes alignment slack copy budget. The current selection rejects surface-overlap cases. It captures bytes before buffer reservation or descriptor preparation can finish the older batch, then stages the owned snapshot for the new draw.
Versioning deliberately leaves the fixed mirror stale. A separate stale-page bitmap survives consumption of the ordinary dirty bit and forces repair when a later draw returns to the fixed mirror; a full mirror refresh clears that state. Private draws do not mark fixed-mirror pages as read.
Ordered copy. Failed eligibility, unavailable proof or disabled shortcuts preserve the staged path. Before vkCmdCopyBuffer, vertex-input reads are ordered before transfer writes; afterward transfer writes become visible to later vertex-input reads. Staging exhaustion finishes the batch before reuse/growth and can then use a safe direct update. Allocation and command-buffer lifetimes remain coupled.
The public vertex update helpers do not accept a caller-supplied Boolean:
Helper in vk/vertex.c
|
Contract |
|---|---|
pgraph_vk_update_vertex_ram_buffer() |
Performs required surface reconciliation; keeps conservative active-batch ordering. |
pgraph_vk_update_vertex_ram_buffer_after_surface_readback() |
Caller already reconciled surfaces; keeps conservative ordering. |
pgraph_vk_update_unread_vertex_ram_buffer_after_surface_readback() |
Caller already reconciled surfaces and proved the active-batch pages unread; permits mapped update. |
Only the internal update_vertex_ram_buffer() takes surface_readback_complete and allow_mapped_write. The naming makes both proofs explicit at the draw-side call site.
prepare_vertex_ram_backing() resolves fixed/private selection and stale pages. prepare_vertex_data() refreshes CPU-decoded inline values and captures the private snapshot before reservation can roll the command buffer. publish_prepared_vertex_data() publishes the prepared bytes. Pending ranges become the read footprint only after a draw is actually recorded, so a rollover during preparation cannot assign an old draw to the new batch.
Tracking has a 16 quiet-batch retirement window. This bounds steady-state bookkeeping for titles with little opportunity; without tracking an active batch falls back conservatively.
The following measurements describe earlier #87 experimental heads, not current #89 or November main qualification:
| Earlier workload | Pages marked | Direct copies | Observation |
|---|---|---|---|
| PGR2 | about 3.81 million | about 62 | Heavy tracking with few shortcut opportunities |
| Morrowind | about 429,090 | about 27,649 | Many eligible updates |
One trace-only Morrowind capture counted 61,718 render passes and 57,702 ordered copies. It attributed 1,711.548 ms (73.84%) of main-GPU time inside render passes and 375.501 ms (16.20%) around ordered-copy regions. Most pass time is useful drawing; neither percentage is removable overhead or an FPS prediction. The old branch also retained adverse PGR2 p99 observations. These findings motivated adaptive tracking and bounded versions; they do not establish a universal speedup.
The critical cases are surface-to-vertex use before CPU decoding, invalid DMA/VRAM spans, multiple rewrites within one batch, unread versus already-read pages, private-to-fixed stale repair, reservation/descriptor rollover, staging exhaustion, buffer replacement, snapshot and teardown. A CPU page bitmap alone cannot prove their ordering.
OpenGL also needs current bytes, but uses a different host synchronization mechanism. Shared draw-scope lifecycle is monotonic: once a BEGIN/END scope submitted a segment, a later rejected/empty segment must not erase query or dirty ownership.
| File | Responsibility |
|---|---|
vk/draw.c |
Consumed ranges, surface reconciliation, fixed/private backing, snapshot timing and recorded-read footprint |
vk/vertex.c |
Named update contracts, direct/staged copy and inline-value refresh |
vk/vertex-version-policy.h |
Version bounds and stale-page operations |
vertex-fetch-span.h |
Checked consumed-span arithmetic and DMA/VRAM validation |
vk/renderer.h |
Batch read/stale maps, pending spans and scratch lifetime |
vk/buffer.c / vk/surface.c
|
Allocation/retirement and authoritative surface bytes |
See Vulkan Renderer, Vulkan Performance and Shader Binding & Compilation.
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