Skip to content

Hot Path Vulkan Prepared Vertex Layout

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

Prepared Vulkan Vertex Layouts

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: 322986f4c5502b3fa887a9605ac8cd88e9b2c579

Scope: 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.

Why this is applicable

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.

Why it may help

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.

Current flow

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]
Loading

Candidate flow

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]
Loading

Proposed plan contents

A PreparedVertexLayout may contain:

  • active attribute indices in processing order;
  • uniform attribute mask;
  • compressed/conversion mask;
  • D3D swizzle mask;
  • VkFormat for 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.

Layout key and invalidation

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.

Seen in the wild

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.

Code and ownership map

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

Evidence and counters

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

Risks

  • 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.

Qualification

  1. Split current code into pure structural preparation and live bind helpers without changing behavior.
  2. Differentially compare generated masks/descriptions for existing unit/XISO layouts.
  3. Test every supported attribute format/count/stride and inline mode.
  4. Test layout changes one field at a time and prove rebuilds.
  5. Test DMA base/remap changes without layout changes and prove live address refresh.
  6. Test provoking values, stride-zero uniform attributes and conversion/swizzle routes.
  7. Test surface overlap/readback, dirty pages, snapshots and renderer teardown.
  8. Measure plan hit ratio before claiming value.
  9. Compare Release CPU profiles, frame tails and pipeline-cache behavior.

Disposition

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.

Related pages

Full technical reference

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

1. 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.

2. Source seams

File / function Role
hw/xbox/nv2a/pgraph/vk/vertex.c pgraph_vk_bind_vertex_attributes() and format/description construction

3. Current execution path

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
Loading

4. Candidate execution path

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
Loading

5. Why it may help

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.

6. Work removed

  • Repeated full attribute-slot classification
  • Repeated VkVertexInput* description construction
  • Repeated conversion/swizzle-mask derivation

7. Work that remains mandatory

  • Current DMA mapping
  • Vertex range/freshness
  • Uploads/conversion
  • Provoking vertex values
  • Inline data and stride-zero semantics

8. Implementation sketch

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.

9. Correctness contract

  • 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.

10. State, ownership, invalidation, and fallback

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.

11. Advantages

  • Removes repeated structural work
  • Clear separation between layout and data
  • Can reduce pipeline-key/preparation churn
  • Easy hit/rebuild counters

12. Costs and failure modes

  • 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

13. Evidence counters

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.

14. Qualification matrix

  • 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.

15. Acceptance rule

Promote when stable-layout workloads show a high hit rate, output matches reference, and Release profiling shows lower vertex/draw preparation cost.

16. Rejection rule

Reject or use only a current-plan cache if layout churn, key construction, or lookup cost erases the benefit.

17. Related architecture and history

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.

Clone this wiki locally