Skip to content

Native ports and measured gains

Catalog of optimizations that replace a chunk of translated x86 code with an equivalent native ARM implementation (see the mechanism in winx86's documentation, Extension point page). Each row states the validation level of the cited number (see Three-level validation) — a qemu number is never presented as a console result. Numbers drawn from the project's internal measurement campaigns (dated reports, not published in this repository); what follows is their synthesis.

No absolute value carries across a campaign

The binary changes, the console drifts thermally (1.5-10%). The percentages below come from A/B/A measurements interleaved on an identical binary, never from two different dates compared to each other.

Ports that produced a confirmed gain on console

Port Measured gain Level Detail
Native cell loop (NATIVECELLLOOP) +16.1% Console (A/B/A/B, identical binary) Traversals ÷2, blit ÷7.
D2_CELLOPT (optimization mask inside the native loop) +9.3% Console Half the work per cell.
D2_CELLPAR=1 (fork-join parallelism, 1 worker thread) +12% Console D2_CELLPAR=2 = 0% (USER_2 shared, no second core available).
D2_CALLRET +6.8% console / 0% qemu Console (4 interleaved passes, single binary) Textbook case: a real gain qemu doesn't see at all.
fastmmu + D2_BUDGETTAIL +2.8% Console Taking the base ADD out of the dynarec's critical path.
D2_CSINTRIN + WX86_B5 (clock intrinsic) +1.4% Console Clock intrinsic rewritten to leave the critical path.
fog_raise_snap probe disarmed +4 to 7% Console The probe itself cost what it measured.
GPU ring — asynchronous submission (D2_GXMASYNC=2) +52% (28.8 → 43.7 fps bench) Console The GPU takes 11ms/submission; async mode overlaps that time.
GPU ring — flat clear (D2_GXMCLEAR=plat) +10.5% (−2.5ms) Console Simplified framebuffer clear.
CPU-side ring — host view + fast vertices +11.5% (43.75 → 48.78 fps bench) Console Ring traversal ÷2.3.
Hoisting TLS resolution out of CpuBox86::run() +3.03% at the menu bench, +8.6% in real online play Console (interleaved A/B/A/B + guard rail: guest work identical within 0.1%) The gain tracks GIL-take density (190/frame at the menu vs. 2880/frame in play) — a percentage measured at the menu must never be published as an in-game gain.
Trap entry/exit batching (trap_retaddr+trap_epilogue, commit e13edbd) +13.2% as announced in the commit Commit-time figure, cited as-is in a later fix — no dedicated campaign document found for this exact number, take it with that caveat. Already in place today (not an optional knob).
NATIVELIGHTMAP (dynamic light placement) −21% on the light pass (1.20 → 0.95ms/frame), i.e. −2.6% of total drawing qemu (cross-checked oracle D2_LIGHTMAPVERIFY, 51,200 placements, 0 fallback, 0 divergence) — console verdict not taken Never measured on console.
Cumulative, night of 04-05/09, uncapped bench Control 22.5-23.1 → 28.1-28.6 fps (+22%) Console, interleaved campaign of 20 passes Cumulative effect of several of the ports above.
Cumulative GDI → asynchronous Glide 22.5 → 55.7 fps (+96%) bench; 25 steps/s + 60 fps smoothed in real play Console Cumulative effect of moving to the asynchronous GPU rendering path (ring + async submission).

Ports refuted or with no measurable effect — kept as a warning

Port Result Level Why it's instructive
D2_MEMINTRIN (native memcpy/memset) Mechanism proven correct (4.4M calls, 0 divergence) but 0% on console Console The guest CRT already vectorizes its large transfers (NEON); the Glide path makes 90 calls/frame vs. ~1,100 under GDI — the lever had disappeared with the renderer change.
D2_FORWARD Inert (+0.6% = noise) Console "Shorter blocks" hypothesis never actually tested.
D2_NOPEND (lever L4) Refuted, ceiling 0.002% Console The original document targeted the wrong code site (0 real calls instead of the assumed site).
Offloading RLE to a dedicated thread FAIL on three modes; the mode that passed three days earlier no longer passes qemu (oracle) "Prove the oracle before concluding" — the founding PASS had never been replayed since.
Four image-cache hypotheses All refuted (0 identical frame out of 4,000) qemu (D2_CACHEPROBE) Found something else instead: the Perspective video option missing from the registry cost +14.2% guest blocks.
Hardware PMU (Cortex-A9 cycle counters) Abandoned Console The Vita kernel resets the access register on every context switch; would require patching the system scheduler.
Fifteen dynarec levers, campaign summary A single gain in the entire history of the dynarec (D2_CALLRET, +6.8%) Console "The dynarec isn't the lever" — the gain is in native porting of guest code, not in translation itself.

Method for picking a port that pays off

What the campaign learned formalizes as: gain ≈ N × (T_guest − T_native − 69ns) where 69ns is the measured cost of the x86→native crossing itself (the price of a round trip, even for an intrinsic recognized at translation time rather than a real trap). The right target is not the hottest function in the profile — it's a caller with a large number of calls (N) that absorbs an entire subtree in a single traversal. That's exactly the cell loop's pattern: a single call site produces the entirety of the ~1,600 cell-blit calls per frame.

Chosen game configuration

tools/bancs/env_jeu_glide.txt (deployed on console, chosen by the maintainer) — this is the combination of every confirmed lever above:

D2SCHED=native D2_SELECTBLOCK=1 NATIVECELLLOOP=1 D2_CALLRET=1
D2_NOPUMPWAIT=1 D2_LAZYSEEK=1
D2_CELLOPT=63 D2_MMUSTACK=3 D2_MMUFOLD=1 D2_BUDGETTAIL=1 D2_CSINTRIN=1 WX86_B5=1
D2ARGS=game.exe -3dfx  D2_GLIDERING=1 D2_GLIDEGXM=1 D2_GXMASYNC=2 D2_GXMCLEAR=plat
WX86_YIELD=2  D2_ONEDRAW=2  D2_REPLAY60=1

(D2_NOCAP is not in it — it's a bench tool that disengages the game's internal frame limiters, not a setting to keep for real play.)