Repository navigation
Hot Path Vulkan Bounded Vertex Uploads
Lifecycle: Performance - Implemented · Type: Structural optimization
State: Present in currentmain.
| Field | State at reviewed main
|
|---|---|
| Underlying optimization | Implemented |
| Advanced control |
Implemented, Upload only used vertex ranges
|
| Default | On |
| Apply boundary | Live; next remapped vertex upload |
| Reference route | Repack/copy from vertex zero through the draw's upper bound |
| Evidence counters | No shared #94 artifact at reviewed main |
| Qualification | Historical source-scoped evidence; current-main isolated GL/VK performance not rerun |
| Reviewed source |
322986f4c5502b3fa887a9605ac8cd88e9b2c579, 2026-09-14 |
| Primary history | PR #7 archival provenance; control integration PR #37 |
Some NV2A vertex streams cannot be bound directly with their guest layout and are repacked into xemu's inline Vulkan vertex buffers. A draw may reference vertices beginning at start_vertex > 0. The bounded route offsets the input/output pointers to that first used vertex and copies only:
num_vertices - start_vertex
The reference route begins at vertex zero, preserving the older wider copy. Both routes retain the same destination layout and draw indices.
This optimization is separate from the later PR #85/#87/#89 work on surface freshness, ordered vertex-RAM updates, direct writes, and immutable versions. Those are correctness/lifetime experiments and are not controlled by this toggle.
The potential saving is:
unused leading vertices
× active remapped attributes
× source/destination stride copy cost
It matters when indexed or ranged draws reference a suffix/subrange of a larger guest vertex allocation and when one or more attributes require repacking. It can reduce host memory reads/writes, per-vertex conversion/copy calls, and staging-buffer pressure.
It does little when start_vertex == 0, direct vertex binding is possible, ranges are already small, or draw/renderer/GPU work dominates.
PR #7 records that the historical path copied all remapped vertices and asserted start_vertex == 0. The integrated changes specialize common strides and pass the actual minimum element into the copy helper. Equivalent commits are already present in current main.
The PR was closed as archival/superseded rather than merged because main already contained patch-identical changes. Historical results remain provenance, not current-main qualification. No fresh isolated OpenGL/Vulkan retail campaign was performed solely for the control.
Related emulator pattern: Dolphin's vertex manager stages only the vertex/index data required by submitted draws and owns ring/buffer allocation around the current batch: Dolphin VertexManagerBase.cpp.
General transfer precedent: Vulkan buffer copies explicitly name source offset, destination offset, and size; applications commonly limit transfer regions to consumed data while providing the correct synchronization: Khronos synchronization examples.
These precedents support bounded copies. xemu must additionally reproduce NV2A stride, format conversion, DMA, and surface-coherency semantics.
Xbox draw
vertex addresses, formats, strides, min/max element
↓
xemu fetch/repack plan
which attributes require host layout conversion
↓
host memory copy
vertex zero..end OR first used vertex..end
↓
Vulkan draw
same logical vertex values and indices
The Xbox does not expose xemu's repacked staging buffer. The shortcut changes copied host bytes, not the guest draw range.
| Location | Role | Owner |
|---|---|---|
hw/xbox/nv2a/pgraph/vk/draw.c |
Computes remap, first copied vertex, count, input/output offsets | Renderer/PFIFO thread |
vk/vertex.c and common vertex helpers |
Guest fetch spans, formats, DMA/VRAM validation | Renderer/PGRAPH |
vk/buffer.c |
Inline staging/device buffer capacity and offsets | Renderer |
vk/renderer.h |
Remap/buffer state | Renderer |
ui/xemu-tweaks.[ch] |
Live bounded-range bit | UI publishes; renderer reads |
| PR #85/#87/#89 files | Separate surface/freshness/versioning experiments | Not part of this control |
flowchart TD
A[Draw determines min/max required vertices] --> B[Build attribute remap plan]
B --> C[For each active remapped attribute]
C --> D[Read guest source offset and old/new stride]
D --> E{Bounded upload control enabled?}
E -->|Yes| F[first_vertex = start_vertex]
E -->|No| G[first_vertex = 0]
F --> H[copy_count = num_vertices - first_vertex]
G --> H
H --> I[Offset source pointer by first_vertex * old_stride]
I --> J[Offset destination pointer by first_vertex * new_stride]
J --> K[Copy/convert copy_count vertices]
K --> L[Record staging-to-device transfer]
L --> M[Bind same logical base/offset for draw]
The bounded route does not change num_vertices, the guest index stream, or the Vulkan draw. It changes which leading repack bytes are materialized.
flowchart TD
A[Remapped attribute] --> B[first_vertex = 0]
B --> C[Copy every vertex through draw upper bound]
C --> D[Populate staging/device buffer]
D --> E[Bind and draw requested subset]
The reference route is deliberately wider but useful for diagnosing an incorrect minimum, offset, or sparse-range calculation.
| Invariant | Why required | Failure if weakened |
|---|---|---|
start_vertex <= num_vertices |
Count subtraction must not underflow | Huge/out-of-bounds copy |
| Required min/max derived from actual draw/index data | Copy must include every fetched vertex | Missing/corrupt vertices |
| Complete strided fetch span within DMA/VRAM | Last component may extend beyond base vertex | Out-of-range read |
| Per-attribute old/new stride and format preserved | Repack layout must match shader input | Corrupt attributes |
| Input/output pointers both offset consistently | Bounded copy must land at expected logical index | Shifted vertex stream |
| Surface overlap/readback occurs before CPU consumption where required | Guest RAM may be stale behind GPU surface | Stale vertex values |
| Staging capacity/alignment valid | Copy cannot overrun ring | Memory corruption |
| Draw base/index semantics unchanged | Host copy is invisible to guest | Wrong primitive |
Fetch bounds and surface coherency are mandatory correctness work. They are not disabled when the reference copy begins at zero.
Remapped bytes live in transient staging/device buffers for the command-buffer lifetime. The current route relies on the draw's computed range and buffer offset; it does not retain an independent reusable vertex version across future guest writes.
A live control change affects later repacks. It does not rewrite data already recorded into an active command buffer.
Unsupported/direct-binding cases follow their existing paths. Any validation, capacity, or surface-readback requirement remains in force before the copy.
A range-heavy workload should show:
same draw count and fetched vertex values
+ start_vertex > 0 on many remapped draws
+ fewer copied vertices/bytes
+ lower CPU memcpy/repack work
+ lower inline staging consumption
The hypothesis is weakened when:
- most ranges start at zero;
- no attributes require remapping;
- copy bytes fall but ordered vertex-RAM/surface waits dominate;
- local memcpy falls but frame distributions do not move;
- wider reference copies improve locality enough to offset saved bytes (must be measured, not assumed).
| Counter | Unit | Meaning |
|---|---|---|
vk.vertex.remap_draws |
draws | Draws requiring repack |
vk.vertex.range_start_nonzero |
draws | Bounded opportunity exists |
vk.vertex.vertices_required |
vertices | Logical draw range |
vk.vertex.vertices_copied |
vertices | Actual repack work |
vk.vertex.bytes_required/copied |
bytes | Better fixed-work denominator |
vk.vertex.attributes_remapped |
attributes | Active conversion work |
vk.vertex.reference_leading_bytes |
bytes | Extra bytes copied with control Off |
| buffer rollover/growth/finish counts | events | Secondary pressure |
Counters must distinguish this remap copy from vertex-RAM mirror uploads and PR #87/#89 version paths.
- An underestimated
start_vertex/fetch span omits bytes the GPU reads. - Indexed min/max analysis can be wrong for unsupported primitive/index cases.
- Offset only one side and the repacked stream shifts.
- Page-aligned dirty ranges must not replace precise fetch ranges for surface-overlap decisions; PR #85 documents this broader coherency risk.
- A GPU-authored overlapping surface may require readback before CPU repack.
- Later PR #87/#89 work addresses active-batch overwrite/version problems, not this leading-range optimization.
- Fewer copied bytes can increase fragmentation/offset complexity or reveal staging capacity behavior.
- Historical PR #7 data is not a current-main default-benefit proof.
See PR #7, PR #85, PR #87, PR #89, and Pull Request Lessons.
- Non-indexed and indexed draws with
start_vertex0, 1, and near upper bound. - Multiple active attribute formats/strides, including common specialized strides and generic copies.
- Complete strided fetch-span boundary at DMA/VRAM limit.
- True and false surface-overlap/readback cases.
- Control On/Off exact framebuffer/work hash equivalence.
- Copied vertex/byte accounting for reference and bounded routes.
- Staging capacity, alignment, rollover, and command-buffer lifetime tests.
- Vulkan validation and full applicable XISO.
- Representative PGR2/Morrowind cells with p95/p99/max and host CPU/memory bandwidth where available.
- Explicit separation from PR #85/#87/#89 freshness/version telemetry.
A default claim requires real workloads with substantial nonzero starts and repeated end-to-end benefit, not merely a lower byte count in a synthetic draw.
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