Repository navigation
Hot Path Vulkan Prepared Vertex Layout
Lifecycle: Performance - Unproven · Type: Prepared-state optimization
State: Source-backed; runtime value is not established.
Status: Source-backed structural-plan candidate; not implemented and not benchmarked.
Reviewed source:
322986f4c5502b3fa887a9605ac8cd88e9b2c579Scope: Repeated vertex-attribute classification and Vulkan description construction in
pgraph_vk_bind_vertex_attributes().
This candidate separates vertex layout work from vertex data work. Current binding rebuilds structural information each invocation even when attribute formats, counts, strides and enabled/uniform classification remain unchanged.
It is different from Bounded Vertex Uploads. Bounded uploads reduce how much vertex data is copied. A prepared layout avoids reconstructing how that data will be interpreted.
Current code resets masks and active-description counts, walks all NV2A attribute slots, classifies formats, selects Vulkan formats, determines conversion/swizzle requirements, resolves binding locations, and builds VkVertexInputBindingDescription and VkVertexInputAttributeDescription arrays.
The same function also performs draw-dependent operations:
- resolves current DMA-backed source addresses;
- computes ranges from the draw's minimum/maximum element;
- schedules memory freshness/upload work;
- reads the provoking vertex to update inline values;
- handles inline-array versus DMA-backed data.
These groups have different dependencies and should not automatically have the same rebuild lifetime.
Potentially avoided work includes:
- scanning disabled/uniform attributes;
- repeated format switch/classification;
- repeated Vulkan format lookup;
- rebuilding binding and attribute descriptions;
- recomputing conversion and swizzle masks;
- repeatedly assigning structural binding locations;
- resolving the same pair of DMA descriptors once per attribute instead of once per binding operation.
The benefit depends on layout stability:
potential
= vertex-bind calls
- prepared-layout rebuilds
If guest software changes layout almost every draw, a persistent plan may not pay for itself.
flowchart TD
A[Bind vertex attributes for draw] --> B[Reset structural masks and counts]
B --> C[Iterate every NV2A attribute slot]
C --> D[Classify enabled/uniform and format]
D --> E[Select VkFormat and conversion/swizzle behavior]
E --> F[Resolve DMA address and draw range]
F --> G[Update provoking/inline value]
G --> H[Build binding and attribute descriptions]
H --> I[Schedule required memory freshness/upload]
I --> J{More attributes?}
J -->|Yes| C
J -->|No| K[Use constructed vertex input state]
flowchart TD
A[Vertex layout-affecting state] --> B[Build layout key]
B --> C{Prepared layout hit?}
C -->|No| D[Classify active/uniform attributes]
D --> E[Build formats, binding locations and conversion masks]
E --> F[Publish PreparedVertexLayout]
C -->|Yes| G[Reuse prepared structural plan]
F --> G
G --> H[Resolve current DMA descriptors/addresses]
H --> I[Apply draw range and freshness/coherency checks]
I --> J[Update provoking/inline values]
J --> K[Bind using prepared descriptions plus live offsets]
A PreparedVertexLayout may contain:
- active attribute indices in processing order;
- uniform attribute mask;
- compressed/conversion mask;
- D3D swizzle mask;
-
VkFormatfor each active attribute; - stable attribute-to-binding-location mapping;
- binding/attribute description templates;
- format-specific copy/conversion kernel identifiers;
- which of the two vertex DMA objects each attribute uses.
It must not contain stale draw-specific values:
- mapped host pointers or guest addresses without a proven lifetime;
- minimum/maximum element range;
- current dirty/freshness state;
- current inline/provoking values;
- pending surface readback result;
- command-buffer ownership/lifetime data.
The key must cover every field that changes structural interpretation, including:
- attribute enabled/count state;
- format/type/count;
- stride where it appears in Vulkan binding descriptions;
- inline versus DMA-backed mode and inline stride;
- conversion/swizzle requirements;
- any renderer capability or fallback that changes selected format;
- shader-visible location contract if it can change.
DMA base addresses should normally remain live draw data. A narrower per-call optimization can map vertex DMA A and B once each and reuse those results for attributes selecting the same descriptor.
Direct graphics-API precedent: Vulkan expresses vertex input through prepared binding and attribute descriptions, usually incorporated into a pipeline or dynamic vertex-input state. Engines commonly derive a reusable layout object from mesh/format declarations and combine it with live buffers and offsets at draw time.
The NV2A-specific complication is that inline values, conversion, DMA descriptors and guest memory coherency remain dynamic and cannot be frozen into a host-engine-style mesh layout blindly.
| Area | Role |
|---|---|
vk/vertex.c::pgraph_vk_bind_vertex_attributes() |
Current mixed structural/live processing |
PGRAPHState::vertex_attributes[] |
Guest-derived attribute descriptions and inline values |
PGRAPHVkState description arrays/masks |
Current output of layout preparation |
nv_dma_map() |
Live source mapping for vertex DMA A/B |
| Render/PFIFO thread | Owns plan use, state changes and coherency scheduling |
vertex_bind_calls
vertex_layout_key_checks
vertex_layout_plan_hits
vertex_layout_plan_rebuilds
active_attributes_processed
attribute_slots_scanned
vertex_dma_map_calls
prepared_layout_cpu_ns
live_vertex_bind_cpu_ns
Useful ratios:
plan hit ratio
slots scanned per active attribute
DMA maps per bind
Expected successful signature:
stable-layout draw sequence
-> one plan rebuild
-> many plan hits
-> live address/range/coherency work unchanged
-> lower draw-preparation CPU
- Omitting a layout-affecting state bit from the key.
- Caching guest/host pointers across DMA remap or snapshot restore.
- Treating stride as live when it changes Vulkan structural state, or structural when a dynamic path can safely handle it.
- Reusing descriptions across inline and DMA-backed paths incorrectly.
- Stale provoking-vertex or inline-value state.
- Hiding required surface readback or dirty-memory checks inside the plan.
- Cache lookup/hash cost exceeding reconstruction cost for highly dynamic layouts.
- Growing pipeline/layout combinations and shifting work downstream rather than removing it.
- Split current code into pure structural preparation and live bind helpers without changing behavior.
- Differentially compare generated masks/descriptions for existing unit/XISO layouts.
- Test every supported attribute format/count/stride and inline mode.
- Test layout changes one field at a time and prove rebuilds.
- Test DMA base/remap changes without layout changes and prove live address refresh.
- Test provoking values, stride-zero uniform attributes and conversion/swizzle routes.
- Test surface overlap/readback, dirty pages, snapshots and renderer teardown.
- Measure plan hit ratio before claiming value.
- Compare Release CPU profiles, frame tails and pipeline-cache behavior.
Applicable and potentially broad, but only if layout stability is measured. Start by splitting structural and live work and adding counters; do not begin with a complex global hash cache.
- Hot-Path Shortcuts
- Bounded Vertex Uploads
- Surface Coverage Index
- Vulkan Renderer
- Vertex Uploads & Render-Pass Lifetime
Primary reference: This subject page is the design and research reference for Prepared Vertex Layout. Pull requests are implementation/test history only.
Lifecycle: Performance - Unproven · Mechanism: Prepared structural draw state
Reviewed source:322986f4c5502b3fa887a9605ac8cd88e9b2c579· Acceptance class: intended exact or behavior-preserving for its admitted scope
Cache the attribute-format/layout plan across draws while still resolving current addresses, ranges, freshness, and provoking values per draw.
This entry remains Unproven: the source path and opportunity are concrete, but no accepted xemu runtime result has established that the candidate improves representative workloads. The page defines what must be bypassed, what remains mandatory, and how the hypothesis can be rejected.
| File / function | Role |
|---|---|
hw/xbox/nv2a/pgraph/vk/vertex.c |
pgraph_vk_bind_vertex_attributes() and format/description construction |
flowchart TD
N0["Each bind resets masks/counts"]
N1["Iterate all attribute slots"]
N0 --> N1
N2["Classify format/conversion/swizzle"]
N1 --> N2
N3["Build Vulkan binding/attribute descriptions"]
N2 --> N3
N4["Resolve current DMA addresses/ranges"]
N3 --> N4
N5["Update provoking/uniform values"]
N4 --> N5
flowchart TD
N0["Layout-state change builds PreparedVertexLayout"]
N1["Draw reuses active attributes/descriptions/masks"]
N0 --> N1
N2["Resolve current DMA addresses/ranges"]
N1 --> N2
N3["Perform coherency/uploads"]
N2 --> N3
N4["Update provoking/uniform values"]
N3 --> N4
Format classification and descriptor construction change with layout state, while addresses and memory contents can change per draw. Separating those lifetimes can remove repeated structural work without caching live vertex data.
- Repeated full attribute-slot classification
- Repeated VkVertexInput* description construction
- Repeated conversion/swizzle-mask derivation
- Current DMA mapping
- Vertex range/freshness
- Uploads/conversion
- Provoking vertex values
- Inline data and stride-zero semantics
VertexLayoutKey key = build_vertex_layout_key(pg, inline_data,
inline_stride);
PreparedVertexLayout *plan = vertex_layout_cache_lookup(&key);
if (!plan) {
plan = build_vertex_layout_plan(&key);
vertex_layout_cache_insert(plan);
stats.layout_rebuilds++;
} else {
stats.layout_hits++;
}
bind_live_vertex_sources(d, plan, min_element, max_element,
provoking_element);The sketch defines the intended boundary; it is not a complete patch and does not replace source review, ownership design, or focused tests.
- The key must cover count, format, stride structure, inline/uniform classification, conversion/swizzle decisions, and any pipeline-visible layout input.
- Addresses, contents, ranges, and freshness must not be frozen in the plan.
- Unsupported/invalid formats retain current assertions/fallback behavior.
Plans are renderer-owned and invalidated by layout-generation changes, renderer reset, and snapshot restoration. A small bounded cache or one current plan may be enough; measure layout diversity before selecting a structure.
The maintained reference path remains the fallback whenever admission, capability, lifetime, or validation is uncertain. A fast result with an unproven dependency is not acceptable as an exact optimization.
- Removes repeated structural work
- Clear separation between layout and data
- Can reduce pipeline-key/preparation churn
- Easy hit/rebuild counters
- Large or incomplete key
- Plan lookup can cost more than rebuilding for high layout churn
- Inline and stride-zero cases complicate admission
- Stale masks/descriptions can misinterpret memory
Counters must be cheap, subsystem-owned, and snapshotted outside the hot loop. Record at least:
- vertex_bind_calls
- layout_plan_hits
- layout_plan_rebuilds
- distinct_layout_keys
- attributes_classified
- DMA_maps
- vertex_prepare_cpu_us
A local reduction is not enough. Evidence must connect route reachability and removed work to fixed guest progress, CPU/GPU time where applicable, and p95/p99 behavior.
- Mutate every layout field one at a time
- Stable layout with changing addresses/ranges
- Inline and array draws
- Stride zero/uniform attributes
- Compressed/swizzled formats
- DMA descriptor remap
- Provoking element changes
- Reset/snapshot
- Pipeline compatibility
Correctness runs may use validation layers, detailed comparisons, and fault injection. Performance acceptance must use optimized Release builds with matching symbols and minimal instrumentation.
Promote when stable-layout workloads show a high hit rate, output matches reference, and Release profiling shows lower vertex/draw preparation cost.
Reject or use only a current-plan cache if layout churn, key construction, or lookup cost erases the benefit.
Vulkan-Vertex-Uploads-and-Render-Pass-Lifetime · Performance-Unproven · Hot-Path-Vulkan-Bounded-Vertex-Uploads · Vulkan-Adaptive-Buffer-Placement
PR pages may be linked as supporting evidence, but they are not substitutes for this reference.
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