Fixing MSFS 2020 VR on Linux: The Proton OpenXR Time Conversion Patches
Microsoft Flight Simulator 2020 ran in VR on Linux — sort of. The world rendered, the headset tracked, but your controllers spawned locked to the headset's position and never moved. The cause turned out to be two OpenXR functions that Proton's wineopenxr layer had simply never implemented. I implemented them across two PRs (#9347 and #9372), both merged by Valve in January 2026, and MSFS 2020 VR became fully playable on Proton for the first time.
The symptom: controllers glued to your face
MSFS uses the OpenXR extension XR_KHR_win32_convert_performance_counter_time to timestamp controller poses. Every frame it calls two conversion functions between Windows' QueryPerformanceCounter (QPC) ticks and OpenXR's XrTime nanoseconds:
xrConvertWin32PerformanceCounterToTimeKHRxrConvertTimeToWin32PerformanceCounterKHR
In Proton's wineopenxr, neither existed. The debug logs showed the game calling them every frame and failing, so it never got valid pose timestamps — and controllers defaulted to the HMD position.
Implementation: don't assume the clocks agree
My first draft mapped QPC to XrTime directly in the loader. Review (from Proton developer gofman) killed that quickly and for a good reason: nothing in the OpenXR spec guarantees QPC and the runtime's clock share an epoch. A direct mapping happens to work on some runtimes and silently misbehaves on others.
The merged design goes through the native Linux time domain instead:
- Implement the functions on the Unix side of wineopenxr with proper overrides.
- Convert QPC ticks to a
CLOCK_MONOTONICtimespec — Wine implements QPC as 100ns (10 MHz) ticks on top of CLOCK_MONOTONIC, so this direction is well-defined. - Hand the timespec to the runtime's own
xrConvertTimespecTimeToTimeKHR, which is authoritative for its XrTime timeline. Reverse the chain for the other direction. - If the runtime doesn't expose the timespec conversion, return
XR_ERROR_FUNCTION_UNSUPPORTEDinstead of guessing.
The 15-minute freeze
Testing on a Quest 3S surfaced a nasty follow-up: sessions froze after roughly 15 minutes. The conversion math multiplied before dividing, and with large-enough QPC values the intermediate product overflowed a 64-bit integer — which takes about 15 minutes of uptime to reach at 10 MHz. The fix splits the conversion into whole seconds plus remainder so no intermediate ever overflows, keeping precision at any counter frequency.
The follow-up: clock domains drift apart
PR #9372 handled the last correctness gap: QPC and CLOCK_MONOTONIC don't necessarily start from the same zero. The patch computes the offset between them at every conversion call — sampling NtQueryPerformanceCounter and clock_gettime(CLOCK_MONOTONIC) together — and applies it in both directions. Computing per-call rather than caching also absorbs any drift over a long session.
Both PRs were reviewed by gofman and merged by Plagman into Proton Experimental's bleeding edge, then rolled into Proton main — meaning the fix ships to every Linux and Steam Deck user running OpenXR titles, not just MSFS.
FAQ
- Does MSFS 2020 VR work on Linux now?
- Yes. With current Proton (Experimental or any build including the January 2026 wineopenxr patches), controller tracking works and the game is fully playable in VR.
- Do I need a special Proton version?
- No — the patches landed in Proton Experimental and then Proton main. Anything current includes them.
- Does this fix other VR games?
- Any title using
XR_KHR_win32_convert_performance_counter_timefor pose timing benefits — MSFS was the loudest victim, not the only caller.