Repository navigation
Vulkan Learned Fallback Readiness
Reviewed: November 1, 2026 · Source: main
b7140848. Release availability: #228 is merged and included in v0.8.136-0-g458730bf5373 (September 30, 2026); #335/#336 are in reviewed main only. The September evidence below remains tied to its historical candidates.
This page preserves the reusable findings from the 2026 shader-stall investigation around xemu #198 and umbrella PR #203. It is a research/validation record, not a statement that every reported title hitch is fixed.
The focused implementation in PR #228 merged on September 25, 2026. Earlier stacked experiments are archived below so their lessons remain available without leaving them in the delivery queue. See the September 26 development status for the historical snapshot; the current contracts are below.
Fresh configurations select Prewarm, with explicit saved choices preserved. Off/Fallback/Prewarm/Always are selected in Settings → Advance; mode changes require a full process restart. Prewarm requires persistent shader caching for cross-launch history. This startup latch is distinct from renderer creation and from worker readiness.
#228 retains learned families and asynchronously prepares missing vertex, optional geometry and fallback-fragment stages. Completion validates stage/generation/ticket, publishes under renderer ownership, wakes matching families and prepares a complete fallback pipeline. A module alone cannot enable a draw or specialized takeover. Queue priority is REQUIRED > VISIBLE > PREWARM; bounded retries and retained results survive temporary capacity/publication deferral.
#335 makes pending pipeline work completion-driven. A pending request's zero retry deadline no longer schedules an immediate timer loop. Queue pressure, shader waits and consumed failures retain appropriate bounded timed retries.
#336 learns newly observed families for future launches. It removes optional live fallback compilation after a specialized draw already has its executable and restricts speculative prewarm admission to validated history loaded at launch. Actual draw demand and Always retain their distinct paths; the family-history disk ABI is unchanged.
BEFORE #335/#336
pending pipeline -> zero deadline -> PFIFO service -> pending -> timer
served specialized draw -> learn -> optional live fallback build
AFTER
pending pipeline -> worker completion wake -> renderer publication
failed work -> bounded retry timer
served specialized draw -> record once -> saved recipe for next launch
launch-loaded recipes -> bounded stage / fallback pipeline preparation
Current history is bounded to 1,024 records / 8 MiB, with at most 32 launch candidates and a prewarm inflight ceiling of four. History needs lead time: an unlearned first launch is still uncovered.
#335's balanced Deck PGR2 route improved cadence/CPU/tails, but the reported texture-control p95/max increased 2.40%. The owner accepted that tradeoff; its cause was not declared repaired. Windows timings remained cache-ineligible and two selected readiness tests lacked pinned oracles.
#336's Deck Vulkan ABBA/BAAB comparison used parent ecd776d45227 and candidate 458e000d1e (four runs each). Median guest cadence improved 9.505→14.974/s, process CPU 364.131→276.695% and p99 guest interval 199.448→102.395 ms. These are frozen-route guest metrics, not host-presented FPS or proof of equal simulation progress; synthetic metrics were mixed/inconclusive.
A separate #336 loaded-history run, 20261007-102526160-22d97245c9334728bf9de0fbf159b1ba, produced black start/end images and stopped guest flips. The same seeded parent passed. A later candidate repeat passed, but the intermittent failure remains unresolved and was explicitly accepted for proceeding. Its trailing cadence is not a valid performance result. Private raw evidence, missing readiness oracles and Windows cache limits remain disclosed in #336.
Current pinned sources: shaders.c, draw.c, hybrid-ready.h, hybrid-family.c, hybrid-family-history.c, hybrid-prewarm.h. See Hybrid Specialization and Shader Caching.
The remaining sections retain the September #228 investigation, exact candidates and archived attacks. Their references to “main” describe that investigation's parent, not the November implementation above.
The September recheck found that its parent main already had most of the accurate hybrid route:
- complete cached-stage to binding to fallback-pipeline preparation;
- a ready compatible fallback route while exact specialization is prepared;
- specialized takeover only after a complete graphics pipeline is ready;
- completion-woken family retry after matching module publication;
- bounded family service after module completion, without requiring a successful flip.
The missing behavior was narrower: learned prewarm consumed a history candidate when vertex, optional geometry, or fallback-fragment SPIR-V was absent. The stage request path was fragment-focused, and learned service could start too late behind flip/busy gating.
PR #228 starts from main at 134de6616e1d8f5bfe9d919a4e98e0ff7da3c928 and selectively reuses the corrected stage ownership design from archived PR #210. It does not import the older demand-table, omission, blackout, tracker, scheduling, or cache-format chain.
The candidate:
- Retains an exact learned family when a cached graphics stage is missing.
- Requests missing vertex, optional geometry, and fallback-fragment stages through the bounded compiler.
- Validates stage, generation, and ticket before renderer-thread publication.
- Wakes the retained family after matching publication.
- Starts one bounded learned service opportunity after Vulkan initialization and continues at existing service points.
- Uses the existing compatible fallback route, then the existing exact specialized takeover.
Exact head a2ab9eff6edf56c94c8591847eec07d3b433f803 passed 71 focused ASan/UBSan checks and both full GitHub workflow copies (36109347787, 36109352002). The all-stage shutdown test makes V/G/F work active and queued in turn and proves none can publish after stop/join.
The later exact delivery head is
5270e8d649b713804a5587e75f083070393acebd. It adds stable
REQUIRED > VISIBLE > PREWARM selection and promotion in both worker queues,
PFIFO completion wakeups, monotonic timed retry, bounded owner service, owned
GLSL reuse, targeted adoption, and retention of completed artifacts across
temporary publication deferral. It passed 80 focused normal and 80 focused
ASan/UBSan checks plus both complete GitHub workflow copies. The public
exact-head testing release
contains only the CI Windows/Linux release products and required license data;
native test packages and evidence are not bundled.
The follow-up validation review found four lifecycle gaps that were not covered
by the earlier green head. PR #228 head
c7ce0d38699cdf0062654ba5e59bc88b2f6cbf93 addresses them without adding a
new scheduling system:
- A retained family now probes actual compiler admission after a full queue.
QUEUE_FULLis temporary and retries when capacity is released even if the draw-selection epoch does not change. Byte-limit, stopped, invalid, and failed work are terminal outcomes rather than infinite retry states. - Prewarm origin is monotonic across exact-family deduplication, pipeline-work deduplication, publication, and the first submitted use. Later demand cannot erase an earlier prewarm origin.
- One source-deduplicated completion fans out through the production
publication path to every matching exact-key owner. Each owner receives an
independent
VkShaderModule; failure for one alias does not prevent later aliases from being visited. - The supporting XISO oracle validates every pass, family, and combiner variant against literal packed framebuffer values. Each pass renders into a distinct region, so equal-but-wrong output and wrong-first/correct-last output both fail. Framebuffer readback and the visible evidence hold remain outside the timed guest-work region.
The reusable rule is that readiness is a chain of typed ownership transitions, not a Boolean. Admission, completion, publication, provenance, and first use must each retain their own evidence and failure semantics.
The visible three-family XISO from xemu-perf-tests #44 deliberately removes one cached stage at a time. The accepted test artifact is 02189a5b3eba408505cdff5c045ba17b30a79cb7bbb963b23e4b4d4fdcd2fed0; the three-family learned history is 1783dea1ad41feba2cee8bd35ce89faa7ea583fb3fef9d5fcd2955f21905a00d.
PR #44 originally combined this readiness profile with an unfinished
C-1/C/C+1 capacity and queue-promotion campaign. That larger campaign was not
an acceptance gate for #228 because #228 changes neither queue capacity nor
scheduling. Commit f46bf79ca2a2ef6895b54e84125e4c5d03390d3e
therefore removed the six unrelated capacity routes and retained only the five
visible readiness routes:
- train visible;
- replay visible;
- identical replay;
- uniform-only control;
- empty-history early-demand control.
The reduced tree passed all 165 Python contracts and a Release NXDK build. Its
build-only XISO SHA-256 is
f8031968f34a407cbec5ec85e8166045355a49fa8049d0cc9ec0acfecf8526b9.
That hash records reproducibility, not new host-runtime evidence; the accepted
Windows mechanism runs below still identify the earlier exact artifact. The
readiness implementation itself is unchanged by the reduction.
The literal per-pass oracle repair is carried by xemu-perf-tests #44 head
a4a3c7d9231a51e07df30d683b981006a5c6db8e. Static contracts alone do not
qualify its byte ordering or native render behavior; the exact generated XISO
must pass the focused native route before this head replaces the accepted
artifact listed above.
| Missing stage | Windows run | Missing artifacts | Fallback-family readiness lead | Observed route |
|---|---|---|---|---|
| Vertex | 20260925-071059552-5e49c284c5a7404b9bed656a0e76bf1b |
9 / 236,668 bytes | 2,607.632–2,611.966 ms | fallback draw 2, exact draw 8 |
| Geometry | 20260925-071123155-8a91ab58c70e425cb81f607ed70f9305 |
5 / 37,288 bytes | 2,500.751–2,506.502 ms | fallback draw 2, exact draw 8 |
| Fallback fragment | 20260925-071146666-d7fb5cf426784ff28fec494136d79b62 |
22 / 592,557 bytes | 2,586.824–2,590.257 ms | fallback draw 2, exact draw 14 |
All cells retained complete evidence and the expected visible green/purple tile pairs. All nine family/stage combinations adopted the prepared fallback before first target demand.
This is a causal mechanism test, not a gameplay speedup result. Vertex and geometry captures had operator/transfer activity and are comparison-ineligible. The fragment cell was comparison-eligible. The test includes an intentional two-second readiness lead and visible hold, so its total duration is not a frame-time metric.
Empty-history runs on Windows (20260925-070302758-ee4b3ddc43574044bae5951c88637822) and Steam Deck (20260925-070310149-5e32cc03889340fda449eae873cd0c43) remained uncovered as designed: zero learned families were considered and first demand found neither a prepared fallback nor an exact pipeline.
The durable limit is:
Learned preparation needs history and lead time. It cannot guarantee a hitch-free accurate draw for an unlearned first launch.
Windows ran selected-seed and candidate-produced warm-cache A-B-B-A blocks. Both saved tests were baked from never-executed source drafts so an emulator launch could not mutate the reusable source package. Every attempt passed the literal visible-output check, completed normally, retained complete evidence, and reported zero dropped hybrid-trace records.
| Mean of two runs per role | Parent | 5270e8d |
Change |
|---|---|---|---|
| Cold synchronous shader compilations | 20 | 18 | -10.0% |
| Cold synchronous pipeline creates | 18 | 12 | -33.3% |
| Cold required-compile blocked time | 150.658 ms | 146.448 ms | -2.79% |
| Cold synchronous pipeline-create time | 39.512 ms | 33.088 ms | -16.26% |
| Warm synchronous shader compilations | 8 | 8 | unchanged |
| Warm synchronous pipeline creates | 10 | 0 | -100% |
| Warm required-compile blocked time | 117.190 ms | 115.283 ms | -1.63% |
| Warm synchronous pipeline-create time | 25.866 ms | 0 | -100% |
The candidate demonstrably moved the targeted pipeline work off the synchronous path. Guest-frame tails were close but mixed: average ordinary p88 changed by +0.027 ms cold and +0.066 ms warm; p99 changed by +0.440 ms cold and +0.377 ms warm. The visible workload total averaged 0.42% faster cold and 0.34% slower warm. Therefore the supported claim is fewer shader/PSO blocking intervals, not a general FPS or frame-tail win.
A tester subsequently reported better shader performance but strange Morrowind behavior. The reported 12–14 FPS behavior also reproduced in older builds; the tester found a persisted disabled Hard FPU setting, and restoring it restored expected performance. This resolves that specific report, not every Morrowind or shader-loading stall. The draft-era snapshot-carrier migration is tracked in Xemu-Test-Runner #61.
Archived PR #213 treated both shader-miss sites as omission-safe. Native PGR2 testing produced persistent black gameplay because resource-producing draws were consumed and never replayed.
- Windows: 1,837,016 omitted draws and 90,780 blackout framebuffer returns.
- Steam Deck: 410,145 omitted draws and 15,394 blackout framebuffer returns.
- Later valid framebuffer syncs occurred, but could not reconstruct content from the lost draws.
- PR #225's conservative classifier produced zero omissions in its PGR2 run. That proved safe fallback to Wait, not a useful Continue performance path.
Unknown color/depth/stencil/query/render-to-texture/alias effects must fail closed to the accurate Wait route. A short crossfade or presentation blackout cannot repair consumed guest work.
Review of archived PR #214 identified these blockers:
- per-draw hot-path state publication;
- incoherent snapshots spread across multiple atomics;
- unrelated deferred/demand records contaminating blackout state;
- recovery after any submitted draw rather than use of the affected executable;
- permanent failure leaving output black indefinitely;
- first-use GL allocation on the blackout frame;
- “avoided” counters that did not prove work would otherwise occur.
Any revival needs one coherent generation-tagged state transition, affected-executable ownership, explicit permanent-failure recovery, and preallocated presentation resources.
Archived PR #211 originally probed and immediately performed the normal blocking create, adding a second driver call. Sharing the application VkPipelineCache also allowed synchronization to block the probe.
The repaired diagnostic used opt-in policy, VK_NULL_HANDLE, a BUSY outcome, and explicit unexpected-handle destruction. It remained diagnostic-only because idle/background/saturation/cache-state p50/p95/p99/max qualification was not completed.
Archived PR #212 introduced a broader demand table, but review found:
- fixed 16 ms retry delay after accepted module work;
-
PIPELINE_PENDINGrecords consuming the small service budget; - fallback-covered specialization inserted as demand;
- any pending demand globally blocking learned prewarm;
- repeated scans/hashes at multiple service boundaries.
Main's completion-woken family path did not require this table for the learned-stage gap, so #228 reused that path instead.
Archived attribution/tracker work exposed several rules:
- never use an untagged
readiness_classifiedBoolean across generations; - tracker capacity must cover the declared corpus rather than silently dropping cases;
- build failure/driver error is not
UNSUPPORTED; -
TOO_LATEprediction must cover source, module, binding, pipeline, publication, and first use; - queued promotion, already-demand, active match, and result-pending duplicate are different outcomes;
- active speculative work remains a possible head-of-line blocker;
- queue age and starvation must be visible.
Archived PR #219 created a two-phase queue (generate -> renderer lookup -> compile requeue), copied generated GLSL multiple times, and made warm persistent-cache hits wait for worker source generation. One worker also serialized generation behind long compilation.
Any revival needs ownership transfer, urgency-preserving continuation, phase-specific queue delay, warm-hit comparison, byte/allocation metrics, and evidence that total process cost improves rather than merely moving work off the renderer thread.
Archived PR #220 mixed stage IDs, hashes, tickets, and timings into generic columns and retained lifecycle events only on slow frames. That can censor one half of a request and mislead automation.
Durable trace rules:
- explicit stable event IDs and schema versions;
- typed fields or event-specific records;
- complete request-to-first-use lifecycle independent of slow-frame retention;
- timestamps for demand, admission, worker start/finish, module publication, pipeline publication, and first submitted use;
- injected-delay calibration and trace-overhead measurement.
The following branches remain available through immutable Git history and their PR discussions, but are not current delivery prerequisites:
| PR | Archived role |
|---|---|
| #209 | Typed draw lifecycle contract; not required by #228 |
| #210 | Selective donor for corrected stage ownership |
| #211 | Optional compile-probe diagnostics |
| #212 | General retained-demand table |
| #213 | Rejected all-draw omission experiment |
| #214 | Blocked blackout experiment |
| #215 | Fallback mapping/retained-work experiment |
| #216 | Readiness attribution experiment |
| #217 | Promotion/scheduling experiment |
| #219 | Worker-side source generation experiment |
| #220 | Shader lifecycle timing trace experiment |
| #222 | Split demand/background worker stack |
| #223 | Test-fixture stack repair, later carried where needed |
| #225 | Conservative omission repair experiment |
| #226 | Queued pipeline-priority experiment |
| #227 | Diagnostic timing-injection experiment |
The following list records the gates identified during the draft review. PR #228 has since merged; read its final discussion and the dated development status before treating an item below as still pending. The targeted reduction in synchronous pipeline work did not establish a general frame-time or stutter improvement.
- Restore stable Steam Deck runner reachability and rerun the exact three-family matrix (runner #54).
- Add a deterministic Batman or Azurik saved transition using the bounded snapshot/transient-disk flow (runner #55).
- Run the Morrowind snapshot guard after its immutable carrier is registered in the runner, and investigate any visual difference before accepting the draft.
- Keep the empty-history control explicit; do not relabel fault-injected synthetic results as title performance.
- An interrupted upload can leave a package reservation busy even when no run or emulator process exists. Resume the same package identity after clearing the stale in-memory lease; do not create duplicate IDs or delete resumable content. The permanent runner fix is tracked in Xemu-Test-Runner #56.
- A client or Wi-Fi disconnect after the runner has accepted the complete package and started the operation does not invalidate the test: the runner owns execution and evidence. Poll and retrieve the result later. Distinguish that normal disconnected operation from a pre-operation upload interruption, where no run yet exists and resumable-upload/lease behavior matters.
- Treat runner reachability as evidence eligibility, not as a reason to invent a substitute workload. The Deck recurrence is tracked in #54, and the missing native Batman/Azurik fixture in #55.
- Build validation and runtime validation are distinct. Record the exact source commit and XISO hash for a successful Release build, but do not promote that into a runtime or performance claim until the exact artifact has completed the required host cells.
- Keep large screenshots, traces, runner packages, and raw logs out of source branches. Put concise, reviewable evidence in PR discussion and durable lessons here; keep bulky artifacts in the runner's evidence store.
- Bake reusable tests from a complete never-executed source draft. A tested source package may contain xemu-mutated configuration and shader-cache files; its current bytes can then differ from the declared manifest and make later requests wait forever for uploads that no longer match. The underlying runner recovery gap is tracked in #57.
- A persistent
waitingForUploadrequest must not head-of-line block unrelated valid requests. The observed starvation and required dispatcher change are tracked in #60. - Snapshot workloads belong in a shared immutable
snapshot-carrierasset with a private per-run overlay and cleanup. Do not copy a multi-gigabyte casual HDD into every package, and do not bypass the API to reach an undeclared tester-side path. Morrowind migration is tracked in #61.
- Use one focused main-based candidate after the source gap is identified; do not make research dependencies into a merge train.
- Put reusable lessons and evidence summaries in this wiki. Keep PRs small and delivery-oriented.
- Preserve failed experiments through immutable commits and links, then close them when they are no longer delivery candidates.
- Distinguish “work moved off-thread” from “the user-visible stall improved.”
- A ready module is not a ready binding, pipeline, published executable, or first use.
- Do not omit guest work unless its side effects are proven absent or transactionally recoverable.
- Keep synthetic mechanism proof, native relevance, and measured performance as separate claims.
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