Repository navigation
Hot Path Vulkan Texture Image Sampler Separation
Lifecycle: Performance - Unproven · Type: Cache/ownership redesign
State: Source-backed; runtime value is not established.
Status: Source-backed optimization candidate; not implemented and not benchmarked.
Reviewed source:
322986f4c5502b3fa887a9605ac8cd88e9b2c579Scope: Vulkan texture cache identity, sampler reuse, image ownership and upload avoidance.
This candidate separates two classes of state that currently change at different rates:
- the pixels and host image representation;
- the instructions used to sample those pixels.
The existing clean-stage shortcut can reuse the current complete binding when nothing relevant changed. This proposal is different: it asks whether multiple bindings with different sampling behavior can share one uploaded image and whether identical normalized sampler configurations can share one VkSampler.
In hw/xbox/nv2a/pgraph/vk/texture.c, the texture-cache key contains the image shape and memory identity and sampler-related state:
// FIXME: Separate sampler from texture
key.filter = filter;
key.address = address;
key.border_color = border_color_pack32;
key.max_anisotropy = max_anisotropy;A cache miss can therefore allocate/upload another host image even when guest pixels are unchanged but filtering or address mode changed. Sampler creation is attached to the same cache entry.
This candidate was not covered by Clean Texture Stages. That page explains how an already-valid complete stage binding may be skipped during a slow bind. It does not change the cache identity or allow one image to be paired with multiple samplers.
The potential avoided work is:
- duplicate texture-cache entries for identical pixels;
- repeat texture decoding or upload;
- repeat image allocation and view creation;
- duplicate sampler creation;
- avoidable cache pressure and eviction;
- larger retained VRAM and host allocation footprints.
The opportunity should be largest in scenes that reuse the same guest image while changing filtering, wrapping, anisotropy or border behavior.
A useful first-order model is:
avoidable image work
= sampler-only state changes
× current combined-key miss rate
× image creation/upload cost
flowchart TD
A[Guest texture state and memory] --> B[Build one combined TextureKey]
B --> C[Shape and source address]
B --> D[Filter, address mode, border color, anisotropy]
C --> E[Combined texture-cache lookup]
D --> E
E -->|Hit| F[Reuse image, view and sampler entry]
E -->|Miss| G[Choose host representation]
G --> H[Decode or retain compressed data]
H --> I[Allocate and upload VkImage]
I --> J[Create image view and VkSampler]
J --> K[Publish complete cache entry]
A sampling-only difference participates in the same miss decision as a pixel or representation difference.
Keep the current image cache intact, but create/reuse samplers from a normalized effective sampler key.
flowchart TD
A[Guest sampling registers] --> B[Apply host capability and fallback rules]
B --> C[Normalized effective sampler key]
C --> D{Sampler cache hit?}
D -->|Yes| E[Reference existing VkSampler]
D -->|No| F[Create VkSampler]
F --> G[Publish sampler entry]
G --> E
The key must include extension payload values such as a custom border color, not temporary pNext pointer addresses.
flowchart TD
A[Guest texture source and shape] --> B[Select effective host image representation]
B --> C[Image-content and representation key]
C --> D{Image resource hit?}
D -->|Yes| E[Reference existing image and view]
D -->|No| F[Decode or retain source data]
F --> G[Allocate and upload image]
G --> H[Publish image resource]
H --> E
I[Normalized sampling state] --> J[Sampler cache]
E --> K[Binding references image resource]
J --> K
Multiple bindings may then pair one image/view resource with different sampler resources.
Filtering is not always sampler-only in this fork. It can affect whether native block-compressed upload is eligible because the host format must support required filtering features.
Therefore the safe split is not “remove filter from the old key.” The renderer must first resolve the effective host representation. Anything that changes the selected image format remains part of image identity. Only state that changes sampling of an otherwise identical representation belongs exclusively to sampler identity.
| Object | Suggested owner | Lifetime rule |
|---|---|---|
| Image allocation and view | Image-resource cache | Retain while referenced by bindings or in-flight commands |
| Sampler | Sampler cache | Retain while referenced by descriptors/in-flight commands |
| Binding | Texture-stage/cache entry | References image and sampler independently |
| Guest-content validation | Image resource | Invalidates/reuploads image data, not unrelated samplers |
Eviction must respect Vulkan object lifetime and command-buffer use. Separating objects without independent reference/in-flight ownership would create use-after-free risk.
Direct API precedent: Vulkan deliberately represents sampled images and samplers as separate objects. Applications commonly cache immutable sampler descriptions separately from image resources and combine them through descriptors.
That precedent supports the ownership split, but it does not prove xemu's guest-state normalization or invalidation rules. NV2A texture representation, palette dependencies, surface aliases and native-BC eligibility remain xemu-specific.
Add counters before changing ownership:
texture_bind_requests
combined_key_hits / misses
image_resource_hits / misses
image_creations
image_upload_bytes
sampler_requests
sampler_cache_hits / misses
sampler_creations
sampler_only_state_changes
sampler_only_changes_that_change_representation
image_cache_entries / sampler_cache_entries
The decisive focused test alternates filter/address/border state while holding guest pixels, dimensions and source address constant.
Expected successful signature:
sampling state changes
-> sampler request changes
-> image resource remains a hit
-> image_creations and uploaded bytes do not increase
-> output remains equivalent
- Treating representation-affecting filter capability as sampler-only.
- Omitting palette identity or palette dirtiness from image validity.
- Sharing an image across incompatible surface-source/scaling states.
- Incorrect custom-border-color keying.
- Destroying image/sampler objects while descriptors or command buffers still reference them.
- Increasing lookup/refcount overhead enough to offset savings in low-churn workloads.
- Cache growth from separating one bounded cache into two unbounded caches.
- Unit-test normalized sampler-key equality and inequality.
- Unit-test representation selection before image-key construction.
- Add a sampler-only state-churn XISO oracle.
- Verify image creation/upload counts fall while output hashes remain stable.
- Exercise texture/palette dirtying, DMA remap, surface aliases and native/fallback BC formats.
- Exercise cache eviction, command-buffer rollover, renderer teardown and snapshot restore.
- Run Vulkan validation and representative texture-heavy titles.
- Compare frame tails, CPU time, VRAM and cache occupancy.
A small sampler-cache experiment should be evaluated separately from the larger image-ownership split.
Applicable and the highest-value additional graphics candidate from the audit. Implement sampler-object sharing first, then evaluate independent image ownership only after representation and lifetime counters prove the larger split is worthwhile.
Primary reference: This subject page is the design and research reference for Texture Image / Sampler Separation. Pull requests are implementation/test history only.
Lifecycle: Performance - Unproven · Mechanism: Cache and ownership redesign
Reviewed source:322986f4c5502b3fa887a9605ac8cd88e9b2c579· Acceptance class: intended exact or behavior-preserving for its admitted scope
Separate texture pixel representation from sampling state so filter/address/border changes do not duplicate image allocations and uploads when the underlying host image can be shared.
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/texture.c |
create_texture(), combined cache key, sampler creation and FIXME |
flowchart TD
N0["Build combined image+sampler key"]
N1["Lookup texture entry"]
N0 --> N1
N2["Sampler-only difference causes miss"]
N1 --> N2
N3["Allocate image"]
N2 --> N3
N4["Upload/convert pixels"]
N3 --> N4
N5["Create sampler"]
N4 --> N5
N6["Bind combined entry"]
N5 --> N6
flowchart TD
N0["Select effective host image representation"]
N1["Lookup/create image by content/representation key"]
N0 --> N1
N2["Normalize sampler state"]
N1 --> N2
N3["Lookup/create sampler by sampling key"]
N2 --> N3
N4["Bind image view + sampler"]
N3 --> N4
Pixels and sampling instructions change at different rates. The source explicitly notes that sampler and texture should be separated. The complication is that filtering can affect whether a native compressed representation is legal, so representation selection must happen before image identity is declared independent.
- Duplicate image entries caused only by sampling-state changes
- Duplicate uploads/allocations
- Duplicate sampler objects after normalization
- Content validation
- Image representation selection
- Sampler capability/fallback handling
- Binding update
- Lifetime tracking for both objects
ImageKey image_key = select_image_representation(pg, guest_texture);
ImageResource *image = image_cache_get_or_create(&image_key);
SamplerKey sampler_key = normalize_sampler_state(pg, guest_sampler,
&image_key);
VkSampler sampler = sampler_cache_get_or_create(&sampler_key);
bind_texture_stage(stage, image->view, sampler);The sketch defines the intended boundary; it is not a complete patch and does not replace source review, ownership design, or focused tests.
- Image identity covers every input that changes uploaded representation: format, dimensions, mip layout, palette/source, scale, compression decision, and content version.
- Sampler identity uses effective host behavior, including capability fallbacks and extension data values rather than temporary pointer addresses.
- Image/view/sampler lifetimes must extend through in-flight draws.
Start with sampler-object sharing while retaining the existing image cache. The second stage introduces independent image ownership/reference counting. Reset, eviction, renderer recreation, capability changes, and in-flight use govern destruction.
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.
- Can eliminate large duplicate image work
- Normalizes and reuses equivalent samplers
- Improves cache occupancy
- Separates lifetimes conceptually
- Major ownership/refcount change
- Incomplete image key causes representation mismatch
- Filtering/compression interaction complicates sharing
- More cache lookups and objects to manage
Counters must be cheap, subsystem-owned, and snapshotted outside the hot loop. Record at least:
- texture_requests
- image_cache_hits/misses
- sampler_cache_hits/misses
- image_creations
- sampler_creations
- uploaded_bytes
- entries_per_guest_image
- frame p95/p99
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.
- Hold pixels constant while alternating filter/address/border
- Native compressed vs decompressed representation
- Palette and mip changes
- Capability fallback normalization
- Eviction with several samplers per image
- In-flight destruction
- Reset/snapshot/recreation
- Compare output and upload counts
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 sampler sharing first when normalized keys are correct and creation falls. Promote image separation only after representation/lifetime tests and meaningful duplicate upload evidence.
Reject or stage more narrowly if sampler churn does not create image misses, representation choice cannot be separated safely, or ownership complexity outweighs measured savings.
Vulkan-Renderer · Performance-Unproven · Hot-Path-Vulkan-Clean-Texture-Stages · Hot-Path-Vulkan-Direct-Surface-Sampling
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