Skip to content

Vulkan Learned Fallback Readiness

OpenAI Codex edited this page Nov 1, 2026 · 7 revisions

Vulkan learned fallback readiness — findings and archived attacks

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.

Current readiness and admission

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.

October evidence and adverse limits

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

Historical September investigation and evidence

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.

What the September parent already provided

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.

Historical focused #228 repair

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:

  1. Retains an exact learned family when a cached graphics stage is missing.
  2. Requests missing vertex, optional geometry, and fallback-fragment stages through the bounded compiler.
  3. Validates stage, generation, and ticket before renderer-thread publication.
  4. Wakes the retained family after matching publication.
  5. Starts one bounded learned service opportunity after Vulkan initialization and continues at existing service points.
  6. 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.

September 25 review hardening

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:

  1. A retained family now probes actual compiler admission after a full queue. QUEUE_FULL is 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.
  2. 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.
  3. 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.
  4. 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.

Synthetic mechanism evidence

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.

Focused test-suite delivery

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.

Required negative control

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.

September exact-head cold and warm performance check

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.

Failed or parked attacks

All-draw omission failed correctness

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.

Blackout is a state machine, not a presentation toggle

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.

A compile-forbidden probe is not automatically nonblocking

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.

General demand scheduling was unnecessary for the focused repair

Archived PR #212 introduced a broader demand table, but review found:

  • fixed 16 ms retry delay after accepted module work;
  • PIPELINE_PENDING records 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.

Readiness must remain typed and generation-safe

Archived attribution/tracker work exposed several rules:

  • never use an untagged readiness_classified Boolean across generations;
  • tracker capacity must cover the declared corpus rather than silently dropping cases;
  • build failure/driver error is not UNSUPPORTED;
  • TOO_LATE prediction 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.

Moving source generation can make warm hits slower

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.

Trace data needs a stable contract

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.

Archived PR disposition

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

Historical pre-merge evidence gates

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.

  1. Restore stable Steam Deck runner reachability and rerun the exact three-family matrix (runner #54).
  2. Add a deterministic Batman or Azurik saved transition using the bounded snapshot/transient-disk flow (runner #55).
  3. Run the Morrowind snapshot guard after its immutable carrier is registered in the runner, and investigate any visual difference before accepting the draft.
  4. Keep the empty-history control explicit; do not relabel fault-injected synthetic results as title performance.

Test-runner lessons

  • 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 waitingForUpload request 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-carrier asset 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.

Durable development rules

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

Clone this wiki locally